blob: 4ccd58484cc386c0b9493f1607e55e110cf84976 [file]
#ifndef _RIVE_TEXT_INPUT_HPP_
#define _RIVE_TEXT_INPUT_HPP_
#include "rive/generated/text/text_input_base.hpp"
#include "rive/advancing_component.hpp"
#include "rive/text/raw_text_input.hpp"
#include "rive/text/text_interface.hpp"
#include "rive/animation/listener_invocation.hpp"
#include "rive/input/focusable.hpp"
#include <cmath>
namespace rive
{
class TextStyle;
class ScrollConstraint;
class LayoutComponent;
class TextInput : public TextInputBase,
public TextInterface,
public Focusable,
public AdvancingComponent
{
public:
void draw(Renderer* renderer) override;
Core* hitTest(HitInfo*, const Mat2D&) override;
bool hitTestPoint(
const Vec2D& position,
bool skipOnUnclipped,
bool isPrimaryHit) override;
#ifdef WITH_RIVE_TEXT
RawTextInput* rawTextInput() { return &m_rawTextInput; }
#endif
void markPaintDirty() override;
void markShapeDirty() override;
StatusCode onAddedClean(CoreContext* context) override;
// localBounds() is the LAYOUT box, not the ink. Glyphs escape it through
// negative side bearings, italic overhang, TextOverflow::visible,
// TextModifierGroup transforms, and per-TextStyle strokes and feathers. It
// is the right neighbourhood but not a bound, so it is approximate -- a
// caller sizing a surface must pad it.
BoundsFidelity paintedWorldBounds(AABB* out) override;
AABB localBounds() const override;
void update(ComponentDirt value) override;
Vec2D measureLayout(
float width,
LayoutMeasureMode widthMode,
float height,
LayoutMeasureMode heightMode) override;
void controlSize(
Vec2D size,
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction) override;
bool keyInput(
Key value,
KeyModifiers modifiers,
bool isPressed,
bool isRepeat) override;
bool textInput(const std::string& text) override;
bool selectedText(std::string& outText) const override;
bool acceptsTextInput() const override { return true; }
bool gamepadDispatch(
const ListenerInvocation& invocation,
ScriptedDrawable** outDispatchedScriptedDrawable = nullptr) override;
void focused() override;
void blurred() override;
bool worldPosition(Vec2D& outPosition) override;
bool worldBounds(AABB& outBounds) override;
Artboard* focusableArtboard() const override { return artboard(); }
bool acceptsKeyboardInput() const override { return true; }
bool blursOnOutsidePress() const override { return true; }
bool hitTestFocusable(const Vec2D& rootPosition) override;
/// Called when the user starts dragging on the text input.
/// Places the cursor at the given world position.
void startDrag(Vec2D worldPosition);
/// Called when the user continues dragging on the text input.
/// Extends the selection to the given world position.
void drag(Vec2D worldPosition);
/// Called when the user ends dragging on the text input.
void endDrag(Vec2D worldPosition);
/// Selects the word at the current cursor (e.g. on double-click).
void selectWord();
/// Selects the visual line at the current cursor (e.g. on triple-click).
void selectLine();
/// Advance edge scrolling during drag. Returns true if still scrolling.
bool advanceDrag(float elapsedSeconds);
bool advanceComponent(
float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::Animate |
AdvanceFlags::NewFrame) override;
/// Whether currently dragging (for hit test to avoid interference).
bool isDragging() const { return m_isDragging; }
/// Whether this text input currently has focus.
bool isFocused() const { return m_focused; }
/// Whether the caret should be drawn this frame: focused, in the blink
/// cycle's visible phase, and with nothing selected (a selection shows
/// instead of the caret, as platform text fields draw it).
bool isCursorVisible() const;
protected:
void textChanged() override;
void selectionRadiusChanged() override;
void multilineChanged() override;
void alignValueChanged() override;
void verticalAlignValueChanged() override;
void obscuredChanged() override;
void maxCharactersChanged() override;
void maxLinesChanged() override;
private:
/// Push the current alignment and the width to align it within down to the
/// raw text input. Returns true if either actually changed.
bool updateAlignment();
/// Convert a world position to local text input coordinates.
/// Handles viewport clamping and auto-scroll for scroll constraints.
bool worldToLocalWithViewport(
Vec2D worldPosition,
Vec2D& outLocal,
bool enableAutoScroll);
/// The scroll viewport the field is laid out in, if any.
LayoutComponent* fieldViewport() const;
float edgeScrollSpeedForDistance(float distanceFromEdge) const;
float edgeActivationDistance(float position, float edgeStart) const;
/// Show the caret and restart its blink cycle, so it stays solid while the
/// user is typing or moving it around.
void restartCursorBlink();
/// Toggle the caret when the blink interval elapses, dirtying the cursor
/// drawables whenever that changes whether the caret shows. Returns true
/// while focused so frames keep coming and the caret keeps blinking.
bool advanceCursorBlink(float elapsedSeconds);
/// Whether the focus being granted came from Tab or directional traversal.
bool focusedByTraversal();
void updateMultiline(bool syncDisplayedText = false);
static std::string strippedLineBreaks(const std::string& text);
std::string displayedText() const;
void syncDisplayedTextFromSource(bool preserveCursor);
void syncSourceTextFromRaw();
AABB m_worldBounds;
std::string m_sourceText;
TextStyle* m_textStyle = nullptr;
ScrollConstraint* m_scrollConstraint = nullptr;
/// Whether the user is currently dragging to select text.
bool m_isDragging = false;
/// Whether the caret was placed (by a press, a key, typing or a focus)
/// since text set from outside last reset it to the start.
bool m_caretPlaced = false;
/// Whether this text input currently has focus.
bool m_focused = false;
/// Whether the caret is in the shown phase of its blink cycle.
bool m_cursorBlinkVisible = true;
/// Time spent in the current phase of the caret's blink cycle.
float m_cursorBlinkSeconds = 0.0f;
Vec2D m_lastDragWorldPosition = Vec2D(NAN, NAN);
/// Scroll velocity for edge scrolling during drag in X.
float m_scrollX = 0.0f;
/// Scroll velocity for edge scrolling during drag.
float m_scrollY = 0.0f;
float m_layoutWidth = NAN;
float m_layoutHeight = NAN;
#ifdef WITH_RIVE_TEXT
RawTextInput m_rawTextInput;
#endif
};
} // namespace rive
#endif