Print messages using SkParagraph and new TextParagraph element

This commit is contained in:
Yousef Khadadeh 2025-10-15 16:33:24 +03:00
parent 9ceb4e1b8a
commit 921e111255
9 changed files with 147 additions and 16 deletions

View file

@ -94,6 +94,7 @@ set(sources "src/main.cpp"
"src/GUIStuff/Elements/MovableTabList.cpp"
"src/GUIStuff/Elements/PaintCircleMenu.cpp"
"src/GUIStuff/Elements/RotateWheel.cpp"
"src/GUIStuff/Elements/TextParagraph.cpp"
"include/sago/platform_folders.cpp"
"include/Helpers/Networking/NetLibrary.cpp"
"include/Helpers/Networking/NetServer.cpp"

View file

@ -29,6 +29,8 @@ FontData::FontData()
mgr = SkFontMgr_New_Custom_Empty();
#endif
map["Roboto"] = mgr->makeFromFile("data/fonts/Roboto-variable.ttf");
collection = sk_make_sp<skia::textlayout::FontCollection>();
collection->setDefaultFontManager(mgr);
}
FontData::~FontData() {

View file

@ -4,12 +4,14 @@
#include <include/core/SkFont.h>
#include <unordered_map>
#include <Eigen/Dense>
#include <modules/skparagraph/include/FontCollection.h>
using namespace Eigen;
struct FontData {
FontData();
sk_sp<SkFontMgr> mgr;
sk_sp<skia::textlayout::FontCollection> collection;
std::unordered_map<std::string, sk_sp<SkTypeface>> map;
~FontData();
};

View file

@ -5,6 +5,7 @@
#include <Eigen/Dense>
#include <include/core/SkFont.h>
#include <include/core/SkFontMgr.h>
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/svg/include/SkSVGDOM.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Serializers.hpp>
@ -95,6 +96,7 @@ struct UpdateInputData {
bool copy = false;
bool paste = false;
bool cut = false;
bool escape = false;
} key;
struct {
@ -107,6 +109,8 @@ struct UpdateInputData {
uint16_t fontSize = 20;
sk_sp<SkTypeface> textTypeface;
sk_sp<skia::textlayout::FontCollection> fontCollection;
std::string textInput;
bool hoverObstructed = false;
bool acceptingTextInput = false;

View file

@ -0,0 +1,28 @@
#include "TextParagraph.hpp"
#include <include/core/SkPath.h>
#include <iostream>
namespace GUIStuff {
void TextParagraph::update(UpdateInputData& io, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate) {
data = std::move(paragraph);
data->layout(width);
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIXED(data->getHeight())}
},
.custom = { .customData = this }
}) {
if(elemUpdate)
elemUpdate();
}
}
void TextParagraph::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
bb = get_bb(command);
if(data)
data->paint(canvas, bb.min.x(), bb.min.y());
}
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "Element.hpp"
#include <modules/skparagraph/include/Paragraph.h>
namespace GUIStuff {
class TextParagraph : public Element {
public:
void update(UpdateInputData& io, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
private:
SCollision::AABB<float> bb = SCollision::AABB<float>({0.0f, 0.0f}, {0.0f, 0.0f});
std::unique_ptr<skia::textlayout::Paragraph> data;
};
}

View file

@ -7,13 +7,21 @@
#include "Elements/SVGIcon.hpp"
#include "Elements/SelectableButton.hpp"
#include "Elements/RotateWheel.hpp"
#include "Elements/TextParagraph.hpp"
#include "Elements/MovableTabList.hpp"
#include "Elements/Element.hpp"
#include "Elements/TextParagraph.hpp"
#include <Helpers/ConvertVec.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <cmath>
#include <limits>
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
#include <modules/skparagraph/include/ParagraphStyle.h>
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/skparagraph/include/TextStyle.h>
#include <modules/skunicode/include/SkUnicode_icu.h>
namespace GUIStuff {
@ -28,11 +36,20 @@ GUIManager::GUIManager()
Clay_Dimensions GUIManager::clay_skia_measure_text(Clay_StringSlice str, Clay_TextElementConfig* config, void* userData) {
GUIManager* window = static_cast<GUIManager*>(userData);
SkFont font = window->io->get_font(config->fontSize);
SkFontMetrics metrics;
font.getMetrics(&metrics);
float nextText = font.measureText(str.chars, str.length, SkTextEncoding::kUTF8, nullptr);
return Clay_Dimensions(nextText, - metrics.fAscent + metrics.fDescent);
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(config->fontSize);
tStyle.setFontFamilies({SkString{"Roboto"}});
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, window->io->fontCollection, SkUnicodes::ICU::Make());
a.addText(str.chars, str.length);
std::unique_ptr<skia::textlayout::Paragraph> p = a.Build();
p->layout(std::numeric_limits<float>::max());
return Clay_Dimensions(p->getMaxIntrinsicWidth(), p->getHeight());
}
void GUIManager::begin() {
@ -92,12 +109,20 @@ void GUIManager::draw(SkCanvas* canvas) {
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData* config = &command->renderData.text;
SkPaint paint;
paint.setColor4f(convert_vec4<SkColor4f>(config->textColor));
SkFont f = io->get_font(config->fontSize);
SkFontMetrics metrics;
f.getMetrics(&metrics);
canvas->drawSimpleText(config->stringContents.chars, config->stringContents.length, SkTextEncoding::kUTF8, bb.x, (bb.y + bb.height - metrics.fDescent), io->get_font(config->fontSize), paint);
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(config->fontSize);
tStyle.setFontFamilies({SkString{"Roboto"}});
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, io->fontCollection, SkUnicodes::ICU::Make());
a.addText(config->stringContents.chars, config->stringContents.length);
std::unique_ptr<skia::textlayout::Paragraph> p = a.Build();
p->layout(std::numeric_limits<float>::max());
p->paint(canvas, bb.x, bb.y);
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
@ -679,6 +704,13 @@ bool GUIManager::rotate_wheel(const std::string& id, double* angle, float size,
return toRet;
}
void GUIManager::text_paragraph(const std::string& id, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate) {
push_id(id);
TextParagraph* e = insert_element<TextParagraph>();
e->update(*io, std::move(paragraph), width, elemUpdate);
pop_id();
}
void GUIManager::paint_circle_popup_menu(const std::string& id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function<void()>& elemUpdate) {
float size = 300.0f;
push_id(id);

View file

@ -14,6 +14,8 @@
#include <type_traits>
#include <clay.h>
#include <modules/skparagraph/include/Paragraph.h>
using namespace Eigen;
namespace GUIStuff {
@ -43,6 +45,7 @@ class GUIManager {
void input_text_field(const std::string& id, const std::string& name, std::string* val, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr);
void text_paragraph(const std::string& id, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate = nullptr);
void text_label_color(const std::string& val, const SkColor4f& color);
void text_label_size(const std::string& val, float modifier);
void text_label(const std::string& val);

View file

@ -18,6 +18,12 @@
#include <Helpers/StringHelpers.hpp>
#include <Helpers/VersionNumber.hpp>
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
#include <modules/skparagraph/include/ParagraphStyle.h>
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/skparagraph/include/TextStyle.h>
#include <modules/skunicode/include/SkUnicode_icu.h>
#define UPDATE_DOWNLOAD_URL "https://infinipaint.com/download.html"
#define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersion.txt"
@ -33,6 +39,7 @@ Toolbar::Toolbar(MainProgram& initMain):
{
io->textTypeface = main.fonts.map["Roboto"];
io->textFontMgr = main.fonts.mgr;
io->fontCollection = main.fonts.collection;
io->fontSize = 20;
load_default_palette();
@ -819,6 +826,7 @@ void Toolbar::bookmark_menu(bool justOpened) {
}
void Toolbar::chat_box() {
constexpr float CHATBOX_WIDTH = 700;
if(main.world->network_being_used()) {
CLAY({
.layout = {
@ -839,7 +847,7 @@ void Toolbar::chat_box() {
}
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(700), .height = CLAY_SIZING_FIT(0) },
.sizing = {.width = CLAY_SIZING_FIXED(CHATBOX_WIDTH), .height = CLAY_SIZING_FIT(0) },
.childGap = 0,
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM},
.layoutDirection = CLAY_TOP_TO_BOTTOM
@ -849,6 +857,7 @@ void Toolbar::chat_box() {
gui.obstructing_window();
gui.push_id("chat box");
if(chatBoxState == CHATBOXSTATE_JUSTOPEN || chatBoxState == CHATBOXSTATE_OPEN) {
gui.push_id("messages");
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
@ -859,7 +868,9 @@ void Toolbar::chat_box() {
},
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1)
}) {
int id = 0;
for(auto& chatMessage : main.world->chatMessages | std::views::reverse) {
id++;
SkColor4f c;
switch(chatMessage.color) {
case ChatMessage::COLOR_NORMAL:
@ -869,9 +880,23 @@ void Toolbar::chat_box() {
c = io->theme->fillColor4;
break;
}
gui.text_label_color(chatMessage.text, c);
gui.push_id(id);
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(io->fontSize);
tStyle.setFontFamilies({SkString{"Roboto"}});
tStyle.setForegroundColor(SkPaint{c});
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, io->fontCollection, SkUnicodes::ICU::Make());
a.addText(chatMessage.text.c_str(), chatMessage.text.length());
gui.text_paragraph("text", a.Build(), CHATBOX_WIDTH);
gui.pop_id();
}
}
gui.pop_id();
gui.left_to_right_line_layout([&]() {
gui.input_text("message input", &chatMessageInput);
@ -888,9 +913,9 @@ void Toolbar::chat_box() {
t.selection.selected = true;
chatBoxState = CHATBOXSTATE_OPEN;
}
if(main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed)
if(io->key.escape)
t.selection.selected = false;
if(main.input.key(InputManager::KEY_TEXT_ENTER).pressed) {
if(io->key.enter) {
main.world->send_chat_message(chatMessageInput);
t.selection.selected = false;
}
@ -902,7 +927,10 @@ void Toolbar::chat_box() {
else {
constexpr float DISPLAY_TIME = 8.0f;
constexpr float FADE_START_TIME = 7.0f;
gui.push_id("messages popup");
int id = 0;
for(auto& chatMessage : main.world->chatMessages | std::views::reverse) {
id++;
chatMessage.time.update_time_since();
if(chatMessage.time < DISPLAY_TIME) {
float a = 1.0f - lerp_time<float>(chatMessage.time, DISPLAY_TIME, FADE_START_TIME);
@ -925,11 +953,25 @@ void Toolbar::chat_box() {
},
.backgroundColor = convert_vec4<Clay_Color>(color_mul_alpha(io->theme->backColor1, a)),
}) {
gui.push_id(id);
gui.obstructing_window();
gui.text_label_color(chatMessage.text, color_mul_alpha(c, a));
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(io->fontSize);
tStyle.setFontFamilies({SkString{"Roboto"}});
tStyle.setForegroundColor(SkPaint{color_mul_alpha(c, a)});
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, io->fontCollection, SkUnicodes::ICU::Make());
a.addText(chatMessage.text.c_str(), chatMessage.text.length());
gui.text_paragraph("text", a.Build(), CHATBOX_WIDTH);
gui.pop_id();
}
}
}
gui.pop_id();
}
gui.pop_id();
}
@ -1910,6 +1952,7 @@ void Toolbar::initialize_io_before_update() {
io->key.copy = main.input.key(InputManager::KEY_TEXT_COPY).repeat;
io->key.paste = main.input.get_clipboard_paste_happened();
io->key.cut = main.input.key(InputManager::KEY_TEXT_CUT).repeat;
io->key.escape = main.input.key(InputManager::KEY_GENERIC_ESCAPE).repeat;
io->textInput = main.input.text.newInput;
io->clipboard.textInFunc = [&]() {