//! The state of your Application use super::xml::{XmlNodeTree, XmlNodeKey, AttributeValue, AttributeValueVec, AttributeValueType}; use crate::{Error, error, String, ArcStr, Vec, Box, Rc, HashMap, LiteMap, DEFAULT_FONT_NAME}; use super::visual::{Pixels, Position, Size, write_framebuffer, constrain, Texture as _}; use super::style::{Theme, Style, DEFAULT_STYLE}; use super::layout::{compute_layout, hit_test}; use super::node::{NodeTree, NodeKey, Mutator}; use super::state::{Namespace, root_ns}; use core::{time::Duration, ops::Deref}; use super::event::UserInputEvent; use super::text_edit::Cursor; use super::for_each_child; use super::rgb::RGBA8; use oakwood::{NodeKey as _}; use lmfu::json::{JsonFile, Value, Path, parse_path}; use super::glyph::FONT_MUTATOR; use crate::builtin::{ inflate::INFLATE_MUTATOR, import::IMPORT_MUTATOR, png::PNG_MUTATOR, container::CONTAINERS, label::LABEL_MUTATOR, paragraph::{PARAGRAPH_MUTATOR, UNBREAKABLE_MUTATOR}, }; #[cfg(doc)] use super::node::Node; /// General-purpose callbacks that containers can call based on their attributes. pub type SimpleCallback = fn(&mut Application, NodeKey) -> Result<(), Error>; /// General-purpose callbacks that containers can call based on their attributes. pub type SimpleCallbackMap = HashMap; struct Request { asset: ArcStr, parse: bool, origin: NodeKey, } enum Asset { Parsed, Raw(Rc<[u8]>), } pub struct DebuggingOptions { pub skip_glyph_rendering: bool, pub skip_container_decoration: bool, pub freeze_layout: bool, pub draw_layout: bool, } /// A Singleton which represents your application. /// /// Its content includes: /// - the list of [`Mutator`]s /// - the XML layout /// - the JSON state and related triggers /// - the internal view representation (a Node tree) /// - the [`Theme`] /// - a cache of assets pub struct Application { pub root: NodeKey, pub view: NodeTree, pub xml_tree: XmlNodeTree, pub theme: Theme, pub callbacks: SimpleCallbackMap, pub debug: DebuggingOptions, pub state: JsonFile, pub(crate) namespaces: LiteMap, pub(crate) mutators: Vec, pub(crate) text_cursors: Vec, implicit_focus: NodeKey, focus_coords: Position, explicit_focus: Option, must_check_layout: bool, _source_files: Vec, _age: Duration, render_list: Vec<(Position, Size)>, assets: HashMap, requests: Vec, } pub const IMPORT_MUTATOR_INDEX: usize = 0; pub const FONT_MUTATOR_INDEX: usize = 1; pub const UNBREAKABLE_MUTATOR_INDEX: usize = 5; impl Application { /// Main constructor pub fn new(layout_asset: ArcStr, callbacks: SimpleCallbackMap) -> Self { let default_mutators = &[ IMPORT_MUTATOR, FONT_MUTATOR, PNG_MUTATOR, LABEL_MUTATOR, PARAGRAPH_MUTATOR, UNBREAKABLE_MUTATOR, INFLATE_MUTATOR, ]; assert_eq!(default_mutators[IMPORT_MUTATOR_INDEX].name, "ImportMutator"); assert_eq!(default_mutators[FONT_MUTATOR_INDEX].name, "FontMutator"); assert_eq!(default_mutators[UNBREAKABLE_MUTATOR_INDEX].name, "UnbreakableMutator"); let mut mutators = Vec::with_capacity(default_mutators.len() + CONTAINERS.len()); mutators.extend_from_slice(default_mutators); mutators.extend_from_slice(&CONTAINERS); let mut app = Self { root: Default::default(), view: NodeTree::new(), xml_tree: XmlNodeTree::new(), state: JsonFile::new(Some(include_str!("default.json"))).unwrap(), namespaces: LiteMap::new(), // monitors: LiteMap::new(), callbacks, mutators, must_check_layout: false, _source_files: Vec::new(), text_cursors: Vec::new(), focus_coords: Position::zero(), implicit_focus: Default::default(), explicit_focus: None, theme: Theme::parse(include_str!("default-theme.json")).unwrap(), _age: Duration::from_secs(0), render_list: Vec::new(), debug: DebuggingOptions { skip_glyph_rendering: false, skip_container_decoration: false, freeze_layout: false, draw_layout: false, }, assets: HashMap::new(), requests: Vec::new(), }; for i in 0..app.mutators.len() { (app.mutators[i].handlers.initializer)(&mut app, i.into()).unwrap(); } if true { let default_font = crate::NOTO_SANS.to_vec().into_boxed_slice(); let font_parser = app.mutators[FONT_MUTATOR_INDEX].handlers.parser; font_parser( &mut app, FONT_MUTATOR_INDEX.into(), Default::default(), &DEFAULT_FONT_NAME, default_font, ).unwrap(); app.assets.insert((*DEFAULT_FONT_NAME).clone(), Asset::Parsed); } let factory = Some(IMPORT_MUTATOR_INDEX.into()).into(); let xml_root = app.xml_tree.create(); app.xml_tree[xml_root].factory = factory; app.xml_tree[xml_root].attributes = AttributeValueVec::new_import(layout_asset); app.root = app.view.create(); app.view[app.root].factory = factory; app.view[app.root].xml_node_index = Some(xml_root.index().into()).into(); app.namespaces.insert(app.root, root_ns()); app.implicit_focus = app.root; app.call_populator(app.root, xml_root).unwrap(); app } fn node_path(&self, mut node_key: NodeKey) -> Vec { let mut path = Vec::new(); while let Some(parent) = self.view.parent(node_key) { let index = self.view.child_index(node_key).unwrap(); path.push(index); node_key = parent; } path } fn resolve_path(&self, mut path: Vec) -> NodeKey { let mut node_key = self.root; while let Some(index) = path.pop() { node_key = self.view.first_child(node_key).unwrap(); for _ in 0..index { node_key = self.view.next_sibling(node_key); } } node_key } /// Reload the view, allowing it to pick up state changes pub fn reload_view(&mut self) { let backup = &self.view[self.root]; let factory = backup.factory; let xml_node_index = backup.xml_node_index; let exf = self.explicit_focus.map(|nk| self.node_path(nk)); let imf = self.node_path(self.implicit_focus); self.view.reset(self.root); self.invalidate_layout(); let root_ns = self.namespaces.remove(&self.root).unwrap(); self.namespaces.clear(); self.namespaces.insert(self.root, root_ns); let root = &mut self.view[self.root]; root.factory = factory; root.xml_node_index = xml_node_index; if let Some(xml_root) = xml_node_index.get() { let xml_root = self.xml_tree.node_key(xml_root); self.call_populator(self.root, xml_root).unwrap(); } self.explicit_focus = exf.map(|p| self.resolve_path(p)); self.implicit_focus = self.resolve_path(imf); } /// Quick way to tell the application to recompute its layout before the next frame pub fn invalidate_layout(&mut self) { self.must_check_layout = true; } /// Read an asset from the internal cache pub fn get_asset(&self, asset: &ArcStr) -> Result, Error> { match self.assets.get(asset) { Some(Asset::Raw(rc)) => Ok(rc.clone()), Some(Asset::Parsed) => Err(error!("Asset {} was stored in mutator storage", asset.deref())), None => Err(error!("Asset {} was not found", asset.deref())), } } /// Platforms use this method to read the next asset to load. pub fn requested(&self) -> Option { self.requests.first().map(|r| r.asset.clone()) } /// Notify the system that an asset is required by some [`Node`] /// /// If `asset` is already loaded, this will trigger /// Handling of an `AssetLoaded` event immediately pub fn request(&mut self, asset: &ArcStr, origin: NodeKey, parse: bool) -> Result<(), Error> { if let Some(content) = self.assets.get(&asset) { let illegal = match (parse, content) { (true, Asset::Raw(_)) => true, (false, Asset::Parsed) => true, _ => false, }; if illegal { return Err(error!("Asset {} was previously loaded with a different `parse` flag", asset.deref())); } self.call_finalizer(origin) } else { self.requests.push(Request { asset: asset.clone(), origin, parse, }); Ok(()) } } /// Platforms use this method to deliver an asset's content pub fn data_response(&mut self, asset: ArcStr, data: Box<[u8]>) -> Result<(), Error> { let mut data = Some(data); let mut i = 0; while let Some(request) = self.requests.get(i) { if request.asset == asset { let request = self.requests.swap_remove(i); let node_key = request.origin; if let Some(data) = data.take() { let result = if request.parse { self.call_parser(node_key, &asset, data)?; Asset::Parsed } else { Asset::Raw(data.into()) }; self.assets.insert(asset.clone(), result); } self.request(&asset, request.origin, request.parse)?; } else { i += 1; } } Ok(()) } /// Retrieves the style that the nearest parent of `node_key` has set. pub fn get_inherited_style(&self, node_key: NodeKey) -> Result { let mut parent_style = match self.theme.resolve(DEFAULT_STYLE) { Some(style_index) => Ok(style_index), None => Err(error!("Missing default style in theme")), }?; let mut current = node_key; while let Some(parent) = self.view.parent(current) { if let Some(p_style) = self.view[parent].style_override.get() { parent_style = p_style.into(); break; } else { current = parent; } } Ok(self.theme.get(parent_style)) } /// Retrieves a value from the JSON state pub fn resolve( &self, node: NodeKey, ns_name: &str, ns_path: &str, ) -> Result { let mut target = node; loop { match self.namespaces.get(&target) { Some(ns) if &*ns.name == ns_name => { let mut jp = ns.path.clone(); (ns.callback)(&self, target, node, &mut jp)?; jp.append(parse_path(ns_path)); break Ok(jp); }, _ => match self.view.parent(target) { Some(parent) => target = parent, None => break Err(error!("Missing {} namespace", ns_name)), }, } } } /// Retrieves the XML tag name of a node /// /// This can return the following special strings: /// - `` if the node wasn't created from an XML tag /// - `` if the node's [`Mutator`] has no defined XML tag pub fn xml_tag(&self, node: NodeKey) -> ArcStr { let mutator_index = match self.view[node].factory.get() { Some(index) => index, None => return "".into(), }; let mutator = &self.mutators[usize::from(mutator_index)]; match &mutator.xml_params { Some(params) => params.tag_name.clone(), None => "".into(), } } #[doc(hidden)] pub fn attr_state_path(&mut self, node: NodeKey, attr: usize) -> Result, Error> { let xml_node_index = self.view[node].xml_node_index.get() .expect("cannot use Application::attr on nodes without xml_node_index"); let xml_node_key = self.xml_tree.node_key(xml_node_index); let xml_node = &self.xml_tree[xml_node_key]; let (namespace, path, value_type) = match xml_node.attributes.get(attr).clone() { AttributeValue::StateLookup { namespace, path, value_type } => (namespace, path, value_type), value => return Ok(Err(value)), }; Ok(Ok((self.resolve(node, &namespace, path.deref())?, value_type))) } /// Retrieves the value of an XML attribute, resolving optional JSON state dependencies. /// /// # Attribute syntax /// /// ## Immediate syntax /// /// `` /// /// Here, the `file` attribute will contain the string `acrylic.png` /// /// ## JSON state dependency /// /// `