Fix artboard tool and remove old artboard code

This commit is contained in:
0hypercube 2023-08-21 21:43:51 +01:00 committed by Keavon Chambers
parent 9a39c4a0cc
commit b52f831b21
27 changed files with 196 additions and 553 deletions

View file

@ -81,36 +81,53 @@ impl DocumentMetadata {
self.document_to_viewport * self.transform_from_document(layer)
}
/// Runs an intersection test with all layers and a document space quad
pub fn intersect_quad(&self, viewport_quad: Quad) -> Option<(LayerNodeIdentifier, &Vec<ClickTarget>)> {
/// Runs an intersection test with all layers and a viewport space quad
pub fn intersect_quad(&self, viewport_quad: Quad) -> Option<LayerNodeIdentifier> {
let document_quad = self.document_to_viewport.inverse() * viewport_quad;
self.root()
.decendants(self)
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
.find(|(layer, target)| target.iter().any(|target| target.intersect_rectangle(document_quad, self.transform_from_document(*layer))))
.map(|(layer, _)| layer)
}
/// Find the layer that has been clicked on from a document space location
pub fn click(&self, viewport_location: DVec2) -> Option<(LayerNodeIdentifier, &Vec<ClickTarget>)> {
/// Find all of the layers that were clicked on from a viewport space location
pub fn click_xray<'a>(&'a self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
let point = self.document_to_viewport.inverse().transform_point2(viewport_location);
self.root()
.decendants(self)
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
.find(|(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer))))
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer))))
.map(|(layer, _)| layer)
}
/// Find the layer that has been clicked on from a viewport space location
pub fn click(&self, viewport_location: DVec2) -> Option<LayerNodeIdentifier> {
self.click_xray(viewport_location).next()
}
/// Get the bounding box of the click target of the specified layer in the specified transform space
pub fn bounding_box(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
self.click_targets
.get(&layer)?
.iter()
.filter_map(|click_target| click_target.subpath.bounding_box_with_transform(transform))
.reduce(Quad::combine_bounds)
}
/// Get the bounding box of the click target of the specified layer in document space
pub fn bounding_box_document(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
self.bounding_box_with_transform(layer, self.transform_from_document(layer))
}
/// Get the bounding box of the click target of the specified layer in viewport space
pub fn bounding_box_viewport(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
self.bounding_box_with_transform(layer, self.transform_from_viewport(layer))
}
}
/// Id of a layer node
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct LayerNodeIdentifier(NonZeroU64);
impl Default for LayerNodeIdentifier {
@ -119,6 +136,18 @@ impl Default for LayerNodeIdentifier {
}
}
impl core::fmt::Debug for LayerNodeIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("LayerNodeIdentifier").field(&self.to_node()).finish()
}
}
impl core::fmt::Display for LayerNodeIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Layer(node_id={})", self.to_node()))
}
}
impl LayerNodeIdentifier {
const ROOT: Self = LayerNodeIdentifier::new_unchecked(0);

View file

@ -33,9 +33,6 @@ pub struct DispatcherMessageHandlers {
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Rerender))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Artboard(
ArtboardMessageDiscriminant::RenderArtboards,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::ResendActiveProperties,

View file

@ -1,6 +1,7 @@
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::is_artboard;
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
#[derive(Debug, Default, Clone)]
@ -67,23 +68,19 @@ impl MessageHandler<DialogMessage, (&PortfolioMessageHandler, &PreferencesMessag
}
DialogMessage::RequestExportDialog => {
if let Some(document) = portfolio.active_document() {
let artboard_handler = &document.artboard_message_handler;
let mut index = 0;
let artboards = artboard_handler
.artboard_ids
.iter()
.rev()
.filter_map(|&artboard| artboard_handler.artboards_document.layer(&[artboard]).ok().map(|layer| (artboard, layer)))
.map(|(artboard, layer)| {
let artboards = document
.document_legacy
.metadata
.all_layers()
.filter(|&layer| is_artboard(layer, &document.document_legacy))
.map(|layer| {
(
artboard,
format!(
"Artboard: {}",
layer.name.clone().unwrap_or_else(|| {
index += 1;
format!("Untitled {index}")
})
),
layer,
format!("Artboard: {}", {
index += 1;
format!("Untitled {index}")
}),
)
})
.collect();

View file

@ -2,7 +2,7 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use document_legacy::LayerId;
use document_legacy::document_metadata::LayerNodeIdentifier;
/// A dialog to allow users to customize their file export.
#[derive(Debug, Clone, Default)]
@ -11,7 +11,7 @@ pub struct ExportDialogMessageHandler {
pub scale_factor: f64,
pub bounds: ExportBounds,
pub transparent_background: bool,
pub artboards: HashMap<LayerId, String>,
pub artboards: HashMap<LayerNodeIdentifier, String>,
pub has_selection: bool,
}
@ -86,7 +86,20 @@ impl LayoutHolder for ExportDialogMessageHandler {
.widget_holder(),
];
let artboards = self.artboards.iter().map(|(&val, name)| (ExportBounds::Artboard(val), name.to_string(), false));
let resolution = vec![
TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(self.scale_factor))
.unit("")
.min(0.)
.max((1u64 << std::f64::MANTISSA_DIGITS) as f64)
.disabled(self.file_type == FileType::Svg)
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
.min_width(200)
.widget_holder(),
];
let artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false));
let mut export_area_options = vec![
(ExportBounds::AllArtwork, "All Artwork".to_string(), false),
(ExportBounds::Selection, "Selection".to_string(), !self.has_selection),

View file

@ -26,11 +26,6 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 {
let id = generate_uuid();
responses.add(ArtboardMessage::AddArtboard {
id: Some(id),
position: (0., 0.),
size: (self.dimensions.x as f64, self.dimensions.y as f64),
});
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),

View file

@ -1,3 +1,4 @@
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use serde::{Deserialize, Serialize};
@ -63,5 +64,5 @@ pub enum ExportBounds {
#[default]
AllArtwork,
Selection,
Artboard(LayerId),
Artboard(LayerNodeIdentifier),
}

View file

@ -1,38 +0,0 @@
use crate::messages::prelude::*;
use document_legacy::LayerId;
use document_legacy::Operation as DocumentOperation;
use serde::{Deserialize, Serialize};
#[remain::sorted]
#[impl_message(Message, DocumentMessage, Artboard)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum ArtboardMessage {
// Sub-messages
#[remain::unsorted]
DispatchOperation(Box<DocumentOperation>),
// Messages
AddArtboard {
id: Option<LayerId>,
position: (f64, f64),
size: (f64, f64),
},
ClearArtboards,
DeleteArtboard {
artboard: LayerId,
},
RenderArtboards,
ResizeArtboard {
artboard: LayerId,
position: (f64, f64),
size: (f64, f64),
},
}
impl From<DocumentOperation> for ArtboardMessage {
fn from(operation: DocumentOperation) -> Self {
Self::DispatchOperation(Box::new(operation))
}
}

View file

@ -1,119 +0,0 @@
use crate::application::generate_uuid;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use document_legacy::document::Document as DocumentLegacy;
use document_legacy::layers::style::{self, Fill};
use document_legacy::DocumentResponse;
use document_legacy::LayerId;
use document_legacy::Operation as DocumentOperation;
use graphene_core::raster::color::Color;
use glam::DAffine2;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ArtboardMessageHandler {
pub artboards_document: DocumentLegacy,
pub artboard_ids: Vec<LayerId>,
}
impl MessageHandler<ArtboardMessage, &PersistentData> for ArtboardMessageHandler {
#[remain::check]
fn process_message(&mut self, message: ArtboardMessage, responses: &mut VecDeque<Message>, _persistent_data: &PersistentData) {
use ArtboardMessage::*;
#[remain::sorted]
match message {
// Sub-messages
#[remain::unsorted]
DispatchOperation(operation) => match self.artboards_document.handle_operation(*operation) {
Ok(Some(document_responses)) => {
for response in document_responses {
match &response {
DocumentResponse::LayerChanged { path } => responses.add(PropertiesPanelMessage::CheckSelectedWasUpdated { path: path.clone() }),
DocumentResponse::DeletedLayer { path } => responses.add(PropertiesPanelMessage::CheckSelectedWasDeleted { path: path.clone() }),
DocumentResponse::DocumentChanged => responses.add(ArtboardMessage::RenderArtboards),
_ => {}
};
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
Ok(None) => {}
Err(e) => 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.add(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(),
));
responses.add(DocumentMessage::RenderDocument);
}
ClearArtboards => {
// TODO: Make this remove the artboard layers from the graph (and cleanly reconnect the artwork)
responses.add(DialogMessage::RequestComingSoonDialog { issue: None });
// for &artboard in self.artboard_ids.iter() {
// responses.add_front(ArtboardMessage::DeleteArtboard { artboard });
// }
}
DeleteArtboard { artboard } => {
self.artboard_ids.retain(|&id| id != artboard);
responses.add(ArtboardMessage::DispatchOperation(Box::new(DocumentOperation::DeleteLayer { path: vec![artboard] })));
responses.add(DocumentMessage::RenderDocument);
}
RenderArtboards => {
// Render an infinite canvas if there are no artboards
if self.artboard_ids.is_empty() {
responses.add(FrontendMessage::UpdateDocumentArtboards {
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
})
// TODO: Delete this whole legacy code path when cleaning up/removing the old (non-node based) artboard implementation
// TODO: The below code was used to draw the non-node based artboards, but we still need the above code to draw the infinite canvas until the refactor is complete and all this code can be removed
// } else {
// let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, None);
// responses.add(FrontendMessage::UpdateDocumentArtboards {
// svg: self.artboards_document.render_root(&render_data),
// });
}
}
ResizeArtboard { artboard, position, mut size } => {
if size.0.abs() == 0. {
size.0 = size.0.signum();
}
if size.1.abs() == 0. {
size.1 = size.1.signum();
}
responses.add(ArtboardMessage::DispatchOperation(Box::new(DocumentOperation::SetLayerTransform {
path: vec![artboard],
transform: DAffine2::from_scale_angle_translation(size.into(), 0., position.into()).to_cols_array(),
})));
responses.add(DocumentMessage::RenderDocument);
}
}
}
fn actions(&self) -> ActionList {
actions!(ArtboardMessageDiscriminant;)
}
}
impl ArtboardMessageHandler {
pub fn is_infinite_canvas(&self) -> bool {
self.artboard_ids.is_empty()
}
}

View file

@ -1,7 +0,0 @@
mod artboard_message;
mod artboard_message_handler;
#[doc(inline)]
pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant};
#[doc(inline)]
pub use artboard_message_handler::ArtboardMessageHandler;

View file

@ -23,9 +23,6 @@ pub enum DocumentMessage {
DispatchOperation(Box<DocumentOperation>),
#[remain::unsorted]
#[child]
Artboard(ArtboardMessage),
#[remain::unsorted]
#[child]
Navigation(NavigationMessage),
#[remain::unsorted]
#[child]
@ -51,7 +48,6 @@ pub enum DocumentMessage {
},
BackupDocument {
document: DocumentLegacy,
artboard: Box<ArtboardMessageHandler>,
layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
},
ClearLayerTree,

View file

@ -62,7 +62,6 @@ pub struct DocumentMessageHandler {
navigation_handler: NavigationMessageHandler,
#[serde(skip)]
overlays_message_handler: OverlaysMessageHandler,
pub artboard_message_handler: ArtboardMessageHandler,
properties_panel_message_handler: PropertiesPanelMessageHandler,
#[serde(skip)]
node_graph_handler: NodeGraphMessageHandler,
@ -95,7 +94,6 @@ impl Default for DocumentMessageHandler {
navigation_handler: NavigationMessageHandler::default(),
overlays_message_handler: OverlaysMessageHandler::default(),
artboard_message_handler: ArtboardMessageHandler::default(),
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
node_graph_handler: Default::default(),
}
@ -176,10 +174,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
}
}
#[remain::unsorted]
Artboard(message) => {
self.artboard_message_handler.process_message(message, responses, persistent_data);
}
#[remain::unsorted]
Navigation(message) => {
let document_bounds = self.document_bounds(&render_data);
self.navigation_handler.process_message(
@ -197,7 +191,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
document_name: self.name.as_str(),
artwork_document: &self.document_legacy,
artboard_document: &self.artboard_message_handler.artboards_document,
selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())),
node_graph_message_handler: &self.node_graph_handler,
executor,
@ -275,7 +268,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
BackupDocument { document, artboard, layer_metadata } => self.backup_with_document(document, *artboard, layer_metadata, responses),
BackupDocument { document, layer_metadata } => self.backup_with_document(document, layer_metadata, responses),
ClearLayerTree => {
// Send an empty layer tree
let data_buffer: RawBuffer = Self::default().serialize_root().as_slice().into();
@ -375,13 +368,13 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
bounds,
transparent_background,
} => {
let old_transforms = self.remove_document_transform();
let old_artwork_transform = self.remove_document_transform();
// Calculate the bounding box of the region to be exported
let bounds = match bounds {
ExportBounds::AllArtwork => self.all_layer_bounds(&render_data),
ExportBounds::Selection => self.selected_visible_layers_bounding_box(&render_data),
ExportBounds::Artboard(id) => self.artboard_message_handler.artboards_document.layer(&[id]).ok().and_then(|layer| layer.aabb(&render_data)),
ExportBounds::Artboard(id) => self.document_legacy.metadata.bounding_box_document(id),
}
.unwrap_or_default();
let size = bounds[1] - bounds[0];
@ -389,7 +382,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let document = self.render_document(size, transform, transparent_background, persistent_data, DocumentRenderMode::Root);
self.restore_document_transform(old_transforms);
self.restore_document_transform(old_artwork_transform);
let file_suffix = &format!(".{file_type:?}").to_lowercase();
let name = match file_name.ends_with(FILE_SAVE_SUFFIX) {
@ -681,7 +674,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.add(FrontendMessage::UpdateDocumentArtwork {
svg: self.document_legacy.render_root(&render_data),
});
responses.add(ArtboardMessage::RenderArtboards);
let document_transform_scale = self.navigation_handler.snapped_scale();
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
@ -699,7 +691,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };
let ruler_spacing = ruler_interval * document_transform_scale;
let ruler_origin = self.document_legacy.root.transform.transform_point2(DVec2::ZERO);
let ruler_origin = self.document_legacy.metadata.document_to_viewport.transform_point2(DVec2::ZERO);
responses.add(FrontendMessage::UpdateDocumentScrollbars {
position: scrollbar_position.into(),
@ -1020,7 +1012,7 @@ impl DocumentMessageHandler {
// If the Input Frame node is connected upstream, rasterize the artwork below this layer by calling into JS
let response = if input_frame_connected_to_graph_output {
let old_transforms = self.remove_document_transform();
let old_artwork_transform = self.remove_document_transform();
// Calculate the size of the region to be exported and generate an SVG of the artwork below this layer within that region
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
@ -1028,7 +1020,7 @@ impl DocumentMessageHandler {
// TODO: Test if this would be better to have a transparent background
let svg = self.render_document(size, transform.inverse(), false, persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
self.restore_document_transform(old_transforms);
self.restore_document_transform(old_artwork_transform);
// Once JS asynchronously rasterizes the SVG, it will call the `PortfolioMessage::RenderGraphUsingRasterizedRegionBelowLayer` message with the rasterized image data
FrontendMessage::TriggerRasterizeRegionBelowLayer { document_id, layer_path, svg, size }.into()
@ -1047,25 +1039,18 @@ impl DocumentMessageHandler {
}
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
pub(crate) fn remove_document_transform(&mut self) -> [DAffine2; 2] {
let old_artwork_transform = self.document_legacy.root.transform;
self.document_legacy.root.transform = DAffine2::IDENTITY;
pub(crate) fn remove_document_transform(&mut self) -> DAffine2 {
let old_artwork_transform = self.document_legacy.metadata.document_to_viewport;
self.document_legacy.metadata.document_to_viewport = DAffine2::IDENTITY;
DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root);
let old_artboard_transform = self.artboard_message_handler.artboards_document.root.transform;
self.artboard_message_handler.artboards_document.root.transform = DAffine2::IDENTITY;
DocumentLegacy::mark_children_as_dirty(&mut self.artboard_message_handler.artboards_document.root);
[old_artwork_transform, old_artboard_transform]
old_artwork_transform
}
/// Transform the artwork and artboard back to their original scales
pub(crate) fn restore_document_transform(&mut self, [old_artwork_transform, old_artboard_transform]: [DAffine2; 2]) {
self.document_legacy.root.transform = old_artwork_transform;
pub(crate) fn restore_document_transform(&mut self, old_artwork_transform: DAffine2) {
self.document_legacy.metadata.document_to_viewport = old_artwork_transform;
DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root);
self.artboard_message_handler.artboards_document.root.transform = old_artboard_transform;
DocumentLegacy::mark_children_as_dirty(&mut self.artboard_message_handler.artboards_document.root);
}
pub fn render_document(&mut self, size: DVec2, transform: DAffine2, transparent_background: bool, persistent_data: &PersistentData, render_mode: DocumentRenderMode) -> String {
@ -1079,13 +1064,10 @@ impl DocumentMessageHandler {
DocumentRenderMode::LayerCutout(layer_path, background) => (self.document_legacy.render_layer(layer_path, &render_data).unwrap(), Some(background)),
};
let artboards = match transparent_background {
false => self.artboard_message_handler.artboards_document.render_root(&render_data),
false => "<!--artboards-->",
true => "".into(),
};
let outside_artboards_color = outside.map_or_else(
|| if self.artboard_message_handler.artboard_ids.is_empty() { "ffffff" } else { "222222" }.to_string(),
|col| col.rgba_hex(),
);
let outside_artboards_color = outside.map_or_else(|| if false { "ffffff" } else { "222222" }.to_string(), |col| col.rgba_hex());
let outside_artboards = match transparent_background {
false => format!(r##"<rect x="0" y="0" width="100%" height="100%" fill="#{}" />"##, outside_artboards_color),
true => "".into(),
@ -1123,11 +1105,11 @@ impl DocumentMessageHandler {
}
}
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self {
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Self {
let mut document = Self { name, ..Self::default() };
let starting_root_transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
document.document_legacy.metadata.document_to_viewport = starting_root_transform;
document.artboard_message_handler.artboards_document.root.transform = starting_root_transform;
let transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
document.document_legacy.metadata.document_to_viewport = transform;
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
document
}
@ -1170,10 +1152,6 @@ impl DocumentMessageHandler {
self.document_legacy.combined_viewport_bounding_box(paths, render_data)
}
pub fn artboard_bounding_box_and_transform(&self, path: &[LayerId], render_data: &RenderData) -> Option<([DVec2; 2], DAffine2)> {
self.artboard_message_handler.artboards_document.bounding_box_and_transform(path, render_data).unwrap_or(None)
}
pub fn selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice()))
}
@ -1216,18 +1194,11 @@ impl DocumentMessageHandler {
})
}
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, ignore_artboard: Option<LayerId>, render_data: &'a RenderData) -> impl Iterator<Item = [DVec2; 2]> + 'a {
/// Returns the bounding boxes for all visible layers, optionally excluding any paths.
pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, _ignore_artboard: Option<LayerId>, render_data: &'a RenderData) -> impl Iterator<Item = [DVec2; 2]> + 'a {
self.visible_layers()
.filter(move |path| ignore_document.map_or(true, |ignore_document| !ignore_document.iter().any(|ig| ig.as_slice() == *path)))
.filter_map(|path| self.document_legacy.viewport_bounding_box(path, render_data).ok()?)
.chain(
self.artboard_message_handler
.artboard_ids
.iter()
.filter(move |&&id| Some(id) != ignore_artboard)
.filter_map(|&path| self.artboard_message_handler.artboards_document.viewport_bounding_box(&[path], render_data).ok()?),
)
}
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) {
@ -1348,9 +1319,9 @@ impl DocumentMessageHandler {
}
/// Places a document into the history system
fn backup_with_document(&mut self, document: DocumentLegacy, artboard: ArtboardMessageHandler, layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>, responses: &mut VecDeque<Message>) {
fn backup_with_document(&mut self, document: DocumentLegacy, layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>, responses: &mut VecDeque<Message>) {
self.document_redo_history.clear();
self.document_undo_history.push_back(DocumentSave { document, artboard, layer_metadata });
self.document_undo_history.push_back(DocumentSave { document, layer_metadata });
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
}
@ -1361,14 +1332,13 @@ impl DocumentMessageHandler {
/// Copies the entire document into the history system
pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
self.backup_with_document(self.document_legacy.clone(), self.artboard_message_handler.clone(), self.layer_metadata.clone(), responses);
self.backup_with_document(self.document_legacy.clone(), self.layer_metadata.clone(), responses);
}
/// Push a message backing up the document in its current state
pub fn backup_nonmut(&self, responses: &mut VecDeque<Message>) {
responses.add(DocumentMessage::BackupDocument {
document: self.document_legacy.clone(),
artboard: Box::new(self.artboard_message_handler.clone()),
layer_metadata: self.layer_metadata.clone(),
});
}
@ -1380,20 +1350,16 @@ impl DocumentMessageHandler {
}
/// Replace the document with a new document save, returning the document save.
pub fn replace_document(&mut self, DocumentSave { document, artboard, layer_metadata }: DocumentSave) -> DocumentSave {
pub fn replace_document(&mut self, DocumentSave { document, layer_metadata }: DocumentSave) -> DocumentSave {
// Keeping the root is required if the bounds of the viewport have changed during the operation
let old_root = self.document_legacy.root.transform;
let old_artboard_root = self.artboard_message_handler.artboards_document.root.transform;
let old_root = self.document_legacy.metadata.document_to_viewport;
let document = std::mem::replace(&mut self.document_legacy, document);
let artboard = std::mem::replace(&mut self.artboard_message_handler, artboard);
self.document_legacy.root.transform = old_root;
self.artboard_message_handler.artboards_document.root.transform = old_artboard_root;
self.document_legacy.metadata.document_to_viewport = old_root;
self.document_legacy.root.cache_dirty = true;
self.artboard_message_handler.artboards_document.root.cache_dirty = true;
let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata);
DocumentSave { document, artboard, layer_metadata }
DocumentSave { document, layer_metadata }
}
pub fn undo(&mut self, responses: &mut VecDeque<Message>) -> Result<(), EditorError> {
@ -1403,7 +1369,7 @@ impl DocumentMessageHandler {
let selected_paths: Vec<Vec<LayerId>> = self.selected_layers().map(|path| path.to_vec()).collect();
match self.document_undo_history.pop_back() {
Some(DocumentSave { document, artboard, layer_metadata }) => {
Some(DocumentSave { document, layer_metadata }) => {
// Update the currently displayed layer on the Properties panel if the selection changes after an undo action
// Also appropriately update the Properties panel if an undo action results in a layer being deleted
let prev_selected_paths: Vec<Vec<LayerId>> = layer_metadata.iter().filter_map(|(layer_id, metadata)| metadata.selected.then_some(layer_id.clone())).collect();
@ -1412,7 +1378,7 @@ impl DocumentMessageHandler {
responses.add(BroadcastEvent::SelectionChanged);
}
let document_save = self.replace_document(DocumentSave { document, artboard, layer_metadata });
let document_save = self.replace_document(DocumentSave { document, layer_metadata });
self.document_redo_history.push_back(document_save);
if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
@ -1438,7 +1404,7 @@ impl DocumentMessageHandler {
let selected_paths: Vec<Vec<LayerId>> = self.selected_layers().map(|path| path.to_vec()).collect();
match self.document_redo_history.pop_back() {
Some(DocumentSave { document, artboard, layer_metadata }) => {
Some(DocumentSave { document, layer_metadata }) => {
// Update currently displayed layer on property panel if selection changes after redo action
// Also appropriately update property panel if redo action results in a layer being added
let next_selected_paths: Vec<Vec<LayerId>> = layer_metadata.iter().filter_map(|(layer_id, metadata)| metadata.selected.then_some(layer_id.clone())).collect();
@ -1447,7 +1413,7 @@ impl DocumentMessageHandler {
responses.add(BroadcastEvent::SelectionChanged);
}
let document_save = self.replace_document(DocumentSave { document, artboard, layer_metadata });
let document_save = self.replace_document(DocumentSave { document, layer_metadata });
self.document_undo_history.push_back(document_save);
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
@ -1521,7 +1487,10 @@ impl DocumentMessageHandler {
pub fn layer_panel_entry_from_path(&self, path: &[LayerId], render_data: &RenderData) -> Option<LayerPanelEntry> {
let layer_metadata = self.layer_metadata(path);
let transform = self.document_legacy.generate_transform_across_scope(path, Some(self.document_legacy.root.transform.inverse())).ok()?;
let transform = self
.document_legacy
.generate_transform_across_scope(path, Some(self.document_legacy.metadata.document_to_viewport.inverse()))
.ok()?;
let layer = self.document_legacy.layer(path).ok()?;
Some(LayerPanelEntry::new(layer_metadata, transform, layer, path.to_vec(), render_data))
@ -1545,11 +1514,7 @@ impl DocumentMessageHandler {
/// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable))
pub fn document_bounds(&self, render_data: &RenderData) -> Option<[DVec2; 2]> {
if self.artboard_message_handler.is_infinite_canvas() {
self.all_layer_bounds(render_data)
} else {
self.artboard_message_handler.artboards_document.viewport_bounding_box(&[], render_data).ok().flatten()
}
self.all_layer_bounds(render_data)
}
/// Calculate the path that new layers should be inserted to.

View file

@ -1,7 +1,6 @@
mod document_message;
mod document_message_handler;
pub mod artboard;
pub mod navigation;
pub mod node_graph;
pub mod overlays;

View file

@ -378,37 +378,42 @@ impl<'a> ModifyInputsContext<'a> {
}
fn delete_layer(&mut self, id: NodeId) {
if !self.network.nodes.contains_key(&id) {
let Some(node) = self.network.nodes.get(&id) else {
warn!("Deleting layer node that does not exist");
return;
}
};
let mut new_input = None;
let post_node = self.outwards_links.get(&id).and_then(|links| links.first().copied());
let mut delete_nodes = vec![id];
let mut is_artboard = false;
for (node, id) in self.network.primary_flow_from_opt(Some(id)) {
delete_nodes.push(id);
if node.name == "Artboard" {
new_input = Some(node.inputs[0].clone());
is_artboard = true;
break;
LayerNodeIdentifier::new(id, self.network).delete(self.document_metadata);
let new_input = node.inputs[7].clone();
for post_node in self.outwards_links.get(&id).unwrap_or(&Vec::new()) {
let Some(node) = self.network.nodes.get_mut(post_node) else {
continue;
};
for input in &mut node.inputs {
if let NodeInput::Node { node_id, .. } = input {
if *node_id == id {
*input = new_input.clone();
}
}
}
}
if !is_artboard {
LayerNodeIdentifier::new(id, self.network).delete(self.document_metadata);
let mut delete_nodes = vec![id];
for (_node, id) in self.network.primary_flow_from_opt(Some(id)) {
if self.outwards_links.get(&id).is_some_and(|outwards| outwards.len() == 1) {
delete_nodes.push(id);
}
}
self.responses.add(DocumentMessage::DocumentStructureChanged);
for node_id in delete_nodes {
self.network.nodes.remove(&node_id);
}
if let (Some(new_input), Some(post_node)) = (new_input, post_node) {
if let Some(node) = self.network.nodes.get_mut(&post_node) {
node.inputs[0] = new_input;
}
}
self.responses.add(DocumentMessage::DocumentStructureChanged);
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
}

View file

@ -1,6 +1,5 @@
use super::utility_types::TransformOp;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::prelude::*;
use document_legacy::layers::style::{Fill, Stroke};
@ -24,7 +23,7 @@ pub enum PropertiesPanelMessage {
ModifyStroke { stroke: Stroke },
ModifyTransform { value: f64, transform_op: TransformOp },
ResendActiveProperties,
SetActiveLayers { paths: Vec<Vec<LayerId>>, document: TargetDocument },
SetActiveLayers { paths: Vec<Vec<LayerId>> },
SetPivot { new_position: PivotPosition },
UpdateSelectedDocumentProperties,
}

View file

@ -1,8 +1,7 @@
use super::utility_functions::{register_artboard_layer_properties, register_artwork_layer_properties, register_document_graph_properties};
use super::utility_functions::{register_artwork_layer_properties, register_document_graph_properties};
use super::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::properties_panel::utility_functions::apply_transform_operation;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
@ -14,7 +13,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PropertiesPanelMessageHandler {
active_selection: Option<(Vec<LayerId>, TargetDocument)>,
active_selection: Option<Vec<LayerId>>,
}
impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'a>)> for PropertiesPanelMessageHandler {
@ -25,28 +24,23 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let PropertiesPanelMessageHandlerData {
document_name,
artwork_document,
artboard_document,
selected_layers,
node_graph_message_handler,
executor,
} = data;
let get_document = |document_selector: TargetDocument| match document_selector {
TargetDocument::Artboard => artboard_document,
TargetDocument::Artwork => artwork_document,
};
let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, None);
match message {
SetActiveLayers { paths, document } => {
SetActiveLayers { paths } => {
if paths.len() != 1 {
// TODO: Allow for multiple selected layers
responses.add(PropertiesPanelMessage::ClearSelection);
responses.add(NodeGraphMessage::CloseNodeGraph);
} else {
let path = paths.into_iter().next().unwrap();
if Some((path.clone(), document)) != self.active_selection {
if Some(&path) != self.active_selection.as_ref() {
// Update the layer visibility
if get_document(document)
if artwork_document
.layer(&path)
.ok()
.filter(|layer| LayerDataTypeDiscriminant::from(&layer.data) == LayerDataTypeDiscriminant::Layer)
@ -57,7 +51,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
responses.add(NodeGraphMessage::CloseNodeGraph);
}
self.active_selection = Some((path, document));
self.active_selection = Some(path);
responses.add(PropertiesPanelMessage::ResendActiveProperties);
}
}
@ -85,31 +79,31 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()),
}),
ModifyTransform { value, transform_op } => {
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
let layer = get_document(*target_document).layer(path).unwrap();
let path = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
let layer = artwork_document.layer(path).unwrap();
let transform = apply_transform_operation(layer, transform_op, value, &render_data);
self.create_document_operation(Operation::SetLayerTransform { path: path.clone(), transform }, true, responses);
}
ModifyName { name } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let path = self.active_selection.clone().expect("Received update for properties panel with no active layer");
self.create_document_operation(Operation::SetLayerName { path, name }, true, responses);
}
ModifyPreserveAspect { preserve_aspect } => {
let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let layer_path = self.active_selection.clone().expect("Received update for properties panel with no active layer");
self.create_document_operation(Operation::SetLayerPreserveAspect { layer_path, preserve_aspect }, true, responses);
}
ModifyFill { fill } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let path = self.active_selection.clone().expect("Received update for properties panel with no active layer");
self.create_document_operation(Operation::SetLayerFill { path, fill }, true, responses);
}
ModifyStroke { stroke } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let path = self.active_selection.clone().expect("Received update for properties panel with no active layer");
self.create_document_operation(Operation::SetLayerStroke { path, stroke }, true, responses);
}
SetPivot { new_position } => {
let (layer, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let layer = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let position: Option<glam::DVec2> = new_position.into();
let pivot = position.unwrap();
@ -136,13 +130,9 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
}
}
ResendActiveProperties => {
if let Some((path, target_document)) = self.active_selection.clone() {
let document = get_document(target_document);
let layer = document.layer(&path).unwrap();
match target_document {
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, persistent_data),
TargetDocument::Artwork => register_artwork_layer_properties(document, path, layer, responses, persistent_data, node_graph_message_handler, executor),
}
if let Some(path) = self.active_selection.clone() {
let layer = artwork_document.layer(&path).unwrap();
register_artwork_layer_properties(artwork_document, path, layer, responses, persistent_data, node_graph_message_handler, executor);
} else {
let context = crate::messages::portfolio::document::node_graph::NodePropertiesContext {
persistent_data,
@ -158,7 +148,6 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
}
UpdateSelectedDocumentProperties => responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: selected_layers.map(|path| path.to_vec()).collect(),
document: TargetDocument::Artwork,
}),
}
}
@ -170,29 +159,18 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
impl PropertiesPanelMessageHandler {
fn matches_selected(&self, path: &[LayerId]) -> bool {
let last_active_path_id = self.active_selection.as_ref().and_then(|(v, _)| v.last().copied());
let last_active_path_id = self.active_selection.as_ref().and_then(|v| v.last().copied());
let last_modified = path.last().copied();
matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last)
}
fn create_document_operation(&self, operation: Operation, commit_history: bool, responses: &mut VecDeque<Message>) {
let (_, target_document) = self.active_selection.as_ref().unwrap();
match *target_document {
TargetDocument::Artboard => {
// Commit history is not respected as the artboard document is not saved in the history system.
// Dispatch the relevant operation to the artboard document
responses.add(ArtboardMessage::DispatchOperation(Box::new(operation)))
}
TargetDocument::Artwork => {
// Commit to history before the modification
if commit_history {
responses.add(DocumentMessage::StartTransaction);
}
// Dispatch the relevant operation to the main document
responses.add(DocumentMessage::DispatchOperation(Box::new(operation)));
}
// Commit to history before the modification
if commit_history {
responses.add(DocumentMessage::StartTransaction);
}
// Dispatch the relevant operation to the main document
responses.add(DocumentMessage::DispatchOperation(Box::new(operation)));
}
}

View file

@ -64,139 +64,6 @@ pub fn apply_transform_operation(layer: &Layer, transform_op: TransformOp, value
((pivot * delta * pivot.inverse()) * layer.transform).to_cols_array()
}
pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, persistent_data: &PersistentData) {
let options_bar = vec![LayoutGroup::Row {
widgets: vec![
IconLabel::new("NodeArtboard").tooltip("Artboard").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextInput::new(layer.name.clone().unwrap_or_else(|| "Untitled Artboard".to_string()))
.on_update(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into())
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
PopoverButton::new("Additional Options", "Coming soon").widget_holder(),
],
}];
let properties_body = {
let LayerDataType::Shape(shape) = &layer.data else { panic!("Artboards can only be shapes") };
let Fill::Solid(color) = shape.style.fill() else { panic!("Artboard must have a solid fill") };
let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::default(), None);
let pivot = layer.transform.transform_vector2(layer.layerspace_pivot(&render_data));
vec![LayoutGroup::Section {
name: "Artboard".into(),
layout: vec![
LayoutGroup::Row {
widgets: vec![
TextLabel::new("Location").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px,
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area.
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(layer.transform.x() + pivot.x))
.label("X")
.unit(" px")
.min(1.)
.max(std::i32::MAX as f64)
.on_update(move |number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value.unwrap() - pivot.x,
transform_op: TransformOp::X,
}
.into()
})
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(layer.transform.y() + pivot.y))
.label("Y")
.unit(" px")
.min(1.)
.max(std::i32::MAX as f64)
.on_update(move |number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value.unwrap() - pivot.y,
transform_op: TransformOp::Y,
}
.into()
})
.widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
TextLabel::new("Dimensions").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(layer.preserve_aspect)
.icon("Link")
.tooltip("Preserve Aspect Ratio")
.on_update(|input: &CheckboxInput| PropertiesPanelMessage::ModifyPreserveAspect { preserve_aspect: input.checked }.into())
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(layer.bounding_transform(&render_data).scale_x()))
.label("W")
.unit(" px")
.is_integer(true)
.min(1.)
.max(std::i32::MAX as f64)
.on_update(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value.unwrap(),
transform_op: TransformOp::Width,
}
.into()
})
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(layer.bounding_transform(&render_data).scale_y()))
.label("H")
.unit(" px")
.is_integer(true)
.min(1.)
.max(std::i32::MAX as f64)
.on_update(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value.unwrap(),
transform_op: TransformOp::Height,
}
.into()
})
.widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
TextLabel::new("Background").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px,
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area.
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
Separator::new(SeparatorType::Unrelated).widget_holder(),
ColorInput::new(Some(*color))
.on_update(|text_input: &ColorInput| {
let fill = if let Some(value) = text_input.value { value } else { Color::TRANSPARENT };
PropertiesPanelMessage::ModifyFill { fill: Fill::Solid(fill) }.into()
})
.widget_holder(),
],
},
],
}]
};
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)),
layout_target: LayoutTarget::PropertiesOptions,
});
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(properties_body)),
layout_target: LayoutTarget::PropertiesSections,
});
}
pub fn register_artwork_layer_properties(
document: &Document,
layer_path: Vec<document_legacy::LayerId>,

View file

@ -8,7 +8,6 @@ use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::Nod
pub struct PropertiesPanelMessageHandlerData<'a> {
pub document_name: &'a str,
pub artwork_document: &'a DocumentLegacy,
pub artboard_document: &'a DocumentLegacy,
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
pub executor: &'a mut NodeGraphExecutor,

View file

@ -1,6 +1,4 @@
pub use super::layer_panel::{LayerMetadata, LayerPanelEntry};
use crate::messages::prelude::ArtboardMessageHandler;
use document_legacy::document::Document as DocumentLegacy;
use document_legacy::LayerId;
use graphene_core::raster::color::Color;
@ -12,7 +10,6 @@ use std::fmt;
#[derive(Debug, Clone)]
pub struct DocumentSave {
pub document: DocumentLegacy,
pub artboard: ArtboardMessageHandler,
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
}
@ -36,12 +33,6 @@ pub enum AlignAggregate {
Average,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TargetDocument {
Artboard,
Artwork,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum DocumentMode {
DesignMode,

View file

@ -249,7 +249,7 @@ impl LayoutHolder for MenuBarMessageHandler {
no_active_document,
MenuBarEntryChildren(vec![vec![MenuBarEntry {
label: "Clear Artboards".into(),
action: MenuBarEntry::create_action(|_| ArtboardMessage::ClearArtboards.into()),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
disabled: no_active_document,
..MenuBarEntry::default()
}]]),

View file

@ -299,7 +299,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
}
PortfolioMessage::NewDocumentWithName { name } => {
let new_document = DocumentMessageHandler::with_name(name, ipp);
let new_document = DocumentMessageHandler::with_name(name, ipp, responses);
let document_id = generate_uuid();
if self.active_document().is_some() {
responses.add(BroadcastEvent::ToolAbort);

View file

@ -14,7 +14,6 @@ pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappi
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageDiscriminant, InputMapperMessageHandler};
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
pub use crate::messages::portfolio::document::artboard::{ArtboardMessage, ArtboardMessageDiscriminant, ArtboardMessageHandler};
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageDiscriminant, NavigationMessageHandler};
pub use crate::messages::portfolio::document::node_graph::{GraphOperationMessage, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};

View file

@ -91,6 +91,11 @@ pub fn get_gradient(layer: LayerNodeIdentifier, document: &Document) -> Option<G
})
}
/// Is a specified layer an artboard?
pub fn is_artboard(layer: LayerNodeIdentifier, document: &Document) -> bool {
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Artboard"))
}
/// Convert subpaths to an iterator of manipulator groups
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
@ -145,14 +150,14 @@ impl<'a> NodeGraphLayer<'a> {
self.node_graph.primary_flow_from_opt(Some(self.layer_node))
}
/// Does a node exist in the layer's primary flow
pub fn uses_node(&self, node_name: &str) -> bool {
self.primary_layer_flow().any(|(node, _id)| node.name == node_name)
}
/// Find all of the inputs of a specific node within the layer's primary flow
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
for (node, _id) in self.primary_layer_flow() {
if node.name == node_name {
return Some(&node.inputs);
}
}
None
self.primary_layer_flow().find(|(node, _id)| node.name == node_name).map(|(node, _id)| &node.inputs)
}
/// Find a specific input of a node within the layer's primary flow

