#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(800.0, 600.0)),
..Default::default()
};
eframe::run_native(
"egui_tiles example",
options,
Box::new(|cc| {
let mut app = MyApp::default();
if let Some(storage) = cc.storage {
if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) {
app = state;
}
}
Box::new(app)
}),
)
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct Pane {
nr: usize,
}
impl std::fmt::Debug for Pane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("View").field("nr", &self.nr).finish()
}
}
impl Pane {
pub fn with_nr(nr: usize) -> Self {
Self { nr }
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> egui_tiles::UiResponse {
let color = egui::epaint::Hsva::new(0.103 * self.nr as f32, 0.5, 0.5, 1.0);
ui.painter().rect_filled(ui.max_rect(), 0.0, color);
let dragged = ui
.allocate_rect(ui.max_rect(), egui::Sense::drag())
.on_hover_cursor(egui::CursorIcon::Grab)
.dragged();
if dragged {
egui_tiles::UiResponse::DragStarted
} else {
egui_tiles::UiResponse::None
}
}
}
struct TreeBehavior {
simplification_options: egui_tiles::SimplificationOptions,
tab_bar_height: f32,
gap_width: f32,
add_child_to: Option<egui_tiles::TileId>,
}
impl Default for TreeBehavior {
fn default() -> Self {
Self {
simplification_options: Default::default(),
tab_bar_height: 24.0,
gap_width: 2.0,
add_child_to: None,
}
}
}
impl TreeBehavior {
fn ui(&mut self, ui: &mut egui::Ui) {
let Self {
simplification_options,
tab_bar_height,
gap_width,
add_child_to: _,
} = self;
egui::Grid::new("behavior_ui")
.num_columns(2)
.show(ui, |ui| {
ui.label("All panes must have tabs:");
ui.checkbox(&mut simplification_options.all_panes_must_have_tabs, "");
ui.end_row();
ui.label("Join nested containers:");
ui.checkbox(
&mut simplification_options.join_nested_linear_containerss,
"",
);
ui.end_row();
ui.label("Tab bar height:");
ui.add(
egui::DragValue::new(tab_bar_height)
.clamp_range(0.0..=100.0)
.speed(1.0),
);
ui.end_row();
ui.label("Gap width:");
ui.add(
egui::DragValue::new(gap_width)
.clamp_range(0.0..=20.0)
.speed(1.0),
);
ui.end_row();
});
}
}
impl egui_tiles::Behavior<Pane> for TreeBehavior {
fn pane_ui(
&mut self,
ui: &mut egui::Ui,
_tile_id: egui_tiles::TileId,
view: &mut Pane,
) -> egui_tiles::UiResponse {
view.ui(ui)
}
fn tab_title_for_pane(&mut self, view: &Pane) -> egui::WidgetText {
format!("View {}", view.nr).into()
}
fn top_bar_rtl_ui(
&mut self,
_tiles: &egui_tiles::Tiles<Pane>,
ui: &mut egui::Ui,
tile_id: egui_tiles::TileId,
_tabs: &egui_tiles::Tabs,
) {
if ui.button("âž•").clicked() {
self.add_child_to = Some(tile_id);
}
}
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
self.tab_bar_height
}
fn gap_width(&self, _style: &egui::Style) -> f32 {
self.gap_width
}
fn simplification_options(&self) -> egui_tiles::SimplificationOptions {
self.simplification_options
}
}
#[derive(serde::Deserialize, serde::Serialize)]
struct MyApp {
tree: egui_tiles::Tree<Pane>,
#[serde(skip)]
behavior: TreeBehavior,
#[serde(skip)]
last_tree_debug: String,
}
impl Default for MyApp {
fn default() -> Self {
let mut next_view_nr = 0;
let mut gen_view = || {
let view = Pane::with_nr(next_view_nr);
next_view_nr += 1;
view
};
let mut tiles = egui_tiles::Tiles::default();
let mut tabs = vec![];
let tab_tile = {
let children = (0..7).map(|_| tiles.insert_pane(gen_view())).collect();
tiles.insert_tab_tile(children)
};
tabs.push(tab_tile);
tabs.push({
let children = (0..7).map(|_| tiles.insert_pane(gen_view())).collect();
tiles.insert_horizontal_tile(children)
});
tabs.push({
let children = (0..7).map(|_| tiles.insert_pane(gen_view())).collect();
tiles.insert_vertical_tile(children)
});
tabs.push({
let cells = (0..11).map(|_| tiles.insert_pane(gen_view())).collect();
tiles.insert_grid_tile(cells)
});
tabs.push(tiles.insert_pane(gen_view()));
let root = tiles.insert_tab_tile(tabs);
let tree = egui_tiles::Tree::new(root, tiles);
Self {
tree,
behavior: Default::default(),
last_tree_debug: Default::default(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::SidePanel::left("tree").show(ctx, |ui| {
if ui.button("Reset").clicked() {
*self = Default::default();
}
self.behavior.ui(ui);
ui.separator();
if let Some(root) = self.tree.root() {
tree_ui(ui, &mut self.behavior, &mut self.tree.tiles, root);
}
if let Some(parent) = self.behavior.add_child_to.take() {
let new_child = self.tree.tiles.insert_pane(Pane::with_nr(100));
if let Some(egui_tiles::Tile::Container(egui_tiles::Container::Tabs(tabs))) =
self.tree.tiles.get_mut(parent)
{
tabs.add_child(new_child);
tabs.set_active(new_child);
}
}
ui.separator();
ui.style_mut().wrap = Some(false);
let tree_debug = format!("{:#?}", self.tree);
ui.monospace(&tree_debug);
if self.last_tree_debug != tree_debug {
self.last_tree_debug = tree_debug;
}
});
egui::CentralPanel::default().show(ctx, |ui| {
self.tree.ui(&mut self.behavior, ui);
});
}
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, &self);
}
}
fn tree_ui(
ui: &mut egui::Ui,
behavior: &mut dyn egui_tiles::Behavior<Pane>,
tiles: &mut egui_tiles::Tiles<Pane>,
tile_id: egui_tiles::TileId,
) {
let text = format!(
"{} - {tile_id:?}",
behavior.tab_title_for_tile(tiles, tile_id).text()
);
let Some(mut tile) = tiles.remove(tile_id) else {
log::warn!("Missing tile {tile_id:?}");
return;
};
let default_open = true;
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
egui::Id::new((tile_id, "tree")),
default_open,
)
.show_header(ui, |ui| {
ui.label(text);
let mut visible = tiles.is_visible(tile_id);
ui.checkbox(&mut visible, "Visible");
tiles.set_visible(tile_id, visible);
})
.body(|ui| match &mut tile {
egui_tiles::Tile::Pane(_) => {}
egui_tiles::Tile::Container(container) => {
let mut kind = container.kind();
egui::ComboBox::from_label("Kind")
.selected_text(format!("{kind:?}"))
.show_ui(ui, |ui| {
for typ in egui_tiles::ContainerKind::ALL {
ui.selectable_value(&mut kind, typ, format!("{typ:?}"))
.clicked();
}
});
if kind != container.kind() {
container.set_kind(kind);
}
for &child in container.children() {
tree_ui(ui, behavior, tiles, child);
}
}
});
tiles.insert(tile_id, tile);
}