mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Split Fill's optional transform into a has-transform toggle and add the DashPattern and BoxCorners value types
This commit is contained in:
committed by
Dennis Kobert
parent
8e233d3229
commit
194c3d0a72
@@ -2,6 +2,7 @@ use super::PointId;
|
||||
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup};
|
||||
use crate::vector::{SegmentId, Vector};
|
||||
use core_types::list::{Item, List};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, QuadBez};
|
||||
@@ -49,6 +50,77 @@ pub enum RowsOrColumns {
|
||||
Columns,
|
||||
}
|
||||
|
||||
/// A box's four corner values, such as a rectangle's corner radii, expanded on read from any number of stored
|
||||
/// values by the CSS `border-radius` shorthand rules.
|
||||
///
|
||||
/// Wraps a `List<f64>` so the Data panel can introspect its values, mirroring how `DashPattern` wraps its lengths,
|
||||
/// while remaining a single rank-0 value on the wire.
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
pub struct BoxCorners(pub List<f64>);
|
||||
|
||||
impl BoxCorners {
|
||||
/// Expands the stored values to the four corners, clockwise from the top-left, by the CSS `border-radius` shorthand rules.
|
||||
/// - `[]` → `[0, 0, 0, 0]`
|
||||
/// - `[a]` → `[a, a, a, a]`
|
||||
/// - `[a, b]` → `[a, b, a, b]`
|
||||
/// - `[a, b, c]` → `[a, b, c, b]`
|
||||
/// - `[a, b, c, d, …]` → `[a, b, c, d]`
|
||||
pub fn to_corner_values(&self) -> [f64; 4] {
|
||||
let values: Vec<f64> = self.0.iter_element_values().copied().collect();
|
||||
match values.as_slice() {
|
||||
[] => [0., 0., 0., 0.],
|
||||
&[a] => [a, a, a, a],
|
||||
&[a, b] => [a, b, a, b],
|
||||
&[a, b, c] => [a, b, c, b],
|
||||
&[a, b, c, d, ..] => [a, b, c, d],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `List<f64>` is a runtime-only wire type, so serialize the corners as their bare values to keep documents stable
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for BoxCorners {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.collect_seq(self.0.iter_element_values())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for BoxCorners {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(Self::from(<Vec<f64> as serde::Deserialize>::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for BoxCorners {
|
||||
fn from(value: f64) -> Self {
|
||||
Self(List::new_from_element(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<f64>> for BoxCorners {
|
||||
fn from(values: Vec<f64>) -> Self {
|
||||
Self(values.into_iter().map(Item::new_from_element).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for BoxCorners {
|
||||
fn from(text: &str) -> Self {
|
||||
Self::from(
|
||||
text.split([',', ' '])
|
||||
.filter(|piece| !piece.is_empty())
|
||||
.filter_map(|piece| piece.parse::<f64>().ok())
|
||||
.collect::<Vec<f64>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for BoxCorners {
|
||||
fn from(text: String) -> Self {
|
||||
Self::from(text.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsU64 {
|
||||
fn as_u64(&self) -> u64;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
pub use crate::gradient::*;
|
||||
use core_types::Color;
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::transform::Transform;
|
||||
use dyn_any::DynAny;
|
||||
use glam::DAffine2;
|
||||
use std::f64::consts::{PI, TAU};
|
||||
|
||||
/// Describes an editable fill choice, storing color or gradient stops without gradient placement metadata.
|
||||
/// The editor's in-memory paint picker state, storing color or gradient stops without gradient placement metadata.
|
||||
/// Not stored in documents: paint inputs hold the picked value as a plain color, gradient, or no-paint type default.
|
||||
///
|
||||
/// Can be None, a solid [Color], or a linear/radial [Gradient].
|
||||
///
|
||||
@@ -201,6 +203,65 @@ fn daffine2_identity() -> DAffine2 {
|
||||
DAffine2::IDENTITY
|
||||
}
|
||||
|
||||
/// A stroke's dash pattern: a sequence of lengths that alternate dash, gap, dash, gap, and so on. An odd-length
|
||||
/// sequence repeats with the dash and gap roles swapped.
|
||||
///
|
||||
/// Wraps a `List<f64>` so the Data panel can introspect its lengths, mirroring how `Artboard` wraps a `List<Graphic>`,
|
||||
/// while remaining a single rank-0 value on the wire.
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
pub struct DashPattern(pub List<f64>);
|
||||
|
||||
impl DashPattern {
|
||||
/// Returns the dash lengths with any negative values clamped to zero.
|
||||
pub fn clamped_lengths(&self) -> Vec<f64> {
|
||||
self.0.iter_element_values().map(|length| length.max(0.)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
// `List<f64>` is a runtime-only wire type, so serialize the pattern as its bare lengths to keep documents stable
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for DashPattern {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.collect_seq(self.0.iter_element_values())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for DashPattern {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(Self::from(<Vec<f64> as serde::Deserialize>::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for DashPattern {
|
||||
fn from(length: f64) -> Self {
|
||||
Self(List::new_from_element(length))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<f64>> for DashPattern {
|
||||
fn from(lengths: Vec<f64>) -> Self {
|
||||
Self(lengths.into_iter().map(Item::new_from_element).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for DashPattern {
|
||||
fn from(text: &str) -> Self {
|
||||
Self::from(
|
||||
text.split([',', ' '])
|
||||
.filter(|piece| !piece.is_empty())
|
||||
.filter_map(|piece| piece.parse::<f64>().ok())
|
||||
.collect::<Vec<f64>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DashPattern {
|
||||
fn from(text: String) -> Self {
|
||||
Self::from(text.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||
|
||||
Reference in New Issue
Block a user