Fixed many issues with text paragraphs in GUI

This commit is contained in:
Yousef Khadadeh 2026-04-25 19:01:19 +03:00
parent 2d039dc47b
commit eb490906a0
13 changed files with 132 additions and 37 deletions

View file

@ -76,7 +76,7 @@ void TextBoxCanvasComponent::set_data_from(const CanvasComponent& other) {
void TextBoxCanvasComponent::init_text_box(DrawingProgram& drawP) {
textBox->set_font_data(drawP.world.main.fonts); // Getting a segfault relating to the paragraph cache means that the font collection hasn't been set yet
textBox->set_width(d.p2.x() - d.p1.x() - TEXTBOX_PADDING * 2.0f);
textBox->set_max_width(d.p2.x() - d.p1.x() - TEXTBOX_PADDING * 2.0f);
}
void TextBoxCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const {

View file

@ -18,6 +18,10 @@ namespace GUIStuff {
void Element::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {}
void Element::clear_bb() {
boundingBox = std::nullopt;
}
void Element::set_bounding_box_from_elem_data(const Clay_ElementData& elemData) {
if(elemData.found)
boundingBox = {{elemData.boundingBox.x, elemData.boundingBox.y}, {elemData.boundingBox.x + elemData.boundingBox.width, elemData.boundingBox.y + elemData.boundingBox.height}};

View file

@ -14,6 +14,7 @@ namespace GUIStuff {
void set_bounding_box_from_elem_data(const Clay_ElementData& elemData);
void set_parent_clipping_region(const std::optional<SCollision::AABB<float>>& bb);
const std::optional<SCollision::AABB<float>>& get_bb() const;
void clear_bb();
virtual ~Element() = default;
int16_t zIndex;

View file

@ -90,17 +90,23 @@ void FontPicker::layout(const Clay_ElementId& id, std::string* newFontName, cons
if(dropdownOpen) {
gui.set_z_index(gui.get_z_index() + 1, [&] {
gui.element<LayoutElement>("Font picker floating element", [&] (LayoutElement*, const Clay_ElementId& lId) {
gui.element<LayoutElement>("DROPDOWN", [&](LayoutElement*, const Clay_ElementId& lId) {
Clay_ElementData dropdownElemData = Clay_GetElementData(lId);
float calculatedDropdownMaxHeight = 0.0f;
if(dropdownElemData.found) {
calculatedDropdownMaxHeight = std::max(gui.io.windowSize.y() - dropdownElemData.boundingBox.y - 2.0f, 0.0f);
calculatedDropdownMaxHeight = std::min<float>(calculatedDropdownMaxHeight, 300);
}
CLAY(lId, {
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0, 300)},
.layoutDirection = CLAY_TOP_TO_BOTTOM
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0, calculatedDropdownMaxHeight)},
.childGap = 0
},
.backgroundColor = convert_vec4<Clay_Color>(gui.io.theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(4),
.floating = {
.offset = {
.y = 2
.y = 4
},
.zIndex = gui.get_z_index(),
.attachPoints = {
@ -114,16 +120,17 @@ void FontPicker::layout(const Clay_ElementId& id, std::string* newFontName, cons
.width = CLAY_BORDER_OUTSIDE(1)
}
}) {
scroll_area_many_entries(gui, "Font Selector", {
ElementHelpers::scroll_area_many_entries(gui, "dropdown scroll area", {
.entryHeight = ENTRY_HEIGHT,
.entryCount = sortedFontList.size(),
.clipHorizontal = true,
.elementContent = [&] (size_t i) {
.growing = true,
.elementContent = [&](size_t i) {
bool selectedEntry = val == i;
gui.element<LayoutElement>("elem", [&](LayoutElement*, const Clay_ElementId& lId2) {
CLAY(lId2, {
gui.element<LayoutElement>("elem", [&] (LayoutElement*, const Clay_ElementId& lId) {
CLAY(lId, {
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(25)},
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_GROW(0)},
.childGap = 1,
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
@ -138,7 +145,7 @@ void FontPicker::layout(const Clay_ElementId& id, std::string* newFontName, cons
entryColor = gui.io.theme->backColor1;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(25)},
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.padding = {.left = 2, .right = 2},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
},
@ -147,7 +154,9 @@ void FontPicker::layout(const Clay_ElementId& id, std::string* newFontName, cons
TextParagraph::Data d;
RichText::TextData::Paragraph& par = d.text.paragraphs.emplace_back();
par.text = sortedFontList[i];
d.width = 10000.0f; // Setting width to numeric_limits::max will lead to multiplying it by any number greater than 1 to = infinity, which will cause issues
d.allowNewlines = false;
d.ellipsis = false;
RichText::PositionedTextStyleMod& positionedMod = d.text.tStyleMods.emplace_back();
positionedMod.pos = {0, 0};
@ -174,13 +183,6 @@ void FontPicker::layout(const Clay_ElementId& id, std::string* newFontName, cons
gui.set_to_layout();
}
});
},
.innerContentExtraCallback = [&](const ScrollArea::InnerContentParameters& innerContentParams) {
if(jumpToFontName) {
if(val != std::numeric_limits<size_t>::max())
innerContentParams.scrollOffset->y() = -static_cast<float>(val) * ENTRY_HEIGHT;
jumpToFontName = false;
}
}
});
}

View file

@ -159,7 +159,7 @@ template <typename T> void TextBox<T>::input_mouse_motion_callback(const InputMa
template <typename T> void TextBox<T>::init_textbox(UpdateInputData& io) {
if(!textbox) {
textbox = std::make_shared<RichText::TextBox>();
textbox->set_width(std::numeric_limits<float>::max());
textbox->set_max_width(std::numeric_limits<float>::max());
textbox->set_font_data(io.fonts);
textbox->set_allow_newlines(false);
cur = std::make_shared<RichText::TextBox::Cursor>();

View file

@ -2,6 +2,7 @@
#include <include/core/SkPath.h>
#include <iostream>
#include "../GUIManager.hpp"
#include "Helpers/ConvertVec.hpp"
namespace GUIStuff {
@ -14,20 +15,32 @@ void TextParagraph::layout(const Clay_ElementId& id, const Data& newData) {
if(!textbox) {
d = newData;
textbox = std::make_unique<RichText::TextBox>();
textbox->set_width(d.width);
textbox->set_rich_text_data(d.text);
textbox->set_ellipsis(newData.ellipsis);
textbox->set_font_data(gui.io.fonts);
textbox->set_allow_newlines(true);
textbox->set_allow_newlines(newData.allowNewlines);
textbox->set_max_width(newData.maxGrowX);
textbox->set_max_height(newData.maxGrowY);
skia::textlayout::TextStyle tStyle;
tStyle.setFontFamilies(gui.io.fonts->get_default_font_families());
tStyle.setFontSize(gui.io.fontSize);
textbox->set_initial_text_style(tStyle);
fontSize = gui.io.fontSize;
redraw = true;
}
if(d.width != newData.width) {
d.width = newData.width;
textbox->set_width(d.width);
if(d.allowNewlines != newData.allowNewlines) {
d.allowNewlines = newData.allowNewlines;
textbox->set_allow_newlines(d.allowNewlines);
redraw = true;
}
if(gui.io.fontSize != fontSize) {
fontSize = gui.io.fontSize;
skia::textlayout::TextStyle tStyle;
tStyle.setFontFamilies(gui.io.fonts->get_default_font_families());
tStyle.setFontSize(gui.io.fontSize);
textbox->set_initial_text_style(tStyle);
redraw = true;
}
@ -37,12 +50,29 @@ void TextParagraph::layout(const Clay_ElementId& id, const Data& newData) {
redraw = true;
}
if(d.maxGrowX != newData.maxGrowX) {
d.maxGrowX = newData.maxGrowX;
textbox->set_max_width(d.maxGrowX);
redraw = true;
}
if(d.maxGrowY != newData.maxGrowY) {
d.maxGrowY = newData.maxGrowY;
textbox->set_max_height(d.maxGrowY);
redraw = true;
}
if(d.ellipsis != newData.ellipsis) {
d.ellipsis = newData.ellipsis;
textbox->set_ellipsis(d.ellipsis);
redraw = true;
}
if(redraw)
gui.invalidate_draw_element(this);
CLAY(id, {
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(d.width), .height = CLAY_SIZING_FIXED(textbox->get_height())}
.sizing = {.width = CLAY_SIZING_GROW(0, textbox->get_width()), .height = CLAY_SIZING_FIXED(textbox->get_height())}
},
.custom = { .customData = this }
}) {}
@ -54,7 +84,7 @@ void TextParagraph::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_Render
canvas->translate(bb.min.x(), bb.min.y());
skia::textlayout::TextStyle tStyle;
tStyle.setFontFamilies(gui.io.fonts->get_default_font_families());
tStyle.setFontSize(gui.io.fontSize);
tStyle.setFontSize(fontSize);
tStyle.setForegroundPaint(SkPaint{io.theme->frontColor1});
textbox->set_initial_text_style(tStyle);
textbox->paint(canvas, { .skiaAA = skiaAA });

View file

@ -8,7 +8,10 @@ class TextParagraph : public Element {
public:
struct Data {
RichText::TextData text;
float width;
float maxGrowX = 10000.0f;
float maxGrowY = 0.0f;
bool ellipsis = true;
bool allowNewlines = true;
bool operator==(const Data& d) const = default;
bool operator!=(const Data& d) const = default;
};
@ -18,6 +21,7 @@ class TextParagraph : public Element {
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
private:
Data d;
float fontSize;
std::unique_ptr<RichText::TextBox> textbox;
};

View file

@ -544,7 +544,7 @@ void GUIManager::layout_if_necessary() {
}
void GUIManager::layout() {
constexpr int LAYOUT_RUN_COUNT = 4;
constexpr int LAYOUT_RUN_COUNT = 6;
for(int i = 0; i < LAYOUT_RUN_COUNT; i++)
single_layout_run();
}

View file

@ -3,7 +3,6 @@
#include "TextStyleModifier.hpp"
#include "cereal/archives/portable_binary.hpp"
#include <include/core/SkFontMetrics.h>
#include <limits>
#include <modules/skparagraph/include/DartTypes.h>
#include <modules/skparagraph/include/Metrics.h>
#include <modules/skparagraph/include/Paragraph.h>
@ -838,13 +837,36 @@ int TextBox::get_line_number_at_from_byte_text_pos(TextPosition pos) {
return paragraphs[pos.fParagraphIndex].p->getLineNumberAt(renderPos.fTextByteIndex);
}
void TextBox::set_width(float newWidth) {
void TextBox::set_max_width(float newWidth) {
if(width != newWidth) {
width = std::max(newWidth, 4.0f);
needsRebuild = true;
}
}
void TextBox::set_max_height(float newMaxHeight) {
if(maxHeight != newMaxHeight) {
maxHeight = newMaxHeight;
needsRebuild = true;
}
}
void TextBox::set_ellipsis(bool newEllipsis) {
if(ellipsis != newEllipsis) {
ellipsis = newEllipsis;
needsRebuild = true;
}
}
float TextBox::get_width() {
rebuild();
float toRet = 0.0f;
for(auto& p : paragraphs)
toRet = std::max(toRet, p.p->getLongestLine());
toRet += 1.0f;
return toRet;
}
float TextBox::get_height() {
rebuild();
return paragraphs.back().heightOffset + paragraphs.back().p->getHeight();
@ -875,6 +897,10 @@ void TextBox::rebuild() {
ParagraphData& pData = paragraphs[pIndex];
skia::textlayout::ParagraphStyle pStyle = pData.pStyleData.get_paragraph_style();
pStyle.setTextStyle(tStyle);
if(ellipsis)
pStyle.setEllipsis(SkString("..."));
if(maxHeight != 0.0f)
pStyle.setHeight(maxHeight);
if(!newlinesAllowed) {
pStyle.setTextHeightBehavior(skia::textlayout::kDisableAll);

View file

@ -107,7 +107,9 @@ class TextBox {
};
TextPosition move(Movement movement, TextPosition pos, std::optional<float>* previousX = nullptr, bool flipDependingOnTextDirection = false);
void set_width(float newWidth);
void set_max_width(float newWidth);
void set_max_height(float newMaxHeight);
void set_ellipsis(bool newEllipsis);
TextPosition insert(TextPosition pos, std::string_view textToInsert, const std::optional<TextStyleModifier::ModifierMap>& inputModMap = std::nullopt);
TextPosition remove(TextPosition p1, TextPosition p2);
void set_font_data(const std::shared_ptr<FontData>& fD);
@ -129,6 +131,8 @@ class TextBox {
SELECT_ALL
};
float get_width();
void set_initial_text_style(const skia::textlayout::TextStyle& tStyle);
void set_initial_text_style_modifier(const std::shared_ptr<TextStyleModifier>& modifier);
void set_text_style_modifier_between(TextPosition p1, TextPosition p2, const std::shared_ptr<TextStyleModifier>& modifier);
@ -193,7 +197,9 @@ class TextBox {
int get_line_number_at_from_byte_text_pos(TextPosition pos);
bool newlinesAllowed = true;
float width = 0.0f;
bool ellipsis = false;
float width = 4.0f;
float maxHeight = 0.0f;
bool needsRebuild = true;
unsigned tabWidth = 4;
std::shared_ptr<FontData> fontData;

View file

@ -9,6 +9,7 @@
#include "../GUIStuff/Elements/ImageDisplay.hpp"
#include "../GUIStuff/Elements/LayoutElement.hpp"
#include "../GUIStuff/Elements/SVGIcon.hpp"
#include "../GUIStuff/Elements/TextParagraph.hpp"
#include "../GUIStuff/Elements/PositionAdjustingPopupMenu.hpp"
#include "../World.hpp"
#include "Helpers/StringHelpers.hpp"
@ -527,7 +528,7 @@ void FileSelectScreen::file_view() {
}
CLAY_AUTO_ID({
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)},
.sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
.childGap = 6,
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_TOP_TO_BOTTOM
@ -556,7 +557,24 @@ void FileSelectScreen::file_view() {
.radius = 20
});
}
text_label(gui, fileList[i].fileName);
gui.element<LayoutElement>("file name layout elem", [&](LayoutElement* l, const Clay_ElementId& lId) {
CLAY(lId, {
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP },
},
}) {
if(l->get_bb()) {
RichText::TextData fileNameText;
fileNameText.paragraphs.emplace_back(fileList[i].fileName);
gui.element<TextParagraph>("file name", TextParagraph::Data{
.text = fileNameText,
.maxGrowX = l->get_bb().value().width(),
.maxGrowY = 0.0f
});
}
}
});
text_label_light(gui, fileList[i].lastModifyDate);
}
}
@ -569,6 +587,7 @@ void FileSelectScreen::file_view() {
.entryHeight = 150.0f,
.entryCount = fileList.size(),
.clipHorizontal = true,
.xElementSize = CLAY_SIZING_FIT(0),
.elementContent = [&](size_t i) {
fileButton(i, true, Vector2f{100.0f, 100.0f});
}

View file

@ -1039,7 +1039,8 @@ void Toolbar::chat_box() {
gui.new_id(id++, [&] {
gui.element<TextParagraph>("text", TextParagraph::Data{
.text = build_paragraph_from_chat_message(chatMessage, 1.0f),
.width = CHATBOX_WIDTH
.maxGrowX = CHATBOX_WIDTH,
.ellipsis = false
});
});
}
@ -1088,7 +1089,8 @@ void Toolbar::chat_box() {
}) {
gui.element<TextParagraph>("message", TextParagraph::Data{
.text = build_paragraph_from_chat_message(chatMessage, a),
.width = CHATBOX_WIDTH
.maxGrowX = CHATBOX_WIDTH,
.ellipsis = false
});
}
});

View file

@ -3,6 +3,7 @@
#include "Helpers/StringHelpers.hpp"
#include "Helpers/FileDownloader.hpp"
#include "Screens/FileSelectScreen.hpp"
#include "Screens/DrawingProgramScreen.hpp"
#include "VersionConstants.hpp"
#include "include/gpu/GpuTypes.h"
#include <SDL3/SDL_filesystem.h>