diff --git a/CMakeLists.txt b/CMakeLists.txt index bfaf862..653128e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(sources "src/main.cpp" "src/Server/MainServer.cpp" "src/Server/ServerData.cpp" "src/RichTextBox/RichTextBox.cpp" + "src/RichTextBox/TextStyleModifier.cpp" "src/DrawingProgram/DrawingProgramCache.cpp" "src/DrawingProgram/DrawingProgram.cpp" "src/DrawingProgram/BrushTool.cpp" diff --git a/include/Helpers/Serializers.hpp b/include/Helpers/Serializers.hpp index d5ef997..99822fe 100644 --- a/include/Helpers/Serializers.hpp +++ b/include/Helpers/Serializers.hpp @@ -4,9 +4,22 @@ #include #include #include "FixedPoint.hpp" +#include #include +template void save(Archive& ar, SkString const & str) { + ar(cereal::make_size_tag(static_cast(str.size()))); + ar(cereal::binary_data(str.data(), str.size() * sizeof(char))); +} + +template void load(Archive& ar, SkString& str) { + uint64_t size; + ar(cereal::make_size_tag(size)); + str.resize(static_cast(size)); + ar(cereal::binary_data(const_cast(str.data()), static_cast(size) * sizeof(char))); +} + namespace cereal { // https://stackoverflow.com/questions/22884216/serializing-eigenmatrix-using-cereal-library (with some modifications using constexpr) diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 2e1241f..a7c1895 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -26,11 +26,7 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) { //}); t.gui.pop_id(); - TextBoxEditToolAllData newData = get_all_data(a); - bool editHappened = newData != oldData; - if(editHappened) - oldData = newData; - return editHappened; + return true; // NOTE: In general, the update data should always be sent even if no changes were made, because we're sending the data over an unreliable channel. } void TextBoxEditTool::commit_edit_updates(const std::shared_ptr& comp, std::any& prevData) { @@ -74,8 +70,7 @@ void TextBoxEditTool::edit_start(EditTool& editTool, const std::shared_ptr(); Vector2f textSelectPos = a->get_mouse_pos(drawP); textbox->process_mouse_left_button(*cur, textSelectPos, true, false, false); - oldData = get_all_data(a); - prevData = oldData; + prevData = get_all_data(a); a->d.editing = true; a->client_send_update(drawP, false); editTool.add_point_handle({&a->d.p1, nullptr, &a->d.p2}); diff --git a/src/DrawingProgram/TextBoxEditTool.hpp b/src/DrawingProgram/TextBoxEditTool.hpp index ae5e143..36306f9 100644 --- a/src/DrawingProgram/TextBoxEditTool.hpp +++ b/src/DrawingProgram/TextBoxEditTool.hpp @@ -18,10 +18,6 @@ class TextBoxEditTool : public DrawingProgramEditToolBase { struct TextBoxEditToolAllData { DrawTextBox::Data textboxData; RichTextBox::RichTextData richText; - bool operator==(const TextBoxEditToolAllData& o) const = default; - bool operator!=(const TextBoxEditToolAllData& o) const = default; }; TextBoxEditToolAllData get_all_data(const std::shared_ptr& a); - - TextBoxEditToolAllData oldData; }; diff --git a/src/GUIStuff/Elements/SelectableButton.cpp b/src/GUIStuff/Elements/SelectableButton.cpp index 96bb189..d183cd0 100644 --- a/src/GUIStuff/Elements/SelectableButton.cpp +++ b/src/GUIStuff/Elements/SelectableButton.cpp @@ -38,7 +38,7 @@ void SelectableButton::update(UpdateInputData& io, DrawType drawType, const std: .cornerRadius = CLAY_CORNER_RADIUS(4), .border = { .color = convert_vec4(borderColor), - .width = CLAY_BORDER_OUTSIDE(1) + .width = CLAY_BORDER_OUTSIDE(2) } }) { CLAY({.layout = { diff --git a/src/GUIStuff/Elements/TextBox.hpp b/src/GUIStuff/Elements/TextBox.hpp index 7836bfa..98f2dd5 100644 --- a/src/GUIStuff/Elements/TextBox.hpp +++ b/src/GUIStuff/Elements/TextBox.hpp @@ -59,13 +59,23 @@ template class TextBox : public Element { canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height())); - canvas->translate(bb.min.x(), bb.min.y()); + canvas->translate(bb.min.x() + 2.0f, bb.min.y()); RichTextBox::PaintOpts paintOpts; if(selection.selected) paintOpts.cursor = *cur; paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB}; + auto fontFamiliesMod = std::make_shared(); + fontFamiliesMod->families.emplace_back("Roboto"); + textbox->set_initial_text_style_modifier(fontFamiliesMod); + auto fontSizeMod = std::make_shared(); + fontSizeMod->size = io.fontSize; + textbox->set_initial_text_style_modifier(fontSizeMod); + auto fontColorMod = std::make_shared(); + fontColorMod->color = convert_vec4(io.theme->frontColor1); + textbox->set_initial_text_style_modifier(fontColorMod); + textbox->paint(canvas, paintOpts); canvas->restore(); @@ -80,6 +90,7 @@ template class TextBox : public Element { textbox = std::make_shared(); textbox->set_font_collection(io.fontCollection); textbox->set_allow_newlines(!singleLine); + cur = std::make_shared(); if(data) { diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index eaa3841..b6ff55f 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -209,6 +209,16 @@ void GUIManager::draw(SkCanvas* canvas) { break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + Clay_ClipRenderData* clip = &command->renderData.clip; + if(!clip->horizontal) { + bb.x = 0.0f; + bb.width = std::numeric_limits::max(); + } + if(!clip->vertical) { + bb.y = 0.0f; + bb.height = std::numeric_limits::max(); + } + canvas->save(); SkRect clipRect = SkRect::MakeXYWH(bb.x - 1.0f, bb.y - 1.0f, bb.width + 1.0f, bb.height + 1.0f); canvas->clipRect(clipRect); @@ -578,6 +588,7 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std:: CLAY({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = 1, .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, .layoutDirection = CLAY_TOP_TO_BOTTOM } @@ -727,7 +738,7 @@ void GUIManager::scroll_bar_many_entries_area(const std::string& uniqueId, float scrollAmount = std::fabs(scrollAmount); float entryHeight = 25.0f; size_t startPoint = scrollAmount / entryHeight; - size_t elementsContainable = (containerHeight / entryHeight) + 1; + size_t elementsContainable = (containerHeight / entryHeight) + 2; // Displaying 2 more elements ensures that there isn't any empty space in the container size_t endPoint = std::min(entryCount, startPoint + elementsContainable); if(startPoint != 0) { diff --git a/src/RichTextBox/RichTextBox.cpp b/src/RichTextBox/RichTextBox.cpp index ae00041..e0c6231 100644 --- a/src/RichTextBox/RichTextBox.cpp +++ b/src/RichTextBox/RichTextBox.cpp @@ -1,4 +1,5 @@ #include "RichTextBox.hpp" +#include "TextStyleModifier.hpp" #include #include #include @@ -9,6 +10,37 @@ #include #include #include +#include + +void RichTextBox::RichTextData::Paragraph::save(cereal::PortableBinaryOutputArchive& a) const { + a(text); + a(static_cast(tStyles.size())); + for(auto& [pos, styles] : tStyles) { + a(pos, static_cast(styles.size())); + for(auto& [styleType, style] : styles) + a(static_cast(styleType), *style); + } +} + +void RichTextBox::RichTextData::Paragraph::load(cereal::PortableBinaryInputArchive& a) { + a(text); + uint32_t styleCount; + a(styleCount); + tStyles.clear(); + for(uint32_t i = 0; i < styleCount; i++) { + uint32_t pos; + uint16_t styleCountAtPos; + a(pos, styleCountAtPos); + TextModAtPosContainer& mods = tStyles[pos]; + for(uint16_t j = 0; j < styleCountAtPos; j++) { + uint16_t styleType; + a(styleType); + std::shared_ptr modifier = TextStyleModifier::allocate_modifier(static_cast(styleType)); + a(*modifier); + mods.emplace(static_cast(styleType), modifier); + } + } +} bool RichTextBox::TextPosition::operator<(const RichTextBox::TextPosition& o) const { return (this->fParagraphIndex < o.fParagraphIndex) || (this->fParagraphIndex == o.fParagraphIndex && this->fTextByteIndex < o.fTextByteIndex); @@ -50,13 +82,13 @@ void RichTextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool sh if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(Movement::LEFT, cur.pos)); + cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::LEFT_WORD : Movement::LEFT, cur.pos)); break; case InputKey::DELETE: if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); else - cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(Movement::RIGHT, cur.pos)); + cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::RIGHT_WORD : Movement::RIGHT, cur.pos)); break; case InputKey::ENTER: process_text_input(cur, "\n"); @@ -217,7 +249,13 @@ RichTextBox::TextPosition RichTextBox::get_text_pos_closest_to_point(Vector2f po } RichTextBox::RichTextData RichTextBox::get_rich_text_data() { - return {get_string()}; + RichTextData toRet; + for(auto& p : paragraphs) { + toRet.paragraphs.emplace_back(); + toRet.paragraphs.back().text = p.text; + toRet.paragraphs.back().tStyles = p.tStyles; + } + return toRet; } void RichTextBox::clear_text() { @@ -231,7 +269,15 @@ void RichTextBox::set_string(const std::string& str) { } void RichTextBox::set_rich_text_data(const RichTextData& richText) { - set_string(richText.text); + paragraphs.clear(); + for(auto& p : richText.paragraphs) { + paragraphs.emplace_back(); + paragraphs.back().text = p.text; + paragraphs.back().tStyles = p.tStyles; + } + if(richText.paragraphs.empty()) + paragraphs.emplace_back(); + needsRebuild = true; } std::string RichTextBox::get_string() { @@ -261,6 +307,16 @@ std::string RichTextBox::get_text_between(TextPosition p1, TextPosition p2) { return toRet; } +void RichTextBox::set_initial_text_style_modifier(const std::shared_ptr& modifier) { + std::shared_ptr& modToSet = paragraphs.back().tStyles[0][modifier->get_type()]; + needsRebuild |= ((modToSet == nullptr) || (!modifier->equivalent(*modToSet))); + modToSet = modifier; +} + +void RichTextBox::set_text_style_modifier_between(TextPosition p1, TextPosition p2, const std::shared_ptr& modifier) { + needsRebuild = true; +} + RichTextBox::TextPosition RichTextBox::move(Movement movement, TextPosition pos, std::optional* previousX, bool flipDependingOnTextDirection) { if(pos.fParagraphIndex >= paragraphs.size()) { pos.fParagraphIndex = paragraphs.size() - 1; @@ -457,18 +513,35 @@ void RichTextBox::set_font_collection(const sk_sp pData.text.size()) + break; + + for(auto& [styleType, styleMod] : tStylesAtPos) + styleMod->modify_text_style(tStyle); + if(startPos != pos) { + a.pushStyle(tStyle); + a.addText(pData.text.c_str() + startPos, pos - startPos); + a.pop(); + } + startPos = pos; + } + + if(startPos < pData.text.size()) { + a.pushStyle(tStyle); + a.addText(pData.text.c_str() + startPos, pData.text.size() - startPos); + a.pop(); + } + pData.p = a.Build(); pData.p->layout(width); pData.heightOffset = heightOffset; diff --git a/src/RichTextBox/RichTextBox.hpp b/src/RichTextBox/RichTextBox.hpp index 6cce41d..d68ef2a 100644 --- a/src/RichTextBox/RichTextBox.hpp +++ b/src/RichTextBox/RichTextBox.hpp @@ -3,10 +3,16 @@ #include #include #include +#include #include "../SharedTypes.hpp" +#include "TextStyleModifier.hpp" +#include "cereal/archives/portable_binary.hpp" class RichTextBox { public: + typedef std::unordered_map> TextModAtPosContainer; + typedef std::map TextModContainer; + RichTextBox(); enum class Movement { @@ -48,15 +54,16 @@ class RichTextBox { }; struct RichTextData { - std::string text; - bool operator==(const RichTextData& o) const = default; - bool operator!=(const RichTextData& o) const = default; - template void save(Archive& a) const { - a(text); - } - template void load(Archive& a) { - a(text); + struct Paragraph { + std::string text; + TextModContainer tStyles; + void save(cereal::PortableBinaryOutputArchive& a) const; + void load(cereal::PortableBinaryInputArchive& a); + }; + template void serialize(Archive& a) { + a(paragraphs); } + std::vector paragraphs; }; TextPosition move(Movement movement, TextPosition pos, std::optional* previousX = nullptr, bool flipDependingOnTextDirection = false); @@ -81,6 +88,9 @@ class RichTextBox { SELECT_ALL }; + void set_initial_text_style_modifier(const std::shared_ptr& modifier); + void set_text_style_modifier_between(TextPosition p1, TextPosition p2, const std::shared_ptr& modifier); + RichTextData get_rich_text_data(); void set_rich_text_data(const RichTextData& richText); void clear_text(); @@ -100,6 +110,7 @@ class RichTextBox { static size_t next_grapheme(const std::string& text, size_t textBytePos); static size_t prev_grapheme(const std::string& text, size_t textBytePos); static TextPosition get_text_pos_from_byte_pos(const std::string& text, size_t textBytePos); + size_t get_byte_pos_from_text_pos(TextPosition textPos); void rects_between_text_positions_func(TextPosition p1, TextPosition p2, std::function f); @@ -112,7 +123,6 @@ class RichTextBox { bool newlinesAllowed = true; float width = 0.0f; - float fontSize = 0.0f; bool needsRebuild = true; unsigned tabWidth = 8; sk_sp fontCollection; @@ -122,6 +132,7 @@ class RichTextBox { std::unique_ptr p; float heightOffset; skia::textlayout::ParagraphStyle pStyle; + TextModContainer tStyles; }; std::vector paragraphs; diff --git a/src/RichTextBox/TextStyleModifier.cpp b/src/RichTextBox/TextStyleModifier.cpp new file mode 100644 index 0000000..004fa6c --- /dev/null +++ b/src/RichTextBox/TextStyleModifier.cpp @@ -0,0 +1,155 @@ +#include "TextStyleModifier.hpp" +#include +#include + +#define WEIGHT_VALUE_MODIFIER 100 + +std::shared_ptr TextStyleModifier::allocate_modifier(ModifierType type) { + switch(type) { + case ModifierType::SIZE: + return std::make_shared(); + case ModifierType::COLOR: + return std::make_shared(); + case ModifierType::WEIGHT: + return std::make_shared(); + case ModifierType::SLANT: + return std::make_shared(); + case ModifierType::FONT_FAMILIES: + return std::make_shared(); + } + return nullptr; +} + +bool TextStyleModifier::equivalent(TextStyleModifier& modifier) const { + if(modifier.get_type() != get_type()) + return false; + return equivalent_data(modifier); +} + +TextStyleModifier::~TextStyleModifier() {} + + + +TextStyleModifier::ModifierType WeightTextStyleModifier::get_type() const { + return ModifierType::WEIGHT; +} + +void WeightTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const { + a(weightValue); +} + +void WeightTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { + a(weightValue); +} + +void WeightTextStyleModifier::set_weight(SkFontStyle::Weight newWeight) { + weightValue = newWeight / WEIGHT_VALUE_MODIFIER; +} + +SkFontStyle::Weight WeightTextStyleModifier::get_weight() { + return static_cast(static_cast(weightValue) * WEIGHT_VALUE_MODIFIER); +} + +void WeightTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { + style.setFontStyle(SkFontStyle(static_cast(weightValue) * WEIGHT_VALUE_MODIFIER, style.getFontStyle().width(), style.getFontStyle().slant())); +} + +bool WeightTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { + return weightValue == static_cast(modifier).weightValue; +} + + + +TextStyleModifier::ModifierType SlantTextStyleModifier::get_type() const { + return ModifierType::SLANT; +} + +void SlantTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const { + a(slantValue); +} + +void SlantTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { + a(slantValue); +} + +void SlantTextStyleModifier::set_slant(SkFontStyle::Slant newSlant) { + slantValue = newSlant; +} + +SkFontStyle::Slant SlantTextStyleModifier::get_slant() { + return static_cast(slantValue); +} + +void SlantTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { + style.setFontStyle(SkFontStyle(style.getFontStyle().weight(), style.getFontStyle().width(), static_cast(slantValue))); +} + +bool SlantTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { + return slantValue == static_cast(modifier).slantValue; +} + + + +TextStyleModifier::ModifierType ColorTextStyleModifier::get_type() const { + return ModifierType::COLOR; +} + +void ColorTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const { + a(color); +} + +void ColorTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { + a(color); +} + +void ColorTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { + style.setForegroundPaint(SkPaint{convert_vec4(color)}); +} + +bool ColorTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { + return color == static_cast(modifier).color; +} + + + +TextStyleModifier::ModifierType SizeTextStyleModifier::get_type() const { + return ModifierType::SIZE; +} + +void SizeTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const { + a(size); +} + +void SizeTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { + a(size); +} + +void SizeTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { + style.setFontSize(size); +} + +bool SizeTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { + return size == static_cast(modifier).size; +} + + + +TextStyleModifier::ModifierType FontFamiliesTextStyleModifier::get_type() const { + return ModifierType::FONT_FAMILIES; +} + +void FontFamiliesTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const { + a(families); +} + +void FontFamiliesTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { + a(families); +} + +void FontFamiliesTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { + style.setFontFamilies(families); +} + +bool FontFamiliesTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { + return families == static_cast(modifier).families; +} diff --git a/src/RichTextBox/TextStyleModifier.hpp b/src/RichTextBox/TextStyleModifier.hpp new file mode 100644 index 0000000..869952c --- /dev/null +++ b/src/RichTextBox/TextStyleModifier.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include +#include "cereal/archives/portable_binary.hpp" +#include "../SharedTypes.hpp" + +class TextStyleModifier { + public: + enum class ModifierType : uint16_t { + SIZE = 0, + COLOR, + WEIGHT, + SLANT, + FONT_FAMILIES + }; + static std::shared_ptr allocate_modifier(ModifierType type); + virtual ModifierType get_type() const = 0; + virtual void save(cereal::PortableBinaryOutputArchive& a) const = 0; + virtual void load(cereal::PortableBinaryInputArchive& a) = 0; + virtual void modify_text_style(skia::textlayout::TextStyle& style) const = 0; + bool equivalent(TextStyleModifier& modifier) const; + virtual ~TextStyleModifier(); + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const = 0; +}; + +class WeightTextStyleModifier : public TextStyleModifier { + public: + virtual ModifierType get_type() const override; + virtual void save(cereal::PortableBinaryOutputArchive& a) const override; + virtual void load(cereal::PortableBinaryInputArchive& a) override; + virtual void modify_text_style(skia::textlayout::TextStyle& style) const override; + void set_weight(SkFontStyle::Weight newWeight); + SkFontStyle::Weight get_weight(); + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const override; + uint8_t weightValue; +}; + +class SlantTextStyleModifier : public TextStyleModifier { + public: + virtual ModifierType get_type() const override; + virtual void save(cereal::PortableBinaryOutputArchive& a) const override; + virtual void load(cereal::PortableBinaryInputArchive& a) override; + virtual void modify_text_style(skia::textlayout::TextStyle& style) const override; + void set_slant(SkFontStyle::Slant newSlant); + SkFontStyle::Slant get_slant(); + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const override; + uint8_t slantValue; +}; + +class ColorTextStyleModifier : public TextStyleModifier { + public: + virtual ModifierType get_type() const override; + virtual void save(cereal::PortableBinaryOutputArchive& a) const override; + virtual void load(cereal::PortableBinaryInputArchive& a) override; + virtual void modify_text_style(skia::textlayout::TextStyle& style) const override; + Vector4f color; + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const override; +}; + +class SizeTextStyleModifier : public TextStyleModifier { + public: + virtual ModifierType get_type() const override; + virtual void save(cereal::PortableBinaryOutputArchive& a) const override; + virtual void load(cereal::PortableBinaryInputArchive& a) override; + virtual void modify_text_style(skia::textlayout::TextStyle& style) const override; + float size; + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const override; +}; + +class FontFamiliesTextStyleModifier : public TextStyleModifier { + public: + virtual ModifierType get_type() const override; + virtual void save(cereal::PortableBinaryOutputArchive& a) const override; + virtual void load(cereal::PortableBinaryInputArchive& a) override; + virtual void modify_text_style(skia::textlayout::TextStyle& style) const override; + std::vector families; + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const override; +};