22//! Data uniform across all covers (the paint) rides the outer `List<Coverage>` so columnar presence holds,
33//! while cover-specific data rides the inner `Item<Cover>`, reusing `ATTR_TRANSFORM` for the stroke-authoring space.
44
5- use crate :: graphic:: { Graphic , is_paint_present} ;
5+ use crate :: graphic:: Graphic ;
6+ use core_types:: Color ;
67use core_types:: graphene_hash:: CacheHash ;
78use core_types:: list:: { ATTR_ALIGN , ATTR_APPEARANCE , ATTR_CAP , ATTR_DASH_OFFSET , ATTR_DASH_PATTERN , ATTR_JOIN , ATTR_JOIN_MITER_LIMIT , ATTR_PAINT , ATTR_TRANSFORM , ATTR_WEIGHT , Item , List } ;
9+ use raster_types:: { CPU , GPU , Raster } ;
810use vector_types:: vector:: style:: { DashPattern , Stroke } ;
11+ use vector_types:: { Gradient , Vector } ;
912
1013/// The geometry-to-region operator a coverage applies before painting:
1114/// the interior of the geometry (fill) or the region swept along its outline (stroke).
@@ -114,18 +117,18 @@ impl Coverage {
114117 }
115118}
116119
117- /// Builds an appearance row, eliding the paint attribute when it draws nothing .
118- fn cover_row ( coverage : Coverage , paint : List < Graphic > ) -> Item < Coverage > {
120+ /// Builds an appearance row, eliding the paint attribute when it is the default none-paint .
121+ fn cover_row ( coverage : Coverage , paint : Graphic ) -> Item < Coverage > {
119122 let mut row = Item :: new_from_element ( coverage) ;
120- if is_paint_present ( & paint) {
123+ if paint != Graphic :: default ( ) {
121124 row. set_attribute ( ATTR_PAINT , paint) ;
122125 }
123126 row
124127}
125128
126129impl Appearance {
127130 /// Creates an appearance holding a single coverage with the given paint.
128- pub fn new_single ( coverage : Coverage , paint : List < Graphic > ) -> Self {
131+ pub fn new_single ( coverage : Coverage , paint : Graphic ) -> Self {
129132 Self ( List :: new_from_item ( cover_row ( coverage, paint) ) )
130133 }
131134
@@ -161,8 +164,8 @@ impl Appearance {
161164 }
162165
163166 /// The paint of the coverage at the given index, or `None` if the paint attribute is absent.
164- pub fn paint_at ( & self , index : usize ) -> Option < & List < Graphic > > {
165- self . 0 . attribute :: < List < Graphic > > ( ATTR_PAINT , index)
167+ pub fn paint_at ( & self , index : usize ) -> Option < & Graphic > {
168+ self . 0 . attribute :: < Graphic > ( ATTR_PAINT , index)
166169 }
167170
168171 /// The index of the first coverage of the given cover in paint order.
@@ -176,15 +179,13 @@ impl Appearance {
176179 }
177180
178181 /// The paint of the first coverage of the given cover, filtered to paint that draws something.
179- pub fn first_paint_of ( & self , cover : Cover ) -> Option < & List < Graphic > > {
180- self . first_index_of ( cover) . and_then ( |index| self . paint_at ( index) ) . filter ( |paint| is_paint_present ( paint) )
182+ pub fn first_paint_of ( & self , cover : Cover ) -> Option < & Graphic > {
183+ self . first_index_of ( cover) . and_then ( |index| self . paint_at ( index) ) . filter ( |paint| ! paint. is_empty ( ) )
181184 }
182185
183186 /// Iterates the coverages in paint order together with their paint, which is `None` when absent or drawing nothing.
184- pub fn covers_with_paints ( & self ) -> impl Iterator < Item = ( & Coverage , Option < & List < Graphic > > ) > {
185- self . covers ( )
186- . enumerate ( )
187- . map ( |( index, coverage) | ( coverage, self . paint_at ( index) . filter ( |paint| is_paint_present ( paint) ) ) )
187+ pub fn covers_with_paints ( & self ) -> impl Iterator < Item = ( & Coverage , Option < & Graphic > ) > {
188+ self . covers ( ) . enumerate ( ) . map ( |( index, coverage) | ( coverage, self . paint_at ( index) . filter ( |paint| !paint. is_empty ( ) ) ) )
188189 }
189190
190191 /// Gathers the renderer's per-item reads in one walk of the coverage list.
@@ -199,7 +200,7 @@ impl Appearance {
199200 }
200201 }
201202
202- let painted = |index| self . paint_at ( index) . filter ( |paint| is_paint_present ( paint) ) ;
203+ let painted = |index| self . paint_at ( index) . filter ( |paint| ! paint. is_empty ( ) ) ;
203204 FillAndStroke {
204205 stroke : first_stroke. map ( |( _, coverage) | coverage. stroke_params ( ) ) ,
205206 fill_paint : first_fill. and_then ( painted) ,
@@ -218,12 +219,12 @@ impl Appearance {
218219 pub fn has_painted_cover ( & self , cover : Cover ) -> bool {
219220 self . covers ( )
220221 . enumerate ( )
221- . any ( |( index, coverage) | coverage. cover ( ) == cover && self . paint_at ( index) . is_some_and ( is_paint_present ) )
222+ . any ( |( index, coverage) | coverage. cover ( ) == cover && self . paint_at ( index) . is_some_and ( |paint| !paint . is_empty ( ) ) )
222223 }
223224
224225 /// Replaces the first coverage of the incoming cover in place (keeping its position in the paint order),
225226 /// or inserts a new row at the requested end of the paint order if none exists.
226- pub fn replace_or_insert ( & mut self , coverage : Coverage , paint : List < Graphic > , placement : CoverPlacement ) {
227+ pub fn replace_or_insert ( & mut self , coverage : Coverage , paint : Graphic , placement : CoverPlacement ) {
227228 if let Some ( index) = self . first_index_of ( coverage. cover ( ) ) {
228229 if let Some ( element) = self . 0 . element_mut ( index) {
229230 * element = coverage;
@@ -245,7 +246,7 @@ impl Appearance {
245246
246247 /// Sets the paint of the first coverage of the given cover, leaving its other parameters untouched.
247248 /// Returns `false` without changing anything if no coverage of that cover exists.
248- pub fn set_paint_of ( & mut self , cover : Cover , paint : List < Graphic > ) -> bool {
249+ pub fn set_paint_of ( & mut self , cover : Cover , paint : Graphic ) -> bool {
249250 let Some ( index) = self . first_index_of ( cover) else { return false } ;
250251 self . 0 . set_attribute ( ATTR_PAINT , index, paint) ;
251252 true
@@ -262,32 +263,117 @@ impl Appearance {
262263#[ derive( Debug , Default ) ]
263264pub struct FillAndStroke < ' a > {
264265 pub stroke : Option < Stroke > ,
265- pub fill_paint : Option < & ' a List < Graphic > > ,
266- pub stroke_paint : Option < & ' a List < Graphic > > ,
266+ pub fill_paint : Option < & ' a Graphic > ,
267+ pub stroke_paint : Option < & ' a Graphic > ,
267268 /// Whether the first stroke coverage sits before the first fill in the paint order, painting below it.
268269 pub stroke_below : bool ,
269270}
270271
271272/// Stamps a coverage into the item's `ATTR_APPEARANCE` cell, creating the attribute if absent.
272273/// The coverage replaces the first same-cover one in place, or lands at the placement end of the paint order.
273- pub fn stamp_coverage < T > ( item : & mut Item < T > , coverage : Coverage , paint : List < Graphic > , placement : CoverPlacement ) {
274+ pub fn stamp_coverage < T > ( item : & mut Item < T > , coverage : Coverage , paint : Graphic , placement : CoverPlacement ) {
274275 item. attribute_mut_or_insert_default :: < Appearance > ( ATTR_APPEARANCE ) . replace_or_insert ( coverage, paint, placement) ;
275276}
276277
278+ // ================
279+ // TRAIT: IntoPaint
280+ // ================
281+
282+ /// Converts the types accepted by a paint input into the canonical `Graphic` stored in the `ATTR_PAINT` attribute.
283+ /// `List<Graphic>` deliberately has no impl: a multi-element paint is a type error.
284+ pub trait IntoPaint : Clone + Send + Sync + Default + std:: fmt:: Debug + PartialEq + CacheHash + ' static {
285+ fn into_paint ( self ) -> Graphic ;
286+ }
287+
288+ impl IntoPaint for Item < Graphic > {
289+ fn into_paint ( self ) -> Graphic {
290+ // Wrapping to keep the record's attributes would nest the paint as a group, changing how it renders
291+ self . into_element ( )
292+ }
293+ }
294+
295+ impl IntoPaint for Item < Vector > {
296+ fn into_paint ( self ) -> Graphic {
297+ Graphic :: VectorList ( List :: new_from_item ( self ) )
298+ }
299+ }
300+
301+ impl IntoPaint for Item < Raster < CPU > > {
302+ fn into_paint ( self ) -> Graphic {
303+ Graphic :: RasterCPUList ( List :: new_from_item ( self ) )
304+ }
305+ }
306+
307+ // No Item<Raster<GPU>> impl: GPU rasters have no Default, which the trait bounds require of the element
308+
309+ impl IntoPaint for Item < Color > {
310+ fn into_paint ( self ) -> Graphic {
311+ Graphic :: ColorList ( List :: new_from_item ( self ) )
312+ }
313+ }
314+
315+ impl IntoPaint for Item < Gradient > {
316+ fn into_paint ( self ) -> Graphic {
317+ Graphic :: GradientList ( List :: new_from_item ( self ) )
318+ }
319+ }
320+
321+ impl IntoPaint for Item < String > {
322+ fn into_paint ( self ) -> Graphic {
323+ Graphic :: TextList ( List :: new_from_item ( self ) )
324+ }
325+ }
326+
327+ impl IntoPaint for List < Vector > {
328+ fn into_paint ( self ) -> Graphic {
329+ Graphic :: VectorList ( self )
330+ }
331+ }
332+
333+ impl IntoPaint for List < Raster < CPU > > {
334+ fn into_paint ( self ) -> Graphic {
335+ Graphic :: RasterCPUList ( self )
336+ }
337+ }
338+
339+ impl IntoPaint for List < Raster < GPU > > {
340+ fn into_paint ( self ) -> Graphic {
341+ Graphic :: RasterGPUList ( self )
342+ }
343+ }
344+
345+ impl IntoPaint for List < Color > {
346+ fn into_paint ( self ) -> Graphic {
347+ Graphic :: ColorList ( self )
348+ }
349+ }
350+
351+ impl IntoPaint for List < Gradient > {
352+ fn into_paint ( self ) -> Graphic {
353+ Graphic :: GradientList ( self )
354+ }
355+ }
356+
357+ impl IntoPaint for List < String > {
358+ fn into_paint ( self ) -> Graphic {
359+ Graphic :: TextList ( self )
360+ }
361+ }
362+
277363#[ cfg( test) ]
278364mod tests {
279365 use super :: * ;
280- use core_types:: Color ;
366+ use core_types:: list :: ATTR_POSITION ;
281367 use glam:: { DAffine2 , DVec2 } ;
282368 use vector_types:: vector:: style:: { StrokeAlign , StrokeCap , StrokeJoin } ;
283369
284- fn solid_paint ( color : Color ) -> List < Graphic > {
285- List :: new_from_element ( Graphic :: ColorList ( List :: new_from_element ( color) ) )
370+ fn solid_paint ( color : Color ) -> Graphic {
371+ Graphic :: ColorList ( List :: new_from_element ( color) )
286372 }
287373
288374 fn paint_color ( appearance : & Appearance , index : usize ) -> Option < Color > {
289375 let paint = appearance. paint_at ( index) ?;
290- let Some ( Graphic :: ColorList ( colors) ) = paint. element ( 0 ) else { return None } ;
376+ let Graphic :: ColorList ( colors) = paint else { return None } ;
291377 colors. element ( 0 ) . copied ( )
292378 }
293379
@@ -374,7 +460,7 @@ mod tests {
374460 #[ test]
375461 fn painted_cover_distinguishes_none_paint_from_absence ( ) {
376462 let mut appearance = Appearance :: default ( ) ;
377- appearance. replace_or_insert ( Coverage :: new_fill ( ) , List :: new_from_element ( Graphic :: None ) , CoverPlacement :: Above ) ;
463+ appearance. replace_or_insert ( Coverage :: new_fill ( ) , Graphic :: None , CoverPlacement :: Above ) ;
378464
379465 assert ! ( appearance. has_cover( Cover :: Fill ) , "a none-painted coverage still exists" ) ;
380466 assert ! ( !appearance. has_painted_cover( Cover :: Fill ) , "a none-painted coverage draws nothing" ) ;
@@ -384,4 +470,24 @@ mod tests {
384470 appearance. replace_or_insert ( Coverage :: new_fill ( ) , solid_paint ( Color :: RED ) , CoverPlacement :: Above ) ;
385471 assert ! ( appearance. has_painted_cover( Cover :: Fill ) ) ;
386472 }
473+
474+ #[ test]
475+ fn list_paint_becomes_one_graphic_holding_every_element ( ) {
476+ let mut colors = List :: new_from_element ( Color :: RED ) ;
477+ colors. push ( Item :: new_from_element ( Color :: BLUE ) ) ;
478+
479+ let paint = colors. into_paint ( ) ;
480+ let Graphic :: ColorList ( inner) = & paint else { panic ! ( "expected a color graphic" ) } ;
481+ assert_eq ! ( inner. len( ) , 2 , "a list paint is one graphic holding all its elements" ) ;
482+ }
483+
484+ #[ test]
485+ fn item_paint_keeps_its_attributes_on_the_inner_row ( ) {
486+ let color = Item :: new_from_element ( Color :: RED ) . with_attribute ( ATTR_POSITION , 0.25_f64 ) ;
487+
488+ let paint = color. into_paint ( ) ;
489+ let Graphic :: ColorList ( inner) = & paint else { panic ! ( "expected a color graphic" ) } ;
490+ assert_eq ! ( inner. len( ) , 1 ) ;
491+ assert_eq ! ( inner. attribute:: <f64 >( ATTR_POSITION , 0 ) , Some ( & 0.25 ) ) ;
492+ }
387493}
0 commit comments