#![deny(missing_docs)]
use std::future::Future;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
use std::task::{Poll, Waker};
use futures::task::ArcWake;
use futures::Stream;
use crate::draw::DrawList;
use crate::event::Event;
use crate::graphics::Graphics;
use crate::layout::Rectangle;
use crate::loader::Loader;
use crate::model_view::ModelView;
use crate::stylesheet::Style;
use crate::widget::{Context, Node};
mod atlas;
pub mod backend;
mod bitset;
pub mod cache;
pub mod draw;
pub mod event;
pub mod graphics;
pub mod layout;
pub mod loader;
mod model_view;
#[cfg(feature = "winit")]
#[cfg(feature = "wgpu")]
pub mod sandbox;
pub mod stylesheet;
pub mod text;
pub mod tracker;
pub mod widget;
pub trait Model: 'static {
type Message;
fn update(&mut self, message: Self::Message) -> Vec<Command<Self::Message>>;
fn view(&mut self) -> Node<Self::Message>;
}
pub trait EventLoop<T: Send>: Clone + Send {
type Error;
fn send_event(&self, event: T) -> Result<(), Self::Error>;
}
pub struct Ui<M: Model, E: EventLoop<Command<M::Message>>, L: Loader> {
model_view: ModelView<M>,
style: Arc<Style>,
viewport: Rectangle,
redraw: bool,
cursor: (f32, f32),
event_loop: E,
loader: Arc<L>,
hot_reload_style: Option<String>,
}
pub enum Command<Message> {
Await(Task<Message>),
Subscribe(Box<dyn Stream<Item = Message> + Send>),
Stylesheet(Task<Result<Style, stylesheet::Error>>),
}
impl<M: Model, E: 'static + EventLoop<Command<M::Message>>, L: 'static + Loader> Ui<M, E, L> {
pub fn new(model: M, event_loop: E, loader: L, viewport: Rectangle) -> Self {
let style = Arc::new(Style::new(512, 0));
Self {
model_view: ModelView::new(model),
style,
viewport,
redraw: true,
cursor: (0.0, 0.0),
event_loop,
hot_reload_style: None,
loader: Arc::new(loader),
}
}
pub async fn set_stylesheet<U: AsRef<str>>(&mut self, url: U) -> Result<(), stylesheet::Error> {
let style = Style::load(&*self.loader, url.as_ref(), 512, 0).await?;
self.style = Arc::new(style);
self.hot_reload_style = Some(url.as_ref().to_string());
let loader = self.loader.clone();
let url = url.as_ref().to_string();
self.command(Command::from_future_style(async move {
loader
.wait(&url)
.await
.map_err(|e| stylesheet::Error::Io(Box::new(e)))?;
Ok(Style::load(&*loader, url, 512, 0).await?)
}));
Ok(())
}
pub fn replace_stylesheet(&mut self, style: Arc<Style>) {
if !Arc::ptr_eq(&self.style, &style) {
self.style = style;
self.hot_reload_style = None;
self.model_view.set_dirty();
}
}
pub fn reload_stylesheet<U: 'static + AsRef<str> + Send>(&mut self, url: U) {
let loader = self.loader.clone();
self.hot_reload_style = Some(url.as_ref().to_string());
self.command(Command::from_future_style(async move {
Style::load(&*loader, url, 512, 0).await
}));
}
pub fn graphics(&self) -> Graphics<L> {
Graphics {
cache: self.style.cache(),
loader: self.loader.clone(),
}
}
pub fn resize(&mut self, viewport: Rectangle) {
self.model_view.set_dirty();
self.redraw = true;
self.viewport = viewport;
}
pub fn command(&mut self, command: Command<M::Message>) {
match command {
Command::Await(mut task) => {
let complete = task.complete.clone();
if !complete.load(Relaxed) {
let ptr = task.future.deref_mut() as *mut dyn Future<Output=M::Message>;
let pin = unsafe { std::pin::Pin::new_unchecked(ptr.as_mut().unwrap()) };
let waker = EventLoopWaker::new(self.event_loop.clone(), Command::Await(task));
match pin.poll(&mut std::task::Context::from_waker(&waker)) {
Poll::Ready(message) => {
complete.store(true, Relaxed);
self.update(message);
}
_ => (),
}
}
}
Command::Subscribe(mut stream) => {
let ptr = stream.deref_mut() as *mut dyn Stream<Item = M::Message>;
let pin = unsafe { std::pin::Pin::new_unchecked(ptr.as_mut().unwrap()) };
let waker = EventLoopWaker::new(self.event_loop.clone(), Command::Subscribe(stream));
match pin.poll_next(&mut std::task::Context::from_waker(&waker)) {
Poll::Ready(Some(message)) => {
waker.wake();
self.update(message);
}
_ => (),
}
}
Command::Stylesheet(mut task) => {
let complete = task.complete.clone();
if !complete.load(Relaxed) {
let ptr = task.future.deref_mut() as *mut dyn Future<Output=Result<Style, stylesheet::Error>>;
let pin = unsafe { std::pin::Pin::new_unchecked(ptr.as_mut().unwrap()) };
let waker = EventLoopWaker::new(self.event_loop.clone(), Command::Stylesheet(task));
match pin.poll(&mut std::task::Context::from_waker(&waker)) {
Poll::Ready(style) => {
complete.store(true, Relaxed);
match style {
Ok(style) => {
self.style = Arc::new(style);
self.model_view.set_dirty();
}
Err(error) => {
eprintln!("Unable to load stylesheet: {}", error);
}
}
if let Some(url) = self.hot_reload_style.clone() {
let loader = self.loader.clone();
let url = url.clone();
self.command(Command::from_future_style(async move {
loader
.wait(&url)
.await
.map_err(|e| stylesheet::Error::Io(Box::new(e)))?;
Ok(Style::load(&*loader, url, 512, 0).await?)
}));
}
}
_ => (),
}
}
}
}
}
pub fn update(&mut self, message: M::Message) {
for command in self.model_view.model_mut().update(message) {
self.command(command);
}
}
pub fn event(&mut self, event: Event) {
if let Event::Cursor(x, y) = event {
self.cursor = (x, y);
}
let mut context = Context::new(self.needs_redraw(), self.cursor);
{
let view = self.model_view.view(self.style.clone());
let (w, h) = view.size();
let layout = Rectangle::from_wh(
w.resolve(self.viewport.width(), w.parts()),
h.resolve(self.viewport.height(), h.parts()),
);
view.event(layout, self.viewport, event, &mut context);
}
self.redraw = context.redraw_requested();
for message in context {
for command in self.model_view.model_mut().update(message) {
self.command(command);
}
}
}
pub fn needs_redraw(&self) -> bool {
self.redraw || self.model_view.dirty()
}
pub fn draw(&mut self) -> DrawList {
use self::draw::*;
let viewport = self.viewport;
let primitives = {
let view = self.model_view.view(self.style.clone());
let (w, h) = view.size();
let layout = Rectangle::from_wh(
w.resolve(viewport.width(), w.parts()),
h.resolve(viewport.height(), h.parts()),
);
view.draw(layout, viewport)
};
self.redraw = false;
struct Layer {
vtx: Vec<Vertex>,
cmd: Vec<Command>,
};
impl Layer {
fn append(&mut self, command: Command) {
if let Some(next) = self.cmd.last_mut().unwrap().append(command) {
self.cmd.push(next);
}
}
}
let mut layers = vec![Layer {
vtx: Vec::new(),
cmd: vec![Command::Nop],
}];
let mut layer: usize = 0;
let mut scissors = Vec::new();
scissors.push(viewport);
let validate_clip = move |clip: Rectangle| {
let v = Rectangle {
left: clip.left.max(0.0).min(viewport.right),
top: clip.top.max(0.0).min(viewport.bottom),
right: clip.right.max(0.0).min(viewport.right),
bottom: clip.bottom.max(0.0).min(viewport.bottom),
};
if v.width() > 0.0 && v.height() > 0.0 {
Some(v)
} else {
None
}
};
let mut draw_enabled = true;
for primitive in primitives.into_iter() {
match primitive {
Primitive::PushClip(scissor) => {
scissors.push(scissor);
draw_enabled = validate_clip(scissor).map_or(false, |s| {
layers[layer].append(Command::Clip { scissor: s });
true
});
}
Primitive::PopClip => {
scissors.pop();
let scissor = scissors[scissors.len() - 1];
draw_enabled = validate_clip(scissor).map_or(false, |s| {
layers[layer].append(Command::Clip { scissor: s });
true
});
}
Primitive::LayerUp => {
layer += 1;
while layer >= layers.len() {
layers.push(Layer {
vtx: Vec::new(),
cmd: vec![Command::Nop],
});
}
}
Primitive::LayerDown => {
layer -= 1;
}
Primitive::DrawRect(r, color) => {
if draw_enabled {
let r = r.to_device_coordinates(viewport);
let color = [color.r, color.g, color.b, color.a];
let mode = 2;
let offset = layers[layer].vtx.len();
layers[layer].vtx.push(Vertex {
pos: [r.left, r.top],
uv: [0.0; 2],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.right, r.top],
uv: [0.0; 2],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.right, r.bottom],
uv: [0.0; 2],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.left, r.top],
uv: [0.0; 2],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.right, r.bottom],
uv: [0.0; 2],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.left, r.bottom],
uv: [0.0; 2],
color,
mode,
});
layers[layer].append(Command::Colored { offset, count: 6 });
}
}
Primitive::DrawText(text, rect) => {
if draw_enabled {
let color = [text.color.r, text.color.g, text.color.b, text.color.a];
let mode = 0;
let offset = layers[layer].vtx.len();
self.style.cache().lock().unwrap().draw_text(&text, rect, |uv, pos| {
let rc = Rectangle {
left: pos.left,
top: pos.top,
right: pos.right,
bottom: pos.bottom,
}
.to_device_coordinates(viewport);
layers[layer].vtx.push(Vertex {
pos: [rc.left, rc.top],
uv: uv.pt(0.0, 0.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.right, rc.top],
uv: uv.pt(1.0, 0.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.right, rc.bottom],
uv: uv.pt(1.0, 1.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.left, rc.top],
uv: uv.pt(0.0, 0.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.right, rc.bottom],
uv: uv.pt(1.0, 1.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.left, rc.bottom],
uv: uv.pt(0.0, 1.0),
color,
mode,
});
});
let count = layers[layer].vtx.len() - offset;
layers[layer].append(Command::Textured {
texture: text.font.tex_slot,
offset,
count,
});
}
}
Primitive::Draw9(patch, rect, color) => {
if draw_enabled {
let uv = patch.image.texcoords;
let color = [color.r, color.g, color.b, color.a];
let mode = 1;
let offset = layers[layer].vtx.len();
patch.iterate_sections(false, rect.width(), |x, u| {
patch.iterate_sections(true, rect.height(), |y, v| {
let rc = Rectangle {
left: x.0 + rect.left,
right: x.1 + rect.left,
top: y.0 + rect.top,
bottom: y.1 + rect.top,
}
.to_device_coordinates(viewport);
layers[layer].vtx.push(Vertex {
pos: [rc.left, rc.top],
uv: uv.pt(u.0, v.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.right, rc.top],
uv: uv.pt(u.1, v.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.right, rc.bottom],
uv: uv.pt(u.1, v.1),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.left, rc.top],
uv: uv.pt(u.0, v.0),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.right, rc.bottom],
uv: uv.pt(u.1, v.1),
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [rc.left, rc.bottom],
uv: uv.pt(u.0, v.1),
color,
mode,
});
});
});
let count = layers[layer].vtx.len() - offset;
layers[layer].append(Command::Textured {
texture: patch.image.texture,
offset,
count,
});
}
}
Primitive::DrawImage(image, r, color) => {
if draw_enabled {
let r = r.to_device_coordinates(viewport);
let uv = image.texcoords;
let color = [color.r, color.g, color.b, color.a];
let mode = 1;
let offset = layers[layer].vtx.len();
layers[layer].vtx.push(Vertex {
pos: [r.left, r.top],
uv: [uv.left, uv.top],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.right, r.top],
uv: [uv.right, uv.top],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.right, r.bottom],
uv: [uv.right, uv.bottom],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.left, r.top],
uv: [uv.left, uv.top],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.right, r.bottom],
uv: [uv.right, uv.bottom],
color,
mode,
});
layers[layer].vtx.push(Vertex {
pos: [r.left, r.bottom],
uv: [uv.left, uv.bottom],
color,
mode,
});
layers[layer].append(Command::Textured {
texture: image.texture,
offset,
count: 6,
});
}
}
}
}
let (vertices, commands) =
layers
.into_iter()
.fold((Vec::new(), Vec::new()), |(mut vtx, mut cmd), mut layer| {
let layer_offset = vtx.len();
vtx.append(&mut layer.vtx);
cmd.extend(layer.cmd.into_iter().map(|command| match command {
Command::Textured { texture, offset, count } => Command::Textured {
texture,
offset: offset + layer_offset,
count,
},
Command::Colored { offset, count } => Command::Colored {
offset: offset + layer_offset,
count,
},
other => other,
}));
(vtx, cmd)
});
DrawList {
updates: self.style.cache().lock().unwrap().take_updates(),
vertices,
commands,
}
}
}
pub struct Task<T> {
future: Box<dyn Future<Output = T> + Send>,
complete: Arc<AtomicBool>,
}
struct EventLoopWaker<T: Send, E: EventLoop<T>> {
message: Mutex<(E, Option<T>)>,
}
impl<T: 'static + Send, E: 'static + EventLoop<T>> EventLoopWaker<T, E> {
fn new(event_loop: E, message: T) -> Waker {
futures::task::waker(Arc::new(Self {
message: Mutex::new((event_loop, Some(message))),
}))
}
}
impl<T: Send, E: EventLoop<T>> ArcWake for EventLoopWaker<T, E> {
fn wake_by_ref(arc_self: &Arc<Self>) {
let mut guard = arc_self.message.lock().unwrap();
if let Some(message) = guard.1.take() {
guard.0.send_event(message).ok();
}
}
}
impl<M: Model, E: EventLoop<Command<M::Message>>, L: Loader> Deref for Ui<M, E, L> {
type Target = M;
fn deref(&self) -> &Self::Target {
&self.model_view.model()
}
}
impl<M: Model, E: EventLoop<Command<M::Message>>, L: Loader> DerefMut for Ui<M, E, L> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.model_view.model_mut()
}
}
impl<Message> Command<Message> {
pub fn from_future_message<F: 'static + Future<Output = Message> + Send>(fut: F) -> Self {
Command::Await(Task { complete: Arc::new(AtomicBool::new(false)), future: Box::new(fut) })
}
pub fn from_future_style<F: 'static + Future<Output = Result<Style, stylesheet::Error>> + Send>(fut: F) -> Self {
Command::Stylesheet(Task { complete: Arc::new(AtomicBool::new(false)), future: Box::new(fut) })
}
pub fn from_stream<S: 'static + Stream<Item = Message> + Send>(stream: S) -> Self {
Command::Subscribe(Box::new(stream))
}
}
pub mod prelude {
#[cfg(feature = "winit")]
#[cfg(feature = "wgpu")]
pub use crate::sandbox::Sandbox;
pub use crate::{layout::Rectangle, stylesheet::Style, tracker::ManagedState, widget::*, Command, Model, Ui};
}