Skip to content

Commit

Permalink
review feedback
Browse files Browse the repository at this point in the history
- support ctrl+home/end
- fix delete with non-collapsed selections
- move to start/end of line when maxing out up/down respectively
- fix panic on empty text buffer
- proper cursor placement when using left/right to collapse selection
- fix weird cursor offset bug when line ends with RTL run containing only whitespace that is reordered
- triple click to select line
  • Loading branch information
dfrg committed Aug 22, 2024
1 parent 66979c5 commit d02765a
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 76 deletions.
10 changes: 10 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
# word is treated as always correct. If the value is an empty string, the word
# is treated as always incorrect.

[default]
extend-ignore-re = [
# Matches lorem ipsum text.
# In general, regexes are only matched until the end of a line by typos,
# and the repeated matcher at the end of both of these also ensures that
# matching ends at quotes or symbols commonly used to terminate comments.
"Lorem ipsum [a-zA-Z .,]*",
"Phasellus in viverra dolor [a-zA-Z .,]*",
]

# Match Identifier - Case Sensitive
[default.extend-identifiers]
Beng = "Beng"
Expand Down
7 changes: 6 additions & 1 deletion examples/vello_editor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ pollster = "0.3.0"
winit = "0.30.3"
parley = { workspace = true, default-features = true }
peniko = { workspace = true }
clipboard-rs = "0.1.11"

[lints]
workspace = true

[target.'cfg(target_os = "android")'.dependencies]
winit = { version = "0.30.3", features = ["android-native-activity"] }

[target.'cfg(not(target_os = "android"))'.dependencies]
clipboard-rs = "0.1.11"
134 changes: 88 additions & 46 deletions examples/vello_editor/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,68 @@ impl Editor {
self.width = width;
}

#[allow(unused)]
pub fn active_text(&self) -> ActiveText {
if self.selection.is_collapsed() {
let range = self
.selection
.focus()
.cluster_path()
.cluster(&self.layout)
.unwrap()
.text_range();
.map(|c| c.text_range())
.unwrap_or_default();
ActiveText::FocusedCluster(self.selection.focus().affinity(), &self.buffer[range])
} else {
ActiveText::Selection(&self.buffer[self.selection.text_range()])
}
}

#[cfg(not(target_os = "android"))]
fn handle_clipboard(&mut self, code: KeyCode) {
match code {
KeyCode::KeyC => {
if !self.selection.is_collapsed() {
let text = &self.buffer[self.selection.text_range()];
let cb = ClipboardContext::new().unwrap();
cb.set_text(text.to_owned()).ok();
}
}
KeyCode::KeyX => {
if !self.selection.is_collapsed() {
let text = &self.buffer[self.selection.text_range()];
let cb = ClipboardContext::new().unwrap();
cb.set_text(text.to_owned()).ok();
if let Some(start) = self.delete_current_selection() {
self.update_layout(self.width, 1.0);
let (start, affinity) = if start > 0 {
(start - 1, Affinity::Upstream)
} else {
(start, Affinity::Downstream)
};
self.selection = Selection::from_index(&self.layout, start, affinity);
}
}
}
KeyCode::KeyV => {
let cb = ClipboardContext::new().unwrap();
let text = cb.get_text().unwrap_or_default();
let start = self
.delete_current_selection()
.unwrap_or_else(|| self.selection.focus().text_range().start);
self.buffer.insert_str(start, &text);
self.update_layout(self.width, 1.0);
self.selection =
Selection::from_index(&self.layout, start + text.len(), Affinity::default());
}
_ => {}
}
}

#[cfg(target_os = "android")]
fn handle_clipboard(&mut self, _code: KeyCode) {
// TODO: support clipboard on Android
}

