use std::{cell::RefCell, collections::VecDeque, rc::Rc}; use chrono::Timelike; use glam::Vec2; use wgui::{ assets::{AssetPath, AssetProvider}, components::button::ComponentButton, font_config::WguiFontConfig, globals::WguiGlobals, i18n::Translation, layout::{LayoutParams, RcLayout, WidgetID}, parser::{Fetchable, ParseDocumentParams, ParserState}, widget::{label::WidgetLabel, rectangle::WidgetRectangle}, }; use crate::{ assets, settings, tab::{ Tab, TabParams, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings, }, }; pub struct Frontend { pub layout: RcLayout, globals: WguiGlobals, pub settings: Box, #[allow(dead_code)] state: ParserState, current_tab: Option>, tasks: VecDeque, ticks: u32, id_label_time: WidgetID, id_rect_content: WidgetID, } pub struct InitParams { pub settings: Box, } pub type RcFrontend = Rc>; pub enum FrontendTask { SetTab(TabType), RefreshClock, RefreshBackground, } impl Frontend { pub fn new(params: InitParams) -> anyhow::Result<(RcFrontend, RcLayout)> { let mut assets = Box::new(assets::Asset {}); let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?; let font_binary_regular = assets.load_from_path_gzip("Quicksand-Regular.ttf.gz")?; let font_binary_light = assets.load_from_path_gzip("Quicksand-Light.ttf.gz")?; let globals = WguiGlobals::new( assets, wgui::globals::Defaults::default(), &WguiFontConfig { binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light], family_name_sans_serif: "Quicksand", family_name_serif: "Quicksand", family_name_monospace: "", }, )?; let (layout, state) = wgui::parser::new_layout_from_assets( &ParseDocumentParams { globals: globals.clone(), path: AssetPath::BuiltIn("gui/dashboard.xml"), extra: Default::default(), }, &LayoutParams { resize_to_parent: true }, )?; let rc_layout = layout.as_rc(); let mut tasks = VecDeque::::new(); tasks.push_back(FrontendTask::SetTab(TabType::Home)); let id_label_time = state.get_widget_id("label_time")?; let id_rect_content = state.get_widget_id("rect_content")?; let frontend = Self { layout: rc_layout.clone(), state, current_tab: None, globals, tasks, ticks: 0, id_label_time, id_rect_content, settings: params.settings, }; // init some things first frontend.update_background()?; frontend.update_time()?; let res = Rc::new(RefCell::new(frontend)); Frontend::register_widgets(&res)?; Ok((res, rc_layout)) } pub fn update(&mut self, rc_this: &RcFrontend, width: f32, height: f32, timestep_alpha: f32) -> anyhow::Result<()> { while let Some(task) = self.tasks.pop_front() { self.process_task(rc_this, task)?; } self.tick(width, height, timestep_alpha)?; self.ticks += 1; Ok(()) } fn tick(&mut self, width: f32, height: f32, timestep_alpha: f32) -> anyhow::Result<()> { // fixme: timer events instead of this thing if self.ticks.is_multiple_of(1000) { self.update_time()?; } { let mut layout = self.layout.borrow_mut(); layout.update(Vec2::new(width, height), timestep_alpha)?; } Ok(()) } fn update_time(&self) -> anyhow::Result<()> { let mut layout = self.layout.borrow_mut(); let mut c = layout.start_common(); let mut common = c.common(); { let Some(mut label) = common.state.widgets.get_as::(self.id_label_time) else { anyhow::bail!(""); }; let now = chrono::Local::now(); let hours = now.hour(); let minutes = now.minute(); let text: String = if !self.settings.get().general.am_pm_clock { format!("{hours:02}:{minutes:02}") } else { let hours_ampm = (hours + 11) % 12 + 1; let suffix = if hours >= 12 { "PM" } else { "AM" }; format!("{hours_ampm:02}:{minutes:02} {suffix}") }; label.set_text(&mut common, Translation::from_raw_text(&text)); } c.finish()?; Ok(()) } fn update_background(&self) -> anyhow::Result<()> { let layout = self.layout.borrow_mut(); let Some(mut rect) = layout.state.widgets.get_as::(self.id_rect_content) else { anyhow::bail!(""); }; let (alpha1, alpha2) = if !self.settings.get().general.opaque_background { (0.8666, 0.9333) } else { (1.0, 1.0) }; rect.params.color.a = alpha1; rect.params.color2.a = alpha2; Ok(()) } pub fn get_layout(&self) -> &RcLayout { &self.layout } pub fn push_task(&mut self, task: FrontendTask) { self.tasks.push_back(task); } fn process_task(&mut self, rc_this: &RcFrontend, task: FrontendTask) -> anyhow::Result<()> { match task { FrontendTask::SetTab(tab_type) => self.set_tab(tab_type, rc_this)?, FrontendTask::RefreshClock => self.update_time()?, FrontendTask::RefreshBackground => self.update_background()?, } Ok(()) } fn set_tab(&mut self, tab_type: TabType, rc_this: &RcFrontend) -> anyhow::Result<()> { log::info!("Setting tab to {tab_type:?}"); let mut layout = self.layout.borrow_mut(); let widget_content = self.state.fetch_widget(&layout.state, "content")?; layout.remove_children(widget_content.id); let tab_params = TabParams { globals: &self.globals, layout: &mut layout, parent_id: widget_content.id, frontend: rc_this, settings: self.settings.get_mut(), }; let tab: Box = match tab_type { TabType::Home => Box::new(TabHome::new(tab_params)?), TabType::Apps => Box::new(TabApps::new(tab_params)?), TabType::Games => Box::new(TabGames::new(tab_params)?), TabType::Monado => Box::new(TabMonado::new(tab_params)?), TabType::Processes => Box::new(TabProcesses::new(tab_params)?), TabType::Settings => Box::new(TabSettings::new(tab_params)?), }; self.current_tab = Some(tab); Ok(()) } fn register_widgets(rc_this: &RcFrontend) -> anyhow::Result<()> { let this = rc_this.borrow_mut(); let btn_home = this.state.fetch_component_as::("btn_side_home")?; let btn_apps = this.state.fetch_component_as::("btn_side_apps")?; let btn_games = this.state.fetch_component_as::("btn_side_games")?; let btn_monado = this.state.fetch_component_as::("btn_side_monado")?; let btn_processes = this.state.fetch_component_as::("btn_side_processes")?; let btn_settings = this.state.fetch_component_as::("btn_side_settings")?; TabType::register_button(rc_this.clone(), &btn_home, TabType::Home); TabType::register_button(rc_this.clone(), &btn_apps, TabType::Apps); TabType::register_button(rc_this.clone(), &btn_games, TabType::Games); TabType::register_button(rc_this.clone(), &btn_monado, TabType::Monado); TabType::register_button(rc_this.clone(), &btn_processes, TabType::Processes); TabType::register_button(rc_this.clone(), &btn_settings, TabType::Settings); Ok(()) } }