Started working on RichTextBox (Broken commit)

This commit is contained in:
Yousef Khadadeh 2025-10-16 21:22:08 +03:00
parent 5f93445506
commit e507473e3a
7 changed files with 454 additions and 50 deletions

View file

@ -55,6 +55,7 @@ set(sources "src/main.cpp"
"src/Server/ServerData.cpp"
"src/CollabTextBox/CollabTextBox.cpp"
"src/CollabTextBox/CollabTextBoxShaper.cpp"
"src/RichTextBox/RichTextBox.cpp"
"src/DrawingProgram/DrawingProgramCache.cpp"
"src/DrawingProgram/DrawingProgram.cpp"
"src/DrawingProgram/BrushTool.cpp"

View file

@ -77,6 +77,17 @@ void InputManager::text_input_silence_everything() {
text.newInput.clear();
}
void InputManager::Text::set_rich_text_box_input(const std::shared_ptr<RichTextBox>& nTextBox, const std::shared_ptr<RichTextBox::Cursor>& nCursor) {
set_accepting_input();
newTextBox = nTextBox;
newCursor = nCursor;
}
void InputManager::Text::add_text_to_textbox(const std::string& inputText) {
if(textBox)
textBox->process_text_input(*cursor, inputText);
}
void InputManager::Text::set_accepting_input() {
acceptingInputNew = true;
}
@ -206,24 +217,43 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
switch(kPress) {
case SDLK_UP:
set_key_down(e, KEY_TEXT_UP);
if(text.textBox && key(KEY_TEXT_UP).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::UP, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_DOWN:
set_key_down(e, KEY_TEXT_DOWN);
if(text.textBox && key(KEY_TEXT_DOWN).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::DOWN, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_LEFT:
set_key_down(e, KEY_TEXT_LEFT);
if(text.textBox && key(KEY_TEXT_LEFT).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::LEFT, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_RIGHT:
set_key_down(e, KEY_TEXT_RIGHT);
if(text.textBox && key(KEY_TEXT_RIGHT).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::RIGHT, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_BACKSPACE:
set_key_down(e, KEY_TEXT_BACKSPACE);
if(text.textBox && key(KEY_TEXT_BACKSPACE).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::BACKSPACE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_DELETE:
set_key_down(e, KEY_TEXT_DELETE);
if(text.textBox && key(KEY_TEXT_DELETE).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::DELETE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_HOME:
set_key_down(e, KEY_TEXT_HOME);
if(text.textBox && key(KEY_TEXT_HOME).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::HOME, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_END:
set_key_down(e, KEY_TEXT_END);
if(text.textBox && key(KEY_TEXT_END).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::END, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_LSHIFT:
set_key_down(e, KEY_TEXT_SHIFT);
@ -236,26 +266,41 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
set_key_down(e, KEY_TEXT_CTRL);
set_key_down(e, KEY_GENERIC_LCTRL);
break;
case SDLK_LMETA:
set_key_down(e, KEY_TEXT_META);
set_key_down(e, KEY_GENERIC_LMETA);
break;
case SDLK_C:
// Use either Ctrl or Meta (command for Mac) keys. We do either instead of checking, since checking can get complicated on Emscripten
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_COPY);
if(text.textBox && key(KEY_TEXT_COPY).repeat)
set_clipboard_str(text.textBox->process_copy(*text.cursor));
break;
case SDLK_X:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_CUT);
if(text.textBox && key(KEY_TEXT_CUT).repeat)
set_clipboard_str(text.textBox->process_cut(*text.cursor));
break;
case SDLK_V:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_PASTE);
if(text.textBox && key(KEY_TEXT_PASTE).repeat)
text.textBox->process_text_input(*text.cursor, get_clipboard_str());
break;
case SDLK_A:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_SELECTALL);
if(text.textBox && key(KEY_TEXT_SELECTALL).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::SELECT_ALL, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
case SDLK_RETURN:
case SDLK_RETURN: {
set_key_down(e, KEY_TEXT_ENTER);
if(text.textBox && key(KEY_TEXT_ENTER).repeat)
text.textBox->process_key_input(*text.cursor, RichTextBox::InputKey::ENTER, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held);
break;
}
case SDLK_ESCAPE:
set_key_down(e, KEY_GENERIC_ESCAPE);
break;
@ -271,6 +316,10 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
set_key_down(e, f->second);
}
bool InputManager::ctrl_or_meta_held() {
return key(KEY_GENERIC_LCTRL).held || key(KEY_GENERIC_LMETA).held;
}
void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) {
auto kPress = e.key;
//auto kMod = e.mod;
@ -297,6 +346,9 @@ void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) {
case SDLK_HOME:
set_key_up(e, KEY_TEXT_HOME);
break;
case SDLK_END:
set_key_up(e, KEY_TEXT_END);
break;
case SDLK_LSHIFT:
set_key_up(e, KEY_TEXT_SHIFT);
set_key_up(e, KEY_GENERIC_LSHIFT);
@ -354,6 +406,11 @@ void InputManager::frame_reset(const Vector2i& windowSize) {
mouse.middleClicks = 0;
text.acceptingInput = text.acceptingInputNew;
text.acceptingInputNew = false;
text.cursor = text.newCursor;
text.textBox = text.newTextBox;
text.newCursor = nullptr;
text.newTextBox = nullptr;
text.newInput.clear();
droppedItems.clear();

View file

@ -10,6 +10,7 @@
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keycode.h>
#include "RichTextBox/RichTextBox.hpp"
using namespace Eigen;
@ -85,9 +86,20 @@ struct InputManager {
void set_accepting_input();
bool get_accepting_input();
void set_rich_text_box_input(const std::shared_ptr<RichTextBox>& nTextBox, const std::shared_ptr<RichTextBox::Cursor>& nCursor);
void add_text_to_textbox(const std::string& inputText);
private:
std::shared_ptr<RichTextBox> textBox;
std::shared_ptr<RichTextBox::Cursor> cursor;
std::shared_ptr<RichTextBox> newTextBox;
std::shared_ptr<RichTextBox::Cursor> newCursor;
bool acceptingInput = false;
bool acceptingInputNew = false;
friend struct InputManager;
} text;
@ -147,22 +159,26 @@ struct InputManager {
KEY_TEXT_BACKSPACE,
KEY_TEXT_DELETE,
KEY_TEXT_HOME,
KEY_TEXT_END,
KEY_TEXT_SHIFT,
KEY_TEXT_COPY,
KEY_TEXT_CUT,
KEY_TEXT_PASTE,
KEY_TEXT_ENTER,
KEY_TEXT_CTRL,
KEY_TEXT_META,
KEY_TEXT_SELECTALL,
KEY_GENERIC_ESCAPE,
KEY_GENERIC_LSHIFT,
KEY_GENERIC_LALT,
KEY_GENERIC_LCTRL,
KEY_GENERIC_LMETA,
KEY_COUNT
};
typedef unsigned KeyCode;
bool ctrl_or_meta_held();
void stop_key_input();
void set_pen_button_up(const SDL_PenButtonEvent& e);
void set_pen_button_down(const SDL_PenButtonEvent& e);

View file

@ -1,4 +1,6 @@
#include "MainProgram.hpp"
#include "CollabTextBox/CollabTextBoxCursor.hpp"
#include "DrawingProgram/DrawingProgramToolBase.hpp"
#include "cereal/archives/portable_binary.hpp"
#include "cereal/details/helpers.hpp"
#include <chrono>
@ -35,6 +37,12 @@
#include <Helpers/Logger.hpp>
#include <Helpers/Networking/NetLibrary.hpp>
#include "RichTextBox/RichTextBox.hpp"
std::shared_ptr<RichTextBox> textBox;
std::shared_ptr<RichTextBox::Cursor> textBoxCursor;
RichTextBox::PaintOpts paintOpts;
MainProgram::MainProgram():
toolbar(*this)
{
@ -60,67 +68,97 @@ MainProgram::MainProgram():
*logFile << "[CHAT] " << text << std::endl;
std::cout << "[CHAT] " << text << std::endl;
});
textBox = std::make_shared<RichTextBox>();
textBoxCursor = std::make_shared<RichTextBox::Cursor>();
textBox->set_width(1000.0f);
textBox->set_font_collection(fonts.collection);
textBox->insert(0, R"(
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
)");
paintOpts.cursor = RichTextBox::Cursor{};
paintOpts.cursorColor = {1.0f, 0.0f, 0.0f, 0.7f};
}
void MainProgram::update() {
while(std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastFrameTime).count() < 1.0 / fpsLimit);
lastFrameTime = std::chrono::steady_clock::now();
input.text.set_rich_text_box_input(textBox, textBoxCursor);
std::erase_if(input.droppedItems, [&](auto& droppedItem) {
if(droppedItem.isFile && std::filesystem::is_regular_file(droppedItem.droppedData)) {
std::filesystem::path droppedFilePath(droppedItem.droppedData);
if(droppedFilePath.has_extension() && droppedFilePath.extension().string() == std::string("." + World::FILE_EXTENSION)) {
new_tab({
.conType = World::CONNECTIONTYPE_LOCAL,
.fileSource = droppedFilePath.string()
}, true);
return true;
}
}
return false;
});
//while(std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastFrameTime).count() < 1.0 / fpsLimit);
//lastFrameTime = std::chrono::steady_clock::now();
if(setTabToClose) {
worlds.erase(worlds.begin() + setTabToClose.value());
setTabToClose.reset();
}
//if(input.key(InputManager::KEY_DRAW_TOOL_BRUSH).repeat)
// paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kRight);
//else if(input.key(InputManager::KEY_DRAW_TOOL_ERASER).repeat)
// paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kLeft);
//else if(input.key(InputManager::KEY_DRAW_TOOL_TEXTBOX).repeat)
// paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kUp);
//else if(input.key(InputManager::KEY_DRAW_TOOL_ELLIPSE).repeat)
// paintOpts.cursor.value().pos = textBox.move(paintOpts.cursor.value().pos, RichTextBox::Movement::kDown);
if(worldIndex >= worlds.size())
worldIndex = worlds.size() - 1;
if(worlds.size() == 0)
new_tab({
.conType = World::CONNECTIONTYPE_LOCAL
}, true);
//std::erase_if(input.droppedItems, [&](auto& droppedItem) {
// if(droppedItem.isFile && std::filesystem::is_regular_file(droppedItem.droppedData)) {
// std::filesystem::path droppedFilePath(droppedItem.droppedData);
// if(droppedFilePath.has_extension() && droppedFilePath.extension().string() == std::string("." + World::FILE_EXTENSION)) {
// new_tab({
// .conType = World::CONNECTIONTYPE_LOCAL,
// .fileSource = droppedFilePath.string()
// }, true);
// return true;
// }
// }
// return false;
//});
std::shared_ptr<World> oldWorld = world;
world = worlds[worldIndex];
//if(setTabToClose) {
// worlds.erase(worlds.begin() + setTabToClose.value());
// setTabToClose.reset();
//}
deltaTime.update_time_since();
deltaTime.update_time_point();
//if(worldIndex >= worlds.size())
// worldIndex = worlds.size() - 1;
//if(worlds.size() == 0)
// new_tab({
// .conType = World::CONNECTIONTYPE_LOCAL
// }, true);
toolbar.initialize_io_before_update();
//std::shared_ptr<World> oldWorld = world;
//world = worlds[worldIndex];
for(auto& w : worlds) {
if(w == world)
w->focus_update();
else
w->unfocus_update();
}
//deltaTime.update_time_since();
//deltaTime.update_time_point();
toolbar.update(); // GUI should be setup after the world data has been fully updated for this frame, so that the GUI reflects the current state of the world data
//toolbar.initialize_io_before_update();
if(input.key(InputManager::KEY_NOGUI).pressed)
drawGui = !drawGui;
//for(auto& w : worlds) {
// if(w == world)
// w->focus_update();
// else
// w->unfocus_update();
//}
if(input.key(InputManager::KEY_FULLSCREEN).pressed)
input.toggleFullscreen = true;
//toolbar.update(); // GUI should be setup after the world data has been fully updated for this frame, so that the GUI reflects the current state of the world data
NetLibrary::update();
//if(input.key(InputManager::KEY_NOGUI).pressed)
// drawGui = !drawGui;
if(tabSetToOpen) {
tabSetToOpen = false;
new_tab_open();
}
//if(input.key(InputManager::KEY_FULLSCREEN).pressed)
// input.toggleFullscreen = true;
//NetLibrary::update();
//if(tabSetToOpen) {
// tabSetToOpen = false;
// new_tab_open();
//}
return;
}
@ -229,9 +267,16 @@ void MainProgram::load_config() {
}
void MainProgram::draw(SkCanvas* canvas) {
canvas->clear(transparentBackground ? SkColor4f{0.0f, 0.0f, 0.0f, 0.0f} : world->canvasTheme.backColor);
world->draw(canvas);
toolbar.draw(canvas);
//canvas->clear(transparentBackground ? SkColor4f{0.0f, 0.0f, 0.0f, 0.0f} : world->canvasTheme.backColor);
//world->draw(canvas);
//toolbar.draw(canvas);
canvas->clear(SkColor4f{0.0f, 0.0f, 0.0f, 1.0f});
canvas->save();
canvas->translate(200.0f, 200.0f);
paintOpts.cursor = *textBoxCursor;
textBox->paint(canvas, paintOpts);
canvas->restore();
}
bool MainProgram::network_being_used() {

View file

@ -0,0 +1,215 @@
#include "RichTextBox.hpp"
#include <modules/skunicode/include/SkUnicode_icu.h>
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
#include <modules/skparagraph/src/ParagraphImpl.h>
#include <Helpers/MathExtras.hpp>
RichTextBox::RichTextBox() {
}
void RichTextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift) {
bool moved = false;
switch(in) {
case InputKey::LEFT:
cur.pos = cur.selectionBeginPos = move(Movement::LEFT, cur.pos);
moved = true;
break;
case InputKey::RIGHT:
cur.pos = cur.selectionBeginPos = move(Movement::RIGHT, cur.pos);
moved = true;
break;
case InputKey::UP:
cur.pos = cur.selectionBeginPos = move(Movement::UP, cur.pos);
moved = true;
break;
case InputKey::DOWN:
cur.pos = cur.selectionBeginPos = move(Movement::DOWN, cur.pos);
moved = true;
break;
case InputKey::HOME:
cur.pos = cur.selectionBeginPos = move(Movement::HOME, cur.pos);
moved = true;
break;
case InputKey::END:
cur.pos = cur.selectionBeginPos = move(Movement::END, cur.pos);
moved = true;
break;
case InputKey::BACKSPACE:
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));
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));
break;
case InputKey::ENTER:
if(newlinesAllowed)
process_text_input(cur, "\n");
break;
case InputKey::SELECT_ALL:
cur.selectionBeginPos = 0;
cur.pos = cur.selectionEndPos = text.size();
break;
}
if(moved && !shift)
cur.selectionEndPos = cur.selectionBeginPos;
}
std::string RichTextBox::process_copy(Cursor& cur) {
uint64_t trueBegin = std::min(cur.selectionBeginPos, cur.selectionEndPos);
uint64_t trueEnd = std::max(cur.selectionBeginPos, cur.selectionEndPos);
return text.substr(trueBegin, trueEnd - trueBegin);
}
std::string RichTextBox::process_cut(Cursor& cur) {
std::string toRet = process_copy(cur);
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos);
return toRet;
}
void RichTextBox::process_text_input(Cursor& cur, const std::string& in) {
if(!in.empty()) {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos);
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert(cur.pos, in);
}
}
uint64_t RichTextBox::move(Movement movement, uint64_t pos) {
rebuild();
if(pos >= text.size())
pos = text.size();
switch (movement) {
case Movement::LEFT:
if(pos != 0)
pos--;
break;
case Movement::RIGHT:
if(pos < text.size())
pos++;
break;
case Movement::UP: {
SkRect cursorRect = get_cursor_rect(pos);
Vector2f pointToCheck{cursorRect.x(), cursorRect.top() - 0.1f};
pos = paragraph->getGlyphPositionAtCoordinate(pointToCheck.x(), pointToCheck.y()).position;
break;
}
case Movement::DOWN: {
SkRect cursorRect = get_cursor_rect(pos);
Vector2f pointToCheck{cursorRect.x(), cursorRect.bottom() + 0.1f};
pos = paragraph->getGlyphPositionAtCoordinate(pointToCheck.x(), pointToCheck.y()).position;
break;
}
case Movement::HOME:
pos = 0;
break;
case Movement::END:
pos = text.size();
break;
}
return pos;
}
void RichTextBox::set_width(float newWidth) {
width = newWidth;
needsRebuild = true;
}
void RichTextBox::set_allow_newlines(bool allow) {
newlinesAllowed = true;
}
void RichTextBox::set_font_collection(const sk_sp<skia::textlayout::FontCollection>& fC) {
fontCollection = fC;
needsRebuild = true;
}
void RichTextBox::rebuild() {
if(needsRebuild) {
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(20.0f);
tStyle.setFontFamilies({SkString{"Roboto"}});
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, fontCollection, SkUnicodes::ICU::Make());
a.addText(text.c_str(), text.length());
paragraph = a.Build();
paragraph->layout(width);
}
}
uint64_t RichTextBox::insert(uint64_t pos, const std::string& textToInsert) {
text.insert(pos, textToInsert);
needsRebuild = true;
return pos + textToInsert.size();
}
uint64_t RichTextBox::remove(uint64_t start, uint64_t end) {
if(start > end)
std::swap(start, end);
text.erase(start, end - start);
needsRebuild = true;
return start;
}
SkRect RichTextBox::get_cursor_rect(uint64_t pos) {
SkPoint topLeft;
float height;
if(text.empty()) {
topLeft = {0.0f, 0.0f};
height = paragraph->getHeight();
}
else if(pos == text.size()) {
std::vector<skia::textlayout::TextBox> posBoxes = paragraph->getRectsForRange(pos - 1, pos, skia::textlayout::RectHeightStyle::kMax, skia::textlayout::RectWidthStyle::kMax);
if(text.back() == '\n') {
topLeft = {0.0f, posBoxes.back().rect.bottom()};
height = std::abs(paragraph->getHeight() - topLeft.y());
}
else {
topLeft = posBoxes.front().rect.TR();
height = posBoxes.front().rect.height();
}
}
else {
std::vector<skia::textlayout::TextBox> posBoxes = paragraph->getRectsForRange(pos, pos + 1, skia::textlayout::RectHeightStyle::kMax, skia::textlayout::RectWidthStyle::kMax);
topLeft = posBoxes.front().rect.TL();
height = posBoxes.front().rect.height();
}
return SkRect::MakeXYWH(topLeft.x(), topLeft.y(), 3.0f, height);
}
void RichTextBox::paint(SkCanvas* canvas, const PaintOpts& paintOpts) {
rebuild();
if(paintOpts.cursor.has_value()) {
auto& cur = paintOpts.cursor.value();
SkPaint selection{color_mul_alpha(paintOpts.cursorColor, 0.6f)};
uint64_t trueSelectionStart = std::min(cur.selectionBeginPos, cur.selectionEndPos);
uint64_t trueSelectionEnd = std::max(cur.selectionBeginPos, cur.selectionEndPos);
std::vector<skia::textlayout::TextBox> selectionBoxes = paragraph->getRectsForRange(trueSelectionStart, trueSelectionEnd, skia::textlayout::RectHeightStyle::kTight, skia::textlayout::RectWidthStyle::kTight);
for(auto& selectionBox : selectionBoxes) {
SkRect rectFixSmallOverlap = SkRect::MakeXYWH(selectionBox.rect.x(), selectionBox.rect.y(), selectionBox.rect.width(), std::max(selectionBox.rect.height() - 0.25f, 0.0f));
canvas->drawRect(rectFixSmallOverlap, selection);
}
if(cur.pos <= text.size()) {
canvas->drawRect(get_cursor_rect(cur.pos), SkPaint{paintOpts.cursorColor});
}
}
paragraph->paint(canvas, 0.0f, 0.0f);
}

