From 5591a590012ee26b6e551fda1a4e164f542ebc3e Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 19 Jun 2024 16:41:28 -0300 Subject: [PATCH] Render text/highlight selected text on the canvas Now we don't render the default ui::Entry edges, but we paint just a border of the text bounds + the rendered text highlighting selected text on the canvas itself. With this change click mouse positions are translated with a scale factor that changes depending on the app::Editor zoom. --- src/app/ui/editor/writing_text_state.cpp | 113 ++++++++++++++++++----- src/app/util/render_text.cpp | 1 + src/ui/entry.cpp | 18 +++- src/ui/entry.h | 10 ++ 4 files changed, 118 insertions(+), 24 deletions(-) diff --git a/src/app/ui/editor/writing_text_state.cpp b/src/app/ui/editor/writing_text_state.cpp index 1c025dfc5..630d31487 100644 --- a/src/app/ui/editor/writing_text_state.cpp +++ b/src/app/ui/editor/writing_text_state.cpp @@ -35,6 +35,12 @@ #include "ui/message.h" #include "ui/paint_event.h" +#ifdef LAF_SKIA + #include "app/util/shader_helpers.h" + #include "os/skia/skia_helpers.h" + #include "os/skia/skia_surface.h" +#endif + namespace app { using namespace ui; @@ -82,7 +88,13 @@ public: m_doc->generateMaskBoundaries(); } - ExtraCelRef extraCel() const { return m_extraCel; } + // Returns the extra cel with the text rendered (but without the + // selected text highlighted). + ExtraCelRef extraCel() { + renderExtraCelBase(); + renderExtraCelText(false); + return m_extraCel; + } private: bool onProcessMessage(Message* msg) override { @@ -112,36 +124,48 @@ private: } void onPaint(PaintEvent& ev) override { - Entry::onPaint(ev); + Graphics* g = ev.graphics(); + + // Don't paint the base Entry borders + //Entry::onPaint(ev); + if (!hasText()) return; + // Paint border + { + ui::Paint paint; + paint.style(ui::Paint::Stroke); + set_checkered_paint_mode(paint, 0, + gfx::rgba(0, 0, 0, 255), + gfx::rgba(255, 255, 255, 255)); + g->drawRect(clientBounds(), paint); + } + try { - text::TextBlobRef blob = textBlob(); - if (!blob) { + if (!textBlob()) { m_doc->setExtraCel(nullptr); m_editor->invalidate(); return; } - const auto textColor = - color_utils::color_for_image( - Preferences::instance().colorBar.fgColor(), - IMAGE_RGB); + // Render extra cel with text + selected text + renderExtraCelBase(); + renderExtraCelText(true); + m_doc->setExtraCel(m_extraCel); - doc::ImageRef image = render_text_blob(blob, textColor); - if (image) { - renderExtraCelBase(); + // Paint caret + if (isCaretVisible()) { + int scroll, caret; + getEntryThemeInfo(&scroll, &caret, nullptr, nullptr); - doc::blend_image( - m_extraCel->image(), image.get(), - gfx::Clip(image->bounds().size()), - m_doc->sprite()->palette(m_editor->frame()), - 255, doc::BlendMode::NORMAL); - - m_doc->setExtraCel(m_extraCel); - m_editor->invalidate(); + gfx::RectF caretBounds = getCharBoxBounds(caret); + caretBounds *= gfx::SizeF(scale()); + caretBounds.w = 1; + g->fillRect(gfx::rgba(0, 0, 0), caretBounds); } + + m_editor->invalidate(); } catch (const std::exception& e) { StatusBar::instance()->showTip(500, std::string(e.what())); @@ -159,6 +183,50 @@ private: doc::BlendMode::SRC); } + void renderExtraCelText(const bool withSelection) { + const auto textColor = + color_utils::color_for_image( + Preferences::instance().colorBar.fgColor(), + IMAGE_RGB); + + text::TextBlobRef blob = textBlob(); + if (!blob) + return; + + doc::ImageRef image = render_text_blob(blob, textColor); + if (!image) + return; + + // Invert selected range in the image + if (withSelection) { + Range range; + getEntryThemeInfo(nullptr, nullptr, nullptr, &range); + if (!range.isEmpty()) { + gfx::RectF selectedBounds = + getCharBoxBounds(range.from) | + getCharBoxBounds(range.to-1); + + if (!selectedBounds.isEmpty()) { +#ifdef LAF_SKIA + sk_sp skSurface = wrap_docimage_in_sksurface(image.get()); + os::SurfaceRef surface = base::make_ref(skSurface); + + os::Paint paint; + paint.blendMode(os::BlendMode::Xor); + paint.color(textColor); + surface->drawRect(selectedBounds, paint); +#endif // LAF_SKIA + } + } + } + + doc::blend_image( + m_extraCel->image(), image.get(), + gfx::Clip(image->bounds().size()), + m_doc->sprite()->palette(m_editor->frame()), + 255, doc::BlendMode::NORMAL); + } + Editor* m_editor; Doc* m_doc; ExtraCelRef m_extraCel; @@ -178,7 +246,7 @@ WritingTextState::WritingTextState(Editor* editor, App::instance()->contextBar()->FontChange.connect( &WritingTextState::onFontChange, this); - m_entry->setBounds(calcEntryBounds()); + onEditorResize(editor); } WritingTextState::~WritingTextState() @@ -232,8 +300,11 @@ bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg) return true; } -void WritingTextState::onEditorResize(Editor*) +void WritingTextState::onEditorResize(Editor* editor) { + const gfx::PointF scale(editor->projection().scaleX(), + editor->projection().scaleY()); + m_entry->setScale(scale); m_entry->setBounds(calcEntryBounds()); } diff --git a/src/app/util/render_text.cpp b/src/app/util/render_text.cpp index 39c3221e1..8c941556b 100644 --- a/src/app/util/render_text.cpp +++ b/src/app/util/render_text.cpp @@ -25,6 +25,7 @@ #ifdef LAF_SKIA #include "app/util/shader_helpers.h" + #include "os/skia/skia_helpers.h" #include "os/skia/skia_surface.h" #endif diff --git a/src/ui/entry.cpp b/src/ui/entry.cpp index 68631d272..00efafc0a 100644 --- a/src/ui/entry.cpp +++ b/src/ui/entry.cpp @@ -52,6 +52,7 @@ Entry::Entry(const int maxsize, const char* format, ...) , m_recent_focused(false) , m_lock_selection(false) , m_translate_dead_keys(true) + , m_scale(1.0f, 1.0f) { enableFlags(CTRL_RIGHT_CLICK); @@ -235,6 +236,16 @@ gfx::Rect Entry::getEntryTextBounds() const return onGetEntryTextBounds(); } +gfx::Rect Entry::getCharBoxBounds(const int charBoxIndex) +{ + int i = 0; + int x = 0; + for (; itype()) { @@ -551,9 +562,9 @@ gfx::Rect Entry::onGetEntryTextBounds() const return bounds; } -int Entry::getCaretFromMouse(MouseMessage* mousemsg) +int Entry::getCaretFromMouse(MouseMessage* mouseMsg) { - int mouseX = mousemsg->position().x; + const int mouseX = mouseMsg->position().x; if (mouseX < bounds().x+border().left()) { // Scroll to the left return std::max(0, m_scroll-1); @@ -569,7 +580,8 @@ int Entry::getCaretFromMouse(MouseMessage* mousemsg) indexBox = j+1; } - int x = bounds().x + border().left() + segmentWidth + m_boxes[indexBox].width / 2; + const int x = bounds().x + border().left() + + (segmentWidth + m_boxes[indexBox].width / 2) * m_scale.x; if (mouseX > bounds().x2() - border().right()) { if (x >= bounds().x2() - border().right()) { diff --git a/src/ui/entry.h b/src/ui/entry.h index f74a354c3..28de3c021 100644 --- a/src/ui/entry.h +++ b/src/ui/entry.h @@ -63,6 +63,9 @@ namespace ui { void getEntryThemeInfo(int* scroll, int* caret, int* state, Range* range) const; gfx::Rect getEntryTextBounds() const; + gfx::PointF scale() const { return m_scale; } + void setScale(const gfx::PointF& scale) { m_scale = scale; } + static gfx::Size sizeHintWithText(Entry* entry, const std::string& text); @@ -70,6 +73,8 @@ namespace ui { obs::signal Change; protected: + gfx::Rect getCharBoxBounds(int charBoxIndex); + // Events bool onProcessMessage(Message* msg) override; void onSizeHint(SizeHintEvent& ev) override; @@ -137,6 +142,11 @@ namespace ui { bool m_translate_dead_keys : 1; Range m_selecting_words; std::unique_ptr m_suffix; + + // Scale (1.0 by default) applied to each axis. Can be used in + // case you are going to display/paint the text scaled and want to + // convert the mouse position correctly. + gfx::PointF m_scale; }; } // namespace ui