diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss
index c3ce018..24108ab 100644
--- a/extra/DefaultTheme.oss
+++ b/extra/DefaultTheme.oss
@@ -263,7 +263,7 @@ const $accentColorDark = #6B0A1DFF
(item) {
defaultTextColor = #202020FF
defaultFontSize = 16
- defaultPadding = rect(5, 5, 20, 0)
+ defaultPadding = rect(5, 15, 20, 40)
defaultSelectionColor = $accentColor
defaultSelectionTextColor = color_white
}
diff --git a/other/TODO.txt b/other/TODO.txt
index bcb7989..d784ef7 100644
--- a/other/TODO.txt
+++ b/other/TODO.txt
@@ -31,7 +31,9 @@ Add buttons to scroll tabs in TabPanel
Add Font class, to handle different font attributes
Add multi-selection to ListView
Cache getStringDimensions() call in TabPanel::draw_tabs
-FIX: Optimize ListView
+FIX: Optimize ListView
+FIX: Padding for ListView items
+
diff --git a/src/ogfx/gui/widgets/ListView.cpp b/src/ogfx/gui/widgets/ListView.cpp
index f13647a..2542d9c 100644
--- a/src/ogfx/gui/widgets/ListView.cpp
+++ b/src/ogfx/gui/widgets/ListView.cpp
@@ -135,14 +135,17 @@ namespace ogfx
if (!item.isValid()) continue;
f32 itemH = item.getDimensions().y;
if (y > visibleEnd) break;
+ f32 lineW = content.w;
+ if (content.w < bounds.w)
+ lineW = bounds.w;
if (item.isSelected())
{
textColor = item.getSelectedTextColor();
- gfx.fillRect({ Vec2 { bounds.x, bounds.y + y + 4 } + getScrollOffset(), { content.w, itemH } }, item.getSelectedColor());
+ gfx.fillRect({ Vec2 { bounds.x, bounds.y + y + item.getPadding().h } + getScrollOffset(), { lineW, itemH } }, item.getSelectedColor());
}
else
textColor = item.getTextColor();
- gfx.drawLine({ Vec2 { bounds.x, bounds.y + y + itemH + 4 } + getScrollOffset(), Vec2 { bounds.x + content.w, bounds.y + y + itemH + 4 } + getScrollOffset() }, Colors::Black, 1);
+ gfx.drawLine({ Vec2 { bounds.x, bounds.y + y + itemH + item.getPadding().h } + getScrollOffset(), Vec2 { bounds.x + lineW, bounds.y + y + itemH + item.getPadding().h } + getScrollOffset() }, getSeparatorLineColor(), 1);
gfx.drawString(item, bounds.getPosition() + Vec2 { 0, y } + getScrollOffset() + item.getPadding().getPosition(), textColor, item.getFontSize());
y += itemH;
}
@@ -234,22 +237,36 @@ namespace ogfx
bool ListView::removeLine(const String& text)
{
- return false; //TODO: Implement
+ auto it = std::find_if(m_list.begin(), m_list.end(), [&](const Item& item) {
+ return item.getText() == text;
+ });
+ if (it != m_list.end())
+ {
+ m_list.erase(it);
+ return true;
+ }
+ return false;
}
bool ListView::removeLine(u32 index)
{
- return false; //TODO: Implement
+ if (!hasLine(index))
+ return false;
+ m_list.erase(m_list.begin() + index);
+ return true;
}
bool ListView::hasLine(const String& text)
{
- return false; //TODO: Implement
+ for (auto& item : m_list)
+ if (item.getText() == text)
+ return true;
+ return false;
}
bool ListView::hasLine(u32 index)
{
- return false; //TODO: Implement
+ return m_list.size() > index;
}
}
}
diff --git a/src/ostd/io/StaticHashMap.hpp b/src/ostd/io/StaticHashMap.hpp
new file mode 100644
index 0000000..2a07e8f
--- /dev/null
+++ b/src/ostd/io/StaticHashMap.hpp
@@ -0,0 +1,310 @@
+/*
+ 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
+#include
+#include
+
+namespace ostd
+{
+ // ============================================================================
+ // StaticHashMap
+ // A drop-in replacement for stdumap (std::unordered_map) that preserves
+ // insertion order. Provides:
+ // - O(1) lookup by key (via internal unordered_map index)
+ // - O(1) lookup by index (via internal deque)
+ // - O(n) removal (index rebuild after erase)
+ // - Insertion-order iteration via begin()/end()
+ // ============================================================================
+ template
+ class StaticHashMap
+ {
+ public:
+ using value_type = std::pair;
+ using storage_type = std::deque;
+ using iterator = typename storage_type::iterator;
+ using const_iterator = typename storage_type::const_iterator;
+
+ // ============================================================
+ // Capacity
+ // ============================================================
+ public:
+ inline i32 size(void) const { return cast(m_entries.size()); }
+ inline bool empty(void) const { return m_entries.empty(); }
+
+ // ============================================================
+ // Element access — key based
+ // ============================================================
+ public:
+ // STL-style operator[]: inserts a default-constructed V if key absent,
+ // then returns a reference. Allows map["key"] = value idiom.
+ V& operator[](const K& key)
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end())
+ {
+ m_index[key] = cast(m_entries.size());
+ m_entries.push_back({ key, V{} });
+ return m_entries.back().second;
+ }
+ return m_entries[it->second].second;
+ }
+
+ // Bounds-checked key access — throws std::out_of_range if absent.
+ V& at(const K& key)
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end())
+ throw std::out_of_range("StaticHashMap::at — key not found");
+ return m_entries[it->second].second;
+ }
+
+ const V& at(const K& key) const
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end())
+ throw std::out_of_range("StaticHashMap::at — key not found");
+ return m_entries[it->second].second;
+ }
+
+ // ============================================================
+ // Element access — index based (vector-style)
+ // ============================================================
+ public:
+ // Direct index access — no bounds check (mirrors std::vector::operator[])
+ V& operator[](i32 index)
+ {
+ return m_entries[cast(index)].second;
+ }
+
+ const V& operator[](i32 index) const
+ {
+ return m_entries[cast(index)].second;
+ }
+
+ // Bounds-checked index access — throws std::out_of_range
+ V& at(i32 index)
+ {
+ if (index < 0 || index >= size())
+ throw std::out_of_range("StaticHashMap::at — index out of range");
+ return m_entries[cast(index)].second;
+ }
+
+ const V& at(i32 index) const
+ {
+ if (index < 0 || index >= size())
+ throw std::out_of_range("StaticHashMap::at — index out of range");
+ return m_entries[cast(index)].second;
+ }
+
+ // Access to the full pair at a given index (key + value)
+ value_type& entry(i32 index)
+ {
+ return m_entries[cast(index)];
+ }
+
+ const value_type& entry(i32 index) const
+ {
+ return m_entries[cast(index)];
+ }
+
+ // Returns the key at a given insertion-order index
+ const K& keyAt(i32 index) const
+ {
+ return m_entries[cast(index)].first;
+ }
+
+ V& front(void) { return m_entries.front().second; }
+ V& back(void) { return m_entries.back().second; }
+ const V& front(void) const { return m_entries.front().second; }
+ const V& back(void) const { return m_entries.back().second; }
+
+ // ============================================================
+ // Lookup
+ // ============================================================
+ public:
+ // Returns 1 if key exists, 0 otherwise — mirrors stdumap::count()
+ inline i32 count(const K& key) const
+ {
+ return m_index.count(key) ? 1 : 0;
+ }
+
+ // C++20-style contains — cleaner than count() for boolean checks
+ inline bool contains(const K& key) const
+ {
+ return m_index.count(key) > 0;
+ }
+
+ // Returns the insertion-order index of a key, or -1 if not found
+ inline i32 indexOf(const K& key) const
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end()) return -1;
+ return it->second;
+ }
+
+ // Returns an iterator to the entry with the given key, or end() if absent.
+ // The iterator dereferences to std::pair, same as stdumap.
+ iterator find(const K& key)
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end()) return m_entries.end();
+ return m_entries.begin() + it->second;
+ }
+
+ const_iterator find(const K& key) const
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end()) return m_entries.end();
+ return m_entries.begin() + it->second;
+ }
+
+ // ============================================================
+ // Insertion
+ // ============================================================
+ public:
+ // Appends at the end. If key already exists the value is updated in-place,
+ // insertion order is preserved (no re-insertion at end).
+ // Returns a reference to the stored value.
+ V& insert(const K& key, const V& value)
+ {
+ auto it = m_index.find(key);
+ if (it != m_index.end())
+ {
+ m_entries[it->second].second = value;
+ return m_entries[it->second].second;
+ }
+ m_index[key] = cast(m_entries.size());
+ m_entries.push_back({ key, value });
+ return m_entries.back().second;
+ }
+
+ // STL-style pair insert — mirrors stdumap::insert({k, v})
+ // Returns pair: iterator to element, true if inserted
+ std::pair insert(const value_type& kv)
+ {
+ auto it = m_index.find(kv.first);
+ if (it != m_index.end())
+ return { m_entries.begin() + it->second, false };
+ i32 idx = cast(m_entries.size());
+ m_index[kv.first] = idx;
+ m_entries.push_back(kv);
+ return { m_entries.begin() + idx, true };
+ }
+
+ // Inserts at a specific position (O(n) — rebuilds indices from pos onward)
+ // If key already exists, does nothing and returns false.
+ bool insertAt(i32 pos, const K& key, const V& value)
+ {
+ if (contains(key)) return false;
+ pos = std::clamp(pos, 0, size());
+ m_entries.insert(m_entries.begin() + pos, { key, value });
+ // Rebuild index for every entry from pos onward
+ for (i32 i = pos; i < size(); i++)
+ m_index[m_entries[cast(i)].first] = i;
+ return true;
+ }
+
+ // In-place construction — mirrors stdumap::emplace()
+ // Returns pair
+ template
+ std::pair emplace(const K& key, Args&&... args)
+ {
+ auto it = m_index.find(key);
+ if (it != m_index.end())
+ return { m_entries.begin() + it->second, false };
+ i32 idx = cast(m_entries.size());
+ m_index[key] = idx;
+ m_entries.emplace_back(key, V(std::forward(args)...));
+ return { m_entries.begin() + idx, true };
+ }
+
+ // Appends a pair — mirrors push_back on a vector of pairs
+ inline void push_back(const value_type& kv) { insert(kv); }
+
+ // Removes the last element
+ void pop_back(void)
+ {
+ if (m_entries.empty()) return;
+ m_index.erase(m_entries.back().first);
+ m_entries.pop_back();
+ }
+
+ // ============================================================
+ // Removal
+ // ============================================================
+ public:
+ // Removes by key — O(n) index rebuild for entries after the removed slot.
+ // Returns true if the key was found and removed.
+ bool erase(const K& key)
+ {
+ auto it = m_index.find(key);
+ if (it == m_index.end()) return false;
+ i32 pos = it->second;
+ m_entries.erase(m_entries.begin() + pos);
+ m_index.erase(it);
+ // Fix up indices for everything that shifted
+ for (i32 i = pos; i < size(); i++)
+ m_index[m_entries[cast(i)].first] = i;
+ return true;
+ }
+
+ // Removes by insertion-order index — O(n) index rebuild.
+ bool eraseAt(i32 index)
+ {
+ if (index < 0 || index >= size()) return false;
+ return erase(m_entries[cast(index)].first);
+ }
+
+ // Removes all entries — O(1)
+ void clear(void)
+ {
+ m_entries.clear();
+ m_index.clear();
+ }
+
+ // ============================================================
+ // Iteration (insertion order, mirrors stdumap range-for)
+ // ============================================================
+ public:
+ inline iterator begin(void) { return m_entries.begin(); }
+ inline iterator end(void) { return m_entries.end(); }
+ inline const_iterator begin(void) const { return m_entries.begin(); }
+ inline const_iterator end(void) const { return m_entries.end(); }
+ inline const_iterator cbegin(void) const { return m_entries.cbegin(); }
+ inline const_iterator cend(void) const { return m_entries.cend(); }
+
+ // ============================================================
+ // Private
+ // ============================================================
+ private:
+ storage_type m_entries; // insertion-ordered pairs
+ std::unordered_map m_index; // key → index into m_entries
+ };
+
+ // Convenience alias that matches the stdumap naming convention in Types.hpp
+ template
+ using stdomap = StaticHashMap;
+
+} // namespace ostd
diff --git a/src/ostd/ostd.hpp b/src/ostd/ostd.hpp
index 7a2bad2..7eb7599 100644
--- a/src/ostd/ostd.hpp
+++ b/src/ostd/ostd.hpp
@@ -1,21 +1,21 @@
/*
- OmniaFramework - A collection of useful functionality
- Copyright (C) 2025 OmniaX-Dev
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
- This file is part of OmniaFramework.
+ This file is part of OmniaFramework.
- OmniaFramework is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- OmniaFramework is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
+ OmniaFramework is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with OmniaFramework. If not, see .
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
*/
#pragma once
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp
index 0c16097..f25b93c 100644
--- a/src/test/GuiTest.cpp
+++ b/src/test/GuiTest.cpp
@@ -123,6 +123,9 @@ class Window : public ogfx::gui::Window
{
m_list.addLine(ostd::Random::getString(ostd::Random::getui8(1, 40)));
}
+ m_list.addLine("Item 1");
+ m_list.addLine("Item 222");
+ m_list.addLine("Item 3333333");
m_list.getLine(10).setFontSize(40);
m_list.getLine(160).setTextColor(Colors::Crimson);
m_list.setSelectionChangedCallback([&](stdvec& selection) -> void {\