Added FocusManager

This commit is contained in:
OmniaX-Dev 2026-05-03 06:49:44 +02:00
parent 67a1dd605d
commit 203a305633
24 changed files with 373 additions and 164 deletions

View file

@ -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/MenuBar.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/ToolBar.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/Layout.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/FocusManager.cpp
# gui/widgets # gui/widgets
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp

View file

@ -25,6 +25,8 @@
***FIX: Limit FPS even when continuous events happen ***FIX: Limit FPS even when continuous events happen
***FIX: Float values in stylesheet needing to have decimal point to be read as float ***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 ***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 Implement cursors in Stylesheet
FIX: Window getting exponentially bigger when Desktop scale changes FIX: Window getting exponentially bigger when Desktop scale changes
FIX: Refreshing scroll when content extent 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 Update theme for visible TreeView lines
Add ToolBar::addButton overload that takes a ogfx::Icon wrapper Add ToolBar::addButton overload that takes a ogfx::Icon wrapper
Redesign theme overrides to be more robust Redesign theme overrides to be more robust
FIX: Animated icons not working in TreeView
Add modifiers to Event.keyboard path

View file

@ -160,14 +160,14 @@ namespace ogfx
Instance m_data; Instance m_data;
f32 m_entryHeight { 0 }; f32 m_entryHeight { 0 };
Vec2 m_mousePos { 0, 0 }; Vec2 m_mousePos { 0, 0 };
ostd::Timer m_animClock; ostd::Counter m_animClock;
stdvec<Panel> m_panels; stdvec<Panel> m_panels;
i32 m_pendingOpenPanelDepth { -1 }; i32 m_pendingOpenPanelDepth { -1 };
i32 m_pendingOpenEntryIndex { -1 }; i32 m_pendingOpenEntryIndex { -1 };
ostd::Timer m_hoverOpenTimer; ostd::Counter m_hoverOpenTimer;
ostd::Timer m_hoverCloseTimer; ostd::Counter m_hoverCloseTimer;
bool m_hoverOpenTimerActive { false }; bool m_hoverOpenTimerActive { false };
bool m_hoverCloseTimerActive { false }; bool m_hoverCloseTimerActive { false };

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#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();
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
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<Widget*, i32> m_tabIndexMap;
};
}
}

View file

@ -1,21 +1,21 @@
/* /*
OmniaFramework - A collection of useful functionality OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev 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 OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful, OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>. along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/ */
#pragma once #pragma once
@ -60,35 +60,35 @@ namespace ogfx
u8 extraPaddingLeft { 0 }; u8 extraPaddingLeft { 0 };
bool cursorBlink { true }; bool cursorBlink { true };
ostd::Counter cursorBlinkCounter { 30 }; ostd::BasicCounter cursorBlinkCounter { 30 };
}; };
public: struct tDefaultThemes public: struct tDefaultThemes
{ {
inline static const Theme DebugTheme { inline static const Theme DebugTheme {
{ 220, 220, 255 }, //Text Color { 220, 220, 255 }, //Text Color
{ 10, 20, 120 }, //Border Color { 10, 20, 120 }, //Border Color
{ 0, 0, 22 }, //Background Color { 0, 0, 22 }, //Background Color
{ 220, 10, 10 }, //Cursor Color { 220, 10, 10 }, //Cursor Color
2, //Cursor Width 2, //Cursor Width
0, //Extra Padding Top 0, //Extra Padding Top
0, //Extra Padding Left 0, //Extra Padding Left
true, //Cursor Blink true, //Cursor Blink
{ 30 } //Cursor Blink Timer { 30 } //Cursor Blink Timer
}; };
inline static const Theme DefaultTheme { inline static const Theme DefaultTheme {
{ 120, 120, 180 }, //Text Color { 120, 120, 180 }, //Text Color
{ 10, 20, 120 }, //Border Color { 10, 20, 120 }, //Border Color
{ 0, 2, 10 }, //Background Color { 0, 2, 10 }, //Background Color
{ 20, 100, 255 }, //Cursor Color { 20, 100, 255 }, //Cursor Color
2, //Cursor Width 2, //Cursor Width
0, //Extra Padding Top 0, //Extra Padding Top
0, //Extra Padding Left 0, //Extra Padding Left
true, //Cursor Blink true, //Cursor Blink
{ 30 } //Cursor Blink Timer { 30 } //Cursor Blink Timer
}; };
}; };
public: enum eActionEventType { None = 0, Enter, Tab }; public: enum eActionEventType { None = 0, Enter, Tab };
@ -156,7 +156,7 @@ namespace ogfx
u16 m_cursorPosition { 0 }; u16 m_cursorPosition { 0 };
bool m_cursorState { true }; bool m_cursorState { true };
ostd::Counter m_keyRepeatCounter { 1 }; ostd::BasicCounter m_keyRepeatCounter { 1 };
char m_lastChar { 0 }; char m_lastChar { 0 };
i32 m_lastKeyCode { 0 }; i32 m_lastKeyCode { 0 };

