Improve Text tool resize/drag behavior (#2428)

* Have red be below quads

* Code review pt 1

* Skip rendering of pivot

* Code review pt 2

* Code review pt 3

* Cancel resize and its hints

* Remove the redundant placing message

* Dragging state for text tool fsm

* Cleanup

* Fix line tool undo and abort problems

* Code review

* 3px textbox overflow bottom

* Some more cleanup

* Fix reversed match arms that had been converted to if-else

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare 2025-03-19 12:19:49 +05:30 committed by GitHub
parent 43275b7a1e
commit b98711dbdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 234 additions and 259 deletions

View File

@ -88,10 +88,9 @@ pub struct MouseState {
impl MouseState { impl MouseState {
pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) { pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) {
match drag_start.distance(self.position) <= DRAG_THRESHOLD { let drag_too_small = drag_start.distance(self.position) <= DRAG_THRESHOLD;
true => responses.add(DocumentMessage::AbortTransaction), let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
false => responses.add(DocumentMessage::EndTransaction), responses.add(response);
}
} }
} }

View File

@ -174,10 +174,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
responses.add(DocumentMessage::PTZUpdate); responses.add(DocumentMessage::PTZUpdate);
} }
NavigationMessage::CanvasPanMouseWheel { use_y_as_x } => { NavigationMessage::CanvasPanMouseWheel { use_y_as_x } => {
let delta = match use_y_as_x { let delta = if use_y_as_x { (-ipp.mouse.scroll_delta.y, 0.).into() } else { -ipp.mouse.scroll_delta.as_dvec2() } * VIEWPORT_SCROLL_RATE;
false => -ipp.mouse.scroll_delta.as_dvec2(),
true => (-ipp.mouse.scroll_delta.y, 0.).into(),
} * VIEWPORT_SCROLL_RATE;
responses.add(NavigationMessage::CanvasPan { delta }); responses.add(NavigationMessage::CanvasPan { delta });
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
} }

View File

@ -36,7 +36,7 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
} else { } else {
DVec2::new(secondary_pos, primary_end) DVec2::new(secondary_pos, primary_end)
}; };
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color)); overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color), None);
} }
} }
} }
@ -105,7 +105,7 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x; let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x;
let start = DVec2::new(x_pos, min_y); let start = DVec2::new(x_pos, min_y);
let end = DVec2::new(x_pos, max_y); let end = DVec2::new(x_pos, max_y);
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color)); overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color), None);
} }
for (tan, multiply) in [(tan_a, -1.), (tan_b, 1.)] { for (tan, multiply) in [(tan_a, -1.), (tan_b, 1.)] {
@ -119,7 +119,7 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y; let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y;
let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos))); let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos)));
let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos))); let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos)));
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color)); overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color), None);
} }
} }
} }
@ -166,6 +166,7 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(start),
document_to_viewport.transform_point2(end), document_to_viewport.transform_point2(end),
Some(&grid_color), Some(&grid_color),
None,
Some(1.), Some(1.),
Some((spacing_x / cos_a) * document_to_viewport.matrix2.x_axis.length() - 1.), Some((spacing_x / cos_a) * document_to_viewport.matrix2.x_axis.length() - 1.),
None, None,

View File

@ -59,17 +59,17 @@ fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAff
match bezier.handles { match bezier.handles {
BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
overlay_context.line(handle, bezier.start, None); overlay_context.line(handle, bezier.start, None, None);
overlay_context.line(handle, bezier.end, None); overlay_context.line(handle, bezier.end, None, None);
overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
} }
BezierHandles::Cubic { handle_start, handle_end } => { BezierHandles::Cubic { handle_start, handle_end } => {
if not_under_anchor(handle_start, bezier.start) { if not_under_anchor(handle_start, bezier.start) {
overlay_context.line(handle_start, bezier.start, None); overlay_context.line(handle_start, bezier.start, None, None);
overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
} }
if not_under_anchor(handle_end, bezier.end) { if not_under_anchor(handle_end, bezier.end) {
overlay_context.line(handle_end, bezier.end, None); overlay_context.line(handle_end, bezier.end, None, None);
overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None); overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);
} }
} }
@ -93,17 +93,17 @@ pub fn overlay_bezier_handle_specific_point(
BezierHandles::Quadratic { handle } => { BezierHandles::Quadratic { handle } => {
if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) { if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) {
let end = if start == point_to_render { bezier.start } else { bezier.end }; let end = if start == point_to_render { bezier.start } else { bezier.end };
overlay_context.line(handle, end, None); overlay_context.line(handle, end, None, None);
overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
} }
} }
BezierHandles::Cubic { handle_start, handle_end } => { BezierHandles::Cubic { handle_start, handle_end } => {
if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) { if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) {
overlay_context.line(handle_start, bezier.start, None); overlay_context.line(handle_start, bezier.start, None, None);
overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
} }
if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) { if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) {
overlay_context.line(handle_end, bezier.end, None); overlay_context.line(handle_end, bezier.end, None, None);
overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None); overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);
} }
} }

View File

