Bye Bye Moore

PoCソルジャーな零細事業主が作業メモを残すブログ

eframeクレートでwebGL経由でブラウザ表示

実際のところ

toml

[package]
name = "hiragana-counter"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
eframe = { version = "0.24.0", features = ["wasm"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = "0.3"

html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ひらがなカウンター</title>
    <style>
        html, body {
            margin: 0;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        canvas {
            width: 200px;
            height: 200px;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script type="module">
        import init, { start } from './pkg/hiragana_counter.js';
        async function run() {
            await init();
            start("canvas");
        }
        run();
    </script>
</body>
</html>

スクリプト

use eframe::wasm_bindgen::{self, prelude::*};
use eframe::{egui, epaint::FontFamily, FontData};
use std::collections::BTreeMap;

#[wasm_bindgen]
pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
    let web_options = eframe::WebOptions::default();
    
    wasm_bindgen_futures::spawn_local(async move {
        eframe::start_web(
            canvas_id,
            web_options,
            Box::new(|cc| {
                // フォントの設定
                let mut fonts = BTreeMap::new();
                fonts.insert(
                    "NotoSansJP".to_owned(),
                    FontData::from_static(include_bytes!("assets/NotoSansJP-Regular.otf")),
                );
                
                cc.egui_ctx.set_fonts(egui::FontDefinitions {
                    font_data: fonts,
                    families: BTreeMap::from([(
                        FontFamily::Proportional,
                        vec!["NotoSansJP".to_owned()],
                    )]),
                });
                
                Box::new(CharCounterApp::default())
            }),
        )
        .await
        .expect("failed to start eframe");
    });

    Ok(())
}

struct CharCounterApp {
    current_char_index: usize,
    chars: Vec<char>,
}

impl Default for CharCounterApp {
    fn default() -> Self {
        Self {
            current_char_index: 0,
            chars: vec![
                'あ', 'い', 'う', 'え', 'お',
                'か', 'き', 'く', 'け', 'こ',
                'さ', 'し', 'す', 'せ', 'そ',
                'た', 'ち', 'つ', 'て', 'と',
                'な', 'に', 'ぬ', 'ね', 'の',
                'は', 'ひ', 'ふ', 'へ', 'ほ',
                'ま', 'み', 'む', 'め', 'も',
                'や', 'ゆ', 'よ',
                'ら', 'り', 'る', 'れ', 'ろ',
                'わ', 'を', 'ん'
            ],
        }
    }
}

impl eframe::App for CharCounterApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.vertical_centered(|ui| {
                ui.add_space(50.0);
                ui.heading(self.chars[self.current_char_index].to_string());
                ui.label(format!("{} / {}", self.current_char_index + 1, self.chars.len()));
                ui.add_space(20.0);
                if ui.button("次へ").clicked() {
                    self.current_char_index = 
                        if self.current_char_index >= self.chars.len() - 1 {
                            0
                        } else {
                            self.current_char_index + 1
                        };
                }
            });
        });
    }
}

ビルド

必要な環境用意
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
ビルド実行
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --out-dir pkg --target web target/wasm32-unknown-unknown/release/hiragana_counter.wasm
サーバー

webGLなのでサーバーは別途建てないといけまsん

python -m http.server