Skip to content

Commit 998286e

Browse files
committed
Unify druid & druid-shell Movement types
This deletes the old Movement enum in druid, and moves to using druid-shell for everything.
1 parent 3646a76 commit 998286e

File tree

4 files changed

+79
-80
lines changed

4 files changed

+79
-80
lines changed

druid-shell/src/text.rs

+19
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,25 @@ pub enum Direction {
591591
Downstream,
592592
}
593593

594+
impl Direction {
595+
/// Returns `true` if this direction is byte-wise backwards for
596+
/// the provided [`WritingDirection`].
597+
///
598+
/// The provided direction *must not be* `WritingDirection::Natural`.
599+
pub fn is_upstream_for_direction(self, direction: WritingDirection) -> bool {
600+
assert!(
601+
!matches!(direction, WritingDirection::Natural),
602+
"writing direction must be resolved"
603+
);
604+
match self {
605+
Direction::Upstream => true,
606+
Direction::Downstream => false,
607+
Direction::Left => matches!(direction, WritingDirection::LeftToRight),
608+
Direction::Right => matches!(direction, WritingDirection::RightToLeft),
609+
}
610+
}
611+
}
612+
594613
/// Distinguishes between two visually distinct locations with the same byte
595614
/// index.
596615
///

druid/src/text/input_component.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -551,14 +551,12 @@ impl<T: TextStorage + EditableText> EditSession<T> {
551551
fn do_action(&mut self, buffer: &mut T, action: ImeAction) {
552552
match action {
553553
ImeAction::Move(movement) => {
554-
let sel =
555-
crate::text::movement(movement.into(), self.selection, &self.layout, false);
554+
let sel = crate::text::movement(movement, self.selection, &self.layout, false);
556555
self.external_selection_change = Some(sel);
557556
self.scroll_to_selection_end(false);
558557
}
559558
ImeAction::MoveSelecting(movement) => {
560-
let sel =
561-
crate::text::movement(movement.into(), self.selection, &self.layout, true);
559+
let sel = crate::text::movement(movement, self.selection, &self.layout, true);
562560
self.external_selection_change = Some(sel);
563561
self.scroll_to_selection_end(false);
564562
}
@@ -570,8 +568,7 @@ impl<T: TextStorage + EditableText> EditSession<T> {
570568
//tracing::warn!("Line/Word selection actions are not implemented");
571569
//}
572570
ImeAction::Delete(movement) if self.selection.is_caret() => {
573-
let movement: Movement = movement.into();
574-
if movement == Movement::Left {
571+
if movement == Movement::Grapheme(druid_shell::text::Direction::Upstream) {
575572
self.backspace(buffer);
576573
} else {
577574
let to_delete =

druid/src/text/layout.rs

+21-10
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub struct TextLayout<T> {
5858
wrap_width: f64,
5959
alignment: TextAlignment,
6060
links: Rc<[(Rect, usize)]>,
61+
text_is_rtl: bool,
6162
}
6263

6364
/// Metrics describing the layout text.
@@ -88,16 +89,7 @@ impl<T> TextLayout<T> {
8889
wrap_width: f64::INFINITY,
8990
alignment: Default::default(),
9091
links: Rc::new([]),
91-
}
92-
}
93-
94-
/// Create a new `TextLayout` with the provided text.
95-
///
96-
/// This is useful when the text is not died to application data.
97-
pub fn from_text(text: impl Into<T>) -> Self {
98-
TextLayout {
99-
text: Some(text.into()),
100-
..TextLayout::new()
92+
text_is_rtl: false,
10193
}
10294
}
10395

@@ -163,9 +155,27 @@ impl<T> TextLayout<T> {
163155
self.layout = None;
164156
}
165157
}
158+
159+
/// Returns `true` if this layout's text appears to be right-to-left.
160+
///
161+
/// See [`piet::util::first_strong_rtl`] for more information.
162+
///
163+
/// [`piet::util::first_strong_rtl`]: crate::piet::util::first_strong_rtl
164+
pub fn text_is_rtl(&self) -> bool {
165+
self.text_is_rtl
166+
}
166167
}
167168

168169
impl<T: TextStorage> TextLayout<T> {
170+
/// Create a new `TextLayout` with the provided text.
171+
///
172+
/// This is useful when the text is not tied to application data.
173+
pub fn from_text(text: impl Into<T>) -> Self {
174+
let mut this = TextLayout::new();
175+
this.set_text(text.into());
176+
this
177+
}
178+
169179
/// Returns `true` if this layout needs to be rebuilt.
170180
///
171181
/// This happens (for instance) after style attributes are modified.
@@ -179,6 +189,7 @@ impl<T: TextStorage> TextLayout<T> {
179189
/// Set the text to display.
180190
pub fn set_text(&mut self, text: T) {
181191
if self.text.is_none() || !self.text.as_ref().unwrap().same(&text) {
192+
self.text_is_rtl = crate::piet::util::first_strong_rtl(text.as_str());
182193
self.text = Some(text);
183194
self.layout = None;
184195
}

druid/src/text/movement.rs

+36-64
Original file line numberDiff line numberDiff line change
@@ -16,60 +16,9 @@
1616
1717
use crate::kurbo::Point;
1818
use crate::piet::TextLayout as _;
19+
pub use crate::shell::text::{Direction, Movement, VerticalMovement, WritingDirection};
1920
use crate::text::{EditableText, Selection, TextLayout, TextStorage};
2021

21-
/// The specification of a movement.
22-
#[derive(Debug, PartialEq, Clone, Copy)]
23-
pub enum Movement {
24-
/// Move to the left by one grapheme cluster.
25-
Left,
26-
/// Move to the right by one grapheme cluster.
27-
Right,
28-
/// Move up one visible line.
29-
Up,
30-
/// Move down one visible line.
31-
Down,
32-
/// Move to the left by one word.
33-
LeftWord,
34-
/// Move to the right by one word.
35-
RightWord,
36-
/// Move to left end of visible line.
37-
PrecedingLineBreak,
38-
/// Move to right end of visible line.
39-
NextLineBreak,
40-
/// Move to the beginning of the document
41-
StartOfDocument,
42-
/// Move to the end of the document
43-
EndOfDocument,
44-
}
45-
46-
//FIXME: we should remove this whole file, and use the Movement type defined in druid-shell?
47-
impl From<crate::shell::text::Movement> for Movement {
48-
fn from(src: crate::shell::text::Movement) -> Movement {
49-
use crate::shell::text::{Direction, Movement as SMovemement, VerticalMovement};
50-
match src {
51-
SMovemement::Grapheme(Direction::Left) | SMovemement::Grapheme(Direction::Upstream) => {
52-
Movement::Left
53-
}
54-
SMovemement::Grapheme(_) => Movement::Right,
55-
SMovemement::Word(Direction::Left) => Movement::LeftWord,
56-
SMovemement::Word(_) => Movement::RightWord,
57-
SMovemement::Line(Direction::Left) | SMovemement::ParagraphStart => {
58-
Movement::PrecedingLineBreak
59-
}
60-
SMovemement::Line(_) | SMovemement::ParagraphEnd => Movement::NextLineBreak,
61-
SMovemement::Vertical(VerticalMovement::LineUp)
62-
| SMovemement::Vertical(VerticalMovement::PageUp) => Movement::Up,
63-
SMovemement::Vertical(VerticalMovement::LineDown)
64-
| SMovemement::Vertical(VerticalMovement::PageDown) => Movement::Down,
65-
SMovemement::Vertical(VerticalMovement::DocumentStart) => Movement::StartOfDocument,
66-
SMovemement::Vertical(VerticalMovement::DocumentEnd) => Movement::EndOfDocument,
67-
// the source enum is non_exhaustive
68-
_ => panic!("unhandled movement {:?}", src),
69-
}
70-
}
71-
}
72-
7322
/// Compute the result of movement on a selection.
7423
///
7524
/// returns a new selection representing the state after the movement.
@@ -91,8 +40,14 @@ pub fn movement<T: EditableText + TextStorage>(
9140
}
9241
};
9342

43+
let writing_direction = if crate::piet::util::first_strong_rtl(text.as_str()) {
44+
WritingDirection::RightToLeft
45+
} else {
46+
WritingDirection::LeftToRight
47+
};
48+
9449
let (offset, h_pos) = match m {
95-
Movement::Left => {
50+
Movement::Grapheme(d) if d.is_upstream_for_direction(writing_direction) => {
9651
if s.is_caret() || modify {
9752
text.prev_grapheme_offset(s.active)
9853
.map(|off| (off, None))
@@ -101,7 +56,7 @@ pub fn movement<T: EditableText + TextStorage>(
10156
(s.min(), None)
10257
}
10358
}
104-
Movement::Right => {
59+
Movement::Grapheme(_) => {
10560
if s.is_caret() || modify {
10661
text.next_grapheme_offset(s.active)
10762
.map(|off| (off, None))
@@ -110,8 +65,7 @@ pub fn movement<T: EditableText + TextStorage>(
11065
(s.max(), None)
11166
}
11267
}
113-
114-
Movement::Up => {
68+
Movement::Vertical(VerticalMovement::LineUp) => {
11569
let cur_pos = layout.hit_test_text_position(s.active);
11670
let h_pos = s.h_pos.unwrap_or(cur_pos.point.x);
11771
if cur_pos.line == 0 {
@@ -123,7 +77,7 @@ pub fn movement<T: EditableText + TextStorage>(
12377
(up_pos.idx, Some(point_above.x))
12478
}
12579
}
126-
Movement::Down => {
80+
Movement::Vertical(VerticalMovement::LineDown) => {
12781
let cur_pos = layout.hit_test_text_position(s.active);
12882
let h_pos = s.h_pos.unwrap_or(cur_pos.point.x);
12983
if cur_pos.line == layout.line_count() - 1 {
@@ -137,29 +91,47 @@ pub fn movement<T: EditableText + TextStorage>(
13791
(up_pos.idx, Some(point_below.x))
13892
}
13993
}
94+
Movement::Vertical(VerticalMovement::DocumentStart) => (0, None),
95+
Movement::Vertical(VerticalMovement::DocumentEnd) => (text.len(), None),
14096

141-
Movement::PrecedingLineBreak => (text.preceding_line_break(s.active), None),
142-
Movement::NextLineBreak => (text.next_line_break(s.active), None),
143-
144-
Movement::StartOfDocument => (0, None),
145-
Movement::EndOfDocument => (text.len(), None),
97+
Movement::ParagraphStart => (text.preceding_line_break(s.active), None),
98+
Movement::ParagraphEnd => (text.next_line_break(s.active), None),
14699

147-
Movement::LeftWord => {
100+
Movement::Line(d) => {
101+
let hit = layout.hit_test_text_position(s.active);
102+
let lm = layout.line_metric(hit.line).unwrap();
103+
let offset = if d.is_upstream_for_direction(writing_direction) {
104+
lm.start_offset
105+
} else {
106+
lm.end_offset
107+
};
108+
(offset, None)
109+
}
110+
Movement::Word(d) if d.is_upstream_for_direction(writing_direction) => {
148111
let offset = if s.is_caret() || modify {
149112
text.prev_word_offset(s.active).unwrap_or(0)
150113
} else {
151114
s.min()
152115
};
153116
(offset, None)
154117
}
155-
Movement::RightWord => {
118+
Movement::Word(_) => {
156119
let offset = if s.is_caret() || modify {
157120
text.next_word_offset(s.active).unwrap_or(s.active)
158121
} else {
159122
s.max()
160123
};
161124
(offset, None)
162125
}
126+
127+
// These two are not handled; they require knowledge of the size
128+
// of the viewport.
129+
Movement::Vertical(VerticalMovement::PageDown)
130+
| Movement::Vertical(VerticalMovement::PageUp) => (s.active, s.h_pos),
131+
other => {
132+
tracing::warn!("unhandled movement {:?}", other);
133+
(s.anchor, s.h_pos)
134+
}
163135
};
164136

165137
let start = if modify { s.anchor } else { offset };

0 commit comments

Comments
 (0)