Add tool skeletons

This commit is contained in:
Skyler Lehmkuhl 2026-03-06 07:22:50 -05:00
parent 37ac9b6abe
commit 2c9d8c1589
10 changed files with 298 additions and 78 deletions

View File

@ -11,9 +11,10 @@ use vello::kurbo::Point;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum Tool { pub enum Tool {
// ── Vector / shared tools ──────────────────────────────────────────────
/// Selection tool - select and move objects /// Selection tool - select and move objects
Select, Select,
/// Draw/Pen tool - freehand drawing /// Draw/Brush tool - freehand drawing (vector) / paintbrush (raster)
Draw, Draw,
/// Transform tool - scale, rotate, skew /// Transform tool - scale, rotate, skew
Transform, Transform,
@ -37,12 +38,48 @@ pub enum Tool {
RegionSelect, RegionSelect,
/// Split tool - split audio/video clips at a point /// Split tool - split audio/video clips at a point
Split, 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 tool - erase raster pixels
Erase, Erase,
/// Smudge tool - smudge/blend raster pixels /// Smudge tool - smudge/blend raster pixels
Smudge, 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, 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 /// 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 /// Tool state tracking for interactive operations
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ToolState { pub enum ToolState {
@ -229,44 +283,77 @@ impl Tool {
/// Get display name for the tool /// Get display name for the tool
pub fn display_name(self) -> &'static str { pub fn display_name(self) -> &'static str {
match self { match self {
Tool::Select => "Select", Tool::Select => "Select",
Tool::Draw => "Draw", Tool::Draw => "Brush",
Tool::Transform => "Transform", Tool::Transform => "Transform",
Tool::Rectangle => "Rectangle", Tool::Rectangle => "Rectangle",
Tool::Ellipse => "Ellipse", Tool::Ellipse => "Ellipse",
Tool::PaintBucket => "Paint Bucket", Tool::PaintBucket => "Paint Bucket",
Tool::Eyedropper => "Eyedropper", Tool::Eyedropper => "Eyedropper",
Tool::Line => "Line", Tool::Line => "Line",
Tool::Polygon => "Polygon", Tool::Polygon => "Polygon",
Tool::BezierEdit => "Bezier Edit", Tool::BezierEdit => "Bezier Edit",
Tool::Text => "Text", Tool::Text => "Text",
Tool::RegionSelect => "Region Select", Tool::RegionSelect => "Region Select",
Tool::Split => "Split", Tool::Split => "Split",
Tool::Erase => "Erase", Tool::Pencil => "Pencil",
Tool::Smudge => "Smudge", Tool::Pen => "Pen",
Tool::SelectLasso => "Lasso Select", 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 /// Get SVG icon file name for the tool
pub fn icon_file(self) -> &'static str { pub fn icon_file(self) -> &'static str {
match self { match self {
Tool::Select => "select.svg", Tool::Select => "select.svg",
Tool::Draw => "draw.svg", Tool::Draw => "draw.svg",
Tool::Transform => "transform.svg", Tool::Transform => "transform.svg",
Tool::Rectangle => "rectangle.svg", Tool::Rectangle => "rectangle.svg",
Tool::Ellipse => "ellipse.svg", Tool::Ellipse => "ellipse.svg",
Tool::PaintBucket => "paint_bucket.svg", Tool::PaintBucket => "paint_bucket.svg",
Tool::Eyedropper => "eyedropper.svg", Tool::Eyedropper => "eyedropper.svg",
Tool::Line => "line.svg", Tool::Line => "line.svg",
Tool::Polygon => "polygon.svg", Tool::Polygon => "polygon.svg",
Tool::BezierEdit => "bezier_edit.svg", Tool::BezierEdit => "bezier_edit.svg",
Tool::Text => "text.svg", Tool::Text => "text.svg",
Tool::RegionSelect => "region_select.svg", Tool::RegionSelect => "region_select.svg",
Tool::Split => "split.svg", Tool::Split => "split.svg",
Tool::Erase => "erase.svg", Tool::Erase => "erase.svg",
Tool::Smudge => "smudge.svg", Tool::Smudge => "smudge.svg",
Tool::SelectLasso => "lasso.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 { match layer_type {
None | Some(LayerType::Vector) => Tool::all(), None | Some(LayerType::Vector) => Tool::all(),
Some(LayerType::Audio) | Some(LayerType::Video) => &[Tool::Select, Tool::Split], 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], _ => &[Tool::Select],
} }
} }

View File

@ -124,7 +124,7 @@
"children": [ "children": [
{ {
"type": "vertical-grid", "type": "vertical-grid",
"percent": 30, "percent": 67,
"children": [ "children": [
{ "type": "pane", "name": "toolbar" }, { "type": "pane", "name": "toolbar" },
{ "type": "pane", "name": "infopanel" } { "type": "pane", "name": "infopanel" }

View File

@ -32,22 +32,41 @@ impl CustomCursor {
/// Convert a Tool enum to the corresponding custom cursor /// Convert a Tool enum to the corresponding custom cursor
pub fn from_tool(tool: Tool) -> Self { pub fn from_tool(tool: Tool) -> Self {
match tool { match tool {
Tool::Select => CustomCursor::Select, Tool::Select => CustomCursor::Select,
Tool::Draw => CustomCursor::Draw, Tool::Draw => CustomCursor::Draw,
Tool::Transform => CustomCursor::Transform, Tool::Transform => CustomCursor::Transform,
Tool::Rectangle => CustomCursor::Rectangle, Tool::Rectangle => CustomCursor::Rectangle,
Tool::Ellipse => CustomCursor::Ellipse, Tool::Ellipse => CustomCursor::Ellipse,
Tool::PaintBucket => CustomCursor::PaintBucket, Tool::PaintBucket => CustomCursor::PaintBucket,
Tool::Eyedropper => CustomCursor::Eyedropper, Tool::Eyedropper => CustomCursor::Eyedropper,
Tool::Line => CustomCursor::Line, Tool::Line => CustomCursor::Line,
Tool::Polygon => CustomCursor::Polygon, Tool::Polygon => CustomCursor::Polygon,
Tool::BezierEdit => CustomCursor::BezierEdit, Tool::BezierEdit => CustomCursor::BezierEdit,
Tool::Text => CustomCursor::Text, Tool::Text => CustomCursor::Text,
Tool::RegionSelect => CustomCursor::Select, // Reuse select cursor for now Tool::RegionSelect => CustomCursor::Select,
Tool::Split => CustomCursor::Select, // Reuse select cursor for now Tool::Split => CustomCursor::Select,
Tool::Erase => CustomCursor::Draw, // Reuse draw cursor for raster erase Tool::Erase => CustomCursor::Draw,
Tool::Smudge => CustomCursor::Draw, // Reuse draw cursor for raster smudge Tool::Smudge => CustomCursor::Draw,
Tool::SelectLasso => CustomCursor::Select, // Reuse select cursor for lasso 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,
} }
} }

View File

@ -429,24 +429,26 @@ impl AppAction {
/// `Tool::Split` has no tool-shortcut action (it's triggered via the menu). /// `Tool::Split` has no tool-shortcut action (it's triggered via the menu).
pub fn tool_app_action(tool: lightningbeam_core::tool::Tool) -> Option<AppAction> { pub fn tool_app_action(tool: lightningbeam_core::tool::Tool) -> Option<AppAction> {
use lightningbeam_core::tool::Tool; use lightningbeam_core::tool::Tool;
Some(match tool { match tool {
Tool::Select => AppAction::ToolSelect, Tool::Select => Some(AppAction::ToolSelect),
Tool::Draw => AppAction::ToolDraw, Tool::Draw => Some(AppAction::ToolDraw),
Tool::Transform => AppAction::ToolTransform, Tool::Transform => Some(AppAction::ToolTransform),
Tool::Rectangle => AppAction::ToolRectangle, Tool::Rectangle => Some(AppAction::ToolRectangle),
Tool::Ellipse => AppAction::ToolEllipse, Tool::Ellipse => Some(AppAction::ToolEllipse),
Tool::PaintBucket => AppAction::ToolPaintBucket, Tool::PaintBucket => Some(AppAction::ToolPaintBucket),
Tool::Eyedropper => AppAction::ToolEyedropper, Tool::Eyedropper => Some(AppAction::ToolEyedropper),
Tool::Line => AppAction::ToolLine, Tool::Line => Some(AppAction::ToolLine),
Tool::Polygon => AppAction::ToolPolygon, Tool::Polygon => Some(AppAction::ToolPolygon),
Tool::BezierEdit => AppAction::ToolBezierEdit, Tool::BezierEdit => Some(AppAction::ToolBezierEdit),
Tool::Text => AppAction::ToolText, Tool::Text => Some(AppAction::ToolText),
Tool::RegionSelect => AppAction::ToolRegionSelect, Tool::RegionSelect => Some(AppAction::ToolRegionSelect),
Tool::Erase => AppAction::ToolErase, Tool::Erase => Some(AppAction::ToolErase),
Tool::Smudge => AppAction::ToolSmudge, Tool::Smudge => Some(AppAction::ToolSmudge),
Tool::SelectLasso => AppAction::ToolSelectLasso, Tool::SelectLasso => Some(AppAction::ToolSelectLasso),
Tool::Split => AppAction::ToolSplit, Tool::Split => Some(AppAction::ToolSplit),
}) // New tools have no keybinding yet
_ => None,
}
} }
// === Default bindings === // === Default bindings ===

View File

@ -332,6 +332,7 @@ mod tool_icons {
pub static ERASE: &[u8] = include_bytes!("../../../src/assets/erase.svg"); pub static ERASE: &[u8] = include_bytes!("../../../src/assets/erase.svg");
pub static SMUDGE: &[u8] = include_bytes!("../../../src/assets/smudge.svg"); pub static SMUDGE: &[u8] = include_bytes!("../../../src/assets/smudge.svg");
pub static LASSO: &[u8] = include_bytes!("../../../src/assets/lasso.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 /// Embedded focus icon SVGs
@ -399,11 +400,28 @@ impl ToolIconCache {
Tool::Polygon => tool_icons::POLYGON, Tool::Polygon => tool_icons::POLYGON,
Tool::BezierEdit => tool_icons::BEZIER_EDIT, Tool::BezierEdit => tool_icons::BEZIER_EDIT,
Tool::Text => tool_icons::TEXT, 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::Split => tool_icons::SPLIT,
Tool::Erase => tool_icons::ERASE, Tool::Erase => tool_icons::ERASE,
Tool::Smudge => tool_icons::SMUDGE, Tool::Smudge => tool_icons::SMUDGE,
Tool::SelectLasso => tool_icons::LASSO, 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) { if let Some(texture) = rasterize_svg(svg_data, tool.icon_file(), 180, ctx) {
self.icons.insert(tool, texture); self.icons.insert(tool, texture);
@ -856,6 +874,7 @@ struct EditorApp {
// Region select state // Region select state
region_selection: Option<lightningbeam_core::selection::RegionSelection>, region_selection: Option<lightningbeam_core::selection::RegionSelection>,
region_select_mode: lightningbeam_core::tool::RegionSelectMode, region_select_mode: lightningbeam_core::tool::RegionSelectMode,
lasso_mode: lightningbeam_core::tool::LassoMode,
// VU meter levels // VU meter levels
input_level: f32, input_level: f32,
@ -1127,6 +1146,7 @@ impl EditorApp {
polygon_sides: 5, // Default to pentagon polygon_sides: 5, // Default to pentagon
region_selection: None, region_selection: None,
region_select_mode: lightningbeam_core::tool::RegionSelectMode::default(), region_select_mode: lightningbeam_core::tool::RegionSelectMode::default(),
lasso_mode: lightningbeam_core::tool::LassoMode::default(),
input_level: 0.0, input_level: 0.0,
output_level: (0.0, 0.0), output_level: (0.0, 0.0),
track_levels: HashMap::new(), track_levels: HashMap::new(),
@ -5590,6 +5610,7 @@ impl eframe::App for EditorApp {
script_saved: &mut self.script_saved, script_saved: &mut self.script_saved,
region_selection: &mut self.region_selection, region_selection: &mut self.region_selection,
region_select_mode: &mut self.region_select_mode, region_select_mode: &mut self.region_select_mode,
lasso_mode: &mut self.lasso_mode,
pending_graph_loads: &self.pending_graph_loads, pending_graph_loads: &self.pending_graph_loads,
clipboard_consumed: &mut clipboard_consumed, clipboard_consumed: &mut clipboard_consumed,
keymap: &self.keymap, keymap: &self.keymap,

View File

@ -169,7 +169,10 @@ impl InfopanelPane {
.and_then(|id| shared.action_executor.document().get_layer(&id)) .and_then(|id| shared.action_executor.document().get_layer(&id))
.map_or(false, |l| matches!(l, AnyLayer::Raster(_))); .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 // Only show tool options for tools that have options
let is_vector_tool = !active_is_raster && matches!( let is_vector_tool = !active_is_raster && matches!(
@ -319,7 +322,7 @@ impl InfopanelPane {
} }
// Raster paint tools // 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)); 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_hardness = s.hardness.clamp(0.0, 1.0);
*shared.brush_spacing = s.dabs_per_radius; *shared.brush_spacing = s.dabs_per_radius;
*shared.active_brush_settings = s.clone(); *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;
}
} }
} }
} }

View File

@ -283,6 +283,8 @@ pub struct SharedPaneState<'a> {
pub region_selection: &'a mut Option<lightningbeam_core::selection::RegionSelection>, pub region_selection: &'a mut Option<lightningbeam_core::selection::RegionSelection>,
/// Region select mode (Rectangle or Lasso) /// Region select mode (Rectangle or Lasso)
pub region_select_mode: &'a mut lightningbeam_core::tool::RegionSelectMode, 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 /// Counter for in-flight graph preset loads — increment when sending a
/// GraphLoadPreset command so the repaint loop stays alive until the /// GraphLoadPreset command so the repaint loop stays alive until the
/// audio thread sends GraphPresetLoaded back /// audio thread sends GraphPresetLoaded back

View File

@ -7518,6 +7518,9 @@ impl StagePane {
self.handle_draw_tool(ui, &response, world_pos, shared); 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 => { Tool::Erase => {
self.handle_raster_stroke_tool(ui, &response, world_pos, lightningbeam_core::raster_layer::RasterBlendMode::Erase, shared); 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. // Compute semi-axes (world pixels) and dab rotation angle.
let (a_world, b_world, dab_angle_rad) = match *shared.selected_tool { let (a_world, b_world, dab_angle_rad) = match *shared.selected_tool {
Tool::Erase => (*shared.eraser_radius, *shared.eraser_radius, 0.0_f32), Tool::Erase => (*shared.eraser_radius, *shared.eraser_radius, 0.0_f32),
Tool::Smudge => (*shared.smudge_radius, *shared.smudge_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 bs = &shared.active_brush_settings;
let r = *shared.brush_radius; let r = *shared.brush_radius;
@ -8637,7 +8643,10 @@ impl PaneRenderer for StagePane {
use lightningbeam_core::tool::Tool; use lightningbeam_core::tool::Tool;
let is_raster_paint = matches!( let is_raster_paint = matches!(
*shared.selected_tool, *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.active_layer_id.and_then(|id| {
shared.action_executor.document().get_layer(&id) shared.action_executor.document().get_layer(&id)
}).map_or(false, |l| matches!(l, lightningbeam_core::layer::AnyLayer::Raster(_))); }).map_or(false, |l| matches!(l, lightningbeam_core::layer::AnyLayer::Raster(_)));

View File

@ -5,7 +5,8 @@
use eframe::egui; use eframe::egui;
use lightningbeam_core::layer::{AnyLayer, LayerType}; 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 crate::keymap::tool_app_action;
use super::{NodePath, PaneRenderer, SharedPaneState}; use super::{NodePath, PaneRenderer, SharedPaneState};
@ -101,7 +102,7 @@ impl PaneRenderer for ToolbarPane {
} }
// Draw sub-tool arrow indicator for tools with modes // 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 { if has_sub_tools {
let arrow_size = 6.0; let arrow_size = 6.0;
let margin = 4.0; let margin = 4.0;
@ -125,6 +126,22 @@ impl PaneRenderer for ToolbarPane {
// Check for click first // Check for click first
if response.clicked() { if response.clicked() {
*shared.selected_tool = *tool; *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 // Right-click context menu for tools with sub-options
@ -150,6 +167,33 @@ impl PaneRenderer for ToolbarPane {
ui.close(); 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", RegionSelectMode::Lasso => "Lasso",
}; };
format!("{} - {}{}\nRight-click for options", tool.display_name(), mode, hint) 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 { } else {
format!("{}{}", tool.display_name(), hint) format!("{}{}", tool.display_name(), hint)
}; };

5
src/assets/todo.svg Normal file
View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>

After

Width:  |  Height:  |  Size: 297 B