kitgit

tirbofish/dropbear · commit

34109573e20503d598b6a12613760425d9b01cd3

feature: docks implemented as a trait instead of hardcoded, potential plugin possibility

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-21 10:00

view full diff

diff --git a/crates/eucalyptus-editor/Cargo.toml b/crates/eucalyptus-editor/Cargo.toml
index 75971c9..4a57944 100644
--- a/crates/eucalyptus-editor/Cargo.toml
+++ b/crates/eucalyptus-editor/Cargo.toml
@@ -55,6 +55,7 @@ rfd.workspace = true
 semver.workspace = true
 serde.workspace = true
 bitflags.workspace = true
+downcast-rs.workspace = true
 
 [target.'cfg(unix)'.dependencies]
 daemonize = "0.5.0"
diff --git a/crates/eucalyptus-editor/src/editor/asset_viewer.rs b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
index 1ed454b..dc25844 100644
--- a/crates/eucalyptus-editor/src/editor/asset_viewer.rs
+++ b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
@@ -1,5 +1,4 @@
 use dropbear_engine::asset::ASSET_REGISTRY;
-use dropbear_engine::entity::MeshRenderer;
 use dropbear_engine::model::Model;
 use dropbear_engine::texture::Texture;
 use dropbear_engine::{graphics::NO_TEXTURE, utils::ResourceReference};
@@ -13,8 +12,8 @@ use std::{cmp::Ordering, fs, hash::DefaultHasher, io, path::Path};
 
 use crate::editor::{
     AssetDivision, AssetNodeInfo, AssetNodeKind, ComponentNodeSelection, DraggedAsset,
-    EditorTabViewer, FsEntry, ResourceDivision, SceneDivision, ScriptDivision, Signal,
-    StaticallyKept, TABS_GLOBAL,
+    EditorTabDock, EditorTabDockDescriptor, EditorTabViewer, FsEntry, ResourceDivision,
+    SceneDivision, ScriptDivision, Signal, StaticallyKept, TABS_GLOBAL,
 };
 use eucalyptus_core::component::DRAGGED_ASSET_ID;
 
@@ -882,10 +881,10 @@ impl<'a> EditorTabViewer<'a> {
         )
     }
 
-    fn with_icon_kind<'ui>(
-        builder: NodeBuilder<'ui, u64>,
+    fn with_icon_kind(
+        builder: NodeBuilder<u64>,
         kind: AssetNodeKind,
-    ) -> NodeBuilder<'ui, u64> {
+    ) -> NodeBuilder<u64> {
         builder.icon(move |ui| {
             egui_extras::install_image_loaders(ui.ctx());
             Self::draw_asset_icon(ui, kind)
@@ -1241,7 +1240,7 @@ impl<'a> EditorTabViewer<'a> {
             .get_texture_handle_by_reference(&reference)
             .is_some()
         {
-            info!("Texture already loaded: {}", label);
+            eucalyptus_core::info!("Texture already loaded: {}", label);
             return;
         }
 
@@ -1252,7 +1251,7 @@ impl<'a> EditorTabViewer<'a> {
             let texture = match Texture::from_file(graphics.clone(), &path, Some(&label)).await {
                 Ok(v) => v,
                 Err(e) => {
-                    warn!("Unable to load texture {}: {}", reference, e);
+                    eucalyptus_core::warn!("Unable to load texture {}: {}", reference, e);
                     return Err(e);
                 }
             };
@@ -1299,13 +1298,14 @@ impl<'a> EditorTabViewer<'a> {
                 .first()
                 .and_then(|node_id| cfg.component_selection(*node_id))
         });
-
+        
         if let Some(selection) = selection {
             self.inspect_component_selection(cfg, selection);
             if let Some(target_entity) = Self::entity_from_node_id(drag.target) {
-                log::info!(
-                    "Component id #{} ready to drop onto entity {:?}",
-                    selection.component_type_id,
+                let v = if crate::features::is_enabled(crate::features::ShowComponentTypeIDInEditor) { format!(" id #{}", selection.component_type_id) } else { "".to_string() };
+                eucalyptus_core::info!(
+                    "Component{} ready to drop onto entity {:?}",
+                    v,
                     target_entity
                 );
             }
@@ -1366,3 +1366,17 @@ impl<'a> EditorTabViewer<'a> {
         }
     }
 }
+
+pub struct AssetViewerDock;
+
+impl EditorTabDock for AssetViewerDock {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            title: "Asset Viewer".to_string(),
+        }
+    }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui) {
+        viewer.show_asset_viewer(ui);
+    }
+}
diff --git a/crates/eucalyptus-editor/src/editor/build_console.rs b/crates/eucalyptus-editor/src/editor/build_console.rs
index 2c87e5b..ab423df 100644
--- a/crates/eucalyptus-editor/src/editor/build_console.rs
+++ b/crates/eucalyptus-editor/src/editor/build_console.rs
@@ -3,7 +3,7 @@ use std::path::PathBuf;
 use egui::{Margin, RichText};
 
 use crate::editor::{
-    EditorTabViewer,
+    EditorTabDock, EditorTabDockDescriptor, EditorTabViewer,
     console_error::{ConsoleItem, ErrorLevel},
 };
 
@@ -142,3 +142,17 @@ impl<'a> EditorTabViewer<'a> {
             });
     }
 }
