Add struct field visualization to the editor message hierarchy tree visualization on the website (#2917)
Some checks failed
Editor: Dev & CI / build (push) Waiting to run
Editor: Dev & CI / cargo-deny (push) Waiting to run
Website / build (push) Has been cancelled

* Fix Message Tree: Enforce Structure and Visibility

* Code review

* fix the erroreous ouputs

* error handling for MessageHandler

* Fix website visualization HTML generation

* error handling for tuple-style message enum variant

* cleanup

* Update messages

* Normalize BroadcastEvent

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Mohd Mohsin 2025-08-19 09:34:29 +05:30 committed by GitHub
parent 5ed45ead6f
commit 17d70dc60e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 988 additions and 506 deletions

View file

@ -61,8 +61,8 @@ impl WinitApp {
} }
fn send_messages_to_editor(&mut self, mut responses: Vec<FrontendMessage>) { fn send_messages_to_editor(&mut self, mut responses: Vec<FrontendMessage>) {
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays(_))) { for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays { .. })) {
let FrontendMessage::RenderOverlays(overlay_context) = message else { unreachable!() }; let FrontendMessage::RenderOverlays { context: overlay_context } = message else { unreachable!() };
if let Some(graphics_state) = &mut self.graphics_state { if let Some(graphics_state) = &mut self.graphics_state {
let scene = overlay_context.take_scene(); let scene = overlay_context.take_scene();
graphics_state.set_overlays_scene(scene); graphics_state.set_overlays_scene(scene);

View file

@ -52,7 +52,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
]; ];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[ const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)), MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter), MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
]; ];
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best. // TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.

View file

@ -1,6 +1,5 @@
use crate::messages::prelude::*;
use super::animation_message_handler::AnimationTimeMode; use super::animation_message_handler::AnimationTimeMode;
use crate::messages::prelude::*;
#[impl_message(Message, Animation)] #[impl_message(Message, Animation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]

View file

@ -5,15 +5,15 @@ use crate::messages::prelude::*;
pub enum BroadcastMessage { pub enum BroadcastMessage {
// Sub-messages // Sub-messages
#[child] #[child]
TriggerEvent(BroadcastEvent), TriggerEvent(EventMessage),
// Messages // Messages
SubscribeEvent { SubscribeEvent {
on: BroadcastEvent, on: EventMessage,
send: Box<Message>, send: Box<Message>,
}, },
UnsubscribeEvent { UnsubscribeEvent {
on: BroadcastEvent, on: EventMessage,
message: Box<Message>, send: Box<Message>,
}, },
} }

View file

@ -2,7 +2,8 @@ use crate::messages::prelude::*;
#[derive(Debug, Clone, Default, ExtractField)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct BroadcastMessageHandler { pub struct BroadcastMessageHandler {
listeners: HashMap<BroadcastEvent, Vec<Message>>, event: EventMessageHandler,
listeners: HashMap<EventMessage, Vec<Message>>,
} }
#[message_handler_data] #[message_handler_data]
@ -10,19 +11,15 @@ impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) { fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
match message { match message {
// Sub-messages // Sub-messages
BroadcastMessage::TriggerEvent(event) => { BroadcastMessage::TriggerEvent(message) => self.event.process_message(message, responses, EventMessageContext { listeners: &mut self.listeners }),
for message in self.listeners.entry(event).or_default() {
responses.add_front(message.clone())
}
}
// Messages // Messages
BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send), BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message), BroadcastMessage::UnsubscribeEvent { on, send } => self.listeners.entry(on).or_default().retain(|msg| *msg != *send),
} }
} }
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
actions!(BroadcastEventDiscriminant;) actions!(EventMessageDiscriminant;)
} }
} }

View file

@ -1,8 +1,8 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
#[impl_message(Message, BroadcastMessage, TriggerEvent)] #[impl_message(Message, BroadcastMessage, TriggerEvent)]
pub enum BroadcastEvent { #[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
pub enum EventMessage {
/// Triggered by requestAnimationFrame in JS /// Triggered by requestAnimationFrame in JS
AnimationFrame, AnimationFrame,
CanvasTransformed, CanvasTransformed,

View file

@ -0,0 +1,22 @@
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct EventMessageContext<'a> {
pub listeners: &'a mut HashMap<EventMessage, Vec<Message>>,
}
#[derive(Debug, Clone, Default, ExtractField)]
pub struct EventMessageHandler {}
#[message_handler_data]
impl MessageHandler<EventMessage, EventMessageContext<'_>> for EventMessageHandler {
fn process_message(&mut self, message: EventMessage, responses: &mut VecDeque<Message>, context: EventMessageContext) {
for message in context.listeners.entry(message).or_default() {
responses.add_front(message.clone())
}
}
fn actions(&self) -> ActionList {
actions!(EventMessageDiscriminant;)
}
}

View file

@ -0,0 +1,7 @@
mod event_message;
mod event_message_handler;
#[doc(inline)]
pub use event_message::{EventMessage, EventMessageDiscriminant};
#[doc(inline)]
pub use event_message_handler::{EventMessageContext, EventMessageHandler};

View file

@ -1,7 +1,7 @@
mod broadcast_message; mod broadcast_message;
mod broadcast_message_handler; mod broadcast_message_handler;
pub mod broadcast_event; pub mod event;
#[doc(inline)] #[doc(inline)]
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant}; pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};

View file

@ -3,8 +3,8 @@ use crate::messages::prelude::*;
#[impl_message(Message, Defer)] #[impl_message(Message, Defer)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum DeferMessage { pub enum DeferMessage {
SetGraphSubmissionIndex(u64), SetGraphSubmissionIndex { execution_id: u64 },
TriggerGraphRun(u64, DocumentId), TriggerGraphRun { execution_id: u64, document_id: DocumentId },
AfterGraphRun { messages: Vec<Message> }, AfterGraphRun { messages: Vec<Message> },
TriggerNavigationReady, TriggerNavigationReady,
AfterNavigationReady { messages: Vec<Message> }, AfterNavigationReady { messages: Vec<Message> },

View file

@ -24,10 +24,10 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
DeferMessage::AfterNavigationReady { messages } => { DeferMessage::AfterNavigationReady { messages } => {
self.after_viewport_resize.extend_from_slice(&messages); self.after_viewport_resize.extend_from_slice(&messages);
} }
DeferMessage::SetGraphSubmissionIndex(execution_id) => { DeferMessage::SetGraphSubmissionIndex { execution_id } => {
self.current_graph_submission_id = execution_id + 1; self.current_graph_submission_id = execution_id + 1;
} }
DeferMessage::TriggerGraphRun(execution_id, document_id) => { DeferMessage::TriggerGraphRun { execution_id, document_id } => {
let after_graph_run = self.after_graph_run.entry(document_id).or_default(); let after_graph_run = self.after_graph_run.entry(document_id).or_default();
if after_graph_run.is_empty() { if after_graph_run.is_empty() {
return; return;

View file

@ -4,10 +4,10 @@ use crate::messages::prelude::*;
#[impl_message(Message, DialogMessage, ExportDialog)] #[impl_message(Message, DialogMessage, ExportDialog)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ExportDialogMessage { pub enum ExportDialogMessage {
FileType(FileType), FileType { file_type: FileType },
ScaleFactor(f64), ScaleFactor { factor: f64 },
TransparentBackground(bool), TransparentBackground { transparent: bool },
ExportBounds(ExportBounds), ExportBounds { bounds: ExportBounds },
Submit, Submit,
} }

View file

@ -38,10 +38,10 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
let ExportDialogMessageContext { portfolio } = context; let ExportDialogMessageContext { portfolio } = context;
match message { match message {
ExportDialogMessage::FileType(export_type) => self.file_type = export_type, ExportDialogMessage::FileType { file_type } => self.file_type = file_type,
ExportDialogMessage::ScaleFactor(factor) => self.scale_factor = factor, ExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background, ExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area, ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport { ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(), file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
@ -84,7 +84,11 @@ impl LayoutHolder for ExportDialogMessageHandler {
fn layout(&self) -> Layout { fn layout(&self) -> Layout {
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")] let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
.into_iter() .into_iter()
.map(|(val, name)| RadioEntryData::new(format!("{val:?}")).label(name).on_update(move |_| ExportDialogMessage::FileType(val).into())) .map(|(file_type, name)| {
RadioEntryData::new(format!("{file_type:?}"))
.label(name)
.on_update(move |_| ExportDialogMessage::FileType { file_type }.into())
})
.collect(); .collect();
let export_type = vec![ let export_type = vec![
@ -101,7 +105,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
.min(0.) .min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.disabled(self.file_type == FileType::Svg) .disabled(self.file_type == FileType::Svg)
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()) .on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor { factor: number_input.value.unwrap() }.into())
.min_width(200) .min_width(200)
.widget_holder(), .widget_holder(),
]; ];
@ -125,10 +129,10 @@ impl LayoutHolder for ExportDialogMessageHandler {
.map(|choice| { .map(|choice| {
choice choice
.into_iter() .into_iter()
.map(|(val, name, disabled)| { .map(|(bounds, name, disabled)| {
MenuListEntry::new(format!("{val:?}")) MenuListEntry::new(format!("{bounds:?}"))
.label(name) .label(name)
.on_commit(move |_| ExportDialogMessage::ExportBounds(val).into()) .on_commit(move |_| ExportDialogMessage::ExportBounds { bounds }.into())
.disabled(disabled) .disabled(disabled)
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -151,7 +155,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.transparent_background) CheckboxInput::new(self.transparent_background)
.disabled(self.file_type == FileType::Jpg) .disabled(self.file_type == FileType::Jpg)
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into()) .on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground { transparent: value.checked }.into())
.for_label(checkbox_id) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
]; ];

View file

@ -3,10 +3,10 @@ use crate::messages::prelude::*;
#[impl_message(Message, DialogMessage, NewDocumentDialog)] #[impl_message(Message, DialogMessage, NewDocumentDialog)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum NewDocumentDialogMessage { pub enum NewDocumentDialogMessage {
Name(String), Name { name: String },
Infinite(bool), Infinite { infinite: bool },
DimensionsX(f64), DimensionsX { width: f64 },
DimensionsY(f64), DimensionsY { height: f64 },
Submit, Submit,
} }

View file

@ -20,10 +20,10 @@ pub struct NewDocumentDialogMessageHandler {
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler { impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) { fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
match message { match message {
NewDocumentDialogMessage::Name(name) => self.name = name, NewDocumentDialogMessage::Name { name } => self.name = name,
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite, NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32, NewDocumentDialogMessage::DimensionsX { width } => self.dimensions.x = width as u32,
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32, NewDocumentDialogMessage::DimensionsY { height } => self.dimensions.y = height as u32,
NewDocumentDialogMessage::Submit => { NewDocumentDialogMessage::Submit => {
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }); responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
@ -82,7 +82,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
TextLabel::new("Name").table_align(true).min_width(90).widget_holder(), TextLabel::new("Name").table_align(true).min_width(90).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
TextInput::new(&self.name) TextInput::new(&self.name)
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into()) .on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
.min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator .min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator
.widget_holder(), .widget_holder(),
]; ];
@ -92,7 +92,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(), TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.infinite) CheckboxInput::new(self.infinite)
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into()) .on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
.for_label(checkbox_id) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
]; ];
@ -108,7 +108,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
.is_integer(true) .is_integer(true)
.disabled(self.infinite) .disabled(self.infinite)
.min_width(100) .min_width(100)
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into()) .on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX { width: number_input.value.unwrap() }.into())
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.dimensions.y as f64)) NumberInput::new(Some(self.dimensions.y as f64))
@ -119,7 +119,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
.is_integer(true) .is_integer(true)
.disabled(self.infinite) .disabled(self.infinite)
.min_width(100) .min_width(100)
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into()) .on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY { height: number_input.value.unwrap() }.into())
.widget_holder(), .widget_holder(),
]; ];

View file

@ -1,4 +1,4 @@
use crate::messages::broadcast::broadcast_event::BroadcastEvent; use crate::messages::broadcast::event::EventMessage;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
@ -27,7 +27,7 @@ impl DialogLayoutHolder for CloseDocumentDialog {
TextButton::new("Discard") TextButton::new("Discard")
.on_update(move |_| { .on_update(move |_| {
DialogMessage::CloseDialogAndThen { DialogMessage::CloseDialogAndThen {
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()], followups: vec![EventMessage::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
} }
.into() .into()
}) })

View file

@ -335,9 +335,9 @@ pub enum FrontendMessage {
active: bool, active: bool,
}, },
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
RenderOverlays( RenderOverlays {
#[serde(skip, default = "OverlayContext::default")] #[serde(skip, default = "OverlayContext::default")]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
OverlayContext, context: OverlayContext,
), },
} }

View file

@ -96,7 +96,7 @@ pub fn input_mappings() -> Mapping {
entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }), entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }),
// //
// SelectToolMessage // SelectToolMessage
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt })), entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove { modifier_keys: SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt } }),
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }), entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }), entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter), entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
@ -178,7 +178,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides), entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides), entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control])), entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove { modifier: [Alt, Shift, Control] }),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }), entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
@ -297,8 +297,8 @@ pub fn input_mappings() -> Mapping {
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove), entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart), entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop), entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))), entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD) }),
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD))), entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD) }),
entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort), entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
// //

View file

@ -3,13 +3,16 @@ use crate::messages::prelude::*;
#[impl_message(Message, KeyMapping)] #[impl_message(Message, KeyMapping)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
pub enum KeyMappingMessage { pub enum KeyMappingMessage {
// Sub-messages
#[child] #[child]
Lookup(InputMapperMessage), Lookup(InputMapperMessage),
#[child]
ModifyMapping(MappingVariant), // Messages
ModifyMapping {
mapping: MappingVariant,
},
} }
#[impl_message(Message, KeyMappingMessage, ModifyMapping)]
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]
pub enum MappingVariant { pub enum MappingVariant {
#[default] #[default]

View file

@ -19,8 +19,11 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMapp
let KeyMappingMessageContext { input, actions } = context; let KeyMappingMessageContext { input, actions } = context;
match message { match message {
// Sub-messages
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }), KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
// Messages
KeyMappingMessage::ModifyMapping { mapping } => self.mapping_handler.set_mapping(mapping.into()),
} }
} }
advertise_actions!(); advertise_actions!();

