Added Slider widget

This commit is contained in:
OmniaX-Dev 2026-04-23 00:46:10 +02:00
parent 327582c463
commit 19f64f6af3
10 changed files with 386 additions and 79 deletions

View file

@ -95,6 +95,7 @@ list(APPEND OGFX_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Button.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/RadioButton.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/ProgressBar.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Slider.cpp
# render
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/BasicRenderer.cpp

View file

@ -235,3 +235,23 @@ const $accentColorDark = #6B0A1DFF
progressGradient = gradientV(#C21135FF - #820B23FF)
}
% ===================
% ====== Slider ======
(slider) {
(handle) {
color = $accentColor
size = vec2(10, 18)
}
(track) {
color = #500000FF
progressColor = #AA0000FF
width = 6.0
}
showStepTicks = true
stepTickHeight = 6.0
stepTickColor = $accentColor
padding = rect(5, 5, 5, 5)
}
% ===================

View file

@ -47,8 +47,8 @@ Implement following widgets:
***Radio Button
***Grouping
***Progressbar
Horizontal Slider
Vertical Slider
***Horizontal Slider
***Vertical Slider
ListBox
ComboBox
Context Menu

View file

@ -27,3 +27,4 @@
#include <ogfx/gui/widgets/Button.hpp>
#include <ogfx/gui/widgets/RadioButton.hpp>
#include <ogfx/gui/widgets/ProgressBar.hpp>
#include <ogfx/gui/widgets/Slider.hpp>

View file

@ -69,9 +69,8 @@ namespace ogfx
void ProgressBar::setProgressNormalized(f32 normalized_value)
{
f32 current = m_progress.load(std::memory_order_relaxed);
f32 next = current;
next = current + std::clamp(normalized_value, 0.0f, 1.0f) * (m_max - m_min);
normalized_value = std::clamp(normalized_value, 0.0f, 1.0f);
f32 next = m_min + normalized_value * (m_max - m_min);
m_progress.store(next, std::memory_order_relaxed);
}

View file

@ -0,0 +1,170 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2025 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Slider.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
namespace widgets
{
Slider& Slider::create(bool vertical, f32 min, f32 max, f32 step)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::widgets::Slider");
disableChildren();
disableBackground();
disableBorder();
setContentOffset({ 0, 0 });
setStylesheetCategoryName("slider");
setMinValue(min);
setMaxValue(max);
setStep(step);
validate();
return *this;
}
void Slider::applyTheme(const ostd::Stylesheet& theme)
{
setHandleColor(getThemeValue<Color>(theme, "handle.color", getHandleColor()));
setHandleSize(getThemeValue<Vec2>(theme, "handle.size", getHandleSize()));
setTrackColor(getThemeValue<Color>(theme, "track.color", getTrackColor()));
setTrackProgressColor(getThemeValue<Color>(theme, "track.progressColor", getTrackProgressColor()));
setTrackWidth(getThemeValue<f32>(theme, "track.width", getTrackWidth()));
enableDrawTicks(getThemeValue<bool>(theme, "showStepTicks", isDrawTicksEnabled()));
setStepTickHeight(getThemeValue<f32>(theme, "stepTickHeight", getStepTickHeight()));
setStepTickColor(getThemeValue<Color>(theme, "stepTickColor", getStepTickColor()));
}
void Slider::onMouseReleased(const Event& event)
{
if (!isMouseInside())
return;
if (isMiddleClickShortcutEnabled() && event.mouse->button == MouseEventData::eButton::Middle)
{
set_value(snap_to_step((m_min + m_max) * 0.5f));
return;
}
set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y }));
}
void Slider::onMouseDragged(const Event& event)
{
if (!isMouseInside())
return;
set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y }));
}
void Slider::onDraw(ogfx::BasicRenderer2D& gfx)
{
const auto& bounds = getGlobalContentBounds();
f32 value = m_value;
f32 t = (value - m_min) / (m_max - m_min); // normalized [0, 1]
auto l_centerh = [&](f32 value) -> f32 {
return (geth() / 2.0f) - (value / 2.0f);
};
auto l_centerw = [&](f32 value) -> f32 {
return (getw() / 2.0f) - (value / 2.0f);
};
if (isHorizontal())
{
if (m_step > 0.0f && m_drawTicks)
{
int tickCount = (int)std::round((m_max - m_min) / m_step) + 1;
for (int i = 0; i < tickCount; i++)
{
f32 v = m_min + i * m_step;
v = std::min(v, m_max);
f32 tickT = (v - m_min) / (m_max - m_min);
f32 tickX = bounds.x + bounds.w * tickT;
gfx.fillRect({ tickX - 1, bounds.y + l_centerh(getStepTickHeight()) - getStepTickHeight(), 2, getStepTickHeight() }, getStepTickColor());
}
}
Vec2 correction { -2, 0 };
Vec2 trackPos = bounds.getPosition() + Vec2 { 0, l_centerh(getTrackWidth()) };
gfx.fillRect({ trackPos + correction, bounds.w + (correction.x * -2), getTrackWidth() }, getTrackColor());
gfx.fillRect({ trackPos + correction, (bounds.w + (correction.x * -2)) * t, getTrackWidth() }, getTrackProgressColor());
f32 handleX = bounds.x + bounds.w * t - (getHandleSize().x / 2.0f);
gfx.fillRect({ handleX, bounds.y + l_centerh(getHandleSize().y), getHandleSize() }, getHandleColor());
}
else
{
if (m_step > 0.0f && m_drawTicks)
{
int tickCount = (int)std::round((m_max - m_min) / m_step) + 1;
for (int i = 0; i < tickCount; i++)
{
f32 v = m_min + i * m_step;
v = std::min(v, m_max);
f32 tickT = (v - m_min) / (m_max - m_min);
f32 tickY = bounds.y + bounds.h * (1.0f - tickT);
gfx.fillRect({ bounds.x + l_centerw(getStepTickHeight()) - getStepTickHeight(), tickY - 1, getStepTickHeight(), 2 }, getStepTickColor());
}
}
Vec2 correction { 0, -2 };
Vec2 trackPos = bounds.getPosition() + Vec2 { l_centerw(getTrackWidth()), 0 };
gfx.fillRect({ trackPos + correction, getTrackWidth(), bounds.h + (correction.y * -2) }, getTrackColor());
f32 progressH = (bounds.h + (correction.y * -2)) * t;
gfx.fillRect({ trackPos.x + correction.x, trackPos.y + correction.y + (bounds.h + (correction.y * -2)) - progressH, getTrackWidth(), progressH }, getTrackProgressColor());
Vec2 swappedSize = getHandleSize().swap();
f32 handleY = bounds.y + bounds.h * (1.0f - t) - (swappedSize.y / 2.0f);
gfx.fillRect({ bounds.x + l_centerw(swappedSize.x), handleY, swappedSize }, getHandleColor());
}
}
void Slider::enableVertical(bool enable)
{
if (isVertical() && enable) return;
if (isHorizontal() && !enable) return;
m_vertical = enable;
setSize(getSize().swap());
}
f32 Slider::snap_to_step(f32 val)
{
if (m_step <= 0.0f) return val; // full precision
f32 snapped = std::round((val - m_min) / m_step) * m_step + m_min;
return std::clamp(snapped, m_min, m_max);
}
f32 Slider::mouse_position_to_value(Vec2 mousePosition)
{
const auto& bounds = getGlobalBounds();
f32 raw = isHorizontal() ? (mousePosition.x - bounds.x) / bounds.w : 1.0f - (mousePosition.y - bounds.y) / bounds.h;
raw = std::clamp(raw, 0.0f, 1.0f);
return m_min + raw * (m_max - m_min);
}
void Slider::set_value(f32 val)
{
if (m_value == val)
return;
f32 old = m_value;
m_value = snap_to_step(val);
if (callback_onValueChanged)
callback_onValueChanged(old, m_value);
}
}
}
}

