/*! * CanvasEventBox - Canvas入力イベント管理Box * * ## 📝 概要 * HTML5 Canvasでのマウス・タッチ・キーボードイベントを * Nyashから利用可能にするBox。ゲーム開発、インタラクティブ * アプリケーション開発に必須の入力機能を提供。 * * ## 🛠️ 利用可能メソッド * * ### 🖱️ マウスイベント * - `onMouseDown(callback)` - マウスボタン押下 * - `onMouseUp(callback)` - マウスボタン離上 * - `onMouseMove(callback)` - マウス移動 * - `onMouseClick(callback)` - マウスクリック * - `onMouseWheel(callback)` - マウスホイール * * ### 👆 タッチイベント * - `onTouchStart(callback)` - タッチ開始 * - `onTouchMove(callback)` - タッチ移動 * - `onTouchEnd(callback)` - タッチ終了 * * ### ⌨️ キーボードイベント * - `onKeyDown(callback)` - キー押下 * - `onKeyUp(callback)` - キー離上 * * ### 📊 座標取得 * - `getMouseX()` - 現在のマウスX座標 * - `getMouseY()` - 現在のマウスY座標 * - `isPressed(button)` - ボタン押下状態確認 * * ## 💡 使用例 * ```nyash * local events, canvas * events = new CanvasEventBox("game-canvas") * canvas = new WebCanvasBox("game-canvas", 800, 600) * * // マウスクリックで円を描画 * events.onMouseClick(function(x, y) { * canvas.fillCircle(x, y, 10, "red") * }) * * // ドラッグで線を描画 * local isDrawing = false * events.onMouseDown(function(x, y) { * isDrawing = true * canvas.beginPath() * canvas.moveTo(x, y) * }) * * events.onMouseMove(function(x, y) { * if (isDrawing) { * canvas.lineTo(x, y) * canvas.stroke("black", 2) * } * }) * * events.onMouseUp(function() { * isDrawing = false * }) * ``` */ use crate::box_trait::{NyashBox, StringBox, BoolBox, BoxCore, BoxBase}; use std::any::Any; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use web_sys::{ HtmlCanvasElement, MouseEvent, TouchEvent, KeyboardEvent, EventTarget, Element }; /// Canvas入力イベント管理Box #[derive(Debug, Clone)] pub struct CanvasEventBox { base: BoxBase, canvas_id: String, mouse_x: f64, mouse_y: f64, pressed_buttons: Vec, } impl CanvasEventBox { pub fn new(canvas_id: String) -> Self { Self { base: BoxBase::new(), canvas_id, mouse_x: 0.0, mouse_y: 0.0, pressed_buttons: Vec::new(), } } #[cfg(target_arch = "wasm32")] /// Canvas要素を取得 fn get_canvas_element(&self) -> Option { let window = web_sys::window()?; let document = window.document()?; let element = document.get_element_by_id(&self.canvas_id)?; element.dyn_into::().ok() } /// 現在のマウスX座標を取得 pub fn get_mouse_x(&self) -> f64 { self.mouse_x } /// 現在のマウスY座標を取得 pub fn get_mouse_y(&self) -> f64 { self.mouse_y } /// 指定ボタンが押下されているかチェック pub fn is_pressed(&self, button: i16) -> bool { self.pressed_buttons.contains(&button) } #[cfg(target_arch = "wasm32")] /// マウス座標を Canvas 座標系に変換 fn get_canvas_coordinates(&self, event: &MouseEvent) -> (f64, f64) { if let Some(canvas) = self.get_canvas_element() { let rect = canvas.get_bounding_client_rect(); let x = event.client_x() as f64 - rect.left(); let y = event.client_y() as f64 - rect.top(); (x, y) } else { (event.client_x() as f64, event.client_y() as f64) } } #[cfg(target_arch = "wasm32")] /// マウスダウンイベントリスナーを設定 pub fn on_mouse_down(&self, callback: js_sys::Function) { if let Some(canvas) = self.get_canvas_element() { let closure = Closure::wrap(Box::new(move |event: MouseEvent| { // ここで座標変換とコールバック呼び出し callback.call0(&JsValue::NULL).unwrap_or_default(); }) as Box); canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref()) .unwrap_or_default(); closure.forget(); // メモリリークを防ぐため通常は適切な管理が必要 } } #[cfg(target_arch = "wasm32")] /// マウスアップイベントリスナーを設定 pub fn on_mouse_up(&self, callback: js_sys::Function) { if let Some(canvas) = self.get_canvas_element() { let closure = Closure::wrap(Box::new(move |event: MouseEvent| { callback.call0(&JsValue::NULL).unwrap_or_default(); }) as Box); canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref()) .unwrap_or_default(); closure.forget(); } } #[cfg(target_arch = "wasm32")] /// マウス移動イベントリスナーを設定 pub fn on_mouse_move(&self, callback: js_sys::Function) { if let Some(canvas) = self.get_canvas_element() { let closure = Closure::wrap(Box::new(move |event: MouseEvent| { callback.call0(&JsValue::NULL).unwrap_or_default(); }) as Box); canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref()) .unwrap_or_default(); closure.forget(); } } #[cfg(target_arch = "wasm32")] /// マウスクリックイベントリスナーを設定 pub fn on_mouse_click(&self, callback: js_sys::Function) { if let Some(canvas) = self.get_canvas_element() { let closure = Closure::wrap(Box::new(move |event: MouseEvent| { callback.call0(&JsValue::NULL).unwrap_or_default(); }) as Box); canvas.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()) .unwrap_or_default(); closure.forget(); } } #[cfg(target_arch = "wasm32")] /// タッチ開始イベントリスナーを設定 pub fn on_touch_start(&self, callback: js_sys::Function) { if let Some(canvas) = self.get_canvas_element() { let closure = Closure::wrap(Box::new(move |event: TouchEvent| { callback.call0(&JsValue::NULL).unwrap_or_default(); }) as Box); canvas.add_event_listener_with_callback("touchstart", closure.as_ref().unchecked_ref()) .unwrap_or_default(); closure.forget(); } } #[cfg(target_arch = "wasm32")] /// キーダウンイベントリスナーを設定 pub fn on_key_down(&self, callback: js_sys::Function) { if let Some(window) = web_sys::window() { let closure = Closure::wrap(Box::new(move |event: KeyboardEvent| { callback.call0(&JsValue::NULL).unwrap_or_default(); }) as Box); window.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) .unwrap_or_default(); closure.forget(); } } #[cfg(not(target_arch = "wasm32"))] /// Non-WASM環境用のダミー実装 pub fn on_mouse_down(&self) { println!("CanvasEventBox: Mouse events not supported in non-WASM environment"); } #[cfg(not(target_arch = "wasm32"))] pub fn on_mouse_up(&self) { println!("CanvasEventBox: Mouse events not supported in non-WASM environment"); } #[cfg(not(target_arch = "wasm32"))] pub fn on_mouse_move(&self) { println!("CanvasEventBox: Mouse events not supported in non-WASM environment"); } #[cfg(not(target_arch = "wasm32"))] pub fn on_mouse_click(&self) { println!("CanvasEventBox: Mouse events not supported in non-WASM environment"); } #[cfg(not(target_arch = "wasm32"))] pub fn on_touch_start(&self) { println!("CanvasEventBox: Touch events not supported in non-WASM environment"); } #[cfg(not(target_arch = "wasm32"))] pub fn on_key_down(&self) { println!("CanvasEventBox: Keyboard events not supported in non-WASM environment"); } } impl BoxCore for CanvasEventBox { fn box_id(&self) -> u64 { self.base.id } fn parent_type_id(&self) -> Option { self.base.parent_type_id } fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "CanvasEventBox({})", self.canvas_id) } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } impl NyashBox for CanvasEventBox { fn clone_box(&self) -> Box { Box::new(self.clone()) } fn to_string_box(&self) -> StringBox { StringBox::new(format!("CanvasEventBox({})", self.canvas_id)) } fn type_name(&self) -> &'static str { "CanvasEventBox" } fn equals(&self, other: &dyn NyashBox) -> BoolBox { if let Some(other_events) = other.as_any().downcast_ref::() { BoolBox::new(self.base.id == other_events.base.id) } else { BoolBox::new(false) } } } impl std::fmt::Display for CanvasEventBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }