@@ -59,12 +59,78 @@ impl Folder {
5959 }
6060
6161 pub fn remove_layer ( & mut self , id : LayerId ) -> Result < ( ) , DocumentError > {
62- let pos = self . layer_ids . iter ( ) . position ( |x| * x == id ) . ok_or ( DocumentError :: LayerNotFound ) ?;
62+ let pos = self . position_of_layer ( id ) ?;
6363 self . layers . remove ( pos) ;
6464 self . layer_ids . remove ( pos) ;
6565 Ok ( ( ) )
6666 }
6767
68+ pub fn reorder_layers ( & mut self , source_ids : Vec < LayerId > , target_id : LayerId ) -> Result < ( ) , DocumentError > {
69+ let source_pos = self . position_of_layer ( source_ids[ 0 ] ) ?;
70+ let source_pos_end = source_pos + source_ids. len ( ) - 1 ;
71+ let target_pos = self . position_of_layer ( target_id) ?;
72+
73+ let mut last_pos = source_pos;
74+ for layer_id in & source_ids[ 1 ..] {
75+ let layer_pos = self . position_of_layer ( * layer_id) ?;
76+ if ( layer_pos as i32 - last_pos as i32 ) . abs ( ) > 1 {
77+ // Selection is not contiguous
78+ return Err ( DocumentError :: NonReorderableSelection ) ;
79+ }
80+ last_pos = layer_pos;
81+ }
82+
83+ if source_pos < target_pos {
84+ // Moving layers up the hierarchy
85+
86+ // Prevent shifting past end
87+ if source_pos_end + 1 >= self . layers . len ( ) {
88+ return Err ( DocumentError :: NonReorderableSelection ) ;
89+ }
90+
91+ fn reorder_up < T > ( arr : & mut Vec < T > , source_pos : usize , source_pos_end : usize , target_pos : usize )
92+ where
93+ T : Clone ,
94+ {
95+ * arr = [
96+ & arr[ 0 ..source_pos] , // Elements before selection
97+ & arr[ source_pos_end + 1 ..=target_pos] , // Elements between selection end and target
98+ & arr[ source_pos..=source_pos_end] , // Selection itself
99+ & arr[ target_pos + 1 ..] , // Elements before target
100+ ]
101+ . concat ( ) ;
102+ }
103+
104+ reorder_up ( & mut self . layers , source_pos, source_pos_end, target_pos) ;
105+ reorder_up ( & mut self . layer_ids , source_pos, source_pos_end, target_pos) ;
106+ } else {
107+ // Moving layers down the hierarchy
108+
109+ // Prevent shifting past end
110+ if source_pos == 0 {
111+ return Err ( DocumentError :: NonReorderableSelection ) ;
112+ }
113+
114+ fn reorder_down < T > ( arr : & mut Vec < T > , source_pos : usize , source_pos_end : usize , target_pos : usize )
115+ where
116+ T : Clone ,
117+ {
118+ * arr = [
119+ & arr[ 0 ..target_pos] , // Elements before target
120+ & arr[ source_pos..=source_pos_end] , // Selection itself
121+ & arr[ target_pos..source_pos] , // Elements between selection and target
122+ & arr[ source_pos_end + 1 ..] , // Elements before selection
123+ ]
124+ . concat ( ) ;
125+ }
126+
127+ reorder_down ( & mut self . layers , source_pos, source_pos_end, target_pos) ;
128+ reorder_down ( & mut self . layer_ids , source_pos, source_pos_end, target_pos) ;
129+ }
130+
131+ Ok ( ( ) )
132+ }
133+
68134 /// Returns a list of layers in the folder
69135 pub fn list_layers ( & self ) -> & [ LayerId ] {
70136 self . layer_ids . as_slice ( )
@@ -79,15 +145,19 @@ impl Folder {
79145 }
80146
81147 pub fn layer ( & self , id : LayerId ) -> Option < & Layer > {
82- let pos = self . layer_ids . iter ( ) . position ( |x| * x == id ) ?;
148+ let pos = self . position_of_layer ( id ) . ok ( ) ?;
83149 Some ( & self . layers [ pos] )
84150 }
85151
86152 pub fn layer_mut ( & mut self , id : LayerId ) -> Option < & mut Layer > {
87- let pos = self . layer_ids . iter ( ) . position ( |x| * x == id ) ?;
153+ let pos = self . position_of_layer ( id ) . ok ( ) ?;
88154 Some ( & mut self . layers [ pos] )
89155 }
90156
157+ pub fn position_of_layer ( & self , layer_id : LayerId ) -> Result < usize , DocumentError > {
158+ self . layer_ids . iter ( ) . position ( |x| * x == layer_id) . ok_or ( DocumentError :: LayerNotFound )
159+ }
160+
91161 pub fn folder ( & self , id : LayerId ) -> Option < & Folder > {
92162 match self . layer ( id) {
93163 Some ( Layer {
@@ -143,3 +213,144 @@ impl Default for Folder {
143213 }
144214 }
145215}
216+
217+ #[ cfg( test) ]
218+ mod test {
219+ use glam:: { DAffine2 , DVec2 } ;
220+
221+ use crate :: layers:: { style:: PathStyle , Ellipse , Layer , LayerDataTypes , Line , PolyLine , Rect , Shape } ;
222+
223+ use super :: Folder ;
224+
225+ #[ test]
226+ fn reorder_layers ( ) {
227+ let mut folder = Folder :: default ( ) ;
228+
229+ let identity_transform = DAffine2 :: IDENTITY . to_cols_array ( ) ;
230+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Shape ( Shape :: new ( true , 3 ) ) , identity_transform, PathStyle :: default ( ) ) , 0 ) ;
231+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Rect ( Rect :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 1 ) ;
232+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Ellipse ( Ellipse :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 2 ) ;
233+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Line ( Line :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 3 ) ;
234+ folder. add_layer (
235+ Layer :: new ( LayerDataTypes :: PolyLine ( PolyLine :: new ( vec ! [ DVec2 :: ZERO , DVec2 :: ONE ] ) ) , identity_transform, PathStyle :: default ( ) ) ,
236+ 4 ,
237+ ) ;
238+
239+ assert_eq ! ( folder. layer_ids[ 0 ] , 0 ) ;
240+ assert_eq ! ( folder. layer_ids[ 1 ] , 1 ) ;
241+ assert_eq ! ( folder. layer_ids[ 2 ] , 2 ) ;
242+ assert_eq ! ( folder. layer_ids[ 3 ] , 3 ) ;
243+ assert_eq ! ( folder. layer_ids[ 4 ] , 4 ) ;
244+
245+ assert ! ( matches!( folder. layer( 0 ) . unwrap( ) . data, LayerDataTypes :: Shape ( _) ) ) ;
246+ assert ! ( matches!( folder. layer( 1 ) . unwrap( ) . data, LayerDataTypes :: Rect ( _) ) ) ;
247+ assert ! ( matches!( folder. layer( 2 ) . unwrap( ) . data, LayerDataTypes :: Ellipse ( _) ) ) ;
248+ assert ! ( matches!( folder. layer( 3 ) . unwrap( ) . data, LayerDataTypes :: Line ( _) ) ) ;
249+ assert ! ( matches!( folder. layer( 4 ) . unwrap( ) . data, LayerDataTypes :: PolyLine ( _) ) ) ;
250+
251+ assert_eq ! ( folder. layer_ids. len( ) , 5 ) ;
252+ assert_eq ! ( folder. layers. len( ) , 5 ) ;
253+
254+ folder. reorder_layers ( vec ! [ 0 , 1 ] , 2 ) . unwrap ( ) ;
255+
256+ assert_eq ! ( folder. layer_ids[ 0 ] , 2 ) ;
257+ // Moved layers
258+ assert_eq ! ( folder. layer_ids[ 1 ] , 0 ) ;
259+ assert_eq ! ( folder. layer_ids[ 2 ] , 1 ) ;
260+
261+ assert_eq ! ( folder. layer_ids[ 3 ] , 3 ) ;
262+ assert_eq ! ( folder. layer_ids[ 4 ] , 4 ) ;
263+
264+ assert ! ( matches!( folder. layer( 2 ) . unwrap( ) . data, LayerDataTypes :: Ellipse ( _) ) ) ;
265+ // Moved layers
266+ assert ! ( matches!( folder. layer( 0 ) . unwrap( ) . data, LayerDataTypes :: Shape ( _) ) ) ;
267+ assert ! ( matches!( folder. layer( 1 ) . unwrap( ) . data, LayerDataTypes :: Rect ( _) ) ) ;
268+
269+ assert ! ( matches!( folder. layer( 3 ) . unwrap( ) . data, LayerDataTypes :: Line ( _) ) ) ;
270+ assert ! ( matches!( folder. layer( 4 ) . unwrap( ) . data, LayerDataTypes :: PolyLine ( _) ) ) ;
271+
272+ assert_eq ! ( folder. layer_ids. len( ) , 5 ) ;
273+ assert_eq ! ( folder. layers. len( ) , 5 ) ;
274+ }
275+
276+ #[ test]
277+ fn reorder_layer_to_top ( ) {
278+ let mut folder = Folder :: default ( ) ;
279+
280+ let identity_transform = DAffine2 :: IDENTITY . to_cols_array ( ) ;
281+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Shape ( Shape :: new ( true , 3 ) ) , identity_transform, PathStyle :: default ( ) ) , 0 ) ;
282+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Rect ( Rect :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 1 ) ;
283+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Ellipse ( Ellipse :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 2 ) ;
284+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Line ( Line :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 3 ) ;
285+
286+ assert_eq ! ( folder. layer_ids[ 0 ] , 0 ) ;
287+ assert_eq ! ( folder. layer_ids[ 1 ] , 1 ) ;
288+ assert_eq ! ( folder. layer_ids[ 2 ] , 2 ) ;
289+ assert_eq ! ( folder. layer_ids[ 3 ] , 3 ) ;
290+
291+ assert ! ( matches!( folder. layer( 0 ) . unwrap( ) . data, LayerDataTypes :: Shape ( _) ) ) ;
292+ assert ! ( matches!( folder. layer( 1 ) . unwrap( ) . data, LayerDataTypes :: Rect ( _) ) ) ;
293+ assert ! ( matches!( folder. layer( 2 ) . unwrap( ) . data, LayerDataTypes :: Ellipse ( _) ) ) ;
294+ assert ! ( matches!( folder. layer( 3 ) . unwrap( ) . data, LayerDataTypes :: Line ( _) ) ) ;
295+
296+ assert_eq ! ( folder. layer_ids. len( ) , 4 ) ;
297+ assert_eq ! ( folder. layers. len( ) , 4 ) ;
298+
299+ folder. reorder_layers ( vec ! [ 1 ] , 3 ) . unwrap ( ) ;
300+
301+ assert_eq ! ( folder. layer_ids[ 0 ] , 0 ) ;
302+ assert_eq ! ( folder. layer_ids[ 1 ] , 2 ) ;
303+ assert_eq ! ( folder. layer_ids[ 2 ] , 3 ) ;
304+ // Moved layer
305+ assert_eq ! ( folder. layer_ids[ 3 ] , 1 ) ;
306+
307+ assert ! ( matches!( folder. layer( 0 ) . unwrap( ) . data, LayerDataTypes :: Shape ( _) ) ) ;
308+ assert ! ( matches!( folder. layer( 2 ) . unwrap( ) . data, LayerDataTypes :: Ellipse ( _) ) ) ;
309+ assert ! ( matches!( folder. layer( 3 ) . unwrap( ) . data, LayerDataTypes :: Line ( _) ) ) ;
310+ // Moved layer
311+ assert ! ( matches!( folder. layer( 1 ) . unwrap( ) . data, LayerDataTypes :: Rect ( _) ) ) ;
312+
313+ assert_eq ! ( folder. layer_ids. len( ) , 4 ) ;
314+ assert_eq ! ( folder. layers. len( ) , 4 ) ;
315+ }
316+
317+ #[ test]
318+ fn reorder_non_contiguous_selection ( ) {
319+ let mut folder = Folder :: default ( ) ;
320+
321+ let identity_transform = DAffine2 :: IDENTITY . to_cols_array ( ) ;
322+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Shape ( Shape :: new ( true , 3 ) ) , identity_transform, PathStyle :: default ( ) ) , 0 ) ;
323+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Rect ( Rect :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 1 ) ;
324+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Ellipse ( Ellipse :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 2 ) ;
325+ folder. add_layer ( Layer :: new ( LayerDataTypes :: Line ( Line :: default ( ) ) , identity_transform, PathStyle :: default ( ) ) , 3 ) ;
326+
327+ assert_eq ! ( folder. layer_ids[ 0 ] , 0 ) ;
328+ assert_eq ! ( folder. layer_ids[ 1 ] , 1 ) ;
329+ assert_eq ! ( folder. layer_ids[ 2 ] , 2 ) ;
330+ assert_eq ! ( folder. layer_ids[ 3 ] , 3 ) ;
331+
332+ assert ! ( matches!( folder. layer( 0 ) . unwrap( ) . data, LayerDataTypes :: Shape ( _) ) ) ;
333+ assert ! ( matches!( folder. layer( 1 ) . unwrap( ) . data, LayerDataTypes :: Rect ( _) ) ) ;
334+ assert ! ( matches!( folder. layer( 2 ) . unwrap( ) . data, LayerDataTypes :: Ellipse ( _) ) ) ;
335+ assert ! ( matches!( folder. layer( 3 ) . unwrap( ) . data, LayerDataTypes :: Line ( _) ) ) ;
336+
337+ assert_eq ! ( folder. layer_ids. len( ) , 4 ) ;
338+ assert_eq ! ( folder. layers. len( ) , 4 ) ;
339+
340+ folder. reorder_layers ( vec ! [ 0 , 2 ] , 3 ) . expect_err ( "Non-contiguous selections can't be reordered" ) ;
341+
342+ // Expect identical state
343+ assert_eq ! ( folder. layer_ids[ 0 ] , 0 ) ;
344+ assert_eq ! ( folder. layer_ids[ 1 ] , 1 ) ;
345+ assert_eq ! ( folder. layer_ids[ 2 ] , 2 ) ;
346+ assert_eq ! ( folder. layer_ids[ 3 ] , 3 ) ;
347+
348+ assert ! ( matches!( folder. layer( 0 ) . unwrap( ) . data, LayerDataTypes :: Shape ( _) ) ) ;
349+ assert ! ( matches!( folder. layer( 1 ) . unwrap( ) . data, LayerDataTypes :: Rect ( _) ) ) ;
350+ assert ! ( matches!( folder. layer( 2 ) . unwrap( ) . data, LayerDataTypes :: Ellipse ( _) ) ) ;
351+ assert ! ( matches!( folder. layer( 3 ) . unwrap( ) . data, LayerDataTypes :: Line ( _) ) ) ;
352+
353+ assert_eq ! ( folder. layer_ids. len( ) , 4 ) ;
354+ assert_eq ! ( folder. layers. len( ) , 4 ) ;
355+ }
356+ }
0 commit comments