use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::Operation; use glam::DAffine2; use serde::{Deserialize, Serialize}; #[derive(Default)] pub struct ImaginateTool { fsm_state: ImaginateToolFsmState, tool_data: ImaginateToolData, } #[remain::sorted] #[impl_message(Message, ToolMessage, Imaginate)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] pub enum ImaginateToolMessage { // Standard messages #[remain::unsorted] Abort, // Tool-specific messages DragStart, DragStop, Resize { center: Key, lock_ratio: Key, }, } impl PropertyHolder for ImaginateTool {} impl<'a> MessageHandler> for ImaginateTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } fn actions(&self) -> ActionList { use ImaginateToolFsmState::*; match self.fsm_state { Ready => actions!(ImaginateToolMessageDiscriminant; DragStart, ), Drawing => actions!(ImaginateToolMessageDiscriminant; DragStop, Abort, Resize, ), } } } impl ToolMetadata for ImaginateTool { fn icon_name(&self) -> String { "RasterImaginateTool".into() } fn tooltip(&self) -> String { "Imaginate Tool".into() } fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Imaginate } } impl ToolTransition for ImaginateTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { document_dirty: None, tool_abort: Some(ImaginateToolMessage::Abort.into()), selection_changed: None, } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum ImaginateToolFsmState { #[default] Ready, Drawing, } #[derive(Clone, Debug, Default)] struct ImaginateToolData { data: Resize, } impl Fsm for ImaginateToolFsmState { type ToolData = ImaginateToolData; type ToolOptions = (); fn transition( self, event: ToolMessage, tool_data: &mut Self::ToolData, ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { use ImaginateToolFsmState::*; use ImaginateToolMessage::*; let mut shape_data = &mut tool_data.data; if let ToolMessage::Imaginate(event) = event { match (self, event) { (Ready, DragStart) => { shape_data.start(responses, document, input, render_data); responses.add(DocumentMessage::StartTransaction); shape_data.path = Some(document.get_path_for_new_layer()); responses.add(DocumentMessage::DeselectAllLayers); use graph_craft::document::*; // Utility function to offset the position of each consecutive node let mut pos = 8; let mut next_pos = || { pos += 8; graph_craft::document::DocumentNodeMetadata::position((pos, 4)) }; // Get the node type for the Transform and Imaginate nodes let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else { warn!("Transform node should be in registry"); return Drawing; }; let imaginate_node_type = &*IMAGINATE_NODE; // Give them a unique ID let [transform_node_id, imaginate_node_id] = [100, 101]; // Create the network based on the Input -> Output passthrough default network let mut network = node_graph::new_image_network(16, imaginate_node_id); // Insert the nodes into the default network network .nodes .insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos())); network.nodes.insert( imaginate_node_id, imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()), ); // Add a layer with a frame to the document responses.add(Operation::AddFrame { path: shape_data.path.clone().unwrap(), insert_index: -1, transform: DAffine2::ZERO.to_cols_array(), network, }); responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id }); Drawing } (state, Resize { center, lock_ratio }) => { let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true); responses.try_add(message); state } (Drawing, DragStop) => { if let Some(layer_path) = &shape_data.path { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() }); } input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); Ready } (Drawing, Abort) => { responses.add(DocumentMessage::AbortTransaction); shape_data.cleanup(responses); Ready } _ => self, } } else { self } } fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { ImaginateToolFsmState::Ready => HintData(vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"), HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])]), ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]), }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); } fn update_cursor(&self, responses: &mut VecDeque) { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }); } }