// Copyright (c) 2022 Marceline Cramer // SPDX-License-Identifier: Apache-2.0 use super::*; use num_traits::FromPrimitive; pub mod abi; #[macro_export] macro_rules! export_abi { ($bind_panel: ident) => { #[no_mangle] pub extern "C" fn bind_panel(panel: u32, protocol: u32, msg: u32) -> u32 { ::canary_script::api::abi::bind_panel($bind_panel, panel, protocol, msg) } #[no_mangle] pub extern "C" fn update(panel_data: u32, dt: f32) { ::canary_script::api::abi::update(panel_data, dt) } #[no_mangle] pub extern "C" fn draw(panel_data: u32) { ::canary_script::api::abi::draw(panel_data) } #[no_mangle] pub extern "C" fn on_resize(panel_data: u32, width: f32, height: f32) { ::canary_script::api::abi::on_resize(panel_data, width, height) } #[no_mangle] pub extern "C" fn on_cursor_event(panel_data: u32, kind: u32, x: f32, y: f32) { ::canary_script::api::abi::on_cursor_event(panel_data, kind, x, y) } #[no_mangle] pub extern "C" fn on_message(panel_data: u32, msg: u32) { ::canary_script::api::abi::on_message(panel_data, msg) } }; } pub trait PanelImpl { fn update(&mut self, dt: f32); fn draw(&mut self); fn on_resize(&mut self, new_size: Vec2); fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2); fn on_message(&mut self, msg: Message); } #[repr(transparent)] #[derive(Copy, Clone)] pub struct Panel(u32); impl Panel { pub unsafe fn bind(id: u32) -> Self { Self(id) } pub fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) { unsafe { draw_indexed( vertices.as_ptr() as u32, vertices.len() as u32, indices.as_ptr() as u32, indices.len() as u32, ) } } pub fn draw_text_layout(&self, layout: &TextLayout, offset: Vec2, scale: f32, color: Color) { unsafe { draw_text_layout(layout.0, offset.x, offset.y, scale, color.0) } } pub fn draw_triangle(&self, v1: Vec2, v2: Vec2, v3: Vec2, color: Color) { let vertices = [ MeshVertex { position: v1, color, }, MeshVertex { position: v2, color, }, MeshVertex { position: v3, color, }, ]; let indices = [0, 1, 2]; self.draw_indexed(&vertices, &indices); } pub fn send_message(&self, message: &[u8]) { unsafe { panel_send_message(self.0, message.as_ptr() as u32, message.len() as u32) } } } #[repr(transparent)] #[derive(Copy, Clone)] pub struct Font(u32); impl Font { pub fn new(family: &str) -> Self { unsafe { Self(font_load(family.as_ptr() as u32, family.len() as u32)) } } } #[repr(transparent)] pub struct TextLayout(u32); impl TextLayout { pub fn new(font: &Font, text: &str) -> Self { unsafe { Self(text_layout_new( font.0, text.as_ptr() as u32, text.len() as u32, )) } } pub fn get_bounds(&self) -> Rect { unsafe { let mut bounds = Rect::default(); let bounds_ptr: *mut Rect = &mut bounds; text_layout_get_bounds(self.0, bounds_ptr as u32); bounds } } } impl Drop for TextLayout { fn drop(&mut self) { unsafe { text_layout_delete(self.0) } } } #[repr(transparent)] pub struct Message(u32); impl Message { pub fn to_vec(self) -> Vec { unsafe { let len = message_get_len(self.0) as usize; let mut vec = Vec::with_capacity(len); vec.set_len(len); message_get_data(self.0, vec.as_ptr() as u32); vec } } } pub struct DrawContext { panel: Panel, offset: Option, clip_rect: Option, opacity: Option, } impl DrawContext { pub fn new(panel: Panel) -> Self { Self { panel, offset: None, clip_rect: None, opacity: None, } } pub fn child_at(&self, at: Vec2) -> Self { let at = self.offset.map(|old| old + at).unwrap_or(at); self.child_at_absolute(at) } pub fn child_at_absolute(&self, at: Vec2) -> Self { Self { panel: self.panel, offset: Some(at), clip_rect: None, opacity: None, } } pub fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) { let mut vertices = vertices.to_vec(); if let Some(offset) = self.offset { for v in vertices.iter_mut() { v.position += offset; } } if let Some(opacity) = self.opacity { for v in vertices.iter_mut() { v.color = v.color.alpha_multiply(opacity); } } self.panel.draw_indexed(&vertices, &indices); } pub fn draw_triangle(&self, v1: Vec2, v2: Vec2, v3: Vec2, color: Color) { if let Some(clip_rect) = self.clip_rect.as_ref() { let tri = ColoredTriangle { v1, v2, v3, color }; let bb = Rect::from_triangle_bounds(&tri); if clip_rect.contains_rect(&bb) { self.draw_triangle_noclip(tri.v1, tri.v2, tri.v3, tri.color); } } else { self.draw_triangle_noclip(v1, v2, v3, color); } } fn draw_triangle_noclip(&self, mut v1: Vec2, mut v2: Vec2, mut v3: Vec2, mut color: Color) { if let Some(offset) = self.offset { v1 += offset; v2 += offset; v3 += offset; } if let Some(opacity) = self.opacity.as_ref() { color = color.alpha_multiply(*opacity); } self.panel .draw_triangle(v1.into(), v2.into(), v3.into(), color); } pub fn draw_circle(&self, center: Vec2, radius: f32, color: Color) { use std::f32::consts::PI; let delta = PI / 16.0; let limit = PI * 2.0 + delta; let mut last_spoke = Vec2::new(radius + center.x, center.y); let mut theta = delta; while theta < limit { let new_spoke = Vec2::from_angle(theta) * radius + center; self.draw_triangle(center, last_spoke, new_spoke, color); last_spoke = new_spoke; theta += delta; } } pub fn draw_quarter_circle(&self, corner: Corner, center: Vec2, radius: f32, color: Color) { use std::f32::consts::{FRAC_PI_2, PI}; let spoke_num = 16.0; let delta = PI / 4.0 / spoke_num; let (mut theta, limit) = match corner { Corner::TopRight => (FRAC_PI_2 * 3.0, PI * 2.0), Corner::BottomRight => (0.0, FRAC_PI_2), Corner::BottomLeft => (FRAC_PI_2, PI), Corner::TopLeft => (PI, FRAC_PI_2 * 3.0), }; let mut last_spoke = Vec2::from_angle(theta) * radius + center; while theta < limit { theta += delta; let new_spoke = Vec2::from_angle(theta) * radius + center; self.draw_triangle(center, last_spoke, new_spoke, color); last_spoke = new_spoke; } } pub fn draw_ring(&self, center: Vec2, radius: f32, thickness: f32, color: Color) { use std::f32::consts::PI; let delta = PI / 64.0; let limit = PI * 2.0 + delta; let mut last_spoke = glam::Vec2::new(radius + center.x, center.y); let mut last_theta = 0.0; let mut theta = delta; while theta < limit { let angle = Vec2::from_angle(theta); let new_spoke = angle * radius + center; let new_spoke2 = angle * (radius + thickness) + center; let last_spoke2 = Vec2::from_angle(last_theta) * (radius + thickness) + center; self.draw_triangle(new_spoke2, last_spoke, new_spoke, color); self.draw_triangle(new_spoke2, last_spoke2, last_spoke, color); last_spoke = new_spoke; last_theta = theta; theta += delta; } } pub fn draw_rect(&self, rect: Rect, color: Color) { let rect = if let Some(clip_rect) = self.clip_rect.as_ref() { if let Some(clipped) = clip_rect.intersection(&rect) { clipped } else { return; } } else { rect }; let v1 = rect.tl; let v2 = Vec2::new(rect.tl.x, rect.br.y); let v3 = Vec2::new(rect.br.x, rect.tl.y); let v4 = rect.br; self.draw_triangle_noclip(v1, v2, v3, color); self.draw_triangle_noclip(v2, v3, v4, color); } pub fn draw_rounded_rect(&self, rect: Rect, radius: f32, color: Color) { self.draw_partially_rounded_rect(CornerFlags::ALL, rect, radius, color); } pub fn draw_partially_rounded_rect( &self, corners: CornerFlags, rect: Rect, radius: f32, color: Color, ) { if corners.is_empty() { self.draw_rect(rect, color); return; } let mut inner_rect = rect; let inset = rect.inset(radius); if corners.intersects(CornerFlags::TOP) { inner_rect.tl.y += radius; let mut top_edge = Rect { tl: rect.tl, br: Vec2::new(rect.br.x, rect.tl.y + radius), }; if corners.contains(CornerFlags::TOP_LEFT) { top_edge.tl.x += radius; self.draw_quarter_circle(Corner::TopLeft, inset.tl, radius, color); } if corners.contains(CornerFlags::TOP_RIGHT) { top_edge.br.x -= radius; self.draw_quarter_circle(Corner::TopRight, inset.tr(), radius, color); } self.draw_rect(top_edge, color); } if corners.intersects(CornerFlags::BOTTOM) { inner_rect.br.y -= radius; let mut bottom_edge = Rect { tl: Vec2::new(rect.tl.x, rect.br.y - radius), br: rect.br, }; if corners.contains(CornerFlags::BOTTOM_LEFT) { bottom_edge.tl.x += radius; self.draw_quarter_circle(Corner::BottomLeft, inset.bl(), radius, color); } if corners.contains(CornerFlags::BOTTOM_RIGHT) { bottom_edge.br.x -= radius; self.draw_quarter_circle(Corner::BottomRight, inset.br, radius, color); } self.draw_rect(bottom_edge, color); } self.draw_rect(inner_rect, color); } pub fn draw_text_layout( &self, layout: &TextLayout, mut offset: Vec2, scale: f32, mut color: Color, ) { if let Some(delta) = self.offset { offset += delta; } if let Some(opacity) = self.opacity { color = color.alpha_multiply(opacity); } self.panel .draw_text_layout(layout, offset.into(), scale, color); } pub fn get_clip_rect(&self) -> &Option { &self.clip_rect } pub fn with_offset(&self, offset: Vec2) -> Self { Self { offset: self.offset.map(|old| old + offset).or(Some(offset)), clip_rect: self.clip_rect.map(|r| r.offset(-offset)), ..*self } } pub fn with_clip_rect(&self, mut clip_rect: Rect) -> Option { if let Some(old) = self.clip_rect { if let Some(clipped) = old.intersection(&clip_rect) { clip_rect = clipped; } else { return None; } } Some(Self { clip_rect: Some(clip_rect), ..*self }) } pub fn with_opacity(&self, mut opacity: u8) -> Self { if let Some(old) = self.opacity { opacity = (((opacity as u16) * (old as u16)) >> 8) as u8; } Self { opacity: Some(opacity), ..*self } } } extern "C" { fn draw_indexed(vertices_ptr: u32, vertices_num: u32, indices_ptr: u32, indices_num: u32); fn draw_text_layout(id: u32, xoff: f32, yoff: f32, scale: f32, color: u32); fn font_load(family_ptr: u32, family_len: u32) -> u32; fn text_layout_new(font_id: u32, text_ptr: u32, text_len: u32) -> u32; fn text_layout_delete(id: u32); fn text_layout_get_bounds(id: u32, rect_ptr: u32); fn message_get_len(id: u32) -> u32; fn message_get_data(id: u32, ptr: u32); fn panel_send_message(id: u32, message_ptr: u32, message_len: u32); }