| #ifndef _RIVE_TEXT_SELECTION_CONTROLLER_HPP_ |
| #define _RIVE_TEXT_SELECTION_CONTROLLER_HPP_ |
| |
| #ifdef WITH_RIVE_TEXT |
| #include "rive/input/focusable.hpp" |
| #include "rive/text/cursor.hpp" |
| #include "rive/shapes/paint/color.hpp" |
| #include "rive/selection_style.hpp" |
| #include <memory> |
| #include <string> |
| |
| namespace rive |
| { |
| class Text; |
| class Artboard; |
| class Renderer; |
| class File; |
| |
| // A host-owned selection region for regular Text. Add actual Text instances |
| // in reading order (including instances inside nested artboards). Registration |
| // opts each Text into selection; the Text continues to draw its own content. |
| // Advance the artboards before delivering input. Positions are in root |
| // artboard coordinates, before the host's canvas/view transform. |
| // |
| // This first core API deliberately leaves hit priority, pointer capture, |
| // clipboard access, scrolling, and focus ownership to the host. Pass the Text |
| // chosen by normal hit testing to pointerDown, and keep forwarding the captured |
| // pointer across object boundaries until up/cancel. The controller never edits |
| // text or requests an IME. Text modifiers and ellipsis are not supported yet. |
| class TextSelectionController |
| { |
| public: |
| TextSelectionController(); |
| ~TextSelectionController(); |
| TextSelectionController(const TextSelectionController&) = delete; |
| TextSelectionController& operator=(const TextSelectionController&) = delete; |
| |
| // A Text can belong to one controller. Removal/destruction of a participant |
| // clears the active range; either controller or Text may be destroyed |
| // first. |
| bool add(Text* text, std::string separatorBefore = "\n"); |
| void remove(Text* text); |
| |
| // Discover opted-in Text instances, recursively including hosted artboards. |
| // Existing registrations retain stable tokens; removed Text unregisters |
| // through its destructor. These tokens are safe for host language bridges. |
| void synchronize(Artboard* root); |
| std::vector<uint32_t> targetIds() const; |
| Text* target(uint32_t id) const; |
| std::string sourceText(Text* text) const; |
| bool isEligible(Text* text) const; |
| AABB rootBounds(Text* text) const; |
| bool hitTest(Text* text, |
| Vec2D rootPosition, |
| bool nearest, |
| uint32_t& offset, |
| float& distanceSquared); |
| // A host coordinating native and external participants can supply each |
| // local range. Source changes still invalidate these ranges. |
| void setRange(Text* text, uint32_t start, uint32_t end); |
| |
| bool pointerDown(Text* hitText, |
| Vec2D rootPosition, |
| int pointerId = 0, |
| bool extend = false); |
| bool pointerMove(Vec2D rootPosition, int pointerId = 0); |
| bool pointerUp(Vec2D rootPosition, int pointerId = 0); |
| bool pointerCancel(int pointerId = 0); |
| bool isDragging() const { return m_dragging; } |
| |
| // Offsets are Unicode code points, inclusive at the start and exclusive at |
| // the end. Layout/line changes preserve the source range; source changes |
| // clear it when the Text updates. Reversing endpoints preserves copy order. |
| bool select(Text* anchorText, |
| uint32_t anchorOffset, |
| Text* extentText, |
| uint32_t extentOffset); |
| void selectAll(); |
| void clear(); |
| bool hasSelection() const; |
| std::string selectedText() const; |
| Cursor selection(Text* text) const; |
| |
| // Borrowed rectangles in the Text's shape coordinates. These share the |
| // same cursor geometry as TextInput and are also used by Text::draw. |
| Span<const AABB> selectionRects(Text* text); |
| |
| // One style for the whole selection, including nested targets. A host |
| // override must outlive the controller; nullptr restores the root file's |
| // default style, or the built-in default when no resource is authored. |
| const SelectionStyle& style() const; |
| void style(const SelectionStyle* value) { m_style = value; } |
| |
| #ifdef TESTING |
| size_t lookupBuildCount(Text* text) const; |
| #endif |
| |
| // Returns true only for selection commands (Escape and Cmd/Ctrl+A). |
| // The host handles copy by reading selectedText(). Editing keys are |
| // ignored. |
| bool keyInput(Key key, KeyModifiers modifiers, bool isPressed); |
| |
| private: |
| friend class Text; |
| struct Participant; |
| struct Endpoint |
| { |
| Participant* participant = nullptr; |
| CursorPosition position = CursorPosition::zero(); |
| }; |
| |
| Participant* find(Text* text) const; |
| TextLayoutView layout(Participant& participant); |
| bool eligible(const Participant& participant) const; |
| bool validSelection() const; |
| bool endpointAt(Participant& participant, |
| Vec2D rootPosition, |
| Endpoint& out); |
| bool nearestEndpoint(Vec2D rootPosition, Endpoint& out); |
| void updateRanges(); |
| void textUpdated(Text* text, bool shapeChanged); |
| void draw(Text* text, Renderer* renderer); |
| |
| std::vector<std::unique_ptr<Participant>> m_participants; |
| Endpoint m_anchor; |
| Endpoint m_extent; |
| int m_pointerId = 0; |
| bool m_dragging = false; |
| bool m_externalRanges = false; |
| uint32_t m_nextTargetId = 1; |
| const SelectionStyle* m_style = nullptr; |
| // Keep the default resource alive even if the host releases its root. |
| rcp<const File> m_styleFile; |
| }; |
| } // namespace rive |
| #endif |
| #endif |