dash-frontend: toast messages, 📦📎

This commit is contained in:
Aleksander
2025-12-08 22:42:05 +01:00
parent e7746f5981
commit b0985e33af
28 changed files with 316 additions and 80 deletions

1
Cargo.lock generated
View File

@@ -1432,6 +1432,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"wgui", "wgui",
"wlx-common",
] ]
[[package]] [[package]]

View File

@@ -22,7 +22,7 @@ resolver = "3"
[workspace.dependencies] [workspace.dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
glam = "0.30.7" glam = { version = "0.30.7", features = ["mint", "serde"] }
idmap = "0.2.2" idmap = "0.2.2"
idmap-derive = "0.2.2" idmap-derive = "0.2.2"
log = "0.4.28" log = "0.4.28"

View File

@@ -6,7 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
wgui = { path = "../wgui/" } wgui = { path = "../wgui/" }
glam = { workspace = true } glam = { workspace = true, features = ["mint", "serde"] }
log = { workspace = true } log = { workspace = true }
rust-embed = { workspace = true } rust-embed = { workspace = true }
chrono = "0.4.42" chrono = "0.4.42"
@@ -14,3 +14,4 @@ gio = "0.21.2"
gtk = "0.18.2" gtk = "0.18.2"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145" serde_json = "1.0.145"
wlx-common = { path = "../wlx-common" }

View File

@@ -98,7 +98,6 @@
</div> </div>
<div position="absolute" id="popup_manager" width="100%" height="100%" /> <div position="absolute" id="popup_manager" width="100%" height="100%" />
</rectangle> </rectangle>
<!-- BOTTOM PANEL --> <!-- BOTTOM PANEL -->
<rectangle <rectangle

View File

@@ -35,7 +35,13 @@
"SPEAKERS": "Lautsprecher", "SPEAKERS": "Lautsprecher",
"MICROPHONES": "Mikrofone", "MICROPHONES": "Mikrofone",
"CARDS": "Karten", "CARDS": "Karten",
"SELECT_AUDIO_CARD_PROFILE": "Wählen Sie das Audio-Kartenprofil" "SELECT_AUDIO_CARD_PROFILE": "Wählen Sie das Audio-Kartenprofil",
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Keine VR-Lautsprecher gefunden. Schalten Sie diese manuell um.",
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "Kein VR-Mikrofon gefunden. Schalten Sie es manuell um.",
"FAILED_TO_SWITCH_MICROPHONE": "Fehler beim Wechseln des Mikrofons",
"MICROPHONE_SET_SUCCESSFULLY": "Mikrofon erfolgreich eingestellt",
"SPEAKERS_SET_SUCCESSFULLY": "Lautsprecher erfolgreich eingestellt",
"DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Gerät gefunden und initialisiert, aber nicht umgeschaltet"
}, },
"ACTIONS": { "ACTIONS": {
"RECENTER_PLAYSPACE": "Playspace neu zentrieren" "RECENTER_PLAYSPACE": "Playspace neu zentrieren"

View File

@@ -35,7 +35,13 @@
"AUTO_SWITCH_TO_VR_AUDIO": "Auto-switch to VR audio", "AUTO_SWITCH_TO_VR_AUDIO": "Auto-switch to VR audio",
"SPEAKERS": "Speakers", "SPEAKERS": "Speakers",
"MICROPHONES": "Microphones", "MICROPHONES": "Microphones",
"CARDS": "Cards" "CARDS": "Cards",
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "No VR speakers found. Switch them manually.",
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "No VR microphone found. Switch it manually.",
"FAILED_TO_SWITCH_MICROPHONE": "Failed to switch microphone",
"MICROPHONE_SET_SUCCESSFULLY": "Microphone set successfully",
"SPEAKERS_SET_SUCCESSFULLY": "Speakers set successfully",
"DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Device found and initialized, but not switched"
}, },
"ACTIONS": { "ACTIONS": {
"RECENTER_PLAYSPACE": "Re-center playspace" "RECENTER_PLAYSPACE": "Re-center playspace"

View File

@@ -35,7 +35,13 @@
"SPEAKERS": "Altavoces", "SPEAKERS": "Altavoces",
"MICROPHONES": "Micrófonos", "MICROPHONES": "Micrófonos",
"CARDS": "Tarjetas", "CARDS": "Tarjetas",
"SELECT_AUDIO_CARD_PROFILE": "Seleccionar perfil de tarjeta de audio" "SELECT_AUDIO_CARD_PROFILE": "Seleccionar perfil de tarjeta de audio",
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "No se encontraron altavoces VR. Actívelos manualmente.",
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "No se encontró micrófono VR. Actívelo manualmente.",
"FAILED_TO_SWITCH_MICROPHONE": "No se pudo cambiar el micrófono",
"MICROPHONE_SET_SUCCESSFULLY": "Micrófono configurado correctamente",
"SPEAKERS_SET_SUCCESSFULLY": "Altavoces configurados correctamente",
"DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Dispositivo encontrado e inicializado, pero no cambiado"
}, },
"ACTIONS": { "ACTIONS": {
"RECENTER_PLAYSPACE": "Re-centrar espacio de juego" "RECENTER_PLAYSPACE": "Re-centrar espacio de juego"

View File

@@ -35,7 +35,13 @@
"SPEAKERS": "スピーカー", "SPEAKERS": "スピーカー",
"MICROPHONES": "マイク", "MICROPHONES": "マイク",
"CARDS": "カード", "CARDS": "カード",
"SELECT_AUDIO_CARD_PROFILE": "オーディオカードプロファイルを選択" "SELECT_AUDIO_CARD_PROFILE": "オーディオカードプロファイルを選択",
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "VRスピーカーが見つかりませんでした。手動で切り替えてください。",
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "VRマイクが見つかりませんでした。手動で切り替えてください。",
"FAILED_TO_SWITCH_MICROPHONE": "マイクの切り替えに失敗しました",
"MICROPHONE_SET_SUCCESSFULLY": "マイクの設定が完了しました",
"SPEAKERS_SET_SUCCESSFULLY": "スピーカーを設定しました",
"DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "デバイスが見つかり、初期化されましたが、切り替えられていません"
}, },
"ACTIONS": { "ACTIONS": {
"RECENTER_PLAYSPACE": "プレイスペースを再中央" "RECENTER_PLAYSPACE": "プレイスペースを再中央"

View File

@@ -35,7 +35,13 @@
"SPEAKERS": "Głośniki", "SPEAKERS": "Głośniki",
"MICROPHONES": "Mikrofony", "MICROPHONES": "Mikrofony",
"CARDS": "Karty", "CARDS": "Karty",
"SELECT_AUDIO_CARD_PROFILE": "Wybierz profil karty dźwiękowej" "SELECT_AUDIO_CARD_PROFILE": "Wybierz profil karty dźwiękowej",
"NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "Brak głośników VR. Włącz je ręcznie.",
"NO_VR_MICROPHONE_SWITCH_MANUALLY": "Brak mikrofonu VR. Włącz go ręcznie.",
"FAILED_TO_SWITCH_MICROPHONE": "Nie udało się przełączyć mikrofon",
"MICROPHONE_SET_SUCCESSFULLY": "Mikrofon ustawiono pomyślnie",
"SPEAKERS_SET_SUCCESSFULLY": "Głośniki ustawiono pomyślnie",
"DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "Urządzenie znalezione i zainicjalizowane, ale nie przełączone"
}, },
"ACTIONS": { "ACTIONS": {
"RECENTER_PLAYSPACE": "Wycentruj przestrzeń" "RECENTER_PLAYSPACE": "Wycentruj przestrzeń"

View File

@@ -13,6 +13,7 @@ use wgui::{
widget::{label::WidgetLabel, rectangle::WidgetRectangle}, widget::{label::WidgetLabel, rectangle::WidgetRectangle},
windowing::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement}, windowing::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
}; };
use wlx_common::timestep::Timestep;
use crate::{ use crate::{
assets, settings, assets, settings,
@@ -21,7 +22,10 @@ use crate::{
settings::TabSettings, settings::TabSettings,
}, },
task::Tasks, task::Tasks,
util::popup_manager::{MountPopupParams, PopupManager, PopupManagerParams}, util::{
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
toast_manager::ToastManager,
},
views, views,
}; };
@@ -49,6 +53,8 @@ pub struct Frontend {
widgets: FrontendWidgets, widgets: FrontendWidgets,
popup_manager: PopupManager, popup_manager: PopupManager,
toast_manager: ToastManager,
timestep: Timestep,
window_audio_settings: WguiWindow, window_audio_settings: WguiWindow,
view_audio_settings: Option<views::audio_settings::View>, view_audio_settings: Option<views::audio_settings::View>,
@@ -70,7 +76,7 @@ pub enum FrontendTask {
ShowAudioSettings, ShowAudioSettings,
UpdateAudioSettingsView, UpdateAudioSettingsView,
RecenterPlayspace, RecenterPlayspace,
PushToast(String), PushToast(Translation),
} }
impl Frontend { impl Frontend {
@@ -92,7 +98,7 @@ impl Frontend {
}, },
)?; )?;
let (mut layout, state) = wgui::parser::new_layout_from_assets( let (layout, state) = wgui::parser::new_layout_from_assets(
&ParseDocumentParams { &ParseDocumentParams {
globals: globals.clone(), globals: globals.clone(),
path: AssetPath::BuiltIn("gui/dashboard.xml"), path: AssetPath::BuiltIn("gui/dashboard.xml"),
@@ -103,10 +109,10 @@ impl Frontend {
let id_popup_manager = state.get_widget_id("popup_manager")?; let id_popup_manager = state.get_widget_id("popup_manager")?;
let popup_manager = PopupManager::new(PopupManagerParams { let popup_manager = PopupManager::new(PopupManagerParams {
globals: globals.clone(),
layout: &mut layout,
parent_id: id_popup_manager, parent_id: id_popup_manager,
})?; });
let toast_manager = ToastManager::new();
let rc_layout = layout.as_rc(); let rc_layout = layout.as_rc();
@@ -116,6 +122,9 @@ impl Frontend {
let id_label_time = state.get_widget_id("label_time")?; let id_label_time = state.get_widget_id("label_time")?;
let id_rect_content = state.get_widget_id("rect_content")?; let id_rect_content = state.get_widget_id("rect_content")?;
let mut timestep = Timestep::new();
timestep.set_tps(30.0); // 30 ticks per second
let frontend = Self { let frontend = Self {
layout: rc_layout.clone(), layout: rc_layout.clone(),
state, state,
@@ -127,8 +136,10 @@ impl Frontend {
id_label_time, id_label_time,
id_rect_content, id_rect_content,
}, },
timestep,
settings: params.settings, settings: params.settings,
popup_manager, popup_manager,
toast_manager,
window_audio_settings: WguiWindow::default(), window_audio_settings: WguiWindow::default(),
view_audio_settings: None, view_audio_settings: None,
}; };
@@ -165,6 +176,12 @@ impl Frontend {
{ {
let mut layout = self.layout.borrow_mut(); let mut layout = self.layout.borrow_mut();
// always 30 times per second
while self.timestep.on_tick() {
self.toast_manager.tick(&self.globals, &mut layout)?;
}
layout.update(Vec2::new(width, height), timestep_alpha)?; layout.update(Vec2::new(width, height), timestep_alpha)?;
} }
@@ -253,8 +270,8 @@ impl Frontend {
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?, FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?, FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
FrontendTask::RecenterPlayspace => self.action_recenter_playspace()?, FrontendTask::RecenterPlayspace => self.action_recenter_playspace()?,
FrontendTask::PushToast(text) => self.push_toast(text)?, FrontendTask::PushToast(content) => self.toast_manager.push(content),
} };
Ok(()) Ok(())
} }
@@ -269,7 +286,7 @@ impl Frontend {
layout: &mut layout, layout: &mut layout,
parent_id: widget_content.id, parent_id: widget_content.id,
frontend: rc_this, frontend: rc_this,
frontend_widgets: &self.widgets, //frontend_widgets: &self.widgets,
settings: self.settings.get_mut(), settings: self.settings.get_mut(),
}; };
@@ -413,9 +430,4 @@ impl Frontend {
log::info!("todo"); log::info!("todo");
Ok(()) Ok(())
} }
fn push_toast(&mut self, text: String) -> anyhow::Result<()> {
log::info!("TODO toast: {}", text);
Ok(())
}
} }

