mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Make Brush tool use per-stroke options and improve its performance (#1242)
* Laid groundwork for per-stroke brush parameters. * Added new spacing parameter. * Added back interpolation, using spacing parameter. * Move bounding box code into core. * Initial working prototype of per-stroke styles. * Removed now useless brush node properties. * Made default spacing 50% for performance comparison. * Quick and dirty prototype for BlitNode copied from blend. * Fixed error after rebase. * Optimized the blitting loop. * Pretty big optimization for into_flat_u8. * Insert brush node for images * Fix starting position transform * UX polish * Code review nits --------- Co-authored-by: 0hypercube <0hypercube@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
|
||||
@@ -46,6 +47,10 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerIdentifier,
|
||||
modification: VectorDataModification,
|
||||
},
|
||||
Brush {
|
||||
layer: LayerIdentifier,
|
||||
strokes: Vec<BrushStroke>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
||||
@@ -5,12 +5,13 @@ use document_legacy::document::Document;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, FillType, Stroke};
|
||||
use transform_utils::LayerBounds;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
mod transform_utils;
|
||||
pub mod transform_utils;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GraphOperationMessageHandler;
|
||||
@@ -220,6 +221,16 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
|
||||
self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
|
||||
}
|
||||
|
||||
fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
|
||||
self.modify_inputs("Brush", false, |inputs| {
|
||||
if matches!(inputs[0], NodeInput::Node { .. }) {
|
||||
inputs[1] = core::mem::replace(&mut inputs[0], NodeInput::value(TaggedValue::None, false));
|
||||
}
|
||||
inputs[0] = NodeInput::value(TaggedValue::None, false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessageHandler)> for GraphOperationMessageHandler {
|
||||
@@ -244,7 +255,6 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
responses.add(Operation::SetLayerStroke { path: layer, stroke });
|
||||
}
|
||||
}
|
||||
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer,
|
||||
transform,
|
||||
@@ -298,12 +308,16 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
let pivot = pivot.into();
|
||||
responses.add(Operation::SetPivot { layer_path: layer, pivot });
|
||||
}
|
||||
|
||||
GraphOperationMessage::Vector { layer, modification } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.vector_modify(modification);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::Brush { layer, strokes } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.brush_modify(strokes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -639,17 +639,13 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
DocumentInputType::value("None", TaggedValue::None, false),
|
||||
DocumentInputType::value("Background", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Bounds", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Trace", TaggedValue::VecDVec2((0..2).map(|x| DVec2::new(x as f64 * 10., 0.)).collect()), true),
|
||||
DocumentInputType::value("Diameter", TaggedValue::F64(40.), false),
|
||||
DocumentInputType::value("Hardness", TaggedValue::F64(50.), false),
|
||||
DocumentInputType::value("Flow", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("Color", TaggedValue::Color(Color::BLACK), false),
|
||||
DocumentInputType::value("Trace", TaggedValue::BrushStrokes(Vec::new()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
}],
|
||||
properties: node_properties::brush_node_properties,
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Extract Vector Points",
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
use super::document_node_types::NodePropertiesContext;
|
||||
use super::FrontendGraphDataType;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
||||
use document_legacy::Operation;
|
||||
use glam::{DVec2, IVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin};
|
||||
|
||||
use graphene_core::{Cow, Type, TypeDescriptor};
|
||||
|
||||
use super::document_node_types::NodePropertiesContext;
|
||||
use super::FrontendGraphDataType;
|
||||
use glam::{DVec2, IVec2};
|
||||
|
||||
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
let widget = WidgetHolder::text_widget(text);
|
||||
@@ -129,7 +125,7 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
||||
widgets
|
||||
}
|
||||
|
||||
fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, mut assist: impl FnMut(&mut Vec<WidgetHolder>)) -> LayoutGroup {
|
||||
fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, unit: &str, mut assist: impl FnMut(&mut Vec<WidgetHolder>)) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, false);
|
||||
|
||||
assist(&mut widgets);
|
||||
@@ -143,13 +139,13 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
||||
WidgetHolder::unrelated_separator(),
|
||||
NumberInput::new(Some(vec2.x))
|
||||
.label(x)
|
||||
.unit(" px")
|
||||
.unit(unit)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index))
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
NumberInput::new(Some(vec2.y))
|
||||
.label(y)
|
||||
.unit(" px")
|
||||
.unit(unit)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, input.value.unwrap())), node_id, index))
|
||||
.widget_holder(),
|
||||
]);
|
||||
@@ -165,14 +161,14 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
||||
NumberInput::new(Some(vec2.x as f64))
|
||||
.int()
|
||||
.label(x)
|
||||
.unit(" px")
|
||||
.unit(unit)
|
||||
.on_update(update_value(update_x, node_id, index))
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
NumberInput::new(Some(vec2.y as f64))
|
||||
.int()
|
||||
.label(y)
|
||||
.unit(" px")
|
||||
.unit(unit)
|
||||
.on_update(update_value(update_y, node_id, index))
|
||||
.widget_holder(),
|
||||
]);
|
||||
@@ -707,16 +703,6 @@ pub fn blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
||||
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
|
||||
}
|
||||
|
||||
pub fn brush_node_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let color = color_widget(document_node, node_id, 7, "Color", ColorInput::default().allow_none(false), true);
|
||||
|
||||
let size = number_widget(document_node, node_id, 4, "Diameter", NumberInput::default().min(1.).max(100.).unit(" px"), true);
|
||||
let hardness = number_widget(document_node, node_id, 5, "Hardness", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
||||
let flow = number_widget(document_node, node_id, 6, "Flow", NumberInput::default().min(1.).max(100.).unit("%"), true);
|
||||
|
||||
vec![color, LayoutGroup::Row { widgets: size }, LayoutGroup::Row { widgets: hardness }, LayoutGroup::Row { widgets: flow }]
|
||||
}
|
||||
|
||||
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
||||
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
|
||||
@@ -945,7 +931,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||
add_blank_assist(widgets);
|
||||
}
|
||||
};
|
||||
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", translation_assist);
|
||||
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", translation_assist);
|
||||
|
||||
let rotation = {
|
||||
let index = 2;
|
||||
@@ -972,7 +958,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||
LayoutGroup::Row { widgets }
|
||||
};
|
||||
|
||||
let scale = vec2_widget(document_node, node_id, 3, "Scale", "X", "Y", add_blank_assist);
|
||||
let scale = vec2_widget(document_node, node_id, 3, "Scale", "W", "H", "x", add_blank_assist);
|
||||
vec![translation, rotation, scale]
|
||||
}
|
||||
|
||||
@@ -1658,8 +1644,8 @@ pub fn layer_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
||||
]
|
||||
}
|
||||
pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", add_blank_assist);
|
||||
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", add_blank_assist);
|
||||
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", " px", add_blank_assist);
|
||||
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", " px", add_blank_assist);
|
||||
let background = color_widget(document_node, node_id, 3, "Background", ColorInput::default().allow_none(false), true);
|
||||
vec![location, dimensions, background]
|
||||
}
|
||||
|
||||
@@ -4,19 +4,22 @@ use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup,
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::layer_layer::CachedOutputData;
|
||||
use document_legacy::LayerId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -30,6 +33,7 @@ pub struct BrushOptions {
|
||||
diameter: f64,
|
||||
hardness: f64,
|
||||
flow: f64,
|
||||
spacing: f64,
|
||||
color: ToolColorOptions,
|
||||
}
|
||||
|
||||
@@ -39,6 +43,7 @@ impl Default for BrushOptions {
|
||||
diameter: 40.,
|
||||
hardness: 50.,
|
||||
flow: 100.,
|
||||
spacing: 50.,
|
||||
color: ToolColorOptions::default(),
|
||||
}
|
||||
}
|
||||
@@ -70,6 +75,7 @@ pub enum BrushToolMessageOptionsUpdate {
|
||||
Diameter(f64),
|
||||
Flow(f64),
|
||||
Hardness(f64),
|
||||
Spacing(f64),
|
||||
WorkingColors(Option<Color>, Option<Color>),
|
||||
}
|
||||
|
||||
@@ -117,6 +123,14 @@ impl PropertyHolder for BrushTool {
|
||||
.unit("%")
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into())
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
NumberInput::new(Some(self.options.spacing))
|
||||
.label("Spacing")
|
||||
.min(1.)
|
||||
.max(100.)
|
||||
.unit("%")
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Spacing(number_input.value.unwrap())).into())
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
widgets.push(WidgetHolder::section_separator());
|
||||
@@ -152,6 +166,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
|
||||
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
|
||||
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
|
||||
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
|
||||
BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,
|
||||
BrushToolMessageOptionsUpdate::Color(color) => {
|
||||
self.options.color.custom_color = color;
|
||||
self.options.color.color_type = ToolColorType::Custom;
|
||||
@@ -206,39 +221,45 @@ impl ToolTransition for BrushTool {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct BrushToolData {
|
||||
points: Vec<Vec<DVec2>>,
|
||||
path: Option<Vec<LayerId>>,
|
||||
strokes: Vec<BrushStroke>,
|
||||
layer_path: Vec<LayerId>,
|
||||
node_path: Vec<NodeId>,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
impl BrushToolData {
|
||||
fn update_points(&self, responses: &mut VecDeque<Message>) {
|
||||
if let Some(layer_path) = self.path.clone() {
|
||||
let points = self.points.iter().flatten().cloned().collect();
|
||||
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path: vec![0],
|
||||
input_index: 3,
|
||||
value: TaggedValue::VecDVec2(points),
|
||||
});
|
||||
fn load_existing_strokes(&mut self, document: &DocumentMessageHandler) -> Option<&Vec<LayerId>> {
|
||||
self.transform = DAffine2::IDENTITY;
|
||||
if document.selected_layers().count() != 1 {
|
||||
return None;
|
||||
}
|
||||
self.layer_path = document.selected_layers().next()?.to_vec();
|
||||
let layer = document.document_legacy.layer(&self.layer_path).ok().and_then(|layer| layer.as_layer().ok())?;
|
||||
let network = &layer.network;
|
||||
for (node, _node_id) in network.primary_flow() {
|
||||
if node.name == "Brush" {
|
||||
let points_input = node.inputs.get(3)?;
|
||||
let NodeInput::Value { tagged_value: TaggedValue::BrushStrokes(strokes), .. } = points_input else {
|
||||
continue;
|
||||
};
|
||||
self.strokes = strokes.clone();
|
||||
|
||||
return Some(&self.layer_path);
|
||||
} else if node.name == "Transform" {
|
||||
self.transform = get_current_transform(&node.inputs) * self.transform;
|
||||
}
|
||||
}
|
||||
|
||||
self.transform = DAffine2::IDENTITY;
|
||||
|
||||
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_)).then_some(&self.layer_path)
|
||||
}
|
||||
|
||||
// fn update_image(&self, node_graph: &NodeGraphExecutor, responses: &mut VecDeque<Message>) {
|
||||
// let Some(image) = node_graph.introspect_node(&[1]) else { return; };
|
||||
// let image: &ImageFrame<Color> = image.downcast_ref().unwrap();
|
||||
// self.set_image(image.clone(), responses)
|
||||
// }
|
||||
//
|
||||
// fn set_image(&self, image_frame: ImageFrame<Color>, responses: &mut VecDeque<Message>) {
|
||||
// if let Some(layer_path) = self.path.clone() {
|
||||
// responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||
// layer_path,
|
||||
// node_path: vec![0],
|
||||
// input_index: 1,
|
||||
// value: TaggedValue::ImageFrame(image_frame),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
fn update_strokes(&self, brush_options: &BrushOptions, responses: &mut VecDeque<Message>) {
|
||||
let layer = self.layer_path.clone();
|
||||
let strokes = self.strokes.clone();
|
||||
responses.add(GraphOperationMessage::Brush { layer, strokes });
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for BrushToolFsmState {
|
||||
@@ -255,78 +276,68 @@ impl Fsm for BrushToolFsmState {
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use BrushToolFsmState::*;
|
||||
use BrushToolMessage::*;
|
||||
|
||||
let transform = document.document_legacy.root.transform;
|
||||
let document_position = (document.document_legacy.root.transform).inverse().transform_point2(input.mouse.position);
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
|
||||
if let ToolMessage::Brush(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let existing_points = load_existing_points(document);
|
||||
let new_layer = existing_points.is_none();
|
||||
if let Some((layer_path, points)) = existing_points {
|
||||
tool_data.path = Some(layer_path);
|
||||
//tool_data.set_image(image, responses);
|
||||
if tool_data.points.is_empty() {
|
||||
tool_data.points.push(points);
|
||||
}
|
||||
} else {
|
||||
let layer_path = tool_data.load_existing_strokes(document);
|
||||
let new_layer = layer_path.is_none();
|
||||
if new_layer {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
}
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
// TODO: Also scale it based on the input image ('Background' parameter).
|
||||
// TODO: Resizing the input image results in a different brush size from the chosen diameter.
|
||||
let layer_scale = 0.0001_f64 // Safety against division by zero
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::X).length())
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
|
||||
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
tool_data.points.push(vec![pos]);
|
||||
// Start a new stroke with a single sample
|
||||
tool_data.strokes.push(BrushStroke {
|
||||
trace: vec![BrushInputSample { position: layer_position }],
|
||||
style: BrushStyle {
|
||||
color: tool_options.color.active_color().unwrap_or_default(),
|
||||
diameter: tool_options.diameter / layer_scale,
|
||||
hardness: tool_options.hardness,
|
||||
flow: tool_options.flow,
|
||||
spacing: tool_options.spacing,
|
||||
},
|
||||
});
|
||||
|
||||
if new_layer {
|
||||
add_brush_render(tool_options, tool_data, responses);
|
||||
} else {
|
||||
//tool_data.update_image(node_graph, responses);
|
||||
tool_data.update_points(responses);
|
||||
}
|
||||
tool_data.update_strokes(tool_options, responses);
|
||||
|
||||
Drawing
|
||||
BrushToolFsmState::Drawing
|
||||
}
|
||||
(Drawing, PointerMove) => {
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
if tool_data.points.last().and_then(|x| x.last()) != Some(&pos) {
|
||||
// Linear interpolation for when the mouse has moved a lot between frames
|
||||
if let Some(&last_point) = tool_data.points.last().and_then(|x| x.last()) {
|
||||
let distance = (last_point - pos).length();
|
||||
let extra_points = (distance / (tool_options.diameter / 2.)).floor() as usize;
|
||||
tool_data
|
||||
.points
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.extend((0..extra_points).map(|i| last_point.lerp(pos, (i as f64 + 1.) / (extra_points as f64 + 1.))));
|
||||
}
|
||||
|
||||
if let Some(x) = tool_data.points.last_mut() {
|
||||
x.push(pos)
|
||||
}
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
|
||||
if let Some(stroke) = tool_data.strokes.last_mut() {
|
||||
stroke.trace.push(BrushInputSample { position: layer_position })
|
||||
}
|
||||
tool_data.update_strokes(tool_options, responses);
|
||||
|
||||
tool_data.update_points(responses);
|
||||
|
||||
Drawing
|
||||
BrushToolFsmState::Drawing
|
||||
}
|
||||
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||
if !tool_data.points.is_empty() {
|
||||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => {
|
||||
if !tool_data.strokes.is_empty() {
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.points.clear();
|
||||
tool_data.path = None;
|
||||
tool_data.strokes.clear();
|
||||
|
||||
Ready
|
||||
BrushToolFsmState::Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
|
||||
(_, BrushToolMessage::WorkingColorChanged) => {
|
||||
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
@@ -342,7 +353,7 @@ impl Fsm for BrushToolFsmState {
|
||||
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
let hint_data = match self {
|
||||
BrushToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline")])]),
|
||||
BrushToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Stroke")])]),
|
||||
BrushToolFsmState::Drawing => HintData(vec![]),
|
||||
};
|
||||
|
||||
@@ -355,55 +366,10 @@ impl Fsm for BrushToolFsmState {
|
||||
}
|
||||
|
||||
fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque<Message>) {
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
|
||||
let brush_node = DocumentNode {
|
||||
name: "Brush".to_string(),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
NodeInput::value(TaggedValue::VecDVec2(data.points.last().cloned().unwrap_or_default()), false),
|
||||
// Diameter
|
||||
NodeInput::value(TaggedValue::F64(tool_options.diameter), false),
|
||||
// Hardness
|
||||
NodeInput::value(TaggedValue::F64(tool_options.hardness), false),
|
||||
// Flow
|
||||
NodeInput::value(TaggedValue::F64(tool_options.flow), false),
|
||||
// Color
|
||||
NodeInput::value(TaggedValue::Color(tool_options.color.active_color().unwrap()), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (8, 4).into() },
|
||||
..Default::default()
|
||||
};
|
||||
// let monitor_node = DocumentNode {
|
||||
// name: "Monitor".to_string(),
|
||||
// implementation: DocumentNodeImplementation::Unresolved("graphene_std::memo::MonitorNode<_>".into()),
|
||||
// ..Default::default()
|
||||
// };
|
||||
let mut network = NodeNetwork::value_network(brush_node);
|
||||
//network.push_node(monitor_node, true);
|
||||
network.push_output_node();
|
||||
graph_modification_utils::new_custom_layer(network, layer_path, responses);
|
||||
}
|
||||
|
||||
fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>)> {
|
||||
if document.selected_layers().count() != 1 {
|
||||
return None;
|
||||
let mut network = NodeNetwork::default();
|
||||
let output_node = network.push_output_node();
|
||||
if let Some(node) = network.nodes.get_mut(&output_node) {
|
||||
node.inputs.push(NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true))
|
||||
}
|
||||
let layer_path = document.selected_layers().next()?.to_vec();
|
||||
let network = document.document_legacy.layer(&layer_path).ok().and_then(|layer| layer.as_layer_network().ok())?;
|
||||
let brush_node = network.nodes.get(&0)?;
|
||||
if brush_node.implementation != DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()) {
|
||||
return None;
|
||||
}
|
||||
let points_input = brush_node.inputs.get(3)?;
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::VecDVec2(points),
|
||||
..
|
||||
} = points_input else {
|
||||
return None };
|
||||
|
||||
Some((layer_path, points.clone()))
|
||||
graph_modification_utils::new_custom_layer(network, data.layer_path.clone(), responses);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
||||
use graph_craft::executor::Compiler;
|
||||
use graph_craft::imaginate_input::*;
|
||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
|
||||
|
||||
Reference in New Issue
Block a user