Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ set(GUI_LIB_SOURCES
src/gui/gui2_entrylist.cpp
src/gui/gui2_progressbar.cpp
src/gui/gui2_progressslider.cpp
src/gui/gui2_multilinetext.cpp
src/gui/gui2_scrolltext.cpp
src/gui/gui2_scrollcontainer.cpp
src/gui/gui2_advancedscrolltext.cpp
src/gui/gui2_button.cpp
src/gui/gui2_resizabledialog.cpp
Expand Down Expand Up @@ -194,6 +196,8 @@ set(GUI_LIB_SOURCES
src/gui/gui2_resizabledialog.h
src/gui/gui2_rotationdial.h
src/gui/gui2_scrollbar.h
src/gui/gui2_scrollcontainer.h
src/gui/gui2_multilinetext.h
src/gui/gui2_scrolltext.h
src/gui/gui2_selector.h
src/gui/gui2_slider.h
Expand Down
241 changes: 144 additions & 97 deletions src/gui/gui2_advancedscrolltext.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,78 @@
#include "gui2_advancedscrolltext.h"

GuiAdvancedScrollText::GuiAdvancedScrollText(GuiContainer* owner, string id)
: GuiElement(owner, id), text_size(30.0f), rect_width(rect.size.x), max_prefix_width(0.0f), mouse_scroll_steps(25)
: GuiScrollContainer(owner, id)
{
scrollbar = new GuiScrollbar(this, id + "_SCROLL", 0, 1, 0, nullptr);
// Calculate scrolling a one-line entry by scrollbar arrow buttons.
scrollbar
->setClickChange(sp::RenderTarget::getDefaultFont()->prepare("1", 32, text_size, {255, 255, 255, 255}, rect.size, sp::Alignment::TopLeft).getUsedAreaSize().y)
->setPosition(0.0f, 0.0f, sp::Alignment::TopRight)
->setSize(50.0f, GuiElement::GuiSizeMax);
entry_canvas = new EntryCanvas(this, id + "_CANVAS");
entry_canvas->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax);
}

GuiAdvancedScrollText::EntryCanvas::EntryCanvas(GuiAdvancedScrollText* owner, const string& id)
: GuiElement(owner, id)
{
}

void GuiAdvancedScrollText::EntryCanvas::onDraw(sp::RenderTarget& renderer)
{
auto* scroll_text = static_cast<GuiAdvancedScrollText*>(owner);

// Re-prep all entries if the canvas width has changed. Two passes: first
// collect prefix widths to find the max, then prep text with that width.
if (prev_canvas_width != rect.size.x)
{
prev_canvas_width = rect.size.x;
scroll_text->prefix_widths.clear();
scroll_text->max_prefix_width = 0.0f;

for (auto& e : scroll_text->entries)
{
scroll_text->prepEntryPrefix(e, prev_canvas_width);
const float pw = e.prepared_prefix.getUsedAreaSize().x;
scroll_text->prefix_widths[pw] += 1;
scroll_text->max_prefix_width = std::max(scroll_text->max_prefix_width, pw);
}

const float text_col_width = prev_canvas_width - scroll_text->max_prefix_width;
for (auto& e : scroll_text->entries)
scroll_text->prepEntryText(e, text_col_width);
}

// Render entries stacked top-to-bottom, preserving the leading gap from the
// original implementation so that ascenders of the first line are visible.
float y_offset = scroll_text->text_size + 12.0f;

for (auto& e : scroll_text->entries)
{
const float height = e.prepared_text.getUsedAreaSize().y;
const float y_start = e.prepared_prefix.data.empty() ? 0.0f
: e.prepared_prefix.data[0].position.y;

// Copy prepared strings and remap their y positions to y_offset.
auto draw_prefix = e.prepared_prefix;
auto draw_text = e.prepared_text;

for (auto& g : draw_prefix.data)
g.position.y = y_offset;
for (auto& g : draw_text.data)
g.position.y = (g.position.y - y_start) + y_offset;

renderer.drawText(rect, draw_prefix);
renderer.drawText(
sp::Rect{
rect.position.x + scroll_text->max_prefix_width,
rect.position.y,
rect.size.x - scroll_text->max_prefix_width,
rect.size.y
},
draw_text
);

y_offset += height;
}

// Auto-size the canvas height to fit all entries.
setSize(GuiElement::GuiSizeMax, y_offset);
layout.fill_height = false;
}

GuiAdvancedScrollText* GuiAdvancedScrollText::addEntry(string prefix, string text, glm::u8vec4 color, unsigned int seq)
Expand All @@ -18,20 +82,81 @@ GuiAdvancedScrollText* GuiAdvancedScrollText::addEntry(string prefix, string tex
entry.text = text;
entry.color = color;
entry.seq = seq;
prepEntry(entry);

const float canvas_width = entry_canvas->getRect().size.x;

// Prep the prefix to get its width.
prepEntryPrefix(entry, canvas_width);
const float new_prefix_width = entry.prepared_prefix.getUsedAreaSize().x;
prefix_widths[new_prefix_width] += 1;

if (new_prefix_width > max_prefix_width)
{
// New prefix is wider: re-prep all existing entries' text with the
// narrower text column.
max_prefix_width = new_prefix_width;
const float text_col_width = canvas_width - max_prefix_width;
for (auto& e : entries)
prepEntryText(e, text_col_width);
}
else
{
prepEntryText(entry, canvas_width - max_prefix_width);
}

if (auto_scroll_down) scrollToFraction(1.0f);
return this;
}

unsigned int GuiAdvancedScrollText::getEntryCount() const
GuiAdvancedScrollText* GuiAdvancedScrollText::setTextSize(float new_text_size)
{
return entries.size();
text_size = std::max(1.0f, new_text_size);

// Re-prep all entries with the new text size. The EntryCanvas::onDraw
// resize path will also catch this on next draw if canvas width changed,
// but we force it here for the height-only case.
const float canvas_width = entry_canvas->getRect().size.x;
if (canvas_width > 0.0f)
{
prefix_widths.clear();
max_prefix_width = 0.0f;

for (auto& e : entries)
{
prepEntryPrefix(e, canvas_width);
const float pw = e.prepared_prefix.getUsedAreaSize().x;
prefix_widths[pw] += 1;
max_prefix_width = std::max(max_prefix_width, pw);
}

const float text_col_width = canvas_width - max_prefix_width;
for (auto& e : entries)
prepEntryText(e, text_col_width);
}

return this;
}

GuiAdvancedScrollText* GuiAdvancedScrollText::setTextSize(float text_size)
void GuiAdvancedScrollText::prepEntryPrefix(Entry& e, float canvas_width)
{
this->text_size = std::max(1.0F, text_size);
scrollbar->setClickChange(sp::RenderTarget::getDefaultFont()->prepare("1", 32, text_size, {255, 255, 255, 255}, rect.size, sp::Alignment::TopLeft).getUsedAreaSize().y);
return this;
e.prepared_prefix = sp::RenderTarget::getDefaultFont()->prepare(
e.prefix, 32, text_size, {255, 255, 255, 255},
{canvas_width, 10000.0f}, sp::Alignment::TopLeft
);
}

void GuiAdvancedScrollText::prepEntryText(Entry& e, float text_column_width)
{
e.prepared_text = sp::RenderTarget::getDefaultFont()->prepare(
e.text, 32, text_size, e.color,
{text_column_width, 10000.0f},
sp::Alignment::TopLeft, sp::Font::FlagLineWrap
);
}

unsigned int GuiAdvancedScrollText::getEntryCount() const
{
return entries.size();
}

string GuiAdvancedScrollText::getEntryText(int index) const
Expand All @@ -41,15 +166,6 @@ string GuiAdvancedScrollText::getEntryText(int index) const
return entries[index].text;
}

