mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
151 lines
5.6 KiB
Rust
151 lines
5.6 KiB
Rust
use super::{Affine, Arc, Deserialize, Node, NodeId, Point, Serialize};
|
||
|
||
/// New art on a locked artboard ([`Document::new_art`]). Setting it changes the document in
|
||
/// place, so a selection change that sets it doesn't replace the document.
|
||
#[derive(Default)]
|
||
pub struct NewArtIds(pub(crate) std::sync::RwLock<Arc<std::collections::HashSet<NodeId>>>);
|
||
|
||
impl NewArtIds {
|
||
/// The objects.
|
||
pub fn get(&self) -> Arc<std::collections::HashSet<NodeId>> {
|
||
self.0.read().map(|g| g.clone()).unwrap_or_default()
|
||
}
|
||
/// Make `ids` the objects.
|
||
pub fn set(&self, ids: Arc<std::collections::HashSet<NodeId>>) {
|
||
if let Ok(mut g) = self.0.write() {
|
||
*g = ids;
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Clone for NewArtIds {
|
||
fn clone(&self) -> Self {
|
||
NewArtIds(std::sync::RwLock::new(self.get()))
|
||
}
|
||
}
|
||
|
||
/// Editing state, not document content: two documents are equal whatever new art they name.
|
||
impl PartialEq for NewArtIds {
|
||
fn eq(&self, _: &Self) -> bool {
|
||
true
|
||
}
|
||
}
|
||
|
||
impl std::fmt::Debug for NewArtIds {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
f.debug_tuple("NewArtIds").field(&self.get()).finish()
|
||
}
|
||
}
|
||
|
||
/// Opacity-mask editing mode: `object`'s mask art lives on the temporary `layer` while editing.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct MaskEdit {
|
||
pub object: NodeId,
|
||
pub layer: NodeId,
|
||
}
|
||
|
||
/// Symbol editing mode: the art of symbol `symbol` lives on the temporary `layer` while editing,
|
||
/// placed by `xf` (from the symbol's coordinates to the document's), and goes back into the symbol
|
||
/// after each edit, so every instance follows it.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct SymbolEdit {
|
||
pub symbol: String,
|
||
pub layer: NodeId,
|
||
/// The instance editing was entered from (a double-click on it), selected again on leaving after an edit;
|
||
/// `None` when it was entered from the Symbols panel.
|
||
pub instance: Option<NodeId>,
|
||
pub xf: Affine,
|
||
/// The symbol's art is a group whose members are on the layer: the group takes them back.
|
||
pub group: bool,
|
||
/// An edit changed the art since editing was entered.
|
||
pub changed: bool,
|
||
/// The isolated object from before editing was entered, isolated again on leaving.
|
||
#[serde(default)]
|
||
pub back_isolation: Option<NodeId>,
|
||
/// The current layer from before editing was entered, current again on leaving.
|
||
#[serde(default)]
|
||
pub back_layer: Option<NodeId>,
|
||
}
|
||
|
||
/// A saved view (View → New View…): zoom, centre and rotation, listed at the bottom of the View menu.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct SavedView {
|
||
pub name: String,
|
||
pub center: Point,
|
||
pub zoom: f64,
|
||
#[serde(default)]
|
||
pub rotation: f64,
|
||
}
|
||
|
||
impl SavedView {
|
||
/// Most views a document keeps (the View menu lists every one).
|
||
pub const MAX: usize = 25;
|
||
/// Longest name, in characters.
|
||
pub const MAX_NAME: usize = 255;
|
||
|
||
/// `name` trimmed and cut to [`Self::MAX_NAME`] characters (`None` when blank).
|
||
pub fn clean_name(name: &str) -> Option<String> {
|
||
let name: String = name.trim().chars().take(Self::MAX_NAME).collect();
|
||
(!name.is_empty()).then_some(name)
|
||
}
|
||
|
||
/// The first "View N" none of `saved` is named.
|
||
pub fn default_name(saved: &[SavedView]) -> String {
|
||
(1..=saved.len() + 1).map(|i| format!("View {i}")).find(|n| !saved.iter().any(|x| &x.name == n)).unwrap_or_default()
|
||
}
|
||
|
||
/// Can the canvas show this view: a finite centre and a finite, positive zoom.
|
||
pub fn is_usable(&self) -> bool {
|
||
self.center.x.is_finite() && self.center.y.is_finite() && self.zoom.is_finite() && self.zoom > 0.0
|
||
}
|
||
}
|
||
|
||
/// A saved selection (Select → Save Selection…): the objects that were selected, listed by name at
|
||
/// the bottom of the Select menu.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct SavedSelection {
|
||
pub name: String,
|
||
pub objects: Vec<NodeId>,
|
||
}
|
||
|
||
impl SavedSelection {
|
||
/// Most saved selections a document keeps (the Select menu lists every one).
|
||
pub const MAX: usize = 25;
|
||
/// Longest name, in characters.
|
||
pub const MAX_NAME: usize = 255;
|
||
|
||
/// `name` trimmed and cut to [`Self::MAX_NAME`] characters (`None` when blank).
|
||
pub fn clean_name(name: &str) -> Option<String> {
|
||
let name: String = name.trim().chars().take(Self::MAX_NAME).collect();
|
||
(!name.is_empty()).then_some(name)
|
||
}
|
||
|
||
/// The first "Selection N" none of `saved` is named.
|
||
pub fn default_name(saved: &[SavedSelection]) -> String {
|
||
(1..=saved.len() + 1).map(|i| format!("Selection {i}")).find(|n| !saved.iter().any(|x| &x.name == n)).unwrap_or_default()
|
||
}
|
||
}
|
||
|
||
/// A symbol definition.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct Symbol {
|
||
pub name: String,
|
||
pub art: Arc<Node>,
|
||
/// Symbol Options › Symbol Type › Dynamic Symbol: each instance may change the appearance of
|
||
/// objects in the art and stay linked (see [`crate::symbol_overrides`]). Static by default.
|
||
#[serde(default, skip_serializing_if = "crate::skip::is_default")]
|
||
pub dynamic: bool,
|
||
}
|
||
|
||
/// Encoded image bytes (PNG/JPEG/WebP…) shared by image objects.
|
||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct ImageBlob {
|
||
pub mime: String,
|
||
#[serde(skip)]
|
||
pub bytes: Arc<Vec<u8>>,
|
||
/// A linked image's low-resolution preview (PNG, see [`links`]): what a save writes when only
|
||
/// linked images show this blob, and what `bytes` hold while the linked file can't be read.
|
||
#[serde(skip)]
|
||
pub proxy: Option<Arc<Vec<u8>>>,
|
||
}
|