khora_editor/
bootstrap.rs1use std::sync::{Arc, Mutex};
16
17use khora_sdk::khora_core::ui::EditorOverlay;
18use khora_sdk::prelude::*;
19use khora_sdk::run_winit;
20use khora_sdk::winit;
21use khora_sdk::winit_adapters::WinitWindowProvider;
22use khora_sdk::{
23 AudioDevice, AudioMixBus, AudioStream, CpalAudioDevice, DefaultMixBus, EditorShell,
24 LayoutSystem, PhysicsProvider, PipelineSystem, RapierPhysicsWorld, RenderSystem,
25 StandardTextRenderer, StreamInfo, TaffyLayoutSystem, TextRenderer, WgpuPipelineSystem,
26 WgpuRenderSystem, TEXT_WGSL,
27};
28
29use crate::app::EditorApp;
30
31pub static PROJECT_PATH: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
33
34pub fn load_logo_icon() -> WindowIcon {
36 let png_bytes = include_bytes!("../assets/khora_small_logo.png");
37 match image::load_from_memory(png_bytes) {
38 Ok(img) => {
39 let rgba_img = img.to_rgba8();
40 let (w, h) = rgba_img.dimensions();
41 WindowIcon {
42 rgba: rgba_img.into_raw(),
43 width: w,
44 height: h,
45 }
46 }
47 Err(e) => {
48 log::warn!("Failed to decode logo PNG: {}", e);
49 WindowIcon {
50 rgba: vec![0, 0, 0, 0],
51 width: 1,
52 height: 1,
53 }
54 }
55 }
56}
57
58pub fn run() -> anyhow::Result<()> {
61 let args: Vec<String> = std::env::args().collect();
62 let project = args
63 .windows(2)
64 .find(|w| w[0] == "--project")
65 .map(|w| w[1].clone());
66 let _ = PROJECT_PATH.set(project);
67
68 run_winit::<WinitWindowProvider, EditorApp>(|window, runtime, event_loop_any| {
69 let mut rs = WgpuRenderSystem::new();
70 rs.init(window).expect("renderer init failed");
71 runtime.backends.insert(rs.graphics_device());
72
73 let event_loop = event_loop_any
76 .downcast_ref::<winit::event_loop::ActiveEventLoop>()
77 .expect("editor: bootstrap expects a winit ActiveEventLoop");
78 let theme = khora_sdk::khora_core::ui::UiTheme::default();
79 match rs.create_editor_overlay_and_shell(
80 event_loop,
81 khora_sdk::khora_lanes::render_lane::shaders::EGUI_WGSL,
82 theme,
83 khora_sdk::PRIMARY_VIEWPORT,
84 ) {
85 Ok((overlay, shell)) => {
86 let overlay: Box<dyn EditorOverlay> = Box::new(overlay);
87 let shell: Box<dyn EditorShell> = Box::new(shell);
88 runtime.backends.insert(Arc::new(Mutex::new(overlay)));
89 runtime.backends.insert(Arc::new(Mutex::new(shell)));
90 log::info!("editor: overlay + shell created");
91 }
92 Err(e) => {
93 log::error!("editor: failed to create overlay+shell: {e:?}");
94 }
95 }
96
97 let rs: Box<dyn RenderSystem> = Box::new(rs);
98 runtime.backends.insert(Arc::new(Mutex::new(rs)));
99
100 match WgpuPipelineSystem::new() {
103 Ok(sys) => {
104 let sys: Arc<dyn PipelineSystem> = Arc::new(sys);
105 runtime.resources.insert(sys);
106 }
107 Err(e) => log::error!("pipeline system init failed: {e}"),
108 }
109
110 let physics: Box<dyn PhysicsProvider> = Box::new(RapierPhysicsWorld::default());
112 runtime.backends.insert(Arc::new(Mutex::new(physics)));
113
114 let layout: Box<dyn LayoutSystem> = Box::new(TaffyLayoutSystem::new());
116 runtime.backends.insert(Arc::new(Mutex::new(layout)));
117
118 let text: Arc<dyn TextRenderer> = Arc::new(StandardTextRenderer::new(TEXT_WGSL.to_owned()));
120 runtime.backends.insert(text);
121
122 let stream_info = StreamInfo {
128 channels: 2,
129 sample_rate: 48_000,
130 };
131 let mix_bus: Arc<dyn AudioMixBus> = Arc::new(DefaultMixBus::new(stream_info, 8192));
132 runtime.resources.insert(Arc::clone(&mix_bus));
133 let device: Box<dyn AudioDevice> = Box::new(CpalAudioDevice::new());
134 match device.open(mix_bus) {
135 Ok(stream) => {
136 let stream: Arc<dyn AudioStream> = Arc::from(stream);
137 runtime.backends.insert(stream);
138 }
139 Err(e) => log::error!("audio open failed: {}", e),
140 }
141 })?;
142 Ok(())
143}