GuiAdvancedScrollText::Entry GuiAdvancedScrollText::prepEntry(GuiAdvancedScrollText::Entry& e){
e.prepared_prefix = sp::RenderTarget::getDefaultFont()->prepare(e.prefix, 32, text_size, {255, 255, 255, 255}, rect.size, sp::Alignment::TopLeft);
const float entry_prefix_width = e.prepared_prefix.getUsedAreaSize().x;
prefix_widths[entry_prefix_width] += 1;
max_prefix_width = std::max(max_prefix_width, entry_prefix_width);
e.prepared_text = sp::RenderTarget::getDefaultFont()->prepare(e.text, 32, text_size, e.color, {rect.size.x - max_prefix_width - 50.0f, rect.size.y}, sp::Alignment::TopLeft, sp::Font::FlagLineWrap | sp::Font::FlagClip);
return e;
}

unsigned int GuiAdvancedScrollText::getEntrySeq(int index) const
{
if (index < 0 || index >= static_cast<int>(getEntryCount()))
Expand All @@ -62,91 +178,22 @@ GuiAdvancedScrollText* GuiAdvancedScrollText::removeEntry(int index)
if (index < 0 || index >= static_cast<int>(getEntryCount()))
return this;

// Find new max prefix if entry was the last one with the current max
const float entry_prefix_width = entries[index].prepared_prefix.getUsedAreaSize().x;
bool last_with_width = false;
if(--prefix_widths[entry_prefix_width] == 0){
last_with_width = true;
if (--prefix_widths[entry_prefix_width] == 0)
{
prefix_widths.erase(entry_prefix_width);
}
if (entry_prefix_width == max_prefix_width && last_with_width){
max_prefix_width = prefix_widths.end()->first;
if (entry_prefix_width == max_prefix_width)
max_prefix_width = prefix_widths.empty() ? 0.0f : prefix_widths.rbegin()->first;
}

entries.erase(entries.begin() + index);

return this;
}

GuiAdvancedScrollText* GuiAdvancedScrollText::clearEntries()
{
entries.clear();
prefix_widths.clear();
max_prefix_width = 0;
max_prefix_width = 0.0f;
return this;
}

void GuiAdvancedScrollText::onDraw(sp::RenderTarget& renderer)
{
const bool is_resized = rect_width != rect.size.x;
if (is_resized) {
rect_width = rect.size.x;
prefix_widths.clear();
max_prefix_width = 0;
}

//Draw the visible entries
float draw_offset = -scrollbar->getValue() + text_size + 12.0f;

for(Entry& e : entries)
{
// Window width has changed. Re-prep fonts.
if (is_resized){ prepEntry(e); }

const float height = e.prepared_text.getUsedAreaSize().y;

if (draw_offset + height > 0
&& draw_offset < rect.size.y)
{
const float y_start = e.prepared_prefix.data[0].position.y;

auto prepared_prefix = e.prepared_prefix;
auto prepared_text = e.prepared_text;
for(auto& g : prepared_prefix.data)
{
g.position.y = draw_offset;
}
for(auto& g : prepared_text.data)
{
g.position.y = (g.position.y - y_start) + draw_offset;
}
renderer.drawText(rect, prepared_prefix, sp::Font::FlagClip);
renderer.drawText(sp::Rect(rect.position.x + max_prefix_width, rect.position.y, rect.size.x - 50 - max_prefix_width, rect.size.y), prepared_text, sp::Font::FlagClip);
}

draw_offset += height;
}

//Calculate how many lines we have to display in total.
const int line_count = (draw_offset - text_size - 12.0f) + scrollbar->getValue();

//Check if we need to update the scroll bar.
if (scrollbar->getMax() != line_count)
{
const int diff = line_count - scrollbar->getMax();
scrollbar
->setRange(0, line_count)
->setValueSize(rect.size.y);
if (auto_scroll_down)
scrollbar->setValue(scrollbar->getValue() + diff);
}

scrollbar->setVisible(rect.size.y > 100);
}

bool GuiAdvancedScrollText::onMouseWheelScroll(glm::vec2 position, float value)
{
float range = scrollbar->getCorrectedMax() - scrollbar->getMin();
scrollbar->setValue((scrollbar->getValue() - value * range / mouse_scroll_steps) );
return true;
}
Loading
Loading