pub fn handle_event(&mut self, event: &WindowEvent) {
match event {
WindowEvent::Resized(size) => {
Expand Down Expand Up @@ -109,42 +156,13 @@ impl Editor {
if let PhysicalKey::Code(code) = event.physical_key {
match code {
KeyCode::KeyC if action_mod => {
if !self.selection.is_collapsed() {
let text = &self.buffer[self.selection.text_range()];
let cb = ClipboardContext::new().unwrap();
cb.set_text(text.to_owned()).ok();
}
self.handle_clipboard(code);
}
KeyCode::KeyX if action_mod => {
if !self.selection.is_collapsed() {
let text = &self.buffer[self.selection.text_range()];
let cb = ClipboardContext::new().unwrap();
cb.set_text(text.to_owned()).ok();
if let Some(start) = self.delete_current_selection() {
self.update_layout(self.width, 1.0);
let (start, affinity) = if start > 0 {
(start - 1, Affinity::Upstream)
} else {
(start, Affinity::Downstream)
};
self.selection =
Selection::from_index(&self.layout, start, affinity);
}
}
self.handle_clipboard(code);
}
KeyCode::KeyV if action_mod => {
let cb = ClipboardContext::new().unwrap();
let text = cb.get_text().unwrap_or_default();
let start = self
.delete_current_selection()
.unwrap_or_else(|| self.selection.focus().text_range().start);
self.buffer.insert_str(start, &text);
self.update_layout(self.width, 1.0);
self.selection = Selection::from_index(
&self.layout,
start + text.len(),
Affinity::default(),
);
self.handle_clipboard(code);
}
KeyCode::ArrowLeft => {
self.selection = if ctrl {
Expand Down Expand Up @@ -172,20 +190,35 @@ impl Editor {
self.selection = self.selection.next_line(&self.layout, shift);
}
KeyCode::Home => {
self.selection = self.selection.line_start(&self.layout, shift);
if ctrl {
self.selection =
self.selection.move_lines(&self.layout, isize::MIN, shift);
} else {
self.selection = self.selection.line_start(&self.layout, shift);
}
}
KeyCode::End => {
self.selection = self.selection.line_end(&self.layout, shift);
if ctrl {
self.selection =
self.selection.move_lines(&self.layout, isize::MAX, shift);
} else {
self.selection = self.selection.line_end(&self.layout, shift);
}
}
KeyCode::Delete => {
if self.selection.is_collapsed() {
let start = if self.selection.is_collapsed() {
let range = self.selection.focus().text_range();
let start = range.start;
self.buffer.replace_range(range, "");
Some(start)
} else {
self.delete_current_selection();
self.delete_current_selection()
};
self.update_layout(self.width, 1.0);
self.selection = self.selection.refresh(&self.layout);
if let Some(start) = start {
self.update_layout(self.width, 1.0);
self.selection =
Selection::from_index(&self.layout, start, Affinity::default());
}
}
KeyCode::Backspace => {
let start = if self.selection.is_collapsed() {
Expand Down Expand Up @@ -229,7 +262,7 @@ impl Editor {
}
}

println!("Active text: {:?}", self.active_text());
// println!("Active text: {:?}", self.active_text());
}
WindowEvent::MouseInput { state, button, .. } => {
if *button == winit::event::MouseButton::Left {
Expand All @@ -238,7 +271,7 @@ impl Editor {
let now = Instant::now();
if let Some(last) = self.last_click_time.take() {
if now.duration_since(last).as_secs_f64() < 0.25 {
self.click_count = (self.click_count + 1) % 3;
self.click_count = (self.click_count + 1) % 4;
} else {
self.click_count = 1;
}
Expand All @@ -248,14 +281,23 @@ impl Editor {
self.last_click_time = Some(now);
match self.click_count {
2 => {
println!("SELECTING WORD");
self.selection = Selection::word_from_point(
&self.layout,
self.cursor_pos.0,
self.cursor_pos.1,
);
}
// TODO: handle line
3 => {
let focus = *Selection::from_point(
&self.layout,
self.cursor_pos.0,
self.cursor_pos.1,
)
.line_start(&self.layout, true)
.focus();
self.selection =
Selection::from(focus).line_end(&self.layout, true);
}
_ => {
self.selection = Selection::from_point(
&self.layout,
Expand All @@ -264,7 +306,7 @@ impl Editor {
);
}
}
println!("Active text: {:?}", self.active_text());
// println!("Active text: {:?}", self.active_text());
}
}
}
Expand All @@ -278,7 +320,7 @@ impl Editor {
self.cursor_pos.0,
self.cursor_pos.1,
);
println!("Active text: {:?}", self.active_text());
// println!("Active text: {:?}", self.active_text());
}
}
_ => {}
Expand Down
19 changes: 8 additions & 11 deletions parley/src/layout/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::*;

impl<'a, B: Brush> Cluster<'a, B> {
/// Returns the cluster for the given layout and byte index.
pub fn from_index(layout: &'a Layout<B>, byte_index: usize) -> Self {
pub fn from_index(layout: &'a Layout<B>, byte_index: usize) -> Option<Self> {
let mut path = ClusterPath::default();
if let Some((line_index, line)) = layout.line_for_byte_index(byte_index) {
path.line_index = line_index as u32;
Expand All @@ -17,16 +17,16 @@ impl<'a, B: Brush> Cluster<'a, B> {
for (cluster_index, cluster) in run.clusters().enumerate() {
path.logical_index = cluster_index as u32;
if cluster.text_range().contains(&byte_index) {
return path.cluster(layout).unwrap();
return path.cluster(layout);
}
}
}
}
path.cluster(layout).unwrap()
path.cluster(layout)
}

/// Returns the cluster and affinity for the given layout and point.
pub fn from_point(layout: &'a Layout<B>, x: f32, y: f32) -> (Self, Affinity) {
pub fn from_point(layout: &'a Layout<B>, x: f32, y: f32) -> Option<(Self, Affinity)> {
let mut path = ClusterPath::default();
if let Some((line_index, line)) = layout.line_for_offset(y) {
path.line_index = line_index as u32;
Expand Down Expand Up @@ -54,11 +54,11 @@ impl<'a, B: Brush> Cluster<'a, B> {
}
let affinity =
Affinity::new(cluster.is_rtl(), x <= edge + cluster_advance * 0.5);
return (path.cluster(layout).unwrap(), affinity);
return Some((path.cluster(layout)?, affinity));
}
}
}
(path.cluster(layout).unwrap(), Affinity::default())
Some((path.cluster(layout)?, Affinity::default()))
}

/// Returns the line that contains the cluster.
Expand Down Expand Up @@ -205,7 +205,7 @@ impl<'a, B: Brush> Cluster<'a, B> {
return None;
}
// We have to search for the cluster containing our end index
Some(Self::from_index(self.run.layout, index))
Self::from_index(self.run.layout, index)
}
}

Expand All @@ -220,10 +220,7 @@ impl<'a, B: Brush> Cluster<'a, B> {
}
.cluster(self.run.layout)
} else {
Some(Self::from_index(
self.run.layout,
self.text_range().start.checked_sub(1)?,
))
Self::from_index(self.run.layout, self.text_range().start.checked_sub(1)?)
}
}

Expand Down
Loading

0 comments on commit d02765a

Please sign in to comment.