cotis_raylib/renderer.rs
1//! Raylib window renderer and handle types.
2//!
3//! [`RaylibRender`] owns the raylib window, drawing thread, font table, and image texture cache.
4//! It implements [`CotisRenderer`](cotis::renders::CotisRenderer) in [`render_trait`] for
5//! [`crate::drawables::RaylibDrawable`] command streams.
6//!
7//! Application code usually constructs a [`RaylibRender`] via [`RaylibRender::auto_start`] or
8//! [`RaylibRender::new`], then passes it to [`CotisApp`](cotis::cotis_app::CotisApp).
9
10use crate::fonts::ScalableFont;
11use cotis::renders::RenderCompatibleWith;
12use raylib::color::Color;
13use raylib::consts::TextureFilter;
14use raylib::error::RaylibError;
15use raylib::prelude::Texture2D;
16use raylib::{RaylibBuilder, RaylibHandle, RaylibThread};
17use std::collections::HashMap;
18use std::ops::{Deref, DerefMut};
19use std::sync::{Arc, Mutex, MutexGuard};
20
21pub mod render_trait;
22
23/// Shared, mutex-protected raylib handle used across threads for input polling.
24pub type SharedRaylibHandle = Arc<Mutex<RayRenderHandle>>;
25
26/// Mutex-guarded wrapper around [`RaylibHandle`].
27///
28/// Implements [`Deref`] and [`DerefMut`] to the inner handle so raylib methods can be called
29/// directly on `&RayRenderHandle`. Input traits from `cotis-utils` are implemented on this type in
30/// [`crate::interactivity`].
31pub struct RayRenderHandle(pub(crate) RaylibHandle);
32
33impl Deref for RayRenderHandle {
34 type Target = RaylibHandle;
35 fn deref(&self) -> &Self::Target {
36 &self.0
37 }
38}
39impl DerefMut for RayRenderHandle {
40 fn deref_mut(&mut self) -> &mut Self::Target {
41 &mut self.0
42 }
43}
44
45/// Primary raylib-backed renderer for Cotis applications.
46///
47/// Owns the window handle (behind a mutex), the raylib drawing thread, loaded fonts, and a
48/// path-keyed texture cache. Implements [`CotisRenderer`](cotis::renders::CotisRenderer) for
49/// [`crate::drawables::RaylibDrawable`] streams and Cotis context traits in [`crate::cotis`].
50///
51/// # Construction
52///
53/// - [`RaylibRender::auto_start`] — gray transparent background, 1000×1000 resizable window.
54/// - [`RaylibRender::new`] — custom clear color, same window defaults.
55/// - [`RaylibRender::new_custom`] — full control via [`RaylibBuilder`].
56pub struct RaylibRender {
57 rl: Arc<Mutex<RayRenderHandle>>,
58 thread: RaylibThread,
59 fonts: Arc<Mutex<HashMap<usize, ScalableFont>>>,
60 font_max_id: usize,
61 image_cache: HashMap<String, Arc<Texture2D>>,
62 /// Clear color used at the start of each frame.
63 pub background_color: Color,
64}
65
66impl<Manager> RenderCompatibleWith<Manager> for RaylibRender {
67 fn init(&mut self, _layout_manager: &mut Manager) {}
68}
69
70impl RaylibRender {
71 /// Creates a renderer with a gray transparent background and a default 1000×1000 resizable window.
72 pub fn auto_start() -> Self {
73 Self::new(Color::new(100, 100, 100, 0))
74 }
75
76 /// Creates a renderer with the given background clear color and a default 1000×1000 resizable window.
77 pub fn new(background_color: Color) -> Self {
78 let (rl, thread) = raylib::init()
79 .resizable()
80 .size(1000, 1000)
81 .title("Raylib")
82 .build();
83 Self {
84 rl: Arc::new(Mutex::new(RayRenderHandle(rl))),
85 thread,
86 fonts: Default::default(),
87 font_max_id: 0,
88 image_cache: Default::default(),
89 background_color,
90 }
91 }
92
93 /// Creates a renderer from a custom [`RaylibBuilder`].
94 pub fn new_custom(raylib_builder: RaylibBuilder, background_color: Color) -> Self {
95 let (rl, thread) = raylib_builder.build();
96 Self {
97 rl: Arc::new(Mutex::new(RayRenderHandle(rl))),
98 thread,
99 fonts: Default::default(),
100 font_max_id: 0,
101 image_cache: Default::default(),
102 background_color,
103 }
104 }
105
106 /// Locks and returns the raylib handle.
107 ///
108 /// # Panics
109 ///
110 /// Panics if the mutex is poisoned.
111 pub fn get_handle(&mut self) -> MutexGuard<'_, RayRenderHandle> {
112 self.rl.lock().unwrap()
113 }
114
115 /// Locks and returns the raylib handle (immutable receiver).
116 ///
117 /// # Panics
118 ///
119 /// Panics if the mutex is poisoned.
120 pub fn lock_handle(&self) -> MutexGuard<'_, RayRenderHandle> {
121 self.rl.lock().unwrap()
122 }
123
124 /// Returns the raylib drawing thread.
125 ///
126 /// Must be passed to drawing calls on the thread that created the window.
127 pub fn get_thread(&self) -> &RaylibThread {
128 &self.thread
129 }
130
131 /// Returns the raylib drawing thread.
132 ///
133 /// Alias for [`Self::get_thread`].
134 pub fn thread(&self) -> &RaylibThread {
135 &self.thread
136 }
137
138 /// Returns the locked handle and drawing thread together for low-level drawing.
139 ///
140 /// # Panics
141 ///
142 /// Panics if the mutex is poisoned.
143 pub fn get_raylib(&mut self) -> (MutexGuard<'_, RayRenderHandle>, &mut RaylibThread) {
144 (self.rl.lock().unwrap(), &mut self.thread)
145 }
146
147 /// Returns whether the window close flag is set.
148 ///
149 /// # Panics
150 ///
151 /// Panics if the mutex is poisoned.
152 pub fn window_should_close(&self) -> bool {
153 self.rl.lock().unwrap().window_should_close()
154 }
155
156 /// Loads a font at size 16 and registers it in the font table.
157 ///
158 /// Returns a font ID to store in [`TextConfig::font_id`](cotis_defaults::element_configs::text_config::TextConfig::font_id).
159 /// IDs are assigned in load order starting at `0` and are never reused.
160 ///
161 /// # Errors
162 ///
163 /// Returns [`RaylibError`] if raylib cannot load the font file.
164 pub fn load_font_and_keep(&mut self, file_name: &str) -> Result<usize, RaylibError> {
165 self.load_font_ex_and_keep(file_name, 16, None, None)
166 }
167
168 pub(crate) fn load_new_font_sizes(&mut self) {
169 let mut fonts = self.fonts.lock().unwrap();
170 for font in fonts.values_mut() {
171 font.load_queued_fonts(&mut self.rl.lock().unwrap(), &self.thread)
172 .unwrap();
173 }
174 }
175
176 /// Loads a font with custom size, optional character subset, and texture filter.
177 ///
178 /// Returns a font ID (0-based, monotonic) for use in [`TextConfig::font_id`](cotis_defaults::element_configs::text_config::TextConfig::font_id).
179 /// Additional pixel sizes requested during drawing are queued and loaded at the start of the
180 /// next frame.
181 ///
182 /// # Errors
183 ///
184 /// Returns [`RaylibError`] if raylib cannot load the font file.
185 pub fn load_font_ex_and_keep(
186 &mut self,
187 file_name: &str,
188 font_size: i32,
189 chars: Option<&str>,
190 filter: Option<TextureFilter>,
191 ) -> Result<usize, RaylibError> {
192 let mut rl = self.rl.lock().unwrap();
193 self.fonts.lock().unwrap().insert(
194 self.font_max_id,
195 ScalableFont::new(
196 file_name.to_string(),
197 chars.map(|s| s.to_string()),
198 filter,
199 &mut rl,
200 &self.thread,
201 font_size as usize,
202 )?,
203 );
204 self.font_max_id += 1;
205 Ok(self.font_max_id - 1)
206 }
207
208 /// Returns the elapsed time between the previous and current frame, in seconds.
209 ///
210 /// # Panics
211 ///
212 /// Panics if the mutex is poisoned.
213 pub fn delta_time(&self) -> f32 {
214 self.rl.lock().unwrap().get_frame_time()
215 }
216
217 /// Returns a clone of the shared raylib handle for cross-thread input polling.
218 ///
219 /// Pair with [`Self::thread`] when calling raylib drawing APIs from the owning thread.
220 pub fn get_shared_handle(&self) -> SharedRaylibHandle {
221 self.rl.clone()
222 }
223
224 /// Returns a clone of the shared raylib handle.
225 ///
226 /// Alias for [`Self::get_shared_handle`].
227 pub fn shared_handle(&self) -> SharedRaylibHandle {
228 self.get_shared_handle()
229 }
230
231 /// Returns the path-keyed texture cache populated by [`translate_generic_image`](RaylibRender::translate_generic_image).
232 pub fn image_cache(&self) -> &HashMap<String, Arc<Texture2D>> {
233 &self.image_cache
234 }
235
236 /// Returns a mutable reference to the texture cache for manual preload or inspection.
237 pub fn image_cache_mut(&mut self) -> &mut HashMap<String, Arc<Texture2D>> {
238 &mut self.image_cache
239 }
240
241 /// Returns the shared font table used during drawing and text measurement.
242 pub fn fonts(&self) -> Arc<Mutex<HashMap<usize, ScalableFont>>> {
243 self.fonts.clone()
244 }
245}