mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 03:48:12 +08:00
Give VectorRow a top lane and thread it through the walk recursion
This commit is contained in:
@@ -252,6 +252,10 @@ pub struct VectorRow<'w> {
|
|||||||
scale: FlattenScale,
|
scale: FlattenScale,
|
||||||
layer_path: Option<&'w [NodeId]>,
|
layer_path: Option<&'w [NodeId]>,
|
||||||
paint: LanePaint<'w>,
|
paint: LanePaint<'w>,
|
||||||
|
/// The lane of the walk's OWN top level whose subtree produced this row.
|
||||||
|
/// A leaf at any depth reports the top-level row it descends from, which is
|
||||||
|
/// the lane whose columns a consumer carries onto it.
|
||||||
|
top_lane: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RowSourceRef<'w> {
|
enum RowSourceRef<'w> {
|
||||||
@@ -262,6 +266,15 @@ enum RowSourceRef<'w> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VectorRow<'_> {
|
impl VectorRow<'_> {
|
||||||
|
/// The lane of the walked level's top row this row descends from.
|
||||||
|
///
|
||||||
|
/// Constant for every leaf under one top-level row, however deep the leaf
|
||||||
|
/// sits, which is what lets a consumer carry ONE lane's columns onto all of
|
||||||
|
/// them and resolve its output layout from the top level alone.
|
||||||
|
pub fn top_lane(&self) -> usize {
|
||||||
|
self.top_lane
|
||||||
|
}
|
||||||
|
|
||||||
/// The row's vector, borrowed.
|
/// The row's vector, borrowed.
|
||||||
pub fn element(&self) -> &Vector {
|
pub fn element(&self) -> &Vector {
|
||||||
match &self.source {
|
match &self.source {
|
||||||
@@ -319,7 +332,16 @@ impl VectorRow<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_rows_of_run(item: &core_types::record::GroupItem, scale: FlattenScale, layer_path: Option<&[NodeId]>, paint: LanePaint<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) -> RowStep {
|
/// `top_lane` is `None` only when the run IS the walk's top level, where each
|
||||||
|
/// lane is its own top row; nested runs inherit the row they descend from.
|
||||||
|
fn walk_rows_of_run(
|
||||||
|
item: &core_types::record::GroupItem,
|
||||||
|
scale: FlattenScale,
|
||||||
|
layer_path: Option<&[NodeId]>,
|
||||||
|
paint: LanePaint<'_>,
|
||||||
|
top_lane: Option<usize>,
|
||||||
|
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
||||||
|
) -> RowStep {
|
||||||
let Some(run) = core_types::record::RunView::<Vector>::new(item) else {
|
let Some(run) = core_types::record::RunView::<Vector>::new(item) else {
|
||||||
return RowStep::Continue;
|
return RowStep::Continue;
|
||||||
};
|
};
|
||||||
@@ -329,6 +351,7 @@ fn walk_rows_of_run(item: &core_types::record::GroupItem, scale: FlattenScale, l
|
|||||||
scale,
|
scale,
|
||||||
layer_path,
|
layer_path,
|
||||||
paint,
|
paint,
|
||||||
|
top_lane: top_lane.unwrap_or(lane),
|
||||||
}) {
|
}) {
|
||||||
return RowStep::Stop;
|
return RowStep::Stop;
|
||||||
}
|
}
|
||||||
@@ -342,14 +365,18 @@ fn walk_rows_of_run(item: &core_types::record::GroupItem, scale: FlattenScale, l
|
|||||||
/// level's parent layer path overwrites its rows, and non-vector content is
|
/// level's parent layer path overwrites its rows, and non-vector content is
|
||||||
/// discarded. A de-tabled leaf's row is its lane, attributes included.
|
/// discarded. A de-tabled leaf's row is its lane, attributes included.
|
||||||
pub fn walk_vector_rows(level: GraphicLevel<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) {
|
pub fn walk_vector_rows(level: GraphicLevel<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) {
|
||||||
walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, visit);
|
walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, None, visit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `top_lane` carries the walk's own top-level row down the recursion: `None`
|
||||||
|
/// at the top, where each lane names itself, and `Some(row)` thereafter so every
|
||||||
|
/// leaf beneath a row reports that row rather than its immediate parent's index.
|
||||||
fn walk_vector_rows_impl<'a>(
|
fn walk_vector_rows_impl<'a>(
|
||||||
level: GraphicLevel<'a>,
|
level: GraphicLevel<'a>,
|
||||||
scale: FlattenScale,
|
scale: FlattenScale,
|
||||||
parent_layer_path: Option<&'a [NodeId]>,
|
parent_layer_path: Option<&'a [NodeId]>,
|
||||||
inherited: PaintReach<'a>,
|
inherited: PaintReach<'a>,
|
||||||
|
top_lane: Option<usize>,
|
||||||
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
||||||
) -> RowStep {
|
) -> RowStep {
|
||||||
if let GraphicLevel::Run(item) = level {
|
if let GraphicLevel::Run(item) = level {
|
||||||
@@ -359,7 +386,7 @@ fn walk_vector_rows_impl<'a>(
|
|||||||
true => inherited.paint,
|
true => inherited.paint,
|
||||||
false => LanePaint::NONE,
|
false => LanePaint::NONE,
|
||||||
};
|
};
|
||||||
return walk_rows_of_run(item, scale, parent_layer_path, paint, visit);
|
return walk_rows_of_run(item, scale, parent_layer_path, paint, top_lane, visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let columns = PaintColumns::new(&level);
|
let columns = PaintColumns::new(&level);
|
||||||
@@ -370,30 +397,35 @@ fn walk_vector_rows_impl<'a>(
|
|||||||
true => reach.paint,
|
true => reach.paint,
|
||||||
false => LanePaint::NONE,
|
false => LanePaint::NONE,
|
||||||
};
|
};
|
||||||
|
// At the top level this lane IS the row every leaf under it reports.
|
||||||
|
let row_top = top_lane.unwrap_or(index);
|
||||||
let step = match element {
|
let step = match element {
|
||||||
Graphic::Vector(_) => visit(VectorRow {
|
Graphic::Vector(_) => visit(VectorRow {
|
||||||
source: RowSourceRef::Lane(level, index),
|
source: RowSourceRef::Lane(level, index),
|
||||||
scale,
|
scale,
|
||||||
layer_path: parent_layer_path,
|
layer_path: parent_layer_path,
|
||||||
paint: row_paint,
|
paint: row_paint,
|
||||||
|
top_lane: row_top,
|
||||||
}),
|
}),
|
||||||
Graphic::Graphic(children) => walk_vector_rows_impl(
|
Graphic::Graphic(children) => walk_vector_rows_impl(
|
||||||
GraphicLevel::Legacy(children),
|
GraphicLevel::Legacy(children),
|
||||||
scale.composed(&level, index),
|
scale.composed(&level, index),
|
||||||
level.try_attr::<EditorLayerPath>(index),
|
level.try_attr::<EditorLayerPath>(index),
|
||||||
reach.nested(),
|
reach.nested(),
|
||||||
|
Some(row_top),
|
||||||
visit,
|
visit,
|
||||||
),
|
),
|
||||||
Graphic::Group(group) => {
|
Graphic::Group(group) => {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if item.typed_lanes::<Vector>().is_some() {
|
if item.typed_lanes::<Vector>().is_some() {
|
||||||
walk_rows_of_run(item, scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), row_paint, visit)
|
walk_rows_of_run(item, scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), row_paint, Some(row_top), visit)
|
||||||
} else if item.typed_lanes::<Graphic>().is_some() {
|
} else if item.typed_lanes::<Graphic>().is_some() {
|
||||||
walk_vector_rows_impl(
|
walk_vector_rows_impl(
|
||||||
GraphicLevel::Run(item),
|
GraphicLevel::Run(item),
|
||||||
scale.composed(&level, index),
|
scale.composed(&level, index),
|
||||||
level.try_attr::<EditorLayerPath>(index),
|
level.try_attr::<EditorLayerPath>(index),
|
||||||
reach.into_group_graphics(),
|
reach.into_group_graphics(),
|
||||||
|
Some(row_top),
|
||||||
visit,
|
visit,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -477,6 +509,39 @@ mod run_tests {
|
|||||||
use crate::graphic::{IntoGraphicList, map_groups_to_legacy, run_to_legacy_list};
|
use crate::graphic::{IntoGraphicList, map_groups_to_legacy, run_to_legacy_list};
|
||||||
use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed};
|
use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leaves_at_different_depths_report_the_same_top_row() {
|
||||||
|
// One top-level row holding a leaf at depth 1 AND a leaf at depth 2, so
|
||||||
|
// "which lane's columns does this row carry" cannot depend on the depth
|
||||||
|
// the leaf was found at. That independence is what lets an adopter's
|
||||||
|
// output layout resolve from the input's TOP-LEVEL layout alone instead
|
||||||
|
// of being discovered per leaf.
|
||||||
|
//
|
||||||
|
// The nesting sits at top row 1 on purpose: both inner leaves have index
|
||||||
|
// 0 within their own parents, so an implementation reporting the
|
||||||
|
// immediate lane rather than the top row would answer 0 and be caught,
|
||||||
|
// where a fixture nested at row 0 would have agreed by coincidence.
|
||||||
|
let deep = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(3., 3.))));
|
||||||
|
let mut mixed = List::new();
|
||||||
|
mixed.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ZERO))));
|
||||||
|
mixed.push(Item::new_from_element(Graphic::Graphic(deep)));
|
||||||
|
|
||||||
|
let mut top = List::new();
|
||||||
|
top.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(9., 9.)))));
|
||||||
|
top.push(Item::new_from_element(Graphic::Graphic(mixed)));
|
||||||
|
|
||||||
|
let mut reported = Vec::new();
|
||||||
|
walk_vector_rows(GraphicLevel::Legacy(&top), &mut |row| {
|
||||||
|
reported.push(row.top_lane());
|
||||||
|
RowStep::Continue
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(reported.len(), 3, "one top-level leaf plus two leaves under row 1");
|
||||||
|
assert_eq!(reported[0], 0, "a top-level leaf reports its own lane");
|
||||||
|
assert_eq!(reported[1], 1, "the depth-1 leaf reports the top row it descends from");
|
||||||
|
assert_eq!(reported[2], 1, "the depth-2 leaf reports the SAME top row, not its own index within its parent");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_vector_row_walk_matches_the_legacy_flatten() {
|
fn the_vector_row_walk_matches_the_legacy_flatten() {
|
||||||
let inner_vector = unit_square_at(DVec2::ZERO);
|
let inner_vector = unit_square_at(DVec2::ZERO);
|
||||||
|
|||||||
Reference in New Issue
Block a user