Skip to content

Commit d7d414d

Browse files
bodymovinbodymovin
andcommitted
feat(editor - text input): expose trigger property to focus on text i… (#12975) 50fdb3bb9f
* feat(editor - text input): expose trigger property to focus on text input * add text keyboard shortcuts * add text selection from pointers Co-authored-by: hernan <hernan@rive.app>
1 parent 74ae9ae commit d7d414d

8 files changed

Lines changed: 315 additions & 1 deletion

File tree

.rive_head

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
d6079802297ddfbac2ca28c2d467d5e2686010f5
1+
50fdb3bb9fe4efb95a7cf94f005d4ea1ad86e5c7

include/rive/animation/text_input_listener_group.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define _RIVE_TEXT_INPUT_LISTENER_GROUP_HPP_
33

44
#include "rive/listener_group.hpp"
5+
#include "rive/math/vec2d.hpp"
56

67
namespace rive
78
{
@@ -31,8 +32,20 @@ class TextInputListenerGroup : public ListenerGroup
3132
StateMachineInstance* stateMachineInstance) override;
3233

3334
private:
35+
// Returns a timestamp in microseconds for multi-click detection. Uses the
36+
// passed-in (deterministic) timeStamp when File::deterministicMode is set,
37+
// otherwise wall-clock time, mirroring ScrollPhysics.
38+
long long nowMicros(float timeStamp) const;
39+
3440
TextInput* m_textInput;
3541
bool m_isDragging = false;
42+
43+
// Multi-click (double/triple-click) detection state.
44+
int m_clickCount = 0;
45+
long long m_lastClickTime = 0; // microseconds
46+
Vec2D m_lastClickPosition = Vec2D(0, 0);
47+
static constexpr long long kMultiClickIntervalUs = 500000; // 0.5s
48+
static constexpr float kMultiClickDistance = 16.0f; // world units
3649
};
3750

3851
} // namespace rive

include/rive/text/raw_text_input.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ class RawTextInput
111111
// Selects the word at the cursor.
112112
void selectWord();
113113

114+
// Selects the entire text.
115+
void selectAll();
116+
117+
// Selects the visual line at the cursor.
118+
void selectLine();
119+
114120
std::string text() const;
115121
void text(std::string value);
116122
void textPreserveCursor(std::string value);

include/rive/text/text_input.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ class TextInput : public TextInputBase,
7171
/// Called when the user ends dragging on the text input.
7272
void endDrag(Vec2D worldPosition);
7373

74+
/// Selects the word at the current cursor (e.g. on double-click).
75+
void selectWord();
76+
77+
/// Selects the visual line at the current cursor (e.g. on triple-click).
78+
void selectLine();
79+
7480
/// Advance edge scrolling during drag. Returns true if still scrolling.
7581
bool advanceDrag(float elapsedSeconds);
7682
bool advanceComponent(float elapsedSeconds,

src/animation/text_input_listener_group.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
#include "rive/animation/text_input_listener_group.hpp"
22
#include "rive/animation/state_machine_instance.hpp"
3+
#include "rive/file.hpp"
34
#include "rive/focus_data.hpp"
45
#include "rive/input/focus_manager.hpp"
56
#include "rive/input/focus_node.hpp"
67
#include "rive/text/text_input.hpp"
8+
#include <chrono>
79

810
using namespace rive;
911

12+
long long TextInputListenerGroup::nowMicros(float timeStamp) const
13+
{
14+
if (File::deterministicMode)
15+
{
16+
return (long long)(timeStamp * 1000000.0f);
17+
}
18+
return std::chrono::duration_cast<std::chrono::microseconds>(
19+
std::chrono::high_resolution_clock::now().time_since_epoch())
20+
.count();
21+
}
22+
1023
TextInputListenerGroup::TextInputListenerGroup(
1124
TextInput* textInput,
1225
StateMachineInstance* stateMachineInstance) :
@@ -64,6 +77,23 @@ ProcessEventResult TextInputListenerGroup::processEvent(
6477
if (prevPhase != GestureClickPhase::down &&
6578
newPhase == GestureClickPhase::down)
6679
{
80+
// Detect double/triple-click from the time and distance since the
81+
// previous click.
82+
long long now = nowMicros(timeStamp);
83+
long long dt = now - m_lastClickTime;
84+
float dist = Vec2D::distance(position, m_lastClickPosition);
85+
if (dt >= 0 && dt <= kMultiClickIntervalUs &&
86+
dist <= kMultiClickDistance)
87+
{
88+
m_clickCount = m_clickCount >= 3 ? 1 : m_clickCount + 1;
89+
}
90+
else
91+
{
92+
m_clickCount = 1;
93+
}
94+
m_lastClickTime = now;
95+
m_lastClickPosition = position;
96+
6797
m_textInput->startDrag(position);
6898
m_isDragging = true;
6999

@@ -83,6 +113,17 @@ ProcessEventResult TextInputListenerGroup::processEvent(
83113
}
84114
}
85115
}
116+
117+
// Select the word (double-click) or visual line (triple-click) at the
118+
// cursor that startDrag just placed.
119+
if (m_clickCount == 2)
120+
{
121+
m_textInput->selectWord();
122+
}
123+
else if (m_clickCount == 3)
124+
{
125+
m_textInput->selectLine();
126+
}
86127
return ProcessEventResult::scroll;
87128
}
88129
// Handle drag continue: pointer is moving while down