View file

@ -48,6 +48,7 @@ namespace ogfx
setPosition(0, win.getMenuBar().getHeight()); setPosition(0, win.getMenuBar().getHeight());
else else
setPosition(0, 0); setPosition(0, 0);
return *this; return *this;
} }

View file

@ -19,9 +19,9 @@
*/ */
#include "WidgetManager.hpp" #include "WidgetManager.hpp"
#include "Window.hpp"
#include "widgets/Widget.hpp" #include "widgets/Widget.hpp"
#include "../render/BasicRenderer.hpp" #include "../render/BasicRenderer.hpp"
#include "../utils/Keycodes.hpp"
#include "../../ostd/io/Memory.hpp" #include "../../ostd/io/Memory.hpp"
namespace ogfx namespace ogfx
@ -39,41 +39,11 @@ namespace ogfx
return found && widget.isValid(); 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) bool WidgetManager::addWidget(Widget& widget)
{ {
if (hasWidget(widget)) return false; if (hasWidget(widget)) return false;
widget.m_parent = &m_owner; widget.m_parent = &m_owner;
m_widgetList.push_back(&widget); m_widgetList.push_back(&widget);
if (widget.m_topMost)
return true;
std::ranges::sort(m_widgetList, {}, [](Widget* w) {
return w->m_zIndex;
});
widget.refresh(); widget.refresh();
return true; return true;
} }
@ -84,32 +54,6 @@ namespace ogfx
return true; 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<i32>::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) void WidgetManager::draw(ogfx::BasicRenderer2D& gfx)
{ {
for (auto& w : m_widgetList) for (auto& w : m_widgetList)
@ -157,6 +101,7 @@ namespace ogfx
if (!w->contains(event.mouse->position_x, event.mouse->position_y, true)) if (!w->contains(event.mouse->position_x, event.mouse->position_y, true))
continue; continue;
event.mouse->mousePressedOnWidget = w; event.mouse->mousePressedOnWidget = w;
cast<Window&>(m_window).getFocusManager().requestFocus(*w);
w->__onMousePressed(event); w->__onMousePressed(event);
m_mousePressedOnWidget = w; m_mousePressedOnWidget = w;
if (event.isHandled() || w->m_stopEvents) if (event.isHandled() || w->m_stopEvents)
@ -182,7 +127,6 @@ namespace ogfx
event.mouse->mousePressedOnWidget = m_mousePressedOnWidget; event.mouse->mousePressedOnWidget = m_mousePressedOnWidget;
w->__onMouseReleased(event); w->__onMouseReleased(event);
processDragAndDrop(w, event); processDragAndDrop(w, event);
// requestFocus(*w);
if (w->isMouseInside() && w->m_stopEvents) if (w->isMouseInside() && w->m_stopEvents)
break; break;
} }
@ -275,22 +219,24 @@ namespace ogfx
void WidgetManager::onKeyPressed(const Event& event) void WidgetManager::onKeyPressed(const Event& event)
{ {
if (!m_focused || !m_focused->isVisible()) return; auto focused = cast<Window&>(m_window).getFocusManager().getFocused();
m_focused->__onKeyPressed(event); if (!focused || !focused->isVisible()) return;
focused->__onKeyPressed(event);
} }
void WidgetManager::onKeyReleased(const Event& event) void WidgetManager::onKeyReleased(const Event& event)
{ {
if (!m_focused || !m_focused->isVisible()) return; auto focus = cast<Window&>(m_window).getFocusManager();
m_focused->__onKeyReleased(event); auto focused = focus.getFocused();
if (m_tabNavigationEnabled && event.keyboard->keyCode == KeyCode::Tab) if (!focused || !focused->isVisible()) return;
focusNext(); focused->__onKeyReleased(event);
} }
void WidgetManager::onTextEntered(const Event& event) void WidgetManager::onTextEntered(const Event& event)
{ {
if (!m_focused || !m_focused->isVisible()) return; auto focused = cast<Window&>(m_window).getFocusManager().getFocused();
m_focused->__onTextEntered(event); if (!focused || !focused->isVisible()) return;
focused->__onTextEntered(event);
} }
void WidgetManager::onWindowClosed(const Event& event) void WidgetManager::onWindowClosed(const Event& event)