+
+pub struct BuildConsoleDock;
+
+impl EditorTabDock for BuildConsoleDock {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            title: "Build Output".to_string(),
+        }
+    }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui) {
+        viewer.build_console(ui);
+    }
+}
diff --git a/crates/eucalyptus-editor/src/editor/dock.rs b/crates/eucalyptus-editor/src/editor/dock.rs
index b806ed4..e52b57e 100644
--- a/crates/eucalyptus-editor/src/editor/dock.rs
+++ b/crates/eucalyptus-editor/src/editor/dock.rs
@@ -1,7 +1,7 @@
 use super::*;
 use crate::editor::ViewportMode;
-use std::{collections::HashMap, hash::Hash, path::PathBuf, sync::LazyLock};
-
+use std::hash::Hasher;
+use std::{any::TypeId, collections::HashMap, hash::Hash, path::PathBuf, sync::LazyLock};
 use crate::editor::console::EucalyptusConsole;
 use crate::plugin::PluginRegistry;
 use dropbear_engine::entity::{EntityTransform, Transform};
@@ -28,13 +28,14 @@ pub struct EditorTabViewer<'a> {
     pub active_camera: &'a mut Arc<Mutex<Option<Entity>>>,
     pub plugin_registry: &'a mut PluginRegistry,
     pub component_registry: &'a ComponentRegistry,
+    pub tab_registry: &'a EditorTabRegistry,
     pub build_logs: &'a mut Vec<String>,
     pub eucalyptus_console: &'a mut EucalyptusConsole,
-
-    // "wah wah its unsafe, its using raw pointers" shut the fuck up if it breaks i will know
-    pub editor: *mut Editor,
+    pub current_scene_name: &'a mut Option<String>,
 }
 
