Restructure project directories (#333)

`/client/web` -> `/frontend`
`/client/cli` -> *delete for now*
`/client/native` -> *delete for now*
`/core/editor` -> `/editor`
`/core/document` -> `/graphene`
`/core/renderer` -> `/charcoal`
`/core/proc-macro` -> `/proc-macros` *(now plural)*
This commit is contained in:
Keavon Chambers
2021-08-07 05:17:18 -07:00
parent 434695d578
commit 53ad105f57
239 changed files with 197 additions and 224 deletions
+44
View File
@@ -0,0 +1,44 @@
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
pub enum BlendMode {
Normal,
Multiply,
Darken,
ColorBurn,
Screen,
Lighten,
ColorDodge,
Overlay,
SoftLight,
HardLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl BlendMode {
pub fn to_svg_style_name(&self) -> &str {
match self {
BlendMode::Normal => "normal",
BlendMode::Multiply => "multiply",
BlendMode::Darken => "darken",
BlendMode::ColorBurn => "color-burn",
BlendMode::Screen => "screen",
BlendMode::Lighten => "lighten",
BlendMode::ColorDodge => "color-dodge",
BlendMode::Overlay => "overlay",
BlendMode::SoftLight => "soft-light",
BlendMode::HardLight => "hard-light",
BlendMode::Difference => "difference",
BlendMode::Exclusion => "exclusion",
BlendMode::Hue => "hue",
BlendMode::Saturation => "saturation",
BlendMode::Color => "color",
BlendMode::Luminosity => "luminosity",
}
}
}
+121
View File
@@ -0,0 +1,121 @@
use glam::DVec2;
use crate::{DocumentError, LayerId, Quad};
use super::{Layer, LayerData, LayerDataType};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct Folder {
next_assignment_id: LayerId,
pub layer_ids: Vec<LayerId>,
layers: Vec<Layer>,
}
impl LayerData for Folder {
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>) {
for layer in &mut self.layers {
let _ = writeln!(svg, "{}", layer.render(transforms));
}
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) {
path.push(*layer_id);
layer.intersects_quad(quad, path, intersections);
path.pop();
}
}
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
self.layers
.iter()
.filter_map(|layer| layer.data.bounding_box(transform * layer.transform))
.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
}
}
impl Folder {
/// When a insertion id is provided, try to insert the layer with the given id.
/// If that id is already used, return None.
/// When no insertion id is provided, search for the next free id and insert it with that.
pub fn add_layer(&mut self, layer: Layer, id: Option<LayerId>, insert_index: isize) -> Option<LayerId> {
let mut insert_index = insert_index as i128;
if insert_index < 0 {
insert_index = self.layers.len() as i128 + insert_index as i128 + 1;
}
if insert_index <= self.layers.len() as i128 && insert_index >= 0 {
if let Some(id) = id {
self.next_assignment_id = id;
}
if self.layer_ids.contains(&self.next_assignment_id) {
return None;
}
let id = self.next_assignment_id;
self.layers.insert(insert_index as usize, layer);
self.layer_ids.insert(insert_index as usize, id);
// Linear probing for collision avoidance
while self.layer_ids.contains(&self.next_assignment_id) {
self.next_assignment_id += 1;
}
Some(id)
} else {
None
}
}
pub fn remove_layer(&mut self, id: LayerId) -> Result<(), DocumentError> {
let pos = self.position_of_layer(id)?;
self.layers.remove(pos);
self.layer_ids.remove(pos);
Ok(())
}
/// Returns a list of layers in the folder
pub fn list_layers(&self) -> &[LayerId] {
self.layer_ids.as_slice()
}
pub fn layers(&self) -> &[Layer] {
self.layers.as_slice()
}
pub fn layers_mut(&mut self) -> &mut [Layer] {
self.layers.as_mut_slice()
}
pub fn layer(&self, id: LayerId) -> Option<&Layer> {
let pos = self.position_of_layer(id).ok()?;
Some(&self.layers[pos])
}
pub fn layer_mut(&mut self, id: LayerId) -> Option<&mut Layer> {
let pos = self.position_of_layer(id).ok()?;
Some(&mut self.layers[pos])
}
pub fn position_of_layer(&self, layer_id: LayerId) -> Result<usize, DocumentError> {
self.layer_ids.iter().position(|x| *x == layer_id).ok_or(DocumentError::LayerNotFound)
}
pub fn folder(&self, id: LayerId) -> Option<&Folder> {
match self.layer(id) {
Some(Layer {
data: LayerDataType::Folder(folder), ..
}) => Some(folder),
_ => None,
}
}
pub fn folder_mut(&mut self, id: LayerId) -> Option<&mut Folder> {
match self.layer_mut(id) {
Some(Layer {
data: LayerDataType::Folder(folder), ..
}) => Some(folder),
_ => None,
}
}
}
+164
View File
@@ -0,0 +1,164 @@
pub mod style;
use glam::DAffine2;
use glam::{DMat2, DVec2};
pub mod blend_mode;
pub use blend_mode::BlendMode;
pub mod simple_shape;
pub use simple_shape::Shape;
pub mod folder;
use crate::LayerId;
use crate::{DocumentError, Quad};
pub use folder::Folder;
use serde::{Deserialize, Serialize};
use std::fmt::Write;
pub trait LayerData {
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>);
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>);
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]>;
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum LayerDataType {
Folder(Folder),
Shape(Shape),
}
impl LayerDataType {
pub fn inner(&self) -> &dyn LayerData {
match self {
LayerDataType::Shape(s) => s,
LayerDataType::Folder(f) => f,
}
}
pub fn inner_mut(&mut self) -> &mut dyn LayerData {
match self {
LayerDataType::Shape(s) => s,
LayerDataType::Folder(f) => f,
}
}
}
impl LayerData for LayerDataType {
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>) {
self.inner_mut().render(svg, transforms)
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
self.inner().intersects_quad(quad, path, intersections)
}
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
self.inner().bounding_box(transform)
}
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "glam::DAffine2")]
struct DAffine2Ref {
pub matrix2: DMat2,
pub translation: DVec2,
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Layer {
pub visible: bool,
pub name: Option<String>,
pub data: LayerDataType,
#[serde(with = "DAffine2Ref")]
pub transform: glam::DAffine2,
pub cache: String,
pub thumbnail_cache: String,
pub cache_dirty: bool,
pub blend_mode: BlendMode,
pub opacity: f64,
}
impl Layer {
pub fn new(data: LayerDataType, transform: [f64; 6]) -> Self {
Self {
visible: true,
name: None,
data,
transform: glam::DAffine2::from_cols_array(&transform),
cache: String::new(),
thumbnail_cache: String::new(),
cache_dirty: true,
blend_mode: BlendMode::Normal,
opacity: 1.,
}
}
pub fn render(&mut self, transforms: &mut Vec<DAffine2>) -> &str {
if !self.visible {
return "";
}
if self.cache_dirty {
transforms.push(self.transform);
self.thumbnail_cache.clear();
self.data.render(&mut self.thumbnail_cache, transforms);
self.cache.clear();
let _ = writeln!(self.cache, r#"<g transform="matrix("#);
self.transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
let _ = self.cache.write_str(&(f.to_string() + if i != 5 { "," } else { "" }));
});
let _ = write!(
self.cache,
r#")" style="mix-blend-mode: {}; opacity: {}">{}</g>"#,
self.blend_mode.to_svg_style_name(),
self.opacity,
self.thumbnail_cache.as_str()
);
transforms.pop();
self.cache_dirty = false;
}
self.cache.as_str()
}
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
if !self.visible {
return;
}
let transformed_quad = self.transform.inverse() * quad;
self.data.intersects_quad(transformed_quad, path, intersections)
}
pub fn current_bounding_box(&self) -> Option<[DVec2; 2]> {
self.data.bounding_box(self.transform)
}
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
match &mut self.data {
LayerDataType::Folder(f) => Ok(f),
_ => Err(DocumentError::NotAFolder),
}
}
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
match &self.data {
LayerDataType::Folder(f) => Ok(f),
_ => Err(DocumentError::NotAFolder),
}
}
}
impl Clone for Layer {
fn clone(&self) -> Self {
Self {
visible: self.visible,
name: self.name.clone(),
data: self.data.clone(),
transform: self.transform,
cache: String::new(),
thumbnail_cache: String::new(),
cache_dirty: true,
blend_mode: self.blend_mode,
opacity: self.opacity,
}
}
}
+146
View File
@@ -0,0 +1,146 @@
use glam::DAffine2;
use glam::DMat2;
use glam::DVec2;
use kurbo::Affine;
use kurbo::Shape as KurboShape;
use crate::intersection::intersect_quad_bez_path;
use crate::LayerId;
use crate::Quad;
use kurbo::BezPath;
use super::style;
use super::style::PathStyle;
use super::LayerData;
use serde::{Deserialize, Serialize};
use std::fmt::Write;
fn glam_to_kurbo(transform: DAffine2) -> Affine {
Affine::new(transform.to_cols_array())
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Shape {
pub path: BezPath,
pub style: style::PathStyle,
pub render_index: i32,
pub solid: bool,
}
impl LayerData for Shape {
fn render(&mut self, svg: &mut String, transforms: &mut Vec<DAffine2>) {
let mut path = self.path.clone();
let transform = self.transform(transforms);
let inverse = transform.inverse();
if !inverse.is_finite() {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return;
}
path.apply_affine(glam_to_kurbo(transform));
let _ = writeln!(svg, r#"<g transform="matrix("#);
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
let _ = svg.write_str(&(entry.to_string() + if i != 5 { "," } else { "" }));
});
let _ = svg.write_str(r#")">"#);
let _ = write!(svg, r#"<path d="{}" {} />"#, path.to_svg(), self.style.render());
let _ = svg.write_str("</g>");
}
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
let mut path = self.path.clone();
if transform.matrix2 == DMat2::ZERO {
return None;
}
path.apply_affine(glam_to_kurbo(transform));
use kurbo::Shape;
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
Some([(x0, y0).into(), (x1, y1).into()])
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
if intersect_quad_bez_path(quad, &self.path, self.solid) {
intersections.push(path.clone());
}
}
}
impl Shape {
pub fn transform(&self, transforms: &[DAffine2]) -> DAffine2 {
let start = match self.render_index {
-1 => 0,
x => (transforms.len() as i32 - x).max(0) as usize,
};
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
}
pub fn shape(sides: u8, style: PathStyle) -> Self {
use std::f64::consts::{FRAC_PI_2, TAU};
fn unit_rotation(theta: f64) -> DVec2 {
DVec2::new(theta.sin(), theta.cos())
}
let mut path = kurbo::BezPath::new();
let apothem_offset_angle = TAU / (sides as f64);
// Rotate odd sided shapes by 90 degrees
let offset = ((sides + 1) % 2) as f64 * FRAC_PI_2;
let relative_points = (0..sides).map(|i| apothem_offset_angle * i as f64 + offset).map(unit_rotation);
let min = relative_points.clone().reduce(|a, b| a.min(b)).unwrap_or_default();
let transform = DAffine2::from_scale_angle_translation(DVec2::ONE / 2., 0., -min / 2.);
let point = |vec: DVec2| kurbo::Point::new(vec.x, vec.y);
let mut relative_points = relative_points.map(|p| point(transform.transform_point2(p)));
path.move_to(relative_points.next().expect("Tried to create an ngon with 0 sides"));
relative_points.for_each(|p| path.line_to(p));
path.close_path();
Self {
path,
style,
render_index: 1,
solid: true,
}
}
pub fn rectangle(style: PathStyle) -> Self {
Self {
path: kurbo::Rect::new(0., 0., 1., 1.).to_path(0.01),
style,
render_index: 1,
solid: true,
}
}
pub fn ellipse(style: PathStyle) -> Self {
Self {
path: kurbo::Ellipse::from_rect(kurbo::Rect::new(0., 0., 1., 1.)).to_path(0.01),
style,
render_index: 1,
solid: true,
}
}
pub fn line(style: PathStyle) -> Self {
Self {
path: kurbo::Line::new((0., 0.), (1., 0.)).to_path(0.01),
style,
render_index: 1,
solid: true,
}
}
pub fn poly_line(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
let mut path = kurbo::BezPath::new();
points
.into_iter()
.map(|v| v.into())
.map(|v: DVec2| kurbo::Point { x: v.x, y: v.y })
.enumerate()
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
Self {
path,
style,
render_index: 0,
solid: false,
}
}
}
+99
View File
@@ -0,0 +1,99 @@
use crate::color::Color;
use serde::{Deserialize, Serialize};
const OPACITY_PRECISION: usize = 3;
fn format_opacity(name: &str, opacity: f32) -> String {
if (opacity - 1.).abs() > 10f32.powi(-(OPACITY_PRECISION as i32)) {
format!(r#" {}-opacity="{:.precision$}""#, name, opacity, precision = OPACITY_PRECISION)
} else {
String::new()
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct Fill {
color: Option<Color>,
}
impl Fill {
pub fn new(color: Color) -> Self {
Self { color: Some(color) }
}
pub fn color(&self) -> Option<Color> {
self.color
}
pub fn none() -> Self {
Self { color: None }
}
pub fn render(&self) -> String {
match self.color {
Some(c) => format!(r##" fill="#{}"{}"##, c.rgb_hex(), format_opacity("fill", c.a())),
None => r#" fill="none""#.to_string(),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct Stroke {
color: Color,
width: f32,
}
impl Stroke {
pub fn new(color: Color, width: f32) -> Self {
Self { color, width }
}
pub fn color(&self) -> Color {
self.color
}
pub fn width(&self) -> f32 {
self.width
}
pub fn render(&self) -> String {
format!(r##" stroke="#{}"{} stroke-width="{}""##, self.color.rgb_hex(), format_opacity("stroke", self.color.a()), self.width)
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct PathStyle {
stroke: Option<Stroke>,
fill: Option<Fill>,
}
impl PathStyle {
pub fn new(stroke: Option<Stroke>, fill: Option<Fill>) -> Self {
Self { stroke, fill }
}
pub fn fill(&self) -> Option<Fill> {
self.fill
}
pub fn stroke(&self) -> Option<Stroke> {
self.stroke
}
pub fn set_fill(&mut self, fill: Fill) {
self.fill = Some(fill);
}
pub fn set_stroke(&mut self, stroke: Stroke) {
self.stroke = Some(stroke);
}
pub fn clear_fill(&mut self) {
self.fill = None;
}
pub fn clear_stroke(&mut self) {
self.stroke = None;
}
pub fn render(&self) -> String {
format!(
"{}{}",
match self.fill {
Some(fill) => fill.render(),
None => String::new(),
},
match self.stroke {
Some(stroke) => stroke.render(),
None => String::new(),
},
)
}
}