View file

@ -2,6 +2,6 @@ mod key_mapping_message;
mod key_mapping_message_handler; mod key_mapping_message_handler;
#[doc(inline)] #[doc(inline)]
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant}; pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant};
#[doc(inline)] #[doc(inline)]
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler}; pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};

View file

@ -35,10 +35,10 @@ pub enum Message {
Tool(ToolMessage), Tool(ToolMessage),
// Messages // Messages
NoOp,
Batched { Batched {
messages: Box<[Message]>, messages: Box<[Message]>,
}, },
NoOp,
} }
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`. /// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
@ -94,6 +94,17 @@ mod test {
} }
} }
// Print message field if any
if let Some(fields) = tree.fields() {
let len = fields.len();
for (i, field) in fields.iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", child_prefix, branch, field).as_bytes()).unwrap();
}
}
// Print handler field if any // Print handler field if any
if let Some(data) = tree.message_handler_fields() { if let Some(data) = tree.message_handler_fields() {
let len = data.fields().len(); let len = data.fields().len();
@ -102,16 +113,19 @@ mod test {
} else { } else {
("└── ", format!("{} ", prefix)) ("└── ", format!("{} ", prefix))
}; };
if data.path().is_empty() {
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
} else {
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
}
for (i, field) in data.fields().iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap(); const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
if data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {
panic!("{}'s MessageHandler is missing #[message_handler_data]", tree.name());
} else if tree.name() != FRONTEND_MESSAGE_STR {
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
for (i, field) in data.fields().iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
}
} }
} }

View file

@ -53,7 +53,9 @@ pub enum DocumentMessage {
DocumentHistoryBackward, DocumentHistoryBackward,
DocumentHistoryForward, DocumentHistoryForward,
DocumentStructureChanged, DocumentStructureChanged,
DrawArtboardOverlays(OverlayContext), DrawArtboardOverlays {
context: OverlayContext,
},
DuplicateSelectedLayers, DuplicateSelectedLayers,
EnterNestedNetwork { EnterNestedNetwork {
node_id: NodeId, node_id: NodeId,
@ -72,9 +74,15 @@ pub enum DocumentMessage {
open: bool, open: bool,
}, },
GraphViewOverlayToggle, GraphViewOverlayToggle,
GridOptions(GridSnapping), GridOptions {
GridOverlays(OverlayContext), options: GridSnapping,
GridVisibility(bool), },
GridOverlays {
context: OverlayContext,
},
GridVisibility {
visible: bool,
},
GroupSelectedLayers { GroupSelectedLayers {
group_folder_type: GroupFolderType, group_folder_type: GroupFolderType,
}, },

View file

@ -391,7 +391,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
} }
} }
DocumentMessage::DrawArtboardOverlays(overlay_context) => { DocumentMessage::DrawArtboardOverlays { context: overlay_context } => {
if !overlay_context.visibility_settings.artboard_name() { if !overlay_context.visibility_settings.artboard_name() {
return; return;
} }
@ -588,19 +588,19 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::GraphViewOverlayToggle => { DocumentMessage::GraphViewOverlayToggle => {
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open }); responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
} }
DocumentMessage::GridOptions(grid) => { DocumentMessage::GridOptions { options } => {
self.snapping_state.grid = grid; self.snapping_state.grid = options;
self.snapping_state.grid_snapping = true; self.snapping_state.grid_snapping = true;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
DocumentMessage::GridOverlays(mut overlay_context) => { DocumentMessage::GridOverlays { context: mut overlay_context } => {
if self.snapping_state.grid_snapping { if self.snapping_state.grid_snapping {
grid_overlay(self, &mut overlay_context) grid_overlay(self, &mut overlay_context)
} }
} }
DocumentMessage::GridVisibility(enabled) => { DocumentMessage::GridVisibility { visible } => {
self.snapping_state.grid_snapping = enabled; self.snapping_state.grid_snapping = visible;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
DocumentMessage::GroupSelectedLayers { group_folder_type } => { DocumentMessage::GroupSelectedLayers { group_folder_type } => {
@ -1062,7 +1062,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
if !parent_layers.is_empty() { if !parent_layers.is_empty() {
let nodes = parent_layers.into_iter().collect(); let nodes = parent_layers.into_iter().collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
} }
DocumentMessage::SelectAllLayers => { DocumentMessage::SelectAllLayers => {
@ -1137,7 +1137,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
} else { } else {
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
} }
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} else { } else {
nodes.push(id); nodes.push(id);
} }
@ -1206,7 +1206,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
Some(overlays_type) => overlays_type, Some(overlays_type) => overlays_type,
None => { None => {
visibility_settings.all = visible; visibility_settings.all = visible;
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
return; return;
} }
@ -1229,7 +1229,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
OverlaysType::Handles => visibility_settings.handles = visible, OverlaysType::Handles => visibility_settings.handles = visible,
} }
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
DocumentMessage::SetRangeSelectionLayer { new_layer } => { DocumentMessage::SetRangeSelectionLayer { new_layer } => {
@ -1443,12 +1443,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
self.network_interface.set_document_to_viewport_transform(transform); self.network_interface.set_document_to_viewport_transform(transform);
// Ensure selection box is kept in sync with the pointer when the PTZ changes // Ensure selection box is kept in sync with the pointer when the PTZ changes
responses.add(SelectToolMessage::PointerMove(SelectToolPointerKeys { responses.add(SelectToolMessage::PointerMove {
axis_align: Key::Shift, modifier_keys: SelectToolPointerKeys {
snap_angle: Key::Shift, axis_align: Key::Shift,
center: Key::Alt, snap_angle: Key::Shift,
duplicate: Key::Alt, center: Key::Alt,
})); duplicate: Key::Alt,
},
});
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} else { } else {
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else { let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
@ -1477,11 +1479,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
} }
DocumentMessage::SelectionStepBack => { DocumentMessage::SelectionStepBack => {
self.network_interface.selection_step_back(&self.selection_network_path); self.network_interface.selection_step_back(&self.selection_network_path);
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
DocumentMessage::SelectionStepForward => { DocumentMessage::SelectionStepForward => {
self.network_interface.selection_step_forward(&self.selection_network_path); self.network_interface.selection_step_forward(&self.selection_network_path);
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => { DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {
// Get bounding box of all layers // Get bounding box of all layers
@ -2484,7 +2486,7 @@ impl DocumentMessageHandler {
.icon("Grid") .icon("Grid")
.tooltip("Grid") .tooltip("Grid")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility)) .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility(optional_input.checked).into()) .on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into())
.widget_holder(), .widget_holder(),
PopoverButton::new() PopoverButton::new()
.popover_layout(overlay_options(&self.snapping_state.grid)) .popover_layout(overlay_options(&self.snapping_state.grid))

View file

@ -139,7 +139,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta); let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
ptz.pan += transformed_delta; ptz.pan += transformed_delta;
responses.add(BroadcastEvent::CanvasTransformed); responses.add(EventMessage::CanvasTransformed);
responses.add(DocumentMessage::PTZUpdate); responses.add(DocumentMessage::PTZUpdate);
} }
NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => { NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {
@ -286,7 +286,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
ptz.flip = !ptz.flip; ptz.flip = !ptz.flip;
responses.add(DocumentMessage::PTZUpdate); responses.add(DocumentMessage::PTZUpdate);
responses.add(BroadcastEvent::CanvasTransformed); responses.add(EventMessage::CanvasTransformed);
responses.add(MenuBarMessage::SendLayout); responses.add(MenuBarMessage::SendLayout);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
@ -325,7 +325,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
self.navigation_operation = NavigationOperation::None; self.navigation_operation = NavigationOperation::None;
// Send the final messages to close out the operation // Send the final messages to close out the operation
responses.add(BroadcastEvent::CanvasTransformed); responses.add(EventMessage::CanvasTransformed);
responses.add(ToolMessage::UpdateCursor); responses.add(ToolMessage::UpdateCursor);
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NavigateToolMessage::End); responses.add(NavigateToolMessage::End);

View file

@ -129,7 +129,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::AddPathNode => { NodeGraphMessage::AddPathNode => {
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) { if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer }); responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
} }
NodeGraphMessage::AddImport => { NodeGraphMessage::AddImport => {
@ -142,7 +142,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
} }
NodeGraphMessage::Init => { NodeGraphMessage::Init => {
responses.add(BroadcastMessage::SubscribeEvent { responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged, on: EventMessage::SelectionChanged,
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()), send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
}); });
network_interface.load_structure(); network_interface.load_structure();
@ -1472,7 +1472,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return; return;
}; };
selected_nodes.add_selected_nodes(nodes); selected_nodes.add_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
NodeGraphMessage::SelectedNodesRemove { nodes } => { NodeGraphMessage::SelectedNodesRemove { nodes } => {
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else { let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
@ -1480,7 +1480,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return; return;
}; };
selected_nodes.retain_selected_nodes(|node| !nodes.contains(node)); selected_nodes.retain_selected_nodes(|node| !nodes.contains(node));
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
NodeGraphMessage::SelectedNodesSet { nodes } => { NodeGraphMessage::SelectedNodesSet { nodes } => {
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else { let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
@ -1488,7 +1488,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return; return;
}; };
selected_nodes.set_selected_nodes(nodes); selected_nodes.set_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
} }
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)), click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
@ -1888,7 +1888,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return; return;
}; };
selected_nodes.clear_selected_nodes(); selected_nodes.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }

View file

@ -200,7 +200,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
move |input: &I| { move |input: &I| {
let mut grid = grid.clone(); let mut grid = grid.clone();
update(&mut grid, input); update(&mut grid, input);
DocumentMessage::GridOptions(grid).into() DocumentMessage::GridOptions { options: grid }.into()
} }
} }
let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| { let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {

View file

@ -7,14 +7,14 @@ use crate::messages::prelude::*;
pub enum OverlaysMessage { pub enum OverlaysMessage {
Draw, Draw,
// Serde functionality isn't used but is required by the message system macros // Serde functionality isn't used but is required by the message system macros
AddProvider( AddProvider {
#[serde(skip, default = "empty_provider")] #[serde(skip, default = "empty_provider")]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
OverlayProvider, provider: OverlayProvider,
), },
RemoveProvider( RemoveProvider {
#[serde(skip, default = "empty_provider")] #[serde(skip, default = "empty_provider")]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
OverlayProvider, provider: OverlayProvider,
), },
} }

View file

@ -56,12 +56,14 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
let _ = canvas_context.reset_transform(); let _ = canvas_context.reset_transform();
if visibility_settings.all() { if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays(OverlayContext { responses.add(DocumentMessage::GridOverlays {
render_context: canvas_context.clone(), context: OverlayContext {
size: size.as_dvec2(), render_context: canvas_context.clone(),
device_pixel_ratio, size: size.as_dvec2(),
visibility_settings: visibility_settings.clone(), device_pixel_ratio,
})); visibility_settings: visibility_settings.clone(),
},
});
for provider in &self.overlay_providers { for provider in &self.overlay_providers {
responses.add(provider(OverlayContext { responses.add(provider(OverlayContext {
render_context: canvas_context.clone(), render_context: canvas_context.clone(),
@ -81,22 +83,22 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings); let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
if visibility_settings.all() { if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays(overlay_context.clone())); responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
for provider in &self.overlay_providers { for provider in &self.overlay_providers {
responses.add(provider(overlay_context.clone())); responses.add(provider(overlay_context.clone()));
} }
} }
responses.add(FrontendMessage::RenderOverlays(overlay_context)); responses.add(FrontendMessage::RenderOverlays { context: overlay_context });
} }
#[cfg(all(not(target_family = "wasm"), test))] #[cfg(all(not(target_family = "wasm"), test))]
OverlaysMessage::Draw => { OverlaysMessage::Draw => {
let _ = (responses, visibility_settings, ipp, device_pixel_ratio); let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
} }
OverlaysMessage::AddProvider(message) => { OverlaysMessage::AddProvider { provider: message } => {
self.overlay_providers.insert(message); self.overlay_providers.insert(message);
} }
OverlaysMessage::RemoveProvider(message) => { OverlaysMessage::RemoveProvider { provider: message } => {
self.overlay_providers.remove(&message); self.overlay_providers.remove(&message);
} }
} }

View file

@ -204,7 +204,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
PortfolioMessage::CloseAllDocuments => { PortfolioMessage::CloseAllDocuments => {
if self.active_document_id.is_some() { if self.active_document_id.is_some() {
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
responses.add(ToolMessage::DeactivateTools); responses.add(ToolMessage::DeactivateTools);
// Clear relevant UI layouts if there are no documents // Clear relevant UI layouts if there are no documents
@ -250,7 +250,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
PortfolioMessage::CloseDocumentWithConfirmation { document_id } => { PortfolioMessage::CloseDocumentWithConfirmation { document_id } => {
let target_document = self.documents.get(&document_id).unwrap(); let target_document = self.documents.get(&document_id).unwrap();
if target_document.is_saved() { if target_document.is_saved() {
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
responses.add(PortfolioMessage::CloseDocument { document_id }); responses.add(PortfolioMessage::CloseDocument { document_id });
} else { } else {
let dialog = simple_dialogs::CloseDocumentDialog { let dialog = simple_dialogs::CloseDocumentDialog {
@ -395,7 +395,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let document_id = DocumentId(generate_uuid()); let document_id = DocumentId(generate_uuid());
if self.active_document().is_some() { if self.active_document().is_some() {
new_responses.add(BroadcastEvent::ToolAbort); new_responses.add(EventMessage::ToolAbort);
new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
} }
@ -874,8 +874,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(ToolMessage::InitTools); responses.add(ToolMessage::InitTools);
responses.add(NodeGraphMessage::Init); responses.add(NodeGraphMessage::Init);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
@ -1132,7 +1132,7 @@ impl PortfolioMessageHandler {
self.documents.insert(document_id, new_document); self.documents.insert(document_id, new_document);
if self.active_document().is_some() { if self.active_document().is_some() {
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
responses.add(ToolMessage::DeactivateTools); responses.add(ToolMessage::DeactivateTools);
} else { } else {
// Load the default font upon creating the first document // Load the default font upon creating the first document

View file

@ -62,7 +62,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
} }
PreferencesMessage::ResetToDefaults => { PreferencesMessage::ResetToDefaults => {
refresh_dialog(responses); refresh_dialog(responses);
responses.add(KeyMappingMessage::ModifyMapping(MappingVariant::Default)); responses.add(KeyMappingMessage::ModifyMapping { mapping: MappingVariant::Default });
*self = Self::default() *self = Self::default()
} }
@ -80,7 +80,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
self.zoom_with_scroll = zoom_with_scroll; self.zoom_with_scroll = zoom_with_scroll;
let variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default }; let variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default };
responses.add(KeyMappingMessage::ModifyMapping(variant)); responses.add(KeyMappingMessage::ModifyMapping { mapping: variant });
} }
PreferencesMessage::SelectionMode { selection_mode } => { PreferencesMessage::SelectionMode { selection_mode } => {
self.selection_mode = selection_mode; self.selection_mode = selection_mode;

View file

@ -1,9 +1,11 @@
// Root // Message-related
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild}; pub use crate::utility_traits::{ActionList, AsMessage, ExtractField, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
pub use crate::utility_types::{DebugMessageTree, MessageData}; pub use crate::utility_types::{DebugMessageTree, MessageData};
// Message, MessageData, MessageDiscriminant, MessageHandler // Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler}; pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler}; pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
pub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler}; pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler}; pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
@ -31,7 +33,6 @@ pub use crate::messages::tool::transform_layer::{TransformLayerMessage, Transfor
pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler}; pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};
// Message, MessageDiscriminant // Message, MessageDiscriminant
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
pub use crate::messages::message::{Message, MessageDiscriminant}; pub use crate::messages::message::{Message, MessageDiscriminant};
pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant};
@ -47,7 +48,7 @@ pub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, Sha
pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant};
pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant}; pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant};
// Helper // Helper/miscellaneous
pub use crate::messages::globals::global_variables::*; pub use crate::messages::globals::global_variables::*;
pub use crate::messages::portfolio::document::utility_types::misc::DocumentId; pub use crate::messages::portfolio::document::utility_types::misc::DocumentId;
pub use graphite_proc_macros::*; pub use graphite_proc_macros::*;

View file

@ -14,7 +14,7 @@ impl AutoPanning {
for message in messages { for message in messages {
responses.add(BroadcastMessage::SubscribeEvent { responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::AnimationFrame, on: EventMessage::AnimationFrame,
send: Box::new(message.clone()), send: Box::new(message.clone()),
}); });
} }
@ -27,8 +27,8 @@ impl AutoPanning {
for message in messages { for message in messages {
responses.add(BroadcastMessage::UnsubscribeEvent { responses.add(BroadcastMessage::UnsubscribeEvent {
on: BroadcastEvent::AnimationFrame, on: EventMessage::AnimationFrame,
message: Box::new(message.clone()), send: Box::new(message.clone()),
}); });
} }
} }

View file

@ -17,8 +17,14 @@ pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) ->
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.") .tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
.disabled(!enabled) .disabled(!enabled)
.on_update(move |_| match source { .on_update(move |_| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(), PivotToolSource::Select => SelectToolMessage::SelectOptions {
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(), options: SelectOptionsUpdate::TogglePivotPinned,
}
.into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::TogglePivotPinned,
}
.into(),
}) })
.widget_holder() .widget_holder()
} }
@ -41,8 +47,14 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({ MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
let value = source.clone(); let value = source.clone();
move |_| match value { move |_| match value {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(), PivotToolSource::Select => SelectToolMessage::SelectOptions {
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(), options: SelectOptionsUpdate::PivotGizmoType(*gizmo_type),
}
.into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::PivotGizmoType(*gizmo_type),
}
.into(),
} }
}) })
}) })
@ -57,8 +69,14 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
Disabled: rotation and scaling occurs about the center of the selection bounds.", Disabled: rotation and scaling occurs about the center of the selection bounds.",
) )
.on_update(move |optional_input: &CheckboxInput| match source { .on_update(move |optional_input: &CheckboxInput| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(), PivotToolSource::Select => SelectToolMessage::SelectOptions {
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(), options: SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
}
.into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
}
.into(),
}) })
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),

View file

@ -11,7 +11,7 @@ use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor; use crate::node_graph_executor::NodeGraphExecutor;
use graphene_std::raster::color::Color; use graphene_std::raster::color::Color;
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |overlay_context| DocumentMessage::DrawArtboardOverlays(overlay_context).into(); const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays { context }.into();
#[derive(ExtractField)] #[derive(ExtractField)]
pub struct ToolMessageContext<'a> { pub struct ToolMessageContext<'a> {
@ -75,8 +75,8 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
self.tool_state.tool_data.active_tool_type = ToolType::Shape; self.tool_state.tool_data.active_tool_type = ToolType::Shape;
} }
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });
responses.add(ShapeToolMessage::SetShape(ShapeType::Polygon)); responses.add(ShapeToolMessage::SetShape { shape: ShapeType::Polygon });
responses.add(ShapeToolMessage::HideShapeTypeWidget(false)) responses.add(ShapeToolMessage::HideShapeTypeWidget { hide: false })
} }
ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }), ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }),
ToolMessage::ActivateToolShapeLine | ToolMessage::ActivateToolShapeRectangle | ToolMessage::ActivateToolShapeEllipse => { ToolMessage::ActivateToolShapeLine | ToolMessage::ActivateToolShapeRectangle | ToolMessage::ActivateToolShapeEllipse => {
@ -89,8 +89,8 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
self.tool_state.tool_data.active_shape_type = Some(shape.tool_type()); self.tool_state.tool_data.active_shape_type = Some(shape.tool_type());
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }); responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });
responses.add(ShapeToolMessage::HideShapeTypeWidget(true)); responses.add(ShapeToolMessage::HideShapeTypeWidget { hide: true });
responses.add(ShapeToolMessage::SetShape(shape)); responses.add(ShapeToolMessage::SetShape { shape });
} }
ToolMessage::ActivateTool { tool_type } => { ToolMessage::ActivateTool { tool_type } => {
let tool_data = &mut self.tool_state.tool_data; let tool_data = &mut self.tool_state.tool_data;
@ -157,13 +157,13 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
tool_data.tools.get(&tool_type).unwrap().activate(responses); tool_data.tools.get(&tool_type).unwrap().activate(responses);
// Re-add the artboard overlay provider when tools are reactivated // Re-add the artboard overlay provider when tools are reactivated
responses.add(OverlaysMessage::AddProvider(ARTBOARD_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
// Send the SelectionChanged message to the active tool, this will ensure the selection is updated // Send the SelectionChanged message to the active tool, this will ensure the selection is updated
responses.add(BroadcastEvent::SelectionChanged); responses.add(EventMessage::SelectionChanged);
// Update the working colors for the active tool // Update the working colors for the active tool
responses.add(BroadcastEvent::WorkingColorChanged); responses.add(EventMessage::WorkingColorChanged);
// Send tool options to the frontend // Send tool options to the frontend
responses.add(ToolMessage::RefreshToolOptions); responses.add(ToolMessage::RefreshToolOptions);
@ -176,11 +176,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
tool_data.tools.get(&tool_data.active_tool_type).unwrap().deactivate(responses); tool_data.tools.get(&tool_data.active_tool_type).unwrap().deactivate(responses);
// Unsubscribe the transform layer to selection change events // Unsubscribe the transform layer to selection change events
let message = Box::new(TransformLayerMessage::SelectionChanged.into()); responses.add(BroadcastMessage::UnsubscribeEvent {
let on = BroadcastEvent::SelectionChanged; on: EventMessage::SelectionChanged,
responses.add(BroadcastMessage::UnsubscribeEvent { message, on }); send: Box::new(TransformLayerMessage::SelectionChanged.into()),
});
responses.add(OverlaysMessage::RemoveProvider(ARTBOARD_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
responses.add(FrontendMessage::UpdateInputHints { hint_data: Default::default() }); responses.add(FrontendMessage::UpdateInputHints { hint_data: Default::default() });
responses.add(FrontendMessage::UpdateMouseCursor { cursor: Default::default() }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: Default::default() });
@ -190,12 +191,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
ToolMessage::InitTools => { ToolMessage::InitTools => {
// Subscribe the transform layer to selection change events // Subscribe the transform layer to selection change events
responses.add(BroadcastMessage::SubscribeEvent { responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged, on: EventMessage::SelectionChanged,
send: Box::new(TransformLayerMessage::SelectionChanged.into()), send: Box::new(TransformLayerMessage::SelectionChanged.into()),
}); });
responses.add(BroadcastMessage::SubscribeEvent { responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged, on: EventMessage::SelectionChanged,
send: Box::new(SelectToolMessage::SyncHistory.into()), send: Box::new(SelectToolMessage::SyncHistory.into()),
}); });
@ -232,12 +233,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
tool_data.active_tool_mut().process_message(ToolMessage::UpdateHints, responses, &mut data); tool_data.active_tool_mut().process_message(ToolMessage::UpdateHints, responses, &mut data);
tool_data.active_tool_mut().process_message(ToolMessage::UpdateCursor, responses, &mut data); tool_data.active_tool_mut().process_message(ToolMessage::UpdateCursor, responses, &mut data);
responses.add(OverlaysMessage::AddProvider(ARTBOARD_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
} }
ToolMessage::PreUndo => { ToolMessage::PreUndo => {
let tool_data = &mut self.tool_state.tool_data; let tool_data = &mut self.tool_state.tool_data;
if tool_data.active_tool_type != ToolType::Pen { if tool_data.active_tool_type != ToolType::Pen {
responses.add(BroadcastEvent::ToolAbort); responses.add(EventMessage::ToolAbort);
} }
} }
ToolMessage::Redo => { ToolMessage::Redo => {

View file

@ -26,7 +26,7 @@ pub struct ArtboardTool {
pub enum ArtboardToolMessage { pub enum ArtboardToolMessage {
// Standard messages // Standard messages
Abort, Abort,
Overlays(OverlayContext), Overlays { context: OverlayContext },
// Tool-specific messages // Tool-specific messages
UpdateSelectedArtboard, UpdateSelectedArtboard,
@ -83,7 +83,7 @@ impl ToolTransition for ArtboardTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(ArtboardToolMessage::Abort.into()), tool_abort: Some(ArtboardToolMessage::Abort.into()),
overlay_provider: Some(|overlay_context| ArtboardToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| ArtboardToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -227,7 +227,7 @@ impl Fsm for ArtboardToolFsmState {
let ToolMessage::Artboard(event) = event else { return self }; let ToolMessage::Artboard(event) = event else { return self };
match (self, event) { match (self, event) {
(state, ArtboardToolMessage::Overlays(mut overlay_context)) => { (state, ArtboardToolMessage::Overlays { context: mut overlay_context }) => {
let display_transform_cage = overlay_context.visibility_settings.transform_cage(); let display_transform_cage = overlay_context.visibility_settings.transform_cage();
if display_transform_cage && state != ArtboardToolFsmState::Drawing { if display_transform_cage && state != ArtboardToolFsmState::Drawing {
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) { if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {

View file

@ -62,7 +62,7 @@ pub enum BrushToolMessage {
DragStart, DragStart,
DragStop, DragStop,
PointerMove, PointerMove,
UpdateOptions(BrushToolMessageOptionsUpdate), UpdateOptions { options: BrushToolMessageOptionsUpdate },
} }
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@ -106,7 +106,7 @@ impl LayoutHolder for BrushTool {
.min(1.) .min(1.)
.max(BRUSH_MAX_SIZE) /* Anything bigger would cause the application to be unresponsive and eventually die */ .max(BRUSH_MAX_SIZE) /* Anything bigger would cause the application to be unresponsive and eventually die */
.unit(" px") .unit(" px")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap()) }.into())
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.options.hardness)) NumberInput::new(Some(self.options.hardness))
@ -115,7 +115,12 @@ impl LayoutHolder for BrushTool {
.max(100.) .max(100.)
.mode_range() .mode_range()
.unit("%") .unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap()),
}
.into()
})
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.options.flow)) NumberInput::new(Some(self.options.flow))
@ -124,7 +129,12 @@ impl LayoutHolder for BrushTool {
.max(100.) .max(100.)
.mode_range() .mode_range()
.unit("%") .unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap()),
}
.into()
})
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(), Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.options.spacing)) NumberInput::new(Some(self.options.spacing))
@ -133,7 +143,12 @@ impl LayoutHolder for BrushTool {
.max(100.) .max(100.)
.mode_range() .mode_range()
.unit("%") .unit("%")
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Spacing(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::Spacing(number_input.value.unwrap()),
}
.into()
})
.widget_holder(), .widget_holder(),
]; ];
@ -142,9 +157,12 @@ impl LayoutHolder for BrushTool {
let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore] let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]
.into_iter() .into_iter()
.map(|draw_mode| { .map(|draw_mode| {
RadioEntryData::new(format!("{draw_mode:?}")) RadioEntryData::new(format!("{draw_mode:?}")).label(format!("{draw_mode:?}")).on_update(move |_| {
.label(format!("{draw_mode:?}")) BrushToolMessage::UpdateOptions {
.on_update(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::DrawMode(draw_mode)).into()) options: BrushToolMessageOptionsUpdate::DrawMode(draw_mode),
}
.into()
})
}) })
.collect(); .collect();
widgets.push(RadioInput::new(draw_mode_entries).selected_index(Some(self.options.draw_mode as u32)).widget_holder()); widgets.push(RadioInput::new(draw_mode_entries).selected_index(Some(self.options.draw_mode as u32)).widget_holder());
@ -154,9 +172,26 @@ impl LayoutHolder for BrushTool {
widgets.append(&mut self.options.color.create_widgets( widgets.append(&mut self.options.color.create_widgets(
"Color", "Color",
false, false,
|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()), BrushToolMessage::UpdateOptions {
|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: BrushToolMessageOptionsUpdate::Color(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::ColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
widgets.push(Separator::new(SeparatorType::Related).widget_holder()); widgets.push(Separator::new(SeparatorType::Related).widget_holder());
@ -167,9 +202,12 @@ impl LayoutHolder for BrushTool {
section section
.iter() .iter()
.map(|blend_mode| { .map(|blend_mode| {
MenuListEntry::new(format!("{blend_mode:?}")) MenuListEntry::new(format!("{blend_mode:?}")).label(blend_mode.to_string()).on_commit(|_| {
.label(blend_mode.to_string()) BrushToolMessage::UpdateOptions {
.on_commit(|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::BlendMode(*blend_mode)).into()) options: BrushToolMessageOptionsUpdate::BlendMode(*blend_mode),
}
.into()
})
}) })
.collect() .collect()
}) })
@ -189,11 +227,11 @@ impl LayoutHolder for BrushTool {
#[message_handler_data] #[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for BrushTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for BrushTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Brush(BrushToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true); self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);
return; return;
}; };
match action { match options {
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode, BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => { BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5; let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
@ -408,10 +446,9 @@ impl Fsm for BrushToolFsmState {
BrushToolFsmState::Ready BrushToolFsmState::Ready
} }
(_, BrushToolMessage::WorkingColorChanged) => { (_, BrushToolMessage::WorkingColorChanged) => {
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors( responses.add(BrushToolMessage::UpdateOptions {
Some(global_tool_data.primary_color), options: BrushToolMessageOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
Some(global_tool_data.secondary_color), });
)));
self self
} }
_ => self, _ => self,

View file

@ -14,7 +14,7 @@ pub enum FillToolMessage {
// Standard messages // Standard messages
Abort, Abort,
WorkingColorChanged, WorkingColorChanged,
Overlays(OverlayContext), Overlays { context: OverlayContext },
// Tool-specific messages // Tool-specific messages
PointerMove, PointerMove,
@ -67,7 +67,7 @@ impl ToolTransition for FillTool {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(FillToolMessage::Abort.into()), tool_abort: Some(FillToolMessage::Abort.into()),
working_color_changed: Some(FillToolMessage::WorkingColorChanged.into()), working_color_changed: Some(FillToolMessage::WorkingColorChanged.into()),
overlay_provider: Some(|overlay_context| FillToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| FillToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -99,7 +99,7 @@ impl Fsm for FillToolFsmState {
let ToolMessage::Fill(event) = event else { return self }; let ToolMessage::Fill(event) = event else { return self };
match (self, event) { match (self, event) {
(_, FillToolMessage::Overlays(mut overlay_context)) => { (_, FillToolMessage::Overlays { context: mut overlay_context }) => {
// Choose the working color to preview // Choose the working color to preview
let use_secondary = input.keyboard.get(Key::Shift as usize); let use_secondary = input.keyboard.get(Key::Shift as usize);
let preview_color = if use_secondary { global_tool_data.secondary_color } else { global_tool_data.primary_color }; let preview_color = if use_secondary { global_tool_data.secondary_color } else { global_tool_data.primary_color };

View file

@ -40,7 +40,7 @@ impl Default for FreehandOptions {
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FreehandToolMessage { pub enum FreehandToolMessage {
// Standard messages // Standard messages
Overlays(OverlayContext), Overlays { context: OverlayContext },
Abort, Abort,
WorkingColorChanged, WorkingColorChanged,
@ -48,7 +48,7 @@ pub enum FreehandToolMessage {
DragStart { append_to_selected: Key }, DragStart { append_to_selected: Key },
DragStop, DragStop,
PointerMove, PointerMove,
UpdateOptions(FreehandOptionsUpdate), UpdateOptions { options: FreehandOptionsUpdate },
} }
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@ -86,7 +86,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight") .label("Weight")
.min(1.) .min(1.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::LineWeight(number_input.value.unwrap()),
}
.into()
})
.widget_holder() .widget_holder()
} }
@ -95,9 +100,26 @@ impl LayoutHolder for FreehandTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, true,
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()), FreehandToolMessage::UpdateOptions {
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: FreehandOptionsUpdate::FillColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::FillColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
); );
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@ -105,9 +127,26 @@ impl LayoutHolder for FreehandTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, true,
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()), FreehandToolMessage::UpdateOptions {
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: FreehandOptionsUpdate::StrokeColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::StrokeColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(create_weight_widget(self.options.line_weight)); widgets.push(create_weight_widget(self.options.line_weight));
@ -119,11 +158,11 @@ impl LayoutHolder for FreehandTool {
#[message_handler_data] #[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FreehandTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true); self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);
return; return;
}; };
match action { match options {
FreehandOptionsUpdate::FillColor(color) => { FreehandOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color; self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom; self.options.fill.color_type = ToolColorType::Custom;
@ -164,7 +203,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Free
impl ToolTransition for FreehandTool { impl ToolTransition for FreehandTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
overlay_provider: Some(|overlay_context: OverlayContext| FreehandToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context: OverlayContext| FreehandToolMessage::Overlays { context }.into()),
tool_abort: Some(FreehandToolMessage::Abort.into()), tool_abort: Some(FreehandToolMessage::Abort.into()),
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()), working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
@ -203,7 +242,7 @@ impl Fsm for FreehandToolFsmState {
let ToolMessage::Freehand(event) = event else { return self }; let ToolMessage::Freehand(event) = event else { return self };
match (self, event) { match (self, event) {
(_, FreehandToolMessage::Overlays(mut overlay_context)) => { (_, FreehandToolMessage::Overlays { context: mut overlay_context }) => {
path_endpoint_overlays(document, shape_editor, &mut overlay_context, tool_action_data.preferences); path_endpoint_overlays(document, shape_editor, &mut overlay_context, tool_action_data.preferences);
self self
@ -287,10 +326,9 @@ impl Fsm for FreehandToolFsmState {
FreehandToolFsmState::Ready FreehandToolFsmState::Ready
} }
(_, FreehandToolMessage::WorkingColorChanged) => { (_, FreehandToolMessage::WorkingColorChanged) => {
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors( responses.add(FreehandToolMessage::UpdateOptions {
Some(global_tool_data.primary_color), options: FreehandOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
Some(global_tool_data.secondary_color), });
)));
self self
} }
_ => self, _ => self,
@ -679,7 +717,9 @@ mod test_freehand {
let custom_line_weight = 5.; let custom_line_weight = 5.;
editor editor
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight)))) .handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::LineWeight(custom_line_weight),
}))
.await; .await;
let points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)]; let points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];

View file

@ -24,7 +24,7 @@ pub struct GradientOptions {
pub enum GradientToolMessage { pub enum GradientToolMessage {
// Standard messages // Standard messages
Abort, Abort,
Overlays(OverlayContext), Overlays { context: OverlayContext },
// Tool-specific messages // Tool-specific messages
DeleteStop, DeleteStop,
@ -33,7 +33,7 @@ pub enum GradientToolMessage {
PointerMove { constrain_axis: Key }, PointerMove { constrain_axis: Key },
PointerOutsideViewport { constrain_axis: Key }, PointerOutsideViewport { constrain_axis: Key },
PointerUp, PointerUp,
UpdateOptions(GradientOptionsUpdate), UpdateOptions { options: GradientOptionsUpdate },
} }
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
@ -56,11 +56,11 @@ impl ToolMetadata for GradientTool {
#[message_handler_data] #[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false); self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
return; return;
}; };
match action { match options {
GradientOptionsUpdate::Type(gradient_type) => { GradientOptionsUpdate::Type(gradient_type) => {
self.options.gradient_type = gradient_type; self.options.gradient_type = gradient_type;
// Update the selected gradient if it exists // Update the selected gradient if it exists
@ -91,14 +91,18 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
impl LayoutHolder for GradientTool { impl LayoutHolder for GradientTool {
fn layout(&self) -> Layout { fn layout(&self) -> Layout {
let gradient_type = RadioInput::new(vec![ let gradient_type = RadioInput::new(vec![
RadioEntryData::new("Linear") RadioEntryData::new("Linear").label("Linear").tooltip("Linear gradient").on_update(move |_| {
.label("Linear") GradientToolMessage::UpdateOptions {
.tooltip("Linear gradient") options: GradientOptionsUpdate::Type(GradientType::Linear),
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()), }
RadioEntryData::new("Radial") .into()
.label("Radial") }),
.tooltip("Radial gradient") RadioEntryData::new("Radial").label("Radial").tooltip("Radial gradient").on_update(move |_| {
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()), GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::Type(GradientType::Radial),
}
.into()
}),
]) ])
.selected_index(Some((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32)) .selected_index(Some((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32))
.widget_holder(); .widget_holder();
@ -224,7 +228,7 @@ impl ToolTransition for GradientTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(GradientToolMessage::Abort.into()), tool_abort: Some(GradientToolMessage::Abort.into()),
overlay_provider: Some(|overlay_context| GradientToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| GradientToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -256,7 +260,7 @@ impl Fsm for GradientToolFsmState {
let ToolMessage::Gradient(event) = event else { return self }; let ToolMessage::Gradient(event) = event else { return self };
match (self, event) { match (self, event) {
(_, GradientToolMessage::Overlays(mut overlay_context)) => { (_, GradientToolMessage::Overlays { context: mut overlay_context }) => {
let selected = tool_data.selected_gradient.as_ref(); let selected = tool_data.selected_gradient.as_ref();
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) { for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {

View file

@ -51,8 +51,10 @@ pub struct PathToolOptions {
pub enum PathToolMessage { pub enum PathToolMessage {
// Standard messages // Standard messages
Abort, Abort,
Overlays(OverlayContext),
SelectionChanged, SelectionChanged,
Overlays {
context: OverlayContext,
},
// Tool-specific messages // Tool-specific messages
BreakPath, BreakPath,
@ -123,7 +125,9 @@ pub enum PathToolMessage {
position: ReferencePoint, position: ReferencePoint,
}, },
SwapSelectedHandles, SwapSelectedHandles,
UpdateOptions(PathOptionsUpdate), UpdateOptions {
options: PathOptionsUpdate,
},
UpdateSelectedPointsStatus { UpdateSelectedPointsStatus {
overlay_context: OverlayContext, overlay_context: OverlayContext,
}, },
@ -275,15 +279,30 @@ impl LayoutHolder for PathTool {
RadioEntryData::new("all") RadioEntryData::new("all")
.icon("HandleVisibilityAll") .icon("HandleVisibilityAll")
.tooltip("Show all handles regardless of selection") .tooltip("Show all handles regardless of selection")
.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles)).into()), .on_update(move |_| {
PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles),
}
.into()
}),
RadioEntryData::new("selected") RadioEntryData::new("selected")
.icon("HandleVisibilitySelected") .icon("HandleVisibilitySelected")
.tooltip("Show only handles of the segments connected to selected points") .tooltip("Show only handles of the segments connected to selected points")
.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles)).into()), .on_update(move |_| {
PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles),
}
.into()
}),
RadioEntryData::new("frontier") RadioEntryData::new("frontier")
.icon("HandleVisibilityFrontier") .icon("HandleVisibilityFrontier")
.tooltip("Show only handles at the frontiers of the segments connected to selected points") .tooltip("Show only handles at the frontiers of the segments connected to selected points")
.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles)).into()), .on_update(move |_| {
PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles),
}
.into()
}),
]) ])
.selected_index(Some(self.options.path_overlay_mode as u32)) .selected_index(Some(self.options.path_overlay_mode as u32))
.widget_holder(); .widget_holder();
@ -345,7 +364,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
match message { match message {
ToolMessage::Path(PathToolMessage::UpdateOptions(action)) => match action { ToolMessage::Path(PathToolMessage::UpdateOptions { options }) => match options {
PathOptionsUpdate::OverlayModeType(overlay_mode_type) => { PathOptionsUpdate::OverlayModeType(overlay_mode_type) => {
self.options.path_overlay_mode = overlay_mode_type; self.options.path_overlay_mode = overlay_mode_type;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@ -478,7 +497,7 @@ impl ToolTransition for PathTool {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(PathToolMessage::Abort.into()), tool_abort: Some(PathToolMessage::Abort.into()),
selection_changed: Some(PathToolMessage::SelectionChanged.into()), selection_changed: Some(PathToolMessage::SelectionChanged.into()),
overlay_provider: Some(|overlay_context| PathToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| PathToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -1557,14 +1576,22 @@ impl Fsm for PathToolFsmState {
match (multiple_toggle, point_edit) { match (multiple_toggle, point_edit) {
(true, true) => { (true, true) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: false })); responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::PointEditingMode { enabled: false },
});
} }
(true, false) => { (true, false) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: true })); responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::PointEditingMode { enabled: true },
});
} }
(_, _) => { (_, _) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: true })); responses.add(PathToolMessage::UpdateOptions {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: false })); options: PathOptionsUpdate::PointEditingMode { enabled: true },
});
responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::SegmentEditingMode { enabled: false },
});
// Select all of the end points of selected segments // Select all of the end points of selected segments
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>(); let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
@ -1602,14 +1629,22 @@ impl Fsm for PathToolFsmState {
match (multiple_toggle, segment_edit) { match (multiple_toggle, segment_edit) {
(true, true) => { (true, true) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: false })); responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::SegmentEditingMode { enabled: false },
});
} }
(true, false) => { (true, false) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: true })); responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::SegmentEditingMode { enabled: true },
});
} }
(_, _) => { (_, _) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: false })); responses.add(PathToolMessage::UpdateOptions {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: true })); options: PathOptionsUpdate::PointEditingMode { enabled: false },
});
responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::SegmentEditingMode { enabled: true },
});
// Select all the segments which have both of the ends selected // Select all the segments which have both of the ends selected
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>(); let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
@ -1632,7 +1667,7 @@ impl Fsm for PathToolFsmState {
self self
} }
(_, PathToolMessage::Overlays(mut overlay_context)) => { (_, PathToolMessage::Overlays { context: mut overlay_context }) => {
// Set this to show ghost line only if drag actually happened // Set this to show ghost line only if drag actually happened
if matches!(self, Self::Dragging(_)) && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { if matches!(self, Self::Dragging(_)) && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
for (outline, layer) in &tool_data.ghost_outline { for (outline, layer) in &tool_data.ghost_outline {

View file

@ -50,7 +50,9 @@ pub enum PenToolMessage {
Abort, Abort,
SelectionChanged, SelectionChanged,
WorkingColorChanged, WorkingColorChanged,
Overlays(OverlayContext), Overlays {
context: OverlayContext,
},
// Tool-specific messages // Tool-specific messages
@ -80,7 +82,9 @@ pub enum PenToolMessage {
}, },
Redo, Redo,
Undo, Undo,
UpdateOptions(PenOptionsUpdate), UpdateOptions {
options: PenOptionsUpdate,
},
RecalculateLatestPointsPosition, RecalculateLatestPointsPosition,
RemovePreviousHandle, RemovePreviousHandle,
GRS { GRS {
@ -138,7 +142,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight") .label("Weight")
.min(0.) .min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::LineWeight(number_input.value.unwrap()),
}
.into()
})
.widget_holder() .widget_holder()
} }
@ -147,9 +156,26 @@ impl LayoutHolder for PenTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, true,
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()), PenToolMessage::UpdateOptions {
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: PenOptionsUpdate::FillColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::FillColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
); );
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@ -157,9 +183,26 @@ impl LayoutHolder for PenTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, true,
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()), PenToolMessage::UpdateOptions {
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: PenOptionsUpdate::StrokeColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::StrokeColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@ -173,11 +216,21 @@ impl LayoutHolder for PenTool {
RadioEntryData::new("all") RadioEntryData::new("all")
.icon("HandleVisibilityAll") .icon("HandleVisibilityAll")
.tooltip("Show all handles regardless of selection") .tooltip("Show all handles regardless of selection")
.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles)).into()), .on_update(move |_| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles),
}
.into()
}),
RadioEntryData::new("frontier") RadioEntryData::new("frontier")
.icon("HandleVisibilityFrontier") .icon("HandleVisibilityFrontier")
.tooltip("Show only handles at the frontiers of the segments connected to selected points") .tooltip("Show only handles at the frontiers of the segments connected to selected points")
.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles)).into()), .on_update(move |_| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles),
}
.into()
}),
]) ])
.selected_index(Some(self.options.pen_overlay_mode as u32)) .selected_index(Some(self.options.pen_overlay_mode as u32))
.widget_holder(), .widget_holder(),
@ -190,12 +243,12 @@ impl LayoutHolder for PenTool {
#[message_handler_data] #[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PenTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Pen(PenToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return; return;
}; };
match action { match options {
PenOptionsUpdate::OverlayModeType(overlay_mode_type) => { PenOptionsUpdate::OverlayModeType(overlay_mode_type) => {
self.options.pen_overlay_mode = overlay_mode_type; self.options.pen_overlay_mode = overlay_mode_type;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@ -253,7 +306,7 @@ impl ToolTransition for PenTool {
tool_abort: Some(PenToolMessage::Abort.into()), tool_abort: Some(PenToolMessage::Abort.into()),
selection_changed: Some(PenToolMessage::SelectionChanged.into()), selection_changed: Some(PenToolMessage::SelectionChanged.into()),
working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()), working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()),
overlay_provider: Some(|overlay_context| PenToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| PenToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -1547,7 +1600,7 @@ impl Fsm for PenToolFsmState {
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
self self
} }
(PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => { (PenToolFsmState::Ready, PenToolMessage::Overlays { context: mut overlay_context }) => {
match tool_options.pen_overlay_mode { match tool_options.pen_overlay_mode {
PenOverlayMode::AllHandles => { PenOverlayMode::AllHandles => {
path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
@ -1575,7 +1628,7 @@ impl Fsm for PenToolFsmState {
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
self self
} }
(_, PenToolMessage::Overlays(mut overlay_context)) => { (_, PenToolMessage::Overlays { context: mut overlay_context }) => {
let display_anchors = overlay_context.visibility_settings.anchors(); let display_anchors = overlay_context.visibility_settings.anchors();
let display_handles = overlay_context.visibility_settings.handles(); let display_handles = overlay_context.visibility_settings.handles();
@ -1753,10 +1806,9 @@ impl Fsm for PenToolFsmState {
self self
} }
(_, PenToolMessage::WorkingColorChanged) => { (_, PenToolMessage::WorkingColorChanged) => {
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors( responses.add(PenToolMessage::UpdateOptions {
Some(global_tool_data.primary_color), options: PenOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
Some(global_tool_data.secondary_color), });
)));
self self
} }
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => { (PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {

View file

@ -78,7 +78,9 @@ pub struct SelectToolPointerKeys {
pub enum SelectToolMessage { pub enum SelectToolMessage {
// Standard messages // Standard messages
Abort, Abort,
Overlays(OverlayContext), Overlays {
context: OverlayContext,
},
// Tool-specific messages // Tool-specific messages
DragStart { DragStart {
@ -94,9 +96,15 @@ pub enum SelectToolMessage {
EditLayer, EditLayer,
EditLayerExec, EditLayerExec,
Enter, Enter,
PointerMove(SelectToolPointerKeys), PointerMove {
PointerOutsideViewport(SelectToolPointerKeys), modifier_keys: SelectToolPointerKeys,
SelectOptions(SelectOptionsUpdate), },
PointerOutsideViewport {
modifier_keys: SelectToolPointerKeys,
},
SelectOptions {
options: SelectOptionsUpdate,
},
SetPivot { SetPivot {
position: ReferencePoint, position: ReferencePoint,
}, },
@ -127,9 +135,12 @@ impl SelectTool {
let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest] let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]
.iter() .iter()
.map(|mode| { .map(|mode| {
MenuListEntry::new(format!("{mode:?}")) MenuListEntry::new(format!("{mode:?}")).label(mode.to_string()).on_commit(move |_| {
.label(mode.to_string()) SelectToolMessage::SelectOptions {
.on_commit(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into()) options: SelectOptionsUpdate::NestedSelectionBehavior(*mode),
}
.into()
})
}) })
.collect(); .collect();
@ -278,7 +289,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Sele
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let mut redraw_reference_pivot = false; let mut redraw_reference_pivot = false;
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message { if let ToolMessage::Select(SelectToolMessage::SelectOptions { options: ref option_update }) = message {
match option_update { match option_update {
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => { SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
self.tool_data.nested_selection_behavior = *nested_selection_behavior; self.tool_data.nested_selection_behavior = *nested_selection_behavior;
@ -342,7 +353,7 @@ impl ToolTransition for SelectTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
tool_abort: Some(SelectToolMessage::Abort.into()), tool_abort: Some(SelectToolMessage::Abort.into()),
overlay_provider: Some(|overlay_context| SelectToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| SelectToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -591,7 +602,7 @@ impl Fsm for SelectToolFsmState {
let ToolMessage::Select(event) = event else { return self }; let ToolMessage::Select(event) = event else { return self };
match (self, event) { match (self, event) {
(_, SelectToolMessage::Overlays(mut overlay_context)) => { (_, SelectToolMessage::Overlays { context: mut overlay_context }) => {
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count(); let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count();
@ -1142,7 +1153,7 @@ impl Fsm for SelectToolFsmState {
deepest, deepest,
remove, remove,
}, },
SelectToolMessage::PointerMove(modifier_keys), SelectToolMessage::PointerMove { modifier_keys },
) => { ) => {
if !has_dragged { if !has_dragged {
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
@ -1187,8 +1198,8 @@ impl Fsm for SelectToolFsmState {
// Auto-panning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove { modifier_keys }.into(),
]; ];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
@ -1200,7 +1211,7 @@ impl Fsm for SelectToolFsmState {
remove, remove,
} }
} }
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { modifier_keys }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
resize_bounds( resize_bounds(
document, document,
@ -1215,14 +1226,14 @@ impl Fsm for SelectToolFsmState {
ToolType::Select, ToolType::Select,
); );
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove { modifier_keys }.into(),
]; ];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
} }
SelectToolFsmState::ResizingBounds SelectToolFsmState::ResizingBounds
} }
(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove(_)) => { (SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove { .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
skew_bounds( skew_bounds(
document, document,
@ -1236,7 +1247,7 @@ impl Fsm for SelectToolFsmState {
} }
SelectToolFsmState::SkewingBounds { skew } SelectToolFsmState::SkewingBounds { skew }
} }
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(_)) => { (SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
rotate_bounds( rotate_bounds(
document, document,
@ -1252,7 +1263,7 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::RotatingBounds SelectToolFsmState::RotatingBounds
} }
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove { modifier_keys }) => {
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let snapped_mouse_position = mouse_position; let snapped_mouse_position = mouse_position;
@ -1262,14 +1273,14 @@ impl Fsm for SelectToolFsmState {
// Auto-panning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove { modifier_keys }.into(),
]; ];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
SelectToolFsmState::DraggingPivot SelectToolFsmState::DraggingPivot
} }
(SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove { modifier_keys }) => {
if !has_drawn { if !has_drawn {
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
} }
@ -1283,14 +1294,14 @@ impl Fsm for SelectToolFsmState {
// Auto-panning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove { modifier_keys }.into(),
]; ];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
SelectToolFsmState::Drawing { selection_shape, has_drawn: true } SelectToolFsmState::Drawing { selection_shape, has_drawn: true }
} }
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => { (SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove { .. }) => {
let dragging_bounds = tool_data let dragging_bounds = tool_data
.bounding_box_manager .bounding_box_manager
.as_mut() .as_mut()
@ -1326,7 +1337,7 @@ impl Fsm for SelectToolFsmState {
deepest, deepest,
remove, remove,
}, },
SelectToolMessage::PointerOutsideViewport(_), SelectToolMessage::PointerOutsideViewport { .. },
) => { ) => {
// Auto-panning // Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
@ -1342,7 +1353,7 @@ impl Fsm for SelectToolFsmState {
remove, remove,
} }
} }
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport(_)) => { (SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning // Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
@ -1353,13 +1364,13 @@ impl Fsm for SelectToolFsmState {
self self
} }
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport(_)) => { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning // Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses); let _ = tool_data.auto_panning.shift_viewport(input, responses);
self self
} }
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => { (SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning // Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
tool_data.drag_start += shift; tool_data.drag_start += shift;
@ -1367,11 +1378,11 @@ impl Fsm for SelectToolFsmState {
self self
} }
(state, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => { (state, SelectToolMessage::PointerOutsideViewport { modifier_keys }) => {
// Auto-panning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove { modifier_keys }.into(),
]; ];
tool_data.auto_panning.stop(&messages, responses); tool_data.auto_panning.stop(&messages, responses);

View file

@ -27,7 +27,7 @@ use graphene_std::renderer::Quad;
use graphene_std::vector::misc::ArcType; use graphene_std::vector::misc::ArcType;
use std::vec; use std::vec;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct ShapeTool { pub struct ShapeTool {
fsm_state: ShapeToolFsmState, fsm_state: ShapeToolFsmState,
tool_data: ShapeToolData, tool_data: ShapeToolData,
@ -73,18 +73,18 @@ pub enum ShapeOptionsUpdate {
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ShapeToolMessage { pub enum ShapeToolMessage {
// Standard messages // Standard messages
Overlays(OverlayContext), Overlays { context: OverlayContext },
Abort, Abort,
WorkingColorChanged, WorkingColorChanged,
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
DragStop, DragStop,
HideShapeTypeWidget(bool), HideShapeTypeWidget { hide: bool },
PointerMove(ShapeToolModifierKey), PointerMove { modifier: ShapeToolModifierKey },
PointerOutsideViewport(ShapeToolModifierKey), PointerOutsideViewport { modifier: ShapeToolModifierKey },
UpdateOptions(ShapeOptionsUpdate), UpdateOptions { options: ShapeOptionsUpdate },
SetShape(ShapeType), SetShape { shape: ShapeType },
IncreaseSides, IncreaseSides,
DecreaseSides, DecreaseSides,
@ -99,39 +99,65 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder {
.min(3.) .min(3.)
.max(1000.) .max(1000.)
.mode(NumberInputMode::Increment) .mode(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 {
options: ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32),
}
.into()
})
.widget_holder() .widget_holder()
} }
fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
let entries = vec![vec![ let entries = vec![vec![
MenuListEntry::new("Polygon") MenuListEntry::new("Polygon").label("Polygon").on_commit(move |_| {
.label("Polygon") ShapeToolMessage::UpdateOptions {
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Polygon)).into()), options: ShapeOptionsUpdate::ShapeType(ShapeType::Polygon),
MenuListEntry::new("Star") }
.label("Star") .into()
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), }),
MenuListEntry::new("Circle") MenuListEntry::new("Star").label("Star").on_commit(move |_| {
.label("Circle") ShapeToolMessage::UpdateOptions {
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Circle)).into()), options: ShapeOptionsUpdate::ShapeType(ShapeType::Star),
MenuListEntry::new("Arc") }
.label("Arc") .into()
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()), }),
MenuListEntry::new("Circle").label("Circle").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Circle),
}
.into()
}),
MenuListEntry::new("Arc").label("Arc").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Arc),
}
.into()
}),
]]; ]];
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
} }
fn create_arc_type_widget(arc_type: ArcType) -> WidgetHolder { fn create_arc_type_widget(arc_type: ArcType) -> WidgetHolder {
let entries = vec![ let entries = vec![
RadioEntryData::new("Open") RadioEntryData::new("Open").label("Open").on_update(move |_| {
.label("Open") ShapeToolMessage::UpdateOptions {
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Open)).into()), options: ShapeOptionsUpdate::ArcType(ArcType::Open),
RadioEntryData::new("Closed") }
.label("Closed") .into()
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Closed)).into()), }),
RadioEntryData::new("Pie") RadioEntryData::new("Closed").label("Closed").on_update(move |_| {
.label("Pie") ShapeToolMessage::UpdateOptions {
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::PieSlice)).into()), options: ShapeOptionsUpdate::ArcType(ArcType::Closed),
}
.into()
}),
RadioEntryData::new("Pie").label("Pie").on_update(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ArcType(ArcType::PieSlice),
}
.into()
}),
]; ];
RadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_holder() RadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_holder()
} }
@ -142,7 +168,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight") .label("Weight")
.min(0.) .min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::LineWeight(number_input.value.unwrap()),
}
.into()
})
.widget_holder() .widget_holder()
} }
@ -169,9 +200,26 @@ impl LayoutHolder for ShapeTool {
widgets.append(&mut self.options.fill.create_widgets( widgets.append(&mut self.options.fill.create_widgets(
"Fill", "Fill",
true, true,
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()), ShapeToolMessage::UpdateOptions {
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: ShapeOptionsUpdate::FillColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::FillColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@ -180,9 +228,26 @@ impl LayoutHolder for ShapeTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, true,
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()), ShapeToolMessage::UpdateOptions {
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: ShapeOptionsUpdate::StrokeColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::StrokeColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(create_weight_widget(self.options.line_weight)); widgets.push(create_weight_widget(self.options.line_weight));
@ -191,13 +256,14 @@ impl LayoutHolder for ShapeTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ShapeTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Shape(ShapeToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return; return;
}; };
match action { match options {
ShapeOptionsUpdate::FillColor(color) => { ShapeOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color; self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom; self.options.fill.color_type = ToolColorType::Custom;
@ -285,7 +351,7 @@ impl ToolMetadata for ShapeTool {
impl ToolTransition for ShapeTool { impl ToolTransition for ShapeTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
overlay_provider: Some(|overlay_context| ShapeToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| ShapeToolMessage::Overlays { context }.into()),
tool_abort: Some(ShapeToolMessage::Abort.into()), tool_abort: Some(ShapeToolMessage::Abort.into()),
working_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()), working_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()),
..Default::default() ..Default::default()
@ -402,7 +468,7 @@ impl Fsm for ShapeToolFsmState {
let ToolMessage::Shape(event) = event else { return self }; let ToolMessage::Shape(event) = event else { return self };
match (self, event) { match (self, event) {
(_, ShapeToolMessage::Overlays(mut overlay_context)) => { (_, ShapeToolMessage::Overlays { context: mut overlay_context }) => {
let mouse_position = tool_data let mouse_position = tool_data
.data .data
.snap_manager .snap_manager
@ -484,11 +550,15 @@ impl Fsm for ShapeToolFsmState {
self self
} }
(ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => { (ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => {
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(tool_options.vertices + 1))); responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(tool_options.vertices + 1),
});
self self
} }
(ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => { (ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => {
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)))); responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)),
});
self self
} }
( (
@ -542,7 +612,9 @@ impl Fsm for ShapeToolFsmState {
return self; return self;
}; };
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(n + 1))); responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(n + 1),
});
responses.add(NodeGraphMessage::SetInput { responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1), input_connector: InputConnector::node(node_id, 1),
@ -571,7 +643,9 @@ impl Fsm for ShapeToolFsmState {
return self; return self;
}; };
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((n - 1).max(3)))); responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices((n - 1).max(3)),
});
responses.add(NodeGraphMessage::SetInput { responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1), input_connector: InputConnector::node(node_id, 1),
@ -603,7 +677,9 @@ impl Fsm for ShapeToolFsmState {
tool_data.cursor = cursor; tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor }); responses.add(FrontendMessage::UpdateMouseCursor { cursor });
// Send a PointerMove message to refresh the cursor icon // Send a PointerMove message to refresh the cursor icon
responses.add(ShapeToolMessage::PointerMove(ShapeToolData::shape_tool_modifier_keys())); responses.add(ShapeToolMessage::PointerMove {
modifier: ShapeToolData::shape_tool_modifier_keys(),
});
return ShapeToolFsmState::ModifyingGizmo; return ShapeToolFsmState::ModifyingGizmo;
} }
@ -630,7 +706,9 @@ impl Fsm for ShapeToolFsmState {
let cursor = tool_data.transform_cage_mouse_icon(input); let cursor = tool_data.transform_cage_mouse_icon(input);
tool_data.cursor = cursor; tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor }); responses.add(FrontendMessage::UpdateMouseCursor { cursor });
responses.add(ShapeToolMessage::PointerMove(ShapeToolData::shape_tool_modifier_keys())); responses.add(ShapeToolMessage::PointerMove {
modifier: ShapeToolData::shape_tool_modifier_keys(),
});
}; };
match (resize, rotate, skew) { match (resize, rotate, skew) {
@ -710,7 +788,7 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::Drawing(tool_data.current_shape) ShapeToolFsmState::Drawing(tool_data.current_shape)
} }
(ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove(modifier)) => { (ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove { modifier }) => {
let Some(layer) = tool_data.data.layer else { let Some(layer) = tool_data.data.layer else {
return ShapeToolFsmState::Ready(shape); return ShapeToolFsmState::Ready(shape);
}; };
@ -726,33 +804,33 @@ impl Fsm for ShapeToolFsmState {
} }
// Auto-panning // Auto-panning
let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
self self
} }
(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove(modifier)) => { (ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove { modifier }) => {
let Some(layer) = tool_data.line_data.editing_layer else { let Some(layer) = tool_data.line_data.editing_layer else {
return ShapeToolFsmState::Ready(tool_data.current_shape); return ShapeToolFsmState::Ready(tool_data.current_shape);
}; };
Line::update_shape(document, input, layer, tool_data, modifier, responses); Line::update_shape(document, input, layer, tool_data, modifier, responses);
// Auto-panning // Auto-panning
let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
self self
} }
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => { (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { .. }) => {
tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses); tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
ShapeToolFsmState::ModifyingGizmo ShapeToolFsmState::ModifyingGizmo
} }
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => { (ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove { modifier }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()]; let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
resize_bounds( resize_bounds(
document, document,
responses, responses,
@ -771,7 +849,7 @@ impl Fsm for ShapeToolFsmState {
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
ShapeToolFsmState::ResizingBounds ShapeToolFsmState::ResizingBounds
} }
(ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove(modifier)) => { (ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove { modifier }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
rotate_bounds( rotate_bounds(
document, document,
@ -787,7 +865,7 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::RotatingBounds ShapeToolFsmState::RotatingBounds
} }
(ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove(_)) => { (ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove { .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
skew_bounds( skew_bounds(
document, document,
@ -803,7 +881,7 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::SkewingBounds { skew } ShapeToolFsmState::SkewingBounds { skew }
} }
(_, ShapeToolMessage::PointerMove(_)) => { (_, ShapeToolMessage::PointerMove { .. }) => {
let dragging_bounds = tool_data let dragging_bounds = tool_data
.bounding_box_manager .bounding_box_manager
.as_mut() .as_mut()
@ -825,7 +903,7 @@ impl Fsm for ShapeToolFsmState {
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
self self
} }
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport(_)) => { (ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning // Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
@ -836,7 +914,7 @@ impl Fsm for ShapeToolFsmState {
self self
} }
(ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport(..)) => self, (ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport { .. }) => self,
(_, ShapeToolMessage::PointerOutsideViewport { .. }) => { (_, ShapeToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning // Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses); let _ = tool_data.auto_panning.shift_viewport(input, responses);
@ -891,21 +969,22 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::Ready(tool_data.current_shape) ShapeToolFsmState::Ready(tool_data.current_shape)
} }
(_, ShapeToolMessage::WorkingColorChanged) => { (_, ShapeToolMessage::WorkingColorChanged) => {
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors( responses.add(ShapeToolMessage::UpdateOptions {
Some(global_tool_data.primary_color), options: ShapeOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
Some(global_tool_data.secondary_color), });
)));
self self
} }
(_, ShapeToolMessage::SetShape(shape)) => { (_, ShapeToolMessage::SetShape { shape }) => {
responses.add(DocumentMessage::AbortTransaction); responses.add(DocumentMessage::AbortTransaction);
tool_data.data.cleanup(responses); tool_data.data.cleanup(responses);
tool_data.current_shape = shape; tool_data.current_shape = shape;
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape))); responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(shape),
});
ShapeToolFsmState::Ready(shape) ShapeToolFsmState::Ready(shape)
} }
(_, ShapeToolMessage::HideShapeTypeWidget(hide)) => { (_, ShapeToolMessage::HideShapeTypeWidget { hide }) => {
tool_data.hide_shape_option_widget = hide; tool_data.hide_shape_option_widget = hide;
responses.add(ToolMessage::RefreshToolOptions); responses.add(ToolMessage::RefreshToolOptions);
self self

View file

@ -41,7 +41,7 @@ impl Default for SplineOptions {
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SplineToolMessage { pub enum SplineToolMessage {
// Standard messages // Standard messages
Overlays(OverlayContext), Overlays { context: OverlayContext },
CanvasTransformed, CanvasTransformed,
Abort, Abort,
WorkingColorChanged, WorkingColorChanged,
@ -54,7 +54,7 @@ pub enum SplineToolMessage {
PointerMove, PointerMove,
PointerOutsideViewport, PointerOutsideViewport,
Undo, Undo,
UpdateOptions(SplineOptionsUpdate), UpdateOptions { options: SplineOptionsUpdate },
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@ -93,7 +93,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight") .label("Weight")
.min(0.) .min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::LineWeight(number_input.value.unwrap()),
}
.into()
})
.widget_holder() .widget_holder()
} }
@ -102,9 +107,26 @@ impl LayoutHolder for SplineTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, true,
|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColorType(color_type.clone())).into()), SplineToolMessage::UpdateOptions {
|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: SplineOptionsUpdate::FillColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::FillColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
); );
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
@ -112,9 +134,26 @@ impl LayoutHolder for SplineTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, true,
|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColorType(color_type.clone())).into()), SplineToolMessage::UpdateOptions {
|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: SplineOptionsUpdate::StrokeColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::StrokeColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(create_weight_widget(self.options.line_weight)); widgets.push(create_weight_widget(self.options.line_weight));
@ -126,11 +165,11 @@ impl LayoutHolder for SplineTool {
#[message_handler_data] #[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SplineTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Spline(SplineToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return; return;
}; };
match action { match options {
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
SplineOptionsUpdate::FillColor(color) => { SplineOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color; self.options.fill.custom_color = color;
@ -179,7 +218,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Spli
impl ToolTransition for SplineTool { impl ToolTransition for SplineTool {
fn event_to_message_map(&self) -> EventToMessageMap { fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap { EventToMessageMap {
overlay_provider: Some(|overlay_context: OverlayContext| SplineToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context: OverlayContext| SplineToolMessage::Overlays { context }.into()),
canvas_transformed: Some(SplineToolMessage::CanvasTransformed.into()), canvas_transformed: Some(SplineToolMessage::CanvasTransformed.into()),
tool_abort: Some(SplineToolMessage::Abort.into()), tool_abort: Some(SplineToolMessage::Abort.into()),
working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()), working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()),
@ -262,7 +301,7 @@ impl Fsm for SplineToolFsmState {
let ToolMessage::Spline(event) = event else { return self }; let ToolMessage::Spline(event) = event else { return self };
match (self, event) { match (self, event) {
(_, SplineToolMessage::CanvasTransformed) => self, (_, SplineToolMessage::CanvasTransformed) => self,
(_, SplineToolMessage::Overlays(mut overlay_context)) => { (_, SplineToolMessage::Overlays { context: mut overlay_context }) => {
path_endpoint_overlays(document, shape_editor, &mut overlay_context, preferences); path_endpoint_overlays(document, shape_editor, &mut overlay_context, preferences);
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
self self
@ -440,10 +479,9 @@ impl Fsm for SplineToolFsmState {
SplineToolFsmState::Ready SplineToolFsmState::Ready
} }
(_, SplineToolMessage::WorkingColorChanged) => { (_, SplineToolMessage::WorkingColorChanged) => {
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors( responses.add(SplineToolMessage::UpdateOptions {
Some(global_tool_data.primary_color), options: SplineOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
Some(global_tool_data.secondary_color), });
)));
self self
} }
_ => self, _ => self,

View file

@ -59,7 +59,7 @@ pub enum TextToolMessage {
// Standard messages // Standard messages
Abort, Abort,
WorkingColorChanged, WorkingColorChanged,
Overlays(OverlayContext), Overlays { context: OverlayContext },
// Tool-specific messages // Tool-specific messages
DragStart, DragStart,
@ -70,7 +70,7 @@ pub enum TextToolMessage {
PointerOutsideViewport { center: Key, lock_ratio: Key }, PointerOutsideViewport { center: Key, lock_ratio: Key },
TextChange { new_text: String, is_left_or_right_click: bool }, TextChange { new_text: String, is_left_or_right_click: bool },
UpdateBounds { new_text: String }, UpdateBounds { new_text: String },
UpdateOptions(TextOptionsUpdate), UpdateOptions { options: TextOptionsUpdate },
} }
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@ -100,20 +100,24 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
let font = FontInput::new(&tool.options.font_name, &tool.options.font_style) let font = FontInput::new(&tool.options.font_name, &tool.options.font_style)
.is_style_picker(false) .is_style_picker(false)
.on_update(|font_input: &FontInput| { .on_update(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { TextToolMessage::UpdateOptions {
family: font_input.font_family.clone(), options: TextOptionsUpdate::Font {
style: font_input.font_style.clone(), family: font_input.font_family.clone(),
}) style: font_input.font_style.clone(),
},
}
.into() .into()
}) })
.widget_holder(); .widget_holder();
let style = FontInput::new(&tool.options.font_name, &tool.options.font_style) let style = FontInput::new(&tool.options.font_name, &tool.options.font_style)
.is_style_picker(true) .is_style_picker(true)
.on_update(|font_input: &FontInput| { .on_update(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { TextToolMessage::UpdateOptions {
family: font_input.font_family.clone(), options: TextOptionsUpdate::Font {
style: font_input.font_style.clone(), family: font_input.font_family.clone(),
}) style: font_input.font_style.clone(),
},
}
.into() .into()
}) })
.widget_holder(); .widget_holder();
@ -123,7 +127,12 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
.int() .int()
.min(1.) .min(1.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
TextToolMessage::UpdateOptions {
options: TextOptionsUpdate::FontSize(number_input.value.unwrap()),
}
.into()
})
.widget_holder(); .widget_holder();
let line_height_ratio = NumberInput::new(Some(tool.options.line_height_ratio)) let line_height_ratio = NumberInput::new(Some(tool.options.line_height_ratio))
.label("Line Height") .label("Line Height")
@ -131,14 +140,22 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
.min(0.) .min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64) .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.step(0.1) .step(0.1)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::LineHeightRatio(number_input.value.unwrap())).into()) .on_update(|number_input: &NumberInput| {
TextToolMessage::UpdateOptions {
options: TextOptionsUpdate::LineHeightRatio(number_input.value.unwrap()),
}
.into()
})
.widget_holder(); .widget_holder();
let align_entries: Vec<_> = [TextAlign::Left, TextAlign::Center, TextAlign::Right, TextAlign::JustifyLeft] let align_entries: Vec<_> = [TextAlign::Left, TextAlign::Center, TextAlign::Right, TextAlign::JustifyLeft]
.into_iter() .into_iter()
.map(|align| { .map(|align| {
RadioEntryData::new(format!("{align:?}")) RadioEntryData::new(format!("{align:?}")).label(align.to_string()).on_update(move |_| {
.label(align.to_string()) TextToolMessage::UpdateOptions {
.on_update(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::Align(align)).into()) options: TextOptionsUpdate::Align(align),
}
.into()
})
}) })
.collect(); .collect();
let align = RadioInput::new(align_entries).selected_index(Some(tool.options.align as u32)).widget_holder(); let align = RadioInput::new(align_entries).selected_index(Some(tool.options.align as u32)).widget_holder();
@ -164,9 +181,26 @@ impl LayoutHolder for TextTool {
widgets.append(&mut self.options.fill.create_widgets( widgets.append(&mut self.options.fill.create_widgets(
"Fill", "Fill",
true, true,
|_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into(), |_| {
|color_type: ToolColorType| WidgetCallback::new(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColorType(color_type.clone())).into()), TextToolMessage::UpdateOptions {
|color: &ColorInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(), options: TextOptionsUpdate::FillColor(None),
}
.into()
},
|color_type: ToolColorType| {
WidgetCallback::new(move |_| {
TextToolMessage::UpdateOptions {
options: TextOptionsUpdate::FillColorType(color_type.clone()),
}
.into()
})
},
|color: &ColorInput| {
TextToolMessage::UpdateOptions {
options: TextOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
}
.into()
},
)); ));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
@ -176,11 +210,11 @@ impl LayoutHolder for TextTool {
#[message_handler_data] #[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for TextTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Text(TextToolMessage::UpdateOptions { options }) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return; return;
}; };
match action { match options {
TextOptionsUpdate::Font { family, style } => { TextOptionsUpdate::Font { family, style } => {
self.options.font_name = family; self.options.font_name = family;
self.options.font_style = style; self.options.font_style = style;
@ -237,7 +271,7 @@ impl ToolTransition for TextTool {
canvas_transformed: None, canvas_transformed: None,
tool_abort: Some(TextToolMessage::Abort.into()), tool_abort: Some(TextToolMessage::Abort.into()),
working_color_changed: Some(TextToolMessage::WorkingColorChanged.into()), working_color_changed: Some(TextToolMessage::WorkingColorChanged.into()),
overlay_provider: Some(|overlay_context| TextToolMessage::Overlays(overlay_context).into()), overlay_provider: Some(|context| TextToolMessage::Overlays { context }.into()),
..Default::default() ..Default::default()
} }
} }
@ -474,7 +508,7 @@ impl Fsm for TextToolFsmState {
let ToolMessage::Text(event) = event else { return self }; let ToolMessage::Text(event) = event else { return self };
match (self, event) { match (self, event) {
(TextToolFsmState::Editing, TextToolMessage::Overlays(mut overlay_context)) => { (TextToolFsmState::Editing, TextToolMessage::Overlays { context: mut overlay_context }) => {
responses.add(FrontendMessage::DisplayEditableTextboxTransform { responses.add(FrontendMessage::DisplayEditableTextboxTransform {
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(), transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
}); });
@ -490,7 +524,7 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Editing TextToolFsmState::Editing
} }
(_, TextToolMessage::Overlays(mut overlay_context)) => { (_, TextToolMessage::Overlays { context: mut overlay_context }) => {
if matches!(self, Self::Placing) { if matches!(self, Self::Placing) {
// Get the updated selection box bounds // Get the updated selection box bounds
let quad = Quad::from_box(tool_data.cached_resize_bounds); let quad = Quad::from_box(tool_data.cached_resize_bounds);
@ -852,10 +886,9 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Editing TextToolFsmState::Editing
} }
(_, TextToolMessage::WorkingColorChanged) => { (_, TextToolMessage::WorkingColorChanged) => {
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors( responses.add(TextToolMessage::UpdateOptions {
Some(global_tool_data.primary_color), options: TextOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
Some(global_tool_data.secondary_color), });
)));
self self
} }
(TextToolFsmState::Editing, TextToolMessage::Abort) => { (TextToolFsmState::Editing, TextToolMessage::Abort) => {

View file

@ -8,10 +8,8 @@ use glam::DVec2;
#[impl_message(Message, ToolMessage, TransformLayer)] #[impl_message(Message, ToolMessage, TransformLayer)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformLayerMessage { pub enum TransformLayerMessage {
// Overlays
Overlays(OverlayContext),
// Messages // Messages
Overlays { context: OverlayContext },
ApplyTransformOperation { final_transform: bool }, ApplyTransformOperation { final_transform: bool },
BeginTransformOperation { operation: TransformType }, BeginTransformOperation { operation: TransformType },
BeginGrab, BeginGrab,

View file

@ -16,7 +16,7 @@ use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::{Vector, VectorModificationType}; use graphene_std::vector::{Vector, VectorModificationType};
use std::f64::consts::{PI, TAU}; use std::f64::consts::{PI, TAU};
const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays(context).into(); const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays { context }.into();
// TODO: Get these from the input mapper // TODO: Get these from the input mapper
const SLOW_KEY: Key = Key::Shift; const SLOW_KEY: Key = Key::Shift;
@ -69,6 +69,7 @@ pub struct TransformLayerMessageHandler {
was_grabbing: bool, was_grabbing: bool,
} }
#[message_handler_data]
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler { impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, context: TransformLayerMessageContext) { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, context: TransformLayerMessageContext) {
let TransformLayerMessageContext { let TransformLayerMessageContext {
@ -172,7 +173,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
match message { match message {
// Overlays // Overlays
TransformLayerMessage::Overlays(mut overlay_context) => { TransformLayerMessage::Overlays { context: mut overlay_context } => {
if !overlay_context.visibility_settings.transform_measurement() { if !overlay_context.visibility_settings.transform_measurement() {
return; return;
} }
@ -304,7 +305,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
if final_transform { if final_transform {
self.was_grabbing = false; self.was_grabbing = false;
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
} }
} }
TransformLayerMessage::BeginTransformOperation { operation } => { TransformLayerMessage::BeginTransformOperation { operation } => {
@ -343,7 +346,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
_ => unreachable!(), // Safe because the match arms are exhaustive _ => unreachable!(), // Safe because the match arms are exhaustive
}; };
responses.add(OverlaysMessage::AddProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::AddProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
// Find a way better than this hack // Find a way better than this hack
responses.add(TransformLayerMessage::PointerMove { responses.add(TransformLayerMessage::PointerMove {
slow_key: SLOW_KEY, slow_key: SLOW_KEY,
@ -428,7 +433,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
if chain_operation { if chain_operation {
responses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false }); responses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false });
} else { } else {
responses.add(OverlaysMessage::AddProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::AddProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
} }
responses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type }); responses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type });
responses.add(TransformLayerMessage::PointerMove { responses.add(TransformLayerMessage::PointerMove {
@ -465,7 +472,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
} }
responses.add(SelectToolMessage::PivotShift { offset: None, flush: false }); responses.add(SelectToolMessage::PivotShift { offset: None, flush: false });
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
} }
TransformLayerMessage::ConstrainX => { TransformLayerMessage::ConstrainX => {
let pivot = document_to_viewport.transform_point2(self.local_pivot); let pivot = document_to_viewport.transform_point2(self.local_pivot);

View file

@ -3,7 +3,7 @@
use super::common_functionality::shape_editor::ShapeState; use super::common_functionality::shape_editor::ShapeState;
use super::tool_messages::*; use super::tool_messages::*;
use crate::messages::broadcast::BroadcastMessage; use crate::messages::broadcast::BroadcastMessage;
use crate::messages::broadcast::broadcast_event::BroadcastEvent; use crate::messages::broadcast::event::EventMessage;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::input_mapper::utility_types::misc::ActionKeys;
@ -141,7 +141,7 @@ impl DocumentToolData {
layout_target: LayoutTarget::WorkingColors, layout_target: LayoutTarget::WorkingColors,
}); });
responses.add(BroadcastMessage::TriggerEvent(BroadcastEvent::WorkingColorChanged)); responses.add(BroadcastMessage::TriggerEvent(EventMessage::WorkingColorChanged));
} }
} }
@ -158,7 +158,7 @@ pub trait ToolTransition {
fn event_to_message_map(&self) -> EventToMessageMap; fn event_to_message_map(&self) -> EventToMessageMap;
fn activate(&self, responses: &mut VecDeque<Message>) { fn activate(&self, responses: &mut VecDeque<Message>) {
let mut subscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, event: BroadcastEvent| { let mut subscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, event: EventMessage| {
if let Some(mapping) = broadcast_to_tool_mapping { if let Some(mapping) = broadcast_to_tool_mapping {
responses.add(BroadcastMessage::SubscribeEvent { responses.add(BroadcastMessage::SubscribeEvent {
on: event, on: event,
@ -168,32 +168,32 @@ pub trait ToolTransition {
}; };
let event_to_tool_map = self.event_to_message_map(); let event_to_tool_map = self.event_to_message_map();
subscribe_message(event_to_tool_map.canvas_transformed, BroadcastEvent::CanvasTransformed); subscribe_message(event_to_tool_map.canvas_transformed, EventMessage::CanvasTransformed);
subscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort); subscribe_message(event_to_tool_map.tool_abort, EventMessage::ToolAbort);
subscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged); subscribe_message(event_to_tool_map.selection_changed, EventMessage::SelectionChanged);
subscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged); subscribe_message(event_to_tool_map.working_color_changed, EventMessage::WorkingColorChanged);
if let Some(overlay_provider) = event_to_tool_map.overlay_provider { if let Some(overlay_provider) = event_to_tool_map.overlay_provider {
responses.add(OverlaysMessage::AddProvider(overlay_provider)); responses.add(OverlaysMessage::AddProvider { provider: overlay_provider });
} }
} }
fn deactivate(&self, responses: &mut VecDeque<Message>) { fn deactivate(&self, responses: &mut VecDeque<Message>) {
let mut unsubscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, event: BroadcastEvent| { let mut unsubscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, event: EventMessage| {
if let Some(mapping) = broadcast_to_tool_mapping { if let Some(mapping) = broadcast_to_tool_mapping {
responses.add(BroadcastMessage::UnsubscribeEvent { responses.add(BroadcastMessage::UnsubscribeEvent {
on: event, on: event,
message: Box::new(mapping.into()), send: Box::new(mapping.into()),
}); });
} }
}; };
let event_to_tool_map = self.event_to_message_map(); let event_to_tool_map = self.event_to_message_map();
unsubscribe_message(event_to_tool_map.canvas_transformed, BroadcastEvent::CanvasTransformed); unsubscribe_message(event_to_tool_map.canvas_transformed, EventMessage::CanvasTransformed);
unsubscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort); unsubscribe_message(event_to_tool_map.tool_abort, EventMessage::ToolAbort);
unsubscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged); unsubscribe_message(event_to_tool_map.selection_changed, EventMessage::SelectionChanged);
unsubscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged); unsubscribe_message(event_to_tool_map.working_color_changed, EventMessage::WorkingColorChanged);
if let Some(overlay_provider) = event_to_tool_map.overlay_provider { if let Some(overlay_provider) = event_to_tool_map.overlay_provider {
responses.add(OverlaysMessage::RemoveProvider(overlay_provider)); responses.add(OverlaysMessage::RemoveProvider { provider: overlay_provider });
} }
} }
} }

View file

@ -160,7 +160,7 @@ impl NodeGraphExecutor {
self.futures.insert(execution_id, ExecutionContext { export_config: None, document_id }); self.futures.insert(execution_id, ExecutionContext { export_config: None, document_id });
Ok(DeferMessage::SetGraphSubmissionIndex(execution_id).into()) Ok(DeferMessage::SetGraphSubmissionIndex { execution_id }.into())
} }
/// Evaluates a node graph, computing the entire graph /// Evaluates a node graph, computing the entire graph
@ -288,7 +288,10 @@ impl NodeGraphExecutor {
} else { } else {
self.process_node_graph_output(node_graph_output, responses)?; self.process_node_graph_output(node_graph_output, responses)?;
} }
responses.add(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id)); responses.add(DeferMessage::TriggerGraphRun {
execution_id,
document_id: execution_context.document_id,
});
// Update the Data panel on the frontend using the value of the inspect result. // Update the Data panel on the frontend using the value of the inspect result.
if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() { if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() {

View file

@ -60,3 +60,9 @@ pub trait HierarchicalTree {
"" ""
} }
} }
pub trait ExtractField {
fn field_types() -> Vec<(String, usize)>;
fn path() -> &'static str;
fn print_field_types();
}

View file

@ -26,6 +26,7 @@ impl MessageData {
#[derive(Debug)] #[derive(Debug)]
pub struct DebugMessageTree { pub struct DebugMessageTree {
name: String, name: String,
fields: Option<Vec<String>>,
variants: Option<Vec<DebugMessageTree>>, variants: Option<Vec<DebugMessageTree>>,
message_handler: Option<MessageData>, message_handler: Option<MessageData>,
message_handler_data: Option<MessageData>, message_handler_data: Option<MessageData>,
@ -36,6 +37,7 @@ impl DebugMessageTree {
pub fn new(name: &str) -> DebugMessageTree { pub fn new(name: &str) -> DebugMessageTree {
DebugMessageTree { DebugMessageTree {
name: name.to_string(), name: name.to_string(),
fields: None,
variants: None, variants: None,
message_handler: None, message_handler: None,
message_handler_data: None, message_handler_data: None,
@ -43,6 +45,10 @@ impl DebugMessageTree {
} }
} }
pub fn add_fields(&mut self, fields: Vec<String>) {
self.fields = Some(fields);
}
pub fn set_path(&mut self, path: &'static str) { pub fn set_path(&mut self, path: &'static str) {
self.path = path; self.path = path;
} }
@ -67,6 +73,10 @@ impl DebugMessageTree {
&self.name &self.name
} }
pub fn fields(&self) -> Option<&Vec<String>> {
self.fields.as_ref()
}
pub fn path(&self) -> &'static str { pub fn path(&self) -> &'static str {
self.path self.path
} }

View file

@ -277,7 +277,7 @@ impl EditorHandle {
// Used by auto-panning, but this could possibly be refactored in the future, see: // Used by auto-panning, but this could possibly be refactored in the future, see:
// <https://github.com/GraphiteEditor/Graphite/pull/2562#discussion_r2041102786> // <https://github.com/GraphiteEditor/Graphite/pull/2562#discussion_r2041102786>
handle.dispatch(BroadcastMessage::TriggerEvent(BroadcastEvent::AnimationFrame)); handle.dispatch(BroadcastMessage::TriggerEvent(EventMessage::AnimationFrame));
}); });
} }

View file

@ -31,23 +31,23 @@ pub fn derive_extract_field_impl(input: TokenStream) -> syn::Result<TokenStream>
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let field_str = field_info.into_iter().map(|(name, ty)| (format!("{}: {}", name, ty))); let field_str = field_info.into_iter().map(|(name, ty)| (format!("{name}: {ty}")));
let res = quote! { let res = quote! {
impl #impl_generics #struct_name #ty_generics #where_clause { impl #impl_generics ExtractField for #struct_name #ty_generics #where_clause {
pub fn field_types() -> Vec<(String, usize)> { fn field_types() -> Vec<(String, usize)> {
vec![ vec![
#((String::from(#field_str), #field_line)),* #((String::from(#field_str), #field_line)),*
] ]
} }
pub fn print_field_types() { fn print_field_types() {
for (field, line) in Self::field_types() { for (field, line) in Self::field_types() {
println!("{} at line {}", field, line); println!("{} at line {}", field, line);
} }
} }
pub fn path() -> &'static str { fn path() -> &'static str {
file!() file!()
} }
} }

View file

@ -1,3 +1,4 @@
use crate::helpers::clean_rust_type_syntax;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote}; use quote::{ToTokens, quote};
use syn::{Data, DeriveInput, Fields, Type, parse2}; use syn::{Data, DeriveInput, Fields, Type, parse2};
@ -11,51 +12,89 @@ pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result<TokenStream
_ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")), _ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")),
}; };
let build_message_tree = data.variants.iter().map(|variant| { let build_message_tree: Result<Vec<_>, syn::Error> = data
let variant_type = &variant.ident; .variants
.iter()
.map(|variant| {
let variant_type = &variant.ident;
let has_child = variant let has_child = variant
.attrs .attrs
.iter() .iter()
.any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child")); .any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child"));
if has_child { match &variant.fields {
if let Fields::Unnamed(fields) = &variant.fields { Fields::Unit => Ok(quote! {
let field_type = &fields.unnamed.first().unwrap().ty; message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
quote! { }),
{ Fields::Unnamed(fields) => {
let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type)); if has_child {
let field_name = stringify!(#field_type); let field_type = &fields.unnamed.first().unwrap().ty;
const message_string: &str = "Message"; Ok(quote! {
if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] { {
// The field is a Message type, recursively build its tree let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
let sub_tree = #field_type::build_message_tree(); let field_name = stringify!(#field_type);
variant_tree.add_variant(sub_tree); const MESSAGE_SUFFIX: &str = "Message";
} if MESSAGE_SUFFIX == &field_name[field_name.len().saturating_sub(MESSAGE_SUFFIX.len())..] {
message_tree.add_variant(variant_tree); // The field is a Message type, recursively build its tree
let sub_tree = #field_type::build_message_tree();
variant_tree.add_variant(sub_tree);
} else {
variant_tree.add_fields(vec![format!("{field_name}")]);
}
message_tree.add_variant(variant_tree);
}
})
} else {
let error_msg = match fields.unnamed.len() {
0 => format!("Remove the unnecessary `()` from the `{}` message enum variant.", variant_type),
1 => {
let field_type = &fields.unnamed.first().unwrap().ty;
format!(
"The `{}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\
Replace `{}` with a named field using {{curly braces}} instead of a positional field using (parentheses).",
variant_type,
field_type.to_token_stream()
)
}
_ => {
let field_types = fields.unnamed.iter().map(|f| f.ty.to_token_stream().to_string()).collect::<Vec<_>>().join(", ");
format!(
"The `{}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\
Replace `{}` with named fields using {{curly braces}} instead of positional fields using (parentheses).",
variant_type, field_types
)
}
};
Err(syn::Error::new(Span::call_site(), error_msg))
} }
} }
} else { Fields::Named(fields) => {
quote! { let names = fields.named.iter().map(|f| f.ident.as_ref().unwrap());
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); let ty = fields.named.iter().map(|f| clean_rust_type_syntax(f.ty.to_token_stream().to_string()));
Ok(quote! {
{
let mut field_names = Vec::new();
#(field_names.push(format!("{}: {}",stringify!(#names), #ty));)*
let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
variant_tree.add_fields(field_names);
message_tree.add_variant(variant_tree);
}
})
} }
} }
} else { })
quote! { .collect();
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); let build_message_tree = build_message_tree?;
}
}
});
let res = quote! { let res = quote! {
impl HierarchicalTree for #input_type { impl HierarchicalTree for #input_type {
fn build_message_tree() -> DebugMessageTree { fn build_message_tree() -> DebugMessageTree {
let mut message_tree = DebugMessageTree::new(stringify!(#input_type)); let mut message_tree = DebugMessageTree::new(stringify!(#input_type));
#(#build_message_tree)* #(#build_message_tree)*
let message_handler_str = #input_type::message_handler_str(); let message_handler_str = #input_type::message_handler_str();
if message_handler_str.fields().len() > 0 { message_tree.add_message_handler_field(message_handler_str);
message_tree.add_message_handler_field(message_handler_str);
}
let message_handler_data_str = #input_type::message_handler_data_str(); let message_handler_data_str = #input_type::message_handler_data_str();
if message_handler_data_str.fields().len() > 0 { if message_handler_data_str.fields().len() > 0 {

View file

@ -43,10 +43,8 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream
quote! { quote! {
#input_item #input_item
impl #message_type { impl #message_type {
pub fn message_handler_data_str() -> MessageData pub fn message_handler_data_str() -> MessageData {
{
MessageData::new(format!("{}", stringify!(#type_name)), #type_name::field_types(), #type_name::path()) MessageData::new(format!("{}", stringify!(#type_name)), #type_name::field_types(), #type_name::path())
} }
pub fn message_handler_str() -> MessageData { pub fn message_handler_str() -> MessageData {
MessageData::new(format!("{}", stringify!(#input_type)), #input_type::field_types(), #input_type::path()) MessageData::new(format!("{}", stringify!(#input_type)), #input_type::field_types(), #input_type::path())

View file

@ -110,7 +110,7 @@ pub fn derive_widget_builder_impl(input_item: TokenStream2) -> syn::Result<Token
// Construct the `widget_holder` function // Construct the `widget_holder` function
quote::quote! { quote::quote! {
#[doc = #widget_holder_doc_comment] #[doc = #widget_holder_doc_comment]
pub fn widget_holder(self) -> crate::messages::layout::utility_types::layout_widget::WidgetHolder{ pub fn widget_holder(self) -> crate::messages::layout::utility_types::layout_widget::WidgetHolder {
crate::messages::layout::utility_types::layout_widget::WidgetHolder::new( crate::messages::layout::utility_types::layout_widget::Widget::#struct_name_ident(self)) crate::messages::layout::utility_types::layout_widget::WidgetHolder::new( crate::messages::layout::utility_types::layout_widget::Widget::#struct_name_ident(self))
} }
} }

View file

@ -57,31 +57,37 @@ function buildHtmlList(nodes, currentIndex, currentLevel) {
continue; continue;
} }
const hasChildren = (i + 1 < nodes.length) && (nodes[i + 1].level > node.level); const hasDirectChildren = i + 1 < nodes.length && nodes[i + 1].level > node.level;
const hasDeeperChildren = hasDirectChildren && i + 2 < nodes.length && nodes[i + 2].level > nodes[i + 1].level;
const linkHtml = node.link ? `<a href="${node.link}" target="_blank">${path.basename(node.link)}</a>` : ""; const linkHtml = node.link ? `<a href="${node.link}" target="_blank">${path.basename(node.link)}</a>` : "";
const fieldPieces = node.text.match(/([^:]*):(.*)/); const fieldPieces = node.text.match(/([^:]*):(.*)/);
const partOfMessageFromNamingConvention = ["Message", "MessageHandler", "MessageContext"].some((suffix) => node.text.replace(/(.*)<.*>/g, "$1").endsWith(suffix));
const partOfMessageViolatesNamingConvention = node.link && !partOfMessageFromNamingConvention;
const partOfMessage = node.link ? "subsystem" : "";
const messageParent = (hasChildren && !node.link) ? " submessage": "";
const violatesNamingConvention = partOfMessageViolatesNamingConvention ? "<span class=\"warn\">(violates naming convention — should end with 'Message', 'MessageHandler', or 'MessageContext')</span>" : "";
let escapedText; let escapedText;
if (fieldPieces && fieldPieces.length === 3) { if (fieldPieces && fieldPieces.length === 3) {
escapedText = [escapeHtml(fieldPieces[1].trim()), escapeHtml(fieldPieces[2].trim())]; escapedText = [escapeHtml(fieldPieces[1].trim()), escapeHtml(fieldPieces[2].trim())];
} else { } else {
escapedText = [escapeHtml(node.text)]; escapedText = [escapeHtml(node.text)];
} }
let role = "message";
if (node.link) role = "subsystem";
else if (hasDeeperChildren) role = "submessage";
else if (escapedText.length === 2) role = "field";
if (hasChildren) { const partOfMessageFromNamingConvention = ["Message", "MessageHandler", "MessageContext"].some((suffix) => node.text.replace(/(.*)<.*>/g, "$1").endsWith(suffix));
html += `<li><span class="tree-node"><span class="${partOfMessage}${messageParent}">${escapedText}</span>${linkHtml}${violatesNamingConvention}</span>`; const partOfMessageViolatesNamingConvention = node.link && !partOfMessageFromNamingConvention;
const violatesNamingConvention = partOfMessageViolatesNamingConvention ? "<span class=\"warn\">(violates naming convention — should end with 'Message', 'MessageHandler', or 'MessageContext')</span>" : "";
if (hasDirectChildren) {
html += `<li><span class="tree-node"><span class="${role}">${escapedText}</span>${linkHtml}${violatesNamingConvention}</span>`;
const childResult = buildHtmlList(nodes, i + 1, node.level + 1); const childResult = buildHtmlList(nodes, i + 1, node.level + 1);
html += `<div class="nested">${childResult.html}</div></li>\n`; html += `<div class="nested">${childResult.html}</div></li>\n`;
i = childResult.nextIndex; i = childResult.nextIndex;
} else if (escapedText.length === 2) { } else if (role === "field") {
html += `<li><span class="tree-leaf field">${escapedText[0]}</span><span>: ${escapedText[1]}</span>${linkHtml}</li>\n`; html += `<li><span class="tree-leaf field">${escapedText[0]}</span>: <span>${escapedText[1]}</span>${linkHtml}</li>\n`;
i++; i++;
} else { } else {
html += `<li><span class="tree-leaf${partOfMessage}">${escapedText[0]}</span>${linkHtml}${violatesNamingConvention}</li>\n`; html += `<li><span class="tree-leaf ${role}">${escapedText[0]}</span>${linkHtml}${violatesNamingConvention}</li>\n`;
i++; i++;
} }
} }

View file

@ -30,10 +30,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><polygon fill="%2316323f" points="4,0 1,0 6,5 1,10 4,10 9,5 4,0" /></svg>\ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><polygon fill="%2316323f" points="4,0 1,0 6,5 1,10 4,10 9,5 4,0" /></svg>\
'); ');
position: absolute; position: absolute;
margin: auto;
top: 0;
bottom: 0;
left: 0; left: 0;
margin: calc((1.5em - 10px) / 2) auto;
width: 10px; width: 10px;
height: 10px; height: 10px;
} }
@ -41,34 +39,10 @@
&.expanded::before { &.expanded::before {
transform: rotate(90deg); transform: rotate(90deg);
} }
a {
margin-left: 12px;
color: var(--color-crimson);
font-size: 12px;
font-family: Arial, sans-serif;
position: relative;
&:hover::after {
content: "";
margin-left: 4px;
position: absolute;
}
}
} }
.tree-leaf { .tree-leaf {
margin-left: calc(10px + 8px); margin-left: calc(10px + 8px);
&.field {
padding-left: 4px;
color: var(--color-storm);
}
&:not(.field) {
padding: 0 4px;
background: var(--color-fog);
}
} }
.nested { .nested {
@ -80,6 +54,7 @@
} }
.warn { .warn {
display: inline;
margin-left: 12px; margin-left: 12px;
color: var(--color-flamingo); color: var(--color-flamingo);
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
@ -87,6 +62,20 @@
text-decoration: none; text-decoration: none;
font-style: italic; font-style: italic;
} }
a {
margin-left: 12px;
color: var(--color-crimson);
font-size: 12px;
font-family: Arial, sans-serif;
position: relative;
&:hover::after {
content: "";
margin-left: 4px;
position: absolute;
}
}
} }
.subsystem, .subsystem,
@ -94,13 +83,27 @@
font-family: monospace; font-family: monospace;
line-height: 1.5; line-height: 1.5;
padding: 0 4px; padding: 0 4px;
&.subsystem {
color: #ffffff;
background: var(--color-crimson);
}
&.submessage {
background: var(--color-mustard);
}
} }
.subsystem { .message {
color: #ffffff; padding: 0 4px;
background: var(--color-storm); background: var(--color-fog);
} }
.submessage { .field {
background: var(--color-lilac); padding-left: 4px;
color: #8887c0;
+ span {
color: #457297;
}
} }