Add color choices to the options bar of tools (#1199)

* Generalize PenColorType -> ToolColorType

* impl default for ToolColorOptions

* Add stroke color option to the freehand tool

* Consolidate working color update messages

* Update tool working colours when switching tools

* Update working colors on tool activation

* Add stroke color option to line tool

* Add fill color option to freehand tool

* Add tool color options to spline tool

* Fix freehand tool

* Add color options to text

* Add tool color/weight options to rectangle

* Add tool color/weight options to ellipse

* Add tool color/weight options to shape

* Fix spline default fill/stroke

* Reorder widgets and code cleanup

* Add CustomColor icon

* Fix warnings

* Change color defaults to secondary fill, primary stroke

* Fix spacing between brush options number inputs

* Add toolbar color option to brush

* Implement allowNone on color input widget

* Rearrange widget and remove X from brush

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Chase 2023-05-08 18:00:10 +08:00 committed by Keavon Chambers
parent ab2de96640
commit 7e1b452757
21 changed files with 929 additions and 310 deletions

View File

@ -443,6 +443,9 @@ impl WidgetHolder {
pub fn new(widget: Widget) -> Self {
Self { widget_id: generate_uuid(), widget }
}
pub fn section_separator() -> Self {
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder()
}
pub fn unrelated_separator() -> Self {
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Unrelated).widget_holder()
}

View File

@ -53,11 +53,9 @@ pub struct ColorInput {
// #[serde(rename = "allowTransparency")]
// #[derivative(Default(value = "false"))]
// pub allow_transparency: bool,
// TODO: Implement
// #[serde(rename = "allowNone")]
// #[derivative(Default(value = "false"))]
// pub allow_none: bool,
#[serde(rename = "allowNone")]
#[derivative(Default(value = "true"))]
pub allow_none: bool,
// pub disabled: bool,
pub tooltip: String,

View File

@ -588,7 +588,7 @@ pub fn blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _con
}
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(), true);
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);

View File

@ -0,0 +1,106 @@
use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, IconButton, RadioEntryData, RadioInput, TextLabel, WidgetHolder};
use graphene_core::Color;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum ToolColorType {
Primary,
Secondary,
Custom,
}
pub struct ToolColorOptions {
pub custom_color: Option<Color>,
pub primary_working_color: Option<Color>,
pub secondary_working_color: Option<Color>,
pub color_type: ToolColorType,
}
impl Default for ToolColorOptions {
fn default() -> Self {
Self {
color_type: ToolColorType::Primary,
custom_color: Some(Color::BLACK),
primary_working_color: Some(Color::BLACK),
secondary_working_color: Some(Color::WHITE),
}
}
}
impl ToolColorOptions {
pub fn new_primary() -> Self {
Self::default()
}
pub fn new_secondary() -> Self {
Self {
color_type: ToolColorType::Secondary,
..Default::default()
}
}
pub fn new_none() -> Self {
Self {
color_type: ToolColorType::Custom,
custom_color: None,
..Default::default()
}
}
pub fn active_color(&self) -> Option<Color> {
match self.color_type {
ToolColorType::Custom => self.custom_color,
ToolColorType::Primary => self.primary_working_color,
ToolColorType::Secondary => self.secondary_working_color,
}
}
pub fn create_widgets(
&self,
label_text: impl Into<String>,
color_allow_none: bool,
reset_callback: WidgetCallback<IconButton>,
radio_callback: fn(ToolColorType) -> WidgetCallback<()>,
color_callback: WidgetCallback<ColorInput>,
) -> Vec<WidgetHolder> {
let mut widgets = vec![TextLabel::new(label_text).widget_holder()];
if !color_allow_none {
widgets.push(WidgetHolder::unrelated_separator());
} else {
let mut reset = IconButton::new("CloseX", 12)
.disabled(self.custom_color.is_none() && self.color_type == ToolColorType::Custom)
.tooltip("Clear Color");
reset.on_update = reset_callback;
widgets.push(WidgetHolder::related_separator());
widgets.push(reset.widget_holder());
widgets.push(WidgetHolder::related_separator());
};
let entries = vec![
("WorkingColorsPrimary", "Primary Working Color", ToolColorType::Primary),
("WorkingColorsSecondary", "Secondary Working Color", ToolColorType::Secondary),
("CustomColor", "Custom Color", ToolColorType::Custom),
]
.into_iter()
.map(|(icon, tooltip, color_type)| {
let mut entry = RadioEntryData::new("").tooltip(tooltip).icon(icon);
entry.on_update = radio_callback(color_type);
entry
})
.collect();
let radio = RadioInput::new(entries).selected_index(self.color_type.clone() as u32).widget_holder();
widgets.push(radio);
widgets.push(WidgetHolder::related_separator());
let mut color_input = ColorInput::new(self.active_color()).allow_none(color_allow_none);
color_input.on_update = color_callback;
widgets.push(color_input.widget_holder());
widgets
}
}

