Added rich text clipboard
This commit is contained in:
parent
c7cea45e9c
commit
afe1623cf8
5 changed files with 139 additions and 142 deletions
|
|
@ -101,11 +101,6 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr<DrawComponent>& comp) {
|
|||
a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, currentMods[TextStyleModifier::ModifierType::COLOR]);
|
||||
}
|
||||
|
||||
if(t.gui.text_button_wide("print text", "Print HTML Selection")) {
|
||||
std::cout << "-----HTML-----\n" << a->textBox->get_rich_text_data_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos).get_html() << std::endl;
|
||||
}
|
||||
|
||||
|
||||
t.gui.pop_id();
|
||||
|
||||
if(a->textBox->inputChangedTextBox)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <optional>
|
||||
|
||||
#include "Server/CommandList.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <EmscriptenHelpers/emscripten_browser_clipboard.h>
|
||||
|
||||
|
|
@ -116,6 +118,14 @@ std::string InputManager::get_clipboard_str() {
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string InputManager::get_clipboard_data_for_mimetype(const std::string& mimeType) {
|
||||
size_t datSize = 0;
|
||||
void* dat = SDL_GetClipboardData(mimeType.c_str(), &datSize);
|
||||
if(!dat)
|
||||
return "";
|
||||
return std::string(static_cast<char*>(dat), datSize);
|
||||
}
|
||||
|
||||
void InputManager::set_clipboard_str(std::string_view s) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_browser_clipboard::copy(std::string(s));
|
||||
|
|
@ -124,6 +134,40 @@ void InputManager::set_clipboard_str(std::string_view s) {
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string InputManager::get_infpnt_richtext_mimetype() {
|
||||
return "infpnt/richtext" + std::string(VERSION_STRING);
|
||||
}
|
||||
|
||||
void InputManager::set_clipboard_plain_and_richtext_pair(const std::pair<std::string, std::string>& plainAndRichtextPair) {
|
||||
std::unordered_map<std::string, std::string> clipboardData;
|
||||
clipboardData["text/plain"] = plainAndRichtextPair.first;
|
||||
clipboardData[get_infpnt_richtext_mimetype()] = plainAndRichtextPair.second;
|
||||
set_clipboard_data(clipboardData);
|
||||
}
|
||||
|
||||
void InputManager::set_clipboard_data(const std::unordered_map<std::string, std::string>& newClipboardData) {
|
||||
SDL_ClearClipboardData();
|
||||
clipboardData = newClipboardData;
|
||||
|
||||
std::vector<const char*> mimeTypes;
|
||||
for(auto& [k, v] : clipboardData)
|
||||
mimeTypes.emplace_back(k.c_str());
|
||||
|
||||
SDL_SetClipboardData(
|
||||
[](void *userdata, const char *mime_type, size_t *size) {
|
||||
std::unordered_map<std::string, std::string>& clipboardData = *static_cast<std::unordered_map<std::string, std::string>*>(userdata);
|
||||
std::string mimeTypeStr(mime_type);
|
||||
auto it = clipboardData.find(mimeTypeStr);
|
||||
if(it == clipboardData.end())
|
||||
return static_cast<const void*>(nullptr);
|
||||
*size = it->second.length();
|
||||
return static_cast<const void*>(it->second.c_str());
|
||||
},
|
||||
[](void *userdata) {
|
||||
},
|
||||
static_cast<void*>(&clipboardData), mimeTypes.data(), mimeTypes.size());
|
||||
}
|
||||
|
||||
std::string InputManager::key_assignment_to_str(const Vector2ui32& k) {
|
||||
std::string toRet;
|
||||
if(k.x() & SDL_KMOD_CTRL)
|
||||
|
|
@ -276,19 +320,24 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
|
|||
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));
|
||||
set_clipboard_plain_and_richtext_pair(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));
|
||||
set_clipboard_plain_and_richtext_pair(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(), text.modMap);
|
||||
if(text.textBox && key(KEY_TEXT_PASTE).repeat) {
|
||||
std::string richTextClipboard = get_clipboard_data_for_mimetype(get_infpnt_richtext_mimetype());
|
||||
if(!richTextClipboard.empty())
|
||||
text.textBox->process_rich_text_input(*text.cursor, richTextClipboard);
|
||||
else
|
||||
text.textBox->process_text_input(*text.cursor, get_clipboard_data_for_mimetype("text/plain"), text.modMap);
|
||||
}
|
||||
break;
|
||||
case SDLK_A:
|
||||
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
|
||||
|
|
|
|||
|
|
@ -195,7 +195,15 @@ struct InputManager {
|
|||
|
||||
bool get_clipboard_paste_happened();
|
||||
std::string get_clipboard_str();
|
||||
|
||||
std::string get_clipboard_data_for_mimetype(const std::string& mimeType);
|
||||
|
||||
void set_clipboard_str(std::string_view s);
|
||||
void set_clipboard_plain_and_richtext_pair(const std::pair<std::string, std::string>& plainAndRichtextPair);
|
||||
void set_clipboard_data(const std::unordered_map<std::string, std::string>& newClipboardData);
|
||||
std::string get_infpnt_richtext_mimetype();
|
||||
|
||||
std::unordered_map<std::string, std::string> clipboardData;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
bool clipboardPasteEventHappened = false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "TextBox.hpp"
|
||||
#include "TextStyleModifier.hpp"
|
||||
#include "cereal/archives/portable_binary.hpp"
|
||||
#include <limits>
|
||||
#include <modules/skparagraph/include/DartTypes.h>
|
||||
#include <modules/skparagraph/include/Metrics.h>
|
||||
|
|
@ -15,128 +16,13 @@
|
|||
|
||||
namespace RichText {
|
||||
|
||||
std::string TextData::get_html() {
|
||||
auto nextTStyleModIt = tStyleMods.begin();
|
||||
|
||||
skia::textlayout::TextStyle tStyle;
|
||||
|
||||
std::stringstream toRet;
|
||||
|
||||
for(size_t pIndex = 0; pIndex < paragraphs.size(); pIndex++) {
|
||||
Paragraph& pData = paragraphs[pIndex];
|
||||
size_t tIndex = 0;
|
||||
|
||||
toRet << "<p style=\"" << get_css_from_paragraph_style(pData.pStyleData) << "\">";
|
||||
|
||||
for(;;) {
|
||||
if(nextTStyleModIt == tStyleMods.end() || nextTStyleModIt->pos.fParagraphIndex != pIndex) {
|
||||
if(pData.text.length() != tIndex) {
|
||||
toRet << "<span style=\"" << get_css_from_text_style(tStyle) << "\">"
|
||||
<< pData.text.substr(tIndex, pData.text.length() - tIndex)
|
||||
<< "</span>";
|
||||
}
|
||||
break;
|
||||
}
|
||||
else {
|
||||
auto& nextTStyleMod = *nextTStyleModIt;
|
||||
|
||||
// Print with previous style
|
||||
if(nextTStyleMod.pos.fTextByteIndex != tIndex) {
|
||||
toRet << "<span style=\"" << get_css_from_text_style(tStyle) << "\">"
|
||||
<< pData.text.substr(tIndex, nextTStyleMod.pos.fTextByteIndex - tIndex)
|
||||
<< "</span>";
|
||||
}
|
||||
|
||||
tIndex = nextTStyleMod.pos.fTextByteIndex;
|
||||
|
||||
// Modify tStyle with new data
|
||||
for(auto& [modType, modifier] : nextTStyleMod.mods)
|
||||
modifier->modify_text_style(tStyle);
|
||||
|
||||
++nextTStyleModIt;
|
||||
}
|
||||
}
|
||||
|
||||
toRet << "</p>";
|
||||
std::string TextData::get_serialized() {
|
||||
std::stringstream s;
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive a(s);
|
||||
a(*this);
|
||||
}
|
||||
|
||||
return toRet.str();
|
||||
}
|
||||
|
||||
std::string TextData::get_css_from_paragraph_style(const ParagraphStyleData& pStyle) {
|
||||
std::stringstream toRet;
|
||||
toRet << "text-align:";
|
||||
switch(pStyle.textAlignment) {
|
||||
case skia::textlayout::TextAlign::kLeft:
|
||||
toRet << "left";
|
||||
break;
|
||||
case skia::textlayout::TextAlign::kRight:
|
||||
toRet << "right";
|
||||
break;
|
||||
case skia::textlayout::TextAlign::kCenter:
|
||||
toRet << "center";
|
||||
break;
|
||||
case skia::textlayout::TextAlign::kJustify:
|
||||
toRet << "justify";
|
||||
break;
|
||||
default:
|
||||
toRet << "left";
|
||||
break;
|
||||
}
|
||||
toRet << ";"
|
||||
<< "direction:";
|
||||
switch(pStyle.textDirection) {
|
||||
case skia::textlayout::TextDirection::kLtr:
|
||||
toRet << "ltr";
|
||||
break;
|
||||
case skia::textlayout::TextDirection::kRtl:
|
||||
toRet << "rtl";
|
||||
break;
|
||||
}
|
||||
toRet << ";";
|
||||
return toRet.str();
|
||||
}
|
||||
|
||||
std::string TextData::get_css_from_text_style(const skia::textlayout::TextStyle& tStyle) {
|
||||
std::stringstream toRet;
|
||||
toRet << "font: " << static_cast<int>(tStyle.getFontSize()) << "px ";
|
||||
auto& fontFamilies = tStyle.getFontFamilies();
|
||||
for(size_t i = 0; i < fontFamilies.size(); i++) {
|
||||
auto& family = fontFamilies[i];
|
||||
toRet << std::string(family.c_str(), family.size());
|
||||
if(i != fontFamilies.size() - 1)
|
||||
toRet << ",";
|
||||
}
|
||||
SkPaint paint = tStyle.getForeground();
|
||||
toRet << ";"
|
||||
<< "color: rgb(" << std::fixed << std::setprecision(7) << paint.getColor4f().fR * 255.0f << " " << paint.getColor4f().fG * 255.0f << " " << paint.getColor4f().fB * 255.0f << " / " << paint.getColor4f().fA << ");" << std::defaultfloat
|
||||
<< "font-weight:" << tStyle.getFontStyle().weight() << "; "
|
||||
<< "font-style:";
|
||||
switch(tStyle.getFontStyle().slant()) {
|
||||
case SkFontStyle::Slant::kUpright_Slant:
|
||||
toRet << "normal";
|
||||
break;
|
||||
case SkFontStyle::Slant::kItalic_Slant:
|
||||
toRet << "italic";
|
||||
break;
|
||||
case SkFontStyle::Slant::kOblique_Slant:
|
||||
toRet << "oblique";
|
||||
break;
|
||||
}
|
||||
toRet << ";"
|
||||
<< "text-decoration-line:";
|
||||
if(tStyle.getDecorationType() == skia::textlayout::TextDecoration::kNoDecoration)
|
||||
toRet << " none";
|
||||
else {
|
||||
if(tStyle.getDecorationType() & skia::textlayout::TextDecoration::kOverline)
|
||||
toRet << " overline";
|
||||
if(tStyle.getDecorationType() & skia::textlayout::TextDecoration::kUnderline)
|
||||
toRet << " underline";
|
||||
if(tStyle.getDecorationType() & skia::textlayout::TextDecoration::kLineThrough)
|
||||
toRet << " line-through";
|
||||
}
|
||||
toRet << ";";
|
||||
return toRet.str();
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void TextData::save(cereal::PortableBinaryOutputArchive& a) const {
|
||||
|
|
@ -265,12 +151,12 @@ void TextBox::process_mouse_left_button(Cursor& cur, const Vector2f& pos, bool c
|
|||
}
|
||||
}
|
||||
|
||||
std::string TextBox::process_copy(Cursor& cur) {
|
||||
return get_text_between(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
std::pair<std::string, std::string> TextBox::process_copy(Cursor& cur) {
|
||||
return {get_text_between(cur.selectionBeginPos, cur.selectionEndPos), get_rich_text_data_between(cur.selectionBeginPos, cur.selectionEndPos).get_serialized()};
|
||||
}
|
||||
|
||||
std::string TextBox::process_cut(Cursor& cur) {
|
||||
std::string toRet = process_copy(cur);
|
||||
std::pair<std::string, std::string> TextBox::process_cut(Cursor& cur) {
|
||||
auto toRet = process_copy(cur);
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos) {
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
cur.previousX = std::nullopt;
|
||||
|
|
@ -289,6 +175,22 @@ void TextBox::process_text_input(Cursor& cur, const std::string& in, const std::
|
|||
}
|
||||
}
|
||||
|
||||
void TextBox::process_rich_text_input(Cursor& cur, const std::string& serializedRichText) {
|
||||
if(!serializedRichText.empty()) {
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
RichText::TextData textData;
|
||||
std::stringstream ss(serializedRichText);
|
||||
{
|
||||
cereal::PortableBinaryInputArchive a(ss);
|
||||
a(textData);
|
||||
}
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert_rich_text(cur.pos, textData);
|
||||
cur.previousX = std::nullopt;
|
||||
inputChangedTextBox = true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t TextBox::count_grapheme(const std::string& text) {
|
||||
auto u = SkUnicodes::ICU::Make();
|
||||
auto breakIterator = u->makeBreakIterator(SkUnicode::BreakType::kGraphemes);
|
||||
|
|
@ -442,6 +344,44 @@ TextData TextBox::get_rich_text_data_between(TextPosition p1, TextPosition p2) {
|
|||
return toRet;
|
||||
}
|
||||
|
||||
TextPosition TextBox::insert_rich_text(TextPosition p, const TextData& richText) {
|
||||
p = move(Movement::NOWHERE, p);
|
||||
|
||||
if(richText.paragraphs.empty())
|
||||
return p;
|
||||
|
||||
std::string strToInsert;
|
||||
for(auto& p : richText.paragraphs) {
|
||||
strToInsert += p.text;
|
||||
strToInsert += '\n';
|
||||
}
|
||||
strToInsert.pop_back();
|
||||
TextPosition toRet = insert(p, strToInsert);
|
||||
|
||||
TextPosition lastPos;
|
||||
lastPos.fParagraphIndex = p.fParagraphIndex + richText.paragraphs.size() - 1;
|
||||
if(richText.paragraphs.size() == 1)
|
||||
lastPos.fTextByteIndex = p.fTextByteIndex + richText.paragraphs[0].text.size();
|
||||
else
|
||||
lastPos.fTextByteIndex = richText.paragraphs.back().text.size();
|
||||
|
||||
for(auto& [modPos, modMap] : richText.tStyleMods) {
|
||||
TextPosition shiftedModPos;
|
||||
shiftedModPos.fParagraphIndex = modPos.fParagraphIndex + p.fParagraphIndex;
|
||||
if(shiftedModPos.fParagraphIndex == p.fParagraphIndex)
|
||||
shiftedModPos.fTextByteIndex = p.fTextByteIndex + modPos.fTextByteIndex;
|
||||
else
|
||||
shiftedModPos.fTextByteIndex = modPos.fTextByteIndex;
|
||||
for(auto& [modType, modifier] : modMap)
|
||||
set_text_style_modifier_between(shiftedModPos, lastPos, modifier);
|
||||
}
|
||||
|
||||
inputChangedTextBox = true;
|
||||
needsRebuild = true;
|
||||
|
||||
return toRet;
|
||||
}
|
||||
|
||||
void TextBox::clear_text() {
|
||||
paragraphs.clear();
|
||||
tStyleMods.clear();
|
||||
|
|
|
|||
|
|
@ -16,8 +16,14 @@ struct TextPosition {
|
|||
// Set to uint32_t values because theyll be saved and sent over networks. Size has to be consistent
|
||||
size_t fParagraphIndex = std::numeric_limits<uint32_t>::max();
|
||||
size_t fTextByteIndex = std::numeric_limits<uint32_t>::max();
|
||||
template <typename Archive> void serialize(Archive& a) {
|
||||
a((uint32_t)fParagraphIndex, (uint32_t)fTextByteIndex);
|
||||
template <typename Archive> void save(Archive& a) const {
|
||||
a(static_cast<uint32_t>(fParagraphIndex), static_cast<uint32_t>(fTextByteIndex));
|
||||
}
|
||||
template <typename Archive> void load(Archive& a) {
|
||||
uint32_t p, t;
|
||||
a(p, t);
|
||||
fParagraphIndex = p;
|
||||
fTextByteIndex = t;
|
||||
}
|
||||
bool operator==(const TextPosition& o) const = default;
|
||||
bool operator<(const TextPosition& o) const;
|
||||
|
|
@ -42,14 +48,11 @@ class TextData {
|
|||
a(text, pStyleData);
|
||||
}
|
||||
};
|
||||
std::string get_html();
|
||||
std::string get_serialized();
|
||||
void save(cereal::PortableBinaryOutputArchive& a) const;
|
||||
void load(cereal::PortableBinaryInputArchive& a);
|
||||
TextStyleModContainer tStyleMods;
|
||||
std::vector<Paragraph> paragraphs;
|
||||
private:
|
||||
std::string get_css_from_text_style(const skia::textlayout::TextStyle& tStyle);
|
||||
std::string get_css_from_paragraph_style(const ParagraphStyleData& pStyle);
|
||||
};
|
||||
|
||||
class TextBox {
|
||||
|
|
@ -123,18 +126,20 @@ class TextBox {
|
|||
void set_rich_text_data(const TextData& richText);
|
||||
|
||||
TextData get_rich_text_data_between(TextPosition p1, TextPosition p2);
|
||||
TextPosition insert_rich_text(TextPosition p, const TextData& richText);
|
||||
|
||||
void clear_text();
|
||||
void set_string(const std::string& str);
|
||||
|
||||
void process_mouse_left_button(Cursor& cur, const Vector2f& pos, bool clicked, bool held, bool shift);
|
||||
void process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift, const std::optional<TextStyleModifier::ModifierMap>& inputModMap = std::nullopt);
|
||||
std::string process_copy(Cursor& cur);
|
||||
std::string process_cut(Cursor& cur);
|
||||
std::pair<std::string, std::string> process_copy(Cursor& cur);
|
||||
std::pair<std::string, std::string> process_cut(Cursor& cur);
|
||||
std::string get_text_between(TextPosition p1, TextPosition p2);
|
||||
std::string get_string();
|
||||
void set_tab_space_width(unsigned newTabWidth);
|
||||
void process_text_input(Cursor& cur, const std::string& in, const std::optional<TextStyleModifier::ModifierMap>& inputModMap = std::nullopt);
|
||||
void process_rich_text_input(Cursor& cur, const std::string& serializedRichText);
|
||||
|
||||
private:
|
||||
std::pair<TextPosition, TextPosition> get_start_end_text_pos(TextPosition p1, TextPosition p2);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue