diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss index fb5fcc4..56c2f46 100644 --- a/extra/DefaultTheme.oss +++ b/extra/DefaultTheme.oss @@ -1,20 +1,20 @@ % ================== DefaultTheme.oss ================== -% ---- Variables ---- +% ==== Variables ==== const $accentColor = #DC143CFF const $textColor = #111111FF const $accentColorLight = #F16A85FF const $accentColorDark = #6B0A1DFF -% ------------------- +% =================== -% ------ Root Widget ------ +% ====== Root Widget ====== window.backgroundColor = rgba(160, 160, 160, 255) -% ------------------------- +% ========================= -% ------ Panel ------ +% ====== Panel ====== (panel) { backgroundColor = #AAAAAAFF titleColor = $accentColorDark @@ -30,14 +30,25 @@ window.backgroundColor = rgba(160, 160, 160, 255) titlebarHeight = 18 titlebarFontSize = 18 titlebarTextAlign = text_left + % scrollSpeed = Vec2(0.8, 0.8) padding = Rect(15, 15, 15, 15) margin = Rect(0, 0, 0, 0) } -% ------------------- +% =================== -% ------ CheckBox ------ +% ====== Scrollbar ====== +% (scrollbar) { +% (handle) { + +% } +% } +% ======================= + + + +% ====== CheckBox ====== (checkbox) { checkBorderColor = $accentColorDark checkBoxColor = $accentColor @@ -67,11 +78,11 @@ window.backgroundColor = rgba(160, 160, 160, 255) checkBorderColor = $accentColor textColor = $accentColorDark } -% ---------------------- +% ====================== -% ------ Label ------ +% ====== Label ====== (label) { textColor = $textColor backgroundColor = color_transparent @@ -84,4 +95,4 @@ window.backgroundColor = rgba(160, 160, 160, 255) padding = Rect(15, 15, 15, 15) margin = Rect(0, 0, 0, 0) } -% ------------------- +% =================== diff --git a/src/ogfx/gui/Events.hpp b/src/ogfx/gui/Events.hpp index 87f3738..734afc8 100644 --- a/src/ogfx/gui/Events.hpp +++ b/src/ogfx/gui/Events.hpp @@ -1,21 +1,21 @@ /* - OmniaFramework - A collection of useful functionality - Copyright (C) 2025 OmniaX-Dev + OmniaFramework - A collection of useful functionality + Copyright (C) 2025 OmniaX-Dev - This file is part of OmniaFramework. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with OmniaFramework. If not, see . */ #pragma once @@ -102,7 +102,7 @@ namespace ogfx { public: inline Event(WindowCore& _window) : window(_window), drop(window, DropEventData::eDropType::None) { } - inline void handle(void) { m_handled = true; } + inline void handle(void) const { m_handled = true; } inline bool isHandled(void) const { return m_handled; } public: @@ -114,7 +114,7 @@ namespace ogfx u32 __original_signal_id { 0 }; private: - bool m_handled { false }; + mutable bool m_handled { false }; }; } } diff --git a/src/ogfx/gui/WidgetManager.cpp b/src/ogfx/gui/WidgetManager.cpp index 5744387..facc208 100644 --- a/src/ogfx/gui/WidgetManager.cpp +++ b/src/ogfx/gui/WidgetManager.cpp @@ -130,6 +130,7 @@ namespace ogfx if (w == nullptr) continue; if (w->isInvalid()) continue; w->__applyTheme(theme, true); + w->reloadTheme(); } } diff --git a/src/ogfx/gui/WidgetManager.hpp b/src/ogfx/gui/WidgetManager.hpp index 325760b..81429a9 100644 --- a/src/ogfx/gui/WidgetManager.hpp +++ b/src/ogfx/gui/WidgetManager.hpp @@ -1,21 +1,21 @@ /* - OmniaFramework - A collection of useful functionality - Copyright (C) 2025 OmniaX-Dev + OmniaFramework - A collection of useful functionality + Copyright (C) 2025 OmniaX-Dev - This file is part of OmniaFramework. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with OmniaFramework. If not, see . */ #pragma once @@ -61,6 +61,7 @@ namespace ogfx inline i32 widgetCount(void) const { return m_widgetList.size(); } inline WindowCore& getWindow(void) { return m_window; } + inline const stdvec& getWidgets(void) const { return m_widgetList; } private: void processDragAndDrop(Widget* widget, const Event& event); diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index d6d1c30..6568f7f 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -562,9 +562,9 @@ namespace ogfx namespace gui { - void Window::addWidget(Widget& widget) + void Window::addWidget(Widget& widget, const Vec2& position) { - m_rootWidget.addChild(widget); + m_rootWidget.addWidget(widget, position); setTheme(*m_guiTheme); } @@ -572,6 +572,7 @@ namespace ogfx { m_guiTheme = &theme; m_rootWidget.__applyTheme(theme, true); + m_rootWidget.reloadTheme(true); } void Window::__on_window_init(i32 width, i32 height, const String& title) diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index c7ddd3c..eb5bd96 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -212,7 +212,7 @@ namespace ogfx public: inline Window(void) { } inline Window(i32 width, i32 height, const String& title) { initialize(width, height, title); } - void addWidget(Widget& widget); + void addWidget(Widget& widget, const Vec2& position = { 0, 0 }); void setTheme(const ostd::Stylesheet& theme) override; inline virtual void onInitialize(void) { } diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index 279cc39..c38fac5 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -35,6 +35,7 @@ namespace ogfx disableDrawBox(); disableFocus(); enableStopEvents(); + allowScroll(true); validate(); return *this; } @@ -49,6 +50,7 @@ namespace ogfx setBorderColor(getThemeValue(theme, "panel.borderColor", Colors::Black)); setPadding(getThemeValue(theme, "panel.padding", { 15, 15, 15, 15 })); setMargin(getThemeValue(theme, "panel.margin", { 0, 0, 0, 0 })); + m_scrollSpeed = getThemeValue(theme, "panel.scrollSpeed", { 0.0f, 0.8f }); m_basePadding = getPadding(); m_titleColor = getThemeValue(theme, "panel.titleColor", Colors::Black); m_titlebarColor = getThemeValue(theme, "panel.titlebarColor", Colors::Transparent); @@ -61,10 +63,38 @@ namespace ogfx } void Panel::onDraw(ogfx::BasicRenderer2D& gfx) + { + if (isScrollAllowed()) + gfx.fillRect(getContentExtents() + Rectangle { getGlobalPosition(), 0, 0 }, { 80, 0, 0, 30 }); + } + + void Panel::afterDraw(ogfx::BasicRenderer2D& gfx) { draw_titlebar(gfx); } + void Panel::onMouseScrolled(const Event& event) + { + if (!isScrollAllowed()) + return; + auto ext = getContentExtents(); + auto cont = getContentBounds(); + f32 maxScroll = -(ext.h - cont.h); + if (event.mouse->scroll == MouseEventData::eScrollDirection::Down && m_scrollOffset.y > maxScroll) + { + m_scrollOffset -= (m_scrollSpeed * 15.0f); + if (m_scrollOffset.y < maxScroll) + m_scrollOffset.y = maxScroll; + } + else if (event.mouse->scroll == MouseEventData::eScrollDirection::Up && m_scrollOffset.y < 0) + { + m_scrollOffset += (m_scrollSpeed * 15.0f); + if (m_scrollOffset.y > 0) + m_scrollOffset.y = 0; + } + event.handle(); + } + void Panel::setTitlebarType(const String& type) { String t = type.new_toLower().trim(); diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index 8198fb0..4f04c45 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -45,18 +45,22 @@ namespace ogfx Panel& create(void); void applyTheme(const ostd::Stylesheet& theme) override; void onDraw(ogfx::BasicRenderer2D& gfx) override; + void afterDraw(ogfx::BasicRenderer2D& gfx) override; + void onMouseScrolled(const Event& event) override; void setTitlebarType(const String& type); String getTitlebarType(void) const; inline void setBackGroundColor(const Color& color) { m_backgroundColor = color; } inline Color getBackgroundColor(void) { return m_backgroundColor; } inline String getTitle(void) const { return m_title; } inline void setTitle(const String& title) { m_title = title; } + inline Vec2 getScrollOffset(void) const override { return m_scrollOffset; } private: void draw_titlebar(BasicRenderer2D& gfx); private: String m_title { "Panel" }; + Vec2 m_scrollOffset { 0, 0 }; Color m_titleColor { Colors::Black }; i32 m_titlebarType = TitleBarTypes::NoneValue; @@ -67,6 +71,7 @@ namespace ogfx Color m_titlebarBorderColor { Colors::Black }; Rectangle m_basePadding { 0, 0, 0, 0 }; i32 m_titleTextAlign { 0 }; + Vec2 m_scrollSpeed { 0.8f, 0.8f }; }; } } diff --git a/src/ogfx/gui/widgets/Widget.cpp b/src/ogfx/gui/widgets/Widget.cpp index f99b3bc..8597e22 100644 --- a/src/ogfx/gui/widgets/Widget.cpp +++ b/src/ogfx/gui/widgets/Widget.cpp @@ -36,10 +36,12 @@ namespace ogfx m_window = &window; } - bool Widget::addChild(Widget& child) + bool Widget::addWidget(Widget& child, const Vec2& position) { if (!m_allowChildren) return false; + if (position.x != 0 || position.y != 0) + child.setPosition(position); child.reloadTheme(); return m_widgets.addWidget(child); } @@ -48,7 +50,7 @@ namespace ogfx { Vec2 glob = getPosition(); if (!m_rootChild && m_parent != nullptr) - glob += m_parent->getGlobalPosition() + m_parent->getPadding().getPosition(); + glob += m_parent->getGlobalPosition() + m_parent->getPadding().getPosition() + m_parent->getScrollOffset(); glob += m_margin.getPosition(); return glob; } @@ -75,6 +77,19 @@ namespace ogfx return { getGlobalContentPosition(), getContentBounds().getSize() }; } + Rectangle Widget::getContentExtents(void) const + { + f32 maxX = 0, maxY = 0; + for (auto* child : m_widgets.getWidgets()) + { + if (!child || child->isInvalid()) continue; + Vec2 localPos = getPadding().getPosition() + child->getPosition() + child->getMargin().getPosition() + child->getContentBounds().getPosition(); + maxX = std::max(maxX, localPos.x + child->getw() + child->getMargin().w); + maxY = std::max(maxY, localPos.y + child->geth() + child->getMargin().h); + } + return { 0, 0, maxX, maxY }; + } + bool Widget::contains(Vec2 p, bool includeBounds) const { return Rectangle(getGlobalPosition(), getSize()).contains(p, includeBounds); @@ -91,7 +106,7 @@ namespace ogfx m_themeOverrides.push_back({ fullKey, value }); } - void Widget::reloadTheme(void) + void Widget::reloadTheme(bool propagate) { if (getWindow().theme() == nullptr) return; @@ -116,7 +131,7 @@ namespace ogfx backup.push_back({ currentValuePtr, currentValue, rule.fullKey }); const_cast_theme.setFull(rule.fullKey, rule.value); } - __applyTheme(const_cast_theme, false); + __applyTheme(const_cast_theme, propagate); for (auto&[ptr, val, key] : backup) { if (ptr == nullptr) @@ -189,6 +204,7 @@ namespace ogfx m_widgets.draw(gfx); if (m_showBorder) gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); + afterDraw(gfx); } void Widget::__update(void) diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index 0b6ca31..8b7c215 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -41,18 +41,20 @@ namespace ogfx public: using EventCallback = std::function; public: Widget(const Rectangle& bounds, WindowCore& window); - bool addChild(Widget& child); + bool addWidget(Widget& child, const Vec2& position = { 0, 0 }); virtual Vec2 getGlobalPosition(void) const; virtual Vec2 getGlobalContentPosition(void) const; virtual Rectangle getGlobalBounds(void) const; virtual Rectangle getContentBounds(void) const; virtual Rectangle getGlobalContentBounds(void) const; + virtual Rectangle getContentExtents(void) const; using Rectangle::contains; bool contains(Vec2 p, bool includeBounds = false) const override; void enable(bool enable = true); virtual void applyTheme(const ostd::Stylesheet& theme) = 0; + inline virtual Vec2 getScrollOffset(void) const { return { 0, 0 }; } void addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value); - void reloadTheme(void); + void reloadTheme(bool propagate = false); void setThemeQualifier(const String& qualifier, bool value = true); bool getThemeQualifier(const String& qualifier) const; bool addThemeID(const String& id); @@ -61,6 +63,7 @@ namespace ogfx inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; } inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { } + inline virtual void afterDraw(ogfx::BasicRenderer2D& gfx) { } inline virtual void onUpdate(void) { } inline virtual void onMousePressed(const Event& event) { } @@ -156,6 +159,8 @@ namespace ogfx inline bool isVisible(void) const { return m_visible; } inline void show(void) { setVisible(true); } inline void hide(void) { setVisible(false); } + inline void allowScroll(bool allow = true) { m_allowScroll = allow; } + inline bool isScrollAllowed(void) const { return m_allowScroll; } template inline T getThemeValue(const ostd::Stylesheet &theme, const String& key, const T& fallback) @@ -215,6 +220,7 @@ namespace ogfx bool m_clipContents { true }; bool m_acceptDragAndDrop { false }; bool m_visible { true }; + bool m_allowScroll { false }; MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None }; stdvec m_themeIDList; diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index cb40723..60df6ad 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -31,115 +31,59 @@ class Window : public ogfx::gui::Window inline Window(void) { } inline void onInitialize(void) override { - m_img.loadFromFile("./img.png", m_gfx); - ogfx::Animation::AnimationData ad; - ad.backwards = false; - ad.columns = 9; - ad.rows = 4; - ad.frame_width = 256; - ad.frame_height = 256; - ad.length = 36; - ad.random = false; - ad.still = false; - ad.speed = 0; - m_anim.create(ad); - m_anim.setSpriteSheet(m_img); - - m_panel1.setSize(300, 140); - m_panel1.setPosition(350, 50); - m_panel1.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void { - pos = { event.mouse->position_x, event.mouse->position_y }; - }); - m_panel1.setMouseDraggedCallback([&](const ogfx::gui::Event& event) -> void { - if (m_panel1.getPressedMouseButton() == ogfx::MouseEventData::eButton::None) - return; - ostd::Vec2 pos2 { event.mouse->position_x, event.mouse->position_y }; - m_panel1.addPos(pos2 - pos); - pos = pos2; - }); - m_panel1.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void { - std::cout << "SCROLLED IN THE PANEL LOL \n"; - }); + // m_img.loadFromFile("./img.png", m_gfx); + // ogfx::Animation::AnimationData ad; + // ad.backwards = false; + // ad.columns = 9; + // ad.rows = 4; + // ad.frame_width = 256; + // ad.frame_height = 256; + // ad.length = 36; + // ad.random = false; + // ad.still = false; + // ad.speed = 0; + // m_anim.create(ad); + // m_anim.setSpriteSheet(m_img); - m_panel2.setSize(600, 400); - m_panel2.setPosition(170, 200); - m_panel2.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void { - ogfx::gui::Widget::setDragAndDropData(m_label3); - setCursor(eCursor::Move); - }); - m_panel2.setMouseReleasedCallback([&](const ogfx::gui::Event& event) -> void { - setCursor(eCursor::Default); - }); - m_label1.setPosition(100, 200); - m_label1.setText("Hello World!"); + m_label1.setText("Show Panel2"); m_label1.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void { - std::cout << "PRESS!\n"; m_check1.setChecked(!m_check1.isChecked()); }); - m_label1.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void { - std::cout << "SCROLL!\n"; - }); - addWidget(m_label1); - m_panel2.addChild(m_panel1); - addWidget(m_panel2); + m_label1.addThemeOverride("@.label.showBackground", true); + m_label1.addThemeOverride("@.label.backgroundColor", Colors::Beige); + m_label1.addThemeOverride("@.label.showBorder", true); + m_label1.addThemeOverride("@.label.borderWidth", 2); + m_label1.addThemeOverride("@.label.borderColor", Colors::DarkMagenta); + m_label1.addThemeOverride("@:pressed.label.backgroundColor", Colors::Crimson); + m_label1.addThemeOverride("@:hover.label.backgroundColor", Colors::DarkRed); + m_label1.reloadTheme(); - - m_check1.setPosition(30, 30); m_check1.setText("Check this out!"); + m_check1.setChecked(true); m_check1.setStateChangedCallback([&](ogfx::gui::widgets::CheckBox& sender, bool state) -> void { m_panel1.setVisible(state); }); - addWidget(m_check1); - m_label2.setPosition(0, 0); - m_label2.setText("Ciccia Bella!"); - m_label2.connectSignal(ostd::BuiltinSignals::FileDragAndDropped); - m_label2.addThemeID("testLabel"); - m_label2.addThemeID("testLabel2"); - m_label2.setSignalCallback([&](ostd::Signal& signal) -> void { - if (signal.ID == ostd::BuiltinSignals::FileDragAndDropped) - { - auto& data = (ogfx::DropEventData&)signal.userData; - if (ostd::FileSystem::fileExists(data.textOrFilePath)) - { - m_theme.loadFromFile(data.textOrFilePath); - m_rootWidget.reloadTheme(); - } - std::cout << data.textOrFilePath << "\n"; - } - }); - m_label2.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void { - std::cout << "BARBABARBA!\n"; - }); - m_label2.enableDragAndDrop(); - m_label2.setDragAndDropCallback([&](const ogfx::gui::Event& event) -> void { - if (event.drop.userObject->getTypeName() == "ogfx::gui::widgets::Label") - { - std::cout << cast(*event.drop.userObject).getText() << "!\n"; - } - }); - m_panel1.addChild(m_label2); + m_label2.setText("Label2"); + m_label3.setText("Label3"); - m_label1.addThemeOverride("@:pressed.label.textColor", Colors::Crimson); + m_panel1.setSize(300, 140); + m_panel1.allowScroll(false); + + m_panel2.setSize(600, 400); + + m_panel1.addWidget(m_label2); + + m_panel2.addWidget(m_label3); + m_panel2.addWidget(m_panel1, { 0, 50 }); + m_panel2.addWidget(m_label1, { 0, 340 }); + + addWidget(m_check1, { 30, 30 }); + addWidget(m_panel2, { 30, 100 }); m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList()); - - m_label2.addThemeOverride("@:hover.label.showBackground", true); - m_label2.addThemeOverride("@:hover.label.backgroundColor", Colors::DarkGreen); - - m_label3.setPosition(0, 30); - m_label3.setText("Bella!"); - m_label3.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void { - std::cout << "BALALLA!\n"; - }); - m_label3.addThemeID("label3"); - m_panel1.addChild(m_label3); - - wout().setFontSize(80); - - setTheme(m_theme); } @@ -171,7 +115,6 @@ class Window : public ogfx::gui::Window ogfx::gui::widgets::Panel m_panel2 { *this }; ogfx::gui::widgets::CheckBox m_check1 { *this }; ostd::Stylesheet m_theme; - ostd::Vec2 pos { 0, 0 }; ogfx::Animation m_anim; ogfx::Image m_img; };