View file

@ -39,10 +39,8 @@ namespace ogfx
public: public:
WidgetManager(WindowCore& window, Widget& owner); WidgetManager(WindowCore& window, Widget& owner);
bool hasWidget(Widget& widget); bool hasWidget(Widget& widget);
bool requestFocus(Widget& widget);
bool addWidget(Widget& widget); bool addWidget(Widget& widget);
bool removeWidget(Widget& widget); bool removeWidget(Widget& widget);
Widget* focusNext(void);
void draw(ogfx::BasicRenderer2D& gfx); void draw(ogfx::BasicRenderer2D& gfx);
void update(void); void update(void);
@ -71,9 +69,9 @@ namespace ogfx
WindowCore& m_window; WindowCore& m_window;
Widget& m_owner; Widget& m_owner;
stdvec<Widget*> m_widgetList; stdvec<Widget*> m_widgetList;
Widget* m_focused { nullptr };
bool m_tabNavigationEnabled { true };
Widget* m_mousePressedOnWidget { nullptr }; Widget* m_mousePressedOnWidget { nullptr };
friend class FocusManager;
}; };
} }
} }

View file

@ -1008,6 +1008,7 @@ namespace ogfx
{ {
evt.keyboard = &(ogfx::KeyEventData&)signal.userData; evt.keyboard = &(ogfx::KeyEventData&)signal.userData;
evt.__original_signal_id = ostd::BuiltinSignals::KeyReleased; evt.__original_signal_id = ostd::BuiltinSignals::KeyReleased;
m_focusManager.onKeyReleased(evt);
m_toolbar.__onKeyReleased(evt); m_toolbar.__onKeyReleased(evt);
m_statusbar.__onKeyReleased(evt); m_statusbar.__onKeyReleased(evt);
m_rootWidget.__onKeyReleased(evt); m_rootWidget.__onKeyReleased(evt);

View file

@ -32,6 +32,7 @@
#include <ogfx/gui/ContextMenu.hpp> #include <ogfx/gui/ContextMenu.hpp>
#include <ogfx/gui/MenuBar.hpp> #include <ogfx/gui/MenuBar.hpp>
#include <ogfx/gui/ToolBar.hpp> #include <ogfx/gui/ToolBar.hpp>
#include <ogfx/gui/FocusManager.hpp>
namespace ogfx namespace ogfx
{ {
@ -181,7 +182,7 @@ namespace ogfx
String m_title { "" }; String m_title { "" };
i32 m_blockingEventsDelay { 33 }; i32 m_blockingEventsDelay { 33 };
Vec2 m_mousePosition { 0, 0 }; Vec2 m_mousePosition { 0, 0 };
ostd::Timer m_tooltipTimer; ostd::Counter m_tooltipTimer;
String m_tooltipText { "" }; String m_tooltipText { "" };
bool m_running { false }; bool m_running { false };
@ -259,7 +260,7 @@ namespace ogfx
i32 m_fps { 0 }; i32 m_fps { 0 };
ostd::StepTimer m_fixedUpdateTImer; ostd::StepTimer m_fixedUpdateTImer;
ostd::StepTimer m_fpsUpdateTimer; ostd::StepTimer m_fpsUpdateTimer;
ostd::Timer m_fpsUpdateClock; ostd::Counter m_fpsUpdateClock;
u64 m_frameTimeAcc { 0 }; u64 m_frameTimeAcc { 0 };
i32 m_frameCount { 0 }; i32 m_frameCount { 0 };
}; };
@ -284,6 +285,7 @@ namespace ogfx
inline void showMenuBar(bool show = true) { m_menubar.show(show); } inline void showMenuBar(bool show = true) { m_menubar.show(show); }
inline void showToolBar(bool show = true) { m_toolbar.setVisible(show); } inline void showToolBar(bool show = true) { m_toolbar.setVisible(show); }
inline void showStatusBar(bool show = true) { m_statusbar.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 onInitialize(void) { }
inline virtual void onDestroy(void) { } inline virtual void onDestroy(void) { }
@ -308,6 +310,7 @@ namespace ogfx
protected: protected:
RootWidget m_rootWidget { *this }; RootWidget m_rootWidget { *this };
FocusManager m_focusManager { *this };
ostd::StepTimer m_fixedUpdateTimer; ostd::StepTimer m_fixedUpdateTimer;
ostd::StepTimer::TimePoint m_lastFrameTime; ostd::StepTimer::TimePoint m_lastFrameTime;
ostd::StepTimer m_mainLoopTimer; ostd::StepTimer m_mainLoopTimer;
@ -317,6 +320,7 @@ namespace ogfx
ToolBar m_statusbar { *this, true }; ToolBar m_statusbar { *this, true };
friend class RootWidget; friend class RootWidget;
friend class FocusManager;
}; };
} }
} }

