|
use super::tool_prelude::*; |
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; |
|
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; |
|
use graphene_std::vector::style::Fill; |
|
|
|
#[derive(Default)] |
|
pub struct FillTool { |
|
fsm_state: FillToolFsmState, |
|
} |
|
|
|
#[impl_message(Message, ToolMessage, Fill)] |
|
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] |
|
pub enum FillToolMessage { |
|
|
|
Abort, |
|
WorkingColorChanged, |
|
Overlays(OverlayContext), |
|
|
|
|
|
PointerMove, |
|
PointerUp, |
|
FillPrimaryColor, |
|
FillSecondaryColor, |
|
} |
|
|
|
impl ToolMetadata for FillTool { |
|
fn icon_name(&self) -> String { |
|
"GeneralFillTool".into() |
|
} |
|
fn tooltip(&self) -> String { |
|
"Fill Tool".into() |
|
} |
|
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { |
|
ToolType::Fill |
|
} |
|
} |
|
|
|
impl LayoutHolder for FillTool { |
|
fn layout(&self) -> Layout { |
|
Layout::WidgetLayout(WidgetLayout::default()) |
|
} |
|
} |
|
|
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool { |
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { |
|
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true); |
|
} |
|
fn actions(&self) -> ActionList { |
|
match self.fsm_state { |
|
FillToolFsmState::Ready => actions!(FillToolMessageDiscriminant; |
|
FillPrimaryColor, |
|
FillSecondaryColor, |
|
PointerMove, |
|
), |
|
FillToolFsmState::Filling => actions!(FillToolMessageDiscriminant; |
|
PointerMove, |
|
PointerUp, |
|
Abort, |
|
), |
|
} |
|
} |
|
} |
|
|
|
impl ToolTransition for FillTool { |
|
fn event_to_message_map(&self) -> EventToMessageMap { |
|
EventToMessageMap { |
|
tool_abort: Some(FillToolMessage::Abort.into()), |
|
working_color_changed: Some(FillToolMessage::WorkingColorChanged.into()), |
|
overlay_provider: Some(|overlay_context| FillToolMessage::Overlays(overlay_context).into()), |
|
..Default::default() |
|
} |
|
} |
|
} |
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] |
|
enum FillToolFsmState { |
|
#[default] |
|
Ready, |
|
|
|
Filling, |
|
} |
|
|
|
impl Fsm for FillToolFsmState { |
|
type ToolData = (); |
|
type ToolOptions = (); |
|
|
|
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, handler_data: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self { |
|
let ToolActionHandlerData { |
|
document, global_tool_data, input, .. |
|
} = handler_data; |
|
|
|
let ToolMessage::Fill(event) = event else { return self }; |
|
match (self, event) { |
|
(_, FillToolMessage::Overlays(mut overlay_context)) => { |
|
|
|
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 }; |
|
|
|
|
|
if let Some(layer) = document.click(input) { |
|
overlay_context.fill_path_pattern(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer), &preview_color); |
|
} |
|
|
|
self |
|
} |
|
(_, FillToolMessage::PointerMove | FillToolMessage::WorkingColorChanged) => { |
|
|
|
responses.add(OverlaysMessage::Draw); |
|
self |
|
} |
|
(FillToolFsmState::Ready, color_event) => { |
|
let Some(layer_identifier) = document.click(input) else { |
|
return self; |
|
}; |
|
|
|
if NodeGraphLayer::is_raster_layer(layer_identifier, &mut document.network_interface) { |
|
return self; |
|
} |
|
let fill = match color_event { |
|
FillToolMessage::FillPrimaryColor => Fill::Solid(global_tool_data.primary_color.to_gamma_srgb()), |
|
FillToolMessage::FillSecondaryColor => Fill::Solid(global_tool_data.secondary_color.to_gamma_srgb()), |
|
_ => return self, |
|
}; |
|
|
|
responses.add(DocumentMessage::AddTransaction); |
|
responses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill }); |
|
|
|
FillToolFsmState::Filling |
|
} |
|
(FillToolFsmState::Filling, FillToolMessage::PointerUp) => FillToolFsmState::Ready, |
|
(FillToolFsmState::Filling, FillToolMessage::Abort) => { |
|
responses.add(DocumentMessage::AbortTransaction); |
|
|
|
FillToolFsmState::Ready |
|
} |
|
_ => self, |
|
} |
|
} |
|
|
|
fn update_hints(&self, responses: &mut VecDeque<Message>) { |
|
let hint_data = match self { |
|
FillToolFsmState::Ready => HintData(vec![HintGroup(vec![ |
|
HintInfo::mouse(MouseMotion::Lmb, "Fill with Primary"), |
|
HintInfo::keys([Key::Shift], "Fill with Secondary").prepend_plus(), |
|
])]), |
|
FillToolFsmState::Filling => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), |
|
}; |
|
|
|
responses.add(FrontendMessage::UpdateInputHints { hint_data }); |
|
} |
|
|
|
fn update_cursor(&self, responses: &mut VecDeque<Message>) { |
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); |
|
} |
|
} |
|
|
|
#[cfg(test)] |
|
mod test_fill { |
|
pub use crate::test_utils::test_prelude::*; |
|
use graphene_std::vector::fill; |
|
use graphene_std::vector::style::Fill; |
|
|
|
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<Fill> { |
|
let instrumented = match editor.eval_graph().await { |
|
Ok(instrumented) => instrumented, |
|
Err(e) => panic!("Failed to evaluate graph: {e}"), |
|
}; |
|
|
|
instrumented.grab_all_input::<fill::FillInput<Fill>>(&editor.runtime).collect() |
|
} |
|
|
|
#[tokio::test] |
|
async fn ignore_artboard() { |
|
let mut editor = EditorTestUtils::create(); |
|
editor.new_document().await; |
|
editor.drag_tool(ToolType::Artboard, 0., 0., 100., 100., ModifierKeys::empty()).await; |
|
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await; |
|
assert!(get_fills(&mut editor,).await.is_empty()); |
|
} |
|
|
|
#[tokio::test] |
|
async fn ignore_raster() { |
|
let mut editor = EditorTestUtils::create(); |
|
editor.new_document().await; |
|
editor.create_raster_image(Image::new(100, 100, Color::WHITE), Some((0., 0.))).await; |
|
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await; |
|
assert!(get_fills(&mut editor,).await.is_empty()); |
|
} |
|
|
|
#[tokio::test] |
|
async fn primary() { |
|
let mut editor = EditorTestUtils::create(); |
|
editor.new_document().await; |
|
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await; |
|
editor.select_primary_color(Color::GREEN).await; |
|
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await; |
|
let fills = get_fills(&mut editor).await; |
|
assert_eq!(fills.len(), 1); |
|
assert_eq!(fills[0].as_solid().unwrap().to_rgba8_srgb(), Color::GREEN.to_rgba8_srgb()); |
|
} |
|
|
|
#[tokio::test] |
|
async fn secondary() { |
|
let mut editor = EditorTestUtils::create(); |
|
editor.new_document().await; |
|
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await; |
|
editor.select_secondary_color(Color::YELLOW).await; |
|
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::SHIFT).await; |
|
let fills = get_fills(&mut editor).await; |
|
assert_eq!(fills.len(), 1); |
|
assert_eq!(fills[0].as_solid().unwrap().to_rgba8_srgb(), Color::YELLOW.to_rgba8_srgb()); |
|
} |
|
} |
|
|