View file

@ -1,12 +1,10 @@
use super::tool_prelude::*;
use crate::application::generate_uuid;
use crate::consts::SELECTION_TOLERANCE;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::tool::common_functionality::graph_modification_utils::is_artboard;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*;
use document_legacy::intersection::Quad;
use document_legacy::LayerId;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::RenderData;
use glam::{IVec2, Vec2Swizzles};
@ -96,7 +94,7 @@ enum ArtboardToolFsmState {
#[derive(Clone, Debug, Default)]
struct ArtboardToolData {
bounding_box_overlays: Option<BoundingBoxOverlays>,
selected_artboard: Option<LayerId>,
selected_artboard: Option<LayerNodeIdentifier>,
snap_manager: SnapManager,
cursor: MouseCursorIcon,
drag_start: DVec2,
@ -104,15 +102,15 @@ struct ArtboardToolData {
}
impl ArtboardToolData {
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
let current_artboard = self.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data));
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
let current_artboard = self.selected_artboard.and_then(|layer| document.document_legacy.metadata.bounding_box_document(layer));
match (current_artboard, self.bounding_box_overlays.take()) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
(Some((bounds, transform)), paths) => {
(Some(bounds), paths) => {
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
bounding_box_overlays.bounds = bounds;
bounding_box_overlays.transform = transform;
bounding_box_overlays.transform = document.document_legacy.metadata.document_to_viewport;
bounding_box_overlays.transform(responses);
@ -130,6 +128,7 @@ impl ArtboardToolData {
let (top, bottom, left, right) = edges;
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
bounding_box.selected_edges = Some(selected_edges);
Some(edges)
}
@ -140,29 +139,29 @@ impl ArtboardToolData {
let artboard = self.selected_artboard.unwrap();
self.snap_manager
.start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y);
.start_snap(document, input, document.bounding_boxes(None, Some(artboard.to_node()), render_data), snap_x, snap_y);
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
if let Some(bounds) = &mut self.bounding_box_overlays {
let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default();
let root = document.document_legacy.metadata.document_to_viewport;
let pivot = root.inverse().transform_point2(pivot);
bounds.center_of_transformation = pivot;
bounds.center_of_transformation = (bounds.bounds[0] + bounds.bounds[1]) / 2.;
}
}
fn select_artboard(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
responses.add(DocumentMessage::StartTransaction);
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data);
let mut intersections = document
.document_legacy
.metadata
.click_xray(input.mouse.position)
.filter(|&layer| is_artboard(layer, &document.document_legacy));
responses.add(BroadcastEvent::DocumentIsDirty);
if let Some(intersection) = intersection.last() {
self.selected_artboard = Some(intersection[0]);
if let Some(intersection) = intersections.next() {
self.selected_artboard = Some(intersection);
self.snap_manager
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
.start_snap(document, input, document.bounding_boxes(None, Some(intersection.to_node()), render_data), true, true);
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
true
@ -186,13 +185,8 @@ impl ArtboardToolData {
let snapped_mouse_position: DVec2 = self.snap_manager.snap_position(responses, document, mouse_position);
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
responses.add(ArtboardMessage::ResizeArtboard {
artboard: self.selected_artboard.unwrap(),
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: self.selected_artboard.unwrap(),
id: self.selected_artboard.unwrap().to_node(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
@ -214,7 +208,7 @@ impl Fsm for ArtboardToolFsmState {
match (self, event) {
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
tool_data.refresh_overlays(document, render_data, responses);
tool_data.refresh_overlays(document, responses);
self
}
@ -255,13 +249,8 @@ impl Fsm for ArtboardToolFsmState {
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
id: tool_data.selected_artboard.unwrap().to_node(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
@ -294,28 +283,18 @@ impl Fsm for ArtboardToolFsmState {
let size = root_transform.transform_vector2(size);
if let Some(artboard) = tool_data.selected_artboard {
responses.add(ArtboardMessage::ResizeArtboard {
artboard,
position: start.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
id: artboard.to_node(),
location: start.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
} else {
let id = generate_uuid();
tool_data.selected_artboard = Some(id);
tool_data.selected_artboard = Some(LayerNodeIdentifier::new_unchecked(id));
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
responses.add(ArtboardMessage::AddArtboard {
id: Some(id),
position: start.round().into(),
size: (1., 1.),
});
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_core::Artboard {
@ -376,8 +355,8 @@ impl Fsm for ArtboardToolFsmState {
}
(_, ArtboardToolMessage::DeleteSelected) => {
if let Some(artboard) = tool_data.selected_artboard.take() {
responses.add(ArtboardMessage::DeleteArtboard { artboard });
responses.add(GraphOperationMessage::DeleteLayer { id: artboard });
let id = artboard.to_node();
responses.add(GraphOperationMessage::DeleteLayer { id });
responses.add(BroadcastEvent::DocumentIsDirty);
}
@ -385,13 +364,8 @@ impl Fsm for ArtboardToolFsmState {
}
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
responses.add(ArtboardMessage::ResizeArtboard {
artboard: tool_data.selected_artboard.unwrap(),
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
id: tool_data.selected_artboard.unwrap().to_node(),
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
});
@ -407,7 +381,6 @@ impl Fsm for ArtboardToolFsmState {
// Register properties when switching back to other tools
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
document: TargetDocument::Artwork,
});
tool_data.snap_manager.cleanup(responses);

View file

@ -68,7 +68,7 @@ impl Fsm for FillToolFsmState {
let ToolMessage::Fill(event) = event else {
return self;
};
let Some((layer_identifier, _)) = document.document_legacy.metadata.click(input.mouse.position) else {
let Some(layer_identifier) = document.document_legacy.metadata.click(input.mouse.position) else {
return self;
};
let layer = layer_identifier.to_path();

View file

@ -116,7 +116,7 @@ enum GradientToolFsmState {
/// Computes the transform from gradient space to viewport space (where gradient space is 0..1)
fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let bounds = document.document_legacy.metadata.bounding_box(layer, DAffine2::IDENTITY).unwrap();
let bounds = document.document_legacy.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let multiplied = document.document_legacy.metadata.transform_from_viewport(layer);
@ -529,7 +529,7 @@ impl Fsm for GradientToolFsmState {
let selected_layer = document.document_legacy.metadata.click(input.mouse.position);
// Apply the gradient to the selected layer
if let Some((layer, _)) = selected_layer {
if let Some(layer) = selected_layer {
// let is_bitmap = document
// .document_legacy
// .layer(&layer)

View file

@ -242,7 +242,7 @@ impl PathToolData {
PathToolFsmState::Dragging
}
// We didn't find a point nearby, so consider selecting the nearest shape instead
else if let Some((layer, _)) = document.document_legacy.metadata.click(input.mouse.position) {
else if let Some(layer) = document.document_legacy.metadata.click(input.mouse.position) {
// TODO: Actual selection
let layer_list = vec![layer.to_path()];
if shift {

View file

@ -484,7 +484,6 @@ impl NodeGraphExecutor {
affected_layer_path: execution_context.layer_path,
});
responses.add(DocumentMessage::RenderDocument);
responses.add(ArtboardMessage::RenderArtboards);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(DocumentMessage::DirtyRenderDocument);