Added ListView widget
This commit is contained in:
parent
69e7fb7f1d
commit
183df32659
7 changed files with 226 additions and 19 deletions
|
|
@ -255,3 +255,22 @@ const $accentColorDark = #6B0A1DFF
|
|||
padding = rect(5, 5, 5, 5)
|
||||
}
|
||||
% ===================
|
||||
|
||||
|
||||
|
||||
% ====== Slider ======
|
||||
(list) {
|
||||
(item) {
|
||||
defaultTextColor = #202020FF
|
||||
defaultFontSize = 16
|
||||
defaultPadding = rect(5, 5, 20, 0)
|
||||
defaultSelectionColor = $accentColor
|
||||
defaultSelectionTextColor = color_white
|
||||
}
|
||||
backgroundColor = rgb(160, 160, 160)
|
||||
borderColor = rgb(50, 50, 50)
|
||||
separatorLineColor = rgb(40, 40, 40)
|
||||
showSeparatorLine = true
|
||||
|
||||
}
|
||||
% ===================
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ FIX: Float values in stylesheet needing to have decimal point to be read as floa
|
|||
Make parsing of vec2 and rect in stylesheet, consistent with other value functions like gradients and animations
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
|
@ -49,7 +52,7 @@ Implement following widgets:
|
|||
***Progressbar
|
||||
***Horizontal Slider
|
||||
***Vertical Slider
|
||||
ListBox
|
||||
***ListBox
|
||||
ComboBox
|
||||
Context Menu
|
||||
Menu Bar
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
#include "ListView.hpp"
|
||||
#include "../../render/BasicRenderer.hpp"
|
||||
#include "../Window.hpp"
|
||||
#include "../../../ostd/math/Random.hpp"
|
||||
#include "../../../ostd/io/Memory.hpp"
|
||||
|
||||
namespace ogfx
|
||||
{
|
||||
|
|
@ -53,6 +53,31 @@ namespace ogfx
|
|||
m_dimensions += Vec2 { m_padding.x + m_padding.w, m_padding.y + m_padding.h };
|
||||
}
|
||||
|
||||
void ListView::Item::set_selected(stdvec<Item*>& selectionList)
|
||||
{
|
||||
for (auto& sel : selectionList)
|
||||
sel->m_selected = false;
|
||||
selectionList.clear();
|
||||
selectionList.push_back(this);
|
||||
m_selected = true;
|
||||
}
|
||||
|
||||
void ListView::Item::add_selected(stdvec<Item*>& selectionList)
|
||||
{
|
||||
if (STDVEC_CONTAINS(selectionList, this))
|
||||
return;
|
||||
selectionList.push_back(this);
|
||||
m_selected = true;
|
||||
}
|
||||
|
||||
void ListView::Item::remove_selected(stdvec<Item*>& selectionList)
|
||||
{
|
||||
if (!STDVEC_CONTAINS(selectionList, this))
|
||||
return;
|
||||
STDVEC_REMOVE(selectionList, this);
|
||||
m_selected = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -67,29 +92,78 @@ namespace ogfx
|
|||
setBorderColor({ 50, 50, 50 });
|
||||
setStylesheetCategoryName("list");
|
||||
validate();
|
||||
for (i32 i = 0; i < 30; i++)
|
||||
{
|
||||
Item k { *this };
|
||||
m_list.push_back(k);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ListView::applyTheme(const ostd::Stylesheet& theme)
|
||||
{
|
||||
setSeparatorLineColor(getThemeValue<Color>(theme, "separatorLineColor", getSeparatorLineColor()));
|
||||
enableShowSeparatorLine(getThemeValue<bool>(theme, "showSeparatorLine", isShowSeparatorLineEnabled()));
|
||||
setDefaultLineTextColor(getThemeValue<Color>(theme, "item.defaultTextColor", getDefaultLineTextColor()));
|
||||
setDefaultLineFontSize(getThemeValue<i32>(theme, "item.defaultFontSize", getDefaultLineFontSize()));
|
||||
setDefaultLinePadding(getThemeValue<Rectangle>(theme, "item.defaultPadding", getDefaultLinePadding()));
|
||||
setDefaultLineSelectionColor(getThemeValue<Color>(theme, "item.defaultSelectionColor", getDefaultLineSelectionColor()));
|
||||
setDefaultLineSelectionTextColor(getThemeValue<Color>(theme, "item.defaultSelectionTextColor", getDefaultLineSelectionTextColor()));
|
||||
}
|
||||
|
||||
void ListView::onDraw(ogfx::BasicRenderer2D& gfx)
|
||||
{
|
||||
f32 y = 0;
|
||||
// f32 y = 0;
|
||||
const auto& bounds = getGlobalBounds();
|
||||
const auto& content = getContentExtents();
|
||||
Color textColor;
|
||||
// Compute the visible Y window in content space
|
||||
const f32 scrollY = -getScrollOffset().y; // positive offset into content
|
||||
const f32 visibleH = getContentBounds().h;
|
||||
const f32 visibleStart = scrollY;
|
||||
const f32 visibleEnd = scrollY + visibleH;
|
||||
|
||||
// Find the starting item and its Y via a linear scan...
|
||||
f32 y = 0;
|
||||
i32 startIdx = 0;
|
||||
for (auto& item : m_list)
|
||||
{
|
||||
if (!item.isValid()) continue;
|
||||
const auto& size = item.getDimensions();
|
||||
gfx.drawString(item, bounds.getPosition() + Vec2 { 0, y } + getScrollOffset() + item.getPadding().getPosition(), item.getTextColor(), item.getFontSize());
|
||||
y += size.y;
|
||||
if (!item.isValid()) { startIdx++; continue; }
|
||||
f32 itemH = item.getDimensions().y;
|
||||
if (y + itemH > visibleStart) break; // this item is the first one in view
|
||||
y += itemH;
|
||||
startIdx++;
|
||||
}
|
||||
|
||||
// ...then process only items from startIdx until we exceed visibleEnd
|
||||
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; // past the visible window, stop
|
||||
if (item.isSelected())
|
||||
{
|
||||
textColor = item.getSelectedTextColor();
|
||||
gfx.fillRect({ Vec2 { bounds.x, bounds.y + y + 4 } + getScrollOffset(), { content.w, 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.drawString(item, bounds.getPosition() + Vec2 { 0, y } + getScrollOffset() + item.getPadding().getPosition(), textColor, item.getFontSize());
|
||||
// draw or hit-test item at content Y = y
|
||||
y += itemH;
|
||||
}
|
||||
// for (auto& item : m_list)
|
||||
// {
|
||||
// if (!item.isValid()) continue;
|
||||
// const auto& size = item.getDimensions();
|
||||
// if (item.isSelected())
|
||||
// {
|
||||
// textColor = item.getSelectedTextColor();
|
||||
// gfx.fillRect({ Vec2 { bounds.x, bounds.y + y + 4 } + getScrollOffset(), { content.w, size.y } }, item.getSelectedColor());
|
||||
// }
|
||||
// else
|
||||
// textColor = item.getTextColor();
|
||||
// gfx.drawLine({ Vec2 { bounds.x, bounds.y + y + size.y + 4 } + getScrollOffset(), Vec2 { bounds.x + content.w, bounds.y + y + size.y + 4 } + getScrollOffset() }, Colors::Black, 1);
|
||||
// gfx.drawString(item, bounds.getPosition() + Vec2 { 0, y } + getScrollOffset() + item.getPadding().getPosition(), textColor, item.getFontSize());
|
||||
// y += size.y;
|
||||
// }
|
||||
}
|
||||
|
||||
void ListView::afterDraw(ogfx::BasicRenderer2D& gfx)
|
||||
|
|
@ -97,6 +171,35 @@ namespace ogfx
|
|||
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 (!item.isSelected() && localY >= y && localY < y + itemH)
|
||||
{
|
||||
item.set_selected(m_selectedList);
|
||||
if (callback_onSelectionChanged)
|
||||
callback_onSelectionChanged(m_selectedList);
|
||||
event.handle();
|
||||
break;
|
||||
}
|
||||
y += itemH;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle ListView::getContentExtents(void) const
|
||||
{
|
||||
f32 maxX = 0, maxY = 0;
|
||||
|
|
@ -110,15 +213,34 @@ namespace ogfx
|
|||
return { 0, 0, maxX, maxY };
|
||||
}
|
||||
|
||||
void ListView::refresh(void)
|
||||
ListView::Item& ListView::getItem(const String& text)
|
||||
{
|
||||
for (auto& item : m_list)
|
||||
{
|
||||
item.setText(ostd::Random::getString(ostd::Random::getui8(0, 40)));
|
||||
}
|
||||
if (item.getText() == text)
|
||||
return item;
|
||||
return InvalidItem;
|
||||
}
|
||||
|
||||
m_list[10].setFontSize(40);
|
||||
m_list[16].setTextColor(Colors::Crimson);
|
||||
ListView::Item& ListView::getItem(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() == 1)
|
||||
m_list.back().set_selected(m_selectedList);
|
||||
return m_list.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,21 +44,31 @@ namespace ogfx
|
|||
inline Vec2 getDimensions(void) const { return m_dimensions; }
|
||||
inline i32 getFontSize(void) const { return m_fontSize; }
|
||||
inline String getText(void) const { return m_text; }
|
||||
inline bool isSelected(void) const { return m_selected; }
|
||||
|
||||
OSTD_PARAM_GETSET(Color, TextColor, m_textColor);
|
||||
OSTD_PARAM_GETSET(Rectangle, Padding, m_padding);
|
||||
OSTD_PARAM_GETSET(Color, SelectedTextColor, m_selectedTextColor);
|
||||
OSTD_PARAM_GETSET(Color, SelectedColor, m_selectedColor);
|
||||
|
||||
private:
|
||||
void update_dimensions(void);
|
||||
void set_selected(stdvec<Item*>& selectionList);
|
||||
void add_selected(stdvec<Item*>& selectionList);
|
||||
void remove_selected(stdvec<Item*>& selectionList);
|
||||
|
||||
private:
|
||||
Vec2 m_dimensions { 0, 0 };
|
||||
bool m_selected { false };
|
||||
ListView* m_parent { nullptr };
|
||||
String m_text { "" };
|
||||
i32 m_fontSize { 16 };
|
||||
Color m_textColor { 30, 30, 30 };
|
||||
Color m_selectedTextColor { Colors::White };
|
||||
Color m_selectedColor { Colors::Crimson };
|
||||
Rectangle m_padding { 5, 5, 20, 0 };
|
||||
|
||||
friend class ListView;
|
||||
};
|
||||
public:
|
||||
inline ListView(WindowCore& window) : ScrollableWidget(window) { create(); }
|
||||
|
|
@ -66,11 +76,35 @@ namespace ogfx
|
|||
void applyTheme(const ostd::Stylesheet& theme) override;
|
||||
void onDraw(ogfx::BasicRenderer2D& gfx) override;
|
||||
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
|
||||
void onMouseReleased(const Event& event) override;
|
||||
Rectangle getContentExtents(void) const override;
|
||||
void refresh(void) override;
|
||||
Item& getItem(const String& text);
|
||||
Item& getItem(u32 index);
|
||||
Item& addLine(const String& text);
|
||||
inline void addLine(const Item& line) { m_list.push_back(line); if (m_list.size() == 1) m_list.back().set_selected(m_selectedList); }
|
||||
inline stdvec<Item*>& getSelection(void) { return m_selectedList; }
|
||||
inline void setSelectionChangedCallback(std::function<void(stdvec<Item*>& selection)> callback) { callback_onSelectionChanged = std::move(callback); }
|
||||
|
||||
OSTD_PARAM_GETSET(Color, SeparatorLineColor, m_lineColor);
|
||||
OSTD_BOOL_PARAM_GETSET_E(ShowSeparatorLine, m_showLine);
|
||||
OSTD_PARAM_GETSET(i32, DefaultLineFontSize, m_defaultLinefontSize);
|
||||
OSTD_PARAM_GETSET(Color, DefaultLineTextColor, m_defaultLinetextColor);
|
||||
OSTD_PARAM_GETSET(Rectangle, DefaultLinePadding, m_defaultLinepadding);
|
||||
OSTD_PARAM_GETSET(Color, DefaultLineSelectionColor, m_defaultSelectionColor);
|
||||
OSTD_PARAM_GETSET(Color, DefaultLineSelectionTextColor, m_defaultSelectionTextColor);
|
||||
|
||||
private:
|
||||
Item InvalidItem { *this };
|
||||
stdvec<Item> m_list;
|
||||
stdvec<Item*> m_selectedList;
|
||||
std::function<void(stdvec<Item*>& selection)> callback_onSelectionChanged { nullptr };
|
||||
Color m_lineColor { 40, 40, 40 };
|
||||
bool m_showLine { true };
|
||||
i32 m_defaultLinefontSize { 16 };
|
||||
Color m_defaultLinetextColor { 30, 30, 30 };
|
||||
Rectangle m_defaultLinepadding { 5, 5, 20, 0 };
|
||||
Color m_defaultSelectionColor { Colors::Crimson };
|
||||
Color m_defaultSelectionTextColor { Colors::White };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,6 +355,16 @@ namespace ogfx
|
|||
}
|
||||
}
|
||||
|
||||
bool ScrollableWidget::isMouseInsideVScrollbar(void) const
|
||||
{
|
||||
return m_vScrollbar.isMouseInside();
|
||||
}
|
||||
|
||||
bool ScrollableWidget::isMouseInsideHScrollbar(void) const
|
||||
{
|
||||
return m_hScrollbar.isMouseInside();
|
||||
}
|
||||
|
||||
void ScrollableWidget::onMouseScrolled(const Event& event)
|
||||
{
|
||||
if (isVScrollEnabled())
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ namespace ogfx
|
|||
void drawScrollbars(ogfx::BasicRenderer2D& gfx);
|
||||
void updateScrollbarsSize(void);
|
||||
void resetScroll(bool horizontal = true, bool vertical = true, bool propagate = true) override;
|
||||
bool isMouseInsideVScrollbar(void) const;
|
||||
bool isMouseInsideHScrollbar(void) const;
|
||||
inline bool isMouseInsideAnyScrollbar(void) const { return isMouseInsideVScrollbar() || isMouseInsideHScrollbar(); }
|
||||
virtual void onMouseScrolled(const Event& event) override;
|
||||
virtual void setScrollOffset(const Vec2& offset) override;
|
||||
virtual void addScrollOffset(const Vec2& offset) override;
|
||||
|
|
|
|||
|
|
@ -114,8 +114,21 @@ class Window : public ogfx::gui::Window
|
|||
});
|
||||
m_slideLbl.setText(String("").add(m_slide.getValue(), 2));
|
||||
|
||||
m_drawCallsLbl.addThemeOverride("@.label.fontSize", 30);
|
||||
m_drawCallsLbl.addThemeOverride("@.label.textColor", Colors::Crimson);
|
||||
|
||||
m_list.setSize(200, 300);
|
||||
|
||||
for (i32 i = 0; i < 100; i++)
|
||||
{
|
||||
m_list.addLine(ostd::Random::getString(ostd::Random::getui8(0, 40)));
|
||||
}
|
||||
m_list.getItem(10).setFontSize(40);
|
||||
m_list.getItem(160).setTextColor(Colors::Crimson);
|
||||
m_list.setSelectionChangedCallback([&](stdvec<ogfx::gui::widgets::ListView::Item*>& selection) -> void {\
|
||||
std::cout << *(selection[0]) << "\n";
|
||||
});
|
||||
|
||||
m_tabs.setSize(900, 700);
|
||||
auto& t1 = m_tabs.addTab("Tab1");
|
||||
auto& t2 = m_tabs.addTab("Tab2 Test");
|
||||
|
|
@ -151,6 +164,7 @@ class Window : public ogfx::gui::Window
|
|||
m_panel2.addWidget(m_img, { 20, 50 });
|
||||
|
||||
addWidget(m_tabs, { 0, 0 });
|
||||
addWidget(m_drawCallsLbl, { 39, m_tabs.getSize().y + 30 });
|
||||
|
||||
m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList());
|
||||
setTheme(m_theme);
|
||||
|
|
@ -180,6 +194,7 @@ class Window : public ogfx::gui::Window
|
|||
void onRedraw(ogfx::BasicRenderer2D& gfx) override
|
||||
{
|
||||
// gfx.fillRect(m_tabs.getGlobalPureContentBounds(), { 0, 255, 0, 100 });
|
||||
m_drawCallsLbl.setText(String("").add(gfx.getDrawCallCount()));
|
||||
}
|
||||
|
||||
void onFixedUpdate(void) override
|
||||
|
|
@ -204,6 +219,7 @@ class Window : public ogfx::gui::Window
|
|||
Slider m_slide { *this };
|
||||
Label m_slideLbl { *this };
|
||||
ListView m_list { *this };
|
||||
Label m_drawCallsLbl { *this };
|
||||
|
||||
ostd::StepTimer m_timer;
|
||||
ostd::AsyncJob<bool> m_progressJob;
|
||||
|
|
|
|||
Loading…
Reference in a new issue