diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index b80301ecf2..a5b130b529 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -80,7 +80,7 @@ impl LayoutMessageHandler { }; responses.add(callback_message); } - Widget::ColorButton(color_button) => { + Widget::ColorInput(color_button) => { let callback_message = match action { WidgetValueAction::Commit => (color_button.on_commit.callback)(&()), WidgetValueAction::Update => { @@ -99,7 +99,7 @@ impl LayoutMessageHandler { }; (|| { - let update_value = value.as_object().expect("ColorButton update was not of type: object"); + let update_value = value.as_object().expect("ColorInput update was not of type: object"); // None let is_none = update_value.get("none").and_then(|x| x.as_bool()); @@ -139,7 +139,7 @@ impl LayoutMessageHandler { return (color_button.on_update.callback)(color_button); } - panic!("ColorButton update was not able to be parsed with color data: {color_button:?}"); + panic!("ColorInput update was not able to be parsed with color data: {color_button:?}"); })() } }; diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 4b0acd3808..9b580b1b7e 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -327,7 +327,7 @@ impl LayoutGroup { for widget in &mut widgets { let val = match &mut widget.widget { Widget::CheckboxInput(x) => &mut x.tooltip, - Widget::ColorButton(x) => &mut x.tooltip, + Widget::ColorInput(x) => &mut x.tooltip, Widget::CurveInput(x) => &mut x.tooltip, Widget::DropdownInput(x) => &mut x.tooltip, Widget::FontInput(x) => &mut x.tooltip, @@ -498,7 +498,7 @@ impl Default for WidgetCallback { pub enum Widget { BreadcrumbTrailButtons(BreadcrumbTrailButtons), CheckboxInput(CheckboxInput), - ColorButton(ColorButton), + ColorInput(ColorInput), CurveInput(CurveInput), DropdownInput(DropdownInput), FontInput(FontInput), @@ -571,7 +571,7 @@ impl DiffUpdate { let mut tooltip_shortcut = match &mut widget_holder.widget { Widget::BreadcrumbTrailButtons(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), - Widget::ColorButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), + Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), Widget::DropdownInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), Widget::FontInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), 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 e4ec11eb3c..8854667ecf 100644 --- a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs @@ -148,7 +148,7 @@ pub struct ImageButton { #[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] #[derivative(Debug, PartialEq, Default)] -pub struct ColorButton { +pub struct ColorInput { #[widget_builder(constructor)] pub value: FillChoice, @@ -174,7 +174,7 @@ pub struct ColorButton { // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, + pub on_update: WidgetCallback, #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message.rs b/editor/src/messages/portfolio/document/navigation/navigation_message.rs index 13e22d274a..b5c4095222 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message.rs @@ -11,6 +11,8 @@ pub enum NavigationMessage { BeginCanvasTilt { was_dispatched_from_menu: bool }, BeginCanvasZoom, CanvasPan { delta: DVec2 }, + CanvasPanAbortPrepare { x_not_y_axis: bool }, + CanvasPanAbort { x_not_y_axis: bool }, CanvasPanByViewportFraction { delta: DVec2 }, CanvasPanMouseWheel { use_y_as_x: bool }, CanvasTiltResetAndZoomTo100Percent, diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 9bff84b35d..10e4564a26 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -29,6 +29,7 @@ pub struct NavigationMessageHandler { navigation_operation: NavigationOperation, mouse_position: ViewportPosition, finish_operation_with_click: bool, + abortable_pan_start: Option, } impl MessageHandler> for NavigationMessageHandler { @@ -141,6 +142,28 @@ impl MessageHandler> for Navigation responses.add(BroadcastEvent::CanvasTransformed); responses.add(DocumentMessage::PTZUpdate); } + NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => { + let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { + log::error!("Could not get PTZ in CanvasPanAbortPrepare"); + return; + }; + self.abortable_pan_start = Some(if x_not_y_axis { ptz.pan.x } else { ptz.pan.y }); + } + NavigationMessage::CanvasPanAbort { x_not_y_axis } => { + let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { + log::error!("Could not get PTZ in CanvasPanAbort"); + return; + }; + if let Some(abortable_pan_start) = self.abortable_pan_start { + if x_not_y_axis { + ptz.pan.x = abortable_pan_start; + } else { + ptz.pan.y = abortable_pan_start; + } + } + self.abortable_pan_start = None; + responses.add(DocumentMessage::PTZUpdate); + } NavigationMessage::CanvasPanByViewportFraction { delta } => { let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction"); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 264ee3c9da..8d6ab11b8f 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -3303,7 +3303,7 @@ fn static_input_properties() -> InputProperties { "assign_colors_gradient".to_string(), Box::new(|node_id, index, context| { let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; - let gradient_row = node_properties::color_widget(document_node, node_id, index, input_name, ColorButton::default().allow_none(false), true); + let gradient_row = node_properties::color_widget(document_node, node_id, index, input_name, ColorInput::default().allow_none(false), true); Ok(vec![gradient_row]) }), ); @@ -3329,7 +3329,7 @@ fn static_input_properties() -> InputProperties { "mask_stencil".to_string(), Box::new(|node_id, index, context| { let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; - let mask = node_properties::color_widget(document_node, node_id, index, input_name, ColorButton::default(), true); + let mask = node_properties::color_widget(document_node, node_id, index, input_name, ColorInput::default(), true); Ok(vec![mask]) }), ); @@ -3403,7 +3403,7 @@ fn static_input_properties() -> InputProperties { node_id, index, input_name, - ColorButton::default().allow_none(false), + ColorInput::default().allow_none(false), true, )]) }), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 8a5075397d..4a452864ca 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1822,7 +1822,7 @@ impl NodeGraphMessageHandler { // If only one node is selected then show the preview or stop previewing button if let Some(node_id) = previewing { let button = TextButton::new("End Preview") - .icon(Some("Rescale".to_string())) + .icon(Some("FrameAll".to_string())) .tooltip("Restore preview to the graph output") .on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into()) .widget_holder(); @@ -1834,7 +1834,7 @@ impl NodeGraphMessageHandler { .any(|export| matches!(export, NodeInput::Node { node_id: export_node_id, .. } if *export_node_id == node_id)); if selection_is_not_already_the_output && no_other_selections { let button = TextButton::new("Preview") - .icon(Some("Rescale".to_string())) + .icon(Some("FrameAll".to_string())) .tooltip("Preview selected node/layer (Shortcut: Alt-click node/layer)") .on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into()) .widget_holder(); diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index c17f8bf931..8e137f8a74 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -140,8 +140,8 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, numbe Some(x) if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX))), true).into(), Some(x) if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(), Some(x) if x == TypeId::of::() => text_widget(document_node, node_id, index, name, true).into(), - Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), - Some(x) if x == TypeId::of::>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true), + Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorInput::default().allow_none(false), true), + Some(x) if x == TypeId::of::>() => color_widget(document_node, node_id, index, name, ColorInput::default().allow_none(true), true), Some(x) if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), Some(x) if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", Some(0.), add_blank_assist), Some(x) if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), @@ -152,7 +152,7 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, numbe font_widgets.into_iter().chain(style_widgets.unwrap_or_default()).collect::>().into() } Some(x) if x == TypeId::of::() => curves_widget(document_node, node_id, index, name, true), - Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), + Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorInput::default().allow_none(false), true), Some(x) if x == TypeId::of::() => vector_widget(document_node, node_id, index, name, true).into(), Some(x) if x == TypeId::of::() || x == TypeId::of::>() || x == TypeId::of::() => { raster_widget(document_node, node_id, index, name, true).into() @@ -1067,7 +1067,7 @@ pub fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: us LayoutGroup::Row { widgets } } -pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_button: ColorButton, blank_assist: bool) -> LayoutGroup { +pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_button: ColorInput, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); // Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel @@ -1080,7 +1080,7 @@ pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, TaggedValue::Color(color) => widgets.push( color_button .value(FillChoice::Solid(*color)) - .on_update(update_value(|x: &ColorButton| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index)) + .on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index)) .on_commit(commit_value) .widget_holder(), ), @@ -1090,7 +1090,7 @@ pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, Some(color) => FillChoice::Solid(*color), None => FillChoice::None, }) - .on_update(update_value(|x: &ColorButton| TaggedValue::OptionalColor(x.value.as_solid()), node_id, index)) + .on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value.as_solid()), node_id, index)) .on_commit(commit_value) .widget_holder(), ), @@ -1098,7 +1098,7 @@ pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, color_button .value(FillChoice::Gradient(x.clone())) .on_update(update_value( - |x: &ColorButton| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()), + |x: &ColorInput| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()), node_id, index, )) @@ -1776,7 +1776,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties // if let Some(&TaggedValue::F64(seed)) = &input.as_non_exposed_value() { // widgets.extend_from_slice(&[ // Separator::new(SeparatorType::Unrelated).widget_holder(), -// IconButton::new("Regenerate", 24) +// IconButton::new("Resync", 24) // .tooltip("Set a new random seed") // .on_update({ // let imaginate_node = imaginate_node.clone(); @@ -1856,7 +1856,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties // widgets.extend_from_slice(&[ // Separator::new(SeparatorType::Unrelated).widget_holder(), -// IconButton::new("Rescale", 24) +// IconButton::new("FrameAll", 24) // .tooltip("Set the layer dimensions to this resolution") // .on_update(move |_| DialogMessage::RequestComingSoonDialog { issue: None }.into()) // .widget_holder(), @@ -2211,9 +2211,9 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte widgets_first_row.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets_first_row.push( - ColorButton::default() + ColorInput::default() .value(fill.clone().into()) - .on_update(move |x: &ColorButton| { + .on_update(move |x: &ColorInput| { Message::Batched(Box::new([ match &fill2 { Fill::None => NodeGraphMessage::SetInputValue { @@ -2380,7 +2380,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - let line_join_index = 6; let miter_limit_index = 7; - let color = color_widget(document_node, node_id, color_index, "Color", ColorButton::default(), true); + let color = color_widget(document_node, node_id, color_index, "Color", ColorInput::default(), true); let weight = number_widget(document_node, node_id, weight_index, "Weight", NumberInput::default().unit(" px").min(0.), true); let dash_lengths_val = match &document_node.inputs[dash_lengths_index].as_value() { diff --git a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs index ad6eb18b57..2bf84526c1 100644 --- a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs +++ b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs @@ -229,7 +229,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec { }) }; let update_color = |grid, update: fn(&mut GridSnapping) -> Option<&mut Color>| { - update_val::(grid, move |grid, color| { + update_val::(grid, move |grid, color| { if let FillChoice::Solid(color) = color.value { if let Some(update_color) = update(grid) { *update_color = color; @@ -280,7 +280,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec { Separator::new(SeparatorType::Related).widget_holder(), ]); color_widgets.push( - ColorButton::new(FillChoice::Solid(grid.grid_color)) + ColorInput::new(FillChoice::Solid(grid.grid_color)) .tooltip("Grid display color") .allow_none(false) .on_update(update_color(grid, |grid| Some(&mut grid.grid_color))) diff --git a/editor/src/messages/tool/common_functionality/color_selector.rs b/editor/src/messages/tool/common_functionality/color_selector.rs index ce42df044b..2b73b4f1f8 100644 --- a/editor/src/messages/tool/common_functionality/color_selector.rs +++ b/editor/src/messages/tool/common_functionality/color_selector.rs @@ -79,7 +79,7 @@ impl ToolColorOptions { color_allow_none: bool, reset_callback: impl Fn(&IconButton) -> Message + 'static + Send + Sync, radio_callback: fn(ToolColorType) -> WidgetCallback<()>, - color_callback: impl Fn(&ColorButton) -> Message + 'static + Send + Sync, + color_callback: impl Fn(&ColorInput) -> Message + 'static + Send + Sync, ) -> Vec { let mut widgets = vec![TextLabel::new(label_text).widget_holder()]; @@ -112,7 +112,7 @@ impl ToolColorOptions { widgets.push(radio); widgets.push(Separator::new(SeparatorType::Related).widget_holder()); - let color_button = ColorButton::new(FillChoice::from_optional_color(self.active_color())) + let color_button = ColorInput::new(FillChoice::from_optional_color(self.active_color())) .allow_none(color_allow_none) .on_update(color_callback); widgets.push(color_button.widget_holder()); diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 4cacebe355..383e54d59d 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -156,7 +156,7 @@ impl LayoutHolder for BrushTool { false, |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()), - |color: &ColorButton| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value.as_solid())).into(), + |color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Related).widget_holder()); diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index ecbd2cbb5d..858c177eb5 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -91,7 +91,7 @@ impl LayoutHolder for EllipseTool { true, |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value.as_solid())).into(), ); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -101,7 +101,7 @@ impl LayoutHolder for EllipseTool { true, |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index c75092a87c..7d48148acd 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -99,7 +99,7 @@ impl LayoutHolder for FreehandTool { true, |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value.as_solid())).into(), ); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -109,7 +109,7 @@ impl LayoutHolder for FreehandTool { true, |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index b866129944..65bfa2a45d 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -85,7 +85,7 @@ impl LayoutHolder for LineTool { true, |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value.as_solid())).into(), ); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 278771c310..9ec62caebc 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -114,7 +114,7 @@ impl LayoutHolder for PenTool { true, |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value.as_solid())).into(), ); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -124,7 +124,7 @@ impl LayoutHolder for PenTool { true, |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index 1d27f38317..0097c22a18 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -135,7 +135,7 @@ impl LayoutHolder for PolygonTool { true, |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -145,7 +145,7 @@ impl LayoutHolder for PolygonTool { true, |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 09b589024f..fb34ed88eb 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -77,7 +77,7 @@ impl LayoutHolder for RectangleTool { true, |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value.as_solid())).into(), ); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -87,7 +87,7 @@ impl LayoutHolder for RectangleTool { true, |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 7f1ab31fb4..f615c69cc7 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -102,7 +102,7 @@ impl LayoutHolder for SplineTool { true, |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value.as_solid())).into(), ); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -112,7 +112,7 @@ impl LayoutHolder for SplineTool { true, |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColorType(color_type.clone())).into()), - |color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value.as_solid())).into(), + |color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 14da9427fc..69b1e2e8d8 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -160,7 +160,7 @@ impl LayoutHolder for TextTool { true, |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into(), |color_type: ToolColorType| WidgetCallback::new(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColorType(color_type.clone())).into()), - |color: &ColorButton| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value.as_solid())).into(), + |color: &ColorInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value.as_solid())).into(), )); Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index 36c0c6a04a..634ddda395 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -138,6 +138,7 @@ linear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%), linear-gradient(#ffffff, #ffffff); --color-transparent-checkered-background-size: 16px 16px, 16px 16px, 16px 16px; --color-transparent-checkered-background-position: 0 0, 8px 8px, 8px 8px; + --color-transparent-checkered-background-position-plus-one: 1px 1px, 9px 9px, 9px 9px; --color-transparent-checkered-background-size-mini: 8px 8px, 8px 8px, 8px 8px; --color-transparent-checkered-background-position-mini: 0 0, 4px 4px, 4px 4px; --color-transparent-checkered-background-repeat: repeat, repeat, repeat; diff --git a/frontend/src/components/floating-menus/ColorPicker.svelte b/frontend/src/components/floating-menus/ColorPicker.svelte index bf70447f71..6167897b16 100644 --- a/frontend/src/components/floating-menus/ColorPicker.svelte +++ b/frontend/src/components/floating-menus/ColorPicker.svelte @@ -3,10 +3,11 @@ import type { Editor } from "@graphite/editor"; import type { HSV, RGB, FillChoice } from "@graphite/messages"; - import { Color, Gradient } from "@graphite/messages"; + import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages"; import { clamp } from "@graphite/utility-functions/math"; import FloatingMenu, { type MenuDirection } from "@graphite/components/layout/FloatingMenu.svelte"; + import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; @@ -65,7 +66,19 @@ // Transient state let draggingPickerTrack: HTMLDivElement | undefined = undefined; let strayCloses = true; - + let gradientSpectrumDragging = false; + let shiftPressed = false; + let alignedAxis: "saturation" | "value" | undefined = undefined; + let hueBeforeDrag = 0; + let saturationBeforeDrag = 0; + let valueBeforeDrag = 0; + let alphaBeforeDrag = 0; + let saturationStartOfAxisAlign: number | undefined = undefined; + let valueStartOfAxisAlign: number | undefined = undefined; + let saturationRestoreWhenShiftReleased: number | undefined = undefined; + let valueRestoreWhenShiftReleased: number | undefined = undefined; + + let self: FloatingMenu | undefined; let hexCodeInputWidget: TextInput | undefined; let gradientSpectrumInputWidget: SpectrumInput | undefined; @@ -77,6 +90,9 @@ $: rgbChannels = Object.entries(newColor.toRgb255() || { r: undefined, g: undefined, b: undefined }) as [keyof RGB, number | undefined][]; $: hsvChannels = Object.entries(!isNone ? { h: hue * 360, s: saturation * 100, v: value * 100 } : { h: undefined, s: undefined, v: undefined }) as [keyof HSV, number | undefined][]; $: opaqueHueColor = new Color({ h: hue, s: 1, v: 1, a: 1 }); + $: outlineFactor = Math.max(contrastingOutlineFactor(newColor, "--color-2-mildblack", 0.01), contrastingOutlineFactor(oldColor, "--color-2-mildblack", 0.01)); + $: outlined = outlineFactor > 0.0001; + $: transparency = newColor.alpha < 1 || oldColor.alpha < 1; function generateColor(h: number, s: number, v: number, a: number, none: boolean) { if (none) return new Color("none"); @@ -119,6 +135,14 @@ const target = (e.target || undefined) as HTMLElement | undefined; draggingPickerTrack = target?.closest("[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]") || undefined; + hueBeforeDrag = hue; + saturationBeforeDrag = saturation; + valueBeforeDrag = value; + alphaBeforeDrag = alpha; + + saturationStartOfAxisAlign = undefined; + valueStartOfAxisAlign = undefined; + addEvents(); onPointerMove(e); @@ -134,6 +158,8 @@ saturation = clamp((e.clientX - rectangle.left) / rectangle.width, 0, 1); value = clamp(1 - (e.clientY - rectangle.top) / rectangle.height, 0, 1); strayCloses = false; + + if (shiftPressed) updateAxisLock(); } else if (draggingPickerTrack?.hasAttribute("data-hue-picker")) { const rectangle = draggingPickerTrack.getBoundingClientRect(); @@ -148,25 +174,102 @@ const color = new Color({ h: hue, s: saturation, v: value, a: alpha }); setColor(color); + + if (!e.shiftKey) { + shiftPressed = false; + alignedAxis = undefined; + } else if (!shiftPressed && draggingPickerTrack) { + shiftPressed = true; + saturationStartOfAxisAlign = saturation; + valueStartOfAxisAlign = value; + } } function onPointerUp() { removeEvents(); } + function onMouseDown(e: MouseEvent) { + const BUTTONS_RIGHT = 0b0000_0010; + if (e.buttons & BUTTONS_RIGHT) abortDrag(); + } + + function onKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") { + const element = self?.div(); + if (element) preventEscapeClosingParentFloatingMenu(element); + + abortDrag(); + } + } + + function onKeyUp(e: KeyboardEvent) { + if (e.key === "Shift") { + shiftPressed = false; + alignedAxis = undefined; + + if (saturationRestoreWhenShiftReleased !== undefined && valueRestoreWhenShiftReleased !== undefined) { + saturation = saturationRestoreWhenShiftReleased; + value = valueRestoreWhenShiftReleased; + + const color = new Color({ h: hue, s: saturation, v: value, a: alpha }); + setColor(color); + } + } + } + function addEvents() { document.addEventListener("pointermove", onPointerMove); document.addEventListener("pointerup", onPointerUp); + document.addEventListener("mousedown", onMouseDown); + document.addEventListener("keydown", onKeyDown); + document.addEventListener("keyup", onKeyUp); dispatch("startHistoryTransaction"); } function removeEvents() { draggingPickerTrack = undefined; - strayCloses = true; + // The setTimeout is necessary to prevent the FloatingMenu's `escapeCloses` from becoming true immediately upon pressing the Escape key, and thus closing + setTimeout(() => (strayCloses = true), 0); + shiftPressed = false; + alignedAxis = undefined; document.removeEventListener("pointermove", onPointerMove); document.removeEventListener("pointerup", onPointerUp); + document.removeEventListener("mousedown", onMouseDown); + document.removeEventListener("keydown", onKeyDown); + document.removeEventListener("keyup", onKeyUp); + } + + function updateAxisLock() { + if (!saturationStartOfAxisAlign || !valueStartOfAxisAlign) return; + + const deltaSaturation = saturation - saturationStartOfAxisAlign; + const deltaValue = value - valueStartOfAxisAlign; + + saturationRestoreWhenShiftReleased = saturation; + valueRestoreWhenShiftReleased = value; + + if (Math.abs(deltaSaturation) < Math.abs(deltaValue)) { + alignedAxis = "saturation"; + saturation = saturationStartOfAxisAlign; + } else { + alignedAxis = "value"; + value = valueStartOfAxisAlign; + } + } + + function abortDrag() { + removeEvents(); + + hue = hueBeforeDrag; + saturation = saturationBeforeDrag; + value = valueBeforeDrag; + alpha = alphaBeforeDrag; + + const color = new Color({ h: hue, s: saturation, v: value, a: alpha }); + setColor(color); } function setColor(color?: Color) { @@ -299,7 +402,7 @@ }); - + {/if} + {#if alignedAxis} +
+ {/if} {#if !isNone} @@ -340,6 +452,7 @@ }} on:activeMarkerIndexChange={gradientActiveMarkerIndexChange} activeMarkerIndex={activeIndex} + on:dragging={({ detail }) => (gradientSpectrumDragging = detail)} bind:this={gradientSpectrumInputWidget} /> {#if gradientSpectrumInputWidget && activeIndex !== undefined} @@ -360,6 +473,8 @@ {#if !newColor.equals(oldColor)} @@ -453,7 +568,7 @@ - Alpha + Alpha {#if allowNone && !gradient} - {/if} - - + (open = detail)} + colorOrGradient={value} + on:colorOrGradient={({ detail }) => { + value = detail; + dispatch("value", detail); + }} + on:startHistoryTransaction={() => { + // This event is sent to the backend so it knows to start a transaction for the history system. See discussion for some explanation: + // + dispatch("startHistoryTransaction"); + }} + {allowNone} + /> + + + diff --git a/frontend/src/components/widgets/inputs/FieldInput.svelte b/frontend/src/components/widgets/inputs/FieldInput.svelte index 4a92fc71e3..2efc1353b9 100644 --- a/frontend/src/components/widgets/inputs/FieldInput.svelte +++ b/frontend/src/components/widgets/inputs/FieldInput.svelte @@ -3,6 +3,7 @@ import { platformIsMac } from "@graphite/utility-functions/platform"; + import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; const dispatch = createEventDispatcher<{ @@ -65,6 +66,12 @@ export function element(): HTMLInputElement | HTMLTextAreaElement | undefined { return inputOrTextarea; } + + function cancel() { + dispatch("textChangeCanceled"); + + if (inputOrTextarea) preventEscapeClosingParentFloatingMenu(inputOrTextarea); + } @@ -83,7 +90,7 @@ on:blur={() => dispatch("textChanged")} on:change={() => dispatch("textChanged")} on:keydown={(e) => e.key === "Enter" && dispatch("textChanged")} - on:keydown={(e) => e.key === "Escape" && dispatch("textChangeCanceled")} + on:keydown={(e) => e.key === "Escape" && cancel()} on:pointerdown on:contextmenu={(e) => hideContextMenu && e.preventDefault()} data-input-element @@ -102,7 +109,7 @@ on:blur={() => dispatch("textChanged")} on:change={() => dispatch("textChanged")} on:keydown={(e) => (macKeyboardLayout ? e.metaKey : e.ctrlKey) && e.key === "Enter" && dispatch("textChanged")} - on:keydown={(e) => e.key === "Escape" && dispatch("textChangeCanceled")} + on:keydown={(e) => e.key === "Escape" && cancel()} on:pointerdown on:contextmenu={(e) => hideContextMenu && e.preventDefault()} /> diff --git a/frontend/src/components/widgets/inputs/NumberInput.svelte b/frontend/src/components/widgets/inputs/NumberInput.svelte index 08b75a5ef4..ae26c20ee8 100644 --- a/frontend/src/components/widgets/inputs/NumberInput.svelte +++ b/frontend/src/components/widgets/inputs/NumberInput.svelte @@ -1,9 +1,11 @@ -
dispatch("pointerup")}> - +
+
-