View File

@ -1,3 +1,4 @@
pub mod color_selector;
pub mod graph_modification_utils;
pub mod overlay_renderer;
pub mod path_outline;

View File

@ -129,6 +129,9 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
// Send the DocumentIsDirty message to the active tool's sub-tool message handler
responses.add(BroadcastEvent::DocumentIsDirty);
// Update the working colors for the active tool
responses.add(BroadcastEvent::WorkingColorChanged);
// Send tool options to the frontend
responses.add(ToolMessage::RefreshToolOptions);

View File

@ -1,18 +1,20 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
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::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::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
use graphene_core::raster::ImageFrame;
use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@ -28,6 +30,7 @@ pub struct BrushOptions {
diameter: f64,
hardness: f64,
flow: f64,
color: ToolColorOptions,
}
impl Default for BrushOptions {
@ -36,6 +39,7 @@ impl Default for BrushOptions {
diameter: 40.,
hardness: 50.,
flow: 100.,
color: ToolColorOptions::default(),
}
}
}
@ -47,6 +51,8 @@ pub enum BrushToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
DragStart,
@ -59,9 +65,12 @@ pub enum BrushToolMessage {
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum BrushToolMessageOptionsUpdate {
ChangeDiameter(f64),
Color(Option<Color>),
ColorType(ToolColorType),
Diameter(f64),
Flow(f64),
Hardness(f64),
WorkingColors(Option<Color>, Option<Color>),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@ -85,32 +94,42 @@ impl ToolMetadata for BrushTool {
impl PropertyHolder for BrushTool {
fn properties(&self) -> Layout {
let diameter = NumberInput::new(Some(self.options.diameter))
.label("Diameter")
.min(1.)
.unit(" px")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into())
.widget_holder();
let hardness = NumberInput::new(Some(self.options.hardness))
.label("Hardness")
.min(0.)
.max(100.)
.unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into())
.widget_holder();
let flow = NumberInput::new(Some(self.options.flow))
.label("Flow")
.min(1.)
.max(100.)
.unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into())
.widget_holder();
let mut widgets = vec![
NumberInput::new(Some(self.options.diameter))
.label("Diameter")
.min(1.)
.unit(" px")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into())
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(Some(self.options.hardness))
.label("Hardness")
.min(0.)
.max(100.)
.unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into())
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(Some(self.options.flow))
.label("Flow")
.min(1.)
.max(100.)
.unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into())
.widget_holder(),
];
let separator = Separator::new(SeparatorDirection::Horizontal, SeparatorType::Related).widget_holder();
widgets.push(WidgetHolder::section_separator());
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![diameter, separator.clone(), hardness, separator, flow],
}]))
widgets.append(&mut self.options.color.create_widgets(
"Color",
false,
WidgetCallback::new(|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value)).into()),
));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
@ -133,7 +152,22 @@ 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::Color(color) => {
self.options.color.custom_color = color;
self.options.color.color_type = ToolColorType::Custom;
}
BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type,
BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.color.primary_working_color = primary;
self.options.color.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
@ -164,6 +198,7 @@ impl ToolTransition for BrushTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(BrushToolMessage::Abort.into()),
working_color_changed: Some(BrushToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -247,7 +282,7 @@ impl Fsm for BrushToolFsmState {
tool_data.points.push(vec![pos]);
if new_layer {
add_brush_render(tool_options, tool_data, global_tool_data, responses);
add_brush_render(tool_options, tool_data, responses);
} else {
//tool_data.update_image(node_graph, responses);
tool_data.update_points(responses);
@ -291,6 +326,13 @@ impl Fsm for BrushToolFsmState {
Ready
}
(_, WorkingColorChanged) => {
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {
@ -312,7 +354,7 @@ impl Fsm for BrushToolFsmState {
}
}
fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque<Message>) {
let layer_path = data.path.clone().unwrap();
let brush_node = DocumentNode {
@ -329,7 +371,7 @@ fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, tool_data
// Flow
NodeInput::value(TaggedValue::F64(tool_options.flow), false),
// Color
NodeInput::value(TaggedValue::Color(tool_data.primary_color), false),
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() },

View File

@ -1,13 +1,17 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, NumberInput, WidgetHolder};
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::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use graphene_core::vector::style::Fill;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@ -16,15 +20,45 @@ use serde::{Deserialize, Serialize};
pub struct EllipseTool {
fsm_state: EllipseToolFsmState,
data: EllipseToolData,
options: EllipseToolOptions,
}
pub struct EllipseToolOptions {
line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
}
impl Default for EllipseToolOptions {
fn default() -> Self {
Self {
line_weight: 5.,
fill: ToolColorOptions::new_secondary(),
stroke: ToolColorOptions::new_primary(),
}
}
}
#[remain::sorted]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum EllipseOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
}
#[remain::sorted]
#[impl_message(Message, ToolMessage, Ellipse)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum EllipseToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
DragStart,
@ -33,6 +67,7 @@ pub enum EllipseToolMessage {
center: Key,
lock_ratio: Key,
},
UpdateOptions(EllipseOptionsUpdate),
}
impl ToolMetadata for EllipseTool {
@ -47,11 +82,73 @@ impl ToolMetadata for EllipseTool {
}
}
impl PropertyHolder for EllipseTool {}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder()
}
impl PropertyHolder for EllipseTool {
fn properties(&self) -> Layout {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value)).into()),
);
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value)).into()),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EllipseTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
if let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message {
match action {
EllipseOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
EllipseOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
EllipseOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@ -74,6 +171,7 @@ impl ToolTransition for EllipseTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(EllipseToolMessage::Abort.into()),
working_color_changed: Some(EllipseToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -93,7 +191,7 @@ struct EllipseToolData {
impl Fsm for EllipseToolFsmState {
type ToolData = EllipseToolData;
type ToolOptions = ();
type ToolOptions = EllipseToolOptions;
fn transition(
self,
@ -106,7 +204,7 @@ impl Fsm for EllipseToolFsmState {
render_data,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use EllipseToolFsmState::*;
@ -130,10 +228,15 @@ impl Fsm for EllipseToolFsmState {
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses);
// Set the fill color to the primary working color
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if fill_color.is_some() { Fill::Solid(fill_color.unwrap()) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color),
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
@ -157,6 +260,13 @@ impl Fsm for EllipseToolFsmState {
Ready
}
(_, WorkingColorChanged) => {
responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {

View File

@ -1,15 +1,19 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
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::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId;
use document_legacy::Operation;
use graphene_core::vector::style::Stroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@ -23,11 +27,17 @@ pub struct FreehandTool {
pub struct FreehandOptions {
line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
}
impl Default for FreehandOptions {
fn default() -> Self {
Self { line_weight: 5. }
Self {
line_weight: 5.,
fill: ToolColorOptions::new_none(),
stroke: ToolColorOptions::new_primary(),
}
}
}
@ -38,18 +48,25 @@ pub enum FreehandToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
DragStart,
DragStop,
PointerMove,
UpdateOptions(FreehandToolMessageOptionsUpdate),
UpdateOptions(FreehandOptionsUpdate),
}
#[remain::sorted]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum FreehandToolMessageOptionsUpdate {
pub enum FreehandOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@ -71,15 +88,38 @@ impl ToolMetadata for FreehandTool {
}
}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
.label("Weight")
.min(1.)
.on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder()
}
impl PropertyHolder for FreehandTool {
fn properties(&self) -> Layout {
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(1.)
.on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value)).into()),
);
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value)).into()),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
@ -87,8 +127,30 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Freehan
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message {
match action {
FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
FreehandOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
FreehandOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
FreehandOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
@ -117,6 +179,7 @@ impl ToolTransition for FreehandTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(FreehandToolMessage::Abort.into()),
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -161,7 +224,7 @@ impl Fsm for FreehandToolFsmState {
tool_data.weight = tool_options.line_weight;
add_polyline(tool_data, global_tool_data, responses);
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
Drawing
}
@ -172,14 +235,14 @@ impl Fsm for FreehandToolFsmState {
tool_data.points.push(pos);
}
add_polyline(tool_data, global_tool_data, responses);
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
Drawing
}
(Drawing, DragStop) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data));
add_polyline(tool_data, global_tool_data, responses);
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
@ -190,6 +253,13 @@ impl Fsm for FreehandToolFsmState {
Ready
}
(_, FreehandToolMessage::WorkingColorChanged) => {
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {
@ -215,14 +285,19 @@ fn remove_preview(data: &FreehandToolData) -> Message {
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
}
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
fn add_polyline(data: &FreehandToolData, stroke_color: Option<Color>, fill_color: Option<Color>, responses: &mut VecDeque<Message>) {
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
let layer_path = data.path.clone().unwrap();
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if fill_color.is_some() { Fill::Solid(fill_color.unwrap()) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(Some(tool_data.primary_color), data.weight),
stroke: Stroke::new(stroke_color, data.weight),
});
}

View File

@ -117,6 +117,7 @@ impl PropertyHolder for GradientTool {
])
.selected_index((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32)
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![gradient_type] }]))
}
}

