Skip to content

Commit d62891d

Browse files
committed
Improve the spreadsheet visualization details for VectorData
1 parent 74a6881 commit d62891d

File tree

4 files changed

+110
-33
lines changed

4 files changed

+110
-33
lines changed

editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,42 @@ impl InstanceLayout for VectorData {
180180
format!("Vector Data (points={}, segments={})", self.point_domain.ids().len(), self.segment_domain.ids().len())
181181
}
182182
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
183-
let mut rows = Vec::new();
183+
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
184+
let colinear = if colinear.is_empty() { "None" } else { &colinear };
185+
let style = vec![
186+
TextLabel::new(format!(
187+
"{}\n\nColinear Handle IDs: {}\n\nUpstream Graphic Group Table: {}",
188+
self.style,
189+
colinear,
190+
if self.upstream_graphic_group.is_some() { "Yes" } else { "No" }
191+
))
192+
.multiline(true)
193+
.widget_holder(),
194+
];
195+
196+
let domain_entries = [VectorDataDomain::Points, VectorDataDomain::Segments, VectorDataDomain::Regions]
197+
.into_iter()
198+
.map(|domain| {
199+
RadioEntryData::new(format!("{domain:?}"))
200+
.label(format!("{domain:?}"))
201+
.on_update(move |_| SpreadsheetMessage::ViewVectorDataDomain { domain }.into())
202+
})
203+
.collect();
204+
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_data_domain as u32)).widget_holder()];
205+
206+
let mut table_rows = Vec::new();
184207
match data.vector_data_domain {
185208
VectorDataDomain::Points => {
186-
rows.push(column_headings(&["", "position"]));
187-
rows.extend(
209+
table_rows.push(column_headings(&["", "position"]));
210+
table_rows.extend(
188211
self.point_domain
189212
.iter()
190213
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
191214
);
192215
}
193216
VectorDataDomain::Segments => {
194-
rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
195-
rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
217+
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
218+
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
196219
vec![
197220
TextLabel::new(format!("{}", id.inner())).widget_holder(),
198221
TextLabel::new(format!("{}", start)).widget_holder(),
@@ -202,8 +225,8 @@ impl InstanceLayout for VectorData {
202225
}));
203226
}
204227
VectorDataDomain::Regions => {
205-
rows.push(column_headings(&["", "segment_range", "fill"]));
206-
rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
228+
table_rows.push(column_headings(&["", "segment_range", "fill"]));
229+
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
207230
vec![
208231
TextLabel::new(format!("{}", id.inner())).widget_holder(),
209232
TextLabel::new(format!("{:?}", segment_range)).widget_holder(),
@@ -213,17 +236,7 @@ impl InstanceLayout for VectorData {
213236
}
214237
}
215238

216-
let entries = [VectorDataDomain::Points, VectorDataDomain::Segments, VectorDataDomain::Regions]
217-
.into_iter()
218-
.map(|domain| {
219-
RadioEntryData::new(format!("{domain:?}"))
220-
.label(format!("{domain:?}"))
221-
.on_update(move |_| SpreadsheetMessage::ViewVectorDataDomain { domain }.into())
222-
})
223-
.collect();
224-
225-
let domain = vec![RadioInput::new(entries).selected_index(Some(data.vector_data_domain as u32)).widget_holder()];
226-
vec![LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows }]
239+
vec![LayoutGroup::Row { widgets: style }, LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows: table_rows }]
227240
}
228241
}
229242

@@ -276,13 +289,23 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
276289
.instance_ref_iter()
277290
.enumerate()
278291
.map(|(index, instance)| {
292+
let (scale, angle, translation) = instance.transform.to_scale_angle_translation();
293+
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
294+
let round = |x: f64| (x * 1e3).round() / 1e3;
279295
vec![
280296
TextLabel::new(format!("{}", index)).widget_holder(),
281297
TextButton::new(instance.instance.identifier())
282298
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into())
283299
.widget_holder(),
284-
TextLabel::new(format!("{}", instance.transform)).widget_holder(),
285-
TextLabel::new(format!("{:?}", instance.alpha_blending)).widget_holder(),
300+
TextLabel::new(format!(
301+
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
302+
round(translation.x),
303+
round(translation.y),
304+
round(scale.x),
305+
round(scale.y)
306+
))
307+
.widget_holder(),
308+
TextLabel::new(format!("{}", instance.alpha_blending)).widget_holder(),
286309
TextLabel::new(instance.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
287310
]
288311
})

