Skip to content

Commit aa4651a

Browse files
philterphilter
andcommitted
feat: Single/multiline support in TextInput and improved scrolling (#12127) 74f49dd1be
A few updates to TextInput: - Adds a multiline core property so that TextInput can internally handle autoWidth vs autoHeight for text, and also encapsulates that functionality in the case where the core object is used on its own. - Adds a ScrollDirection system enum and makes DraggableConstraint.directionValue animatable/bindable - Scrolling and selection improvements. Click now places the caret immediately, drag consistently extends selection, and edge auto-scroll is smoother, with a scroll speed ramp up - Adds an additional scroll to focus node fix if the focus node is larger than the scroll viewport. Co-authored-by: Philip Chung <philterdesign@gmail.com>
1 parent 8ece2bd commit aa4651a

10 files changed

Lines changed: 271 additions & 55 deletions

File tree

.rive_head

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1902f9ddf3a529552d7e621652f47ff95642cd90
1+
74f49dd1be01d3a7918fa9881ce6de863dbb3fda

dev/defs/constraints/draggable_constraint.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
"directionValue": {
1111
"type": "uint",
1212
"initialValue": "1",
13+
"animates": true,
1314
"key": {
1415
"int": 722,
1516
"string": "directionvalue"
1617
},
17-
"description": "Direction to scroll bar horizontal|vertical|all"
18+
"description": "Direction to scroll bar horizontal|vertical|all",
19+
"bindable": true
1820
}
1921
}
2022
}

dev/defs/text/text_input.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636
"description": "An edit time property used to track the height of the field in the inspector",
3737
"runtime": false,
3838
"coop": false
39+
},
40+
"multiline": {
41+
"type": "bool",
42+
"initialValue": "true",
43+
"key": {
44+
"int": 979,
45+
"string": "multiline"
46+
},
47+
"description": "Whether the text input is single line or multiline",
48+
"bindable": true
3949
}
4050
}
4151
}

include/rive/generated/core_registry.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,6 +1887,9 @@ class CoreRegistry
18871887
case TextFollowPathModifierBase::orientPropertyKey:
18881888
object->as<TextFollowPathModifierBase>()->orient(value);
18891889
break;
1890+
case TextInputBase::multilinePropertyKey:
1891+
object->as<TextInputBase>()->multiline(value);
1892+
break;
18901893
case TextBase::fitFromBaselinePropertyKey:
18911894
object->as<TextBase>()->fitFromBaseline(value);
18921895
break;
@@ -3317,6 +3320,8 @@ class CoreRegistry
33173320
return object->as<TextFollowPathModifierBase>()->radial();
33183321
case TextFollowPathModifierBase::orientPropertyKey:
33193322
return object->as<TextFollowPathModifierBase>()->orient();
3323+
case TextInputBase::multilinePropertyKey:
3324+
return object->as<TextInputBase>()->multiline();
33203325
case TextBase::fitFromBaselinePropertyKey:
33213326
return object->as<TextBase>()->fitFromBaseline();
33223327
case ScriptAssetBase::isModulePropertyKey:
@@ -4107,6 +4112,7 @@ class CoreRegistry
41074112
case TextModifierRangeBase::clampPropertyKey:
41084113
case TextFollowPathModifierBase::radialPropertyKey:
41094114
case TextFollowPathModifierBase::orientPropertyKey:
4115+
case TextInputBase::multilinePropertyKey:
41104116
case TextBase::fitFromBaselinePropertyKey:
41114117
case ScriptAssetBase::isModulePropertyKey:
41124118
return CoreBoolType::id;
@@ -4984,6 +4990,8 @@ class CoreRegistry
49844990
return object->is<TextFollowPathModifierBase>();
49854991
case TextFollowPathModifierBase::orientPropertyKey:
49864992
return object->is<TextFollowPathModifierBase>();
4993+
case TextInputBase::multilinePropertyKey:
4994+
return object->is<TextInputBase>();
49874995
case TextBase::fitFromBaselinePropertyKey:
49884996
return object->is<TextBase>();
49894997
case ScriptAssetBase::isModulePropertyKey:

