Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursor rework #170

Merged
merged 20 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/vello_editor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use winit::window::Window;
mod access_ids;
use access_ids::{TEXT_INPUT_ID, WINDOW_ID};

// #[path = "text2.rs"]
mod text;
use parley::{GenericFamily, StyleProperty};

Expand Down Expand Up @@ -304,7 +303,7 @@ impl ApplicationHandler<accesskit_winit::Event> for SimpleVelloApp<'_> {
base_color: Color::rgb8(30, 30, 30), // Background color
width,
height,
antialiasing_method: AaConfig::Msaa16,
antialiasing_method: AaConfig::Area,
},
)
.expect("failed to render to surface");
Expand Down
7 changes: 3 additions & 4 deletions examples/vello_editor/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ impl Editor {
WindowEvent::MouseInput { state, button, .. } => {
if button == winit::event::MouseButton::Left {
self.pointer_down = state.is_pressed();
self.cursor_reset();
if self.pointer_down {
let now = Instant::now();
if let Some(last) = self.last_click_time.take() {
Expand Down Expand Up @@ -291,6 +292,7 @@ impl Editor {
self.cursor_pos = (position.x as f32 - INSET, position.y as f32 - INSET);
// macOS seems to generate a spurious move after selecting word?
if self.pointer_down && prev_pos != self.cursor_pos {
self.cursor_reset();
let cursor_pos = self.cursor_pos;
self.transact(|txn| txn.extend_selection_to_point(cursor_pos.0, cursor_pos.1));
// println!("Active text: {:?}", self.active_text());
Expand Down Expand Up @@ -324,12 +326,9 @@ impl Editor {
scene.fill(Fill::NonZero, transform, Color::STEEL_BLUE, None, &rect);
}
if self.cursor_visible {
if let Some(cursor) = self.editor.selection_strong_geometry(1.5) {
if let Some(cursor) = self.editor.cursor_geometry(1.5) {
scene.fill(Fill::NonZero, transform, Color::WHITE, None, &cursor);
};
if let Some(cursor) = self.editor.selection_weak_geometry(1.5) {
scene.fill(Fill::NonZero, transform, Color::LIGHT_GRAY, None, &cursor);
};
}
for line in self.editor.lines() {
for item in line.items() {
Expand Down
89 changes: 59 additions & 30 deletions parley/src/layout/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT

use super::*;
use swash::text::cluster::Whitespace;

/// Defines the visual side of the cluster for hit testing.
///
/// See [`Cluster::from_point`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ClusterSide {
/// Cluster was hit on the left half.
Left,
/// Cluster was hit on the right half.
Right,
}

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) -> Option<Self> {
pub fn from_byte_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 @@ -22,11 +34,11 @@ impl<'a, B: Brush> Cluster<'a, B> {
}
}
}
path.cluster(layout)
None
}

/// Returns the cluster and affinity for the given layout and point.
pub fn from_point(layout: &'a Layout<B>, x: f32, y: f32) -> Option<(Self, Affinity)> {
/// Returns the cluster and side for the given layout and point.
pub fn from_point(layout: &'a Layout<B>, x: f32, y: f32) -> Option<(Self, ClusterSide)> {
let mut path = ClusterPath::default();
if let Some((line_index, line)) = layout.line_for_offset(y) {
path.line_index = line_index as u32;
Expand All @@ -52,13 +64,16 @@ impl<'a, B: Brush> Cluster<'a, B> {
if x > offset && !is_last_cluster {
continue;
}
let affinity =
Affinity::new(cluster.is_rtl(), x <= edge + cluster_advance * 0.5);
return Some((path.cluster(layout)?, affinity));
let side = if x <= edge + cluster_advance * 0.5 {
ClusterSide::Left
} else {
ClusterSide::Right
};
return Some((path.cluster(layout)?, side));
}
}
}
Some((path.cluster(layout)?, Affinity::default()))
Some((path.cluster(layout)?, ClusterSide::Left))
}

/// Returns the line that contains the cluster.
Expand Down Expand Up @@ -109,12 +124,16 @@ impl<'a, B: Brush> Cluster<'a, B> {

/// Returns `true` if the cluster is a soft line break.
pub fn is_soft_line_break(&self) -> bool {
self.data.info.boundary() == Boundary::Line
self.is_end_of_line()
&& matches!(
self.line().data.break_reason,
BreakReason::Regular | BreakReason::Emergency
)
}

/// Returns `true` if the cluster is a hard line break.
pub fn is_hard_line_break(&self) -> bool {
self.data.info.boundary() == Boundary::Mandatory
self.data.info.whitespace() == Whitespace::Newline
}

/// Returns `true` if the cluster is a space or no-break space.
Expand Down Expand Up @@ -205,7 +224,7 @@ impl<'a, B: Brush> Cluster<'a, B> {
return None;
}
// We have to search for the cluster containing our end index
Self::from_index(self.run.layout, index)
Self::from_byte_index(self.run.layout, index)
}
}

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

Expand Down Expand Up @@ -299,7 +318,7 @@ impl<'a, B: Brush> Cluster<'a, B> {
}

/// Returns the next cluster that is marked as a word boundary.
pub fn next_word(&self) -> Option<Self> {
pub fn next_logical_word(&self) -> Option<Self> {
let mut cluster = self.clone();
while let Some(next) = cluster.next_logical() {
if next.is_word_boundary() {
Expand All @@ -310,8 +329,20 @@ impl<'a, B: Brush> Cluster<'a, B> {
None
}

/// Returns the next cluster that is marked as a word boundary.
pub fn next_visual_word(&self) -> Option<Self> {
let mut cluster = self.clone();
while let Some(next) = cluster.next_visual() {
if next.is_word_boundary() {
return Some(next);
}
cluster = next;
}
None
}

/// Returns the previous cluster that is marked as a word boundary.
pub fn previous_word(&self) -> Option<Self> {
pub fn previous_logical_word(&self) -> Option<Self> {
let mut cluster = self.clone();
while let Some(prev) = cluster.previous_logical() {
if prev.is_word_boundary() {
Expand All @@ -322,13 +353,25 @@ impl<'a, B: Brush> Cluster<'a, B> {
None
}

/// Returns the previous cluster that is marked as a word boundary.
pub fn previous_visual_word(&self) -> Option<Self> {
let mut cluster = self.clone();
while let Some(prev) = cluster.previous_visual() {
if prev.is_word_boundary() {
return Some(prev);
}
cluster = prev;
}
None
}

/// Returns the visual offset of this cluster along direction of text flow.
///
/// This cost of this function is roughly linear in the number of clusters
/// on the containing line.
pub fn visual_offset(&self) -> Option<f32> {
let line = self.path.line(self.run.layout)?;
let mut offset = 0.0;
let mut offset = line.metrics().offset;
for run_index in 0..=self.path.run_index() {
let run = line.run(run_index)?;
if run_index != self.path.run_index() {
Expand Down Expand Up @@ -359,15 +402,6 @@ pub enum Affinity {
}

impl Affinity {
pub(crate) fn new(is_rtl: bool, is_leading: bool) -> Self {
match (is_rtl, is_leading) {
// trailing edge of RTL and leading edge of LTR
(true, false) | (false, true) => Affinity::Downstream,
// leading edge of RTL and trailing edge of LTR
(true, true) | (false, false) => Affinity::Upstream,
}
}

pub fn invert(&self) -> Self {
match self {
Self::Downstream => Self::Upstream,
Expand All @@ -376,17 +410,12 @@ impl Affinity {
}

/// Returns true if the cursor should be placed on the leading edge.
pub fn is_visually_leading(&self, is_rtl: bool) -> bool {
pub(crate) fn is_visually_leading(&self, is_rtl: bool) -> bool {
match (*self, is_rtl) {
(Self::Upstream, true) | (Self::Downstream, false) => true,
(Self::Upstream, false) | (Self::Downstream, true) => false,
}
}

/// Returns true if the cursor should be placed on the trailing edge.
pub fn is_visually_trailing(&self, is_rtl: bool) -> bool {
!self.is_visually_leading(is_rtl)
}
}

/// Index based path to a cluster.
Expand Down
Loading