1use std::collections::HashMap;
32use std::sync::{Arc, Mutex};
33
34use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
35use raylib::math::Vector2;
36
37use crate::fonts::ScalableFont;
38use crate::raylib_images::GenericCompatibleImage;
39use crate::renderer::RaylibRender;
40use cotis_defaults::colors::Color as CotisColor;
41use cotis_defaults::render_commands::render_t_list::{IsRenderList, RenderTList};
42use cotis_defaults::render_commands::*;
43use cotis_utils::math::BoundingBox;
44use raylib::texture::Texture2D;
45
46pub struct RaylibDrawContext<'d, 'rl> {
48 pub d: &'d mut RaylibDrawHandle<'rl>,
50 pub fonts: Arc<Mutex<HashMap<usize, ScalableFont>>>,
52 pub clip_stack: &'d mut Vec<(f32, f32, f32, f32)>,
54 pub image_cache: Option<&'d HashMap<String, Arc<Texture2D>>>,
56}
57
58pub trait RaylibDrawable {
60 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>);
62 fn scale_by(&mut self, factor: f32);
66 fn preload_generic_images(&self, _render: &mut RaylibRender) {}
70}
71
72pub(crate) fn cotis_to_raylib_color(c: CotisColor) -> raylib::color::Color {
79 raylib::color::Color::new(c.r as u8, c.g as u8, c.b as u8, c.a as u8)
80}
81
82fn scale_corner_radii(r: &mut CornerRadii, factor: f32) {
83 r.top_left *= factor;
84 r.top_right *= factor;
85 r.bottom_left *= factor;
86 r.bottom_right *= factor;
87}
88
89fn scale_bounding_box(bb: &mut BoundingBox, factor: f32) {
90 bb.x *= factor;
91 bb.y *= factor;
92 bb.width *= factor;
93 bb.height *= factor;
94}
95
96fn intersect_clip(a: (f32, f32, f32, f32), b: (f32, f32, f32, f32)) -> (f32, f32, f32, f32) {
97 let ax2 = a.0 + a.2;
98 let ay2 = a.1 + a.3;
99 let bx2 = b.0 + b.2;
100 let by2 = b.1 + b.3;
101 let x = a.0.max(b.0);
102 let y = a.1.max(b.1);
103 let w = ax2.min(bx2) - x;
104 let h = ay2.min(by2) - y;
105 (x, y, w.max(0.0), h.max(0.0))
106}
107
108impl<'a, E> RaylibDrawable for Rectangle<'a, E> {
114 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
115 let bb = self.info.bounding_box;
116
117 #[cfg(feature = "complex-color")]
118 {
119 let clip = ctx.clip_stack.last().copied();
120 crate::color_paint::draw_color_layer_in_rect(
121 ctx.d,
122 bb.x,
123 bb.y,
124 bb.width,
125 bb.height,
126 &self.color,
127 &self.corner_radii,
128 clip,
129 );
130 }
131
132 #[cfg(not(feature = "complex-color"))]
133 {
134 if self.color.a > 0.0 {
135 if self.corner_radii.top_left > 0.0 {
136 let radius = (self.corner_radii.top_left * 2.0)
137 / if bb.width > bb.height {
138 bb.height
139 } else {
140 bb.width
141 };
142 ctx.d.draw_rectangle_rounded(
143 raylib::math::Rectangle::new(bb.x, bb.y, bb.width, bb.height),
144 radius,
145 8,
146 cotis_to_raylib_color(self.color),
147 );
148 } else {
149 ctx.d.draw_rectangle(
150 bb.x as i32,
151 bb.y as i32,
152 bb.width as i32,
153 bb.height as i32,
154 cotis_to_raylib_color(self.color),
155 );
156 }
157 }
158 }
159 }
160
161 fn scale_by(&mut self, factor: f32) {
162 scale_bounding_box(&mut self.info.bounding_box, factor);
163 scale_corner_radii(&mut self.corner_radii, factor);
164 }
165}
166
167impl<'a, E> RaylibDrawable for Text<'a, E> {
173 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
174 let bb = self.info.bounding_box;
175 let text_str: &str = self.text.as_ref();
176
177 let fonts_arc = ctx.fonts.clone();
178 let mut guard = fonts_arc.lock().unwrap();
179
180 #[cfg(feature = "complex-color")]
181 {
182 if let Some(font) = guard.get_mut(&(self.font_id as usize)) {
183 let f = font.get_font_for_size(self.font_size as usize);
184 crate::color_paint::draw_text_color_layer(
185 ctx.d,
186 f,
187 text_str,
188 Vector2::new(bb.x, bb.y),
189 self.font_size,
190 self.letter_spacing,
191 &self.color,
192 );
193 } else {
194 let c = crate::color_paint::text_color_at(&self.color, 0.0);
195 ctx.d.draw_text(
196 text_str,
197 bb.x as i32,
198 bb.y as i32,
199 self.font_size as i32,
200 crate::color_paint::cotis_color_to_raylib(c),
201 );
202 }
203 }
204
205 #[cfg(not(feature = "complex-color"))]
206 {
207 if let Some(font) = guard.get_mut(&(self.font_id as usize)) {
208 let raylib_font = font.get_font_for_size(self.font_size as usize);
209 ctx.d.draw_text_ex(
210 raylib_font,
211 text_str,
212 Vector2::new(bb.x, bb.y),
213 self.font_size,
214 self.letter_spacing,
215 cotis_to_raylib_color(self.color),
216 );
217 } else {
218 ctx.d.draw_text(
219 text_str,
220 bb.x as i32,
221 bb.y as i32,
222 self.font_size as i32,
223 cotis_to_raylib_color(self.color),
224 );
225 }
226 }
227 }
228
229 fn scale_by(&mut self, factor: f32) {
230 scale_bounding_box(&mut self.info.bounding_box, factor);
231 self.font_size *= factor;
232 self.letter_spacing *= factor;
233 self.line_height *= factor;
234 }
235}
236
237impl<'a, E> RaylibDrawable for Border<'a, E> {
242 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
243 let bb = self.info.bounding_box;
244 let color = cotis_to_raylib_color(self.color);
245
246 if self.width.left > 0.0 {
247 ctx.d.draw_rectangle(
248 bb.x as i32,
249 (bb.y + self.corner_radii.top_left) as i32,
250 self.width.left as i32,
251 (bb.height - self.corner_radii.top_left - self.corner_radii.bottom_left) as i32,
252 color,
253 );
254 }
255 if self.width.right > 0.0 {
256 ctx.d.draw_rectangle(
257 (bb.x + bb.width - self.width.right) as i32,
258 (bb.y + self.corner_radii.top_right) as i32,
259 self.width.right as i32,
260 (bb.height - self.corner_radii.top_right - self.corner_radii.bottom_right) as i32,
261 color,
262 );
263 }
264 if self.width.top > 0.0 {
265 ctx.d.draw_rectangle(
266 (bb.x + self.corner_radii.top_left) as i32,
267 bb.y as i32,
268 (bb.width - self.corner_radii.top_left - self.corner_radii.top_right) as i32,
269 self.width.top as i32,
270 color,
271 );
272 }
273 if self.width.bottom > 0.0 {
274 ctx.d.draw_rectangle(
275 (bb.x + self.corner_radii.bottom_left) as i32,
276 (bb.y + bb.height - self.width.bottom) as i32,
277 (bb.width - self.corner_radii.bottom_left - self.corner_radii.bottom_right) as i32,
278 self.width.bottom as i32,
279 color,
280 );
281 }
282
283 if self.corner_radii.top_left > 0.0 {
284 ctx.d.draw_ring(
285 Vector2::new(
286 bb.x + self.corner_radii.top_left,
287 bb.y + self.corner_radii.top_left,
288 ),
289 self.corner_radii.top_left - self.width.top,
290 self.corner_radii.top_left,
291 180.0,
292 270.0,
293 10,
294 color,
295 );
296 }
297 if self.corner_radii.top_right > 0.0 {
298 ctx.d.draw_ring(
299 Vector2::new(
300 bb.x + bb.width - self.corner_radii.top_right,
301 bb.y + self.corner_radii.top_right,
302 ),
303 self.corner_radii.top_right - self.width.top,
304 self.corner_radii.top_right,
305 270.0,
306 360.0,
307 10,
308 color,
309 );
310 }
311 if self.corner_radii.bottom_left > 0.0 {
312 ctx.d.draw_ring(
313 Vector2::new(
314 bb.x + self.corner_radii.bottom_left,
315 bb.y + bb.height - self.corner_radii.bottom_left,
316 ),
317 self.corner_radii.bottom_left - self.width.bottom,
318 self.corner_radii.bottom_left,
319 90.0,
320 180.0,
321 10,
322 color,
323 );
324 }
325 if self.corner_radii.bottom_right > 0.0 {
326 ctx.d.draw_ring(
327 Vector2::new(
328 bb.x + bb.width - self.corner_radii.bottom_right,
329 bb.y + bb.height - self.corner_radii.bottom_right,
330 ),
331 self.corner_radii.bottom_right - self.width.bottom,
332 self.corner_radii.bottom_right,
333 0.0,
334 90.0,
335 10,
336 color,
337 );
338 }
339 }
340
341 fn scale_by(&mut self, factor: f32) {
342 scale_bounding_box(&mut self.info.bounding_box, factor);
343 scale_corner_radii(&mut self.corner_radii, factor);
344 self.width.left *= factor;
345 self.width.right *= factor;
346 self.width.top *= factor;
347 self.width.bottom *= factor;
348 self.width.between_children *= factor;
349 }
350}
351
352impl<'a, I: GenericCompatibleImage, E> RaylibDrawable for Image<'a, I, E> {
358 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
359 GenericCompatibleImage::load_texture(self.data.as_ref(), ctx);
360 let texture = GenericCompatibleImage::get_texture(self.data.as_ref(), ctx);
361
362 let bb = self.info.bounding_box;
363 #[cfg(feature = "complex-color")]
364 {
365 let clip = ctx.clip_stack.last().copied();
366 crate::color_paint::draw_color_layer_in_rect(
367 ctx.d,
368 bb.x,
369 bb.y,
370 bb.width,
371 bb.height,
372 &self.background_color,
373 &self.corner_radii,
374 clip,
375 );
376 }
377
378 #[cfg(not(feature = "complex-color"))]
379 {
380 if self.background_color.a > 0.0 {
381 ctx.d.draw_rectangle(
382 bb.x as i32,
383 bb.y as i32,
384 bb.width as i32,
385 bb.height as i32,
386 cotis_to_raylib_color(self.background_color),
387 );
388 }
389 }
390
391 ctx.d.draw_texture_ex(
392 &*texture,
393 Vector2::new(bb.x, bb.y),
394 0.0,
395 bb.width / texture.width as f32,
396 raylib::color::Color::WHITE,
397 );
398 }
399
400 fn scale_by(&mut self, factor: f32) {
401 scale_bounding_box(&mut self.info.bounding_box, factor);
402 scale_corner_radii(&mut self.corner_radii, factor);
403 }
404
405 fn preload_generic_images(&self, render: &mut RaylibRender) {
406 render.translate_generic_image(self.data.as_ref());
407 }
408}
409
410impl<'a, E> RaylibDrawable for ClipStart<'a, E> {
415 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
416 let bb = self.info.bounding_box;
417 let clip_rect = (bb.x, bb.y, bb.width.max(0.0), bb.height.max(0.0));
418 let effective = if let Some(parent) = ctx.clip_stack.last().copied() {
419 intersect_clip(parent, clip_rect)
420 } else {
421 clip_rect
422 };
423 ctx.clip_stack.push(effective);
424 unsafe {
425 raylib::ffi::BeginScissorMode(
426 effective.0 as i32,
427 effective.1 as i32,
428 effective.2 as i32,
429 effective.3 as i32,
430 );
431 }
432 }
433
434 fn scale_by(&mut self, factor: f32) {
435 scale_bounding_box(&mut self.info.bounding_box, factor);
436 scale_corner_radii(&mut self.corner_radii, factor);
437 }
438}
439
440impl RaylibDrawable for ClipEnd {
445 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
446 if ctx.clip_stack.pop().is_some() {
447 unsafe {
448 raylib::ffi::EndScissorMode();
449 if let Some(prev) = ctx.clip_stack.last().copied() {
450 raylib::ffi::BeginScissorMode(
451 prev.0 as i32,
452 prev.1 as i32,
453 prev.2 as i32,
454 prev.3 as i32,
455 );
456 }
457 }
458 }
459 }
460
461 fn scale_by(&mut self, _factor: f32) {}
462}
463
464impl<T: RaylibDrawable, L: RaylibDrawable + IsRenderList> RaylibDrawable for RenderTList<T, L> {
473 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
474 match *self {
475 RenderTList::Type(t) => Box::new(t).draw(ctx),
476 RenderTList::List(l) => Box::new(l).draw(ctx),
477 }
478 }
479
480 fn scale_by(&mut self, factor: f32) {
481 match self {
482 RenderTList::Type(t) => t.scale_by(factor),
483 RenderTList::List(l) => l.scale_by(factor),
484 }
485 }
486
487 fn preload_generic_images(&self, render: &mut RaylibRender) {
488 match self {
489 RenderTList::Type(t) => t.preload_generic_images(render),
490 RenderTList::List(l) => l.preload_generic_images(render),
491 }
492 }
493}
494
495impl<T: RaylibDrawable> RaylibDrawable for RenderTList<T, ()> {
496 fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
497 match *self {
498 RenderTList::Type(t) => Box::new(t).draw(ctx),
499 RenderTList::List(()) => {}
500 }
501 }
502
503 fn scale_by(&mut self, factor: f32) {
504 match self {
505 RenderTList::Type(t) => t.scale_by(factor),
506 RenderTList::List(()) => {}
507 }
508 }
509
510 fn preload_generic_images(&self, render: &mut RaylibRender) {
511 match self {
512 RenderTList::Type(t) => t.preload_generic_images(render),
513 RenderTList::List(()) => {}
514 }
515 }
516}
517
518