pub struct TextEdit {
pub text: String,
pub layout: Layout,
pub caret: Place,
pub previous_caret: Place,
pub selection: Selection,
pub sticky_x: f32,
pub scroll: (f32, f32),
pub auto_scroll: bool,
pub offset: (f32, f32),
pub centred: bool,
pub undo: UndoStack,
}Expand description
A text field’s editing state.
The invariant is one sentence: caret and both ends of selection are
places in layout, and layout is what laying text out again would
produce.
let mut edit = TextEdit::new("Hello", &config, &metrics, true);
assert_eq!(edit.text, "Hello");
// A fresh control's caret sits at the line header, before the text.
assert_eq!(edit.caret_index(), 0);
assert!(!edit.has_selection());
edit.set_selection(0, 4);
assert_eq!(edit.selected_text(), "Hell");Fields§
§text: StringThe text as the user has it, which may differ from the field’s stored value until the edit commits.
layout: LayoutThe laid-out form of that text.
caret: PlaceWhere the caret is.
previous_caret: PlaceThe caret’s position before the last movement, which a shift-move anchors on when no anchor exists yet.
selection: SelectionThe selection: directional, empty when its ends agree.
sticky_x: f32The column a vertical move aims for, in layout space.
Re-seeded by every horizontal move and every mutation, and deliberately not by a vertical one — which is what lets a run of up-arrows through a ragged paragraph keep returning to the column the caret started in.
scroll: (f32, f32)How far the view is scrolled, in layout space.
A distance, not a position: LiveState::shift negates it to move
the drawn text. Upstream’s scroll_pos_point_ is the same quantity
seeded at rcPlate.left, so ours is upstream’s minus that — see
scroll_to_caret.
auto_scroll: boolWhether the view follows the caret out of the plate.
True for any text field without the DoNotScroll flag —
single-line and multi-line alike. It gates every writer of the scroll
position, so a field that declines it never moves its view at all,
however far past the plate the caret goes.
offset: (f32, f32)The vertical alignment offset the text is drawn with.
A single-line field is drawn vertically centred in its plate, so its glyphs sit below where the layout placed them. Every geometric query — turning a click into a place, a place into a caret rectangle — has to be told about that shift or it works against the wrong box: a click at a real field’s mid-height falls below the content and clamps to the end of the text, silently, putting the caret at the end of the field instead of where it was clicked.
It is recomputed on every relayout because it depends on the content’s height, which an edit changes.
centred: boolWhether the field draws its text vertically centred.
undo: UndoStackThe undo stack.
Implementations§
Source§impl TextEdit
impl TextEdit
Sourcepub fn new(
text: impl Into<String>,
config: &Config,
metrics: &Metrics<'_>,
centred: bool,
) -> TextEdit
pub fn new( text: impl Into<String>, config: &Config, metrics: &Metrics<'_>, centred: bool, ) -> TextEdit
An edit control over text, laid out with config.
centred says whether the field draws its text vertically centred,
which a single-line field does and a multiline one does not.
The text is normalized to what the layout will hold before it is
stored — see normalize_breaks. Storing the caller’s string
unchanged would break the invariant this type exists to keep: a
single-line field’s layout silently drops line breaks, so a raw
"Foo\nBar" would report seven characters while the layout held six,
and every index derived from one would miss in the other.
// The text arrives normalized, so the control and its layout agree.
let edit = TextEdit::new("Foo\nBar", &config, &metrics, true);
assert_eq!(edit.text, "FooBar");
assert_eq!(edit.len_chars(), 6);Sourcepub fn caret_index(&self) -> usize
pub fn caret_index(&self) -> usize
The caret’s flat character index.
let mut edit = TextEdit::new("Hello", &config, &metrics, true);
// A fresh control starts at the line header, index zero.
assert_eq!(edit.caret_index(), 0);
edit.set_caret_index(2);
assert_eq!(edit.caret_index(), 2);Sourcepub fn selection_indices(&self) -> (usize, usize)
pub fn selection_indices(&self) -> (usize, usize)
The selection’s flat character range, ordered.
Ordered whichever way the selection runs, so a backwards selection answers the same pair as the forwards one over the same run.
let mut edit = TextEdit::new("ABCDEF", &config, &metrics, true);
edit.set_selection(4, 1);
assert_eq!(edit.selection_indices(), (1, 4));Sourcepub fn selected_text(&self) -> String
pub fn selected_text(&self) -> String
The selected text, empty when nothing is selected.
let mut edit = TextEdit::new("ABCDEF", &config, &metrics, true);
assert_eq!(edit.selected_text(), "");
edit.set_selection(1, 4);
assert_eq!(edit.selected_text(), "BCD");Sourcepub fn has_selection(&self) -> bool
pub fn has_selection(&self) -> bool
Whether anything is selected.
let mut edit = TextEdit::new("ABCDEF", &config, &metrics, true);
assert!(!edit.has_selection());
edit.select_all();
assert!(edit.has_selection());Sourcepub fn len_chars(&self) -> usize
pub fn len_chars(&self) -> usize
How many characters the text holds.
Characters, not bytes: a field of Hebrew letters is as long as it looks.
let edit = TextEdit::new("\u{05D1}\u{05D2}\u{05EA}", &config, &metrics, true);
assert_eq!(edit.len_chars(), 3);Sourcepub fn set_caret_index(&mut self, index: usize)
pub fn set_caret_index(&mut self, index: usize)
Moves the caret to a flat character index, collapsing the selection.
The selection is left collapsed at the new caret, which is a live anchor rather than no anchor.
let mut edit = TextEdit::new("ABCDEF", &config, &metrics, true);
edit.select_all();
edit.set_caret_index(2);
assert_eq!(edit.caret_index(), 2);
assert!(!edit.has_selection());Sourcepub fn move_caret_keeping_selection(&mut self, index: usize)
pub fn move_caret_keeping_selection(&mut self, index: usize)
Moves the caret without touching the selection — what a shift-move needs, since it must extend from an anchor the caret is leaving.
let mut edit = TextEdit::new("ABCDEF", &config, &metrics, true);
edit.set_selection(1, 4);
edit.move_caret_keeping_selection(0);
assert_eq!(edit.caret_index(), 0);
// The selection is untouched, unlike after `set_caret_index`.
assert_eq!(edit.selected_text(), "BCD");Sourcepub fn select_all(&mut self)
pub fn select_all(&mut self)
Selects everything. Records no undo item — selecting is not an edit.
let mut edit = TextEdit::new("Hello", &config, &metrics, true);
edit.select_all();
assert_eq!(edit.selected_text(), "Hello");
assert!(!edit.undo.can_undo(), "selecting records nothing");
// An empty field has nothing to select.
let mut blank = TextEdit::new("", &config, &metrics, true);
blank.select_all();
assert_eq!(blank.selected_text(), "");Sourcepub fn select_none(&mut self)
pub fn select_none(&mut self)
Drops the selection, leaving a live anchor at the caret.
let mut edit = TextEdit::new("Hello", &config, &metrics, true);
edit.select_all();
edit.select_none();
assert!(!edit.has_selection());
// Collapsed, not reset: the next shift-move extends from here.
assert!(!edit.selection.is_reset());Sourcepub fn set_selection(&mut self, start: i32, end: i32)
pub fn set_selection(&mut self, start: i32, end: i32)
Selects a signed character range, the way an embedder asks for one.
The signs are not an accident of the C API and are not clamping: they are three distinct instructions sharing one signature, and the order they are tested in is what makes them unambiguous.
(0, negative)selects everything. This is the documented spelling of “to the end”, and it is tested first, so it wins over the rule below even though its end is also negative.(negative, anything)selects nothing. A negative start is not clamped to zero — it clears the selection outright, so(-8, -1)is empty rather than the whole field.- otherwise the two are ordered and used as they are, so
(23, 12)and(12, 23)select the same run. An end past the text clamps to its end, which is ordinary index saturation rather than a fourth rule.
let mut edit = TextEdit::new("ABCDEFGHIJ", &config, &metrics, true);
// (0, negative) is "to the end".
edit.set_selection(0, -1);
assert_eq!(edit.selected_text(), "ABCDEFGHIJ");
// A negative start selects nothing; it is not clamped to zero.
edit.set_selection(-8, -1);
assert_eq!(edit.selected_text(), "");
// Otherwise the two are ordered, so either way round is the same run.
edit.set_selection(5, 2);
assert_eq!(edit.selected_text(), "CDE");
edit.set_selection(2, 5);
assert_eq!(edit.selected_text(), "CDE");
// An end past the text clamps, which is ordinary saturation.
edit.set_selection(9, 99);
assert_eq!(edit.selected_text(), "J");Trait Implementations§
impl StructuralPartialEq for TextEdit
Auto Trait Implementations§
impl Freeze for TextEdit
impl RefUnwindSafe for TextEdit
impl Send for TextEdit
impl Sync for TextEdit
impl Unpin for TextEdit
impl UnsafeUnpin for TextEdit
impl UnwindSafe for TextEdit
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.