diff --git a/data/icons/RemixIcon/align-center.svg b/data/icons/RemixIcon/align-center.svg
new file mode 100644
index 0000000..45d0d29
--- /dev/null
+++ b/data/icons/RemixIcon/align-center.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/data/icons/RemixIcon/align-justify.svg b/data/icons/RemixIcon/align-justify.svg
new file mode 100644
index 0000000..d5e3f05
--- /dev/null
+++ b/data/icons/RemixIcon/align-justify.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/data/icons/RemixIcon/align-left.svg b/data/icons/RemixIcon/align-left.svg
new file mode 100644
index 0000000..14ab85e
--- /dev/null
+++ b/data/icons/RemixIcon/align-left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/data/icons/RemixIcon/align-right.svg b/data/icons/RemixIcon/align-right.svg
new file mode 100644
index 0000000..ef8aaa8
--- /dev/null
+++ b/data/icons/RemixIcon/align-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/data/icons/RemixIcon/text-direction-l.svg b/data/icons/RemixIcon/text-direction-l.svg
new file mode 100644
index 0000000..c1d8cbf
--- /dev/null
+++ b/data/icons/RemixIcon/text-direction-l.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/data/icons/RemixIcon/text-direction-r.svg b/data/icons/RemixIcon/text-direction-r.svg
new file mode 100644
index 0000000..a5a734d
--- /dev/null
+++ b/data/icons/RemixIcon/text-direction-r.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp
index 51c97f9..8230d1b 100644
--- a/src/DrawingProgram/TextBoxEditTool.cpp
+++ b/src/DrawingProgram/TextBoxEditTool.cpp
@@ -68,6 +68,21 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) {
}
});
+ t.gui.left_to_right_line_centered_layout([&]() {
+ if(t.gui.svg_icon_button("Align left button", "data/icons/RemixIcon/align-left.svg", currentPStyle.textAlignment == skia::textlayout::TextAlign::kLeft))
+ a->textBox->set_text_alignment_between(a->cursor->selectionBeginPos.fParagraphIndex, a->cursor->selectionEndPos.fParagraphIndex, skia::textlayout::TextAlign::kLeft);
+ if(t.gui.svg_icon_button("Align center button", "data/icons/RemixIcon/align-center.svg", currentPStyle.textAlignment == skia::textlayout::TextAlign::kCenter))
+ a->textBox->set_text_alignment_between(a->cursor->selectionBeginPos.fParagraphIndex, a->cursor->selectionEndPos.fParagraphIndex, skia::textlayout::TextAlign::kCenter);
+ if(t.gui.svg_icon_button("Align right button", "data/icons/RemixIcon/align-right.svg", currentPStyle.textAlignment == skia::textlayout::TextAlign::kRight))
+ a->textBox->set_text_alignment_between(a->cursor->selectionBeginPos.fParagraphIndex, a->cursor->selectionEndPos.fParagraphIndex, skia::textlayout::TextAlign::kRight);
+ if(t.gui.svg_icon_button("Align justify button", "data/icons/RemixIcon/align-justify.svg", currentPStyle.textAlignment == skia::textlayout::TextAlign::kJustify))
+ a->textBox->set_text_alignment_between(a->cursor->selectionBeginPos.fParagraphIndex, a->cursor->selectionEndPos.fParagraphIndex, skia::textlayout::TextAlign::kJustify);
+ if(t.gui.svg_icon_button("Text direction left", "data/icons/RemixIcon/text-direction-l.svg", currentPStyle.textDirection == skia::textlayout::TextDirection::kLtr))
+ a->textBox->set_text_direction_between(a->cursor->selectionBeginPos.fParagraphIndex, a->cursor->selectionEndPos.fParagraphIndex, skia::textlayout::TextDirection::kLtr);
+ if(t.gui.svg_icon_button("Text direction right", "data/icons/RemixIcon/text-direction-r.svg", currentPStyle.textDirection == skia::textlayout::TextDirection::kRtl))
+ a->textBox->set_text_direction_between(a->cursor->selectionBeginPos.fParagraphIndex, a->cursor->selectionEndPos.fParagraphIndex, skia::textlayout::TextDirection::kRtl);
+ });
+
if(t.gui.slider_scalar_field("Font Size Slider", "Font Size", &newFontSize, 3, 100)) {
currentMods[TextStyleModifier::ModifierType::SIZE] = std::make_shared(newFontSize);
a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, currentMods[TextStyleModifier::ModifierType::SIZE]);
@@ -90,17 +105,8 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) {
t.gui.pop_id();
- if(a->textBox->inputChangedTextBox) {
- set_mods_at_selection(a);
- newFontName = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::FONT_FAMILIES])->get_families().back().c_str();
- newFontSize = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::SIZE])->get_size();
- newTextColor = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::COLOR])->get_color();
- newIsBold = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::WEIGHT])->get_weight() == SkFontStyle::Weight::kBold_Weight;
- newIsItalic = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::SLANT])->get_slant() == SkFontStyle::Slant::kItalic_Slant;
- newIsUnderlined = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::DECORATION])->get_decoration_value() & skia::textlayout::TextDecoration::kUnderline;
- newIsLinethrough = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::DECORATION])->get_decoration_value() & skia::textlayout::TextDecoration::kLineThrough;
- newIsOverline = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::DECORATION])->get_decoration_value() & skia::textlayout::TextDecoration::kOverline;
- }
+ if(a->textBox->inputChangedTextBox)
+ set_styles_at_selection(a);
// 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;
@@ -119,13 +125,24 @@ uint8_t TextBoxEditTool::get_new_decoration_value() {
return toRet;
}
-void TextBoxEditTool::set_mods_at_selection(const std::shared_ptr& a) {
+void TextBoxEditTool::set_styles_at_selection(const std::shared_ptr& a) {
TextPosition start = std::min(a->cursor->selectionBeginPos, a->cursor->selectionEndPos);
TextPosition end = std::max(a->cursor->selectionBeginPos, a->cursor->selectionEndPos);
if(start == end)
currentMods = a->textBox->get_mods_used_at_pos(a->textBox->move(TextBox::Movement::LEFT, start));
else
currentMods = a->textBox->get_mods_used_at_pos(start);
+
+ newFontName = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::FONT_FAMILIES])->get_families().back().c_str();
+ newFontSize = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::SIZE])->get_size();
+ newTextColor = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::COLOR])->get_color();
+ newIsBold = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::WEIGHT])->get_weight() == SkFontStyle::Weight::kBold_Weight;
+ newIsItalic = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::SLANT])->get_slant() == SkFontStyle::Slant::kItalic_Slant;
+ newIsUnderlined = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::DECORATION])->get_decoration_value() & skia::textlayout::TextDecoration::kUnderline;
+ newIsLinethrough = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::DECORATION])->get_decoration_value() & skia::textlayout::TextDecoration::kLineThrough;
+ newIsOverline = std::static_pointer_cast(currentMods[TextStyleModifier::ModifierType::DECORATION])->get_decoration_value() & skia::textlayout::TextDecoration::kOverline;
+
+ currentPStyle = a->textBox->get_paragraph_style_data_at(start.fParagraphIndex);
}
void TextBoxEditTool::commit_edit_updates(const std::shared_ptr& comp, std::any& prevData) {
@@ -176,7 +193,7 @@ void TextBoxEditTool::edit_start(EditTool& editTool, const std::shared_ptrcommit_update(drawP);
a->client_send_update(drawP, false);
- set_mods_at_selection(a);
+ set_styles_at_selection(a);
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 37cf2ae..d114692 100644
--- a/src/DrawingProgram/TextBoxEditTool.hpp
+++ b/src/DrawingProgram/TextBoxEditTool.hpp
@@ -28,9 +28,10 @@ class TextBoxEditTool : public DrawingProgramEditToolBase {
bool newIsLinethrough = false;
bool newIsOverline = false;
std::string newFontName;
+ ParagraphStyleData currentPStyle;
uint8_t get_new_decoration_value();
- void set_mods_at_selection(const std::shared_ptr& a);
+ void set_styles_at_selection(const std::shared_ptr& a);
RichText::TextStyleModifier::ModifierMap currentMods;
std::vector sortedFontList;
diff --git a/src/RichText/ParagraphStyleData.cpp b/src/RichText/ParagraphStyleData.cpp
index e69de29..a3fc29b 100644
--- a/src/RichText/ParagraphStyleData.cpp
+++ b/src/RichText/ParagraphStyleData.cpp
@@ -0,0 +1,8 @@
+#include "ParagraphStyleData.hpp"
+
+skia::textlayout::ParagraphStyle ParagraphStyleData::get_paragraph_style() const {
+ skia::textlayout::ParagraphStyle pStyle;
+ pStyle.setTextAlign(textAlignment);
+ pStyle.setTextDirection(textDirection);
+ return pStyle;
+}
diff --git a/src/RichText/ParagraphStyleData.hpp b/src/RichText/ParagraphStyleData.hpp
index 4e67884..e1c7ecf 100644
--- a/src/RichText/ParagraphStyleData.hpp
+++ b/src/RichText/ParagraphStyleData.hpp
@@ -1,6 +1,18 @@
#pragma once
+#include
struct ParagraphStyleData {
-
+ skia::textlayout::TextAlign textAlignment = skia::textlayout::TextAlign::kLeft;
+ skia::textlayout::TextDirection textDirection = skia::textlayout::TextDirection::kLtr;
+ template void save(Archive& a) const {
+ a(static_cast(textAlignment), static_cast(textDirection));
+ }
+ template void load(Archive& a) {
+ uint8_t alignRead, directionRead;
+ a(alignRead, directionRead);
+ textAlignment = static_cast(alignRead);
+ textDirection = static_cast(directionRead);
+ }
+ skia::textlayout::ParagraphStyle get_paragraph_style() const;
};
diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp
index 6808db5..4e14f76 100644
--- a/src/RichText/TextBox.cpp
+++ b/src/RichText/TextBox.cpp
@@ -274,6 +274,7 @@ TextData TextBox::get_rich_text_data() {
for(auto& p : paragraphs) {
toRet.paragraphs.emplace_back();
toRet.paragraphs.back().text = p.text;
+ toRet.paragraphs.back().pStyleData = p.pStyleData;
}
toRet.tStyleMods = tStyleMods;
return toRet;
@@ -295,6 +296,7 @@ void TextBox::set_rich_text_data(const TextData& richText) {
for(auto& p : richText.paragraphs) {
paragraphs.emplace_back();
paragraphs.back().text = p.text;
+ paragraphs.back().pStyleData = p.pStyleData;
}
if(richText.paragraphs.empty())
paragraphs.emplace_back();
@@ -308,15 +310,11 @@ std::string TextBox::get_string() {
}
std::string TextBox::get_text_between(TextPosition p1, TextPosition p2) {
- p1 = move(Movement::NOWHERE, p1);
- p2 = move(Movement::NOWHERE, p2);
+ auto [start, end] = get_start_end_text_pos(p1, p2);
- if(p1 == p2)
+ if(start == end)
return "";
- TextPosition start = std::min(p1, p2);
- TextPosition end = std::max(p1, p2);
-
std::string toRet;
for(size_t pIndex = start.fParagraphIndex; pIndex <= end.fParagraphIndex; pIndex++) {
@@ -395,11 +393,8 @@ void TextBox::erase_if_over_all_styles_until_pos(TextPosition pos, const std::fu
}
void TextBox::set_text_style_modifier_between(TextPosition p1, TextPosition p2, const std::shared_ptr& modifier) {
- p1 = move(Movement::NOWHERE, p1);
- p2 = move(Movement::NOWHERE, p2);
- if(p1 != p2) {
- TextPosition start = std::min(p1, p2);
- TextPosition end = std::max(p1, p2);
+ auto [start, end] = get_start_end_text_pos(p1, p2);
+ if(start != end) {
auto lastModOfThisTypeBeforeEnd = get_last_text_style_mod_before_pos(end, modifier->get_type());
assert(lastModOfThisTypeBeforeEnd != nullptr);
erase_if_over_all_styles_until_pos(end, [&](TextPosition p, const std::shared_ptr& modToCheck) {
@@ -413,6 +408,26 @@ void TextBox::set_text_style_modifier_between(TextPosition p1, TextPosition p2,
}
}
+void TextBox::set_text_alignment_between(size_t paragraphIndex1, size_t paragraphIndex2, skia::textlayout::TextAlign newAlignment) {
+ auto [start, end] = get_start_end_paragraph_pos(paragraphIndex1, paragraphIndex2);
+ for(size_t i = start; i <= end; i++)
+ paragraphs[i].pStyleData.textAlignment = newAlignment;
+ inputChangedTextBox = true;
+ needsRebuild = true;
+}
+
+void TextBox::set_text_direction_between(size_t paragraphIndex1, size_t paragraphIndex2, skia::textlayout::TextDirection newDirection) {
+ auto [start, end] = get_start_end_paragraph_pos(paragraphIndex1, paragraphIndex2);
+ for(size_t i = start; i <= end; i++)
+ paragraphs[i].pStyleData.textDirection = newDirection;
+ inputChangedTextBox = true;
+ needsRebuild = true;
+}
+
+ParagraphStyleData TextBox::get_paragraph_style_data_at(size_t paragraphIndex) {
+ return paragraphs[std::min(paragraphIndex, paragraphs.size() - 1)].pStyleData;
+}
+
void TextBox::insert_style_at_pos(TextPosition pos, const std::shared_ptr& modifier) {
size_t indexToPlaceAt = 0;
bool finishedInsertingStyle = false;
@@ -445,7 +460,7 @@ TextPosition TextBox::move(Movement movement, TextPosition pos, std::optionalpos.fParagraphIndex != pIndex) {
@@ -721,6 +735,7 @@ TextPosition TextBox::insert(TextPosition pos, std::string_view textToInsert, co
}
}
paragraphs.insert(paragraphs.begin() + pos.fParagraphIndex + 1, ParagraphData{});
+ paragraphs[pos.fParagraphIndex + 1].pStyleData = paragraphs[pos.fParagraphIndex].pStyleData;
paragraphs[pos.fParagraphIndex + 1].text = paragraphs[pos.fParagraphIndex].text.substr(pos.fTextByteIndex, paragraphs[pos.fParagraphIndex].text.size() - pos.fTextByteIndex);
paragraphs[pos.fParagraphIndex].text.erase(pos.fTextByteIndex, paragraphs[pos.fParagraphIndex].text.size() - pos.fTextByteIndex);
pos.fParagraphIndex++;
@@ -759,11 +774,8 @@ TextPosition TextBox::insert(TextPosition pos, std::string_view textToInsert, co
}
TextPosition TextBox::remove(TextPosition p1, TextPosition p2) {
- p1 = move(Movement::NOWHERE, p1);
- p2 = move(Movement::NOWHERE, p2);
+ auto [start, end] = get_start_end_text_pos(p1, p2);
- TextPosition start = std::min(p1, p2);
- TextPosition end = std::max(p1, p2);
if(start == end || start.fParagraphIndex == paragraphs.size())
return start;
if(start.fParagraphIndex == end.fParagraphIndex) {
@@ -817,7 +829,7 @@ SkRect TextBox::get_cursor_rect(TextPosition pos) {
const ParagraphData& pData = paragraphs[pos.fParagraphIndex];
if(pData.text.empty()) {
- bool isAlignLeft = (pData.pStyle.getTextAlign() == skia::textlayout::TextAlign::kLeft) || (pData.pStyle.getTextAlign() == skia::textlayout::TextAlign::kJustify && pData.pStyle.getTextDirection() == skia::textlayout::TextDirection::kLtr);
+ bool isAlignLeft = (pData.pStyleData.textAlignment == skia::textlayout::TextAlign::kLeft) || (pData.pStyleData.textAlignment == skia::textlayout::TextAlign::kJustify && pData.pStyleData.textDirection == skia::textlayout::TextDirection::kLtr);
topPoint = {isAlignLeft ? 0.0f : pData.p->getMaxWidth(), pData.heightOffset};
height = pData.p->getHeight();
}
@@ -856,14 +868,10 @@ SkRect TextBox::get_cursor_rect(TextPosition pos) {
}
void TextBox::rects_between_text_positions_func(TextPosition p1, TextPosition p2, std::function f) {
- p1 = move(Movement::NOWHERE, p1);
- p2 = move(Movement::NOWHERE, p2);
+ auto [start, end] = get_start_end_text_pos(p1, p2);
constexpr float SELECTION_RECT_EXTRA_AREA = 1.0f;
- TextPosition start = std::min(p1, p2);
- TextPosition end = std::max(p1, p2);
-
for(size_t p = start.fParagraphIndex; p <= end.fParagraphIndex; p++) {
size_t tStart = p == start.fParagraphIndex ? start.fTextByteIndex : 0;
size_t tEnd = p == end.fParagraphIndex ? end.fTextByteIndex : paragraphs[p].text.size();
@@ -884,6 +892,18 @@ void TextBox::rects_between_text_positions_func(TextPosition p1, TextPosition p2
}
}
+std::pair TextBox::get_start_end_text_pos(TextPosition p1, TextPosition p2) {
+ p1 = move(Movement::NOWHERE, p1);
+ p2 = move(Movement::NOWHERE, p2);
+ return {std::min(p1, p2), std::max(p1, p2)};
+}
+
+std::pair TextBox::get_start_end_paragraph_pos(size_t p1, size_t p2) {
+ p1 = std::min(p1, paragraphs.size() - 1);
+ p2 = std::min(p2, paragraphs.size() - 1);
+ return {std::min(p1, p2), std::max(p1, p2)};
+}
+
void TextBox::paint(SkCanvas* canvas, const PaintOpts& paintOpts) {
rebuild();
diff --git a/src/RichText/TextBox.hpp b/src/RichText/TextBox.hpp
index 1ae3a12..70cd1fb 100644
--- a/src/RichText/TextBox.hpp
+++ b/src/RichText/TextBox.hpp
@@ -8,6 +8,7 @@
#include "../SharedTypes.hpp"
#include "TextStyleModifier.hpp"
#include "cereal/archives/portable_binary.hpp"
+#include "ParagraphStyleData.hpp"
namespace RichText {
@@ -35,8 +36,9 @@ typedef std::vector TextStyleModContainer;
struct TextData {
struct Paragraph {
std::string text;
+ ParagraphStyleData pStyleData;
template void serialize(Archive& a) {
- a(text);
+ a(text, pStyleData);
}
};
void save(cereal::PortableBinaryOutputArchive& a) const;
@@ -105,6 +107,11 @@ class TextBox {
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);
+
+ void set_text_alignment_between(size_t paragraphIndex1, size_t paragraphIndex2, skia::textlayout::TextAlign newAlignment);
+ void set_text_direction_between(size_t paragraphIndex1, size_t paragraphIndex2, skia::textlayout::TextDirection newDirection);
+ ParagraphStyleData get_paragraph_style_data_at(size_t paragraphIndex);
+
TextStyleModifier::ModifierMap get_mods_used_at_pos(TextPosition p);
TextData get_rich_text_data();
@@ -122,6 +129,9 @@ class TextBox {
void process_text_input(Cursor& cur, const std::string& in, const std::optional& inputModMap = std::nullopt);
private:
+ std::pair get_start_end_text_pos(TextPosition p1, TextPosition p2);
+ std::pair get_start_end_paragraph_pos(size_t p1, size_t p2);
+
static size_t count_grapheme(const std::string& text);
static size_t next_grapheme(const std::string& text, size_t textBytePos);
static size_t prev_grapheme(const std::string& text, size_t textBytePos);
@@ -152,7 +162,7 @@ class TextBox {
std::string text;
std::unique_ptr p;
float heightOffset;
- skia::textlayout::ParagraphStyle pStyle;
+ ParagraphStyleData pStyleData;
};
skia::textlayout::TextStyle initialTStyle;