Files
hakorune/src/boxes/canvas_event_box.rs

308 lines
9.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* 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::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use web_sys::{Element, EventTarget, HtmlCanvasElement, KeyboardEvent, MouseEvent, TouchEvent};
/// 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())
}
/// 仮実装: clone_boxと同じ後で修正
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
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)
}
}