use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use raylib::math::Vector2;
use crate::fonts::ScalableFont;
use crate::raylib_images::GenericCompatibleImage;
use crate::renderer::RaylibRender;
use cotis_defaults::colors::Color as CotisColor;
use cotis_defaults::render_commands::render_t_list::{IsRenderList, RenderTList};
use cotis_defaults::render_commands::*;
use cotis_utils::math::BoundingBox;
use raylib::texture::Texture2D;
pub struct RaylibDrawContext<'d, 'rl> {
pub d: &'d mut RaylibDrawHandle<'rl>,
pub fonts: Arc<Mutex<HashMap<usize, ScalableFont>>>,
pub clip_stack: &'d mut Vec<(f32, f32, f32, f32)>,
pub image_cache: Option<&'d HashMap<String, Arc<Texture2D>>>,
}
pub trait RaylibDrawable {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>);
fn scale_by(&mut self, factor: f32);
fn preload_generic_images(&self, _render: &mut RaylibRender) {}
}
pub(crate) fn cotis_to_raylib_color(c: CotisColor) -> raylib::color::Color {
raylib::color::Color::new(c.r as u8, c.g as u8, c.b as u8, c.a as u8)
}
fn scale_corner_radii(r: &mut CornerRadii, factor: f32) {
r.top_left *= factor;
r.top_right *= factor;
r.bottom_left *= factor;
r.bottom_right *= factor;
}
fn scale_bounding_box(bb: &mut BoundingBox, factor: f32) {
bb.x *= factor;
bb.y *= factor;
bb.width *= factor;
bb.height *= factor;
}
fn intersect_clip(a: (f32, f32, f32, f32), b: (f32, f32, f32, f32)) -> (f32, f32, f32, f32) {
let ax2 = a.0 + a.2;
let ay2 = a.1 + a.3;
let bx2 = b.0 + b.2;
let by2 = b.1 + b.3;
let x = a.0.max(b.0);
let y = a.1.max(b.1);
let w = ax2.min(bx2) - x;
let h = ay2.min(by2) - y;
(x, y, w.max(0.0), h.max(0.0))
}
impl<'a, E> RaylibDrawable for Rectangle<'a, E> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
let bb = self.info.bounding_box;
#[cfg(feature = "complex-color")]
{
let clip = ctx.clip_stack.last().copied();
crate::color_paint::draw_color_layer_in_rect(
ctx.d,
bb.x,
bb.y,
bb.width,
bb.height,
&self.color,
&self.corner_radii,
clip,
);
}
#[cfg(not(feature = "complex-color"))]
{
if self.color.a > 0.0 {
if self.corner_radii.top_left > 0.0 {
let radius = (self.corner_radii.top_left * 2.0)
/ if bb.width > bb.height {
bb.height
} else {
bb.width
};
ctx.d.draw_rectangle_rounded(
raylib::math::Rectangle::new(bb.x, bb.y, bb.width, bb.height),
radius,
8,
cotis_to_raylib_color(self.color),
);
} else {
ctx.d.draw_rectangle(
bb.x as i32,
bb.y as i32,
bb.width as i32,
bb.height as i32,
cotis_to_raylib_color(self.color),
);
}
}
}
}
fn scale_by(&mut self, factor: f32) {
scale_bounding_box(&mut self.info.bounding_box, factor);
scale_corner_radii(&mut self.corner_radii, factor);
}
}
impl<'a, E> RaylibDrawable for Text<'a, E> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
let bb = self.info.bounding_box;
let text_str: &str = self.text.as_ref();
let fonts_arc = ctx.fonts.clone();
let mut guard = fonts_arc.lock().unwrap();
#[cfg(feature = "complex-color")]
{
if let Some(font) = guard.get_mut(&(self.font_id as usize)) {
let f = font.get_font_for_size(self.font_size as usize);
crate::color_paint::draw_text_color_layer(
ctx.d,
f,
text_str,
Vector2::new(bb.x, bb.y),
self.font_size,
self.letter_spacing,
&self.color,
);
} else {
let c = crate::color_paint::text_color_at(&self.color, 0.0);
ctx.d.draw_text(
text_str,
bb.x as i32,
bb.y as i32,
self.font_size as i32,
crate::color_paint::cotis_color_to_raylib(c),
);
}
}
#[cfg(not(feature = "complex-color"))]
{
if let Some(font) = guard.get_mut(&(self.font_id as usize)) {
let raylib_font = font.get_font_for_size(self.font_size as usize);
ctx.d.draw_text_ex(
raylib_font,
text_str,
Vector2::new(bb.x, bb.y),
self.font_size,
self.letter_spacing,
cotis_to_raylib_color(self.color),
);
} else {
ctx.d.draw_text(
text_str,
bb.x as i32,
bb.y as i32,
self.font_size as i32,
cotis_to_raylib_color(self.color),
);
}
}
}
fn scale_by(&mut self, factor: f32) {
scale_bounding_box(&mut self.info.bounding_box, factor);
self.font_size *= factor;
self.letter_spacing *= factor;
self.line_height *= factor;
}
}
impl<'a, E> RaylibDrawable for Border<'a, E> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
let bb = self.info.bounding_box;
let color = cotis_to_raylib_color(self.color);
if self.width.left > 0.0 {
ctx.d.draw_rectangle(
bb.x as i32,
(bb.y + self.corner_radii.top_left) as i32,
self.width.left as i32,
(bb.height - self.corner_radii.top_left - self.corner_radii.bottom_left) as i32,
color,
);
}
if self.width.right > 0.0 {
ctx.d.draw_rectangle(
(bb.x + bb.width - self.width.right) as i32,
(bb.y + self.corner_radii.top_right) as i32,
self.width.right as i32,
(bb.height - self.corner_radii.top_right - self.corner_radii.bottom_right) as i32,
color,
);
}
if self.width.top > 0.0 {
ctx.d.draw_rectangle(
(bb.x + self.corner_radii.top_left) as i32,
bb.y as i32,
(bb.width - self.corner_radii.top_left - self.corner_radii.top_right) as i32,
self.width.top as i32,
color,
);
}
if self.width.bottom > 0.0 {
ctx.d.draw_rectangle(
(bb.x + self.corner_radii.bottom_left) as i32,
(bb.y + bb.height - self.width.bottom) as i32,
(bb.width - self.corner_radii.bottom_left - self.corner_radii.bottom_right) as i32,
self.width.bottom as i32,
color,
);
}
if self.corner_radii.top_left > 0.0 {
ctx.d.draw_ring(
Vector2::new(
bb.x + self.corner_radii.top_left,
bb.y + self.corner_radii.top_left,
),
self.corner_radii.top_left - self.width.top,
self.corner_radii.top_left,
180.0,
270.0,
10,
color,
);
}
if self.corner_radii.top_right > 0.0 {
ctx.d.draw_ring(
Vector2::new(
bb.x + bb.width - self.corner_radii.top_right,
bb.y + self.corner_radii.top_right,
),
self.corner_radii.top_right - self.width.top,
self.corner_radii.top_right,
270.0,
360.0,
10,
color,
);
}
if self.corner_radii.bottom_left > 0.0 {
ctx.d.draw_ring(
Vector2::new(
bb.x + self.corner_radii.bottom_left,
bb.y + bb.height - self.corner_radii.bottom_left,
),
self.corner_radii.bottom_left - self.width.bottom,
self.corner_radii.bottom_left,
90.0,
180.0,
10,
color,
);
}
if self.corner_radii.bottom_right > 0.0 {
ctx.d.draw_ring(
Vector2::new(
bb.x + bb.width - self.corner_radii.bottom_right,
bb.y + bb.height - self.corner_radii.bottom_right,
),
self.corner_radii.bottom_right - self.width.bottom,
self.corner_radii.bottom_right,
0.0,
90.0,
10,
color,
);
}
}
fn scale_by(&mut self, factor: f32) {
scale_bounding_box(&mut self.info.bounding_box, factor);
scale_corner_radii(&mut self.corner_radii, factor);
self.width.left *= factor;
self.width.right *= factor;
self.width.top *= factor;
self.width.bottom *= factor;
self.width.between_children *= factor;
}
}
impl<'a, I: GenericCompatibleImage, E> RaylibDrawable for Image<'a, I, E> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
GenericCompatibleImage::load_texture(self.data.as_ref(), ctx);
let texture = GenericCompatibleImage::get_texture(self.data.as_ref(), ctx);
let bb = self.info.bounding_box;
#[cfg(feature = "complex-color")]
{
let clip = ctx.clip_stack.last().copied();
crate::color_paint::draw_color_layer_in_rect(
ctx.d,
bb.x,
bb.y,
bb.width,
bb.height,
&self.background_color,
&self.corner_radii,
clip,
);
}
#[cfg(not(feature = "complex-color"))]
{
if self.background_color.a > 0.0 {
ctx.d.draw_rectangle(
bb.x as i32,
bb.y as i32,
bb.width as i32,
bb.height as i32,
cotis_to_raylib_color(self.background_color),
);
}
}
ctx.d.draw_texture_ex(
&*texture,
Vector2::new(bb.x, bb.y),
0.0,
bb.width / texture.width as f32,
raylib::color::Color::WHITE,
);
}
fn scale_by(&mut self, factor: f32) {
scale_bounding_box(&mut self.info.bounding_box, factor);
scale_corner_radii(&mut self.corner_radii, factor);
}
fn preload_generic_images(&self, render: &mut RaylibRender) {
render.translate_generic_image(self.data.as_ref());
}
}
impl<'a, E> RaylibDrawable for ClipStart<'a, E> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
let bb = self.info.bounding_box;
let clip_rect = (bb.x, bb.y, bb.width.max(0.0), bb.height.max(0.0));
let effective = if let Some(parent) = ctx.clip_stack.last().copied() {
intersect_clip(parent, clip_rect)
} else {
clip_rect
};
ctx.clip_stack.push(effective);
unsafe {
raylib::ffi::BeginScissorMode(
effective.0 as i32,
effective.1 as i32,
effective.2 as i32,
effective.3 as i32,
);
}
}
fn scale_by(&mut self, factor: f32) {
scale_bounding_box(&mut self.info.bounding_box, factor);
scale_corner_radii(&mut self.corner_radii, factor);
}
}
impl RaylibDrawable for ClipEnd {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
if ctx.clip_stack.pop().is_some() {
unsafe {
raylib::ffi::EndScissorMode();
if let Some(prev) = ctx.clip_stack.last().copied() {
raylib::ffi::BeginScissorMode(
prev.0 as i32,
prev.1 as i32,
prev.2 as i32,
prev.3 as i32,
);
}
}
}
}
fn scale_by(&mut self, _factor: f32) {}
}
impl<T: RaylibDrawable, L: RaylibDrawable + IsRenderList> RaylibDrawable for RenderTList<T, L> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
match *self {
RenderTList::Type(t) => Box::new(t).draw(ctx),
RenderTList::List(l) => Box::new(l).draw(ctx),
}
}
fn scale_by(&mut self, factor: f32) {
match self {
RenderTList::Type(t) => t.scale_by(factor),
RenderTList::List(l) => l.scale_by(factor),
}
}
fn preload_generic_images(&self, render: &mut RaylibRender) {
match self {
RenderTList::Type(t) => t.preload_generic_images(render),
RenderTList::List(l) => l.preload_generic_images(render),
}
}
}
impl<T: RaylibDrawable> RaylibDrawable for RenderTList<T, ()> {
fn draw(self: Box<Self>, ctx: &mut RaylibDrawContext<'_, '_>) {
match *self {
RenderTList::Type(t) => Box::new(t).draw(ctx),
RenderTList::List(()) => {}
}
}
fn scale_by(&mut self, factor: f32) {
match self {
RenderTList::Type(t) => t.scale_by(factor),
RenderTList::List(()) => {}
}
}
fn preload_generic_images(&self, render: &mut RaylibRender) {
match self {
RenderTList::Type(t) => t.preload_generic_images(render),
RenderTList::List(()) => {}
}
}
}