{"id":12793,"date":"2026-10-04T01:00:00","date_gmt":"2026-10-03T16:00:00","guid":{"rendered":"https:\/\/www.webcyou.com\/?p=12793"},"modified":"2026-09-25T01:10:03","modified_gmt":"2026-09-24T16:10:03","slug":"%e3%80%90r-chr%e3%80%91rust-egui-%e3%81%a7%e3%83%95%e3%82%a1%e3%83%9f%e3%82%b3%e3%83%b3%e7%94%a8%e3%82%b9%e3%83%97%e3%83%a9%e3%82%a4%e3%83%88%e3%82%a8%e3%83%87%e3%82%a3%e3%82%bf%e3%82%92%e4%bd%9c-7","status":"publish","type":"post","link":"https:\/\/www.webcyou.com\/?p=12793","title":{"rendered":"\u3010R-CHR\u3011Rust + egui \u3067\u30d5\u30a1\u30df\u30b3\u30f3\u7528\u30b9\u30d7\u30e9\u30a4\u30c8\u30a8\u30c7\u30a3\u30bf\u3092\u4f5c\u3063\u305f\u8a71 \u305d\u306e7 macOS \u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u30fb\u591a\u8a00\u8a9e\u5bfe\u5fdc\u30fbGitHub Actions \u3067\u306e\u30ea\u30ea\u30fc\u30b9"},"content":{"rendered":"\n<p>\u3069\u3082\u3067\u3059\u3002<\/p>\n<p>R-CHR \u306e\u89e3\u8aac\u3082\u3044\u3088\u3044\u3088\u6700\u7d42\u56de\u3067\u3059\u3002\u3053\u3053\u307e\u3067\u3067 CHR \u306e\u8aad\u307f\u66f8\u304d\u3001UI\u3001\u63cf\u753b\u30c4\u30fc\u30eb\u3001PNG \u30a4\u30f3\u30dd\u30fc\u30c8\u3068\u3001\u30a8\u30c7\u30a3\u30bf\u3068\u3057\u3066\u306e\u6a5f\u80fd\u306f\u4e00\u901a\u308a\u89e3\u8aac\u3057\u3066\u304d\u307e\u3057\u305f\u3002<\/p>\n<p>\u6700\u7d42\u56de\u306f\u300cegui \u3060\u3051\u3067\u306f\u8db3\u308a\u306a\u304b\u3063\u305f\u90e8\u5206\u3092\u3069\u3046\u57cb\u3081\u305f\u304b\u300d\u3068\u3044\u3046\u8a71\u3067\u3001<strong>macOS \u306e\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u3001\u65e5\u672c\u8a9e \/ \u82f1\u8a9e\u306e\u5207\u308a\u66ff\u3048\u3001\u305d\u3057\u3066 GitHub Actions \u3067 Windows \/ macOS \/ Linux \u5411\u3051\u306b\u30d3\u30eb\u30c9\u3057\u3066\u914d\u5e03\u3059\u308b\u4ed5\u7d44\u307f<\/strong>\u3092\u307e\u3068\u3081\u305f\u5f62\u306b\u306a\u308a\u307e\u3059\u3002<\/p>\n<p>\u4f5c\u3063\u3066\u52d5\u304f\u3068\u3053\u308d\u307e\u3067\u306f\u697d\u3057\u3044\u306e\u3067\u3059\u304c\u3001\u300c\u4ed6\u306e\u4eba\u304c\u666e\u901a\u306b\u4f7f\u3048\u308b\u5f62\u3067\u914d\u308b\u300d\u3068\u306a\u308b\u3068\u9014\u7aef\u306b\u3084\u308b\u3053\u3068\u304c\u5897\u3048\u308b\u3093\u3067\u3059\u3088\u306d\u3002\u3002<\/p>\n<h4>\u9023\u8f09\u306e\u76ee\u6b21<\/h4>\n<ol>\n<li>\u6982\u8981\u3068\u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3<\/li>\n<li>NES \u306e CHR \u30d5\u30a9\u30fc\u30de\u30c3\u30c8\uff082BPP\uff09\u3068 iNES \u30d8\u30c3\u30c0\u3001\u30d1\u30ec\u30c3\u30c8<\/li>\n<li>egui \u3067\u7d44\u3080 3 \u30d1\u30cd\u30eb UI \u3068\u30d0\u30f3\u30af\u30d3\u30e5\u30fc<\/li>\n<li>\u30c9\u30c3\u30c8\u30a8\u30c7\u30a3\u30bf\u306e\u57fa\u790e\u3068\u30a2\u30f3\u30c9\u30a5\u8a2d\u8a08<\/li>\n<li>\u56f3\u5f62\u30c4\u30fc\u30eb\uff08Bresenham\u30fb\u6955\u5186\u30fb\u5857\u308a\u3064\u3076\u3057\u30fb\u30b9\u30bf\u30f3\u30d7\uff09<\/li>\n<li>PNG \u30a4\u30f3\u30dd\u30fc\u30c8\u3068 3 \u3064\u306e\u30de\u30c3\u30d4\u30f3\u30b0\u6226\u7565<\/li>\n<li><strong>macOS \u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u30fb\u591a\u8a00\u8a9e\u5bfe\u5fdc\u30fbGitHub Actions \u3067\u306e\u30ea\u30ea\u30fc\u30b9\uff08\u3053\u306e\u8a18\u4e8b\uff09<\/strong><\/li>\n<\/ol>\n<h4>macOS \u3067\u306f\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u3092\u4f7f\u3044\u305f\u3044<\/h4>\n<p>egui \u306b\u306f\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u306e\u30a6\u30a3\u30b8\u30a7\u30c3\u30c8\uff08<code>egui::menu::bar<\/code>\uff09\u304c\u3042\u3063\u3066\u3001Windows \u3084 Linux \u3067\u306f\u305d\u308c\u3067\u5341\u5206\u3067\u3059\u3002\u305f\u3060 macOS \u306e\u5834\u5408\u3001\u30e1\u30cb\u30e5\u30fc\u306f\u30a6\u30a3\u30f3\u30c9\u30a6\u306e\u4e2d\u3067\u306f\u306a\u304f<strong>\u753b\u9762\u4e0a\u90e8\u306e\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc<\/strong>\u306b\u3042\u308b\u306e\u304c\u5f53\u305f\u308a\u524d\u3067\u3057\u3066\u3001\u30a6\u30a3\u30f3\u30c9\u30a6\u5185\u306b egui \u306e\u30e1\u30cb\u30e5\u30fc\u304c\u51fa\u3066\u3044\u308b\u3068\u3001\u306a\u3093\u3068\u3044\u3046\u304b\u300cMac \u306e\u30a2\u30d7\u30ea\u3067\u306f\u306a\u3044\u611f\u300d\u304c\u3059\u3054\u3044\u3002<\/p>\n<p>\u3042\u3068\u3001<code>Cmd+S<\/code> \u3084 <code>Cmd+Z<\/code> \u306e\u3088\u3046\u306a\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\u3082\u3001macOS \u3067\u306f\u30e1\u30cb\u30e5\u30fc\u9805\u76ee\u306b\u7d10\u3065\u3044\u305f\u30a2\u30af\u30bb\u30e9\u30ec\u30fc\u30bf\u3068\u3057\u3066\u6271\u308f\u308c\u308b\u306e\u304c\u81ea\u7136\u3067\u3059\u3002<\/p>\n<p>\u3068\u3044\u3046\u3053\u3068\u3067\u3001macOS \u3060\u3051 <strong>muda<\/strong> \u30af\u30ec\u30fc\u30c8\u3067\u30cd\u30a4\u30c6\u30a3\u30d6 NSMenu \u3092\u7d44\u307f\u3001\u305d\u308c\u4ee5\u5916\u306e OS \u306f egui \u306e\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u3001\u3068\u5207\u308a\u66ff\u3048\u3066\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">\/\/ \u2500\u2500 \u30e1\u30cb\u30e5\u30fc\u30d0\u30fc (macOS \u306f\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u3092\u4f7f\u3046\u305f\u3081\u975e\u8868\u793a)\n#[cfg(not(target_os = \"macos\"))]\nself.show_menu_bar(ctx);<\/pre>\n<p><code>native_menu.rs<\/code> \u306f\u5148\u982d\u306b <code>#![cfg(target_os = \"macos\")]<\/code> \u3092\u4ed8\u3051\u3066\u30d5\u30a1\u30a4\u30eb\u3054\u3068 macOS \u5c02\u7528\u306b\u3057\u3001<code>Cargo.toml<\/code> \u3067\u3082\u4f9d\u5b58\u3092 <code>[target.'cfg(target_os = \"macos\")'.dependencies]<\/code> \u306b\u5165\u308c\u3066\u3044\u308b\u306e\u3067\u3001\u4ed6\u306e OS \u3067\u306f muda \u3084 objc2 \u304c\u305d\u3082\u305d\u3082\u30d3\u30eb\u30c9\u3055\u308c\u307e\u305b\u3093\u3002<\/p>\n<h5>\u521d\u671f\u5316\u30bf\u30a4\u30df\u30f3\u30b0\u306e\u5236\u7d04<\/h5>\n<p>muda \u3067 NSMenu \u3092\u7d44\u3093\u3067\u30a2\u30d7\u30ea\u306b\u767b\u9332\u3059\u308b\u306b\u306f <code>menu.init_for_nsapp()<\/code> \u3092\u547c\u3076\u306e\u3067\u3059\u304c\u3001\u3053\u308c\u306f <strong><code>NSApplication<\/code> \u304c\u751f\u6210\u3055\u308c\u305f\u5f8c<\/strong>\u3067\u306a\u3044\u3068\u52d5\u304d\u307e\u305b\u3093\u3002\u305d\u3057\u3066 <code>NSApplication<\/code> \u3092\u4f5c\u3063\u3066\u3044\u308b\u306e\u306f eframe \u306e <code>run_native()<\/code> \u306e\u4e2d\u3067\u3059\u3002<\/p>\n<p>\u306a\u306e\u3067\u3001\u521d\u671f\u5316\u306f <code>run_native()<\/code> \u306b\u6e21\u3059<strong>\u30a2\u30d7\u30ea\u751f\u6210\u30af\u30ed\u30fc\u30b8\u30e3\u306e\u4e2d<\/strong>\u3067\u884c\u3044\u307e\u3059\u3002\u7b2c 3 \u56de\u306e <code>main.rs<\/code> \u306b\u3042\u3063\u305f\u3053\u308c\u3067\u3059\u306d\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">eframe::run_native(\n    \"R-CHR\",\n    options,\n    Box::new(|cc| {\n        \/\/ macOS: NSApp \u304c\u521d\u671f\u5316\u3055\u308c\u305f\u5f8c\uff08\u3053\u3053\uff09\u3067\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u3092\u69cb\u7bc9\u3057\u5916\u89b3\u3092\u8a2d\u5b9a\u3059\u308b\n        #[cfg(target_os = \"macos\")]\n        {\n            native_menu::init();\n            native_menu::set_app_appearance(true); \/\/ \u30c7\u30d5\u30a9\u30eb\u30c8\u306f\u30c0\u30fc\u30af\u30e2\u30fc\u30c9\n        }\n        \/\/ ...\n    }),\n)<\/pre>\n<p><code>run_native()<\/code> \u306e\u524d\u3067\u547c\u3076\u3068 NSApp \u304c\u7121\u304f\u3066\u843d\u3061\u308b\u3001\u3068\u3044\u3046\u306e\u3092\u6700\u521d\u306b\u3084\u3089\u304b\u3057\u307e\u3057\u305f\u3002<\/p>\n<h5>MenuItem \u306f Send \u3067\u306f\u306a\u3044<\/h5>\n<p>\u3082\u3046\u3072\u3068\u3064\u306e\u7f60\u304c\u3001muda \u306e <code>MenuItem<\/code> \u304c\u5185\u90e8\u3067 <code>Rc<\/code> \u3092\u4f7f\u3063\u3066\u3044\u3066 <strong><code>Send<\/code> \u3067\u306f\u306a\u3044<\/strong>\u3053\u3068\u3067\u3059\u3002\u30e1\u30cb\u30e5\u30fc\u9805\u76ee\u306e\u30cf\u30f3\u30c9\u30eb\uff08\u3042\u3068\u3067\u6709\u52b9 \/ \u7121\u52b9\u3092\u5207\u308a\u66ff\u3048\u305f\u308a\u30c6\u30ad\u30b9\u30c8\u3092\u5909\u3048\u305f\u308a\u3059\u308b\u305f\u3081\u306b\u5fc5\u8981\uff09\u3092\u30b0\u30ed\u30fc\u30d0\u30eb\u306b\u6301\u3061\u305f\u3044\u306e\u3067\u3059\u304c\u3001<code>static<\/code> \u3084 <code>OnceLock<\/code> \u306b\u306f <code>Send + Sync<\/code> \u304c\u8981\u6c42\u3055\u308c\u308b\u306e\u3067\u5165\u308c\u3089\u308c\u307e\u305b\u3093\u3002<\/p>\n<p>\u305d\u3053\u3067 <code>thread_local!<\/code> + <code>RefCell<\/code> \u3067\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">struct MenuHandles {\n    about:            MenuItem,\n    lang_english:     CheckMenuItem,\n    file_new:         MenuItem,\n    file_open:        MenuItem,\n    \/\/ ...\n    view_dark_mode:   CheckMenuItem,\n    \/\/ \u30b5\u30d6\u30e1\u30cb\u30e5\u30fc\uff08\u8a00\u8a9e\u5207\u66ff\u6642\u306b set_text \u3059\u308b\u305f\u3081\u4fdd\u6301\uff09\n    sub_file:         Submenu,\n    sub_edit:         Submenu,\n    sub_view:         Submenu,\n    sub_palette:      Submenu,\n}\n\nthread_local! {\n    static HANDLES: RefCell&lt;&lt;option&gt;&gt; = const { RefCell::new(None) };\n}<\/pre>\n<p>\u30e1\u30cb\u30e5\u30fc\u306f\u30e1\u30a4\u30f3\u30b9\u30ec\u30c3\u30c9\u304b\u3089\u3057\u304b\u89e6\u3089\u306a\u3044\u306e\u3067\u3001\u30b9\u30ec\u30c3\u30c9\u30ed\u30fc\u30ab\u30eb\u3067\u6301\u3064\u306e\u306f\u7406\u306b\u304b\u306a\u3063\u3066\u3044\u307e\u3059\u3002<\/p>\n<h5>\u5148\u982d\u306f\u30a2\u30d7\u30ea\u540d\u30e1\u30cb\u30e5\u30fc<\/h5>\n<p>macOS \u306e\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u306f\u3001<strong>\u6700\u521d\u306e\u30b5\u30d6\u30e1\u30cb\u30e5\u30fc\u304c\u5fc5\u305a\u30a2\u30d7\u30ea\u540d\u306e\u30e1\u30cb\u30e5\u30fc\uff08About \u3084 Quit \u304c\u5165\u308b\u3084\u3064\uff09\u3068\u3057\u3066\u6271\u308f\u308c\u308b<\/strong>\u3068\u3044\u3046\u30eb\u30fc\u30eb\u304c\u3042\u308a\u307e\u3059\u3002\u3053\u308c\u3092\u77e5\u3089\u305a\u306b\u30d5\u30a1\u30a4\u30eb\u30e1\u30cb\u30e5\u30fc\u3092\u5148\u982d\u306b\u7f6e\u304f\u3068\u3001\u300c\u30d5\u30a1\u30a4\u30eb\u300d\u306e\u4e2d\u8eab\u304c\u30a2\u30d7\u30ea\u540d\u30e1\u30cb\u30e5\u30fc\u306b\u5438\u3044\u8fbc\u307e\u308c\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">\/\/ \u2500\u2500 macOS: \u5148\u982d\u306f\u30a2\u30d7\u30ea\u540d\u30e1\u30cb\u30e5\u30fc\uff08\u7701\u7565\u3059\u308b\u3068\u30d5\u30a1\u30a4\u30eb\u30e1\u30cb\u30e5\u30fc\u304c\u305d\u3053\u306b\u5165\u308b\uff09\nlet app_menu = Submenu::new(\"R-CHR\", true);\napp_menu.append(&amp;h.about).unwrap();\napp_menu.append(&amp;PredefinedMenuItem::separator()).unwrap();\napp_menu.append(&amp;h.lang_english).unwrap();\napp_menu.append(&amp;PredefinedMenuItem::separator()).unwrap();\napp_menu.append(&amp;PredefinedMenuItem::quit(None)).unwrap();\n\n\/\/ \u2500\u2500 \u30eb\u30fc\u30c8\u30e1\u30cb\u30e5\u30fc\u306b\u8ffd\u52a0\u3057\u3066\u30a2\u30d7\u30ea\u306e\u30e1\u30cb\u30e5\u30fc\u30d0\u30fc\u3078\nlet menu = Menu::new();\nmenu.append(&amp;app_menu).unwrap();\nmenu.append(&amp;h.sub_file).unwrap();\nmenu.append(&amp;h.sub_edit).unwrap();\nmenu.append(&amp;h.sub_view).unwrap();\nmenu.append(&amp;h.sub_palette).unwrap();\nmenu.init_for_nsapp();<\/pre>\n<p>\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\u306f <code>Accelerator<\/code> \u3067\u9805\u76ee\u306b\u4ed8\u3051\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">file_save: MenuItem::new(s.file_save, false, Some(Accelerator::new(Some(cmd), Code::KeyS))),<\/pre>\n<h5>\u30a4\u30d9\u30f3\u30c8\u306e\u53d7\u3051\u53d6\u308a<\/h5>\n<p>muda \u306e\u30e1\u30cb\u30e5\u30fc\u306f\u30af\u30ea\u30c3\u30af\u3055\u308c\u308b\u3068 <code>MenuEvent::receiver()<\/code> \u306e\u30c1\u30e3\u30cd\u30eb\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u6d41\u3057\u307e\u3059\u3002R-CHR \u3067\u306f\u3053\u308c\u3092\u6bce\u30d5\u30ec\u30fc\u30e0 <code>try_recv()<\/code> \u3067\u30dd\u30fc\u30ea\u30f3\u30b0\u3057\u3066\u3001\u9805\u76ee ID \u3092\u7167\u5408\u3057\u3066\u81ea\u524d\u306e <code>MenuAction<\/code> enum \u306b\u5909\u63db\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">pub fn try_recv_action() -&gt; Option {\n    let event = MenuEvent::receiver().try_recv().ok()?;\n    HANDLES.with(|slot| {\n        let borrow = slot.borrow();\n        let h = borrow.as_ref()?;\n        let id = &amp;event.id;\n        if      id == h.about.id()            { Some(MenuAction::About) }\n        else if id == h.file_new.id()         { Some(MenuAction::FileNew) }\n        else if id == h.file_open.id()        { Some(MenuAction::FileOpen) }\n        \/\/ ...\n        else if id == h.view_dark_mode.id()   { Some(MenuAction::ViewDarkMode(h.view_dark_mode.is_checked())) }\n        else { None }\n    })\n}<\/pre>\n<p>\u53d7\u3051\u53d6\u308a\u5074\u306f <code>update()<\/code> \u306e\u5148\u982d\u3067\u3001\u30ad\u30e5\u30fc\u304c\u7a7a\u306b\u306a\u308b\u307e\u3067\u56de\u3057\u3066\u30c7\u30a3\u30b9\u30d1\u30c3\u30c1\u3057\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">pub(in crate::editor) fn handle_native_menu(&amp;mut self, ctx: &amp;egui::Context) {\n    while let Some(action) = native_menu::try_recv_action() {\n        match action {\n            MenuAction::About           =&gt; self.show_about = true,\n            MenuAction::FileNew         =&gt; self.new_file(),\n            MenuAction::FileOpen        =&gt; self.open_file(),\n            MenuAction::FileSave        =&gt; { if let Err(e) = self.save_file() { self.error_msg = Some(e); } }\n            MenuAction::EditUndo        =&gt; self.do_undo(),\n            MenuAction::ViewDarkMode(v) =&gt; {\n                self.dark_mode = v;\n                native_menu::set_app_appearance(v);\n            }\n            MenuAction::LangEnglish(en) =&gt; {\n                self.lang = if en { Lang::En } else { Lang::Ja };\n                native_menu::set_menu_lang(self.lang);\n            }\n            \/\/ ...\n        }\n    }\n\n    \/\/ enabled \/ checked \u72b6\u614b\u3092\u6bce\u30d5\u30ec\u30fc\u30e0\u540c\u671f\n    let has_tile = self.selected_tile.is_some()\n        &amp;&amp; self.rom.as_ref().map_or(false, |r| !r.chr_data().is_empty());\n    native_menu::sync_state(\n        self.file_path.is_some() &amp;&amp; self.is_modified,\n        !self.undo_stack.is_empty(),\n        has_tile,\n        has_tile &amp;&amp; self.tile_clipboard.is_some(),\n        self.dark_mode,\n        self.lang,\n    );\n}<\/pre>\n<p><code>native_menu<\/code> \u304c\u8fd4\u3059\u306e\u306f\u3042\u304f\u307e\u3067\u300c\u4f55\u304c\u62bc\u3055\u308c\u305f\u304b\u300d\u3060\u3051\u3067\u3001\u30a2\u30d7\u30ea\u306e\u72b6\u614b\u306f\u4e00\u5207\u77e5\u308a\u307e\u305b\u3093\u3002\u30a2\u30d7\u30ea\u5074\u306e\u30c7\u30a3\u30b9\u30d1\u30c3\u30c1\u306f <code>editor\/mac\/menu_events.rs<\/code> \u306b\u7f6e\u3044\u3066\u3042\u308a\u307e\u3059\u3002<\/p>\n<h5>enabled \/ checked \u306e\u540c\u671f<\/h5>\n<p>\u300c\u672a\u4fdd\u5b58\u306e\u5909\u66f4\u304c\u7121\u3044\u3068\u304d\u306f\u4fdd\u5b58\u3092\u30b0\u30ec\u30fc\u30a2\u30a6\u30c8\u300d\u300c\u30a2\u30f3\u30c9\u30a5\u30b9\u30bf\u30c3\u30af\u304c\u7a7a\u306a\u3089\u5143\u306b\u623b\u3059\u3092\u30b0\u30ec\u30fc\u30a2\u30a6\u30c8\u300d\u3068\u3044\u3063\u305f\u72b6\u614b\u306f\u3001<strong>\u6bce\u30d5\u30ec\u30fc\u30e0 <code>sync_state()<\/code> \u3067 egui \u5074\u306e\u72b6\u614b\u304b\u3089\u30e1\u30cb\u30e5\u30fc\u3078\u66f8\u304d\u8fbc\u3080<\/strong>\u5f62\u306b\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">pub fn sync_state(can_save: bool, can_undo: bool, can_copy: bool, can_paste: bool, dark_mode: bool, lang: Lang) {\n    HANDLES.with(|slot| {\n        let borrow = slot.borrow();\n        let Some(h) = borrow.as_ref() else { return };\n        h.file_save.set_enabled(can_save);\n        h.edit_undo.set_enabled(can_undo);\n        h.edit_copy.set_enabled(can_copy);\n        h.edit_paste.set_enabled(can_paste);\n        if h.view_dark_mode.is_checked() != dark_mode {\n            h.view_dark_mode.set_checked(dark_mode);\n        }\n        \/\/ ...\n    });\n}<\/pre>\n<p>\u5373\u6642\u30e2\u30fc\u30c9\u306e\u767a\u60f3\u3092\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u306b\u3082\u6301\u3061\u8fbc\u3093\u3060\u5f62\u3067\u3059\u306d\u3002\u300c\u72b6\u614b\u304c\u5909\u308f\u3063\u305f\u3089\u30e1\u30cb\u30e5\u30fc\u3092\u66f4\u65b0\u3059\u308b\u300d\u306e\u3067\u306f\u306a\u304f\u300c\u6bce\u30d5\u30ec\u30fc\u30e0\u72b6\u614b\u3092\u30e1\u30cb\u30e5\u30fc\u306b\u6d41\u3057\u8fbc\u3080\u300d\u3002\u30a4\u30d9\u30f3\u30c8\u99c6\u52d5\u3067\u66f4\u65b0\u6f0f\u308c\u3092\u6c17\u306b\u3059\u308b\u3088\u308a\u3001\u3053\u3063\u3061\u306e\u307b\u3046\u304c\u5727\u5012\u7684\u306b\u697d\u3067\u3057\u305f\u3002<\/p>\n<h5>\u30ad\u30fc\u30dc\u30fc\u30c9\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\u306e\u4e8c\u91cd\u5b9a\u7fa9<\/h5>\n<p>\u3053\u3053\u3067\u3072\u3068\u3064\u6ce8\u610f\u70b9\u3002macOS \u3067 NSMenu \u306b\u30a2\u30af\u30bb\u30e9\u30ec\u30fc\u30bf\u3092\u4ed8\u3051\u308b\u3068\u3001<strong><code>Cmd+Z<\/code> \u3084 <code>Cmd+S<\/code> \u306f NSMenu \u304c\u5148\u306b\u6a2a\u53d6\u308a\u3057\u3066\u3001egui \u306b\u306f\u5c4a\u304d\u307e\u305b\u3093<\/strong>\u3002\u306a\u306e\u3067\u7b2c 3 \u56de\u3067\u898b\u305f <code>keyboard.rs<\/code> \u306e <code>Cmd+Z<\/code> \u51e6\u7406\u306f\u3001macOS \u3067\u306f\u30e1\u30cb\u30e5\u30fc\u7d4c\u7531\u3067\u52d5\u3044\u3066\u3044\u307e\u3059\u3002<\/p>\n<p><code>keyboard.rs<\/code> \u306b\u3082\u540c\u3058\u51e6\u7406\u3092\u6b8b\u3057\u3066\u3044\u308b\u306e\u306f\u3001NSMenu \u304c\u6a5f\u80fd\u3057\u306a\u3044\u72b6\u6cc1\u3078\u306e\u30d5\u30a9\u30fc\u30eb\u30d0\u30c3\u30af\u3067\u3059\u3002\u4e8c\u91cd\u306b\u5b9f\u884c\u3055\u308c\u308b\u5fc3\u914d\u306f\u7121\u3044\u306e\u3067\uff08\u5c4a\u304f\u306e\u306f\u3069\u3061\u3089\u304b\u4e00\u65b9\u3060\u3051\uff09\u3001\u305d\u306e\u307e\u307e\u306b\u3057\u3066\u3042\u308a\u307e\u3059\u3002<\/p>\n<h5>\u30c0\u30fc\u30af\u30e2\u30fc\u30c9\u3068 NSAlert<\/h5>\n<p>\u30bf\u30a4\u30c8\u30eb\u30d0\u30fc\u306e\u8272\u307e\u3067\u542b\u3081\u3066\u30c0\u30fc\u30af\u30e2\u30fc\u30c9\u306b\u3059\u308b\u306b\u306f\u3001egui \u306e <code>Visuals<\/code> \u3060\u3051\u3067\u306f\u8db3\u308a\u305a\u3001<code>NSApplication<\/code> \u306e\u5916\u89b3\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002<code>objc2-app-kit<\/code> \u3067 <code>NSAppearance<\/code> \u3092\u5207\u308a\u66ff\u3048\u3066\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">pub fn set_app_appearance(dark: bool) {\n    let Some(mtm) = MainThreadMarker::new() else { return };\n    let app = NSApplication::sharedApplication(mtm);\n    let name = if dark {\n        unsafe { NSAppearanceNameDarkAqua }\n    } else {\n        unsafe { NSAppearanceNameAqua }\n    };\n    let appearance = NSAppearance::appearanceNamed(name);\n    unsafe { app.setAppearance(appearance.as_deref()); }\n}<\/pre>\n<p>\u672a\u4fdd\u5b58\u306e\u5909\u66f4\u304c\u3042\u308b\u3068\u304d\u306e\u78ba\u8a8d\u30c0\u30a4\u30a2\u30ed\u30b0\u3082\u3001macOS \u3067\u306f <code>NSAlert<\/code> \u3092\u76f4\u63a5\u547c\u3093\u3067\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">pub fn unsaved_changes_dialog(file_name: &amp;str, lang: Lang) -&gt; UnsavedChoice {\n    let Some(mtm) = MainThreadMarker::new() else {\n        return UnsavedChoice::Cancel;\n    };\n    let s = crate::editor::i18n::t(lang);\n    let alert = unsafe { NSAlert::new(mtm) };\n    unsafe {\n        alert.setMessageText(&amp;NSString::from_str(s.unsaved_title));\n        alert.setInformativeText(&amp;NSString::from_str(&amp;lang.fmt_unsaved_body(file_name)));\n        alert.setAlertStyle(NSAlertStyle::Warning);\n        alert.addButtonWithTitle(&amp;NSString::from_str(s.save_and_close));\n        alert.addButtonWithTitle(&amp;NSString::from_str(s.discard_btn));\n        alert.addButtonWithTitle(&amp;NSString::from_str(s.cancel_btn2));\n        let response = alert.runModal();\n        \/\/ NSAlertFirstButtonReturn=1000, Second=1001, Third=1002\n        match response {\n            1000 =&gt; UnsavedChoice::Save,\n            1001 =&gt; UnsavedChoice::Discard,\n            _    =&gt; UnsavedChoice::Cancel,\n        }\n    }\n}<\/pre>\n<p><code>objc2<\/code> \u7d4c\u7531\u3060\u3068 Objective-C \u306e API \u304c\u307b\u307c\u305d\u306e\u307e\u307e Rust \u304b\u3089\u547c\u3079\u307e\u3059\u3002<code>unsafe<\/code> \u3060\u3089\u3051\u306b\u306f\u306a\u308a\u307e\u3059\u304c\u3001<code>MainThreadMarker<\/code> \u3067\u300c\u30e1\u30a4\u30f3\u30b9\u30ec\u30c3\u30c9\u304b\u3089\u3057\u304b\u547c\u3079\u306a\u3044\u300d\u3053\u3068\u304c\u578b\u3067\u4fdd\u8a3c\u3055\u308c\u308b\u306e\u306f\u3001\u3055\u3059\u304c Rust \u3060\u306a\u3041\u3068\u3002<\/p>\n<p>macOS \u4ee5\u5916\u3067\u306f\u3001\u540c\u3058\u78ba\u8a8d\u3092 <code>rfd::MessageDialog<\/code> \u306e Yes \/ No \/ Cancel \u3067\u51fa\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n<h4>\u65e5\u672c\u8a9e \/ \u82f1\u8a9e\u306e\u5207\u308a\u66ff\u3048<\/h4>\n<p>UI \u306e\u6587\u8a00\u306f\u65e5\u672c\u8a9e\u3068\u82f1\u8a9e\u3092\u5207\u308a\u66ff\u3048\u3089\u308c\u307e\u3059\u3002\u4ed5\u7d44\u307f\u306f\u3068\u3066\u3082\u7d20\u6734\u3067\u3001<strong>\u5168\u3066\u306e\u9759\u7684\u6587\u5b57\u5217\u3092 1 \u3064\u306e\u69cb\u9020\u4f53\u306b\u307e\u3068\u3081\u3066\u3001\u8a00\u8a9e\u3054\u3068\u306b <code>static<\/code> \u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u6301\u3064<\/strong>\u3060\u3051\u3067\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">#[derive(Clone, Copy, PartialEq, Eq, Default)]\npub enum Lang { #[default] Ja, En }\n\n\/\/\/ UI \u3067\u4f7f\u3046\u5168\u9759\u7684\u6587\u5b57\u5217\u306e\u30bb\u30c3\u30c8\npub struct Strings {\n    pub about:              &amp;'static str,\n    pub menu_file:          &amp;'static str,\n    pub file_new:           &amp;'static str,\n    pub file_open:          &amp;'static str,\n    \/\/ ...\uff0860 \u500b\u307b\u3069\uff09\n}\n\nstatic JA: Strings = Strings {\n    about:              \"R-CHR \u306b\u3064\u3044\u3066\u2026\",\n    menu_file:          \"\u30d5\u30a1\u30a4\u30eb\",\n    file_new:           \"\u65b0\u898f\u4f5c\u6210\",\n    file_open:          \"\u958b\u304f\u2026\",\n    \/\/ ...\n};\n\nstatic EN: Strings = Strings { \/* ... *\/ };\n\npub fn t(lang: Lang) -&gt; &amp;'static Strings {\n    match lang { Lang::Ja =&gt; &amp;JA, Lang::En =&gt; &amp;EN }\n}<\/pre>\n<p>\u4f7f\u3046\u5074\u306f <code>self.t().file_open<\/code> \u306e\u3088\u3046\u306b\u66f8\u304d\u307e\u3059\u3002\u5168\u90e8 <code>&amp;'static str<\/code> \u306a\u306e\u3067\u5b9f\u884c\u6642\u306e\u30a2\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u30bc\u30ed\u3067\u3059\u3057\u3001\u69cb\u9020\u4f53\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u306a\u306e\u3067<strong>\u6587\u8a00\u3092 1 \u3064\u8ffd\u52a0\u3057\u305f\u3089\u4e21\u8a00\u8a9e\u306b\u8ffd\u52a0\u3057\u306a\u3044\u3068\u30b3\u30f3\u30d1\u30a4\u30eb\u30a8\u30e9\u30fc\u306b\u306a\u308b<\/strong>\u306e\u304c\u5730\u5473\u306b\u4fbf\u5229\u3067\u3059\u3002\u7ffb\u8a33\u6f0f\u308c\u304c\u69cb\u9020\u7684\u306b\u8d77\u304d\u307e\u305b\u3093\u3002<\/p>\n<p>\u6570\u5024\u3084\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u57cb\u3081\u8fbc\u3080\u6587\u8a00\u306f\u3001<code>Lang<\/code> \u306e\u30e1\u30bd\u30c3\u30c9\u3068\u3057\u3066\u7528\u610f\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"rust\">impl Lang {\n    pub fn fmt_png_done(self, tw: usize, th: usize) -&gt; String { \/* ... *\/ }\n    pub fn fmt_unsaved_body(self, file_name: &amp;str) -&gt; String { \/* ... *\/ }\n    pub fn fmt_approx_colors(self, n: usize) -&gt; String { \/* ... *\/ }\n    \/\/ ...\n}<\/pre>\n<p>\u524d\u56de\u306e <code>PngWarning<\/code> \u3092 enum \u306b\u3057\u305f\u8a71\u3082\u3001\u3053\u306e\u69cb\u9020\u306b\u4e57\u305b\u308b\u305f\u3081\u3067\u3057\u305f\u3002<\/p>\n<p>\u8a00\u8a9e\u3092\u5207\u308a\u66ff\u3048\u305f\u3068\u304d\u306f\u3001egui \u5074\u306f\u6b21\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u52dd\u624b\u306b\u65b0\u3057\u3044\u6587\u8a00\u3067\u63cf\u304d\u76f4\u3055\u308c\u307e\u3059\u304c\u3001\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u306f\u81ea\u5206\u3067\u66f4\u65b0\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u305d\u308c\u304c <code>set_menu_lang()<\/code> \u3067\u3001\u4fdd\u6301\u3057\u3066\u3044\u308b\u30cf\u30f3\u30c9\u30eb\u5168\u90e8\u306b <code>set_text()<\/code> \u3092\u547c\u3093\u3067\u56de\u308a\u307e\u3059\u3002\u30b5\u30d6\u30e1\u30cb\u30e5\u30fc\u306e\u30cf\u30f3\u30c9\u30eb\u307e\u3067 <code>MenuHandles<\/code> \u306b\u6301\u305f\u305b\u3066\u3044\u308b\u306e\u306f\u3053\u306e\u305f\u3081\u3067\u3059\u3002<\/p>\n<h4>GitHub Actions \u3067 3 OS \u5411\u3051\u306b\u30d3\u30eb\u30c9\u3057\u3066\u30ea\u30ea\u30fc\u30b9<\/h4>\n<p>\u6700\u5f8c\u306b\u914d\u5e03\u306e\u8a71\u3067\u3059\u3002<code>main<\/code> \u306b push \u3059\u308b\u3068\u3001Windows \/ macOS \/ Linux \u306e 3 \u3064\u3067\u30d3\u30eb\u30c9\u3057\u3066\u3001GitHub Releases \u306b\u6210\u679c\u7269\u3092\u4e26\u3079\u308b\u3068\u3053\u308d\u307e\u3067\u81ea\u52d5\u5316\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">name: Build &amp; Release\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build:\n    name: Build (${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: ubuntu-latest\n            artifact_name: R-CHR\n            asset_name: R-CHR-linux-x86_64\n            asset_path: R-CHR-linux-x86_64\n          - os: windows-latest\n            artifact_name: R-CHR.exe\n            asset_name: R-CHR-windows-x86_64.exe\n            asset_path: R-CHR-windows-x86_64.exe\n          - os: macos-latest\n            artifact_name: R-CHR\n            asset_name: R-CHR-macos-aarch64\n            asset_path: R-CHR-macos-aarch64.zip\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v4\n\n      - name: Install Rust toolchain\n        uses: dtolnay\/rust-toolchain@stable\n\n      - name: Cache cargo\n        uses: actions\/cache@v4\n        with:\n          path: |\n            ~\/.cargo\/registry\n            ~\/.cargo\/git\n            target\n          key: ${{ runner.os }}-cargo-${{ hashFiles('**\/Cargo.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-cargo-\n\n      - name: Install Linux system dependencies\n        if: matrix.os == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y \\\n            libxcb-render0-dev \\\n            libxcb-shape0-dev \\\n            libxcb-xfixes0-dev \\\n            libxkbcommon-dev \\\n            libssl-dev \\\n            libgtk-3-dev\n\n      - name: Build\n        run: cargo build --release<\/pre>\n<p><code>fail-fast: false<\/code> \u306b\u3057\u3066\u3044\u308b\u306e\u306f\u30011 \u3064\u306e OS \u3067\u30b3\u30b1\u3066\u3082\u4ed6\u306e OS \u306e\u30d3\u30eb\u30c9\u306f\u6700\u5f8c\u307e\u3067\u8d70\u3089\u305b\u305f\u3044\u304b\u3089\u3067\u3059\u3002Linux \u3060\u3051 <code>apt-get<\/code> \u3067 X11 \/ GTK \u7cfb\u306e\u30d8\u30c3\u30c0\u3092\u5165\u308c\u3066\u3044\u308b\u306e\u306f\u3001eframe\uff08winit\uff09\u3068 rfd\uff08GTK \u306e\u30d5\u30a1\u30a4\u30eb\u30c0\u30a4\u30a2\u30ed\u30b0\uff09\u306e\u30d3\u30eb\u30c9\u306b\u5fc5\u8981\u306a\u305f\u3081\u3067\u3059\u306d\u3002\u3053\u3053\u3001\u30ed\u30fc\u30ab\u30eb\u306e Mac \u3067\u306f\u4e00\u5207\u6c17\u306b\u3057\u306a\u304f\u3066\u826f\u3044\u306e\u3067\u3001CI \u3067\u521d\u3081\u3066\u6c17\u3065\u304f\u3084\u3064\u3067\u3059\u3002<\/p>\n<p><code>macos-latest<\/code> \u306f\u73fe\u5728 Apple Silicon \u306e\u30e9\u30f3\u30ca\u30fc\u306a\u306e\u3067\u3001\u6210\u679c\u7269\u306f <code>aarch64<\/code> \u306b\u306a\u308a\u307e\u3059\u3002<\/p>\n<h5>macOS \u306f .app \u30d0\u30f3\u30c9\u30eb\u306b\u3059\u308b<\/h5>\n<p>Windows \u3068 Linux \u306f <code>cargo build<\/code> \u3067\u51fa\u3066\u304d\u305f\u30d0\u30a4\u30ca\u30ea\u3092\u305d\u306e\u307e\u307e\u7f6e\u3051\u3070\u826f\u3044\u306e\u3067\u3059\u304c\u3001macOS \u306f\u305d\u3046\u3082\u3044\u304b\u305a\u3001<strong><code>.app<\/code> \u30d0\u30f3\u30c9\u30eb<\/strong>\u306e\u5f62\u306b\u3057\u306a\u3044\u3068\u30c0\u30d6\u30eb\u30af\u30ea\u30c3\u30af\u3067\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093\uff08\u30a2\u30a4\u30b3\u30f3\u3082\u4ed8\u304d\u307e\u305b\u3093\uff09\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">      - name: Package (macOS .app bundle)\n        if: matrix.os == 'macos-latest'\n        run: |\n          APP=\"R-CHR.app\"\n          VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's\/.*\"\\(.*\\)\"\/\\1\/')\n\n          # \u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u69cb\u9020\n          mkdir -p \"$APP\/Contents\/MacOS\"\n          mkdir -p \"$APP\/Contents\/Resources\"\n\n          # \u30d0\u30a4\u30ca\u30ea\n          cp target\/release\/R-CHR \"$APP\/Contents\/MacOS\/R-CHR\"\n\n          # Info.plist\uff08\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u57cb\u3081\u8fbc\u3080\uff09\n          sed \"s\/{{VERSION}}\/$VERSION\/g\" macos\/Info.plist &gt; \"$APP\/Contents\/Info.plist\"\n\n          # icon.png \u2192 AppIcon.icns\n          ICONSET=\"AppIcon.iconset\"\n          mkdir -p \"$ICONSET\"\n          for size in 16 32 128 256 512; do\n            sips -z $size $size assets\/icon.png --out \"$ICONSET\/icon_${size}x${size}.png\"\n            sips -z $((size * 2)) $((size * 2)) assets\/icon.png --out \"$ICONSET\/icon_${size}x${size}@2x.png\"\n          done\n          iconutil -c icns \"$ICONSET\" -o \"$APP\/Contents\/Resources\/AppIcon.icns\"\n          rm -rf \"$ICONSET\"\n\n          # ad-hoc \u7f72\u540d\uff08Gatekeeper \u306e\u300c\u58ca\u308c\u3066\u3044\u308b\u300d\u30a8\u30e9\u30fc\u3092\u56de\u907f\uff09\n          codesign --force --deep --sign - \"$APP\"\n\n          # ditto \u3067 zip\uff08\u30d1\u30fc\u30df\u30c3\u30b7\u30e7\u30f3\u30fb\u62e1\u5f35\u5c5e\u6027\u3092\u4fdd\u6301\uff09\n          ditto -c -k --keepParent \"$APP\" ${{ matrix.asset_path }}<\/pre>\n<p>\u3084\u3063\u3066\u3044\u308b\u3053\u3068\u3092\u9806\u306b\u3002<\/p>\n<ol>\n<li><strong>\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u69cb\u9020<\/strong>\uff1a<code>.app<\/code> \u306f\u5b9f\u4f53\u3068\u3057\u3066\u306f\u305f\u3060\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3067\u3001<code>Contents\/MacOS\/<\/code> \u306b\u30d0\u30a4\u30ca\u30ea\u3001<code>Contents\/Resources\/<\/code> \u306b\u30a2\u30a4\u30b3\u30f3\u3001<code>Contents\/Info.plist<\/code> \u306b\u30e1\u30bf\u60c5\u5831\u3001\u3068\u3044\u3046\u6c7a\u307e\u3063\u305f\u69cb\u9020\u3067\u3059<\/li>\n<li><strong>Info.plist<\/strong>\uff1a\u30ea\u30dd\u30b8\u30c8\u30ea\u306b <code>{{VERSION}}<\/code> \u3068\u3044\u3046\u30d7\u30ec\u30fc\u30b9\u30db\u30eb\u30c0\u5165\u308a\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u7f6e\u3044\u3066\u304a\u304d\u3001<code>Cargo.toml<\/code> \u304b\u3089\u53d6\u3063\u305f\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 <code>sed<\/code> \u3067\u57cb\u3081\u307e\u3059\u3002\u30d0\u30fc\u30b8\u30e7\u30f3\u306e\u7ba1\u7406\u5834\u6240\u3092 <code>Cargo.toml<\/code> \u4e00\u7b87\u6240\u306b\u3059\u308b\u305f\u3081\u3067\u3059<\/li>\n<li><strong>\u30a2\u30a4\u30b3\u30f3<\/strong>\uff1a<code>assets\/icon.png<\/code> \u304b\u3089 <code>sips<\/code> \u3067\u5404\u30b5\u30a4\u30ba\u306e PNG \u3092\u4f5c\u3063\u3066 <code>.iconset<\/code> \u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306b\u4e26\u3079\u3001<code>iconutil<\/code> \u3067 <code>.icns<\/code> \u306b\u56fa\u3081\u307e\u3059\u3002macOS \u6a19\u6e96\u30c4\u30fc\u30eb\u3060\u3051\u3067\u5b8c\u7d50\u3057\u307e\u3059<\/li>\n<li><strong>ad-hoc \u7f72\u540d<\/strong>\uff1a\u3053\u3053\u304c\u91cd\u8981\u3067\u3057\u3066\u3001\u7f72\u540d\u306a\u3057\u306e <code>.app<\/code> \u3092 zip \u3067\u914d\u308b\u3068\u3001\u53d7\u3051\u53d6\u3063\u305f\u5074\u3067\u300c\u58ca\u308c\u3066\u3044\u308b\u305f\u3081\u958b\u3051\u307e\u305b\u3093\u300d\u3068\u8a00\u308f\u308c\u307e\u3059\u3002Apple Developer \u306e\u8a3c\u660e\u66f8\u306f\u7121\u304f\u3066\u3082\u3001<code>codesign --sign -<\/code> \u306e <strong>ad-hoc \u7f72\u540d<\/strong>\u3092\u3057\u3066\u304a\u304f\u3060\u3051\u3067\u3053\u306e\u30a8\u30e9\u30fc\u306f\u56de\u907f\u3067\u304d\u307e\u3059\uff08\u300c\u958b\u767a\u5143\u3092\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093\u300d\u306e\u8b66\u544a\u306f\u51fa\u308b\u306e\u3067\u3001\u53f3\u30af\u30ea\u30c3\u30af \u2192 \u958b\u304f\u3001\u306f\u5fc5\u8981\u3067\u3059\uff09<\/li>\n<li><strong>ditto \u3067 zip<\/strong>\uff1a\u666e\u901a\u306e <code>zip<\/code> \u30b3\u30de\u30f3\u30c9\u3060\u3068\u5b9f\u884c\u6a29\u9650\u3084\u62e1\u5f35\u5c5e\u6027\u304c\u843d\u3061\u3066\u3057\u307e\u3046\u3053\u3068\u304c\u3042\u308b\u306e\u3067\u3001macOS \u306e <code>ditto -c -k --keepParent<\/code> \u3067\u56fa\u3081\u307e\u3059<\/li>\n<\/ol>\n<p><code>Info.plist<\/code> \u306f\u3053\u3093\u306a\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3067\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\"><!--?xml version=\"1.0\" encoding=\"UTF-8\"?-->\n\n\n\n    CFBundleName\n    R-CHR\n    CFBundleDisplayName\n    R-CHR\n    CFBundleIdentifier\n    com.rchr.app\n    CFBundleVersion\n    {{VERSION}}\n    CFBundleShortVersionString\n    {{VERSION}}\n    CFBundleExecutable\n    R-CHR\n    CFBundlePackageType\n    APPL\n    CFBundleIconFile\n    AppIcon\n    NSHighResolutionCapable\n    \n    LSMinimumSystemVersion\n    11.0\n\n<\/pre>\n<p><code>NSHighResolutionCapable<\/code> \u3092\u5165\u308c\u3066\u304a\u304b\u306a\u3044\u3068 Retina \u3067\u30dc\u30b1\u30dc\u30b1\u306b\u306a\u308b\u306e\u3067\u3001\u3053\u308c\u306f\u5fc5\u9808\u3067\u3059\u3002<\/p>\n<h5>Release \u30b8\u30e7\u30d6<\/h5>\n<p>3 OS \u306e\u30d3\u30eb\u30c9\u304c\u7d42\u308f\u3063\u305f\u3089\u3001\u5225\u30b8\u30e7\u30d6\u3067\u6210\u679c\u7269\u3092\u96c6\u3081\u3066 Release \u3092\u4f5c\u308a\u307e\u3059\u3002<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">  release:\n    name: Create Release\n    needs: build\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v4\n\n      - name: Get version from Cargo.toml\n        id: version\n        run: |\n          VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's\/.*\"\\(.*\\)\"\/\\1\/')\n          echo \"tag=v${VERSION}\" &gt;&gt; $GITHUB_OUTPUT\n          echo \"name=v${VERSION}\" &gt;&gt; $GITHUB_OUTPUT\n\n      - name: Download all artifacts\n        uses: actions\/download-artifact@v4\n        with:\n          path: artifacts\n          merge-multiple: true\n\n      - name: Create or update Release\n        uses: softprops\/action-gh-release@v2\n        with:\n          tag_name: ${{ steps.version.outputs.tag }}\n          name: ${{ steps.version.outputs.name }}\n          files: artifacts\/*\n          fail_on_unmatched_files: true\n          make_latest: true<\/pre>\n<p>\u30bf\u30b0\u540d\u3082 <code>Cargo.toml<\/code> \u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u304b\u3089\u4f5c\u3063\u3066\u3044\u307e\u3059\u3002<code>v0.1.0<\/code> \u306e\u3088\u3046\u306a\u30bf\u30b0\u306e Release \u304c\u7121\u3051\u308c\u3070\u4f5c\u308a\u3001\u3042\u308c\u3070\u6210\u679c\u7269\u3092\u66f4\u65b0\u3059\u308b\u3001\u3068\u3044\u3046\u52d5\u304d\u3067\u3059\u3002\u306a\u306e\u3067<strong>\u30ea\u30ea\u30fc\u30b9\u3057\u305f\u3044\u3068\u304d\u306f <code>Cargo.toml<\/code> \u306e <code>version<\/code> \u3092\u4e0a\u3052\u3066 push \u3059\u308b\u3060\u3051<\/strong>\u3002\u30bf\u30b0\u3092\u624b\u3067\u6253\u3064\u5fc5\u8981\u306f\u3042\u308a\u307e\u305b\u3093\u3002<\/p>\n<p><code>permissions: contents: write<\/code> \u3092\u5fd8\u308c\u308b\u3068 Release \u306e\u4f5c\u6210\u3067\u6a29\u9650\u30a8\u30e9\u30fc\u306b\u306a\u308b\u306e\u3067\u3001\u3053\u3053\u3082 CI \u3067\u521d\u3081\u3066\u6c17\u3065\u304f\u3084\u3064\u3067\u3059\u306d\u3002<\/p>\n<h4>\u9023\u8f09\u306e\u307e\u3068\u3081<\/h4>\n<p>7 \u56de\u306b\u308f\u305f\u3063\u3066 R-CHR \u306e\u4e2d\u8eab\u3092\u89e3\u8aac\u3057\u3066\u304d\u307e\u3057\u305f\u3002\u632f\u308a\u8fd4\u308b\u3068\u3001\u8a2d\u8a08\u4e0a\u306e\u5224\u65ad\u3067\u52b9\u3044\u305f\u306e\u306f\u3053\u306e\u8fba\u308a\u304b\u306a\u3068\u601d\u3044\u307e\u3059\u3002<\/p>\n<ul>\n<li><strong>\u5c64\u3092\u5206\u3051\u308b<\/strong>\uff1a<code>model \u2192 io \u2192 editor<\/code> \u306e\u4e00\u65b9\u901a\u884c\u306b\u3057\u3066\u3001CHR \u306e\u5909\u63db\u3084 PNG \u306e\u30de\u30c3\u30d4\u30f3\u30b0\u3092 egui \u304b\u3089\u5207\u308a\u96e2\u3057\u305f\u3002\u304a\u304b\u3052\u3067\u7d14\u7c8b\u306a\u30c7\u30fc\u30bf\u51e6\u7406\u3068\u3057\u3066\u66f8\u3051\u3066\u3001\u3042\u3068\u304b\u3089\u8aad\u307f\u8fd4\u3057\u3066\u3082\u5206\u304b\u308a\u3084\u3059\u3044<\/li>\n<li><strong>\u66f8\u304d\u8fbc\u307f\u306e\u51fa\u53e3\u3092 1 \u3064\u306b\u3059\u308b<\/strong>\uff1a\u5168\u30c4\u30fc\u30eb\u306e\u66f8\u304d\u8fbc\u307f\u304c <code>encode_dot()<\/code>\u3001\u30a2\u30f3\u30c9\u30a5\u306e\u5165\u53e3\u304c <code>push_undo_batch()<\/code>\u3002\u30c4\u30fc\u30eb\u3092\u8db3\u3059\u306e\u304c\u300c\u30c9\u30c3\u30c8\u5ea7\u6a19\u306e\u30ea\u30b9\u30c8\u3092\u4f5c\u308b\u95a2\u6570\u3092\u66f8\u304f\u300d\u3060\u3051\u306b\u306a\u3063\u305f<\/li>\n<li><strong>UI \u3068\u30c7\u30fc\u30bf\u66f4\u65b0\u3092\u5206\u3051\u308b<\/strong>\uff1a<code>EditorAction<\/code> \u3067\u300c\u4f55\u3092\u3059\u308b\u304b\u300d\u3060\u3051\u3092\u8fd4\u3057\u3066\u3001\u63cf\u753b\u5f8c\u306b\u9069\u7528\u3059\u308b\u3002\u5373\u6642\u30e2\u30fc\u30c9 + \u501f\u7528\u30c1\u30a7\u30c3\u30ab\u30fc\u3068\u4ef2\u826f\u304f\u3084\u308b\u5b9a\u77f3<\/li>\n<li><strong>\u5373\u6642\u30e2\u30fc\u30c9\u306e\u767a\u60f3\u3092\u5916\u306b\u3082\u5e83\u3052\u308b<\/strong>\uff1a\u30cd\u30a4\u30c6\u30a3\u30d6\u30e1\u30cb\u30e5\u30fc\u306e enabled \/ checked \u3082\u300c\u6bce\u30d5\u30ec\u30fc\u30e0\u72b6\u614b\u3092\u6d41\u3057\u8fbc\u3080\u300d\u306b\u3057\u305f\u3089\u3001\u66f4\u65b0\u6f0f\u308c\u3092\u8003\u3048\u306a\u304f\u3066\u826f\u304f\u306a\u3063\u305f<\/li>\n<li><strong>\u914d\u5e03\u307e\u3067\u542b\u3081\u3066\u81ea\u52d5\u5316\u3059\u308b<\/strong>\uff1a<code>.app<\/code> \u30d0\u30f3\u30c9\u30eb\u3068 ad-hoc \u7f72\u540d\u307e\u3067 CI \u306b\u5165\u308c\u3066\u304a\u3044\u305f\u306e\u3067\u3001<code>Cargo.toml<\/code> \u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u4e0a\u3052\u308b\u3060\u3051\u3067\u30ea\u30ea\u30fc\u30b9\u3067\u304d\u308b<\/li>\n<\/ul>\n<p>Rust + egui \u3067\u30c7\u30b9\u30af\u30c8\u30c3\u30d7\u30a2\u30d7\u30ea\u3092\u914d\u5e03\u7528\u3068\u3057\u3066\u304d\u3061\u3093\u3068\u4f5c\u308b\u306e\u306f\u521d\u3081\u3066\u3060\u3063\u305f\u306e\u3067\u3059\u304c\u3001\u601d\u3063\u3066\u3044\u305f\u3088\u308a\u7d20\u76f4\u306b\u7d44\u3081\u305f\u3001\u3068\u3044\u3046\u306e\u304c\u6b63\u76f4\u306a\u611f\u60f3\u3067\u3059\u3002\u7279\u306b\u30c9\u30c3\u30c8\u30a8\u30c7\u30a3\u30bf\u306e\u3088\u3046\u306a\u300c<strong>\u72b6\u614b\u304b\u3089\u7d75\u3092\u63cf\u304d\u76f4\u3059<\/strong>\u300d\u30bf\u30a4\u30d7\u306e\u30a2\u30d7\u30ea\u3068\u5373\u6642\u30e2\u30fc\u30c9\u306e\u76f8\u6027\u306f\u672c\u5f53\u306b\u826f\u3044\u306e\u3067\u3001\u540c\u3058\u3088\u3046\u306a\u30c4\u30fc\u30eb\u3092\u4f5c\u308d\u3046\u3068\u3057\u3066\u3044\u308b\u65b9\u306b\u306f\u7d50\u69cb\u304a\u3059\u3059\u3081\u3067\u304d\u308b\u306e\u304b\u3068\u3002<\/p>\n<p>\u5f53\u521d\u306f\u300c<strong>YY-CHR \u306e\u4ee3\u308f\u308a\u306b Mac \u3067\u4f7f\u3048\u308b\u3082\u306e\u304c\u3042\u308c\u3070<\/strong>\u300d\u304f\u3089\u3044\u306e\u6c17\u6301\u3061\u3067\u59cb\u3081\u305f\u306e\u3067\u3059\u304c\u3001\u6c17\u304c\u3064\u3044\u305f\u3089\u63cf\u753b\u30c4\u30fc\u30eb\u304c 10 \u500b\u306b\u306a\u308a\u3001PNG \u30a4\u30f3\u30dd\u30fc\u30c8\u306b 3 \u3064\u3082\u6226\u7565\u304c\u4ed8\u304d\u3001macOS \u306e NSAlert \u307e\u3067\u547c\u3093\u3067\u3044\u307e\u3057\u305f\u3002\u307e\u3041\u3001\u3053\u3046\u3044\u3046\u3082\u306e\u3067\u3059\u3088\u306dw<\/p>\n<p>\u30ea\u30dd\u30b8\u30c8\u30ea\u306f\u3053\u3061\u3089\u3067\u3059\u3002\u30d0\u30b0\u5831\u544a\u3084\u8981\u671b\u306a\u3069\u3042\u308c\u3070 Issue \u306b\u3044\u305f\u3060\u3051\u308b\u3068\u5b09\u3057\u3044\u3067\u3059\u3002<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/retrodig\/r-chr\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/retrodig\/r-chr<\/a><\/li>\n<\/ul>\n<p>\u3067\u306f\u3067\u306f\u3041\u3002<\/p>\n<p>\u307e\u305f\u307e\u305f\u3041\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u3069\u3082\u3067\u3059\u3002 R-CHR \u306e\u89e3\u8aac\u3082\u3044\u3088\u3044\u3088\u6700\u7d42\u56de\u3067\u3059\u3002\u3053\u3053\u307e\u3067\u3067 CHR \u306e\u8aad\u307f\u66f8\u304d\u3001UI\u3001\u63cf\u753b\u30c4\u30fc\u30eb\u3001PNG \u30a4\u30f3\u30dd\u30fc\u30c8\u3068\u3001\u30a8\u30c7\u30a3\u30bf\u3068\u3057\u3066\u306e\u6a5f\u80fd\u306f\u4e00\u901a\u308a\u89e3\u8aac\u3057\u3066\u304d\u307e\u3057\u305f\u3002 \u6700\u7d42\u56de\u306f\u300cegui \u3060\u3051\u3067\u306f\u8db3\u308a\u306a\u304b\u3063\u305f\u90e8\u5206\u3092\u3069 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":12828,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[299],"tags":[308,169,381,39,390,389,388,78,118],"class_list":["post-12793","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-rust","tag-gui","tag-mac","tag-nes","tag-39","tag-390","tag-389","tag-388","tag-78","tag-118"],"_links":{"self":[{"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/posts\/12793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12793"}],"version-history":[{"count":3,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/posts\/12793\/revisions"}],"predecessor-version":[{"id":12837,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/posts\/12793\/revisions\/12837"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=\/wp\/v2\/media\/12828"}],"wp:attachment":[{"href":"https:\/\/www.webcyou.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12793"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12793"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcyou.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}