Disable the Brush tool by default and add it to preferences under experimental

This commit is contained in:
Keavon Chambers 2025-12-08 04:02:50 -08:00
parent 68a9bbced0
commit 90c91db550
7 changed files with 114 additions and 58 deletions

View file

@ -89,7 +89,6 @@ impl PreferencesDialogMessageHandler {
.tooltip_label("Zoom with Scroll")
.tooltip_description(zoom_with_scroll_description)
.for_checkbox(checkbox_id)
.table_align(true)
.widget_instance(),
];
@ -102,7 +101,10 @@ impl PreferencesDialogMessageHandler {
let selection_label = vec![
Separator::new(SeparatorType::Unrelated).widget_instance(),
Separator::new(SeparatorType::Unrelated).widget_instance(),
TextLabel::new("Selection").widget_instance(),
TextLabel::new("Selection")
.tooltip_label("Selection")
.tooltip_description("Choose how targets are selected within dragged rectangular and lasso areas.")
.widget_instance(),
];
let selection_mode = RadioInput::new(vec![
@ -151,7 +153,7 @@ impl PreferencesDialogMessageHandler {
let experimental_header = vec![TextLabel::new("Experimental").italic(true).widget_instance()];
let node_graph_section_description = "Appearance of the wires running between node connections in the graph.";
let node_graph_section_description = "Configure the appearance of the wires running between node connections in the graph.";
let node_graph_wires_label = vec![
Separator::new(SeparatorType::Unrelated).widget_instance(),
Separator::new(SeparatorType::Unrelated).widget_instance(),
@ -181,13 +183,18 @@ impl PreferencesDialogMessageHandler {
];
let checkbox_id = CheckboxId::new();
let vello_description = "Use the experimental Vello renderer. (Your browser must support WebGPU).";
let vello_description = "Use the experimental Vello renderer instead of SVG-based rendering.".to_string();
#[cfg(target_family = "wasm")]
let mut vello_description = vello_description;
#[cfg(target_family = "wasm")]
vello_description.push_str("\n\n(Your browser must support WebGPU.)");
let use_vello = vec![
Separator::new(SeparatorType::Unrelated).widget_instance(),
Separator::new(SeparatorType::Unrelated).widget_instance(),
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
.tooltip_label("Vello Renderer")
.tooltip_description(vello_description)
.tooltip_description(vello_description.clone())
.disabled(!preferences.supports_wgpu())
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
.for_label(checkbox_id)
@ -197,14 +204,14 @@ impl PreferencesDialogMessageHandler {
.tooltip_description(vello_description)
.disabled(!preferences.supports_wgpu())
.for_checkbox(checkbox_id)
.table_align(true)
.widget_instance(),
];
let checkbox_id = CheckboxId::new();
let vector_mesh_description = "
Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\
Currently this does not properly handle stroke joins and fills.
Allow the Pen tool to produce branching geometry, where more than two segments may be connected to one anchor point.\n\
\n\
Currently, vector meshes do not properly render strokes (branching joins) and fills (multiple regions).
"
.trim();
let vector_meshes = vec![
@ -220,23 +227,58 @@ impl PreferencesDialogMessageHandler {
.tooltip_label("Vector Meshes")
.tooltip_description(vector_mesh_description)
.for_checkbox(checkbox_id)
.table_align(true)
.widget_instance(),
];
let checkbox_id = CheckboxId::new();
let brush_tool_description = "
Enable the Brush tool to support basic raster-based layer painting.\n\
\n\
This legacy tool has performance and quality limitations and is slated for replacement in future versions of Graphite that will focus on raster graphics editing.
"
.trim();
let brush_tool = vec![
Separator::new(SeparatorType::Unrelated).widget_instance(),
Separator::new(SeparatorType::Unrelated).widget_instance(),
CheckboxInput::new(preferences.brush_tool)
.tooltip_label("Brush Tool")
.tooltip_description(brush_tool_description)
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::BrushTool { enabled: checkbox_input.checked }.into())
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Brush Tool")
.tooltip_label("Brush Tool")
.tooltip_description(brush_tool_description)
.for_checkbox(checkbox_id)
.widget_instance(),
];
Layout(vec![
// NAVIGATION
LayoutGroup::Row { widgets: navigation_header },
// Navigation: Zoom Rate
LayoutGroup::Row { widgets: zoom_rate_label },
LayoutGroup::Row { widgets: zoom_rate },
// Navigation: Zoom with Scroll
LayoutGroup::Row { widgets: zoom_with_scroll },
//
// EDITING
LayoutGroup::Row { widgets: editing_header },
// Editing: Selection
LayoutGroup::Row { widgets: selection_label },
LayoutGroup::Row { widgets: selection_mode },
//
// EXPERIMENTAL
LayoutGroup::Row { widgets: experimental_header },
// Experimental: Node Graph Wires
LayoutGroup::Row { widgets: node_graph_wires_label },
LayoutGroup::Row { widgets: graph_wire_style },
// Experimental: Vello Renderer
LayoutGroup::Row { widgets: use_vello },
// Experimental: Vector Meshes
LayoutGroup::Row { widgets: vector_meshes },
// Experimental: Brush Tool
LayoutGroup::Row { widgets: brush_tool },
])
}

View file

@ -13,6 +13,7 @@ pub enum PreferencesMessage {
UseVello { use_vello: bool },
SelectionMode { selection_mode: SelectionMode },
VectorMeshes { enabled: bool },
BrushTool { enabled: bool },
ModifyLayout { zoom_with_scroll: bool },
GraphWireStyle { style: GraphWireStyle },
ViewportZoomWheelRate { rate: f64 },

View file

@ -11,6 +11,7 @@ pub struct PreferencesMessageHandler {
pub zoom_with_scroll: bool,
pub use_vello: bool,
pub vector_meshes: bool,
pub brush_tool: bool,
pub graph_wire_style: GraphWireStyle,
pub viewport_zoom_wheel_rate: f64,
}
@ -38,6 +39,7 @@ impl Default for PreferencesMessageHandler {
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
use_vello: EditorPreferences::default().use_vello,
vector_meshes: false,
brush_tool: false,
graph_wire_style: GraphWireStyle::default(),
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
}
@ -76,6 +78,10 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
PreferencesMessage::VectorMeshes { enabled } => {
self.vector_meshes = enabled;
}
PreferencesMessage::BrushTool { enabled } => {
self.brush_tool = enabled;
responses.add(ToolMessage::RefreshToolShelf);
}
PreferencesMessage::ModifyLayout { zoom_with_scroll } => {
self.zoom_with_scroll = zoom_with_scroll;

View file

@ -79,6 +79,7 @@ pub enum ToolMessage {
PreUndo,
Redo,
RefreshToolOptions,
RefreshToolShelf,
ResetColors,
SelectWorkingColor {
color: Color,

View file

@ -101,7 +101,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
let tool_type = tool_type.get_tool();
responses.add(ToolMessage::RefreshToolOptions);
tool_data.send_layout(responses, LayoutTarget::ToolShelf);
responses.add(ToolMessage::RefreshToolShelf);
// Do nothing if switching to the same tool
if self.tool_is_active && tool_type == old_tool {
@ -176,7 +176,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
responses.add(ToolMessage::RefreshToolOptions);
// Notify the frontend about the new active tool to be displayed
tool_data.send_layout(responses, LayoutTarget::ToolShelf);
responses.add(ToolMessage::RefreshToolShelf);
}
ToolMessage::DeactivateTools => {
let tool_data = &mut self.tool_state.tool_data;
@ -220,7 +220,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
tool_data.tools.get(active_tool).unwrap().send_layout(responses, LayoutTarget::ToolOptions);
// Notify the frontend about the initial active tool
tool_data.send_layout(responses, LayoutTarget::ToolShelf);
tool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);
// Notify the frontend about the initial working colors
document_data.update_working_colors(responses);
@ -259,6 +259,10 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
let tool_data = &mut self.tool_state.tool_data;
tool_data.tools.get(&tool_data.active_tool_type).unwrap().send_layout(responses, LayoutTarget::ToolOptions);
}
ToolMessage::RefreshToolShelf => {
let tool_data = &mut self.tool_state.tool_data;
tool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);
}
ToolMessage::ResetColors => {
let document_data = &mut self.tool_state.document_tool_data;

View file

@ -231,8 +231,15 @@ impl ToolData {
}
}
impl LayoutHolder for ToolData {
fn layout(&self) -> Layout {
impl ToolData {
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, brush_tool: bool) {
responses.add(LayoutMessage::SendLayout {
layout: self.layout(brush_tool),
layout_target,
});
}
fn layout(&self, brush_tool: bool) -> Layout {
let active_tool = self.active_shape_type.unwrap_or(self.active_tool_type);
let tool_groups_layout = list_tools_in_groups()
@ -240,22 +247,26 @@ impl LayoutHolder for ToolData {
.map(|tool_group|
tool_group
.iter()
.map(|tool_availability| {
match tool_availability {
ToolAvailability::Available(tool) =>
.filter_map(|tool_availability| {
if !brush_tool && let ToolRole::Normal(tool) = tool_availability && tool.tool_type() == ToolType::Brush {
return None;
}
Some(match tool_availability {
ToolRole::Normal(tool) =>
ToolEntry::new(tool.tool_type(), tool.icon_name())
.tooltip_label(tool.tooltip_label())
.tooltip_shortcut(action_shortcut!(tool_type_to_activate_tool_message(tool.tool_type()))),
ToolAvailability::AvailableAsShape(shape) =>
ToolRole::Shape(shape) =>
ToolEntry::new(shape.tool_type(), shape.icon_name())
.tooltip_label(shape.tooltip_label())
.tooltip_description(shape.tooltip_description())
.tooltip_shortcut(action_shortcut!(tool_type_to_activate_tool_message(shape.tool_type()))),
// ToolAvailability::ComingSoon(tool) => tool.clone(),
}
})
})
.collect::<Vec<_>>()
)
.filter(|group| !group.is_empty())
.flat_map(|group| {
let separator = std::iter::once(Separator::new(SeparatorType::Section).direction(SeparatorDirection::Vertical).widget_instance());
let buttons = group.into_iter().map(|ToolEntry { tooltip_label, tooltip_description, tooltip_shortcut, tool_type, icon_name }| {
@ -319,9 +330,8 @@ impl Default for ToolFsmState {
.into_iter()
.flatten()
.filter_map(|tool| match tool {
ToolAvailability::Available(tool) => Some((tool.tool_type(), tool)),
ToolAvailability::AvailableAsShape(_) => None,
// ToolAvailability::ComingSoon(_) => None,
ToolRole::Normal(tool) => Some((tool.tool_type(), tool)),
ToolRole::Shape(_) => None,
})
.collect(),
},
@ -369,7 +379,6 @@ pub enum ToolType {
Patch,
Detail,
Relight,
Frame,
}
impl ToolType {
@ -385,58 +394,57 @@ impl ToolType {
}
}
enum ToolAvailability {
Available(Box<Tool>),
AvailableAsShape(ShapeType),
// ComingSoon(ToolEntry),
enum ToolRole {
Normal(Box<Tool>),
Shape(ShapeType),
}
/// List of all the tools in their conventional ordering and grouping.
fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
fn list_tools_in_groups() -> Vec<Vec<ToolRole>> {
vec![
vec![
// General tool group
ToolAvailability::Available(Box::<select_tool::SelectTool>::default()),
ToolAvailability::Available(Box::<artboard_tool::ArtboardTool>::default()),
ToolAvailability::Available(Box::<navigate_tool::NavigateTool>::default()),
ToolAvailability::Available(Box::<eyedropper_tool::EyedropperTool>::default()),
ToolAvailability::Available(Box::<fill_tool::FillTool>::default()),
ToolAvailability::Available(Box::<gradient_tool::GradientTool>::default()),
ToolRole::Normal(Box::<select_tool::SelectTool>::default()),
ToolRole::Normal(Box::<artboard_tool::ArtboardTool>::default()),
ToolRole::Normal(Box::<navigate_tool::NavigateTool>::default()),
ToolRole::Normal(Box::<eyedropper_tool::EyedropperTool>::default()),
ToolRole::Normal(Box::<fill_tool::FillTool>::default()),
ToolRole::Normal(Box::<gradient_tool::GradientTool>::default()),
],
vec![
// Vector tool group
ToolAvailability::Available(Box::<path_tool::PathTool>::default()),
ToolAvailability::Available(Box::<pen_tool::PenTool>::default()),
ToolAvailability::Available(Box::<freehand_tool::FreehandTool>::default()),
ToolAvailability::Available(Box::<spline_tool::SplineTool>::default()),
ToolAvailability::AvailableAsShape(ShapeType::Line),
ToolAvailability::AvailableAsShape(ShapeType::Rectangle),
ToolAvailability::AvailableAsShape(ShapeType::Ellipse),
ToolAvailability::Available(Box::<shape_tool::ShapeTool>::default()),
ToolAvailability::Available(Box::<text_tool::TextTool>::default()),
ToolRole::Normal(Box::<path_tool::PathTool>::default()),
ToolRole::Normal(Box::<pen_tool::PenTool>::default()),
ToolRole::Normal(Box::<freehand_tool::FreehandTool>::default()),
ToolRole::Normal(Box::<spline_tool::SplineTool>::default()),
ToolRole::Shape(ShapeType::Line),
ToolRole::Shape(ShapeType::Rectangle),
ToolRole::Shape(ShapeType::Ellipse),
ToolRole::Normal(Box::<shape_tool::ShapeTool>::default()),
ToolRole::Normal(Box::<text_tool::TextTool>::default()),
],
vec![
// Raster tool group
ToolAvailability::Available(Box::<brush_tool::BrushTool>::default()),
// ToolAvailability::ComingSoon(
ToolRole::Normal(Box::<brush_tool::BrushTool>::default()),
// ToolRole::Normal(
// ToolEntry::new(ToolType::Heal, "RasterHealTool")
// .tooltip_label("Heal Tool")
// .tooltip_shortcut(action_shortcut_manual!(Key::KeyJ)),
// ),
// ToolAvailability::ComingSoon(
// ToolRole::Normal(
// ToolEntry::new(ToolType::Clone, "RasterCloneTool")
// .tooltip_label("Clone Tool")
// .tooltip_shortcut(action_shortcut_manual!(Key::KeyC)),
// ),
// ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool")
// ToolRole::Normal(ToolEntry::new(ToolType::Patch, "RasterPatchTool")
// .tooltip_label("Patch Tool"),
// ),
// ToolAvailability::ComingSoon(
// ToolRole::Normal(
// ToolEntry::new(ToolType::Detail, "RasterDetailTool")
// .tooltip_label("Detail Tool")
// .tooltip_shortcut(action_shortcut_manual!(Key::KeyD)),
// ),
// ToolAvailability::ComingSoon(
// ToolRole::Normal(
// ToolEntry::new(ToolType::Relight, "RasterRelightTool")
// .tooltip_label("Relight Tool")
// .tooltip_shortcut(action_shortcut_manual!(Key::KeyO)),

View file

@ -304,14 +304,8 @@
if (cursor === "custom-rotate") {
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20">
<path transform="translate(2 2)" fill="black" stroke="black" stroke-width="2px" d="
M8,15.2C4,15.2,0.8,12,0.8,8C0.8,4,4,0.8,8,0.8c2,0,3.9,0.8,5.3,2.3l-1,1C11.2,2.9,9.6,2.2,8,2.2C4.8,2.2,2.2,4.8,2.2,8s2.6,5.8,5.8,5.8s5.8-2.6,5.8-5.8h1.4C15.2,12,12,15.2,8,15.2z
" />
<polygon transform="translate(2 2)" fill="black" stroke="black" stroke-width="2px" points="12.6,0 15.5,5 9.7,5" />
<path transform="translate(2 2)" fill="white" d="
M8,15.2C4,15.2,0.8,12,0.8,8C0.8,4,4,0.8,8,0.8c2,0,3.9,0.8,5.3,2.3l-1,1C11.2,2.9,9.6,2.2,8,2.2C4.8,2.2,2.2,4.8,2.2,8s2.6,5.8,5.8,5.8s5.8-2.6,5.8-5.8h1.4C15.2,12,12,15.2,8,15.2z
" />
<polygon transform="translate(2 2)" fill="white" points="12.6,0 15.5,5 9.7,5" />
<path fill="none" stroke="black" stroke-width="2" d="M10,15.8c-3.2,0-5.8-2.6-5.8-5.8S6.8,4.2,10,4.2c0.999,0,1.999,0.273,2.877,0.771L11.7,7h5.8l-2.9-5l-1.013,1.746C12.5,3.125,11.271,2.8,10,2.8C6,2.8,2.8,6,2.8,10S6,17.2,10,17.2s7.2-3.2,7.2-7.2h-1.4C15.8,13.2,13.2,15.8,10,15.8z" />
<path fill="white" d="M10,15.8c-3.2,0-5.8-2.6-5.8-5.8S6.8,4.2,10,4.2c0.999,0,1.999,0.273,2.877,0.771L11.7,7h5.8l-2.9-5l-1.013,1.746C12.5,3.125,11.271,2.8,10,2.8C6,2.8,2.8,6,2.8,10S6,17.2,10,17.2s7.2-3.2,7.2-7.2h-1.4C15.8,13.2,13.2,15.8,10,15.8z" />
</svg>
`
.split("\n")