From 67ba5bcecf8c3c51f70a4091e23fedfa8640c6ff Mon Sep 17 00:00:00 2001 From: Shyam Jayakannan <120047549+shyamjayakannan@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:54:15 +0530 Subject: [PATCH] Added fine-grained choices to Snapping options popover (#1730) * Added Customised Snapping * Fix snapping choice to Anchors; clean up popover menu code --------- Co-authored-by: Keavon Chambers --- .../messages/layout/layout_message_handler.rs | 2 +- .../layout/utility_types/layout_widget.rs | 4 +- .../utility_types/widgets/button_widgets.rs | 20 +- .../portfolio/document/document_message.rs | 6 +- .../document/document_message_handler.rs | 268 +++++++++++++----- .../document/overlays/grid_overlays.rs | 3 + .../portfolio/document/utility_types/misc.rs | 81 ++++-- .../tool/common_functionality/shape_editor.rs | 4 +- .../snapping/layer_snapper.rs | 16 +- .../tool/tool_messages/select_tool.rs | 14 +- .../src/components/widgets/WidgetSpan.svelte | 9 +- .../widgets/buttons/PopoverButton.svelte | 10 + frontend/src/wasm-communication/messages.ts | 16 +- 13 files changed, 318 insertions(+), 135 deletions(-) diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 47f89ede..d15f578c 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -33,7 +33,7 @@ impl LayoutMessageHandler { } if let Widget::PopoverButton(popover) = &widget.widget { - stack.extend(popover.options_widget.iter().enumerate().map(|(child, val)| ([widget_path.as_slice(), &[index, child]].concat(), val))); + stack.extend(popover.popover_layout.iter().enumerate().map(|(child, val)| ([widget_path.as_slice(), &[index, child]].concat(), val))); } } } diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index c95d7486..6af30410 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -219,7 +219,7 @@ impl<'a> Iterator for WidgetIter<'a> { self.current_slice = Some(&self.current_slice.unwrap()[1..]); if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = item { - self.stack.extend(p.options_widget.iter()); + self.stack.extend(p.popover_layout.iter()); return self.next(); } @@ -260,7 +260,7 @@ impl<'a> Iterator for WidgetIterMut<'a> { self.current_slice = Some(rest); if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = first { - self.stack.extend(p.options_widget.iter_mut()); + self.stack.extend(p.popover_layout.iter_mut()); return self.next(); } diff --git a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs index 2918b59f..020fefe6 100644 --- a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs @@ -47,24 +47,16 @@ pub struct PopoverButton { pub disabled: bool, - // Placeholder popover content heading - #[widget_builder(constructor)] - pub header: String, - - // Placeholder popover content paragraph - #[widget_builder(constructor)] - pub text: String, - pub tooltip: String, - #[serde(rename = "optionsWidget")] - pub options_widget: SubLayout, - - #[serde(rename = "popoverMinWidth")] - pub popover_min_width: Option, - #[serde(skip)] pub tooltip_shortcut: Option, + + #[serde(rename = "popoverLayout")] + pub popover_layout: SubLayout, + + #[serde(rename = "popoverMinWidth")] + pub popover_min_width: Option, } #[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 12de6475..26e56c20 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -12,6 +12,8 @@ use graphene_core::Color; use glam::DAffine2; +use super::utility_types::misc::{OptionBoundsSnapping, OptionPointSnapping}; + #[impl_message(Message, PortfolioMessage, Document)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum DocumentMessage { @@ -126,8 +128,8 @@ pub enum DocumentMessage { }, SetSnapping { snapping_enabled: Option, - bounding_box_snapping: Option, - geometry_snapping: Option, + bounding_box_snapping: Option, + geometry_snapping: Option, }, SetViewMode { view_mode: ViewMode, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 16cda6c7..cabadff7 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1,5 +1,5 @@ use super::utility_types::error::EditorError; -use super::utility_types::misc::{SnappingOptions, SnappingState}; +use super::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, OptionBoundsSnapping, OptionPointSnapping, SnappingOptions, SnappingState}; use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH}; use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING}; @@ -718,12 +718,56 @@ impl MessageHandler> for DocumentMessag if let Some(state) = snapping_enabled { self.snapping_state.snapping_enabled = state }; - if let Some(state) = bounding_box_snapping { - self.snapping_state.bounding_box_snapping = state + + if let Some(OptionBoundsSnapping { + edge_midpoints, + edges, + centers, + corners, + }) = bounding_box_snapping + { + if let Some(state) = edge_midpoints { + self.snapping_state.bounds.edge_midpoints = state + }; + if let Some(state) = edges { + self.snapping_state.bounds.edges = state + }; + if let Some(state) = centers { + self.snapping_state.bounds.centers = state + }; + if let Some(state) = corners { + self.snapping_state.bounds.corners = state + }; + } + + if let Some(OptionPointSnapping { + paths, + path_intersections, + anchors, + line_midpoints, + normals, + tangents, + }) = geometry_snapping + { + if let Some(state) = path_intersections { + self.snapping_state.nodes.path_intersections = state + }; + if let Some(state) = paths { + self.snapping_state.nodes.paths = state + }; + if let Some(state) = anchors { + self.snapping_state.nodes.anchors = state + }; + if let Some(state) = line_midpoints { + self.snapping_state.nodes.line_midpoints = state + }; + if let Some(state) = normals { + self.snapping_state.nodes.normals = state + }; + if let Some(state) = tangents { + self.snapping_state.nodes.tangents = state + }; } - if let Some(state) = geometry_snapping { - self.snapping_state.geometry_snapping = state - }; } DocumentMessage::SetViewMode { view_mode } => { self.view_mode = view_mode; @@ -1149,7 +1193,16 @@ impl DocumentMessageHandler { .tooltip("Overlays") .on_update(|optional_input: &CheckboxInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()) .widget_holder(), - PopoverButton::new("Overlays", "Coming soon").widget_holder(), + PopoverButton::new() + .popover_layout(vec![ + LayoutGroup::Row { + widgets: vec![TextLabel::new("Overlays").bold(true).widget_holder()], + }, + LayoutGroup::Row { + widgets: vec![TextLabel::new("Coming soon").widget_holder()], + }, + ]) + .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), CheckboxInput::new(snapping_state.snapping_enabled) .icon("Snapping") @@ -1158,47 +1211,112 @@ impl DocumentMessageHandler { let snapping_enabled = optional_input.checked; DocumentMessage::SetSnapping { snapping_enabled: Some(snapping_enabled), - bounding_box_snapping: Some(snapping_state.bounding_box_snapping), - geometry_snapping: Some(snapping_state.geometry_snapping), + bounding_box_snapping: None, + geometry_snapping: None, } .into() }) .widget_holder(), - PopoverButton::new("Snapping", "Snap customization settings") - .options_widget(vec![ - LayoutGroup::Row { - widgets: vec![ - CheckboxInput::new(snapping_state.bounding_box_snapping) - .tooltip(SnappingOptions::BoundingBoxes.to_string()) - .on_update(move |input: &CheckboxInput| { - DocumentMessage::SetSnapping { - snapping_enabled: None, - bounding_box_snapping: Some(input.checked), - geometry_snapping: None, - } - .into() - }) - .widget_holder(), - TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - CheckboxInput::new(self.snapping_state.geometry_snapping) - .tooltip(SnappingOptions::Geometry.to_string()) - .on_update(|input: &CheckboxInput| { - DocumentMessage::SetSnapping { - snapping_enabled: None, - bounding_box_snapping: None, - geometry_snapping: Some(input.checked), - } - .into() - }) - .widget_holder(), - TextLabel::new(SnappingOptions::Geometry.to_string()).widget_holder(), - ], - }, - ]) + PopoverButton::new() + .popover_layout( + [ + LayoutGroup::Row { + widgets: vec![TextLabel::new("Snapping").bold(true).widget_holder()], + }, + LayoutGroup::Row { + widgets: vec![TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_holder()], + }, + ] + .into_iter() + .chain( + [ + (BoundingBoxSnapTarget::Center, snapping_state.bounds.centers), + (BoundingBoxSnapTarget::Corner, snapping_state.bounds.corners), + (BoundingBoxSnapTarget::Edge, snapping_state.bounds.edges), + (BoundingBoxSnapTarget::EdgeMidpoint, snapping_state.bounds.edge_midpoints), + ] + .into_iter() + .map(|(enum_type, bound_state)| LayoutGroup::Row { + widgets: vec![ + CheckboxInput::new(bound_state) + .on_update(move |input: &CheckboxInput| { + DocumentMessage::SetSnapping { + snapping_enabled: None, + bounding_box_snapping: Some(OptionBoundsSnapping { + edges: if enum_type == BoundingBoxSnapTarget::Edge { Some(input.checked) } else { None }, + edge_midpoints: if enum_type == BoundingBoxSnapTarget::EdgeMidpoint { Some(input.checked) } else { None }, + centers: if enum_type == BoundingBoxSnapTarget::Center { Some(input.checked) } else { None }, + corners: if enum_type == BoundingBoxSnapTarget::Corner { Some(input.checked) } else { None }, + }), + geometry_snapping: None, + } + .into() + }) + .widget_holder(), + TextLabel::new(enum_type.to_string()).widget_holder(), + ], + }) + .chain( + [ + LayoutGroup::Row { + widgets: vec![TextLabel::new(SnappingOptions::Geometry.to_string()).widget_holder()], + }, + LayoutGroup::Row { + widgets: vec![ + CheckboxInput::new(snapping_state.nodes.anchors) + .on_update(move |input: &CheckboxInput| { + DocumentMessage::SetSnapping { + snapping_enabled: None, + bounding_box_snapping: None, + geometry_snapping: Some(OptionPointSnapping { + anchors: Some(input.checked), + ..Default::default() + }), + } + .into() + }) + .widget_holder(), + TextLabel::new("Anchor").widget_holder(), + ], + }, + ] + .into_iter() + .chain( + [ + (GeometrySnapTarget::LineMidpoint, snapping_state.nodes.line_midpoints), + (GeometrySnapTarget::Path, snapping_state.nodes.paths), + (GeometrySnapTarget::Normal, snapping_state.nodes.normals), + (GeometrySnapTarget::Tangent, snapping_state.nodes.tangents), + (GeometrySnapTarget::Intersection, snapping_state.nodes.path_intersections), + ] + .into_iter() + .map(|(enum_type, bound_state)| LayoutGroup::Row { + widgets: vec![ + CheckboxInput::new(bound_state) + .on_update(move |input: &CheckboxInput| { + DocumentMessage::SetSnapping { + snapping_enabled: None, + bounding_box_snapping: None, + geometry_snapping: Some(OptionPointSnapping { + anchors: None, + line_midpoints: if enum_type == GeometrySnapTarget::LineMidpoint { Some(input.checked) } else { None }, + paths: if enum_type == GeometrySnapTarget::Path { Some(input.checked) } else { None }, + normals: if enum_type == GeometrySnapTarget::Normal { Some(input.checked) } else { None }, + tangents: if enum_type == GeometrySnapTarget::Tangent { Some(input.checked) } else { None }, + path_intersections: if enum_type == GeometrySnapTarget::Intersection { Some(input.checked) } else { None }, + }), + } + .into() + }) + .widget_holder(), + TextLabel::new(enum_type.to_string()).widget_holder(), + ], + }), + ), + ), + ) + .collect(), + ) .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), CheckboxInput::new(self.snapping_state.grid_snapping) @@ -1206,8 +1324,8 @@ impl DocumentMessageHandler { .tooltip("Grid") .on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisible(optional_input.checked).into()) .widget_holder(), - PopoverButton::new("Grid", "Grid customization settings") - .options_widget(overlay_options(&self.snapping_state.grid)) + PopoverButton::new() + .popover_layout(overlay_options(&self.snapping_state.grid)) .popover_min_width(Some(320)) .widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), @@ -1230,7 +1348,16 @@ impl DocumentMessageHandler { _ => Some(1), }) .widget_holder(), - PopoverButton::new("View Mode", "Coming soon").widget_holder(), + PopoverButton::new() + .popover_layout(vec![ + LayoutGroup::Row { + widgets: vec![TextLabel::new("View Mode").bold(true).widget_holder()], + }, + LayoutGroup::Row { + widgets: vec![TextLabel::new("Coming soon").widget_holder()], + }, + ]) + .widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), IconButton::new("ZoomIn", 24) .tooltip("Zoom In") @@ -1247,25 +1374,34 @@ impl DocumentMessageHandler { .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::ResetCanvasTiltAndZoomTo100Percent)) .on_update(|_| NavigationMessage::ResetCanvasTiltAndZoomTo100Percent.into()) .widget_holder(), - PopoverButton::new( - "Canvas Navigation", - " - Interactive controls in this\n\ - menu are coming soon.\n\ - \n\ - Pan:\n\ - • Middle Click Drag\n\ - \n\ - Tilt:\n\ - • Alt + Middle Click Drag\n\ - \n\ - Zoom:\n\ - • Shift + Middle Click Drag\n\ - • Ctrl + Scroll Wheel Roll - " - .trim(), - ) - .widget_holder(), + PopoverButton::new() + .popover_layout(vec![ + LayoutGroup::Row { + widgets: vec![TextLabel::new("Canvas Navigation").bold(true).widget_holder()], + }, + LayoutGroup::Row { + widgets: vec![TextLabel::new( + " + Interactive controls in this\n\ + menu are coming soon.\n\ + \n\ + Pan:\n\ + • Middle Click Drag\n\ + \n\ + Tilt:\n\ + • Alt + Middle Click Drag\n\ + \n\ + Zoom:\n\ + • Shift + Middle Click Drag\n\ + • Ctrl + Scroll Wheel Roll + " + .trim(), + ) + .multiline(true) + .widget_holder()], + }, + ]) + .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), NumberInput::new(Some(self.navigation_handler.snapped_scale(self.navigation.zoom) * 100.)) .unit("%") diff --git a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs index 9fefde71..cc6789a2 100644 --- a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs +++ b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs @@ -105,6 +105,9 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec { } }) }; + widgets.push(LayoutGroup::Row { + widgets: vec![TextLabel::new("Grid").bold(true).widget_holder()], + }); widgets.push(LayoutGroup::Row { widgets: vec![ TextLabel::new("Origin").table_align(true).widget_holder(), diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 332025b6..ae0049e6 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -57,8 +57,6 @@ impl DocumentMode { /// SnappingState determines the current individual snapping states pub struct SnappingState { pub snapping_enabled: bool, - pub bounding_box_snapping: bool, - pub geometry_snapping: bool, pub grid_snapping: bool, pub bounds: BoundsSnapping, pub nodes: PointSnapping, @@ -70,8 +68,6 @@ impl Default for SnappingState { fn default() -> Self { Self { snapping_enabled: true, - bounding_box_snapping: true, - geometry_snapping: true, grid_snapping: false, bounds: BoundsSnapping { edges: true, @@ -82,8 +78,7 @@ impl Default for SnappingState { nodes: PointSnapping { paths: true, path_intersections: true, - point_handles_free: true, - point_handles_colinear: true, + anchors: true, line_midpoints: true, normals: true, tangents: true, @@ -103,15 +98,15 @@ impl SnappingState { return false; } match target { - SnapTarget::BoundingBox(bounding_box) if self.bounding_box_snapping => match bounding_box { + SnapTarget::BoundingBox(bounding_box) => match bounding_box { BoundingBoxSnapTarget::Corner => self.bounds.corners, BoundingBoxSnapTarget::Edge => self.bounds.edges, BoundingBoxSnapTarget::EdgeMidpoint => self.bounds.edge_midpoints, BoundingBoxSnapTarget::Center => self.bounds.centers, }, - SnapTarget::Geometry(nodes) if self.geometry_snapping => match nodes { - GeometrySnapTarget::HandlesColinear => self.nodes.point_handles_colinear, - GeometrySnapTarget::HandlesFree => self.nodes.point_handles_free, + SnapTarget::Geometry(nodes) => match nodes { + GeometrySnapTarget::AnchorWithColinearHandles => self.nodes.anchors, + GeometrySnapTarget::AnchorWithFreeHandles => self.nodes.anchors, GeometrySnapTarget::LineMidpoint => self.nodes.line_midpoints, GeometrySnapTarget::Path => self.nodes.paths, GeometrySnapTarget::Normal => self.nodes.normals, @@ -131,16 +126,31 @@ pub struct BoundsSnapping { pub edge_midpoints: bool, pub centers: bool, } +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct OptionBoundsSnapping { + pub edges: Option, + pub corners: Option, + pub edge_midpoints: Option, + pub centers: Option, +} #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct PointSnapping { pub paths: bool, pub path_intersections: bool, - pub point_handles_free: bool, - pub point_handles_colinear: bool, + pub anchors: bool, pub line_midpoints: bool, pub normals: bool, pub tangents: bool, } +#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct OptionPointSnapping { + pub paths: Option, + pub path_intersections: Option, + pub anchors: Option, + pub line_midpoints: Option, + pub normals: Option, + pub tangents: Option, +} #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, PartialEq)] pub enum GridType { Rectangle { spacing: DVec2 }, @@ -216,8 +226,8 @@ impl GridSnapping { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BoundingBoxSnapSource { - Corner, Center, + Corner, EdgeMidpoint, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -227,11 +237,11 @@ pub enum BoardSnapSource { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GeometrySnapSource { - HandlesColinear, - HandlesFree, - LineMidpoint, - PathIntersection, + AnchorWithColinearHandles, + AnchorWithFreeHandles, Handle, + LineMidpoint, + Intersection, } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum SnapSource { @@ -249,23 +259,50 @@ impl SnapSource { matches!(self, Self::BoundingBox(_) | Self::Board(_)) } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum BoundingBoxSnapTarget { + Center, Corner, Edge, EdgeMidpoint, - Center, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] + +impl fmt::Display for BoundingBoxSnapTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Center => write!(f, "Box Center"), + Self::Corner => write!(f, "Box Corner"), + Self::Edge => write!(f, "Along Edge"), + Self::EdgeMidpoint => write!(f, "Midpoint of Edge"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum GeometrySnapTarget { - HandlesColinear, - HandlesFree, + AnchorWithColinearHandles, + AnchorWithFreeHandles, LineMidpoint, Path, Normal, Tangent, Intersection, } + +impl fmt::Display for GeometrySnapTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::AnchorWithColinearHandles => write!(f, "Anchor (Colinear Handles)"), + Self::AnchorWithFreeHandles => write!(f, "Anchor (Free Handles)"), + Self::LineMidpoint => write!(f, "Line Midpoint"), + Self::Path => write!(f, "Path"), + Self::Normal => write!(f, "Normal to Path"), + Self::Tangent => write!(f, "Tangent to Path"), + Self::Intersection => write!(f, "Intersection"), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BoardSnapTarget { Edge, diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 72a9886c..2bccd226 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -278,9 +278,9 @@ impl ShapeState { let source = if handle.is_handle() { SnapSource::Geometry(GeometrySnapSource::Handle) } else if are_manipulator_handles_colinear(group, to_document, subpath, index) { - SnapSource::Geometry(GeometrySnapSource::HandlesColinear) + SnapSource::Geometry(GeometrySnapSource::AnchorWithColinearHandles) } else { - SnapSource::Geometry(GeometrySnapSource::HandlesFree) + SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles) }; let Some(position) = handle.get_position(group) else { continue }; let mut point = SnapCandidatePoint::new_source(to_document.transform_point2(position) + mouse_delta, source); diff --git a/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs index bdd51df7..d06f6aee 100644 --- a/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs @@ -327,10 +327,10 @@ impl SnapCandidatePoint { Self::new(document_point, source, SnapTarget::None) } pub fn handle(document_point: DVec2) -> Self { - Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::HandlesFree)) + Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles)) } pub fn handle_neighbors(document_point: DVec2, neighbors: impl Into>) -> Self { - let mut point = Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::HandlesFree)); + let mut point = Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles)); point.neighbors = neighbors.into(); point } @@ -401,19 +401,19 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath - {popoverButton.header} - {#if popoverButton.optionsWidget?.length} - - {:else} - {popoverButton.text} - {/if} + + {/if} {@const radioInput = narrowWidgetProps(component.props, "RadioInput")} diff --git a/frontend/src/components/widgets/buttons/PopoverButton.svelte b/frontend/src/components/widgets/buttons/PopoverButton.svelte index 5960f733..9808ffcc 100644 --- a/frontend/src/components/widgets/buttons/PopoverButton.svelte +++ b/frontend/src/components/widgets/buttons/PopoverButton.svelte @@ -75,6 +75,16 @@ .floating-menu { left: 50%; bottom: 0; + + .floating-menu-content > :first-child:not(:has(:not(.text-label))), + .floating-menu-content > :first-child:not(:has(:not(.checkbox-input))) { + margin-top: -8px; + } + + .floating-menu-content > :last-child:not(:has(:not(.text-label))), + .floating-menu-content > :last-child:not(:has(:not(.checkbox-input))) { + margin-bottom: -8px; + } } } diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index cf1367cc..5f3ecd01 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -870,17 +870,13 @@ export class PopoverButton extends WidgetProps { disabled!: boolean; - // Body - header!: string; - - text!: string; - @Transform(({ value }: { value: string }) => value || undefined) tooltip!: string | undefined; - popoverMinWidth: number | undefined; + // Body + popoverLayout!: LayoutGroup[]; - optionsWidget: LayoutGroup[] | undefined; + popoverMinWidth: number | undefined; } export type RadioEntryData = { @@ -1086,7 +1082,7 @@ function hoistWidgetHolder(widgetHolder: any): Widget { props.kind = kind; if (kind === "PopoverButton") { - props.optionsWidget = props.optionsWidget.map(createLayoutGroup); + props.popoverLayout = props.popoverLayout.map(createLayoutGroup); } const { widgetId } = widgetHolder; @@ -1136,8 +1132,8 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg if ("rowWidgets" in targetLayout) return targetLayout.rowWidgets[index]; if ("layout" in targetLayout) return targetLayout.layout[index]; if (targetLayout instanceof Widget) { - if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.optionsWidget) { - return targetLayout.props.optionsWidget[index]; + if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.popoverLayout) { + return targetLayout.props.popoverLayout[index]; } // eslint-disable-next-line no-console console.error("Tried to index widget");