use crate::*;
pub(crate) fn canvas_on_draw(state: UseCanvas) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
enter_fullscreen(state);
}))
}
pub(crate) fn use_canvas_state() -> UseCanvas {
let initial_stroke_color: String = load_stroke_color();
let initial_line_width: f64 = load_line_width();
UseCanvas::new(
use_signal(|| false),
use_signal(move || initial_stroke_color.clone()),
use_signal(move || initial_line_width),
use_signal(|| false),
use_signal(String::new),
)
}
pub(crate) fn update_snapshot(state: UseCanvas) {
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(element) = document_value
.query_selector(CANVAS_DRAWING_SELECTOR)
.ok()
.flatten()
else {
return;
};
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let data_url: String = canvas_element.to_data_url().unwrap_or_default();
state.get_snapshot_data_url().set(data_url);
}
pub(crate) fn start_drawing(state: UseCanvas, offset_x: f64, offset_y: f64) {
state.get_drawing().set(true);
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(element) = document_value
.query_selector(CANVAS_DRAWING_SELECTOR)
.ok()
.flatten()
else {
return;
};
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let Some(context_object) = canvas_element.get_context("2d").ok().flatten() else {
return;
};
let context_2d: CanvasRenderingContext2d = context_object.unchecked_into();
context_2d.begin_path();
let _ = Reflect::set(
&context_2d,
&JsValue::from_str("strokeStyle"),
&JsValue::from_str(&state.get_stroke_color().get()),
);
let line_width: f64 = state.get_line_width().get().max(CANVAS_MIN_LINE_WIDTH);
context_2d.set_line_width(line_width);
context_2d.set_line_cap("round");
context_2d.set_line_join("round");
context_2d.move_to(offset_x, offset_y);
}
pub(crate) fn continue_drawing(state: UseCanvas, offset_x: f64, offset_y: f64) {
if !state.get_drawing().get() {
return;
}
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(element) = document_value
.query_selector(CANVAS_DRAWING_SELECTOR)
.ok()
.flatten()
else {
return;
};
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let Some(context_object) = canvas_element.get_context("2d").ok().flatten() else {
return;
};
let context_2d: CanvasRenderingContext2d = context_object.unchecked_into();
context_2d.line_to(offset_x, offset_y);
context_2d.stroke();
}
pub(crate) fn stop_drawing(state: UseCanvas) {
if !state.get_drawing().get() {
return;
}
state.get_drawing().set(false);
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(element) = document_value
.query_selector(CANVAS_DRAWING_SELECTOR)
.ok()
.flatten()
else {
return;
};
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let Some(context_object) = canvas_element.get_context("2d").ok().flatten() else {
return;
};
let context_2d: CanvasRenderingContext2d = context_object.unchecked_into();
context_2d.close_path();
}
pub(crate) fn clear_canvas(canvas_selector: &str) {
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(element) = document_value
.query_selector(canvas_selector)
.ok()
.flatten()
else {
return;
};
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let width: f64 = canvas_element.width() as f64;
let height: f64 = canvas_element.height() as f64;
let Some(context_object) = canvas_element.get_context("2d").ok().flatten() else {
return;
};
let context_2d: CanvasRenderingContext2d = context_object.unchecked_into();
context_2d.clear_rect(0.0, 0.0, width, height);
let _ = Reflect::set(
&context_2d,
&JsValue::from_str("fillStyle"),
&JsValue::from_str(CANVAS_BACKGROUND_COLOR),
);
context_2d.fill_rect(0.0, 0.0, width, height);
}
pub(crate) fn get_pointer_offset(event: &Event) -> (f64, f64) {
let target: JsValue = event
.target()
.map_or(JsValue::NULL, |event_target: EventTarget| {
event_target.into()
});
let offset_x: f64 = Reflect::get(&target, &JsValue::from_str("offsetX"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
let offset_y: f64 = Reflect::get(&target, &JsValue::from_str("offsetY"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
(offset_x, offset_y)
}
pub(crate) fn get_mouse_client(event: &Event) -> (f64, f64) {
let client_x: f64 = Reflect::get(event.as_ref(), &JsValue::from_str("clientX"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
let client_y: f64 = Reflect::get(event.as_ref(), &JsValue::from_str("clientY"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
(client_x, client_y)
}
pub(crate) fn get_touch_offset(event: &Event) -> (f64, f64) {
let target: JsValue = event
.target()
.map_or(JsValue::NULL, |event_target: EventTarget| {
event_target.into()
});
let element: Element = target.unchecked_into();
let rect: DomRect = element.get_bounding_client_rect();
let touches_value: JsValue = Reflect::get(event.as_ref(), &JsValue::from_str("touches"))
.ok()
.unwrap_or(JsValue::NULL);
let touches: Array = touches_value.unchecked_into();
if touches.length() == 0 {
return (0.0, 0.0);
}
let first_touch: JsValue = touches.get(0);
let client_x: f64 = Reflect::get(&first_touch, &JsValue::from_str("clientX"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
let client_y: f64 = Reflect::get(&first_touch, &JsValue::from_str("clientY"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
let offset_x: f64 = client_x - rect.left();
let offset_y: f64 = client_y - rect.top();
(offset_x, offset_y)
}
pub(crate) fn get_touch_client(event: &Event) -> (f64, f64) {
let touches_value: JsValue = Reflect::get(event.as_ref(), &JsValue::from_str("touches"))
.ok()
.unwrap_or(JsValue::NULL);
let touches: Array = touches_value.unchecked_into();
if touches.length() == 0 {
return (0.0, 0.0);
}
let first_touch: JsValue = touches.get(0);
let client_x: f64 = Reflect::get(&first_touch, &JsValue::from_str("clientX"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
let client_y: f64 = Reflect::get(&first_touch, &JsValue::from_str("clientY"))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or(0.0);
(client_x, client_y)
}
pub(crate) fn prevent_event_default(event: &Event) {
event.prevent_default();
}
pub(crate) fn map_rotated_offset(
offset_x: f64,
offset_y: f64,
client_x: f64,
client_y: f64,
is_fullscreen: bool,
) -> (f64, f64) {
if !is_fullscreen {
return (offset_x, offset_y);
}
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(element) = document_value
.query_selector(CANVAS_DRAWING_SELECTOR)
.ok()
.flatten()
else {
return (offset_x, offset_y);
};
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let rect: DomRect = canvas_element.get_bounding_client_rect();
let canvas_x: f64 = client_x - rect.left();
let canvas_y: f64 = client_y - rect.top();
(canvas_x, canvas_y)
}
pub(crate) fn enter_fullscreen(state: UseCanvas) {
state.get_fullscreen().set(true);
let window_value: Window = window().expect("no global window exists");
let history: History = window_value.history().expect("no history object exists");
let _ = history.push_state(&JsValue::NULL, "");
let snapshot_data_url: String = state.get_snapshot_data_url().get();
let resize_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
resize_fullscreen_canvas(&snapshot_data_url);
}));
let _ = window_value.request_animation_frame(resize_closure.as_ref().unchecked_ref());
resize_closure.forget();
}
pub(crate) fn resize_fullscreen_canvas(snapshot_data_url: &str) {
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let Some(wrapper_element) = document_value
.query_selector(CANVAS_FULLSCREEN_WRAPPER_SELECTOR)
.ok()
.flatten()
else {
return;
};
let Some(canvas_element_obj) = document_value
.query_selector(CANVAS_DRAWING_SELECTOR)
.ok()
.flatten()
else {
return;
};
let canvas_element: HtmlCanvasElement = canvas_element_obj.unchecked_into();
let wrapper_width: i32 = wrapper_element.client_width();
let wrapper_height: i32 = wrapper_element.client_height();
let canvas_width: f64 = if (wrapper_height as f64) < (wrapper_width as f64) * 16.0 / 9.0 {
wrapper_height as f64 * 9.0 / 16.0
} else {
wrapper_width as f64
};
let canvas_height: f64 = canvas_width * 16.0 / 9.0;
canvas_element
.style()
.set_property("width", &format!("{}px", canvas_width as i32))
.unwrap_or(());
canvas_element
.style()
.set_property("height", &format!("{}px", canvas_height as i32))
.unwrap_or(());
canvas_element.set_width(canvas_width as u32);
canvas_element.set_height(canvas_height as u32);
let Some(context_object) = canvas_element.get_context("2d").ok().flatten() else {
return;
};
let context_2d: CanvasRenderingContext2d = context_object.unchecked_into();
let _ = Reflect::set(
&context_2d,
&JsValue::from_str("fillStyle"),
&JsValue::from_str(CANVAS_BACKGROUND_COLOR),
);
context_2d.fill_rect(0.0, 0.0, canvas_width, canvas_height);
if snapshot_data_url.is_empty() {
return;
}
let image: HtmlImageElement = HtmlImageElement::new().expect("should create image element");
image.set_src(snapshot_data_url);
let draw_image: HtmlImageElement = image.clone();
let draw_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
let _ = context_2d.draw_image_with_html_image_element_and_dw_and_dh(
&draw_image,
0.0,
0.0,
canvas_width,
canvas_height,
);
}));
image.set_onload(Some(draw_closure.as_ref().unchecked_ref()));
draw_closure.forget();
}
pub(crate) fn exit_fullscreen(state: UseCanvas) {
update_snapshot(state);
state.get_fullscreen().set(false);
let window_value: Window = window().expect("no global window exists");
let history: History = window_value.history().expect("no history object exists");
let _ = history.back();
}
pub(crate) fn exit_fullscreen_from_popstate(state: UseCanvas) {
update_snapshot(state);
state.get_fullscreen().set(false);
}
pub(crate) fn load_stroke_color() -> String {
local_storage_get(CANVAS_STORAGE_KEY_STROKE_COLOR)
.filter(|color: &String| !color.is_empty())
.unwrap_or_else(|| CANVAS_DEFAULT_STROKE_COLOR.to_string())
}
pub(crate) fn load_line_width() -> f64 {
local_storage_get(CANVAS_STORAGE_KEY_LINE_WIDTH)
.and_then(|width: String| width.parse::<f64>().ok())
.unwrap_or(CANVAS_DEFAULT_LINE_WIDTH)
}
pub(crate) fn save_stroke_color(color: &str) {
local_storage_set(CANVAS_STORAGE_KEY_STROKE_COLOR, color);
}
pub(crate) fn save_line_width(width: f64) {
local_storage_set(CANVAS_STORAGE_KEY_LINE_WIDTH, &width.to_string());
}
pub(crate) fn canvas_on_line_width_input(state: UseCanvas) -> Option<Rc<dyn Fn(Event)>> {
let pending_value: Rc<Cell<f64>> = Rc::new(Cell::new(CANVAS_DEFAULT_LINE_WIDTH));
let raf_id: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
Some(Rc::new(move |event: Event| {
let new_width: f64 = Reflect::get(event.as_ref(), &JsValue::from_str("target"))
.ok()
.and_then(|target: JsValue| Reflect::get(&target, &JsValue::from_str("value")).ok())
.and_then(|value: JsValue| value.as_string())
.and_then(|string: String| string.parse::<f64>().ok())
.unwrap_or(CANVAS_DEFAULT_LINE_WIDTH);
pending_value.set(new_width);
if raf_id.get().is_some() {
return;
}
let pending_for_raf: Rc<Cell<f64>> = pending_value.clone();
let raf_id_clone: Rc<Cell<Option<i32>>> = raf_id.clone();
let line_width_signal: Signal<f64> = state.get_line_width();
let window_value: Window = window().expect("no global window exists");
let raf_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
raf_id_clone.set(None);
let current_width: f64 = pending_for_raf.get();
line_width_signal.set(current_width);
save_line_width(current_width);
}));
let id: i32 = window_value
.request_animation_frame(raf_closure.as_ref().unchecked_ref())
.unwrap_or(0);
raf_id.set(Some(id));
raf_closure.forget();
}))
}
pub(crate) fn use_fullscreen_popstate(state: UseCanvas) {
use_window_event("popstate", move || {
if state.get_fullscreen().get() {
exit_fullscreen_from_popstate(state);
}
});
}