Skip to content

Commit bd9d763

Browse files
committed
Add shared Textures cache, refactor Widget::draw()
This shared cache allows textures to be loaded from arbitrary image files and cached into memory, using their file paths on disk as keys.
1 parent 7690e16 commit bd9d763

File tree

3 files changed

+89
-29
lines changed

3 files changed

+89
-29
lines changed

src/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use sdl2::event::Event;
55
use sdl2::video::Window;
66
use sdl2::Sdl;
77

8-
use crate::widget::{Widget, Widgets};
8+
use crate::widget::{Textures, Widget, Widgets};
99

1010
/// An action to take upon receiving an SDL event.
1111
#[derive(Clone, Copy, Debug)]
@@ -59,7 +59,7 @@ impl<W: Widget, S: State<W>> App<W, S> {
5959
let mut canvas = window.into_canvas().accelerated().present_vsync().build()?;
6060

6161
let texture_creator = canvas.texture_creator();
62-
let mut widgets = Widgets::new(self.root_widget, &texture_creator);
62+
let mut widgets = Widgets::new(self.root_widget, Textures::new(&texture_creator));
6363

6464
// Build and populate the `Widgets` cache.
6565
self.state.initialize(&mut widgets)?;

src/menu.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ use sdl2::event::Event;
44
use sdl2::keyboard::Keycode;
55
use sdl2::pixels::Color;
66
use sdl2::rect::Rect;
7-
use sdl2::render::{Canvas, Texture};
8-
use sdl2::video::Window;
7+
use sdl2::render::Texture;
98

109
use crate::app::{Action, State};
11-
use crate::widget::{Properties, Widget, Widgets};
10+
use crate::widget::{Context, Properties, Widget, Widgets};
1211

1312
const BACKGROUND_COLOR: Color = Color::RGB(8, 76, 97);
1413
const TILE_COLOR: Color = Color::RGB(23, 126, 127);
@@ -86,11 +85,11 @@ impl Widget for WidgetKind {
8685
}
8786
}
8887

89-
fn draw(&self, canvas: &mut Canvas<Window>, texture: &mut Texture) -> anyhow::Result<()> {
88+
fn draw(&self, ctx: &mut Context, target: &mut Texture) -> anyhow::Result<()> {
9089
match *self {
9190
WidgetKind::Root { ref properties } => {
9291
let Properties { color, .. } = properties;
93-
canvas.with_texture_canvas(texture, |texture| {
92+
ctx.canvas.with_texture_canvas(target, |texture| {
9493
texture.set_draw_color(*color);
9594
texture.clear();
9695
})?
@@ -99,7 +98,7 @@ impl Widget for WidgetKind {
9998
let (x, y) = properties.origin;
10099
let (width, height) = properties.bounds;
101100
let rect = Rect::new(x, y, width, height);
102-
canvas.with_texture_canvas(texture, |texture| {
101+
ctx.canvas.with_texture_canvas(target, |texture| {
103102
texture.set_draw_color(properties.color);
104103
texture.draw_rect(rect).unwrap();
105104
texture.clear();

src/widget.rs

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::cell::{Ref, RefCell, RefMut};
44
use std::fmt::{self, Debug, Formatter};
5+
use std::path::PathBuf;
56

67
use anyhow::Error;
78
use fnv::FnvHashMap as HashMap;
@@ -19,7 +20,7 @@ pub trait Widget {
1920
fn properties_mut(&mut self) -> &mut Properties;
2021

2122
/// 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<()>;
2324
}
2425

2526
/// Contains properties common to all widgets.
@@ -33,16 +34,32 @@ pub struct Properties {
3334
pub color: Color,
3435
}
3536

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+
3653
/// A shared cache of drawable UI widgets.
3754
pub struct Widgets<'tc, W> {
3855
cache: HashMap<WidgetId, CacheEntry<'tc, W>>,
3956
next_id: u32,
40-
textures: &'tc TextureCreator<WindowContext>,
57+
textures: Textures<'tc>,
4158
}
4259

4360
impl<'tc, W: Widget> Widgets<'tc, W> {
4461
/// 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 {
4663
let mut cache = HashMap::default();
4764
cache.insert(WidgetId(0), CacheEntry::new(root_widget, WidgetId(0)));
4865
Widgets {
@@ -116,7 +133,7 @@ impl<'tc, W: Widget> Widgets<'tc, W> {
116133
}
117134

118135
/// 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<()> {
120137
canvas.set_draw_color(Color::RGBA(255, 255, 255, 255));
121138
canvas.clear();
122139

@@ -126,23 +143,26 @@ impl<'tc, W: Widget> Widgets<'tc, W> {
126143
Ok(())
127144
}
128145

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+
}
142162

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)?;
146166
}
147167
}
148168

@@ -166,7 +186,6 @@ struct CacheEntry<'tc, W> {
166186
}
167187

168188
impl<'tc, W> CacheEntry<'tc, W> {
169-
/// Creates and returns a new `CacheEntry` with an empty texture.
170189
fn new(widget: W, parent: WidgetId) -> Self {
171190
CacheEntry {
172191
widget: RefCell::new(widget),
@@ -223,3 +242,45 @@ impl<'tc> Debug for WidgetTexture<'tc> {
223242
.finish()
224243
}
225244
}
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

Comments
 (0)