use crate::message_prelude::*; use graphene::color::Color; use graphene::document::Document as GrapheneDocument; use graphene::layers::style::{self, Fill, RenderData, ViewMode}; use graphene::layers::text_layer::FontCache; use graphene::DocumentResponse; use graphene::Operation as DocumentOperation; use glam::DAffine2; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ArtboardMessageHandler { pub artboards_graphene_document: GrapheneDocument, pub artboard_ids: Vec, } impl ArtboardMessageHandler { pub fn is_infinite_canvas(&self) -> bool { self.artboard_ids.is_empty() } } impl MessageHandler for ArtboardMessageHandler { #[remain::check] fn process_action(&mut self, message: ArtboardMessage, font_cache: &FontCache, responses: &mut VecDeque) { use ArtboardMessage::*; #[remain::sorted] match message { // Sub-messages #[remain::unsorted] DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(*operation, font_cache) { Ok(Some(document_responses)) => { for response in document_responses { match &response { DocumentResponse::LayerChanged { path } => responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: path.clone() }.into()), DocumentResponse::DeletedLayer { path } => responses.push_back(PropertiesPanelMessage::CheckSelectedWasDeleted { path: path.clone() }.into()), DocumentResponse::DocumentChanged => responses.push_back(ArtboardMessage::RenderArtboards.into()), _ => {} }; responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } } Ok(None) => {} Err(e) => log::error!("Artboard Error: {:?}", e), }, // Messages AddArtboard { id, position, size } => { let artboard_id = id.unwrap_or_else(generate_uuid); self.artboard_ids.push(artboard_id); responses.push_back( ArtboardMessage::DispatchOperation( DocumentOperation::AddRect { path: vec![artboard_id], insert_index: -1, transform: DAffine2::from_scale_angle_translation(size.into(), 0., position.into()).to_cols_array(), style: style::PathStyle::new(None, Fill::solid(Color::WHITE)), } .into(), ) .into(), ); responses.push_back(DocumentMessage::RenderDocument.into()); } DeleteArtboard { artboard } => { self.artboard_ids.retain(|&id| id != artboard); responses.push_back(ArtboardMessage::DispatchOperation(Box::new(DocumentOperation::DeleteLayer { path: vec![artboard] })).into()); responses.push_back(DocumentMessage::RenderDocument.into()); } RenderArtboards => { // Render an infinite canvas if there are no artboards if self.artboard_ids.is_empty() { responses.push_back( FrontendMessage::UpdateDocumentArtboards { svg: r##""##.to_string(), } .into(), ) } else { let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false); responses.push_back( FrontendMessage::UpdateDocumentArtboards { svg: self.artboards_graphene_document.render_root(render_data), } .into(), ); } } ResizeArtboard { artboard, position, size } => { responses.push_back( ArtboardMessage::DispatchOperation(Box::new(DocumentOperation::SetLayerTransform { path: vec![artboard], transform: DAffine2::from_scale_angle_translation(size.into(), 0., position.into()).to_cols_array(), })) .into(), ); responses.push_back(DocumentMessage::RenderDocument.into()); } } } fn actions(&self) -> ActionList { actions!(ArtboardMessageDiscriminant;) } }