Files
hakorune/src/boxes/canvas_event_box.rs
2025-08-13 00:27:07 +00:00

299 lines
9.5 KiB
Rust

/*!
* 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<i16>,
}
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<HtmlCanvasElement> {
let window = web_sys::window()?;
let document = window.document()?;
let element = document.get_element_by_id(&self.canvas_id)?;
element.dyn_into::<HtmlCanvasElement>().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<dyn FnMut(MouseEvent)>);
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<dyn FnMut(MouseEvent)>);
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<dyn FnMut(MouseEvent)>);
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<dyn FnMut(MouseEvent)>);
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<dyn FnMut(TouchEvent)>);
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<dyn FnMut(KeyboardEvent)>);
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<std::any::TypeId> {
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<dyn NyashBox> {
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::<CanvasEventBox>() {
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)
}
}