UI refinements for radio buttons and the Snapping/Grid popovers

This commit is contained in:
Keavon Chambers
2024-01-18 05:03:43 -08:00
parent 2df329fc0e
commit 5c9d3c5d75
8 changed files with 43 additions and 26 deletions
@@ -264,6 +264,9 @@ pub struct RadioInput {
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI) // This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
#[serde(rename = "selectedIndex")] #[serde(rename = "selectedIndex")]
pub selected_index: Option<u32>, pub selected_index: Option<u32>,
#[serde(rename = "minWidth")]
pub min_width: u32,
} }
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)] #[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
@@ -1168,8 +1168,6 @@ impl DocumentMessageHandler {
.options_widget(vec![ .options_widget(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).table_align(true).min_width(96).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(snapping_state.bounding_box_snapping) CheckboxInput::new(snapping_state.bounding_box_snapping)
.tooltip(SnappingOptions::BoundingBoxes.to_string()) .tooltip(SnappingOptions::BoundingBoxes.to_string())
.on_update(move |input: &CheckboxInput| { .on_update(move |input: &CheckboxInput| {
@@ -1181,13 +1179,11 @@ impl DocumentMessageHandler {
.into() .into()
}) })
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_holder(),
], ],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new(SnappingOptions::Geometry.to_string()).table_align(true).min_width(96).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(self.snapping_state.geometry_snapping) CheckboxInput::new(self.snapping_state.geometry_snapping)
.tooltip(SnappingOptions::Geometry.to_string()) .tooltip(SnappingOptions::Geometry.to_string())
.on_update(|input: &CheckboxInput| { .on_update(|input: &CheckboxInput| {
@@ -1199,6 +1195,7 @@ impl DocumentMessageHandler {
.into() .into()
}) })
.widget_holder(), .widget_holder(),
TextLabel::new(SnappingOptions::Geometry.to_string()).widget_holder(),
], ],
}, },
]) ])
@@ -108,7 +108,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
widgets.push(LayoutGroup::Row { widgets.push(LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new("Origin").table_align(true).widget_holder(), TextLabel::new("Origin").table_align(true).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(grid.origin.x)) NumberInput::new(Some(grid.origin.x))
.label("X") .label("X")
.unit(" px") .unit(" px")
@@ -127,11 +127,12 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
widgets.push(LayoutGroup::Row { widgets.push(LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new("Type").table_align(true).widget_holder(), TextLabel::new("Type").table_align(true).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
RadioInput::new(vec![ RadioInput::new(vec![
RadioEntryData::new("Rectangular").on_update(update_val(grid, |grid, _| grid.grid_type = GridType::RECTANGLE)), RadioEntryData::new("Rectangular").on_update(update_val(grid, |grid, _| grid.grid_type = GridType::RECTANGLE)),
RadioEntryData::new("Isometric").on_update(update_val(grid, |grid, _| grid.grid_type = GridType::ISOMETRIC)), RadioEntryData::new("Isometric").on_update(update_val(grid, |grid, _| grid.grid_type = GridType::ISOMETRIC)),
]) ])
.min_width(200)
.selected_index(Some(if matches!(grid.grid_type, GridType::Rectangle { .. }) { 0 } else { 1 })) .selected_index(Some(if matches!(grid.grid_type, GridType::Rectangle { .. }) { 0 } else { 1 }))
.widget_holder(), .widget_holder(),
], ],
@@ -141,7 +142,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
GridType::Rectangle { spacing } => widgets.push(LayoutGroup::Row { GridType::Rectangle { spacing } => widgets.push(LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new("Spacing").table_align(true).widget_holder(), TextLabel::new("Spacing").table_align(true).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(spacing.x)) NumberInput::new(Some(spacing.x))
.label("X") .label("X")
.unit(" px") .unit(" px")
@@ -162,10 +163,9 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => { GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
widgets.push(LayoutGroup::Row { widgets.push(LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new("Spacing").table_align(true).widget_holder(), TextLabel::new("Y Spacing").table_align(true).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(y_axis_spacing)) NumberInput::new(Some(y_axis_spacing))
.label("Y")
.unit(" px") .unit(" px")
.min(0.) .min(0.)
.min_width(200) .min_width(200)
@@ -175,22 +175,17 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
}); });
widgets.push(LayoutGroup::Row { widgets.push(LayoutGroup::Row {
widgets: vec![ widgets: vec![
TextLabel::new("Angle A").table_align(true).widget_holder(), TextLabel::new("Angles").table_align(true).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(angle_a)) NumberInput::new(Some(angle_a))
.unit("°") .unit("°")
.min_width(200) .min_width(98)
.on_update(update_origin(&grid, |grid| grid.grid_type.angle_a())) .on_update(update_origin(&grid, |grid| grid.grid_type.angle_a()))
.widget_holder(), .widget_holder(),
],
});
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Angle B").table_align(true).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(angle_b)) NumberInput::new(Some(angle_b))
.unit("°") .unit("°")
.min_width(200) .min_width(98)
.on_update(update_origin(&grid, |grid| grid.grid_type.angle_b())) .on_update(update_origin(&grid, |grid| grid.grid_type.angle_b()))
.widget_holder(), .widget_holder(),
], ],
@@ -487,6 +487,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderRulers);
responses.add(MenuBarMessage::SendLayout); responses.add(MenuBarMessage::SendLayout);
responses.add(FrontendMessage::TriggerRefreshBoundsOfViewports);
} }
} }
PortfolioMessage::UpdateDocumentWidgets => { PortfolioMessage::UpdateDocumentWidgets => {
@@ -111,6 +111,7 @@
padding: 0 7px; padding: 0 7px;
padding-top: 1px; padding-top: 1px;
margin-top: -1px; margin-top: -1px;
background: var(--color-3-darkgray);
border: 1px solid var(--color-2-mildblack); border: 1px solid var(--color-2-mildblack);
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
overflow: hidden; overflow: hidden;
@@ -105,5 +105,9 @@
background: var(--color-8-uppergray); background: var(--color-8-uppergray);
} }
} }
+ .text-label.text-label {
margin-left: 8px;
}
} }
</style> </style>
@@ -12,6 +12,7 @@
export let entries: RadioEntries; export let entries: RadioEntries;
export let selectedIndex: number | undefined = undefined; export let selectedIndex: number | undefined = undefined;
export let disabled = false; export let disabled = false;
export let minWidth = 0;
$: mixed = selectedIndex === undefined && !disabled; $: mixed = selectedIndex === undefined && !disabled;
@@ -23,7 +24,7 @@
} }
</script> </script>
<LayoutRow class="radio-input" classes={{ disabled }}> <LayoutRow class="radio-input" classes={{ disabled }} styles={{ "min-width": minWidth > 0 ? `${minWidth}px` : "" }}>
{#each entries as entry, index} {#each entries as entry, index}
<button class:active={index === selectedIndex} class:mixed class:disabled on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}> <button class:active={index === selectedIndex} class:mixed class:disabled on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}>
{#if entry.icon} {#if entry.icon}
@@ -43,11 +44,15 @@
fill: var(--color-e-nearwhite); fill: var(--color-e-nearwhite);
height: 24px; height: 24px;
margin: 0; margin: 0;
padding: 0 4px; padding: 0;
border: none; border: none;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
// `min-width: fit-content` and `flex: 1 1 0` together allow us to occupy space such that we're always at least the content width,
// but if the container is set wider, we distribute the space evenly (so buttons with short and long labels would have equal widths).
min-width: fit-content;
flex: 1 1 0;
&.mixed { &.mixed {
background: var(--color-4-dimgray); background: var(--color-4-dimgray);
@@ -100,11 +105,20 @@
&:last-of-type { &:last-of-type {
border-radius: 0 2px 2px 0; border-radius: 0 2px 2px 0;
} }
}
.text-label { .icon-label {
margin: 0 4px; margin: 0 4px;
overflow: hidden;
+ .text-label {
margin-left: 0;
}
}
.text-label {
margin: 0 8px;
overflow: hidden;
flex: 0 0 auto;
}
} }
&.combined-before button:first-of-type, &.combined-before button:first-of-type,
@@ -891,6 +891,8 @@ export class RadioInput extends WidgetProps {
disabled!: boolean; disabled!: boolean;
selectedIndex!: number | undefined; selectedIndex!: number | undefined;
minWidth!: number;
} }
export type SeparatorDirection = "Horizontal" | "Vertical"; export type SeparatorDirection = "Horizontal" | "Vertical";