diff --git a/CMakeLists.txt b/CMakeLists.txt index 49aef5e..40434e3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ list(APPEND OGFX_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/MenuBar.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/ToolBar.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/Layout.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/FocusManager.cpp # gui/widgets ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp diff --git a/other/TODO.txt b/other/TODO.txt index 7c88f2a..f0d12f7 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -25,6 +25,8 @@ ***FIX: Limit FPS even when continuous events happen ***FIX: Float values in stylesheet needing to have decimal point to be read as float ***Make parsing of vec2 and rect in stylesheet, consistent with other value functions like gradients and animations +***FIX: ToolBar::AddButton not consistent order +***FIX: KeyPress crash on TreeView Implement cursors in Stylesheet FIX: Window getting exponentially bigger when Desktop scale changes FIX: Refreshing scroll when content extent changes @@ -52,6 +54,8 @@ FIX: ContextMenu rendering partly outside the screen when showing it close to th Update theme for visible TreeView lines Add ToolBar::addButton overload that takes a ogfx::Icon wrapper Redesign theme overrides to be more robust +FIX: Animated icons not working in TreeView +Add modifiers to Event.keyboard path diff --git a/src/ogfx/gui/ContextMenu.hpp b/src/ogfx/gui/ContextMenu.hpp index 82c2e8f..fe1ea97 100644 --- a/src/ogfx/gui/ContextMenu.hpp +++ b/src/ogfx/gui/ContextMenu.hpp @@ -160,14 +160,14 @@ namespace ogfx Instance m_data; f32 m_entryHeight { 0 }; Vec2 m_mousePos { 0, 0 }; - ostd::Timer m_animClock; + ostd::Counter m_animClock; stdvec m_panels; i32 m_pendingOpenPanelDepth { -1 }; i32 m_pendingOpenEntryIndex { -1 }; - ostd::Timer m_hoverOpenTimer; - ostd::Timer m_hoverCloseTimer; + ostd::Counter m_hoverOpenTimer; + ostd::Counter m_hoverCloseTimer; bool m_hoverOpenTimerActive { false }; bool m_hoverCloseTimerActive { false }; diff --git a/src/ogfx/gui/FocusManager.cpp b/src/ogfx/gui/FocusManager.cpp new file mode 100644 index 0000000..b44a411 --- /dev/null +++ b/src/ogfx/gui/FocusManager.cpp @@ -0,0 +1,154 @@ +/* + OmniaFramework - A collection of useful functionality + Copyright (C) 2026 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 "FocusManager.hpp" +#include "Window.hpp" +#include "../utils/Keycodes.hpp" + +namespace ogfx +{ + namespace gui + { + FocusManager::FocusManager(Window& window) : m_window(window) + { + + } + + bool FocusManager::requestFocus(Widget& widget) + { + if (&widget == m_focused) return true; + if (!widget.isFocusEnabled()) return false; + if (m_window.getToolBar().isVisible()) + { + if (requestFocus(widget, m_window.getToolBar())) + return true; + } + if (m_window.getStatusBar().isVisible()) + { + if (requestFocus(widget, m_window.getStatusBar())) + return true; + } + return requestFocus(widget, m_window.m_rootWidget); + } + + bool FocusManager::requestFocus(Widget& widget, Widget& root) + { + if (&widget == m_focused) return true; + if (!widget.isFocusEnabled()) return false; + if (widget.isInvalid()) return false; + if (!widget.isVisible()) return false; + if (!root.hasChildren() || !root.isChildrenEnabled()) + return false; + if (!root.getChildren().hasWidget(widget)) + { + for (auto& child : root.getChildren().m_widgetList) + { + if (child && requestFocus(widget, *child)) + return true; + } + return false; + } + + if (m_focused) + { + m_focused->m_focused = false; + m_focused->onFocusLost(Event(m_window)); + m_focused->setThemeQualifier("focused", false); + } + + widget.m_focused = true; + widget.onFocusGained(Event(m_window)); + widget.setThemeQualifier("focused", true); + m_focused = &widget; + return true; + } + + Widget* FocusManager::focusNext(void) + { + if (m_tabIndexMap.empty()) + return nullptr; + + i32 startIndex = -1, smallest = i32Range::max(); + if (m_focused) + startIndex = m_focused->m_tabIndex; + i32 nextIndex = i32Range::max(); + Widget* nextPtr = nullptr; + Widget* smallestPtr = nullptr; + + for (auto&[wptr, index] : m_tabIndexMap) + { + if (index < smallest) + { + smallest = index; + smallestPtr = wptr; + } + if (index > startIndex && index < nextIndex) + { + nextIndex = index; + nextPtr = wptr; + } + } + + Widget* w = nullptr; + if (!nextPtr) + { + if (!smallestPtr) + return nullptr; + w = smallestPtr; + } + else + w = nextPtr; + + requestFocus(*w); + return w; + } + + bool FocusManager::registerTabIndex(Widget& widget, i32 index) + { + if (index >= 0) + { + m_tabIndexMap[&widget] = index; + return true; + } + return false; + } + + void FocusManager::unregisterTabIndex(Widget& widget) + { + if (m_tabIndexMap.count(&widget) > 0) + m_tabIndexMap.erase(m_tabIndexMap.find(&widget)); + } + + void FocusManager::clearFocus(void) + { + if (m_focused) + { + m_focused->m_focused = false; + m_focused = nullptr; + } + } + + void FocusManager::onKeyReleased(const Event& event) + { + if (isTabNavigationEnabled() && event.keyboard->keyCode == KeyCode::Tab) + focusNext(); + } + } +} diff --git a/src/ogfx/gui/FocusManager.hpp b/src/ogfx/gui/FocusManager.hpp new file mode 100644 index 0000000..c650811 --- /dev/null +++ b/src/ogfx/gui/FocusManager.hpp @@ -0,0 +1,55 @@ +/* + OmniaFramework - A collection of useful functionality + Copyright (C) 2026 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 + { + class Window; + class FocusManager + { + public: + FocusManager(Window& window); + bool requestFocus(Widget& widget, Widget& root); + bool requestFocus(Widget& widget); + Widget* focusNext(void); + + bool registerTabIndex(Widget& widget, i32 index); + void unregisterTabIndex(Widget& widget); + + void clearFocus(void); + + void onKeyReleased(const Event& event); + + inline Widget* getFocused(void) { return m_focused; } + OSTD_BOOL_PARAM_GETSET_E(TabNavigation, m_tabNavigationEnabled); + + private: + Window& m_window; + Widget* m_focused { nullptr }; + bool m_tabNavigationEnabled { true }; + stdumap m_tabIndexMap; + }; + } +} diff --git a/src/ogfx/gui/RawTextInput.hpp b/src/ogfx/gui/RawTextInput.hpp index 02434ec..39f8a97 100644 --- a/src/ogfx/gui/RawTextInput.hpp +++ b/src/ogfx/gui/RawTextInput.hpp @@ -1,21 +1,21 @@ /* - OmniaFramework - A collection of useful functionality - Copyright (C) 2026 OmniaX-Dev + OmniaFramework - A collection of useful functionality + Copyright (C) 2026 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 @@ -60,35 +60,35 @@ namespace ogfx u8 extraPaddingLeft { 0 }; bool cursorBlink { true }; - ostd::Counter cursorBlinkCounter { 30 }; + ostd::BasicCounter cursorBlinkCounter { 30 }; }; public: struct tDefaultThemes { inline static const Theme DebugTheme { - { 220, 220, 255 }, //Text Color - { 10, 20, 120 }, //Border Color - { 0, 0, 22 }, //Background Color - { 220, 10, 10 }, //Cursor Color + { 220, 220, 255 }, //Text Color + { 10, 20, 120 }, //Border Color + { 0, 0, 22 }, //Background Color + { 220, 10, 10 }, //Cursor Color - 2, //Cursor Width - 0, //Extra Padding Top - 0, //Extra Padding Left + 2, //Cursor Width + 0, //Extra Padding Top + 0, //Extra Padding Left - true, //Cursor Blink - { 30 } //Cursor Blink Timer + true, //Cursor Blink + { 30 } //Cursor Blink Timer }; inline static const Theme DefaultTheme { - { 120, 120, 180 }, //Text Color - { 10, 20, 120 }, //Border Color - { 0, 2, 10 }, //Background Color - { 20, 100, 255 }, //Cursor Color + { 120, 120, 180 }, //Text Color + { 10, 20, 120 }, //Border Color + { 0, 2, 10 }, //Background Color + { 20, 100, 255 }, //Cursor Color - 2, //Cursor Width - 0, //Extra Padding Top - 0, //Extra Padding Left + 2, //Cursor Width + 0, //Extra Padding Top + 0, //Extra Padding Left - true, //Cursor Blink - { 30 } //Cursor Blink Timer + true, //Cursor Blink + { 30 } //Cursor Blink Timer }; }; public: enum eActionEventType { None = 0, Enter, Tab }; @@ -156,7 +156,7 @@ namespace ogfx u16 m_cursorPosition { 0 }; bool m_cursorState { true }; - ostd::Counter m_keyRepeatCounter { 1 }; + ostd::BasicCounter m_keyRepeatCounter { 1 }; char m_lastChar { 0 }; i32 m_lastKeyCode { 0 }; diff --git a/src/ogfx/gui/ToolBar.cpp b/src/ogfx/gui/ToolBar.cpp index 0446c41..3d9c4d0 100644 --- a/src/ogfx/gui/ToolBar.cpp +++ b/src/ogfx/gui/ToolBar.cpp @@ -48,6 +48,7 @@ namespace ogfx setPosition(0, win.getMenuBar().getHeight()); else setPosition(0, 0); + return *this; } diff --git a/src/ogfx/gui/WidgetManager.cpp b/src/ogfx/gui/WidgetManager.cpp index 26ff9ca..bc49543 100644 --- a/src/ogfx/gui/WidgetManager.cpp +++ b/src/ogfx/gui/WidgetManager.cpp @@ -19,9 +19,9 @@ */ #include "WidgetManager.hpp" +#include "Window.hpp" #include "widgets/Widget.hpp" #include "../render/BasicRenderer.hpp" -#include "../utils/Keycodes.hpp" #include "../../ostd/io/Memory.hpp" namespace ogfx @@ -39,41 +39,11 @@ namespace ogfx return found && widget.isValid(); } - bool WidgetManager::requestFocus(Widget& widget) - { - if (!hasWidget(widget)) return false; - if (&widget == m_focused) return true; - for (auto& w : m_widgetList) - { - if (w == nullptr) continue; - if (w->isInvalid()) continue; - if (!w->isVisible()) continue; - if (w->m_focused) - { - w->m_focused = false; - w->onFocusLost(Event(m_window)); - w->setThemeQualifier("focused", false); - } - else - w->m_focused = false; - } - widget.m_focused = true; - widget.onFocusGained(Event(m_window)); - widget.setThemeQualifier("focused", true); - m_focused = &widget; - return true; - } - bool WidgetManager::addWidget(Widget& widget) { if (hasWidget(widget)) return false; widget.m_parent = &m_owner; m_widgetList.push_back(&widget); - if (widget.m_topMost) - return true; - std::ranges::sort(m_widgetList, {}, [](Widget* w) { - return w->m_zIndex; - }); widget.refresh(); return true; } @@ -84,32 +54,6 @@ namespace ogfx return true; } - Widget* WidgetManager::focusNext(void) - { - if (m_widgetList.empty()) - return nullptr; - - Widget* next = nullptr; - Widget* smallest = nullptr; - - i32 currentTabIndex = (m_focused != nullptr ? m_focused->m_tabIndex : std::numeric_limits::max()); - for (Widget* w : m_widgetList) - { - i32 tab_i = w->m_tabIndex; - if (tab_i < 0) continue; - if (!smallest || tab_i < smallest->m_tabIndex) - smallest = w; - if (tab_i > currentTabIndex) - { - if (!next || tab_i < next->m_tabIndex) - next = w; - } - } - Widget* w = next ? next : smallest; - requestFocus(*w); - return w; - } - void WidgetManager::draw(ogfx::BasicRenderer2D& gfx) { for (auto& w : m_widgetList) @@ -157,6 +101,7 @@ namespace ogfx if (!w->contains(event.mouse->position_x, event.mouse->position_y, true)) continue; event.mouse->mousePressedOnWidget = w; + cast(m_window).getFocusManager().requestFocus(*w); w->__onMousePressed(event); m_mousePressedOnWidget = w; if (event.isHandled() || w->m_stopEvents) @@ -182,7 +127,6 @@ namespace ogfx event.mouse->mousePressedOnWidget = m_mousePressedOnWidget; w->__onMouseReleased(event); processDragAndDrop(w, event); - // requestFocus(*w); if (w->isMouseInside() && w->m_stopEvents) break; } @@ -275,22 +219,24 @@ namespace ogfx void WidgetManager::onKeyPressed(const Event& event) { - if (!m_focused || !m_focused->isVisible()) return; - m_focused->__onKeyPressed(event); + auto focused = cast(m_window).getFocusManager().getFocused(); + if (!focused || !focused->isVisible()) return; + focused->__onKeyPressed(event); } void WidgetManager::onKeyReleased(const Event& event) { - if (!m_focused || !m_focused->isVisible()) return; - m_focused->__onKeyReleased(event); - if (m_tabNavigationEnabled && event.keyboard->keyCode == KeyCode::Tab) - focusNext(); + auto focus = cast(m_window).getFocusManager(); + auto focused = focus.getFocused(); + if (!focused || !focused->isVisible()) return; + focused->__onKeyReleased(event); } void WidgetManager::onTextEntered(const Event& event) { - if (!m_focused || !m_focused->isVisible()) return; - m_focused->__onTextEntered(event); + auto focused = cast(m_window).getFocusManager().getFocused(); + if (!focused || !focused->isVisible()) return; + focused->__onTextEntered(event); } void WidgetManager::onWindowClosed(const Event& event) diff --git a/src/ogfx/gui/WidgetManager.hpp b/src/ogfx/gui/WidgetManager.hpp index 62e17b0..f3946a8 100644 --- a/src/ogfx/gui/WidgetManager.hpp +++ b/src/ogfx/gui/WidgetManager.hpp @@ -39,10 +39,8 @@ namespace ogfx public: WidgetManager(WindowCore& window, Widget& owner); bool hasWidget(Widget& widget); - bool requestFocus(Widget& widget); bool addWidget(Widget& widget); bool removeWidget(Widget& widget); - Widget* focusNext(void); void draw(ogfx::BasicRenderer2D& gfx); void update(void); @@ -71,9 +69,9 @@ namespace ogfx WindowCore& m_window; Widget& m_owner; stdvec m_widgetList; - Widget* m_focused { nullptr }; - bool m_tabNavigationEnabled { true }; Widget* m_mousePressedOnWidget { nullptr }; + + friend class FocusManager; }; } } diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 628efad..38a1a89 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -1008,6 +1008,7 @@ namespace ogfx { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; evt.__original_signal_id = ostd::BuiltinSignals::KeyReleased; + m_focusManager.onKeyReleased(evt); m_toolbar.__onKeyReleased(evt); m_statusbar.__onKeyReleased(evt); m_rootWidget.__onKeyReleased(evt); diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 79598b4..13275b7 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace ogfx { @@ -181,7 +182,7 @@ namespace ogfx String m_title { "" }; i32 m_blockingEventsDelay { 33 }; Vec2 m_mousePosition { 0, 0 }; - ostd::Timer m_tooltipTimer; + ostd::Counter m_tooltipTimer; String m_tooltipText { "" }; bool m_running { false }; @@ -259,7 +260,7 @@ namespace ogfx i32 m_fps { 0 }; ostd::StepTimer m_fixedUpdateTImer; ostd::StepTimer m_fpsUpdateTimer; - ostd::Timer m_fpsUpdateClock; + ostd::Counter m_fpsUpdateClock; u64 m_frameTimeAcc { 0 }; i32 m_frameCount { 0 }; }; @@ -284,6 +285,7 @@ namespace ogfx inline void showMenuBar(bool show = true) { m_menubar.show(show); } inline void showToolBar(bool show = true) { m_toolbar.setVisible(show); } inline void showStatusBar(bool show = true) { m_statusbar.setVisible(show); } + inline FocusManager& getFocusManager(void) { return m_focusManager; } inline virtual void onInitialize(void) { } inline virtual void onDestroy(void) { } @@ -308,6 +310,7 @@ namespace ogfx protected: RootWidget m_rootWidget { *this }; + FocusManager m_focusManager { *this }; ostd::StepTimer m_fixedUpdateTimer; ostd::StepTimer::TimePoint m_lastFrameTime; ostd::StepTimer m_mainLoopTimer; @@ -317,6 +320,7 @@ namespace ogfx ToolBar m_statusbar { *this, true }; friend class RootWidget; + friend class FocusManager; }; } } diff --git a/src/ogfx/gui/widgets/Label.cpp b/src/ogfx/gui/widgets/Label.cpp index 95076a4..bec7d8a 100644 --- a/src/ogfx/gui/widgets/Label.cpp +++ b/src/ogfx/gui/widgets/Label.cpp @@ -38,6 +38,7 @@ namespace ogfx setPadding({ 5, 5, 5, 5 }); setTypeName("ogfx::gui::Label"); disableChildren(); + disableFocus(); enableBackground(false); setStylesheetCategoryName("label"); validate(); @@ -127,6 +128,7 @@ namespace ogfx setPadding({ 0, 0, 0, 0 }); setTypeName("ogfx::gui::ImageLabel"); disableChildren(); + disableFocus(); disableBackground(); disableBorder(); setStylesheetCategoryName("image"); diff --git a/src/ogfx/gui/widgets/ProgressBar.cpp b/src/ogfx/gui/widgets/ProgressBar.cpp index 3bc2e36..9912caa 100644 --- a/src/ogfx/gui/widgets/ProgressBar.cpp +++ b/src/ogfx/gui/widgets/ProgressBar.cpp @@ -30,6 +30,7 @@ namespace ogfx setPadding({ 0, 0, 0, 0 }); setTypeName("ogfx::gui::ProgressBar"); disableChildren(); + disableFocus(); enableBackground(); enableBorder(); setStylesheetCategoryName("progressbar"); diff --git a/src/ogfx/gui/widgets/RootWidget.cpp b/src/ogfx/gui/widgets/RootWidget.cpp index caef303..4fed4de 100644 --- a/src/ogfx/gui/widgets/RootWidget.cpp +++ b/src/ogfx/gui/widgets/RootWidget.cpp @@ -29,6 +29,7 @@ namespace ogfx RootWidget::RootWidget(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { setRootChild(); + disableFocus(); setSize(cast(window.getWindowWidth()), cast(window.getWindowHeight())); setStylesheetCategoryName("window"); setTypeName("ogfx::gui::RootWidget"); diff --git a/src/ogfx/gui/widgets/Scrollbar.cpp b/src/ogfx/gui/widgets/Scrollbar.cpp index 3804e71..50e6534 100644 --- a/src/ogfx/gui/widgets/Scrollbar.cpp +++ b/src/ogfx/gui/widgets/Scrollbar.cpp @@ -31,6 +31,7 @@ namespace ogfx setMargin({ 0, 0, 0, 0 }); setTypeName("ogfx::gui::Label"); disableChildren(); + disableFocus(); enableBackground(true); enableBorder(false); enableTopMost(true); @@ -154,6 +155,7 @@ namespace ogfx setMargin({ 0, 0, 0, 0 }); setTypeName("ogfx::gui::Label"); disableChildren(); + disableFocus(); enableBackground(true); enableBorder(false); enableTopMost(true); diff --git a/src/ogfx/gui/widgets/TreeView.cpp b/src/ogfx/gui/widgets/TreeView.cpp index eb7c38a..e306e9e 100644 --- a/src/ogfx/gui/widgets/TreeView.cpp +++ b/src/ogfx/gui/widgets/TreeView.cpp @@ -277,11 +277,11 @@ namespace ogfx auto& item = *m_list[i]; if (!item.isValid()) continue; if (!item.isVisible()) continue; - if (!item.isIconEnabled()) continue; + f32 itemH = item.getDimensions().y; if (y > visibleEnd) break; - if (item.getIcon().getFrameCount() != 1) - std::cout << item.getIcon().getCurrentFrame() << "\n"; - item.getIcon().update(); + if (item.isIconEnabled()) + item.getIcon().update(); + y += itemH; } } diff --git a/src/ogfx/gui/widgets/Widget.cpp b/src/ogfx/gui/widgets/Widget.cpp index 8bfa5b5..4760716 100644 --- a/src/ogfx/gui/widgets/Widget.cpp +++ b/src/ogfx/gui/widgets/Widget.cpp @@ -36,6 +36,15 @@ namespace ogfx m_window = &window; } + Widget::~Widget(void) + { + if (m_window && m_tabIndex >= 0) + cast(*m_window).getFocusManager().unregisterTabIndex(*this); + auto& fm = cast(*m_window).getFocusManager(); + if (fm.getFocused() == this) + fm.clearFocus(); // you'd add this method + } + bool Widget::addWidget(Widget& child, const Vec2& position, bool __skip_callback) { if (!m_allowChildren) @@ -229,6 +238,16 @@ namespace ogfx m_visible = v; } + bool Widget::setTabIndex(i32 index) + { + if (cast(getWindow()).getFocusManager().registerTabIndex(*this, index)) + { + m_tabIndex = index; + return true; + } + return false; + } + void Widget::setLayout(std::unique_ptr layout) { m_layout = std::move(layout); @@ -297,6 +316,9 @@ namespace ogfx if (m_showBorder) gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); afterDraw(gfx); + + if (isFocused()) + gfx.drawRoundRect({ getGlobalPosition(), getSize() }, Colors::Red, m_borderRadius, 3); } void Widget::__update(void) @@ -416,41 +438,26 @@ namespace ogfx void Widget::__onKeyPressed(const Event& event) { if (event.isHandled() || !isKeyPressedEventEnabled()) return; - if (hasChildren()) - m_widgets.onKeyPressed(event); - if (!event.isHandled()) - { - if (callback_onKeyPressed) - callback_onKeyPressed(event); - onKeyPressed(event); - } + if (callback_onKeyPressed) + callback_onKeyPressed(event); + onKeyPressed(event); } void Widget::__onKeyReleased(const Event& event) { if (event.isHandled() || !isKeyReleasedEventEnabled()) return; m_pressedButton = ogfx::MouseEventData::eButton::None; - if (hasChildren()) - m_widgets.onKeyReleased(event); - if (!event.isHandled()) - { - if (callback_onKeyReleased) - callback_onKeyReleased(event); - onKeyReleased(event); - } + if (callback_onKeyReleased) + callback_onKeyReleased(event); + onKeyReleased(event); } void Widget::__onTextEntered(const Event& event) { if (event.isHandled() || !isTextEnteredEventEnabled()) return; - if (hasChildren()) - m_widgets.onTextEntered(event); - if (!event.isHandled()) - { - if (callback_onTextEntered) - callback_onTextEntered(event); - onTextEntered(event); - } + if (callback_onTextEntered) + callback_onTextEntered(event); + onTextEntered(event); } void Widget::__onWindowClosed(const Event& event) diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index ece73d0..cc12a6e 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -66,6 +66,11 @@ namespace ogfx public: // ================================== MAIN ================================= Widget(const Rectangle& bounds, WindowCore& window); + Widget(Widget&&) = default; + Widget& operator=(Widget&&) = delete; + Widget(const Widget&) = delete; + Widget& operator=(const Widget&) = delete; + virtual ~Widget(void); bool addWidget(Widget& child, const Vec2& position = { 0, 0 }, bool __skip_callback = false); bool removeWidget(Widget& child); void enable(bool enable = true); @@ -76,6 +81,7 @@ namespace ogfx bool addThemeID(const String& id); bool removeThemeID(const String& id); void setVisible(bool v); + bool setTabIndex(i32 index); virtual void setCallback(eCallback type, EventCallback callback); using Rectangle::contains; bool contains(Vec2 p, bool includeBounds = false) const override; template @@ -154,10 +160,10 @@ namespace ogfx inline static void setDragAndDropData(ostd::BaseObject& data) { s_dragAndDropData = &data; s_hasDragAndDropData = true; } inline static void clearDragAndDropData(void) { s_dragAndDropData = nullptr; } inline static ostd::BaseObject* getDragAndDropData(void) { return s_dragAndDropData; } + inline i32 getTabIndex(void) const { return m_tabIndex; } inline WindowCore& getWindow(void) { return *m_window; } inline Widget* getParent(void) { return m_parent; } inline const Widget* getParent(void) const { return m_parent; } - inline i32 getZIndex(void) const { return m_zIndex; } inline ogfx::MouseEventData::eButton getPressedMouseButton(void) const { return m_pressedButton; } inline const stdvec& getThemeIDList(void) const { return m_themeIDList; } inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; } @@ -172,7 +178,6 @@ namespace ogfx OSTD_PARAM_GETSET(ostd::ColorGradient, BackgroundGradient, m_backgroundGradient); OSTD_PARAM_GETSET(Rectangle, Padding, m_padding); OSTD_PARAM_GETSET(Rectangle, Margin, m_margin); - OSTD_PARAM_GETSET(i32, TabIndex, m_tabIndex); OSTD_PARAM_GETSET(Vec2, ContentOffset, m_contentOffset); OSTD_PARAM_GETSET(String, TooltipText, m_tooltipText); @@ -236,7 +241,6 @@ namespace ogfx bool m_rootChild { false }; bool m_focused { false }; i32 m_tabIndex { -1 }; - i32 m_zIndex { -1 }; String m_tooltipText { "" }; MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None }; // ==================== @@ -313,6 +317,7 @@ namespace ogfx static ostd::BaseObject* s_dragAndDropData; static bool s_hasDragAndDropData; friend class WidgetManager; + friend class FocusManager; // ==================== diff --git a/src/ogfx/utils/Animation.cpp b/src/ogfx/utils/Animation.cpp index a86352f..c1803b9 100644 --- a/src/ogfx/utils/Animation.cpp +++ b/src/ogfx/utils/Animation.cpp @@ -27,13 +27,21 @@ namespace ogfx { m_animData = ad; m_spriteSheet = &spriteSheet; - m_timer.create(m_animData.fps, [&](f64 dt) -> void { - update_animation(); - }); + m_timer.startCount(ostd::eTimeUnits::Milliseconds); update_animation(); return *this; } + void Animation::update(void) + { + f64 dtms = (1.0 / cast(m_animData.fps)) * 1000.0; + if (m_timer.read() >= dtms) + { + update_animation(); + m_timer.restart(ostd::eTimeUnits::Milliseconds); + } + } + void Animation::resetAnimation(void) { m_currentFrame = 0; diff --git a/src/ogfx/utils/Animation.hpp b/src/ogfx/utils/Animation.hpp index 8a79163..bef0f62 100644 --- a/src/ogfx/utils/Animation.hpp +++ b/src/ogfx/utils/Animation.hpp @@ -35,9 +35,9 @@ namespace ogfx inline Animation(const AnimationData& ad) { create(ad); }; inline Animation(const AnimationData& ad, Image& spriteSheet) { create(ad, spriteSheet); }; inline Animation& create(const AnimationData& ad) { return create(ad, InvalidImage); } - inline void update(void) { m_timer.update(); } inline i32 getCurrentFrame(void) const { return m_currentFrame; } Animation& create(const AnimationData& ad, Image& spriteSheet); + void update(void); void resetAnimation(void); inline void setFrameCount(i32 n) { m_animData.frameCount = n; } @@ -88,7 +88,7 @@ namespace ogfx private: inline static Image InvalidImage; AnimationData m_animData; - ostd::StepTimer m_timer; + ostd::Counter m_timer; Image* m_spriteSheet { nullptr }; i32 m_currentFrame { 0 }; bool m_back { false }; diff --git a/src/ostd/data/Types.hpp b/src/ostd/data/Types.hpp index 1669654..32c6c2b 100755 --- a/src/ostd/data/Types.hpp +++ b/src/ostd/data/Types.hpp @@ -44,6 +44,19 @@ using stdvec = std::vector; template using stdumap = std::unordered_map; +using i8Range = std::numeric_limits; +using i16Range = std::numeric_limits; +using i32Range = std::numeric_limits; +using i64Range = std::numeric_limits; + +using u8Range = std::numeric_limits; +using u16Range = std::numeric_limits; +using u32Range = std::numeric_limits; +using u64Range = std::numeric_limits; + +using f32Range = std::numeric_limits; +using f64Range = std::numeric_limits; + #define cast static_cast namespace ostd diff --git a/src/ostd/utils/Time.cpp b/src/ostd/utils/Time.cpp index e30fd7f..c806104 100644 --- a/src/ostd/utils/Time.cpp +++ b/src/ostd/utils/Time.cpp @@ -412,7 +412,7 @@ namespace ostd - u64 Timer::start(bool print, const String& name, eTimeUnits timeUnit, OutputHandlerBase* __destination) + u64 Counter::start(bool print, const String& name, eTimeUnits timeUnit, OutputHandlerBase* __destination) { m_timeUnit = timeUnit; m_started = true; @@ -459,7 +459,7 @@ namespace ostd return 0; } - u64 Timer::startCount(eTimeUnits timeUnit) + u64 Counter::startCount(eTimeUnits timeUnit) { m_timeUnit = timeUnit; m_started = true; @@ -483,7 +483,7 @@ namespace ostd return 0; } - u64 Timer::end(bool print) + u64 Counter::end(bool print) { if (!m_started) return 0; m_started = false; @@ -537,7 +537,7 @@ namespace ostd return diff; } - u64 Timer::endCount(bool stop) + u64 Counter::endCount(bool stop) { if (!m_started) return 0; m_started = !stop; @@ -562,7 +562,7 @@ namespace ostd return diff; } - u64 Timer::restart(eTimeUnits timeUnit) + u64 Counter::restart(eTimeUnits timeUnit) { if (!m_started) { @@ -574,7 +574,7 @@ namespace ostd return elapsed; } - u64 Timer::getEpoch(eTimeUnits timeUnit) + u64 Counter::getEpoch(eTimeUnits timeUnit) { switch (timeUnit) { diff --git a/src/ostd/utils/Time.hpp b/src/ostd/utils/Time.hpp index aa5f537..52b56dd 100644 --- a/src/ostd/utils/Time.hpp +++ b/src/ostd/utils/Time.hpp @@ -59,11 +59,11 @@ namespace ostd template - class _Counter + class _BasicCounter { public: - inline _Counter(void) { m_count = 200; m_current = 0; } - inline _Counter(T count, T step = 1) { m_count = count; m_current = 0; m_step = step; } + inline _BasicCounter(void) { m_count = 200; m_current = 0; } + inline _BasicCounter(T count, T step = 1) { m_count = count; m_current = 0; m_step = step; } inline void setCount(T count) { m_count = count; m_counting = false; m_current = 0; } inline T getCount(void) { return m_count; } inline T getCurrent(void) { return m_current; }; @@ -87,15 +87,15 @@ namespace ostd bool m_counting { false }; }; - typedef _Counter Counter; - typedef _Counter FloatCounter; - typedef _Counter DoubleCounter; + typedef _BasicCounter BasicCounter; + typedef _BasicCounter BasicFloatCounter; + typedef _BasicCounter BasicDoubleCounter; class OutputHandlerBase; - class Timer + class Counter { public: - inline Timer(void) { m_started = false; m_current = 0; m_timeUnit = eTimeUnits::Nanoseconds; m_dest = nullptr; } + inline Counter(void) { m_started = false; m_current = 0; m_timeUnit = eTimeUnits::Nanoseconds; m_dest = nullptr; } u64 start(bool print = true, const String& name = "", eTimeUnits timeUnit = eTimeUnits::Nanoseconds, OutputHandlerBase* __destination = nullptr); u64 startCount(eTimeUnits timeUnit = eTimeUnits::Nanoseconds); u64 end(bool print = true); @@ -172,7 +172,7 @@ namespace ostd u16 years; private: - Timer m_rtClock; + Counter m_rtClock; f32 m_timeOfDay; f32 m_totalSeconds; }; diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 0a67537..1ef8430 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -100,9 +100,11 @@ class TestWindow : public Window m_btn1.setCallback(Widget::eCallback::ActionPerformed, [&](const Event& event) -> void { std::cout << showOpenFileDialog(filters) << "\n"; }); + m_btn1.setTabIndex(1); m_check1.setText("Check this out!"); m_check1.setChecked(true); + m_check1.setTabIndex(2); m_check1.setStateChangedCallback([&](CheckBox& sender, bool state) -> void { m_panel1.setVisible(state); }); @@ -195,12 +197,13 @@ class TestWindow : public Window // }); m_list.collapseAll(); - for (i32 i = 0; i < 20; i++) + for (i32 i = 0; i < 17; i++) { auto icon = l_randomIcon(iconsAD); auto& btn = getToolBar().addButton("./icons.png"); btn.enableAnimated(); btn.setAnimationData(icon); + btn.setTabIndex(i + 100); } m_tabs.setSize(900, 700); @@ -210,9 +213,12 @@ class TestWindow : public Window auto& t3 = m_tabs.addTab("Long Tab Test"); t1.addWidget(m_check1, { 30, 30 }); - m_radioGroup.addButton(t1, "Radio this out!", { 30, 80 }); - m_radioGroup.addButton(t1, "Radio Opt. 2", { 30, 110 }); - m_radioGroup.addButton(t1, "Radio 3", { 30, 140 }); + auto& rb1 = m_radioGroup.addButton(t1, "Radio this out!", { 30, 80 }); + rb1.setTabIndex(3); + auto& rb2 = m_radioGroup.addButton(t1, "Radio Opt. 2", { 30, 110 }); + rb2.setTabIndex(4); + auto& rb3 = m_radioGroup.addButton(t1, "Radio 3", { 30, 140 }); + rb3.setTabIndex(5); m_radioGroup.setSelectionChangedCallback([&](RadioButton& previous, RadioButton& sender) -> void { std::cout << previous.getText() << "\n"; std::cout << sender.getText() << "\n\n"; @@ -492,7 +498,7 @@ i32 main(i32 argc, char** argv) ostd::Random::autoSeed(); ostd::initialize(); TestWindow window; - window.initialize(1200, 1100, "OmniaFramework - Test Window"); + window.initialize(1200, 900, "OmniaFramework - Test Window"); window.setClearColor({ 0, 0, 0 }); window.setPosition({ 50, 50 }); // window.setWindowState(ogfx::WindowCore::eWindowState::Maximized);