/*! * WebCanvasBox - ブラウザCanvas完全制御Box * * WebAssembly環境でHTML5 Canvasの完全制御 * ピクセルの世界を制圧する革命的Box! */ 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::{CanvasRenderingContext2d, HtmlCanvasElement}; // 🎨 Browser Canvas complete control Box #[cfg(target_arch = "wasm32")] #[derive(Debug, Clone)] pub struct WebCanvasBox { base: BoxBase, canvas_id: String, width: u32, height: u32, } #[cfg(target_arch = "wasm32")] impl WebCanvasBox { pub fn new(canvas_id: String, width: u32, height: u32) -> Self { let instance = Self { base: BoxBase::new(), canvas_id: canvas_id.clone(), width, height, }; // キャンバス要素を初期化 if let Some(canvas) = instance.get_canvas_element() { canvas.set_width(width); canvas.set_height(height); } instance } /// 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() } /// 2Dレンダリングコンテキストを取得 fn get_2d_context(&self) -> Option { let canvas = self.get_canvas_element()?; canvas .get_context("2d") .ok()? .and_then(|ctx| ctx.dyn_into::().ok()) } /// キャンバスをクリア pub fn clear(&self) { if let Some(ctx) = self.get_2d_context() { ctx.clear_rect(0.0, 0.0, self.width as f64, self.height as f64); } } /// 塗りつぶし色を設定 pub fn set_fill_style(&self, color: &str) { if let Some(ctx) = self.get_2d_context() { ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color)); } } /// 線の色を設定 pub fn set_stroke_style(&self, color: &str) { if let Some(ctx) = self.get_2d_context() { ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color)); } } /// 線の太さを設定 pub fn set_line_width(&self, width: f64) { if let Some(ctx) = self.get_2d_context() { ctx.set_line_width(width); } } /// 塗りつぶし矩形を描画 pub fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64, color: &str) { if let Some(ctx) = self.get_2d_context() { ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color)); ctx.fill_rect(x, y, width, height); } } /// 枠線矩形を描画 pub fn stroke_rect( &self, x: f64, y: f64, width: f64, height: f64, color: &str, line_width: f64, ) { if let Some(ctx) = self.get_2d_context() { ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color)); ctx.set_line_width(line_width); ctx.stroke_rect(x, y, width, height); } } /// 塗りつぶし円を描画 pub fn fill_circle(&self, x: f64, y: f64, radius: f64, color: &str) { if let Some(ctx) = self.get_2d_context() { ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color)); ctx.begin_path(); ctx.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI) .unwrap_or_default(); ctx.fill(); } } /// 枠線円を描画 pub fn stroke_circle(&self, x: f64, y: f64, radius: f64, color: &str, line_width: f64) { if let Some(ctx) = self.get_2d_context() { ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color)); ctx.set_line_width(line_width); ctx.begin_path(); ctx.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI) .unwrap_or_default(); ctx.stroke(); } } /// 直線を描画 pub fn draw_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, color: &str, line_width: f64) { if let Some(ctx) = self.get_2d_context() { ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color)); ctx.set_line_width(line_width); ctx.begin_path(); ctx.move_to(x1, y1); ctx.line_to(x2, y2); ctx.stroke(); } } /// テキストを描画(塗りつぶし) pub fn fill_text(&self, text: &str, x: f64, y: f64, font: &str, color: &str) { if let Some(ctx) = self.get_2d_context() { ctx.set_font(font); ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color)); ctx.fill_text(text, x, y).unwrap_or_default(); } } /// テキストを描画(枠線) pub fn stroke_text( &self, text: &str, x: f64, y: f64, font: &str, color: &str, line_width: f64, ) { if let Some(ctx) = self.get_2d_context() { ctx.set_font(font); ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color)); ctx.set_line_width(line_width); ctx.stroke_text(text, x, y).unwrap_or_default(); } } /// パス描画開始 pub fn begin_path(&self) { if let Some(ctx) = self.get_2d_context() { ctx.begin_path(); } } /// パスを指定位置に移動 pub fn move_to(&self, x: f64, y: f64) { if let Some(ctx) = self.get_2d_context() { ctx.move_to(x, y); } } /// パスに直線を追加 pub fn line_to(&self, x: f64, y: f64) { if let Some(ctx) = self.get_2d_context() { ctx.line_to(x, y); } } /// パスを閉じる pub fn close_path(&self) { if let Some(ctx) = self.get_2d_context() { ctx.close_path(); } } /// パスを塗りつぶし pub fn fill(&self, color: &str) { if let Some(ctx) = self.get_2d_context() { ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color)); ctx.fill(); } } /// パスを枠線描画 pub fn stroke(&self, color: &str, line_width: f64) { if let Some(ctx) = self.get_2d_context() { ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color)); ctx.set_line_width(line_width); ctx.stroke(); } } /// 現在の描画状態を保存 pub fn save(&self) { if let Some(ctx) = self.get_2d_context() { ctx.save(); } } /// 描画状態を復元 pub fn restore(&self) { if let Some(ctx) = self.get_2d_context() { ctx.restore(); } } /// 座標系を回転 pub fn rotate(&self, angle: f64) { if let Some(ctx) = self.get_2d_context() { ctx.rotate(angle).unwrap_or_default(); } } /// 座標系をスケール pub fn scale(&self, x: f64, y: f64) { if let Some(ctx) = self.get_2d_context() { ctx.scale(x, y).unwrap_or_default(); } } /// 座標系を平行移動 pub fn translate(&self, x: f64, y: f64) { if let Some(ctx) = self.get_2d_context() { ctx.translate(x, y).unwrap_or_default(); } } /// キャンバスのサイズを取得 pub fn get_width(&self) -> u32 { self.width } pub fn get_height(&self) -> u32 { self.height } /// キャンバスのサイズを変更 pub fn resize(&mut self, width: u32, height: u32) { self.width = width; self.height = height; if let Some(canvas) = self.get_canvas_element() { canvas.set_width(width); canvas.set_height(height); } } } #[cfg(target_arch = "wasm32")] impl BoxCore for WebCanvasBox { 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, "WebCanvasBox({}, {}x{})", self.canvas_id, self.width, self.height ) } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } #[cfg(target_arch = "wasm32")] impl NyashBox for WebCanvasBox { fn clone_box(&self) -> Box { Box::new(self.clone()) } /// 仮実装: clone_boxと同じ(後で修正) fn share_box(&self) -> Box { self.clone_box() } fn to_string_box(&self) -> StringBox { StringBox::new(format!( "WebCanvasBox({}, {}x{})", self.canvas_id, self.width, self.height )) } fn type_name(&self) -> &'static str { "WebCanvasBox" } fn equals(&self, other: &dyn NyashBox) -> BoolBox { if let Some(other_canvas) = other.as_any().downcast_ref::() { BoolBox::new(self.base.id == other_canvas.base.id) } else { BoolBox::new(false) } } } #[cfg(target_arch = "wasm32")] impl std::fmt::Display for WebCanvasBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }