Add Snapping Options to the Snap Dropdown Menu (#1321)

* [wip]feat: add snapping options

* [wip]fix: use svelte component for optionsWidget

* fix: use apt PopoverButton types

* refactor: minor formatting improvements

* Fix popover layout

* [wip]feat: attempt implementing CheckboxInputData struct

* fix: use correct Checkbox struct 's default method

* fix: revert adding CheckboxInputData struct

- This reverts commit 2a481887fc.

* feat: use checkboxes for snapping options

* feat: add label to dropdown checkbox elements

* fix: separate Snap dropdown menu elements
- move each element into separate row

* [wip]feat: modularize snapping states
- maintain individual snapping states for document

* fix: snapping checkboxes' behavior
- checkboxes now update internal snapping state

* refactor: update snap states individually
- this prevents out-of-sync states
- enables reusing existing snap state object

* feat: snap to boxes and nodes conditionally

* [wip]feat: attempt to invert checkbox on update
- attempt implementing mutable WidgetCallback struct
- attempt using above struct to invert checkbox state on update

* Fix widget diffing

* refactor: remove unused code

* feat: align checkboxes consistently with labels

* feat: use separators to stylize snapping menu
- removes need for custom CSS and label property
- ensures consistency across the application

* refactor: remove unneeded css

---------

Co-authored-by: hypercube <0hypercube@gmail.com>, TrueDoctor <dennis@kobert.dev>
This commit is contained in:
Dhruv
2023-07-15 15:07:18 +05:30
committed by Keavon Chambers
co-authored by hypercube <0hypercube@gmail.com>, TrueDoctor
parent 4c9daadb01
commit 743803ce04
12 changed files with 204 additions and 41 deletions
@@ -31,6 +31,10 @@ impl LayoutMessageHandler {
widget_path.push(index); widget_path.push(index);
return Some((widget, widget_path)); return Some((widget, widget_path));
} }
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)));
}
} }
} }
// A section contains more LayoutGroups which we add to the stack. // A section contains more LayoutGroups which we add to the stack.
@@ -241,6 +241,12 @@ impl<'a> Iterator for WidgetIter<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.current_slice.and_then(|slice| slice.first()) { if let Some(item) = self.current_slice.and_then(|slice| slice.first()) {
self.current_slice = Some(&self.current_slice.unwrap()[1..]); self.current_slice = Some(&self.current_slice.unwrap()[1..]);
if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = item {
self.stack.extend(p.options_widget.iter());
return self.next();
}
return Some(item); return Some(item);
} }
@@ -276,6 +282,12 @@ impl<'a> Iterator for WidgetIterMut<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) { if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) {
self.current_slice = Some(rest); self.current_slice = Some(rest);
if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = first {
self.stack.extend(p.options_widget.iter_mut());
return self.next();
}
return Some(first); return Some(first);
}; };
@@ -1,5 +1,6 @@
use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::WidgetCallback; use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
use crate::messages::layout::utility_types::widget_prelude::SubLayout;
use crate::messages::portfolio::document::node_graph::FrontendGraphDataType; use crate::messages::portfolio::document::node_graph::FrontendGraphDataType;
use graphite_proc_macros::WidgetBuilder; use graphite_proc_macros::WidgetBuilder;
@@ -48,6 +49,9 @@ pub struct PopoverButton {
pub tooltip: String, pub tooltip: String,
#[serde(rename = "optionsWidget")]
pub options_widget: SubLayout,
#[serde(skip)] #[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>, pub tooltip_shortcut: Option<ActionKeys>,
} }
@@ -171,7 +171,9 @@ pub enum DocumentMessage {
replacement_selected_layers: Vec<Vec<LayerId>>, replacement_selected_layers: Vec<Vec<LayerId>>,
}, },
SetSnapping { SetSnapping {
snap: bool, snapping_enabled: Option<bool>,
bounding_box_snapping: Option<bool>,
node_snapping: Option<bool>,
}, },
SetViewMode { SetViewMode {
view_mode: ViewMode, view_mode: ViewMode,
@@ -1,5 +1,5 @@
use super::utility_types::error::EditorError; use super::utility_types::error::EditorError;
use super::utility_types::misc::DocumentRenderMode; use super::utility_types::misc::{DocumentRenderMode, SnappingOptions, SnappingState};
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR}; use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR};
use crate::messages::frontend::utility_types::ExportBounds; use crate::messages::frontend::utility_types::ExportBounds;
@@ -7,6 +7,7 @@ use crate::messages::frontend::utility_types::FileType;
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget; use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{CheckboxInput, TextLabel};
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton}; use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
use crate::messages::layout::utility_types::widgets::input_widgets::{ use crate::messages::layout::utility_types::widgets::input_widgets::{
DropdownEntryData, DropdownInput, NumberInput, NumberInputIncrementBehavior, NumberInputMode, OptionalInput, RadioEntryData, RadioInput, DropdownEntryData, DropdownInput, NumberInput, NumberInputIncrementBehavior, NumberInputMode, OptionalInput, RadioEntryData, RadioInput,
@@ -48,7 +49,8 @@ pub struct DocumentMessageHandler {
pub document_mode: DocumentMode, pub document_mode: DocumentMode,
pub view_mode: ViewMode, pub view_mode: ViewMode,
pub snapping_enabled: bool, #[serde(skip)]
pub snapping_state: SnappingState,
pub overlays_visible: bool, pub overlays_visible: bool,
#[serde(skip)] #[serde(skip)]
@@ -83,7 +85,7 @@ impl Default for DocumentMessageHandler {
document_mode: DocumentMode::DesignMode, document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(), view_mode: ViewMode::default(),
snapping_enabled: true, snapping_state: SnappingState::default(),
overlays_visible: true, overlays_visible: true,
document_undo_history: VecDeque::new(), document_undo_history: VecDeque::new(),
@@ -845,8 +847,20 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let additional_layers = replacement_selected_layers; let additional_layers = replacement_selected_layers;
responses.add_front(AddSelectedLayers { additional_layers }); responses.add_front(AddSelectedLayers { additional_layers });
} }
SetSnapping { snap } => { SetSnapping {
self.snapping_enabled = snap; snapping_enabled,
bounding_box_snapping,
node_snapping,
} => {
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(state) = node_snapping {
self.snapping_state.node_snapping = state
};
} }
SetViewMode { view_mode } => { SetViewMode { view_mode } => {
self.view_mode = view_mode; self.view_mode = view_mode;
@@ -1553,17 +1567,88 @@ impl DocumentMessageHandler {
} }
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) { pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
let snapping_state = self.snapping_state.clone();
let mut widgets = vec![ let mut widgets = vec![
WidgetHolder::new(Widget::OptionalInput(OptionalInput { WidgetHolder::new(Widget::OptionalInput(OptionalInput {
checked: self.snapping_enabled, checked: snapping_state.snapping_enabled,
icon: "Snapping".into(), icon: "Snapping".into(),
tooltip: "Snapping".into(), tooltip: "Snapping".into(),
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()), on_update: WidgetCallback::new(move |optional_input: &OptionalInput| {
let snapping_enabled = optional_input.checked;
DocumentMessage::SetSnapping {
snapping_enabled: Some(snapping_enabled),
bounding_box_snapping: Some(snapping_state.bounding_box_snapping),
node_snapping: Some(snapping_state.node_snapping),
}
.into()
}),
..Default::default() ..Default::default()
})), })),
WidgetHolder::new(Widget::PopoverButton(PopoverButton { WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Snapping".into(), header: "Snapping".into(),
text: "Coming soon".into(), text: "Select the vectors to snap to.".into(), // TODO: check whether this is an apt description
options_widget: vec![
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
tooltip: SnappingOptions::BoundingBoxes.to_string(),
checked: snapping_state.bounding_box_snapping,
on_update: WidgetCallback::new(move |input: &CheckboxInput| {
DocumentMessage::SetSnapping {
snapping_enabled: None,
bounding_box_snapping: Some(input.checked),
node_snapping: None,
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Unrelated,
})),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: SnappingOptions::BoundingBoxes.to_string(),
table_align: false,
min_width: 60,
..Default::default()
})),
// adds appropriate space between row elements
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Vertical,
separator_type: SeparatorType::Related,
})),
],
},
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: self.snapping_state.node_snapping,
tooltip: SnappingOptions::Points.to_string(),
on_update: WidgetCallback::new(|input: &CheckboxInput| {
DocumentMessage::SetSnapping {
snapping_enabled: None,
bounding_box_snapping: None,
node_snapping: Some(input.checked),
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Unrelated,
})),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: SnappingOptions::Points.to_string(),
table_align: false,
min_width: 60,
..Default::default()
})),
],
},
],
..Default::default() ..Default::default()
})), })),
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::new(Widget::Separator(Separator {
@@ -1866,7 +1866,7 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
impl DocumentNodeType { impl DocumentNodeType {
/// Generate a [`DocumentNodeImplementation`] from this node type, using a nested network. /// Generate a [`DocumentNodeImplementation`] from this node type, using a nested network.
pub fn generate_implementation(&self) -> DocumentNodeImplementation { pub fn generate_implementation(&self) -> DocumentNodeImplementation {
let num_inputs = self.inputs.len(); // let num_inputs = self.inputs.len();
let inner_network = match &self.identifier { let inner_network = match &self.identifier {
NodeImplementation::DocumentNode(network) => network.clone(), NodeImplementation::DocumentNode(network) => network.clone(),
@@ -74,3 +74,36 @@ pub enum DocumentRenderMode<'a> {
OnlyBelowLayerInFolder(&'a [LayerId]), OnlyBelowLayerInFolder(&'a [LayerId]),
LayerCutout(&'a [LayerId], Color), LayerCutout(&'a [LayerId], Color),
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
/// SnappingState determines the current individual snapping states
pub struct SnappingState {
pub snapping_enabled: bool,
pub bounding_box_snapping: bool,
pub node_snapping: bool,
}
impl Default for SnappingState {
fn default() -> Self {
Self {
snapping_enabled: true,
bounding_box_snapping: true,
node_snapping: true,
}
}
}
// TODO: implement icons for SnappingOptions eventually
pub enum SnappingOptions {
BoundingBoxes,
Points,
}
impl fmt::Display for SnappingOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SnappingOptions::BoundingBoxes => write!(f, "Bounding Boxes"),
SnappingOptions::Points => write!(f, "Points"),
}
}
}
@@ -216,7 +216,9 @@ impl SnapManager {
snap_x: bool, snap_x: bool,
snap_y: bool, snap_y: bool,
) { ) {
if document_message_handler.snapping_enabled { let snapping_enabled = document_message_handler.snapping_state.snapping_enabled;
let bounding_box_snapping = document_message_handler.snapping_state.bounding_box_snapping;
if snapping_enabled && bounding_box_snapping {
self.snap_x = snap_x; self.snap_x = snap_x;
self.snap_y = snap_y; self.snap_y = snap_y;
@@ -235,7 +237,9 @@ impl SnapManager {
/// ///
/// This should be called after start_snap /// This should be called after start_snap
pub fn add_snap_points(&mut self, document_message_handler: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, snap_points: impl Iterator<Item = DVec2>) { pub fn add_snap_points(&mut self, document_message_handler: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, snap_points: impl Iterator<Item = DVec2>) {
if document_message_handler.snapping_enabled { let snapping_enabled = document_message_handler.snapping_state.snapping_enabled;
let node_snapping = document_message_handler.snapping_state.node_snapping;
if snapping_enabled && node_snapping {
let snap_points = snap_points.filter(|&pos| pos.x >= 0. && pos.y >= 0. && pos.x < input.viewport_bounds.size().x && pos.y <= input.viewport_bounds.size().y); let snap_points = snap_points.filter(|&pos| pos.x >= 0. && pos.y >= 0. && pos.x < input.viewport_bounds.size().x && pos.y <= input.viewport_bounds.size().y);
if let Some(targets) = &mut self.point_targets { if let Some(targets) = &mut self.point_targets {
targets.extend(snap_points); targets.extend(snap_points);
@@ -259,6 +263,10 @@ impl SnapManager {
) { ) {
let Some(vector_data) = &layer.as_vector_data() else { return }; let Some(vector_data) = &layer.as_vector_data() else { return };
if !document_message_handler.snapping_state.node_snapping {
return;
};
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap(); let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
let snap_points = vector_data let snap_points = vector_data
.manipulator_groups() .manipulator_groups()
@@ -299,7 +307,7 @@ impl SnapManager {
/// Finds the closest snap from an array of layers to the specified snap targets in viewport coords. /// Finds the closest snap from an array of layers to the specified snap targets in viewport coords.
/// Returns 0 for each axis that there is no snap less than the snap tolerance. /// Returns 0 for each axis that there is no snap less than the snap tolerance.
pub fn snap_layers(&mut self, responses: &mut VecDeque<Message>, document_message_handler: &DocumentMessageHandler, snap_anchors: Vec<DVec2>, mouse_delta: DVec2) -> DVec2 { pub fn snap_layers(&mut self, responses: &mut VecDeque<Message>, document_message_handler: &DocumentMessageHandler, snap_anchors: Vec<DVec2>, mouse_delta: DVec2) -> DVec2 {
if document_message_handler.snapping_enabled { if document_message_handler.snapping_state.snapping_enabled {
self.calculate_snap(snap_anchors.iter().map(move |&snap| mouse_delta + snap), responses) self.calculate_snap(snap_anchors.iter().map(move |&snap| mouse_delta + snap), responses)
} else { } else {
DVec2::ZERO DVec2::ZERO
@@ -308,7 +316,7 @@ impl SnapManager {
/// Handles snapping of a viewport position, returning another viewport position. /// Handles snapping of a viewport position, returning another viewport position.
pub fn snap_position(&mut self, responses: &mut VecDeque<Message>, document_message_handler: &DocumentMessageHandler, position_viewport: DVec2) -> DVec2 { pub fn snap_position(&mut self, responses: &mut VecDeque<Message>, document_message_handler: &DocumentMessageHandler, position_viewport: DVec2) -> DVec2 {
if document_message_handler.snapping_enabled { if document_message_handler.snapping_state.snapping_enabled {
self.calculate_snap([position_viewport].into_iter(), responses) + position_viewport self.calculate_snap([position_viewport].into_iter(), responses) + position_viewport
} else { } else {
position_viewport position_viewport
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { isWidgetColumn, isWidgetRow, isWidgetSection, type WidgetLayout } from "@graphite/wasm-communication/messages"; import { isWidgetColumn, isWidgetRow, isWidgetSection, LayoutGroup, type WidgetLayout } from "@graphite/wasm-communication/messages";
import WidgetSection from "@graphite/components/widgets/groups/WidgetSection.svelte"; import WidgetSection from "@graphite/components/widgets/groups/WidgetSection.svelte";
import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte"; import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
@@ -5,6 +5,7 @@
import { isWidgetColumn, isWidgetRow, type WidgetColumn, type WidgetRow } from "@graphite/wasm-communication/messages"; import { isWidgetColumn, isWidgetRow, type WidgetColumn, type WidgetRow } from "@graphite/wasm-communication/messages";
import PivotAssist from "@graphite/components/widgets/assists/PivotAssist.svelte"; import PivotAssist from "@graphite/components/widgets/assists/PivotAssist.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte"; import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte"; import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
@@ -138,9 +139,13 @@
{/if} {/if}
{@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")} {@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")}
{#if popoverButton} {#if popoverButton}
<PopoverButton {...exclude(popoverButton, ["header", "text"])}> <PopoverButton {...exclude(popoverButton, ["header", "text", "optionsWidget"])}>
<TextLabel bold={true}>{popoverButton.header}</TextLabel> <TextLabel bold={true}>{popoverButton.header}</TextLabel>
<TextLabel multiline={true}>{popoverButton.text}</TextLabel> {#if popoverButton.optionsWidget}
<WidgetLayout layout={{ layout: popoverButton.optionsWidget, layoutTarget: layoutTarget }} />
{:else}
<TextLabel multiline={true}>{popoverButton.text}</TextLabel>
{/if}
</PopoverButton> </PopoverButton>
{/if} {/if}
{@const radioInput = narrowWidgetProps(component.props, "RadioInput")} {@const radioInput = narrowWidgetProps(component.props, "RadioInput")}
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { IconName } from "@graphite/utility-functions/icons"; import type { IconName } from "@graphite/utility-functions/icons";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte"; import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
@@ -8,6 +7,7 @@
export let icon: IconName = "DropdownArrow"; export let icon: IconName = "DropdownArrow";
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let disabled = false; export let disabled = false;
// Callbacks // Callbacks
export let action: (() => void) | undefined = undefined; export let action: (() => void) | undefined = undefined;
@@ -21,6 +21,7 @@
<LayoutRow class="popover-button"> <LayoutRow class="popover-button">
<IconButton classes={{ open }} {disabled} action={() => onClick()} icon={icon || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner /> <IconButton classes={{ open }} {disabled} action={() => onClick()} icon={icon || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
<FloatingMenu {open} on:open={({ detail }) => (open = detail)} type="Popover" direction="Bottom"> <FloatingMenu {open} on:open={({ detail }) => (open = detail)} type="Popover" direction="Bottom">
<slot /> <slot />
</FloatingMenu> </FloatingMenu>
+32 -23
View File
@@ -499,15 +499,15 @@ export class UpdateMouseCursor extends JsMessage {
readonly cursor!: MouseCursorIcon; readonly cursor!: MouseCursorIcon;
} }
export class TriggerLoadAutoSaveDocuments extends JsMessage {} export class TriggerLoadAutoSaveDocuments extends JsMessage { }
export class TriggerLoadPreferences extends JsMessage {} export class TriggerLoadPreferences extends JsMessage { }
export class TriggerOpenDocument extends JsMessage {} export class TriggerOpenDocument extends JsMessage { }
export class TriggerImport extends JsMessage {} export class TriggerImport extends JsMessage { }
export class TriggerPaste extends JsMessage {} export class TriggerPaste extends JsMessage { }
export class TriggerCopyToClipboardBlobUrl extends JsMessage { export class TriggerCopyToClipboardBlobUrl extends JsMessage {
readonly blobUrl!: string; readonly blobUrl!: string;
@@ -546,7 +546,7 @@ export class TriggerRasterizeRegionBelowLayer extends JsMessage {
readonly size!: [number, number]; readonly size!: [number, number];
} }
export class TriggerRefreshBoundsOfViewports extends JsMessage {} export class TriggerRefreshBoundsOfViewports extends JsMessage { }
export class TriggerRevokeBlobUrl extends JsMessage { export class TriggerRevokeBlobUrl extends JsMessage {
readonly url!: string; readonly url!: string;
@@ -556,7 +556,7 @@ export class TriggerSavePreferences extends JsMessage {
readonly preferences!: Record<string, unknown>; readonly preferences!: Record<string, unknown>;
} }
export class DocumentChanged extends JsMessage {} export class DocumentChanged extends JsMessage { }
export class UpdateDocumentLayerTreeStructureJs extends JsMessage { export class UpdateDocumentLayerTreeStructureJs extends JsMessage {
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) { constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) {
@@ -649,7 +649,7 @@ export class UpdateImageData extends JsMessage {
readonly imageData!: ImaginateImageData[]; readonly imageData!: ImaginateImageData[];
} }
export class DisplayRemoveEditableTextbox extends JsMessage {} export class DisplayRemoveEditableTextbox extends JsMessage { }
export class UpdateDocumentLayerDetails extends JsMessage { export class UpdateDocumentLayerDetails extends JsMessage {
@Type(() => LayerPanelEntry) @Type(() => LayerPanelEntry)
@@ -700,7 +700,7 @@ export class ImaginateImageData {
readonly transform!: Float64Array; readonly transform!: Float64Array;
} }
export class DisplayDialogDismiss extends JsMessage {} export class DisplayDialogDismiss extends JsMessage { }
export class Font { export class Font {
fontFamily!: string; fontFamily!: string;
@@ -719,7 +719,7 @@ export class TriggerVisitLink extends JsMessage {
url!: string; url!: string;
} }
export class TriggerTextCommit extends JsMessage {} export class TriggerTextCommit extends JsMessage { }
export class TriggerTextCopy extends JsMessage { export class TriggerTextCopy extends JsMessage {
readonly copyText!: string; readonly copyText!: string;
@@ -729,7 +729,7 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
readonly commitDate!: string; readonly commitDate!: string;
} }
export class TriggerViewportResize extends JsMessage {} export class TriggerViewportResize extends JsMessage { }
// WIDGET PROPS // WIDGET PROPS
@@ -774,7 +774,7 @@ type MenuEntryCommon = {
export type MenuBarEntry = MenuEntryCommon & { export type MenuBarEntry = MenuEntryCommon & {
action: Widget; action: Widget;
children?: MenuBarEntry[][]; children?: MenuBarEntry[][];
disabled?: boolean, disabled?: boolean,
}; };
// An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets // An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets
@@ -931,6 +931,8 @@ export class PopoverButton extends WidgetProps {
@Transform(({ value }: { value: string }) => value || undefined) @Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined; tooltip!: string | undefined;
optionsWidget: LayoutGroup[] | undefined;
} }
export type RadioEntryData = { export type RadioEntryData = {
@@ -1126,6 +1128,10 @@ function hoistWidgetHolder(widgetHolder: any): Widget {
const props = widgetHolder.widget[kind]; const props = widgetHolder.widget[kind];
props.kind = kind; props.kind = kind;
if (kind === "PopoverButton") {
props.optionsWidget = props.optionsWidget.map(createLayoutGroup);
}
const { widgetId } = widgetHolder; const { widgetId } = widgetHolder;
return plainToClass(Widget, { props, widgetId }); return plainToClass(Widget, { props, widgetId });
@@ -1173,6 +1179,9 @@ export function patchWidgetLayout(/* mut */ layout: WidgetLayout, updates: Widge
if ("rowWidgets" in targetLayout) return targetLayout.rowWidgets[index]; if ("rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
if ("layout" in targetLayout) return targetLayout.layout[index]; if ("layout" in targetLayout) return targetLayout.layout[index];
if (targetLayout instanceof Widget) { if (targetLayout instanceof Widget) {
if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.optionsWidget) {
return targetLayout.props.optionsWidget[index];
}
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Tried to index widget"); console.error("Tried to index widget");
return targetLayout; return targetLayout;
@@ -1258,25 +1267,25 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
} }
// WIDGET LAYOUTS // WIDGET LAYOUTS
export class UpdateDialogDetails extends WidgetDiffUpdate {} export class UpdateDialogDetails extends WidgetDiffUpdate { }
export class UpdateDocumentModeLayout extends WidgetDiffUpdate {} export class UpdateDocumentModeLayout extends WidgetDiffUpdate { }
export class UpdateToolOptionsLayout extends WidgetDiffUpdate {} export class UpdateToolOptionsLayout extends WidgetDiffUpdate { }
export class UpdateDocumentBarLayout extends WidgetDiffUpdate {} export class UpdateDocumentBarLayout extends WidgetDiffUpdate { }
export class UpdateToolShelfLayout extends WidgetDiffUpdate {} export class UpdateToolShelfLayout extends WidgetDiffUpdate { }
export class UpdateWorkingColorsLayout extends WidgetDiffUpdate {} export class UpdateWorkingColorsLayout extends WidgetDiffUpdate { }
export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate {} export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate { }
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate {} export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate { }
export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate {} export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate { }
export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate {} export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate { }
export class UpdateMenuBarLayout extends JsMessage { export class UpdateMenuBarLayout extends JsMessage {
layoutTarget!: unknown; layoutTarget!: unknown;
@@ -1301,7 +1310,7 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
...entry, ...entry,
action: hoistWidgetHolders([entry.action])[0], action: hoistWidgetHolders([entry.action])[0],
children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined, children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined,
disabled: entry.disabled ?? false, disabled: entry.disabled ?? false,
})) }))
); );
} }