use alloc::rc::Rc;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, Ordering};
use super::consts::*;
use super::Ui;
use gfx::shapes::Shapes;
use gfx::{Dc, Surface};
use text::Text;
pub(crate) const CAPS_MAIN: [u32; L_COUNT] = [1024 * 1024, 256 * 1024, 256 * 1024, 256 * 1024];
#[allow(dead_code)] pub(crate) const CAPS_SMALL: [u32; L_COUNT] = [256 * 1024, 64 * 1024, 128 * 1024, 16 * 1024];
pub struct Gfx<'a> {
pub dc: &'a mut Dc,
pub sf: &'a mut Surface,
pub text: &'a mut Text,
}
impl Gfx<'_> {
pub fn shapes(&mut self) -> Shapes<'_> {
Shapes { dc: &mut *self.dc, sf: &mut *self.sf, path: &mut self.text.path, ras: &mut self.text.ras }
}
}
pub trait Draw {
fn draw(&self, g: &mut Gfx, r: [i32; 4]);
}
pub type DrawRef = Rc<dyn Draw>;
pub type IconHook = Rc<dyn Fn(&mut Gfx, u32, i32, i32, i32, u32) -> bool>;
#[derive(Clone)]
#[non_exhaustive]
pub enum Cmd {
Rect { x: i32, y: i32, w: i32, h: i32, c: u32 },
RRect { x: i32, y: i32, w: i32, h: i32, r: u16, t: u16, fill: u32, stroke: u32, stroke2: u32 },
PanelFx { x: i32, y: i32, w: i32, h: i32, r: i32, t: i32, fill: u32, stroke: u32 },
Text { x: i32, y: i32, font: u16, size: i32, c: u32, maxw: i32, s: Vec<u8> },
Icon { x: i32, y: i32, cp: u32, size: i32, c: u32 },
Image { x: i32, y: i32, w: i32, h: i32, sf: Rc<Surface>, ver: u32 },
Clip { x: i32, y: i32, w: i32, h: i32 },
Unclip,
Circle { x: i32, y: i32, r: i32, t: i32, c: u32 },
Line { x0: i32, y0: i32, x1: i32, y1: i32, w: i32, c: u32 },
Custom { x: i32, y: i32, w: i32, h: i32, f: Option<DrawRef>, user: usize, ver: u32 },
Shadow { x: i32, y: i32, w: i32, h: i32, r: i32, depth: i32, c: u32 },
}
impl Cmd {
fn size(&self) -> u32 {
let n = match self {
Cmd::Rect { .. } | Cmd::Icon { .. } | Cmd::Circle { .. } | Cmd::Clip { .. } => 24,
Cmd::RRect { .. } | Cmd::Image { .. } => 40,
Cmd::PanelFx { .. } => 40,
Cmd::Text { s, .. } => 28 + s.len() as u32,
Cmd::Unclip => 8,
Cmd::Line { .. } | Cmd::Shadow { .. } => 32,
Cmd::Custom { .. } => 48,
};
(n + 7) & !7
}
}
pub(crate) struct W {
pub(crate) layers: [Vec<Cmd>; L_COUNT],
pub(crate) len: [u32; L_COUNT],
pub(crate) cap: [u32; L_COUNT],
pub(crate) layer: usize,
pub(crate) dcol: u32,
pub(crate) overflow: bool,
}
impl W {
pub(crate) fn new(caps: &[u32; L_COUNT]) -> W {
W {
layers: Default::default(),
len: [0; L_COUNT],
cap: *caps,
layer: 0,
dcol: 0xFF00_0000,
overflow: false,
}
}
}
struct Fnv(u64);
impl Fnv {
fn new() -> Fnv {
Fnv(0xCBF2_9CE4_8422_2325)
}
fn bytes(&mut self, b: &[u8]) {
for &x in b {
self.0 ^= x as u64;
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
}
}
fn u32(&mut self, v: u32) {
self.bytes(&v.to_le_bytes());
}
fn i32(&mut self, v: i32) {
self.bytes(&v.to_le_bytes());
}
fn u64(&mut self, v: u64) {
self.bytes(&v.to_le_bytes());
}
}
pub(crate) fn cmd_hash(c: &Cmd) -> u64 {
let (body, x, y, _) = cmd_parts(c);
pos_mix(body, x as i64, y as i64)
}
#[inline]
pub(crate) fn pos_mix(body: u64, x: i64, y: i64) -> u64 {
const K: u64 = 0x9E37_79B9_7F4A_7C15;
let h = (body ^ x as u64).wrapping_mul(K);
let h = (h.rotate_left(29) ^ y as u64).wrapping_mul(K);
h ^ h >> 31
}
pub(crate) fn cmd_parts(c: &Cmd) -> (u64, i32, i32, u32) {
let mut h = Fnv::new();
let size = c.size();
let head = |h: &mut Fnv, t: u8, argb: u32| {
h.bytes(&[t, 0]);
h.bytes(&(size as u16).to_le_bytes());
h.u32(argb);
};
let (x, y, sh) = match *c {
Cmd::Rect { x, y, w, h: hh, c } => {
head(&mut h, 1, c);
for v in [w, hh] {
h.i32(v);
}
(x, y, 0)
}
Cmd::RRect { x, y, w, h: hh, r, t, fill, stroke, stroke2 } => {
head(&mut h, 2, fill);
for v in [w, hh] {
h.i32(v);
}
h.bytes(&r.to_le_bytes());
h.bytes(&t.to_le_bytes());
h.u32(stroke);
h.u32(stroke2);
(x, y, 0)
}
Cmd::PanelFx { x, y, w, h: hh, r, t, fill, stroke } => {
head(&mut h, 12, fill);
for v in [w, hh, r, t] {
h.i32(v);
}
h.u32(stroke);
(x, y, 8)
}
Cmd::Text { x, y, font, size: sz, c, maxw, ref s } => {
head(&mut h, 3, c);
h.bytes(&font.to_le_bytes());
h.bytes(&(s.len() as u16).to_le_bytes());
h.i32(sz);
h.i32(maxw);
h.bytes(s);
(x, y, 0)
}
Cmd::Icon { x, y, cp, size: sz, c } => {
head(&mut h, 4, c);
h.u32(cp);
h.i32(sz);
(x, y, 0)
}
Cmd::Image { x, y, w, h: hh, ref sf, ver } => {
head(&mut h, 5, 0);
for v in [w, hh] {
h.i32(v);
}
h.u64(Rc::as_ptr(sf) as usize as u64);
h.u32(ver);
(x, y, 0)
}
Cmd::Clip { x, y, w, h: hh } => {
head(&mut h, 6, 0);
for v in [w, hh] {
h.i32(v);
}
(x, y, 0)
}
Cmd::Unclip => {
head(&mut h, 7, 0);
(0, 0, 0)
}
Cmd::Circle { x, y, r, t, c } => {
head(&mut h, 8, c);
for v in [r, t] {
h.i32(v);
}
(x, y, 8)
}
Cmd::Line { x0, y0, x1, y1, w, c } => {
head(&mut h, 9, c);
for v in [x1.wrapping_sub(x0), y1.wrapping_sub(y0), w] {
h.i32(v);
}
(x0, y0, 8)
}
Cmd::Custom { x, y, w, h: hh, ref f, user, ver } => {
head(&mut h, 10, 0);
for v in [w, hh] {
h.i32(v);
}
h.u64(f.as_ref().map_or(0, |f| Rc::as_ptr(f) as *const () as usize as u64));
h.u64(user as u64);
h.u32(ver);
(x, y, 0)
}
Cmd::Shadow { x, y, w, h: hh, r, depth, c } => {
head(&mut h, 11, c);
for v in [w, hh, r, depth] {
h.i32(v);
}
(x, y, 0)
}
};
(h.0, x, y, sh)
}
pub(crate) fn cmd_bbox(c: &Cmd) -> Option<[i32; 4]> {
Some(match *c {
Cmd::PanelFx { x, y, w, h, .. } => [x >> 8, y >> 8,
((x as i64 + w as i64 + 255) >> 8) as i32,
((y as i64 + h as i64 + 255) >> 8) as i32],
Cmd::Text { x, y, size, maxw, ref s, .. } => {
let px = ((size as u32) >> 8).wrapping_add(1) as i32;
let x0 = x.wrapping_sub(2);
let tw = if maxw != 0 { maxw } else { (s.len() as i32).wrapping_mul(px) };
let half = ((px as u32) >> 1) as i32;
[x0, y.wrapping_sub(half.wrapping_add(px)), x0.wrapping_add(tw).wrapping_add(4), y.wrapping_add(half).wrapping_add(1)]
}
Cmd::Icon { x, y, size, .. } => {
let s = ((size as u32) >> 8).wrapping_add(2) as i32; let (x0, y0) = (x.wrapping_sub(1), y.wrapping_sub(1));
[x0, y0, x0.wrapping_add(s), y0.wrapping_add(s)]
}
Cmd::Circle { x, y, r, t, .. } => {
let d = (r.wrapping_add(t) >> 8).wrapping_add(2);
let (cx, cy) = (x >> 8, y >> 8);
[cx.wrapping_sub(d), cy.wrapping_sub(d), cx.wrapping_add(d).wrapping_add(1), cy.wrapping_add(d).wrapping_add(1)]
}
Cmd::Line { x0, y0, x1, y1, w, .. } => {
let d = (w >> 8).wrapping_add(2); [
(x0.min(x1) >> 8).wrapping_sub(d),
(y0.min(y1) >> 8).wrapping_sub(d),
(x0.max(x1) >> 8).wrapping_add(d).wrapping_add(1),
(y0.max(y1) >> 8).wrapping_add(d).wrapping_add(1),
]
}
Cmd::Shadow { x, y, w, h, depth: d, .. } => {
[x.wrapping_sub(d), y.wrapping_sub(d), x.wrapping_add(w).wrapping_add(d), y.wrapping_add(h).wrapping_add(d).wrapping_add(d)]
}
Cmd::Clip { .. } | Cmd::Unclip => return None,
Cmd::Rect { x, y, w, h, .. }
| Cmd::RRect { x, y, w, h, .. }
| Cmd::Image { x, y, w, h, .. }
| Cmd::Custom { x, y, w, h, .. } => [x, y, x.wrapping_add(w), y.wrapping_add(h)],
})
}
pub(crate) static ICON_PAINTED: AtomicBool = AtomicBool::new(false);
fn cmd_visible(c: &Cmd, dc: &Dc) -> bool {
let Some(b) = cmd_bbox(c) else { return true };
let k = dc.clip;
!(b[0] >= k[2] || b[2] <= k[0] || b[1] >= k[3] || b[3] <= k[1])
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_layer(list: &[Cmd], from: usize, dc: &mut Dc, sf: &mut Surface, text: &mut Text, hook: &Option<IconHook>,
icon_style: (&dyn crate::Style, u32, bool, u32, u32)) {
let mut depth = 0; for (i, c) in list.iter().enumerate() {
if !matches!(c, Cmd::Clip { .. } | Cmd::Unclip) && (i < from || !cmd_visible(c, dc)) {
continue;
}
match c {
&Cmd::PanelFx { x, y, w, h, r, t, fill, stroke } => {
let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
sh.panel_fx(x, y, w, h, r, t, fill, stroke);
}
&Cmd::Rect { x, y, w, h, c } => {
dc.set_color(c);
dc.fill(sf, x, y, w, h);
}
&Cmd::RRect { x, y, w, h, r, t, fill, stroke, stroke2 } => {
let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
draw_rrect(&mut sh, [x, y, w, h], r as i32, t as i32, fill, stroke, stroke2);
}
Cmd::Text { x, y, font, size, c, maxw, s } => {
dc.set_color(*c);
text.set_font(*font as u32, *size);
if *maxw != 0 {
dc.clip_push(*x, -100000, *maxw, 200000);
}
text.draw_n(dc, sf, *x, *y, s, s.len());
if *maxw != 0 {
dc.clip_pop();
}
}
&Cmd::Icon { x, y, cp, size, c } => {
if let Some(f) = hook {
let (tx, ty) = (x.wrapping_add(dc.ox), y.wrapping_add(dc.oy));
let mut g = Gfx { dc: &mut *dc, sf: &mut *sf, text: &mut *text };
if f(&mut g, cp, tx, ty, size, c) {
ICON_PAINTED.store(true, Ordering::Relaxed);
continue;
}
}
let (style, accent, dark, disabled, dpi) = icon_style;
let (tx, ty) = (x.wrapping_add(dc.ox), y.wrapping_add(dc.oy));
let mut g = Gfx { dc: &mut *dc, sf: &mut *sf, text: &mut *text };
if style.paint_icon(&mut g, cp, tx, ty, size, c, accent, dark, c == disabled, dpi) {
ICON_PAINTED.store(true, Ordering::Relaxed);
continue;
}
dc.set_color(c);
text.icon_draw(dc, sf, cp, x, y, size);
}
Cmd::Image { x, y, w, h, sf: src, .. } => {
dc.blit_scaled(sf, src, *x, *y, *w, *h);
}
&Cmd::Clip { x, y, w, h } => {
dc.clip_push(x, y, w, h);
depth += 1;
}
Cmd::Unclip => {
if depth > 0 {
dc.clip_pop();
depth -= 1;
}
}
&Cmd::Circle { x, y, r, t, c } => {
dc.set_color(c);
let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
if t != 0 {
sh.stroke_circle_fx(x, y, r, t);
} else {
sh.fill_circle_fx(x, y, r);
}
}
&Cmd::Line { x0, y0, x1, y1, w, c } => {
dc.set_color(c);
let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
sh.line_fx(x0, y0, x1, y1, w);
}
Cmd::Custom { x, y, w, h, f, .. } => {
dc.clip_push(*x, *y, *w, *h);
if let Some(f) = f {
let mut g = Gfx { dc: &mut *dc, sf: &mut *sf, text: &mut *text };
f.draw(&mut g, [*x, *y, *w, *h]);
}
dc.clip_pop();
}
&Cmd::Shadow { x, y, w, h, r, depth: d, c } => {
let mut sh = Shapes { dc: &mut *dc, sf: &mut *sf, path: &mut text.path, ras: &mut text.ras };
draw_shadow(&mut sh, [x, y, w, h], r, d, c);
}
}
}
while depth > 0 {
dc.clip_pop();
depth -= 1;
}
}
pub(crate) fn draw_rrect(sh: &mut Shapes, b: [i32; 4], r: i32, t: i32, fill: u32, stroke: u32, stroke2: u32) {
let [x, y, w, h] = b;
if fill & 0xFF00_0000 != 0 {
sh.dc.set_color(fill);
sh.fill_rrect(x, y, w, h, r);
}
if t == 0 {
return;
}
if stroke & 0xFF00_0000 != 0 {
sh.dc.set_color(stroke);
sh.stroke_rrect(x, y, w, h, r, t);
}
if stroke2 != stroke && stroke2 & 0xFF00_0000 != 0 {
sh.dc.set_color(stroke2);
let band = r.max(t);
sh.dc.clip_push(x, y.wrapping_add(h).wrapping_sub(band), w, band);
sh.stroke_rrect(x, y, w, h, r, t);
sh.dc.clip_pop();
}
}
pub(crate) fn draw_shadow(sh: &mut Shapes, b: [i32; 4], r: i32, d: i32, c: u32) {
let [x, y, w, h] = b;
if d <= 0 {
return;
}
let base = c >> 24;
let down = (d as u32 / 3) as i32;
let mut i = d;
while i != 0 {
let a = base.wrapping_mul((d - i + 1) as u32) / ((d + d) as u32);
sh.dc.set_color((c & 0xFF_FFFF) | a << 24);
sh.fill_rrect(x - i, y - i + down, w + i * 2, h + i * 2, r + i);
i -= 1;
}
}
#[allow(dead_code)] fn translate(list: &mut [Cmd], dx: i32, dy: i32) {
for c in list.iter_mut() {
match c {
Cmd::Unclip => {}
Cmd::Circle { x, y, .. } | Cmd::PanelFx { x, y, .. } => {
*x = x.wrapping_add(dx << 8);
*y = y.wrapping_add(dy << 8);
}
Cmd::Line { x0, y0, x1, y1, .. } => {
*x1 = x1.wrapping_add(dx << 8);
*y1 = y1.wrapping_add(dy << 8);
*x0 = x0.wrapping_add(dx << 8);
*y0 = y0.wrapping_add(dy << 8);
}
Cmd::Rect { x, y, .. }
| Cmd::RRect { x, y, .. }
| Cmd::Text { x, y, .. }
| Cmd::Icon { x, y, .. }
| Cmd::Image { x, y, .. }
| Cmd::Clip { x, y, .. }
| Cmd::Custom { x, y, .. }
| Cmd::Shadow { x, y, .. } => {
*x = x.wrapping_add(dx);
*y = y.wrapping_add(dy);
}
}
}
}
impl Ui {
pub(crate) fn cmd_reset(&mut self) {
let w = &mut self.wp.cmd;
for l in w.layers.iter_mut() {
l.clear();
}
w.len = [0; L_COUNT];
w.layer = 0;
w.dcol = 0xFF00_0000;
self.wp.rtl.reset();
self.wp.scopy.reset(); self.wp.canv.recs.clear(); }
pub fn cmd_layer(&mut self, l: u32) -> u32 {
let prev = self.wp.cmd.layer as u32;
self.wp.cmd.layer = (l & 3) as usize;
prev
}
fn push(&mut self, c: Cmd) {
let w = &mut self.wp.cmd;
let l = w.layer;
let n = w.len[l] + c.size();
if n > w.cap[l] {
w.overflow = true;
return;
}
w.len[l] = n;
w.layers[l].push(c);
}
pub fn draw_color(&mut self, argb: u32) {
self.wp.cmd.dcol = argb;
}
pub fn draw_color_get(&self) -> u32 {
self.wp.cmd.dcol
}
pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32) {
let c = self.wp.cmd.dcol;
self.push(Cmd::Rect { x, y, w, h, c });
}
pub fn draw_rrect(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32) {
let c = self.wp.cmd.dcol;
self.cmd_rrect(x, y, w, h, r, 0, c, 0, 0);
}
pub fn draw_frame(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, t: i32) {
let c = self.wp.cmd.dcol;
self.cmd_rrect(x, y, w, h, r, t, 0, c, c);
}
#[allow(clippy::too_many_arguments)]
pub fn cmd_rrect(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, t: i32, fill: u32, stroke: u32, stroke2: u32) {
let r = if self.look.rad_mode != 0 { self.look_radius(r, w, h) } else { r }; self.push(Cmd::RRect { x, y, w, h, r: r as u16, t: t as u16, fill, stroke, stroke2 });
}
#[allow(clippy::too_many_arguments)]
pub fn cmd_panel_fx(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, t: i32, fill: u32, stroke: u32) {
self.push(Cmd::PanelFx { x, y, w, h, r, t, fill, stroke });
}
pub fn draw_text(&mut self, x: i32, y: i32, s: &str, font_size: u32) {
let c = self.wp.cmd.dcol;
self.cmd_text(x, y, s.as_bytes(), font_size & 255, (font_size >> 8) as i32, c, 0);
}
#[allow(clippy::too_many_arguments)]
pub fn cmd_text(&mut self, x: i32, y: i32, s: &[u8], font: u32, size: i32, c: u32, maxw: i32) {
let mut n = s.len();
if n > 4000 {
n = 4000;
while n > 0 && s[n] & 0xC0 == 0x80 {
n -= 1;
}
}
self.push(Cmd::Text { x, y, font: font as u16, size, c, maxw, s: s[..n].to_vec() });
}
pub fn emit_text(&mut self, x: i32, y: i32, s: &str, c: u32) {
let (font, size) = self.text.get_font();
let b = s.as_bytes();
let n = b.iter().position(|&x| x == 0).unwrap_or(b.len());
self.cmd_text(x, y, &b[..n], font, size, c, 0);
}
pub fn draw_icon(&mut self, cp: u32, x: i32, y: i32, size: i32) {
let c = self.wp.cmd.dcol;
self.push(Cmd::Icon { x, y, cp, size, c });
}
pub fn draw_circle(&mut self, x: i32, y: i32, r: i32, t: i32) {
let c = self.wp.cmd.dcol;
self.push(Cmd::Circle { x, y, r, t, c });
}
pub fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, w: i32) {
let c = self.wp.cmd.dcol;
self.push(Cmd::Line { x0, y0, x1, y1, w, c });
}
pub fn draw_image(&mut self, sf: &Rc<Surface>, ver: u32, x: i32, y: i32, w: i32, h: i32) {
let w = if w == 0 { sf.w } else { w };
let h = if h == 0 { sf.h } else { h };
self.push(Cmd::Image { x, y, w, h, sf: sf.clone(), ver });
}
pub fn draw_clip(&mut self, x: i32, y: i32, w: i32, h: i32) {
self.push(Cmd::Clip { x, y, w, h });
}
pub fn draw_unclip(&mut self) {
self.push(Cmd::Unclip);
}
pub fn icon_hook(&mut self, f: Option<IconHook>) {
self.icon_hook = f;
}
#[allow(clippy::too_many_arguments)]
pub fn cmd_custom(&mut self, x: i32, y: i32, w: i32, h: i32, f: Option<DrawRef>, user: usize, ver: u32) {
self.push(Cmd::Custom { x, y, w, h, f, user, ver });
}
pub fn cmd_shadow(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32, depth: i32) {
let r = if self.look.rad_mode != 0 { self.look_radius(r, w, h) } else { r }; let c = self.wp.cmd.dcol;
self.push(Cmd::Shadow { x, y, w, h, r, depth, c });
}
pub(crate) fn render_all_to(&mut self, shadow: bool) {
self.render_from(shadow, 0, 0);
}
pub(crate) fn render_from(&mut self, shadow: bool, layer: usize, from: usize) {
let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
let Ui { wp, dc, text, icon_hook, .. } = self;
let sf = if shadow { &mut wp.rt.shadow } else { &mut wp.rt.fb };
for (k, l) in wp.cmd.layers.iter().enumerate().skip(layer) {
render_layer(l, if k == layer { from } else { 0 }, dc, sf, text, icon_hook, icon_style);
}
}
#[allow(dead_code)] pub(crate) fn render_popup(&mut self, sf: &mut Surface, dx: i32, dy: i32) {
let icon_style = (self.style, self.color(T_ACCENT), self.theme_dark, self.color(T_TEXT_DISABLED), self.wp.rt.dpi as u32);
let Ui { wp, dc, text, icon_hook, .. } = self;
let layers = &mut wp.cmd.layers;
translate(&mut layers[L_POPUP as usize], dx, dy);
translate(&mut layers[L_TIP as usize], dx, dy);
render_layer(&layers[L_POPUP as usize], 0, dc, sf, text, icon_hook, icon_style);
render_layer(&layers[L_TIP as usize], 0, dc, sf, text, icon_hook, icon_style);
translate(&mut layers[L_POPUP as usize], -dx, -dy);
translate(&mut layers[L_TIP as usize], -dx, -dy);
}
}