4. レイアウトシステムとパネル・ウィンドウ構成
画面全体を分割するパネルと配置順序のルール
本格的なデスクトップアプリでは、画面の一番上にメニューバー、一番下にステータスバー、左側にナビゲーション用のサイドバー、そして残った中央にメインの作業領域を配置するのが一般的です。egui ではこれらを「パネル(Panel)」と呼びます。
| パネル名 | 配置場所と主な用途 |
|---|---|
egui::TopBottomPanel::top | 画面の最上部。メニューバーやツールバーの配置に使います。 |
egui::TopBottomPanel::bottom | 画面の最下部。ステータスバーや進捗表示の配置に使います。 |
egui::SidePanel::left / right | 画面の左端または右端。フォルダツリーや設定一覧の配置に使います。境界線をドラッグして幅を変えられます。 |
egui::CentralPanel | 上下左右のパネルを置いた後に残った中央の領域すべてを埋めます。 |
flowchart TD
Screen["ウィンドウ全体(ctx)"] --> Top["1. TopBottomPanel::top(上部メニュー)"]
Top --> Bottom["2. TopBottomPanel::bottom(下部ステータスバー)"]
Bottom --> Side["3. SidePanel::left(左サイドバー)"]
Side --> Central["4. CentralPanel(残りの中央領域すべて)"]
▸ パネル配置の鉄則:CentralPanelは必ず最後に呼ぶ
egui のパネル配置には、必ず守らなければならない重要なルールが 1 つあります。それは「CentralPanel は必ずすべてのパネルの一番最後に呼び出す」というルールです。
egui のパネルは、呼ばれた順番に画面の端から場所を切り取っていきます。CentralPanel は「残っている空きスペースをすべて使い切る」という性質を持っているため、最初に CentralPanel を呼んでしまうと画面全体が埋め尽くされ、後から SidePanel などを置くと重なってしまいます。
#![allow(unused)]
fn main() {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// 1. 最上部のパネル
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
});
});
// 2. 最下部のパネル
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
ui.label("Ready");
});
// 3. 左側のサイドパネル(初期幅 180px)
egui::SidePanel::left("left_panel")
.default_width(180.0)
.show(ctx, |ui| {
ui.heading("Menu");
ui.separator();
ui.label("Item 1");
ui.label("Item 2");
});
// 4. 最後に残りの中央領域をすべて埋める CentralPanel
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Main Content Area");
ui.label("ここがメインの作業領域になります。");
});
}
}
}
TopBottomPanel と SidePanel の第 1 引数に渡している文字列("top_panel" や "left_panel")は、パネルのサイズ変更状態などを egui が記憶するための識別 ID です。同じ画面内で重複しない名前を付けます。
横並びと縦並びのレイアウト制御
パネルの中(ui)では、何もしなければウィジェットは上から下へ「縦並び」で配置されていきます。これを横並びにしたり、右寄せにしたりする方法を見ていきましょう。
▸ ui.horizontal と ui.vertical
部品を横に並べたいときは ui.horizontal(|ui| { ... }) で囲みます。横並びの途中で再び縦並びにしたいときは ui.vertical(|ui| { ... }) を入れ子(ネスト)にできます。
#![allow(unused)]
fn main() {
ui.horizontal(|ui| {
ui.label("検索ワード:");
ui.text_edit_singleline(&mut self.query);
if ui.button("検索").clicked() {
self.search();
}
});
}
また、横幅がいっぱいになったときに自動で次の行へ折り返したい場合は、ui.horizontal_wrapped(|ui| { ... }) を使います。タグの一覧ボタンなどを並べるときに便利です。
▸ Layout構造体による右寄せや中央揃え
「画面の右端にボタンを寄せたい」「中央に文字を配置したい」という場合は、egui::Layout と ui.with_layout を組み合わせます。
#![allow(unused)]
fn main() {
// 横並びの行の中で、残りの右側部分を使って右から左へ部品を配置する
ui.horizontal(|ui| {
ui.label("左側のタイトル");
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("削除").clicked() {
// 一番右端に配置される
}
if ui.button("編集").clicked() {
// 「削除」の左隣に配置される
}
});
});
// 縦方向に進みつつ、水平方向の中央に揃える
ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
ui.heading("中央揃えのタイトル");
ui.button("中央のボタン");
});
}
空白・区切り線とグループ化コンテナ
画面の情報量が増えてきたときに、見た目を整理して読みやすくするための部品です。
#![allow(unused)]
fn main() {
// 水平の区切り線を引く(horizontal の中では垂直の区切り線になる)
ui.separator();
// 指定したピクセル数(ここでは 16.0px)だけ空白を空ける
ui.add_space(16.0);
// 枠線で囲まれたグループ領域を作る
ui.group(|ui| {
ui.label("アカウント設定");
ui.checkbox(&mut self.is_public, "プロフィールを公開する");
});
// クリックで開閉できる折りたたみ領域を作る
egui::CollapsingHeader::new("詳細オプション")
.default_open(false)
.show(ui, |ui| {
ui.label("普段は隠しておきたい細かい設定項目をここに入れます。");
ui.add(egui::Slider::new(&mut self.timeout, 1..=60).text("タイムアウト(秒)"));
});
}
グリッド配置とスクロール領域
▸ Grid(表・フォーム形式の整列)
設定画面や入力フォームを作るときに、ui.horizontal だけを並べると、ラベルの文字数の違いによって入力欄の左端がガタガタにずれてしまいます。このような場面では egui::Grid を使うと、列の幅が自動的に揃います。
#![allow(unused)]
fn main() {
egui::Grid::new("profile_grid")
.num_columns(2)
.spacing([20.0, 8.0]) // [列の間隔, 行の間隔]
.striped(true) // 1行おきに背景色を交互に変える
.show(ui, |ui| {
ui.label("氏名:");
ui.text_edit_singleline(&mut self.name);
ui.end_row(); // ここで1行目を終えて次の行へ進む
ui.label("メールアドレス:");
ui.text_edit_singleline(&mut self.email);
ui.end_row();
ui.label("年齢:");
ui.add(egui::DragValue::new(&mut self.age));
ui.end_row();
});
}
Grid では、1 行分の部品を置いたあとに必ず ui.end_row() を呼ぶことで改行されます。
▸ ScrollArea(スクロール可能な領域)
通常のパネルは、中身が増えても自動的にスクロールバーを出してくれません。長い文章やたくさんのリストを表示するときは、egui::ScrollArea で囲みます。
#![allow(unused)]
fn main() {
// 縦方向のスクロール領域(高さを最大 200px に制限する例)
egui::ScrollArea::vertical()
.max_height(200.0)
.show(ui, |ui| {
for i in 1..=50 {
ui.label(format!("リスト項目 #{i}"));
}
});
}
なお、何万件もの大量データを表示する場合は、画面に見えている行だけを計算して高速に描画する ScrollArea::vertical().show_rows(ui, row_height, total_rows, |ui, row_range| { ... }) という仮想スクロール用メソッドも用意されています。
アプリ内サブウィンドウの活用
パネルが画面に固定された領域であるのに対し、egui::Window はアプリの画面内でユーザーがマウスで自由にドラッグ移動したり、折りたたんだり、サイズを変えたりできる「サブウィンドウ(フローティングウィンドウ)」を作ります。
#![allow(unused)]
fn main() {
// self.show_settings が true のときだけ表示され、右上の「×」ボタンで閉じられるウィンドウ
egui::Window::new("環境設定")
.open(&mut self.show_settings) // ×ボタンが押されると自動で false になる
.resizable(true)
.default_size([280.0, 180.0])
.show(ctx, |ui| {
ui.label("ウィンドウをドラッグして移動できます。");
ui.checkbox(&mut self.auto_save, "自動保存を有効にする");
});
}
.open(&mut self.show_settings) に bool 変数の可変参照を渡しておくと、ウィンドウの右上に閉じるボタン(×)が自動で表示され、ユーザーがクリックすると self.show_settings が false に切り替わってウィンドウが閉じます。
次章では、アプリ全体の配色テーマや余白をカスタマイズする方法と、Painter を使って自由な 2D 図形やお絵かきキャンバスを描画する方法を学びます。