View File

@@ -6,7 +6,7 @@ use wgui::{
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::WidgetPair, layout::WidgetPair,
parser::{Fetchable, ParseDocumentParams, ParserData, ParserState}, parser::{Fetchable, ParseDocumentParams, ParserState},
}; };
use crate::{ use crate::{
@@ -28,6 +28,7 @@ pub struct TabApps {
#[allow(dead_code)] #[allow(dead_code)]
pub parser_state: ParserState, pub parser_state: ParserState,
#[allow(dead_code)]
state: Rc<RefCell<State>>, state: Rc<RefCell<State>>,
#[allow(dead_code)] #[allow(dead_code)]
@@ -44,7 +45,7 @@ impl Tab for TabApps {
#[derive(Default)] #[derive(Default)]
struct AppList { struct AppList {
data: Vec<ParserData>, //data: Vec<ParserData>,
} }
// called after the user clicks any desktop entry // called after the user clicks any desktop entry

View File

@@ -1,12 +1,9 @@
use std::rc::Rc;
use wgui::{ use wgui::{
components::button::ComponentButton,
globals::WguiGlobals, globals::WguiGlobals,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
}; };
use crate::frontend::{FrontendTask, FrontendWidgets, RcFrontend}; use crate::frontend::RcFrontend;
pub mod apps; pub mod apps;
pub mod games; pub mod games;
@@ -30,7 +27,6 @@ pub struct TabParams<'a> {
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub frontend: &'a RcFrontend, pub frontend: &'a RcFrontend,
pub frontend_widgets: &'a FrontendWidgets,
pub settings: &'a mut crate::settings::Settings, pub settings: &'a mut crate::settings::Settings,
} }

View File

@@ -19,3 +19,9 @@ impl<TaskType: Clone + 'static> Tasks<TaskType> {
std::mem::take(&mut *tasks) std::mem::take(&mut *tasks)
} }
} }
impl<TaskType: Clone + 'static> Default for Tasks<TaskType> {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,3 +1,4 @@
pub mod desktop_finder; pub mod desktop_finder;
pub mod pactl_wrapper; pub mod pactl_wrapper;
pub mod popup_manager; pub mod popup_manager;
pub mod toast_manager;

