Added ostd::StaticHashMap

This commit is contained in:
OmniaX-Dev 2026-04-24 04:42:12 +02:00
parent 94e298394c
commit 4bbf6a9b98
6 changed files with 354 additions and 21 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <deque>
#include <unordered_map>
#include <stdexcept>
#include <algorithm>
#include <ostd/data/Types.hpp>
namespace ostd
{
// ============================================================================
// StaticHashMap<K, V>
// 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 <typename K, typename V>
class StaticHashMap
{
public:
using value_type = std::pair<K, V>;
using storage_type = std::deque<value_type>;
using iterator = typename storage_type::iterator;
using const_iterator = typename storage_type::const_iterator;
// ============================================================
// Capacity
// ============================================================
public:
inline i32 size(void) const { return cast<i32>(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<i32>(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<u32>(index)].second;
}
const V& operator[](i32 index) const
{
return m_entries[cast<u32>(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<u32>(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<u32>(index)].second;
}
// Access to the full pair at a given index (key + value)
value_type& entry(i32 index)
{
return m_entries[cast<u32>(index)];
}
const value_type& entry(i32 index) const
{
return m_entries[cast<u32>(index)];
}
// Returns the key at a given insertion-order index
const K& keyAt(i32 index) const
{
return m_entries[cast<u32>(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<K, V>, 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<i32>(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, bool>: iterator to element, true if inserted
std::pair<iterator, bool> 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<i32>(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<u32>(i)].first] = i;
return true;
}
// In-place construction — mirrors stdumap::emplace()
// Returns pair<iterator, bool>
template <typename... Args>
std::pair<iterator, bool> 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<i32>(m_entries.size());
m_index[key] = idx;
m_entries.emplace_back(key, V(std::forward<Args>(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<u32>(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<u32>(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<K, i32> m_index; // key → index into m_entries
};
// Convenience alias that matches the stdumap naming convention in Types.hpp
template <typename K, typename V>
using stdomap = StaticHashMap<K, V>;
} // namespace ostd

View file

@ -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 <https://www.gnu.org/licenses/>.
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
@ -35,6 +35,7 @@
#include <ostd/io/Logger.hpp>
#include <ostd/io/Memory.hpp>
#include <ostd/io/Serial.hpp>
#include <ostd/io/StaticHashMap.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/math/MathUtils.hpp>

View file

@ -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<ogfx::gui::widgets::ListView::Item*>& selection) -> void {\