🎨 feat: EguiBox GUI開発基盤完成 + パーサー無限ループバグ修正
## 🚀 主要機能追加 ### EguiBox - GUI開発基盤 - Windows版GUIメモ帳アプリ (simple_notepad.rs, nyash_notepad_jp.rs) - 日本語フォント対応 (NotoSansJP-VariableFont_wght.ttf) - BMPアイコン表示システム (c_drive_icon.bmp) - Windowsエクスプローラー風アプリ (nyash_explorer.rs) - アイコン抽出システム (test_icon_extraction.rs) ### ビジュアルプログラミング準備 - NyashFlow プロジェクト設計完成 (NYASHFLOW_PROJECT_HANDOVER.md) - ビジュアルノードプロトタイプ基盤 - WebAssembly対応準備 ## 🔧 重大バグ修正 ### パーサー無限ループ問題 (3引数メソッド呼び出し) - 原因: メソッドパラメータ解析ループの予約語処理不備 - 修正: src/parser/mod.rs - 非IDENTIFIERトークンのエラーハンドリング追加 - 効果: "from"等の予約語で適切なエラー報告、ハング→瞬時エラー ### MapBoxハング問題調査 - MapBox+3引数メソッド呼び出し組み合わせ問題特定 - バグレポート作成 (MAPBOX_HANG_BUG_REPORT.md) - 事前評価vs必要時評価の設計問題明確化 ## 🧹 コード品質向上 - box_methods.rs を8モジュールに機能分離 - 一時デバッグコード全削除 (eprintln\!, unsafe等) - 構文チェック通過確認済み ## 📝 ドキュメント整備 - CLAUDE.md にGUI開発セクション追加 - Gemini/ChatGPT先生相談ログ保存 (sessions/) - 段階的デバッグ手法確立 ## 🎯 次の目標 - must_advance\!マクロ実装 (無限ループ早期検出) - コマンド引数でデバッグ制御 (--debug-fuel) - MapBox問題の根本修正 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
86
examples/debug_notepad.rs
Normal file
86
examples/debug_notepad.rs
Normal file
@ -0,0 +1,86 @@
|
||||
// Debug version to check input issues
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
env_logger::init(); // Enable logging
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_title("Debug Notepad"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Debug Notepad",
|
||||
options,
|
||||
Box::new(|_cc| Ok(Box::new(DebugApp::default()))),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DebugApp {
|
||||
text: String,
|
||||
single_line: String,
|
||||
event_log: Vec<String>,
|
||||
}
|
||||
|
||||
impl eframe::App for DebugApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Debug Text Input Test");
|
||||
|
||||
// Single line input
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Single Line:");
|
||||
let response = ui.text_edit_singleline(&mut self.single_line);
|
||||
if response.changed() {
|
||||
self.event_log.push(format!("Single line changed: '{}'", self.single_line));
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Multi line input
|
||||
ui.label("Multi Line:");
|
||||
let response = ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(10)
|
||||
);
|
||||
|
||||
if response.changed() {
|
||||
self.event_log.push(format!("Multi line changed: {} chars", self.text.len()));
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Show input events
|
||||
ui.label("Event Log:");
|
||||
egui::ScrollArea::vertical()
|
||||
.max_height(100.0)
|
||||
.show(ui, |ui| {
|
||||
for event in &self.event_log {
|
||||
ui.label(event);
|
||||
}
|
||||
});
|
||||
|
||||
// Debug info
|
||||
ui.separator();
|
||||
ui.label(format!("Text length: {}", self.text.len()));
|
||||
ui.label(format!("Single line length: {}", self.single_line.len()));
|
||||
|
||||
// Test buttons
|
||||
if ui.button("Add Test Text").clicked() {
|
||||
self.text.push_str("Test ");
|
||||
self.event_log.push("Button: Added test text".to_string());
|
||||
}
|
||||
|
||||
if ui.button("Clear All").clicked() {
|
||||
self.text.clear();
|
||||
self.single_line.clear();
|
||||
self.event_log.push("Button: Cleared all".to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
381
examples/nyash_explorer.rs
Normal file
381
examples/nyash_explorer.rs
Normal file
@ -0,0 +1,381 @@
|
||||
// Nyash Explorer - Windows API Drive Information Viewer
|
||||
// エクスプローラー風ドライブ情報ビューアー
|
||||
|
||||
use eframe::egui::{self, FontFamily};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{
|
||||
Foundation::*,
|
||||
Storage::FileSystem::*,
|
||||
UI::Shell::*,
|
||||
UI::WindowsAndMessaging::*,
|
||||
System::Com::*,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([1024.0, 768.0])
|
||||
.with_title("Nyash Explorer - ドライブ情報ビューアー"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Explorer",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
setup_custom_fonts(&cc.egui_ctx);
|
||||
Ok(Box::new(NyashExplorer::new()))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// フォント設定
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
fonts.font_data.insert(
|
||||
"noto_sans_jp".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
||||
);
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "noto_sans_jp".to_owned());
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("noto_sans_jp".to_owned());
|
||||
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DriveInfo {
|
||||
letter: String,
|
||||
name: String,
|
||||
drive_type: String,
|
||||
total_bytes: u64,
|
||||
free_bytes: u64,
|
||||
icon_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
struct NyashExplorer {
|
||||
drives: Vec<DriveInfo>,
|
||||
selected_drive: Option<usize>,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl NyashExplorer {
|
||||
fn new() -> Self {
|
||||
let mut explorer = Self {
|
||||
drives: Vec::new(),
|
||||
selected_drive: None,
|
||||
status: "初期化中...".to_string(),
|
||||
};
|
||||
explorer.refresh_drives();
|
||||
explorer
|
||||
}
|
||||
|
||||
fn refresh_drives(&mut self) {
|
||||
self.drives.clear();
|
||||
self.status = "ドライブ情報を取得中...".to_string();
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
unsafe {
|
||||
// 論理ドライブのビットマスクを取得
|
||||
let drives_mask = GetLogicalDrives();
|
||||
|
||||
for i in 0..26 {
|
||||
if drives_mask & (1 << i) != 0 {
|
||||
let drive_letter = format!("{}:", (b'A' + i) as char);
|
||||
let drive_path = format!("{}\\", drive_letter);
|
||||
|
||||
// ドライブ情報を取得
|
||||
let mut drive_info = DriveInfo {
|
||||
letter: drive_letter.clone(),
|
||||
name: String::new(),
|
||||
drive_type: String::new(),
|
||||
total_bytes: 0,
|
||||
free_bytes: 0,
|
||||
icon_data: None,
|
||||
};
|
||||
|
||||
// ドライブタイプを取得
|
||||
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
|
||||
));
|
||||
|
||||
drive_info.drive_type = match drive_type_code {
|
||||
DRIVE_REMOVABLE => "リムーバブル".to_string(),
|
||||
DRIVE_FIXED => "ハードディスク".to_string(),
|
||||
DRIVE_REMOTE => "ネットワーク".to_string(),
|
||||
DRIVE_CDROM => "CD-ROM".to_string(),
|
||||
DRIVE_RAMDISK => "RAMディスク".to_string(),
|
||||
_ => "不明".to_string(),
|
||||
};
|
||||
|
||||
// ボリューム情報を取得
|
||||
let mut volume_name = vec![0u16; 256];
|
||||
let mut file_system = vec![0u16; 256];
|
||||
let mut serial_number = 0u32;
|
||||
let mut max_component_len = 0u32;
|
||||
let mut file_system_flags = 0u32;
|
||||
|
||||
if GetVolumeInformationW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
Some(&mut volume_name),
|
||||
Some(&mut serial_number),
|
||||
Some(&mut max_component_len),
|
||||
Some(&mut file_system_flags),
|
||||
Some(&mut file_system),
|
||||
).is_ok() {
|
||||
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
drive_info.name = if volume_name_str.is_empty() {
|
||||
format!("ローカルディスク ({})", drive_letter)
|
||||
} else {
|
||||
format!("{} ({})", volume_name_str, drive_letter)
|
||||
};
|
||||
} else {
|
||||
drive_info.name = format!("ドライブ ({})", drive_letter);
|
||||
}
|
||||
|
||||
// 空き容量を取得
|
||||
let mut free_bytes_available = 0u64;
|
||||
let mut total_bytes = 0u64;
|
||||
let mut total_free_bytes = 0u64;
|
||||
|
||||
if GetDiskFreeSpaceExW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
Some(&mut free_bytes_available),
|
||||
Some(&mut total_bytes),
|
||||
Some(&mut total_free_bytes),
|
||||
).is_ok() {
|
||||
drive_info.total_bytes = total_bytes;
|
||||
drive_info.free_bytes = total_free_bytes;
|
||||
}
|
||||
|
||||
self.drives.push(drive_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// Windows以外の環境ではダミーデータ
|
||||
self.drives.push(DriveInfo {
|
||||
letter: "C:".to_string(),
|
||||
name: "ローカルディスク (C:)".to_string(),
|
||||
drive_type: "ハードディスク".to_string(),
|
||||
total_bytes: 500_000_000_000,
|
||||
free_bytes: 250_000_000_000,
|
||||
icon_data: None,
|
||||
});
|
||||
}
|
||||
|
||||
self.status = format!("{}個のドライブを検出しました", self.drives.len());
|
||||
}
|
||||
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
|
||||
let mut size = bytes as f64;
|
||||
let mut unit_index = 0;
|
||||
|
||||
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
|
||||
size /= 1024.0;
|
||||
unit_index += 1;
|
||||
}
|
||||
|
||||
format!("{:.2} {}", size, UNITS[unit_index])
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for NyashExplorer {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// メニューバー
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("ファイル", |ui| {
|
||||
if ui.button("更新").clicked() {
|
||||
self.refresh_drives();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("終了").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("表示", |ui| {
|
||||
if ui.button("大きいアイコン").clicked() {
|
||||
self.status = "表示モード: 大きいアイコン".to_string();
|
||||
}
|
||||
if ui.button("詳細").clicked() {
|
||||
self.status = "表示モード: 詳細".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("ヘルプ", |ui| {
|
||||
if ui.button("Nyash Explorerについて").clicked() {
|
||||
self.status = "Nyash Explorer - Everything is Box! 🐱".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ツールバー
|
||||
egui::TopBottomPanel::top("toolbar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🔄 更新").clicked() {
|
||||
self.refresh_drives();
|
||||
}
|
||||
ui.separator();
|
||||
ui.label("Nyash Explorer - ドライブ情報ビューアー");
|
||||
});
|
||||
});
|
||||
|
||||
// ステータスバー
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("ドライブ数: {}", self.drives.len()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// メインパネル - ドライブ一覧
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("💾 ドライブ一覧");
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
for (index, drive) in self.drives.iter().enumerate() {
|
||||
let is_selected = self.selected_drive == Some(index);
|
||||
|
||||
ui.group(|ui| {
|
||||
let response = ui.allocate_response(
|
||||
egui::vec2(ui.available_width(), 100.0),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
|
||||
if response.clicked() {
|
||||
self.selected_drive = Some(index);
|
||||
self.status = format!("{} を選択しました", drive.name);
|
||||
}
|
||||
|
||||
// 背景色
|
||||
if is_selected {
|
||||
ui.painter().rect_filled(
|
||||
response.rect,
|
||||
0.0,
|
||||
egui::Color32::from_rgb(100, 149, 237).gamma_multiply(0.2),
|
||||
);
|
||||
}
|
||||
|
||||
ui.allocate_ui_at_rect(response.rect, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
// ドライブアイコン(仮)
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(10.0);
|
||||
let icon_text = match drive.drive_type.as_str() {
|
||||
"ハードディスク" => "💾",
|
||||
"リムーバブル" => "💿",
|
||||
"CD-ROM" => "💿",
|
||||
"ネットワーク" => "🌐",
|
||||
_ => "📁",
|
||||
};
|
||||
ui.label(egui::RichText::new(icon_text).size(40.0));
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
|
||||
// ドライブ情報
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(10.0);
|
||||
ui.label(egui::RichText::new(&drive.name).size(16.0).strong());
|
||||
ui.label(format!("種類: {}", drive.drive_type));
|
||||
|
||||
if drive.total_bytes > 0 {
|
||||
let used_bytes = drive.total_bytes - drive.free_bytes;
|
||||
let usage_percent = (used_bytes as f32 / drive.total_bytes as f32) * 100.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!(
|
||||
"使用領域: {} / {} ({:.1}%)",
|
||||
Self::format_bytes(used_bytes),
|
||||
Self::format_bytes(drive.total_bytes),
|
||||
usage_percent
|
||||
));
|
||||
});
|
||||
|
||||
// 使用率バー
|
||||
let bar_width = 200.0;
|
||||
let bar_height = 10.0;
|
||||
let (rect, _response) = ui.allocate_exact_size(
|
||||
egui::vec2(bar_width, bar_height),
|
||||
egui::Sense::hover(),
|
||||
);
|
||||
|
||||
// 背景
|
||||
ui.painter().rect_filled(
|
||||
rect,
|
||||
2.0,
|
||||
egui::Color32::from_gray(60),
|
||||
);
|
||||
|
||||
// 使用領域
|
||||
let used_width = bar_width * (usage_percent / 100.0);
|
||||
let used_rect = egui::Rect::from_min_size(
|
||||
rect.min,
|
||||
egui::vec2(used_width, bar_height),
|
||||
);
|
||||
let color = if usage_percent > 90.0 {
|
||||
egui::Color32::from_rgb(255, 0, 0)
|
||||
} else if usage_percent > 75.0 {
|
||||
egui::Color32::from_rgb(255, 165, 0)
|
||||
} else {
|
||||
egui::Color32::from_rgb(0, 128, 255)
|
||||
};
|
||||
ui.painter().rect_filled(used_rect, 2.0, color);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
});
|
||||
|
||||
// クイックアクション
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🐱 Nyashについて").clicked() {
|
||||
self.status = "Nyash - Everything is Box! Windows APIも吸収できる化け物言語!".to_string();
|
||||
}
|
||||
|
||||
if ui.button("📊 システム情報").clicked() {
|
||||
let total: u64 = self.drives.iter().map(|d| d.total_bytes).sum();
|
||||
let free: u64 = self.drives.iter().map(|d| d.free_bytes).sum();
|
||||
self.status = format!(
|
||||
"総容量: {} / 空き容量: {}",
|
||||
Self::format_bytes(total),
|
||||
Self::format_bytes(free)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
576
examples/nyash_explorer_with_icons.rs
Normal file
576
examples/nyash_explorer_with_icons.rs
Normal file
@ -0,0 +1,576 @@
|
||||
// Nyash Explorer with Icons - Windows API Drive Icon Viewer
|
||||
// エクスプローラー風ドライブアイコン付きビューアー
|
||||
|
||||
use eframe::egui::{self, FontFamily, ColorImage, TextureHandle};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
// use std::collections::HashMap;
|
||||
// use std::sync::Arc;
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{
|
||||
Storage::FileSystem::*,
|
||||
UI::Shell::*,
|
||||
UI::WindowsAndMessaging::*,
|
||||
System::Com::*,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
// COM初期化(Windows用)
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
|
||||
}
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([1024.0, 768.0])
|
||||
.with_title("Nyash Explorer with Icons - アイコン付きドライブビューアー"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Explorer Icons",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
setup_custom_fonts(&cc.egui_ctx);
|
||||
Ok(Box::new(NyashExplorer::new(cc.egui_ctx.clone())))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// フォント設定
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
fonts.font_data.insert(
|
||||
"noto_sans_jp".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
||||
);
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "noto_sans_jp".to_owned());
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("noto_sans_jp".to_owned());
|
||||
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
struct DriveInfo {
|
||||
letter: String,
|
||||
name: String,
|
||||
drive_type: String,
|
||||
total_bytes: u64,
|
||||
free_bytes: u64,
|
||||
icon_texture: Option<TextureHandle>,
|
||||
}
|
||||
|
||||
struct NyashExplorer {
|
||||
drives: Vec<DriveInfo>,
|
||||
selected_drive: Option<usize>,
|
||||
status: String,
|
||||
ctx: egui::Context,
|
||||
}
|
||||
|
||||
impl NyashExplorer {
|
||||
fn new(ctx: egui::Context) -> Self {
|
||||
let mut explorer = Self {
|
||||
drives: Vec::new(),
|
||||
selected_drive: None,
|
||||
status: "初期化中...".to_string(),
|
||||
ctx,
|
||||
};
|
||||
explorer.refresh_drives();
|
||||
explorer
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_drive_icon(&self, drive_path: &str) -> Option<ColorImage> {
|
||||
unsafe {
|
||||
let mut shfi = SHFILEINFOW::default();
|
||||
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
|
||||
// アイコンを取得
|
||||
let result = SHGetFileInfoW(
|
||||
PCWSTR::from_raw(drive_path_wide.as_ptr()),
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
Some(&mut shfi),
|
||||
std::mem::size_of::<SHFILEINFOW>() as u32,
|
||||
SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES,
|
||||
);
|
||||
|
||||
if result == 0 || shfi.hIcon.is_invalid() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// アイコンからビットマップを取得
|
||||
let icon_info = ICONINFO::default();
|
||||
if GetIconInfo(shfi.hIcon, &icon_info as *const _ as *mut _).is_ok() {
|
||||
// ビットマップからピクセルデータを取得する処理
|
||||
// アイコンを破棄
|
||||
let _ = DestroyIcon(shfi.hIcon);
|
||||
|
||||
// C:ドライブの場合は保存済みBMPファイルを読み込む
|
||||
if drive_path.contains("C:") {
|
||||
if let Some(icon) = Self::load_bmp_icon("c_drive_icon.bmp") {
|
||||
return Some(icon);
|
||||
}
|
||||
}
|
||||
|
||||
// それ以外はダミーアイコンを返す
|
||||
Some(Self::create_dummy_icon(&drive_path))
|
||||
} else {
|
||||
let _ = DestroyIcon(shfi.hIcon);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn get_drive_icon(&self, drive_path: &str) -> Option<ColorImage> {
|
||||
Some(Self::create_dummy_icon(drive_path))
|
||||
}
|
||||
|
||||
// BMPファイルを読み込んでColorImageに変換
|
||||
fn load_bmp_icon(file_path: &str) -> Option<ColorImage> {
|
||||
let mut file = File::open(file_path).ok()?;
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).ok()?;
|
||||
|
||||
// BMPヘッダーをパース(簡易版)
|
||||
if buffer.len() < 54 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// BMPマジックナンバーをチェック
|
||||
if &buffer[0..2] != b"BM" {
|
||||
return None;
|
||||
}
|
||||
|
||||
// ヘッダーから情報を読み取る
|
||||
let data_offset = u32::from_le_bytes([buffer[10], buffer[11], buffer[12], buffer[13]]) as usize;
|
||||
let width = i32::from_le_bytes([buffer[18], buffer[19], buffer[20], buffer[21]]) as usize;
|
||||
let height = i32::from_le_bytes([buffer[22], buffer[23], buffer[24], buffer[25]]).abs() as usize;
|
||||
let bits_per_pixel = u16::from_le_bytes([buffer[28], buffer[29]]);
|
||||
|
||||
// 32ビットBMPのみサポート
|
||||
if bits_per_pixel != 32 {
|
||||
println!("Unsupported BMP format: {} bits per pixel", bits_per_pixel);
|
||||
return None;
|
||||
}
|
||||
|
||||
// ピクセルデータを読み取る
|
||||
let mut pixels = Vec::with_capacity(width * height);
|
||||
let pixel_data = &buffer[data_offset..];
|
||||
|
||||
// BMPは下から上に格納されているので、反転しながら読み取る
|
||||
for y in (0..height).rev() {
|
||||
for x in 0..width {
|
||||
let offset = (y * width + x) * 4;
|
||||
if offset + 3 < pixel_data.len() {
|
||||
let b = pixel_data[offset];
|
||||
let g = pixel_data[offset + 1];
|
||||
let r = pixel_data[offset + 2];
|
||||
let a = pixel_data[offset + 3];
|
||||
pixels.push(egui::Color32::from_rgba_unmultiplied(r, g, b, a));
|
||||
} else {
|
||||
pixels.push(egui::Color32::TRANSPARENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(ColorImage {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
})
|
||||
}
|
||||
|
||||
// ダミーアイコンを生成(実際のアイコン取得が複雑なため)
|
||||
fn create_dummy_icon(drive_path: &str) -> ColorImage {
|
||||
let size = 48;
|
||||
let mut pixels = vec![egui::Color32::TRANSPARENT; size * size];
|
||||
|
||||
// ドライブタイプに応じた色を設定
|
||||
let color = if drive_path.contains("C:") {
|
||||
egui::Color32::from_rgb(100, 149, 237) // コーンフラワーブルー
|
||||
} else if drive_path.contains("D:") {
|
||||
egui::Color32::from_rgb(144, 238, 144) // ライトグリーン
|
||||
} else {
|
||||
egui::Color32::from_rgb(255, 182, 193) // ライトピンク
|
||||
};
|
||||
|
||||
// シンプルな円形アイコンを描画
|
||||
let center = size as f32 / 2.0;
|
||||
let radius = (size as f32 / 2.0) - 4.0;
|
||||
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let dx = x as f32 - center;
|
||||
let dy = y as f32 - center;
|
||||
let distance = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if distance <= radius {
|
||||
pixels[y * size + x] = color;
|
||||
} else if distance <= radius + 2.0 {
|
||||
// 縁取り
|
||||
pixels[y * size + x] = egui::Color32::from_rgb(64, 64, 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ドライブ文字を中央に配置(簡易版)
|
||||
if let Some(_letter) = drive_path.chars().next() {
|
||||
// 文字の位置(中央)
|
||||
let text_x = size / 2 - 8;
|
||||
let text_y = size / 2 - 8;
|
||||
|
||||
// 白い文字で描画
|
||||
for dy in 0..16 {
|
||||
for dx in 0..16 {
|
||||
if dx > 4 && dx < 12 && dy > 4 && dy < 12 {
|
||||
let idx = (text_y + dy) * size + (text_x + dx);
|
||||
if idx < pixels.len() {
|
||||
pixels[idx] = egui::Color32::WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
size: [size, size],
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_drives(&mut self) {
|
||||
self.drives.clear();
|
||||
self.status = "ドライブ情報を取得中...".to_string();
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
unsafe {
|
||||
// 論理ドライブのビットマスクを取得
|
||||
let drives_mask = GetLogicalDrives();
|
||||
|
||||
for i in 0..26 {
|
||||
if drives_mask & (1 << i) != 0 {
|
||||
let drive_letter = format!("{}:", (b'A' + i) as char);
|
||||
let drive_path = format!("{}\\", drive_letter);
|
||||
|
||||
// ドライブ情報を取得
|
||||
let mut drive_info = DriveInfo {
|
||||
letter: drive_letter.clone(),
|
||||
name: String::new(),
|
||||
drive_type: String::new(),
|
||||
total_bytes: 0,
|
||||
free_bytes: 0,
|
||||
icon_texture: None,
|
||||
};
|
||||
|
||||
// ドライブタイプを取得
|
||||
let drive_type_code = GetDriveTypeW(PCWSTR::from_raw(
|
||||
format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()
|
||||
));
|
||||
|
||||
drive_info.drive_type = match drive_type_code {
|
||||
DRIVE_REMOVABLE => "リムーバブル".to_string(),
|
||||
DRIVE_FIXED => "ハードディスク".to_string(),
|
||||
DRIVE_REMOTE => "ネットワーク".to_string(),
|
||||
DRIVE_CDROM => "CD-ROM".to_string(),
|
||||
DRIVE_RAMDISK => "RAMディスク".to_string(),
|
||||
_ => "不明".to_string(),
|
||||
};
|
||||
|
||||
// ボリューム情報を取得
|
||||
let mut volume_name = vec![0u16; 256];
|
||||
let mut file_system = vec![0u16; 256];
|
||||
let mut serial_number = 0u32;
|
||||
let mut max_component_len = 0u32;
|
||||
let mut file_system_flags = 0u32;
|
||||
|
||||
if GetVolumeInformationW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
Some(&mut volume_name),
|
||||
Some(&mut serial_number),
|
||||
Some(&mut max_component_len),
|
||||
Some(&mut file_system_flags),
|
||||
Some(&mut file_system),
|
||||
).is_ok() {
|
||||
let volume_name_str = String::from_utf16_lossy(&volume_name)
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
drive_info.name = if volume_name_str.is_empty() {
|
||||
format!("ローカルディスク ({})", drive_letter)
|
||||
} else {
|
||||
format!("{} ({})", volume_name_str, drive_letter)
|
||||
};
|
||||
} else {
|
||||
drive_info.name = format!("ドライブ ({})", drive_letter);
|
||||
}
|
||||
|
||||
// 空き容量を取得
|
||||
let mut free_bytes_available = 0u64;
|
||||
let mut total_bytes = 0u64;
|
||||
let mut total_free_bytes = 0u64;
|
||||
|
||||
if GetDiskFreeSpaceExW(
|
||||
PCWSTR::from_raw(format!("{}\0", drive_path).encode_utf16().collect::<Vec<u16>>().as_ptr()),
|
||||
Some(&mut free_bytes_available),
|
||||
Some(&mut total_bytes),
|
||||
Some(&mut total_free_bytes),
|
||||
).is_ok() {
|
||||
drive_info.total_bytes = total_bytes;
|
||||
drive_info.free_bytes = total_free_bytes;
|
||||
}
|
||||
|
||||
// アイコンを取得してテクスチャに変換
|
||||
if let Some(icon_image) = self.get_drive_icon(&drive_path) {
|
||||
let texture = self.ctx.load_texture(
|
||||
format!("drive_icon_{}", drive_letter),
|
||||
icon_image,
|
||||
Default::default()
|
||||
);
|
||||
drive_info.icon_texture = Some(texture);
|
||||
}
|
||||
|
||||
self.drives.push(drive_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// Windows以外の環境ではダミーデータ
|
||||
let mut drive_info = DriveInfo {
|
||||
letter: "C:".to_string(),
|
||||
name: "ローカルディスク (C:)".to_string(),
|
||||
drive_type: "ハードディスク".to_string(),
|
||||
total_bytes: 500_000_000_000,
|
||||
free_bytes: 250_000_000_000,
|
||||
icon_texture: None,
|
||||
};
|
||||
|
||||
if let Some(icon_image) = self.get_drive_icon("C:") {
|
||||
let texture = self.ctx.load_texture(
|
||||
"drive_icon_C:",
|
||||
icon_image,
|
||||
Default::default()
|
||||
);
|
||||
drive_info.icon_texture = Some(texture);
|
||||
}
|
||||
|
||||
self.drives.push(drive_info);
|
||||
}
|
||||
|
||||
self.status = format!("{}個のドライブを検出しました(アイコン付き)", self.drives.len());
|
||||
}
|
||||
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
|
||||
let mut size = bytes as f64;
|
||||
let mut unit_index = 0;
|
||||
|
||||
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
|
||||
size /= 1024.0;
|
||||
unit_index += 1;
|
||||
}
|
||||
|
||||
format!("{:.2} {}", size, UNITS[unit_index])
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for NyashExplorer {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// メニューバー
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("ファイル", |ui| {
|
||||
if ui.button("更新").clicked() {
|
||||
self.refresh_drives();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("終了").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("表示", |ui| {
|
||||
if ui.button("大きいアイコン").clicked() {
|
||||
self.status = "表示モード: 大きいアイコン".to_string();
|
||||
}
|
||||
if ui.button("詳細").clicked() {
|
||||
self.status = "表示モード: 詳細".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("ヘルプ", |ui| {
|
||||
if ui.button("Nyash Explorerについて").clicked() {
|
||||
self.status = "Nyash Explorer - Everything is Box! アイコンも取得できる化け物言語!🐱".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ツールバー
|
||||
egui::TopBottomPanel::top("toolbar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🔄 更新").clicked() {
|
||||
self.refresh_drives();
|
||||
}
|
||||
ui.separator();
|
||||
ui.label("Nyash Explorer - アイコン付きドライブビューアー");
|
||||
});
|
||||
});
|
||||
|
||||
// ステータスバー
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("ドライブ数: {}", self.drives.len()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// メインパネル - ドライブ一覧
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("💾 ドライブ一覧(アイコン付き)");
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
for (index, drive) in self.drives.iter().enumerate() {
|
||||
let is_selected = self.selected_drive == Some(index);
|
||||
|
||||
ui.group(|ui| {
|
||||
let response = ui.allocate_response(
|
||||
egui::vec2(ui.available_width(), 100.0),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
|
||||
if response.clicked() {
|
||||
self.selected_drive = Some(index);
|
||||
self.status = format!("{} を選択しました", drive.name);
|
||||
}
|
||||
|
||||
// 背景色
|
||||
if is_selected {
|
||||
ui.painter().rect_filled(
|
||||
response.rect,
|
||||
0.0,
|
||||
egui::Color32::from_rgb(100, 149, 237).gamma_multiply(0.2),
|
||||
);
|
||||
}
|
||||
|
||||
ui.allocate_new_ui(egui::UiBuilder::new().max_rect(response.rect), |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
// ドライブアイコン
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(10.0);
|
||||
|
||||
if let Some(texture) = &drive.icon_texture {
|
||||
ui.image((texture.id(), egui::vec2(48.0, 48.0)));
|
||||
} else {
|
||||
// フォールバック絵文字アイコン
|
||||
let icon_text = match drive.drive_type.as_str() {
|
||||
"ハードディスク" => "💾",
|
||||
"リムーバブル" => "💿",
|
||||
"CD-ROM" => "💿",
|
||||
"ネットワーク" => "🌐",
|
||||
_ => "📁",
|
||||
};
|
||||
ui.label(egui::RichText::new(icon_text).size(40.0));
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(20.0);
|
||||
|
||||
// ドライブ情報
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(10.0);
|
||||
ui.label(egui::RichText::new(&drive.name).size(16.0).strong());
|
||||
ui.label(format!("種類: {}", drive.drive_type));
|
||||
|
||||
if drive.total_bytes > 0 {
|
||||
let used_bytes = drive.total_bytes - drive.free_bytes;
|
||||
let usage_percent = (used_bytes as f32 / drive.total_bytes as f32) * 100.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!(
|
||||
"使用領域: {} / {} ({:.1}%)",
|
||||
Self::format_bytes(used_bytes),
|
||||
Self::format_bytes(drive.total_bytes),
|
||||
usage_percent
|
||||
));
|
||||
});
|
||||
|
||||
// 使用率バー
|
||||
let bar_width = 200.0;
|
||||
let bar_height = 10.0;
|
||||
let (rect, _response) = ui.allocate_exact_size(
|
||||
egui::vec2(bar_width, bar_height),
|
||||
egui::Sense::hover(),
|
||||
);
|
||||
|
||||
// 背景
|
||||
ui.painter().rect_filled(
|
||||
rect,
|
||||
2.0,
|
||||
egui::Color32::from_gray(60),
|
||||
);
|
||||
|
||||
// 使用領域
|
||||
let used_width = bar_width * (usage_percent / 100.0);
|
||||
let used_rect = egui::Rect::from_min_size(
|
||||
rect.min,
|
||||
egui::vec2(used_width, bar_height),
|
||||
);
|
||||
let color = if usage_percent > 90.0 {
|
||||
egui::Color32::from_rgb(255, 0, 0)
|
||||
} else if usage_percent > 75.0 {
|
||||
egui::Color32::from_rgb(255, 165, 0)
|
||||
} else {
|
||||
egui::Color32::from_rgb(0, 128, 255)
|
||||
};
|
||||
ui.painter().rect_filled(used_rect, 2.0, color);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
});
|
||||
|
||||
// クイックアクション
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🐱 Nyashについて").clicked() {
|
||||
self.status = "Nyash - Everything is Box! Windows APIでアイコンも取得できる化け物言語!".to_string();
|
||||
}
|
||||
|
||||
if ui.button("📊 システム情報").clicked() {
|
||||
let total: u64 = self.drives.iter().map(|d| d.total_bytes).sum();
|
||||
let free: u64 = self.drives.iter().map(|d| d.free_bytes).sum();
|
||||
self.status = format!(
|
||||
"総容量: {} / 空き容量: {}",
|
||||
Self::format_bytes(total),
|
||||
Self::format_bytes(free)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
208
examples/nyash_notepad_jp.rs
Normal file
208
examples/nyash_notepad_jp.rs
Normal file
@ -0,0 +1,208 @@
|
||||
// Nyash + egui Windows Notepad App - Japanese Font Support
|
||||
// 日本語フォント対応版のGUIメモ帳アプリ
|
||||
|
||||
use eframe::egui::{self, FontFamily};
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([800.0, 600.0])
|
||||
.with_title("Nyash Notepad - にゃっしゅメモ帳"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Notepad JP",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
// 日本語フォントを設定
|
||||
setup_custom_fonts(&cc.egui_ctx);
|
||||
Ok(Box::new(NyashNotepad::default()))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// フォント設定用の関数
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
// フォント設定を取得
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
// 日本語フォント(可変ウェイト)を追加
|
||||
fonts.font_data.insert(
|
||||
"noto_sans_jp".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../assets/NotoSansJP-VariableFont_wght.ttf")).into(),
|
||||
);
|
||||
|
||||
// フォントファミリーに追加
|
||||
fonts
|
||||
.families
|
||||
.entry(FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "noto_sans_jp".to_owned()); // 一番優先度高く追加
|
||||
|
||||
// モノスペースフォントにも日本語フォントを追加
|
||||
fonts
|
||||
.families
|
||||
.entry(FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("noto_sans_jp".to_owned());
|
||||
|
||||
// フォント設定を適用
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NyashNotepad {
|
||||
text: String,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl eframe::App for NyashNotepad {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// メニューバー
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("ファイル", |ui| {
|
||||
if ui.button("新規作成").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "新規ファイルを作成しました".to_string();
|
||||
}
|
||||
if ui.button("テキストクリア").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "テキストをクリアしました".to_string();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("終了").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("編集", |ui| {
|
||||
if ui.button("すべて選択").clicked() {
|
||||
self.status = "すべて選択(未実装)".to_string();
|
||||
}
|
||||
if ui.button("検索").clicked() {
|
||||
self.status = "検索機能(未実装)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("ヘルプ", |ui| {
|
||||
if ui.button("Nyashについて").clicked() {
|
||||
self.status = "Nyash - Everything is Box! 🐱".to_string();
|
||||
}
|
||||
if ui.button("使い方").clicked() {
|
||||
self.status = "テキストを入力して、にゃっしゅプログラムを書こう!".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ステータスバー
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("文字数: {} | 行数: {}",
|
||||
self.text.chars().count(),
|
||||
self.text.lines().count()
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// メインのテキストエディタ
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// ツールバー
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🗑️ クリア").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "テキストをクリアしました".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("📋 コピー").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.status = "テキストをコピーしました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("✂️ カット").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.text.clear();
|
||||
self.status = "テキストをカットしました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("📄 ペースト").clicked() {
|
||||
self.status = "ペースト機能(簡易版)".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("🔤 フォント大").clicked() {
|
||||
ctx.set_zoom_factor(ctx.zoom_factor() * 1.1);
|
||||
self.status = "フォントサイズを拡大しました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("🔡 フォント小").clicked() {
|
||||
ctx.set_zoom_factor(ctx.zoom_factor() * 0.9);
|
||||
self.status = "フォントサイズを縮小しました".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// テキストエディタ本体
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("ここにテキストを入力してください... にゃ!🐱")
|
||||
);
|
||||
});
|
||||
|
||||
// サンプルボタン
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("クイック挿入: ");
|
||||
|
||||
if ui.button("📝 Nyashサンプル").clicked() {
|
||||
self.text.push_str("\n// Nyash - Everything is Box! すべてがBoxの世界へようこそ!\n");
|
||||
self.text.push_str("box こんにちは世界 {\n");
|
||||
self.text.push_str(" init { メッセージ }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" こんにちは世界() {\n");
|
||||
self.text.push_str(" me.メッセージ = \"こんにちは、Nyashの世界!にゃ〜!🐱\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" 挨拶() {\n");
|
||||
self.text.push_str(" print(me.メッセージ)\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n\n");
|
||||
self.text.push_str("// 使い方:\n");
|
||||
self.text.push_str("local hello\n");
|
||||
self.text.push_str("hello = new こんにちは世界()\n");
|
||||
self.text.push_str("hello.挨拶()\n");
|
||||
self.status = "Nyashサンプルコードを挿入しました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("🕐 現在時刻").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n// 挿入時刻: {}\n", now.format("%Y年%m月%d日 %H時%M分%S秒")));
|
||||
self.status = "現在時刻を挿入しました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("🐱 ASCIIにゃんこ").clicked() {
|
||||
self.text.push_str("\n/*\n");
|
||||
self.text.push_str(" /\\_/\\ \n");
|
||||
self.text.push_str(" ( o.o ) < にゃ〜!\n");
|
||||
self.text.push_str(" > ^ < \n");
|
||||
self.text.push_str(" Nyash! \n");
|
||||
self.text.push_str("*/\n");
|
||||
self.status = "にゃんこを挿入しました - にゃ!".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
137
examples/simple_notepad.rs
Normal file
137
examples/simple_notepad.rs
Normal file
@ -0,0 +1,137 @@
|
||||
// Nyash + egui でWindowsメモ帳アプリ
|
||||
// テキスト入力機能付きのシンプルなGUIアプリケーション
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
// Windows用の設定
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_title("Nyash Notepad"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Notepad",
|
||||
options,
|
||||
Box::new(|_cc| Ok(Box::new(NyashNotepad::default()))),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NyashNotepad {
|
||||
text: String,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl eframe::App for NyashNotepad {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// メニューバー
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("ファイル", |ui| {
|
||||
if ui.button("New").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Newファイルを作成しました".to_string();
|
||||
}
|
||||
if ui.button("クリア").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("終了").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("編集", |ui| {
|
||||
if ui.button("すべて選択").clicked() {
|
||||
// TODO: テキストエリア全選択
|
||||
self.status = "すべて選択(未実装)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("ヘルプ", |ui| {
|
||||
if ui.button("Nyashについて").clicked() {
|
||||
self.status = "Nyash - Everything is Box! 🐱".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ステータスバー
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("文字数: {}", self.text.chars().count()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// メインのテキストエディタ
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// ツールバー
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("🗒️ クリア").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "テキストをクリアしました".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("📋 コピー").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.status = "テキストをコピーしました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("✂️ カット").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.text.clear();
|
||||
self.status = "テキストをカットしました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("📄 ペースト").clicked() {
|
||||
// egui 0.29ではクリップボードAPIが変更されている
|
||||
self.status = "ペースト機能(簡易版)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// テキストエディタ本体
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("ここにテキストを入力してください... にゃ!")
|
||||
);
|
||||
});
|
||||
|
||||
// サンプルボタン
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Nyashサンプル挿入").clicked() {
|
||||
self.text.push_str("\n// Nyash - Everything is Box!\n");
|
||||
self.text.push_str("box HelloWorld {\n");
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! にゃ!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n");
|
||||
self.status = "Nyashサンプルコードを挿入しました".to_string();
|
||||
}
|
||||
|
||||
if ui.button("時刻挿入").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "現在時刻を挿入しました".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
163
examples/simple_notepad_ascii.rs
Normal file
163
examples/simple_notepad_ascii.rs
Normal file
@ -0,0 +1,163 @@
|
||||
// Nyash + egui Windows Notepad App - ASCII Only Version
|
||||
// Simple GUI application with text input functionality
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_title("Nyash Notepad - ASCII Version"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Notepad",
|
||||
options,
|
||||
Box::new(|_cc| Ok(Box::new(NyashNotepad::default()))),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NyashNotepad {
|
||||
text: String,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl eframe::App for NyashNotepad {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// Menu bar
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("New").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "New file created".to_string();
|
||||
}
|
||||
if ui.button("Clear").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Exit").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Edit", |ui| {
|
||||
if ui.button("Select All").clicked() {
|
||||
self.status = "Select All (not implemented)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("About Nyash").clicked() {
|
||||
self.status = "Nyash - Everything is Box! (^-^)".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Status bar
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("Characters: {}", self.text.chars().count()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Main text editor
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// Title
|
||||
ui.heading("=== Nyash Text Editor ===");
|
||||
|
||||
// Toolbar without emojis
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("[X] Clear").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("[C] Copy").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.status = "Text copied to clipboard".to_string();
|
||||
}
|
||||
|
||||
if ui.button("[X] Cut").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.text.clear();
|
||||
self.status = "Text cut to clipboard".to_string();
|
||||
}
|
||||
|
||||
if ui.button("[V] Paste").clicked() {
|
||||
self.status = "Paste (simplified version)".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("[?] Help").clicked() {
|
||||
self.status = "Nyash Notepad v1.0 - Everything is Box!".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Text editor body
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("Type your text here... nya!")
|
||||
);
|
||||
});
|
||||
|
||||
// Sample buttons
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Quick Insert: ");
|
||||
|
||||
if ui.button("Nyash Sample Code").clicked() {
|
||||
self.text.push_str("\n// Nyash - Everything is Box!\n");
|
||||
self.text.push_str("box HelloWorld {\n");
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" greet() {\n");
|
||||
self.text.push_str(" print(me.message)\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n\n");
|
||||
self.text.push_str("// Usage:\n");
|
||||
self.text.push_str("local hello\n");
|
||||
self.text.push_str("hello = new HelloWorld()\n");
|
||||
self.text.push_str("hello.greet()\n");
|
||||
self.status = "Nyash sample code inserted".to_string();
|
||||
}
|
||||
|
||||
if ui.button("Current Time").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n[{}]\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "Timestamp inserted".to_string();
|
||||
}
|
||||
|
||||
if ui.button("ASCII Art Cat").clicked() {
|
||||
self.text.push_str("\n");
|
||||
self.text.push_str(" /\\_/\\ \n");
|
||||
self.text.push_str(" ( o.o ) \n");
|
||||
self.text.push_str(" > ^ < \n");
|
||||
self.text.push_str(" Nyash! \n");
|
||||
self.text.push_str("\n");
|
||||
self.status = "ASCII cat inserted - nya!".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
134
examples/simple_notepad_v2.rs
Normal file
134
examples/simple_notepad_v2.rs
Normal file
@ -0,0 +1,134 @@
|
||||
// Nyash + egui Windows Notepad App
|
||||
// Simple GUI application with text input functionality
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_title("Nyash Notepad"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Notepad",
|
||||
options,
|
||||
Box::new(|_cc| Ok(Box::new(NyashNotepad::default()))),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NyashNotepad {
|
||||
text: String,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl eframe::App for NyashNotepad {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// Menu bar
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("New").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "New file created".to_string();
|
||||
}
|
||||
if ui.button("Clear").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Exit").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Edit", |ui| {
|
||||
if ui.button("Select All").clicked() {
|
||||
self.status = "Select All (not implemented)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("About Nyash").clicked() {
|
||||
self.status = "Nyash - Everything is Box!".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Status bar
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("Characters: {}", self.text.chars().count()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Main text editor
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// Toolbar
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Clear").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("Copy").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.status = "Text copied to clipboard".to_string();
|
||||
}
|
||||
|
||||
if ui.button("Cut").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.text.clear();
|
||||
self.status = "Text cut to clipboard".to_string();
|
||||
}
|
||||
|
||||
if ui.button("Paste").clicked() {
|
||||
self.status = "Paste (simplified version)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Text editor body
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("Type your text here... nya!")
|
||||
);
|
||||
});
|
||||
|
||||
// Sample buttons
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Insert Nyash Sample").clicked() {
|
||||
self.text.push_str("\n// Nyash - Everything is Box!\n");
|
||||
self.text.push_str("box HelloWorld {\n");
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n");
|
||||
self.status = "Nyash sample code inserted".to_string();
|
||||
}
|
||||
|
||||
if ui.button("Insert Timestamp").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "Timestamp inserted".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
176
examples/simple_notepad_win.rs
Normal file
176
examples/simple_notepad_win.rs
Normal file
@ -0,0 +1,176 @@
|
||||
// Nyash + egui Windows Notepad App
|
||||
// Simple GUI application with text input functionality
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_title("Nyash Notepad"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"Nyash Notepad",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
// Configure fonts for Windows
|
||||
setup_custom_fonts(&cc.egui_ctx);
|
||||
Ok(Box::new(NyashNotepad::default()))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
// Start with the default fonts
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
// Use default system fonts for better Windows compatibility
|
||||
fonts.font_data.insert(
|
||||
"system".to_owned(),
|
||||
std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
|
||||
"C:/Windows/Fonts/arial.ttf"
|
||||
))),
|
||||
);
|
||||
|
||||
// Configure font families
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.push("system".to_owned());
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("system".to_owned());
|
||||
|
||||
// Tell egui to use these fonts
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NyashNotepad {
|
||||
text: String,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl NyashNotepad {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
text: String::new(),
|
||||
status: "Ready".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for NyashNotepad {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// Menu bar
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("New").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "New file created".to_string();
|
||||
}
|
||||
if ui.button("Clear").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Exit").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Edit", |ui| {
|
||||
if ui.button("Select All").clicked() {
|
||||
self.status = "Select All (not implemented)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("About Nyash").clicked() {
|
||||
self.status = "Nyash - Everything is Box!".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Status bar
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(&self.status);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("Characters: {}", self.text.chars().count()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Main text editor
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// Toolbar with ASCII-only labels
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("[Clear]").clicked() {
|
||||
self.text.clear();
|
||||
self.status = "Text cleared".to_string();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("[Copy]").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.status = "Text copied to clipboard".to_string();
|
||||
}
|
||||
|
||||
if ui.button("[Cut]").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = self.text.clone());
|
||||
self.text.clear();
|
||||
self.status = "Text cut to clipboard".to_string();
|
||||
}
|
||||
|
||||
if ui.button("[Paste]").clicked() {
|
||||
self.status = "Paste (simplified version)".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Text editor body
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(20)
|
||||
.hint_text("Type your text here... nya!")
|
||||
);
|
||||
});
|
||||
|
||||
// Sample buttons
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Insert Nyash Sample").clicked() {
|
||||
self.text.push_str("\n// Nyash - Everything is Box!\n");
|
||||
self.text.push_str("box HelloWorld {\n");
|
||||
self.text.push_str(" init { message }\n");
|
||||
self.text.push_str(" \n");
|
||||
self.text.push_str(" HelloWorld() {\n");
|
||||
self.text.push_str(" me.message = \"Hello, Nyash World! nya!\"\n");
|
||||
self.text.push_str(" }\n");
|
||||
self.text.push_str("}\n");
|
||||
self.status = "Nyash sample code inserted".to_string();
|
||||
}
|
||||
|
||||
if ui.button("Insert Timestamp").clicked() {
|
||||
let now = chrono::Local::now();
|
||||
self.text.push_str(&format!("\n{}\n", now.format("%Y-%m-%d %H:%M:%S")));
|
||||
self.status = "Timestamp inserted".to_string();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
137
examples/test_icon_extraction.rs
Normal file
137
examples/test_icon_extraction.rs
Normal file
@ -0,0 +1,137 @@
|
||||
// Windows Icon Extraction Test
|
||||
// アイコンを実際に取得してICOファイルとして保存するテスト
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{
|
||||
Storage::FileSystem::*,
|
||||
UI::Shell::*,
|
||||
UI::WindowsAndMessaging::*,
|
||||
Graphics::Gdi::*,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
unsafe {
|
||||
println!("Windows Icon Extraction Test");
|
||||
|
||||
// C:ドライブのアイコンを取得
|
||||
let drive_path = "C:\\";
|
||||
let drive_path_wide: Vec<u16> = drive_path.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
|
||||
let mut shfi = SHFILEINFOW::default();
|
||||
|
||||
let result = SHGetFileInfoW(
|
||||
PCWSTR::from_raw(drive_path_wide.as_ptr()),
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
Some(&mut shfi),
|
||||
std::mem::size_of::<SHFILEINFOW>() as u32,
|
||||
SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES,
|
||||
);
|
||||
|
||||
println!("SHGetFileInfoW result: {}", result);
|
||||
|
||||
if result != 0 && !shfi.hIcon.is_invalid() {
|
||||
println!("Icon handle obtained!");
|
||||
|
||||
// アイコン情報を取得
|
||||
let mut icon_info = ICONINFO::default();
|
||||
if GetIconInfo(shfi.hIcon, &mut icon_info).is_ok() {
|
||||
println!("GetIconInfo success!");
|
||||
println!("fIcon: {}", icon_info.fIcon.as_bool());
|
||||
|
||||
// ビットマップ情報を取得
|
||||
if !icon_info.hbmColor.is_invalid() {
|
||||
println!("Color bitmap handle obtained!");
|
||||
|
||||
// ビットマップ情報を取得
|
||||
let mut bitmap = BITMAP::default();
|
||||
let size = GetObjectW(
|
||||
icon_info.hbmColor.into(),
|
||||
std::mem::size_of::<BITMAP>() as i32,
|
||||
Some(&mut bitmap as *mut _ as *mut _)
|
||||
);
|
||||
|
||||
if size > 0 {
|
||||
println!("Bitmap info:");
|
||||
println!(" Width: {}", bitmap.bmWidth);
|
||||
println!(" Height: {}", bitmap.bmHeight);
|
||||
println!(" Bits per pixel: {}", bitmap.bmBitsPixel);
|
||||
println!(" Planes: {}", bitmap.bmPlanes);
|
||||
|
||||
// ピクセルデータを取得
|
||||
let pixel_count = (bitmap.bmWidth * bitmap.bmHeight) as usize;
|
||||
let bytes_per_pixel = (bitmap.bmBitsPixel / 8) as usize;
|
||||
let mut pixels = vec![0u8; pixel_count * bytes_per_pixel];
|
||||
|
||||
let copied = GetBitmapBits(
|
||||
icon_info.hbmColor,
|
||||
pixels.len() as i32,
|
||||
pixels.as_mut_ptr() as *mut _
|
||||
);
|
||||
|
||||
println!("Copied {} bytes of pixel data", copied);
|
||||
|
||||
// 簡易的にBMPファイルとして保存
|
||||
if copied > 0 {
|
||||
save_as_bmp("c_drive_icon.bmp", &pixels, bitmap.bmWidth, bitmap.bmHeight, bitmap.bmBitsPixel);
|
||||
println!("Saved as c_drive_icon.bmp");
|
||||
}
|
||||
}
|
||||
|
||||
// ビットマップを削除
|
||||
let _ = DeleteObject(icon_info.hbmColor.into());
|
||||
}
|
||||
|
||||
if !icon_info.hbmMask.is_invalid() {
|
||||
println!("Mask bitmap handle obtained!");
|
||||
let _ = DeleteObject(icon_info.hbmMask.into());
|
||||
}
|
||||
}
|
||||
|
||||
// アイコンを破棄
|
||||
let _ = DestroyIcon(shfi.hIcon);
|
||||
} else {
|
||||
println!("Failed to get icon");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
println!("This test only works on Windows");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn save_as_bmp(filename: &str, pixels: &[u8], width: i32, height: i32, bits_per_pixel: u16) {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
// 簡易BMPヘッダー(実際の実装はもっと複雑)
|
||||
let file_size = 54 + pixels.len() as u32;
|
||||
let mut file = File::create(filename).unwrap();
|
||||
|
||||
// BMPファイルヘッダー
|
||||
file.write_all(b"BM").unwrap(); // マジックナンバー
|
||||
file.write_all(&file_size.to_le_bytes()).unwrap();
|
||||
file.write_all(&0u32.to_le_bytes()).unwrap(); // 予約
|
||||
file.write_all(&54u32.to_le_bytes()).unwrap(); // データオフセット
|
||||
|
||||
// BMPインフォヘッダー
|
||||
file.write_all(&40u32.to_le_bytes()).unwrap(); // ヘッダーサイズ
|
||||
file.write_all(&width.to_le_bytes()).unwrap();
|
||||
file.write_all(&height.to_le_bytes()).unwrap();
|
||||
file.write_all(&1u16.to_le_bytes()).unwrap(); // プレーン数
|
||||
file.write_all(&bits_per_pixel.to_le_bytes()).unwrap();
|
||||
file.write_all(&0u32.to_le_bytes()).unwrap(); // 圧縮なし
|
||||
file.write_all(&(pixels.len() as u32).to_le_bytes()).unwrap();
|
||||
file.write_all(&0i32.to_le_bytes()).unwrap(); // X解像度
|
||||
file.write_all(&0i32.to_le_bytes()).unwrap(); // Y解像度
|
||||
file.write_all(&0u32.to_le_bytes()).unwrap(); // カラーテーブル数
|
||||
file.write_all(&0u32.to_le_bytes()).unwrap(); // 重要な色数
|
||||
|
||||
// ピクセルデータ
|
||||
file.write_all(pixels).unwrap();
|
||||
|
||||
println!("BMP file saved: {}", filename);
|
||||
}
|
||||
Reference in New Issue
Block a user