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