/* 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 "ListView.hpp" #include "../../render/BasicRenderer.hpp" #include "../Window.hpp" #include "../../../ostd/io/Memory.hpp" namespace ogfx { namespace gui { namespace widgets { void ListView::Item::setText(const String& text) { if (m_text == text) return; m_text = text; update_dimensions(); if (m_parent) m_parent->m_extentsDirty = true; } void ListView::Item::setFontSize(i32 fontSize) { if (m_fontSize == fontSize) return; m_fontSize = fontSize; update_dimensions(); if (m_parent) m_parent->m_extentsDirty = true; } void ListView::Item::update_dimensions(void) { if (!isValid()) return; m_dimensions = m_parent->getWindow().getGFX().getStringDimensions(m_text, m_fontSize); m_dimensions += Vec2 { m_padding.x + m_padding.w, m_padding.y + m_padding.h }; } void ListView::Item::set_selected(stdvec& selectionList) { for (auto& sel : selectionList) sel->m_selected = false; selectionList.clear(); selectionList.push_back(this); m_selected = true; } void ListView::Item::add_selected(stdvec& selectionList) { if (STDVEC_CONTAINS(selectionList, this)) return; selectionList.push_back(this); m_selected = true; } void ListView::Item::remove_selected(stdvec& selectionList) { if (!STDVEC_CONTAINS(selectionList, this)) return; STDVEC_REMOVE(selectionList, this); m_selected = false; } ListView& ListView::create(void) { setPadding({ 0, 0, 0, 0 }); setTypeName("ogfx::gui::widgets::ListView"); enableStopEvents(); enableBackground(); enableBorder(); setBackgroundColor({ 160, 160, 160 }); setBorderColor({ 50, 50, 50 }); setStylesheetCategoryName("list"); validate(); return *this; } void ListView::applyTheme(const ostd::Stylesheet& theme) { setSeparatorLineColor(getThemeValue(theme, "separatorLineColor", getSeparatorLineColor())); enableShowSeparatorLine(getThemeValue(theme, "showSeparatorLine", isShowSeparatorLineEnabled())); setDefaultLineTextColor(getThemeValue(theme, "item.defaultTextColor", getDefaultLineTextColor())); setDefaultLineFontSize(getThemeValue(theme, "item.defaultFontSize", getDefaultLineFontSize())); setDefaultLinePadding(getThemeValue(theme, "item.defaultPadding", getDefaultLinePadding())); setDefaultLineSelectionColor(getThemeValue(theme, "item.defaultSelectionColor", getDefaultLineSelectionColor())); setDefaultLineSelectionTextColor(getThemeValue(theme, "item.defaultSelectionTextColor", getDefaultLineSelectionTextColor())); } void ListView::onDraw(ogfx::BasicRenderer2D& gfx) { const auto& bounds = getGlobalBounds(); const auto& content = getContentExtents(); const f32 scrollY = -getScrollOffset().y; const f32 visibleH = getContentBounds().h; const f32 visibleStart = scrollY; const f32 visibleEnd = scrollY + visibleH; Color textColor; f32 y = 0; i32 startIdx = 0; for (auto& item : m_list) { if (!item.isValid()) { startIdx++; continue; } f32 itemH = item.getDimensions().y; if (y + itemH > visibleStart) break; y += itemH; startIdx++; } for (i32 i = startIdx; i < (i32)m_list.size(); i++) { auto& item = m_list[i]; 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 + item.getPadding().h } + getScrollOffset(), { lineW, itemH } }, item.getSelectedColor()); } else textColor = item.getTextColor(); 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; } } void ListView::afterDraw(ogfx::BasicRenderer2D& gfx) { drawScrollbars(gfx); } void ListView::onMouseReleased(const Event& event) { if (event.mouse->button != ogfx::MouseEventData::eButton::Left) return; if (isMouseInsideAnyScrollbar()) return; const Vec2 mousePos { event.mouse->position_x, event.mouse->position_y }; const Vec2 origin = getGlobalBounds().getPosition(); const Vec2 scroll = getScrollOffset(); const f32 localY = mousePos.y - origin.y - scroll.y; f32 y = 0; for (auto& item : m_list) { if (!item.isValid()) continue; const f32 itemH = item.getDimensions().y; if (localY >= y && localY < y + itemH) { bool wasSelected = item.isSelected(); item.set_selected(m_selectedList); if (!wasSelected && callback_onSelectionChanged) callback_onSelectionChanged(m_selectedList); event.handle(); break; } y += itemH; } } Rectangle ListView::getContentExtents(void) const { if (!m_extentsDirty) return m_cachedExtents; f32 maxX = 0, maxY = 0; for (auto& item : m_list) { if (!item.isValid()) continue; Vec2 size = item.getDimensions(); maxX = std::max(maxX, size.x); maxY += size.y; } m_cachedExtents = { 0, 0, maxX, maxY }; m_extentsDirty = false; return m_cachedExtents; } ListView::Item& ListView::getLine(const String& text) { for (auto& item : m_list) if (item.getText() == text) return item; return InvalidItem; } ListView::Item& ListView::getLine(u32 index) { if (index < m_list.size()) return m_list[index]; return InvalidItem; } ListView::Item& ListView::addLine(const String& text) { Item item { *this }; item.setTextColor(m_defaultLinetextColor); item.setPadding(m_defaultLinepadding); item.setFontSize(m_defaultLinefontSize); item.setSelectedColor(m_defaultSelectionColor); item.setSelectedTextColor(m_defaultSelectionTextColor); item.setText(text); m_list.push_back(item); if (m_list.size() > 0) m_list[0].set_selected(m_selectedList); m_extentsDirty = true; return m_list.back(); } bool ListView::removeLine(const String& text) { 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) { if (!hasLine(index)) return false; m_list.erase(m_list.begin() + index); return true; } bool ListView::hasLine(const String& text) { for (auto& item : m_list) if (item.getText() == text) return true; return false; } bool ListView::hasLine(u32 index) { return m_list.size() > index; } } } }