View File

@ -2,9 +2,12 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
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::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@ -12,6 +15,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId;
use graphene_core::vector::style::Stroke;
use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@ -25,11 +29,15 @@ pub struct LineTool {
pub struct LineOptions {
line_weight: f64,
stroke: ToolColorOptions,
}
impl Default for LineOptions {
fn default() -> Self {
Self { line_weight: 5. }
Self {
line_weight: 5.,
stroke: ToolColorOptions::new_primary(),
}
}
}
@ -40,6 +48,8 @@ pub enum LineToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
DragStart,
@ -56,6 +66,9 @@ pub enum LineToolMessage {
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum LineOptionsUpdate {
LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
}
impl ToolMetadata for LineTool {
@ -70,15 +83,28 @@ impl ToolMetadata for LineTool {
}
}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder()
}
impl PropertyHolder for LineTool {
fn properties(&self) -> Layout {
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
let mut widgets = self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value)).into()),
);
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
@ -87,7 +113,22 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineToo
if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message {
match action {
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
LineOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
LineOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
@ -106,6 +147,7 @@ impl ToolTransition for LineTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(LineToolMessage::Abort.into()),
working_color_changed: Some(LineToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -164,7 +206,7 @@ impl Fsm for LineToolFsmState {
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
stroke: Stroke::new(Some(global_tool_data.primary_color), tool_options.line_weight),
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
tool_data.weight = tool_options.line_weight;
@ -192,6 +234,13 @@ impl Fsm for LineToolFsmState {
tool_data.path = None;
Ready
}
(_, WorkingColorChanged) => {
responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {

View File

@ -1,12 +1,13 @@
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, IconButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextLabel, WidgetHolder};
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
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::common_functionality::overlay_renderer::OverlayRenderer;
@ -30,52 +31,18 @@ pub struct PenTool {
options: PenOptions,
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum PenColorType {
Primary,
Secondary,
Custom,
}
pub struct PenColorOptions {
color: Option<Color>,
primary_working_color: Option<Color>,
secondary_working_color: Option<Color>,
color_type: PenColorType,
}
impl PenColorOptions {
pub fn active_color(&self) -> Option<Color> {
match self.color_type {
PenColorType::Custom => self.color,
PenColorType::Primary => self.primary_working_color,
PenColorType::Secondary => self.secondary_working_color,
}
}
}
pub struct PenOptions {
line_weight: f64,
fill: PenColorOptions,
stroke: PenColorOptions,
fill: ToolColorOptions,
stroke: ToolColorOptions,
}
impl Default for PenOptions {
fn default() -> Self {
Self {
line_weight: 5.,
fill: PenColorOptions {
color: Some(Color::BLACK),
primary_working_color: Some(Color::BLACK),
secondary_working_color: Some(Color::WHITE),
color_type: PenColorType::Primary,
},
stroke: PenColorOptions {
color: None,
primary_working_color: Some(Color::BLACK),
secondary_working_color: Some(Color::WHITE),
color_type: PenColorType::Custom,
},
fill: ToolColorOptions::new_secondary(),
stroke: ToolColorOptions::new_primary(),
}
}
}
@ -119,12 +86,11 @@ enum PenToolFsmState {
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum PenOptionsUpdate {
FillColor(Option<Color>),
FillColorType(PenColorType),
FillColorType(ToolColorType),
LineWeight(f64),
PrimaryColor(Option<Color>),
SecondaryColor(Option<Color>),
StrokeColor(Option<Color>),
StrokeColorType(PenColorType),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
}
impl ToolMetadata for PenTool {
@ -139,85 +105,6 @@ impl ToolMetadata for PenTool {
}
}
// TODO: Generalize create_fill_widget and create_stroke_widget into one function.
fn create_fill_widget(fill: &PenColorOptions) -> Vec<WidgetHolder> {
let label = TextLabel::new("Fill").widget_holder();
let reset = IconButton::new("CloseX", 12)
.disabled(fill.color.is_none() && fill.color_type == PenColorType::Custom)
.on_update(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into())
.tooltip("Clear color")
.widget_holder();
let entries = vec![
("WorkingColorsPrimary", "Primary Working Color", PenColorType::Primary),
("WorkingColorsSecondary", "Secondary Working Color", PenColorType::Secondary),
("Edit", "Custom Color", PenColorType::Custom),
]
.into_iter()
.map(|(icon, tooltip, color_type)| {
RadioEntryData::new("")
.tooltip(tooltip)
.icon(icon)
.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into())
})
.collect();
let radio = RadioInput::new(entries).selected_index(fill.color_type.clone() as u32).widget_holder();
let color_input = ColorInput::new(fill.active_color())
.on_update(|fill_color| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(fill_color.value)).into())
.widget_holder();
vec![
label,
WidgetHolder::related_separator(),
reset,
WidgetHolder::related_separator(),
radio,
WidgetHolder::related_separator(),
color_input,
]
}
fn create_stroke_widget(stroke: &PenColorOptions) -> Vec<WidgetHolder> {
let label = TextLabel::new("Stroke").widget_holder();
let reset = IconButton::new("CloseX", 12)
.disabled(stroke.color.is_none() && stroke.color_type == PenColorType::Custom)
.on_update(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into())
.tooltip("Clear color")
.widget_holder();
let entries = vec![
("WorkingColorsPrimary", "Primary Working Color", PenColorType::Primary),
("WorkingColorsSecondary", "Secondary Working Color", PenColorType::Secondary),
("Edit", "Custom Color", PenColorType::Custom),
]
.into_iter()
.map(|(icon, tooltip, color_type)| {
RadioEntryData::new("")
.tooltip(tooltip)
.icon(icon)
.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into())
})
.collect();
let radio = RadioInput::new(entries).selected_index(stroke.color_type.clone() as u32).widget_holder();
let color_input = ColorInput::new(stroke.active_color())
.on_update(|stroke_color| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(stroke_color.value)).into())
.widget_holder();
vec![
label,
WidgetHolder::related_separator(),
reset,
WidgetHolder::related_separator(),
radio,
WidgetHolder::related_separator(),
color_input,
]
}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
@ -229,11 +116,26 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
impl PropertyHolder for PenTool {
fn properties(&self) -> Layout {
let mut widgets = create_fill_widget(&self.options.fill);
widgets.push(Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder());
widgets.append(&mut create_stroke_widget(&self.options.stroke));
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value)).into()),
);
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value)).into()),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
@ -244,22 +146,20 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
match action {
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
PenOptionsUpdate::FillColor(color) => {
self.options.fill.color = color;
self.options.fill.color_type = PenColorType::Custom;
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
PenOptionsUpdate::StrokeColor(color) => {
self.options.stroke.color = color;
self.options.stroke.color_type = PenColorType::Custom;
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
PenOptionsUpdate::PrimaryColor(color) => {
self.options.stroke.primary_working_color = color;
self.options.fill.primary_working_color = color;
}
PenOptionsUpdate::SecondaryColor(color) => {
self.options.stroke.secondary_working_color = color;
self.options.fill.secondary_working_color = color;
PenOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
@ -701,8 +601,10 @@ impl Fsm for PenToolFsmState {
self
}
(_, PenToolMessage::WorkingColorChanged) => {
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::PrimaryColor(Some(global_tool_data.primary_color))));
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::SecondaryColor(Some(global_tool_data.secondary_color))));
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {

View File

@ -1,29 +1,63 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, NumberInput, WidgetHolder};
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::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::DVec2;
use graphene_core::vector::style::Fill;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct RectangleTool {
fsm_state: RectangleToolFsmState,
tool_data: RectangleToolData,
options: RectangleToolOptions,
}
pub struct RectangleToolOptions {
line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
}
impl Default for RectangleToolOptions {
fn default() -> Self {
Self {
line_weight: 5.,
fill: ToolColorOptions::new_secondary(),
stroke: ToolColorOptions::new_primary(),
}
}
}
#[remain::sorted]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum RectangleOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
}
#[remain::sorted]
#[impl_message(Message, ToolMessage, Rectangle)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum RectangleToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
DragStart,
@ -32,13 +66,76 @@ pub enum RectangleToolMessage {
center: Key,
lock_ratio: Key,
},
UpdateOptions(RectangleOptionsUpdate),
}
impl PropertyHolder for RectangleTool {}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder()
}
impl PropertyHolder for RectangleTool {
fn properties(&self) -> Layout {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value)).into()),
);
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value)).into()),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for RectangleTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
if let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message {
match action {
RectangleOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
RectangleOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
RectangleOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@ -73,6 +170,7 @@ impl ToolTransition for RectangleTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(RectangleToolMessage::Abort.into()),
working_color_changed: Some(RectangleToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -92,7 +190,7 @@ struct RectangleToolData {
impl Fsm for RectangleToolFsmState {
type ToolData = RectangleToolData;
type ToolOptions = ();
type ToolOptions = RectangleToolOptions;
fn transition(
self,
@ -105,7 +203,7 @@ impl Fsm for RectangleToolFsmState {
render_data,
..
}: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
use RectangleToolFsmState::*;
@ -124,9 +222,16 @@ impl Fsm for RectangleToolFsmState {
responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if fill_color.is_some() { Fill::Solid(fill_color.unwrap()) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color),
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
@ -151,6 +256,13 @@ impl Fsm for RectangleToolFsmState {
Ready
}
(_, WorkingColorChanged) => {
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {

View File

@ -8,7 +8,6 @@ use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::assist_widgets::{PivotAssist, PivotPosition};
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
use crate::messages::layout::utility_types::widgets::input_widgets::{DropdownEntryData, DropdownInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*;
@ -137,7 +136,7 @@ impl PropertyHolder for SelectTool {
PivotAssist::new(self.tool_data.pivot.to_pivot_position())
.on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into())
.widget_holder(),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
WidgetHolder::section_separator(),
IconButton::new("AlignLeft", 24)
.tooltip("Align Left")
.on_update(|_| {
@ -201,7 +200,7 @@ impl PropertyHolder for SelectTool {
.widget_holder(),
WidgetHolder::related_separator(),
PopoverButton::new("Align", "Coming soon").widget_holder(),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
WidgetHolder::section_separator(),
IconButton::new("FlipHorizontal", 24)
.tooltip("Flip Horizontal")
.on_update(|_| SelectToolMessage::FlipHorizontal.into())
@ -216,7 +215,7 @@ impl PropertyHolder for SelectTool {
text: "Coming soon".into(),
..Default::default()
})),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
WidgetHolder::section_separator(),
IconButton::new("BooleanUnion", 24)
.tooltip("Boolean Union (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())

View File

@ -1,14 +1,17 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, NumberInput, WidgetHolder};
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::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use graphene_core::vector::style::Fill;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@ -21,22 +24,32 @@ pub struct ShapeTool {
}
pub struct ShapeOptions {
line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
vertices: u32,
}
impl Default for ShapeOptions {
fn default() -> Self {
Self { vertices: 5 }
Self {
vertices: 5,
line_weight: 5.,
fill: ToolColorOptions::new_secondary(),
stroke: ToolColorOptions::new_primary(),
}
}
}
#[remain::sorted]
#[impl_message(Message, ToolMessage, Shape)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum ShapeToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
DragStart,
@ -49,9 +62,15 @@ pub enum ShapeToolMessage {
}
#[remain::sorted]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum ShapeOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
Vertices(u32),
WorkingColors(Option<Color>, Option<Color>),
}
impl ToolMetadata for ShapeTool {
@ -66,26 +85,84 @@ impl ToolMetadata for ShapeTool {
}
}
impl PropertyHolder for ShapeTool {
fn properties(&self) -> Layout {
let sides = NumberInput::new(Some(self.options.vertices as f64))
.label("Sides")
.int()
.min(3.)
.max(1000.)
.mode(crate::messages::layout::utility_types::widget_prelude::NumberInputMode::Increment)
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![sides] }]))
}
fn create_sides_widget(vertices: u32) -> WidgetHolder {
NumberInput::new(Some(vertices as f64))
.label("Sides")
.int()
.min(3.)
.max(1000.)
.mode(crate::messages::layout::utility_types::widget_prelude::NumberInputMode::Increment)
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into())
.widget_holder()
}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder()
}
impl PropertyHolder for ShapeTool {
fn properties(&self) -> Layout {
let mut widgets = vec![create_sides_widget(self.options.vertices)];
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value)).into()),
));
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value)).into()),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message {
match action {
ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
ShapeOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
ShapeOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
ShapeOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
ShapeOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
ShapeOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
ShapeOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
@ -112,6 +189,7 @@ impl ToolTransition for ShapeTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(ShapeToolMessage::Abort.into()),
working_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -162,9 +240,16 @@ impl Fsm for ShapeToolFsmState {
let subpath = bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.);
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if fill_color.is_some() { Fill::Solid(fill_color.unwrap()) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color),
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
});
Drawing
@ -189,6 +274,13 @@ impl Fsm for ShapeToolFsmState {
Ready
}
(_, WorkingColorChanged) => {
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {

View File

@ -1,16 +1,20 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
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::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::{LayerId, Operation};
use graphene_core::vector::style::Stroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@ -24,11 +28,17 @@ pub struct SplineTool {
pub struct SplineOptions {
line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
}
impl Default for SplineOptions {
fn default() -> Self {
Self { line_weight: 5. }
Self {
line_weight: 5.,
fill: ToolColorOptions::new_none(),
stroke: ToolColorOptions::new_primary(),
}
}
}
@ -39,6 +49,8 @@ pub enum SplineToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
Confirm,
@ -59,7 +71,12 @@ enum SplineToolFsmState {
#[remain::sorted]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum SplineOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
}
impl ToolMetadata for SplineTool {
@ -74,15 +91,38 @@ impl ToolMetadata for SplineTool {
}
}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder()
}
impl PropertyHolder for SplineTool {
fn properties(&self) -> Layout {
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value)).into()),
);
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value)).into()),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
@ -91,7 +131,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineT
if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message {
match action {
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
SplineOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
SplineOptionsUpdate::StrokeColor(color) => {
self.options.stroke.custom_color = color;
self.options.stroke.color_type = ToolColorType::Custom;
}
SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
SplineOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = primary;
self.options.stroke.secondary_working_color = secondary;
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
@ -123,6 +185,7 @@ impl ToolTransition for SplineTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
tool_abort: Some(SplineToolMessage::Abort.into()),
working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -178,7 +241,7 @@ impl Fsm for SplineToolFsmState {
tool_data.weight = tool_options.line_weight;
add_spline(tool_data, global_tool_data, true, responses);
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
@ -194,7 +257,7 @@ impl Fsm for SplineToolFsmState {
}
responses.add(remove_preview(tool_data));
add_spline(tool_data, global_tool_data, true, responses);
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
@ -204,14 +267,14 @@ impl Fsm for SplineToolFsmState {
tool_data.next_point = pos;
responses.add(remove_preview(tool_data));
add_spline(tool_data, global_tool_data, true, responses);
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
Drawing
}
(Drawing, Confirm) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data));
add_spline(tool_data, global_tool_data, false, responses);
add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
@ -223,6 +286,13 @@ impl Fsm for SplineToolFsmState {
Ready
}
(_, WorkingColorChanged) => {
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {
@ -254,7 +324,7 @@ fn remove_preview(tool_data: &SplineToolData) -> Message {
.into()
}
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
fn add_spline(tool_data: &SplineToolData, show_preview: bool, fill_color: Option<Color>, stroke_color: Option<Color>, responses: &mut VecDeque<Message>) {
let mut points = tool_data.points.clone();
if show_preview {
points.push(tool_data.next_point)
@ -267,8 +337,13 @@ fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, s
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses);
responses.add(GraphOperationMessage::FillSet {
layer: layer_path.clone(),
fill: if fill_color.is_some() { Fill::Solid(fill_color.unwrap()) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path.clone(),
stroke: Stroke::new(Some(global_tool_data.primary_color), tool_data.weight),
stroke: Stroke::new(stroke_color, tool_data.weight),
});
}

View File

@ -4,9 +4,10 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::input_widgets::{FontInput, NumberInput};
use crate::messages::layout::utility_types::widgets::input_widgets::{ColorInput, FontInput, NumberInput};
use crate::messages::portfolio::document::node_graph::new_text_network;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@ -34,6 +35,7 @@ pub struct TextOptions {
font_size: u32,
font_name: String,
font_style: String,
fill: ToolColorOptions,
}
impl Default for TextOptions {
@ -42,20 +44,22 @@ impl Default for TextOptions {
font_size: 24,
font_name: "Merriweather".into(),
font_style: "Normal (400)".into(),
fill: ToolColorOptions::new_primary(),
}
}
}
#[remain::sorted]
#[impl_message(Message, ToolMessage, Text)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum TextToolMessage {
// Standard messages
#[remain::unsorted]
Abort,
#[remain::unsorted]
DocumentIsDirty,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages
CommitText,
@ -71,10 +75,13 @@ pub enum TextToolMessage {
}
#[remain::sorted]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum TextOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
Font { family: String, style: String },
FontSize(u32),
WorkingColors(Option<Color>, Option<Color>),
}
impl ToolMetadata for TextTool {
@ -89,46 +96,60 @@ impl ToolMetadata for TextTool {
}
}
fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
let font = FontInput {
is_style_picker: false,
font_family: tool.options.font_name.clone(),
font_style: tool.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let style = FontInput {
is_style_picker: true,
font_family: tool.options.font_name.clone(),
font_style: tool.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let size = NumberInput::new(Some(tool.options.font_size as f64))
.unit(" px")
.label("Size")
.int()
.min(1.)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into())
.widget_holder();
vec![font, WidgetHolder::related_separator(), style, WidgetHolder::related_separator(), size]
}
impl PropertyHolder for TextTool {
fn properties(&self) -> Layout {
let font = FontInput {
is_style_picker: false,
font_family: self.options.font_name.clone(),
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let style = FontInput {
is_style_picker: true,
font_family: self.options.font_name.clone(),
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let size = NumberInput::new(Some(self.options.font_size as f64))
.unit(" px")
.label("Size")
.int()
.min(1.)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![font, WidgetHolder::related_separator(), style, WidgetHolder::related_separator(), size],
}]))
let mut widgets = create_text_widgets(self);
widgets.push(WidgetHolder::section_separator());
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into()),
|color_type: ToolColorType| WidgetCallback::new(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value)).into()),
));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
}
@ -143,7 +164,22 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
self.register_properties(responses, LayoutTarget::ToolOptions);
}
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
TextOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
}
TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
TextOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.fill.primary_working_color = primary;
self.options.fill.secondary_working_color = secondary;
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return;
}
@ -172,6 +208,7 @@ impl ToolTransition for TextTool {
document_dirty: Some(TextToolMessage::DocumentIsDirty.into()),
tool_abort: Some(TextToolMessage::Abort.into()),
selection_changed: Some(TextToolMessage::DocumentIsDirty.into()),
working_color_changed: Some(TextToolMessage::WorkingColorChanged.into()),
..Default::default()
}
}
@ -188,7 +225,7 @@ pub struct EditingText {
text: String,
font: Font,
font_size: f64,
color: Color,
color: Option<Color>,
transform: DAffine2,
}
@ -211,7 +248,7 @@ impl TextToolData {
text: editing_text.text.clone(),
line_width: None,
font_size: editing_text.font_size,
color: editing_text.color,
color: editing_text.color.unwrap_or(Color::BLACK),
url: render_data.font_cache.get_preview_url(&editing_text.font).cloned().unwrap_or_default(),
transform: editing_text.transform.to_cols_array(),
});
@ -237,7 +274,7 @@ impl TextToolData {
text: text.clone(),
font: font.clone(),
font_size,
color,
color: Some(color),
transform,
});
self.new_text = text.clone();
@ -291,7 +328,7 @@ impl TextToolData {
});
responses.add(GraphOperationMessage::FillSet {
layer: self.layer_path.clone(),
fill: Fill::solid(editing_text.color),
fill: if editing_text.color.is_some() { Fill::Solid(editing_text.color.unwrap()) } else { Fill::None },
});
responses.add(GraphOperationMessage::TransformSet {
layer: self.layer_path.clone(),
@ -470,7 +507,7 @@ impl Fsm for TextToolFsmState {
transform: DAffine2::from_translation(input.mouse.position),
font_size: tool_options.font_size as f64,
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
color: global_tool_data.primary_color,
color: tool_options.fill.active_color(),
});
tool_data.new_text = String::new();
tool_data.layer_path = document.get_path_for_new_layer();
@ -521,6 +558,13 @@ impl Fsm for TextToolFsmState {
tool_data.update_bounds_overlay(document, render_data, responses);
TextToolFsmState::Editing
}
(_, TextToolMessage::WorkingColorChanged) => {
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self,
}
} else {

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M8.2,7.2L4.5,4.4l-0.8,1C3,6.2,3.2,7.5,4.1,8.2L5,8.9c-1.1,1.1-3.3,3.2-3.7,3.7c-0.5,0.7-0.4,1.6,0.3,2.1c0.7,0.5,1.6,0.4,2.1-0.3c0.4-0.5,1.9-3.2,2.7-4.5l0.9,0.7c0.9,0.7,2.1,0.5,2.8-0.4l0.8-1L8.2,7.2z" />
<path d="M11.7,8.1L16,2.5c-5.6,0.4-7.9-2.8-9.6-0.6l-1,1.3L11.7,8.1z" />
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@ -15,8 +15,8 @@
export let value: Color;
// TODO: Implement
// export let allowTransparency = false;
// export let allowNone = false;
// export let disabled = false;
export let allowNone = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
@ -45,7 +45,7 @@
value = detail;
dispatch("value", detail);
}}
allowNone={true}
{allowNone}
/>
</LayoutRow>

