mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00:00
Improve the spreadsheet visualization details for VectorData
This commit is contained in:
parent
6111440afd
commit
523cc27523
4 changed files with 110 additions and 33 deletions
|
@ -182,19 +182,42 @@ impl InstanceLayout for VectorData {
|
|||
format!("Vector Data (points={}, segments={})", self.point_domain.ids().len(), self.segment_domain.ids().len())
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let mut rows = Vec::new();
|
||||
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||
let colinear = if colinear.is_empty() { "None" } else { &colinear };
|
||||
let style = vec![
|
||||
TextLabel::new(format!(
|
||||
"{}\n\nColinear Handle IDs: {}\n\nUpstream Graphic Group Table: {}",
|
||||
self.style,
|
||||
colinear,
|
||||
if self.upstream_graphic_group.is_some() { "Yes" } else { "No" }
|
||||
))
|
||||
.multiline(true)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let domain_entries = [VectorDataDomain::Points, VectorDataDomain::Segments, VectorDataDomain::Regions]
|
||||
.into_iter()
|
||||
.map(|domain| {
|
||||
RadioEntryData::new(format!("{domain:?}"))
|
||||
.label(format!("{domain:?}"))
|
||||
.on_update(move |_| SpreadsheetMessage::ViewVectorDataDomain { domain }.into())
|
||||
})
|
||||
.collect();
|
||||
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_data_domain as u32)).widget_holder()];
|
||||
|
||||
let mut table_rows = Vec::new();
|
||||
match data.vector_data_domain {
|
||||
VectorDataDomain::Points => {
|
||||
rows.push(column_headings(&["", "position"]));
|
||||
rows.extend(
|
||||
table_rows.push(column_headings(&["", "position"]));
|
||||
table_rows.extend(
|
||||
self.point_domain
|
||||
.iter()
|
||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
|
||||
);
|
||||
}
|
||||
VectorDataDomain::Segments => {
|
||||
rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||
rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{}", start)).widget_holder(),
|
||||
|
@ -204,8 +227,8 @@ impl InstanceLayout for VectorData {
|
|||
}));
|
||||
}
|
||||
VectorDataDomain::Regions => {
|
||||
rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||
rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{:?}", segment_range)).widget_holder(),
|
||||
|
@ -215,17 +238,7 @@ impl InstanceLayout for VectorData {
|
|||
}
|
||||
}
|
||||
|
||||
let entries = [VectorDataDomain::Points, VectorDataDomain::Segments, VectorDataDomain::Regions]
|
||||
.into_iter()
|
||||
.map(|domain| {
|
||||
RadioEntryData::new(format!("{domain:?}"))
|
||||
.label(format!("{domain:?}"))
|
||||
.on_update(move |_| SpreadsheetMessage::ViewVectorDataDomain { domain }.into())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let domain = vec![RadioInput::new(entries).selected_index(Some(data.vector_data_domain as u32)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows }]
|
||||
vec![LayoutGroup::Row { widgets: style }, LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows: table_rows }]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,13 +291,23 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
|
|||
.instance_ref_iter()
|
||||
.enumerate()
|
||||
.map(|(index, instance)| {
|
||||
let (scale, angle, translation) = instance.transform.to_scale_angle_translation();
|
||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
vec![
|
||||
TextLabel::new(format!("{}", index)).widget_holder(),
|
||||
TextButton::new(instance.instance.identifier())
|
||||
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into())
|
||||
.widget_holder(),
|
||||
TextLabel::new(format!("{}", instance.transform)).widget_holder(),
|
||||
TextLabel::new(format!("{:?}", instance.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(format!(
|
||||
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
||||
round(translation.x),
|
||||
round(translation.y),
|
||||
round(scale.x),
|
||||
round(scale.y)
|
||||
))
|
||||
.widget_holder(),
|
||||
TextLabel::new(format!("{}", instance.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(instance.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||
]
|
||||
})
|
||||
|
|
|
@ -29,6 +29,13 @@ impl core::hash::Hash for AlphaBlending {
|
|||
self.blend_mode.hash(state);
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for AlphaBlending {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let round = |x: f32| (x * 1e3).round() / 1e3;
|
||||
write!(f, "Opacity: {}% — Blend Mode: {}", round(self.opacity * 100.), self.blend_mode)
|
||||
}
|
||||
}
|
||||
|
||||
impl AlphaBlending {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
|
|
|
@ -160,6 +160,20 @@ impl core::hash::Hash for Gradient {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Gradient {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
let stops = self
|
||||
.stops
|
||||
.0
|
||||
.iter()
|
||||
.map(|(position, color)| format!("[{}%: #{}]", round(position * 100.), color.to_rgba_hex_srgb()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
write!(f, "{} Gradient: {stops}", self.gradient_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Constructs a new gradient with the colors at 0 and 1 specified.
|
||||
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, transform: DAffine2, gradient_type: GradientType) -> Self {
|
||||
|
@ -308,6 +322,16 @@ pub enum Fill {
|
|||
Gradient(Gradient),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Fill {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::None => write!(f, "None"),
|
||||
Self::Solid(color) => write!(f, "#{} (Alpha: {}%)", color.to_rgb_hex_srgb(), color.a() * 100.),
|
||||
Self::Gradient(gradient) => write!(f, "{}", gradient),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
/// Construct a new [Fill::Solid] from a [Color].
|
||||
pub fn solid(color: Color) -> Self {
|
||||
|
@ -752,6 +776,19 @@ impl core::hash::Hash for PathStyle {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PathStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let fill = &self.fill;
|
||||
|
||||
let stroke = match &self.stroke {
|
||||
Some(stroke) => format!("#{} (Weight: {} px)", stroke.color.map_or("None".to_string(), |c| c.to_rgba_hex_srgb()), stroke.weight),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
|
||||
write!(f, "Fill: {fill}\nStroke: {stroke}")
|
||||
}
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
|
||||
Self { stroke, fill }
|
||||
|
|
|
@ -91,6 +91,19 @@ pub struct VectorData {
|
|||
pub upstream_graphic_group: Option<GraphicGroupTable>,
|
||||
}
|
||||
|
||||
impl Default for VectorData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
||||
colinear_manipulators: Vec::new(),
|
||||
point_domain: PointDomain::new(),
|
||||
segment_domain: SegmentDomain::new(),
|
||||
region_domain: RegionDomain::new(),
|
||||
upstream_graphic_group: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for VectorData {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.point_domain.hash(state);
|
||||
|
@ -450,19 +463,6 @@ impl VectorData {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for VectorData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
||||
colinear_manipulators: Vec::new(),
|
||||
point_domain: PointDomain::new(),
|
||||
segment_domain: SegmentDomain::new(),
|
||||
region_domain: RegionDomain::new(),
|
||||
upstream_graphic_group: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -569,6 +569,16 @@ pub struct HandleId {
|
|||
pub segment: SegmentId,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for HandleId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.ty {
|
||||
// I haven't checked if "out" and "in" are reversed, or are accurate translations of the "primary" and "end" terms used in the `HandleType` enum, so this naming is an assumption.
|
||||
HandleType::Primary => write!(f, "{} out", self.segment.inner()),
|
||||
HandleType::End => write!(f, "{} in", self.segment.inner()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleId {
|
||||
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||
#[must_use]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue