diff --git a/CMakeLists.txt b/CMakeLists.txt index aff6777..21ec168 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss index ab1d208..ee33fa2 100644 --- a/extra/DefaultTheme.oss +++ b/extra/DefaultTheme.oss @@ -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) +} +% =================== diff --git a/other/TODO.txt b/other/TODO.txt index dc5a5a2..3669822 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -47,8 +47,8 @@ Implement following widgets: ***Radio Button ***Grouping ***Progressbar - Horizontal Slider - Vertical Slider + ***Horizontal Slider + ***Vertical Slider ListBox ComboBox Context Menu diff --git a/src/ogfx/gui/Widgets.hpp b/src/ogfx/gui/Widgets.hpp index 8446102..e158420 100644 --- a/src/ogfx/gui/Widgets.hpp +++ b/src/ogfx/gui/Widgets.hpp @@ -27,3 +27,4 @@ #include #include #include +#include diff --git a/src/ogfx/gui/widgets/ProgressBar.cpp b/src/ogfx/gui/widgets/ProgressBar.cpp index 14796ef..3d50b2c 100644 --- a/src/ogfx/gui/widgets/ProgressBar.cpp +++ b/src/ogfx/gui/widgets/ProgressBar.cpp @@ -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); } diff --git a/src/ogfx/gui/widgets/Slider.cpp b/src/ogfx/gui/widgets/Slider.cpp new file mode 100644 index 0000000..33e6572 --- /dev/null +++ b/src/ogfx/gui/widgets/Slider.cpp @@ -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 . +*/ + +#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(theme, "handle.color", getHandleColor())); + setHandleSize(getThemeValue(theme, "handle.size", getHandleSize())); + setTrackColor(getThemeValue(theme, "track.color", getTrackColor())); + setTrackProgressColor(getThemeValue(theme, "track.progressColor", getTrackProgressColor())); + setTrackWidth(getThemeValue(theme, "track.width", getTrackWidth())); + enableDrawTicks(getThemeValue(theme, "showStepTicks", isDrawTicksEnabled())); + setStepTickHeight(getThemeValue(theme, "stepTickHeight", getStepTickHeight())); + setStepTickColor(getThemeValue(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); + } + } + } +} diff --git a/src/ogfx/gui/widgets/Slider.hpp b/src/ogfx/gui/widgets/Slider.hpp new file mode 100644 index 0000000..bc75179 --- /dev/null +++ b/src/ogfx/gui/widgets/Slider.hpp @@ -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 . +*/ + +#pragma once + +#include + +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 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 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 }; + }; + } + } +} diff --git a/src/ostd/math/Geometry.hpp b/src/ostd/math/Geometry.hpp index d557e32..737998f 100755 --- a/src/ostd/math/Geometry.hpp +++ b/src/ostd/math/Geometry.hpp @@ -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); } diff --git a/src/ostd/utils/Defines.hpp b/src/ostd/utils/Defines.hpp index e366522..8cc79a6 100755 --- a/src/ostd/utils/Defines.hpp +++ b/src/ostd/utils/Defines.hpp @@ -1,73 +1,83 @@ -/* - 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 . -*/ - -#pragma once - -#if defined(_WIN32) || defined(_WIN64) - #define WINDOWS_OS - #ifdef _WIN32_WINNT - #undef _WIN32_WINNT - #endif - #define _WIN32_WINNT 0x0501 -#elif defined(__APPLE__) - #define MAC_OS -#else - #define LINUX_OS -#endif - -//Basic constants -#if !defined(BUILD_NR) - #define BUILD_NR 0 -#endif -#if !defined(MAJ_V) - #define MAJ_V 0 -#endif -#if !defined(MIN_V) - #define MIN_V 0 -#endif -#if !defined(VERSION_STR) - #define VERSION_STR "0.0.0" -#endif -#if !defined(NULL) - #define NULL 0 -#endif - -#ifndef OX_DEBUG_BUILD - #define _DEBUG(n) - #ifndef OX_RELEASE_BUILD - #define OX_RELEASE_BUILD - #endif -#else - #define _DEBUG(n) std::cout << (i32)n << "\n"; -#endif - -#define OSTD_BOOL_PARAM_GETSET_E(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(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; } \ +/* + 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 . +*/ + +#pragma once + +#if defined(_WIN32) || defined(_WIN64) + #define WINDOWS_OS + #ifdef _WIN32_WINNT + #undef _WIN32_WINNT + #endif + #define _WIN32_WINNT 0x0501 +#elif defined(__APPLE__) + #define MAC_OS +#else + #define LINUX_OS +#endif + +//Basic constants +#if !defined(BUILD_NR) + #define BUILD_NR 0 +#endif +#if !defined(MAJ_V) + #define MAJ_V 0 +#endif +#if !defined(MIN_V) + #define MIN_V 0 +#endif +#if !defined(VERSION_STR) + #define VERSION_STR "0.0.0" +#endif +#if !defined(NULL) + #define NULL 0 +#endif + +#ifndef OX_DEBUG_BUILD + #define _DEBUG(n) + #ifndef OX_RELEASE_BUILD + #define OX_RELEASE_BUILD + #endif +#else + #define _DEBUG(n) std::cout << (i32)n << "\n"; +#endif + +#define OSTD_BOOL_PARAM_GETSET_E(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(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_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; } \ diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index d3c8b2b..2f9d16a 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -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 m_progressJob;