View file

@ -38,6 +38,7 @@ namespace ogfx
setPadding({ 5, 5, 5, 5 }); setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::Label"); setTypeName("ogfx::gui::Label");
disableChildren(); disableChildren();
disableFocus();
enableBackground(false); enableBackground(false);
setStylesheetCategoryName("label"); setStylesheetCategoryName("label");
validate(); validate();
@ -127,6 +128,7 @@ namespace ogfx
setPadding({ 0, 0, 0, 0 }); setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::ImageLabel"); setTypeName("ogfx::gui::ImageLabel");
disableChildren(); disableChildren();
disableFocus();
disableBackground(); disableBackground();
disableBorder(); disableBorder();
setStylesheetCategoryName("image"); setStylesheetCategoryName("image");

View file

@ -30,6 +30,7 @@ namespace ogfx
setPadding({ 0, 0, 0, 0 }); setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::ProgressBar"); setTypeName("ogfx::gui::ProgressBar");
disableChildren(); disableChildren();
disableFocus();
enableBackground(); enableBackground();
enableBorder(); enableBorder();
setStylesheetCategoryName("progressbar"); setStylesheetCategoryName("progressbar");

View file

@ -29,6 +29,7 @@ namespace ogfx
RootWidget::RootWidget(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) RootWidget::RootWidget(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window)
{ {
setRootChild(); setRootChild();
disableFocus();
setSize(cast<f32>(window.getWindowWidth()), cast<f32>(window.getWindowHeight())); setSize(cast<f32>(window.getWindowWidth()), cast<f32>(window.getWindowHeight()));
setStylesheetCategoryName("window"); setStylesheetCategoryName("window");
setTypeName("ogfx::gui::RootWidget"); setTypeName("ogfx::gui::RootWidget");

View file

@ -31,6 +31,7 @@ namespace ogfx
setMargin({ 0, 0, 0, 0 }); setMargin({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::Label"); setTypeName("ogfx::gui::Label");
disableChildren(); disableChildren();
disableFocus();
enableBackground(true); enableBackground(true);
enableBorder(false); enableBorder(false);
enableTopMost(true); enableTopMost(true);
@ -154,6 +155,7 @@ namespace ogfx
setMargin({ 0, 0, 0, 0 }); setMargin({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::Label"); setTypeName("ogfx::gui::Label");
disableChildren(); disableChildren();
disableFocus();
enableBackground(true); enableBackground(true);
enableBorder(false); enableBorder(false);
enableTopMost(true); enableTopMost(true);

View file

@ -277,11 +277,11 @@ namespace ogfx
auto& item = *m_list[i]; auto& item = *m_list[i];
if (!item.isValid()) continue; if (!item.isValid()) continue;
if (!item.isVisible()) continue; if (!item.isVisible()) continue;
if (!item.isIconEnabled()) continue; f32 itemH = item.getDimensions().y;
if (y > visibleEnd) break; if (y > visibleEnd) break;
if (item.getIcon().getFrameCount() != 1) if (item.isIconEnabled())
std::cout << item.getIcon().getCurrentFrame() << "\n"; item.getIcon().update();
item.getIcon().update(); y += itemH;
} }
} }

View file

@ -36,6 +36,15 @@ namespace ogfx
m_window = &window; m_window = &window;
} }
Widget::~Widget(void)
{
if (m_window && m_tabIndex >= 0)
cast<Window&>(*m_window).getFocusManager().unregisterTabIndex(*this);
auto& fm = cast<Window&>(*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) bool Widget::addWidget(Widget& child, const Vec2& position, bool __skip_callback)
{ {
if (!m_allowChildren) if (!m_allowChildren)
@ -229,6 +238,16 @@ namespace ogfx
m_visible = v; m_visible = v;
} }
bool Widget::setTabIndex(i32 index)
{
if (cast<Window&>(getWindow()).getFocusManager().registerTabIndex(*this, index))
{
m_tabIndex = index;
return true;
}
return false;
}
void Widget::setLayout(std::unique_ptr<Layout> layout) void Widget::setLayout(std::unique_ptr<Layout> layout)
{ {
m_layout = std::move(layout); m_layout = std::move(layout);
@ -297,6 +316,9 @@ namespace ogfx
if (m_showBorder) if (m_showBorder)
gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth);
afterDraw(gfx); afterDraw(gfx);
if (isFocused())
gfx.drawRoundRect({ getGlobalPosition(), getSize() }, Colors::Red, m_borderRadius, 3);
} }
void Widget::__update(void) void Widget::__update(void)
@ -416,41 +438,26 @@ namespace ogfx
void Widget::__onKeyPressed(const Event& event) void Widget::__onKeyPressed(const Event& event)
{ {
if (event.isHandled() || !isKeyPressedEventEnabled()) return; if (event.isHandled() || !isKeyPressedEventEnabled()) return;
if (hasChildren()) if (callback_onKeyPressed)
m_widgets.onKeyPressed(event); callback_onKeyPressed(event);
if (!event.isHandled()) onKeyPressed(event);
{
if (callback_onKeyPressed)
callback_onKeyPressed(event);
onKeyPressed(event);
}
} }
void Widget::__onKeyReleased(const Event& event) void Widget::__onKeyReleased(const Event& event)
{ {
if (event.isHandled() || !isKeyReleasedEventEnabled()) return; if (event.isHandled() || !isKeyReleasedEventEnabled()) return;
m_pressedButton = ogfx::MouseEventData::eButton::None; m_pressedButton = ogfx::MouseEventData::eButton::None;
if (hasChildren()) if (callback_onKeyReleased)
m_widgets.onKeyReleased(event); callback_onKeyReleased(event);
if (!event.isHandled()) onKeyReleased(event);
{
if (callback_onKeyReleased)
callback_onKeyReleased(event);
onKeyReleased(event);
}
} }
void Widget::__onTextEntered(const Event& event) void Widget::__onTextEntered(const Event& event)
{ {
if (event.isHandled() || !isTextEnteredEventEnabled()) return; if (event.isHandled() || !isTextEnteredEventEnabled()) return;
if (hasChildren()) if (callback_onTextEntered)
m_widgets.onTextEntered(event); callback_onTextEntered(event);
if (!event.isHandled()) onTextEntered(event);
{
if (callback_onTextEntered)
callback_onTextEntered(event);
onTextEntered(event);
}
} }
void Widget::__onWindowClosed(const Event& event) void Widget::__onWindowClosed(const Event& event)

