453 lines
13 KiB
Rust
453 lines
13 KiB
Rust
// 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<u8> {
|
|
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<Vec2>,
|
|
clip_rect: Option<Rect>,
|
|
opacity: Option<u8>,
|
|
}
|
|
|
|
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<Rect> {
|
|
&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<Self> {
|
|
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);
|
|
}
|