+pub type EditorTabId = u64;
+
 #[derive(Clone, Debug)]
 pub struct DraggedAsset {
     pub name: String,
@@ -89,7 +90,7 @@ pub static TABS_GLOBAL: LazyLock<Mutex<StaticallyKept>> =
 pub struct StaticallyKept {
     show_context_menu: bool,
     context_menu_pos: egui::Pos2,
-    context_menu_tab: Option<EditorTab>,
+    context_menu_tab: Option<EditorTabId>,
     pub(crate) is_focused: bool,
     pub(crate) old_pos: Transform,
 
@@ -144,25 +145,105 @@ impl StaticallyKept {
     }
 }
 
+pub struct EditorTabRegistry {
+    pub title_to_id: HashMap<String, EditorTabId>,
+    pub descriptors: HashMap<EditorTabId, EditorTabDockDescriptor>,
+    pub displayers: HashMap<EditorTabId, EditorTabDisplayer>,
+}
+
+pub type EditorTabDisplayer = Box<
+    dyn for<'a> Fn(&mut EditorTabViewer<'a>, &mut egui::Ui) + Send + Sync + 'static,
+>;
+
+impl EditorTabRegistry {
+    pub fn new() -> Self {
+        Self {
+            title_to_id: HashMap::new(),
+            descriptors: HashMap::new(),
+            displayers: HashMap::new(),
+        }
+    }
+
+    pub fn register<D>(&mut self)
+    where
+        D: EditorTabDock + Send + Sync + 'static,
+    {
+        let desc = D::desc();
+        let id = Self::id_for_type::<D>();
+
+        self.title_to_id.insert(desc.title.to_string(), id);
+        self.descriptors.insert(id, desc);
+        self.displayers
+            .insert(id, Box::new(|viewer, ui| D::display(viewer, ui)));
+    }
+
+    pub fn get_descriptor_by_title(&self, title: &str) -> Option<&EditorTabDockDescriptor> {
+        self.title_to_id
+            .get(title)
+            .and_then(|tab_id| self.descriptors.get(tab_id))
+    }
+
+    pub fn id_for_title(&self, title: &str) -> Option<EditorTabId> {
+        self.title_to_id.get(title).copied()
+    }
+
+    pub fn display_by_id(
+        &self,
+        tab_id: EditorTabId,
+        viewer: &mut EditorTabViewer<'_>,
+        ui: &mut egui::Ui,
+    ) -> bool {
+        let Some(displayer) = self.displayers.get(&tab_id) else {
+            return false;
+        };
+        displayer(viewer, ui);
+        true
+    }
+
+    fn id_for_type<T: 'static>() -> EditorTabId {
+        Self::numeric_id(TypeId::of::<T>())
+    }
+
+    fn numeric_id(type_id: TypeId) -> EditorTabId {
+        let mut hasher = std::collections::hash_map::DefaultHasher::new();
+        type_id.hash(&mut hasher);
+        Self::normalize_id(hasher.finish())
+    }
+
+    fn normalize_id(id: u64) -> u64 {
+        if id == 0 {
+            1
+        } else {
+            id
+        }
+    }
+}
+
+impl Default for EditorTabRegistry {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+
+pub struct EditorTabDockDescriptor {
+    pub title: String,
+}
+
+pub trait EditorTabDock {
+    fn desc() -> EditorTabDockDescriptor;
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui);
+}
+
 impl<'a> TabViewer for EditorTabViewer<'a> {
-    type Tab = EditorTab;
+    type Tab = EditorTabId;
 
     fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
-        match tab {
-            EditorTab::Viewport => "Viewport".into(),
-            EditorTab::ModelEntityList => "Model/Entity List".into(),
-            EditorTab::AssetViewer => "Asset Viewer".into(),
-            EditorTab::ResourceInspector => "Resource Inspector".into(),
-            EditorTab::Plugin(dock_index) => {
-                if let Some((_, plugin)) = self.plugin_registry.plugins.get_index_mut(*dock_index) {
-                    plugin.display_name().into()
-                } else {
-                    "Unknown Plugin Name".into()
-                }
-            }
-            EditorTab::ErrorConsole => "Build Output".into(),
-            EditorTab::Console => "Console".into(),
-        }
+        self.tab_registry
+            .descriptors
+            .get(tab)
+            .map(|desc| desc.title.clone().into())
+            .unwrap_or_else(|| "Unknown Tab".into())
     }
 
     fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
@@ -178,113 +259,102 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
             }
         });
 
