From ea2a54dc7d4fd00321bf4b90bb1b8bb971b580ee Mon Sep 17 00:00:00 2001 From: Timon Date: Sat, 29 Aug 2026 15:22:11 +0000 Subject: [PATCH 1/2] Add an Autoscale option to the Brush tool --- .../messages/tool/tool_messages/brush_tool.rs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index c2c19d71d7..48b438acbc 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -6,6 +6,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, OutputConnector}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, selection_changed_since_last_sync, solid}; +use crate::messages::tool::common_functionality::resize::viewport_zoom; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; @@ -30,6 +31,7 @@ pub struct BrushOptions { hardness: f64, flow: f64, color: ToolColorOptions, + autoscale: bool, last_synced_selection: Vec, } @@ -40,6 +42,7 @@ impl Default for BrushOptions { hardness: BRUSH_HARDNESS_DEFAULT, flow: BRUSH_FLOW_DEFAULT, color: ToolColorOptions::default(), + autoscale: false, last_synced_selection: Vec::new(), } } @@ -49,6 +52,10 @@ impl BrushOptions { fn active_color(&self) -> Color { self.color.active_color().unwrap_or_default() } + + fn stroke_diameter(&self, document: &DocumentMessageHandler) -> f64 { + if self.autoscale { self.diameter / viewport_zoom(document) } else { self.diameter } + } } #[impl_message(Message, ToolMessage, Brush)] @@ -75,6 +82,7 @@ pub enum BrushToolMessageOptionsUpdate { Diameter(f64), Hardness(f64), Flow(f64), + Autoscale(bool), WorkingColorsChanged, } @@ -99,6 +107,8 @@ impl ToolMetadata for BrushTool { impl LayoutHolder for BrushTool { fn layout(&self) -> Layout { + let autoscale_id = CheckboxId::new(); + let autoscale_description = "Automatically scale the brush with viewport zoom."; let widgets = vec![ ColorInput::new(FillChoice::::from(self.options.color.fill_choice.as_ref().unwrap_or(&FillChoice::None))) .mixed(self.options.color.fill_choice.is_none()) @@ -150,6 +160,24 @@ impl LayoutHolder for BrushTool { .into() }) .widget_instance(), + Separator::new(SeparatorStyle::Unrelated).widget_instance(), + CheckboxInput::new(self.options.autoscale) + .tooltip_label("Autoscale") + .tooltip_description(autoscale_description) + .for_label(autoscale_id) + .on_update(|checkbox_input: &CheckboxInput| { + BrushToolMessage::UpdateOptions { + options: BrushToolMessageOptionsUpdate::Autoscale(checkbox_input.checked), + } + .into() + }) + .widget_instance(), + Separator::new(SeparatorStyle::Related).widget_instance(), + TextLabel::new("Autoscale") + .tooltip_label("Autoscale") + .tooltip_description(autoscale_description) + .for_checkbox(autoscale_id) + .widget_instance(), ]; Layout(vec![LayoutGroup::row(widgets)]) @@ -191,6 +219,7 @@ impl<'a> MessageHandler> for Brus responses.add(ToolMessage::SelectWorkingColor { color, primary: true }); } } + BrushToolMessageOptionsUpdate::Autoscale(autoscale) => self.options.autoscale = autoscale, BrushToolMessageOptionsUpdate::WorkingColorsChanged => { self.options.color.fill_choice = Some(solid(context.global_tool_data.primary_color)); } @@ -235,7 +264,7 @@ impl BrushTool { }; let value = |index: usize| node.inputs.get(index).and_then(|input| input.as_value()); if let Some(TaggedValue::F64(diameter)) = value(brush_strokes::DiameterInput::INDEX) { - self.options.diameter = *diameter; + self.options.diameter = if self.options.autoscale { *diameter * viewport_zoom(document) } else { *diameter }; } if let Some(TaggedValue::F64(hardness)) = value(brush_strokes::HardnessInput::INDEX) { self.options.hardness = *hardness; @@ -352,7 +381,7 @@ impl BrushToolData { }; let value = |index: usize| node.inputs.get(index).and_then(|input| input.as_value()); matches!(value(brush_strokes::ColorInput::INDEX), Some(TaggedValue::Color(color)) if *color == options.active_color()) - && matches!(value(brush_strokes::DiameterInput::INDEX), Some(TaggedValue::F64(diameter)) if *diameter == options.diameter) + && matches!(value(brush_strokes::DiameterInput::INDEX), Some(TaggedValue::F64(diameter)) if *diameter == options.stroke_diameter(document)) && matches!(value(brush_strokes::HardnessInput::INDEX), Some(TaggedValue::F64(hardness)) if *hardness == options.hardness) && matches!(value(brush_strokes::FlowInput::INDEX), Some(TaggedValue::F64(flow)) if *flow == options.flow) } @@ -444,7 +473,7 @@ impl Fsm for BrushToolFsmState { parent, insert_index, color: tool_options.active_color(), - diameter: tool_options.diameter, + diameter: tool_options.stroke_diameter(document), hardness: tool_options.hardness, flow: tool_options.flow, }); @@ -458,7 +487,7 @@ impl Fsm for BrushToolFsmState { layer, strokes_node_id, color: tool_options.active_color(), - diameter: tool_options.diameter, + diameter: tool_options.stroke_diameter(document), hardness: tool_options.hardness, flow: tool_options.flow, }); From 3a92214ebc67273b8b172df21df94cc53676df2a Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 30 Aug 2026 14:58:34 +0000 Subject: [PATCH 2/2] Put Autoscale option in a Diameter popover --- .../messages/tool/tool_messages/brush_tool.rs | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 48b438acbc..ef819d4757 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -107,7 +107,6 @@ impl ToolMetadata for BrushTool { impl LayoutHolder for BrushTool { fn layout(&self) -> Layout { - let autoscale_id = CheckboxId::new(); let autoscale_description = "Automatically scale the brush with viewport zoom."; let widgets = vec![ ColorInput::new(FillChoice::::from(self.options.color.fill_choice.as_ref().unwrap_or(&FillChoice::None))) @@ -132,6 +131,30 @@ impl LayoutHolder for BrushTool { .into() }) .widget_instance(), + PopoverButton::new() + .popover_layout(Layout(vec![LayoutGroup::row({ + let checkbox_id = CheckboxId::new(); + vec![ + CheckboxInput::new(self.options.autoscale) + .tooltip_label("Autoscale") + .tooltip_description(autoscale_description) + .for_label(checkbox_id) + .on_update(|checkbox_input: &CheckboxInput| { + BrushToolMessage::UpdateOptions { + options: BrushToolMessageOptionsUpdate::Autoscale(checkbox_input.checked), + } + .into() + }) + .widget_instance(), + TextLabel::new("Autoscale") + .tooltip_label("Autoscale") + .tooltip_description(autoscale_description) + .for_checkbox(checkbox_id) + .widget_instance(), + ] + })])) + .tooltip_label("Diameter Options") + .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), NumberInput::new(Some(self.options.hardness)) .label("Hardness") @@ -160,24 +183,6 @@ impl LayoutHolder for BrushTool { .into() }) .widget_instance(), - Separator::new(SeparatorStyle::Unrelated).widget_instance(), - CheckboxInput::new(self.options.autoscale) - .tooltip_label("Autoscale") - .tooltip_description(autoscale_description) - .for_label(autoscale_id) - .on_update(|checkbox_input: &CheckboxInput| { - BrushToolMessage::UpdateOptions { - options: BrushToolMessageOptionsUpdate::Autoscale(checkbox_input.checked), - } - .into() - }) - .widget_instance(), - Separator::new(SeparatorStyle::Related).widget_instance(), - TextLabel::new("Autoscale") - .tooltip_label("Autoscale") - .tooltip_description(autoscale_description) - .for_checkbox(autoscale_id) - .widget_instance(), ]; Layout(vec![LayoutGroup::row(widgets)]) @@ -381,8 +386,10 @@ impl BrushToolData { }; let value = |index: usize| node.inputs.get(index).and_then(|input| input.as_value()); matches!(value(brush_strokes::ColorInput::INDEX), Some(TaggedValue::Color(color)) if *color == options.active_color()) - && matches!(value(brush_strokes::DiameterInput::INDEX), Some(TaggedValue::F64(diameter)) if *diameter == options.stroke_diameter(document)) - && matches!(value(brush_strokes::HardnessInput::INDEX), Some(TaggedValue::F64(hardness)) if *hardness == options.hardness) + && matches!(value(brush_strokes::DiameterInput::INDEX), Some(TaggedValue::F64(diameter)) if { + let target = options.stroke_diameter(document); + (*diameter - target).abs() <= diameter.abs().max(target.abs()) * (f64::EPSILON * 8.) + }) && matches!(value(brush_strokes::HardnessInput::INDEX), Some(TaggedValue::F64(hardness)) if *hardness == options.hardness) && matches!(value(brush_strokes::FlowInput::INDEX), Some(TaggedValue::F64(flow)) if *flow == options.flow) }