mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 02:38:12 +08:00
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:
co-authored by
Keavon Chambers
parent
43275b7a1e
commit
b98711dbdb
@@ -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 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.
|
||||
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 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
|
||||
let length = format!("{:.2}", transform_to_document.transform_vector2(line_end - line_start).length())
|
||||
|
||||
@@ -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);
|
||||
if let Some(pivot) = self.pivot {
|
||||
overlay_context.pivot(pivot, angle);
|
||||
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
|
||||
overlay_context.pivot(pivot, data.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -593,10 +593,7 @@ impl ShapeState {
|
||||
if points_colinear_status.any(|point| first_is_colinear != point) {
|
||||
return ManipulatorAngle::Mixed;
|
||||
}
|
||||
match first_is_colinear {
|
||||
false => ManipulatorAngle::Free,
|
||||
true => ManipulatorAngle::Colinear,
|
||||
}
|
||||
if first_is_colinear { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }
|
||||
}
|
||||
|
||||
pub fn convert_manipulator_handles_to_colinear(&self, vector_data: &VectorData, point_id: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
|
||||
|
||||
@@ -414,12 +414,13 @@ impl SnapManager {
|
||||
let start = DVec2::new(first.max().x, y);
|
||||
let end = DVec2::new(second.min().x, y);
|
||||
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(end), transform.transform_point2(end + 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, None);
|
||||
overlay_context.line(
|
||||
transform.transform_point2(start + DVec2::Y * signed_size / 2.),
|
||||
transform.transform_point2(end + DVec2::Y * signed_size / 2.),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -432,12 +433,13 @@ impl SnapManager {
|
||||
let start = DVec2::new(x, first.max().y);
|
||||
let end = DVec2::new(x, second.min().y);
|
||||
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(end), transform.transform_point2(end + 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, None);
|
||||
overlay_context.line(
|
||||
transform.transform_point2(start + DVec2::X * signed_size / 2.),
|
||||
transform.transform_point2(end + DVec2::X * signed_size / 2.),
|
||||
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 any_align = align.iter().flatten().next().is_some();
|
||||
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() {
|
||||
overlay_context.manipulator_handle(target, false, None);
|
||||
|
||||
@@ -374,7 +374,7 @@ pub struct BoundingBoxManager {
|
||||
pub bounds: [DVec2; 2],
|
||||
/// The transform to viewport space for the bounds co-ordinates when the bounds were last updated.
|
||||
pub transform: DAffine2,
|
||||
/// Was the transform previously singular?
|
||||
/// Whether the transform is actually singular but adjusted to not be so.
|
||||
pub transform_tampered: bool,
|
||||
/// The transform to viewport space for the bounds co-ordinates when the transformation was started.
|
||||
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
|
||||
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 category = self.overlay_display_category();
|
||||
|
||||
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())];
|
||||
|
||||
// Draw the bounding box rectangle
|
||||
overlay_context.quad(quad, None);
|
||||
if render_quad {
|
||||
self.render_quad(overlay_context);
|
||||
}
|
||||
|
||||
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.)]);
|
||||
|
||||
@@ -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 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])
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||
bounding_box_manager.bounds = bounds;
|
||||
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 {
|
||||
tool_data.bounding_box_manager.take();
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ impl Fsm for GradientToolFsmState {
|
||||
let Gradient { start, end, stops, .. } = gradient;
|
||||
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(end, dragging == Some(GradientDragTarget::End), None);
|
||||
|
||||
|
||||
@@ -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));
|
||||
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_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());
|
||||
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() {
|
||||
let transform = document.metadata().transform_to_viewport(*layer);
|
||||
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 = node_type.node_template_input_override([
|
||||
None,
|
||||
@@ -320,9 +320,7 @@ impl Fsm for LineToolFsmState {
|
||||
(LineToolFsmState::Drawing, LineToolMessage::Abort) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.editing_layer.take();
|
||||
if tool_data.dragging_endpoint.is_none() {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
(_, LineToolMessage::WorkingColorChanged) => {
|
||||
@@ -377,12 +375,12 @@ fn generate_line(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle:
|
||||
|
||||
tool_data.angle = angle;
|
||||
|
||||
let angle_vec = DVec2::from_angle(angle);
|
||||
if lock_angle {
|
||||
let angle_vec = DVec2::new(angle.cos(), angle.sin());
|
||||
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 snap = &mut tool_data.snap_manager;
|
||||
|
||||
@@ -970,12 +970,12 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
match axis {
|
||||
Axis::Y => {
|
||||
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_BLUE));
|
||||
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(other));
|
||||
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), None);
|
||||
}
|
||||
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::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(other));
|
||||
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), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -986,8 +986,8 @@ impl Fsm for PathToolFsmState {
|
||||
if let Some(closest_segment) = &tool_data.segment {
|
||||
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()) {
|
||||
overlay_context.line(closest_segment.closest_point_to_viewport(), handle1, Some(COLOR_OVERLAY_BLUE));
|
||||
overlay_context.line(closest_segment.closest_point_to_viewport(), handle2, 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), None);
|
||||
overlay_context.manipulator_handle(handle1, false, Some(COLOR_OVERLAY_BLUE));
|
||||
overlay_context.manipulator_handle(handle2, false, Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
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 {
|
||||
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) {
|
||||
// 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)
|
||||
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 {
|
||||
// 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)
|
||||
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) {
|
||||
|
||||
@@ -594,7 +594,7 @@ impl Fsm for SelectToolFsmState {
|
||||
bounding_box_manager.bounds = bounds;
|
||||
bounding_box_manager.transform = transform;
|
||||
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 {
|
||||
tool_data.bounding_box_manager.take();
|
||||
}
|
||||
@@ -656,7 +656,7 @@ impl Fsm for SelectToolFsmState {
|
||||
});
|
||||
|
||||
// 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
|
||||
tool_data.compass_rose.refresh_position(document);
|
||||
@@ -696,7 +696,7 @@ impl Fsm for SelectToolFsmState {
|
||||
&format!("#{}", color_string)
|
||||
};
|
||||
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 perp = edge.perp();
|
||||
|
||||
overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(COLOR_OVERLAY_BLUE));
|
||||
overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(other));
|
||||
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), None);
|
||||
}
|
||||
|
||||
// Check if the tool is in selection mode
|
||||
@@ -1269,18 +1269,6 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
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 }) => {
|
||||
// Deselect layer if not snap dragging
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
@@ -1337,48 +1325,25 @@ impl Fsm for SelectToolFsmState {
|
||||
let selection = tool_data.nested_selection_behavior;
|
||||
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 {
|
||||
true => DocumentMessage::AbortTransaction,
|
||||
false => DocumentMessage::EndTransaction,
|
||||
};
|
||||
(
|
||||
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::Dragging { .. },
|
||||
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
|
||||
) => {
|
||||
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.axis_align = false;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
if !matches!(self, SelectToolFsmState::DraggingPivot) {
|
||||
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::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 }) => {
|
||||
let quad = tool_data.selection_quad();
|
||||
|
||||
|
||||
@@ -203,6 +203,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
|
||||
match self.fsm_state {
|
||||
TextToolFsmState::Ready => actions!(TextToolMessageDiscriminant;
|
||||
DragStart,
|
||||
PointerOutsideViewport,
|
||||
PointerMove,
|
||||
),
|
||||
TextToolFsmState::Editing => actions!(TextToolMessageDiscriminant;
|
||||
@@ -213,11 +214,13 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
|
||||
DragStop,
|
||||
Abort,
|
||||
PointerMove,
|
||||
PointerOutsideViewport,
|
||||
),
|
||||
TextToolFsmState::ResizingBounds => actions!(TextToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
PointerMove,
|
||||
PointerOutsideViewport,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -242,9 +245,9 @@ enum TextToolFsmState {
|
||||
Ready,
|
||||
/// The user is typing in the interactive viewport text area.
|
||||
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.
|
||||
Placing,
|
||||
/// The user is dragging an existing text layer to move it.
|
||||
Dragging,
|
||||
/// The user is dragging to resize the text area.
|
||||
ResizingBounds,
|
||||
@@ -271,6 +274,8 @@ struct TextToolData {
|
||||
layer: LayerNodeIdentifier,
|
||||
editing_text: Option<EditingText>,
|
||||
new_text: String,
|
||||
drag_start: DVec2,
|
||||
drag_current: DVec2,
|
||||
resize: Resize,
|
||||
auto_panning: AutoPanning,
|
||||
// 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() {
|
||||
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. {
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||
@@ -475,7 +480,7 @@ impl Fsm for TextToolFsmState {
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(_, TextToolMessage::Overlays(mut overlay_context)) => {
|
||||
if matches!(self, Self::Placing | Self::Dragging) {
|
||||
if matches!(self, Self::Placing) {
|
||||
// Get the updated selection box 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.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
|
||||
let transformed_quad = layer_transform * bounds;
|
||||
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));
|
||||
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.
|
||||
let angle = bounding_box_manager.transform.transform_vector2(DVec2::X).to_angle();
|
||||
// Update pivot
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, angle);
|
||||
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
|
||||
} else {
|
||||
tool_data.bounding_box_manager.take();
|
||||
}
|
||||
@@ -540,6 +542,8 @@ impl Fsm for TextToolFsmState {
|
||||
(TextToolFsmState::Ready, TextToolMessage::DragStart) => {
|
||||
tool_data.resize.start(document, input);
|
||||
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 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 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);
|
||||
|
||||
// Set the original transform
|
||||
@@ -568,12 +572,25 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_bound_transform = bounds.transform;
|
||||
|
||||
bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);
|
||||
}
|
||||
tool_data.get_snap_candidates(document, font_cache);
|
||||
|
||||
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
|
||||
}
|
||||
@@ -596,7 +613,7 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
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);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
@@ -608,18 +625,42 @@ impl Fsm for TextToolFsmState {
|
||||
];
|
||||
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::ResizingBounds, TextToolMessage::PointerMove { center, lock_ratio }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
if let Some(movement) = &mut bounds.selected_edges {
|
||||
let (center_bool, lock_ratio_bool) = (input.keyboard.key(center), input.keyboard.key(lock_ratio));
|
||||
let center_position = center_bool.then_some(bounds.center_of_transformation);
|
||||
let (centered, constrain) = (input.keyboard.key(center), input.keyboard.key(lock_ratio));
|
||||
let center_position = centered.then_some(bounds.center_of_transformation);
|
||||
|
||||
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 {
|
||||
warn!("Cannot get text node id");
|
||||
tool_data.layer_dragging = None;
|
||||
tool_data.layer_dragging.take();
|
||||
return TextToolFsmState::Ready;
|
||||
};
|
||||
|
||||
@@ -630,7 +671,7 @@ impl Fsm for TextToolFsmState {
|
||||
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
|
||||
let (position, size) = (position.min(position + size), size.abs());
|
||||
|
||||
@@ -677,13 +718,13 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
self
|
||||
}
|
||||
(TextToolFsmState::Placing | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => {
|
||||
(TextToolFsmState::Placing, TextToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning setup
|
||||
let _ = tool_data.auto_panning.shift_viewport(input, responses);
|
||||
|
||||
TextToolFsmState::Dragging
|
||||
TextToolFsmState::Placing
|
||||
}
|
||||
(TextToolFsmState::ResizingBounds, TextToolMessage::PointerOutsideViewport { .. }) => {
|
||||
(TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// AutoPanning
|
||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
@@ -705,10 +746,8 @@ impl Fsm for TextToolFsmState {
|
||||
state
|
||||
}
|
||||
(TextToolFsmState::ResizingBounds, TextToolMessage::DragStop) => {
|
||||
let response = match input.mouse.position.distance(tool_data.resize.viewport_drag_start(document)) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::AbortTransaction,
|
||||
false => DocumentMessage::EndTransaction,
|
||||
};
|
||||
let drag_too_small = input.mouse.position.distance(tool_data.resize.viewport_drag_start(document)) < 10. * f64::EPSILON;
|
||||
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
|
||||
responses.add(response);
|
||||
|
||||
tool_data.resize.snap_manager.cleanup(responses);
|
||||
@@ -719,7 +758,7 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(TextToolFsmState::Placing | TextToolFsmState::Dragging, TextToolMessage::DragStop) => {
|
||||
(TextToolFsmState::Placing, TextToolMessage::DragStop) => {
|
||||
let [start, end] = tool_data.cached_resize_bounds;
|
||||
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);
|
||||
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 }) => {
|
||||
tool_data.new_text = new_text;
|
||||
|
||||
@@ -789,16 +849,21 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(state, TextToolMessage::Abort) => {
|
||||
input.mouse.finish_transaction(tool_data.resize.viewport_drag_start(document), responses);
|
||||
tool_data.resize.cleanup(responses);
|
||||
|
||||
if state == TextToolFsmState::Editing {
|
||||
tool_data.set_editing(false, font_cache, responses);
|
||||
if matches!(state, TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging) {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -821,12 +886,13 @@ impl Fsm for TextToolFsmState {
|
||||
HintInfo::keys([Key::Control, Key::Enter], "").add_mac_keys([Key::Command, Key::Enter]),
|
||||
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::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![
|
||||
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")]),
|
||||
]),
|
||||
};
|
||||
@@ -836,7 +902,7 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||
let cursor = match self {
|
||||
TextToolFsmState::Dragging => MouseCursorIcon::Crosshair,
|
||||
TextToolFsmState::Placing => MouseCursorIcon::Crosshair,
|
||||
_ => MouseCursorIcon::Text,
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
|
||||
@@ -242,7 +242,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
|
||||
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] };
|
||||
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.);
|
||||
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. {
|
||||
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 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 };
|
||||
@@ -259,8 +259,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
}
|
||||
}
|
||||
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[3], 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, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
@@ -274,9 +274,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
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.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
|
||||
Reference in New Issue
Block a user