View file

@ -0,0 +1,92 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2025 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
namespace widgets
{
class Slider : public Widget
{
public:
inline Slider(WindowCore& window, bool vertical = false, f32 min = 0.0f, f32 max = 1.0f, f32 step = 0.1f) : Widget({ 0, 0, 0, 0 }, window) { create(vertical, min, max, step); }
Slider& create(bool vertical, f32 min, f32 max, f32 step);
void applyTheme(const ostd::Stylesheet& theme) override;
void onMouseReleased(const Event& event) override;
void onMouseDragged(const Event& event) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
inline f32 getValue(void) const { return m_value; }
inline void setValueChangedCallback(std::function<void(f32 oldValue, f32 newValue)> callback) { callback_onValueChanged = std::move(callback); }
void enableVertical(bool enable = true);
inline bool isVertical(void) const { return m_vertical; }
inline void disableVertical(void) { enableVertical(false); }
inline bool isHorizontal(void) const { return !isVertical(); }
inline void enableHorizontal(bool enable = true) { enableVertical(!enable); }
inline void disableHorizontal(void) { enableHorizontal(false); }
OSTD_BOOL_PARAM_GETSET_E(MiddleClickShortcut, m_middleClickShortcut);
OSTD_PARAM_GETSET(f32, MinValue, m_min);
OSTD_PARAM_GETSET(f32, MaxValue, m_max);
OSTD_PARAM_GETSET(f32, Step, m_step);
OSTD_PARAM_GETSET(f32, TrackWidth, m_trackWidth);
OSTD_PARAM_GETSET(Color, TrackColor, m_trackColor);
OSTD_PARAM_GETSET(Color, TrackProgressColor, m_trackProgressColor);
OSTD_BOOL_PARAM_GETSET_E(DrawTicks, m_drawTicks);
OSTD_PARAM_GETSET(f32, StepTickHeight, m_tickHeight);
OSTD_PARAM_GETSET(Color, StepTickColor, m_tickColor);
OSTD_PARAM_GETSET(Color, HandleColor, m_handleColor);
OSTD_PARAM_GETSET(Vec2, HandleSize, m_handleSize);
private:
f32 snap_to_step(f32 val);
f32 mouse_position_to_value(Vec2 mousePosition);
void set_value(f32 val);
private:
f32 m_value { 0.0f };
std::function<void(f32 oldValue, f32 newValue)> callback_onValueChanged { nullptr };
f32 m_min { 0.0f };
f32 m_max { 1.0f };
f32 m_step { 0.1f };
bool m_vertical { false };
bool m_middleClickShortcut { true };
f32 m_trackWidth { 6 };
Color m_trackColor { "#500000FF" };
Color m_trackProgressColor { "#AA0000FF" };
bool m_drawTicks { true };
f32 m_tickHeight { 4 };
Color m_tickColor { Colors::Crimson };
Color m_handleColor { Colors::Crimson };
Vec2 m_handleSize { 10, 18 };
};
}
}
}

