use crate::context::Context;
use crate::render::Buffer;
use crate::util::Rect;
use crate::ui::{
Widget, WidgetId, UIEvent, UIResult, UIError, EventDispatcher, ThemeManager,
AppEvent
};
use crate::event::Event as InputEvent;
use std::time::{Duration, Instant};
pub struct UIPage {
root_widget: Option<Box<dyn Widget>>,
event_dispatcher: EventDispatcher,
theme_manager: ThemeManager,
buffer: Buffer,
running: bool,
frame_time: Duration,
last_frame: Option<Instant>,
}
#[allow(non_camel_case_types)]
pub type UiApp = UIPage;
pub type UIApp = UIPage;
impl UIPage {
pub fn new(width: u16, height: u16) -> Self {
Self {
root_widget: None,
event_dispatcher: EventDispatcher::new(),
theme_manager: ThemeManager::new(),
buffer: Buffer::empty(Rect::new(0, 0, width, height)),
running: false,
frame_time: Duration::from_millis(16), #[cfg(not(target_arch = "wasm32"))]
last_frame: Some(Instant::now()),
#[cfg(target_arch = "wasm32")]
last_frame: None,
}
}
pub fn set_root_widget(&mut self, widget: Box<dyn Widget>) {
self.root_widget = Some(widget);
self.layout();
}
pub fn set_theme(&mut self, theme_name: &str) -> UIResult<()> {
self.theme_manager.set_theme(theme_name)
.map_err(UIError::ThemeError)?;
self.event_dispatcher.emit_event(AppEvent::ThemeChanged(theme_name.to_string()).into());
Ok(())
}
pub fn set_frame_rate(&mut self, fps: u32) {
self.frame_time = Duration::from_millis(1000 / fps as u64);
}
pub fn handle_input_event(&mut self, input_event: InputEvent) {
if let InputEvent::Mouse(mouse_event) = &input_event {
if let Some(ref root) = self.root_widget {
let hovered_widget = self.find_widget_at_point(root.as_ref(), mouse_event.column, mouse_event.row);
self.event_dispatcher.set_hover(hovered_widget);
}
}
self.event_dispatcher.process_input(input_event);
}
pub fn update(&mut self, dt: f32) -> UIResult<()> {
let mut ctx = Context::new();
let events = self.event_dispatcher.drain_events();
for event in events {
match &event {
UIEvent::App(AppEvent::Quit) => {
self.running = false;
}
_ => {
if let Some(ref mut root) = self.root_widget {
root.handle_event(&event, &mut ctx)?;
}
}
}
}
if let Some(ref mut root) = self.root_widget {
root.update(dt, &mut ctx)?;
}
Ok(())
}
pub fn render(&mut self) -> UIResult<()> {
self.clear_buffer();
let ctx = Context::new();
if let Some(ref root) = self.root_widget {
root.render(&mut self.buffer, &ctx)?;
}
Ok(())
}
pub fn render_into(&mut self, target_buffer: &mut Buffer) -> UIResult<()> {
target_buffer.reset();
let ctx = Context::new();
if let Some(ref root) = self.root_widget {
root.render(target_buffer, &ctx)?;
}
Ok(())
}
pub fn layout(&mut self) {
if let Some(ref mut root) = self.root_widget {
let area = *self.buffer.area();
root.set_bounds(area);
root.layout_children();
}
}
pub fn resize(&mut self, width: u16, height: u16) {
self.buffer = Buffer::empty(Rect::new(0, 0, width, height));
self.layout();
}
pub fn quit(&mut self) {
self.event_dispatcher.emit_event(AppEvent::Quit.into());
}
pub fn is_running(&self) -> bool {
self.running
}
pub fn start(&mut self) {
self.running = true;
#[cfg(not(target_arch = "wasm32"))]
{ self.last_frame = Some(Instant::now()); }
}
pub fn should_render(&self) -> bool {
match self.last_frame {
Some(t) => t.elapsed() >= self.frame_time,
None => true,
}
}
pub fn frame_complete(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
{ self.last_frame = Some(Instant::now()); }
}
pub fn buffer(&self) -> &Buffer {
&self.buffer
}
pub fn buffer_mut(&mut self) -> &mut Buffer {
&mut self.buffer
}
pub fn event_dispatcher(&mut self) -> &mut EventDispatcher {
&mut self.event_dispatcher
}
pub fn theme_manager(&self) -> &ThemeManager {
&self.theme_manager
}
pub fn theme_manager_mut(&mut self) -> &mut ThemeManager {
&mut self.theme_manager
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run_simple(&mut self) -> UIResult<()> {
self.start();
while self.running {
let dt = self.last_frame.map_or(0.016, |t| t.elapsed().as_secs_f32());
self.update(dt)?;
if self.should_render() {
self.render()?;
self.frame_complete();
}
std::thread::sleep(Duration::from_millis(1));
}
Ok(())
}
fn clear_buffer(&mut self) {
self.buffer.reset();
}
fn find_widget_at_point(&self, widget: &dyn Widget, x: u16, y: u16) -> Option<WidgetId> {
if !widget.state().visible || !widget.hit_test(x, y) {
return None;
}
Some(widget.id())
}
}
pub struct UIPageBuilder {
width: u16,
height: u16,
title: Option<String>,
theme: Option<String>,
frame_rate: Option<u32>,
}
pub type UIAppBuilder = UIPageBuilder;
impl UIPageBuilder {
pub fn new(width: u16, height: u16) -> Self {
Self {
width,
height,
title: None,
theme: None,
frame_rate: None,
}
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = Some(title.to_string());
self
}
pub fn with_theme(mut self, theme: &str) -> Self {
self.theme = Some(theme.to_string());
self
}
pub fn with_frame_rate(mut self, fps: u32) -> Self {
self.frame_rate = Some(fps);
self
}
pub fn build(self) -> UIResult<UIPage> {
let mut page = UIPage::new(self.width, self.height);
if let Some(theme) = self.theme {
page.set_theme(&theme)?;
}
if let Some(fps) = self.frame_rate {
page.set_frame_rate(fps);
}
Ok(page)
}
}