1use crate::{
4 error::{Error, Result},
5 gui::theme::{FontId, FontSrc},
6 prelude::*,
7 renderer::{RendererSettings, Rendering},
8};
9use anyhow::{anyhow, Context};
10use log::{debug, warn};
11use lru::LruCache;
12use once_cell::sync::Lazy;
13use sdl2::{
14 audio::{AudioQueue, AudioSpecDesired},
15 controller::GameController,
16 gfx::primitives::{DrawRenderer, ToColor},
17 mouse::{Cursor, SystemCursor},
18 pixels::{Color as SdlColor, PixelFormatEnum as SdlPixelFormat},
19 rect::{Point as SdlPoint, Rect as SdlRect},
20 render::{BlendMode as SdlBlendMode, Canvas, TextureQuery},
21 rwops::RWops,
22 ttf::{Font as SdlFont, FontStyle as SdlFontStyle, Sdl2TtfContext},
23 video::Window,
24 EventPump, GameControllerSubsystem, Sdl,
25};
26use std::{collections::HashMap, fmt};
27use texture::RendererTexture;
28use window::{TextCacheKey, WindowCanvas};
29
30#[allow(clippy::expect_used)]
31static TTF: Lazy<Sdl2TtfContext> = Lazy::new(|| sdl2::ttf::init().expect("sdl2_ttf initialized"));
32
33pub use audio::{AudioDevice, AudioFormatNum};
34
35pub mod audio;
36mod event;
37mod texture;
38mod window;
39
40pub(crate) struct Renderer {
42 context: Sdl,
43 event_pump: EventPump,
44 audio_device: AudioQueue<f32>,
45 controller_subsys: GameControllerSubsystem,
46 controllers: HashMap<ControllerId, GameController>,
47 title: String,
48 settings: RendererSettings,
49 cursor: Option<Cursor>,
50 blend_mode: SdlBlendMode,
51 current_font: FontId,
52 font_size: u16,
53 font_style: SdlFontStyle,
54 primary_window_id: WindowId,
55 window_target: WindowId,
56 texture_target: Option<TextureId>,
57 windows: HashMap<WindowId, WindowCanvas>,
58 next_texture_id: usize,
59 font_data: LruCache<FontId, Font>,
60 loaded_fonts: LruCache<(FontId, u16), SdlFont<'static, 'static>>,
61}
62
63impl Renderer {
64 fn update_canvas<F>(&mut self, f: F) -> Result<()>
66 where
67 F: FnOnce(&mut Canvas<Window>) -> Result<()>,
68 {
69 if let Some(texture_id) = self.texture_target {
70 let window = self
71 .windows
72 .values_mut()
73 .find(|w| w.textures.contains_key(&texture_id));
74 if let Some(window) = window {
75 let texture = window
77 .textures
78 .get(&texture_id)
79 .ok_or_else(|| anyhow!(Error::InvalidTexture(texture_id)))?;
80 let mut result = Ok(());
81 window
82 .canvas
83 .with_texture_canvas(&mut texture.borrow_mut(), |canvas| {
84 result = f(canvas);
85 })
86 .with_context(|| format!("failed to update texture target {texture_id}"))?;
87 result
88 } else {
89 Err(Error::InvalidTexture(texture_id).into())
90 }
91 } else {
92 f(self.canvas_mut()?)
93 }
94 }
95
96 fn load_font(&mut self) -> Result<bool> {
99 let key = (self.current_font, self.font_size);
100 if self.loaded_fonts.contains(&key) {
101 return Ok(false);
102 }
103
104 let font_data = self
105 .font_data
106 .get(&self.current_font)
107 .ok_or_else(|| anyhow!("invalid current font"))?;
108 let loaded_font = match font_data.source() {
109 FontSrc::None => return Err(anyhow!("Must provide a font data source")),
110 FontSrc::Bytes(bytes) => {
111 let rwops = RWops::from_bytes(bytes).map_err(Error::Renderer)?;
112 TTF.load_font_from_rwops(rwops, self.font_size)
113 .map_err(Error::Renderer)?
114 }
115 FontSrc::Path(ref path) => TTF
116 .load_font(path, self.font_size)
117 .map_err(Error::Renderer)?,
118 };
119 self.loaded_fonts.put(key, loaded_font);
120 Ok(true)
121 }
122
123 #[inline]
125 fn font(&self) -> &SdlFont<'static, 'static> {
126 #[allow(clippy::expect_used)]
127 self.loaded_fonts
128 .peek(&(self.current_font, self.font_size))
129 .expect("valid font")
130 }
131
132 #[inline]
134 fn font_mut(&mut self) -> &mut SdlFont<'static, 'static> {
135 #[allow(clippy::expect_used)]
136 self.loaded_fonts
137 .get_mut(&(self.current_font, self.font_size))
138 .expect("valid font")
139 }
140}
141
142impl Rendering for Renderer {
143 #[inline]
145 fn new(mut s: RendererSettings) -> Result<Self> {
146 debug!("Initializing SDLRenderer");
147
148 let context = sdl2::init().map_err(Error::Renderer)?;
149 let event_pump = context.event_pump().map_err(Error::Renderer)?;
150
151 let title = s.title.clone();
152 let primary_window = WindowCanvas::new(&context, &mut s)?;
153 let cursor_result = Cursor::from_system(SystemCursor::Arrow).map_err(Error::Renderer);
154 let cursor = match cursor_result {
155 Ok(c) => Some(c),
156 Err(_) => None,
157 };
158 if let Ok(cursor) = Cursor::from_system(SystemCursor::Arrow) {
159 cursor.set();
160 }
161 let window_target = primary_window.id;
162 let mut windows = HashMap::new();
163 windows.insert(primary_window.id, primary_window);
164
165 let audio_subsys = context.audio().map_err(Error::Renderer)?;
167 let desired_spec = AudioSpecDesired {
168 freq: s.audio_sample_rate,
169 channels: s.audio_channels,
170 samples: s.audio_buffer_size,
171 };
172 let audio_device = audio_subsys
173 .open_queue(None, &desired_spec)
174 .map_err(Error::Renderer)?;
175 debug!("Loaded AudioDevice: {:?}", audio_device.spec());
176 let controller_subsys = context.game_controller().map_err(Error::Renderer)?;
177
178 let default_font = Font::default();
179 let current_font = default_font.id();
180 let mut font_data = LruCache::new(s.text_cache_size);
181 font_data.put(current_font, default_font);
182
183 let texture_cache_size = s.texture_cache_size;
184 let mut renderer = Self {
185 context,
186 event_pump,
187 audio_device,
188 controller_subsys,
189 controllers: HashMap::new(),
190 settings: s,
191 title,
192 cursor,
193 blend_mode: SdlBlendMode::None,
194 current_font,
195 font_size: 14,
196 font_style: SdlFontStyle::NORMAL,
197 primary_window_id: window_target,
198 window_target,
199 texture_target: None,
200 windows,
201 next_texture_id: 0,
202 font_data,
203 loaded_fonts: LruCache::new(texture_cache_size),
204 };
205 renderer.load_font()?;
206
207 Ok(renderer)
208 }
209
210 #[inline]
212 fn clear(&mut self) -> Result<()> {
213 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
214 canvas.clear();
215 Ok(())
216 })
217 }
218
219 #[inline]
221 fn set_draw_color(&mut self, color: Color) -> Result<()> {
222 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
223 canvas.set_draw_color(color);
224 Ok(())
225 })
226 }
227
228 #[inline]
230 fn clip(&mut self, rect: Option<Rect<i32>>) -> Result<()> {
231 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
232 canvas.set_clip_rect(rect.map(Into::into));
233 Ok(())
234 })
235 }
236
237 #[inline]
239 fn blend_mode(&mut self, mode: BlendMode) {
240 self.blend_mode = mode.into();
241 }
242
243 #[inline]
245 fn present(&mut self) {
246 for window in self.windows.values_mut() {
247 window.canvas.present();
248 }
249 }
250
251 #[inline]
254 fn scale(&mut self, x: f32, y: f32) -> Result<()> {
255 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
256 Ok(canvas.set_scale(x, y).map_err(Error::Renderer)?)
257 })
258 }
259
260 #[inline]
262 fn font_size(&mut self, size: u32) -> Result<()> {
263 self.font_size = size as u16;
264 self.load_font()?;
265 Ok(())
266 }
267
268 #[inline]
270 fn font_style(&mut self, style: FontStyle) {
271 let style = style.into();
272 if self.font_style != style {
273 self.font_style = style;
274 self.font_mut().set_style(style);
275 }
276 }
277
278 #[inline]
280 fn font_family(&mut self, font: &Font) -> Result<()> {
281 self.current_font = font.id();
282 if !self.font_data.contains(&self.current_font) {
283 self.font_data.put(self.current_font, font.clone());
284 }
285 self.load_font()?;
286 Ok(())
287 }
288
289 #[inline]
291 fn text(
292 &mut self,
293 pos: Point<i32>,
294 text: &str,
295 wrap_width: Option<u32>,
296 angle: Option<f64>,
297 center: Option<Point<i32>>,
298 flipped: Option<Flipped>,
299 fill: Option<Color>,
300 outline: u16,
301 ) -> Result<(u32, u32)> {
302 if text.is_empty() {
303 return self.size_of(text, wrap_width);
304 }
305 if let Some(fill) = fill {
306 let window = self
307 .windows
308 .get_mut(&self.window_target)
309 .ok_or(Error::InvalidWindow(self.window_target))?;
310
311 let texture = {
312 let font = self
314 .loaded_fonts
315 .get_mut(&(self.current_font, self.font_size))
316 .ok_or_else(|| anyhow!("invalid current font"))?;
317 if font.get_outline_width() != outline {
318 font.set_outline_width(outline);
319 }
320
321 let key = TextCacheKey::new(text, self.current_font, fill, self.font_size);
322 if !window.text_cache.contains(&key) {
323 let surface = wrap_width
324 .map_or_else(
325 || font.render(text).blended(fill),
326 |width| font.render(text).blended_wrapped(fill, width),
327 )
328 .context("invalid text")?;
329 window.text_cache.put(
330 key,
331 RendererTexture::new(
332 window
333 .canvas
334 .create_texture_from_surface(surface)
335 .context("failed to create text surface")?,
336 ),
337 );
338 }
339
340 #[allow(clippy::expect_used)]
342 window.text_cache.get_mut(&key).expect("valid text cache")
343 };
344
345 let TextureQuery {
346 width, mut height, ..
347 } = texture.query();
348 let update = |canvas: &mut Canvas<_>| -> Result<()> {
349 let src = None;
350 let dst = Some(SdlRect::new(pos.x(), pos.y(), width, height));
351 let result = if angle.is_some() || center.is_some() || flipped.is_some() {
352 let angle = angle.unwrap_or(0.0);
353 let center = center.map(Into::into);
354 let horizontal = matches!(flipped, Some(Flipped::Horizontal | Flipped::Both));
355 let vertical = matches!(flipped, Some(Flipped::Vertical | Flipped::Both));
356 canvas.copy_ex(texture, src, dst, angle, center, horizontal, vertical)
357 } else {
358 canvas.copy(texture, src, dst)
359 };
360 Ok(result.map_err(Error::Renderer)?)
361 };
362
363 if let Some(texture_id) = self.texture_target {
364 if let Some(texture) = window.textures.get(&texture_id) {
365 let mut result = Ok(());
366 window
367 .canvas
368 .with_texture_canvas(&mut texture.borrow_mut(), |canvas| {
369 result = update(canvas);
370 })
371 .with_context(|| format!("failed to update texture target {texture_id}"))?;
372 result?;
373 } else {
374 return Err(Error::InvalidTexture(texture_id).into());
375 }
376 } else {
377 update(&mut window.canvas)?;
378 }
379 if text.ends_with('\n') {
380 let font = self
381 .loaded_fonts
382 .get_mut(&(self.current_font, self.font_size))
383 .ok_or_else(|| anyhow!("invalid current font"))?;
384 height += font.height() as u32;
385 }
386 Ok((width, height))
387 } else {
388 self.size_of(text, wrap_width)
389 }
390 }
391
392 #[inline]
394 fn clipboard_text(&self) -> String {
395 if let Ok(video) = self.context.video() {
396 video.clipboard().clipboard_text().unwrap_or_default()
397 } else {
398 String::default()
399 }
400 }
401
402 #[inline]
404 fn set_clipboard_text(&self, value: &str) -> Result<()> {
405 Ok(self
406 .context
407 .video()
408 .map_err(Error::Renderer)?
409 .clipboard()
410 .set_clipboard_text(value)
411 .map_err(Error::Renderer)?)
412 }
413
414 #[inline]
416 fn open_url(&self, url: &str) -> Result<()> {
417 sdl2::url::open_url(url).context("invalid url")
418 }
419
420 #[inline]
423 fn size_of(&self, text: &str, wrap_width: Option<u32>) -> Result<(u32, u32)> {
424 let font = self.font();
425 if text.is_empty() {
426 return Ok((0, font.height() as u32));
427 }
428 let (width, mut height) = if let Some(width) = wrap_width {
429 let (width, height) = font
430 .render(text)
431 .blended_wrapped(Color::BLACK, width)
432 .context("invalid text")?
433 .size();
434 (width, height)
435 } else {
436 font.size_of(text)?
437 };
438 if text.ends_with('\n') {
439 height += font.height() as u32;
440 }
441 Ok((width, height))
442 }
443
444 #[inline]
446 fn point(&mut self, p: Point<i32>, color: Color) -> Result<()> {
447 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
448 let [x, y] = p.map(|v| v as i16);
449 Ok(canvas.pixel(x, y, color).map_err(Error::Renderer)?)
450 })
451 }
452
453 #[inline]
455 fn line(&mut self, line: Line<i32>, smooth: bool, width: u8, color: Color) -> Result<()> {
456 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
457 let [x1, y1] = line.start().map(|v| v as i16);
458 let [x2, y2] = line.end().map(|v| v as i16);
459 if width == 1 {
460 if y1 == y2 {
461 canvas.hline(x1, x2, y1, color)
462 } else if x1 == x2 {
463 canvas.vline(x1, y1, y2, color)
464 } else if smooth {
465 canvas.aa_line(x1, y1, x2, y2, color)
466 } else {
467 canvas.line(x1, y1, x2, y2, color)
468 }
469 } else {
470 canvas.thick_line(x1, y1, x2, y2, width, color)
471 }
472 .map_err(Error::Renderer)?;
473 Ok(())
474 })
475 }
476
477 #[inline]
479 fn bezier<I>(&mut self, ps: I, detail: i32, stroke: Option<Color>) -> Result<()>
480 where
481 I: Iterator<Item = Point<i32>>,
482 {
483 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
484 let (vx, vy): (Vec<i16>, Vec<i16>) = ps
485 .map(|p| -> (i16, i16) {
486 let [x, y] = p.map(|v| v as i16);
487 (x, y)
488 })
489 .unzip();
490 if let Some(stroke) = stroke {
491 canvas
492 .bezier(&vx, &vy, detail, stroke)
493 .map_err(Error::Renderer)?;
494 }
495 Ok(())
496 })
497 }
498
499 #[inline]
501 fn triangle(
502 &mut self,
503 tri: Tri<i32>,
504 smooth: bool,
505 fill: Option<Color>,
506 stroke: Option<Color>,
507 ) -> Result<()> {
508 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
509 let [x1, y1] = tri.p1().map(|v| v as i16);
510 let [x2, y2] = tri.p2().map(|v| v as i16);
511 let [x3, y3] = tri.p3().map(|v| v as i16);
512 if let Some(fill) = fill {
513 canvas
514 .filled_trigon(x1, y1, x2, y2, x3, y3, fill)
515 .map_err(Error::Renderer)?;
516 }
517 if let Some(stroke) = stroke {
518 if smooth {
519 canvas.aa_trigon(x1, y1, x2, y2, x3, y3, stroke)
520 } else {
521 canvas.trigon(x1, y1, x2, y2, x3, y3, stroke)
522 }
523 .map_err(Error::Renderer)?;
524 }
525 Ok(())
526 })
527 }
528
529 #[inline]
531 fn rect(
532 &mut self,
533 rect: Rect<i32>,
534 radius: Option<i32>,
535 fill: Option<Color>,
536 stroke: Option<Color>,
537 ) -> Result<()> {
538 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
539 let [x, y, width, height] = rect.map(|v| v as i16);
540 if let Some(fill) = fill {
541 radius
542 .map_or_else(
543 || canvas.box_(x, y, x + width, y + height, fill),
544 |radius| {
545 let radius = radius as i16;
546 canvas.rounded_box(x, y, x + width, y + height, radius, fill)
547 },
548 )
549 .map_err(Error::Renderer)?;
550 }
551 if let Some(stroke) = stroke {
552 radius
553 .map_or_else(
554 || canvas.rectangle(x, y, x + width + 1, y + height + 1, stroke),
556 |radius| {
557 let radius = radius as i16;
558 canvas.rounded_rectangle(x, y, x + width, y + height, radius, stroke)
559 },
560 )
561 .map_err(Error::Renderer)?;
562 }
563 Ok(())
564 })
565 }
566
567 #[inline]
569 fn quad(
570 &mut self,
571 quad: Quad<i32>,
572 smooth: bool,
573 fill: Option<Color>,
574 stroke: Option<Color>,
575 ) -> Result<()> {
576 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
577 let [x1, y1] = quad.p1().map(|v| v as i16);
578 let [x2, y2] = quad.p2().map(|v| v as i16);
579 let [x3, y3] = quad.p3().map(|v| v as i16);
580 let [x4, y4] = quad.p4().map(|v| v as i16);
581 let vx = [x1, x2, x3, x4];
582 let vy = [y1, y2, y3, y4];
583 if let Some(fill) = fill {
584 canvas
585 .filled_polygon(&vx, &vy, fill)
586 .map_err(Error::Renderer)?;
587 }
588 if let Some(stroke) = stroke {
589 if smooth {
590 canvas.aa_polygon(&vx, &vy, stroke)
591 } else {
592 canvas.polygon(&vx, &vy, stroke)
593 }
594 .map_err(Error::Renderer)?;
595 }
596 Ok(())
597 })
598 }
599
600 #[inline]
602 fn polygon<I>(
603 &mut self,
604 ps: I,
605 smooth: bool,
606 fill: Option<Color>,
607 stroke: Option<Color>,
608 ) -> Result<()>
609 where
610 I: Iterator<Item = Point<i32>>,
611 {
612 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
613 let (vx, vy): (Vec<i16>, Vec<i16>) = ps
614 .map(|p| -> (i16, i16) {
615 let [x, y] = p.map(|v| v as i16);
616 (x, y)
617 })
618 .unzip();
619 if let Some(fill) = fill {
620 canvas
621 .filled_polygon(&vx, &vy, fill)
622 .map_err(Error::Renderer)?;
623 }
624 if let Some(stroke) = stroke {
625 if smooth {
626 canvas.aa_polygon(&vx, &vy, stroke)
627 } else {
628 canvas.polygon(&vx, &vy, stroke)
629 }
630 .map_err(Error::Renderer)?;
631 }
632 Ok(())
633 })
634 }
635
636 #[inline]
638 fn ellipse(
639 &mut self,
640 ellipse: Ellipse<i32>,
641 smooth: bool,
642 fill: Option<Color>,
643 stroke: Option<Color>,
644 ) -> Result<()> {
645 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
646 let [x, y, width, height] = ellipse.map(|v| v as i16);
647 let rw = width / 2;
648 let rh = height / 2;
649 if let Some(fill) = fill {
650 if width == height {
651 canvas.filled_circle(x, y, rw, fill)
652 } else {
653 canvas.filled_ellipse(x, y, rw, rh, fill)
654 }
655 .map_err(Error::Renderer)?;
656 }
657 if let Some(stroke) = stroke {
658 if width == height {
659 if smooth {
660 canvas.aa_circle(x, y, rw, stroke)
661 } else {
662 canvas.circle(x, y, rw, stroke)
663 }
664 } else if smooth {
665 canvas.aa_ellipse(x, y, rw, rh, stroke)
666 } else {
667 canvas.ellipse(x, y, rw, rh, stroke)
668 }
669 .map_err(Error::Renderer)?;
670 }
671 Ok(())
672 })
673 }
674
675 #[inline]
677 fn arc(
678 &mut self,
679 p: Point<i32>,
680 radius: i32,
681 start: i32,
682 end: i32,
683 mode: ArcMode,
684 fill: Option<Color>,
685 stroke: Option<Color>,
686 ) -> Result<()> {
687 self.update_canvas(|canvas: &mut Canvas<_>| -> Result<()> {
688 let [x, y] = p.map(|v| v as i16);
689 let radius = radius as i16;
690 let start = start as i16;
691 let end = end as i16;
692 match mode {
693 ArcMode::Default => {
694 if let Some(stroke) = stroke {
695 canvas
696 .arc(x, y, radius, start, end, stroke)
697 .map_err(Error::Renderer)?;
698 }
699 }
700 ArcMode::Pie => {
701 if let Some(fill) = fill {
702 canvas
703 .filled_pie(x, y, radius, start, end, fill)
704 .map_err(Error::Renderer)?;
705 }
706 if let Some(stroke) = stroke {
707 canvas
708 .pie(x, y, radius, start, end, stroke)
709 .map_err(Error::Renderer)?;
710 }
711 }
712 }
713 Ok(())
714 })
715 }
716
717 #[inline]
719 fn image(
720 &mut self,
721 img: &Image,
722 src: Option<Rect<i32>>,
723 dst: Option<Rect<i32>>,
724 angle: f64,
725 center: Option<Point<i32>>,
726 flipped: Option<Flipped>,
727 tint: Option<Color>,
728 ) -> Result<()> {
729 let window = self
730 .windows
731 .get_mut(&self.window_target)
732 .ok_or(Error::InvalidWindow(self.window_target))?;
733 let texture = {
734 let key: *const Image = img;
735 if !window.image_cache.contains(&key) {
736 window.image_cache.put(
737 key,
738 RendererTexture::new(
739 window
740 .canvas
741 .create_texture_static(
742 Some(img.format().into()),
743 img.width(),
744 img.height(),
745 )
746 .context("failed to create image texture")?,
747 ),
748 );
749 }
750 #[allow(clippy::expect_used)]
752 window.image_cache.get_mut(&key).expect("valid image cache")
753 };
754 let [r, g, b, a] = tint.map_or([255; 4], |t| t.channels());
755 texture.set_color_mod(r, g, b);
756 texture.set_alpha_mod(a);
757 texture.set_blend_mode(self.blend_mode);
758 texture
759 .update(
760 None,
761 img.as_bytes(),
762 img.format().channels() * img.width() as usize,
763 )
764 .context("failed to update image texture")?;
765
766 let update = |canvas: &mut Canvas<_>| -> Result<()> {
767 let src = src.map(Into::into);
768 let dst = dst.map(Into::into);
769 if angle > 0.0 || center.is_some() || flipped.is_some() {
770 let center = center.map(Into::into);
771 let horizontal = matches!(flipped, Some(Flipped::Horizontal | Flipped::Both));
772 let vertical = matches!(flipped, Some(Flipped::Vertical | Flipped::Both));
773 canvas.copy_ex(texture, src, dst, angle, center, horizontal, vertical)
774 } else {
775 canvas.copy(texture, src, dst)
776 }
777 .map_err(Error::Renderer)?;
778 Ok(())
779 };
780
781 if let Some(texture_id) = self.texture_target {
782 if let Some(texture) = window.textures.get(&texture_id) {
783 let mut result = Ok(());
784 window
785 .canvas
786 .with_texture_canvas(&mut texture.borrow_mut(), |canvas| {
787 result = update(canvas);
788 })
789 .with_context(|| format!("failed to update texture target {texture_id}"))?;
790 result?;
791 } else {
792 return Err(Error::InvalidTexture(texture_id).into());
793 }
794 } else {
795 update(&mut window.canvas)?;
796 }
797
798 Ok(())
799 }
800
801 #[inline]
803 fn to_bytes(&mut self) -> Result<Vec<u8>> {
804 if let Some(texture_id) = self.texture_target {
805 let window = self
806 .windows
807 .values_mut()
808 .find(|w| w.textures.contains_key(&texture_id));
809 if let Some(window) = window {
810 let texture = window
812 .textures
813 .get(&texture_id)
814 .ok_or_else(|| anyhow!(Error::InvalidTexture(texture_id)))?;
815 let mut result = Ok(vec![]);
816 window
817 .canvas
818 .with_texture_canvas(&mut texture.borrow_mut(), |canvas| {
819 result = canvas.read_pixels(None, SdlPixelFormat::RGBA32);
820 })
821 .with_context(|| format!("failed to read texture target {texture_id}"))?;
822 Ok(result.map_err(Error::Renderer)?)
823 } else {
824 Err(Error::InvalidTexture(texture_id).into())
825 }
826 } else {
827 Ok(self
828 .canvas()?
829 .read_pixels(None, SdlPixelFormat::RGBA32)
830 .map_err(Error::Renderer)?)
831 }
832 }
833
834 fn open_controller(&mut self, controller_id: ControllerId) -> Result<()> {
836 let joystick_index = *controller_id;
837 if self.controller_subsys.is_game_controller(joystick_index) {
838 self.controllers
839 .insert(controller_id, self.controller_subsys.open(joystick_index)?);
840 } else {
841 warn!("Joystick {} is not a game controller. Generic joysticks are currently unsupported.", joystick_index);
842 }
843 Ok(())
844 }
845
846 fn close_controller(&mut self, controller_id: ControllerId) {
848 self.controllers.remove(&controller_id);
849 }
850}
851
852impl fmt::Debug for Renderer {
853 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
854 f.debug_struct("Renderer")
855 .field(
856 "audio_device",
857 &format_args!(
858 "{{ spec: {:?}, status: {:?}, queue: {:?} }}",
859 self.audio_device.spec(),
860 self.audio_device.status(),
861 self.audio_device.size()
862 ),
863 )
864 .field("title", &self.title)
865 .field("settings", &self.settings)
866 .field("blend_mode", &self.blend_mode)
867 .field(
868 "current_font",
869 &self.font_data.peek(&self.current_font).map(Font::name),
870 )
871 .field("font_size", &self.font_size)
872 .field("font_style", &self.font_style)
873 .field("window_target", &self.texture_target)
874 .field("texture_target", &self.texture_target)
875 .field("windows", &self.windows)
876 .field("next_texture_id", &self.next_texture_id)
877 .field("font_data", &self.font_data)
878 .field("loaded_fonts", &self.loaded_fonts)
879 .finish_non_exhaustive()
880 }
881}
882
883#[doc(hidden)]
888impl ToColor for Color {
889 fn as_rgba(&self) -> (u8, u8, u8, u8) {
891 let [r, g, b, a] = self.channels();
892 (r, g, b, a)
893 }
894}
895
896#[doc(hidden)]
897impl From<Color> for SdlColor {
898 fn from(color: Color) -> Self {
900 let [r, g, b, a] = color.channels();
901 Self::RGBA(r, g, b, a)
902 }
903}
904
905#[doc(hidden)]
906impl From<FontStyle> for SdlFontStyle {
907 fn from(style: FontStyle) -> Self {
909 #[allow(clippy::expect_used)]
910 Self::from_bits(style.bits()).expect("valid FontStyle")
911 }
912}
913
914#[doc(hidden)]
915impl From<Rect<i32>> for SdlRect {
916 fn from(rect: Rect<i32>) -> Self {
918 Self::new(
919 rect.x(),
920 rect.y(),
921 rect.width() as u32,
922 rect.height() as u32,
923 )
924 }
925}
926
927#[doc(hidden)]
928impl From<SdlRect> for Rect<i32> {
929 fn from(rect: SdlRect) -> Self {
931 Self::new(
932 rect.x(),
933 rect.y(),
934 rect.width() as i32,
935 rect.height() as i32,
936 )
937 }
938}
939
940#[doc(hidden)]
941impl From<&Rect<i32>> for SdlRect {
942 fn from(rect: &Rect<i32>) -> Self {
944 Self::new(
945 rect.x(),
946 rect.y(),
947 rect.width() as u32,
948 rect.height() as u32,
949 )
950 }
951}
952
953#[doc(hidden)]
954impl From<Point<i32>> for SdlPoint {
955 fn from(p: Point<i32>) -> Self {
957 Self::new(p.x(), p.y())
958 }
959}
960
961#[doc(hidden)]
962impl From<&Point<i32>> for SdlPoint {
963 fn from(p: &Point<i32>) -> Self {
965 Self::new(p.x(), p.y())
966 }
967}
968
969#[doc(hidden)]
970impl From<BlendMode> for SdlBlendMode {
971 fn from(mode: BlendMode) -> Self {
973 match mode {
974 BlendMode::None => Self::None,
975 BlendMode::Blend => Self::Blend,
976 BlendMode::Add => Self::Add,
977 BlendMode::Mod => Self::Mod,
978 }
979 }
980}
981
982#[doc(hidden)]
983impl From<PixelFormat> for SdlPixelFormat {
984 fn from(format: PixelFormat) -> Self {
986 match format {
987 PixelFormat::Rgb => Self::RGB24,
988 PixelFormat::Rgba => Self::RGBA32,
989 }
990 }
991}