Improve the spreadsheet visualization details for VectorData
This commit is contained in:
parent
6111440afd
commit
523cc27523
|
|
@ -182,19 +182,42 @@ impl InstanceLayout for VectorData {
|
||||||
format!("Vector Data (points={}, segments={})", self.point_domain.ids().len(), self.segment_domain.ids().len())
|
format!("Vector Data (points={}, segments={})", self.point_domain.ids().len(), self.segment_domain.ids().len())
|
||||||
}
|
}
|
||||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
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 {
|
match data.vector_data_domain {
|
||||||
VectorDataDomain::Points => {
|
VectorDataDomain::Points => {
|
||||||
rows.push(column_headings(&["", "position"]));
|
table_rows.push(column_headings(&["", "position"]));
|
||||||
rows.extend(
|
table_rows.extend(
|
||||||
self.point_domain
|
self.point_domain
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
|
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
VectorDataDomain::Segments => {
|
VectorDataDomain::Segments => {
|
||||||
rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||||
rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||||
vec![
|
vec![
|
||||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||||
TextLabel::new(format!("{}", start)).widget_holder(),
|
TextLabel::new(format!("{}", start)).widget_holder(),
|
||||||
|
|
@ -204,8 +227,8 @@ impl InstanceLayout for VectorData {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
VectorDataDomain::Regions => {
|
VectorDataDomain::Regions => {
|
||||||
rows.push(column_headings(&["", "segment_range", "fill"]));
|
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||||
rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||||
vec![
|
vec![
|
||||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||||
TextLabel::new(format!("{:?}", segment_range)).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]
|
vec![LayoutGroup::Row { widgets: style }, LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows: table_rows }]
|
||||||
.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 }]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,13 +291,23 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
|
||||||
.instance_ref_iter()
|
.instance_ref_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, instance)| {
|
.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![
|
vec![
|
||||||
TextLabel::new(format!("{}", index)).widget_holder(),
|
TextLabel::new(format!("{}", index)).widget_holder(),
|
||||||
TextButton::new(instance.instance.identifier())
|
TextButton::new(instance.instance.identifier())
|
||||||
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into())
|
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
TextLabel::new(format!("{}", instance.transform)).widget_holder(),
|
TextLabel::new(format!(
|
||||||
TextLabel::new(format!("{:?}", instance.alpha_blending)).widget_holder(),
|
"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(),
|
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);
|
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 {
|
impl AlphaBlending {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
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 {
|
impl Gradient {
|
||||||
/// Constructs a new gradient with the colors at 0 and 1 specified.
|
/// 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 {
|
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),
|
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 {
|
impl Fill {
|
||||||
/// Construct a new [Fill::Solid] from a [Color].
|
/// Construct a new [Fill::Solid] from a [Color].
|
||||||
pub fn solid(color: Color) -> Self {
|
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 {
|
impl PathStyle {
|
||||||
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
|
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
|
||||||
Self { stroke, fill }
|
Self { stroke, fill }
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,19 @@ pub struct VectorData {
|
||||||
pub upstream_graphic_group: Option<GraphicGroupTable>,
|
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 {
|
impl core::hash::Hash for VectorData {
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.point_domain.hash(state);
|
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).
|
/// 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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
|
@ -569,6 +569,16 @@ pub struct HandleId {
|
||||||
pub segment: SegmentId,
|
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 {
|
impl HandleId {
|
||||||
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
|
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue