pub mod drawable_defaults;
use crate::web_functions::primitives::{
append_to_current_container, begin_frame, end_frame, get_or_create_host_element,
scissor_stack_pop, scissor_stack_push,
};
use cotis_defaults::render_commands::CornerRadii;
use js_sys::Set;
use std::collections::HashMap;
use wasm_bindgen::{JsCast, JsValue, closure::Closure};
pub struct HTMLCanvas {
used_element_ids: Set,
current_element: Option<ElementBuilder>,
active_command_element_id: Option<u32>,
element_counter: usize,
}
struct ElementBuilder {
id: String,
tag_name: String,
extra_style: Option<String>,
css_properties: HashMap<String, String>,
text_content: Option<String>,
inner_html: Option<String>,
}
impl Default for HTMLCanvas {
fn default() -> Self {
Self::new()
}
}
impl HTMLCanvas {
pub fn new() -> Self {
begin_frame();
Self {
used_element_ids: Set::new(&JsValue::NULL),
current_element: None,
active_command_element_id: None,
element_counter: 0,
}
}
pub fn ensure_command_element(
&mut self,
command_id: u32,
html_type: &str,
extra_style: Option<&str>,
) {
if self.active_command_element_id == Some(command_id) {
return;
}
self.finalize_current_element();
self.active_command_element_id = Some(command_id);
let dom_id = format!("cotis-{}", command_id);
self.current_element = Some(ElementBuilder {
id: dom_id,
tag_name: html_type.to_string(),
extra_style: extra_style.map(String::from),
css_properties: HashMap::new(),
text_content: None,
inner_html: None,
});
}
pub fn current_element_css_push(&mut self, value: &str) -> &mut Self {
if let Some(builder) = &mut self.current_element {
for declaration in value.split(';') {
let declaration = declaration.trim();
if declaration.is_empty() {
continue;
}
if let Some((prop, val)) = declaration.split_once(':') {
let prop = prop.trim().to_string();
let val = val.trim().to_string();
builder.css_properties.insert(prop, val);
}
}
}
self
}
pub fn set_text_content(&mut self, text: String) {
if let Some(builder) = &mut self.current_element {
builder.text_content = Some(text);
}
}
pub fn set_inner_html(&mut self, html: String) {
if let Some(builder) = &mut self.current_element {
builder.inner_html = Some(html);
}
}
pub fn new_element(&mut self, prefix: &str, html_type: &str) -> &mut Self {
self.finalize_current_element();
self.element_counter += 1;
let id = format!("{}_{}", prefix, self.element_counter);
self.current_element = Some(ElementBuilder {
id,
tag_name: html_type.to_string(),
extra_style: None,
css_properties: HashMap::new(),
text_content: None,
inner_html: None,
});
self
}
fn finalize_current_element(&mut self) {
if let Some(builder) = self.current_element.take() {
self.active_command_element_id = None;
let css_map = builder.css_properties.clone();
let text_content = builder.text_content.clone();
let inner_html = builder.inner_html.clone();
let id_str = builder.id.clone();
let apply_style_fn = Closure::wrap(Box::new(move |el: web_sys::Element| {
if let Some(html) = &inner_html {
if let Some(html_el) = el.dyn_ref::<web_sys::HtmlElement>() {
html_el.set_inner_html(html);
}
} else if let Some(text) = &text_content {
el.set_text_content(Some(text));
}
if let Some(html_el) = el.dyn_ref::<web_sys::HtmlElement>() {
let style = html_el.style();
for (prop, value) in &css_map {
let _ = style.set_property(prop, value);
}
}
}) as Box<dyn FnMut(web_sys::Element)>);
let el = get_or_create_host_element(
&builder.id,
&builder.tag_name,
builder.extra_style.as_deref(),
apply_style_fn.as_ref().unchecked_ref(),
);
append_to_current_container(&el);
self.used_element_ids.add(&JsValue::from_str(&id_str));
apply_style_fn.forget();
}
}
pub fn finish(mut self) {
self.finalize_current_element();
end_frame(&self.used_element_ids);
}
pub(crate) fn push_scissor(
&mut self,
command_id: u32,
x: f32,
y: f32,
width: f32,
height: f32,
corner_radii: CornerRadii,
) {
self.finalize_current_element();
let id = format!("cotis-{}", command_id);
let container = web_sys::window()
.unwrap()
.document()
.unwrap()
.create_element("div")
.unwrap();
container.set_id(&id);
let style = container.dyn_ref::<web_sys::HtmlElement>().unwrap().style();
let _ = style.set_property("position", "absolute");
let _ = style.set_property("left", &format!("{}px", x));
let _ = style.set_property("top", &format!("{}px", y));
let _ = style.set_property("width", &format!("{}px", width));
let _ = style.set_property("height", &format!("{}px", height));
let _ = style.set_property("overflow", "hidden");
let _ = style.set_property(
"border-radius",
&format!(
"{}px {}px {}px {}px",
corner_radii.top_left,
corner_radii.top_right,
corner_radii.bottom_right,
corner_radii.bottom_left
),
);
let _ = style.set_property("box-sizing", "border-box");
append_to_current_container(&container);
scissor_stack_push(&container, x as f64, y as f64);
self.used_element_ids.add(&JsValue::from_str(&id));
}
pub(crate) fn pop_scissor(&mut self) {
self.finalize_current_element();
scissor_stack_pop();
}
}
pub trait HTMLDrawable {
fn draw(self, canvas: &mut HTMLCanvas);
}
pub fn draw_html_render_commands(commands: impl Iterator<Item = impl HTMLDrawable>) {
let mut canvas = HTMLCanvas::new();
for cmd in commands {
cmd.draw(&mut canvas);
}
canvas.finish();
}