include/rive/generated/text/text_input_base.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef _RIVE_TEXT_INPUT_BASE_HPP_
22
#define _RIVE_TEXT_INPUT_BASE_HPP_
33
#include <string>
4+
#include "rive/core/field_types/core_bool_type.hpp"
45
#include "rive/core/field_types/core_double_type.hpp"
56
#include "rive/core/field_types/core_string_type.hpp"
67
#include "rive/drawable.hpp"
@@ -37,10 +38,12 @@ class TextInputBase : public Drawable
3738

3839
static const uint16_t textPropertyKey = 817;
3940
static const uint16_t selectionRadiusPropertyKey = 818;
41+
static const uint16_t multilinePropertyKey = 979;
4042

4143
protected:
4244
std::string m_Text = "";
4345
float m_SelectionRadius = 5.0f;
46+
bool m_Multiline = true;
4447

4548
public:
4649
inline const std::string& text() const { return m_Text; }
@@ -65,11 +68,23 @@ class TextInputBase : public Drawable
6568
selectionRadiusChanged();
6669
}
6770

71+
inline bool multiline() const { return m_Multiline; }
72+
void multiline(bool value)
73+
{
74+
if (m_Multiline == value)
75+
{
76+
return;
77+
}
78+
m_Multiline = value;
79+
multilineChanged();
80+
}
81+
6882
Core* clone() const override;
6983
void copy(const TextInputBase& object)
7084
{
7185
m_Text = object.m_Text;
7286
m_SelectionRadius = object.m_SelectionRadius;
87+
m_Multiline = object.m_Multiline;
7388
Drawable::copy(object);
7489
}
7590

@@ -83,13 +98,17 @@ class TextInputBase : public Drawable
8398
case selectionRadiusPropertyKey:
8499
m_SelectionRadius = CoreDoubleType::deserialize(reader);
85100
return true;
101+
case multilinePropertyKey:
102+
m_Multiline = CoreBoolType::deserialize(reader);
103+
return true;
86104
}
87105
return Drawable::deserialize(propertyKey, reader);
88106
}
89107

90108
protected:
91109
virtual void textChanged() {}
92110
virtual void selectionRadiusChanged() {}
111+
virtual void multilineChanged() {}
93112
};
94113
} // namespace rive
95114

include/rive/text/text_input.hpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22
#define _RIVE_TEXT_INPUT_HPP_
33

44
#include "rive/generated/text/text_input_base.hpp"
5+
#include "rive/advancing_component.hpp"
56
#include "rive/text/raw_text_input.hpp"
67
#include "rive/text/text_interface.hpp"
78
#include "rive/input/focusable.hpp"
9+
#include <cmath>
810

