honeycomb_render/
gui.rs

1use bevy::prelude::*;
2use bevy::utils::HashSet;
3use bevy::window::PrimaryWindow;
4use bevy_egui::egui::{Ui, WidgetText};
5use bevy_egui::{EguiContext, EguiPlugin, EguiSet};
6use egui_dock::{DockArea, DockState, NodeIndex, Style, Tree};
7
8use crate::systems::{draw_inspected_data, draw_options};
9
10// --- plugin
11
12/// Plugin used to build the interface.
13pub struct GuiPlugin;
14
15impl Plugin for GuiPlugin {
16    fn build(&self, app: &mut App) {
17        app.add_plugins(EguiPlugin)
18            .insert_resource(UiState::default())
19            .add_systems(
20                PostUpdate,
21                show_ui
22                    .before(EguiSet::ProcessOutput)
23                    .before(TransformSystem::TransformPropagate),
24            );
25    }
26}
27
28// --- system
29
30fn show_ui(world: &mut World) {
31    let Ok(egui_context) = world
32        .query_filtered::<&mut EguiContext, With<PrimaryWindow>>()
33        .get_single(world)
34    else {
35        return;
36    };
37
38    let mut egui_context = egui_context.clone();
39
40    world.resource_scope::<UiState, _>(|world, mut ui_state| {
41        ui_state.ui(world, egui_context.get_mut());
42    });
43}
44
45// --- structs
46
47#[derive(Debug, PartialEq, Eq)]
48pub enum CustomTab {
49    Render,
50    Inspector,
51    Options,
52}
53
54#[derive(Resource)]
55pub struct UiState {
56    state: DockState<CustomTab>,
57    viewport_rect: bevy_egui::egui::Rect,
58    pub(crate) selected_entities: HashSet<Entity>,
59}
60
61impl Default for UiState {
62    fn default() -> Self {
63        let mut state = DockState::new(vec![CustomTab::Render]);
64        let tree = state.main_surface_mut();
65        let [_render, options_and_inspector] =
66            tree.split_left(NodeIndex::root(), 0.3, vec![CustomTab::Options]);
67        let [_options, _inspector] =
68            tree.split_below(options_and_inspector, 0.5, vec![CustomTab::Inspector]);
69
70        Self {
71            state,
72            viewport_rect: egui_dock::egui::Rect::NOTHING,
73            selected_entities: HashSet::new(),
74        }
75    }
76}
77
78impl UiState {
79    pub fn ui(&mut self, world: &mut World, ctx: &mut bevy_egui::egui::Context) {
80        let mut tab_viewer = TabViewer {
81            world,
82            viewport_rect: &mut self.viewport_rect,
83            selected_entities: &mut self.selected_entities,
84        };
85        DockArea::new(&mut self.state)
86            .style(Style::from_egui(ctx.style().as_ref()))
87            .show(ctx, &mut tab_viewer);
88    }
89
90    pub fn tree(&self) -> &Tree<CustomTab> {
91        self.state.main_surface()
92    }
93
94    pub fn tree_mut(&mut self) -> &mut Tree<CustomTab> {
95        self.state.main_surface_mut()
96    }
97}
98
99pub struct TabViewer<'a> {
100    world: &'a mut World,
101    selected_entities: &'a mut HashSet<Entity>,
102    viewport_rect: &'a mut bevy_egui::egui::Rect,
103}
104
105impl egui_dock::TabViewer for TabViewer<'_> {
106    type Tab = CustomTab;
107
108    fn title(&mut self, tab: &mut Self::Tab) -> WidgetText {
109        format!("{tab:?}").into()
110    }
111
112    fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) {
113        match tab {
114            CustomTab::Render => *self.viewport_rect = ui.clip_rect(),
115            CustomTab::Inspector => draw_inspected_data(ui, self.world, self.selected_entities),
116            CustomTab::Options => draw_options(ui, self.world),
117        }
118    }
119
120    fn clear_background(&self, tab: &Self::Tab) -> bool {
121        !matches!(tab, CustomTab::Render)
122    }
123}