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 { pub fn new(widget: Widget) -> Self {
Self { widget_id: generate_uuid(), widget } Self { widget_id: generate_uuid(), widget }
} }
pub fn section_separator() -> Self {
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder()
}
pub fn unrelated_separator() -> Self { pub fn unrelated_separator() -> Self {
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Unrelated).widget_holder() Separator::new(SeparatorDirection::Horizontal, SeparatorType::Unrelated).widget_holder()
} }

View File

@ -53,11 +53,9 @@ pub struct ColorInput {
// #[serde(rename = "allowTransparency")] // #[serde(rename = "allowTransparency")]
// #[derivative(Default(value = "false"))] // #[derivative(Default(value = "false"))]
// pub allow_transparency: bool, // pub allow_transparency: bool,
#[serde(rename = "allowNone")]
// TODO: Implement #[derivative(Default(value = "true"))]
// #[serde(rename = "allowNone")] pub allow_none: bool,
// #[derivative(Default(value = "false"))]
// pub allow_none: bool,
// pub disabled: bool, // pub disabled: bool,
pub tooltip: String, 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> { 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 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 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 graph_modification_utils;
pub mod overlay_renderer; pub mod overlay_renderer;
pub mod path_outline; 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 // Send the DocumentIsDirty message to the active tool's sub-tool message handler
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
// Update the working colors for the active tool
responses.add(BroadcastEvent::WorkingColorChanged);
// Send tool options to the frontend // Send tool options to the frontend
responses.add(ToolMessage::RefreshToolOptions); responses.add(ToolMessage::RefreshToolOptions);

View File

@ -1,18 +1,20 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; 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::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 crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId; use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
use graphene_core::raster::ImageFrame; use graphene_core::raster::ImageFrame;
use graphene_core::Color;
use glam::DVec2; use glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -28,6 +30,7 @@ pub struct BrushOptions {
diameter: f64, diameter: f64,
hardness: f64, hardness: f64,
flow: f64, flow: f64,
color: ToolColorOptions,
} }
impl Default for BrushOptions { impl Default for BrushOptions {
@ -36,6 +39,7 @@ impl Default for BrushOptions {
diameter: 40., diameter: 40.,
hardness: 50., hardness: 50.,
flow: 100., flow: 100.,
color: ToolColorOptions::default(),
} }
} }
} }
@ -47,6 +51,8 @@ pub enum BrushToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
@ -59,9 +65,12 @@ pub enum BrushToolMessage {
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum BrushToolMessageOptionsUpdate { pub enum BrushToolMessageOptionsUpdate {
ChangeDiameter(f64), ChangeDiameter(f64),
Color(Option<Color>),
ColorType(ToolColorType),
Diameter(f64), Diameter(f64),
Flow(f64), Flow(f64),
Hardness(f64), Hardness(f64),
WorkingColors(Option<Color>, Option<Color>),
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@ -85,32 +94,42 @@ impl ToolMetadata for BrushTool {
impl PropertyHolder for BrushTool { impl PropertyHolder for BrushTool {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let diameter = NumberInput::new(Some(self.options.diameter)) let mut widgets = vec![
.label("Diameter") NumberInput::new(Some(self.options.diameter))
.min(1.) .label("Diameter")
.unit(" px") .min(1.)
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into()) .unit(" px")
.widget_holder(); .on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into())
let hardness = NumberInput::new(Some(self.options.hardness)) .widget_holder(),
.label("Hardness") WidgetHolder::related_separator(),
.min(0.) NumberInput::new(Some(self.options.hardness))
.max(100.) .label("Hardness")
.unit("%") .min(0.)
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into()) .max(100.)
.widget_holder(); .unit("%")
let flow = NumberInput::new(Some(self.options.flow)) .on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into())
.label("Flow") .widget_holder(),
.min(1.) WidgetHolder::related_separator(),
.max(100.) NumberInput::new(Some(self.options.flow))
.unit("%") .label("Flow")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into()) .min(1.)
.widget_holder(); .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.append(&mut self.options.color.create_widgets(
widgets: vec![diameter, separator.clone(), hardness, separator, flow], "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::Diameter(diameter) => self.options.diameter = diameter,
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness, BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow, 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; return;
} }
@ -164,6 +198,7 @@ impl ToolTransition for BrushTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(BrushToolMessage::Abort.into()), tool_abort: Some(BrushToolMessage::Abort.into()),
working_color_changed: Some(BrushToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -247,7 +282,7 @@ impl Fsm for BrushToolFsmState {
tool_data.points.push(vec![pos]); tool_data.points.push(vec![pos]);
if new_layer { if new_layer {
add_brush_render(tool_options, tool_data, global_tool_data, responses); add_brush_render(tool_options, tool_data, responses);
} else { } else {
//tool_data.update_image(node_graph, responses); //tool_data.update_image(node_graph, responses);
tool_data.update_points(responses); tool_data.update_points(responses);
@ -291,6 +326,13 @@ impl Fsm for BrushToolFsmState {
Ready Ready
} }
(_, WorkingColorChanged) => {
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } 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 layer_path = data.path.clone().unwrap();
let brush_node = DocumentNode { let brush_node = DocumentNode {
@ -329,7 +371,7 @@ fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, tool_data
// Flow // Flow
NodeInput::value(TaggedValue::F64(tool_options.flow), false), NodeInput::value(TaggedValue::F64(tool_options.flow), false),
// Color // 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()), implementation: DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()),
metadata: graph_craft::document::DocumentNodeMetadata { position: (8, 4).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::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; 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::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::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize; 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::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; 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 glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -16,15 +20,45 @@ use serde::{Deserialize, Serialize};
pub struct EllipseTool { pub struct EllipseTool {
fsm_state: EllipseToolFsmState, fsm_state: EllipseToolFsmState,
data: EllipseToolData, 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] #[remain::sorted]
#[impl_message(Message, ToolMessage, Ellipse)] #[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 { pub enum EllipseToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
@ -33,6 +67,7 @@ pub enum EllipseToolMessage {
center: Key, center: Key,
lock_ratio: Key, lock_ratio: Key,
}, },
UpdateOptions(EllipseOptionsUpdate),
} }
impl ToolMetadata for EllipseTool { 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 { 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>) { 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 { fn actions(&self) -> ActionList {
@ -74,6 +171,7 @@ impl ToolTransition for EllipseTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(EllipseToolMessage::Abort.into()), tool_abort: Some(EllipseToolMessage::Abort.into()),
working_color_changed: Some(EllipseToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -93,7 +191,7 @@ struct EllipseToolData {
impl Fsm for EllipseToolFsmState { impl Fsm for EllipseToolFsmState {
type ToolData = EllipseToolData; type ToolData = EllipseToolData;
type ToolOptions = (); type ToolOptions = EllipseToolOptions;
fn transition( fn transition(
self, self,
@ -106,7 +204,7 @@ impl Fsm for EllipseToolFsmState {
render_data, render_data,
.. ..
}: &mut ToolActionHandlerData, }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use EllipseToolFsmState::*; 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::new_vector_layer(vec![subpath], layer_path.clone(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, 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 { 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, layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color), stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
}); });
Drawing Drawing
@ -157,6 +260,13 @@ impl Fsm for EllipseToolFsmState {
Ready Ready
} }
(_, WorkingColorChanged) => {
responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } else {

View File

@ -1,15 +1,19 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; 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::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; 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::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 crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::LayerId; use document_legacy::LayerId;
use document_legacy::Operation; 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 glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -23,11 +27,17 @@ pub struct FreehandTool {
pub struct FreehandOptions { pub struct FreehandOptions {
line_weight: f64, line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
} }
impl Default for FreehandOptions { impl Default for FreehandOptions {
fn default() -> Self { 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 // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
DragStop, DragStop,
PointerMove, PointerMove,
UpdateOptions(FreehandToolMessageOptionsUpdate), UpdateOptions(FreehandOptionsUpdate),
} }
#[remain::sorted] #[remain::sorted]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum FreehandToolMessageOptionsUpdate { pub enum FreehandOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64), LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[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 { impl PropertyHolder for FreehandTool {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let weight = NumberInput::new(Some(self.options.line_weight)) let mut widgets = self.options.fill.create_widgets(
.unit(" px") "Fill",
.label("Weight") true,
.min(1.) WidgetCallback::new(|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into()),
.on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) |color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()),
.widget_holder(); WidgetCallback::new(|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value)).into()),
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) );
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>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message { if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message {
match action { 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; return;
} }
@ -117,6 +179,7 @@ impl ToolTransition for FreehandTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(FreehandToolMessage::Abort.into()), tool_abort: Some(FreehandToolMessage::Abort.into()),
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -161,7 +224,7 @@ impl Fsm for FreehandToolFsmState {
tool_data.weight = tool_options.line_weight; 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 Drawing
} }
@ -172,14 +235,14 @@ impl Fsm for FreehandToolFsmState {
tool_data.points.push(pos); 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
} }
(Drawing, DragStop) | (Drawing, Abort) => { (Drawing, DragStop) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 { if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data)); 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); responses.add(DocumentMessage::CommitTransaction);
} else { } else {
responses.add(DocumentMessage::AbortTransaction); responses.add(DocumentMessage::AbortTransaction);
@ -190,6 +253,13 @@ impl Fsm for FreehandToolFsmState {
Ready Ready
} }
(_, FreehandToolMessage::WorkingColorChanged) => {
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } else {
@ -215,14 +285,19 @@ fn remove_preview(data: &FreehandToolData) -> Message {
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into() 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 subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
let layer_path = data.path.clone().unwrap(); let layer_path = data.path.clone().unwrap();
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); 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 { responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path, 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) .selected_index((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32)
.widget_holder(); .widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![gradient_type] }])) 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::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; 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::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; 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::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; 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 document_legacy::LayerId;
use graphene_core::vector::style::Stroke; use graphene_core::vector::style::Stroke;
use graphene_core::Color;
use glam::DVec2; use glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -25,11 +29,15 @@ pub struct LineTool {
pub struct LineOptions { pub struct LineOptions {
line_weight: f64, line_weight: f64,
stroke: ToolColorOptions,
} }
impl Default for LineOptions { impl Default for LineOptions {
fn default() -> Self { fn default() -> Self {
Self { line_weight: 5. } Self {
line_weight: 5.,
stroke: ToolColorOptions::new_primary(),
}
} }
} }
@ -40,6 +48,8 @@ pub enum LineToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
@ -56,6 +66,9 @@ pub enum LineToolMessage {
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum LineOptionsUpdate { pub enum LineOptionsUpdate {
LineWeight(f64), LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
} }
impl ToolMetadata for LineTool { 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 { impl PropertyHolder for LineTool {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let weight = NumberInput::new(Some(self.options.line_weight)) let mut widgets = self.options.stroke.create_widgets(
.unit(" px") "Stroke",
.label("Weight") true,
.min(0.) WidgetCallback::new(|_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into()),
.on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) |color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
.widget_holder(); WidgetCallback::new(|color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value)).into()),
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) );
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 { if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message {
match action { match action {
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, 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; return;
} }
@ -106,6 +147,7 @@ impl ToolTransition for LineTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(LineToolMessage::Abort.into()), tool_abort: Some(LineToolMessage::Abort.into()),
working_color_changed: Some(LineToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -164,7 +206,7 @@ impl Fsm for LineToolFsmState {
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
responses.add(GraphOperationMessage::StrokeSet { responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path, 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; tool_data.weight = tool_options.line_weight;
@ -192,6 +234,13 @@ impl Fsm for LineToolFsmState {
tool_data.path = None; tool_data.path = None;
Ready Ready
} }
(_, WorkingColorChanged) => {
responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } else {

View File

@ -1,12 +1,13 @@
use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::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::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*; 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::graph_modification_utils;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
@ -30,52 +31,18 @@ pub struct PenTool {
options: PenOptions, 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 { pub struct PenOptions {
line_weight: f64, line_weight: f64,
fill: PenColorOptions, fill: ToolColorOptions,
stroke: PenColorOptions, stroke: ToolColorOptions,
} }
impl Default for PenOptions { impl Default for PenOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
line_weight: 5., line_weight: 5.,
fill: PenColorOptions { fill: ToolColorOptions::new_secondary(),
color: Some(Color::BLACK), stroke: ToolColorOptions::new_primary(),
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,
},
} }
} }
} }
@ -119,12 +86,11 @@ enum PenToolFsmState {
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum PenOptionsUpdate { pub enum PenOptionsUpdate {
FillColor(Option<Color>), FillColor(Option<Color>),
FillColorType(PenColorType), FillColorType(ToolColorType),
LineWeight(f64), LineWeight(f64),
PrimaryColor(Option<Color>),
SecondaryColor(Option<Color>),
StrokeColor(Option<Color>), StrokeColor(Option<Color>),
StrokeColorType(PenColorType), StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
} }
impl ToolMetadata for PenTool { 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 { fn create_weight_widget(line_weight: f64) -> WidgetHolder {
NumberInput::new(Some(line_weight)) NumberInput::new(Some(line_weight))
.unit(" px") .unit(" px")
@ -229,11 +116,26 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
impl PropertyHolder for PenTool { impl PropertyHolder for PenTool {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let mut widgets = create_fill_widget(&self.options.fill); let mut widgets = self.options.fill.create_widgets(
widgets.push(Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder()); "Fill",
widgets.append(&mut create_stroke_widget(&self.options.stroke)); 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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); widgets.push(create_weight_widget(self.options.line_weight));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
} }
} }
@ -244,22 +146,20 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
match action { match action {
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
PenOptionsUpdate::FillColor(color) => { PenOptionsUpdate::FillColor(color) => {
self.options.fill.color = color; self.options.fill.custom_color = color;
self.options.fill.color_type = PenColorType::Custom; self.options.fill.color_type = ToolColorType::Custom;
} }
PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
PenOptionsUpdate::StrokeColor(color) => { PenOptionsUpdate::StrokeColor(color) => {
self.options.stroke.color = color; self.options.stroke.custom_color = color;
self.options.stroke.color_type = PenColorType::Custom; self.options.stroke.color_type = ToolColorType::Custom;
} }
PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
PenOptionsUpdate::PrimaryColor(color) => { PenOptionsUpdate::WorkingColors(primary, secondary) => {
self.options.stroke.primary_working_color = color; self.options.stroke.primary_working_color = primary;
self.options.fill.primary_working_color = color; self.options.stroke.secondary_working_color = secondary;
} self.options.fill.primary_working_color = primary;
PenOptionsUpdate::SecondaryColor(color) => { self.options.fill.secondary_working_color = secondary;
self.options.stroke.secondary_working_color = color;
self.options.fill.secondary_working_color = color;
} }
} }
@ -701,8 +601,10 @@ impl Fsm for PenToolFsmState {
self self
} }
(_, PenToolMessage::WorkingColorChanged) => { (_, PenToolMessage::WorkingColorChanged) => {
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::PrimaryColor(Some(global_tool_data.primary_color)))); responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::SecondaryColor(Some(global_tool_data.secondary_color)))); Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self self
} }
(PenToolFsmState::Ready, PenToolMessage::DragStart) => { (PenToolFsmState::Ready, PenToolMessage::DragStart) => {

View File

@ -1,29 +1,63 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::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::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize; 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::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::DVec2; use glam::DVec2;
use graphene_core::vector::style::Fill; use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct RectangleTool { pub struct RectangleTool {
fsm_state: RectangleToolFsmState, fsm_state: RectangleToolFsmState,
tool_data: RectangleToolData, 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] #[remain::sorted]
#[impl_message(Message, ToolMessage, Rectangle)] #[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 { pub enum RectangleToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
@ -32,13 +66,76 @@ pub enum RectangleToolMessage {
center: Key, center: Key,
lock_ratio: 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 { 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>) { 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 { fn actions(&self) -> ActionList {
@ -73,6 +170,7 @@ impl ToolTransition for RectangleTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(RectangleToolMessage::Abort.into()), tool_abort: Some(RectangleToolMessage::Abort.into()),
working_color_changed: Some(RectangleToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -92,7 +190,7 @@ struct RectangleToolData {
impl Fsm for RectangleToolFsmState { impl Fsm for RectangleToolFsmState {
type ToolData = RectangleToolData; type ToolData = RectangleToolData;
type ToolOptions = (); type ToolOptions = RectangleToolOptions;
fn transition( fn transition(
self, self,
@ -105,7 +203,7 @@ impl Fsm for RectangleToolFsmState {
render_data, render_data,
.. ..
}: &mut ToolActionHandlerData, }: &mut ToolActionHandlerData,
_tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use RectangleToolFsmState::*; use RectangleToolFsmState::*;
@ -124,9 +222,16 @@ impl Fsm for RectangleToolFsmState {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
shape_data.path = Some(layer_path.clone()); shape_data.path = Some(layer_path.clone());
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
let fill_color = tool_options.fill.active_color();
responses.add(GraphOperationMessage::FillSet { 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, layer: layer_path,
fill: Fill::solid(global_tool_data.primary_color), stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
}); });
Drawing Drawing
@ -151,6 +256,13 @@ impl Fsm for RectangleToolFsmState {
Ready Ready
} }
(_, WorkingColorChanged) => {
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } 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::assist_widgets::{PivotAssist, PivotPosition};
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton}; 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::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::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*; use crate::messages::prelude::*;
@ -137,7 +136,7 @@ impl PropertyHolder for SelectTool {
PivotAssist::new(self.tool_data.pivot.to_pivot_position()) PivotAssist::new(self.tool_data.pivot.to_pivot_position())
.on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()) .on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into())
.widget_holder(), .widget_holder(),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), WidgetHolder::section_separator(),
IconButton::new("AlignLeft", 24) IconButton::new("AlignLeft", 24)
.tooltip("Align Left") .tooltip("Align Left")
.on_update(|_| { .on_update(|_| {
@ -201,7 +200,7 @@ impl PropertyHolder for SelectTool {
.widget_holder(), .widget_holder(),
WidgetHolder::related_separator(), WidgetHolder::related_separator(),
PopoverButton::new("Align", "Coming soon").widget_holder(), PopoverButton::new("Align", "Coming soon").widget_holder(),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), WidgetHolder::section_separator(),
IconButton::new("FlipHorizontal", 24) IconButton::new("FlipHorizontal", 24)
.tooltip("Flip Horizontal") .tooltip("Flip Horizontal")
.on_update(|_| SelectToolMessage::FlipHorizontal.into()) .on_update(|_| SelectToolMessage::FlipHorizontal.into())
@ -216,7 +215,7 @@ impl PropertyHolder for SelectTool {
text: "Coming soon".into(), text: "Coming soon".into(),
..Default::default() ..Default::default()
})), })),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), WidgetHolder::section_separator(),
IconButton::new("BooleanUnion", 24) IconButton::new("BooleanUnion", 24)
.tooltip("Boolean Union (coming soon)") .tooltip("Boolean Union (coming soon)")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())

View File

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

View File

@ -1,16 +1,20 @@
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; 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::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager; 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 crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::{LayerId, Operation}; 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 glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -24,11 +28,17 @@ pub struct SplineTool {
pub struct SplineOptions { pub struct SplineOptions {
line_weight: f64, line_weight: f64,
fill: ToolColorOptions,
stroke: ToolColorOptions,
} }
impl Default for SplineOptions { impl Default for SplineOptions {
fn default() -> Self { 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 // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
Confirm, Confirm,
@ -59,7 +71,12 @@ enum SplineToolFsmState {
#[remain::sorted] #[remain::sorted]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum SplineOptionsUpdate { pub enum SplineOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64), LineWeight(f64),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
WorkingColors(Option<Color>, Option<Color>),
} }
impl ToolMetadata for SplineTool { 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 { impl PropertyHolder for SplineTool {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let weight = NumberInput::new(Some(self.options.line_weight)) let mut widgets = self.options.fill.create_widgets(
.unit(" px") "Fill",
.label("Weight") true,
.min(0.) WidgetCallback::new(|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into()),
.on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) |color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColorType(color_type.clone())).into()),
.widget_holder(); WidgetCallback::new(|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value)).into()),
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) );
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 { if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message {
match action { match action {
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, 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; return;
} }
@ -123,6 +185,7 @@ impl ToolTransition for SplineTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(SplineToolMessage::Abort.into()), tool_abort: Some(SplineToolMessage::Abort.into()),
working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -178,7 +241,7 @@ impl Fsm for SplineToolFsmState {
tool_data.weight = tool_options.line_weight; 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 Drawing
} }
@ -194,7 +257,7 @@ impl Fsm for SplineToolFsmState {
} }
responses.add(remove_preview(tool_data)); 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
} }
@ -204,14 +267,14 @@ impl Fsm for SplineToolFsmState {
tool_data.next_point = pos; tool_data.next_point = pos;
responses.add(remove_preview(tool_data)); 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
} }
(Drawing, Confirm) | (Drawing, Abort) => { (Drawing, Confirm) | (Drawing, Abort) => {
if tool_data.points.len() >= 2 { if tool_data.points.len() >= 2 {
responses.add(remove_preview(tool_data)); 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); responses.add(DocumentMessage::CommitTransaction);
} else { } else {
responses.add(DocumentMessage::AbortTransaction); responses.add(DocumentMessage::AbortTransaction);
@ -223,6 +286,13 @@ impl Fsm for SplineToolFsmState {
Ready Ready
} }
(_, WorkingColorChanged) => {
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } else {
@ -254,7 +324,7 @@ fn remove_preview(tool_data: &SplineToolData) -> Message {
.into() .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(); let mut points = tool_data.points.clone();
if show_preview { if show_preview {
points.push(tool_data.next_point) 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::new_vector_layer(vec![subpath], layer_path.clone(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, 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 { responses.add(GraphOperationMessage::StrokeSet {
layer: layer_path.clone(), 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::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::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget; 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::portfolio::document::node_graph::new_text_network;
use crate::messages::prelude::*; 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::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@ -34,6 +35,7 @@ pub struct TextOptions {
font_size: u32, font_size: u32,
font_name: String, font_name: String,
font_style: String, font_style: String,
fill: ToolColorOptions,
} }
impl Default for TextOptions { impl Default for TextOptions {
@ -42,20 +44,22 @@ impl Default for TextOptions {
font_size: 24, font_size: 24,
font_name: "Merriweather".into(), font_name: "Merriweather".into(),
font_style: "Normal (400)".into(), font_style: "Normal (400)".into(),
fill: ToolColorOptions::new_primary(),
} }
} }
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Text)] #[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 { pub enum TextToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
Abort, Abort,
#[remain::unsorted] #[remain::unsorted]
DocumentIsDirty, DocumentIsDirty,
#[remain::unsorted]
WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
CommitText, CommitText,
@ -71,10 +75,13 @@ pub enum TextToolMessage {
} }
#[remain::sorted] #[remain::sorted]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum TextOptionsUpdate { pub enum TextOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
Font { family: String, style: String }, Font { family: String, style: String },
FontSize(u32), FontSize(u32),
WorkingColors(Option<Color>, Option<Color>),
} }
impl ToolMetadata for TextTool { 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 { impl PropertyHolder for TextTool {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let font = FontInput { let mut widgets = create_text_widgets(self);
is_style_picker: false,
font_family: self.options.font_name.clone(), widgets.push(WidgetHolder::section_separator());
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| { widgets.append(&mut self.options.fill.create_widgets(
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { "Fill",
family: font_input.font_family.clone(), true,
style: font_input.font_style.clone(), WidgetCallback::new(|_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into()),
}) |color_type: ToolColorType| WidgetCallback::new(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColorType(color_type.clone())).into()),
.into() WidgetCallback::new(|color: &ColorInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value)).into()),
}), ));
..Default::default()
} Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
.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],
}]))
} }
} }
@ -143,7 +164,22 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
self.register_properties(responses, LayoutTarget::ToolOptions); self.register_properties(responses, LayoutTarget::ToolOptions);
} }
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size, 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; return;
} }
@ -172,6 +208,7 @@ impl ToolTransition for TextTool {
document_dirty: Some(TextToolMessage::DocumentIsDirty.into()), document_dirty: Some(TextToolMessage::DocumentIsDirty.into()),
tool_abort: Some(TextToolMessage::Abort.into()), tool_abort: Some(TextToolMessage::Abort.into()),
selection_changed: Some(TextToolMessage::DocumentIsDirty.into()), selection_changed: Some(TextToolMessage::DocumentIsDirty.into()),
working_color_changed: Some(TextToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
} }
} }
@ -188,7 +225,7 @@ pub struct EditingText {
text: String, text: String,
font: Font, font: Font,
font_size: f64, font_size: f64,
color: Color, color: Option<Color>,
transform: DAffine2, transform: DAffine2,
} }
@ -211,7 +248,7 @@ impl TextToolData {
text: editing_text.text.clone(), text: editing_text.text.clone(),
line_width: None, line_width: None,
font_size: editing_text.font_size, 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(), url: render_data.font_cache.get_preview_url(&editing_text.font).cloned().unwrap_or_default(),
transform: editing_text.transform.to_cols_array(), transform: editing_text.transform.to_cols_array(),
}); });
@ -237,7 +274,7 @@ impl TextToolData {
text: text.clone(), text: text.clone(),
font: font.clone(), font: font.clone(),
font_size, font_size,
color, color: Some(color),
transform, transform,
}); });
self.new_text = text.clone(); self.new_text = text.clone();
@ -291,7 +328,7 @@ impl TextToolData {
}); });
responses.add(GraphOperationMessage::FillSet { responses.add(GraphOperationMessage::FillSet {
layer: self.layer_path.clone(), 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 { responses.add(GraphOperationMessage::TransformSet {
layer: self.layer_path.clone(), layer: self.layer_path.clone(),
@ -470,7 +507,7 @@ impl Fsm for TextToolFsmState {
transform: DAffine2::from_translation(input.mouse.position), transform: DAffine2::from_translation(input.mouse.position),
font_size: tool_options.font_size as f64, font_size: tool_options.font_size as f64,
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()), 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.new_text = String::new();
tool_data.layer_path = document.get_path_for_new_layer(); 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); tool_data.update_bounds_overlay(document, render_data, responses);
TextToolFsmState::Editing TextToolFsmState::Editing
} }
(_, TextToolMessage::WorkingColorChanged) => {
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
self
}
_ => self, _ => self,
} }
} else { } 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; export let value: Color;
// TODO: Implement // TODO: Implement
// export let allowTransparency = false; // export let allowTransparency = false;
// export let allowNone = false;
// export let disabled = false; // export let disabled = false;
export let allowNone = false;
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false; export let sharpRightCorners = false;
@ -45,7 +45,7 @@
value = detail; value = detail;
dispatch("value", detail); dispatch("value", detail);
}} }}
allowNone={true} {allowNone}
/> />
</LayoutRow> </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 CheckboxChecked from "@graphite-frontend/assets/icon-16px-solid/checkbox-checked.svg";
import CheckboxUnchecked from "@graphite-frontend/assets/icon-16px-solid/checkbox-unchecked.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 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 Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg";
import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg"; import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg";
import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.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 }, CheckboxChecked: { svg: CheckboxChecked, size: 16 },
CheckboxUnchecked: { svg: CheckboxUnchecked, size: 16 }, CheckboxUnchecked: { svg: CheckboxUnchecked, size: 16 },
Copy: { svg: Copy, size: 16 }, Copy: { svg: Copy, size: 16 },
CustomColor: { svg: CustomColor, size: 16 },
Edit: { svg: Edit, size: 16 }, Edit: { svg: Edit, size: 16 },
Eyedropper: { svg: Eyedropper, size: 16 }, Eyedropper: { svg: Eyedropper, size: 16 },
EyeHidden: { svg: EyeHidden, size: 16 }, EyeHidden: { svg: EyeHidden, size: 16 },

View File

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