Merge pull request #417 from wlx-team/staging
dash-frontend: UX improvements
This commit is contained in:
3
dash-frontend/assets/dashboard/arrow_left.svg
Normal file
3
dash-frontend/assets/dashboard/arrow_left.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
|
||||
<path fill="currentColor" d="m10 18l-6-6l6-6l1.4 1.45L7.85 11H20v2H7.85l3.55 3.55z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
3
dash-frontend/assets/dashboard/arrow_right.svg
Normal file
3
dash-frontend/assets/dashboard/arrow_right.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
|
||||
<path fill="currentColor" d="m14 18l-1.4-1.45L16.15 13H4v-2h12.15L12.6 7.45L14 6l6 6z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 297 B |
@@ -3,6 +3,7 @@
|
||||
|
||||
<theme>
|
||||
<var key="side_size" value="48" />
|
||||
<var key="top_size" value="40" />
|
||||
<var key="side_sprite_size" value="26" />
|
||||
<var key="side_button_size" value="48" />
|
||||
</theme>
|
||||
@@ -25,13 +26,6 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div position="absolute" width="100%" justify_content="space_between">
|
||||
<div />
|
||||
<div padding="6">
|
||||
<Button id="btn_close" sprite_src_builtin="dashboard/close.svg" color="#000000" border="2" border_color="~color_faded" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- left/right separator (menu and rest) -->
|
||||
<div flex_direction="row" gap="8" width="100%" height="100%" padding="4" interactable="0">
|
||||
<!-- LEFT MENU -->
|
||||
@@ -60,15 +54,29 @@
|
||||
</rectangle>
|
||||
</div>
|
||||
<!-- REST -->
|
||||
<!-- content/bottom panel separator -->
|
||||
<!-- top/content/bottom panel separator -->
|
||||
<div
|
||||
flex_direction="column"
|
||||
gap="8"
|
||||
width="100%"
|
||||
height="100%"
|
||||
overflow_x="scroll">
|
||||
<!-- CONTENT -->
|
||||
<!-- TOP PANEL -->
|
||||
<div position="relative" width="100%" height="~top_size" min_height="~top_size" max_height="~top_size" justify_content="end">
|
||||
<!-- Title bar -->
|
||||
<div width="100%" align_items="center" justify_content="center">
|
||||
<rectangle min_width="300" height="100%" consume_mouse_events="1" round="100%" color="~color_top_panel" align_items="center" justify_content="center" gap="8">
|
||||
<sprite id="sprite_titlebar_icon" width="24" height="24" />
|
||||
<label id="label_titlebar_title" weight="bold" size="16" text="Title" />
|
||||
</rectangle>
|
||||
</div>
|
||||
<!-- Close button -->
|
||||
<Button position="absolute" id="btn_close" color="~color_top_panel" round="100%" width="~top_size" height="~top_size" tooltip="CLOSE_WINDOW" tooltip_side="left">
|
||||
<sprite src_builtin="dashboard/close.svg" width="32" height="32" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<!-- color and color2 alpha will be modified at runtime -->
|
||||
<rectangle
|
||||
id="rect_content"
|
||||
@@ -90,10 +98,7 @@
|
||||
flex_direction="column"
|
||||
overflow_x="scroll"
|
||||
overflow_y="scroll"
|
||||
padding_top="8"
|
||||
padding_bottom="8"
|
||||
padding_left="16"
|
||||
padding_right="16"
|
||||
padding="16"
|
||||
gap="8"
|
||||
width="100%"
|
||||
min_height="100%"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../theme.xml" />
|
||||
|
||||
<template name="AppEntry">
|
||||
@@ -25,7 +24,6 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="APPLICATIONS" icon="dashboard/apps.svg" />
|
||||
<!-- placeholders for now -->
|
||||
<!--
|
||||
<div gap="4" align_items="center">
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="GAMES" icon="dashboard/games.svg" />
|
||||
<rectangle macro="group_box" id="running_games_list_parent" align_self="center" />
|
||||
<div id="game_list_parent" align_items="center" />
|
||||
<div id="running_games_list_parent" align_self="center" />
|
||||
<div id="game_list_parent" align_items="center" flex_direction="column" gap="8" overflow_y="scroll" />
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -1,8 +1,6 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
|
||||
<!-- key: str, value: str -->
|
||||
<template name="BoolFlag">
|
||||
<div flex_direction="row" gap="4">
|
||||
@@ -27,7 +25,6 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
|
||||
<label translation="DISPLAY_BRIGHTNESS" />
|
||||
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<layout>
|
||||
<include src="t_tab_title.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
<include src="../t_dropdown_button.xml" />
|
||||
|
||||
@@ -46,7 +45,6 @@
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<TabTitle translation="SETTINGS" icon="dashboard/settings.svg" />
|
||||
<div gap="4">
|
||||
<Tabs id="tabs">
|
||||
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<layout>
|
||||
<!-- translation, icon -->
|
||||
<template name="TabTitle">
|
||||
<div gap="8" align_items="center">
|
||||
<sprite src_builtin="${icon}" width="24" height="24" />
|
||||
<label translation="${translation}" size="18" weight="bold" />
|
||||
</div>
|
||||
</template>
|
||||
</layout>
|
||||
@@ -2,6 +2,7 @@
|
||||
<theme>
|
||||
<var key="color_menu_dark" value="#0A0A0ACC" />
|
||||
<var key="color_accent" value="#008cff" />
|
||||
<var key="color_top_panel" value="#00000099" />
|
||||
<var key="color_bottom_panel" value="#141e28" />
|
||||
</theme>
|
||||
</layout>
|
||||
@@ -1,7 +1,12 @@
|
||||
<layout>
|
||||
<elements>
|
||||
<div flex_direction="column">
|
||||
<div flex_direction="column" padding_top="8" padding_bottom="8">
|
||||
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" justify_content="center" />
|
||||
</div>
|
||||
<div align_items="center" justify_content="center" gap="16">
|
||||
<Button id="btn_prev" sprite_src_builtin="dashboard/arrow_left.svg" width="32" height="32" />
|
||||
<label id="label_page" text="Page X" weight="bold" />
|
||||
<Button id="btn_next" sprite_src_builtin="dashboard/arrow_right.svg" width="32" height="32" />
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -78,12 +78,16 @@
|
||||
"SCREENCOPY_HELP": "Langsam, keine Bildschirmfreigabe-Popups.\nFunktioniert mit: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Keine",
|
||||
"HMD_PINCH": "HMD + Kneifen",
|
||||
"EYE_PINCH": "Auge + Kneifen"
|
||||
"EYE_PINCH": "Auge + Kneifen",
|
||||
"EYE_ONLY": "Nur Auge",
|
||||
"HMD_ONLY": "Nur HMD"
|
||||
},
|
||||
"AUTOSTART_APPS": "Anwendungen, die beim Start ausgeführt werden sollen",
|
||||
"HANDSFREE_POINTER": "Freihändige Modus",
|
||||
"HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.",
|
||||
"UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität"
|
||||
"UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität",
|
||||
"RESET_PLAYSPACE": "Spielbereich zurücksetzen",
|
||||
"RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen."
|
||||
},
|
||||
"HELLO": "Hallo!",
|
||||
"AUDIO": {
|
||||
@@ -116,8 +120,7 @@
|
||||
"CLOSE_WINDOW": "Fenster schließen",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Keine Spiele gefunden",
|
||||
"RUNNING_GAMES_LIST": "Liste der laufenden Spiele",
|
||||
"NO_RUNNING_GAME_FOUND": "Kein laufendes Spiel gefunden"
|
||||
"RUNNING_GAMES_LIST": "Liste der laufenden Spiele"
|
||||
},
|
||||
"TERMINATE_PROCESS": "Prozess beenden",
|
||||
"GAME_LAUNCHED": "Spiel gestartet",
|
||||
|
||||
@@ -132,8 +132,7 @@
|
||||
"GAME_LAUNCHED": "Game launched",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "No games found",
|
||||
"RUNNING_GAMES_LIST": "List of running games",
|
||||
"NO_RUNNING_GAME_FOUND": "No running game found"
|
||||
"RUNNING_GAMES_LIST": "List of running games"
|
||||
},
|
||||
"GAMES": "Games",
|
||||
"GENERAL_SETTINGS": "General settings",
|
||||
|
||||
@@ -78,12 +78,16 @@
|
||||
"SCREENCOPY_HELP": "Lento, sin ventanas emergentes de uso compartido de pantalla.\nFunciona en: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Ninguno",
|
||||
"HMD_PINCH": "HMD + pellizco",
|
||||
"EYE_PINCH": "Ojo + pellizco"
|
||||
"EYE_PINCH": "Ojo + pellizco",
|
||||
"EYE_ONLY": "Solo ojo",
|
||||
"HMD_ONLY": "Solo HMD"
|
||||
},
|
||||
"AUTOSTART_APPS": "Aplicaciones a ejecutar al inicio",
|
||||
"HANDSFREE_POINTER": "Modo manos libres",
|
||||
"HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU"
|
||||
"UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU",
|
||||
"RESET_PLAYSPACE": "Restablecer espacio de juego",
|
||||
"RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego."
|
||||
},
|
||||
"HELLO": "¡Hola!",
|
||||
"AUDIO": {
|
||||
@@ -116,8 +120,7 @@
|
||||
"CLOSE_WINDOW": "Cerrar ventana",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "No se encontraron juegos",
|
||||
"RUNNING_GAMES_LIST": "Lista de juegos en ejecución",
|
||||
"NO_RUNNING_GAME_FOUND": "No se encontró ningún juego en ejecución"
|
||||
"RUNNING_GAMES_LIST": "Lista de juegos en ejecución"
|
||||
},
|
||||
"TERMINATE_PROCESS": "Finalizar proceso",
|
||||
"GAME_LAUNCHED": "Juego lanzado",
|
||||
|
||||
@@ -75,7 +75,9 @@
|
||||
"SCREENCOPY_HELP": "Lento, nessuna finestra pop-up per la condivisione dello schermo.\nFunziona su: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Nessuno",
|
||||
"HMD_PINCH": "HMD + pizzico",
|
||||
"EYE_PINCH": "Occhio + pizzico"
|
||||
"EYE_PINCH": "Occhio + pizzico",
|
||||
"EYE_ONLY": "Solo occhio",
|
||||
"HMD_ONLY": "Solo HMD"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "Smussamento puntatore",
|
||||
"RESTART_SOFTWARE": "Riavvia il software",
|
||||
@@ -103,7 +105,9 @@
|
||||
"AUTOSTART_APPS": "App da avviare all'avvio",
|
||||
"HANDSFREE_POINTER": "Modalità a mani libere",
|
||||
"HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente"
|
||||
"UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente",
|
||||
"RESET_PLAYSPACE": "Ripristina playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco."
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Lanciatore applicazioni",
|
||||
"APPLICATION_STARTED": "Applicazione avviata",
|
||||
@@ -128,8 +132,7 @@
|
||||
"GAME_LAUNCHED": "Gioco lanciato",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Nessun gioco trovato",
|
||||
"RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione",
|
||||
"NO_RUNNING_GAME_FOUND": "Nessun gioco in esecuzione trovato"
|
||||
"RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione"
|
||||
},
|
||||
"GAMES": "Giochi",
|
||||
"GENERAL_SETTINGS": "Impostazioni generali",
|
||||
|
||||
@@ -78,12 +78,16 @@
|
||||
"SCREENCOPY_HELP": "遅延あり、画面共有ポップアップなし。\n動作する環境: Hyprland, Niri, River, Sway",
|
||||
"NONE": "なし",
|
||||
"HMD_PINCH": "HMD + ピンチ",
|
||||
"EYE_PINCH": "つまんで目を合わせる"
|
||||
"EYE_PINCH": "つまんで目を合わせる",
|
||||
"EYE_ONLY": "視野のみ",
|
||||
"HMD_ONLY": "HMDのみ"
|
||||
},
|
||||
"AUTOSTART_APPS": "起動時に実行するアプリ",
|
||||
"HANDSFREE_POINTER": "ハンズフリーモード",
|
||||
"HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。",
|
||||
"UI_GRADIENT_INTENSITY": "UIグラデーションの強さ"
|
||||
"UI_GRADIENT_INTENSITY": "UIグラデーションの強さ",
|
||||
"RESET_PLAYSPACE": "プレイエリアをリセット",
|
||||
"RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。"
|
||||
},
|
||||
"HELLO": "こんにちは!",
|
||||
"AUDIO": {
|
||||
@@ -116,8 +120,7 @@
|
||||
"CLOSE_WINDOW": "ウィンドウを閉じる",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "ゲームが見つかりませんでした",
|
||||
"RUNNING_GAMES_LIST": "実行中のゲーム一覧",
|
||||
"NO_RUNNING_GAME_FOUND": "実行中のゲームが見つかりません"
|
||||
"RUNNING_GAMES_LIST": "実行中のゲーム一覧"
|
||||
},
|
||||
"TERMINATE_PROCESS": "プロセスを終了する",
|
||||
"GAME_LAUNCHED": "ゲームが起動しました",
|
||||
|
||||
@@ -73,12 +73,16 @@
|
||||
"SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Brak",
|
||||
"HMD_PINCH": "HMD + szczyknięcie",
|
||||
"EYE_PINCH": "Ściśnięcie palcami + oko"
|
||||
"EYE_PINCH": "Ściśnięcie palcami + oko",
|
||||
"EYE_ONLY": "Tylko oko",
|
||||
"HMD_ONLY": "Tylko HMD"
|
||||
},
|
||||
"AUTOSTART_APPS": "Aplikacje do uruchomienia przy starcie",
|
||||
"HANDSFREE_POINTER": "Tryb bez użycia rąk",
|
||||
"HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensywność gradientu UI"
|
||||
"UI_GRADIENT_INTENSITY": "Intensywność gradientu UI",
|
||||
"RESET_PLAYSPACE": "Zresetuj przestrzeń gry",
|
||||
"RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry."
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
|
||||
"APPLICATIONS": "Aplikacje",
|
||||
@@ -116,8 +120,7 @@
|
||||
"CLOSE_WINDOW": "Zamknij okno",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "Nie znaleziono gier",
|
||||
"RUNNING_GAMES_LIST": "Lista uruchomionych gier",
|
||||
"NO_RUNNING_GAME_FOUND": "Nie znaleziono uruchomionej gry"
|
||||
"RUNNING_GAMES_LIST": "Lista uruchomionych gier"
|
||||
},
|
||||
"TERMINATE_PROCESS": "Zakończ proces",
|
||||
"GAME_LAUNCHED": "Gra uruchomiona",
|
||||
|
||||
@@ -75,7 +75,9 @@
|
||||
"SCREENCOPY_HELP": "慢速,无屏幕共享弹窗。\n支持:Hyprland, Niri, River, Sway",
|
||||
"NONE": "无",
|
||||
"HMD_PINCH": "HMD + 捏合",
|
||||
"EYE_PINCH": "眼睛 + 捏合"
|
||||
"EYE_PINCH": "眼睛 + 捏合",
|
||||
"EYE_ONLY": "仅眼球",
|
||||
"HMD_ONLY": "仅限头显"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "指针平滑",
|
||||
"RESTART_SOFTWARE": "重启软件",
|
||||
@@ -103,7 +105,9 @@
|
||||
"AUTOSTART_APPS": "开机启动应用",
|
||||
"HANDSFREE_POINTER": "免提模式",
|
||||
"HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。",
|
||||
"UI_GRADIENT_INTENSITY": "UI 渐变强度"
|
||||
"UI_GRADIENT_INTENSITY": "UI 渐变强度",
|
||||
"RESET_PLAYSPACE": "重置游戏空间",
|
||||
"RESET_PLAYSPACE_HELP": "清除舞台空间偏移。"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "应用启动器",
|
||||
"APPLICATION_STARTED": "应用已启动",
|
||||
@@ -128,8 +132,7 @@
|
||||
"GAME_LAUNCHED": "游戏已启动",
|
||||
"GAME_LIST": {
|
||||
"NO_GAMES_FOUND": "未找到游戏",
|
||||
"RUNNING_GAMES_LIST": "正在运行的游戏列表",
|
||||
"NO_RUNNING_GAME_FOUND": "未找到正在运行的游戏"
|
||||
"RUNNING_GAMES_LIST": "正在运行的游戏列表"
|
||||
},
|
||||
"GAMES": "游戏",
|
||||
"GENERAL_SETTINGS": "通用设置",
|
||||
|
||||
@@ -10,8 +10,9 @@ use wgui::{
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams, LayoutUpdateResult, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||
task::Tasks,
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
||||
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||
};
|
||||
use wlx_common::{
|
||||
@@ -22,7 +23,7 @@ use wlx_common::{
|
||||
|
||||
use crate::{
|
||||
assets,
|
||||
tab::{apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings, Tab, TabType},
|
||||
tab::{Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings},
|
||||
util::{
|
||||
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
|
||||
toast_manager::ToastManager,
|
||||
@@ -32,8 +33,10 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct FrontendWidgets {
|
||||
pub id_label_time: WidgetID,
|
||||
pub id_rect_content: WidgetID,
|
||||
id_label_time: WidgetID,
|
||||
id_rect_content: WidgetID,
|
||||
id_sprite_titlebar_icon: WidgetID,
|
||||
id_label_titlebar_title: WidgetID,
|
||||
}
|
||||
|
||||
pub type FrontendTasks = Tasks<FrontendTask>;
|
||||
@@ -148,6 +151,8 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
let id_label_time = state.get_widget_id("label_time")?;
|
||||
let id_rect_content = state.get_widget_id("rect_content")?;
|
||||
let id_sprite_titlebar_icon = state.get_widget_id("sprite_titlebar_icon")?;
|
||||
let id_label_titlebar_title = state.get_widget_id("label_titlebar_title")?;
|
||||
|
||||
let timestep = Timestep::new(60.0);
|
||||
|
||||
@@ -161,6 +166,8 @@ impl<T: 'static> Frontend<T> {
|
||||
widgets: FrontendWidgets {
|
||||
id_label_time,
|
||||
id_rect_content,
|
||||
id_sprite_titlebar_icon,
|
||||
id_label_titlebar_title,
|
||||
},
|
||||
timestep,
|
||||
interface: params.interface,
|
||||
@@ -283,9 +290,10 @@ impl<T: 'static> Frontend<T> {
|
||||
let mut common = c.common();
|
||||
|
||||
{
|
||||
let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(self.widgets.id_label_time) else {
|
||||
anyhow::bail!("");
|
||||
};
|
||||
let mut label = common
|
||||
.state
|
||||
.widgets
|
||||
.cast_as::<WidgetLabel>(self.widgets.id_label_time)?;
|
||||
|
||||
let now = chrono::Local::now();
|
||||
let hours = now.hour();
|
||||
@@ -370,11 +378,48 @@ impl<T: 'static> Frontend<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tab_title(&mut self, translation: &str, icon: &str) -> anyhow::Result<()> {
|
||||
let mut c = self.layout.start_common();
|
||||
let mut common = c.common();
|
||||
|
||||
{
|
||||
let mut label = common
|
||||
.state
|
||||
.widgets
|
||||
.cast_as::<WidgetLabel>(self.widgets.id_label_titlebar_title)?;
|
||||
label.set_text(&mut common, Translation::from_translation_key(translation));
|
||||
}
|
||||
|
||||
{
|
||||
let mut sprite = common
|
||||
.state
|
||||
.widgets
|
||||
.cast_as::<WidgetSprite>(self.widgets.id_sprite_titlebar_icon)?;
|
||||
sprite.set_content(
|
||||
&mut common,
|
||||
Some(CustomGlyphData::from_assets(&self.globals, AssetPath::BuiltIn(icon))?),
|
||||
);
|
||||
}
|
||||
|
||||
c.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tab(&mut self, data: &mut T, tab_type: TabType) -> anyhow::Result<()> {
|
||||
log::info!("Setting tab to {tab_type:?}");
|
||||
let widget_content = self.state.fetch_widget(&self.layout.state, "content")?;
|
||||
self.layout.remove_children(widget_content.id);
|
||||
|
||||
let (tab_translation, icon_path) = match tab_type {
|
||||
TabType::Home => ("HOME_SCREEN", "dashboard/home.svg"),
|
||||
TabType::Apps => ("APPLICATIONS", "dashboard/apps.svg"),
|
||||
TabType::Games => ("GAMES", "dashboard/games.svg"),
|
||||
TabType::Monado => ("MONADO_RUNTIME", "dashboard/monado.svg"),
|
||||
TabType::Settings => ("SETTINGS", "dashboard/settings.svg"),
|
||||
};
|
||||
|
||||
self.set_tab_title(tab_translation, icon_path)?;
|
||||
|
||||
let tab: Box<dyn Tab<T>> = match tab_type {
|
||||
TabType::Home => Box::new(TabHome::new(self, widget_content.id, data)?),
|
||||
TabType::Apps => Box::new(TabApps::new(self, widget_content.id, data)?),
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
@@ -29,7 +30,10 @@ enum Task {
|
||||
AppManifestClicked(steam_utils::AppManifest),
|
||||
SetCoverArt(AppID, Rc<CoverArt>),
|
||||
CloseLauncher,
|
||||
Refresh,
|
||||
LoadManifests,
|
||||
FillPage(u32),
|
||||
PrevPage,
|
||||
NextPage,
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
@@ -40,7 +44,9 @@ pub struct Params<'a> {
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
pub struct Cell {
|
||||
const MAX_GAMES_PER_PAGE: u32 = 30;
|
||||
|
||||
pub struct GameCoverCell {
|
||||
view_cover: game_cover::View,
|
||||
}
|
||||
|
||||
@@ -55,10 +61,14 @@ pub struct View {
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
cells: HashMap<AppID, Cell>,
|
||||
game_cover_view_common: game_cover::ViewCommon,
|
||||
executor: AsyncExecutor,
|
||||
state: Rc<RefCell<State>>,
|
||||
mounted_game_covers: HashMap<AppID, GameCoverCell>,
|
||||
all_manifests: Vec<steam_utils::AppManifest>,
|
||||
cur_page: u32,
|
||||
page_count: u32,
|
||||
id_label_page: WidgetID,
|
||||
}
|
||||
|
||||
impl View {
|
||||
@@ -71,10 +81,21 @@ impl View {
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?;
|
||||
let id_label_page = parser_state.get_widget_id("label_page")?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_prev")?,
|
||||
Task::PrevPage,
|
||||
);
|
||||
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_next")?,
|
||||
Task::NextPage,
|
||||
);
|
||||
|
||||
tasks.push(Task::LoadManifests);
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
@@ -82,10 +103,14 @@ impl View {
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
globals: params.globals.clone(),
|
||||
id_list_parent: list_parent.id,
|
||||
cells: HashMap::new(),
|
||||
mounted_game_covers: HashMap::new(),
|
||||
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||
state: Rc::new(RefCell::new(State { view_launcher: None })),
|
||||
executor: params.executor,
|
||||
all_manifests: Vec::new(),
|
||||
cur_page: 0,
|
||||
page_count: 0,
|
||||
id_label_page,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,10 +127,13 @@ impl View {
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(layout, steam_utils, executor)?,
|
||||
Task::LoadManifests => self.load_manifests(steam_utils),
|
||||
Task::FillPage(page_idx) => self.fill_page(layout, executor, page_idx)?,
|
||||
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
||||
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(layout, app_id, cover_art),
|
||||
Task::CloseLauncher => self.state.borrow_mut().view_launcher = None,
|
||||
Task::PrevPage => self.page_prev(),
|
||||
Task::NextPage => self.page_next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,18 +147,14 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Games {
|
||||
manifests: Vec<steam_utils::AppManifest>,
|
||||
}
|
||||
|
||||
fn fill_game_list(
|
||||
ess: &mut ConstructEssentials,
|
||||
executor: &AsyncExecutor,
|
||||
cells: &mut HashMap<AppID, Cell>,
|
||||
games: &Games,
|
||||
mounted_game_covers: &mut HashMap<AppID, GameCoverCell>,
|
||||
manifests: &[steam_utils::AppManifest],
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
for manifest in &games.manifests {
|
||||
for manifest in manifests {
|
||||
let on_loaded = {
|
||||
let app_id = manifest.app_id.clone();
|
||||
let tasks = tasks.clone();
|
||||
@@ -156,49 +180,81 @@ fn fill_game_list(
|
||||
})
|
||||
});
|
||||
|
||||
cells.insert(manifest.app_id.clone(), Cell { view_cover });
|
||||
mounted_game_covers.insert(manifest.app_id.clone(), GameCoverCell { view_cover });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl View {
|
||||
fn game_list(&self, steam_utils: &mut SteamUtils) -> anyhow::Result<Games> {
|
||||
let manifests = steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)?;
|
||||
|
||||
Ok(Games { manifests })
|
||||
fn load_manifests(&mut self, steam_utils: &mut SteamUtils) {
|
||||
match steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc) {
|
||||
Ok(manifests) => {
|
||||
self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE;
|
||||
self.all_manifests = manifests;
|
||||
self.tasks.push(Task::FillPage(0));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to list installed games: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
steam_utils: &mut SteamUtils,
|
||||
executor: &AsyncExecutor,
|
||||
) -> anyhow::Result<()> {
|
||||
fn page_prev(&mut self) {
|
||||
if self.cur_page == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.cur_page -= 1;
|
||||
self.tasks.push(Task::FillPage(self.cur_page));
|
||||
}
|
||||
|
||||
fn page_next(&mut self) {
|
||||
if self.cur_page >= self.page_count - 1 {
|
||||
return;
|
||||
}
|
||||
self.cur_page += 1;
|
||||
self.tasks.push(Task::FillPage(self.cur_page));
|
||||
}
|
||||
|
||||
fn fill_page(&mut self, layout: &mut Layout, executor: &AsyncExecutor, page_idx: u32) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_list_parent);
|
||||
self.cells.clear();
|
||||
self.mounted_game_covers.clear();
|
||||
|
||||
let idx_from = (page_idx * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32);
|
||||
let idx_to = ((page_idx + 1) * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32);
|
||||
|
||||
let page_manifests = &self.all_manifests[idx_from as usize..idx_to as usize];
|
||||
|
||||
let mut text: Option<Translation> = None;
|
||||
match self.game_list(steam_utils) {
|
||||
Ok(list) => {
|
||||
if list.manifests.is_empty() {
|
||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||
} else {
|
||||
fill_game_list(
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
executor,
|
||||
&mut self.cells,
|
||||
&list,
|
||||
&self.tasks,
|
||||
)?
|
||||
}
|
||||
}
|
||||
Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))),
|
||||
|
||||
if page_manifests.is_empty() {
|
||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||
}
|
||||
|
||||
// set page text
|
||||
let mut c = layout.start_common();
|
||||
{
|
||||
let mut common = c.common();
|
||||
let mut widget = common.state.widgets.cast_as::<WidgetLabel>(self.id_label_page)?;
|
||||
widget.set_text(
|
||||
&mut common,
|
||||
Translation::from_raw_text_string(format!("{}/{}", self.cur_page + 1, self.page_count)),
|
||||
);
|
||||
}
|
||||
c.finish()?;
|
||||
|
||||
fill_game_list(
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
executor,
|
||||
&mut self.mounted_game_covers,
|
||||
page_manifests,
|
||||
&self.tasks,
|
||||
)?;
|
||||
|
||||
if let Some(text) = text.take() {
|
||||
layout.add_child(
|
||||
self.id_list_parent,
|
||||
@@ -217,11 +273,11 @@ impl View {
|
||||
}
|
||||
|
||||
fn set_cover_art(&mut self, layout: &mut Layout, app_id: AppID, cover_art: Rc<CoverArt>) {
|
||||
let Some(cell) = &mut self.cells.get_mut(&app_id) else {
|
||||
let Some(cover) = &mut self.mounted_game_covers.get_mut(&app_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = cell
|
||||
if let Err(e) = cover
|
||||
.view_cover
|
||||
.set_cover_art(&mut self.game_cover_view_common, layout, &cover_art)
|
||||
{
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
event::StyleSetRequest,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
layout::{Layout, LayoutTask, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
taffy::Display,
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
util::{
|
||||
steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils},
|
||||
wgui_simple,
|
||||
},
|
||||
util::steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -39,6 +38,7 @@ pub struct View {
|
||||
id_list_parent: WidgetID,
|
||||
installed_games: Vec<AppManifest>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
parent_id: WidgetID,
|
||||
}
|
||||
|
||||
fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
|
||||
@@ -58,7 +58,7 @@ impl View {
|
||||
let installed_games = params
|
||||
.steam_utils
|
||||
.list_installed_games(GameSortMethod::None)
|
||||
.unwrap_or(Vec::new());
|
||||
.unwrap_or_default();
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
|
||||
@@ -72,6 +72,7 @@ impl View {
|
||||
id_list_parent,
|
||||
installed_games,
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
parent_id: params.parent_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ impl View {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_name_from_appid<'a>(app_id: &AppID, manifests: &[AppManifest]) -> String {
|
||||
fn extract_name_from_appid(app_id: &AppID, manifests: &[AppManifest]) -> String {
|
||||
for manifest in manifests {
|
||||
if manifest.app_id == *app_id {
|
||||
return manifest.name.clone();
|
||||
@@ -110,14 +111,19 @@ impl View {
|
||||
|
||||
fn fill_list(&mut self, layout: &mut Layout, games: Vec<steam_utils::RunningGame>) -> anyhow::Result<()> {
|
||||
if games.is_empty() {
|
||||
wgui_simple::create_label(
|
||||
layout,
|
||||
self.id_list_parent,
|
||||
Translation::from_translation_key("GAME_LIST.NO_RUNNING_GAME_FOUND"),
|
||||
)?;
|
||||
// hide self
|
||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
||||
self.parent_id,
|
||||
StyleSetRequest::Display(Display::None),
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
||||
self.parent_id,
|
||||
StyleSetRequest::Display(Display::DEFAULT),
|
||||
));
|
||||
|
||||
for game in games {
|
||||
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "llm_translator",
|
||||
|
||||
@@ -55,8 +55,7 @@ fn button_click_callback(
|
||||
) -> ButtonClickCallback {
|
||||
Rc::new(move |common, _e| {
|
||||
label
|
||||
.get_as::<WidgetLabel>()
|
||||
.unwrap()
|
||||
.cast::<WidgetLabel>()?
|
||||
.set_text(common, Translation::from_raw_text(text));
|
||||
|
||||
button.try_cast::<ComponentButton>()?.set_text(
|
||||
@@ -169,7 +168,7 @@ impl TestbedGeneric {
|
||||
let cb_first = parser_state.fetch_component_as::<ComponentCheckbox>("cb_first")?;
|
||||
let label = label_cur_option.widget.clone();
|
||||
cb_first.on_toggle(Box::new(move |common, e| {
|
||||
let mut widget = label.get_as::<WidgetLabel>().unwrap();
|
||||
let mut widget = label.cast::<WidgetLabel>()?;
|
||||
let text = format!("checkbox toggle: {}", e.checked);
|
||||
widget.set_text(common, Translation::from_raw_text(&text));
|
||||
Ok(())
|
||||
|
||||
@@ -103,7 +103,7 @@ pub(super) fn setup_custom_label<S: 'static>(
|
||||
layout
|
||||
.state
|
||||
.widgets
|
||||
.get_as::<WidgetLabel>(attribs.widget_id)
|
||||
.cast_as::<WidgetLabel>(attribs.widget_id)
|
||||
.unwrap()
|
||||
.set_text_simple(&mut globals, Translation::from_raw_text(pretty_tz));
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
color: TOOLTIP_COLOR,
|
||||
border_color: TOOLTIP_BORDER_COLOR,
|
||||
border: 2.0,
|
||||
round: WLength::Percent(1.0),
|
||||
round: WLength::Units(24.0),
|
||||
..Default::default()
|
||||
}),
|
||||
taffy::Style {
|
||||
|
||||
@@ -75,6 +75,10 @@ impl WidgetMap {
|
||||
self.0.get(handle)?.get_as::<T>()
|
||||
}
|
||||
|
||||
pub fn cast_as<T: 'static>(&self, handle: WidgetID) -> anyhow::Result<RefMut<'_, T>> {
|
||||
self.get_as(handle).context("Widget cast failed")
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: WidgetID) -> Option<&Widget> {
|
||||
self.0.get(handle)
|
||||
}
|
||||
@@ -134,6 +138,7 @@ pub type ModifyLayoutStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow
|
||||
|
||||
pub enum LayoutTask {
|
||||
RemoveWidget(WidgetID),
|
||||
SetWidgetStyle(WidgetID, event::StyleSetRequest),
|
||||
ModifyLayoutState(ModifyLayoutStateFunc),
|
||||
PlaySound(WguiSoundType),
|
||||
Dispatch(Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>),
|
||||
@@ -702,12 +707,51 @@ impl Layout {
|
||||
func(&mut c.common())?;
|
||||
c.finish()?;
|
||||
}
|
||||
LayoutTask::SetWidgetStyle(widget_id, style_request) => {
|
||||
self.set_style_request(widget_id, style_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_style_request(&mut self, widget_id: WidgetID, style_request: event::StyleSetRequest) {
|
||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// taffy requires us to copy this whole 536-byte style struct.
|
||||
// we can't get `&mut Style` directly from taffy unfortunately
|
||||
let mut cur_style = self.state.tree.style(*node_id).unwrap().clone() /* always safe */;
|
||||
|
||||
match style_request {
|
||||
event::StyleSetRequest::Display(display) => {
|
||||
// refresh the component in case if visibility/display mode has changed
|
||||
if cur_style.display != display
|
||||
&& let Some(component) = self.registered_components_to_refresh.get(node_id)
|
||||
{
|
||||
self.components_to_refresh_once.insert(component.clone());
|
||||
}
|
||||
|
||||
cur_style.display = display;
|
||||
}
|
||||
event::StyleSetRequest::Margin(margin) => {
|
||||
cur_style.margin = margin;
|
||||
}
|
||||
event::StyleSetRequest::Width(val) => {
|
||||
cur_style.size.width = val;
|
||||
}
|
||||
event::StyleSetRequest::Height(val) => {
|
||||
cur_style.size.height = val;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.state.tree.set_style(*node_id, cur_style) {
|
||||
log::error!("failed to set style for taffy widget ID {node_id:?}: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> {
|
||||
for task in alterables.tasks {
|
||||
self.tasks.push(task);
|
||||
@@ -743,39 +787,7 @@ impl Layout {
|
||||
}
|
||||
|
||||
for (widget_id, style_request) in alterables.style_set_requests {
|
||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// taffy requires us to copy this whole 536-byte style struct.
|
||||
// we can't get `&mut Style` directly from taffy unfortunately
|
||||
let mut cur_style = self.state.tree.style(*node_id).unwrap().clone() /* always safe */;
|
||||
|
||||
match style_request {
|
||||
event::StyleSetRequest::Display(display) => {
|
||||
// refresh the component in case if visibility/display mode has changed
|
||||
if cur_style.display != display
|
||||
&& let Some(component) = self.registered_components_to_refresh.get(node_id)
|
||||
{
|
||||
self.components_to_refresh_once.insert(component.clone());
|
||||
}
|
||||
|
||||
cur_style.display = display;
|
||||
}
|
||||
event::StyleSetRequest::Margin(margin) => {
|
||||
cur_style.margin = margin;
|
||||
}
|
||||
event::StyleSetRequest::Width(val) => {
|
||||
cur_style.size.width = val;
|
||||
}
|
||||
event::StyleSetRequest::Height(val) => {
|
||||
cur_style.size.height = val;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.state.tree.set_style(*node_id, cur_style) {
|
||||
log::error!("failed to set style for taffy widget ID {node_id:?}: {e:?}");
|
||||
}
|
||||
self.set_style_request(widget_id, style_request);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Context;
|
||||
use glam::Vec2;
|
||||
use taffy::{NodeId, TaffyTree};
|
||||
|
||||
@@ -248,10 +249,18 @@ impl dyn WidgetObj {
|
||||
any.downcast_ref::<T>()
|
||||
}
|
||||
|
||||
pub fn cast<T: 'static>(&self) -> anyhow::Result<&T> {
|
||||
self.get_as().context("cast failed")
|
||||
}
|
||||
|
||||
pub fn get_as_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
let any = self.as_any_mut();
|
||||
any.downcast_mut::<T>()
|
||||
}
|
||||
|
||||
pub fn cast_mut<T: 'static>(&mut self) -> anyhow::Result<&mut T> {
|
||||
self.get_as_mut().context("cast failed")
|
||||
}
|
||||
}
|
||||
|
||||
struct InvokeData<'a, 'b, U1: 'static, U2: 'static> {
|
||||
|
||||
@@ -47,7 +47,7 @@ impl WidgetObj for WidgetRectangle {
|
||||
let boundary = drawing::Boundary::construct_relative(state.transform_stack);
|
||||
|
||||
let round_units = match self.params.round {
|
||||
WLength::Units(units) => units as u8,
|
||||
WLength::Units(units) => (f32::min(boundary.size.x, boundary.size.y) as u8 / 2).min(units as u8),
|
||||
WLength::Percent(percent) => (f32::min(boundary.size.x, boundary.size.y) * percent / 2.0) as u8,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user