From 2c9d8c15892d07f94e13cd48af4d8808ed0e9dbf Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Fri, 6 Mar 2026 07:22:50 -0500 Subject: [PATCH] Add tool skeletons --- .../lightningbeam-core/src/tool.rs | 173 ++++++++++++++---- .../lightningbeam-editor/assets/layouts.json | 2 +- .../lightningbeam-editor/src/custom_cursor.rs | 51 ++++-- .../lightningbeam-editor/src/keymap.rs | 38 ++-- .../lightningbeam-editor/src/main.rs | 23 ++- .../src/panes/infopanel.rs | 12 +- .../lightningbeam-editor/src/panes/mod.rs | 2 + .../lightningbeam-editor/src/panes/stage.rs | 15 +- .../lightningbeam-editor/src/panes/toolbar.rs | 55 +++++- src/assets/todo.svg | 5 + 10 files changed, 298 insertions(+), 78 deletions(-) create mode 100644 src/assets/todo.svg diff --git a/lightningbeam-ui/lightningbeam-core/src/tool.rs b/lightningbeam-ui/lightningbeam-core/src/tool.rs index d70f9ea..abe8ad9 100644 --- a/lightningbeam-ui/lightningbeam-core/src/tool.rs +++ b/lightningbeam-ui/lightningbeam-core/src/tool.rs @@ -11,9 +11,10 @@ use vello::kurbo::Point; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Tool { + // ── Vector / shared tools ────────────────────────────────────────────── /// Selection tool - select and move objects Select, - /// Draw/Pen tool - freehand drawing + /// Draw/Brush tool - freehand drawing (vector) / paintbrush (raster) Draw, /// Transform tool - scale, rotate, skew Transform, @@ -37,12 +38,48 @@ pub enum Tool { RegionSelect, /// Split tool - split audio/video clips at a point Split, + // ── Raster brush tools ──────────────────────────────────────────────── + /// Pencil tool - hard-edged raster brush + Pencil, + /// Pen tool - pressure-sensitive raster pen + Pen, + /// Airbrush tool - soft spray raster brush + Airbrush, /// Erase tool - erase raster pixels Erase, /// Smudge tool - smudge/blend raster pixels Smudge, - /// Lasso select tool - freehand selection on raster layers + /// Clone Stamp - copy pixels from a source point + CloneStamp, + /// Healing Brush - content-aware pixel repair + HealingBrush, + /// Pattern Stamp - paint with a repeating pattern + PatternStamp, + /// Dodge/Burn - lighten or darken pixels + DodgeBurn, + /// Sponge - saturate or desaturate pixels + Sponge, + /// Blur/Sharpen - blur or sharpen pixel regions + BlurSharpen, + // ── Raster fill / shape ─────────────────────────────────────────────── + /// Gradient tool - fill with a gradient + Gradient, + /// Custom Shape tool - draw from a shape library + CustomShape, + // ── Raster selection tools ──────────────────────────────────────────── + /// Elliptical marquee selection + SelectEllipse, + /// Lasso select tool - freehand / polygonal / magnetic selection SelectLasso, + /// Magic Wand - select by colour similarity + MagicWand, + /// Quick Select - brush-based smart selection + QuickSelect, + // ── Raster transform tools ──────────────────────────────────────────── + /// Warp / perspective transform + Warp, + /// Liquify - freeform pixel warping + Liquify, } /// Region select mode @@ -60,6 +97,23 @@ impl Default for RegionSelectMode { } } +/// Lasso selection sub-mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum LassoMode { + /// Freehand lasso (existing, implemented) + Freehand, + /// Click-to-place polygonal lasso + Polygonal, + /// Magnetically snaps to edges + Magnetic, +} + +impl Default for LassoMode { + fn default() -> Self { + Self::Freehand + } +} + /// Tool state tracking for interactive operations #[derive(Debug, Clone)] pub enum ToolState { @@ -229,44 +283,77 @@ impl Tool { /// Get display name for the tool pub fn display_name(self) -> &'static str { match self { - Tool::Select => "Select", - Tool::Draw => "Draw", - Tool::Transform => "Transform", - Tool::Rectangle => "Rectangle", - Tool::Ellipse => "Ellipse", - Tool::PaintBucket => "Paint Bucket", - Tool::Eyedropper => "Eyedropper", - Tool::Line => "Line", - Tool::Polygon => "Polygon", - Tool::BezierEdit => "Bezier Edit", - Tool::Text => "Text", - Tool::RegionSelect => "Region Select", - Tool::Split => "Split", - Tool::Erase => "Erase", - Tool::Smudge => "Smudge", - Tool::SelectLasso => "Lasso Select", + Tool::Select => "Select", + Tool::Draw => "Brush", + Tool::Transform => "Transform", + Tool::Rectangle => "Rectangle", + Tool::Ellipse => "Ellipse", + Tool::PaintBucket => "Paint Bucket", + Tool::Eyedropper => "Eyedropper", + Tool::Line => "Line", + Tool::Polygon => "Polygon", + Tool::BezierEdit => "Bezier Edit", + Tool::Text => "Text", + Tool::RegionSelect => "Region Select", + Tool::Split => "Split", + Tool::Pencil => "Pencil", + Tool::Pen => "Pen", + Tool::Airbrush => "Airbrush", + Tool::Erase => "Eraser", + Tool::Smudge => "Smudge", + Tool::CloneStamp => "Clone Stamp", + Tool::HealingBrush => "Healing Brush", + Tool::PatternStamp => "Pattern Stamp", + Tool::DodgeBurn => "Dodge / Burn", + Tool::Sponge => "Sponge", + Tool::BlurSharpen => "Blur / Sharpen", + Tool::Gradient => "Gradient", + Tool::CustomShape => "Custom Shape", + Tool::SelectEllipse => "Elliptical Select", + Tool::SelectLasso => "Lasso Select", + Tool::MagicWand => "Magic Wand", + Tool::QuickSelect => "Quick Select", + Tool::Warp => "Warp", + Tool::Liquify => "Liquify", } } /// Get SVG icon file name for the tool pub fn icon_file(self) -> &'static str { match self { - Tool::Select => "select.svg", - Tool::Draw => "draw.svg", - Tool::Transform => "transform.svg", - Tool::Rectangle => "rectangle.svg", - Tool::Ellipse => "ellipse.svg", - Tool::PaintBucket => "paint_bucket.svg", - Tool::Eyedropper => "eyedropper.svg", - Tool::Line => "line.svg", - Tool::Polygon => "polygon.svg", - Tool::BezierEdit => "bezier_edit.svg", - Tool::Text => "text.svg", - Tool::RegionSelect => "region_select.svg", - Tool::Split => "split.svg", - Tool::Erase => "erase.svg", - Tool::Smudge => "smudge.svg", - Tool::SelectLasso => "lasso.svg", + Tool::Select => "select.svg", + Tool::Draw => "draw.svg", + Tool::Transform => "transform.svg", + Tool::Rectangle => "rectangle.svg", + Tool::Ellipse => "ellipse.svg", + Tool::PaintBucket => "paint_bucket.svg", + Tool::Eyedropper => "eyedropper.svg", + Tool::Line => "line.svg", + Tool::Polygon => "polygon.svg", + Tool::BezierEdit => "bezier_edit.svg", + Tool::Text => "text.svg", + Tool::RegionSelect => "region_select.svg", + Tool::Split => "split.svg", + Tool::Erase => "erase.svg", + Tool::Smudge => "smudge.svg", + Tool::SelectLasso => "lasso.svg", + // Not yet implemented — use the placeholder icon + Tool::Pencil + | Tool::Pen + | Tool::Airbrush + | Tool::CloneStamp + | Tool::HealingBrush + | Tool::PatternStamp + | Tool::DodgeBurn + | Tool::Sponge + | Tool::BlurSharpen + | Tool::Gradient + | Tool::CustomShape + | Tool::SelectEllipse + | Tool::MagicWand + | Tool::QuickSelect + | Tool::Warp + | Tool::Liquify => "todo.svg", } } @@ -294,7 +381,23 @@ impl Tool { match layer_type { None | Some(LayerType::Vector) => Tool::all(), Some(LayerType::Audio) | Some(LayerType::Video) => &[Tool::Select, Tool::Split], - Some(LayerType::Raster) => &[Tool::Select, Tool::SelectLasso, Tool::Draw, Tool::Erase, Tool::Smudge, Tool::Eyedropper], + Some(LayerType::Raster) => &[ + // Brush tools + Tool::Draw, Tool::Pencil, Tool::Pen, Tool::Airbrush, + Tool::Erase, Tool::Smudge, + Tool::CloneStamp, Tool::HealingBrush, Tool::PatternStamp, + Tool::DodgeBurn, Tool::Sponge, Tool::BlurSharpen, + // Fill / shape + Tool::PaintBucket, Tool::Gradient, + Tool::Rectangle, Tool::Ellipse, Tool::Polygon, Tool::Line, Tool::CustomShape, + // Selection + Tool::Select, Tool::SelectEllipse, Tool::SelectLasso, + Tool::MagicWand, Tool::QuickSelect, + // Transform + Tool::Transform, Tool::Warp, Tool::Liquify, + // Utility + Tool::Eyedropper, + ], _ => &[Tool::Select], } } diff --git a/lightningbeam-ui/lightningbeam-editor/assets/layouts.json b/lightningbeam-ui/lightningbeam-editor/assets/layouts.json index 366cbf5..c1a211a 100644 --- a/lightningbeam-ui/lightningbeam-editor/assets/layouts.json +++ b/lightningbeam-ui/lightningbeam-editor/assets/layouts.json @@ -124,7 +124,7 @@ "children": [ { "type": "vertical-grid", - "percent": 30, + "percent": 67, "children": [ { "type": "pane", "name": "toolbar" }, { "type": "pane", "name": "infopanel" } diff --git a/lightningbeam-ui/lightningbeam-editor/src/custom_cursor.rs b/lightningbeam-ui/lightningbeam-editor/src/custom_cursor.rs index 8739fb0..c4ae419 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/custom_cursor.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/custom_cursor.rs @@ -32,22 +32,41 @@ impl CustomCursor { /// Convert a Tool enum to the corresponding custom cursor pub fn from_tool(tool: Tool) -> Self { match tool { - Tool::Select => CustomCursor::Select, - Tool::Draw => CustomCursor::Draw, - Tool::Transform => CustomCursor::Transform, - Tool::Rectangle => CustomCursor::Rectangle, - Tool::Ellipse => CustomCursor::Ellipse, - Tool::PaintBucket => CustomCursor::PaintBucket, - Tool::Eyedropper => CustomCursor::Eyedropper, - Tool::Line => CustomCursor::Line, - Tool::Polygon => CustomCursor::Polygon, - Tool::BezierEdit => CustomCursor::BezierEdit, - Tool::Text => CustomCursor::Text, - Tool::RegionSelect => CustomCursor::Select, // Reuse select cursor for now - Tool::Split => CustomCursor::Select, // Reuse select cursor for now - Tool::Erase => CustomCursor::Draw, // Reuse draw cursor for raster erase - Tool::Smudge => CustomCursor::Draw, // Reuse draw cursor for raster smudge - Tool::SelectLasso => CustomCursor::Select, // Reuse select cursor for lasso + Tool::Select => CustomCursor::Select, + Tool::Draw => CustomCursor::Draw, + Tool::Transform => CustomCursor::Transform, + Tool::Rectangle => CustomCursor::Rectangle, + Tool::Ellipse => CustomCursor::Ellipse, + Tool::PaintBucket => CustomCursor::PaintBucket, + Tool::Eyedropper => CustomCursor::Eyedropper, + Tool::Line => CustomCursor::Line, + Tool::Polygon => CustomCursor::Polygon, + Tool::BezierEdit => CustomCursor::BezierEdit, + Tool::Text => CustomCursor::Text, + Tool::RegionSelect => CustomCursor::Select, + Tool::Split => CustomCursor::Select, + Tool::Erase => CustomCursor::Draw, + Tool::Smudge => CustomCursor::Draw, + Tool::SelectLasso => CustomCursor::Select, + // Raster brush tools — use draw cursor until implemented + Tool::Pencil + | Tool::Pen + | Tool::Airbrush + | Tool::CloneStamp + | Tool::HealingBrush + | Tool::PatternStamp + | Tool::DodgeBurn + | Tool::Sponge + | Tool::BlurSharpen => CustomCursor::Draw, + // Selection tools — use select cursor until implemented + Tool::SelectEllipse + | Tool::MagicWand + | Tool::QuickSelect => CustomCursor::Select, + // Other tools — use select cursor until implemented + Tool::Gradient + | Tool::CustomShape + | Tool::Warp + | Tool::Liquify => CustomCursor::Select, } } diff --git a/lightningbeam-ui/lightningbeam-editor/src/keymap.rs b/lightningbeam-ui/lightningbeam-editor/src/keymap.rs index 77a9716..3f7f147 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/keymap.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/keymap.rs @@ -429,24 +429,26 @@ impl AppAction { /// `Tool::Split` has no tool-shortcut action (it's triggered via the menu). pub fn tool_app_action(tool: lightningbeam_core::tool::Tool) -> Option { use lightningbeam_core::tool::Tool; - Some(match tool { - Tool::Select => AppAction::ToolSelect, - Tool::Draw => AppAction::ToolDraw, - Tool::Transform => AppAction::ToolTransform, - Tool::Rectangle => AppAction::ToolRectangle, - Tool::Ellipse => AppAction::ToolEllipse, - Tool::PaintBucket => AppAction::ToolPaintBucket, - Tool::Eyedropper => AppAction::ToolEyedropper, - Tool::Line => AppAction::ToolLine, - Tool::Polygon => AppAction::ToolPolygon, - Tool::BezierEdit => AppAction::ToolBezierEdit, - Tool::Text => AppAction::ToolText, - Tool::RegionSelect => AppAction::ToolRegionSelect, - Tool::Erase => AppAction::ToolErase, - Tool::Smudge => AppAction::ToolSmudge, - Tool::SelectLasso => AppAction::ToolSelectLasso, - Tool::Split => AppAction::ToolSplit, - }) + match tool { + Tool::Select => Some(AppAction::ToolSelect), + Tool::Draw => Some(AppAction::ToolDraw), + Tool::Transform => Some(AppAction::ToolTransform), + Tool::Rectangle => Some(AppAction::ToolRectangle), + Tool::Ellipse => Some(AppAction::ToolEllipse), + Tool::PaintBucket => Some(AppAction::ToolPaintBucket), + Tool::Eyedropper => Some(AppAction::ToolEyedropper), + Tool::Line => Some(AppAction::ToolLine), + Tool::Polygon => Some(AppAction::ToolPolygon), + Tool::BezierEdit => Some(AppAction::ToolBezierEdit), + Tool::Text => Some(AppAction::ToolText), + Tool::RegionSelect => Some(AppAction::ToolRegionSelect), + Tool::Erase => Some(AppAction::ToolErase), + Tool::Smudge => Some(AppAction::ToolSmudge), + Tool::SelectLasso => Some(AppAction::ToolSelectLasso), + Tool::Split => Some(AppAction::ToolSplit), + // New tools have no keybinding yet + _ => None, + } } // === Default bindings === diff --git a/lightningbeam-ui/lightningbeam-editor/src/main.rs b/lightningbeam-ui/lightningbeam-editor/src/main.rs index aca75bf..966505e 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/main.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/main.rs @@ -332,6 +332,7 @@ mod tool_icons { pub static ERASE: &[u8] = include_bytes!("../../../src/assets/erase.svg"); pub static SMUDGE: &[u8] = include_bytes!("../../../src/assets/smudge.svg"); pub static LASSO: &[u8] = include_bytes!("../../../src/assets/lasso.svg"); + pub static TODO: &[u8] = include_bytes!("../../../src/assets/todo.svg"); } /// Embedded focus icon SVGs @@ -399,11 +400,28 @@ impl ToolIconCache { Tool::Polygon => tool_icons::POLYGON, Tool::BezierEdit => tool_icons::BEZIER_EDIT, Tool::Text => tool_icons::TEXT, - Tool::RegionSelect => tool_icons::SELECT, // Reuse select icon for now + Tool::RegionSelect => tool_icons::SELECT, Tool::Split => tool_icons::SPLIT, Tool::Erase => tool_icons::ERASE, Tool::Smudge => tool_icons::SMUDGE, Tool::SelectLasso => tool_icons::LASSO, + // Not yet implemented — use placeholder icon + Tool::Pencil + | Tool::Pen + | Tool::Airbrush + | Tool::CloneStamp + | Tool::HealingBrush + | Tool::PatternStamp + | Tool::DodgeBurn + | Tool::Sponge + | Tool::BlurSharpen + | Tool::Gradient + | Tool::CustomShape + | Tool::SelectEllipse + | Tool::MagicWand + | Tool::QuickSelect + | Tool::Warp + | Tool::Liquify => tool_icons::TODO, }; if let Some(texture) = rasterize_svg(svg_data, tool.icon_file(), 180, ctx) { self.icons.insert(tool, texture); @@ -856,6 +874,7 @@ struct EditorApp { // Region select state region_selection: Option, region_select_mode: lightningbeam_core::tool::RegionSelectMode, + lasso_mode: lightningbeam_core::tool::LassoMode, // VU meter levels input_level: f32, @@ -1127,6 +1146,7 @@ impl EditorApp { polygon_sides: 5, // Default to pentagon region_selection: None, region_select_mode: lightningbeam_core::tool::RegionSelectMode::default(), + lasso_mode: lightningbeam_core::tool::LassoMode::default(), input_level: 0.0, output_level: (0.0, 0.0), track_levels: HashMap::new(), @@ -5590,6 +5610,7 @@ impl eframe::App for EditorApp { script_saved: &mut self.script_saved, region_selection: &mut self.region_selection, region_select_mode: &mut self.region_select_mode, + lasso_mode: &mut self.lasso_mode, pending_graph_loads: &self.pending_graph_loads, clipboard_consumed: &mut clipboard_consumed, keymap: &self.keymap, diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs index 61e77ab..e3c1ee7 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/infopanel.rs @@ -169,7 +169,10 @@ impl InfopanelPane { .and_then(|id| shared.action_executor.document().get_layer(&id)) .map_or(false, |l| matches!(l, AnyLayer::Raster(_))); - let is_raster_paint_tool = active_is_raster && matches!(tool, Tool::Draw | Tool::Erase | Tool::Smudge); + let is_raster_paint_tool = active_is_raster && matches!( + tool, + Tool::Draw | Tool::Pencil | Tool::Pen | Tool::Airbrush | Tool::Erase | Tool::Smudge + ); // Only show tool options for tools that have options let is_vector_tool = !active_is_raster && matches!( @@ -319,7 +322,7 @@ impl InfopanelPane { } // Raster paint tools - Tool::Draw | Tool::Erase if is_raster_paint_tool => { + Tool::Draw | Tool::Pencil | Tool::Pen | Tool::Airbrush | Tool::Erase if is_raster_paint_tool => { self.render_raster_tool_options(ui, shared, matches!(tool, Tool::Erase)); } @@ -513,6 +516,11 @@ impl InfopanelPane { *shared.brush_hardness = s.hardness.clamp(0.0, 1.0); *shared.brush_spacing = s.dabs_per_radius; *shared.active_brush_settings = s.clone(); + // If the user was on a preset-backed tool (Pencil/Pen/Airbrush) + // and manually picked a different brush, revert to the generic tool. + if matches!(*shared.selected_tool, Tool::Pencil | Tool::Pen | Tool::Airbrush) { + *shared.selected_tool = Tool::Draw; + } } } } diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs index e1b9616..b248c96 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/mod.rs @@ -283,6 +283,8 @@ pub struct SharedPaneState<'a> { pub region_selection: &'a mut Option, /// Region select mode (Rectangle or Lasso) pub region_select_mode: &'a mut lightningbeam_core::tool::RegionSelectMode, + /// Lasso select sub-mode (Freehand / Polygonal / Magnetic) + pub lasso_mode: &'a mut lightningbeam_core::tool::LassoMode, /// Counter for in-flight graph preset loads — increment when sending a /// GraphLoadPreset command so the repaint loop stays alive until the /// audio thread sends GraphPresetLoaded back diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs index 0e2d97d..e3b7cfc 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/stage.rs @@ -7518,6 +7518,9 @@ impl StagePane { self.handle_draw_tool(ui, &response, world_pos, shared); } } + Tool::Pencil | Tool::Pen | Tool::Airbrush => { + self.handle_raster_stroke_tool(ui, &response, world_pos, lightningbeam_core::raster_layer::RasterBlendMode::Normal, shared); + } Tool::Erase => { self.handle_raster_stroke_tool(ui, &response, world_pos, lightningbeam_core::raster_layer::RasterBlendMode::Erase, shared); } @@ -8001,8 +8004,11 @@ impl StagePane { // Compute semi-axes (world pixels) and dab rotation angle. let (a_world, b_world, dab_angle_rad) = match *shared.selected_tool { - Tool::Erase => (*shared.eraser_radius, *shared.eraser_radius, 0.0_f32), - Tool::Smudge => (*shared.smudge_radius, *shared.smudge_radius, 0.0_f32), + Tool::Erase => (*shared.eraser_radius, *shared.eraser_radius, 0.0_f32), + Tool::Smudge + | Tool::BlurSharpen + | Tool::DodgeBurn + | Tool::Sponge => (*shared.smudge_radius, *shared.smudge_radius, 0.0_f32), _ => { let bs = &shared.active_brush_settings; let r = *shared.brush_radius; @@ -8637,7 +8643,10 @@ impl PaneRenderer for StagePane { use lightningbeam_core::tool::Tool; let is_raster_paint = matches!( *shared.selected_tool, - Tool::Draw | Tool::Erase | Tool::Smudge + Tool::Draw | Tool::Pencil | Tool::Pen | Tool::Airbrush + | Tool::Erase | Tool::Smudge + | Tool::CloneStamp | Tool::HealingBrush | Tool::PatternStamp + | Tool::DodgeBurn | Tool::Sponge | Tool::BlurSharpen ) && shared.active_layer_id.and_then(|id| { shared.action_executor.document().get_layer(&id) }).map_or(false, |l| matches!(l, lightningbeam_core::layer::AnyLayer::Raster(_))); diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/toolbar.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/toolbar.rs index daf606b..9d3d42b 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/toolbar.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/toolbar.rs @@ -5,7 +5,8 @@ use eframe::egui; use lightningbeam_core::layer::{AnyLayer, LayerType}; -use lightningbeam_core::tool::{Tool, RegionSelectMode}; +use lightningbeam_core::tool::{Tool, RegionSelectMode, LassoMode}; +use lightningbeam_core::brush_settings::bundled_brushes; use crate::keymap::tool_app_action; use super::{NodePath, PaneRenderer, SharedPaneState}; @@ -101,7 +102,7 @@ impl PaneRenderer for ToolbarPane { } // Draw sub-tool arrow indicator for tools with modes - let has_sub_tools = matches!(tool, Tool::RegionSelect); + let has_sub_tools = matches!(tool, Tool::RegionSelect | Tool::SelectLasso); if has_sub_tools { let arrow_size = 6.0; let margin = 4.0; @@ -125,6 +126,22 @@ impl PaneRenderer for ToolbarPane { // Check for click first if response.clicked() { *shared.selected_tool = *tool; + // Preset-backed tools: auto-select the matching bundled brush. + let preset_name = match tool { + Tool::Pencil => Some("Pencil"), + Tool::Pen => Some("Pen"), + Tool::Airbrush => Some("Airbrush"), + _ => None, + }; + if let Some(name) = preset_name { + if let Some(preset) = bundled_brushes().iter().find(|p| p.name == name) { + let s = &preset.settings; + *shared.brush_opacity = s.opaque.clamp(0.0, 1.0); + *shared.brush_hardness = s.hardness.clamp(0.0, 1.0); + *shared.brush_spacing = s.dabs_per_radius; + *shared.active_brush_settings = s.clone(); + } + } } // Right-click context menu for tools with sub-options @@ -150,6 +167,33 @@ impl PaneRenderer for ToolbarPane { ui.close(); } } + Tool::SelectLasso => { + ui.set_min_width(130.0); + if ui.selectable_label( + *shared.lasso_mode == LassoMode::Freehand, + "Freehand", + ).clicked() { + *shared.lasso_mode = LassoMode::Freehand; + *shared.selected_tool = Tool::SelectLasso; + ui.close(); + } + if ui.selectable_label( + *shared.lasso_mode == LassoMode::Polygonal, + "Polygonal", + ).clicked() { + *shared.lasso_mode = LassoMode::Polygonal; + *shared.selected_tool = Tool::SelectLasso; + ui.close(); + } + if ui.selectable_label( + *shared.lasso_mode == LassoMode::Magnetic, + "Magnetic", + ).clicked() { + *shared.lasso_mode = LassoMode::Magnetic; + *shared.selected_tool = Tool::SelectLasso; + ui.close(); + } + } _ => {} } }); @@ -176,6 +220,13 @@ impl PaneRenderer for ToolbarPane { RegionSelectMode::Lasso => "Lasso", }; format!("{} - {}{}\nRight-click for options", tool.display_name(), mode, hint) + } else if *tool == Tool::SelectLasso { + let mode = match *shared.lasso_mode { + LassoMode::Freehand => "Freehand", + LassoMode::Polygonal => "Polygonal", + LassoMode::Magnetic => "Magnetic", + }; + format!("{} - {}{}\nRight-click for options", tool.display_name(), mode, hint) } else { format!("{}{}", tool.display_name(), hint) }; diff --git a/src/assets/todo.svg b/src/assets/todo.svg new file mode 100644 index 0000000..cdd226f --- /dev/null +++ b/src/assets/todo.svg @@ -0,0 +1,5 @@ + + + + +