-        match tab {
-            EditorTab::Viewport => {
-                self.viewport_tab(ui);
-            }
-            EditorTab::ModelEntityList => {
-                self.entity_list(ui);
-            }
-            EditorTab::AssetViewer => {
-                self.show_asset_viewer(ui);
-            }
-            EditorTab::ResourceInspector => {
-                self.resource_inspector(ui);
+        if !self.tab_registry.display_by_id(*tab, self, ui) {
+            ui.label("Unknown dock");
+        }
+    }
+}
+
+impl<'a> EditorTabViewer<'a> {
+    pub(crate) fn console_tab(&mut self, ui: &mut egui::Ui) {
+        ui.separator();
+
+        ui.horizontal(|ui| {
+            if ui.button("Clear").clicked() {
+                self.eucalyptus_console.history.clear();
             }
-            EditorTab::Plugin(dock_info) => {
-                if self.editor.is_null() {
-                    panic!("Editor pointer is null, unexpected behaviour");
+
+            ui.separator();
+
+            ui.checkbox(&mut self.eucalyptus_console.show_info, "Info");
+            ui.checkbox(&mut self.eucalyptus_console.show_warning, "Warning");
+            ui.checkbox(&mut self.eucalyptus_console.show_error, "Error");
+            ui.checkbox(&mut self.eucalyptus_console.show_debug, "Debug");
+            ui.checkbox(&mut self.eucalyptus_console.show_trace, "Trace");
+
+            ui.separator();
+
+            ui.checkbox(&mut self.eucalyptus_console.auto_scroll, "Auto-scroll");
+
+            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+                ui.label(format!("Logs: {}", self.eucalyptus_console.history.len()));
+            });
+        });
+
+        ui.separator();
+
+        let _ = self.eucalyptus_console.take();
+
+        let scroll = egui::ScrollArea::vertical()
+            .auto_shrink([false, false])
+            .stick_to_bottom(self.eucalyptus_console.auto_scroll);
+
+        scroll.show(ui, |ui| {
+            for log in &self.eucalyptus_console.history {
+                let is_error = log.contains("[ERROR]") || log.contains("[FATAL]");
+                let is_warn = log.contains("[WARN]");
+                let is_debug = log.contains("[DEBUG]");
+                let is_trace = log.contains("[TRACE]");
+                let is_info = !is_error && !is_warn && !is_debug && !is_trace;
+
+                if is_error && !self.eucalyptus_console.show_error {
+                    continue;
                 }
-                let editor = unsafe { &mut *self.editor };
-                if let Some((_, plugin)) = self.plugin_registry.plugins.get_index_mut(*dock_info) {
-                    plugin.ui(ui, editor);
-                } else {
-                    ui.colored_label(
-                        egui::Color32::RED,
-                        format!("Plugin at index '{}' not found", *dock_info),
-                    );
+                if is_warn && !self.eucalyptus_console.show_warning {
+                    continue;
                 }
+                if is_debug && !self.eucalyptus_console.show_debug {
+                    continue;
+                }
+                if is_trace && !self.eucalyptus_console.show_trace {
+                    continue;
+                }
+                if is_info && !self.eucalyptus_console.show_info {
+                    continue;
+                }
+
+                let color = if is_error {
+                    egui::Color32::from_rgb(255, 100, 100)
+                } else if is_warn {
+                    egui::Color32::from_rgb(255, 200, 50)
+                } else if is_debug {
+                    egui::Color32::from_rgb(100, 200, 255)
+                } else if is_trace {
+                    egui::Color32::from_rgb(150, 150, 150)
+                } else {
+                    egui::Color32::LIGHT_GRAY
+                };
+
+                ui.add(egui::Label::new(
+                    egui::RichText::new(log).color(color).monospace(),
+                ));
             }
-            EditorTab::ErrorConsole => {
-                self.build_console(ui);
-            }
-            EditorTab::Console => {
-                ui.separator();
-
-                ui.horizontal(|ui| {
-                    if ui.button("Clear").clicked() {
-                        self.eucalyptus_console.history.clear();
-                    }
-
-                    ui.separator();
-
-                    ui.checkbox(&mut self.eucalyptus_console.show_info, "Info");
-                    ui.checkbox(&mut self.eucalyptus_console.show_warning, "Warning");
-                    ui.checkbox(&mut self.eucalyptus_console.show_error, "Error");
-                    ui.checkbox(&mut self.eucalyptus_console.show_debug, "Debug");
-                    ui.checkbox(&mut self.eucalyptus_console.show_trace, "Trace");
-
-                    ui.separator();
-
-                    ui.checkbox(&mut self.eucalyptus_console.auto_scroll, "Auto-scroll");
-
-                    ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
-                        ui.label(format!("Logs: {}", self.eucalyptus_console.history.len()));
-                    });
-                });
-
-                ui.separator();
-
-                let _ = self.eucalyptus_console.take();
-
-                let scroll = egui::ScrollArea::vertical()
-                    .auto_shrink([false, false])
-                    .stick_to_bottom(self.eucalyptus_console.auto_scroll);
-
-                scroll.show(ui, |ui| {
-                    for log in &self.eucalyptus_console.history {
-                        let is_error = log.contains("[ERROR]") || log.contains("[FATAL]");
-                        let is_warn = log.contains("[WARN]");
-                        let is_debug = log.contains("[DEBUG]");
-                        let is_trace = log.contains("[TRACE]");
-                        let is_info = !is_error && !is_warn && !is_debug && !is_trace;
-
-                        if is_error && !self.eucalyptus_console.show_error {
-                            continue;
-                        }
-                        if is_warn && !self.eucalyptus_console.show_warning {
-                            continue;
-                        }
-                        if is_debug && !self.eucalyptus_console.show_debug {
-                            continue;
-                        }
-                        if is_trace && !self.eucalyptus_console.show_trace {
-                            continue;
-                        }
-                        if is_info && !self.eucalyptus_console.show_info {
-                            continue;
-                        }
-
-                        let color = if is_error {
-                            egui::Color32::from_rgb(255, 100, 100)
-                        } else if is_warn {
-                            egui::Color32::from_rgb(255, 200, 50)
-                        } else if is_debug {
-                            egui::Color32::from_rgb(100, 200, 255)
-                        } else if is_trace {
-                            egui::Color32::from_rgb(150, 150, 150)
-                        } else {
-                            egui::Color32::LIGHT_GRAY
-                        };
-
-                        ui.add(egui::Label::new(
-                            egui::RichText::new(log).color(color).monospace(),
-                        ));
-                    }
-                });
-            }
+        });
+    }
+}
+
+pub struct ConsoleDock;
+
+impl EditorTabDock for ConsoleDock {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            title: "Console".to_string(),
         }
     }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui) {
+        viewer.console_tab(ui);
+    }
 }
 
 #[derive(Clone)]
