From 4857b40af30e95e8d723e887148f713b1a35eb7f Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Wed, 5 Nov 2025 12:29:31 +0300 Subject: [PATCH] Can now toggle some modifiers --- src/DrawComponents/DrawTextBox.cpp | 17 +++++----- src/DrawingProgram/TextBoxEditTool.cpp | 26 ++++++++++++--- src/DrawingProgram/TextBoxEditTool.hpp | 3 ++ src/RichTextBox/RichTextBox.cpp | 46 ++++++++++++++++++++++---- src/RichTextBox/RichTextBox.hpp | 9 +++-- src/RichTextBox/TextStyleModifier.cpp | 26 +++++++++++++++ src/RichTextBox/TextStyleModifier.hpp | 10 +++++- 7 files changed, 113 insertions(+), 24 deletions(-) diff --git a/src/DrawComponents/DrawTextBox.cpp b/src/DrawComponents/DrawTextBox.cpp index f17b19e..d62bc34 100644 --- a/src/DrawComponents/DrawTextBox.cpp +++ b/src/DrawComponents/DrawTextBox.cpp @@ -14,20 +14,19 @@ DrawTextBox::DrawTextBox(): cursor(std::make_shared()) { skia::textlayout::TextStyle tStyle; - tStyle.setFontFamilies({SkString{"Roboto"}}); - tStyle.setFontSize(16.0f); - tStyle.setForegroundPaint(SkPaint{SkColor4f{0.0f, 1.0f, 1.0f, 1.0f}}); textBox->set_initial_text_style(tStyle); - auto boldMod = std::make_shared(); - boldMod->set_weight(SkFontStyle::kNormal_Weight); - textBox->set_initial_text_style_modifier(boldMod); - auto italicMod = std::make_shared(); - italicMod->set_slant(SkFontStyle::Slant::kUpright_Slant); - textBox->set_initial_text_style_modifier(italicMod); auto colorMod = std::make_shared(); colorMod->color = {1.0f, 1.0f, 1.0f, 1.0f}; textBox->set_initial_text_style_modifier(colorMod); + + auto textSizeMod = std::make_shared(); + textSizeMod->size = 16.0f; + textBox->set_initial_text_style_modifier(textSizeMod); + + auto fontFamilyMod = std::make_shared(); + fontFamilyMod->families = {SkString{"Roboto"}}; + textBox->set_initial_text_style_modifier(fontFamilyMod); } DrawComponentType DrawTextBox::get_type() const { diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 6a7d9f1..4007167 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -15,16 +15,20 @@ TextBoxEditTool::TextBoxEditTool(DrawingProgram& initDrawP): bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) { std::shared_ptr a = std::static_pointer_cast(comp); Toolbar& t = drawP.world.main.toolbar; + t.gui.push_id("edit tool text"); t.gui.text_label_centered("Edit Text"); - if(t.gui.text_button_wide("Bold button", "Bold")) { + + bool isBold = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::WEIGHT])->get_weight() == SkFontStyle::Weight::kBold_Weight; + if(t.gui.text_button_wide("Bold button", "Bold", isBold)) { auto boldMod = std::make_shared(); - boldMod->set_weight(SkFontStyle::kBold_Weight); + boldMod->set_weight(isBold ? SkFontStyle::Weight::kNormal_Weight : SkFontStyle::Weight::kBold_Weight); a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, boldMod); } - if(t.gui.text_button_wide("Italic button", "Italic")) { + bool isItalic = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::SLANT])->get_slant() == SkFontStyle::Slant::kItalic_Slant; + if(t.gui.text_button_wide("Italic button", "Italic", isItalic)) { auto italicMod = std::make_shared(); - italicMod->set_slant(SkFontStyle::Slant::kItalic_Slant); + italicMod->set_slant(isItalic ? SkFontStyle::Slant::kUpright_Slant : SkFontStyle::Slant::kItalic_Slant); a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, italicMod); } if(t.gui.text_button_wide("Red button", "Red")) { @@ -51,7 +55,13 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) { //}); t.gui.pop_id(); - 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. + if(a->textBox->inputChangedTextBox) + modsAtStartOfSelection = a->textBox->get_mods_used_at_pos(std::min(a->cursor->selectionBeginPos, a->cursor->selectionEndPos)); + + // NOTE: There should be a system that periodically sends updates even if no changes are made, since unreliable channels can drop update data + bool oldInputChangedTextBox = a->textBox->inputChangedTextBox; + a->textBox->inputChangedTextBox = false; + return oldInputChangedTextBox; } void TextBoxEditTool::commit_edit_updates(const std::shared_ptr& comp, std::any& prevData) { @@ -97,7 +107,13 @@ void TextBoxEditTool::edit_start(EditTool& editTool, const std::shared_ptrprocess_mouse_left_button(*cur, textSelectPos, true, false, false); prevData = get_all_data(a); a->d.editing = true; + a->textBox->inputChangedTextBox = true; + + a->commit_update(drawP); a->client_send_update(drawP, false); + + modsAtStartOfSelection = a->textBox->get_mods_used_at_pos(std::min(a->cursor->selectionBeginPos, a->cursor->selectionEndPos)); + editTool.add_point_handle({&a->d.p1, nullptr, &a->d.p2}); editTool.add_point_handle({&a->d.p2, &a->d.p1, nullptr}); } diff --git a/src/DrawingProgram/TextBoxEditTool.hpp b/src/DrawingProgram/TextBoxEditTool.hpp index 36306f9..7d2f6bf 100644 --- a/src/DrawingProgram/TextBoxEditTool.hpp +++ b/src/DrawingProgram/TextBoxEditTool.hpp @@ -19,5 +19,8 @@ class TextBoxEditTool : public DrawingProgramEditToolBase { DrawTextBox::Data textboxData; RichTextBox::RichTextData richText; }; + + TextStyleModifier::ModifierMap modsAtStartOfSelection; + TextBoxEditToolAllData get_all_data(const std::shared_ptr& a); }; diff --git a/src/RichTextBox/RichTextBox.cpp b/src/RichTextBox/RichTextBox.cpp index 1aa356b..bcdb4d7 100644 --- a/src/RichTextBox/RichTextBox.cpp +++ b/src/RichTextBox/RichTextBox.cpp @@ -118,17 +118,23 @@ void RichTextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool sh if(in != InputKey::UP && in != InputKey::DOWN) cur.previousX = std::nullopt; + + inputChangedTextBox = true; } void RichTextBox::process_mouse_left_button(Cursor& cur, const Vector2f& pos, bool clicked, bool held, bool shift) { if(clicked || held) { rebuild(); + Cursor oldCursor = cur; + cur.pos = get_text_pos_closest_to_point(pos); cur.selectionBeginPos = cur.pos; if(clicked && !shift) cur.selectionEndPos = cur.selectionBeginPos; cur.previousX = std::nullopt; + + inputChangedTextBox |= (oldCursor != cur); } } @@ -141,6 +147,7 @@ std::string RichTextBox::process_cut(Cursor& cur) { if(cur.selectionBeginPos != cur.selectionEndPos) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.previousX = std::nullopt; + inputChangedTextBox = true; } return toRet; } @@ -151,6 +158,7 @@ void RichTextBox::process_text_input(Cursor& cur, const std::string& in) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert(cur.pos, in); cur.previousX = std::nullopt; + inputChangedTextBox = true; } } @@ -289,6 +297,7 @@ void RichTextBox::set_rich_text_data(const RichTextData& richText) { if(richText.paragraphs.empty()) paragraphs.emplace_back(); tStyleMods = richText.tStyleMods; + inputChangedTextBox = true; needsRebuild = true; } @@ -323,6 +332,7 @@ std::string RichTextBox::get_text_between(TextPosition p1, TextPosition p2) { void RichTextBox::set_initial_text_style(const skia::textlayout::TextStyle& tStyle) { if(!tStyle.equals(initialTStyle)) { initialTStyle = tStyle; + inputChangedTextBox = true; needsRebuild = true; } } @@ -339,8 +349,25 @@ void RichTextBox::set_initial_text_style_modifier(const std::shared_ptr RichTextBox::get_last_text_style_mod_before_pos(TextPosition pos, TextStyleModifier::ModifierType modType) { - std::shared_ptr lastModOfThisTypeBeforeEnd; + std::shared_ptr lastModOfThisTypeBeforeEnd = TextStyleModifier::get_default_modifier(modType); for(auto& [tPos, tStyleModsInPos] : tStyleMods) { if(tPos <= pos) { auto containedOfSameType = tStyleModsInPos.find(modType); @@ -377,9 +404,9 @@ void RichTextBox::set_text_style_modifier_between(TextPosition p1, TextPosition return p >= start && modifier->get_type() == modToCheck->get_type(); }); insert_style_at_pos(start, modifier); - if(end != move(Movement::END, {0, 0})) - insert_style_at_pos(end, lastModOfThisTypeBeforeEnd); + insert_style_at_pos(end, lastModOfThisTypeBeforeEnd); // Even if the end is the literal end of the text (which wont be seen), the end style must still be placed at the end so that it can merge with and delete the start style when the styled text is erased remove_duplicate_text_style_mods(); + inputChangedTextBox = true; needsRebuild = true; } } @@ -584,6 +611,7 @@ RichTextBox::TextPosition RichTextBox::move(Movement movement, TextPosition pos, void RichTextBox::set_width(float newWidth) { if(width != newWidth) { width = newWidth; + inputChangedTextBox = true; needsRebuild = true; } } @@ -595,6 +623,7 @@ void RichTextBox::set_allow_newlines(bool allow) { void RichTextBox::set_font_collection(const sk_sp& fC) { if(fontCollection != fC) { fontCollection = fC; + inputChangedTextBox = true; needsRebuild = true; } } @@ -646,16 +675,17 @@ void RichTextBox::rebuild() { heightOffset += pData.p->getHeight(); } - //std::cout << "size: " << tStyleMods.size() << std::endl; - //for(auto& s : tStyleMods) - // std::cout << s.pos.fParagraphIndex << " " << s.pos.fTextByteIndex << std::endl; + std::cout << "size: " << tStyleMods.size() << std::endl; + for(auto& s : tStyleMods) + std::cout << s.pos.fParagraphIndex << " " << s.pos.fTextByteIndex << std::endl; needsRebuild = false; } } void RichTextBox::remove_duplicate_text_style_mods() { - std::unordered_map> previouslyAppliedMods; + TextStyleModifier::ModifierMap previouslyAppliedMods = TextStyleModifier::get_default_modifiers(); + std::erase_if(tStyleMods, [&previouslyAppliedMods](PositionedTextStyleMod& tStyleModsInPos) { std::erase_if(tStyleModsInPos.mods, [&previouslyAppliedMods](const std::pair>& tStyleModPair) { auto [placedIt, insertionTookPlace] = previouslyAppliedMods.emplace(tStyleModPair.first, tStyleModPair.second); @@ -715,6 +745,7 @@ RichTextBox::TextPosition RichTextBox::insert(TextPosition pos, std::string_view if(!textToInsert.empty()) { remove_duplicate_text_style_mods(); + inputChangedTextBox = true; needsRebuild = true; } @@ -766,6 +797,7 @@ RichTextBox::TextPosition RichTextBox::remove(TextPosition p1, TextPosition p2) } remove_duplicate_text_style_mods(); + inputChangedTextBox = true; needsRebuild = true; return start; diff --git a/src/RichTextBox/RichTextBox.hpp b/src/RichTextBox/RichTextBox.hpp index ebc106d..5b87fc1 100644 --- a/src/RichTextBox/RichTextBox.hpp +++ b/src/RichTextBox/RichTextBox.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -10,9 +11,10 @@ class RichTextBox { public: - RichTextBox(); + bool inputChangedTextBox = false; + enum class Movement { NOWHERE, LEFT, @@ -41,7 +43,7 @@ class RichTextBox { struct PositionedTextStyleMod { TextPosition pos; - std::unordered_map> mods; + TextStyleModifier::ModifierMap mods; }; typedef std::vector TextStyleModContainer; @@ -54,6 +56,8 @@ class RichTextBox { a(pos, selectionBeginPos, selectionEndPos); } std::optional previousX; + bool operator==(const Cursor& o) const = default; + bool operator!=(const Cursor& o) const = default; }; struct PaintOpts { @@ -99,6 +103,7 @@ class RichTextBox { void set_initial_text_style(const skia::textlayout::TextStyle& tStyle); 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); + TextStyleModifier::ModifierMap get_mods_used_at_pos(TextPosition p); RichTextData get_rich_text_data(); void set_rich_text_data(const RichTextData& richText); diff --git a/src/RichTextBox/TextStyleModifier.cpp b/src/RichTextBox/TextStyleModifier.cpp index 004fa6c..2ebf748 100644 --- a/src/RichTextBox/TextStyleModifier.cpp +++ b/src/RichTextBox/TextStyleModifier.cpp @@ -1,9 +1,12 @@ #include "TextStyleModifier.hpp" #include #include +#include #define WEIGHT_VALUE_MODIFIER 100 +TextStyleModifier::ModifierMap TextStyleModifier::defaultMods; + std::shared_ptr TextStyleModifier::allocate_modifier(ModifierType type) { switch(type) { case ModifierType::SIZE: @@ -16,10 +19,33 @@ std::shared_ptr TextStyleModifier::allocate_modifier(Modifier return std::make_shared(); case ModifierType::FONT_FAMILIES: return std::make_shared(); + case ModifierType::COUNT: + return nullptr; } return nullptr; } +std::shared_ptr TextStyleModifier::get_default_modifier(ModifierType type) { + auto& defaultMods = get_default_modifiers(); + auto it = defaultMods.find(type); + if(it == defaultMods.end()) + return nullptr; + return it->second; +} + +const TextStyleModifier::ModifierMap& TextStyleModifier::get_default_modifiers() { + if(defaultMods.empty()) { + auto weightMod = std::make_shared(); + weightMod->set_weight(SkFontStyle::kNormal_Weight); + defaultMods[ModifierType::WEIGHT] = weightMod; + + auto slantMod = std::make_shared(); + slantMod->set_slant(SkFontStyle::kUpright_Slant); + defaultMods[ModifierType::SLANT] = slantMod; + } + return defaultMods; +} + bool TextStyleModifier::equivalent(TextStyleModifier& modifier) const { if(modifier.get_type() != get_type()) return false; diff --git a/src/RichTextBox/TextStyleModifier.hpp b/src/RichTextBox/TextStyleModifier.hpp index 869952c..a3c1e2f 100644 --- a/src/RichTextBox/TextStyleModifier.hpp +++ b/src/RichTextBox/TextStyleModifier.hpp @@ -12,9 +12,16 @@ class TextStyleModifier { COLOR, WEIGHT, SLANT, - FONT_FAMILIES + FONT_FAMILIES, + + COUNT }; + + typedef std::unordered_map> ModifierMap; + static std::shared_ptr allocate_modifier(ModifierType type); + static std::shared_ptr get_default_modifier(ModifierType type); + static const ModifierMap& get_default_modifiers(); virtual ModifierType get_type() const = 0; virtual void save(cereal::PortableBinaryOutputArchive& a) const = 0; virtual void load(cereal::PortableBinaryInputArchive& a) = 0; @@ -22,6 +29,7 @@ class TextStyleModifier { bool equivalent(TextStyleModifier& modifier) const; virtual ~TextStyleModifier(); private: + static ModifierMap defaultMods; virtual bool equivalent_data(TextStyleModifier& modifier) const = 0; };