diff --git a/CMakeLists.txt b/CMakeLists.txt
index b69ae79..a33b3b3 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -87,6 +87,7 @@ list(APPEND OGFX_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/ContextMenu.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/Layout.cpp
# gui/widgets
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp
diff --git a/other/TODO.txt b/other/TODO.txt
index 2037977..96dadfd 100644
--- a/other/TODO.txt
+++ b/other/TODO.txt
@@ -23,7 +23,6 @@
***Add Dark Mode Default theme
***FIX: Optimize ListView
***FIX: Limit FPS even when continuous events happen
-Add theme caching
Implement cursors in Stylesheet
FIX: Window getting exponentially bigger when Desktop scale changes
FIX: Refreshing scroll when content extent changes
@@ -47,6 +46,8 @@ Add Hex Editor widget
Add Message Boxes
FIX: Widget Content extents increasing negatively not showing scrollbars
FIX: Button state getting hung after showing an openFileDialog
+FIX: ListView last item is covered by scrollbar
+FIX: ListView last item not clickable even when no horizontal scrollbar present
@@ -73,13 +74,13 @@ Implement following widgets:
***MenuBar
***Toolbar
***StatusBar
+ ***Layouts
+ ***Vertical
+ ***Horizontal
+ ***Grid
+ ***Fill
ComboBox
TreeView
- Layouts
- Vertical
- Horizontal
- Grid
- Fill
Text Input
Text Area
Numeric Field
diff --git a/src/ogfx/gui/Layout.cpp b/src/ogfx/gui/Layout.cpp
new file mode 100644
index 0000000..e1f0c32
--- /dev/null
+++ b/src/ogfx/gui/Layout.cpp
@@ -0,0 +1,332 @@
+/*
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
+
+ This file is part of OmniaFramework.
+
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OmniaFramework is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
+*/
+
+#include "Layout.hpp"
+#include "widgets/Widget.hpp"
+
+#include
+
+namespace ogfx
+{
+ namespace gui
+ {
+ // ============================================================================
+ // Layout — shared helpers
+ // ============================================================================
+
+ Vec2 Layout::resolvePreferred(const Widget& child)
+ {
+ const auto& h = child.layoutHint();
+ Vec2 pref;
+ pref.x = (h.preferred.x < 0) ? child.getw() : h.preferred.x;
+ pref.y = (h.preferred.y < 0) ? child.geth() : h.preferred.y;
+ return pref;
+ }
+
+ f32 Layout::applyMinMax(f32 value, f32 minV, f32 maxV)
+ {
+ if (value < minV) value = minV;
+ if (maxV >= 0 && value > maxV) value = maxV;
+ return value;
+ }
+
+ f32 Layout::alignOffset(eAlign a, f32 available, f32 size)
+ {
+ switch (a)
+ {
+ case eAlign::Center: return (available - size) * 0.5f;
+ case eAlign::End: return available - size;
+ case eAlign::Start:
+ case eAlign::Stretch: // when stretched the caller already used `available`
+ default: return 0.0f;
+ }
+ }
+
+ // Iterate the children we actually care about: visible, valid, not ignored,
+ // not "ignore-scroll" (scrollbars and friends), and not manually drawn.
+ // This is a small inline helper to avoid repeating the predicate across
+ // every layout subclass below.
+ namespace
+ {
+ template
+ void for_each_laid_out(const Widget& parent, F&& fn)
+ {
+ auto& mgr = const_cast(parent).getChildren();
+ for (auto* c : mgr.getWidgets())
+ {
+ if (!c) continue;
+ if (c->isInvalid()) continue;
+ if (!c->isVisible()) continue;
+ if (c->isIgnoreScrollEnabled()) continue; // scrollbars etc.
+ if (c->layoutHint().ignored) continue;
+ fn(*c);
+ }
+ }
+ }
+
+
+ // ============================================================================
+ // FillLayout
+ // ============================================================================
+
+ void FillLayout::arrange(Widget& parent)
+ {
+ // IMPORTANT: child positions are LOCAL to the parent's content frame.
+ // getGlobalPosition() automatically adds the parent's padding and
+ // contentOffset, so we must NOT include them here or they'd be
+ // applied twice. We only use the SIZE from getContentBounds().
+ const Vec2 size = parent.getContentBounds().getSize();
+
+ for_each_laid_out(parent, [&](Widget& c) {
+ c.setPosition({ 0, 0 });
+ c.setSize(size);
+ c.onBoundsChanged(size);
+ });
+ }
+
+ Vec2 FillLayout::measure(const Widget& parent) const
+ {
+ // Fill wants whatever the parent gives it; report the largest child
+ // preferred size as a hint for nested measure passes.
+ Vec2 result { 0, 0 };
+ for_each_laid_out(parent, [&](Widget& c) {
+ Vec2 pref = resolvePreferred(c);
+ result.x = std::max(result.x, pref.x);
+ result.y = std::max(result.y, pref.y);
+ });
+ return result;
+ }
+
+
+ // ============================================================================
+ // BoxLayout
+ // ============================================================================
+
+ void BoxLayout::arrange(Widget& parent)
+ {
+ const bool horiz = (m_orientation == Orientation::Horizontal);
+ // IMPORTANT: child positions are LOCAL to the parent's content frame.
+ // getGlobalPosition() auto-adds parent's padding and contentOffset,
+ // so we use only the SIZE from getContentBounds() and start child
+ // positions at (0, 0). Adding area.x/area.y here would double-count.
+ const Vec2 areaSize = parent.getContentBounds().getSize();
+ const f32 totalMain = horiz ? areaSize.x : areaSize.y;
+ const f32 totalCross = horiz ? areaSize.y : areaSize.x;
+
+ // --- Pass 1: gather children, classify as "fixed" vs "stretching",
+ // and accumulate fixed-size + total stretch weight.
+ struct Slot { Widget* w; f32 main; f32 cross; bool stretches; f32 stretch;
+ f32 mainMin, mainMax; f32 crossMin, crossMax; };
+ stdvec slots;
+ slots.reserve(8);
+
+ f32 fixedMain = 0.0f;
+ f32 stretchTotal = 0.0f;
+
+ for_each_laid_out(parent, [&](Widget& c) {
+ const auto& h = c.layoutHint();
+ const Vec2 pref = resolvePreferred(c);
+
+ Slot s;
+ s.w = &c;
+ s.main = horiz ? pref.x : pref.y;
+ s.cross = horiz ? pref.y : pref.x;
+ s.stretches = (h.stretch > 0.0f);
+ s.stretch = h.stretch;
+ s.mainMin = horiz ? h.minSize.x : h.minSize.y;
+ s.mainMax = horiz ? h.maxSize.x : h.maxSize.y;
+ s.crossMin = horiz ? h.minSize.y : h.minSize.x;
+ s.crossMax = horiz ? h.maxSize.y : h.maxSize.x;
+
+ if (!s.stretches)
+ {
+ s.main = applyMinMax(s.main, s.mainMin, s.mainMax);
+ fixedMain += s.main;
+ }
+ else
+ {
+ stretchTotal += s.stretch;
+ }
+ slots.push_back(s);
+ });
+
+ if (slots.empty())
+ return;
+
+ const f32 spacingTotal = m_spacing * std::max(0, (f32)slots.size() - 1);
+ f32 leftover = std::max(0.0f, totalMain - fixedMain - spacingTotal);
+
+ // --- Pass 2: distribute leftover among stretching children.
+ // We may need multiple rounds: if a stretcher hits its maxSize cap,
+ // we lock it and redistribute the residual among the rest.
+ stdvec locked(slots.size(), false);
+ for (size_t i = 0; i < slots.size(); ++i)
+ if (!slots[i].stretches) locked[i] = true;
+
+ while (stretchTotal > 0.0f && leftover > 0.0f)
+ {
+ bool changed = false;
+ f32 newLeftover = leftover;
+ f32 newStretchTotal = stretchTotal;
+
+ for (size_t i = 0; i < slots.size(); ++i)
+ {
+ if (locked[i]) continue;
+ f32 share = leftover * (slots[i].stretch / stretchTotal);
+ f32 capped = applyMinMax(share, slots[i].mainMin, slots[i].mainMax);
+ if (capped < share) // hit the cap
+ {
+ slots[i].main = capped;
+ newLeftover -= capped;
+ newStretchTotal -= slots[i].stretch;
+ locked[i] = true;
+ changed = true;
+ }
+ else
+ {
+ slots[i].main = share; // tentative; may be re-revised next round
+ }
+ }
+
+ if (!changed) break; // no caps hit -> stable
+ leftover = newLeftover;
+ stretchTotal = newStretchTotal;
+ }
+
+ // --- Pass 3: place children.
+ f32 cursor = 0.0f; // local to parent's content frame
+ for (auto& s : slots)
+ {
+ const auto& h = s.w->layoutHint();
+
+ // Cross size: stretch fills, otherwise use child preferred (clamped).
+ f32 crossSize;
+ if (h.alignCross == eAlign::Stretch)
+ crossSize = totalCross;
+ else
+ crossSize = applyMinMax(s.cross, s.crossMin, s.crossMax);
+ crossSize = std::min(crossSize, totalCross);
+
+ const f32 crossOff = alignOffset(h.alignCross, totalCross, crossSize);
+
+ if (horiz)
+ {
+ s.w->setPosition(cursor, crossOff);
+ s.w->setSize(s.main, crossSize);
+ }
+ else
+ {
+ s.w->setPosition(crossOff, cursor);
+ s.w->setSize(crossSize, s.main);
+ }
+ s.w->onBoundsChanged(s.w->getSize());
+
+ cursor += s.main + m_spacing;
+ }
+ }
+
+ Vec2 BoxLayout::measure(const Widget& parent) const
+ {
+ const bool horiz = (m_orientation == Orientation::Horizontal);
+ f32 mainSum = 0, crossMax = 0;
+ i32 count = 0;
+
+ for_each_laid_out(parent, [&](Widget& c) {
+ Vec2 pref = resolvePreferred(c);
+ f32 m = horiz ? pref.x : pref.y;
+ f32 cr = horiz ? pref.y : pref.x;
+ mainSum += m;
+ crossMax = std::max(crossMax, cr);
+ ++count;
+ });
+ mainSum += m_spacing * std::max(0, count - 1);
+ return horiz ? Vec2 { mainSum, crossMax } : Vec2 { crossMax, mainSum };
+ }
+
+
+ // ============================================================================
+ // GridLayout
+ // ============================================================================
+
+ void GridLayout::arrange(Widget& parent)
+ {
+ if (m_rows == 0 || m_cols == 0) return;
+
+ // IMPORTANT: child positions are LOCAL to the parent's content frame.
+ // getGlobalPosition() auto-adds parent's padding and contentOffset,
+ // so we use only the SIZE from getContentBounds() here.
+ const Vec2 areaSize = parent.getContentBounds().getSize();
+ const f32 cellW = (areaSize.x - m_spacing * (m_cols - 1)) / (f32)m_cols;
+ const f32 cellH = (areaSize.y - m_spacing * (m_rows - 1)) / (f32)m_rows;
+
+ u32 cellIndex = 0;
+ const u32 maxCells = m_rows * m_cols;
+
+ for_each_laid_out(parent, [&](Widget& c) {
+ if (cellIndex >= maxCells) return; // overflow — drop
+ const auto& h = c.layoutHint();
+
+ const u32 col = cellIndex % m_cols;
+ const u32 row = cellIndex / m_cols;
+
+ const u32 colSpan = std::max(1, h.colSpan);
+ const u32 rowSpan = std::max(1, h.rowSpan);
+
+ const f32 slotW = cellW * colSpan + m_spacing * (colSpan - 1);
+ const f32 slotH = cellH * rowSpan + m_spacing * (rowSpan - 1);
+
+ const f32 slotX = col * (cellW + m_spacing); // local to content frame
+ const f32 slotY = row * (cellH + m_spacing);
+
+ // Apply alignment within the cell slot if the child isn't stretching.
+ Vec2 size { slotW, slotH };
+ if (h.alignMain != eAlign::Stretch || h.alignCross != eAlign::Stretch)
+ {
+ Vec2 pref = resolvePreferred(c);
+ if (h.alignMain != eAlign::Stretch) size.x = std::min(pref.x, slotW);
+ if (h.alignCross != eAlign::Stretch) size.y = std::min(pref.y, slotH);
+ }
+
+ const f32 offX = (h.alignMain == eAlign::Stretch) ? 0 : alignOffset(h.alignMain, slotW, size.x);
+ const f32 offY = (h.alignCross == eAlign::Stretch) ? 0 : alignOffset(h.alignCross, slotH, size.y);
+
+ c.setPosition(slotX + offX, slotY + offY);
+ c.setSize(size);
+ c.onBoundsChanged(size);
+
+ cellIndex += colSpan;
+ });
+ }
+
+ Vec2 GridLayout::measure(const Widget& parent) const
+ {
+ // Find the largest preferred size among children; report grid total.
+ f32 maxW = 0, maxH = 0;
+ for_each_laid_out(parent, [&](Widget& c) {
+ Vec2 pref = resolvePreferred(c);
+ maxW = std::max(maxW, pref.x);
+ maxH = std::max(maxH, pref.y);
+ });
+ const f32 totalW = maxW * m_cols + m_spacing * (m_cols - 1);
+ const f32 totalH = maxH * m_rows + m_spacing * (m_rows - 1);
+ return { totalW, totalH };
+ }
+ }
+}
diff --git a/src/ogfx/gui/Layout.hpp b/src/ogfx/gui/Layout.hpp
new file mode 100644
index 0000000..b6ec96c
--- /dev/null
+++ b/src/ogfx/gui/Layout.hpp
@@ -0,0 +1,145 @@
+/*
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
+
+ This file is part of OmniaFramework.
+
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OmniaFramework is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace ogfx
+{
+ namespace gui
+ {
+ class Widget;
+
+ // ============================================================================
+ // Layout — abstract base.
+ //
+ // A Layout is owned by a Widget (the "container") and is responsible for
+ // positioning and sizing that widget's children inside the container's
+ // content bounds (i.e. inside padding, after content offset).
+ //
+ // Two responsibilities:
+ // - arrange(parent): set position + size on every laid-out child.
+ // - measure(parent): report how much space this layout *wants* given
+ // its current children. Used so a Layout can drive
+ // a parent's contentExtents (and therefore scrollbars),
+ // and so a parent Layout can ask a child container
+ // "how big would you like to be?".
+ // ============================================================================
+ class Layout
+ {
+ public:
+ virtual ~Layout(void) = default;
+
+ // Place children. Called from Widget::relayout().
+ virtual void arrange(Widget& parent) = 0;
+
+ // Compute desired size of this layout's content. Should NOT mutate
+ // children. Used by Widget::getContentExtents() override below and
+ // by parent layouts asking for preferred size.
+ virtual Vec2 measure(const Widget& parent) const = 0;
+
+ // Spacing between adjacent items along the main axis (Box) or
+ // between cells (Grid). FillLayout ignores it.
+ inline f32 getSpacing(void) const { return m_spacing; }
+ inline void setSpacing(f32 s) { m_spacing = s; }
+
+ protected:
+ // Helpers used by concrete layouts to honor a child's hint
+ // without each subclass re-implementing the same min/max/preferred
+ // resolution logic.
+ static Vec2 resolvePreferred(const Widget& child);
+ static f32 applyMinMax(f32 value, f32 minV, f32 maxV);
+ static f32 alignOffset(eAlign a, f32 available, f32 size);
+
+ f32 m_spacing { 0.0f };
+ };
+
+
+ // ============================================================================
+ // FillLayout — every laid-out child is sized to fill the parent's content
+ // area. Useful as a default (a "single slot" container) and as a building
+ // block for things like SplitView panels.
+ // ============================================================================
+ class FillLayout : public Layout
+ {
+ public:
+ void arrange(Widget& parent) override;
+ Vec2 measure(const Widget& parent) const override;
+ };
+
+
+ // ============================================================================
+ // BoxLayout — arrange children in a single row (Horizontal) or column
+ // (Vertical) with stretch factors and spacing.
+ //
+ // Algorithm (per axis = "main"):
+ // 1. Sum preferred sizes of non-stretching children + spacing.
+ // 2. The remainder is distributed among stretching children proportional
+ // to their stretch factor.
+ // 3. min/max are clamped per child; if a stretching child hits its max,
+ // the leftover is redistributed among the rest in a second pass.
+ //
+ // Cross axis: each child either fills (Stretch, default) or uses its
+ // preferred size aligned per alignCross.
+ // ============================================================================
+ class BoxLayout : public Layout
+ {
+ public:
+ enum class Orientation { Horizontal, Vertical };
+
+ explicit BoxLayout(Orientation orientation) : m_orientation(orientation) {}
+
+ void arrange(Widget& parent) override;
+ Vec2 measure(const Widget& parent) const override;
+
+ inline Orientation getOrientation(void) const { return m_orientation; }
+ inline void setOrientation(Orientation o) { m_orientation = o; }
+
+ private:
+ Orientation m_orientation;
+ };
+
+
+ // ============================================================================
+ // GridLayout — fixed rows × cols. Cell sizes are uniform (rows = h/N,
+ // cols = w/M). Children are placed in row-major insertion order.
+ // LayoutHint::colSpan / rowSpan let a child cover multiple cells.
+ // ============================================================================
+ class GridLayout : public Layout
+ {
+ public:
+ GridLayout(u32 rows, u32 cols) : m_rows(rows), m_cols(cols) {}
+
+ void arrange(Widget& parent) override;
+ Vec2 measure(const Widget& parent) const override;
+
+ inline u32 getRows(void) const { return m_rows; }
+ inline u32 getCols(void) const { return m_cols; }
+ inline void setDimensions(u32 rows, u32 cols) { m_rows = rows; m_cols = cols; }
+
+ private:
+ u32 m_rows;
+ u32 m_cols;
+ };
+ }
+}
diff --git a/src/ogfx/gui/LayoutHint.hpp b/src/ogfx/gui/LayoutHint.hpp
new file mode 100644
index 0000000..19ff726
--- /dev/null
+++ b/src/ogfx/gui/LayoutHint.hpp
@@ -0,0 +1,80 @@
+/*
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
+
+ This file is part of OmniaFramework.
+
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OmniaFramework is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
+*/
+
+#pragma once
+
+#include
+#include
+
+namespace ogfx
+{
+ namespace gui
+ {
+ // How a child wants to align itself within the slot a Layout gives it.
+ enum class eAlign : u8
+ {
+ Start, // top / left
+ Center, // center along the relevant axis
+ End, // bottom / right
+ Stretch // fill the available space along that axis
+ };
+
+ // Per-child metadata read by the parent's Layout when it arranges children.
+ // All fields have sane defaults so unset hints behave like "use my current size,
+ // no stretching, top-left, span 1".
+ struct LayoutHint
+ {
+ // 0 = use the widget's current/preferred size on the main axis.
+ // > 0 = take a share of leftover space proportional to this value.
+ // e.g. two children with stretch=1.0 split leftover space 50/50;
+ // stretch=2 vs stretch=1 splits it 2/3 vs 1/3.
+ f32 stretch { 0.0f };
+
+ // Alignment along the layout's main axis (only meaningful when the child
+ // is smaller than the slot the layout assigned it — i.e. when stretch=0
+ // and the slot ended up larger than preferred). Mostly relevant for Grid
+ // and for FillLayout in single-child mode.
+ eAlign alignMain { eAlign::Start };
+
+ // Alignment perpendicular to the main axis. Stretch (default) makes the
+ // child fill the cross dimension of its slot — usually what you want.
+ eAlign alignCross { eAlign::Stretch };
+
+ // Hard limits the layout will respect. minSize is honored even if it
+ // breaks stretch ratios; maxSize component < 0 means "unbounded".
+ Vec2 minSize { 0, 0 };
+ Vec2 maxSize { -1, -1 };
+
+ // If a component is < 0, the layout uses the widget's current size
+ // (getw()/geth()) for that axis. Otherwise this overrides it.
+ Vec2 preferred { -1, -1 };
+
+ // Grid-only. How many cells the child occupies. Ignored by Box/Fill.
+ u8 colSpan { 1 };
+ u8 rowSpan { 1 };
+
+ // If true, the layout skips this child entirely. Useful for floating
+ // overlays, drag-ghosts, or scrollbars (which manage their own bounds).
+ // Note: ScrollableWidget already excludes its scrollbars via the
+ // IgnoreScroll flag, but this gives finer control for other cases.
+ bool ignored { false };
+ };
+ }
+}
diff --git a/src/ogfx/gui/ToolBar.cpp b/src/ogfx/gui/ToolBar.cpp
index 5ce2fd0..6897a87 100644
--- a/src/ogfx/gui/ToolBar.cpp
+++ b/src/ogfx/gui/ToolBar.cpp
@@ -35,7 +35,7 @@ namespace ogfx
{
setRootChild();
setStylesheetCategoryName("toolbar");
- setTypeName("ogfx::gui::widgets::ToolBar");
+ setTypeName("ogfx::gui::ToolBar");
auto& win = cast(getWindow());
w = win.getWindowWidth();
h = m_height;
@@ -51,19 +51,18 @@ namespace ogfx
return *this;
}
- widgets::Button& ToolBar::addButton(const String& iconPath, const String& text, EventCallback callback)
+ Button& ToolBar::addButton(const String& iconPath, const String& text, EventCallback callback)
{
- static f32 pad = 0;
m_buttons.push_back({ getWindow() });
auto& btn = m_buttons.back();
btn.addThemeID("tool_button");
btn.setIcon(iconPath);
- btn.setIconSize({ m_height, m_height });
btn.setText(text);
+ btn.setSizeMode(eSizeMode::LayoutManaged);
btn.setCallback(ogfx::gui::Widget::eCallback::ActionPerformed, callback);
btn.enableIconOnly();
- addWidget(btn, { m_height * (m_buttons.size() - 1) + pad, 0 });
- pad += 15;
+ btn.layoutHint().preferred = { m_height, m_height };
+ addWidget(btn);
return btn;
}
diff --git a/src/ogfx/gui/ToolBar.hpp b/src/ogfx/gui/ToolBar.hpp
index 4c01297..1f04d38 100644
--- a/src/ogfx/gui/ToolBar.hpp
+++ b/src/ogfx/gui/ToolBar.hpp
@@ -34,7 +34,7 @@ namespace ogfx
public:
ToolBar(Window& window, bool statusbar = false);
ToolBar& create(bool statusbar = false);
- widgets::Button& addButton(const String& iconPath, const String& text = "", EventCallback callback = nullptr);
+ Button& addButton(const String& iconPath, const String& text = "", EventCallback callback = nullptr);
void onWindowResized(const Event& event) override;
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(BasicRenderer2D& gfx) override;
@@ -59,7 +59,7 @@ namespace ogfx
private:
bool m_disableButtonText { true };
- std::deque m_buttons;
+ std::deque