1use crate::desktop::DesktopApp;
6use kobalt_core::types::Color;
7use kobalt_core::widget::Widget;
8use kobalt_render::RealTextRenderer;
9use winit::event::{Event, WindowEvent};
10
11pub struct KobaltApp {
29 title: Box<dyn Widget>,
30 width: u32,
31 height: u32,
32 background_color: Color,
33 home: Option<Box<dyn Widget>>,
34}
35
36impl KobaltApp {
37 pub fn build() -> Self {
39 let default_title = kobalt_widgets::Text::new("Kobalt App");
41
42 Self {
43 title: Box::new(default_title),
44 width: 800,
45 height: 600,
46 background_color: Color::from_rgb8(20, 20, 30),
47 home: None,
48 }
49 }
50
51 pub fn title<W: Widget + 'static>(mut self, title: W) -> Self {
53 self.title = Box::new(title);
54 self
55 }
56
57 pub fn size(mut self, width: u32, height: u32) -> Self {
59 self.width = width;
60 self.height = height;
61 self
62 }
63
64 pub fn background(mut self, color: Color) -> Self {
66 self.background_color = color;
67 self
68 }
69
70 pub fn home<W: Widget + 'static>(mut self, widget: W) -> Self {
72 self.home = Some(Box::new(widget));
73 self
74 }
75
76 pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
78 let title_widget = self.title;
79 let width = self.width;
80 let height = self.height;
81 let bg_color = self.background_color;
82 let home_widget = self.home.expect("Home widget not set. Call .home() before .run()");
83
84 let window_title = if title_widget.widget_type() == "Text" {
86 let text_ptr = &*title_widget as *const dyn Widget as *const kobalt_widgets::Text;
87 let text = unsafe { &*text_ptr };
88 text.content().to_string()
89 } else {
90 "Kobalt App".to_string()
91 };
92
93 let mut text_renderer: Option<RealTextRenderer> = None;
95
96 let app = DesktopApp::new(&window_title, width, height, move |window, event| {
97 match event {
98 Event::WindowEvent {
99 event: WindowEvent::RedrawRequested,
100 ..
101 } => {
102 if text_renderer.is_none() {
104 let renderer = window.renderer();
105 text_renderer = Some(RealTextRenderer::new(
106 renderer.device(),
107 renderer.config(),
108 ));
109 }
110
111 let screen_size = window.renderer().size();
112
113 let mut widgets_to_render = Vec::new();
115 collect_widgets(&*home_widget, &mut widgets_to_render);
116
117 if let Some(ref mut text_renderer) = text_renderer {
119 for widget in &widgets_to_render {
120 if widget.widget_type() == "Text" {
121 add_text_widget(
122 *widget,
123 text_renderer,
124 window.renderer().device(),
125 window.renderer().queue(),
126 screen_size.width,
127 screen_size.height,
128 );
129 }
130 }
131 }
132
133 match window.renderer().begin_frame() {
135 Ok(frame) => {
136 let mut encoder = frame.create_encoder();
137 {
138 let mut render_pass = frame.begin_render_pass(&mut encoder, bg_color);
139
140 if let Some(ref text_renderer) = text_renderer {
142 text_renderer.render(&mut render_pass);
143 }
144 }
145 frame.present(encoder);
146 }
147 Err(kobalt_render::SurfaceError::Lost) => {
148 window.renderer_mut().resize(screen_size);
149 }
150 Err(kobalt_render::SurfaceError::OutOfMemory) => {
151 eprintln!("Out of memory!");
152 }
153 Err(e) => {
154 eprintln!("Surface error: {:?}", e);
155 }
156 }
157 }
158 _ => {}
159 }
160 });
161
162 app.run()
163 }
164}
165
166fn collect_widgets<'a>(widget: &'a dyn Widget, widgets: &mut Vec<&'a dyn Widget>) {
168 match widget.widget_type() {
169 "Text" => {
170 widgets.push(widget);
171 }
172 "Column" => {
173 let column_ptr = widget as *const dyn Widget as *const kobalt_widgets::Column;
175 let column = unsafe { &*column_ptr };
176
177 for child in column.children() {
178 collect_widgets(&**child, widgets);
179 }
180 }
181 _ => {
182 eprintln!("Warning: Unknown widget type: {}", widget.widget_type());
183 }
184 }
185}
186
187fn add_text_widget(
189 widget: &dyn Widget,
190 text_renderer: &mut RealTextRenderer,
191 device: &wgpu::Device,
192 queue: &wgpu::Queue,
193 screen_width: f32,
194 screen_height: f32,
195) {
196 use kobalt_widgets::Text;
197
198 let text_ptr = widget as *const dyn Widget as *const Text;
199 let text = unsafe { &*text_ptr };
200
201 text_renderer.prepare_text(
202 device,
203 queue,
204 text.content(),
205 text.position,
206 text.font_size,
207 text.color,
208 screen_width,
209 screen_height,
210 );
211}