1#![doc(html_logo_url = "https://raw.githubusercontent.com/ramp-stack/roost_ui/main/logo.png")]
2
3extern crate self as roost;
8
9use std::collections::BTreeMap;
10use std::any::TypeId;
11
12use wgpu_canvas::{Atlas, Item as CanvasItem};
13use maverick_os::window::Lifetime;
14pub use maverick_os::{
15 State,
16 window::Event as WindowEvent,
17 Services,
18 ServiceList,
19 self,
20 IS_WEB,
21 IS_MOBILE
22};
23
24pub use pelican_ui_proc::Component;
25pub use include_dir::include_dir;
26
27mod wgpu;
28use wgpu::Canvas;
29
30pub mod events;
31use events::{EventHandler, Events, Event, TickEvent};
32
33pub mod layouts;
34pub mod layout;
35use layout::Scale;
36
37pub mod emitters;
44
45pub mod drawable;
46pub use drawable::Component;
47use drawable::{Drawable, _Drawable, SizedBranch};
48
49pub mod resources {
53 pub use wgpu_canvas::{Image, Font};
54}
55
56type PluginList = BTreeMap<TypeId, Box<dyn Plugin>>;
57
58pub trait Plugin: Downcast {
59 fn event(&mut self, _ctx: &mut Context, _event: &dyn Event) {}
60}
61impl_downcast!(Plugin);
62
63pub use include_dir::{Dir, DirEntry};
64pub use downcast_rs::{Downcast, impl_downcast};
65pub use maverick_os::{window::Window, RuntimeContext, HardwareContext};
66
67pub struct Assets {
70 dirs: Vec<Dir<'static>>,
71 atlas: Atlas,
72}
73
74impl Default for Assets {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl Assets {
81 pub fn new() -> Self {
82 Assets {
83 dirs: Vec::new(),
84 atlas: Atlas::default(),
85 }
86 }
87
88 pub fn dirs(&self) -> &Vec<Dir<'static>> {&self.dirs}
90 pub fn add_font(&mut self, font: &[u8]) -> resources::Font {self.atlas.add_font(font).unwrap()}
92 pub fn add_image(&mut self, image: image::RgbaImage) -> resources::Image {self.atlas.add_image(image)}
94 pub fn add_svg(&mut self, svg: &[u8], scale: f32) -> resources::Image {
96 let svg = std::str::from_utf8(svg).unwrap();
97 let svg = nsvg::parse_str(svg, nsvg::Units::Pixel, 96.0).unwrap();
98 let rgba = svg.rasterize(scale).unwrap();
99 let size = rgba.dimensions();
100 self.atlas.add_image(image::RgbaImage::from_raw(size.0, size.1, rgba.into_raw()).unwrap())
101 }
102
103 pub fn load_font(&mut self, file: &str) -> Option<resources::Font> {
105 self.load_file(file).map(|b| self.add_font(&b))
106 }
107
108 pub fn load_image(&mut self, file: &str) -> Option<resources::Image> {
110 self.load_file(file).map(|b|
111 self.add_image(image::load_from_memory(&b).unwrap().into())
112 )
113 }
114
115 pub fn load_file(&self, file: &str) -> Option<Vec<u8>> {
117 self.dirs.iter().find_map(|dir|
118 dir.find(file).ok().and_then(|mut f|
119 f.next().and_then(|f|
120 if let DirEntry::File(f) = f {
121 Some(f.contents().to_vec())
122 } else {
123 None
124 }
125 )
126 )
127 )
128 }
129
130 pub fn include_assets(&mut self, dir: Dir<'static>) {
132 self.dirs.push(dir);
133 }
134}
135
136pub struct PluginGuard<'a, P: Plugin>(Option<P>, &'a mut Context);
137impl<'a, P: Plugin> PluginGuard<'a, P> {
138 pub fn get(&mut self) -> (&mut P, &mut Context) {
139 (self.0.as_mut().unwrap(), &mut *self.1)
140 }
141 pub fn run<T>(&mut self, clo: impl FnOnce(&mut P, &mut Context) -> T) -> T {
142 clo(self.0.as_mut().unwrap(), self.1)
143 }
144}
145impl<'a, P: Plugin> Drop for PluginGuard<'a, P> {
146 fn drop(&mut self) {
147 self.1.plugins.insert(TypeId::of::<P>(), Box::new(self.0.take().unwrap()));
148 }
149}
150
151pub struct Context {
153 pub hardware: HardwareContext,
154 pub runtime: RuntimeContext,
155 pub assets: Assets,
156 plugins: PluginList,
157 events: Events,
158 state: Option<State>
159}
160
161impl Context {
162 pub fn new(hardware: HardwareContext, runtime: RuntimeContext, state: Option<State>) -> Self {
164 Context {
165 hardware,
166 runtime,
167 assets: Assets::new(),
168 plugins: PluginList::new(),
169 events: Events::new(),
170 state
171 }
172 }
173
174 pub fn trigger_event(&mut self, event: impl Event + 'static) {
176 self.events.push_back(Box::new(event));
177 }
178
179 pub fn get<P: Plugin + 'static>(&mut self) -> PluginGuard<'_, P> {
180 PluginGuard(Some(*self.plugins.remove(&TypeId::of::<P>())
181 .unwrap_or_else(|| panic!("Plugin Not Configured: {:?}", std::any::type_name::<P>()))
182 .downcast().ok().unwrap()), self)
183 }
184
185 pub fn state(&mut self) -> &mut State {
187 self.state.as_mut().unwrap()
188 }
189
190 }
213
214
215impl AsMut<Atlas> for Context {fn as_mut(&mut self) -> &mut Atlas {&mut self.assets.atlas}}
217
218pub trait Application {
234 fn new(ctx: &mut Context) -> impl Future<Output = impl Drawable>;
235 fn services() -> ServiceList {ServiceList::default()}
236 fn plugins(_ctx: &mut Context) -> Vec<Box<dyn Plugin>> { vec![] }
237}
238
239#[doc(hidden)]
240pub mod __private {
241 use std::sync::Arc;
242 use wgpu_canvas::Area;
243
244 use maverick_os::window::{Window, Event as WindowEvent};
245 pub use maverick_os::{HardwareContext, RuntimeContext, ServiceList, Services, start as maverick_start};
246
247 use crate::{_Drawable, Application, Canvas, CanvasItem, Context, Drawable, EventHandler, Lifetime, Scale, SizedBranch, TickEvent};
248 use crate::layout::Scaling;
249
250 impl<A: Application> Services for PelicanEngine<A> {
252 fn services() -> ServiceList { A::services() }
253 }
254
255 pub struct PelicanEngine<A: Application> {
260 _p: std::marker::PhantomData<A>,
261 scale: Scale,
262 canvas: Canvas,
263 screen: (f32, f32),
264 context: Context,
265 sized_app: SizedBranch,
266 application: Box<dyn Drawable>,
267 event_handler: EventHandler,
268 items: Vec<(Area, CanvasItem)>,
269 }
270
271 impl<A: Application> maverick_os::Application for PelicanEngine<A> {
272 async fn new(ctx: &mut maverick_os::Context) -> Self {
278 ctx.hardware.notifications().register();
279 let size = ctx.window.size;
280 let (canvas, size) = Canvas::new(ctx.window.handle.clone(), size.0, size.1).await;
281 let scale = Scale(ctx.window.scale_factor);
282 let screen = (scale.logical(size.0 as f32), scale.logical(size.1 as f32));
283 let mut context = Context::new(ctx.hardware.clone(), ctx.runtime.clone(), ctx.state.take());
284 let plugins = A::plugins(&mut context);
285 context.plugins = plugins.into_iter().map(|p| ((*p).type_id(), p)).collect();
286 let mut application = A::new(&mut context).await;
287 let size_request = _Drawable::request_size(&application, &mut context);
288 let sized_app = application.build(&mut context, screen, size_request);
289 ctx.state = context.state.take();
290 PelicanEngine{
291 _p: std::marker::PhantomData::<A>,
292 scale,
293 canvas,
294 screen,
295 context,
296 sized_app,
297 application: Box::new(application),
298 event_handler: EventHandler::new(),
299 items: Vec::new()
300 }
301 }
302
303 async fn on_event(&mut self, context: &mut maverick_os::Context, event: WindowEvent) {
304 self.context.state = context.state.take();
305 match event {
306 WindowEvent::Lifetime(lifetime) => match lifetime {
307 Lifetime::Resized => {
308 self.scale.0 = context.window.scale_factor;
309 let size = context.window.size;
310 let size = self.canvas.resize::<Arc<Window>>(None, size.0, size.1);
311 let size = (self.scale.logical(size.0 as f32), self.scale.logical(size.1 as f32));
312 self.screen = size;
313 },
314 Lifetime::Resumed => {
315 let _ = self.items.drain(..);
316 self.scale.0 = context.window.scale_factor;
317 let size = context.window.size;
318 let size = self.canvas.resize(Some(context.window.handle.clone()), size.0, size.1);
319 let size = (self.scale.logical(size.0 as f32), self.scale.logical(size.1 as f32));
320 self.screen = size;
321 },
322 Lifetime::Paused => {},
323 Lifetime::Close => {},
324 Lifetime::Draw => {let result = self.event_handler.on_input(&self.scale, maverick_os::window::Input::Tick);
328 if let Some(event) = result {
329 self.context.events.push_back(event);
330 }
331 self.application.event(&mut self.context, self.sized_app.clone(), Box::new(TickEvent));
332
333 while let Some(event) = self.context.events.pop_front() {
334 if let Some(event) = event
335 .pass(&mut self.context, &vec![((0.0, 0.0), self.sized_app.0)])
336 .remove(0)
337 {
338 for id in self.context.plugins.keys().copied().collect::<Vec<_>>() {
339 let mut plugin = self.context.plugins.remove(&id).unwrap();
340 plugin.event(&mut self.context, &*event);
341 self.context.plugins.insert(id, plugin);
342 }
343 self.application.event(&mut self.context, self.sized_app.clone(), event);
344 }
345 }
346
347 let size_request = _Drawable::request_size(&*self.application, &mut self.context);
348 self.sized_app = self.application.build(&mut self.context, self.screen, size_request);
349 let drawn = self.application.draw(self.sized_app.clone(), (0.0, 0.0), (0.0, 0.0, self.screen.0, self.screen.1));
350 let items: Vec<_> = drawn.into_iter().map(|(a, i)| (a.scale(&self.scale), i.scale(&self.scale))).collect();
351 if self.items != items {
352 self.items = items.clone();
353 self.canvas.draw(&mut self.context.assets.atlas, items);
354 }
355 },
356 Lifetime::MemoryWarning => {},
357 },
358 WindowEvent::Input(input) => {if let Some(event) = self.event_handler.on_input(&self.scale, input) {self.context.events.push_back(event)}}
359 }
360 context.state = self.context.state.take();
361 }
362 }
363}
364
365#[macro_export]
366macro_rules! start {
367 ($app:ty) => {
368 pub use $crate::__private::*;
369
370 maverick_start!(PelicanEngine<$app>);
371 };
372}