mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 18:28:11 +08:00
Add the settings popover menu for the Overlays toggle (#2523)
* Added granular overlays control based on features * Added basic support for pivot, path, anchors and handles overlay settings * Added more overlay checks on anchors and handles * Add new settings over measurements, hover and selection overlays * Fix errors introduced while rebasing * Disable anchors and handles functionality with their overlays, extended selection outline check * Add check to enable/disable outlines on selected layers * Toggle handles checkbox in sync with anchors checkbox * Refactor overlays checks * Remove debug statements * Update select_tool.rs to resolve conflict * Minor fix to reflect anchor checkbox state on the handles * Minor fix to make anchors checkbox work * Rearrange menu items, and code review * Fix pivot dragging * Add handles overlay check when drawing with pen tool * Fix constrained dragging when transform cage is disabled * Fix deselecting user selection when anchors are disabled * Minor fix for disabling anchors * Remove All from OverlaysType * Remove debug statements * Fix editor crash when selecting other layers with path tool and anchors disabled * Minor fix on overlays check for all overlays * Add proper code formatting * Nits --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
1f7a9188ba
commit
1a81e45673
@@ -225,16 +225,17 @@ impl Fsm for ArtboardToolFsmState {
|
||||
let ToolMessage::Artboard(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(state, ArtboardToolMessage::Overlays(mut overlay_context)) => {
|
||||
if state != ArtboardToolFsmState::Drawing {
|
||||
let display_transform_cage = overlay_context.visibility_settings.transform_cage();
|
||||
if display_transform_cage && state != ArtboardToolFsmState::Drawing {
|
||||
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {
|
||||
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||
bounding_box_manager.bounds = bounds;
|
||||
bounding_box_manager.transform = document.metadata().document_to_viewport;
|
||||
|
||||
bounding_box_manager.render_overlays(&mut overlay_context, true);
|
||||
} else {
|
||||
tool_data.bounding_box_manager.take();
|
||||
}
|
||||
} else {
|
||||
tool_data.bounding_box_manager.take();
|
||||
}
|
||||
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
|
||||
@@ -960,6 +960,19 @@ impl Fsm for PathToolFsmState {
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
let display_handles = overlay_context.visibility_settings.handles();
|
||||
if !display_handles {
|
||||
shape_editor.ignore_selected_handles();
|
||||
} else {
|
||||
shape_editor.mark_selected_handles();
|
||||
}
|
||||
if !display_anchors {
|
||||
shape_editor.ignore_selected_anchors();
|
||||
} else {
|
||||
shape_editor.mark_selected_anchors();
|
||||
}
|
||||
|
||||
// TODO: find the segment ids of which the selected points are a part of
|
||||
|
||||
match tool_options.path_overlay_mode {
|
||||
|
||||
@@ -1497,6 +1497,9 @@ impl Fsm for PenToolFsmState {
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
let display_handles = overlay_context.visibility_settings.handles();
|
||||
|
||||
let valid = |point: DVec2, handle: DVec2| point.distance_squared(handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
|
||||
let transform = document.metadata().document_to_viewport * transform;
|
||||
@@ -1523,9 +1526,10 @@ 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, None);
|
||||
|
||||
if display_handles {
|
||||
// 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, None);
|
||||
}
|
||||
match tool_options.pen_overlay_mode {
|
||||
PenOverlayMode::AllHandles => {
|
||||
path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
|
||||
@@ -1540,11 +1544,13 @@ 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, None);
|
||||
if display_handles {
|
||||
// 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, 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, 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, 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)
|
||||
@@ -1556,13 +1562,16 @@ impl Fsm for PenToolFsmState {
|
||||
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) && display_handles {
|
||||
// Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out)
|
||||
let selected = tool_data.handle_type == TargetHandle::PreviewInHandle;
|
||||
overlay_context.manipulator_handle(handle_end, selected, None);
|
||||
if display_handles {
|
||||
overlay_context.manipulator_handle(handle_end, selected, None);
|
||||
overlay_context.manipulator_handle(handle_end, selected, None);
|
||||
}
|
||||
}
|
||||
|
||||
if valid(anchor_start, handle_start) {
|
||||
if valid(anchor_start, handle_start) && display_handles {
|
||||
// Draw the handle circle for the most recently placed anchor's outgoing handle (which is currently influencing the currently-being-placed segment)
|
||||
overlay_context.manipulator_handle(handle_start, false, None);
|
||||
}
|
||||
@@ -1578,13 +1587,13 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) {
|
||||
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) && display_handles {
|
||||
// Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor)
|
||||
let selected = tool_data.handle_type == TargetHandle::FuturePreviewOutHandle;
|
||||
overlay_context.manipulator_handle(next_handle_start, selected, None);
|
||||
}
|
||||
|
||||
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) {
|
||||
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && display_anchors {
|
||||
// Draw the anchor square for the most recently placed anchor
|
||||
overlay_context.manipulator_anchor(next_anchor, false, None);
|
||||
}
|
||||
|
||||
@@ -516,17 +516,19 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.selected_layers_count = selected_layers_count;
|
||||
|
||||
// Outline selected layers, but not artboards
|
||||
for layer in document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
{
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
if overlay_context.visibility_settings.selection_outline() {
|
||||
for layer in document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
{
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
|
||||
if is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") {
|
||||
let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache);
|
||||
overlay_context.dashed_quad(transformed_quad, None, Some(7.), Some(5.), None);
|
||||
if is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") {
|
||||
let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache);
|
||||
overlay_context.dashed_quad(transformed_quad, None, Some(7.), Some(5.), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,11 +568,13 @@ impl Fsm for SelectToolFsmState {
|
||||
let click = document.click(input);
|
||||
let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata()));
|
||||
if let Some(layer) = not_selected_click {
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
if overlay_context.visibility_settings.hover_outline() {
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
}
|
||||
|
||||
// Measure with Alt held down
|
||||
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
|
||||
if !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {
|
||||
if overlay_context.visibility_settings.quick_measurement() && !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {
|
||||
// Get all selected layers and compute their viewport-aligned AABB
|
||||
let selected_bounds_viewport = document
|
||||
.network_interface
|
||||
@@ -602,13 +606,15 @@ impl Fsm for SelectToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bounds) = bounds {
|
||||
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||
if overlay_context.visibility_settings.transform_cage() {
|
||||
if let Some(bounds) = bounds {
|
||||
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||
|
||||
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, true);
|
||||
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, true);
|
||||
}
|
||||
} else {
|
||||
tool_data.bounding_box_manager.take();
|
||||
}
|
||||
@@ -673,71 +679,74 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,)));
|
||||
|
||||
// Update compass rose
|
||||
tool_data.compass_rose.refresh_position(document);
|
||||
let compass_center = tool_data.compass_rose.compass_rose_position();
|
||||
if !matches!(self, Self::Dragging { .. }) {
|
||||
tool_data.line_center = compass_center;
|
||||
}
|
||||
overlay_context.compass_rose(compass_center, angle, show_compass_with_ring);
|
||||
if overlay_context.visibility_settings.compass_rose() {
|
||||
tool_data.compass_rose.refresh_position(document);
|
||||
let compass_center = tool_data.compass_rose.compass_rose_position();
|
||||
if !matches!(self, Self::Dragging { .. }) {
|
||||
tool_data.line_center = compass_center;
|
||||
}
|
||||
|
||||
let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self {
|
||||
Some((axis, false))
|
||||
} else {
|
||||
compass_rose_state.axis_type().and_then(|axis| axis.is_constraint().then_some((axis, true)))
|
||||
};
|
||||
overlay_context.compass_rose(compass_center, angle, show_compass_with_ring);
|
||||
|
||||
if show_compass_with_ring.is_some() {
|
||||
if let Some((axis, hover)) = axis_state {
|
||||
if axis.is_constraint() {
|
||||
let e0 = tool_data
|
||||
.bounding_box_manager
|
||||
.as_ref()
|
||||
.map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds))
|
||||
.map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X));
|
||||
let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self {
|
||||
Some((axis, false))
|
||||
} else {
|
||||
compass_rose_state.axis_type().and_then(|axis| axis.is_constraint().then_some((axis, true)))
|
||||
};
|
||||
|
||||
let (direction, color) = match axis {
|
||||
Axis::X => (e0, COLOR_OVERLAY_RED),
|
||||
Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if show_compass_with_ring.is_some() {
|
||||
if let Some((axis, hover)) = axis_state {
|
||||
if axis.is_constraint() {
|
||||
let e0 = tool_data
|
||||
.bounding_box_manager
|
||||
.as_ref()
|
||||
.map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds))
|
||||
.map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X));
|
||||
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
let (direction, color) = match axis {
|
||||
Axis::X => (e0, COLOR_OVERLAY_RED),
|
||||
Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let color = if !hover {
|
||||
color
|
||||
} else {
|
||||
let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb();
|
||||
&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), None);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
let color = if !hover {
|
||||
color
|
||||
} else {
|
||||
let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb();
|
||||
&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), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align {
|
||||
let mouse_position = mouse_position - tool_data.drag_start;
|
||||
let snap_resolution = SELECTION_DRAG_ANGLE.to_radians();
|
||||
let angle = -mouse_position.angle_to(DVec2::X);
|
||||
let snapped_angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align {
|
||||
let mouse_position = mouse_position - tool_data.drag_start;
|
||||
let snap_resolution = SELECTION_DRAG_ANGLE.to_radians();
|
||||
let angle = -mouse_position.angle_to(DVec2::X);
|
||||
let snapped_angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
|
||||
let extension = tool_data.drag_current - tool_data.drag_start;
|
||||
let origin = compass_center - extension;
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
let extension = tool_data.drag_current - tool_data.drag_start;
|
||||
let origin = compass_center - extension;
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X) * viewport_diagonal;
|
||||
let perp = edge.perp();
|
||||
let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X) * viewport_diagonal;
|
||||
let perp = edge.perp();
|
||||
|
||||
let (edge_color, perp_color) = if edge.x.abs() > edge.y.abs() {
|
||||
(COLOR_OVERLAY_RED, COLOR_OVERLAY_GREEN)
|
||||
} else {
|
||||
(COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED)
|
||||
};
|
||||
let mut perp_color = graphene_std::Color::from_rgb_str(perp_color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb();
|
||||
perp_color.insert(0, '#');
|
||||
let perp_color = perp_color.as_str();
|
||||
overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(edge_color), None);
|
||||
overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(perp_color), None);
|
||||
let (edge_color, perp_color) = if edge.x.abs() > edge.y.abs() {
|
||||
(COLOR_OVERLAY_RED, COLOR_OVERLAY_GREEN)
|
||||
} else {
|
||||
(COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED)
|
||||
};
|
||||
let mut perp_color = graphene_std::Color::from_rgb_str(perp_color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb();
|
||||
perp_color.insert(0, '#');
|
||||
let perp_color = perp_color.as_str();
|
||||
overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(edge_color), None);
|
||||
overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(perp_color), None);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the tool is in selection mode
|
||||
@@ -768,8 +777,11 @@ impl Fsm for SelectToolFsmState {
|
||||
SelectionMode::Directional => unreachable!(),
|
||||
});
|
||||
|
||||
for layer in layers_to_outline {
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
if overlay_context.visibility_settings.selection_outline() {
|
||||
// Draws a temporary outline on the layers that will be selected by the current box/lasso area
|
||||
for layer in layers_to_outline {
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the selection box
|
||||
@@ -854,7 +866,7 @@ impl Fsm for SelectToolFsmState {
|
||||
let is_over_pivot = tool_data.pivot.is_over(mouse_position);
|
||||
|
||||
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
|
||||
let can_grab_compass_rose = compass_rose_state.can_grab() && show_compass;
|
||||
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
|
||||
let is_flat_layer = tool_data
|
||||
.bounding_box_manager
|
||||
.as_ref()
|
||||
|
||||
@@ -506,23 +506,25 @@ impl Fsm for TextToolFsmState {
|
||||
return self;
|
||||
}
|
||||
|
||||
if let Some(bounds) = bounds {
|
||||
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||
bounding_box_manager.bounds = [bounds.0[0], bounds.0[2]];
|
||||
bounding_box_manager.transform = layer_transform;
|
||||
if overlay_context.visibility_settings.transform_cage() {
|
||||
if let Some(bounds) = bounds {
|
||||
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||
bounding_box_manager.bounds = [bounds.0[0], bounds.0[2]];
|
||||
bounding_box_manager.transform = layer_transform;
|
||||
|
||||
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), Some(3.));
|
||||
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), Some(3.));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user