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 <keavon@keavon.com>
This commit is contained in:
Shyam Jayakannan 2024-04-18 09:54:15 +05:30 committed by GitHub
parent 3019cc7253
commit 67ba5bcecf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 318 additions and 135 deletions

View File

@ -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)));
}
}
}

View File

@ -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();
}

View File

@ -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<u32>,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
#[serde(rename = "popoverLayout")]
pub popover_layout: SubLayout,
#[serde(rename = "popoverMinWidth")]
pub popover_min_width: Option<u32>,
}
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]

View File

@ -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<bool>,
bounding_box_snapping: Option<bool>,
geometry_snapping: Option<bool>,
bounding_box_snapping: Option<OptionBoundsSnapping>,
geometry_snapping: Option<OptionPointSnapping>,
},
SetViewMode {
view_mode: ViewMode,

View File

@ -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<DocumentMessage, DocumentMessageData<'_>> 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("%")

View File

@ -105,6 +105,9 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
}
})
};
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(),

View File

@ -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<bool>,
pub corners: Option<bool>,
pub edge_midpoints: Option<bool>,
pub centers: Option<bool>,
}
#[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<bool>,
pub path_intersections: Option<bool>,
pub anchors: Option<bool>,
pub line_midpoints: Option<bool>,
pub normals: Option<bool>,
pub tangents: Option<bool>,
}
#[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,

View File

@ -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);

View File

@ -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<Vec<DVec2>>) -> 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<Poin
let colinear = are_manipulator_handles_colinear(group, to_document, subpath, index);
if colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::HandlesColinear)) {
if colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::AnchorWithColinearHandles)) {
// Colinear handles
points.push(SnapCandidatePoint::new(
to_document.transform_point2(group.anchor),
SnapSource::Geometry(GeometrySnapSource::HandlesColinear),
SnapTarget::Geometry(GeometrySnapTarget::HandlesColinear),
SnapSource::Geometry(GeometrySnapSource::AnchorWithColinearHandles),
SnapTarget::Geometry(GeometrySnapTarget::AnchorWithColinearHandles),
));
} else if !colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::HandlesFree)) {
} else if !colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::AnchorWithFreeHandles)) {
// Free handles
points.push(SnapCandidatePoint::new(
to_document.transform_point2(group.anchor),
SnapSource::Geometry(GeometrySnapSource::HandlesFree),
SnapTarget::Geometry(GeometrySnapTarget::HandlesFree),
SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles),
SnapTarget::Geometry(GeometrySnapTarget::AnchorWithFreeHandles),
));
}
}

View File

@ -171,7 +171,19 @@ impl LayoutHolder for SelectTool {
let disabled = self.tool_data.selected_layers_count < 2;
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.alignment_widgets(disabled));
widgets.push(PopoverButton::new("Align", "Coming soon").disabled(disabled).widget_holder());
widgets.push(
PopoverButton::new()
.popover_layout(vec![
LayoutGroup::Row {
widgets: vec![TextLabel::new("Align").bold(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Coming soon").widget_holder()],
},
])
.disabled(disabled)
.widget_holder(),
);
// Flip
let disabled = self.tool_data.selected_layers_count == 0;

View File

@ -134,13 +134,8 @@
{/if}
{@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")}
{#if popoverButton}
<PopoverButton {...exclude(popoverButton, ["header", "text", "optionsWidget"])}>
<TextLabel bold={true}>{popoverButton.header}</TextLabel>
{#if popoverButton.optionsWidget?.length}
<WidgetLayout layout={{ layout: popoverButton.optionsWidget, layoutTarget: layoutTarget }} />
{:else}
<TextLabel multiline={true}>{popoverButton.text}</TextLabel>
{/if}
<PopoverButton {...exclude(popoverButton, ["popoverLayout"])}>
<WidgetLayout layout={{ layout: popoverButton.popoverLayout, layoutTarget: layoutTarget }} />
</PopoverButton>
{/if}
{@const radioInput = narrowWidgetProps(component.props, "RadioInput")}

View File

@ -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;
}
}
}
</style>

View File

@ -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");