@ -127,11 +127,12 @@ impl OverlayContext {
self.end_dpi_aware_transform(); self.end_dpi_aware_transform();
} }
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>) { pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
self.dashed_line(start, end, color, None, None, None) self.dashed_line(start, end, color, thickness, None, None, None)
} }
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) { #[allow(clippy::too_many_arguments)]
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
self.start_dpi_aware_transform(); self.start_dpi_aware_transform();
// Set the dash pattern // Set the dash pattern
@ -159,8 +160,10 @@ impl OverlayContext {
self.render_context.begin_path(); self.render_context.begin_path();
self.render_context.move_to(start.x, start.y); self.render_context.move_to(start.x, start.y);
self.render_context.line_to(end.x, end.y); self.render_context.line_to(end.x, end.y);
self.render_context.set_line_width(thickness.unwrap_or(1.));
self.render_context.set_stroke_style_str(color.unwrap_or(COLOR_OVERLAY_BLUE)); self.render_context.set_stroke_style_str(color.unwrap_or(COLOR_OVERLAY_BLUE));
self.render_context.stroke(); self.render_context.stroke();
self.render_context.set_line_width(1.);
// Reset the dash pattern back to solid // Reset the dash pattern back to solid
if dash_width.is_some() { if dash_width.is_some() {
@ -309,8 +312,8 @@ impl OverlayContext {
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle); let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle); let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
self.line(pivot, end_point1, Some(color_line)); self.line(pivot, end_point1, Some(color_line), None);
self.line(pivot, end_point2, Some(color_line)); self.line(pivot, end_point2, Some(color_line), None);
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle); self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
} }
@ -323,7 +326,7 @@ impl OverlayContext {
.to_rgba_hex_srgb(); .to_rgba_hex_srgb();
fill_color.insert(0, '#'); fill_color.insert(0, '#');
let fill_color = Some(fill_color.as_str()); let fill_color = Some(fill_color.as_str());
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
self.circle(start, radius, fill_color, None); self.circle(start, radius, fill_color, None);
self.circle(start, radius * scale.abs(), fill_color, None); self.circle(start, radius * scale.abs(), fill_color, None);
self.text( self.text(

View File

@ -89,10 +89,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
PreferencesMessage::ModifyLayout { zoom_with_scroll } => { PreferencesMessage::ModifyLayout { zoom_with_scroll } => {
self.zoom_with_scroll = zoom_with_scroll; self.zoom_with_scroll = zoom_with_scroll;
let variant = match zoom_with_scroll { let variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default };
false => MappingVariant::Default,
true => MappingVariant::ZoomWithScroll,
};
responses.add(KeyMappingMessage::ModifyMapping(variant)); responses.add(KeyMappingMessage::ModifyMapping(variant));
} }
PreferencesMessage::SelectionMode { selection_mode } => { PreferencesMessage::SelectionMode { selection_mode } => {

View File

@ -8,7 +8,7 @@ fn draw_dashed_line(line_start: DVec2, line_end: DVec2, transform: DAffine2, ove
let min_viewport = transform.transform_point2(line_start); let min_viewport = transform.transform_point2(line_start);
let max_viewport = transform.transform_point2(line_end); let max_viewport = transform.transform_point2(line_end);
overlay_context.dashed_line(min_viewport, max_viewport, None, Some(2.), Some(2.), Some(0.5)); overlay_context.dashed_line(min_viewport, max_viewport, None, None, Some(2.), Some(2.), Some(0.5));
} }
/// Draws a solid line with a length annotation between two points transformed by the given affine transformations. /// Draws a solid line with a length annotation between two points transformed by the given affine transformations.
fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext, label_alignment: LabelAlignment) { fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext, label_alignment: LabelAlignment) {
@ -16,7 +16,7 @@ fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2
let min_viewport = transform.transform_point2(line_start); let min_viewport = transform.transform_point2(line_start);
let max_viewport = transform.transform_point2(line_end); let max_viewport = transform.transform_point2(line_end);
overlay_context.line(min_viewport, max_viewport, None); overlay_context.line(min_viewport, max_viewport, None, None);
// Remove trailing zeros from the formatted string // Remove trailing zeros from the formatted string
let length = format!("{:.2}", transform_to_document.transform_vector2(line_end - line_start).length()) let length = format!("{:.2}", transform_to_document.transform_vector2(line_end - line_start).length())

View File

@ -81,10 +81,10 @@ impl Pivot {
} }
} }
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64) { pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
self.recalculate_pivot(document); self.recalculate_pivot(document);
if let Some(pivot) = self.pivot { if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
overlay_context.pivot(pivot, angle); overlay_context.pivot(pivot, data.0);
} }
} }

View File

@ -593,10 +593,7 @@ impl ShapeState {
if points_colinear_status.any(|point| first_is_colinear != point) { if points_colinear_status.any(|point| first_is_colinear != point) {
return ManipulatorAngle::Mixed; return ManipulatorAngle::Mixed;
} }
match first_is_colinear { if first_is_colinear { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }
false => ManipulatorAngle::Free,
true => ManipulatorAngle::Colinear,
}
} }
pub fn convert_manipulator_handles_to_colinear(&self, vector_data: &VectorData, point_id: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) { pub fn convert_manipulator_handles_to_colinear(&self, vector_data: &VectorData, point_id: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {

View File

@ -414,12 +414,13 @@ impl SnapManager {
let start = DVec2::new(first.max().x, y); let start = DVec2::new(first.max().x, y);
let end = DVec2::new(second.min().x, y); let end = DVec2::new(second.min().x, y);
let signed_size = if bottom { y_size } else { -y_size }; let signed_size = if bottom { y_size } else { -y_size };
overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::Y * signed_size), None); overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::Y * signed_size), None, None);
overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::Y * signed_size), None); overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::Y * signed_size), None, None);
overlay_context.line( overlay_context.line(
transform.transform_point2(start + DVec2::Y * signed_size / 2.), transform.transform_point2(start + DVec2::Y * signed_size / 2.),
transform.transform_point2(end + DVec2::Y * signed_size / 2.), transform.transform_point2(end + DVec2::Y * signed_size / 2.),
None, None,
None,
); );
} }
} }
@ -432,12 +433,13 @@ impl SnapManager {
let start = DVec2::new(x, first.max().y); let start = DVec2::new(x, first.max().y);
let end = DVec2::new(x, second.min().y); let end = DVec2::new(x, second.min().y);
let signed_size = if right { x_size } else { -x_size }; let signed_size = if right { x_size } else { -x_size };
overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::X * signed_size), None); overlay_context.line(transform.transform_point2(start), transform.transform_point2(start + DVec2::X * signed_size), None, None);
overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::X * signed_size), None); overlay_context.line(transform.transform_point2(end), transform.transform_point2(end + DVec2::X * signed_size), None, None);
overlay_context.line( overlay_context.line(
transform.transform_point2(start + DVec2::X * signed_size / 2.), transform.transform_point2(start + DVec2::X * signed_size / 2.),
transform.transform_point2(end + DVec2::X * signed_size / 2.), transform.transform_point2(end + DVec2::X * signed_size / 2.),
None, None,
None,
); );
} }
} }
@ -460,7 +462,7 @@ impl SnapManager {
let align = [ind.alignment_target_x, ind.alignment_target_y].map(|target| target.map(|target| to_viewport.transform_point2(target))); let align = [ind.alignment_target_x, ind.alignment_target_y].map(|target| target.map(|target| to_viewport.transform_point2(target)));
let any_align = align.iter().flatten().next().is_some(); let any_align = align.iter().flatten().next().is_some();
for &target in align.iter().flatten() { for &target in align.iter().flatten() {
overlay_context.line(viewport, target, None); overlay_context.line(viewport, target, None, None);
} }
for &target in align.iter().flatten() { for &target in align.iter().flatten() {
overlay_context.manipulator_handle(target, false, None); overlay_context.manipulator_handle(target, false, None);

View File

@ -374,7 +374,7 @@ pub struct BoundingBoxManager {
pub bounds: [DVec2; 2], pub bounds: [DVec2; 2],
/// The transform to viewport space for the bounds co-ordinates when the bounds were last updated. /// The transform to viewport space for the bounds co-ordinates when the bounds were last updated.
pub transform: DAffine2, pub transform: DAffine2,
/// Was the transform previously singular? /// Whether the transform is actually singular but adjusted to not be so.
pub transform_tampered: bool, pub transform_tampered: bool,
/// The transform to viewport space for the bounds co-ordinates when the transformation was started. /// The transform to viewport space for the bounds co-ordinates when the transformation was started.
pub original_bound_transform: DAffine2, pub original_bound_transform: DAffine2,
@ -566,16 +566,24 @@ impl BoundingBoxManager {
} }
} }
pub fn render_quad(&self, overlay_context: &mut OverlayContext) {
let quad = self.transform * Quad::from_box(self.bounds);
// Draw the bounding box rectangle
overlay_context.quad(quad, None);
}
/// Update the position of the bounding box and transform handles /// Update the position of the bounding box and transform handles
pub fn render_overlays(&mut self, overlay_context: &mut OverlayContext) { pub fn render_overlays(&mut self, overlay_context: &mut OverlayContext, render_quad: bool) {
let quad = self.transform * Quad::from_box(self.bounds); let quad = self.transform * Quad::from_box(self.bounds);
let category = self.overlay_display_category(); let category = self.overlay_display_category();
let horizontal_edges = [quad.top_right().midpoint(quad.bottom_right()), quad.bottom_left().midpoint(quad.top_left())]; let horizontal_edges = [quad.top_right().midpoint(quad.bottom_right()), quad.bottom_left().midpoint(quad.top_left())];
let vertical_edges = [quad.top_left().midpoint(quad.top_right()), quad.bottom_right().midpoint(quad.bottom_left())]; let vertical_edges = [quad.top_left().midpoint(quad.top_right()), quad.bottom_right().midpoint(quad.bottom_left())];
// Draw the bounding box rectangle if render_quad {
overlay_context.quad(quad, None); self.render_quad(overlay_context);
}
let mut draw_handle = |point: DVec2, angle: f64| { let mut draw_handle = |point: DVec2, angle: f64| {
let quad = DAffine2::from_angle_translation(angle, point) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]); let quad = DAffine2::from_angle_translation(angle, point) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);

View File

@ -60,7 +60,7 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
let (text, font, typesetting) = get_text(layer, &document.network_interface).expect("Text layer should have text when interacting with the Text tool"); let (text, font, typesetting) = get_text(layer, &document.network_interface).expect("Text layer should have text when interacting with the Text tool");
let buzz_face = font_cache.get(font).map(|data| load_face(data)); let buzz_face = font_cache.get(font).map(|data| load_face(data));
let far = graphene_core::text::bounding_box(text, buzz_face.as_ref(), typesetting); let far = graphene_core::text::bounding_box(text, buzz_face.as_ref(), typesetting, false);
Quad::from_box([DVec2::ZERO, far]) Quad::from_box([DVec2::ZERO, far])
} }