diff --git a/crates/eucalyptus-editor/src/editor/entity_list.rs b/crates/eucalyptus-editor/src/editor/entity_list.rs
index 4479cc5..9466d4d 100644
--- a/crates/eucalyptus-editor/src/editor/entity_list.rs
+++ b/crates/eucalyptus-editor/src/editor/entity_list.rs
@@ -7,7 +7,7 @@ use eucalyptus_core::{
 };
 use hecs::{Entity, World};
 
-use crate::editor::{Editor, EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL};
+use crate::editor::{Editor, EditorTabDock, EditorTabDockDescriptor, EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL};
 
 impl<'a> EditorTabViewer<'a> {
     pub(crate) fn entity_list(&mut self, ui: &mut egui::Ui) {
@@ -204,3 +204,17 @@ impl<'a> EditorTabViewer<'a> {
         }
     }
 }
+
+pub struct EntityListDock;
+
+impl EditorTabDock for EntityListDock {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            title: "Model/Entity List".to_string(),
+        }
+    }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui) {
+        viewer.entity_list(ui);
+    }
+}
diff --git a/crates/eucalyptus-editor/src/editor/input.rs b/crates/eucalyptus-editor/src/editor/input.rs
index 8320c0e..5550da3 100644
--- a/crates/eucalyptus-editor/src/editor/input.rs
+++ b/crates/eucalyptus-editor/src/editor/input.rs
@@ -76,7 +76,10 @@ impl Keyboard for Editor {
             KeyCode::Delete => {
                 if !is_playing {
                     if let Some((_, tab)) = self.dock_state.find_active_focused()
-                        && matches!(tab, EditorTab::ModelEntityList)
+                        && self
+                            .tab_registry
+                            .id_for_title("Model/Entity List")
+                            .map_or(false, |id| *tab == id)
                     {
                         if self.selected_entity.is_some() {
                             self.signal = Signal::Delete;
@@ -161,7 +164,10 @@ impl Keyboard for Editor {
             KeyCode::KeyC => {
                 if ctrl_pressed && !is_playing {
                     if let Some((_, tab)) = self.dock_state.find_active_focused()
-                        && matches!(tab, EditorTab::ModelEntityList)
+                        && self
+                            .tab_registry
+                            .id_for_title("Model/Entity List")
+                            .map_or(false, |id| *tab == id)
                     {
                         if let Some(entity) = &self.selected_entity {
                             let Ok(label) = self.world.get::<&Label>(*entity) else {
diff --git a/crates/eucalyptus-editor/src/editor/mod.rs b/crates/eucalyptus-editor/src/editor/mod.rs
index 26853a0..f671855 100644
--- a/crates/eucalyptus-editor/src/editor/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/mod.rs
@@ -52,7 +52,7 @@ use eucalyptus_core::{
     input::InputState,
     scripting::BuildStatus,
     states,
-    states::{EditorTab, PROJECT, SCENES, WorldLoadingStatus},
+    states::{PROJECT, SCENES, WorldLoadingStatus},
     success,
     utils::ViewportMode,
     warn,
@@ -75,7 +75,7 @@ pub struct Editor {
     pub scene_command: SceneCommand,
     pub world: Box<World>,
     pub physics_state: PhysicsState,
-    pub dock_state: DockState<EditorTab>,
+    pub dock_state: DockState<EditorTabId>,
     pub texture_id: Option<egui::TextureId>,
     pub size: Extent3d,
     pub instance_buffer_cache: HashMap<u64, ResizableBuffer<InstanceRaw>>,
@@ -120,6 +120,7 @@ pub struct Editor {
     pub gizmo_mode: EnumSet<GizmoMode>,
     pub gizmo_orientation: GizmoOrientation,
     pub console: EucalyptusConsole,
+    pub tab_registry: EditorTabRegistry,
 
     // might as well save some memory if its not required...
     // #[allow(unused)] // unused to allow for JVM to startup
@@ -155,7 +156,7 @@ pub struct Editor {
     // plugins
     pub plugin_registry: PluginRegistry,
 
-    pub dock_state_shared: Option<Arc<Mutex<DockState<EditorTab>>>>,
+    pub dock_state_shared: Option<Arc<Mutex<DockState<EditorTabId>>>>,
 
     // scene creation
     open_new_scene_window: bool,
@@ -180,15 +181,30 @@ pub struct Editor {
 
 impl Editor {
     pub fn new() -> anyhow::Result<Self> {
-        let tabs = vec![EditorTab::Viewport];
+        let mut tab_registry = EditorTabRegistry::new();
+        crate::register_docks(&mut tab_registry);
+
+        let viewport_tab = tab_registry
+            .id_for_title("Viewport")
+            .expect("Viewport tab must be registered");
+        let entity_list_tab = tab_registry
+            .id_for_title("Model/Entity List")
+            .expect("Entity list tab must be registered");
+        let resource_tab = tab_registry
+            .id_for_title("Resource Inspector")
+            .expect("Resource inspector tab must be registered");
+        let asset_tab = tab_registry
+            .id_for_title("Asset Viewer")
+            .expect("Asset viewer tab must be registered");
+
+        let tabs = vec![viewport_tab];
         let mut dock_state = DockState::new(tabs);
 
         let surface = dock_state.main_surface_mut();
         let [_old, right] =
-            surface.split_right(NodeIndex::root(), 0.25, vec![EditorTab::ModelEntityList]);
-        let [_old, _] =
-            surface.split_left(NodeIndex::root(), 0.20, vec![EditorTab::ResourceInspector]);
-        let [_old, _] = surface.split_below(right, 0.5, vec![EditorTab::AssetViewer]);
+            surface.split_right(NodeIndex::root(), 0.25, vec![entity_list_tab]);
+        let [_old, _] = surface.split_left(NodeIndex::root(), 0.20, vec![resource_tab]);
+        let [_old, _] = surface.split_below(right, 0.5, vec![asset_tab]);
 
         eucalyptus_core::utils::start_deadlock_detector();
 
@@ -244,6 +260,7 @@ impl Editor {
             gizmo_mode: EnumSet::empty(),
             gizmo_orientation: GizmoOrientation::Global,
             console: EucalyptusConsole::new(None),
+            tab_registry,
             input_state: Box::new(InputState::new()),
             light_cube_pipeline: None,
             active_camera: Arc::new(Mutex::new(None)),
@@ -555,7 +572,7 @@ impl Editor {
         world_sender: Option<oneshot::Sender<hecs::World>>,
         active_camera: Arc<Mutex<Option<hecs::Entity>>>,
         project_path: Arc<Mutex<Option<PathBuf>>>,
-        dock_state: Arc<Mutex<DockState<EditorTab>>>,
+        dock_state: Arc<Mutex<DockState<EditorTabId>>>,
         component_registry: Arc<ComponentRegistry>,
     ) -> anyhow::Result<()> {
         {
@@ -1057,39 +1074,9 @@ impl Editor {
                 });
 
                 ui.menu_button("Window", |ui_window| {
-                    if ui_window.button("Open Asset Viewer").clicked() {
-                        self.dock_state.push_to_focused_leaf(EditorTab::AssetViewer);
-                    }
-                    if ui_window.button("Open Resource Inspector").clicked() {
-                        self.dock_state
-                            .push_to_focused_leaf(EditorTab::ResourceInspector);
-                    }
-                    if ui_window.button("Open Entity List").clicked() {
-                        self.dock_state
-                            .push_to_focused_leaf(EditorTab::ModelEntityList);
-                    }
-                    if ui_window.button("Open Viewport").clicked() {
-                        self.dock_state.push_to_focused_leaf(EditorTab::Viewport);
-                    }
-                    if ui_window.button("Open Error Console").clicked() {
-                        self.dock_state
-                            .push_to_focused_leaf(EditorTab::ErrorConsole);
-                    }
-                    if ui_window.button("Open Debug Console").clicked() {
-                        self.dock_state.push_to_focused_leaf(EditorTab::Console);
-                    }
-                    if self.plugin_registry.plugins.len() == 0 {
-                        ui_window.label(
-                            egui::RichText::new("No plugins")
-                                .color(ui_window.visuals().weak_text_color()),
-                        );
-                    }
-                    for (i, (_, plugin)) in self.plugin_registry.plugins.iter().enumerate() {
-                        if ui_window
-                            .button(format!("Open {}", plugin.display_name()))
-                            .clicked()
-                        {
-                            self.dock_state.push_to_focused_leaf(EditorTab::Plugin(i));
+                    for (k, v) in self.tab_registry.descriptors.iter() {
+                        if ui_window.button(v.title.as_str()).clicked() {
+                            self.dock_state.push_to_focused_leaf(*k);
                         }
                     }
                 });
@@ -1239,8 +1226,6 @@ impl Editor {
                 });
             });
 
-        let editor_ptr = self as *mut Editor;
-
         let Some(view) = self.texture_id else {
             egui::CentralPanel::default().show(ctx, |ui| {
                 ui.centered_and_justified(|ui| {
@@ -1270,10 +1255,11 @@ impl Editor {
                         gizmo_orientation: &mut self.gizmo_orientation,
                         editor_mode: &mut self.editor_state,
                         plugin_registry: &mut self.plugin_registry,
-                        editor: editor_ptr,
                         build_logs: &mut self.build_logs,
                         component_registry: &self.component_registry,
+                        tab_registry: &self.tab_registry,
                         eucalyptus_console: &mut self.console,
+                        current_scene_name: &mut self.current_scene_name,
                     },
                 );
         });
diff --git a/crates/eucalyptus-editor/src/editor/resource.rs b/crates/eucalyptus-editor/src/editor/resource.rs
index 9649336..fcb8234 100644
--- a/crates/eucalyptus-editor/src/editor/resource.rs
+++ b/crates/eucalyptus-editor/src/editor/resource.rs
@@ -1,5 +1,5 @@
 use hecs::Entity;
-use crate::editor::{EditorTabViewer, TABS_GLOBAL};
+use crate::editor::{EditorTabDock, EditorTabDockDescriptor, EditorTabViewer, TABS_GLOBAL};
 use dropbear_engine::camera::Camera;
 use eucalyptus_core::camera::{CameraComponent, CameraType};
 
@@ -84,3 +84,17 @@ impl<'a> EditorTabViewer<'a> {
         }
     }
 }
+
+pub struct ResourceInspectorDock;
+
+impl EditorTabDock for ResourceInspectorDock {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            title: "Resource Inspector".to_string(),
+        }
+    }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui) {
+        viewer.resource_inspector(ui);
+    }
+}
diff --git a/crates/eucalyptus-editor/src/editor/scene.rs b/crates/eucalyptus-editor/src/editor/scene.rs
index 74c928f..c5cef9a 100644
--- a/crates/eucalyptus-editor/src/editor/scene.rs
+++ b/crates/eucalyptus-editor/src/editor/scene.rs
@@ -217,7 +217,8 @@ impl Scene for Editor {
         }
 
         if let Some((_, tab)) = self.dock_state.find_active_focused() {
-            self.is_viewport_focused = matches!(tab, EditorTab::Viewport);
+            let viewport_tab = self.tab_registry.id_for_title("Viewport");
+            self.is_viewport_focused = viewport_tab.map_or(false, |id| *tab == id);
         } else {
             self.is_viewport_focused = false;
         }
diff --git a/crates/eucalyptus-editor/src/editor/settings.rs b/crates/eucalyptus-editor/src/editor/settings.rs
index ed803c7..10ba47b 100644
--- a/crates/eucalyptus-editor/src/editor/settings.rs
+++ b/crates/eucalyptus-editor/src/editor/settings.rs
@@ -8,8 +8,7 @@ impl<'a> EditorTabViewer<'a> {
     pub fn scene_settings(&mut self, _cfg: &mut StaticallyKept, ui: &mut egui::Ui) {
         ui.label("Scene Settings");
 
-        let editor = unsafe { &mut *self.editor };
-        let current_scene_name = editor.current_scene_name.clone();
+        let current_scene_name = self.current_scene_name.clone();
 
         if let Some(scene_name) = current_scene_name {
             let mut scenes = SCENES.write();
diff --git a/crates/eucalyptus-editor/src/editor/settings/editor.rs b/crates/eucalyptus-editor/src/editor/settings/editor.rs
index f1035b7..2cbcb16 100644
--- a/crates/eucalyptus-editor/src/editor/settings/editor.rs
+++ b/crates/eucalyptus-editor/src/editor/settings/editor.rs
@@ -7,7 +7,7 @@ use egui::{CentralPanel, Id, Slider, SliderClamping};
 use egui_dock::DockState;
 use egui_ltreeview::{Action, NodeBuilder};
 use eucalyptus_core::input::InputState;
-use eucalyptus_core::states::EditorTab;
+use crate::editor::EditorTabId;
 use eucalyptus_core::utils::option::HistoricalOption;
 use eucalyptus_core::{APP_INFO, warn};
 use gilrs::{Button, GamepadId};
@@ -30,7 +30,7 @@ pub static EDITOR_SETTINGS: Lazy<RwLock<EditorSettings>> =
 pub struct EditorSettings {
     /// The layout of the dock.
     #[serde(default)]
-    pub dock_layout: Option<DockState<EditorTab>>,
+    pub dock_layout: Option<DockState<EditorTabId>>,
 
     #[serde(default)]
     pub target_fps: HistoricalOption<u32>,
diff --git a/crates/eucalyptus-editor/src/editor/viewport.rs b/crates/eucalyptus-editor/src/editor/viewport.rs
index ee360ed..c12f82c 100644
--- a/crates/eucalyptus-editor/src/editor/viewport.rs
+++ b/crates/eucalyptus-editor/src/editor/viewport.rs
@@ -1,4 +1,4 @@
-use crate::editor::{EditorTabViewer, Signal, TABS_GLOBAL, UndoableAction};
+use crate::editor::{EditorTabDock, EditorTabDockDescriptor, EditorTabViewer, Signal, TABS_GLOBAL, UndoableAction};
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, Transform};
 use dropbear_engine::lighting::Light;
@@ -262,3 +262,17 @@ impl<'a> EditorTabViewer<'a> {
         }
     }
 }
+
+pub struct ViewportDock;
+
+impl EditorTabDock for ViewportDock {
+    fn desc() -> EditorTabDockDescriptor {
+        EditorTabDockDescriptor {
+            title: "Viewport".to_string(),
+        }
+    }
+
+    fn display(viewer: &mut EditorTabViewer<'_>, ui: &mut egui::Ui) {
+        viewer.viewport_tab(ui);
+    }
+}
diff --git a/crates/eucalyptus-editor/src/lib.rs b/crates/eucalyptus-editor/src/lib.rs
index fd6333e..dbfcf32 100644
--- a/crates/eucalyptus-editor/src/lib.rs
+++ b/crates/eucalyptus-editor/src/lib.rs
@@ -12,8 +12,23 @@ pub mod stats;
 pub mod utils;
 pub use redback_runtime as runtime;
 
+use crate::editor::{
+    EditorTabRegistry, asset_viewer::AssetViewerDock, build_console::BuildConsoleDock,
+    dock::ConsoleDock, entity_list::EntityListDock, resource::ResourceInspectorDock,
+    viewport::ViewportDock,
+};
+
 dropbear_engine::features! {
     pub mod features {
         const ShowComponentTypeIDInEditor = 0b00000001
     }
 }
+
+pub fn register_docks(registry: &mut EditorTabRegistry) {
+    registry.register::<ViewportDock>();
+    registry.register::<EntityListDock>();
+    registry.register::<AssetViewerDock>();
+    registry.register::<ResourceInspectorDock>();
+    registry.register::<BuildConsoleDock>();
+    registry.register::<ConsoleDock>();
+}
diff --git a/crates/eucalyptus-editor/src/main.rs b/crates/eucalyptus-editor/src/main.rs
index 3e6e164..75540f9 100644
--- a/crates/eucalyptus-editor/src/main.rs
+++ b/crates/eucalyptus-editor/src/main.rs
@@ -6,6 +6,7 @@ use clap::{Arg, Command};
 use dropbear_engine::DropbearWindowBuilder;
 use dropbear_engine::future::FutureQueue;
 use dropbear_engine::texture::DropbearEngineLogo;
+use eucalyptus_core::APP_INFO;
 use eucalyptus_core::config::ProjectConfig;
 use eucalyptus_core::scripting::jni::{RUNTIME_MODE, RuntimeMode};
 use eucalyptus_core::scripting::{AWAIT_JDB, JVM_ARGS};
@@ -205,7 +206,13 @@ async fn main() -> anyhow::Result<()> {
         dropbear_engine::feature_list::enable(dropbear_engine::feature_list::EnablePuffinTracer)
     }
 
-    EditorSettings::read()?;
+    if let Err(e) = EditorSettings::read() {
+        panic!(
+            "Unable to launch eucalyptus-editor: {}
+            \nTry deleting your editor.eucc file located at {:?}", 
+            e, app_dirs2::app_root(app_dirs2::AppDataType::UserData, &APP_INFO)?
+        );
+    }
 
     match matches.subcommand() {
         Some(("build", sub_matches)) => {
diff --git a/crates/eucalyptus-editor/src/plugin.rs b/crates/eucalyptus-editor/src/plugin.rs
index 7305059..1118fce 100644
--- a/crates/eucalyptus-editor/src/plugin.rs
+++ b/crates/eucalyptus-editor/src/plugin.rs
@@ -1,4 +1,4 @@
-use crate::editor::Editor;
+use crate::editor::EditorTabViewer;
 use app_dirs2::AppDataType;
 use egui::Ui;
 use eucalyptus_core::APP_INFO;
@@ -14,7 +14,7 @@ use std::time::Instant;
 pub trait EditorPlugin: Send + Sync {
     fn id(&self) -> &str;
     fn display_name(&self) -> &str;
-    fn ui(&mut self, ui: &mut Ui, editor: &mut Editor);
+    fn ui(&mut self, ui: &mut Ui, viewer: &mut EditorTabViewer<'_>);
     fn tab_title(&self) -> &str;
 }