node-graph/gcore/src/graphic_element.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ impl core::hash::Hash for AlphaBlending {
2929
self.blend_mode.hash(state);
3030
}
3131
}
32+
impl std::fmt::Display for AlphaBlending {
33+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34+
let round = |x: f32| (x * 1e3).round() / 1e3;
35+
write!(f, "Opacity: {}% — Blend Mode: {}", round(self.opacity * 100.), self.blend_mode)
36+
}
37+
}
38+
3239
impl AlphaBlending {
3340
pub const fn new() -> Self {
3441
Self {

node-graph/gcore/src/vector/style.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ impl core::hash::Hash for Gradient {
160160
}
161161
}
162162

163+
impl std::fmt::Display for Gradient {
164+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165+
let round = |x: f64| (x * 1e3).round() / 1e3;
166+
let stops = self
167+
.stops
168+
.0
169+
.iter()
170+
.map(|(position, color)| format!("[{}%: #{}]", round(position * 100.), color.to_rgba_hex_srgb()))
171+
.collect::<Vec<_>>()
172+
.join(", ");
173+
write!(f, "{} Gradient: {stops}", self.gradient_type)
174+
}
175+
}
176+
163177
impl Gradient {
164178
/// Constructs a new gradient with the colors at 0 and 1 specified.
165179
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 {
308322
Gradient(Gradient),
309323
}
310324

325+
impl std::fmt::Display for Fill {
326+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327+
match self {
328+
Self::None => write!(f, "None"),
329+
Self::Solid(color) => write!(f, "#{} (Alpha: {}%)", color.to_rgb_hex_srgb(), color.a() * 100.),
330+
Self::Gradient(gradient) => write!(f, "{}", gradient),
331+
}
332+
}
333+
}
334+
311335
impl Fill {
312336
/// Construct a new [Fill::Solid] from a [Color].
313337
pub fn solid(color: Color) -> Self {
@@ -752,6 +776,19 @@ impl core::hash::Hash for PathStyle {
752776
}
753777
}
754778

779+
impl std::fmt::Display for PathStyle {
780+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781+
let fill = &self.fill;
782+
783+
let stroke = match &self.stroke {
784+
Some(stroke) => format!("#{} (Weight: {} px)", stroke.color.map_or("None".to_string(), |c| c.to_rgba_hex_srgb()), stroke.weight),
785+
None => "None".to_string(),
786+
};
787+
788+
write!(f, "Fill: {fill}\nStroke: {stroke}")
789+
}
790+
}
791+
755792
impl PathStyle {
756793
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
757794
Self { stroke, fill }

node-graph/gcore/src/vector/vector_data.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ pub struct VectorData {
9191
pub upstream_graphic_group: Option<GraphicGroupTable>,
9292
}
9393

94+
impl Default for VectorData {
95+
fn default() -> Self {
96+
Self {
97+
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
98+
colinear_manipulators: Vec::new(),
99+
point_domain: PointDomain::new(),
100+
segment_domain: SegmentDomain::new(),
101+
region_domain: RegionDomain::new(),
102+
upstream_graphic_group: None,
103+
}
104+
}
105+
}
106+
94107
impl core::hash::Hash for VectorData {
95108
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
96109
self.point_domain.hash(state);
@@ -450,19 +463,6 @@ impl VectorData {
450463
}
451464
}
452465

453-
impl Default for VectorData {
454-
fn default() -> Self {
455-
Self {
456-
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
457-
colinear_manipulators: Vec::new(),
458-
point_domain: PointDomain::new(),
459-
segment_domain: SegmentDomain::new(),
460-
region_domain: RegionDomain::new(),
461-
upstream_graphic_group: None,
462-
}
463-
}
464-
}
465-
466466
/// 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).
467467
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
468468
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -569,6 +569,16 @@ pub struct HandleId {
569569
pub segment: SegmentId,
570570
}
571571

572+
impl std::fmt::Display for HandleId {
573+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574+
match self.ty {
575+
// 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.
576+
HandleType::Primary => write!(f, "{} out", self.segment.inner()),
577+
HandleType::End => write!(f, "{} in", self.segment.inner()),
578+
}
579+
}
580+
}
581+
572582
impl HandleId {
573583
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
574584
#[must_use]

0 commit comments

Comments
 (0)