From 644655544c0420825e5b8b17aa4308332d4150a4 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Wed, 5 Nov 2025 18:21:49 +0300 Subject: [PATCH] Added text decoration modifiers, and icon toggle buttons for text modifiers --- data/icons/RemixIcon/bold.svg | 1 + data/icons/RemixIcon/italic.svg | 1 + data/icons/RemixIcon/overline.svg | 1 + data/icons/RemixIcon/strikethrough.svg | 1 + data/icons/RemixIcon/underline.svg | 1 + data/third_party_licenses/RemixIcon | 201 +++++++++++++++++++++++++ src/DrawingProgram/TextBoxEditTool.cpp | 71 ++++++--- src/DrawingProgram/TextBoxEditTool.hpp | 4 +- src/DrawingProgram/TextBoxTool.cpp | 1 - src/DrawingProgram/TextBoxTool.hpp | 1 - src/GUIStuff/Elements/ColorPicker.hpp | 12 +- src/GUIStuff/Elements/SVGIcon.cpp | 2 + src/GUIStuff/GUIManager.cpp | 17 ++- src/GUIStuff/GUIManager.hpp | 46 +++--- src/RichTextBox/RichTextBox.cpp | 2 +- src/RichTextBox/TextStyleModifier.cpp | 30 ++++ src/RichTextBox/TextStyleModifier.hpp | 12 ++ src/Toolbar.cpp | 15 +- src/Toolbar.hpp | 5 +- 19 files changed, 373 insertions(+), 51 deletions(-) create mode 100644 data/icons/RemixIcon/bold.svg create mode 100644 data/icons/RemixIcon/italic.svg create mode 100644 data/icons/RemixIcon/overline.svg create mode 100644 data/icons/RemixIcon/strikethrough.svg create mode 100644 data/icons/RemixIcon/underline.svg create mode 100644 data/third_party_licenses/RemixIcon diff --git a/data/icons/RemixIcon/bold.svg b/data/icons/RemixIcon/bold.svg new file mode 100644 index 0000000..8685824 --- /dev/null +++ b/data/icons/RemixIcon/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/icons/RemixIcon/italic.svg b/data/icons/RemixIcon/italic.svg new file mode 100644 index 0000000..915ebbd --- /dev/null +++ b/data/icons/RemixIcon/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/icons/RemixIcon/overline.svg b/data/icons/RemixIcon/overline.svg new file mode 100644 index 0000000..cd34979 --- /dev/null +++ b/data/icons/RemixIcon/overline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/icons/RemixIcon/strikethrough.svg b/data/icons/RemixIcon/strikethrough.svg new file mode 100644 index 0000000..bbb38b4 --- /dev/null +++ b/data/icons/RemixIcon/strikethrough.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/icons/RemixIcon/underline.svg b/data/icons/RemixIcon/underline.svg new file mode 100644 index 0000000..69c48c1 --- /dev/null +++ b/data/icons/RemixIcon/underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/third_party_licenses/RemixIcon b/data/third_party_licenses/RemixIcon new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/data/third_party_licenses/RemixIcon @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 30cda9e..3d718ba 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -19,32 +19,65 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) { t.gui.push_id("edit tool text"); t.gui.text_label_centered("Edit Text"); - 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(isBold ? SkFontStyle::Weight::kNormal_Weight : SkFontStyle::Weight::kBold_Weight); - a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, boldMod); - } - 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(isItalic ? SkFontStyle::Slant::kUpright_Slant : SkFontStyle::Slant::kItalic_Slant); - a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, italicMod); - } + t.gui.left_to_right_line_centered_layout([&]() { + bool isBold = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::WEIGHT])->get_weight() == SkFontStyle::Weight::kBold_Weight; + if(t.gui.svg_icon_button("Bold button", "data/icons/RemixIcon/bold.svg", isBold)) { + auto boldMod = std::make_shared(); + 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); + } + + bool isItalic = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::SLANT])->get_slant() == SkFontStyle::Slant::kItalic_Slant; + if(t.gui.svg_icon_button("Italic button", "data/icons/RemixIcon/italic.svg", isItalic)) { + auto italicMod = std::make_shared(); + 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); + } + + uint8_t currentDecorationValue = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::DECORATION])->decorationValue; + bool isUnderlined = currentDecorationValue & skia::textlayout::TextDecoration::kUnderline; + if(t.gui.svg_icon_button("Underline button", "data/icons/RemixIcon/underline.svg", isUnderlined)) { + auto decorationMod = std::make_shared(); + decorationMod->decorationValue = isUnderlined ? (currentDecorationValue & ~static_cast(skia::textlayout::TextDecoration::kUnderline)) : currentDecorationValue | static_cast(skia::textlayout::TextDecoration::kUnderline); + a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, decorationMod); + } + + bool isLinethrough = currentDecorationValue & skia::textlayout::TextDecoration::kLineThrough; + if(t.gui.svg_icon_button("Strikethrough button", "data/icons/RemixIcon/strikethrough.svg", isLinethrough)) { + auto decorationMod = std::make_shared(); + decorationMod->decorationValue = isLinethrough ? (currentDecorationValue & ~static_cast(skia::textlayout::TextDecoration::kLineThrough)) : currentDecorationValue | static_cast(skia::textlayout::TextDecoration::kLineThrough); + a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, decorationMod); + } + + bool isOverline = currentDecorationValue & skia::textlayout::TextDecoration::kOverline; + if(t.gui.svg_icon_button("Overline button", "data/icons/RemixIcon/overline.svg", isOverline)) { + auto decorationMod = std::make_shared(); + decorationMod->decorationValue = isOverline ? (currentDecorationValue & ~static_cast(skia::textlayout::TextDecoration::kOverline)) : currentDecorationValue | static_cast(skia::textlayout::TextDecoration::kOverline); + a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, decorationMod); + } + }); + newFontSize = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::SIZE])->size; if(t.gui.slider_scalar_field("Font Size Slider", "Font Size", &newFontSize, 3, 100)) { auto sizeMod = std::make_shared(); sizeMod->size = newFontSize; a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, sizeMod); } + t.gui.left_to_right_line_layout([&]() { + CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) { + if(t.gui.color_button("Text Color", &newTextColor, &newTextColor == t.colorRight)) { + t.color_selector_right(&newTextColor == t.colorRight ? nullptr : &newTextColor); + } + } + t.gui.text_label("Text Color"); + }); + if((&newTextColor == t.colorRight) && t.isUpdatingColorRight) { + auto colorMod = std::make_shared(); + colorMod->color = newTextColor; + a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, colorMod); + } + newTextColor = std::static_pointer_cast(modsAtStartOfSelection[TextStyleModifier::ModifierType::COLOR])->color; - //t.gui.left_to_right_line_layout([&]() { - // CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) { - // if(t.gui.color_button("Text Color", &a->d.textColor, &a->d.textColor == t.colorRight)) - // t.color_selector_right(&a->d.textColor == t.colorRight ? nullptr : &a->d.textColor); - // } - // t.gui.text_label("Text Color"); - //}); t.gui.pop_id(); if(a->textBox->inputChangedTextBox) diff --git a/src/DrawingProgram/TextBoxEditTool.hpp b/src/DrawingProgram/TextBoxEditTool.hpp index 1925707..7c64b80 100644 --- a/src/DrawingProgram/TextBoxEditTool.hpp +++ b/src/DrawingProgram/TextBoxEditTool.hpp @@ -19,7 +19,9 @@ class TextBoxEditTool : public DrawingProgramEditToolBase { DrawTextBox::Data textboxData; RichTextBox::RichTextData richText; }; - uint32_t newFontSize; + + uint32_t newFontSize = 15; + Vector4f newTextColor{1.0f, 1.0f, 1.0f, 1.0f}; void set_mods_at_selection(const std::shared_ptr& a); TextStyleModifier::ModifierMap modsAtStartOfSelection; diff --git a/src/DrawingProgram/TextBoxTool.cpp b/src/DrawingProgram/TextBoxTool.cpp index 2b1ef57..5060703 100644 --- a/src/DrawingProgram/TextBoxTool.cpp +++ b/src/DrawingProgram/TextBoxTool.cpp @@ -21,7 +21,6 @@ void TextBoxTool::gui_toolbox() { Toolbar& t = drawP.world.main.toolbar; t.gui.push_id("textbox tool"); t.gui.text_label_centered("Textbox"); - t.gui.slider_scalar_field("Text Relative Size", "Text Size", &controls.textRelativeSize, 5.0f, 100.0f); t.gui.pop_id(); } diff --git a/src/DrawingProgram/TextBoxTool.hpp b/src/DrawingProgram/TextBoxTool.hpp index 1e5d295..266546d 100644 --- a/src/DrawingProgram/TextBoxTool.hpp +++ b/src/DrawingProgram/TextBoxTool.hpp @@ -21,7 +21,6 @@ class TextBoxTool : public DrawingProgramToolBase { void commit(); struct TextBoxControls { - float textRelativeSize = 20.0f; Vector2f startAt; Vector2f endAt; int drawStage = 0; diff --git a/src/GUIStuff/Elements/ColorPicker.hpp b/src/GUIStuff/Elements/ColorPicker.hpp index 01a409b..8916072 100644 --- a/src/GUIStuff/Elements/ColorPicker.hpp +++ b/src/GUIStuff/Elements/ColorPicker.hpp @@ -11,12 +11,14 @@ namespace GUIStuff { template class ColorPicker : public Element { public: - void update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function& elemUpdate) { + bool update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function& elemUpdate) { + bool isChanged = false; + T* data = newData; selectAlpha = newSelectAlpha; if(!data) - return; + return false; force_update_colorpicker(data); @@ -39,6 +41,7 @@ template class ColorPicker : public Element { Vector2f alphaBarDim = get_alpha_bar_dim(); modifyingAlpha = SCollision::collide(SCollision::AABB(alphaBarPos, alphaBarPos + alphaBarDim), io.mouse.pos); } + isChanged = true; } if(selection.held && modifyingSv) { float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH); @@ -46,18 +49,21 @@ template class ColorPicker : public Element { savedHsv.y() = newSv.x(); savedHsv.z() = 1.0f - newSv.y(); set_hsv(data, savedHsv); + isChanged = true; } if(selection.held && modifyingHue) { Vector2f huePos = get_hue_bar_pos(); Vector2f hueDim = get_hue_bar_dim(); savedHsv.x() = (1.0f - std::clamp((io.mouse.pos.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f; set_hsv(data, savedHsv); + isChanged = true; } if(selection.held && modifyingAlpha) { Vector2f alphaPos = get_alpha_bar_pos(); Vector2f alphaDim = get_alpha_bar_dim(); (*data)[3] = std::clamp((io.mouse.pos.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f); oldData = *data; + isChanged = true; } if(!selection.held) { modifyingSv = false; @@ -67,6 +73,8 @@ template class ColorPicker : public Element { if(elemUpdate) elemUpdate(); } + + return isChanged; } virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) { diff --git a/src/GUIStuff/Elements/SVGIcon.cpp b/src/GUIStuff/Elements/SVGIcon.cpp index 9c32437..26c467c 100644 --- a/src/GUIStuff/Elements/SVGIcon.cpp +++ b/src/GUIStuff/Elements/SVGIcon.cpp @@ -26,6 +26,8 @@ void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool ne else { io.svgData[newSvgPath] = newSvgDom; svgDom = newSvgDom; + if(svgDom->containerSize().width() == 0 || svgDom->containerSize().height() == 0) + svgDom->setContainerSize({1000, 1000}); } } else diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index 31de512..4f7b1d4 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -275,6 +275,18 @@ void GUIManager::left_to_right_line_layout(const std::function& elemUpda left_to_right_layout(CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0), elemUpdate); } +void GUIManager::left_to_right_line_centered_layout(const std::function& elemUpdate) { + CLAY({.layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .childGap = io->theme->childGap1, + .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + elemUpdate(); + } +} + void GUIManager::text_label_size(const std::string& val, float modifier) { CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io->theme->frontColor1), .fontSize = static_cast(io->fontSize * modifier)})); } @@ -360,9 +372,9 @@ bool GUIManager::radio_button_field(const std::string& id, const std::string& na return toRet; } -void GUIManager::input_color_component_255(const std::string& id, float* val, const std::function& elemUpdate) { +bool GUIManager::input_color_component_255(const std::string& id, float* val, const std::function& elemUpdate) { push_id(id); - insert_element>()->update(*io, val, + bool isUpdating = insert_element>()->update(*io, val, [&](const std::string& str) { int roundTo255; std::stringstream ss; @@ -377,6 +389,7 @@ void GUIManager::input_color_component_255(const std::string& id, float* val, co return ss.str(); }, true, false, elemUpdate); pop_id(); + return isUpdating; } void GUIManager::input_text(const std::string& id, std::string* val, const std::function& elemUpdate) { diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index 73a2a9d..a879987 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -72,6 +72,7 @@ class GUIManager { void top_to_bottom_window_popup_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate); void left_to_right_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate); void left_to_right_line_layout(const std::function& elemUpdate); + void left_to_right_line_centered_layout(const std::function& elemUpdate); bool selectable_button(const std::string& id, const std::function& elemUpdate, GUIStuff::SelectableButton::DrawType drawType, bool isSelected); @@ -82,7 +83,7 @@ class GUIManager { void scroll_bar_area(const std::string& uniqueId, const std::function& elemUpdate); void scroll_bar_many_entries_area(const std::string& uniqueId, float entryHeight, size_t entryCount, const std::function& entryUpdate, const std::function& elemUpdate = nullptr); - void input_color_component_255(const std::string& id, float* val, const std::function& elemUpdate = nullptr); + bool input_color_component_255(const std::string& id, float* val, const std::function& elemUpdate = nullptr); void input_text(const std::string& id, std::string* val, const std::function& elemUpdate = nullptr); bool radio_button(const std::string& id, bool val, const std::function& elemUpdate = nullptr); @@ -162,18 +163,20 @@ class GUIManager { bool input_scalar(const std::string& id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function& elemUpdate); - template void input_scalar_fields(const std::string& id, const std::string& name, TContainer* val, size_t elemCount, T min, T max, int decimalPrecision = 0, const std::function& elemUpdate = nullptr) { + template bool input_scalar_fields(const std::string& id, const std::string& name, TContainer* val, size_t elemCount, T min, T max, int decimalPrecision = 0, const std::function& elemUpdate = nullptr) { + bool isUpdating = false; push_id(id); left_to_right_line_layout([&]() { text_label(name); for(size_t i = 0; i < elemCount; i++) - input_scalar(std::to_string(i), &(*val)[i], min, max, decimalPrecision, elemUpdate); + isUpdating |= input_scalar(std::to_string(i), &(*val)[i], min, max, decimalPrecision, elemUpdate); }); pop_id(); + return isUpdating; } - template void input_color_hex(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { - input_generic(id, val, + template bool input_color_hex(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { + return input_generic(id, val, [&](const std::string& str) { T def = {0.0f, 0.0f, 0.0f, 1.0f}; unsigned startIndex = 0; @@ -209,10 +212,11 @@ class GUIManager { true, false, elemUpdate); } - template void color_picker(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { + template bool color_picker(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { push_id(id); - insert_element>()->update(*io, val, selectAlpha, elemUpdate); + bool isUpdating = insert_element>()->update(*io, val, selectAlpha, elemUpdate); pop_id(); + return isUpdating; } template bool slider_scalar(const std::string& id, T* val, T min, T max, const std::function& elemUpdate = nullptr) { @@ -264,7 +268,8 @@ class GUIManager { }, GUIStuff::SelectableButton::DrawType::TRANSPARENT_BORDER, isSelected); } - template void color_picker_button(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { + template bool color_picker_button(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { + bool isUpdating = false; push_id(id); bool& isOpen = insert_any_with_id(0, false); bool clicked; @@ -275,41 +280,46 @@ class GUIManager { isOpen = true; if(isOpen) { top_to_bottom_window_popup_layout(CLAY_SIZING_FIT(300), CLAY_SIZING_FIT(0), [&]() { - color_picker_items("c", val, selectAlpha); + isUpdating = color_picker_items("c", val, selectAlpha); if(io->mouse.leftClick && !Clay_Hovered() && !clicked) isOpen = false; }); } pop_id(); + return isUpdating; } - template void color_picker_items(const std::string& id, T* val, bool selectAlpha) { + template bool color_picker_items(const std::string& id, T* val, bool selectAlpha) { + bool isUpdating = false; push_id(id); - color_picker("c", val, selectAlpha); + isUpdating |= color_picker("c", val, selectAlpha); left_to_right_line_layout([&]() { text_label("R"); - input_color_component_255("r", &(*val)[0]); + isUpdating |= input_color_component_255("r", &(*val)[0]); text_label("G"); - input_color_component_255("g", &(*val)[1]); + isUpdating |= input_color_component_255("g", &(*val)[1]); text_label("B"); - input_color_component_255("b", &(*val)[2]); + isUpdating |= input_color_component_255("b", &(*val)[2]); if(selectAlpha) { text_label("A"); - input_color_component_255("a", &(*val)[3]); + isUpdating |= input_color_component_255("a", &(*val)[3]); } }); left_to_right_line_layout([&]() { text_label("Hex"); - input_color_hex("h", val, selectAlpha); + isUpdating |= input_color_hex("h", val, selectAlpha); }); pop_id(); + return isUpdating; } - template void color_picker_button_field(const std::string& id, const std::string& name, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { + template bool color_picker_button_field(const std::string& id, const std::string& name, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { + bool isUpdating = false; left_to_right_line_layout([&]() { - color_picker_button(id, val, selectAlpha, elemUpdate); + isUpdating = color_picker_button(id, val, selectAlpha, elemUpdate); text_label(name); }); + return isUpdating; } private: diff --git a/src/RichTextBox/RichTextBox.cpp b/src/RichTextBox/RichTextBox.cpp index 017e136..9a5c1d1 100644 --- a/src/RichTextBox/RichTextBox.cpp +++ b/src/RichTextBox/RichTextBox.cpp @@ -610,7 +610,7 @@ RichTextBox::TextPosition RichTextBox::move(Movement movement, TextPosition pos, void RichTextBox::set_width(float newWidth) { if(width != newWidth) { - width = newWidth; + width = std::max(newWidth, 4.0f); inputChangedTextBox = true; needsRebuild = true; } diff --git a/src/RichTextBox/TextStyleModifier.cpp b/src/RichTextBox/TextStyleModifier.cpp index 2ebf748..c273845 100644 --- a/src/RichTextBox/TextStyleModifier.cpp +++ b/src/RichTextBox/TextStyleModifier.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #define WEIGHT_VALUE_MODIFIER 100 @@ -17,6 +18,8 @@ std::shared_ptr TextStyleModifier::allocate_modifier(Modifier return std::make_shared(); case ModifierType::SLANT: return std::make_shared(); + case ModifierType::DECORATION: + return std::make_shared(); case ModifierType::FONT_FAMILIES: return std::make_shared(); case ModifierType::COUNT: @@ -42,6 +45,10 @@ const TextStyleModifier::ModifierMap& TextStyleModifier::get_default_modifiers() auto slantMod = std::make_shared(); slantMod->set_slant(SkFontStyle::kUpright_Slant); defaultMods[ModifierType::SLANT] = slantMod; + + auto decorationMod = std::make_shared(); + decorationMod->decorationValue = skia::textlayout::TextDecoration::kNoDecoration; + defaultMods[ModifierType::DECORATION] = decorationMod; } return defaultMods; } @@ -130,6 +137,7 @@ void ColorTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { void ColorTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { style.setForegroundPaint(SkPaint{convert_vec4(color)}); + style.setDecorationColor(convert_vec4(color).toSkColor()); } bool ColorTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { @@ -179,3 +187,25 @@ void FontFamiliesTextStyleModifier::modify_text_style(skia::textlayout::TextStyl bool FontFamiliesTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { return families == static_cast(modifier).families; } + + + +TextStyleModifier::ModifierType DecorationTextStyleModifier::get_type() const { + return ModifierType::DECORATION; +} + +void DecorationTextStyleModifier::save(cereal::PortableBinaryOutputArchive& a) const { + a(decorationValue); +} + +void DecorationTextStyleModifier::load(cereal::PortableBinaryInputArchive& a) { + a(decorationValue); +} + +void DecorationTextStyleModifier::modify_text_style(skia::textlayout::TextStyle& style) const { + style.setDecoration(static_cast(decorationValue)); +} + +bool DecorationTextStyleModifier::equivalent_data(TextStyleModifier& modifier) const { + return decorationValue == static_cast(modifier).decorationValue; +} diff --git a/src/RichTextBox/TextStyleModifier.hpp b/src/RichTextBox/TextStyleModifier.hpp index a3c1e2f..7bb81ef 100644 --- a/src/RichTextBox/TextStyleModifier.hpp +++ b/src/RichTextBox/TextStyleModifier.hpp @@ -12,6 +12,7 @@ class TextStyleModifier { COLOR, WEIGHT, SLANT, + DECORATION, FONT_FAMILIES, COUNT @@ -33,6 +34,17 @@ class TextStyleModifier { virtual bool equivalent_data(TextStyleModifier& modifier) const = 0; }; +class DecorationTextStyleModifier : 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; + uint8_t decorationValue; + private: + virtual bool equivalent_data(TextStyleModifier& modifier) const override; +}; + class WeightTextStyleModifier : public TextStyleModifier { public: virtual ModifierType get_type() const override; diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index a8043af..97971e7 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -1012,6 +1012,7 @@ void Toolbar::drawing_program_gui() { }, }) { main.world->drawProg.toolbar_gui(); + isUpdatingColorLeft = isUpdatingColorRight = false; if(colorLeft) { CLAY({ .layout = { @@ -1025,9 +1026,9 @@ void Toolbar::drawing_program_gui() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) }) { gui.obstructing_window(); - gui.color_picker_items("colorpickerleft", colorLeft, true); + isUpdatingColorLeft |= gui.color_picker_items("colorpickerleft", colorLeft, true); bool hoveringOnDropdown = false; - color_palette("colorpickerleftpalette", colorLeft, hoveringOnDropdown); + isUpdatingColorLeft |= color_palette("colorpickerleftpalette", colorLeft, hoveringOnDropdown); if(!Clay_Hovered() && !justAssignedColorLeft && !hoveringOnDropdown && io->mouse.leftClick) colorLeft = nullptr; } @@ -1050,9 +1051,9 @@ void Toolbar::drawing_program_gui() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) }) { gui.obstructing_window(); - gui.color_picker_items("colorpickerright", colorRight, true); + isUpdatingColorRight |= gui.color_picker_items("colorpickerright", colorRight, true); bool hoveringOnDropdown = false; - color_palette("colorpickerrightpalette", colorRight, hoveringOnDropdown); + isUpdatingColorRight |= color_palette("colorpickerrightpalette", colorRight, hoveringOnDropdown); if(!Clay_Hovered() && !justAssignedColorRight && !hoveringOnDropdown && io->mouse.leftClick) colorRight = nullptr; } @@ -1061,7 +1062,8 @@ void Toolbar::drawing_program_gui() { } } -void Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveringOnDropdown) { +bool Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveringOnDropdown) { + bool isUpdating = false; gui.push_id(id); auto& palette = paletteData.palettes[paletteData.selectedPalette].colors; size_t i = 0; @@ -1089,6 +1091,7 @@ void Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveri color->x() = newC.x(); color->y() = newC.y(); color->z() = newC.z(); + isUpdating = true; } if(paletteData.selectedColor == (int)i && (newC.x() != color->x() || newC.y() != color->y() || newC.z() != color->z())) paletteData.selectedColor = -1; @@ -1154,6 +1157,8 @@ void Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveri } } gui.pop_id(); + + return isUpdating; } void Toolbar::performance_metrics() { diff --git a/src/Toolbar.hpp b/src/Toolbar.hpp index a3b6fb9..cab9af3 100644 --- a/src/Toolbar.hpp +++ b/src/Toolbar.hpp @@ -80,6 +80,9 @@ class Toolbar { Vector4f* colorLeft = nullptr; Vector4f* colorRight = nullptr; + bool isUpdatingColorLeft = false; + bool isUpdatingColorRight = false; + std::optional paintPopupLocation; bool flipZoomToolDirection = false; @@ -103,7 +106,7 @@ class Toolbar { void options_menu(); void file_picker_gui(); void performance_metrics(); - void color_palette(const std::string& id, Vector4f* color, bool& hoveringOnDropdown); + bool color_palette(const std::string& id, Vector4f* color, bool& hoveringOnDropdown); void open_world_file(int conType, const std::string& netSource, const std::string& serverLocalID); void load_default_palette(); void load_default_theme();