kozan_core/paint/
display_list.rs1use super::display_item::DisplayItem;
23use super::property_state::PropertyState;
24
25#[derive(Debug, Clone)]
30#[non_exhaustive]
31pub struct PaintChunk {
32 pub state: PropertyState,
34 pub start: usize,
36 pub end: usize,
38}
39
40impl PaintChunk {
41 #[must_use]
43 pub fn len(&self) -> usize {
44 self.end - self.start
45 }
46
47 #[must_use]
49 pub fn is_empty(&self) -> bool {
50 self.start == self.end
51 }
52}
53
54#[derive(Debug, Clone, Default)]
60pub struct DisplayList {
61 items: Vec<DisplayItem>,
63 chunks: Vec<PaintChunk>,
65}
66
67impl DisplayList {
68 #[must_use]
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 #[must_use]
76 pub fn builder() -> DisplayListBuilder {
77 DisplayListBuilder::new()
78 }
79
80 #[must_use]
82 pub fn len(&self) -> usize {
83 self.items.len()
84 }
85
86 #[must_use]
88 pub fn is_empty(&self) -> bool {
89 self.items.is_empty()
90 }
91
92 #[must_use]
94 pub fn items(&self) -> &[DisplayItem] {
95 &self.items
96 }
97
98 #[must_use]
100 pub fn chunks(&self) -> &[PaintChunk] {
101 &self.chunks
102 }
103
104 pub fn iter(&self) -> impl Iterator<Item = &DisplayItem> {
106 self.items.iter()
107 }
108
109 #[must_use]
111 pub fn chunk_items(&self, chunk: &PaintChunk) -> &[DisplayItem] {
112 &self.items[chunk.start..chunk.end]
113 }
114}
115
116pub struct DisplayListBuilder {
131 items: Vec<DisplayItem>,
132 chunks: Vec<PaintChunk>,
133 current_state: PropertyState,
134 current_chunk_start: usize,
135}
136
137impl DisplayListBuilder {
138 #[must_use]
140 pub fn new() -> Self {
141 Self {
142 items: Vec::new(),
143 chunks: Vec::new(),
144 current_state: PropertyState::root(),
145 current_chunk_start: 0,
146 }
147 }
148
149 pub fn push(&mut self, item: DisplayItem) {
151 self.items.push(item);
152 }
153
154 pub fn set_state(&mut self, state: PropertyState) {
158 if state != self.current_state {
159 self.finish_chunk();
160 self.current_state = state;
161 self.current_chunk_start = self.items.len();
162 }
163 }
164
165 #[must_use]
167 pub fn finish(mut self) -> DisplayList {
168 self.finish_chunk();
169 DisplayList {
170 items: self.items,
171 chunks: self.chunks,
172 }
173 }
174
175 fn finish_chunk(&mut self) {
177 let end = self.items.len();
178 if end > self.current_chunk_start {
179 self.chunks.push(PaintChunk {
180 state: self.current_state.clone(),
181 start: self.current_chunk_start,
182 end,
183 });
184 }
185 }
186}
187
188impl Default for DisplayListBuilder {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::paint::display_item::{ClipData, DrawCommand};
198 use kozan_primitives::color::Color;
199 use kozan_primitives::geometry::Rect;
200
201 #[test]
202 fn empty_display_list() {
203 let list = DisplayList::builder().finish();
204 assert!(list.is_empty());
205 assert_eq!(list.len(), 0);
206 assert!(list.chunks().is_empty());
207 }
208
209 #[test]
210 fn single_item_single_chunk() {
211 let mut builder = DisplayList::builder();
212 builder.push(DisplayItem::Draw(DrawCommand::Rect {
213 rect: Rect::new(0.0, 0.0, 100.0, 50.0),
214 color: Color::RED,
215 }));
216 let list = builder.finish();
217
218 assert_eq!(list.len(), 1);
219 assert_eq!(list.chunks().len(), 1);
220 assert_eq!(list.chunks()[0].start, 0);
221 assert_eq!(list.chunks()[0].end, 1);
222 }
223
224 #[test]
225 fn same_state_groups_into_one_chunk() {
226 let mut builder = DisplayList::builder();
227 builder.push(DisplayItem::Draw(DrawCommand::Rect {
228 rect: Rect::new(0.0, 0.0, 100.0, 50.0),
229 color: Color::RED,
230 }));
231 builder.push(DisplayItem::Draw(DrawCommand::Rect {
232 rect: Rect::new(0.0, 50.0, 100.0, 50.0),
233 color: Color::BLUE,
234 }));
235 let list = builder.finish();
236
237 assert_eq!(list.len(), 2);
239 assert_eq!(list.chunks().len(), 1);
240 }
241
242 #[test]
243 fn different_state_creates_new_chunk() {
244 let mut builder = DisplayList::builder();
245 builder.push(DisplayItem::Draw(DrawCommand::Rect {
246 rect: Rect::new(0.0, 0.0, 100.0, 50.0),
247 color: Color::RED,
248 }));
249
250 builder.set_state(PropertyState {
252 opacity: 0.5,
253 ..PropertyState::default()
254 });
255
256 builder.push(DisplayItem::Draw(DrawCommand::Rect {
257 rect: Rect::new(0.0, 50.0, 100.0, 50.0),
258 color: Color::BLUE,
259 }));
260
261 let list = builder.finish();
262
263 assert_eq!(list.len(), 2);
264 assert_eq!(list.chunks().len(), 2);
265 assert_eq!(list.chunks()[0].state.opacity, 1.0);
266 assert_eq!(list.chunks()[1].state.opacity, 0.5);
267 }
268
269 #[test]
270 fn chunk_items_accessor() {
271 let mut builder = DisplayList::builder();
272 builder.push(DisplayItem::Draw(DrawCommand::Rect {
273 rect: Rect::new(0.0, 0.0, 100.0, 50.0),
274 color: Color::RED,
275 }));
276 builder.push(DisplayItem::PushClip(ClipData {
277 rect: Rect::new(0.0, 0.0, 50.0, 50.0),
278 }));
279 let list = builder.finish();
280
281 let chunk = &list.chunks()[0];
282 let items = list.chunk_items(chunk);
283 assert_eq!(items.len(), 2);
284 }
285
286 #[test]
287 fn empty_state_change_no_empty_chunk() {
288 let mut builder = DisplayList::builder();
289
290 builder.set_state(PropertyState {
292 opacity: 0.5,
293 ..PropertyState::default()
294 });
295
296 builder.push(DisplayItem::Draw(DrawCommand::Rect {
297 rect: Rect::new(0.0, 0.0, 100.0, 50.0),
298 color: Color::RED,
299 }));
300
301 let list = builder.finish();
302
303 assert_eq!(list.chunks().len(), 1);
305 assert_eq!(list.chunks()[0].state.opacity, 0.5);
306 }
307}