use std::{ops::Deref, collections::HashMap};
use byteorder::{WriteBytesExt, LittleEndian};
use crate::{graphics::{color as Color}, ui_data::UiData, Rect, Result};
use futures::Future;
use crate::{element::Element, ui_ref::UiRef, JSMessageTx, graphics::bitmap::BitmapData};
static TILE_WIDTH: u32 = 640;
static TILE_HEIGHT: u32 = 640;
static CANVAS_ID: u32 = 0xAAA;
use LittleEndian as Endianness;
use super::context::Context2D;
#[derive(Clone)]
pub struct Canvas {
canvas: Element,
}
fn align(a: u32) -> u32{
(a + 3u32) & !3u32
}
fn write_prelude(vec8: &mut Vec<u8>, data_type: u32, owner: &str, sz: u32, header: &Vec<u32>) {
vec8.write_u32::<Endianness>(data_type).unwrap();
vec8.write_u32::<Endianness>(sz).unwrap();
vec8.write_u32::<Endianness>(align(owner.len() as u32)).unwrap();
vec8.write_u32::<Endianness>(align(header.len() as u32)).unwrap();
}
fn write_epilog(vec8: &mut Vec<u8>, owner: &str, header: &Vec<u32>) {
for header_item in header {
vec8.write_u32::<Endianness>(*header_item).unwrap();
}
assert!(!owner.is_empty());
for byte in owner.chars() {
vec8.write_u16::<Endianness>(byte as u16).unwrap();
}
let padding = align(owner.len() as u32) - owner.len() as u32;
for _i in 0..padding {
vec8.write_u16::<Endianness>(0).unwrap();
}
}
impl Deref for Canvas {
type Target = Element;
fn deref(&self) -> &Element {
&self.canvas
}
}
pub enum DrawNotify {
Kick,
NoKick,
}
impl Canvas {
pub fn new(element: &Element) -> Canvas {
Canvas{
canvas: element.clone(),
}
}
pub fn from(element: Element) -> Canvas {
Canvas{
canvas: element,
}
}
pub fn element(&self) -> &Element {
&self.canvas
}
pub fn element_mut(&mut self) -> &mut Element {
&mut self.canvas
}
pub fn on_draw_cancel(&self) {
self.element().unsubscribe("event_notify");
let msg = JSMessageTx {
element: self.id(),
_type: "event_notify",
name: Some("canvas_draw"),
add: Some(false),
..Default::default()
};
self.element().send(msg);
}
fn init_on_draw(&self, kick: DrawNotify) {
let msg = JSMessageTx {
element: self.id(),
_type: "event_notify",
name: Some("canvas_draw"),
add: Some(true),
..Default::default()
};
self.element().send(msg);
match kick {
DrawNotify::Kick => {
let mut prop = HashMap::new();
prop.insert("name".to_string(), "canvas_draw".to_string());
self.element().call("event_notify", prop);
},
DrawNotify::NoKick => (),
}
}
pub fn on_draw_async<CB, Fut>(&self, draw_completed_cb: CB, kick: DrawNotify)
where CB: FnOnce(UiRef) ->Fut + Send + Clone + 'static,
Fut: Future<Output = ()> + Send + 'static {
self.element().subscribe_async("event_notify", |ui, ev| async move {
if let Some(prop) = ev.property_str("name") {
if prop == "canvas_draw" {
draw_completed_cb(ui).await;
}
}
});
self.init_on_draw(kick);
}
pub fn on_draw<CB>(&self, mut draw_completed_cb: CB, kick: DrawNotify)
where CB: FnMut(UiRef) + Send + 'static { self.element().subscribe("event_notify", move |ui, ev| {
if let Some(prop) = ev.property_str("name") {
if prop == "canvas_draw" {
draw_completed_cb(ui);
}
}
});
self.init_on_draw(kick);
}
pub fn draw_bitmap(&self, bitmap: &dyn BitmapData) {
self.paint_bitmap(0, 0, bitmap, true)
}
pub fn draw_bitmap_at(&self, x: i32, y: i32, bitmap: &dyn BitmapData) {
self.paint_bitmap(x, y, bitmap, true)
}
pub fn paint(&self, x: i32, y: i32, bitmap: &dyn BitmapData) {
self.paint_bitmap(x, y, bitmap, false)
}
fn paint_bitmap(&self, x_pos: i32, y_pos: i32, bitmap: &dyn BitmapData, as_draw: bool) {
if bitmap.height() == 0|| bitmap.width() == 0 {
return;
}
let y = if y_pos < 0 {-y_pos} else {0} as u32;
let y_pos = y_pos.max(0) as u32;
let x = if x_pos < 0 {-x_pos} else {0} as u32;
let x_pos = x_pos.max(0) as u32;
let mut is_last = false;
if y < bitmap.height() && x < bitmap.width() {
for j in (y..bitmap.height()).step_by(TILE_HEIGHT as usize) {
let height = TILE_HEIGHT.min(bitmap.height() - j);
if height == 0 {
continue;
}
for i in (x..bitmap.width()).step_by(TILE_WIDTH as usize) {
let width = TILE_WIDTH.min(bitmap.width() - i);
debug_assert!(!is_last);
is_last = (bitmap.height() - j < TILE_HEIGHT) && (bitmap.width() - i < TILE_WIDTH);
let header = vec!(
i + x_pos,
j + y_pos,
width,
height,
(as_draw && is_last) as u32
);
let mut vec8: Vec<u8> = vec![];
write_prelude(
&mut vec8,
CANVAS_ID,
self.id(),
width * height,
&header
);
for row in j..(j + height) {
let stride = bitmap.slice(i, width, row);
for elem in stride.iter() {
let p = Color::rgba(
Color::r(*elem),
Color::g(*elem),
Color::b(*elem),
Color::a(*elem));
vec8.write_u32::<Endianness>(p).unwrap();
}
}
write_epilog(
&mut vec8,
self.id(),
&header,
);
self.element().send_bin(vec8);
}
}
} else {
is_last = true;
if as_draw {
let header = vec!(
0,0,0,0, is_last as u32
);
let mut vec8: Vec<u8> = vec![];
write_prelude(
&mut vec8,
CANVAS_ID,
self.id(),
0,
&header
);
write_epilog(
&mut vec8,
self.id(),
&header,
);
self.element().send_bin(vec8);
}
}
if !is_last {
panic!("{} {} {}x{}", x, y, bitmap.width(), bitmap.height());
}
}
pub fn add_image_async<OptCB, CB, Fut>(&self, url: &str, image_added_cb: OptCB) -> Result<String>
where OptCB: Into<Option<CB>>,
CB: FnOnce(UiRef, String)-> Fut + Send + Clone + 'static,
Fut: Future<Output = ()> + Send + 'static {
let cb = image_added_cb.into();
match cb {
Some(cf) => self.add_image(url, UiData::as_sync_fn(cf)),
_ => self.add_image(url, |_,_|{})
}
}
pub fn add_image<OptCB, CB>(&self, url: &str, image_added_cb: OptCB) -> Result<String>
where
CB: FnMut(UiRef, String) + Clone + Send + Sync + 'static,
OptCB: Into<Option<CB>> { let name = UiData::random("image");
let id_name = name.clone();
let url = url.to_string();
let cb = image_added_cb.into();
let result = self.ui().add_element_with_id(&name, "IMG", self, move |_, el: Element| {
let cb = cb.clone();
if let Some(mut f) = cb {
let id_name = id_name.clone();
el.subscribe_properties("load",
move |ui, _| f(ui, id_name.clone()), &["complete"]);
}
el.set_attribute("style", "display:none");
el.set_attribute("src", &url);
});
match result {
Ok(_) => Ok(name),
Err(e) => Err(e)
}
}
pub fn draw_image<ORect>(&self, image_id: &str, x: i32, y: i32, clipping_rect: ORect)
where ORect: Into<Option<Rect<i32>>> {
self.paint_image_internal(image_id, Some((x, y)), None, clipping_rect.into())
}
pub fn draw_image_rect<ORect>(&self, image_id: &str, rect: Rect<i32>, clipping_rect: ORect)
where ORect: Into<Option<Rect<i32>>> {
if rect.width <= 0 || rect.height <= 0 {
return;
}
self.paint_image_internal(image_id, None, Some(rect), clipping_rect.into())
}
fn paint_image_internal(&self, image_id: &str, pos: Option<(i32, i32)>, target_rect: Option<Rect<i32>>, clipping_rect: Option<Rect<i32>>) {
let clipping_rect = clipping_rect.map(|rect|
vec!(rect.x as f32, rect.y as f32, rect.width as f32, rect.height as f32));
let target_rect = target_rect.map(|rect|
vec!(rect.x as f32, rect.y as f32, rect.width as f32, rect.height as f32));
let pos = pos.map(|pp| vec!(pp.0 as f32, pp.1 as f32));
let msg = JSMessageTx {
element: self.id(),
_type: "paint_image",
image: Some(image_id),
rect: target_rect,
clip: clipping_rect,
pos,
..Default::default()
};
self.send(msg);
}
pub fn draw_context(&self, context: &Context2D) {
let commands = context.composed();
if commands.is_empty() {
return;
}
let msg = JSMessageTx {
element: self.id(),
_type: "canvas_draw",
commands: Some(commands),
..Default::default()
};
self.send(msg);
}
pub fn erase(&self, rect: Rect<u32>) {
let f_rect = Rect::<f32> {
x: rect.x as f32,
y: rect.y as f32,
width: rect.width as f32,
height: rect.height as f32};
self.draw_context(Context2D::new().clear_rect(&f_rect));
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use byteorder::ReadBytesExt;
use crate::graphics::bitmap::{Bitmap, BitmapRef};
use super::*;
#[test]
fn test_data() {
let mut bitmap = Bitmap::new(10, 10);
bitmap.put(3, 3, 0x1);
bitmap.put(5, 5, 0x100);
bitmap.put(9, 9, 0x10000);
let mut vec8: Vec<u8> = vec![];
let name = "kissa";
let header = vec!(0, 0, bitmap.width() as u32, bitmap.height() as u32, true as u32);
write_prelude (
&mut vec8,
CANVAS_ID,
name,
bitmap.width() * bitmap.height(),
&header,
);
for elem in bitmap.iter() {
vec8.write_u32::<Endianness>(*elem).unwrap();
}
write_epilog (
&mut vec8,
name,
&header,
);
let len = vec8.len() as u32;
let mut c = Cursor::new(vec8);
let read32 = |c: &mut Cursor<Vec<u8>>| {c.read_u32::<Endianness>().unwrap()};
assert_eq!(read32(&mut c), 0xAAA);
let data_sz = read32(&mut c);
assert_eq!(data_sz, 100); let id_len = read32(&mut c);
assert_eq!(id_len, align(name.len() as u32));
let header_len = read32(&mut c);
assert_eq!(header_len, align(5));
let header_offset = (data_sz + 4) * 4;
assert!(header_offset < len - (4 * 4));
let data_offset = 4 * 4;
let id_offset = (4 * 4) + data_offset + header_len * 4;
assert!(id_offset <= len - id_len * 2, "{} <= {} - {} * 2", id_offset, len, id_len);
assert!(len == (10 * 10 + 4) * 4 + 20 + 16);
let mut pixels = Vec::new();
for i in 0..align(100) {
let pix = c.read_u32::<Endianness>();
assert!(pix.is_ok(), "Error at {} ", i);
pixels.push(pix.unwrap());
}
let result = BitmapRef::from_bytes(10, 10, &pixels);
for y in 0..10 {
for x in 0..10 {
assert_eq!(bitmap.get(x, y), result.get(x, y), "at {}x{}", x, y);
}
}
assert_eq!(read32(&mut c), 0); assert_eq!(read32(&mut c), 0); assert_eq!(read32(&mut c), 10); assert_eq!(read32(&mut c), 10); assert_eq!(read32(&mut c), 1);
let mut chrs: Vec<char> = Vec::new();
for i in 0..id_len {
let ch = c.read_u16::<Endianness>();
assert!(ch.is_ok(), "Error at {} of {} - {} vs {:#?}", i, id_len, name, chrs);
let c_v = ch.unwrap() as u32;
if c_v > 0 { chrs.push(char::from_u32(c_v).unwrap());
}
}
let name_string: String = chrs.into_iter().collect();
assert_eq!(name, name_string);
}
}