View file

@ -66,6 +66,11 @@ namespace ogfx
public: public:
// ================================== MAIN ================================= // ================================== MAIN =================================
Widget(const Rectangle& bounds, WindowCore& window); 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 addWidget(Widget& child, const Vec2& position = { 0, 0 }, bool __skip_callback = false);
bool removeWidget(Widget& child); bool removeWidget(Widget& child);
void enable(bool enable = true); void enable(bool enable = true);
@ -76,6 +81,7 @@ namespace ogfx
bool addThemeID(const String& id); bool addThemeID(const String& id);
bool removeThemeID(const String& id); bool removeThemeID(const String& id);
void setVisible(bool v); void setVisible(bool v);
bool setTabIndex(i32 index);
virtual void setCallback(eCallback type, EventCallback callback); virtual void setCallback(eCallback type, EventCallback callback);
using Rectangle::contains; bool contains(Vec2 p, bool includeBounds = false) const override; using Rectangle::contains; bool contains(Vec2 p, bool includeBounds = false) const override;
template<typename T> template<typename T>
@ -154,10 +160,10 @@ namespace ogfx
inline static void setDragAndDropData(ostd::BaseObject& data) { s_dragAndDropData = &data; s_hasDragAndDropData = true; } inline static void setDragAndDropData(ostd::BaseObject& data) { s_dragAndDropData = &data; s_hasDragAndDropData = true; }
inline static void clearDragAndDropData(void) { s_dragAndDropData = nullptr; } inline static void clearDragAndDropData(void) { s_dragAndDropData = nullptr; }
inline static ostd::BaseObject* getDragAndDropData(void) { return s_dragAndDropData; } 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 WindowCore& getWindow(void) { return *m_window; }
inline Widget* getParent(void) { return m_parent; } inline Widget* getParent(void) { return m_parent; }
inline const Widget* getParent(void) const { 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 ogfx::MouseEventData::eButton getPressedMouseButton(void) const { return m_pressedButton; }
inline const stdvec<String>& getThemeIDList(void) const { return m_themeIDList; } inline const stdvec<String>& getThemeIDList(void) const { return m_themeIDList; }
inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; } 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(ostd::ColorGradient, BackgroundGradient, m_backgroundGradient);
OSTD_PARAM_GETSET(Rectangle, Padding, m_padding); OSTD_PARAM_GETSET(Rectangle, Padding, m_padding);
OSTD_PARAM_GETSET(Rectangle, Margin, m_margin); OSTD_PARAM_GETSET(Rectangle, Margin, m_margin);
OSTD_PARAM_GETSET(i32, TabIndex, m_tabIndex);
OSTD_PARAM_GETSET(Vec2, ContentOffset, m_contentOffset); OSTD_PARAM_GETSET(Vec2, ContentOffset, m_contentOffset);
OSTD_PARAM_GETSET(String, TooltipText, m_tooltipText); OSTD_PARAM_GETSET(String, TooltipText, m_tooltipText);
@ -236,7 +241,6 @@ namespace ogfx
bool m_rootChild { false }; bool m_rootChild { false };
bool m_focused { false }; bool m_focused { false };
i32 m_tabIndex { -1 }; i32 m_tabIndex { -1 };
i32 m_zIndex { -1 };
String m_tooltipText { "" }; String m_tooltipText { "" };
MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None }; MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None };
// ==================== // ====================
@ -313,6 +317,7 @@ namespace ogfx
static ostd::BaseObject* s_dragAndDropData; static ostd::BaseObject* s_dragAndDropData;
static bool s_hasDragAndDropData; static bool s_hasDragAndDropData;
friend class WidgetManager; friend class WidgetManager;
friend class FocusManager;
// ==================== // ====================