911
namespace rive
1012
{
1113
class TextStyle;
1214
class ScrollConstraint;
13-
class TextInput : public TextInputBase, public TextInterface, public Focusable
15+
class TextInput : public TextInputBase,
16+
public TextInterface,
17+
public Focusable,
18+
public AdvancingComponent
1419
{
1520
public:
1621
void draw(Renderer* renderer) override;
@@ -63,28 +68,44 @@ class TextInput : public TextInputBase, public TextInterface, public Focusable
6368

6469
/// Advance edge scrolling during drag. Returns true if still scrolling.
6570
bool advanceDrag(float elapsedSeconds);
71+
bool advanceComponent(float elapsedSeconds,
72+
AdvanceFlags flags = AdvanceFlags::Animate |
73+
AdvanceFlags::NewFrame) override;
6674

6775
/// Whether currently dragging (for hit test to avoid interference).
6876
bool isDragging() const { return m_isDragging; }
6977

7078
protected:
7179
void textChanged() override;
7280
void selectionRadiusChanged() override;
81+
void multilineChanged() override;
7382

7483
private:
7584
/// Convert a world position to local text input coordinates.
7685
/// Handles viewport clamping and auto-scroll for scroll constraints.
77-
bool worldToLocalWithViewport(Vec2D worldPosition, Vec2D& outLocal);
86+
bool worldToLocalWithViewport(Vec2D worldPosition,
87+
Vec2D& outLocal,
88+
bool enableAutoScroll);
89+
90+
float edgeScrollSpeedForDistance(float distanceFromEdge) const;
91+
float edgeActivationDistance(float position, float edgeStart) const;
92+
93+
void updateMultiline();
7894

7995
AABB m_worldBounds;
8096
TextStyle* m_textStyle = nullptr;
8197
ScrollConstraint* m_scrollConstraint = nullptr;
8298

8399
/// Whether the user is currently dragging to select text.
84100
bool m_isDragging = false;
101+
Vec2D m_lastDragWorldPosition = Vec2D(NAN, NAN);
102+
103+
/// Scroll velocity for edge scrolling during drag in X.
104+
float m_scrollX = 0.0f;
85105

86106
/// Scroll velocity for edge scrolling during drag.
87107
float m_scrollY = 0.0f;
108+
float m_layoutWidth = NAN;
88109

89110
#ifdef WITH_RIVE_TEXT
90111
RawTextInput m_rawTextInput;

src/advancing_component.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "rive/scripted/scripted_drawable.hpp"
1111
#include "rive/scripted/scripted_layout.hpp"
1212
#include "rive/scripted/scripted_path_effect.hpp"
13+
#include "rive/text/text_input.hpp"
1314

1415
using namespace rive;
1516

@@ -29,6 +30,8 @@ AdvancingComponent* AdvancingComponent::from(Core* component)
2930
return component->as<ArtboardComponentList>();
3031
case ScrollConstraint::typeKey:
3132
return component->as<ScrollConstraint>();
33+
case TextInputBase::typeKey:
34+
return component->as<TextInput>();
3235
case ScriptedDataConverter::typeKey:
3336
return component->as<ScriptedDataConverter>();
3437
case ScriptedDrawable::typeKey:

src/focus_data.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,13 @@ void FocusData::scrollConstraintToShowBounds(ScrollConstraint* constraint,
236236
// Calculate horizontal scroll adjustment.
237237
if (constraint->constrainsHorizontal())
238238
{
239-
if (viewportLeft < 0)
239+
float elementWidth = viewportRight - viewportLeft;
240+
if (elementWidth > viewportWidth)
241+
{
242+
// Oversized element: keep the left edge visible.
243+
deltaX = -viewportLeft;
244+
}
245+
else if (viewportLeft < 0)
240246
{
241247
// Element is to the left of viewport, scroll right (increase
242248
// offset)
@@ -253,7 +259,13 @@ void FocusData::scrollConstraintToShowBounds(ScrollConstraint* constraint,
253259
// Calculate vertical scroll adjustment
254260
if (constraint->constrainsVertical())
255261
{
256-
if (viewportTop < 0)
262+
float elementHeight = viewportBottom - viewportTop;
263+
if (elementHeight > viewportHeight)
264+
{
265+
// Oversized element: keep the top edge visible.
266+
deltaY = -viewportTop;
267+
}
268+
else if (viewportTop < 0)
257269
{
258270
// Element is above viewport, scroll up (increase offset)
259271
deltaY = -viewportTop;

src/text/raw_text_input.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ AABB RawTextInput::measure(float maxWidth, float maxHeight)
922922
m_textRun.unicharCount = (uint32_t)m_text.size();
923923
m_measuringShape->shape(m_text,
924924
Span<TextRun>(&m_textRun, 1),
925-
TextSizing::autoHeight,
925+
m_sizing,
926926
maxWidth,
927927
maxHeight,
928928
m_align,

0 commit comments

Comments
 (0)