Rustで学ぶGTK4
表示
はじめに
[編集]GTK4とRustを組み合わせたGUIプログラミングについて、基本から実践的な例まで解説します。
開発環境のセットアップ
[編集]必要なパッケージのインストール
[編集]# Debian/Ubuntuの場合 sudo apt install libgtk-4-dev build-essential sudo apt install cargo rustc
Cargo.tomlの依存関係
- Cargo.toml
[dependencies] gtk = { version = "0.6", package = "gtk4" } gio = "0.17" glib = "0.17"
基本的なアプリケーション構造
[編集]最小限のGTK4アプリケーション
[編集]use gtk::prelude::*; use gtk::{Application, ApplicationWindow}; fn main() { // アプリケーションの作成 let app = Application::builder() .application_id("com.example.FirstApp") .build(); // アプリケーションの起動時の処理 app.connect_activate(build_ui); app.run(); } fn build_ui(app: &Application) { // ウィンドウの作成 let window = ApplicationWindow::builder() .application(app) .title("My GTK App") .default_width(600) .default_height(400) .build(); window.present(); }
ウィジェットの基本
[編集]ボタンの追加
[編集]use gtk::{Button, Box}; fn build_ui(app: &Application) { let window = ApplicationWindow::builder() .application(app) .title("Button Example") .build(); let button = Button::builder() .label("Click Me!") .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); // クリックイベントの処理 button.connect_clicked(|_| { println!("Button clicked!"); }); window.set_child(Some(&button)); window.present(); }
レイアウトの作成
[編集]fn create_layout() -> Box { let box_layout = Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(6) .build(); let button1 = Button::with_label("Button 1"); let button2 = Button::with_label("Button 2"); box_layout.append(&button1); box_layout.append(&button2); box_layout }
シグナルとイベント処理
[編集]シグナルの接続
[編集]use glib::clone; fn connect_signals(button: &Button, label: &Label) { button.connect_clicked(clone!(@weak label => move |_| { label.set_text("Button was clicked!"); })); }
カスタムシグナル
[編集]use glib::subclass::Signal; use glib::signal::SignalHandlerId; #[derive(Default)] pub struct CustomWidget { count: Cell<i32>, } #[glib::object_subclass] impl ObjectSubclass for CustomWidget { const NAME: &'static str = "CustomWidget"; type Type = super::CustomWidget; type ParentType = gtk::Widget; } impl ObjectImpl for CustomWidget { fn signals() -> &'static [Signal] { static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| { vec![Signal::builder("count-changed") .param_types([i32::static_type()]) .build()] }); SIGNALS.as_ref() } }
GUIコンポーネント
[編集]エントリー(テキスト入力)
[編集]use gtk::Entry; fn create_entry() -> Entry { let entry = Entry::builder() .placeholder_text("Enter text...") .build(); entry.connect_changed(|entry| { println!("Text changed: {}", entry.text()); }); entry }
リスト表示
[編集]use gtk::{ListBox, ListBoxRow}; fn create_list() -> ListBox { let list_box = ListBox::new(); for i in 1..5 { let row = ListBoxRow::new(); let label = Label::new(Some(&format!("Row {}", i))); row.set_child(Some(&label)); list_box.append(&row); } list_box }
データバインディング
[編集]プロパティバインディング
[編集]use gtk::Expression; fn bind_properties(source: &Label, target: &Entry) { let expression = Expression::closure(closure!(|src: &Label| { src.text() })); target.bind_property("text", source, "label") .bidirectional() .build(); }
ファイル操作
[編集]ファイル選択ダイアログ
[編集]use gtk::FileChooserDialog; fn show_file_dialog(window: &ApplicationWindow) { let dialog = FileChooserDialog::builder() .title("Choose a file") .parent(window) .action(gtk::FileChooserAction::Open) .build(); dialog.add_button("Cancel", gtk::ResponseType::Cancel); dialog.add_button("Open", gtk::ResponseType::Accept); dialog.connect_response(|dialog, response| { if response == gtk::ResponseType::Accept { if let Some(file) = dialog.file() { println!("Selected file: {:?}", file.path()); } } dialog.close(); }); dialog.present(); }
スタイリング
[編集]CSSの適用
[編集]fn apply_css(widget: &impl IsA<Widget>) { let css_provider = CssProvider::new(); css_provider.load_from_data(b" button { background: linear-gradient(to bottom, #3498db, #2980b9); color: white; border-radius: 4px; padding: 8px; } "); gtk::style_context_add_provider_for_display( &Display::default().expect("Could not connect to display"), &css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, ); }
エラー処理
[編集]エラーダイアログ
[編集]use gtk::MessageDialog; fn show_error(window: &ApplicationWindow, message: &str) { let dialog = MessageDialog::builder() .modal(true) .buttons(gtk::ButtonsType::Ok) .message_type(gtk::MessageType::Error) .text(message) .parent(window) .build(); dialog.connect_response(|dialog, _| { dialog.close(); }); dialog.present(); }
非同期処理
[編集]非同期タスクの実行
[編集]use glib::MainContext; use std::thread; fn run_async_task<F>(f: F) where F: Future<Output = ()> + 'static, { let main_context = MainContext::default(); main_context.spawn_local(f); } // 使用例 run_async_task(async { // 非同期処理 thread::sleep(Duration::from_secs(2)); println!("Async task completed!"); });
ベストプラクティス
[編集]アプリケーション構造
[編集]mod ui; mod data; mod config; struct App { window: ApplicationWindow, config: Config, ui: UiBuilder, } impl App { fn new(app: &Application) -> Self { // アプリケーションの初期化 } fn build_ui(&self) { // UIの構築 } fn connect_signals(&self) { // シグナルの接続 } }
リソース管理
[編集]use gio::Resource; fn load_resources() { let resource_bytes = include_bytes!("../resources/resources.gresource"); let resource = Resource::from_data(&resource_bytes).unwrap(); gio::resources_register(&resource); }
まとめ
[編集]GTK4とRustの組み合わせは、型安全で高性能なGUIアプリケーションの開発を可能にします。このガイドで紹介した基本的な概念と実装例を基に、独自のアプリケーション開発を始めることができます。