diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index 0c25cf4642..e3960982ac 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -1,4 +1,4 @@ -use crate::consts::{UI_SCALE_DEFAULT, UI_SCALE_MAX, UI_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE}; +use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle; use crate::messages::preferences::SelectionMode; @@ -155,14 +155,14 @@ impl PreferencesDialogMessageHandler { rows.extend_from_slice(&[header, selection_label, selection_mode]); } - // ========== - // UI - // ========== + // ========= + // INTERFACE + // ========= #[cfg(not(target_family = "wasm"))] { - let header = vec![TextLabel::new("UI").italic(true).widget_instance()]; + let header = vec![TextLabel::new("Interface").italic(true).widget_instance()]; - let scale_description = "Adjust the scale of the user interface (100 is default)."; + let scale_description = "Adjust the scale of the entire user interface (100% is default)."; let scale_label = vec![ Separator::new(SeparatorType::Unrelated).widget_instance(), Separator::new(SeparatorType::Unrelated).widget_instance(), @@ -176,15 +176,18 @@ impl PreferencesDialogMessageHandler { .tooltip_description(scale_description) .mode_range() .int() - .min(ui_scale_to_display(UI_SCALE_MIN)) - .max(ui_scale_to_display(UI_SCALE_MAX)) + .min(ui_scale_to_display(crate::consts::UI_SCALE_MIN)) + .max(ui_scale_to_display(crate::consts::UI_SCALE_MAX)) .unit("%") .on_update(|number_input: &NumberInput| { if let Some(display_value) = number_input.value { let scale = map_display_to_ui_scale(display_value); PreferencesMessage::UIScale { scale }.into() } else { - PreferencesMessage::UIScale { scale: UI_SCALE_DEFAULT }.into() + PreferencesMessage::UIScale { + scale: crate::consts::UI_SCALE_DEFAULT, + } + .into() } }) .widget_instance(), @@ -376,11 +379,13 @@ fn map_zoom_rate_to_display(rate: f64) -> f64 { } /// Maps display values in percent to actual ui scale. +#[cfg(not(target_family = "wasm"))] fn map_display_to_ui_scale(display: f64) -> f64 { display / 100. } /// Maps actual ui scale back to display values in percent. +#[cfg(not(target_family = "wasm"))] fn ui_scale_to_display(scale: f64) -> f64 { scale * 100. } diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index b6bc9b63ae..efa500cb87 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -49,8 +49,6 @@ pub enum DocumentMessage { }, DeleteSelectedLayers, DeselectAllLayers, - DocumentHistoryBackward, - DocumentHistoryForward, DocumentStructureChanged, DrawArtboardOverlays { context: OverlayContext, @@ -110,7 +108,6 @@ pub enum DocumentMessage { mouse: Option<(f64, f64)>, parent_and_insert_index: Option<(LayerNodeIdentifier, usize)>, }, - Redo, RenameDocument { new_name: String, }, @@ -179,12 +176,27 @@ pub enum DocumentMessage { SetRenderMode { render_mode: RenderMode, }, + Undo, + Redo, + DocumentHistoryBackward, + DocumentHistoryForward, + // TODO: Rename to HistoryStepPush + /// Create a snapshot of the document at this point in time, by immediately starting and committing a transaction. AddTransaction, + // TODO: Rename to HistoryTransactionStart + /// Take a snapshot of the document to an intermediate state, and then depending on what we do next, we might either commit or abort it. StartTransaction, + // TODO: Rename to HistoryTransactionEnd + /// Either commit (creating a new history step) or cancel (removing the last history step, as if it never happened) the last transaction started with `StartTransaction`. EndTransaction, + // TODO: Remove this, make it into a private function CommitTransaction, + // TODO: Remove this, make it into a private function CancelTransaction, + /// Cause the document to revert back to the state when the transaction was started. For example, the user may be dragging + /// something around and hits Escape to abort the drag. This jumps the document back to the point before the drag began. AbortTransaction, + /// The same as `AbortTransaction` with one step back, but it can also be called with multiple steps back in the history of undos. RepeatedAbortTransaction { undo_count: usize, }, @@ -208,7 +220,6 @@ pub enum DocumentMessage { UpdateClipTargets { clip_targets: HashSet, }, - Undo, UngroupSelectedLayers, UngroupLayer { layer: LayerNodeIdentifier, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index a0b7332c25..51c1d2b9f8 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -372,8 +372,6 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); self.layer_range_selection_reference = None; } - DocumentMessage::DocumentHistoryBackward => self.undo_with_history(viewport, responses), - DocumentMessage::DocumentHistoryForward => self.redo_with_history(viewport, responses), DocumentMessage::DocumentStructureChanged => { if layers_panel_open { self.network_interface.load_structure(); @@ -953,15 +951,6 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select }); } - DocumentMessage::Redo => { - if self.network_interface.transaction_status() != TransactionStatus::Finished { - return; - } - responses.add(SelectToolMessage::Abort); - responses.add(DocumentMessage::DocumentHistoryForward); - responses.add(ToolMessage::Redo); - responses.add(OverlaysMessage::Draw); - } DocumentMessage::RenameDocument { new_name } => { self.name = new_name.clone(); @@ -1291,6 +1280,27 @@ impl MessageHandler> for DocumentMes self.render_mode = render_mode; responses.add_front(NodeGraphMessage::RunDocumentGraph); } + DocumentMessage::Undo => { + if self.network_interface.transaction_status() != TransactionStatus::Finished { + return; + } + responses.add(ToolMessage::PreUndo); + responses.add(DocumentMessage::DocumentHistoryBackward); + responses.add(OverlaysMessage::Draw); + responses.add(ToolMessage::Undo); + } + DocumentMessage::Redo => { + if self.network_interface.transaction_status() != TransactionStatus::Finished { + return; + } + responses.add(SelectToolMessage::Abort); + responses.add(DocumentMessage::DocumentHistoryForward); + responses.add(ToolMessage::Redo); + responses.add(OverlaysMessage::Draw); + } + DocumentMessage::DocumentHistoryBackward => self.undo_with_history(viewport, responses), + DocumentMessage::DocumentHistoryForward => self.redo_with_history(viewport, responses), + // Create a snapshot of the document at this point in time, by immediately starting and committing a transaction. DocumentMessage::AddTransaction => { // Reverse order since they are added to the front responses.add_front(DocumentMessage::CommitTransaction); @@ -1307,20 +1317,18 @@ impl MessageHandler> for DocumentMes // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents responses.add(PortfolioMessage::UpdateOpenDocumentsList); } - // Commits the transaction if the network was mutated since the transaction started, otherwise it cancels the transaction + // Either commit (creating a new history step) or cancel (removing the last history step, as if it never happened) the last transaction started with `StartTransaction`. DocumentMessage::EndTransaction => match self.network_interface.transaction_status() { - TransactionStatus::Started => { - responses.add_front(DocumentMessage::CancelTransaction); - } - TransactionStatus::Modified => { - responses.add_front(DocumentMessage::CommitTransaction); - } + // This is used if, between the start and end of the transaction, the changes were undone by the user. + // For example, dragging something around and then dropping it back at its exact original position. + // So we cancel the transaction to return to the point before the transaction was started. + TransactionStatus::Started => responses.add_front(DocumentMessage::CancelTransaction), + // This is used if, between the start and end of the transaction, actual changes did occur and we want to keep them as part of a history step that the user can undo/redo. + TransactionStatus::Modified => responses.add_front(DocumentMessage::CommitTransaction), + // This is an erroneous state indicating that a transaction is being ended without having ever been started. TransactionStatus::Finished => {} }, - DocumentMessage::CancelTransaction => { - self.network_interface.finish_transaction(); - self.document_undo_history.pop_back(); - } + // Add a history step so the user can undo/redo to the point when the transaction was started. DocumentMessage::CommitTransaction => { if self.network_interface.transaction_status() == TransactionStatus::Finished { return; @@ -1329,25 +1337,38 @@ impl MessageHandler> for DocumentMes self.document_redo_history.clear(); responses.add(PortfolioMessage::UpdateOpenDocumentsList); } + // Retroactively undoes the start of the transaction, as if the transaction was never started. This is useful, for example, if the user + // might have begun performing some action that ends up not changing anything, like dragging something back to its exact original position. + DocumentMessage::CancelTransaction => { + self.network_interface.finish_transaction(); + self.document_undo_history.pop_back(); + } + // Cause the document to revert back to the state when the transaction was started. For example, the user may be dragging + // something around and hits Escape to abort the drag. This jumps the document back to the point before the drag began. DocumentMessage::AbortTransaction => match self.network_interface.transaction_status() { - TransactionStatus::Started => { - responses.add_front(DocumentMessage::CancelTransaction); - } - TransactionStatus::Modified => { - responses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: 1 }); - } + // If we abort a transaction without any changes having been made, we simply remove the transaction as if it never occurred. + TransactionStatus::Started => responses.add_front(DocumentMessage::CancelTransaction), + // If we abort a transaction after changes have been made, we need to undo those changes. + TransactionStatus::Modified => responses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: 1 }), + // This is an erroneous state indicating that a transaction is being aborted without having ever been started. TransactionStatus::Finished => {} }, + // The same as `AbortTransaction` with one step back, but it can also be called with multiple steps back in the history of undos. DocumentMessage::RepeatedAbortTransaction { undo_count } => { + // This prevents us from aborting a transaction multiple times in a row, which would be erroneous. if self.network_interface.transaction_status() == TransactionStatus::Finished { return; } + // Sometimes (like successive G/R/S transformations) we may need to undo multiple steps to fully abort the transaction, before we finish. for _ in 0..undo_count { self.undo(viewport, responses); } + // Finally finish the transaction, ensuring that any future operations are not erroneously redone as part of this aborted transaction. self.network_interface.finish_transaction(); + + // Refresh state responses.add(OverlaysMessage::Draw); responses.add(PortfolioMessage::UpdateOpenDocumentsList); } @@ -1419,15 +1440,6 @@ impl MessageHandler> for DocumentMes DocumentMessage::UpdateClipTargets { clip_targets } => { self.network_interface.update_clip_targets(clip_targets); } - DocumentMessage::Undo => { - if self.network_interface.transaction_status() != TransactionStatus::Finished { - return; - } - responses.add(ToolMessage::PreUndo); - responses.add(DocumentMessage::DocumentHistoryBackward); - responses.add(OverlaysMessage::Draw); - responses.add(ToolMessage::Undo); - } DocumentMessage::UngroupSelectedLayers => { if !self.selection_network_path.is_empty() { log::error!("Ungrouping selected layers is only supported for the Document Network"); diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index 2cc0b5aec0..98af0baca2 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -6,6 +6,7 @@ use crate::messages::prelude::*; use graph_craft::wasm_application_io::EditorPreferences; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] +#[serde(default)] pub struct PreferencesMessageHandler { pub selection_mode: SelectionMode, pub zoom_with_scroll: bool, diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 2b99a347c9..0e61fd9a8a 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -169,6 +169,7 @@ pub struct PivotGizmoState { impl PivotGizmoState { pub fn is_pivot_type(&self) -> bool { + // A disabled pivot is considered a pivot-type gizmo that is always centered self.gizmo_type == PivotGizmoType::Pivot || self.disabled } diff --git a/frontend/src/components/layout/FloatingMenu.svelte b/frontend/src/components/layout/FloatingMenu.svelte index bc6112dc65..b26d6977be 100644 --- a/frontend/src/components/layout/FloatingMenu.svelte +++ b/frontend/src/components/layout/FloatingMenu.svelte @@ -372,6 +372,9 @@ // Start with the parent of the spawner for this floating menu and keep widening the search for any other valid spawners that are hover-transferrable let currentAncestor = (targetSpawner && ownSpawner?.parentElement) || undefined; while (currentAncestor) { + // If the current ancestor blocks hover transfer, stop searching + if (currentAncestor.hasAttribute("data-block-hover-transfer")) break; + const ownSpawnerDepthFromCurrentAncestor = ownSpawner && getDepthFromAncestor(ownSpawner, currentAncestor); const currentAncestor2 = currentAncestor; // This duplicate variable avoids an ESLint warning @@ -382,8 +385,8 @@ const notOurself = !ownDescendantMenuSpawners.includes(item); // And filter away unequal depths from the current ancestor const notUnequalDepths = notOurself && getDepthFromAncestor(item, currentAncestor2) === ownSpawnerDepthFromCurrentAncestor; - // And filter away elements that explicitly disable hover transfer - return notUnequalDepths && !(item as HTMLElement).getAttribute?.("data-floating-menu-spawner")?.includes("no-hover-transfer"); + // And filter away descendants that explicitly disable hover transfer + return notUnequalDepths && !(item instanceof HTMLElement && item.hasAttribute("data-block-hover-transfer")); }); // If none were found, widen the search by a level and keep trying (or stop looping if the root was reached) diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 58e09d7964..90de834064 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -501,7 +501,7 @@ style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`} style:--layer-area-width={layerAreaWidth} style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0} - data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`} + data-tooltip-label={node.displayName === node.reference || !node.reference ? node.displayName : `${node.displayName} (${node.reference})`} data-tooltip-description={` ${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""} `.trim()} @@ -651,7 +651,7 @@ style:--clip-path-id={`url(#${clipPathId})`} style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`} style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`} - data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`} + data-tooltip-label={node.displayName === node.reference || !node.reference ? node.displayName : `${node.displayName} (${node.reference})`} data-tooltip-description={` ${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""} `.trim()} diff --git a/frontend/src/components/widgets/WidgetSection.svelte b/frontend/src/components/widgets/WidgetSection.svelte index c9964c74a2..c51e72428e 100644 --- a/frontend/src/components/widgets/WidgetSection.svelte +++ b/frontend/src/components/widgets/WidgetSection.svelte @@ -59,7 +59,7 @@ /> {#if expanded} - + {#each widgetData.layout as layoutGroup} {#if isWidgetSpanRow(layoutGroup)} diff --git a/frontend/src/components/widgets/buttons/TextButton.svelte b/frontend/src/components/widgets/buttons/TextButton.svelte index 94de4b2f56..86608e3d98 100644 --- a/frontend/src/components/widgets/buttons/TextButton.svelte +++ b/frontend/src/components/widgets/buttons/TextButton.svelte @@ -72,7 +72,8 @@ data-disabled={disabled || undefined} data-text-button tabindex={disabled ? -1 : 0} - data-floating-menu-spawner={menuListChildrenExists ? "" : "no-hover-transfer"} + data-floating-menu-spawner + data-block-hover-transfer={menuListChildrenExists ? undefined : ""} on:click={onClick} > {#if icon} diff --git a/frontend/src/components/widgets/inputs/WorkingColorsInput.svelte b/frontend/src/components/widgets/inputs/WorkingColorsInput.svelte index cff3beacba..051f9367ae 100644 --- a/frontend/src/components/widgets/inputs/WorkingColorsInput.svelte +++ b/frontend/src/components/widgets/inputs/WorkingColorsInput.svelte @@ -37,7 +37,7 @@ - + (primaryOpen = detail)} @@ -47,7 +47,7 @@ /> - + (secondaryOpen = detail)} diff --git a/node-graph/libraries/vector-types/src/vector/misc.rs b/node-graph/libraries/vector-types/src/vector/misc.rs index f42d445d51..63dc5649a0 100644 --- a/node-graph/libraries/vector-types/src/vector/misc.rs +++ b/node-graph/libraries/vector-types/src/vector/misc.rs @@ -7,7 +7,7 @@ use glam::DVec2; use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez}; use std::ops::Sub; -/// Represents different ways of calculating the centroid. +/// Represents different geometric interpretations of calculating the centroid (center of mass). #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] pub enum CentroidType { diff --git a/node-graph/libraries/vector-types/src/vector/style.rs b/node-graph/libraries/vector-types/src/vector/style.rs index 7b0d772e14..10079efd4c 100644 --- a/node-graph/libraries/vector-types/src/vector/style.rs +++ b/node-graph/libraries/vector-types/src/vector/style.rs @@ -194,7 +194,6 @@ impl From for FillChoice { } } -/// Enum describing the type of [Fill]. #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] diff --git a/node-graph/nodes/raster/src/adjustments.rs b/node-graph/nodes/raster/src/adjustments.rs index 9d19457969..412476bb96 100644 --- a/node-graph/nodes/raster/src/adjustments.rs +++ b/node-graph/nodes/raster/src/adjustments.rs @@ -574,7 +574,6 @@ fn vibrance>( image } -/// Color Channel #[repr(u32)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, BufferStruct, FromPrimitive, IntoPrimitive)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] @@ -586,7 +585,6 @@ pub enum RedGreenBlue { Blue, } -/// Color Channel #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, bytemuck::NoUninit, BufferStruct, FromPrimitive, IntoPrimitive)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Radio)] @@ -599,7 +597,7 @@ pub enum RedGreenBlueAlpha { Alpha, } -/// Style of noise pattern +/// Style of noise pattern. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] @@ -616,9 +614,9 @@ pub enum NoiseType { WhiteNoise, } +/// Style of layered levels of the noise pattern. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] -/// Style of layered levels of the noise pattern pub enum FractalType { #[default] None, @@ -632,7 +630,7 @@ pub enum FractalType { DomainWarpIndependent, } -/// Distance function used by the cellular noise +/// Distance function used by the cellular noise. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] pub enum CellularDistanceFunction { @@ -663,7 +661,6 @@ pub enum CellularReturnType { Division, } -/// Type of domain warp #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)]