View File

@ -231,7 +231,7 @@ impl Fsm for ArtboardToolFsmState {
bounding_box_manager.bounds = bounds; bounding_box_manager.bounds = bounds;
bounding_box_manager.transform = document.metadata().document_to_viewport; bounding_box_manager.transform = document.metadata().document_to_viewport;
bounding_box_manager.render_overlays(&mut overlay_context); bounding_box_manager.render_overlays(&mut overlay_context, true);
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
} }

View File

@ -255,7 +255,7 @@ impl Fsm for GradientToolFsmState {
let Gradient { start, end, stops, .. } = gradient; let Gradient { start, end, stops, .. } = gradient;
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end)); let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
overlay_context.line(start, end, None); overlay_context.line(start, end, None, None);
overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start), None); overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start), None);
overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End), None); overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End), None);

View File

@ -189,7 +189,7 @@ impl Fsm for LineToolFsmState {
let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point)); let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point));
if (start.x - end.x).abs() > f64::EPSILON * 1000. && (start.y - end.y).abs() > f64::EPSILON * 1000. { if (start.x - end.x).abs() > f64::EPSILON * 1000. && (start.y - end.y).abs() > f64::EPSILON * 1000. {
overlay_context.line(viewport_start, viewport_end, None); overlay_context.line(viewport_start, viewport_end, None, None);
overlay_context.square(viewport_start, Some(6.), None, None); overlay_context.square(viewport_start, Some(6.), None, None);
overlay_context.square(viewport_end, Some(6.), None, None); overlay_context.square(viewport_end, Some(6.), None, None);
} }
@ -205,6 +205,8 @@ impl Fsm for LineToolFsmState {
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
tool_data.drag_start = snapped.snapped_point_document; tool_data.drag_start = snapped.snapped_point_document;
responses.add(DocumentMessage::StartTransaction);
for (layer, [document_start, document_end]) in tool_data.selected_layers_with_position.iter() { for (layer, [document_start, document_end]) in tool_data.selected_layers_with_position.iter() {
let transform = document.metadata().transform_to_viewport(*layer); let transform = document.metadata().transform_to_viewport(*layer);
let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD;
@ -226,8 +228,6 @@ impl Fsm for LineToolFsmState {
} }
} }
responses.add(DocumentMessage::StartTransaction);
let node_type = resolve_document_node_type("Line").expect("Line node does not exist"); let node_type = resolve_document_node_type("Line").expect("Line node does not exist");
let node = node_type.node_template_input_override([ let node = node_type.node_template_input_override([
None, None,
@ -320,9 +320,7 @@ impl Fsm for LineToolFsmState {
(LineToolFsmState::Drawing, LineToolMessage::Abort) => { (LineToolFsmState::Drawing, LineToolMessage::Abort) => {
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
tool_data.editing_layer.take(); tool_data.editing_layer.take();
if tool_data.dragging_endpoint.is_none() { responses.add(DocumentMessage::AbortTransaction);
responses.add(DocumentMessage::AbortTransaction);
}
LineToolFsmState::Ready LineToolFsmState::Ready
} }
(_, LineToolMessage::WorkingColorChanged) => { (_, LineToolMessage::WorkingColorChanged) => {
@ -377,12 +375,12 @@ fn generate_line(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle:
tool_data.angle = angle; tool_data.angle = angle;
let angle_vec = DVec2::from_angle(angle);
if lock_angle { if lock_angle {
let angle_vec = DVec2::new(angle.cos(), angle.sin());
line_length = (document_points[1] - document_points[0]).dot(angle_vec); line_length = (document_points[1] - document_points[0]).dot(angle_vec);
} }
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin()); document_points[1] = document_points[0] + line_length * angle_vec;
let constrained = snap_angle || lock_angle; let constrained = snap_angle || lock_angle;
let snap = &mut tool_data.snap_manager; let snap = &mut tool_data.snap_manager;

View File

@ -970,12 +970,12 @@ impl Fsm for PathToolFsmState {
match axis { match axis {
Axis::Y => { Axis::Y => {
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_BLUE)); overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(other)); overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(other), None);
} }
Axis::X | Axis::Both => { Axis::X | Axis::Both => {
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(COLOR_OVERLAY_BLUE)); overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(other)); overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(other), None);
} }
} }
} }
@ -986,8 +986,8 @@ impl Fsm for PathToolFsmState {
if let Some(closest_segment) = &tool_data.segment { if let Some(closest_segment) = &tool_data.segment {
overlay_context.manipulator_anchor(closest_segment.closest_point_to_viewport(), false, Some(COLOR_OVERLAY_BLUE)); overlay_context.manipulator_anchor(closest_segment.closest_point_to_viewport(), false, Some(COLOR_OVERLAY_BLUE));
if let (Some(handle1), Some(handle2)) = closest_segment.handle_positions(document.metadata()) { if let (Some(handle1), Some(handle2)) = closest_segment.handle_positions(document.metadata()) {
overlay_context.line(closest_segment.closest_point_to_viewport(), handle1, Some(COLOR_OVERLAY_BLUE)); overlay_context.line(closest_segment.closest_point_to_viewport(), handle1, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.line(closest_segment.closest_point_to_viewport(), handle2, Some(COLOR_OVERLAY_BLUE)); overlay_context.line(closest_segment.closest_point_to_viewport(), handle2, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.manipulator_handle(handle1, false, Some(COLOR_OVERLAY_BLUE)); overlay_context.manipulator_handle(handle1, false, Some(COLOR_OVERLAY_BLUE));
overlay_context.manipulator_handle(handle2, false, Some(COLOR_OVERLAY_BLUE)); overlay_context.manipulator_handle(handle2, false, Some(COLOR_OVERLAY_BLUE));
} }

View File

@ -1272,7 +1272,7 @@ impl Fsm for PenToolFsmState {
} }
// Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out) // Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out)
overlay_context.line(next_anchor, next_handle_start, None); overlay_context.line(next_anchor, next_handle_start, None, None);
match tool_options.pen_overlay_mode { match tool_options.pen_overlay_mode {
PenOverlayMode::AllHandles => { PenOverlayMode::AllHandles => {
@ -1289,19 +1289,19 @@ impl Fsm for PenToolFsmState {
if let (Some(anchor_start), Some(handle_start), Some(handle_end)) = (anchor_start, handle_start, handle_end) { if let (Some(anchor_start), Some(handle_start), Some(handle_end)) = (anchor_start, handle_start, handle_end) {
// Draw the line between the most recently placed anchor and its outgoing handle (which is currently influencing the currently-being-placed segment) // Draw the line between the most recently placed anchor and its outgoing handle (which is currently influencing the currently-being-placed segment)
overlay_context.line(anchor_start, handle_start, None); overlay_context.line(anchor_start, handle_start, None, None);
// Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out) // Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out)
overlay_context.line(next_anchor, handle_end, None); overlay_context.line(next_anchor, handle_end, None, None);
if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle { if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle {
// Draw the line between the currently-being-placed anchor and last-placed point (lock angle bent overlays) // Draw the line between the currently-being-placed anchor and last-placed point (lock angle bent overlays)
overlay_context.dashed_line(anchor_start, next_anchor, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_line(anchor_start, next_anchor, None, None, Some(4.), Some(4.), Some(0.5));
} }
// Draw the line between the currently-being-placed anchor and last-placed point (snap angle bent overlays) // Draw the line between the currently-being-placed anchor and last-placed point (snap angle bent overlays)
if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.snap_angle { if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.snap_angle {
overlay_context.dashed_line(anchor_start, next_anchor, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_line(anchor_start, next_anchor, None, None, Some(4.), Some(4.), Some(0.5));
} }
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) {

View File

@ -594,7 +594,7 @@ impl Fsm for SelectToolFsmState {
bounding_box_manager.bounds = bounds; bounding_box_manager.bounds = bounds;
bounding_box_manager.transform = transform; bounding_box_manager.transform = transform;
bounding_box_manager.transform_tampered = transform_tampered; bounding_box_manager.transform_tampered = transform_tampered;
bounding_box_manager.render_overlays(&mut overlay_context); bounding_box_manager.render_overlays(&mut overlay_context, true);
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
} }
@ -656,7 +656,7 @@ impl Fsm for SelectToolFsmState {
}); });
// Update pivot // Update pivot
tool_data.pivot.update_pivot(document, &mut overlay_context, angle); tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,)));
// Update compass rose // Update compass rose
tool_data.compass_rose.refresh_position(document); tool_data.compass_rose.refresh_position(document);
@ -696,7 +696,7 @@ impl Fsm for SelectToolFsmState {
&format!("#{}", color_string) &format!("#{}", color_string)
}; };
let line_center = tool_data.line_center; let line_center = tool_data.line_center;
overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color)); overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None);
} }
} }
} }
@ -721,8 +721,8 @@ impl Fsm for SelectToolFsmState {
let edge = DVec2::from_angle(snapped_angle) * viewport_diagonal; let edge = DVec2::from_angle(snapped_angle) * viewport_diagonal;
let perp = edge.perp(); let perp = edge.perp();
overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(COLOR_OVERLAY_BLUE)); overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(other)); overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(other), None);
} }
// Check if the tool is in selection mode // Check if the tool is in selection mode
@ -1269,18 +1269,6 @@ impl Fsm for SelectToolFsmState {
state state
} }
(SelectToolFsmState::Dragging { .. }, SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
tool_data.axis_align = false;
tool_data.snap_manager.cleanup(responses);
responses.add_front(response);
let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::Dragging { has_dragged, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { (SelectToolFsmState::Dragging { has_dragged, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
// Deselect layer if not snap dragging // Deselect layer if not snap dragging
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
@ -1337,48 +1325,25 @@ impl Fsm for SelectToolFsmState {
let selection = tool_data.nested_selection_behavior; let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection } SelectToolFsmState::Ready { selection }
} }
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => { (
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::Dragging { .. },
true => DocumentMessage::AbortTransaction, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
false => DocumentMessage::EndTransaction, ) => {
}; let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
responses.add(response); responses.add(response);
tool_data.axis_align = false;
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_manager { if !matches!(self, SelectToolFsmState::DraggingPivot) {
bounds.original_transforms.clear(); if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
} }
let selection = tool_data.nested_selection_behavior; let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection } SelectToolFsmState::Ready { selection }
} }
(SelectToolFsmState::RotatingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
responses.add(response);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
responses.add(response);
tool_data.snap_manager.cleanup(responses);
let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { (SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
let quad = tool_data.selection_quad(); let quad = tool_data.selection_quad();

View File

@ -203,6 +203,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
match self.fsm_state { match self.fsm_state {
TextToolFsmState::Ready => actions!(TextToolMessageDiscriminant; TextToolFsmState::Ready => actions!(TextToolMessageDiscriminant;
DragStart, DragStart,
PointerOutsideViewport,
PointerMove, PointerMove,
), ),
TextToolFsmState::Editing => actions!(TextToolMessageDiscriminant; TextToolFsmState::Editing => actions!(TextToolMessageDiscriminant;
@ -213,11 +214,13 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
DragStop, DragStop,
Abort, Abort,
PointerMove, PointerMove,
PointerOutsideViewport,
), ),
TextToolFsmState::ResizingBounds => actions!(TextToolMessageDiscriminant; TextToolFsmState::ResizingBounds => actions!(TextToolMessageDiscriminant;
DragStop, DragStop,
Abort, Abort,
PointerMove, PointerMove,
PointerOutsideViewport,
), ),
} }
} }
@ -242,9 +245,9 @@ enum TextToolFsmState {
Ready, Ready,
/// The user is typing in the interactive viewport text area. /// The user is typing in the interactive viewport text area.
Editing, Editing,
/// The user is clicking to add a new text layer, but hasn't dragged or released the left mouse button yet.
Placing,
/// The user is dragging to create a new text area. /// The user is dragging to create a new text area.
Placing,
/// The user is dragging an existing text layer to move it.
Dragging, Dragging,
/// The user is dragging to resize the text area. /// The user is dragging to resize the text area.
ResizingBounds, ResizingBounds,
@ -271,6 +274,8 @@ struct TextToolData {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
editing_text: Option<EditingText>, editing_text: Option<EditingText>,
new_text: String, new_text: String,
drag_start: DVec2,
drag_current: DVec2,
resize: Resize, resize: Resize,
auto_panning: AutoPanning, auto_panning: AutoPanning,
// Since the overlays must be drawn without knowledge of the inputs // Since the overlays must be drawn without knowledge of the inputs
@ -464,7 +469,7 @@ impl Fsm for TextToolFsmState {
}); });
if let Some(editing_text) = tool_data.editing_text.as_mut() { if let Some(editing_text) = tool_data.editing_text.as_mut() {
let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data)); let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data));
let far = graphene_core::text::bounding_box(&tool_data.new_text, buzz_face.as_ref(), editing_text.typesetting); let far = graphene_core::text::bounding_box(&tool_data.new_text, buzz_face.as_ref(), editing_text.typesetting, false);
if far.x != 0. && far.y != 0. { if far.x != 0. && far.y != 0. {
let quad = Quad::from_box([DVec2::ZERO, far]); let quad = Quad::from_box([DVec2::ZERO, far]);
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad; let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
@ -475,7 +480,7 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Editing TextToolFsmState::Editing
} }
(_, TextToolMessage::Overlays(mut overlay_context)) => { (_, TextToolMessage::Overlays(mut overlay_context)) => {
if matches!(self, Self::Placing | Self::Dragging) { if matches!(self, Self::Placing) {
// Get the updated selection box bounds // Get the updated selection box bounds
let quad = Quad::from_box(tool_data.cached_resize_bounds); let quad = Quad::from_box(tool_data.cached_resize_bounds);
@ -506,21 +511,18 @@ impl Fsm for TextToolFsmState {
bounding_box_manager.bounds = [bounds.0[0], bounds.0[2]]; bounding_box_manager.bounds = [bounds.0[0], bounds.0[2]];
bounding_box_manager.transform = layer_transform; bounding_box_manager.transform = layer_transform;
bounding_box_manager.render_overlays(&mut overlay_context); bounding_box_manager.render_quad(&mut overlay_context);
// Draw red overlay if text is clipped // Draw red overlay if text is clipped
let transformed_quad = layer_transform * bounds; let transformed_quad = layer_transform * bounds;
if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) { if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) {
let buzz_face = font_cache.get(font).map(|data| load_face(data)); let buzz_face = font_cache.get(font).map(|data| load_face(data));
if lines_clipping(text.as_str(), buzz_face, typesetting) { if lines_clipping(text.as_str(), buzz_face, typesetting) {
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED)); overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
} }
} }
// The angle is choosen to be parallel to the X axis in the bounds transform. bounding_box_manager.render_overlays(&mut overlay_context, false);
let angle = bounding_box_manager.transform.transform_vector2(DVec2::X).to_angle(); tool_data.pivot.update_pivot(document, &mut overlay_context, None);
// Update pivot
tool_data.pivot.update_pivot(document, &mut overlay_context, angle);
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
} }
@ -540,6 +542,8 @@ impl Fsm for TextToolFsmState {
(TextToolFsmState::Ready, TextToolMessage::DragStart) => { (TextToolFsmState::Ready, TextToolMessage::DragStart) => {
tool_data.resize.start(document, input); tool_data.resize.start(document, input);
tool_data.cached_resize_bounds = [tool_data.resize.viewport_drag_start(document); 2]; tool_data.cached_resize_bounds = [tool_data.resize.viewport_drag_start(document); 2];
tool_data.drag_start = input.mouse.position;
tool_data.drag_current = input.mouse.position;
let dragging_bounds = tool_data.bounding_box_manager.as_mut().and_then(|bounding_box| { let dragging_bounds = tool_data.bounding_box_manager.as_mut().and_then(|bounding_box| {
let edges = bounding_box.check_selected_edges(input.mouse.position); let edges = bounding_box.check_selected_edges(input.mouse.position);
@ -557,7 +561,7 @@ impl Fsm for TextToolFsmState {
let mut all_selected = selected.selected_visible_and_unlocked_layers(&document.network_interface); let mut all_selected = selected.selected_visible_and_unlocked_layers(&document.network_interface);
let selected = all_selected.find(|layer| is_layer_fed_by_node_of_name(*layer, &document.network_interface, "Text")); let selected = all_selected.find(|layer| is_layer_fed_by_node_of_name(*layer, &document.network_interface, "Text"));
if let Some(_selected_edges) = dragging_bounds { if dragging_bounds.is_some() {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
// Set the original transform // Set the original transform
@ -568,12 +572,25 @@ impl Fsm for TextToolFsmState {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_bound_transform = bounds.transform; bounds.original_bound_transform = bounds.transform;
bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.); bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);
} }
tool_data.get_snap_candidates(document, font_cache); tool_data.get_snap_candidates(document, font_cache);
return TextToolFsmState::ResizingBounds; return TextToolFsmState::ResizingBounds;
} else if let Some(clicked_layer) = TextToolData::check_click(document, input, font_cache) {
responses.add(DocumentMessage::StartTransaction);
if selected != Some(clicked_layer) {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] });
}
let original_transform = document.metadata().transform_to_document(clicked_layer);
tool_data.layer_dragging = Some(ResizingLayer {
id: clicked_layer,
original_transform,
});
tool_data.get_snap_candidates(document, font_cache);
return TextToolFsmState::Dragging;
} }
TextToolFsmState::Placing TextToolFsmState::Placing
} }
@ -596,7 +613,7 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Ready TextToolFsmState::Ready
} }
(Self::Placing | TextToolFsmState::Dragging, TextToolMessage::PointerMove { center, lock_ratio }) => { (TextToolFsmState::Placing, TextToolMessage::PointerMove { center, lock_ratio }) => {
tool_data.cached_resize_bounds = tool_data.resize.calculate_points_ignore_layer(document, input, center, lock_ratio, false); tool_data.cached_resize_bounds = tool_data.resize.calculate_points_ignore_layer(document, input, center, lock_ratio, false);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@ -608,18 +625,42 @@ impl Fsm for TextToolFsmState {
]; ];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
TextToolFsmState::Placing
}
(TextToolFsmState::Dragging, TextToolMessage::PointerMove { center, lock_ratio }) => {
if let Some(dragging_layer) = &tool_data.layer_dragging {
let delta = input.mouse.position - tool_data.drag_current;
tool_data.drag_current = input.mouse.position;
responses.add(GraphOperationMessage::TransformChange {
layer: dragging_layer.id,
transform: DAffine2::from_translation(delta),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
// Auto-panning
let messages = [
TextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(),
TextToolMessage::PointerMove { center, lock_ratio }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
}
TextToolFsmState::Dragging TextToolFsmState::Dragging
} }
(TextToolFsmState::ResizingBounds, TextToolMessage::PointerMove { center, lock_ratio }) => { (TextToolFsmState::ResizingBounds, TextToolMessage::PointerMove { center, lock_ratio }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
if let Some(movement) = &mut bounds.selected_edges { if let Some(movement) = &mut bounds.selected_edges {
let (center_bool, lock_ratio_bool) = (input.keyboard.key(center), input.keyboard.key(lock_ratio)); let (centered, constrain) = (input.keyboard.key(center), input.keyboard.key(lock_ratio));
let center_position = center_bool.then_some(bounds.center_of_transformation); let center_position = centered.then_some(bounds.center_of_transformation);
let Some(dragging_layer) = tool_data.layer_dragging else { return TextToolFsmState::Ready }; let Some(dragging_layer) = tool_data.layer_dragging else { return TextToolFsmState::Ready };
let Some(node_id) = graph_modification_utils::get_text_id(dragging_layer.id, &document.network_interface) else { let Some(node_id) = graph_modification_utils::get_text_id(dragging_layer.id, &document.network_interface) else {
warn!("Cannot get text node id"); warn!("Cannot get text node id");
tool_data.layer_dragging = None; tool_data.layer_dragging.take();
return TextToolFsmState::Ready; return TextToolFsmState::Ready;
}; };
@ -630,7 +671,7 @@ impl Fsm for TextToolFsmState {
snap_data: SnapData::ignore(document, input, &selected), snap_data: SnapData::ignore(document, input, &selected),
}); });
let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center_position, lock_ratio_bool, snap); let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center_position, constrain, snap);
// Normalize so the size is always positive // Normalize so the size is always positive
let (position, size) = (position.min(position + size), size.abs()); let (position, size) = (position.min(position + size), size.abs());
@ -677,13 +718,13 @@ impl Fsm for TextToolFsmState {
self self
} }
(TextToolFsmState::Placing | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => { (TextToolFsmState::Placing, TextToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning setup // Auto-panning setup
let _ = tool_data.auto_panning.shift_viewport(input, responses); let _ = tool_data.auto_panning.shift_viewport(input, responses);
TextToolFsmState::Dragging TextToolFsmState::Placing
} }
(TextToolFsmState::ResizingBounds, TextToolMessage::PointerOutsideViewport { .. }) => { (TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // AutoPanning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
@ -705,10 +746,8 @@ impl Fsm for TextToolFsmState {
state state
} }
(TextToolFsmState::ResizingBounds, TextToolMessage::DragStop) => { (TextToolFsmState::ResizingBounds, TextToolMessage::DragStop) => {
let response = match input.mouse.position.distance(tool_data.resize.viewport_drag_start(document)) < 10. * f64::EPSILON { let drag_too_small = input.mouse.position.distance(tool_data.resize.viewport_drag_start(document)) < 10. * f64::EPSILON;
true => DocumentMessage::AbortTransaction, let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
false => DocumentMessage::EndTransaction,
};
responses.add(response); responses.add(response);
tool_data.resize.snap_manager.cleanup(responses); tool_data.resize.snap_manager.cleanup(responses);
@ -719,7 +758,7 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Ready TextToolFsmState::Ready
} }
(TextToolFsmState::Placing | TextToolFsmState::Dragging, TextToolMessage::DragStop) => { (TextToolFsmState::Placing, TextToolMessage::DragStop) => {
let [start, end] = tool_data.cached_resize_bounds; let [start, end] = tool_data.cached_resize_bounds;
let has_dragged = (start - end).length_squared() > DRAG_THRESHOLD * DRAG_THRESHOLD; let has_dragged = (start - end).length_squared() > DRAG_THRESHOLD * DRAG_THRESHOLD;
@ -749,6 +788,27 @@ impl Fsm for TextToolFsmState {
tool_data.new_text(document, editing_text, font_cache, responses); tool_data.new_text(document, editing_text, font_cache, responses);
TextToolFsmState::Editing TextToolFsmState::Editing
} }
(TextToolFsmState::Dragging, TextToolMessage::DragStop) => {
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
responses.add(response);
tool_data.resize.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
if drag_too_small {
if let Some(layer_info) = &tool_data.layer_dragging {
tool_data.start_editing_layer(layer_info.id, self, document, font_cache, responses);
return TextToolFsmState::Editing;
}
}
tool_data.layer_dragging.take();
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text, is_left_or_right_click }) => { (TextToolFsmState::Editing, TextToolMessage::TextChange { new_text, is_left_or_right_click }) => {
tool_data.new_text = new_text; tool_data.new_text = new_text;
@ -789,16 +849,21 @@ impl Fsm for TextToolFsmState {
} }
responses.add(FrontendMessage::TriggerTextCommit); responses.add(FrontendMessage::TriggerTextCommit);
TextToolFsmState::Editing TextToolFsmState::Editing
} }
(state, TextToolMessage::Abort) => { (state, TextToolMessage::Abort) => {
input.mouse.finish_transaction(tool_data.resize.viewport_drag_start(document), responses); if matches!(state, TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging) {
tool_data.resize.cleanup(responses); responses.add(DocumentMessage::AbortTransaction);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
if state == TextToolFsmState::Editing { bounds.original_transforms.clear();
tool_data.set_editing(false, font_cache, responses); }
if matches!(state, TextToolFsmState::Dragging) {
tool_data.layer_dragging.take();
}
} else {
input.mouse.finish_transaction(tool_data.resize.viewport_drag_start(document), responses);
} }
tool_data.resize.cleanup(responses);
TextToolFsmState::Ready TextToolFsmState::Ready
} }
@ -821,12 +886,13 @@ impl Fsm for TextToolFsmState {
HintInfo::keys([Key::Control, Key::Enter], "").add_mac_keys([Key::Command, Key::Enter]), HintInfo::keys([Key::Control, Key::Enter], "").add_mac_keys([Key::Command, Key::Enter]),
HintInfo::keys([Key::Escape], "Commit Changes").prepend_slash(), HintInfo::keys([Key::Escape], "Commit Changes").prepend_slash(),
])]), ])]),
TextToolFsmState::Placing | TextToolFsmState::Dragging => HintData(vec![ TextToolFsmState::Placing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
]), ]),
TextToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
TextToolFsmState::ResizingBounds => HintData(vec![ TextToolFsmState::ResizingBounds => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Resize Text Box")]), HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Lock Aspect Ratio"), HintInfo::keys([Key::Alt], "From Center")]), HintGroup(vec![HintInfo::keys([Key::Shift], "Lock Aspect Ratio"), HintInfo::keys([Key::Alt], "From Center")]),
]), ]),
}; };
@ -836,7 +902,7 @@ impl Fsm for TextToolFsmState {
fn update_cursor(&self, responses: &mut VecDeque<Message>) { fn update_cursor(&self, responses: &mut VecDeque<Message>) {
let cursor = match self { let cursor = match self {
TextToolFsmState::Dragging => MouseCursorIcon::Crosshair, TextToolFsmState::Placing => MouseCursorIcon::Crosshair,
_ => MouseCursorIcon::Text, _ => MouseCursorIcon::Text,
}; };
responses.add(FrontendMessage::UpdateMouseCursor { cursor }); responses.add(FrontendMessage::UpdateMouseCursor { cursor });

View File

@ -242,7 +242,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
overlay_context.line(quad[0], end, None); overlay_context.line(quad[0], end, None, None);
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
@ -250,7 +250,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
overlay_context.line(quad[0], end, None); overlay_context.line(quad[0], end, None, None);
let x_parameter = viewport_translate.x.clamp(-1., 1.); let x_parameter = viewport_translate.x.clamp(-1., 1.);
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
@ -259,8 +259,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
} }
} }
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
overlay_context.dashed_line(quad[1], quad[2], None, Some(2.), Some(2.), Some(0.5)); overlay_context.dashed_line(quad[1], quad[2], None, None, Some(2.), Some(2.), Some(0.5));
overlay_context.dashed_line(quad[3], quad[2], None, Some(2.), Some(2.), Some(0.5)); overlay_context.dashed_line(quad[3], quad[2], None, None, Some(2.), Some(2.), Some(0.5));
} }
} }
TransformOperation::Scaling(scale) => { TransformOperation::Scaling(scale) => {
@ -274,9 +274,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
let end_point = pivot + local_edge * scale.max(1.); let end_point = pivot + local_edge * scale.max(1.);
if scale > 0. { if scale > 0. {
overlay_context.dashed_line(pivot, boundary_point, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_line(pivot, boundary_point, None, None, Some(4.), Some(4.), Some(0.5));
} }
overlay_context.line(boundary_point, end_point, None); overlay_context.line(boundary_point, end_point, None, None);
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.); let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);

View File

@ -56,11 +56,7 @@ impl<PointId: crate::Identifier> Iterator for SubpathIter<'_, PointId> {
return None; return None;
} }
let closed = if self.is_always_closed { true } else { self.subpath.closed }; let closed = if self.is_always_closed { true } else { self.subpath.closed };
let len = self.subpath.len() - 1 let len = self.subpath.len() - 1 + if closed { 1 } else { 0 };
+ match closed {
true => 1,
false => 0,
};
if self.index >= len { if self.index >= len {
return None; return None;
} }