View file

@ -83,6 +83,7 @@ namespace ostd
inline Vec2 div(f32 scalar) const { return { x / scalar, y / scalar }; }
inline Vec2 propx(f32 new_x) const { return { new_x, new_x * (y / x) }; }
inline Vec2 propy(f32 new_y) const { return { new_y * (x / y), new_y }; }
inline Vec2 swap(void) { return { y, x }; }
inline Vec2 normalize(void) const { f32 m = _zp(mag()); return { x / m, y / m }; }
inline f32 dist(Vec2 v2) const { return std::sqrt((f32)((v2.x - x) * (v2.x - x)) + ((v2.y - y) * (v2.y - y))); }
@ -102,6 +103,7 @@ namespace ostd
inline Vec2& divm(const f32& scalar) { x /= scalar; y /= scalar; return *this; }
inline Vec2& propxm(const f32& new_x) { x = new_x; y = new_x * (y / x); return *this; }
inline Vec2& propym(const f32& new_y) { x = new_y * (x / y); y = new_y; return *this; }
inline Vec2& swapm(void) { f32 tmp = x; x = y; y = tmp; return *this; }
inline Vec2& normalizem(void) { f32 m = _zp(mag()); x /= m; y /= m; return *this; }
inline Vec2& setMag(const f32& mag) { return normalizem().mulm(mag); }

View file

@ -68,6 +68,16 @@
inline void enable##name(bool enable = true) { member = enable; } \
inline void disable##name(void) { enable##name(false); }
#define OSTD_BOOL_PARAM_GETSET_E_NEG(name, member) \
inline bool is##name##Enabled(void) const { return !member; } \
inline void enable##name(bool enable = true) { member = !enable; } \
inline void disable##name(void) { enable##name(false); }
#define OSTD_BOOL_PARAM_GETSET_I_NEG(name, member) \
inline bool is##name(void) const { return !member; } \
inline void enable##name(bool enable = true) { member = !enable; } \
inline void disable##name(void) { enable##name(false); }
#define OSTD_PARAM_GETSET(type, name, member) \
inline type get##name(void) const { return member; } \
inline void set##name(const type& value) { member = value; } \

View file

@ -105,6 +105,14 @@ class Window : public ogfx::gui::Window
m_panel2.setTooltipText("PANEL tooltip");
m_prog.setSize(300, 30);
m_slide.setSize(300, 30);
m_slide.setMinValue(-1.0f);
// m_slide.enableVertical();
// m_slide.setStep(0);
m_slide.setValueChangedCallback([&](f32 oldVal, f32 newVal) -> void {
m_slideLbl.setText(String("").add(newVal, 2));
});
m_slideLbl.setText(String("").add(m_slide.getValue(), 2));
m_tabs.setSize(900, 700);
auto& t1 = m_tabs.addTab("Tab1");
@ -123,6 +131,8 @@ class Window : public ogfx::gui::Window
std::cout << sender.getText() << "\n\n";
});
t1.addWidget(m_prog, { 30, 200 });
t1.addWidget(m_slide, { 30, 250 });
t1.addWidget(m_slideLbl, { 340, 240 });
t2.addWidget(m_panel2, { 500, 100 });
m_panel3.addWidget(m_label4);
@ -147,7 +157,7 @@ class Window : public ogfx::gui::Window
while (prog < 100)
{
prog += 0.6f;
ostd::Time::sleep(ostd::Random::getui32(10, 200));
ostd::Time::sleep(ostd::Random::getui32(10, 500));
this->m_prog.setProgress(prog);
}
return true;
@ -188,6 +198,8 @@ class Window : public ogfx::gui::Window
TabPanel m_tabs { *this };
RadioButtonGroup m_radioGroup;
ProgressBar m_prog { *this, 0, 100 };
Slider m_slide { *this };
Label m_slideLbl { *this };
ostd::StepTimer m_timer;
ostd::AsyncJob<bool> m_progressJob;