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>) {
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays(_))) {
let FrontendMessage::RenderOverlays(overlay_context) = message else { unreachable!() };
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays { .. })) {
let FrontendMessage::RenderOverlays { context: overlay_context } = message else { unreachable!() };
if let Some(graphics_state) = &mut self.graphics_state {
let scene = overlay_context.take_scene();
graphics_state.set_overlays_scene(scene);

View file

@ -52,7 +52,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
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.

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
use crate::messages::prelude::*;
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
#[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
AnimationFrame,
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_handler;
pub mod broadcast_event;
pub mod event;
#[doc(inline)]
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};

View file

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

View file

@ -24,10 +24,10 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
DeferMessage::AfterNavigationReady { 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;
}
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();
if after_graph_run.is_empty() {
return;

View file

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

View file

@ -38,10 +38,10 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
let ExportDialogMessageContext { portfolio } = context;
match message {
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
ExportDialogMessage::ScaleFactor(factor) => self.scale_factor = factor,
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
ExportDialogMessage::FileType { file_type } => self.file_type = file_type,
ExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,
ExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,
ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
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 {
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
.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();
let export_type = vec![
@ -101,7 +105,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
.min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.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)
.widget_holder(),
];
@ -125,10 +129,10 @@ impl LayoutHolder for ExportDialogMessageHandler {
.map(|choice| {
choice
.into_iter()
.map(|(val, name, disabled)| {
MenuListEntry::new(format!("{val:?}"))
.map(|(bounds, name, disabled)| {
MenuListEntry::new(format!("{bounds:?}"))
.label(name)
.on_commit(move |_| ExportDialogMessage::ExportBounds(val).into())
.on_commit(move |_| ExportDialogMessage::ExportBounds { bounds }.into())
.disabled(disabled)
})
.collect::<Vec<_>>()
@ -151,7 +155,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.transparent_background)
.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)
.widget_holder(),
];

View file

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

View file

@ -20,10 +20,10 @@ pub struct NewDocumentDialogMessageHandler {
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
match message {
NewDocumentDialogMessage::Name(name) => self.name = name,
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32,
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32,
NewDocumentDialogMessage::Name { name } => self.name = name,
NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,
NewDocumentDialogMessage::DimensionsX { width } => self.dimensions.x = width as u32,
NewDocumentDialogMessage::DimensionsY { height } => self.dimensions.y = height as u32,
NewDocumentDialogMessage::Submit => {
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(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
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
.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(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
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)
.widget_holder(),
];
@ -108,7 +108,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
.is_integer(true)
.disabled(self.infinite)
.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(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.dimensions.y as f64))
@ -119,7 +119,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
.is_integer(true)
.disabled(self.infinite)
.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(),
];

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::prelude::*;
@ -27,7 +27,7 @@ impl DialogLayoutHolder for CloseDocumentDialog {
TextButton::new("Discard")
.on_update(move |_| {
DialogMessage::CloseDialogAndThen {
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
followups: vec![EventMessage::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
}
.into()
})

View file

@ -335,9 +335,9 @@ pub enum FrontendMessage {
active: bool,
},
#[cfg(not(target_family = "wasm"))]
RenderOverlays(
RenderOverlays {
#[serde(skip, default = "OverlayContext::default")]
#[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 }),
//
// 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!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
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(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
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, 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 }),
@ -297,8 +297,8 @@ pub fn input_mappings() -> Mapping {
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
entry!(KeyDown(BracketRight); 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 { options: BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD) }),
entry!(KeyDown(MouseRight); 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)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
pub enum KeyMappingMessage {
// Sub-messages
#[child]
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)]
pub enum MappingVariant {
#[default]

View file

@ -19,8 +19,11 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMapp
let KeyMappingMessageContext { input, actions } = context;
match message {
// Sub-messages
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!();

View file

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

View file

@ -35,10 +35,10 @@ pub enum Message {
Tool(ToolMessage),
// Messages
NoOp,
Batched {
messages: Box<[Message]>,
},
NoOp,
}
/// 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
if let Some(data) = tree.message_handler_fields() {
let len = data.fields().len();
@ -102,11 +113,13 @@ mod test {
} else {
("└── ", format!("{} ", prefix))
};
if data.path().is_empty() {
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
} else {
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 { "├── " };
@ -114,6 +127,7 @@ mod test {
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
}
}
}
// Print data field if any
if let Some(data) = tree.message_handler_data_fields() {

View file

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

View file

@ -391,7 +391,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
}
}
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
DocumentMessage::DrawArtboardOverlays { context: overlay_context } => {
if !overlay_context.visibility_settings.artboard_name() {
return;
}
@ -588,19 +588,19 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::GraphViewOverlayToggle => {
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
}
DocumentMessage::GridOptions(grid) => {
self.snapping_state.grid = grid;
DocumentMessage::GridOptions { options } => {
self.snapping_state.grid = options;
self.snapping_state.grid_snapping = true;
responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::GridOverlays(mut overlay_context) => {
DocumentMessage::GridOverlays { context: mut overlay_context } => {
if self.snapping_state.grid_snapping {
grid_overlay(self, &mut overlay_context)
}
}
DocumentMessage::GridVisibility(enabled) => {
self.snapping_state.grid_snapping = enabled;
DocumentMessage::GridVisibility { visible } => {
self.snapping_state.grid_snapping = visible;
responses.add(OverlaysMessage::Draw);
}
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
@ -1062,7 +1062,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
if !parent_layers.is_empty() {
let nodes = parent_layers.into_iter().collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
}
DocumentMessage::SelectAllLayers => {
@ -1137,7 +1137,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
} else {
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
} else {
nodes.push(id);
}
@ -1206,7 +1206,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
Some(overlays_type) => overlays_type,
None => {
visibility_settings.all = visible;
responses.add(BroadcastEvent::ToolAbort);
responses.add(EventMessage::ToolAbort);
responses.add(OverlaysMessage::Draw);
return;
}
@ -1229,7 +1229,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
OverlaysType::Handles => visibility_settings.handles = visible,
}
responses.add(BroadcastEvent::ToolAbort);
responses.add(EventMessage::ToolAbort);
responses.add(OverlaysMessage::Draw);
}
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);
self.network_interface.set_document_to_viewport_transform(transform);
// Ensure selection box is kept in sync with the pointer when the PTZ changes
responses.add(SelectToolMessage::PointerMove(SelectToolPointerKeys {
responses.add(SelectToolMessage::PointerMove {
modifier_keys: SelectToolPointerKeys {
axis_align: Key::Shift,
snap_angle: Key::Shift,
center: Key::Alt,
duplicate: Key::Alt,
}));
},
});
responses.add(NodeGraphMessage::RunDocumentGraph);
} 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 => {
self.network_interface.selection_step_back(&self.selection_network_path);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
DocumentMessage::SelectionStepForward => {
self.network_interface.selection_step_forward(&self.selection_network_path);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {
// Get bounding box of all layers
@ -2484,7 +2486,7 @@ impl DocumentMessageHandler {
.icon("Grid")
.tooltip("Grid")
.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(),
PopoverButton::new()
.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);
ptz.pan += transformed_delta;
responses.add(BroadcastEvent::CanvasTransformed);
responses.add(EventMessage::CanvasTransformed);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {
@ -286,7 +286,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
ptz.flip = !ptz.flip;
responses.add(DocumentMessage::PTZUpdate);
responses.add(BroadcastEvent::CanvasTransformed);
responses.add(EventMessage::CanvasTransformed);
responses.add(MenuBarMessage::SendLayout);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@ -325,7 +325,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
self.navigation_operation = NavigationOperation::None;
// Send the final messages to close out the operation
responses.add(BroadcastEvent::CanvasTransformed);
responses.add(EventMessage::CanvasTransformed);
responses.add(ToolMessage::UpdateCursor);
responses.add(ToolMessage::UpdateHints);
responses.add(NavigateToolMessage::End);

View file

@ -129,7 +129,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::AddPathNode => {
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(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
}
NodeGraphMessage::AddImport => {
@ -142,7 +142,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
NodeGraphMessage::Init => {
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
on: EventMessage::SelectionChanged,
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
});
network_interface.load_structure();
@ -1472,7 +1472,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
return;
};
selected_nodes.add_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
NodeGraphMessage::SelectedNodesRemove { nodes } => {
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;
};
selected_nodes.retain_selected_nodes(|node| !nodes.contains(node));
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
NodeGraphMessage::SelectedNodesSet { nodes } => {
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;
};
selected_nodes.set_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
}
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
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;
};
selected_nodes.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
responses.add(NodeGraphMessage::SendGraph);
}

View file

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

View file

@ -7,14 +7,14 @@ use crate::messages::prelude::*;
pub enum OverlaysMessage {
Draw,
// Serde functionality isn't used but is required by the message system macros
AddProvider(
AddProvider {
#[serde(skip, default = "empty_provider")]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
OverlayProvider,
),
RemoveProvider(
provider: OverlayProvider,
},
RemoveProvider {
#[serde(skip, default = "empty_provider")]
#[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();
if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays(OverlayContext {
responses.add(DocumentMessage::GridOverlays {
context: OverlayContext {
render_context: canvas_context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
}));
},
});
for provider in &self.overlay_providers {
responses.add(provider(OverlayContext {
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);
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 {
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))]
OverlaysMessage::Draw => {
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
}
OverlaysMessage::AddProvider(message) => {
OverlaysMessage::AddProvider { provider: message } => {
self.overlay_providers.insert(message);
}
OverlaysMessage::RemoveProvider(message) => {
OverlaysMessage::RemoveProvider { provider: message } => {
self.overlay_providers.remove(&message);
}
}

View file

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

View file

@ -62,7 +62,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
}
PreferencesMessage::ResetToDefaults => {
refresh_dialog(responses);
responses.add(KeyMappingMessage::ModifyMapping(MappingVariant::Default));
responses.add(KeyMappingMessage::ModifyMapping { mapping: MappingVariant::Default });
*self = Self::default()
}
@ -80,7 +80,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
self.zoom_with_scroll = zoom_with_scroll;
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 } => {
self.selection_mode = selection_mode;

View file

@ -1,9 +1,11 @@
// Root
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
// Message-related
pub use crate::utility_traits::{ActionList, AsMessage, ExtractField, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
pub use crate::utility_types::{DebugMessageTree, MessageData};
// Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
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::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
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};
// Message, MessageDiscriminant
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
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::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::text_tool::{TextToolMessage, TextToolMessageDiscriminant};
// Helper
// Helper/miscellaneous
pub use crate::messages::globals::global_variables::*;
pub use crate::messages::portfolio::document::utility_types::misc::DocumentId;
pub use graphite_proc_macros::*;

View file

@ -14,7 +14,7 @@ impl AutoPanning {
for message in messages {
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::AnimationFrame,
on: EventMessage::AnimationFrame,
send: Box::new(message.clone()),
});
}
@ -27,8 +27,8 @@ impl AutoPanning {
for message in messages {
responses.add(BroadcastMessage::UnsubscribeEvent {
on: BroadcastEvent::AnimationFrame,
message: Box::new(message.clone()),
on: EventMessage::AnimationFrame,
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.")
.disabled(!enabled)
.on_update(move |_| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
PivotToolSource::Select => SelectToolMessage::SelectOptions {
options: SelectOptionsUpdate::TogglePivotPinned,
}
.into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::TogglePivotPinned,
}
.into(),
})
.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({
let value = source.clone();
move |_| match value {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
PivotToolSource::Select => SelectToolMessage::SelectOptions {
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.",
)
.on_update(move |optional_input: &CheckboxInput| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
PivotToolSource::Select => SelectToolMessage::SelectOptions {
options: SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
}
.into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
}
.into(),
})
.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 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)]
pub struct ToolMessageContext<'a> {
@ -75,8 +75,8 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
self.tool_state.tool_data.active_tool_type = ToolType::Shape;
}
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });
responses.add(ShapeToolMessage::SetShape(ShapeType::Polygon));
responses.add(ShapeToolMessage::HideShapeTypeWidget(false))
responses.add(ShapeToolMessage::SetShape { shape: ShapeType::Polygon });
responses.add(ShapeToolMessage::HideShapeTypeWidget { hide: false })
}
ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }),
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());
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });
responses.add(ShapeToolMessage::HideShapeTypeWidget(true));
responses.add(ShapeToolMessage::SetShape(shape));
responses.add(ShapeToolMessage::HideShapeTypeWidget { hide: true });
responses.add(ShapeToolMessage::SetShape { shape });
}
ToolMessage::ActivateTool { tool_type } => {
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);
// 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
responses.add(BroadcastEvent::SelectionChanged);
responses.add(EventMessage::SelectionChanged);
// Update the working colors for the active tool
responses.add(BroadcastEvent::WorkingColorChanged);
responses.add(EventMessage::WorkingColorChanged);
// Send tool options to the frontend
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);
// Unsubscribe the transform layer to selection change events
let message = Box::new(TransformLayerMessage::SelectionChanged.into());
let on = BroadcastEvent::SelectionChanged;
responses.add(BroadcastMessage::UnsubscribeEvent { message, on });
responses.add(BroadcastMessage::UnsubscribeEvent {
on: EventMessage::SelectionChanged,
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::UpdateMouseCursor { cursor: Default::default() });
@ -190,12 +191,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
ToolMessage::InitTools => {
// Subscribe the transform layer to selection change events
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
on: EventMessage::SelectionChanged,
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
});
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
on: EventMessage::SelectionChanged,
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::UpdateCursor, responses, &mut data);
responses.add(OverlaysMessage::AddProvider(ARTBOARD_OVERLAY_PROVIDER));
responses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
}
ToolMessage::PreUndo => {
let tool_data = &mut self.tool_state.tool_data;
if tool_data.active_tool_type != ToolType::Pen {
responses.add(BroadcastEvent::ToolAbort);
responses.add(EventMessage::ToolAbort);
}
}
ToolMessage::Redo => {

View file

@ -26,7 +26,7 @@ pub struct ArtboardTool {
pub enum ArtboardToolMessage {
// Standard messages
Abort,
Overlays(OverlayContext),
Overlays { context: OverlayContext },
// Tool-specific messages
UpdateSelectedArtboard,
@ -83,7 +83,7 @@ impl ToolTransition for ArtboardTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
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()
}
}
@ -227,7 +227,7 @@ impl Fsm for ArtboardToolFsmState {
let ToolMessage::Artboard(event) = event else { return self };
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();
if display_transform_cage && state != ArtboardToolFsmState::Drawing {
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,
DragStop,
PointerMove,
UpdateOptions(BrushToolMessageOptionsUpdate),
UpdateOptions { options: BrushToolMessageOptionsUpdate },
}
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@ -106,7 +106,7 @@ impl LayoutHolder for BrushTool {
.min(1.)
.max(BRUSH_MAX_SIZE) /* Anything bigger would cause the application to be unresponsive and eventually die */
.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(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.options.hardness))
@ -115,7 +115,12 @@ impl LayoutHolder for BrushTool {
.max(100.)
.mode_range()
.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(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.options.flow))
@ -124,7 +129,12 @@ impl LayoutHolder for BrushTool {
.max(100.)
.mode_range()
.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(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.options.spacing))
@ -133,7 +143,12 @@ impl LayoutHolder for BrushTool {
.max(100.)
.mode_range()
.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(),
];
@ -142,9 +157,12 @@ impl LayoutHolder for BrushTool {
let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]
.into_iter()
.map(|draw_mode| {
RadioEntryData::new(format!("{draw_mode:?}"))
.label(format!("{draw_mode:?}"))
.on_update(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::DrawMode(draw_mode)).into())
RadioEntryData::new(format!("{draw_mode:?}")).label(format!("{draw_mode:?}")).on_update(move |_| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::DrawMode(draw_mode),
}
.into()
})
})
.collect();
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(
"Color",
false,
|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()),
|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
BrushToolMessage::UpdateOptions {
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());
@ -167,9 +202,12 @@ impl LayoutHolder for BrushTool {
section
.iter()
.map(|blend_mode| {
MenuListEntry::new(format!("{blend_mode:?}"))
.label(blend_mode.to_string())
.on_commit(|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::BlendMode(*blend_mode)).into())
MenuListEntry::new(format!("{blend_mode:?}")).label(blend_mode.to_string()).on_commit(|_| {
BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::BlendMode(*blend_mode),
}
.into()
})
})
.collect()
})
@ -189,11 +227,11 @@ impl LayoutHolder for BrushTool {
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for BrushTool {
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);
return;
};
match action {
match options {
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
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
}
(_, BrushToolMessage::WorkingColorChanged) => {
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
responses.add(BrushToolMessage::UpdateOptions {
options: BrushToolMessageOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
});
self
}
_ => self,

View file

@ -14,7 +14,7 @@ pub enum FillToolMessage {
// Standard messages
Abort,
WorkingColorChanged,
Overlays(OverlayContext),
Overlays { context: OverlayContext },
// Tool-specific messages
PointerMove,
@ -67,7 +67,7 @@ impl ToolTransition for FillTool {
EventToMessageMap {
tool_abort: Some(FillToolMessage::Abort.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()
}
}
@ -99,7 +99,7 @@ impl Fsm for FillToolFsmState {
let ToolMessage::Fill(event) = event else { return self };
match (self, event) {
(_, FillToolMessage::Overlays(mut overlay_context)) => {
(_, FillToolMessage::Overlays { context: mut overlay_context }) => {
// Choose the working color to preview
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 };

View file

@ -40,7 +40,7 @@ impl Default for FreehandOptions {
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FreehandToolMessage {
// Standard messages
Overlays(OverlayContext),
Overlays { context: OverlayContext },
Abort,
WorkingColorChanged,
@ -48,7 +48,7 @@ pub enum FreehandToolMessage {
DragStart { append_to_selected: Key },
DragStop,
PointerMove,
UpdateOptions(FreehandOptionsUpdate),
UpdateOptions { options: FreehandOptionsUpdate },
}
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@ -86,7 +86,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight")
.min(1.)
.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()
}
@ -95,9 +100,26 @@ impl LayoutHolder for FreehandTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()),
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
FreehandToolMessage::UpdateOptions {
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());
@ -105,9 +127,26 @@ impl LayoutHolder for FreehandTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
FreehandToolMessage::UpdateOptions {
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(create_weight_widget(self.options.line_weight));
@ -119,11 +158,11 @@ impl LayoutHolder for FreehandTool {
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FreehandTool {
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);
return;
};
match action {
match options {
FreehandOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
@ -164,7 +203,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Free
impl ToolTransition for FreehandTool {
fn event_to_message_map(&self) -> 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()),
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
..Default::default()
@ -203,7 +242,7 @@ impl Fsm for FreehandToolFsmState {
let ToolMessage::Freehand(event) = event else { return self };
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);
self
@ -287,10 +326,9 @@ impl Fsm for FreehandToolFsmState {
FreehandToolFsmState::Ready
}
(_, FreehandToolMessage::WorkingColorChanged) => {
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
responses.add(FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
});
self
}
_ => self,
@ -679,7 +717,9 @@ mod test_freehand {
let custom_line_weight = 5.;
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;
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 {
// Standard messages
Abort,
Overlays(OverlayContext),
Overlays { context: OverlayContext },
// Tool-specific messages
DeleteStop,
@ -33,7 +33,7 @@ pub enum GradientToolMessage {
PointerMove { constrain_axis: Key },
PointerOutsideViewport { constrain_axis: Key },
PointerUp,
UpdateOptions(GradientOptionsUpdate),
UpdateOptions { options: GradientOptionsUpdate },
}
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
@ -56,11 +56,11 @@ impl ToolMetadata for GradientTool {
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool {
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);
return;
};
match action {
match options {
GradientOptionsUpdate::Type(gradient_type) => {
self.options.gradient_type = gradient_type;
// Update the selected gradient if it exists
@ -91,14 +91,18 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
impl LayoutHolder for GradientTool {
fn layout(&self) -> Layout {
let gradient_type = RadioInput::new(vec![
RadioEntryData::new("Linear")
.label("Linear")
.tooltip("Linear gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
RadioEntryData::new("Radial")
.label("Radial")
.tooltip("Radial gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
RadioEntryData::new("Linear").label("Linear").tooltip("Linear gradient").on_update(move |_| {
GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::Type(GradientType::Linear),
}
.into()
}),
RadioEntryData::new("Radial").label("Radial").tooltip("Radial gradient").on_update(move |_| {
GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::Type(GradientType::Radial),
}
.into()
}),
])
.selected_index(Some((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32))
.widget_holder();
@ -224,7 +228,7 @@ impl ToolTransition for GradientTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
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()
}
}
@ -256,7 +260,7 @@ impl Fsm for GradientToolFsmState {
let ToolMessage::Gradient(event) = event else { return self };
match (self, event) {
(_, GradientToolMessage::Overlays(mut overlay_context)) => {
(_, GradientToolMessage::Overlays { context: mut overlay_context }) => {
let selected = tool_data.selected_gradient.as_ref();
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 {
// Standard messages
Abort,
Overlays(OverlayContext),
SelectionChanged,
Overlays {
context: OverlayContext,
},
// Tool-specific messages
BreakPath,
@ -123,7 +125,9 @@ pub enum PathToolMessage {
position: ReferencePoint,
},
SwapSelectedHandles,
UpdateOptions(PathOptionsUpdate),
UpdateOptions {
options: PathOptionsUpdate,
},
UpdateSelectedPointsStatus {
overlay_context: OverlayContext,
},
@ -275,15 +279,30 @@ impl LayoutHolder for PathTool {
RadioEntryData::new("all")
.icon("HandleVisibilityAll")
.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")
.icon("HandleVisibilitySelected")
.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")
.icon("HandleVisibilityFrontier")
.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))
.widget_holder();
@ -345,7 +364,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
match message {
ToolMessage::Path(PathToolMessage::UpdateOptions(action)) => match action {
ToolMessage::Path(PathToolMessage::UpdateOptions { options }) => match options {
PathOptionsUpdate::OverlayModeType(overlay_mode_type) => {
self.options.path_overlay_mode = overlay_mode_type;
responses.add(OverlaysMessage::Draw);
@ -478,7 +497,7 @@ impl ToolTransition for PathTool {
EventToMessageMap {
tool_abort: Some(PathToolMessage::Abort.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()
}
}
@ -1557,14 +1576,22 @@ impl Fsm for PathToolFsmState {
match (multiple_toggle, point_edit) {
(true, true) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: false }));
responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::PointEditingMode { enabled: 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(PathOptionsUpdate::SegmentEditingMode { enabled: false }));
responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::PointEditingMode { enabled: true },
});
responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::SegmentEditingMode { enabled: false },
});
// Select all of the end points of selected segments
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
@ -1602,14 +1629,22 @@ impl Fsm for PathToolFsmState {
match (multiple_toggle, segment_edit) {
(true, true) => {
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: false }));
responses.add(PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::SegmentEditingMode { enabled: 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(PathOptionsUpdate::SegmentEditingMode { enabled: true }));
responses.add(PathToolMessage::UpdateOptions {
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
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
@ -1632,7 +1667,7 @@ impl Fsm for PathToolFsmState {
self
}
(_, PathToolMessage::Overlays(mut overlay_context)) => {
(_, PathToolMessage::Overlays { context: mut overlay_context }) => {
// 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 {
for (outline, layer) in &tool_data.ghost_outline {

View file

@ -50,7 +50,9 @@ pub enum PenToolMessage {
Abort,
SelectionChanged,
WorkingColorChanged,
Overlays(OverlayContext),
Overlays {
context: OverlayContext,
},
// Tool-specific messages
@ -80,7 +82,9 @@ pub enum PenToolMessage {
},
Redo,
Undo,
UpdateOptions(PenOptionsUpdate),
UpdateOptions {
options: PenOptionsUpdate,
},
RecalculateLatestPointsPosition,
RemovePreviousHandle,
GRS {
@ -138,7 +142,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight")
.min(0.)
.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()
}
@ -147,9 +156,26 @@ impl LayoutHolder for PenTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()),
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
PenToolMessage::UpdateOptions {
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());
@ -157,9 +183,26 @@ impl LayoutHolder for PenTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
PenToolMessage::UpdateOptions {
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());
@ -173,11 +216,21 @@ impl LayoutHolder for PenTool {
RadioEntryData::new("all")
.icon("HandleVisibilityAll")
.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")
.icon("HandleVisibilityFrontier")
.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))
.widget_holder(),
@ -190,12 +243,12 @@ impl LayoutHolder for PenTool {
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PenTool {
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);
return;
};
match action {
match options {
PenOptionsUpdate::OverlayModeType(overlay_mode_type) => {
self.options.pen_overlay_mode = overlay_mode_type;
responses.add(OverlaysMessage::Draw);
@ -253,7 +306,7 @@ impl ToolTransition for PenTool {
tool_abort: Some(PenToolMessage::Abort.into()),
selection_changed: Some(PenToolMessage::SelectionChanged.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()
}
}
@ -1547,7 +1600,7 @@ impl Fsm for PenToolFsmState {
responses.add(OverlaysMessage::Draw);
self
}
(PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => {
(PenToolFsmState::Ready, PenToolMessage::Overlays { context: mut overlay_context }) => {
match tool_options.pen_overlay_mode {
PenOverlayMode::AllHandles => {
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);
self
}
(_, PenToolMessage::Overlays(mut overlay_context)) => {
(_, PenToolMessage::Overlays { context: mut overlay_context }) => {
let display_anchors = overlay_context.visibility_settings.anchors();
let display_handles = overlay_context.visibility_settings.handles();
@ -1753,10 +1806,9 @@ impl Fsm for PenToolFsmState {
self
}
(_, PenToolMessage::WorkingColorChanged) => {
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
responses.add(PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
});
self
}
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {

View file

@ -78,7 +78,9 @@ pub struct SelectToolPointerKeys {
pub enum SelectToolMessage {
// Standard messages
Abort,
Overlays(OverlayContext),
Overlays {
context: OverlayContext,
},
// Tool-specific messages
DragStart {
@ -94,9 +96,15 @@ pub enum SelectToolMessage {
EditLayer,
EditLayerExec,
Enter,
PointerMove(SelectToolPointerKeys),
PointerOutsideViewport(SelectToolPointerKeys),
SelectOptions(SelectOptionsUpdate),
PointerMove {
modifier_keys: SelectToolPointerKeys,
},
PointerOutsideViewport {
modifier_keys: SelectToolPointerKeys,
},
SelectOptions {
options: SelectOptionsUpdate,
},
SetPivot {
position: ReferencePoint,
},
@ -127,9 +135,12 @@ impl SelectTool {
let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]
.iter()
.map(|mode| {
MenuListEntry::new(format!("{mode:?}"))
.label(mode.to_string())
.on_commit(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into())
MenuListEntry::new(format!("{mode:?}")).label(mode.to_string()).on_commit(move |_| {
SelectToolMessage::SelectOptions {
options: SelectOptionsUpdate::NestedSelectionBehavior(*mode),
}
.into()
})
})
.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>) {
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 {
SelectOptionsUpdate::NestedSelectionBehavior(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 {
EventToMessageMap {
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()
}
}
@ -591,7 +602,7 @@ impl Fsm for SelectToolFsmState {
let ToolMessage::Select(event) = event else { return self };
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);
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,
remove,
},
SelectToolMessage::PointerMove(modifier_keys),
SelectToolMessage::PointerMove { modifier_keys },
) => {
if !has_dragged {
responses.add(ToolMessage::UpdateHints);
@ -1187,8 +1198,8 @@ impl Fsm for SelectToolFsmState {
// Auto-panning
let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(),
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove { modifier_keys }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
@ -1200,7 +1211,7 @@ impl Fsm for SelectToolFsmState {
remove,
}
}
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => {
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { modifier_keys }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
resize_bounds(
document,
@ -1215,14 +1226,14 @@ impl Fsm for SelectToolFsmState {
ToolType::Select,
);
let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(),
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove { modifier_keys }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
}
SelectToolFsmState::ResizingBounds
}
(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove(_)) => {
(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove { .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
skew_bounds(
document,
@ -1236,7 +1247,7 @@ impl Fsm for SelectToolFsmState {
}
SelectToolFsmState::SkewingBounds { skew }
}
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(_)) => {
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
rotate_bounds(
document,
@ -1252,7 +1263,7 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::RotatingBounds
}
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove { modifier_keys }) => {
let mouse_position = input.mouse.position;
let snapped_mouse_position = mouse_position;
@ -1262,14 +1273,14 @@ impl Fsm for SelectToolFsmState {
// Auto-panning
let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(),
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove { modifier_keys }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
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 {
responses.add(ToolMessage::UpdateHints);
}
@ -1283,14 +1294,14 @@ impl Fsm for SelectToolFsmState {
// Auto-panning
let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(),
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove { modifier_keys }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
SelectToolFsmState::Drawing { selection_shape, has_drawn: true }
}
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => {
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove { .. }) => {
let dragging_bounds = tool_data
.bounding_box_manager
.as_mut()
@ -1326,7 +1337,7 @@ impl Fsm for SelectToolFsmState {
deepest,
remove,
},
SelectToolMessage::PointerOutsideViewport(_),
SelectToolMessage::PointerOutsideViewport { .. },
) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
@ -1342,7 +1353,7 @@ impl Fsm for SelectToolFsmState {
remove,
}
}
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
@ -1353,13 +1364,13 @@ impl Fsm for SelectToolFsmState {
self
}
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport(_)) => {
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses);
self
}
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
tool_data.drag_start += shift;
@ -1367,11 +1378,11 @@ impl Fsm for SelectToolFsmState {
self
}
(state, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => {
(state, SelectToolMessage::PointerOutsideViewport { modifier_keys }) => {
// Auto-panning
let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(),
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
SelectToolMessage::PointerMove { modifier_keys }.into(),
];
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 std::vec;
#[derive(Default)]
#[derive(Default, ExtractField)]
pub struct ShapeTool {
fsm_state: ShapeToolFsmState,
tool_data: ShapeToolData,
@ -73,18 +73,18 @@ pub enum ShapeOptionsUpdate {
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum ShapeToolMessage {
// Standard messages
Overlays(OverlayContext),
Overlays { context: OverlayContext },
Abort,
WorkingColorChanged,
// Tool-specific messages
DragStart,
DragStop,
HideShapeTypeWidget(bool),
PointerMove(ShapeToolModifierKey),
PointerOutsideViewport(ShapeToolModifierKey),
UpdateOptions(ShapeOptionsUpdate),
SetShape(ShapeType),
HideShapeTypeWidget { hide: bool },
PointerMove { modifier: ShapeToolModifierKey },
PointerOutsideViewport { modifier: ShapeToolModifierKey },
UpdateOptions { options: ShapeOptionsUpdate },
SetShape { shape: ShapeType },
IncreaseSides,
DecreaseSides,
@ -99,39 +99,65 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder {
.min(3.)
.max(1000.)
.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()
}
fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
let entries = vec![vec![
MenuListEntry::new("Polygon")
.label("Polygon")
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Polygon)).into()),
MenuListEntry::new("Star")
.label("Star")
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()),
MenuListEntry::new("Circle")
.label("Circle")
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Circle)).into()),
MenuListEntry::new("Arc")
.label("Arc")
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()),
MenuListEntry::new("Polygon").label("Polygon").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Polygon),
}
.into()
}),
MenuListEntry::new("Star").label("Star").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Star),
}
.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()
}
fn create_arc_type_widget(arc_type: ArcType) -> WidgetHolder {
let entries = vec![
RadioEntryData::new("Open")
.label("Open")
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Open)).into()),
RadioEntryData::new("Closed")
.label("Closed")
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Closed)).into()),
RadioEntryData::new("Pie")
.label("Pie")
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::PieSlice)).into()),
RadioEntryData::new("Open").label("Open").on_update(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ArcType(ArcType::Open),
}
.into()
}),
RadioEntryData::new("Closed").label("Closed").on_update(move |_| {
ShapeToolMessage::UpdateOptions {
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()
}
@ -142,7 +168,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
.label("Weight")
.min(0.)
.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()
}
@ -169,9 +200,26 @@ impl LayoutHolder for ShapeTool {
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
true,
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()),
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
ShapeToolMessage::UpdateOptions {
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());
@ -180,9 +228,26 @@ impl LayoutHolder for ShapeTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|_| {
ShapeToolMessage::UpdateOptions {
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(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 {
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);
return;
};
match action {
match options {
ShapeOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
@ -285,7 +351,7 @@ impl ToolMetadata for ShapeTool {
impl ToolTransition for ShapeTool {
fn event_to_message_map(&self) -> 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()),
working_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()),
..Default::default()
@ -402,7 +468,7 @@ impl Fsm for ShapeToolFsmState {
let ToolMessage::Shape(event) = event else { return self };
match (self, event) {
(_, ShapeToolMessage::Overlays(mut overlay_context)) => {
(_, ShapeToolMessage::Overlays { context: mut overlay_context }) => {
let mouse_position = tool_data
.data
.snap_manager
@ -484,11 +550,15 @@ impl Fsm for ShapeToolFsmState {
self
}
(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
}
(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
}
(
@ -542,7 +612,9 @@ impl Fsm for ShapeToolFsmState {
return self;
};
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(n + 1)));
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::Vertices(n + 1),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
@ -571,7 +643,9 @@ impl Fsm for ShapeToolFsmState {
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 {
input_connector: InputConnector::node(node_id, 1),
@ -603,7 +677,9 @@ impl Fsm for ShapeToolFsmState {
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
// 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;
}
@ -630,7 +706,9 @@ impl Fsm for ShapeToolFsmState {
let cursor = tool_data.transform_cage_mouse_icon(input);
tool_data.cursor = 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) {
@ -710,7 +788,7 @@ impl Fsm for ShapeToolFsmState {
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 {
return ShapeToolFsmState::Ready(shape);
};
@ -726,33 +804,33 @@ impl Fsm for ShapeToolFsmState {
}
// 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);
self
}
(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove(modifier)) => {
(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove { modifier }) => {
let Some(layer) = tool_data.line_data.editing_layer else {
return ShapeToolFsmState::Ready(tool_data.current_shape);
};
Line::update_shape(document, input, layer, tool_data, modifier, responses);
// 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);
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);
responses.add(OverlaysMessage::Draw);
ShapeToolFsmState::ModifyingGizmo
}
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => {
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove { modifier }) => {
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(
document,
responses,
@ -771,7 +849,7 @@ impl Fsm for ShapeToolFsmState {
responses.add(OverlaysMessage::Draw);
ShapeToolFsmState::ResizingBounds
}
(ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove(modifier)) => {
(ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove { modifier }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
rotate_bounds(
document,
@ -787,7 +865,7 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::RotatingBounds
}
(ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove(_)) => {
(ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove { .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
skew_bounds(
document,
@ -803,7 +881,7 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::SkewingBounds { skew }
}
(_, ShapeToolMessage::PointerMove(_)) => {
(_, ShapeToolMessage::PointerMove { .. }) => {
let dragging_bounds = tool_data
.bounding_box_manager
.as_mut()
@ -825,7 +903,7 @@ impl Fsm for ShapeToolFsmState {
responses.add(OverlaysMessage::Draw);
self
}
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport(_)) => {
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
@ -836,7 +914,7 @@ impl Fsm for ShapeToolFsmState {
self
}
(ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport(..)) => self,
(ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport { .. }) => self,
(_, ShapeToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses);
@ -891,21 +969,22 @@ impl Fsm for ShapeToolFsmState {
ShapeToolFsmState::Ready(tool_data.current_shape)
}
(_, ShapeToolMessage::WorkingColorChanged) => {
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors(
Some(global_tool_data.primary_color),
Some(global_tool_data.secondary_color),
)));
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
});
self
}
(_, ShapeToolMessage::SetShape(shape)) => {
(_, ShapeToolMessage::SetShape { shape }) => {
responses.add(DocumentMessage::AbortTransaction);
tool_data.data.cleanup(responses);
tool_data.current_shape = shape;
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape)));
responses.add(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(shape),
});
ShapeToolFsmState::Ready(shape)
}
(_, ShapeToolMessage::HideShapeTypeWidget(hide)) => {
(_, ShapeToolMessage::HideShapeTypeWidget { hide }) => {
tool_data.hide_shape_option_widget = hide;
responses.add(ToolMessage::RefreshToolOptions);
self

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::{Vector, VectorModificationType};
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
const SLOW_KEY: Key = Key::Shift;
@ -69,6 +69,7 @@ pub struct TransformLayerMessageHandler {
was_grabbing: bool,
}
#[message_handler_data]
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, context: TransformLayerMessageContext) {
let TransformLayerMessageContext {
@ -172,7 +173,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
match message {
// Overlays
TransformLayerMessage::Overlays(mut overlay_context) => {
TransformLayerMessage::Overlays { context: mut overlay_context } => {
if !overlay_context.visibility_settings.transform_measurement() {
return;
}
@ -304,7 +305,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
if final_transform {
self.was_grabbing = false;
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
responses.add(OverlaysMessage::RemoveProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
}
}
TransformLayerMessage::BeginTransformOperation { operation } => {
@ -343,7 +346,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
_ => 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
responses.add(TransformLayerMessage::PointerMove {
slow_key: SLOW_KEY,
@ -428,7 +433,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
if chain_operation {
responses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false });
} 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::PointerMove {
@ -465,7 +472,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
}
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 => {
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::tool_messages::*;
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::macros::action_keys;
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
@ -141,7 +141,7 @@ impl DocumentToolData {
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 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 {
responses.add(BroadcastMessage::SubscribeEvent {
on: event,
@ -168,32 +168,32 @@ pub trait ToolTransition {
};
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.tool_abort, BroadcastEvent::ToolAbort);
subscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged);
subscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged);
subscribe_message(event_to_tool_map.canvas_transformed, EventMessage::CanvasTransformed);
subscribe_message(event_to_tool_map.tool_abort, EventMessage::ToolAbort);
subscribe_message(event_to_tool_map.selection_changed, EventMessage::SelectionChanged);
subscribe_message(event_to_tool_map.working_color_changed, EventMessage::WorkingColorChanged);
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>) {
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 {
responses.add(BroadcastMessage::UnsubscribeEvent {
on: event,
message: Box::new(mapping.into()),
send: Box::new(mapping.into()),
});
}
};
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.tool_abort, BroadcastEvent::ToolAbort);
unsubscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged);
unsubscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged);
unsubscribe_message(event_to_tool_map.canvas_transformed, EventMessage::CanvasTransformed);
unsubscribe_message(event_to_tool_map.tool_abort, EventMessage::ToolAbort);
unsubscribe_message(event_to_tool_map.selection_changed, EventMessage::SelectionChanged);
unsubscribe_message(event_to_tool_map.working_color_changed, EventMessage::WorkingColorChanged);
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 });
Ok(DeferMessage::SetGraphSubmissionIndex(execution_id).into())
Ok(DeferMessage::SetGraphSubmissionIndex { execution_id }.into())
}
/// Evaluates a node graph, computing the entire graph
@ -288,7 +288,10 @@ impl NodeGraphExecutor {
} else {
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.
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)]
pub struct DebugMessageTree {
name: String,
fields: Option<Vec<String>>,
variants: Option<Vec<DebugMessageTree>>,
message_handler: Option<MessageData>,
message_handler_data: Option<MessageData>,
@ -36,6 +37,7 @@ impl DebugMessageTree {
pub fn new(name: &str) -> DebugMessageTree {
DebugMessageTree {
name: name.to_string(),
fields: None,
variants: None,
message_handler: 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) {
self.path = path;
}
@ -67,6 +73,10 @@ impl DebugMessageTree {
&self.name
}
pub fn fields(&self) -> Option<&Vec<String>> {
self.fields.as_ref()
}
pub fn path(&self) -> &'static str {
self.path
}

View file

@ -277,7 +277,7 @@ impl EditorHandle {
// Used by auto-panning, but this could possibly be refactored in the future, see:
// <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<_>>();
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! {
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn field_types() -> Vec<(String, usize)> {
impl #impl_generics ExtractField for #struct_name #ty_generics #where_clause {
fn field_types() -> Vec<(String, usize)> {
vec![
#((String::from(#field_str), #field_line)),*
]
}
pub fn print_field_types() {
fn print_field_types() {
for (field, line) in Self::field_types() {
println!("{} at line {}", field, line);
}
}
pub fn path() -> &'static str {
fn path() -> &'static str {
file!()
}
}

View file

@ -1,3 +1,4 @@
use crate::helpers::clean_rust_type_syntax;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{Data, DeriveInput, Fields, Type, parse2};
@ -11,7 +12,10 @@ 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")),
};
let build_message_tree = data.variants.iter().map(|variant| {
let build_message_tree: Result<Vec<_>, syn::Error> = data
.variants
.iter()
.map(|variant| {
let variant_type = &variant.ident;
let has_child = variant
@ -19,43 +23,78 @@ pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result<TokenStream
.iter()
.any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child"));
match &variant.fields {
Fields::Unit => Ok(quote! {
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
}),
Fields::Unnamed(fields) => {
if has_child {
if let Fields::Unnamed(fields) = &variant.fields {
let field_type = &fields.unnamed.first().unwrap().ty;
quote! {
Ok(quote! {
{
let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
let field_name = stringify!(#field_type);
const message_string: &str = "Message";
if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] {
const MESSAGE_SUFFIX: &str = "Message";
if MESSAGE_SUFFIX == &field_name[field_name.len().saturating_sub(MESSAGE_SUFFIX.len())..] {
// 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 {
quote! {
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
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 {
quote! {
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
Fields::Named(fields) => {
let names = fields.named.iter().map(|f| f.ident.as_ref().unwrap());
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);
}
})
}
}
});
})
.collect();
let build_message_tree = build_message_tree?;
let res = quote! {
impl HierarchicalTree for #input_type {
fn build_message_tree() -> DebugMessageTree {
let mut message_tree = DebugMessageTree::new(stringify!(#input_type));
#(#build_message_tree)*
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);
}
let message_handler_data_str = #input_type::message_handler_data_str();
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! {
#input_item
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())
}
pub fn message_handler_str() -> MessageData {
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
quote::quote! {
#[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))
}
}

View file

@ -57,14 +57,11 @@ function buildHtmlList(nodes, currentIndex, currentLevel) {
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 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;
if (fieldPieces && fieldPieces.length === 3) {
escapedText = [escapeHtml(fieldPieces[1].trim()), escapeHtml(fieldPieces[2].trim())];
@ -72,16 +69,25 @@ function buildHtmlList(nodes, currentIndex, currentLevel) {
escapedText = [escapeHtml(node.text)];
}
if (hasChildren) {
html += `<li><span class="tree-node"><span class="${partOfMessage}${messageParent}">${escapedText}</span>${linkHtml}${violatesNamingConvention}</span>`;
let role = "message";
if (node.link) role = "subsystem";
else if (hasDeeperChildren) role = "submessage";
else if (escapedText.length === 2) role = "field";
const partOfMessageFromNamingConvention = ["Message", "MessageHandler", "MessageContext"].some((suffix) => node.text.replace(/(.*)<.*>/g, "$1").endsWith(suffix));
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);
html += `<div class="nested">${childResult.html}</div></li>\n`;
i = childResult.nextIndex;
} else if (escapedText.length === 2) {
html += `<li><span class="tree-leaf field">${escapedText[0]}</span><span>: ${escapedText[1]}</span>${linkHtml}</li>\n`;
} else if (role === "field") {
html += `<li><span class="tree-leaf field">${escapedText[0]}</span>: <span>${escapedText[1]}</span>${linkHtml}</li>\n`;
i++;
} 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++;
}
}

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>\
');
position: absolute;
margin: auto;
top: 0;
bottom: 0;
left: 0;
margin: calc((1.5em - 10px) / 2) auto;
width: 10px;
height: 10px;
}
@ -41,6 +39,29 @@
&.expanded::before {
transform: rotate(90deg);
}
}
.tree-leaf {
margin-left: calc(10px + 8px);
}
.nested {
display: none;
}
.active {
display: block;
}
.warn {
display: inline;
margin-left: 12px;
color: var(--color-flamingo);
font-family: Arial, sans-serif;
font-size: 12px;
text-decoration: none;
font-style: italic;
}
a {
margin-left: 12px;
@ -55,38 +76,6 @@
position: absolute;
}
}
}
.tree-leaf {
margin-left: calc(10px + 8px);
&.field {
padding-left: 4px;
color: var(--color-storm);
}
&:not(.field) {
padding: 0 4px;
background: var(--color-fog);
}
}
.nested {
display: none;
}
.active {
display: block;
}
.warn {
margin-left: 12px;
color: var(--color-flamingo);
font-family: Arial, sans-serif;
font-size: 12px;
text-decoration: none;
font-style: italic;
}
}
.subsystem,
@ -94,13 +83,27 @@
font-family: monospace;
line-height: 1.5;
padding: 0 4px;
}
.subsystem {
&.subsystem {
color: #ffffff;
background: var(--color-storm);
background: var(--color-crimson);
}
&.submessage {
background: var(--color-mustard);
}
}
.submessage {
background: var(--color-lilac);
.message {
padding: 0 4px;
background: var(--color-fog);
}
.field {
padding-left: 4px;
color: #8887c0;
+ span {
color: #457297;
}
}