クロスプラットフォーム GUI フレームワークな Relm4 と GTK4 を使って GUI アプリをつくるハローワールドのメモ。GTK4 だと macOS で動作させてもあまり GTK ぽさがないかも。良い。

Relm4 makes developing beautiful cross-platform applications idiomatic, simple and fast and enables you to become productive in just a few hours.
GTK4 の devel パッケージ準備
Linux (Debian):
$ sudo apt install libgtk-4-dev build-essential
macOS: – pkg-config
使うので入れること
$ brew install gtk4 pkg-config
Windows: – たぶんこれを(未検証)
Windows – GUI development with Rust and GTK 4
When preparing your Windows machine, you have to decide between either using the MSVC toolchain or the GNU toolchain. If in doubt, go for MSVC since that is the default on Windows. You will want to go for the GNU toolchain if you depend on libraries that can only be compiled with the GNU toolchain.
とりあえず定番の ToDo リストハローワールド
Cargo.toml
[package] name = "relm-test" version = "0.1.0" edition = "2024" [dependencies] relm4 = "0.9.1" relm4-components = "0.9.1"
main.rs
use gtk::glib::clone; use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, WidgetExt, EntryExt, EditableExt}; use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent}; struct AppModel { tasks: Vec<String>, } #[derive(Debug)] enum AppMsg { AddTask(String), RemoveTask(usize), } struct AppWidgets { task_list: gtk::ListBox, } impl SimpleComponent for AppModel { type Input = AppMsg; type Output = (); type Init = (); type Root = gtk::Window; type Widgets = AppWidgets; fn init_root() -> Self::Root { gtk::Window::builder() .title("ToDo List") .default_width(400) .default_height(300) .resizable(false) .build() } fn init( _init: Self::Init, window: Self::Root, sender: ComponentSender<Self>, ) -> relm4::ComponentParts<Self> { let model = AppModel { tasks: Vec::new() }; let vbox = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(5) .build(); let task_entry = gtk::Entry::builder() .placeholder_text("Enter a new task") .build(); let add_button = gtk::Button::with_label("Add Task"); let task_list = gtk::ListBox::new(); let scrolled_window = gtk::ScrolledWindow::builder() .vexpand(true) .child(&task_list) .build(); window.set_child(Some(&vbox)); vbox.set_margin_all(5); vbox.append(&task_entry); vbox.append(&add_button); vbox.append(&scrolled_window); let add_task = { let sender = sender.clone(); let task_entry = task_entry.clone(); move || { if let Some(text) = task_entry.text().as_str().to_owned().into() { if !text.is_empty() { sender.input(AppMsg::AddTask(text)); task_entry.set_text(""); } } } }; task_entry.connect_activate({ let add_task = add_task.clone(); move |_| { add_task(); } }); add_button.connect_clicked({ let add_task = add_task.clone(); move |_| { add_task(); } }); let widgets = AppWidgets { task_list }; ComponentParts { model, widgets } } fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) { match message { AppMsg::AddTask(task) => { self.tasks.push(task); } AppMsg::RemoveTask(index) => { if index < self.tasks.len() { self.tasks.remove(index); } } } } fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) { while let Some(child) = widgets.task_list.first_child() { widgets.task_list.remove(&child); } for (index, task) in self.tasks.iter().enumerate() { let row = gtk::Box::new(gtk::Orientation::Horizontal, 5); let label = gtk::Label::new(Some(task)); label.set_halign(gtk::Align::Start); label.set_wrap(true); label.set_wrap_mode(gtk::pango::WrapMode::Char); label.set_xalign(0.0); label.set_size_request(300, -1); let remove_button = gtk::Button::with_label("Remove"); remove_button.set_halign(gtk::Align::End); remove_button.connect_clicked(clone!( #[strong] sender, move |_| { sender.input(AppMsg::RemoveTask(index)); } )); row.append(&label); row.append(&remove_button); widgets.task_list.append(&row); } widgets.task_list.show(); } } fn main() { let app = RelmApp::new("relm4.test.todo_list"); app.run::<AppModel>(()); }
ToDo: WIndows でも試すこと。もう少しです。こんにちは。
ダークモードメモ。
fn main() { // Initialize GTK gtk::init().expect("Failed to initialize GTK"); // Enable dark mode if let Some(settings) = gtk::Settings::default() { settings.set_gtk_application_prefer_dark_theme(true); } let app = RelmApp::new("relm4.test.todo_list"); app.run::<AppModel>(()); }

以上