Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5207487
Add GuiScrollContainer
oznogon Mar 3, 2026
d71b5e5
Edit GuiContainer formatting/style
oznogon Mar 9, 2026
4c0d2e1
Set default GuiScrollContainer click_change to 50
oznogon Mar 12, 2026
386f4fc
Simplify GuiScrollContainer scrollbar, add comments
oznogon Mar 14, 2026
8eb76fd
Hide scrollbar if ScrollMode is None
oznogon Mar 14, 2026
fd4ef81
Move variable init to header; pragma once
oznogon Mar 13, 2026
7bb21c1
Add GuiMultilineText, a GuiElement text subclass
oznogon Mar 13, 2026
345d7c9
Refactor GuiScrollText, GuiScrollFormattedText
oznogon Mar 13, 2026
2d7c534
Refactor GuiAdvancedScrollText
oznogon Mar 14, 2026
3edbe8f
Refactor ShipsLog with GuiAdvancedScrollText updates
oznogon Mar 14, 2026
e927ddb
Merge branch 'master' into refactor-guiscrolltext
oznogon Mar 15, 2026
8a39f97
Add GuiScrollContainer
oznogon Mar 3, 2026
1b391a8
Edit GuiContainer formatting/style
oznogon Mar 9, 2026
02bc96c
Rename ScissorRect refs to ClipRegion
oznogon Mar 21, 2026
0a70a47
Reimplement recursive descendent scrolling in GuiContainer
oznogon Mar 21, 2026
487774e
Consolidate scrolling to updateLayout
oznogon Mar 22, 2026
eb9fff0
Merge branch 'generic-scrolling-guicontainer' into refactor-guiscroll…
oznogon Mar 23, 2026
fe019f8
Fix GuiSelector merge issue
oznogon Mar 23, 2026
923599c
Replace colorConfig with theme styles
oznogon Mar 23, 2026
71dcc74
Add GuiScrollContainer
oznogon Mar 3, 2026
9efd869
Edit GuiContainer formatting/style
oznogon Mar 9, 2026
17a4896
Rename ScissorRect refs to ClipRegion
oznogon Mar 21, 2026
3a57d1d
Reimplement recursive descendent scrolling in GuiContainer
oznogon Mar 21, 2026
dc85825
Consolidate scrolling to updateLayout
oznogon Mar 22, 2026
98b89f3
Merge branch 'generic-scrolling-guicontainer' into refactor-guiscroll…
oznogon Apr 10, 2026
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
240 changes: 144 additions & 96 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)
: 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,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)
->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)
{
scrollbar->setValue(scrollbar->getValue() - value * text_size * 3.0f);
return true;
}
Loading
Loading