Skip to main content

khora_editor/panels/asset_browser/
handlers.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! `AssetTypeHandler` — extensibility seam for the Asset Browser.
16//!
17//! Each handler owns one `asset_type_name` (the lower-case identifier
18//! produced by the index builder) and decides:
19//!
20//!   - which tile-kind / icon / category to show in the browser;
21//!   - what happens when the user double-clicks it.
22//!
23//! Built-in handlers are registered through `inventory::submit!` from
24//! [`builtins`]. Plugins can register their own by submitting to
25//! [`AssetTypeHandlerRegistration`] from any crate that depends on
26//! `khora-editor` (or via a future plugin loader).
27
28use khora_sdk::editor_ui::Icon;
29
30use crate::widgets::tile::AssetTileKind;
31
32/// Action to take after the user activates (double-clicks) an asset.
33#[derive(Debug, Clone)]
34pub enum ActivationKind {
35    /// Load this scene into the world. The dispatcher in
36    /// `commands::load_scene_dispatch` routes it through the project
37    /// VFS when the path is internal.
38    LoadScene { abs_path: String },
39    /// Spawn the prefab subtree into the active world. The dispatcher
40    /// queues `EditorState::pending_prefab_spawn` with the forward-slash
41    /// path under `<project>/assets/`; spawning happens next frame in
42    /// `commands::process_pending_prefab_spawn`.
43    ///
44    /// `abs_path` is carried for parity with the other variants (and for
45    /// future use when a prefab spawn could feed an OS-level "Reveal in
46    /// Finder" affordance), but the dispatcher reads `rel_path` from the
47    /// asset entry directly.
48    SpawnPrefab {
49        #[allow(dead_code)]
50        abs_path: String,
51    },
52    /// Open externally with the OS-default app.
53    OpenExternal { abs_path: String },
54}
55
56/// Pluggable handler for one asset type.
57///
58/// `icon` and `category_label` are reserved for Phase 4 (folder-tree
59/// sidebar driven entirely by handlers); built-in implementations
60/// already supply them so the rewrite is a drop-in.
61#[allow(dead_code)]
62pub trait AssetTypeHandler: Send + Sync + 'static {
63    /// Lower-case `asset_type_name` (matches `AssetMetadata::asset_type_name`).
64    fn matches_type_name(&self, type_name: &str) -> bool;
65    fn tile_kind(&self) -> AssetTileKind;
66    fn icon(&self) -> Icon;
67    fn category_label(&self) -> &'static str;
68    /// Default activation: open externally.
69    fn activate(&self, abs_path: String) -> ActivationKind {
70        ActivationKind::OpenExternal { abs_path }
71    }
72}
73
74/// Inventory entry — registered through `inventory::submit!`.
75pub struct AssetTypeHandlerRegistration {
76    pub handler: &'static dyn AssetTypeHandler,
77}
78
79inventory::collect!(AssetTypeHandlerRegistration);
80
81/// Lookup the handler for a given `asset_type_name`. Returns `None` only
82/// if no built-in or plugin handler matched (caller should fall back to
83/// the unknown-type tile + open-external behaviour).
84pub fn handler_for(type_name: &str) -> Option<&'static dyn AssetTypeHandler> {
85    inventory::iter::<AssetTypeHandlerRegistration>
86        .into_iter()
87        .map(|r| r.handler)
88        .find(|h| h.matches_type_name(type_name))
89}
90
91/// Tile kind by type name — convenience for the panel grid.
92pub fn tile_kind_for(type_name: &str) -> AssetTileKind {
93    handler_for(type_name)
94        .map(|h| h.tile_kind())
95        .unwrap_or(AssetTileKind::Unknown)
96}
97
98// ─── Built-in handlers ────────────────────────────────────────────────
99
100pub mod builtins {
101    use super::*;
102
103    pub struct SceneHandler;
104    impl AssetTypeHandler for SceneHandler {
105        fn matches_type_name(&self, n: &str) -> bool {
106            n == "scene"
107        }
108        fn tile_kind(&self) -> AssetTileKind {
109            AssetTileKind::Scene
110        }
111        fn icon(&self) -> Icon {
112            Icon::Film
113        }
114        fn category_label(&self) -> &'static str {
115            "Scenes"
116        }
117        fn activate(&self, abs_path: String) -> ActivationKind {
118            ActivationKind::LoadScene { abs_path }
119        }
120    }
121
122    pub struct MeshHandler;
123    impl AssetTypeHandler for MeshHandler {
124        fn matches_type_name(&self, n: &str) -> bool {
125            n == "mesh"
126        }
127        fn tile_kind(&self) -> AssetTileKind {
128            AssetTileKind::Mesh
129        }
130        fn icon(&self) -> Icon {
131            Icon::Cube
132        }
133        fn category_label(&self) -> &'static str {
134            "Meshes"
135        }
136    }
137
138    pub struct TextureHandler;
139    impl AssetTypeHandler for TextureHandler {
140        fn matches_type_name(&self, n: &str) -> bool {
141            n == "texture"
142        }
143        fn tile_kind(&self) -> AssetTileKind {
144            AssetTileKind::Texture
145        }
146        fn icon(&self) -> Icon {
147            Icon::Image
148        }
149        fn category_label(&self) -> &'static str {
150            "Textures"
151        }
152    }
153
154    pub struct AudioHandler;
155    impl AssetTypeHandler for AudioHandler {
156        fn matches_type_name(&self, n: &str) -> bool {
157            n == "audio"
158        }
159        fn tile_kind(&self) -> AssetTileKind {
160            AssetTileKind::Audio
161        }
162        fn icon(&self) -> Icon {
163            Icon::Music
164        }
165        fn category_label(&self) -> &'static str {
166            "Audio"
167        }
168    }
169
170    pub struct ShaderHandler;
171    impl AssetTypeHandler for ShaderHandler {
172        fn matches_type_name(&self, n: &str) -> bool {
173            n == "shader"
174        }
175        fn tile_kind(&self) -> AssetTileKind {
176            AssetTileKind::Shader
177        }
178        fn icon(&self) -> Icon {
179            Icon::Zap
180        }
181        fn category_label(&self) -> &'static str {
182            "Shaders"
183        }
184    }
185
186    pub struct PrefabHandler;
187    impl AssetTypeHandler for PrefabHandler {
188        fn matches_type_name(&self, n: &str) -> bool {
189            n == "prefab"
190        }
191        fn tile_kind(&self) -> AssetTileKind {
192            AssetTileKind::Scene
193        }
194        fn icon(&self) -> Icon {
195            Icon::Cube
196        }
197        fn category_label(&self) -> &'static str {
198            "Prefabs"
199        }
200        fn activate(&self, abs_path: String) -> ActivationKind {
201            ActivationKind::SpawnPrefab { abs_path }
202        }
203    }
204
205    pub struct MaterialHandler;
206    impl AssetTypeHandler for MaterialHandler {
207        fn matches_type_name(&self, n: &str) -> bool {
208            n == "material"
209        }
210        fn tile_kind(&self) -> AssetTileKind {
211            AssetTileKind::Material
212        }
213        fn icon(&self) -> Icon {
214            Icon::Circle
215        }
216        fn category_label(&self) -> &'static str {
217            "Materials"
218        }
219    }
220
221    pub struct ScriptHandler;
222    impl AssetTypeHandler for ScriptHandler {
223        fn matches_type_name(&self, n: &str) -> bool {
224            n == "script"
225        }
226        fn tile_kind(&self) -> AssetTileKind {
227            AssetTileKind::Script
228        }
229        fn icon(&self) -> Icon {
230            Icon::Code
231        }
232        fn category_label(&self) -> &'static str {
233            "Scripts"
234        }
235    }
236
237    inventory::submit! {
238        super::AssetTypeHandlerRegistration { handler: &SceneHandler }
239    }
240    inventory::submit! {
241        super::AssetTypeHandlerRegistration { handler: &MeshHandler }
242    }
243    inventory::submit! {
244        super::AssetTypeHandlerRegistration { handler: &TextureHandler }
245    }
246    inventory::submit! {
247        super::AssetTypeHandlerRegistration { handler: &AudioHandler }
248    }
249    inventory::submit! {
250        super::AssetTypeHandlerRegistration { handler: &ShaderHandler }
251    }
252    inventory::submit! {
253        super::AssetTypeHandlerRegistration { handler: &ScriptHandler }
254    }
255    inventory::submit! {
256        super::AssetTypeHandlerRegistration { handler: &PrefabHandler }
257    }
258    inventory::submit! {
259        super::AssetTypeHandlerRegistration { handler: &MaterialHandler }
260    }
261}