Added ostd::StaticHashMap
This commit is contained in:
parent
94e298394c
commit
4bbf6a9b98
6 changed files with 354 additions and 21 deletions
|
|
@ -263,7 +263,7 @@ const $accentColorDark = #6B0A1DFF
|
||||||
(item) {
|
(item) {
|
||||||
defaultTextColor = #202020FF
|
defaultTextColor = #202020FF
|
||||||
defaultFontSize = 16
|
defaultFontSize = 16
|
||||||
defaultPadding = rect(5, 5, 20, 0)
|
defaultPadding = rect(5, 15, 20, 40)
|
||||||
defaultSelectionColor = $accentColor
|
defaultSelectionColor = $accentColor
|
||||||
defaultSelectionTextColor = color_white
|
defaultSelectionTextColor = color_white
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ Add Font class, to handle different font attributes
|
||||||
Add multi-selection to ListView
|
Add multi-selection to ListView
|
||||||
Cache getStringDimensions() call in TabPanel::draw_tabs
|
Cache getStringDimensions() call in TabPanel::draw_tabs
|
||||||
FIX: Optimize ListView
|
FIX: Optimize ListView
|
||||||
|
FIX: Padding for ListView items
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,14 +135,17 @@ namespace ogfx
|
||||||
if (!item.isValid()) continue;
|
if (!item.isValid()) continue;
|
||||||
f32 itemH = item.getDimensions().y;
|
f32 itemH = item.getDimensions().y;
|
||||||
if (y > visibleEnd) break;
|
if (y > visibleEnd) break;
|
||||||
|
f32 lineW = content.w;
|
||||||
|
if (content.w < bounds.w)
|
||||||
|
lineW = bounds.w;
|
||||||
if (item.isSelected())
|
if (item.isSelected())
|
||||||
{
|
{
|
||||||
textColor = item.getSelectedTextColor();
|
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
|
else
|
||||||
textColor = item.getTextColor();
|
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());
|
gfx.drawString(item, bounds.getPosition() + Vec2 { 0, y } + getScrollOffset() + item.getPadding().getPosition(), textColor, item.getFontSize());
|
||||||
y += itemH;
|
y += itemH;
|
||||||
}
|
}
|
||||||
|
|
@ -234,22 +237,36 @@ namespace ogfx
|
||||||
|
|
||||||
bool ListView::removeLine(const String& text)
|
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)
|
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)
|
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)
|
bool ListView::hasLine(u32 index)
|
||||||
{
|
{
|
||||||
return false; //TODO: Implement
|
return m_list.size() > index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
310
src/ostd/io/StaticHashMap.hpp
Normal file
310
src/ostd/io/StaticHashMap.hpp
Normal 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
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
#include <ostd/io/Logger.hpp>
|
#include <ostd/io/Logger.hpp>
|
||||||
#include <ostd/io/Memory.hpp>
|
#include <ostd/io/Memory.hpp>
|
||||||
#include <ostd/io/Serial.hpp>
|
#include <ostd/io/Serial.hpp>
|
||||||
|
#include <ostd/io/StaticHashMap.hpp>
|
||||||
|
|
||||||
#include <ostd/math/Geometry.hpp>
|
#include <ostd/math/Geometry.hpp>
|
||||||
#include <ostd/math/MathUtils.hpp>
|
#include <ostd/math/MathUtils.hpp>
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ class Window : public ogfx::gui::Window
|
||||||
{
|
{
|
||||||
m_list.addLine(ostd::Random::getString(ostd::Random::getui8(1, 40)));
|
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(10).setFontSize(40);
|
||||||
m_list.getLine(160).setTextColor(Colors::Crimson);
|
m_list.getLine(160).setTextColor(Colors::Crimson);
|
||||||
m_list.setSelectionChangedCallback([&](stdvec<ogfx::gui::widgets::ListView::Item*>& selection) -> void {\
|
m_list.setSelectionChangedCallback([&](stdvec<ogfx::gui::widgets::ListView::Item*>& selection) -> void {\
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue