Skip to content
Draft
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
238 changes: 144 additions & 94 deletions src/gui/gui2_advancedscrolltext.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +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);
scrollbar->setPosition(0, 0, sp::Alignment::TopRight)->setSize(50, GuiElement::GuiSizeMax);
// 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);
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 @@ -16,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 @@ -39,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 @@ -60,90 +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);
scrollbar->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