View file

@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
#include <include/core/SkCanvas.h>
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/skparagraph/include/Paragraph.h>
#include "../SharedTypes.hpp"
class RichTextBox {
public:
RichTextBox();
enum class Movement {
LEFT,
RIGHT,
UP,
DOWN,
HOME,
END
};
struct Cursor {
uint64_t pos = 0;
uint64_t selectionBeginPos = 0;
uint64_t selectionEndPos = 0;
};
struct PaintOpts {
SkColor4f cursorColor = {1, 0, 0, 1};
std::optional<Cursor> cursor;
};
uint64_t move(Movement movement, uint64_t pos);
void set_width(float newWidth);
uint64_t insert(uint64_t pos, const std::string& textToInsert);
uint64_t remove(uint64_t start, uint64_t end);
void set_font_collection(const sk_sp<skia::textlayout::FontCollection>& fC);
void paint(SkCanvas* canvas, const PaintOpts& paintOpts);
void set_allow_newlines(bool allow);
enum class InputKey {
LEFT,
RIGHT,
UP,
DOWN,
HOME,
END,
BACKSPACE,
DELETE,
ENTER,
SELECT_ALL
};
void process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift);
std::string process_copy(Cursor& cur);
std::string process_cut(Cursor& cur);
void process_text_input(Cursor& cur, const std::string& in);
private:
SkRect get_cursor_rect(uint64_t pos);
void rebuild();
bool newlinesAllowed = true;
float width = 100.0f;
float fontSize = 0.0f;
bool needsRebuild = true;
sk_sp<skia::textlayout::FontCollection> fontCollection;
std::unique_ptr<skia::textlayout::Paragraph> paragraph;
std::string text;
};

View file

@ -696,6 +696,7 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
break;
case SDL_EVENT_TEXT_INPUT:
mS.m->input.text.newInput += event->text.text;
mS.m->input.text.add_text_to_textbox(event->text.text);
break;
case SDL_EVENT_DROP_FILE:
mS.m->window.scale = SDL_GetWindowPixelDensity(mS.window);