[go: up one dir, main page]

roost_ui/
lib.rs

1#![doc(html_logo_url = "https://raw.githubusercontent.com/ramp-stack/roost_ui/master/logo.png")]
2
3//! roost is a fast, cross-platform UI renderer.
4//!
5//! Check out the [website](http://ramp-stack.com/roost) for more information, the [Quick Start Guide](http://ramp-stack.com/roost/getting_started) to set up your first app.
6
7extern 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
30// pub use include_dir::include_dir as include_dir;
31
32mod 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
42/// # roost emitters
43///
44/// Emitters are the interactive bridge between user input and component behavior.
45///
46/// Each emitter listens for raw input events and translates them
47/// into meaningful, component-specific events.
48pub mod emitters;
49
50pub mod drawable;
51pub use drawable::Component;
52use drawable::{Drawable, _Drawable, SizedBranch};
53
54//pub mod components;
55// pub mod config;
56
57pub 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
71/// `Assets` stores all the assets required by your project, 
72/// including images and fonts.
73pub 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    /// Returns a reference to a vector containing all included directories.
93    pub fn dirs(&self) -> &Vec<Dir<'static>> {&self.dirs}
94    /// Adds a font to the atlas from the provided byte slice and returns the loaded [`resources::Font`] resource.
95    pub fn add_font(&mut self, font: &[u8]) -> resources::Font {self.atlas.add_font(font).unwrap()}
96    /// Adds an image to the atlas from the provided [`image::RgbaImage`] and returns the loaded [`resources::Image`] resource.
97    pub fn add_image(&mut self, image: image::RgbaImage) -> resources::Image {self.atlas.add_image(image)}
98    /// Adds a svg image to the atlas from the provided byte slice and scale factor and returns the loaded [`resources::Image`] resource.
99    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    /// Loads a font from the given file path and returns an [`Option`] containing the [`resources::Font`] if successful.
108    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    /// Loads an image from the given file path and returns an [`Option`] containing the [`resources::Image`] if successful.
113    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    /// Loads the contents of the specified file from the search directories, returning its bytes if found.
120    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    /// Adds a directory to the list of asset search paths.
135    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
155/// `Context` holds the app context, including hardware, runtime, assets, theme, plugins, events, and state.
156pub 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    /// Creates a new `Context` instance and loads the default Pelican UI assets.
167    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    /// Adds an [`Event`] to the context's event queue to be triggered.
179    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    /// Returns a mutable reference to the [`State`]
190    pub fn state(&mut self) -> &mut State {
191        self.state.as_mut().unwrap()
192    }
193
194  //pub fn state(&mut self) -> &mut State {
195  //    self.base_context.state()
196  //}
197
198
199  //pub fn add_font(&mut self, font: &[u8]) -> canvas::Font {
200  //    self.base_context.as_mut().add_font(font)
201  //}
202
203  //pub fn add_image(&mut self, image: image::RgbaImage) -> canvas::Image {
204  //    self.base_context.as_mut().add_image(image)
205  //}
206
207  //pub fn add_svg(&mut self, svg: &[u8], quality: f32) -> canvas::Image {
208  //    self.base_context.as_mut().add_svg(svg, quality)
209  //}
210
211
212
213    // pub fn as_canvas(&mut self) -> &mut FontAtlas {
214    //     self.as_mut()
215    // }
216}
217
218
219/// Allow [`Context`] to provide mutable access to the [`Atlas`].
220impl AsMut<Atlas> for Context {fn as_mut(&mut self) -> &mut Atlas {&mut self.assets.atlas}}
221
222/// The core application trait.
223///
224/// An `Application` provides services, registers plugins, and defines
225/// the entrypoint for creating the root [`Drawable`] of the app.
226///
227/// # Example
228/// ```
229/// struct MyApp;
230///
231/// impl Application for MyApp {
232///     async fn new(ctx: &mut Context) -> Box<dyn Drawable> {
233///         Box::new(MyRootDrawable::new())
234///     }
235/// }
236/// ```
237pub 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    /// Provide [`Services`] for [`PelicanEngine`] by deferring to the application type.
255    impl<A: Application> Services for PelicanEngine<A> {
256        fn services() -> ServiceList { A::services() }
257    }
258
259    /// The main engine type.
260    ///
261    /// `PelicanEngine` wires together the [`Application`], windowing system,
262    /// plugin management, drawing, and event handling.
263    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        /// Initializes the engine with the given MaverickOS context.
277        ///
278        /// - Creates a canvas bound to the OS window.
279        /// - Constructs an application via [`Application::new`].
280        /// - Registers plugins returned by [`Application::plugins`].
281        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 => {//Size before events because the events are given between
329                                    //resizing
330
331                        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}