src/text/raw_text_input.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,36 @@ void RawTextInput::selectWord()
470470
flag(Flags::selectionDirty);
471471
}
472472

473+
void RawTextInput::selectAll()
474+
{
475+
ensureShape();
476+
m_idealCursorX = -1.0f;
477+
CursorPosition start = CursorPosition::atIndex(0, m_shape);
478+
CursorPosition end = CursorPosition::atIndex((uint32_t)length(), m_shape);
479+
m_cursor = Cursor(start, end);
480+
flag(Flags::selectionDirty);
481+
}
482+
483+
void RawTextInput::selectLine()
484+
{
485+
ensureShape();
486+
m_idealCursorX = -1.0f;
487+
CursorPosition cursor = m_cursor.start();
488+
cursor.resolveLine(m_shape);
489+
const OrderedLine* line = orderedLine(cursor);
490+
if (line == nullptr)
491+
{
492+
return;
493+
}
494+
const auto& glyphLookup = m_shape.glyphLookup();
495+
CursorPosition start(cursor.lineIndex(),
496+
line->firstCodePointIndex(glyphLookup));
497+
CursorPosition end(cursor.lineIndex(),
498+
line->lastCodePointIndex(glyphLookup));
499+
m_cursor = Cursor(start, end);
500+
flag(Flags::selectionDirty);
501+
}
502+
473503
const OrderedLine* RawTextInput::orderedLine(CursorPosition position) const
474504
{
475505
const std::vector<OrderedLine>& orderedLines = m_shape.orderedLines();

src/text/text_input.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,26 @@ bool TextInput::keyInput(Key value,
395395
return true;
396396
}
397397
break;
398+
case Key::a:
399+
if ((modifiers & systemModifier()) != KeyModifiers::none)
400+
{
401+
m_rawTextInput.selectAll();
402+
markPaintDirty();
403+
return true;
404+
}
405+
break;
406+
case Key::home:
407+
m_rawTextInput.cursorLeft(CursorBoundary::line,
408+
(modifiers & KeyModifiers::shift) !=
409+
KeyModifiers::none);
410+
markPaintDirty();
411+
return true;
412+
case Key::end:
413+
m_rawTextInput.cursorRight(CursorBoundary::line,
414+
(modifiers & KeyModifiers::shift) !=
415+
KeyModifiers::none);
416+
markPaintDirty();
417+
return true;
398418
case Key::backspace:
399419
m_rawTextInput.backspace(-1);
400420
syncSourceTextFromRaw();
@@ -668,6 +688,22 @@ void TextInput::endDrag(Vec2D worldPosition)
668688
m_scrollY = 0.0f;
669689
}
670690

691+
void TextInput::selectWord()
692+
{
693+
#ifdef WITH_RIVE_TEXT
694+
m_rawTextInput.selectWord();
695+
markPaintDirty();
696+
#endif
697+
}
698+
699+
void TextInput::selectLine()
700+
{
701+
#ifdef WITH_RIVE_TEXT
702+
m_rawTextInput.selectLine();
703+
markPaintDirty();
704+
#endif
705+
}
706+
671707
bool TextInput::advanceDrag(float elapsedSeconds)
672708
{
673709
#ifdef WITH_RIVE_TEXT

0 commit comments

Comments
 (0)