View File

@@ -17,13 +17,11 @@ use wgui::{
use crate::frontend::{FrontendTask, FrontendTasks}; use crate::frontend::{FrontendTask, FrontendTasks};
pub struct PopupManagerParams<'a> { pub struct PopupManagerParams {
pub globals: WguiGlobals,
pub layout: &'a mut Layout,
pub parent_id: WidgetID, pub parent_id: WidgetID,
} }
pub struct State { struct State {
popup_stack: Vec<Weak<RefCell<MountedPopupState>>>, popup_stack: Vec<Weak<RefCell<MountedPopupState>>>,
} }
@@ -31,7 +29,6 @@ pub struct MountedPopup {
#[allow(dead_code)] #[allow(dead_code)]
state: ParserState, state: ParserState,
id_root: WidgetID, // decorations of a popup id_root: WidgetID, // decorations of a popup
pub id_content: WidgetID, // content of a popup
layout_tasks: LayoutTasks, layout_tasks: LayoutTasks,
frontend_tasks: FrontendTasks, frontend_tasks: FrontendTasks,
} }
@@ -52,8 +49,7 @@ impl PopupHandle {
} }
pub struct PopupManager { pub struct PopupManager {
pub state: Rc<RefCell<State>>, state: Rc<RefCell<State>>,
globals: WguiGlobals,
parent_id: WidgetID, parent_id: WidgetID,
} }
@@ -104,14 +100,13 @@ impl State {
} }
impl PopupManager { impl PopupManager {
pub fn new(params: PopupManagerParams) -> anyhow::Result<Self> { pub fn new(params: PopupManagerParams) -> Self {
Ok(Self { Self {
globals: params.globals,
parent_id: params.parent_id, parent_id: params.parent_id,
state: Rc::new(RefCell::new(State { state: Rc::new(RefCell::new(State {
popup_stack: Vec::new(), popup_stack: Vec::new(),
})), })),
}) }
} }
pub fn refresh(&self, alterables: &mut EventAlterables) { pub fn refresh(&self, alterables: &mut EventAlterables) {
@@ -147,7 +142,6 @@ impl PopupManager {
let mounted_popup = MountedPopup { let mounted_popup = MountedPopup {
state, state,
id_content,
id_root, id_root,
layout_tasks: layout.tasks.clone(), layout_tasks: layout.tasks.clone(),
frontend_tasks: frontend_tasks.clone(), frontend_tasks: frontend_tasks.clone(),

View File

@@ -0,0 +1,190 @@
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use glam::{Mat4, Vec3};
use wgui::{
animation::{Animation, AnimationEasing},
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
drawing::Color,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
renderer_vk::{
text::{FontWeight, TextStyle},
util::centered_matrix,
},
taffy::{
self,
prelude::{length, percent},
},
widget::{
div::WidgetDiv,
label::{WidgetLabel, WidgetLabelParams},
rectangle::{WidgetRectangle, WidgetRectangleParams},
util::WLength,
},
};
struct MountedToast {
#[allow(dead_code)]
id_root: WidgetID, // decorations of a toast
layout_tasks: LayoutTasks,
}
struct State {
toast: Option<MountedToast>,
queue: VecDeque<Translation>,
timeout: u32, // in ticks
}
pub struct ToastManager {
state: Rc<RefCell<State>>,
needs_tick: bool,
}
impl Drop for MountedToast {
fn drop(&mut self) {
self.layout_tasks.push(LayoutTask::RemoveWidget(self.id_root));
}
}
const TOAST_DURATION_TICKS: u32 = 90;
impl ToastManager {
pub fn new() -> Self {
Self {
state: Rc::new(RefCell::new(State {
toast: None,
timeout: 0,
queue: VecDeque::new(),
})),
needs_tick: false,
}
}
fn mount_toast(
&self,
globals: &WguiGlobals,
layout: &mut Layout,
state: &mut State,
content: Translation,
) -> anyhow::Result<()> {
let mut globals = globals.get();
let (root, _) = layout.add_topmost_child(
WidgetDiv::create(),
taffy::Style {
position: taffy::Position::Absolute,
size: taffy::Size {
width: percent(1.0),
height: percent(0.8),
},
align_items: Some(taffy::AlignItems::End),
justify_content: Some(taffy::JustifyContent::Center),
..Default::default()
},
)?;
let (rect, _) = layout.add_child(
root.id,
WidgetRectangle::create(WidgetRectangleParams {
color: TOOLTIP_COLOR,
border_color: TOOLTIP_BORDER_COLOR,
border: 2.0,
round: WLength::Percent(1.0),
..Default::default()
}),
taffy::Style {
position: taffy::Position::Relative,
gap: length(4.0),
padding: taffy::Rect {
left: length(16.0),
right: length(16.0),
top: length(8.0),
bottom: length(8.0),
},
..Default::default()
},
)?;
let (label, _) = layout.add_child(
rect.id,
WidgetLabel::create(
&mut globals,
WidgetLabelParams {
content,
style: TextStyle {
weight: Some(FontWeight::Bold),
..Default::default()
},
},
),
taffy::Style { ..Default::default() },
)?;
// show-up animation
layout.animations.add(Animation::new(
rect.id,
160,
AnimationEasing::Linear,
Box::new(move |common, data| {
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
let opacity = 1.0 - AnimationEasing::OutQuint.interpolate(((data.pos - 0.75) * 4.0).clamp(0.0, 1.0));
let scale = AnimationEasing::OutBack.interpolate((data.pos * 4.0).min(1.0));
{
let mtx = Mat4::from_translation(Vec3::new(0.0, (1.0 - pos_showup) * 100.0, 0.0))
* Mat4::from_scale(Vec3::new(scale, scale, 1.0));
data.data.transform = centered_matrix(data.widget_boundary.size, &mtx);
}
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
rect.params.color.a = opacity;
rect.params.border_color.a = opacity;
let mut label = common.state.widgets.get_as::<WidgetLabel>(label.id).unwrap();
label.set_color(common, Color::new(1.0, 1.0, 1.0, opacity), true);
common.alterables.mark_redraw();
}),
));
state.toast = Some(MountedToast {
id_root: root.id,
layout_tasks: layout.tasks.clone(),
});
Ok(())
}
pub fn tick(&mut self, globals: &WguiGlobals, layout: &mut Layout) -> anyhow::Result<()> {
if !self.needs_tick {
return Ok(());
}
let mut state = self.state.borrow_mut();
if state.timeout > 0 {
state.timeout -= 1;
}
if state.timeout == 0 {
state.toast = None;
state.timeout = TOAST_DURATION_TICKS;
// mount next
if let Some(content) = state.queue.pop_front() {
self.mount_toast(globals, layout, &mut state, content)?;
}
}
if state.queue.is_empty() && state.toast.is_none() {
self.needs_tick = false;
}
Ok(())
}
pub fn push(&mut self, content: Translation) {
let mut state = self.state.borrow_mut();
state.queue.push_back(content);
self.needs_tick = true;
}
}

View File

@@ -424,14 +424,12 @@ fn switch_sink_card(
} }
if sink_found { if sink_found {
frontend_tasks.push(FrontendTask::PushToast(format!( frontend_tasks.push(FrontendTask::PushToast(Translation::from_translation_key(
"Speakers set to \"{}\" successfully!", format!("[AUDIO.SPEAKERS_SET_SUCCESSFULLY]: {}", name.name).as_str(),
name.name
))); )));
} else { } else {
frontend_tasks.push(FrontendTask::PushToast(format!( frontend_tasks.push(FrontendTask::PushToast(Translation::from_translation_key(
"\"{}\" found and initialized! (not switched)", format!("[AUDIO.DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED]: {}", name.name).as_str(),
name.name
))); )));
} }
@@ -441,18 +439,21 @@ fn switch_sink_card(
fn switch_source(frontend_tasks: &FrontendTasks, source: &pactl_wrapper::Source) -> anyhow::Result<()> { fn switch_source(frontend_tasks: &FrontendTasks, source: &pactl_wrapper::Source) -> anyhow::Result<()> {
match pactl_wrapper::set_default_source(source.index) { match pactl_wrapper::set_default_source(source.index) {
Ok(()) => { Ok(()) => {
frontend_tasks.push(FrontendTask::PushToast(format!( frontend_tasks.push(FrontendTask::PushToast(Translation::from_translation_key(
"Microphone set to \"{}\" successfully!", format!(
"[AUDIO.MICROPHONE_SET_SUCCESSFULLY]: {}",
if let Some(card_name) = &source.properties.card_name { if let Some(card_name) = &source.properties.card_name {
card_name card_name
} else { } else {
&source.description &source.description
} }
)
.as_str(),
))); )));
Ok(()) Ok(())
} }
Err(e) => { Err(e) => {
frontend_tasks.push(FrontendTask::PushToast(format!("Failed to switch microphone: {:?}", e))); Translation::from_translation_key(format!("[AUDIO.FAILED_TO_SWITCH_MICROPHONE]: {:?}", e).as_str());
Err(e) Err(e)
} }
} }
@@ -471,9 +472,9 @@ fn switch_to_vr_microphone(frontend_tasks: &FrontendTasks) -> anyhow::Result<()>
} }
if !switched { if !switched {
frontend_tasks.push(FrontendTask::PushToast( frontend_tasks.push(FrontendTask::PushToast(Translation::from_translation_key(
"No VR microphone found. Switch it manually.".to_string(), "AUDIO.NO_VR_MICROPHONE_SWITCH_MANUALLY",
)); )));
} }
Ok(()) Ok(())
@@ -560,9 +561,9 @@ fn switch_to_vr_speakers(frontend_tasks: &FrontendTasks) -> anyhow::Result<()> {
} }
} }
frontend_tasks.push(FrontendTask::PushToast( frontend_tasks.push(FrontendTask::PushToast(Translation::from_translation_key(
"No VR speakers found. Switch them manually.".to_string(), "AUDIO.NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY",
)); )));
Ok(()) Ok(())
} }

View File

@@ -20,7 +20,7 @@ pub enum AnimationEasing {
} }
impl AnimationEasing { impl AnimationEasing {
fn interpolate(&self, x: f32) -> f32 { pub fn interpolate(&self, x: f32) -> f32 {
match self { match self {
Self::Linear => x, Self::Linear => x,
Self::InQuad => x.powi(2), Self::InQuad => x.powi(2),

View File

@@ -95,7 +95,7 @@ pub fn normalize_path(path: &Path) -> PathBuf {
Component::ParentDir => { Component::ParentDir => {
match stack.last() { match stack.last() {
// ../foo, ../../foo, ./../foo → push ".." // ../foo, ../../foo, ./../foo → push ".."
None | Some(Component::ParentDir) | Some(Component::CurDir) => stack.push(Component::ParentDir), None | Some(Component::ParentDir | Component::CurDir) => stack.push(Component::ParentDir),
// "foo/../bar" → pop "foo" and don't push ".." // "foo/../bar" → pop "foo" and don't push ".."
Some(Component::Normal(_)) => { Some(Component::Normal(_)) => {
stack.pop(); stack.pop();

View File

@@ -1,6 +1,6 @@
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use taffy::{ use taffy::{
AlignItems, JustifyContent, AlignItems,
prelude::{length, percent}, prelude::{length, percent},
}; };

View File

@@ -86,6 +86,9 @@ impl Drop for ComponentTooltip {
} }
} }
pub const TOOLTIP_COLOR: Color = Color::new(0.1, 0.1, 0.1, 0.9);
pub const TOOLTIP_BORDER_COLOR: Color = Color::new(0.3, 0.3, 0.3, 1.0);
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc<ComponentTooltip>)> { pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc<ComponentTooltip>)> {
let absolute_boundary = { let absolute_boundary = {
@@ -103,7 +106,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let transform = Mat4::from_translation(Vec3::new(-0.5, 0.0, 0.0)); let transform = Mat4::from_translation(Vec3::new(-0.5, 0.0, 0.0));
let (mut pin_left, mut pin_top, pin_align_items, pin_justify_content) = match params.info.side { let (pin_left, pin_top, pin_align_items, pin_justify_content) = match params.info.side {
TooltipSide::Left => ( TooltipSide::Left => (
absolute_boundary.left() - spacing, absolute_boundary.left() - spacing,
absolute_boundary.top() + absolute_boundary.size.y / 2.0, absolute_boundary.top() + absolute_boundary.size.y / 2.0,
@@ -159,8 +162,8 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
let (rect, _) = ess.layout.add_child( let (rect, _) = ess.layout.add_child(
div.id, div.id,
WidgetRectangle::create(WidgetRectangleParams { WidgetRectangle::create(WidgetRectangleParams {
color: Color::new(0.1, 0.1, 0.1, 0.8), color: TOOLTIP_COLOR,
border_color: Color::new(0.3, 0.3, 0.3, 1.0), border_color: TOOLTIP_BORDER_COLOR,
border: 2.0, border: 2.0,
round: WLength::Percent(1.0), round: WLength::Percent(1.0),
..Default::default() ..Default::default()

View File

@@ -5,7 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive", "rc"] }
glam = { workspace = true } glam = { workspace = true }
chrono = "0.4.42" chrono = "0.4.42"
idmap = { workspace = true, features = ["serde"] } idmap = { workspace = true, features = ["serde"] }

View File

@@ -2,4 +2,5 @@ pub mod astr_containers;
pub mod common; pub mod common;
pub mod config; pub mod config;
pub mod overlays; pub mod overlays;
pub mod timestep;
pub mod windowing; pub mod windowing;

View File

@@ -52,7 +52,7 @@ rodio = { version = "0.21.1", default-features = false, features = [
"hound", "hound",
] } ] }
rosc = { version = "0.11.4", optional = true } rosc = { version = "0.11.4", optional = true }
serde = { version = "1.0.225", features = ["derive", "rc"] } serde = { version = "1.0.228", features = ["derive", "rc"] }
serde_json = "1.0.145" serde_json = "1.0.145"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
slotmap = { workspace = true } slotmap = { workspace = true }

View File

@@ -1,4 +1,3 @@
pub mod asset; pub mod asset;
pub mod panel; pub mod panel;
pub mod timer; pub mod timer;
mod timestep;

View File

@@ -1,7 +1,7 @@
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use button::setup_custom_button; use button::setup_custom_button;
use glam::{vec2, Affine2, Vec2}; use glam::{Affine2, Vec2, vec2};
use label::setup_custom_label; use label::setup_custom_label;
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
@@ -15,19 +15,20 @@ use wgui::{
layout::{Layout, LayoutParams, WidgetID}, layout::{Layout, LayoutParams, WidgetID},
parser::{CustomAttribsInfoOwned, ParserState}, parser::{CustomAttribsInfoOwned, ParserState},
renderer_vk::context::Context as WguiContext, renderer_vk::context::Context as WguiContext,
widget::{label::WidgetLabel, rectangle::WidgetRectangle, EventResult}, widget::{EventResult, label::WidgetLabel, rectangle::WidgetRectangle},
}; };
use wlx_common::timestep::Timestep;
use crate::{ use crate::{
backend::input::{Haptics, HoverResult, PointerHit, PointerMode}, backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
windowing::backend::{ windowing::backend::{
ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform,
}, },
}; };
use super::{timer::GuiTimer, timestep::Timestep}; use super::timer::GuiTimer;
pub mod button; pub mod button;
mod helper; mod helper;

View File

@@ -14,7 +14,7 @@ use wgui::{
parser::Fetchable, parser::Fetchable,
renderer_vk::text::custom_glyph::CustomGlyphData, renderer_vk::text::custom_glyph::CustomGlyphData,
taffy, taffy,
widget::{sprite::WidgetSprite, EventResult}, widget::{EventResult, sprite::WidgetSprite},
}; };
use wlx_common::windowing::{OverlayWindowState, Positioning}; use wlx_common::windowing::{OverlayWindowState, Positioning};
@@ -24,16 +24,16 @@ use crate::{
task::{ManagerTask, TaskType}, task::{ManagerTask, TaskType},
}, },
gui::{ gui::{
panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams, OnCustomAttribFunc}, panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
timer::GuiTimer, timer::GuiTimer,
}, },
overlays::edit::LongPressButtonState, overlays::edit::LongPressButtonState,
state::AppState, state::AppState,
windowing::{ windowing::{
OverlaySelector, Z_ORDER_WATCH,
backend::{OverlayEventData, OverlayMeta}, backend::{OverlayEventData, OverlayMeta},
manager::MAX_OVERLAY_SETS, manager::MAX_OVERLAY_SETS,
window::{OverlayWindowConfig, OverlayWindowData}, window::{OverlayWindowConfig, OverlayWindowData},
OverlaySelector, Z_ORDER_WATCH,
}, },
}; };
@@ -205,7 +205,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
if idx >= num_children { if idx >= num_children {
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new(); let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert("idx".into(), idx.to_string().into()); params.insert("idx".into(), idx.to_string().into());
params.insert("src".into(), "".to_string().into()); params.insert("src".into(), String::new().into());
parser_state.instantiate_template( parser_state.instantiate_template(
doc_params, "Device", layout, widget, params, doc_params, "Device", layout, widget, params,
)?; )?;
@@ -309,7 +309,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
} else { } else {
com.alterables com.alterables
.set_style(*div, StyleSetRequest::Display(taffy::Display::None)); .set_style(*div, StyleSetRequest::Display(taffy::Display::None));
}; }
} }
} }
} }