use core::any::TypeId;
use std::any::Any;
use crate::ui::ecs_utils::get_view_state;
use crate::ui::global_state;
use crate::ui::native_views::{EditorView, EditorViewContext, EditorViewUiState};
use crate::ui::native_windows::{NativeEditorWindow, NativeEditorWindowExtension};
use bevy::ecs::world::CommandQueue;
use bevy::platform::collections::HashMap;
use bevy::prelude::*;
use bevy_egui::{EguiContext, PrimaryEguiContext};
use bevy_inspector_egui::{bevy_egui, egui};
use egui_dock::egui::Color32;
use egui_notify::{Anchor, Toasts};
use super::actions::saving::SaveAction;
use super::actions::{EditorAction, PendingActions, PushQueue};
pub use super::windows::EditorWindowExtension;
use super::windows::{WindowId, Windows};
#[derive(Component)]
pub struct HasLoadedImgLoaders;
pub fn setup_ui(
mut egui_context: Query<(Entity, &mut EguiContext), Without<HasLoadedImgLoaders>>,
mut commands: Commands,
) {
for (entity, mut ctx) in &mut egui_context {
egui_extras::install_image_loaders(ctx.get_mut());
commands.entity(entity).insert(HasLoadedImgLoaders);
}
}
pub fn show_ui_system(world: &mut World) {
let Ok(egui_context) = world
.query_filtered::<&mut EguiContext, With<PrimaryEguiContext>>()
.single(world)
else {
return;
};
let mut egui_context = egui_context.clone();
let mut command_queue = CommandQueue::default();
world.resource_scope::<UiState, _>(|world, mut ui_state| {
world.resource_scope::<PendingActions, _>(|world, mut pending_actions| {
ui_state.ui(
world,
egui_context.get_mut(),
&mut pending_actions,
&mut command_queue,
)
});
});
command_queue.apply(world);
}
#[derive(Resource)]
pub struct UiState {
pub(crate) windows: Windows,
pub(crate) notifications: Toasts,
pub(crate) views: Vec<EditorViewUiState>,
pub(crate) active_view: Option<usize>,
pub(crate) buffers: Buffers,
}
impl UiState {
pub fn init(world: &mut World) {
let mut ui_state = Self {
notifications: Toasts::new()
.with_anchor(Anchor::BottomRight)
.with_default_font(egui::FontId::proportional(12.)),
views: Vec::new(),
active_view: Some(0),
windows: Windows::default(),
buffers: Buffers::default(),
};
let main_view = EditorViewUiState::animation_graphs(world, "Graph editing");
ui_state.new_native_view(main_view);
let event_tracks = EditorViewUiState::event_tracks(world, "Event tracks");
ui_state.new_native_view(event_tracks);
let ragdoll_view = EditorViewUiState::ragdoll(world, &mut ui_state.windows, "Ragdoll");
ui_state.new_native_view(ragdoll_view);
world.insert_resource(ui_state);
global_state::GlobalState::init(world);
}
pub fn new_native_view(&mut self, state: EditorViewUiState) {
self.views.push(state);
}
fn ui(
&mut self,
world: &mut World,
ctx: &mut egui::Context,
queue: &mut PendingActions,
command_queue: &mut CommandQueue,
) {
if let Some(view_action) = view_selection_bar(world, ctx, self) {
queue.actions.push(EditorAction::View(view_action));
}
if ctx.input(|i| i.modifiers.ctrl && i.key_pressed(egui::Key::S)) {
queue
.actions
.push(EditorAction::Save(SaveAction::RequestMultiple));
}
if let Some(active_view_idx) = self.active_view {
let view_state = &mut self.views[active_view_idx];
let view_context = EditorViewContext {
windows: &mut self.windows,
notifications: &mut self.notifications,
buffers: &mut self.buffers,
editor_actions: &mut queue.actions,
view_entity: view_state.entity,
command_queue,
};
view_state.ui(ctx, world, view_context);
}
self.notifications.show(ctx);
}
}
pub struct LegacyEditorWindowContext<'a> {
pub window_id: WindowId,
#[allow(dead_code)] pub notifications: &'a mut Toasts,
pub editor_actions: &'a mut PushQueue<EditorAction>,
}
impl LegacyEditorWindowContext<'_> {
pub fn window_action(&mut self, event: impl Any + Send + Sync) {
self.editor_actions.window(self.window_id, event)
}
}
#[derive(Debug)]
pub enum EguiWindow {
DynWindow(WindowId),
EntityWindow(NativeEditorWindow),
}
impl From<WindowId> for EguiWindow {
fn from(id: WindowId) -> Self {
EguiWindow::DynWindow(id)
}
}
impl From<NativeEditorWindow> for EguiWindow {
fn from(window: NativeEditorWindow) -> Self {
EguiWindow::EntityWindow(window)
}
}
impl EguiWindow {
pub fn display_name(&self, windows: &Windows) -> String {
match self {
EguiWindow::DynWindow(id) => windows.name_for_window(*id),
EguiWindow::EntityWindow(window) => window.display_name(),
}
}
}
#[derive(Debug, Clone)]
pub enum ViewAction {
Close(usize),
Select(usize),
New(String),
}
fn view_selection_bar(
world: &mut World,
ctx: &mut egui::Context,
ui_state: &UiState,
) -> Option<ViewAction> {
egui::TopBottomPanel::top("View selector")
.show(ctx, |ui| {
ui.horizontal(|ui| {
let mut action = None;
for (i, view) in ui_state.views.iter().enumerate() {
action = action.or(view_button(
ui,
world,
i,
view,
ui_state
.active_view
.as_ref()
.map(|active_view| *active_view == i)
.unwrap_or(false),
));
}
if ui.add(egui::Button::new("➕").frame(false)).clicked() {
action = Some(ViewAction::New("new".into()));
}
action
})
.inner
})
.inner
}
fn view_button(
ui: &mut egui::Ui,
world: &mut World,
index: usize,
view: &EditorViewUiState,
is_selected: bool,
) -> Option<ViewAction> {
egui::Frame::NONE
.stroke((1., egui::Color32::DARK_GRAY))
.corner_radius(egui::CornerRadius {
nw: 3,
ne: 3,
sw: 0,
se: 0,
})
.inner_margin(2.)
.outer_margin(egui::Margin {
left: 0,
right: 0,
top: 0,
bottom: 0,
})
.fill(if is_selected {
Color32::DARK_GRAY
} else {
Color32::TRANSPARENT
})
.show(ui, |ui| {
ui.horizontal(|ui| {
let name = get_view_state::<EditorView>(world, view.entity)
.map_or("<VIEW_NOT_FOUND>".into(), |v| v.name.clone());
let select_response = ui.add(egui::Button::new(name).frame(false));
let close_response = ui.add(egui::Button::new("❌").frame(false));
if select_response.clicked() {
Some(ViewAction::Select(index))
} else if close_response.clicked() {
Some(ViewAction::Close(index))
} else {
None
}
})
.inner
})
.inner
}
pub trait BufferType: Any + Send + Sync + 'static {
fn any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Any + Send + Sync + 'static> BufferType for T {
fn any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Default)]
pub struct Buffers {
by_id_and_type: HashMap<(egui::Id, TypeId), Box<dyn BufferType>>,
}
impl Buffers {
pub fn get_mut_or_insert_with<T: BufferType>(
&mut self,
id: egui::Id,
default_provider: impl FnOnce() -> T,
) -> &mut T {
let key = (id, TypeId::of::<T>());
self.by_id_and_type
.entry(key)
.or_insert(Box::new(default_provider()))
.as_mut()
.any_mut()
.downcast_mut::<T>()
.expect("There must never be a type mismatch here")
}
pub fn get_mut_or_default<T: BufferType + Default>(&mut self, id: egui::Id) -> &mut T {
self.get_mut_or_insert_with(id, || T::default())
}
pub fn clear<T: BufferType>(&mut self, id: egui::Id) {
let key = (id, TypeId::of::<T>());
self.by_id_and_type.remove(&key);
}
}