Fixed a ListView dangling pointer bug
This commit is contained in:
parent
183df32659
commit
94e298394c
6 changed files with 84 additions and 41 deletions
1
.clangd
1
.clangd
|
|
@ -1,4 +1,5 @@
|
||||||
CompileFlags:
|
CompileFlags:
|
||||||
CompilationDatabase: bin
|
CompilationDatabase: bin
|
||||||
|
Add: ["--target=x86_64-w64-mingw32"]
|
||||||
Diagnostics:
|
Diagnostics:
|
||||||
MissingIncludes: None
|
MissingIncludes: None
|
||||||
|
|
@ -35,6 +35,7 @@ namespace ogfx
|
||||||
return;
|
return;
|
||||||
m_text = text;
|
m_text = text;
|
||||||
update_dimensions();
|
update_dimensions();
|
||||||
|
if (m_parent) m_parent->m_extentsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListView::Item::setFontSize(i32 fontSize)
|
void ListView::Item::setFontSize(i32 fontSize)
|
||||||
|
|
@ -43,6 +44,7 @@ namespace ogfx
|
||||||
return;
|
return;
|
||||||
m_fontSize = fontSize;
|
m_fontSize = fontSize;
|
||||||
update_dimensions();
|
update_dimensions();
|
||||||
|
if (m_parent) m_parent->m_extentsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListView::Item::update_dimensions(void)
|
void ListView::Item::update_dimensions(void)
|
||||||
|
|
@ -108,35 +110,31 @@ namespace ogfx
|
||||||
|
|
||||||
void ListView::onDraw(ogfx::BasicRenderer2D& gfx)
|
void ListView::onDraw(ogfx::BasicRenderer2D& gfx)
|
||||||
{
|
{
|
||||||
// f32 y = 0;
|
|
||||||
const auto& bounds = getGlobalBounds();
|
const auto& bounds = getGlobalBounds();
|
||||||
const auto& content = getContentExtents();
|
const auto& content = getContentExtents();
|
||||||
Color textColor;
|
const f32 scrollY = -getScrollOffset().y;
|
||||||
// Compute the visible Y window in content space
|
|
||||||
const f32 scrollY = -getScrollOffset().y; // positive offset into content
|
|
||||||
const f32 visibleH = getContentBounds().h;
|
const f32 visibleH = getContentBounds().h;
|
||||||
const f32 visibleStart = scrollY;
|
const f32 visibleStart = scrollY;
|
||||||
const f32 visibleEnd = scrollY + visibleH;
|
const f32 visibleEnd = scrollY + visibleH;
|
||||||
|
Color textColor;
|
||||||
|
|
||||||
// Find the starting item and its Y via a linear scan...
|
|
||||||
f32 y = 0;
|
f32 y = 0;
|
||||||
i32 startIdx = 0;
|
i32 startIdx = 0;
|
||||||
for (auto& item : m_list)
|
for (auto& item : m_list)
|
||||||
{
|
{
|
||||||
if (!item.isValid()) { startIdx++; continue; }
|
if (!item.isValid()) { startIdx++; continue; }
|
||||||
f32 itemH = item.getDimensions().y;
|
f32 itemH = item.getDimensions().y;
|
||||||
if (y + itemH > visibleStart) break; // this item is the first one in view
|
if (y + itemH > visibleStart) break;
|
||||||
y += itemH;
|
y += itemH;
|
||||||
startIdx++;
|
startIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...then process only items from startIdx until we exceed visibleEnd
|
|
||||||
for (i32 i = startIdx; i < (i32)m_list.size(); i++)
|
for (i32 i = startIdx; i < (i32)m_list.size(); i++)
|
||||||
{
|
{
|
||||||
auto& item = m_list[i];
|
auto& item = m_list[i];
|
||||||
if (!item.isValid()) continue;
|
if (!item.isValid()) continue;
|
||||||
f32 itemH = item.getDimensions().y;
|
f32 itemH = item.getDimensions().y;
|
||||||
if (y > visibleEnd) break; // past the visible window, stop
|
if (y > visibleEnd) break;
|
||||||
if (item.isSelected())
|
if (item.isSelected())
|
||||||
{
|
{
|
||||||
textColor = item.getSelectedTextColor();
|
textColor = item.getSelectedTextColor();
|
||||||
|
|
@ -146,24 +144,8 @@ namespace ogfx
|
||||||
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 + 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());
|
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;
|
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)
|
void ListView::afterDraw(ogfx::BasicRenderer2D& gfx)
|
||||||
|
|
@ -188,10 +170,11 @@ namespace ogfx
|
||||||
{
|
{
|
||||||
if (!item.isValid()) continue;
|
if (!item.isValid()) continue;
|
||||||
const f32 itemH = item.getDimensions().y;
|
const f32 itemH = item.getDimensions().y;
|
||||||
if (!item.isSelected() && localY >= y && localY < y + itemH)
|
if (localY >= y && localY < y + itemH)
|
||||||
{
|
{
|
||||||
|
bool wasSelected = item.isSelected();
|
||||||
item.set_selected(m_selectedList);
|
item.set_selected(m_selectedList);
|
||||||
if (callback_onSelectionChanged)
|
if (!wasSelected && callback_onSelectionChanged)
|
||||||
callback_onSelectionChanged(m_selectedList);
|
callback_onSelectionChanged(m_selectedList);
|
||||||
event.handle();
|
event.handle();
|
||||||
break;
|
break;
|
||||||
|
|
@ -202,6 +185,9 @@ namespace ogfx
|
||||||
|
|
||||||
Rectangle ListView::getContentExtents(void) const
|
Rectangle ListView::getContentExtents(void) const
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (!m_extentsDirty)
|
||||||
|
return m_cachedExtents;
|
||||||
f32 maxX = 0, maxY = 0;
|
f32 maxX = 0, maxY = 0;
|
||||||
for (auto& item : m_list)
|
for (auto& item : m_list)
|
||||||
{
|
{
|
||||||
|
|
@ -210,10 +196,12 @@ namespace ogfx
|
||||||
maxX = std::max(maxX, size.x);
|
maxX = std::max(maxX, size.x);
|
||||||
maxY += size.y;
|
maxY += size.y;
|
||||||
}
|
}
|
||||||
return { 0, 0, maxX, maxY };
|
m_cachedExtents = { 0, 0, maxX, maxY };
|
||||||
|
m_extentsDirty = false;
|
||||||
|
return m_cachedExtents;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView::Item& ListView::getItem(const String& text)
|
ListView::Item& ListView::getLine(const String& text)
|
||||||
{
|
{
|
||||||
for (auto& item : m_list)
|
for (auto& item : m_list)
|
||||||
if (item.getText() == text)
|
if (item.getText() == text)
|
||||||
|
|
@ -221,7 +209,7 @@ namespace ogfx
|
||||||
return InvalidItem;
|
return InvalidItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView::Item& ListView::getItem(u32 index)
|
ListView::Item& ListView::getLine(u32 index)
|
||||||
{
|
{
|
||||||
if (index < m_list.size())
|
if (index < m_list.size())
|
||||||
return m_list[index];
|
return m_list[index];
|
||||||
|
|
@ -238,10 +226,31 @@ namespace ogfx
|
||||||
item.setSelectedTextColor(m_defaultSelectionTextColor);
|
item.setSelectedTextColor(m_defaultSelectionTextColor);
|
||||||
item.setText(text);
|
item.setText(text);
|
||||||
m_list.push_back(item);
|
m_list.push_back(item);
|
||||||
if (m_list.size() == 1)
|
if (m_list.size() > 0)
|
||||||
m_list.back().set_selected(m_selectedList);
|
m_list[0].set_selected(m_selectedList);
|
||||||
|
m_extentsDirty = true;
|
||||||
return m_list.back();
|
return m_list.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ListView::removeLine(const String& text)
|
||||||
|
{
|
||||||
|
return false; //TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListView::removeLine(u32 index)
|
||||||
|
{
|
||||||
|
return false; //TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListView::hasLine(const String& text)
|
||||||
|
{
|
||||||
|
return false; //TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ListView::hasLine(u32 index)
|
||||||
|
{
|
||||||
|
return false; //TODO: Implement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include <ogfx/gui/widgets/Widget.hpp>
|
#include <ogfx/gui/widgets/Widget.hpp>
|
||||||
#include <ogfx/gui/widgets/Scrollbar.hpp>
|
#include <ogfx/gui/widgets/Scrollbar.hpp>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
namespace ogfx
|
namespace ogfx
|
||||||
{
|
{
|
||||||
|
|
@ -78,9 +79,13 @@ namespace ogfx
|
||||||
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
|
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
|
||||||
void onMouseReleased(const Event& event) override;
|
void onMouseReleased(const Event& event) override;
|
||||||
Rectangle getContentExtents(void) const override;
|
Rectangle getContentExtents(void) const override;
|
||||||
Item& getItem(const String& text);
|
Item& getLine(const String& text);
|
||||||
Item& getItem(u32 index);
|
Item& getLine(u32 index);
|
||||||
Item& addLine(const String& text);
|
Item& addLine(const String& text);
|
||||||
|
bool removeLine(const String& text);
|
||||||
|
bool removeLine(u32 index);
|
||||||
|
bool hasLine(const String& text);
|
||||||
|
bool hasLine(u32 index);
|
||||||
inline void addLine(const Item& line) { m_list.push_back(line); if (m_list.size() == 1) m_list.back().set_selected(m_selectedList); }
|
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 stdvec<Item*>& getSelection(void) { return m_selectedList; }
|
||||||
inline void setSelectionChangedCallback(std::function<void(stdvec<Item*>& selection)> callback) { callback_onSelectionChanged = std::move(callback); }
|
inline void setSelectionChangedCallback(std::function<void(stdvec<Item*>& selection)> callback) { callback_onSelectionChanged = std::move(callback); }
|
||||||
|
|
@ -95,9 +100,12 @@ namespace ogfx
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Item InvalidItem { *this };
|
Item InvalidItem { *this };
|
||||||
stdvec<Item> m_list;
|
std::deque<Item> m_list;
|
||||||
stdvec<Item*> m_selectedList;
|
stdvec<Item*> m_selectedList;
|
||||||
std::function<void(stdvec<Item*>& selection)> callback_onSelectionChanged { nullptr };
|
std::function<void(stdvec<Item*>& selection)> callback_onSelectionChanged { nullptr };
|
||||||
|
mutable Rectangle m_cachedExtents { 0, 0, 0, 0 };
|
||||||
|
mutable bool m_extentsDirty { true };
|
||||||
|
|
||||||
Color m_lineColor { 40, 40, 40 };
|
Color m_lineColor { 40, 40, 40 };
|
||||||
bool m_showLine { true };
|
bool m_showLine { true };
|
||||||
i32 m_defaultLinefontSize { 16 };
|
i32 m_defaultLinefontSize { 16 };
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,28 @@ namespace ogfx
|
||||||
set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y }));
|
set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Slider::onMouseScrolled(const Event& event)
|
||||||
|
{
|
||||||
|
if (!isMouseInside())
|
||||||
|
return;
|
||||||
|
if (!isScrollWheelShortcutEnabled())
|
||||||
|
return;
|
||||||
|
if (getStep() > 0)
|
||||||
|
{
|
||||||
|
if (event.mouse->scroll == MouseEventData::eScrollDirection::Down ||
|
||||||
|
event.mouse->scroll == MouseEventData::eScrollDirection::Right)
|
||||||
|
set_value(getValue() + (getStep() * -1));
|
||||||
|
else
|
||||||
|
set_value(getValue() + getStep());
|
||||||
|
}
|
||||||
|
else if (event.mouse->scroll == MouseEventData::eScrollDirection::Down ||
|
||||||
|
event.mouse->scroll == MouseEventData::eScrollDirection::Up)
|
||||||
|
set_value(getValue() + (0.1f * event.mouse->scrollAmount.y));
|
||||||
|
else
|
||||||
|
set_value(getValue() + (0.1f * event.mouse->scrollAmount.x));
|
||||||
|
event.handle();
|
||||||
|
}
|
||||||
|
|
||||||
void Slider::onDraw(ogfx::BasicRenderer2D& gfx)
|
void Slider::onDraw(ogfx::BasicRenderer2D& gfx)
|
||||||
{
|
{
|
||||||
const auto& bounds = getGlobalContentBounds();
|
const auto& bounds = getGlobalContentBounds();
|
||||||
|
|
@ -143,7 +165,7 @@ namespace ogfx
|
||||||
|
|
||||||
f32 Slider::snap_to_step(f32 val)
|
f32 Slider::snap_to_step(f32 val)
|
||||||
{
|
{
|
||||||
if (m_step <= 0.0f) return val; // full precision
|
if (m_step <= 0.0f) return std::clamp(val, m_min, m_max); // full precision
|
||||||
f32 snapped = std::round((val - m_min) / m_step) * m_step + m_min;
|
f32 snapped = std::round((val - m_min) / m_step) * m_step + m_min;
|
||||||
return std::clamp(snapped, m_min, m_max);
|
return std::clamp(snapped, m_min, m_max);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ namespace ogfx
|
||||||
void applyTheme(const ostd::Stylesheet& theme) override;
|
void applyTheme(const ostd::Stylesheet& theme) override;
|
||||||
void onMouseReleased(const Event& event) override;
|
void onMouseReleased(const Event& event) override;
|
||||||
void onMouseDragged(const Event& event) override;
|
void onMouseDragged(const Event& event) override;
|
||||||
|
void onMouseScrolled(const Event& event) override;
|
||||||
void onDraw(ogfx::BasicRenderer2D& gfx) override;
|
void onDraw(ogfx::BasicRenderer2D& gfx) override;
|
||||||
inline f32 getValue(void) const { return m_value; }
|
inline f32 getValue(void) const { return m_value; }
|
||||||
inline void setValueChangedCallback(std::function<void(f32 oldValue, f32 newValue)> callback) { callback_onValueChanged = std::move(callback); }
|
inline void setValueChangedCallback(std::function<void(f32 oldValue, f32 newValue)> callback) { callback_onValueChanged = std::move(callback); }
|
||||||
|
|
@ -48,6 +49,7 @@ namespace ogfx
|
||||||
inline void disableHorizontal(void) { enableHorizontal(false); }
|
inline void disableHorizontal(void) { enableHorizontal(false); }
|
||||||
|
|
||||||
OSTD_BOOL_PARAM_GETSET_E(MiddleClickShortcut, m_middleClickShortcut);
|
OSTD_BOOL_PARAM_GETSET_E(MiddleClickShortcut, m_middleClickShortcut);
|
||||||
|
OSTD_BOOL_PARAM_GETSET_E(ScrollWheelShortcut, m_enableScrollWheel);
|
||||||
OSTD_PARAM_GETSET(f32, MinValue, m_min);
|
OSTD_PARAM_GETSET(f32, MinValue, m_min);
|
||||||
OSTD_PARAM_GETSET(f32, MaxValue, m_max);
|
OSTD_PARAM_GETSET(f32, MaxValue, m_max);
|
||||||
OSTD_PARAM_GETSET(f32, Step, m_step);
|
OSTD_PARAM_GETSET(f32, Step, m_step);
|
||||||
|
|
@ -75,6 +77,7 @@ namespace ogfx
|
||||||
f32 m_step { 0.1f };
|
f32 m_step { 0.1f };
|
||||||
bool m_vertical { false };
|
bool m_vertical { false };
|
||||||
bool m_middleClickShortcut { true };
|
bool m_middleClickShortcut { true };
|
||||||
|
bool m_enableScrollWheel { true };
|
||||||
|
|
||||||
f32 m_trackWidth { 6 };
|
f32 m_trackWidth { 6 };
|
||||||
Color m_trackColor { "#500000FF" };
|
Color m_trackColor { "#500000FF" };
|
||||||
|
|
|
||||||
|
|
@ -119,12 +119,12 @@ class Window : public ogfx::gui::Window
|
||||||
|
|
||||||
m_list.setSize(200, 300);
|
m_list.setSize(200, 300);
|
||||||
|
|
||||||
for (i32 i = 0; i < 100; i++)
|
for (i32 i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
m_list.addLine(ostd::Random::getString(ostd::Random::getui8(0, 40)));
|
m_list.addLine(ostd::Random::getString(ostd::Random::getui8(1, 40)));
|
||||||
}
|
}
|
||||||
m_list.getItem(10).setFontSize(40);
|
m_list.getLine(10).setFontSize(40);
|
||||||
m_list.getItem(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 {\
|
||||||
std::cout << *(selection[0]) << "\n";
|
std::cout << *(selection[0]) << "\n";
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue