dash-frontend: tab titles, home screen username

This commit is contained in:
Aleksander
2025-09-20 16:54:16 +02:00
parent 1358af75e3
commit 4f71dc6097
22 changed files with 181 additions and 45 deletions

View File

@@ -27,7 +27,13 @@
<!-- left/right separator (menu and rest) -->
<div flex_direction="row" gap="8" width="100%" height="100%">
<!-- LEFT MENU -->
<div id="menu" width="~size_size" min_width="~side_size" max_width="~side_size" height="100%" align_items="center" justify_content="center">
<div id="menu"
width="~size_size"
min_width="~side_size"
max_width="~side_size"
height="100%"
align_items="center"
justify_content="center">
<rectangle
width="100%"
round="100%"
@@ -48,21 +54,34 @@
</div>
<!-- REST -->
<!-- content/bottom panel separator -->
<div flex_direction="column" gap="8" flex_grow="1">
<div
flex_direction="column"
gap="8"
width="100%"
height="100%"
overflow_x="scroll">
<!-- CONTENT -->
<rectangle
color2="#0d131acc" color="#244179aa" gradient="vertical" round="8" overflow_y="hidden"
justify_content="center"
color2="#0d131acc"
color="#244179aa"
gradient="vertical"
round="8"
flex_grow="1"
width="100%"
overflow_y="scroll"
>
<div
id="content"
overflow_x="scroll"
overflow_y="scroll"
align_items="center"
justify_content="center"
flex_direction="column"
gap="24"
padding_top="8"
padding_bottom="8"
padding_left="16"
padding_right="16"
gap="8"
width="100%"
min_height="100%"
>
<!-- filled-in at runtime -->
</div>

View File

@@ -16,7 +16,7 @@
justify_content="center"
flex_direction="column">
<sprite src="${icon}" width="32" height="32" />
<label weight="bold" size="18" text="${text}" />
<label weight="bold" size="18" text="${text}" translation="${translation}" />
</div>
</Button>
</template>

View File

@@ -1,7 +1,7 @@
<layout>
<include src="t_tab_title.xml" />
<elements>
<label text="Apps" size="64" weight="bold" />
<label text="bottom text" size="16" weight="bold" color="#FFFFFF88" />
<TabTitle translation="APPLICATIONS" icon="dashboard/apps.svg" />
</elements>
</layout>

View File

@@ -1,5 +1,7 @@
<layout>
<include src="t_tab_title.xml" />
<elements>
<label text="Games" size="32" weight="bold" />
<TabTitle translation="GAMES" icon="dashboard/games.svg" />
</elements>
</layout>

View File

@@ -2,17 +2,23 @@
<include src="../t_menu_button.xml" />
<elements>
<sprite src="dashboard/wayvr_dashboard.svg" min_width="96" min_height="96" />
<label text="Hello, user!" size="32" weight="bold" />
<label text="Connected to wlx-overlay-s" size="16" weight="bold" color="#bbffbb" />
<div
flex_direction="column"
justify_content="center"
align_items="center"
flex_grow="1"
gap="24">
<sprite src="dashboard/wayvr_dashboard.svg" width="96" height="96" />
<label id="label_hello" size="32" weight="bold" />
<!-- main button list -->
<div flex_direction="row" gap="8" margin_top="32">
<MenuButton id="btn_apps" icon="dashboard/apps.svg" text="Apps" />
<MenuButton id="btn_games" icon="dashboard/games.svg" text="Games" />
<MenuButton id="btn_monado" icon="dashboard/monado.svg" text="Monado" />
<MenuButton id="btn_processes" icon="dashboard/window.svg" text="Processes" />
<MenuButton id="btn_settings" icon="dashboard/settings.svg" text="Settings" />
<!-- main button list -->
<div flex_direction="row" gap="8">
<MenuButton id="btn_apps" icon="dashboard/apps.svg" translation="APPLICATIONS" />
<MenuButton id="btn_games" icon="dashboard/games.svg" translation="GAMES" />
<MenuButton id="btn_monado" icon="dashboard/monado.svg" text="Monado" />
<MenuButton id="btn_processes" icon="dashboard/window.svg" translation="PROCESSES" />
<MenuButton id="btn_settings" icon="dashboard/settings.svg" translation="SETTINGS" />
</div>
</div>
</elements>
</layout>

View File

@@ -1,5 +1,7 @@
<layout>
<include src="t_tab_title.xml" />
<elements>
<label text="Monado" size="32" weight="bold" />
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
</elements>
</layout>

View File

@@ -1,5 +1,7 @@
<layout>
<include src="t_tab_title.xml" />
<elements>
<label text="Processes" size="32" weight="bold" />
<TabTitle translation="PROCESSES" icon="dashboard/window.svg" />
</elements>
</layout>

View File

