diff --git a/editor/src/messages/broadcast/broadcast_event.rs b/editor/src/messages/broadcast/broadcast_event.rs index 23cc9ebf..3ffcf775 100644 --- a/editor/src/messages/broadcast/broadcast_event.rs +++ b/editor/src/messages/broadcast/broadcast_event.rs @@ -8,4 +8,5 @@ pub enum BroadcastEvent { DocumentIsDirty, ToolAbort, SelectionChanged, + WorkingColorChanged, } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 0114ce7e..964fafcf 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -1193,7 +1193,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte .widget_holder(), WidgetHolder::unrelated_separator(), CheckboxInput::new(!dimensions_is_auto || transform_not_connected) - .icon("Edit") + .icon("Edit12px") .tooltip({ let message = "Set a custom resolution instead of using the input's dimensions (rounded to the nearest 64)"; let manual_message = "Set a custom resolution instead of using the input's dimensions (rounded to the nearest 64).\n\ diff --git a/editor/src/messages/tool/common_functionality/overlay_renderer.rs b/editor/src/messages/tool/common_functionality/overlay_renderer.rs index 3968b351..7146a443 100644 --- a/editor/src/messages/tool/common_functionality/overlay_renderer.rs +++ b/editor/src/messages/tool/common_functionality/overlay_renderer.rs @@ -159,7 +159,7 @@ impl OverlayRenderer { let operation = Operation::AddShape { path: layer_path.clone(), subpath, - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), PATH_OUTLINE_WEIGHT)), Fill::None), insert_index: -1, transform: DAffine2::IDENTITY.to_cols_array(), }; @@ -174,7 +174,7 @@ impl OverlayRenderer { let operation = Operation::AddRect { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Fill::solid(Color::WHITE)), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 2.0)), Fill::solid(Color::WHITE)), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); @@ -187,7 +187,7 @@ impl OverlayRenderer { let operation = Operation::AddEllipse { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Fill::solid(Color::WHITE)), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 2.0)), Fill::solid(Color::WHITE)), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); @@ -212,7 +212,7 @@ impl OverlayRenderer { let operation = Operation::AddLine { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None), insert_index: -1, }; responses.add_front(DocumentMessage::Overlays(operation.into())); @@ -327,8 +327,8 @@ impl OverlayRenderer { /// Sets the overlay style for this point. fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque) { // TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar - let selected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT)); - let deselected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE)); + let selected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT)); + let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE)); let selected_shape_state = state.get(layer_path); // Update if the manipulator points are shown as selected // Here the index is important, even though overlays[..] has five elements we only care about the first three diff --git a/editor/src/messages/tool/common_functionality/path_outline.rs b/editor/src/messages/tool/common_functionality/path_outline.rs index ad1fade2..e25584db 100644 --- a/editor/src/messages/tool/common_functionality/path_outline.rs +++ b/editor/src/messages/tool/common_functionality/path_outline.rs @@ -46,7 +46,7 @@ impl PathOutline { let operation = Operation::AddShape { path: overlay_path.clone(), subpath: Default::default(), - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), PATH_OUTLINE_WEIGHT)), Fill::None), insert_index: -1, transform: DAffine2::IDENTITY.to_cols_array(), }; diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index d2259b37..6e545893 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -112,7 +112,7 @@ impl Pivot { path: layer_paths[0].clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new( - Some(style::Stroke::new(COLOR_ACCENT, PIVOT_OUTER_OUTLINE_THICKNESS)), + Some(style::Stroke::new(Some(COLOR_ACCENT), PIVOT_OUTER_OUTLINE_THICKNESS)), style::Fill::Solid(graphene_core::raster::color::Color::WHITE), ), insert_index: -1, diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 55a48c14..a11310f3 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -34,7 +34,7 @@ impl SnapOverlays { Operation::AddLine { path: layer_path.clone(), transform, - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), style::Fill::None), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), style::Fill::None), insert_index: -1, } } else { diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 1b068d21..54e931c0 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -156,7 +156,7 @@ pub fn add_bounding_box(responses: &mut VecDeque) -> Vec { let operation = Operation::AddRect { path: path.clone(), transform: DAffine2::ZERO.to_cols_array(), - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); @@ -175,7 +175,7 @@ fn add_transform_handles(responses: &mut VecDeque) -> [Vec; 8] let operation = Operation::AddRect { path: current_path.clone(), transform: DAffine2::ZERO.to_cols_array(), - style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Fill::solid(Color::WHITE)), + style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 2.0)), Fill::solid(Color::WHITE)), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 1fd63387..2415014e 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -80,7 +80,7 @@ impl ToolTransition for ArtboardTool { EventToMessageMap { document_dirty: Some(ArtboardToolMessage::DocumentIsDirty.into()), tool_abort: Some(ArtboardToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 69135f31..c7e1f396 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -164,9 +164,8 @@ impl<'a> MessageHandler> for BrushTo impl ToolTransition for BrushTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(BrushToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index d0fb6f60..e28bf8d8 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -73,9 +73,8 @@ impl<'a> MessageHandler> for Ellipse impl ToolTransition for EllipseTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(EllipseToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index 48f24efc..fdc0a802 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -61,9 +61,9 @@ impl<'a> MessageHandler> for Eyedrop impl ToolTransition for EyedropperTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(EyedropperToolMessage::Abort.into()), - selection_changed: None, + working_color_changed: Some(EyedropperToolMessage::PointerMove.into()), + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 4452df88..2f953d33 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -59,9 +59,8 @@ impl<'a> MessageHandler> for FillToo impl ToolTransition for FillTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(FillToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/frame_tool.rs b/editor/src/messages/tool/tool_messages/frame_tool.rs index ae3de04e..acacbbcd 100644 --- a/editor/src/messages/tool/tool_messages/frame_tool.rs +++ b/editor/src/messages/tool/tool_messages/frame_tool.rs @@ -73,9 +73,8 @@ impl ToolMetadata for FrameTool { impl ToolTransition for FrameTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(FrameToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 64f9e2a7..1cac7f63 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -116,9 +116,8 @@ impl<'a> MessageHandler> for Freehan impl ToolTransition for FreehandTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(FreehandToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } @@ -224,6 +223,6 @@ fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses responses.add(GraphOperationMessage::StrokeSet { layer: layer_path, - stroke: Stroke::new(tool_data.primary_color, data.weight), + stroke: Stroke::new(Some(tool_data.primary_color), data.weight), }); } diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index c8067837..8869b095 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -160,7 +160,7 @@ impl GradientOverlay { let operation = Operation::AddEllipse { path: path.clone(), transform: DAffine2::from_scale_angle_translation(size, 0., translation - size / 2.).to_cols_array(), - style: PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), fill), + style: PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), fill), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); @@ -179,7 +179,7 @@ impl GradientOverlay { let operation = Operation::AddLine { path: path.clone(), transform, - style: PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None), + style: PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); @@ -371,6 +371,7 @@ impl ToolTransition for GradientTool { document_dirty: Some(GradientToolMessage::DocumentIsDirty.into()), tool_abort: Some(GradientToolMessage::Abort.into()), selection_changed: Some(GradientToolMessage::DocumentIsDirty.into()), + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index 4f68e7fb..7643452a 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -73,9 +73,8 @@ impl ToolMetadata for ImaginateTool { impl ToolTransition for ImaginateTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(ImaginateToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 1ea9ec35..2b24d08e 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -105,9 +105,8 @@ impl<'a> MessageHandler> for LineToo impl ToolTransition for LineTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(LineToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } @@ -165,7 +164,7 @@ impl Fsm for LineToolFsmState { graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); responses.add(GraphOperationMessage::StrokeSet { layer: layer_path, - stroke: Stroke::new(global_tool_data.primary_color, tool_options.line_weight), + stroke: Stroke::new(Some(global_tool_data.primary_color), tool_options.line_weight), }); tool_data.weight = tool_options.line_weight; diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 6f8c34f2..36514aff 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -76,9 +76,8 @@ impl<'a> MessageHandler> for Navigat impl ToolTransition for NavigateTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(NavigateToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 7c6a9d64..d0978d00 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -99,6 +99,7 @@ impl ToolTransition for PathTool { document_dirty: Some(PathToolMessage::DocumentIsDirty.into()), tool_abort: Some(PathToolMessage::Abort.into()), selection_changed: Some(PathToolMessage::SelectionChanged.into()), + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index c8db9f61..0eef81d3 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -2,6 +2,8 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widget_prelude::{ColorInput, IconButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextLabel, WidgetHolder}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; @@ -14,7 +16,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::LayerId; use graphene_core::uuid::ManipulatorGroupId; -use graphene_core::vector::style::Stroke; +use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::{ManipulatorPointId, SelectedType}; use graphene_core::Color; @@ -28,13 +30,53 @@ pub struct PenTool { options: PenOptions, } +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] +pub enum PenColorType { + Primary, + Secondary, + Custom, +} + +pub struct PenColorOptions { + color: Option, + primary_working_color: Option, + secondary_working_color: Option, + color_type: PenColorType, +} + +impl PenColorOptions { + pub fn active_color(&self) -> Option { + match self.color_type { + PenColorType::Custom => self.color, + PenColorType::Primary => self.primary_working_color, + PenColorType::Secondary => self.secondary_working_color, + } + } +} + pub struct PenOptions { line_weight: f64, + fill: PenColorOptions, + stroke: PenColorOptions, } impl Default for PenOptions { fn default() -> Self { - Self { line_weight: 5. } + Self { + line_weight: 5., + fill: PenColorOptions { + color: Some(Color::BLACK), + primary_working_color: Some(Color::BLACK), + secondary_working_color: Some(Color::WHITE), + color_type: PenColorType::Primary, + }, + stroke: PenColorOptions { + color: None, + primary_working_color: Some(Color::BLACK), + secondary_working_color: Some(Color::WHITE), + color_type: PenColorType::Custom, + }, + } } } @@ -49,6 +91,8 @@ pub enum PenToolMessage { Abort, #[remain::unsorted] SelectionChanged, + #[remain::unsorted] + WorkingColorChanged, // Tool-specific messages Confirm, @@ -74,7 +118,13 @@ enum PenToolFsmState { #[remain::sorted] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] pub enum PenOptionsUpdate { + FillColor(Option), + FillColorType(PenColorType), LineWeight(f64), + PrimaryColor(Option), + SecondaryColor(Option), + StrokeColor(Option), + StrokeColorType(PenColorType), } impl ToolMetadata for PenTool { @@ -89,15 +139,102 @@ impl ToolMetadata for PenTool { } } +// TODO: Generalize create_fill_widget and create_stroke_widget into one function. +fn create_fill_widget(fill: &PenColorOptions) -> Vec { + let label = TextLabel::new("Fill").widget_holder(); + + let reset = IconButton::new("CloseX", 12) + .disabled(fill.color.is_none() && fill.color_type == PenColorType::Custom) + .on_update(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into()) + .tooltip("Clear color") + .widget_holder(); + + let entries = vec![ + ("WorkingColorsPrimary", "Primary Working Color", PenColorType::Primary), + ("WorkingColorsSecondary", "Secondary Working Color", PenColorType::Secondary), + ("Edit", "Custom Color", PenColorType::Custom), + ] + .into_iter() + .map(|(icon, tooltip, color_type)| { + RadioEntryData::new("") + .tooltip(tooltip) + .icon(icon) + .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()) + }) + .collect(); + let radio = RadioInput::new(entries).selected_index(fill.color_type.clone() as u32).widget_holder(); + + let color_input = ColorInput::new(fill.active_color()) + .on_update(|fill_color| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(fill_color.value)).into()) + .widget_holder(); + + vec![ + label, + WidgetHolder::related_separator(), + reset, + WidgetHolder::related_separator(), + radio, + WidgetHolder::related_separator(), + color_input, + ] +} + +fn create_stroke_widget(stroke: &PenColorOptions) -> Vec { + let label = TextLabel::new("Stroke").widget_holder(); + + let reset = IconButton::new("CloseX", 12) + .disabled(stroke.color.is_none() && stroke.color_type == PenColorType::Custom) + .on_update(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into()) + .tooltip("Clear color") + .widget_holder(); + + let entries = vec![ + ("WorkingColorsPrimary", "Primary Working Color", PenColorType::Primary), + ("WorkingColorsSecondary", "Secondary Working Color", PenColorType::Secondary), + ("Edit", "Custom Color", PenColorType::Custom), + ] + .into_iter() + .map(|(icon, tooltip, color_type)| { + RadioEntryData::new("") + .tooltip(tooltip) + .icon(icon) + .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()) + }) + .collect(); + let radio = RadioInput::new(entries).selected_index(stroke.color_type.clone() as u32).widget_holder(); + + let color_input = ColorInput::new(stroke.active_color()) + .on_update(|stroke_color| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(stroke_color.value)).into()) + .widget_holder(); + + vec![ + label, + WidgetHolder::related_separator(), + reset, + WidgetHolder::related_separator(), + radio, + WidgetHolder::related_separator(), + color_input, + ] +} + +fn create_weight_widget(line_weight: f64) -> WidgetHolder { + NumberInput::new(Some(line_weight)) + .unit(" px") + .label("Weight") + .min(0.) + .on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) + .widget_holder() +} + impl PropertyHolder for PenTool { fn properties(&self) -> Layout { - let weight = NumberInput::new(Some(self.options.line_weight)) - .unit(" px") - .label("Weight") - .min(0.) - .on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) - .widget_holder(); - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) + let mut widgets = create_fill_widget(&self.options.fill); + widgets.push(Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder()); + widgets.append(&mut create_stroke_widget(&self.options.stroke)); + widgets.push(WidgetHolder::unrelated_separator()); + widgets.push(create_weight_widget(self.options.line_weight)); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) } } @@ -106,7 +243,31 @@ impl<'a> MessageHandler> for PenTool if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message { match action { PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + PenOptionsUpdate::FillColor(color) => { + self.options.fill.color = color; + self.options.fill.color_type = PenColorType::Custom; + } + PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + PenOptionsUpdate::StrokeColor(color) => { + self.options.stroke.color = color; + self.options.stroke.color_type = PenColorType::Custom; + } + PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + PenOptionsUpdate::PrimaryColor(color) => { + self.options.stroke.primary_working_color = color; + self.options.fill.primary_working_color = color; + } + PenOptionsUpdate::SecondaryColor(color) => { + self.options.stroke.secondary_working_color = color; + self.options.fill.secondary_working_color = color; + } } + + responses.add(LayoutMessage::SendLayout { + layout: self.properties(), + layout_target: LayoutTarget::ToolOptions, + }); + return; } @@ -139,6 +300,7 @@ impl ToolTransition for PenTool { document_dirty: Some(PenToolMessage::DocumentIsDirty.into()), tool_abort: Some(PenToolMessage::Abort.into()), selection_changed: Some(PenToolMessage::SelectionChanged.into()), + working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()), } } } @@ -177,7 +339,16 @@ impl PenToolData { }, }); } - fn create_new_path(&mut self, document: &DocumentMessageHandler, line_weight: f64, color: Color, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + + fn create_new_path( + &mut self, + document: &DocumentMessageHandler, + line_weight: f64, + stroke_color: Option, + fill_color: Option, + input: &InputPreprocessorMessageHandler, + responses: &mut VecDeque, + ) { // Deselect layers because we are now creating a new layer responses.add(DocumentMessage::DeselectAllLayers); @@ -192,18 +363,24 @@ impl PenToolData { // Create the initial shape with a `bez_path` (only contains a moveto initially) let subpath = bezier_rs::Subpath::new(vec![bezier_rs::ManipulatorGroup::new(start_position, Some(start_position), Some(start_position))], false); graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + + responses.add(GraphOperationMessage::FillSet { + layer: layer_path.clone(), + fill: if fill_color.is_some() { Fill::Solid(fill_color.unwrap()) } else { Fill::None }, + }); + responses.add(GraphOperationMessage::StrokeSet { layer: layer_path.clone(), - stroke: Stroke::new(color, line_weight), + stroke: Stroke::new(stroke_color, line_weight), }); self.path = Some(layer_path); self.from_start = false; self.subpath_index = 0; } + + // TODO: tooltip / user documentation? /// If you place the anchor on top of the previous anchor then you break the mirror - /// - /// TODO: tooltip / user documentation? fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque) -> Option<()> { // Get subpath let layer_path = self.path.as_ref()?; @@ -523,6 +700,11 @@ impl Fsm for PenToolFsmState { } self } + (_, PenToolMessage::WorkingColorChanged) => { + responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::PrimaryColor(Some(global_tool_data.primary_color)))); + responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::SecondaryColor(Some(global_tool_data.secondary_color)))); + self + } (PenToolFsmState::Ready, PenToolMessage::DragStart) => { responses.add(DocumentMessage::StartTransaction); @@ -537,7 +719,14 @@ impl Fsm for PenToolFsmState { if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) { tool_data.extend_subpath(layer, subpath_index, from_start, document, responses); } else { - tool_data.create_new_path(document, tool_options.line_weight, global_tool_data.primary_color, input, responses); + tool_data.create_new_path( + document, + tool_options.line_weight, + tool_options.stroke.active_color(), + tool_options.fill.active_color(), + input, + responses, + ); } // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 9856206b..d4c112d6 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -72,9 +72,8 @@ impl ToolMetadata for RectangleTool { impl ToolTransition for RectangleTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(RectangleToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index d981f5f5..33c52cf0 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -287,6 +287,7 @@ impl ToolTransition for SelectTool { document_dirty: Some(SelectToolMessage::DocumentIsDirty.into()), tool_abort: Some(SelectToolMessage::Abort.into()), selection_changed: Some(SelectToolMessage::SelectionChanged.into()), + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index e84c71e5..30242424 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -110,9 +110,8 @@ impl<'a> MessageHandler> for ShapeTo impl ToolTransition for ShapeTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(ShapeToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index ba74278b..2bb7f70f 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -122,9 +122,8 @@ impl<'a> MessageHandler> for SplineT impl ToolTransition for SplineTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { - document_dirty: None, tool_abort: Some(SplineToolMessage::Abort.into()), - selection_changed: None, + ..Default::default() } } } @@ -270,6 +269,6 @@ fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, s responses.add(GraphOperationMessage::StrokeSet { layer: layer_path.clone(), - stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight), + stroke: Stroke::new(Some(global_tool_data.primary_color), tool_data.weight), }); } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index c92559bd..bc9bab82 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -172,6 +172,7 @@ impl ToolTransition for TextTool { document_dirty: Some(TextToolMessage::DocumentIsDirty.into()), tool_abort: Some(TextToolMessage::Abort.into()), selection_changed: Some(TextToolMessage::DocumentIsDirty.into()), + ..Default::default() } } } @@ -370,7 +371,7 @@ fn resize_overlays(overlays: &mut Vec>, responses: &mut VecDeque, pub selection_changed: Option, pub tool_abort: Option, + pub working_color_changed: Option, } pub trait ToolTransition { @@ -197,6 +200,7 @@ pub trait ToolTransition { subscribe_message(event_to_tool_map.document_dirty, BroadcastEvent::DocumentIsDirty); subscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort); subscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged); + subscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged); } fn deactivate(&self, responses: &mut VecDeque) { @@ -213,6 +217,7 @@ pub trait ToolTransition { unsubscribe_message(event_to_tool_map.document_dirty, BroadcastEvent::DocumentIsDirty); unsubscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort); unsubscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged); + unsubscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged); } } diff --git a/frontend/assets/icon-12px-solid/edit.svg b/frontend/assets/icon-12px-solid/edit-12px.svg similarity index 100% rename from frontend/assets/icon-12px-solid/edit.svg rename to frontend/assets/icon-12px-solid/edit-12px.svg diff --git a/frontend/assets/icon-12px-solid/reset-colors.svg b/frontend/assets/icon-12px-solid/working-colors.svg similarity index 100% rename from frontend/assets/icon-12px-solid/reset-colors.svg rename to frontend/assets/icon-12px-solid/working-colors.svg diff --git a/frontend/assets/icon-16px-solid/edit.svg b/frontend/assets/icon-16px-solid/edit.svg new file mode 100644 index 00000000..92cf2d56 --- /dev/null +++ b/frontend/assets/icon-16px-solid/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icon-16px-solid/working-colors-primary.svg b/frontend/assets/icon-16px-solid/working-colors-primary.svg new file mode 100644 index 00000000..24707a96 --- /dev/null +++ b/frontend/assets/icon-16px-solid/working-colors-primary.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icon-16px-solid/working-colors-secondary.svg b/frontend/assets/icon-16px-solid/working-colors-secondary.svg new file mode 100644 index 00000000..2a69afac --- /dev/null +++ b/frontend/assets/icon-16px-solid/working-colors-secondary.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/components/widgets/inputs/ColorInput.svelte b/frontend/src/components/widgets/inputs/ColorInput.svelte index fa430ca1..fd347174 100644 --- a/frontend/src/components/widgets/inputs/ColorInput.svelte +++ b/frontend/src/components/widgets/inputs/ColorInput.svelte @@ -56,6 +56,7 @@ border: 1px solid var(--color-5-dullgray); border-radius: 2px; padding: 1px; + min-width: 80px; > button { position: relative; diff --git a/frontend/src/utility-functions/icons.ts b/frontend/src/utility-functions/icons.ts index 93fb1adb..f577450c 100644 --- a/frontend/src/utility-functions/icons.ts +++ b/frontend/src/utility-functions/icons.ts @@ -12,7 +12,7 @@ import Add from "@graphite-frontend/assets/icon-12px-solid/add.svg"; import Checkmark from "@graphite-frontend/assets/icon-12px-solid/checkmark.svg"; import CloseX from "@graphite-frontend/assets/icon-12px-solid/close-x.svg"; import DropdownArrow from "@graphite-frontend/assets/icon-12px-solid/dropdown-arrow.svg"; -import Edit from "@graphite-frontend/assets/icon-12px-solid/edit.svg"; +import Edit12px from "@graphite-frontend/assets/icon-12px-solid/edit-12px.svg"; import Empty12px from "@graphite-frontend/assets/icon-12px-solid/empty-12px.svg"; import FullscreenEnter from "@graphite-frontend/assets/icon-12px-solid/fullscreen-enter.svg"; import FullscreenExit from "@graphite-frontend/assets/icon-12px-solid/fullscreen-exit.svg"; @@ -33,7 +33,6 @@ import KeyboardTab from "@graphite-frontend/assets/icon-12px-solid/keyboard-tab. import Link from "@graphite-frontend/assets/icon-12px-solid/link.svg"; import Overlays from "@graphite-frontend/assets/icon-12px-solid/overlays.svg"; import Remove from "@graphite-frontend/assets/icon-12px-solid/remove.svg"; -import ResetColors from "@graphite-frontend/assets/icon-12px-solid/reset-colors.svg"; import Snapping from "@graphite-frontend/assets/icon-12px-solid/snapping.svg"; import Swap from "@graphite-frontend/assets/icon-12px-solid/swap.svg"; import VerticalEllipsis from "@graphite-frontend/assets/icon-12px-solid/vertical-ellipsis.svg"; @@ -42,13 +41,14 @@ import WindowButtonWinClose from "@graphite-frontend/assets/icon-12px-solid/wind import WindowButtonWinMaximize from "@graphite-frontend/assets/icon-12px-solid/window-button-win-maximize.svg"; import WindowButtonWinMinimize from "@graphite-frontend/assets/icon-12px-solid/window-button-win-minimize.svg"; import WindowButtonWinRestoreDown from "@graphite-frontend/assets/icon-12px-solid/window-button-win-restore-down.svg"; +import WorkingColors from "@graphite-frontend/assets/icon-12px-solid/working-colors.svg"; const SOLID_12PX = { Add: { svg: Add, size: 12 }, Checkmark: { svg: Checkmark, size: 12 }, CloseX: { svg: CloseX, size: 12 }, DropdownArrow: { svg: DropdownArrow, size: 12 }, - Edit: { svg: Edit, size: 12 }, + Edit12px: { svg: Edit12px, size: 12 }, Empty12px: { svg: Empty12px, size: 12 }, FullscreenEnter: { svg: FullscreenEnter, size: 12 }, FullscreenExit: { svg: FullscreenExit, size: 12 }, @@ -69,7 +69,6 @@ const SOLID_12PX = { Link: { svg: Link, size: 12 }, Overlays: { svg: Overlays, size: 12 }, Remove: { svg: Remove, size: 12 }, - ResetColors: { svg: ResetColors, size: 12 }, Snapping: { svg: Snapping, size: 12 }, Swap: { svg: Swap, size: 12 }, VerticalEllipsis: { svg: VerticalEllipsis, size: 12 }, @@ -78,6 +77,7 @@ const SOLID_12PX = { WindowButtonWinMaximize: { svg: WindowButtonWinMaximize, size: 12 }, WindowButtonWinMinimize: { svg: WindowButtonWinMinimize, size: 12 }, WindowButtonWinRestoreDown: { svg: WindowButtonWinRestoreDown, size: 12 }, + WorkingColors: { svg: WorkingColors, size: 12 }, } as const; // 16px Solid @@ -95,6 +95,7 @@ import BooleanUnion from "@graphite-frontend/assets/icon-16px-solid/boolean-unio import CheckboxChecked from "@graphite-frontend/assets/icon-16px-solid/checkbox-checked.svg"; import CheckboxUnchecked from "@graphite-frontend/assets/icon-16px-solid/checkbox-unchecked.svg"; import Copy from "@graphite-frontend/assets/icon-16px-solid/copy.svg"; +import Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg"; import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg"; import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.svg"; import EyeVisible from "@graphite-frontend/assets/icon-16px-solid/eye-visible.svg"; @@ -133,6 +134,8 @@ import ViewModePixels from "@graphite-frontend/assets/icon-16px-solid/view-mode- import ViewportDesignMode from "@graphite-frontend/assets/icon-16px-solid/viewport-design-mode.svg"; import ViewportGuideMode from "@graphite-frontend/assets/icon-16px-solid/viewport-guide-mode.svg"; import ViewportSelectMode from "@graphite-frontend/assets/icon-16px-solid/viewport-select-mode.svg"; +import WorkingColorsPrimary from "@graphite-frontend/assets/icon-16px-solid/working-colors-primary.svg"; +import WorkingColorsSecondary from "@graphite-frontend/assets/icon-16px-solid/working-colors-secondary.svg"; import ZoomIn from "@graphite-frontend/assets/icon-16px-solid/zoom-in.svg"; import ZoomOut from "@graphite-frontend/assets/icon-16px-solid/zoom-out.svg"; import ZoomReset from "@graphite-frontend/assets/icon-16px-solid/zoom-reset.svg"; @@ -152,6 +155,7 @@ const SOLID_16PX = { CheckboxChecked: { svg: CheckboxChecked, size: 16 }, CheckboxUnchecked: { svg: CheckboxUnchecked, size: 16 }, Copy: { svg: Copy, size: 16 }, + Edit: { svg: Edit, size: 16 }, Eyedropper: { svg: Eyedropper, size: 16 }, EyeHidden: { svg: EyeHidden, size: 16 }, EyeVisible: { svg: EyeVisible, size: 16 }, @@ -190,6 +194,8 @@ const SOLID_16PX = { ViewportDesignMode: { svg: ViewportDesignMode, size: 16 }, ViewportGuideMode: { svg: ViewportGuideMode, size: 16 }, ViewportSelectMode: { svg: ViewportSelectMode, size: 16 }, + WorkingColorsPrimary: { svg: WorkingColorsPrimary, size: 16 }, + WorkingColorsSecondary: { svg: WorkingColorsSecondary, size: 16 }, ZoomIn: { svg: ZoomIn, size: 16 }, ZoomOut: { svg: ZoomOut, size: 16 }, ZoomReset: { svg: ZoomReset, size: 16 }, diff --git a/node-graph/gcore/src/vector/style.rs b/node-graph/gcore/src/vector/style.rs index 0183e386..658cb41c 100644 --- a/node-graph/gcore/src/vector/style.rs +++ b/node-graph/gcore/src/vector/style.rs @@ -281,9 +281,9 @@ impl core::hash::Hash for Stroke { } impl Stroke { - pub const fn new(color: Color, weight: f64) -> Self { + pub const fn new(color: Option, weight: f64) -> Self { Self { - color: Some(color), + color, weight, dash_lengths: Vec::new(), dash_offset: 0., @@ -439,7 +439,7 @@ impl PathStyle { /// ``` /// # use graphene_core::vector::style::{Fill, Stroke, PathStyle}; /// # use graphene_core::raster::color::Color; - /// let stroke = Stroke::new(Color::GREEN, 42.); + /// let stroke = Stroke::new(Some(Color::GREEN), 42.); /// let style = PathStyle::new(Some(stroke.clone()), Fill::None); /// /// assert_eq!(style.stroke(), Some(stroke)); @@ -477,7 +477,7 @@ impl PathStyle { /// /// assert_eq!(style.stroke(), None); /// - /// let stroke = Stroke::new(Color::GREEN, 42.); + /// let stroke = Stroke::new(Some(Color::GREEN), 42.); /// style.set_stroke(stroke.clone()); /// /// assert_eq!(style.stroke(), Some(stroke)); @@ -510,7 +510,7 @@ impl PathStyle { /// ``` /// # use graphene_core::vector::style::{Fill, Stroke, PathStyle}; /// # use graphene_core::raster::color::Color; - /// let mut style = PathStyle::new(Some(Stroke::new(Color::GREEN, 42.)), Fill::None); + /// let mut style = PathStyle::new(Some(Stroke::new(Some(Color::GREEN), 42.)), Fill::None); /// /// assert!(style.stroke().is_some()); /// @@ -528,7 +528,7 @@ impl PathStyle { (_, fill) => fill.render(svg_defs, multiplied_transform, bounds, transformed_bounds), }; let stroke_attribute = match (view_mode, &self.stroke) { - (ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT).render(), + (ViewMode::Outline, _) => Stroke::new(Some(LAYER_OUTLINE_STROKE_COLOR), LAYER_OUTLINE_STROKE_WEIGHT).render(), (_, Some(stroke)) => stroke.render(), (_, None) => String::new(), }; diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index e30af091..734986d3 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -23,7 +23,7 @@ impl VectorData { Self { subpaths: Vec::new(), transform: DAffine2::IDENTITY, - style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::None), + style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None), mirror_angle: Vec::new(), } }