dash-frontend: Application list
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use taffy::{AlignItems, JustifyContent, prelude::length};
|
||||
use taffy::{AlignItems, JustifyContent};
|
||||
|
||||
use crate::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct Params {
|
||||
pub text: Translation,
|
||||
pub text: Option<Translation>, // if unset, label will not be populated
|
||||
pub color: Option<drawing::Color>,
|
||||
pub border_color: Option<drawing::Color>,
|
||||
pub hover_border_color: Option<drawing::Color>,
|
||||
@@ -31,7 +31,7 @@ pub struct Params {
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: Translation::from_raw_text(""),
|
||||
text: Some(Translation::from_raw_text("")),
|
||||
color: None,
|
||||
hover_color: None,
|
||||
border_color: None,
|
||||
@@ -232,7 +232,6 @@ pub fn construct<U1, U2>(
|
||||
// force-override style
|
||||
style.align_items = Some(AlignItems::Center);
|
||||
style.justify_content = Some(JustifyContent::Center);
|
||||
style.padding = length(1.0);
|
||||
style.overflow.x = taffy::Overflow::Hidden;
|
||||
style.overflow.y = taffy::Overflow::Hidden;
|
||||
|
||||
@@ -277,25 +276,30 @@ pub fn construct<U1, U2>(
|
||||
|
||||
let light_text = (color.r + color.g + color.b) < 1.5;
|
||||
|
||||
let (id_label, _node_label) = layout.add_child(
|
||||
id_rect,
|
||||
WidgetLabel::create(
|
||||
globals,
|
||||
WidgetLabelParams {
|
||||
content: params.text,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
color: Some(if light_text {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::new(0.0, 0.0, 0.0, 1.0)
|
||||
}),
|
||||
..params.text_style
|
||||
let id_label = if let Some(content) = params.text {
|
||||
let (id_label, _node_label) = layout.add_child(
|
||||
id_rect,
|
||||
WidgetLabel::create(
|
||||
globals,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
color: Some(if light_text {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::new(0.0, 0.0, 0.0, 1.0)
|
||||
}),
|
||||
..params.text_style
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
id_label
|
||||
} else {
|
||||
WidgetID::default()
|
||||
};
|
||||
|
||||
let data = Rc::new(Data {
|
||||
id_label,
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn parse_component_button<'a, U1, U2>(
|
||||
let mut hover_color: Option<Color> = None;
|
||||
let mut hover_border_color: Option<Color> = None;
|
||||
let mut round = WLength::Units(4.0);
|
||||
let mut translation = Translation::default();
|
||||
let mut translation: Option<Translation> = None;
|
||||
|
||||
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
|
||||
let text_style = parse_text_style(&attribs);
|
||||
@@ -30,10 +30,10 @@ pub fn parse_component_button<'a, U1, U2>(
|
||||
for (key, value) in attribs {
|
||||
match key.as_ref() {
|
||||
"text" => {
|
||||
translation = Translation::from_raw_text(&value);
|
||||
translation = Some(Translation::from_raw_text(&value));
|
||||
}
|
||||
"translation" => {
|
||||
translation = Translation::from_translation_key(&value);
|
||||
translation = Some(Translation::from_translation_key(&value));
|
||||
}
|
||||
"round" => {
|
||||
parse_round(&value, &mut round);
|
||||
|
||||
@@ -49,6 +49,11 @@ struct ParserFile {
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
}
|
||||
|
||||
/*
|
||||
`components` could contain connected listener handles.
|
||||
Do not drop them unless you don't need to handle any events,
|
||||
including mouse-hover animations.
|
||||
*/
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ParserData {
|
||||
pub components_by_id: HashMap<Rc<str>, ComponentWeak>,
|
||||
@@ -60,6 +65,29 @@ pub struct ParserData {
|
||||
macro_attribs: HashMap<Rc<str>, MacroAttribs>,
|
||||
}
|
||||
|
||||
pub trait Fetchable {
|
||||
/// Return a component by its string ID
|
||||
fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component>;
|
||||
|
||||
/// Return a component by the ID of the widget that owns it
|
||||
fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component>;
|
||||
|
||||
/// Fetch a component by string ID and down‑cast it to a concrete component type `T` (see `components/mod.rs`)
|
||||
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>>;
|
||||
|
||||
/// Fetch a component by widget ID and down‑cast it to a concrete component type `T` (see `components/mod.rs`)
|
||||
fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>>;
|
||||
|
||||
/// Return a widget by its string ID
|
||||
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID>;
|
||||
|
||||
/// Retrieve the widget associated with a string ID, returning a `WidgetPair` (id and widget itself)
|
||||
fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair>;
|
||||
|
||||
/// Retrieve a widget by string ID and down‑cast its inner value to type `T` (see `widget/mod.rs`)
|
||||
fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>>;
|
||||
}
|
||||
|
||||
impl ParserData {
|
||||
fn take_results_from(&mut self, from: &mut Self) {
|
||||
let ids = std::mem::take(&mut from.ids);
|
||||
@@ -85,19 +113,9 @@ impl ParserData {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
WARNING: this struct could contain valid components with already bound listener handles.
|
||||
Make sure to store them somewhere in your code.
|
||||
*/
|
||||
#[derive(Default)]
|
||||
pub struct ParserState {
|
||||
pub data: ParserData,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl ParserState {
|
||||
pub fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
|
||||
let Some(weak) = self.data.components_by_id.get(id) else {
|
||||
impl Fetchable for ParserData {
|
||||
fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
|
||||
let Some(weak) = self.components_by_id.get(id) else {
|
||||
anyhow::bail!("Component by ID \"{id}\" doesn't exist");
|
||||
};
|
||||
|
||||
@@ -108,8 +126,8 @@ impl ParserState {
|
||||
Ok(Component(component))
|
||||
}
|
||||
|
||||
pub fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
|
||||
let Some(weak) = self.data.components_by_widget_id.get(&widget_id) else {
|
||||
fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
|
||||
let Some(weak) = self.components_by_widget_id.get(&widget_id) else {
|
||||
anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist");
|
||||
};
|
||||
|
||||
@@ -120,37 +138,36 @@ impl ParserState {
|
||||
Ok(Component(component))
|
||||
}
|
||||
|
||||
pub fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>> {
|
||||
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>> {
|
||||
let component = self.fetch_component_by_id(id)?;
|
||||
|
||||
if !(*component.0).as_any().is::<T>() {
|
||||
anyhow::bail!("fetch_component_as({id}): type not matching");
|
||||
}
|
||||
|
||||
// safety: we already checked it above, should be safe to directly cast it
|
||||
// safety: we just checked the type
|
||||
unsafe { Ok(Rc::from_raw(Rc::into_raw(component.0).cast())) }
|
||||
}
|
||||
|
||||
pub fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>> {
|
||||
fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>> {
|
||||
let component = self.fetch_component_by_widget_id(widget_id)?;
|
||||
|
||||
if !(*component.0).as_any().is::<T>() {
|
||||
anyhow::bail!("fetch_component_by_widget_id({widget_id:?}): type not matching");
|
||||
}
|
||||
|
||||
// safety: we already checked it above, should be safe to directly cast it
|
||||
// safety: we just checked the type
|
||||
unsafe { Ok(Rc::from_raw(Rc::into_raw(component.0).cast())) }
|
||||
}
|
||||
|
||||
pub fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
|
||||
match self.data.ids.get(id) {
|
||||
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
|
||||
match self.ids.get(id) {
|
||||
Some(id) => Ok(*id),
|
||||
None => anyhow::bail!("Widget by ID \"{id}\" doesn't exist"),
|
||||
}
|
||||
}
|
||||
|
||||
// returns widget and its id at once
|
||||
pub fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair> {
|
||||
fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair> {
|
||||
let widget_id = self.get_widget_id(id)?;
|
||||
let widget = state
|
||||
.widgets
|
||||
@@ -162,7 +179,7 @@ impl ParserState {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>> {
|
||||
fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>> {
|
||||
let widget_id = self.get_widget_id(id)?;
|
||||
let widget = state
|
||||
.widgets
|
||||
@@ -175,8 +192,23 @@ impl ParserState {
|
||||
|
||||
Ok(casted)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_template<U1, U2>(
|
||||
/*
|
||||
WARNING: this struct could contain valid components with already bound listener handles.
|
||||
Make sure to store them somewhere in your code.
|
||||
*/
|
||||
#[derive(Default)]
|
||||
pub struct ParserState {
|
||||
pub data: ParserData,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl ParserState {
|
||||
/// This function is suitable in cases if you don't want to pollute main parser state with dynamic IDs
|
||||
/// Use `instantiate_template` instead unless you want to handle `components` results yourself.
|
||||
/// Make sure not to drop them if you want to have your listener handles valid
|
||||
pub fn parse_template<U1, U2>(
|
||||
&mut self,
|
||||
doc_params: &ParseDocumentParams,
|
||||
template_name: &str,
|
||||
@@ -184,7 +216,7 @@ impl ParserState {
|
||||
listeners: &mut EventListenerCollection<U1, U2>,
|
||||
widget_id: WidgetID,
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<ParserData> {
|
||||
let Some(template) = self.data.templates.get(template_name) else {
|
||||
anyhow::bail!("no template named \"{template_name}\" found");
|
||||
};
|
||||
@@ -204,13 +236,64 @@ impl ParserState {
|
||||
};
|
||||
|
||||
parse_widget_other_internal(&template.clone(), template_parameters, &file, &mut ctx, widget_id)?;
|
||||
Ok(ctx.data_local)
|
||||
}
|
||||
|
||||
self.data.take_results_from(&mut ctx.data_local);
|
||||
/// Instantinate template by saving all the results into the main `ParserState`
|
||||
pub fn instantiate_template<U1, U2>(
|
||||
&mut self,
|
||||
doc_params: &ParseDocumentParams,
|
||||
template_name: &str,
|
||||
layout: &mut Layout,
|
||||
listeners: &mut EventListenerCollection<U1, U2>,
|
||||
widget_id: WidgetID,
|
||||
template_parameters: HashMap<Rc<str>, Rc<str>>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut data_local = self.parse_template(
|
||||
doc_params,
|
||||
template_name,
|
||||
layout,
|
||||
listeners,
|
||||
widget_id,
|
||||
template_parameters,
|
||||
)?;
|
||||
|
||||
self.data.take_results_from(&mut data_local);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// convenience wrapper functions for `data`
|
||||
impl Fetchable for ParserState {
|
||||
fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
|
||||
self.data.fetch_component_by_id(id)
|
||||
}
|
||||
|
||||
fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
|
||||
self.data.fetch_component_by_widget_id(widget_id)
|
||||
}
|
||||
|
||||
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>> {
|
||||
self.data.fetch_component_as(id)
|
||||
}
|
||||
|
||||
fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>> {
|
||||
self.data.fetch_component_from_widget_id_as(widget_id)
|
||||
}
|
||||
|
||||
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
|
||||
self.data.get_widget_id(id)
|
||||
}
|
||||
|
||||
fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair> {
|
||||
self.data.fetch_widget(state, id)
|
||||
}
|
||||
|
||||
fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>> {
|
||||
self.data.fetch_widget_as(state, id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct MacroAttribs {
|
||||
attribs: HashMap<Rc<str>, Rc<str>>,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::{
|
||||
layout::WidgetID,
|
||||
parser::{
|
||||
ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal,
|
||||
style::parse_style,
|
||||
},
|
||||
parser::{ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal, style::parse_style},
|
||||
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
||||
widget::sprite::{WidgetSprite, WidgetSpriteParams},
|
||||
};
|
||||
@@ -24,17 +21,18 @@ pub fn parse_widget_sprite<'a, U1, U2>(
|
||||
for (key, value) in attribs {
|
||||
match key.as_ref() {
|
||||
"src" => {
|
||||
glyph =
|
||||
match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), &value) {
|
||||
if !value.is_empty() {
|
||||
glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), &value) {
|
||||
Ok(glyph) => Some(glyph),
|
||||
Err(e) => {
|
||||
log::warn!("failed to load {value}: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"src_ext" => {
|
||||
if std::fs::exists(value.as_ref()).unwrap_or(false) {
|
||||
if !value.is_empty() && std::fs::exists(value.as_ref()).unwrap_or(false) {
|
||||
glyph = CustomGlyphContent::from_file(&value).ok();
|
||||
}
|
||||
}
|
||||
@@ -55,9 +53,7 @@ pub fn parse_widget_sprite<'a, U1, U2>(
|
||||
log::warn!("No source for sprite node!");
|
||||
}
|
||||
|
||||
let (new_id, _) = ctx
|
||||
.layout
|
||||
.add_child(parent_id, WidgetSprite::create(params), style)?;
|
||||
let (new_id, _) = ctx.layout.add_child(parent_id, WidgetSprite::create(params), style)?;
|
||||
|
||||
parse_widget_universal(file, ctx, node, new_id);
|
||||
parse_children(file, ctx, node, new_id)?;
|
||||
|
||||
@@ -160,6 +160,10 @@ pub struct Context {
|
||||
empty_text: Rc<RefCell<Buffer>>,
|
||||
}
|
||||
|
||||
pub struct ContextDrawResult {
|
||||
pub pass_count: u32,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(shared: &mut SharedContext, pixel_scale: f32) -> anyhow::Result<Self> {
|
||||
let viewport = Viewport::new(&shared.gfx)?;
|
||||
@@ -216,7 +220,7 @@ impl Context {
|
||||
shared: &mut SharedContext,
|
||||
cmd_buf: &mut GfxCommandBuffer,
|
||||
primitives: &[drawing::RenderPrimitive],
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<ContextDrawResult> {
|
||||
self.dirty = false;
|
||||
|
||||
let atlas = shared.atlas_map.get_mut(self.shared_ctx_key).unwrap();
|
||||
@@ -279,12 +283,14 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("count {}", passes.len());
|
||||
let res = ContextDrawResult {
|
||||
pass_count: passes.len() as u32,
|
||||
};
|
||||
|
||||
for mut pass in passes {
|
||||
pass.submit(&shared.gfx, &mut self.viewport, cmd_buf, &mut atlas.text_atlas)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user