@@ -1,5 +1,7 @@
<layout>
<include src="t_tab_title.xml" />
<elements>
<label text="Settings" size="32" weight="bold" />
<TabTitle translation="SETTINGS" icon="dashboard/settings.svg" />
</elements>
</layout>

View File

@@ -0,0 +1,9 @@
<layout>
<!-- translation, icon -->
<template name="TabTitle">
<div gap="8" align_items="center">
<sprite src="${icon}" width="24" height="24" />
<label translation="${translation}" size="18" weight="bold" />
</div>
</template>
</layout>

View File

@@ -0,0 +1,9 @@
{
"HOME_SCREEN": "Startbildschirm",
"MONADO_RUNTIME": "„Monado”-Laufzeitumgebung",
"APPLICATIONS": "Anwendungen",
"GAMES": "Spiele",
"SETTINGS": "Einstellungen",
"PROCESSES": "Prozesse",
"HELLO_USER": "Hallo, {USER}!"
}

View File

@@ -1 +1,9 @@
{}
{
"HOME_SCREEN": "Home",
"MONADO_RUNTIME": "„Monado” runtime",
"APPLICATIONS": "Applications",
"GAMES": "Games",
"SETTINGS": "Settings",
"PROCESSES": "Processes",
"HELLO_USER": "Hello, {USER}!"
}

View File

@@ -0,0 +1,9 @@
{
"HOME_SCREEN": "Inicio",
"MONADO_RUNTIME": "„Monado” tiempo de ejecución",
"APPLICATIONS": "Aplicaciones",
"GAMES": "Juegos",
"SETTINGS": "Ajustes",
"PROCESSES": "Procesos",
"HELLO_USER": "¡Hola, {USER}!"
}

View File

@@ -0,0 +1,9 @@
{
"HOME_SCREEN": "ホーム",
"MONADO_RUNTIME": "「Monado」ランタイム",
"APPLICATIONS": "アプリケーション",
"GAMES": "ゲーム",
"SETTINGS": "設定",
"PROCESSES": "プロセス",
"HELLO_USER": "こんにちは、{USER}"
}

View File

@@ -0,0 +1,9 @@
{
"HOME_SCREEN": "Ekran główny",
"MONADO_RUNTIME": "„Monado” środowisko uruchomieniowe",
"APPLICATIONS": "Aplikacje",
"GAMES": "Gry",
"SETTINGS": "Ustawienia",
"PROCESSES": "Procesy",
"HELLO_USER": "Witaj, {USER}!"
}

View File

