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 {\