#![doc(html_logo_url = "https://raw.githubusercontent.com/ramp-stack/roost_ui/master/logo.png")]
extern crate self as roost;
use std::collections::BTreeMap;
use std::any::TypeId;
use wgpu_canvas::{Atlas, Item as CanvasItem};
use maverick_os::window::Lifetime;
pub use maverick_os::{
State,
window::Event as WindowEvent,
Services,
ServiceList,
self,
IS_WEB,
IS_MOBILE
};
pub use pelican_ui_proc::Component;
pub use include_dir::include_dir;
mod wgpu;
use wgpu::Canvas;
pub mod events;
use events::{EventHandler, Events, Event, TickEvent};
pub mod layouts;
pub mod layout;
use layout::Scale;
pub mod emitters;
pub mod drawable;
pub use drawable::Component;
use drawable::{Drawable, _Drawable, SizedBranch};
pub mod resources {
pub use wgpu_canvas::{Image, Font};
}
type PluginList = BTreeMap<TypeId, Box<dyn Plugin>>;
pub trait Plugin: Downcast {
fn event(&mut self, _ctx: &mut Context, _event: &dyn Event) {}
}
impl_downcast!(Plugin);
pub use include_dir::{Dir, DirEntry};
pub use downcast_rs::{Downcast, impl_downcast};
pub use maverick_os::{window::Window, RuntimeContext, HardwareContext};
pub struct Assets {
dirs: Vec<Dir<'static>>,
atlas: Atlas,
}
impl Default for Assets {
fn default() -> Self {
Self::new()
}
}
impl Assets {
pub fn new() -> Self {
Assets {
dirs: Vec::new(),
atlas: Atlas::default(),
}
}
pub fn dirs(&self) -> &Vec<Dir<'static>> {&self.dirs}
pub fn add_font(&mut self, font: &[u8]) -> resources::Font {self.atlas.add_font(font).unwrap()}
pub fn add_image(&mut self, image: image::RgbaImage) -> resources::Image {self.atlas.add_image(image)}
pub fn add_svg(&mut self, svg: &[u8], scale: f32) -> resources::Image {
let svg = std::str::from_utf8(svg).unwrap();
let svg = nsvg::parse_str(svg, nsvg::Units::Pixel, 96.0).unwrap();
let rgba = svg.rasterize(scale).unwrap();
let size = rgba.dimensions();
self.atlas.add_image(image::RgbaImage::from_raw(size.0, size.1, rgba.into_raw()).unwrap())
}
pub fn load_font(&mut self, file: &str) -> Option<resources::Font> {
self.load_file(file).map(|b| self.add_font(&b))
}
pub fn load_image(&mut self, file: &str) -> Option<resources::Image> {
self.load_file(file).map(|b|
self.add_image(image::load_from_memory(&b).unwrap().into())
)
}
pub fn load_file(&self, file: &str) -> Option<Vec<u8>> {
self.dirs.iter().find_map(|dir|
dir.find(file).ok().and_then(|mut f|
f.next().and_then(|f|
if let DirEntry::File(f) = f {
Some(f.contents().to_vec())
} else {
None
}
)
)
)
}
pub fn include_assets(&mut self, dir: Dir<'static>) {
self.dirs.push(dir);
}
}
pub struct PluginGuard<'a, P: Plugin>(Option<P>, &'a mut Context);
impl<'a, P: Plugin> PluginGuard<'a, P> {
pub fn get(&mut self) -> (&mut P, &mut Context) {
(self.0.as_mut().unwrap(), &mut *self.1)
}
pub fn run<T>(&mut self, clo: impl FnOnce(&mut P, &mut Context) -> T) -> T {
clo(self.0.as_mut().unwrap(), self.1)
}
}
impl<'a, P: Plugin> Drop for PluginGuard<'a, P> {
fn drop(&mut self) {
self.1.plugins.insert(TypeId::of::<P>(), Box::new(self.0.take().unwrap()));
}
}
pub struct Context {
pub hardware: HardwareContext,
pub runtime: RuntimeContext,
pub assets: Assets,
plugins: PluginList,
events: Events,
state: Option<State>
}
impl Context {
pub fn new(hardware: HardwareContext, runtime: RuntimeContext, state: Option<State>) -> Self {
Context {
hardware,
runtime,
assets: Assets::new(),
plugins: PluginList::new(),
events: Events::new(),
state
}
}
pub fn trigger_event(&mut self, event: impl Event + 'static) {
self.events.push_back(Box::new(event));
}
pub fn get<P: Plugin + 'static>(&mut self) -> PluginGuard<'_, P> {
PluginGuard(Some(*self.plugins.remove(&TypeId::of::<P>())
.unwrap_or_else(|| panic!("Plugin Not Configured: {:?}", std::any::type_name::<P>()))
.downcast().ok().unwrap()), self)
}
pub fn state(&mut self) -> &mut State {
self.state.as_mut().unwrap()
}
}
impl AsMut<Atlas> for Context {fn as_mut(&mut self) -> &mut Atlas {&mut self.assets.atlas}}
pub trait Application {
fn new(ctx: &mut Context) -> impl Future<Output = impl Drawable>;
fn services() -> ServiceList {ServiceList::default()}
fn plugins(_ctx: &mut Context) -> Vec<Box<dyn Plugin>> { vec![] }
}
#[doc(hidden)]
pub mod __private {
use std::sync::Arc;
use wgpu_canvas::Area;
use maverick_os::window::{Window, Event as WindowEvent};
pub use maverick_os::{HardwareContext, RuntimeContext, ServiceList, Services, start as maverick_start};
use crate::{_Drawable, Application, Canvas, CanvasItem, Context, Drawable, EventHandler, Lifetime, Scale, SizedBranch, TickEvent};
use crate::layout::Scaling;
impl<A: Application> Services for PelicanEngine<A> {
fn services() -> ServiceList { A::services() }
}
pub struct PelicanEngine<A: Application> {
_p: std::marker::PhantomData<A>,
scale: Scale,
canvas: Canvas,
screen: (f32, f32),
context: Context,
sized_app: SizedBranch,
application: Box<dyn Drawable>,
event_handler: EventHandler,
items: Vec<(Area, CanvasItem)>,
}
impl<A: Application> maverick_os::Application for PelicanEngine<A> {
async fn new(ctx: &mut maverick_os::Context) -> Self {
ctx.hardware.notifications().register();
let size = ctx.window.size;
let (canvas, size) = Canvas::new(ctx.window.handle.clone(), size.0, size.1).await;
let scale = Scale(ctx.window.scale_factor);
let screen = (scale.logical(size.0 as f32), scale.logical(size.1 as f32));
let mut context = Context::new(ctx.hardware.clone(), ctx.runtime.clone(), ctx.state.take());
let plugins = A::plugins(&mut context);
context.plugins = plugins.into_iter().map(|p| ((*p).type_id(), p)).collect();
let mut application = A::new(&mut context).await;
let size_request = _Drawable::request_size(&application, &mut context);
let sized_app = application.build(&mut context, screen, size_request);
ctx.state = context.state.take();
PelicanEngine{
_p: std::marker::PhantomData::<A>,
scale,
canvas,
screen,
context,
sized_app,
application: Box::new(application),
event_handler: EventHandler::new(),
items: Vec::new()
}
}
async fn on_event(&mut self, context: &mut maverick_os::Context, event: WindowEvent) {
self.context.state = context.state.take();
match event {
WindowEvent::Lifetime(lifetime) => match lifetime {
Lifetime::Resized => {
self.scale.0 = context.window.scale_factor;
let size = context.window.size;
let size = self.canvas.resize::<Arc<Window>>(None, size.0, size.1);
let size = (self.scale.logical(size.0 as f32), self.scale.logical(size.1 as f32));
self.screen = size;
},
Lifetime::Resumed => {
let _ = self.items.drain(..);
self.scale.0 = context.window.scale_factor;
let size = context.window.size;
let size = self.canvas.resize(Some(context.window.handle.clone()), size.0, size.1);
let size = (self.scale.logical(size.0 as f32), self.scale.logical(size.1 as f32));
self.screen = size;
},
Lifetime::Paused => {},
Lifetime::Close => {},
Lifetime::Draw => {
let result = self.event_handler.on_input(&self.scale, maverick_os::window::Input::Tick);
if let Some(event) = result {
self.context.events.push_back(event);
}
self.application.event(&mut self.context, self.sized_app.clone(), Box::new(TickEvent));
while let Some(event) = self.context.events.pop_front() {
if let Some(event) = event
.pass(&mut self.context, &vec![((0.0, 0.0), self.sized_app.0)])
.remove(0)
{
for id in self.context.plugins.keys().copied().collect::<Vec<_>>() {
let mut plugin = self.context.plugins.remove(&id).unwrap();
plugin.event(&mut self.context, &*event);
self.context.plugins.insert(id, plugin);
}
self.application.event(&mut self.context, self.sized_app.clone(), event);
}
}
let size_request = _Drawable::request_size(&*self.application, &mut self.context);
self.sized_app = self.application.build(&mut self.context, self.screen, size_request);
let drawn = self.application.draw(self.sized_app.clone(), (0.0, 0.0), (0.0, 0.0, self.screen.0, self.screen.1));
let items: Vec<_> = drawn.into_iter().map(|(a, i)| (a.scale(&self.scale), i.scale(&self.scale))).collect();
if self.items != items {
self.items = items.clone();
self.canvas.draw(&mut self.context.assets.atlas, items);
}
},
Lifetime::MemoryWarning => {},
},
WindowEvent::Input(input) => {if let Some(event) = self.event_handler.on_input(&self.scale, input) {self.context.events.push_back(event)}}
}
context.state = self.context.state.take();
}
}
}
#[macro_export]
macro_rules! start {
($app:ty) => {
pub use $crate::__private::*;
maverick_start!(PelicanEngine<$app>);
};
}