@@ -20,6 +20,7 @@ use crate::tab::{
mod assets;
mod tab;
mod various;
pub struct Frontend {
pub layout: RcLayout,

View File

@@ -1,9 +1,14 @@
use wgui::{
components::button::ComponentButton,
i18n::Translation,
parser::{ParseDocumentParams, ParserState},
widget::label::WidgetLabel,
};
use crate::tab::{Tab, TabParams, TabType};
use crate::{
tab::{Tab, TabParams, TabType},
various,
};
pub struct TabHome {
#[allow(dead_code)]
@@ -16,6 +21,18 @@ impl Tab for TabHome {
}
}
fn configure_label_hello(label_hello: &mut WidgetLabel, i18n: &mut wgui::i18n::I18n) {
let mut username = various::get_username();
// first character as uppercase
if let Some(first) = username.chars().next() {
let first = first.to_uppercase().to_string();
username.replace_range(0..1, &first);
}
let translated = i18n.translate_and_replace("HELLO_USER", ("{USER}", &username));
label_hello.set_text_simple(i18n, Translation::from_raw_text(&translated));
}
impl TabHome {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
@@ -29,6 +46,9 @@ impl TabHome {
params.parent_id,
)?;
let mut label_hello = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_hello")?;
configure_label_hello(&mut label_hello, &mut params.globals.i18n());
let btn_apps = state.fetch_component_as::<ComponentButton>("btn_apps")?;
let btn_games = state.fetch_component_as::<ComponentButton>("btn_games")?;
let btn_monado = state.fetch_component_as::<ComponentButton>("btn_monado")?;

View File

@@ -0,0 +1,6 @@
pub fn get_username() -> String {
match std::env::var("USER") {
Ok(user) => user,
Err(_) => String::from("anonymous"),
}
}

View File

@@ -19,7 +19,7 @@ if (template_name === undefined) {
}
if (lang_path === undefined) {
console.log("LANG_PATH not set. Try \"LANG_PATH=../../uidev/assets/lang/ ./run.sh\"");
console.log("LANG_PATH is not set. Try one of these:\n\nLANG_PATH=../../uidev/assets/lang/ ./run.sh\nLANG_PATH=../../dash-frontend/assets/lang/ ./run.sh\n");
exit(-1);
}

View File

@@ -77,10 +77,7 @@ impl I18n {
match lang.as_str() {
"en" | "pl" | "it" | "ja" | "es" => {}
_ => {
log::warn!(
"Unsupported language \"{}\", defaulting to \"en\".",
lang.as_str()
);
log::warn!("Unsupported language \"{}\", defaulting to \"en\".", lang.as_str());
lang = String::from("en");
}
@@ -111,4 +108,9 @@ impl I18n {
log::error!("missing translation for key \"{translation_key}\"");
Rc::from(translation_key) // show translation key as a fallback
}
pub fn translate_and_replace(&mut self, translation_key: &str, to_replace: (&str, &str)) -> String {
let translated = self.translate(translation_key);
translated.replace(to_replace.0, to_replace.1)
}
}

View File

@@ -68,11 +68,11 @@ pub struct ParserState {
impl ParserState {
pub 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 \"{}\" doesn't exist", id);
anyhow::bail!("Component by ID \"{id}\" doesn't exist");
};
let Some(component) = weak.upgrade() else {
anyhow::bail!("Component by ID \"{}\" doesn't exist", id);
anyhow::bail!("Component by ID \"{id}\" doesn't exist");
};
Ok(Component(component))
@@ -80,11 +80,11 @@ impl ParserState {
pub 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 \"{:?}\" doesn't exist", widget_id);
anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist");
};
let Some(component) = weak.upgrade() else {
anyhow::bail!("Component by widget ID \"{:?}\" doesn't exist", widget_id);
anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist");
};
Ok(Component(component))
@@ -94,7 +94,7 @@ impl ParserState {
let component = self.fetch_component_by_id(id)?;
if !(*component.0).as_any().is::<T>() {
anyhow::bail!("fetch_component_as({}): type not matching", id);
anyhow::bail!("fetch_component_as({id}): type not matching");
}
// safety: we already checked it above, should be safe to directly cast it
@@ -105,7 +105,7 @@ impl ParserState {
let component = self.fetch_component_by_widget_id(widget_id)?;
if !(*component.0).as_any().is::<T>() {
anyhow::bail!("fetch_component_by_widget_id({:?}): type not matching", widget_id);
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
@@ -115,22 +115,37 @@ impl ParserState {
pub 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 \"{}\" doesn't exist", 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> {
let widget_id = self.get_widget_id(id)?;
let widget = state
.widgets
.get(widget_id)
.ok_or_else(|| anyhow::anyhow!("fetch_widget({}): widget not found", id))?;
.ok_or_else(|| anyhow::anyhow!("fetch_widget({id}): widget not found"))?;
Ok(WidgetPair {
id: widget_id,
widget: widget.clone(),
})
}
pub 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
.get(widget_id)
.ok_or_else(|| anyhow::anyhow!("fetch_widget_as({id}): widget not found"))?;
let casted = widget
.get_as_mut::<T>()
.ok_or_else(|| anyhow::anyhow!("fetch_widget_as({id}): failed to cast"))?;
Ok(casted)
}
pub fn process_template<U1, U2>(
&mut self,
doc_params: &ParseDocumentParams,
@@ -141,7 +156,7 @@ impl ParserState {
template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<()> {
let Some(template) = self.templates.get(template_name) else {
anyhow::bail!("no template named \"{}\" found", template_name);
anyhow::bail!("no template named \"{template_name}\" found");
};
let mut ctx = ParserContext {
@@ -231,7 +246,7 @@ fn get_tag_by_name<'a>(node: &roxmltree::Node<'a, 'a>, name: &str) -> Option<rox
}
fn require_tag_by_name<'a>(node: &roxmltree::Node<'a, 'a>, name: &str) -> anyhow::Result<roxmltree::Node<'a, 'a>> {
get_tag_by_name(node, name).ok_or_else(|| anyhow::anyhow!("Tag \"{}\" not found", name))
get_tag_by_name(node, name).ok_or_else(|| anyhow::anyhow!("Tag \"{name}\" not found"))
}
fn print_invalid_attrib(key: &str, value: &str) {

View File

@@ -23,9 +23,15 @@ pub fn parse_widget_label<'a, U1, U2>(
for (key, value) in attribs {
match &*key {
"text" => {
params.content = Translation::from_raw_text(&value);
if !value.is_empty() {
params.content = Translation::from_raw_text(&value);
}
}
"translation" => {
if !value.is_empty() {
params.content = Translation::from_translation_key(&value);
}
}
"translation" => params.content = Translation::from_translation_key(&value),
_ => {}
}
}

View File

@@ -25,7 +25,7 @@
<template name="Set">
<Button macro="button_style" _press="::OverlayToggle ${handle}">
<sprite width="40" height="40" color="~set_color" src="watch/set2.svg" />
<div position="absolute" margin_top="11" >
<div position="absolute" margin_top="11">
<label text="${display}" size="24" color="#000000" weight="bold" />
</div>
</Button>
@@ -74,4 +74,4 @@
</rectangle>
</div>
</elements>
</layout>
</layout>