2
2
3
3
use std:: cell:: { Ref , RefCell , RefMut } ;
4
4
use std:: fmt:: { self , Debug , Formatter } ;
5
+ use std:: path:: PathBuf ;
5
6
6
7
use anyhow:: Error ;
7
8
use fnv:: FnvHashMap as HashMap ;
@@ -19,7 +20,7 @@ pub trait Widget {
19
20
fn properties_mut ( & mut self ) -> & mut Properties ;
20
21
21
22
/// Renders the widget into the given [`Texture`](sdl2::render::Texture).
22
- fn draw ( & self , canvas : & mut Canvas < Window > , texture : & mut Texture ) -> anyhow:: Result < ( ) > ;
23
+ fn draw ( & self , ctx : & mut Context , target : & mut Texture ) -> anyhow:: Result < ( ) > ;
23
24
}
24
25
25
26
/// Contains properties common to all widgets.
@@ -33,16 +34,32 @@ pub struct Properties {
33
34
pub color : Color ,
34
35
}
35
36
37
+ /// A shared context passed to every [`Widget::draw()`] call.
38
+ pub struct Context < ' a , ' tc > {
39
+ /// Handle to the window canvas.
40
+ pub canvas : & ' a mut Canvas < Window > ,
41
+ /// Shared texture cache.
42
+ pub textures : & ' a mut Textures < ' tc > ,
43
+ }
44
+
45
+ impl < ' a , ' tc > Debug for Context < ' a , ' tc > {
46
+ fn fmt ( & self , f : & mut Formatter ) -> fmt:: Result {
47
+ f. debug_struct ( stringify ! ( Context ) )
48
+ . field ( "textures" , & self . textures )
49
+ . finish ( )
50
+ }
51
+ }
52
+
36
53
/// A shared cache of drawable UI widgets.
37
54
pub struct Widgets < ' tc , W > {
38
55
cache : HashMap < WidgetId , CacheEntry < ' tc , W > > ,
39
56
next_id : u32 ,
40
- textures : & ' tc TextureCreator < WindowContext > ,
57
+ textures : Textures < ' tc > ,
41
58
}
42
59
43
60
impl < ' tc , W : Widget > Widgets < ' tc , W > {
44
61
/// Creates a new [`Widgets`] cache anchored relative to the given `root_widget`.
45
- pub ( crate ) fn new ( root_widget : W , textures : & ' tc TextureCreator < WindowContext > ) -> Self {
62
+ pub ( crate ) fn new ( root_widget : W , textures : Textures < ' tc > ) -> Self {
46
63
let mut cache = HashMap :: default ( ) ;
47
64
cache. insert ( WidgetId ( 0 ) , CacheEntry :: new ( root_widget, WidgetId ( 0 ) ) ) ;
48
65
Widgets {
@@ -116,7 +133,7 @@ impl<'tc, W: Widget> Widgets<'tc, W> {
116
133
}
117
134
118
135
/// Renders all the widgets in the cache to the canvas.
119
- pub fn draw ( & self , canvas : & mut Canvas < Window > ) -> anyhow:: Result < ( ) > {
136
+ pub fn draw ( & mut self , canvas : & mut Canvas < Window > ) -> anyhow:: Result < ( ) > {
120
137
canvas. set_draw_color ( Color :: RGBA ( 255 , 255 , 255 , 255 ) ) ;
121
138
canvas. clear ( ) ;
122
139
@@ -126,23 +143,26 @@ impl<'tc, W: Widget> Widgets<'tc, W> {
126
143
Ok ( ( ) )
127
144
}
128
145
129
- fn draw_widget ( & self , id : WidgetId , canvas : & mut Canvas < Window > ) -> anyhow:: Result < ( ) > {
130
- let widget = self . get ( id) ;
131
- let ( x, y) = widget. properties ( ) . origin ;
132
- let ( width, height) = widget. properties ( ) . bounds ;
133
-
134
- // Retrieve base widget texture, resizing if bounds have changed.
135
- let mut widget_texture = self . cache [ & id] . texture . borrow_mut ( ) ;
136
- let texture = widget_texture. create_or_resize ( & self . textures , width, height) ?;
137
-
138
- // Draw the widget to the texture and copy the texture to the canvas.
139
- widget. draw ( canvas, texture) ?;
140
- let dst = Rect :: new ( x, y, width, height) ;
141
- canvas. copy ( & * texture, None , dst) . map_err ( Error :: msg) ?;
146
+ fn draw_widget ( & mut self , id : WidgetId , canvas : & mut Canvas < Window > ) -> anyhow:: Result < ( ) > {
147
+ {
148
+ let widget = self . cache [ & id] . widget . borrow ( ) ;
149
+ let ( x, y) = widget. properties ( ) . origin ;
150
+ let ( width, height) = widget. properties ( ) . bounds ;
151
+
152
+ // Retrieve base widget texture, resizing if bounds have changed.
153
+ let textures = & mut self . textures ;
154
+ let mut widget_texture = self . cache [ & id] . texture . borrow_mut ( ) ;
155
+ let target = widget_texture. create_or_resize ( textures. creator , width, height) ?;
156
+
157
+ // Draw the widget to the target texture and copy it to the canvas.
158
+ widget. draw ( & mut Context { canvas, textures } , target) ?;
159
+ let dst = Rect :: new ( x, y, width, height) ;
160
+ canvas. copy ( target, None , dst) . map_err ( Error :: msg) ?;
161
+ }
142
162
143
- for child_id in self . get_children_of ( id) {
144
- if * child_id != id {
145
- self . draw_widget ( * child_id, canvas) ?;
163
+ for child_id in self . get_children_of ( id) . to_vec ( ) {
164
+ if child_id != id {
165
+ self . draw_widget ( child_id, canvas) ?;
146
166
}
147
167
}
148
168
@@ -166,7 +186,6 @@ struct CacheEntry<'tc, W> {
166
186
}
167
187
168
188
impl < ' tc , W > CacheEntry < ' tc , W > {
169
- /// Creates and returns a new `CacheEntry` with an empty texture.
170
189
fn new ( widget : W , parent : WidgetId ) -> Self {
171
190
CacheEntry {
172
191
widget : RefCell :: new ( widget) ,
@@ -223,3 +242,45 @@ impl<'tc> Debug for WidgetTexture<'tc> {
223
242
. finish ( )
224
243
}
225
244
}
245
+
246
+ /// A shared mechanism for caching textures.
247
+ ///
248
+ /// This struct is accessible from the shared [`Context`] passed to every [`Widget::draw()`] call.
249
+ pub struct Textures < ' tc > {
250
+ creator : & ' tc TextureCreator < WindowContext > ,
251
+ cache : HashMap < PathBuf , Texture < ' tc > > ,
252
+ }
253
+
254
+ impl < ' tc > Textures < ' tc > {
255
+ pub ( crate ) fn new ( creator : & ' tc TextureCreator < WindowContext > ) -> Self {
256
+ Textures {
257
+ creator,
258
+ cache : HashMap :: default ( ) ,
259
+ }
260
+ }
261
+
262
+ /// Returns a [`Texture`](sdl2::render::Texture) from an image file, caching it in memory.
263
+ ///
264
+ /// Returns `Err` if the image file could not be found at the destination `path`, or if SDL was
265
+ /// unable to load the file successfully.
266
+ pub fn load_from < P : Into < PathBuf > > ( & mut self , path : P ) -> anyhow:: Result < & Texture < ' tc > > {
267
+ use sdl2:: image:: LoadTexture ;
268
+ use std:: collections:: hash_map:: Entry ;
269
+
270
+ match self . cache . entry ( path. into ( ) ) {
271
+ Entry :: Occupied ( e) => Ok ( e. into_mut ( ) ) ,
272
+ Entry :: Vacant ( e) => {
273
+ let texture = self . creator . load_texture ( e. key ( ) ) . map_err ( Error :: msg) ?;
274
+ Ok ( e. insert ( texture) )
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ impl < ' tc > Debug for Textures < ' tc > {
281
+ fn fmt ( & self , f : & mut Formatter ) -> fmt:: Result {
282
+ f. debug_struct ( stringify ! ( Textures ) )
283
+ . field ( "cache" , & self . cache . keys ( ) )
284
+ . finish ( )
285
+ }
286
+ }
0 commit comments