-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathShapeContainerNode.java
More file actions
133 lines (127 loc) · 6.03 KB
/
ShapeContainerNode.java
File metadata and controls
133 lines (127 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.demcha.compose.document.node;
import com.demcha.compose.document.style.ClipPolicy;
import com.demcha.compose.document.style.DocumentColor;
import com.demcha.compose.document.style.DocumentInsets;
import com.demcha.compose.document.style.DocumentStroke;
import com.demcha.compose.document.style.DocumentTransform;
import com.demcha.compose.document.style.ShapeOutline;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Composite node whose bounding box is dictated by a {@link ShapeOutline}
* (rectangle, rounded rectangle, or ellipse) and that hosts one or more child
* layers positioned inside that outline.
*
* <p>The container differs from {@link LayerStackNode} in two ways. First,
* the bounding box is derived from {@code outline.width()} /
* {@code outline.height()} rather than from {@code max(child outer size)} —
* so the outline is the authoritative size, not the children. Second, a
* {@link ClipPolicy} declares whether children clip to the outline path,
* to the bounding box, or are allowed to overflow.</p>
*
* <p>Children are reused from {@link LayerStackNode.Layer} so the same
* alignment + on-screen offset semantics apply: the first layer is painted
* behind, the last layer is painted in front, and each layer is anchored
* inside the inner box (outline minus {@code padding}) by its
* {@link LayerAlign} corner / edge plus its {@code offsetX} / {@code offsetY}.</p>
*
* <p>Pagination is atomic: the outline plus all of its layers stays on the
* same page or moves to the next as one unit.</p>
*
* @param name node name used in snapshots and layout graph paths
* @param outline geometric outline that drives the bounding box
* @param layers child layers in back-to-front order; must contain at least one
* @param clipPolicy how children are clipped relative to the outline
* @param fillColor optional outline fill colour
* @param stroke optional outline stroke
* @param padding inner padding applied around all layers (subtracted from outline)
* @param margin outer margin around the container
* @param transform render-time affine transform (rotation around the
* placement centre and/or scaling) applied to the
* outline + layers as a single unit; defaults to
* {@link DocumentTransform#NONE}. The canonical layout
* layer measures and places the node against its
* natural bounding box — backends apply the transform
* during render so layout snapshots stay deterministic
* regardless of rotation/scale.
*
* @author Artem Demchyshyn
*/
public record ShapeContainerNode(
String name,
ShapeOutline outline,
List<LayerStackNode.Layer> layers,
ClipPolicy clipPolicy,
DocumentColor fillColor,
DocumentStroke stroke,
DocumentInsets padding,
DocumentInsets margin,
DocumentTransform transform
) implements DocumentNode {
/**
* Convenience constructor that defaults the {@code transform} to
* {@link DocumentTransform#NONE}. Lets existing callers built before
* Phase C compile without changes.
*
* @param name node name used in snapshots and layout graph paths
* @param outline geometric outline that drives the bounding box
* @param layers child layers in back-to-front order
* @param clipPolicy how children are clipped relative to the outline
* @param fillColor optional outline fill colour
* @param stroke optional outline stroke
* @param padding inner padding applied around all layers
* @param margin outer margin around the container
*/
public ShapeContainerNode(String name,
ShapeOutline outline,
List<LayerStackNode.Layer> layers,
ClipPolicy clipPolicy,
DocumentColor fillColor,
DocumentStroke stroke,
DocumentInsets padding,
DocumentInsets margin) {
this(name, outline, layers, clipPolicy, fillColor, stroke, padding, margin, DocumentTransform.NONE);
}
/**
* Normalizes optional values, copies the layer list defensively, and
* validates that at least one layer exists.
*/
public ShapeContainerNode {
name = name == null ? "" : name;
Objects.requireNonNull(outline, "outline");
Objects.requireNonNull(layers, "layers");
if (layers.isEmpty()) {
throw new IllegalArgumentException(
"ShapeContainerNode '" + name + "' must have at least one layer.");
}
List<LayerStackNode.Layer> normalized = new ArrayList<>(layers.size());
for (LayerStackNode.Layer layer : layers) {
normalized.add(Objects.requireNonNull(layer, "layer"));
}
layers = List.copyOf(normalized);
// Default to CLIP_PATH per ADR §Decision: a ShapeContainerNode is
// *the* shape-with-children primitive, and the natural reading of
// "add a circle with a label inside" is that the label is clipped
// by the circle's outline. Callers who explicitly want
// axis-aligned bbox clipping or no clipping at all set the policy
// through the builder.
clipPolicy = clipPolicy == null ? ClipPolicy.CLIP_PATH : clipPolicy;
padding = padding == null ? DocumentInsets.zero() : padding;
margin = margin == null ? DocumentInsets.zero() : margin;
transform = transform == null ? DocumentTransform.NONE : transform;
}
/**
* @return ordered child node references — equivalent to
* {@code layers.stream().map(Layer::node)}, kept aligned with
* {@link DocumentNode#children()}'s contract
*/
@Override
public List<DocumentNode> children() {
List<DocumentNode> nodes = new ArrayList<>(layers.size());
for (LayerStackNode.Layer layer : layers) {
nodes.add(layer.node());
}
return List.copyOf(nodes);
}
}