View file

@ -27,13 +27,21 @@ namespace ogfx
{ {
m_animData = ad; m_animData = ad;
m_spriteSheet = &spriteSheet; m_spriteSheet = &spriteSheet;
m_timer.create(m_animData.fps, [&](f64 dt) -> void { m_timer.startCount(ostd::eTimeUnits::Milliseconds);
update_animation();
});
update_animation(); update_animation();
return *this; return *this;
} }
void Animation::update(void)
{
f64 dtms = (1.0 / cast<f64>(m_animData.fps)) * 1000.0;
if (m_timer.read() >= dtms)
{
update_animation();
m_timer.restart(ostd::eTimeUnits::Milliseconds);
}
}
void Animation::resetAnimation(void) void Animation::resetAnimation(void)
{ {
m_currentFrame = 0; m_currentFrame = 0;

View file

@ -35,9 +35,9 @@ namespace ogfx
inline Animation(const AnimationData& ad) { create(ad); }; inline Animation(const AnimationData& ad) { create(ad); };
inline Animation(const AnimationData& ad, Image& spriteSheet) { create(ad, spriteSheet); }; inline Animation(const AnimationData& ad, Image& spriteSheet) { create(ad, spriteSheet); };
inline Animation& create(const AnimationData& ad) { return create(ad, InvalidImage); } 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; } inline i32 getCurrentFrame(void) const { return m_currentFrame; }
Animation& create(const AnimationData& ad, Image& spriteSheet); Animation& create(const AnimationData& ad, Image& spriteSheet);
void update(void);
void resetAnimation(void); void resetAnimation(void);
inline void setFrameCount(i32 n) { m_animData.frameCount = n; } inline void setFrameCount(i32 n) { m_animData.frameCount = n; }
@ -88,7 +88,7 @@ namespace ogfx
private: private:
inline static Image InvalidImage; inline static Image InvalidImage;
AnimationData m_animData; AnimationData m_animData;
ostd::StepTimer m_timer; ostd::Counter m_timer;
Image* m_spriteSheet { nullptr }; Image* m_spriteSheet { nullptr };
i32 m_currentFrame { 0 }; i32 m_currentFrame { 0 };
bool m_back { false }; bool m_back { false };

View file

@ -44,6 +44,19 @@ using stdvec = std::vector<T>;
template<typename Key, typename Value> template<typename Key, typename Value>
using stdumap = std::unordered_map<Key, Value>; using stdumap = std::unordered_map<Key, Value>;
using i8Range = std::numeric_limits<i8>;
using i16Range = std::numeric_limits<i16>;
using i32Range = std::numeric_limits<i32>;
using i64Range = std::numeric_limits<i64>;
using u8Range = std::numeric_limits<u8>;
using u16Range = std::numeric_limits<u16>;
using u32Range = std::numeric_limits<u32>;
using u64Range = std::numeric_limits<u64>;
using f32Range = std::numeric_limits<f32>;
using f64Range = std::numeric_limits<f64>;
#define cast static_cast #define cast static_cast
namespace ostd namespace ostd

View file

@ -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_timeUnit = timeUnit;
m_started = true; m_started = true;
@ -459,7 +459,7 @@ namespace ostd
return 0; return 0;
} }
u64 Timer::startCount(eTimeUnits timeUnit) u64 Counter::startCount(eTimeUnits timeUnit)
{ {
m_timeUnit = timeUnit; m_timeUnit = timeUnit;
m_started = true; m_started = true;
@ -483,7 +483,7 @@ namespace ostd
return 0; return 0;
} }
u64 Timer::end(bool print) u64 Counter::end(bool print)
{ {
if (!m_started) return 0; if (!m_started) return 0;
m_started = false; m_started = false;
@ -537,7 +537,7 @@ namespace ostd
return diff; return diff;
} }
u64 Timer::endCount(bool stop) u64 Counter::endCount(bool stop)
{ {
if (!m_started) return 0; if (!m_started) return 0;
m_started = !stop; m_started = !stop;
@ -562,7 +562,7 @@ namespace ostd
return diff; return diff;
} }
u64 Timer::restart(eTimeUnits timeUnit) u64 Counter::restart(eTimeUnits timeUnit)
{ {
if (!m_started) if (!m_started)
{ {
@ -574,7 +574,7 @@ namespace ostd
return elapsed; return elapsed;
} }
u64 Timer::getEpoch(eTimeUnits timeUnit) u64 Counter::getEpoch(eTimeUnits timeUnit)
{ {
switch (timeUnit) switch (timeUnit)
{ {

View file

@ -59,11 +59,11 @@ namespace ostd
template <class T> template <class T>
class _Counter class _BasicCounter
{ {
public: public:
inline _Counter(void) { m_count = 200; m_current = 0; } inline _BasicCounter(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(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 void setCount(T count) { m_count = count; m_counting = false; m_current = 0; }
inline T getCount(void) { return m_count; } inline T getCount(void) { return m_count; }
inline T getCurrent(void) { return m_current; }; inline T getCurrent(void) { return m_current; };
@ -87,15 +87,15 @@ namespace ostd
bool m_counting { false }; bool m_counting { false };
}; };
typedef _Counter<u64> Counter; typedef _BasicCounter<u64> BasicCounter;
typedef _Counter<f32> FloatCounter; typedef _BasicCounter<f32> BasicFloatCounter;
typedef _Counter<f64> DoubleCounter; typedef _BasicCounter<f64> BasicDoubleCounter;
class OutputHandlerBase; class OutputHandlerBase;
class Timer class Counter
{ {
public: 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 start(bool print = true, const String& name = "", eTimeUnits timeUnit = eTimeUnits::Nanoseconds, OutputHandlerBase* __destination = nullptr);
u64 startCount(eTimeUnits timeUnit = eTimeUnits::Nanoseconds); u64 startCount(eTimeUnits timeUnit = eTimeUnits::Nanoseconds);
u64 end(bool print = true); u64 end(bool print = true);
@ -172,7 +172,7 @@ namespace ostd
u16 years; u16 years;
private: private:
Timer m_rtClock; Counter m_rtClock;
f32 m_timeOfDay; f32 m_timeOfDay;
f32 m_totalSeconds; f32 m_totalSeconds;
}; };

View file

@ -100,9 +100,11 @@ class TestWindow : public Window
m_btn1.setCallback(Widget::eCallback::ActionPerformed, [&](const Event& event) -> void { m_btn1.setCallback(Widget::eCallback::ActionPerformed, [&](const Event& event) -> void {
std::cout << showOpenFileDialog(filters) << "\n"; std::cout << showOpenFileDialog(filters) << "\n";
}); });
m_btn1.setTabIndex(1);
m_check1.setText("Check this out!"); m_check1.setText("Check this out!");
m_check1.setChecked(true); m_check1.setChecked(true);
m_check1.setTabIndex(2);
m_check1.setStateChangedCallback([&](CheckBox& sender, bool state) -> void { m_check1.setStateChangedCallback([&](CheckBox& sender, bool state) -> void {
m_panel1.setVisible(state); m_panel1.setVisible(state);
}); });
@ -195,12 +197,13 @@ class TestWindow : public Window
// }); // });
m_list.collapseAll(); m_list.collapseAll();
for (i32 i = 0; i < 20; i++) for (i32 i = 0; i < 17; i++)
{ {
auto icon = l_randomIcon(iconsAD); auto icon = l_randomIcon(iconsAD);
auto& btn = getToolBar().addButton("./icons.png"); auto& btn = getToolBar().addButton("./icons.png");
btn.enableAnimated(); btn.enableAnimated();
btn.setAnimationData(icon); btn.setAnimationData(icon);
btn.setTabIndex(i + 100);
} }
m_tabs.setSize(900, 700); m_tabs.setSize(900, 700);
@ -210,9 +213,12 @@ class TestWindow : public Window
auto& t3 = m_tabs.addTab("Long Tab Test"); auto& t3 = m_tabs.addTab("Long Tab Test");
t1.addWidget(m_check1, { 30, 30 }); t1.addWidget(m_check1, { 30, 30 });
m_radioGroup.addButton(t1, "Radio this out!", { 30, 80 }); auto& rb1 = m_radioGroup.addButton(t1, "Radio this out!", { 30, 80 });
m_radioGroup.addButton(t1, "Radio Opt. 2", { 30, 110 }); rb1.setTabIndex(3);
m_radioGroup.addButton(t1, "Radio 3", { 30, 140 }); 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 { m_radioGroup.setSelectionChangedCallback([&](RadioButton& previous, RadioButton& sender) -> void {
std::cout << previous.getText() << "\n"; std::cout << previous.getText() << "\n";
std::cout << sender.getText() << "\n\n"; std::cout << sender.getText() << "\n\n";
@ -492,7 +498,7 @@ i32 main(i32 argc, char** argv)
ostd::Random::autoSeed(); ostd::Random::autoSeed();
ostd::initialize(); ostd::initialize();
TestWindow window; TestWindow window;
window.initialize(1200, 1100, "OmniaFramework - Test Window"); window.initialize(1200, 900, "OmniaFramework - Test Window");
window.setClearColor({ 0, 0, 0 }); window.setClearColor({ 0, 0, 0 });
window.setPosition({ 50, 50 }); window.setPosition({ 50, 50 });
// window.setWindowState(ogfx::WindowCore::eWindowState::Maximized); // window.setWindowState(ogfx::WindowCore::eWindowState::Maximized);