View File

@ -299,19 +299,13 @@ fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f6
/// The minimum function (min) picks the smaller of two numbers. /// The minimum function (min) picks the smaller of two numbers.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn min<T: core::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { fn min<T: core::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
match value < other_value { if value < other_value { value } else { other_value }
true => value,
false => other_value,
}
} }
/// The maximum function (max) picks the larger of two numbers. /// The maximum function (max) picks the larger of two numbers.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn max<T: core::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { fn max<T: core::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
match value > other_value { if value > other_value { value } else { other_value }
true => value,
false => other_value,
}
} }
/// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed. /// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.

View File

@ -7,7 +7,7 @@ use rustybuzz::{GlyphBuffer, UnicodeBuffer};
struct Builder { struct Builder {
current_subpath: Subpath<PointId>, current_subpath: Subpath<PointId>,
other_subpaths: Vec<Subpath<PointId>>, other_subpaths: Vec<Subpath<PointId>>,
pos: DVec2, text_cursor: DVec2,
offset: DVec2, offset: DVec2,
ascender: f64, ascender: f64,
scale: f64, scale: f64,
@ -16,7 +16,7 @@ struct Builder {
impl Builder { impl Builder {
fn point(&self, x: f32, y: f32) -> DVec2 { fn point(&self, x: f32, y: f32) -> DVec2 {
self.pos + self.offset + DVec2::new(x as f64, self.ascender - y as f64) * self.scale self.text_cursor + self.offset + DVec2::new(x as f64, self.ascender - y as f64) * self.scale
} }
} }
@ -99,11 +99,7 @@ impl Default for TypesettingConfig {
} }
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> { pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> {
let buzz_face = match buzz_face { let Some(buzz_face) = buzz_face else { return vec![] };
Some(face) => face,
// Show blank layer if font has not loaded
None => return vec![],
};
let space_glyph = buzz_face.glyph_index(' '); let space_glyph = buzz_face.glyph_index(' ');
let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio); let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio);
@ -111,7 +107,7 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: Types
let mut builder = Builder { let mut builder = Builder {
current_subpath: Subpath::new(Vec::new(), false), current_subpath: Subpath::new(Vec::new(), false),
other_subpaths: Vec::new(), other_subpaths: Vec::new(),
pos: DVec2::ZERO, text_cursor: DVec2::ZERO,
offset: DVec2::ZERO, offset: DVec2::ZERO,
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * typesetting.font_size / scale, ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * typesetting.font_size / scale,
scale, scale,
@ -124,19 +120,19 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: Types
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer); let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
// Don't wrap the first word // Don't wrap the first word
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, builder.pos.x, space_glyph) { if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, builder.text_cursor.x, space_glyph) {
builder.pos = DVec2::new(0., builder.pos.y + line_height); builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
} }
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) { for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
let glyph_id = GlyphId(glyph_info.glyph_id as u16); let glyph_id = GlyphId(glyph_info.glyph_id as u16);
if let Some(max_width) = typesetting.max_width { if let Some(max_width) = typesetting.max_width {
if space_glyph != Some(glyph_id) && builder.pos.x + (glyph_position.x_advance as f64 * builder.scale * typesetting.character_spacing) >= max_width { if space_glyph != Some(glyph_id) && builder.text_cursor.x + (glyph_position.x_advance as f64 * builder.scale * typesetting.character_spacing) >= max_width {
builder.pos = DVec2::new(0., builder.pos.y + line_height); builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
} }
} }
// Clip when the height is exceeded // Clip when the height is exceeded
if typesetting.max_height.is_some_and(|max_height| builder.pos.y > max_height - line_height) { if typesetting.max_height.is_some_and(|max_height| builder.text_cursor.y > max_height - line_height) {
return builder.other_subpaths; return builder.other_subpaths;
} }
@ -146,30 +142,31 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: Types
builder.other_subpaths.push(core::mem::replace(&mut builder.current_subpath, Subpath::new(Vec::new(), false))); builder.other_subpaths.push(core::mem::replace(&mut builder.current_subpath, Subpath::new(Vec::new(), false)));
} }
builder.pos += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * builder.scale; builder.text_cursor += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * builder.scale;
} }
buffer = glyph_buffer.clear(); buffer = glyph_buffer.clear();
} }
builder.pos = DVec2::new(0., builder.pos.y + line_height); builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
} }
builder.other_subpaths builder.other_subpaths
} }
pub fn bounding_box(str: &str, buzz_face: Option<&rustybuzz::Face>, typesetting: TypesettingConfig) -> DVec2 { pub fn bounding_box(str: &str, buzz_face: Option<&rustybuzz::Face>, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
let buzz_face = match buzz_face { // Show blank layer if font has not loaded
Some(face) => face, let Some(buzz_face) = buzz_face else { return DVec2::ZERO };
// Show blank layer if font has not loaded
None => return DVec2::ZERO,
};
let space_glyph = buzz_face.glyph_index(' '); let space_glyph = buzz_face.glyph_index(' ');
let (scale, line_height, mut buffer) = font_properties(buzz_face, typesetting.font_size, typesetting.line_height_ratio); let (scale, line_height, mut buffer) = font_properties(buzz_face, typesetting.font_size, typesetting.line_height_ratio);
let mut pos = DVec2::ZERO; let [mut text_cursor, mut bounds] = [DVec2::ZERO; 2];
let mut bounds = DVec2::ZERO; if !for_clipping_test {
if let (Some(max_height), Some(max_width)) = (typesetting.max_height, typesetting.max_width) {
return DVec2::new(max_width, max_height);
}
}
for line in str.split('\n') { for line in str.split('\n') {
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() { for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
@ -178,32 +175,34 @@ pub fn bounding_box(str: &str, buzz_face: Option<&rustybuzz::Face>, typesetting:
let glyph_buffer = rustybuzz::shape(buzz_face, &[], buffer); let glyph_buffer = rustybuzz::shape(buzz_face, &[], buffer);
// Don't wrap the first word // Don't wrap the first word
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, pos.x, space_glyph) { if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, text_cursor.x, space_glyph) {
pos = DVec2::new(0., pos.y + line_height); text_cursor = DVec2::new(0., text_cursor.y + line_height);
} }
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) { for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
let glyph_id = GlyphId(glyph_info.glyph_id as u16); let glyph_id = GlyphId(glyph_info.glyph_id as u16);
if let Some(max_width) = typesetting.max_width { if let Some(max_width) = typesetting.max_width {
if space_glyph != Some(glyph_id) && pos.x + (glyph_position.x_advance as f64 * scale * typesetting.character_spacing) >= max_width { if space_glyph != Some(glyph_id) && text_cursor.x + (glyph_position.x_advance as f64 * scale * typesetting.character_spacing) >= max_width {
pos = DVec2::new(0., pos.y + line_height); text_cursor = DVec2::new(0., text_cursor.y + line_height);
} }
} }
pos += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * scale; text_cursor += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * scale;
bounds = bounds.max(pos + DVec2::new(0., line_height)); bounds = bounds.max(text_cursor + DVec2::new(0., line_height));
} }
buffer = glyph_buffer.clear(); buffer = glyph_buffer.clear();
} }
pos = DVec2::new(0., pos.y + line_height); text_cursor = DVec2::new(0., text_cursor.y + line_height);
bounds = bounds.max(pos); bounds = bounds.max(text_cursor);
} }
if let Some(max_width) = typesetting.max_width { if !for_clipping_test {
bounds.x = max_width; if let Some(max_width) = typesetting.max_width {
} bounds.x = max_width;
if let Some(max_height) = typesetting.max_height { }
bounds.y = max_height; if let Some(max_height) = typesetting.max_height {
bounds.y = max_height;
}
} }
bounds bounds
@ -214,56 +213,9 @@ pub fn load_face(data: &[u8]) -> rustybuzz::Face {
} }
pub fn lines_clipping(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> bool { pub fn lines_clipping(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> bool {
let buzz_face = match buzz_face { let Some(max_height) = typesetting.max_height else { return false };
Some(face) => face, let bounds = bounding_box(str, buzz_face.as_ref(), typesetting, true);
// False if font hasn't loaded max_height < bounds.y
None => return false,
};
if typesetting.max_height.is_none() {
return false;
}
let space_glyph = buzz_face.glyph_index(' ');
let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio);
let mut pos = DVec2::ZERO;
let mut bounds = DVec2::ZERO;
for line in str.split('\n') {
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
push_str(&mut buffer, word);
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
// Don't wrap the first word
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, pos.x, space_glyph) {
pos = DVec2::new(0., pos.y + line_height);
}
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
let glyph_id = GlyphId(glyph_info.glyph_id as u16);
if let Some(max_width) = typesetting.max_width {
if space_glyph != Some(glyph_id) && pos.x + (glyph_position.x_advance as f64 * scale * typesetting.character_spacing) >= max_width {
pos = DVec2::new(0., pos.y + line_height);
}
}
pos += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * scale;
bounds = bounds.max(pos + DVec2::new(0., line_height));
}
buffer = glyph_buffer.clear();
}
pos = DVec2::new(0., pos.y + line_height);
bounds = bounds.max(pos);
}
if typesetting.max_height.unwrap() < bounds.y {
return true;
}
false
} }
struct SplitWordsIncludingSpaces<'a> { struct SplitWordsIncludingSpaces<'a> {