2. 開発環境の構築と最初のeframeアプリケーション
プロジェクトの作成と依存クレートの追加
手元のパソコンに Rust の開発環境(rustup と cargo)が入っていれば、すぐに egui の開発を始められます。まずはターミナル(Windows なら PowerShell やコマンドプロンプト)を開き、新しいプロジェクトを作成します。
cargo new egui_hello
cd egui_hello
作成されたディレクトリの中にある Cargo.toml を開き、[dependencies] セクションに eframe を追加します。本書では安定して広く使われている 0.29 系をベースに解説します。
[package]
name = "egui_hello"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = "0.29"
eframe を追加すると、その内部で自動的に同じバージョンの egui クレートも読み込まれます。ソースコードの中では use eframe::egui; と書くことで、egui のすべての機能にアクセスできます。
最小構成のHello Worldアプリ
src/main.rs を以下のコードに書き換えて、最初のウィンドウを表示してみましょう。
use eframe::egui;
fn main() -> eframe::Result<()> {
// ウィンドウの初期サイズなどのオプションを設定する
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([400.0, 250.0]),
..Default::default()
};
// アプリケーションを起動する
eframe::run_native(
"My First egui App",
options,
Box::new(|_cc| Ok(Box::<MyApp>::default())),
)
}
// アプリの状態(データ)を保持する構造体
struct MyApp {
name: String,
age: u32,
}
// 初期値を設定する Default トレイトの実装
impl Default for MyApp {
fn default() -> Self {
Self {
name: "Taro".to_owned(),
age: 20,
}
}
}
// eframe::App トレイトを実装して画面の描画処理を定義する
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Hello, egui!");
ui.horizontal(|ui| {
ui.label("Your name:");
ui.text_edit_singleline(&mut self.name);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Increment age").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
});
}
}
ファイルを保存したら、ターミナルで cargo run を実行します。初回は関連ライブラリのダウンロードとコンパイルが行われるため 1〜2 分ほどかかりますが、2 回目以降は変更した部分だけが数秒でビルドされます。
実行すると幅 400 ピクセル、高さ 250 ピクセルのウィンドウが立ち上がり、名前の入力欄と年齢のスライダー、そしてボタンが表示されます。
eframeのライフサイクルとupdateメソッド
先ほどのコードがどのような流れで動いているのかを順番に確認します。
sequenceDiagram
participant Main as main関数
participant Eframe as eframe::run_native
participant App as MyApp構造体
Main->>Eframe: 1. ウィンドウタイトル・NativeOptions・生成クロージャを渡す
Eframe->>App: 2. 起動時に1回だけ MyApp::default()(または new)を呼ぶ
loop アプリが閉じられるまで繰り返し
Eframe->>App: 3. update(&mut self, ctx, frame) を呼び出す
App->>Eframe: 4. CentralPanel 等を通じて UI 部品を配置・入力判定
end
▸ NativeOptions と ViewportBuilder
main 関数で作っている eframe::NativeOptions は、OS のウィンドウに関する設定をまとめた構造体です。ウィンドウの初期サイズや最小サイズ、アイコン、最前面表示(Always on top)などは、viewport フィールドに渡す egui::ViewportBuilder のメソッドチェーンで設定します。
#![allow(unused)]
fn main() {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([600.0, 400.0]) // 初期サイズ(幅, 高さ)
.with_min_inner_size([300.0, 200.0]) // 最小サイズ
.with_resizable(true), // リサイズ許可
..Default::default()
};
}
▸ run_native の第3引数(アプリ生成クロージャ)
eframe::run_native の第 3 引数には、ウィンドウの準備が整った直後に 1 回だけ呼ばれるクロージャ(無名関数)を渡します。引数の _cc は eframe::CreationContext 型で、フォントの初期登録や前回保存したデータの読み込みに使います。
▸ update メソッドと ctx / ui の違い
eframe::App トレイトで必ず実装するのが update メソッドです。ここで登場する ctx(&egui::Context)と、パネルの中で登場する ui(&mut egui::Ui)は、egui において最も重要な 2 つの変数です。
| 変数名と型 | 役割の違い |
|---|---|
ctx: &egui::Context | アプリ全体を管理する司令塔です。画面全体のテーマ変更、フォント設定、パネルやサブウィンドウの配置、キーボードやマウスの全体入力状態の取得に使います。 |
ui: &mut egui::Ui | パネルやウィンドウなど「特定の領域の中」を表す変数です。ボタン(ui.button)やラベル(ui.label)などの具体的なウィジェットは、すべてこの ui に対して追加します。 |
つまり、最初に ctx を使って CentralPanel(中央パネル)などの大きな箱を作り、その箱が渡してくれる ui を使って中にボタンや文字を並べていく、というのが egui の基本パターンです。
日本語フォントの設定と文字化け対策
先ほどの Hello World アプリの名前入力欄に「太郎」と日本語を入力したり、ラベルの文字列を日本語に変えたりすると、文字が「□□」のような四角い記号(通称:豆腐)になってしまいます。
これは、egui のファイルサイズを小さく保つために、標準では英語(ラテン文字)と一部の記号しか含まれない軽量フォントが内蔵されているためです。日本語を表示するには、アプリ起動時に日本語フォント(.ttf や .otf)を読み込んで登録します。
▸ 推奨される方法:フォントファイルをプロジェクトに含める
最も確実でどの OS でも同じ見た目になる方法は、NotoSansJP-Regular.ttf(Google Fonts 等から無料で入手可能)などの日本語フォントファイルをプロジェクト内の assets/ フォルダなどに置き、include_bytes! マクロで実行ファイルに埋め込む方法です。
一方、手元ですぐに試したい場合は、パソコンの OS に最初から入っている標準の日本語フォントファイルを読み込むこともできます。以下のコードは、アプリ起動時のコンストラクタ MyApp::new(cc) の中で日本語フォントを追加する完全な実装例です。
use eframe::egui;
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([450.0, 300.0]),
..Default::default()
};
eframe::run_native(
"日本語表示サンプル",
options,
// CreationContext (cc) を MyApp::new に渡して初期化する
Box::new(|cc| Ok(Box::new(MyApp::new(cc)))),
)
}
struct MyApp {
message: String,
}
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
// 起動時に1回だけフォント設定を行う
setup_custom_fonts(&cc.egui_ctx);
Self {
message: "こんにちは、eguiの世界!".to_owned(),
}
}
}
fn setup_custom_fonts(ctx: &egui::Context) {
// 1. デフォルトのフォント定義を取得する
let mut fonts = egui::FontDefinitions::default();
// 2. 日本語フォントデータを読み込む
// ※ 配布用アプリでは FontData::from_static(include_bytes!("../assets/NotoSansJP-Regular.ttf")) を推奨
// ここでは手軽な動作確認用として Windows 標準のメイリオ(または游ゴシック)を読み込む例を示す
if let Ok(font_bytes) = std::fs::read("C:/Windows/Fonts/meiryo.ttc") {
fonts.font_data.insert(
"japanese_font".to_owned(),
egui::FontData::from_owned(font_bytes).into(),
);
// 3. プロポーショナル(通常文字)の一番優先される先頭(インデックス0)に追加する
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "japanese_font".to_owned());
// 4. モノスペース(等幅文字)の末尾(フォールバック)にも追加する
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("japanese_font".to_owned());
// 5. コンテキストに新しいフォント定義を反映する
ctx.set_fonts(fonts);
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("日本語フォントの表示テスト");
ui.separator();
ui.label("日本語が文字化けせずにきれいに表示されます。");
ui.text_edit_singleline(&mut self.message);
ui.label(format!("入力内容: {}", self.message));
});
}
}
setup_custom_fonts 関数は、毎フレーム呼ばれる update の中ではなく、起動時に 1 回だけ呼ばれる MyApp::new の中で実行するのが鉄則です。毎フレームフォントを読み直すと動作が極端に重くなってしまうためです。
なお、macOS でシステムフォントを試す場合は /System/Library/Fonts/ヒラギノ角ゴシック W3.ttc、Ubuntu などの Linux では /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc などを指定できます。他人に配布するアプリを作る際は、プロジェクト内に .ttf ファイルを配置して egui::FontData::from_static(include_bytes!("...")) を使うと、どの OS でも確実に動作します。
次章では、画面を彩るさまざまな基本ウィジェット(ボタン、入力欄、スライダー、選択リストなど)の詳しい使い方を学びます。