View File

@ -95,6 +95,7 @@ import BooleanUnion from "@graphite-frontend/assets/icon-16px-solid/boolean-unio
import CheckboxChecked from "@graphite-frontend/assets/icon-16px-solid/checkbox-checked.svg";
import CheckboxUnchecked from "@graphite-frontend/assets/icon-16px-solid/checkbox-unchecked.svg";
import Copy from "@graphite-frontend/assets/icon-16px-solid/copy.svg";
import CustomColor from "@graphite-frontend/assets/icon-16px-solid/custom-color.svg";
import Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg";
import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg";
import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.svg";
@ -155,6 +156,7 @@ const SOLID_16PX = {
CheckboxChecked: { svg: CheckboxChecked, size: 16 },
CheckboxUnchecked: { svg: CheckboxUnchecked, size: 16 },
Copy: { svg: Copy, size: 16 },
CustomColor: { svg: CustomColor, size: 16 },
Edit: { svg: Edit, size: 16 },
Eyedropper: { svg: Eyedropper, size: 16 },
EyeHidden: { svg: EyeHidden, size: 16 },

View File

@ -832,9 +832,10 @@ export class ColorInput extends WidgetProps {
)
value!: Color;
allowNone!: boolean;
// TODO: Implement
// allowTransparency!: boolean;
// allowNone!: boolean;
// disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)