dash and wgui sounds

This commit is contained in:
Aleksander
2026-01-03 15:00:31 +01:00
parent 383bf3b11f
commit feba52c28f
34 changed files with 258 additions and 96 deletions

1
Cargo.lock generated
View File

@@ -6801,6 +6801,7 @@ dependencies = [
"serde", "serde",
"smol", "smol",
"wayvr-ipc", "wayvr-ipc",
"wgui",
"xdg 3.0.0", "xdg 3.0.0",
] ]

Binary file not shown.

Binary file not shown.

View File

@@ -8,19 +8,20 @@ use wgui::{
font_config::WguiFontConfig, font_config::WguiFontConfig,
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, LayoutParams, WidgetID}, layout::{Layout, LayoutParams, LayoutUpdateParams, LayoutUpdateResult, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState}, parser::{Fetchable, ParseDocumentParams, ParserState},
sound::WguiSoundType,
task::Tasks, task::Tasks,
widget::{label::WidgetLabel, rectangle::WidgetRectangle}, widget::{label::WidgetLabel, rectangle::WidgetRectangle},
windowing::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement}, windowing::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
}; };
use wlx_common::{dash_interface::BoxDashInterface, timestep::Timestep}; use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
use crate::{ use crate::{
assets, settings, assets, settings,
tab::{ tab::{
apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings, Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses,
Tab, TabType, settings::TabSettings,
}, },
util::{ util::{
desktop_finder::DesktopFinder, desktop_finder::DesktopFinder,
@@ -68,6 +69,13 @@ pub struct Frontend<T> {
pub(crate) desktop_finder: DesktopFinder, pub(crate) desktop_finder: DesktopFinder,
} }
pub struct FrontendUpdateParams<'a, T> {
pub data: &'a mut T,
pub width: f32,
pub height: f32,
pub timestep_alpha: f32,
}
pub struct InitParams<T> { pub struct InitParams<T> {
pub settings: Box<dyn settings::SettingsIO>, pub settings: Box<dyn settings::SettingsIO>,
pub interface: BoxDashInterface<T>, pub interface: BoxDashInterface<T>,
@@ -164,15 +172,27 @@ impl<T: 'static> Frontend<T> {
Ok(frontend) Ok(frontend)
} }
pub fn update(&mut self, data: &mut T, width: f32, height: f32, timestep_alpha: f32) -> anyhow::Result<()> { pub fn play_startup_sound(
&mut self,
audio_system: &mut audio::AudioSystem,
audio_sample_player: &mut audio::SamplePlayer,
) -> anyhow::Result<()> {
// play startup sound
let mut assets = self.globals.assets_builtin();
audio_sample_player.register_mp3_sample_from_assets("dash_startup", assets.as_mut(), "sound/startup.mp3")?;
audio_sample_player.play_sample(audio_system, "dash_startup");
Ok(())
}
pub fn update(&mut self, params: FrontendUpdateParams<T>) -> anyhow::Result<LayoutUpdateResult> {
let mut tasks = self.tasks.drain(); let mut tasks = self.tasks.drain();
while let Some(task) = tasks.pop_front() { while let Some(task) = tasks.pop_front() {
self.process_task(data, task)?; self.process_task(params.data, task)?;
} }
if let Some(mut tab) = self.current_tab.take() { if let Some(mut tab) = self.current_tab.take() {
tab.update(self, data)?; tab.update(self, params.data)?;
self.current_tab = Some(tab); self.current_tab = Some(tab);
} }
@@ -180,13 +200,14 @@ impl<T: 'static> Frontend<T> {
// process async runtime tasks // process async runtime tasks
while self.executor.try_tick() {} while self.executor.try_tick() {}
self.tick(width, height, timestep_alpha)?; let res = self.tick(params)?;
self.ticks += 1; self.ticks += 1;
Ok(()) Ok(res)
} }
fn tick(&mut self, width: f32, height: f32, timestep_alpha: f32) -> anyhow::Result<()> { fn tick(&mut self, params: FrontendUpdateParams<T>) -> anyhow::Result<LayoutUpdateResult> {
// fixme: timer events instead of this thing // fixme: timer events instead of this thing
if self.ticks.is_multiple_of(1000) { if self.ticks.is_multiple_of(1000) {
self.update_time()?; self.update_time()?;
@@ -197,11 +218,12 @@ impl<T: 'static> Frontend<T> {
while self.timestep.on_tick() { while self.timestep.on_tick() {
self.toast_manager.tick(&self.globals, &mut self.layout)?; self.toast_manager.tick(&self.globals, &mut self.layout)?;
} }
self.layout.update(Vec2::new(width, height), timestep_alpha)?;
} }
Ok(()) self.layout.update(&mut LayoutUpdateParams {
size: Vec2::new(params.width, params.height),
timestep_alpha: params.timestep_alpha,
})
} }
fn update_time(&mut self) -> anyhow::Result<()> { fn update_time(&mut self) -> anyhow::Result<()> {

View File

@@ -68,7 +68,7 @@ pub struct CardProperties {
pub device_name: String, // alsa_card.pci-0000_0c_00.4 pub device_name: String, // alsa_card.pci-0000_0c_00.4
#[serde(rename = "device.nick")] #[serde(rename = "device.nick")]
pub device_nick: String, // HD-Audio Generic pub device_nick: Option<String>, // HD-Audio Generic
} }
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
@@ -296,7 +296,12 @@ pub fn list_cards() -> anyhow::Result<Vec<Card>> {
let mut cards: Vec<Card> = serde_json::from_str(json_str)?; let mut cards: Vec<Card> = serde_json::from_str(json_str)?;
// exclude card which has "Loopback" in name // exclude card which has "Loopback" in name
cards.retain(|card| card.properties.device_nick != "Loopback"); cards.retain(|card| {
if let Some(nick) = &card.properties.device_nick {
return nick != "Loopback";
};
true
});
Ok(cards) Ok(cards)
} }

View File

@@ -0,0 +1 @@
../../../wlx-overlay-s/src/assets/sound/wgui_button_press.mp3

View File

@@ -0,0 +1 @@
../../../wlx-overlay-s/src/assets/sound/wgui_button_release.mp3

View File

@@ -0,0 +1 @@
../../../wlx-overlay-s/src/assets/sound/wgui_mouse_enter.mp3

View File

@@ -18,6 +18,7 @@ use vulkano::{
sync::GpuFuture, sync::GpuFuture,
}; };
use wgui::{ use wgui::{
assets::AssetProvider,
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent}, event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
gfx::{WGfx, cmd::WGfxClearMode}, gfx::{WGfx, cmd::WGfxClearMode},
renderer_vk::{self}, renderer_vk::{self},
@@ -27,7 +28,7 @@ use winit::{
event_loop::ControlFlow, event_loop::ControlFlow,
keyboard::{KeyCode, PhysicalKey}, keyboard::{KeyCode, PhysicalKey},
}; };
use wlx_common::timestep::Timestep; use wlx_common::{audio, timestep::Timestep};
use crate::{ use crate::{
rate_limiter::RateLimiter, rate_limiter::RateLimiter,
@@ -59,12 +60,15 @@ fn init_logging() {
.init(); .init();
} }
fn load_testbed() -> anyhow::Result<Box<dyn Testbed>> { fn load_testbed(audio_sample_player: &mut audio::SamplePlayer) -> anyhow::Result<Box<dyn Testbed>> {
let mut assets = Box::new(assets::Asset {});
audio_sample_player.register_wgui_samples(assets.as_mut())?;
let name = std::env::var("TESTBED").unwrap_or_default(); let name = std::env::var("TESTBED").unwrap_or_default();
Ok(match name.as_str() { Ok(match name.as_str() {
"dashboard" => Box::new(TestbedDashboard::new()?), "dashboard" => Box::new(TestbedDashboard::new()?),
"" => Box::new(TestbedGeneric::new()?), "" => Box::new(TestbedGeneric::new(assets)?),
_ => Box::new(TestbedAny::new(&name)?), _ => Box::new(TestbedAny::new(assets, &name)?),
}) })
} }
@@ -98,7 +102,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut scale = window.scale_factor() as f32; let mut scale = window.scale_factor() as f32;
let mut testbed = load_testbed()?; let mut audio_system = audio::AudioSystem::new();
let mut audio_sample_player = audio::SamplePlayer::new();
let mut testbed = load_testbed(&mut audio_sample_player)?;
let mut mouse = Vec2::ZERO; let mut mouse = Vec2::ZERO;
@@ -291,6 +297,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
width: (swapchain_size[0] as f32 / scale) as _, width: (swapchain_size[0] as f32 / scale) as _,
height: (swapchain_size[1] as f32 / scale) as _, height: (swapchain_size[1] as f32 / scale) as _,
timestep_alpha: timestep.alpha, timestep_alpha: timestep.alpha,
audio_system: &mut audio_system,
audio_sample_player: &mut audio_sample_player,
}) })
.unwrap(); .unwrap();

View File

@@ -1,13 +1,24 @@
use wgui::layout::Layout; use wgui::layout::{Layout, LayoutUpdateResult};
use wlx_common::audio;
pub mod testbed_any; pub mod testbed_any;
pub mod testbed_dashboard; pub mod testbed_dashboard;
pub mod testbed_generic; pub mod testbed_generic;
pub struct TestbedUpdateParams { pub struct TestbedUpdateParams<'a> {
pub width: f32, pub width: f32,
pub height: f32, pub height: f32,
pub timestep_alpha: f32, pub timestep_alpha: f32,
pub audio_system: &'a mut audio::AudioSystem,
pub audio_sample_player: &'a mut audio::SamplePlayer,
}
impl<'a> TestbedUpdateParams<'a> {
pub fn process_layout_result(&mut self, res: LayoutUpdateResult) {
self
.audio_sample_player
.play_wgui_samples(self.audio_system, res.sounds_to_play);
}
} }
pub trait Testbed { pub trait Testbed {

View File

@@ -9,7 +9,7 @@ use wgui::{
assets::AssetPath, assets::AssetPath,
font_config::WguiFontConfig, font_config::WguiFontConfig,
globals::WguiGlobals, globals::WguiGlobals,
layout::{Layout, LayoutParams}, layout::{Layout, LayoutParams, LayoutUpdateParams},
parser::{ParseDocumentParams, ParserState}, parser::{ParseDocumentParams, ParserState},
}; };
@@ -21,7 +21,7 @@ pub struct TestbedAny {
} }
impl TestbedAny { impl TestbedAny {
pub fn new(name: &str) -> anyhow::Result<Self> { pub fn new(assets: Box<assets::Asset>, name: &str) -> anyhow::Result<Self> {
let path = if name.ends_with(".xml") { let path = if name.ends_with(".xml") {
AssetPath::FileOrBuiltIn(name) AssetPath::FileOrBuiltIn(name)
} else { } else {
@@ -29,7 +29,7 @@ impl TestbedAny {
}; };
let globals = WguiGlobals::new( let globals = WguiGlobals::new(
Box::new(assets::Asset {}), assets,
wgui::globals::Defaults::default(), wgui::globals::Defaults::default(),
&WguiFontConfig::default(), &WguiFontConfig::default(),
PathBuf::new(), // cwd PathBuf::new(), // cwd
@@ -48,11 +48,12 @@ impl TestbedAny {
} }
impl Testbed for TestbedAny { impl Testbed for TestbedAny {
fn update(&mut self, params: TestbedUpdateParams) -> anyhow::Result<()> { fn update(&mut self, mut params: TestbedUpdateParams) -> anyhow::Result<()> {
self.layout.update( let res = self.layout.update(&mut LayoutUpdateParams {
Vec2::new(params.width, params.height), size: Vec2::new(params.width, params.height),
params.timestep_alpha, timestep_alpha: params.timestep_alpha,
)?; })?;
params.process_layout_result(res);
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,6 @@
use crate::testbed::{Testbed, TestbedUpdateParams}; use crate::testbed::{Testbed, TestbedUpdateParams};
use dash_frontend::{ use dash_frontend::{
frontend, frontend::{self, FrontendUpdateParams},
settings::{self, SettingsIO}, settings::{self, SettingsIO},
}; };
use wgui::layout::Layout; use wgui::layout::Layout;
@@ -70,13 +70,15 @@ impl TestbedDashboard {
} }
impl Testbed for TestbedDashboard { impl Testbed for TestbedDashboard {
fn update(&mut self, params: TestbedUpdateParams) -> anyhow::Result<()> { fn update(&mut self, mut params: TestbedUpdateParams) -> anyhow::Result<()> {
self.frontend.update( let res = self.frontend.update(FrontendUpdateParams {
&mut (), /* nothing */ data: &mut (), /* nothing */
params.width, width: params.width,
params.height, height: params.height,
params.timestep_alpha, timestep_alpha: params.timestep_alpha,
) })?;
params.process_layout_result(res);
Ok(())
} }
fn layout(&mut self) -> &mut Layout { fn layout(&mut self) -> &mut Layout {

View File

@@ -17,7 +17,7 @@ use wgui::{
font_config::WguiFontConfig, font_config::WguiFontConfig,
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, LayoutParams, Widget}, layout::{Layout, LayoutParams, LayoutUpdateParams, Widget},
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState}, parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
taffy, taffy,
task::Tasks, task::Tasks,
@@ -73,11 +73,11 @@ fn handle_button_click(button: Rc<ComponentButton>, label: Widget, text: &'stati
} }
impl TestbedGeneric { impl TestbedGeneric {
pub fn new() -> anyhow::Result<Self> { pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
const XML_PATH: AssetPath = AssetPath::BuiltIn("gui/various_widgets.xml"); const XML_PATH: AssetPath = AssetPath::BuiltIn("gui/various_widgets.xml");
let globals = WguiGlobals::new( let globals = WguiGlobals::new(
Box::new(assets::Asset {}), assets,
wgui::globals::Defaults::default(), wgui::globals::Defaults::default(),
&WguiFontConfig::default(), &WguiFontConfig::default(),
PathBuf::new(), // cwd PathBuf::new(), // cwd
@@ -223,10 +223,12 @@ impl Testbed for TestbedGeneric {
let data = self.data.clone(); let data = self.data.clone();
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
self.layout.update( let res = self.layout.update(&mut LayoutUpdateParams {
Vec2::new(params.width, params.height), size: Vec2::new(params.width, params.height),
params.timestep_alpha, timestep_alpha: params.timestep_alpha,
)?; })?;
params.process_layout_result(res);
for task in self.tasks.drain() { for task in self.tasks.drain() {
self.process_task(&task, &mut params, &mut data)?; self.process_task(&task, &mut params, &mut data)?;

View File

@@ -10,6 +10,7 @@ use crate::{
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData}, text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
util::centered_matrix, util::centered_matrix,
}, },
sound::WguiSoundType,
widget::{ widget::{
self, ConstructEssentials, EventResult, WidgetData, self, ConstructEssentials, EventResult, WidgetData,
label::{WidgetLabel, WidgetLabelParams}, label::{WidgetLabel, WidgetLabelParams},
@@ -270,6 +271,7 @@ fn register_event_mouse_enter(
listeners.register( listeners.register(
EventListenerKind::MouseEnter, EventListenerKind::MouseEnter,
Box::new(move |common, event_data, (), ()| { Box::new(move |common, event_data, (), ()| {
common.alterables.play_sound(WguiSoundType::ButtonMouseEnter);
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
common.alterables.mark_redraw(); common.alterables.mark_redraw();
common common
@@ -332,6 +334,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
); );
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
common.alterables.play_sound(WguiSoundType::ButtonPress);
common.alterables.mark_redraw(); common.alterables.mark_redraw();
if state.hovered { if state.hovered {
@@ -362,6 +365,7 @@ fn register_event_mouse_release(
} }
common.alterables.trigger_haptics(); common.alterables.trigger_haptics();
common.alterables.play_sound(WguiSoundType::ButtonRelease);
common.alterables.mark_redraw(); common.alterables.mark_redraw();
if state.down { if state.down {

View File

@@ -11,6 +11,7 @@ use crate::{
animation::{self, Animation}, animation::{self, Animation},
i18n::I18n, i18n::I18n,
layout::{LayoutState, LayoutTask, WidgetID}, layout::{LayoutState, LayoutTask, WidgetID},
sound::WguiSoundType,
stack::{ScissorStack, Transform, TransformStack}, stack::{ScissorStack, Transform, TransformStack},
widget::{EventResult, WidgetData, WidgetObj}, widget::{EventResult, WidgetData, WidgetObj},
}; };
@@ -140,6 +141,10 @@ impl EventAlterables {
pub fn animate(&mut self, animation: Animation) { pub fn animate(&mut self, animation: Animation) {
self.animations.push(animation); self.animations.push(animation);
} }
pub fn play_sound(&mut self, sound_type: WguiSoundType) {
self.tasks.push(LayoutTask::PlaySound(sound_type));
}
} }
pub struct CallbackDataCommon<'a> { pub struct CallbackDataCommon<'a> {

View File

@@ -1,6 +1,6 @@
use std::{ use std::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
collections::{HashMap, HashSet, VecDeque}, collections::{HashMap, HashSet},
io::Write, io::Write,
rc::{Rc, Weak}, rc::{Rc, Weak},
}; };
@@ -11,6 +11,8 @@ use crate::{
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack}, drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
event::{self, CallbackDataCommon, EventAlterables}, event::{self, CallbackDataCommon, EventAlterables},
globals::WguiGlobals, globals::WguiGlobals,
sound::WguiSoundType,
task::Tasks,
widget::{self, EventParams, EventResult, WidgetObj, WidgetState, WidgetStateFlags, div::WidgetDiv}, widget::{self, EventParams, EventResult, WidgetObj, WidgetState, WidgetStateFlags, div::WidgetDiv},
}; };
@@ -119,25 +121,24 @@ pub struct ModifyLayoutStateData<'a> {
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
} }
pub struct LayoutUpdateParams {
pub size: Vec2, // logical resolution
pub timestep_alpha: f32,
}
pub struct LayoutUpdateResult {
pub sounds_to_play: Vec<WguiSoundType>,
}
pub type ModifyLayoutStateFunc = Box<dyn Fn(ModifyLayoutStateData) -> anyhow::Result<()>>; pub type ModifyLayoutStateFunc = Box<dyn Fn(ModifyLayoutStateData) -> anyhow::Result<()>>;
pub enum LayoutTask { pub enum LayoutTask {
RemoveWidget(WidgetID), RemoveWidget(WidgetID),
ModifyLayoutState(ModifyLayoutStateFunc), ModifyLayoutState(ModifyLayoutStateFunc),
PlaySound(WguiSoundType),
} }
#[derive(Clone)] pub type LayoutTasks = Tasks<LayoutTask>;
pub struct LayoutTasks(pub Rc<RefCell<VecDeque<LayoutTask>>>);
impl LayoutTasks {
fn new() -> Self {
Self(Rc::new(RefCell::new(VecDeque::new())))
}
pub fn push(&self, task: LayoutTask) {
self.0.borrow_mut().push_back(task);
}
}
pub struct Layout { pub struct Layout {
pub state: LayoutState, pub state: LayoutState,
@@ -146,6 +147,7 @@ pub struct Layout {
components_to_refresh_once: HashSet<Component>, components_to_refresh_once: HashSet<Component>,
registered_components_to_refresh: HashMap<taffy::NodeId, Component>, registered_components_to_refresh: HashMap<taffy::NodeId, Component>,
sounds_to_play_once: Vec<WguiSoundType>,
pub widgets_to_tick: Vec<WidgetID>, pub widgets_to_tick: Vec<WidgetID>,
@@ -266,15 +268,12 @@ impl Layout {
} }
pub fn get_parent(&self, widget_id: WidgetID) -> Option<(WidgetID, taffy::NodeId)> { pub fn get_parent(&self, widget_id: WidgetID) -> Option<(WidgetID, taffy::NodeId)> {
let Some(node_id) = self.state.nodes.get(widget_id) else { let node_id = self.state.nodes.get(widget_id)?;
return None;
};
self.state.tree.parent(*node_id).map(|parent_id| { self.state.tree.parent(*node_id).map(|parent_id| {
let parent_widget_id = self.state.tree.get_node_context(parent_id).unwrap(); let parent_widget_id = self.state.tree.get_node_context(parent_id).unwrap();
(*parent_widget_id, parent_id) (*parent_widget_id, parent_id)
} })
)
} }
fn collect_children_ids_recursive(&self, widget_id: WidgetID, out: &mut Vec<(WidgetID, taffy::NodeId)>) { fn collect_children_ids_recursive(&self, widget_id: WidgetID, out: &mut Vec<(WidgetID, taffy::NodeId)>) {
@@ -575,6 +574,7 @@ impl Layout {
registered_components_to_refresh: HashMap::new(), registered_components_to_refresh: HashMap::new(),
widgets_to_tick: Vec::new(), widgets_to_tick: Vec::new(),
tasks: LayoutTasks::new(), tasks: LayoutTasks::new(),
sounds_to_play_once: Vec::new(),
}) })
} }
@@ -659,12 +659,17 @@ impl Layout {
Ok(()) Ok(())
} }
pub fn update(&mut self, size: Vec2, timestep_alpha: f32) -> anyhow::Result<()> { pub fn update(&mut self, params: &mut LayoutUpdateParams) -> anyhow::Result<LayoutUpdateResult> {
let mut alterables = EventAlterables::default(); let mut alterables = EventAlterables::default();
self.animations.process(&self.state, &mut alterables, timestep_alpha); self
.animations
.process(&self.state, &mut alterables, params.timestep_alpha);
self.process_alterables(alterables)?; self.process_alterables(alterables)?;
self.try_recompute_layout(size)?; self.try_recompute_layout(params.size)?;
Ok(())
Ok(LayoutUpdateResult {
sounds_to_play: std::mem::take(&mut self.sounds_to_play_once),
})
} }
pub fn tick(&mut self) -> anyhow::Result<()> { pub fn tick(&mut self) -> anyhow::Result<()> {
@@ -677,8 +682,7 @@ impl Layout {
} }
pub fn process_tasks(&mut self) -> anyhow::Result<()> { pub fn process_tasks(&mut self) -> anyhow::Result<()> {
let tasks = self.tasks.clone(); let mut tasks = self.tasks.drain();
let mut tasks = tasks.0.borrow_mut();
while let Some(task) = tasks.pop_front() { while let Some(task) = tasks.pop_front() {
match task { match task {
LayoutTask::RemoveWidget(widget_id) => { LayoutTask::RemoveWidget(widget_id) => {
@@ -687,6 +691,11 @@ impl Layout {
LayoutTask::ModifyLayoutState(callback) => { LayoutTask::ModifyLayoutState(callback) => {
(*callback)(ModifyLayoutStateData { layout: self })?; (*callback)(ModifyLayoutStateData { layout: self })?;
} }
LayoutTask::PlaySound(sound) => {
if !self.sounds_to_play_once.contains(&sound) {
self.sounds_to_play_once.push(sound);
}
}
} }
} }

View File

@@ -34,6 +34,7 @@ pub mod i18n;
pub mod layout; pub mod layout;
pub mod parser; pub mod parser;
pub mod renderer_vk; pub mod renderer_vk;
pub mod sound;
pub mod stack; pub mod stack;
pub mod task; pub mod task;
pub mod widget; pub mod widget;

6
wgui/src/sound.rs Normal file
View File

@@ -0,0 +1,6 @@
#[derive(Clone, Eq, PartialEq)]
pub enum WguiSoundType {
ButtonMouseEnter,
ButtonPress,
ButtonRelease,
}

View File

@@ -16,6 +16,7 @@ serde = { workspace = true, features = ["rc"] }
xdg.workspace = true xdg.workspace = true
chrono = "0.4.42" chrono = "0.4.42"
smol = "2.0.2" smol = "2.0.2"
wgui = { path = "../wgui/" }
rodio = { version = "0.21.1", default-features = false, features = [ rodio = { version = "0.21.1", default-features = false, features = [
"playback", "playback",
"mp3", "mp3",

View File

@@ -1,6 +1,10 @@
use std::{collections::HashMap, io::Cursor}; use std::{collections::HashMap, io::Cursor};
use rodio::Source; use rodio::Source;
use wgui::{
assets::{self, AssetProvider},
sound::WguiSoundType,
};
pub struct AudioSystem { pub struct AudioSystem {
audio_stream: Option<rodio::OutputStream>, audio_stream: Option<rodio::OutputStream>,
@@ -15,6 +19,14 @@ pub struct SamplePlayer {
samples: HashMap<String, AudioSample>, samples: HashMap<String, AudioSample>,
} }
fn get_sample_name_from_wgui_sound_type(sound: WguiSoundType) -> &'static str {
match sound {
WguiSoundType::ButtonMouseEnter => "wgui_mouse_enter",
WguiSoundType::ButtonPress => "wgui_button_press",
WguiSoundType::ButtonRelease => "wgui_button_release",
}
}
impl SamplePlayer { impl SamplePlayer {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@@ -23,9 +35,39 @@ impl SamplePlayer {
} }
pub fn register_sample(&mut self, sample_name: &str, sample: AudioSample) { pub fn register_sample(&mut self, sample_name: &str, sample: AudioSample) {
log::debug!("registering audio sample \"{sample_name}\"");
self.samples.insert(String::from(sample_name), sample); self.samples.insert(String::from(sample_name), sample);
} }
pub fn register_mp3_sample_from_assets(
&mut self,
sample_name: &str,
assets: &mut dyn AssetProvider,
path: &str,
) -> anyhow::Result<()> {
// load only once
if self.samples.contains_key(sample_name) {
return Ok(());
}
let data = assets.load_from_path(path)?;
self.register_sample(sample_name, AudioSample::from_mp3(&data)?);
Ok(())
}
pub fn register_wgui_samples(&mut self, assets: &mut dyn AssetProvider) -> anyhow::Result<()> {
let mut load = |sound: WguiSoundType| -> anyhow::Result<()> {
let sample_name = get_sample_name_from_wgui_sound_type(sound);
self.register_mp3_sample_from_assets(sample_name, assets, &format!("sound/{}.mp3", sample_name))
};
load(WguiSoundType::ButtonPress)?;
load(WguiSoundType::ButtonRelease)?;
load(WguiSoundType::ButtonMouseEnter)?;
Ok(())
}
pub fn play_sample(&mut self, system: &mut AudioSystem, sample_name: &str) { pub fn play_sample(&mut self, system: &mut AudioSystem, sample_name: &str) {
let Some(sample) = self.samples.get(sample_name) else { let Some(sample) = self.samples.get(sample_name) else {
log::error!("failed to play sample by name {}", sample_name); log::error!("failed to play sample by name {}", sample_name);
@@ -34,6 +76,12 @@ impl SamplePlayer {
system.play_sample(sample); system.play_sample(sample);
} }
pub fn play_wgui_samples(&mut self, system: &mut AudioSystem, samples: Vec<WguiSoundType>) {
for sample in samples {
self.play_sample(system, get_sample_name_from_wgui_sound_type(sample));
}
}
} }
impl Default for SamplePlayer { impl Default for SamplePlayer {

View File

@@ -0,0 +1,8 @@
use wgui::layout::LayoutUpdateResult;
use crate::state::AppState;
pub fn process_layout_result(app: &mut AppState, res: LayoutUpdateResult) {
app.audio_sample_player
.play_wgui_samples(&mut app.audio_system, res.sounds_to_play);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -13,7 +13,7 @@ use wgui::{
MouseMotionEvent, MouseUpEvent, MouseWheelEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent,
}, },
gfx::cmd::WGfxClearMode, gfx::cmd::WGfxClearMode,
layout::{Layout, LayoutParams, WidgetID}, layout::{Layout, LayoutParams, LayoutUpdateParams, WidgetID},
parser::{CustomAttribsInfoOwned, Fetchable, ParserState}, parser::{CustomAttribsInfoOwned, Fetchable, ParserState},
renderer_vk::context::Context as WguiContext, renderer_vk::context::Context as WguiContext,
widget::{EventResult, label::WidgetLabel}, widget::{EventResult, label::WidgetLabel},
@@ -22,6 +22,7 @@ use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
use wlx_common::timestep::Timestep; use wlx_common::timestep::Timestep;
use crate::{ use crate::{
app_misc,
backend::input::{Haptics, HoverResult, PointerHit, PointerMode}, backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
state::AppState, state::AppState,
subsystem::hid::WheelDelta, subsystem::hid::WheelDelta,
@@ -211,8 +212,15 @@ impl<S: 'static> GuiPanel<S> {
}) })
} }
pub fn update_layout(&mut self) -> anyhow::Result<()> { pub fn update_layout(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.layout.update(self.max_size, 0.0) app_misc::process_layout_result(
app,
self.layout.update(&mut LayoutUpdateParams {
size: self.max_size / self.gui_scale,
timestep_alpha: self.timestep.alpha,
})?,
);
Ok(())
} }
pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) -> EventResult { pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) -> EventResult {
@@ -236,9 +244,9 @@ impl<S: 'static> GuiPanel<S> {
} }
impl<S: 'static> OverlayBackend for GuiPanel<S> { impl<S: 'static> OverlayBackend for GuiPanel<S> {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> { fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
if self.layout.content_size.x * self.layout.content_size.y != 0.0 { if self.layout.content_size.x * self.layout.content_size.y != 0.0 {
self.update_layout()?; self.update_layout(app)?;
self.interaction_transform = Some(ui_transform([ self.interaction_transform = Some(ui_transform([
self.layout.content_size.x as _, self.layout.content_size.x as _,
self.layout.content_size.y as _, self.layout.content_size.y as _,
@@ -297,9 +305,7 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> { fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> {
self.context self.context
.update_viewport(&mut app.wgui_shared, rdr.extent, self.gui_scale)?; .update_viewport(&mut app.wgui_shared, rdr.extent, self.gui_scale)?;
self.layout self.update_layout(app)?;
.update(self.max_size / self.gui_scale, self.timestep.alpha)?;
let globals = self.layout.state.globals.clone(); // sorry let globals = self.layout.state.globals.clone(); // sorry
let mut globals = globals.get(); let mut globals = globals.get();

View File

@@ -17,6 +17,7 @@
clippy::cargo_common_metadata, clippy::cargo_common_metadata,
clippy::option_if_let_else clippy::option_if_let_else
)] )]
mod app_misc;
mod backend; mod backend;
mod config; mod config;
mod config_io; mod config_io;

View File

@@ -16,7 +16,7 @@ pub static ANCHOR_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("anchor"
pub fn create_anchor(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> { pub fn create_anchor(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
let mut panel = GuiPanel::new_from_template(app, "gui/anchor.xml", (), Default::default())?; let mut panel = GuiPanel::new_from_template(app, "gui/anchor.xml", (), Default::default())?;
panel.update_layout()?; panel.update_layout(app)?;
Ok(OverlayWindowConfig { Ok(OverlayWindowConfig {
name: ANCHOR_NAME.clone(), name: ANCHOR_NAME.clone(),
@@ -41,7 +41,7 @@ pub static GRAB_HELP_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("grab
pub fn create_grab_help(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> { pub fn create_grab_help(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
let mut panel = GuiPanel::new_from_template(app, "gui/grab-help.xml", (), Default::default())?; let mut panel = GuiPanel::new_from_template(app, "gui/grab-help.xml", (), Default::default())?;
panel.update_layout()?; panel.update_layout(app)?;
let id_watch = panel.parser_state.data.get_widget_id("grabbing_watch")?; let id_watch = panel.parser_state.data.get_widget_id("grabbing_watch")?;
let id_static = panel.parser_state.data.get_widget_id("grabbing_static")?; let id_static = panel.parser_state.data.get_widget_id("grabbing_static")?;

View File

@@ -42,7 +42,7 @@ pub fn create_custom(app: &mut AppState, name: Arc<str>) -> Option<OverlayWindow
.ok()?; .ok()?;
panel panel
.update_layout() .update_layout(app)
.inspect_err(|e| log::warn!("Error layouting '{name}': {e:?}")) .inspect_err(|e| log::warn!("Error layouting '{name}': {e:?}"))
.ok()?; .ok()?;

View File

@@ -1,5 +1,5 @@
use dash_frontend::{ use dash_frontend::{
frontend, frontend::{self, FrontendUpdateParams},
settings::{self, SettingsIO}, settings::{self, SettingsIO},
}; };
use glam::{Affine2, Affine3A, Vec2, vec2, vec3}; use glam::{Affine2, Affine3A, Vec2, vec2, vec3};
@@ -27,6 +27,7 @@ use wlx_common::{
}; };
use crate::{ use crate::{
app_misc,
backend::{ backend::{
input::{Haptics, HoverResult, PointerHit, PointerMode}, input::{Haptics, HoverResult, PointerHit, PointerMode},
task::{OverlayTask, PlayspaceTask, TaskType}, task::{OverlayTask, PlayspaceTask, TaskType},
@@ -103,10 +104,13 @@ impl DashFrontend {
let settings = SimpleSettingsIO::new(); let settings = SimpleSettingsIO::new();
let interface = DashInterfaceLive::new(); let interface = DashInterfaceLive::new();
let frontend = frontend::Frontend::new(frontend::InitParams { let mut frontend = frontend::Frontend::new(frontend::InitParams {
settings: Box::new(settings), settings: Box::new(settings),
interface: Box::new(interface), interface: Box::new(interface),
})?; })?;
frontend.play_startup_sound(&mut app.audio_system, &mut app.audio_sample_player)?;
let context = WguiContext::new(&mut app.wgui_shared, 1.0)?; let context = WguiContext::new(&mut app.wgui_shared, 1.0)?;
Ok(Self { Ok(Self {
inner: frontend, inner: frontend,
@@ -119,12 +123,14 @@ impl DashFrontend {
} }
fn update(&mut self, app: &mut AppState, timestep_alpha: f32) -> anyhow::Result<()> { fn update(&mut self, app: &mut AppState, timestep_alpha: f32) -> anyhow::Result<()> {
self.inner.update( let res = self.inner.update(FrontendUpdateParams {
app, data: app,
DASH_RES_VEC2.x / GUI_SCALE, width: DASH_RES_VEC2.x / GUI_SCALE,
DASH_RES_VEC2.y / GUI_SCALE, height: DASH_RES_VEC2.y / GUI_SCALE,
timestep_alpha, timestep_alpha,
) })?;
app_misc::process_layout_result(app, res);
Ok(())
} }
fn push_event(&mut self, event: &WguiEvent) -> EventResult { fn push_event(&mut self, event: &WguiEvent) -> EventResult {

View File

@@ -167,7 +167,7 @@ impl OverlayBackend for EditModeBackendWrapper {
let gui_scale = (new_size.x / 750.0).min(new_size.y / 300.0); let gui_scale = (new_size.x / 750.0).min(new_size.y / 300.0);
self.panel.gui_scale = (gui_scale * 4.0).round() / 4.0; self.panel.gui_scale = (gui_scale * 4.0).round() / 4.0;
self.panel.update_layout()?; self.panel.update_layout(app)?;
} }
self.can_render_inner = true; self.can_render_inner = true;

View File

@@ -1,13 +1,13 @@
use std::{collections::HashMap, rc::Rc}; use std::{collections::HashMap, rc::Rc};
use crate::{gui::panel::GuiPanel, state::AppState, subsystem::hid::XkbKeymap}; use crate::{app_misc, gui::panel::GuiPanel, state::AppState, subsystem::hid::XkbKeymap};
use glam::{FloatExt, Mat4, Vec2, vec2, vec3}; use glam::{FloatExt, Mat4, Vec2, vec2, vec3};
use wgui::{ use wgui::{
animation::{Animation, AnimationEasing}, animation::{Animation, AnimationEasing},
assets::AssetPath, assets::AssetPath,
drawing::{self, Color}, drawing::{self, Color},
event::{self, CallbackMetadata, EventListenerKind}, event::{self, CallbackMetadata, EventListenerKind},
layout::LayoutParams, layout::{LayoutParams, LayoutUpdateParams},
parser::Fetchable, parser::Fetchable,
renderer_vk::util, renderer_vk::util,
taffy::{self, prelude::length}, taffy::{self, prelude::length},
@@ -264,7 +264,13 @@ pub(super) fn create_keyboard_panel(
} }
} }
panel.layout.update(vec2(2048., 2048.), 0.0)?; app_misc::process_layout_result(
app,
panel.layout.update(&mut LayoutUpdateParams {
size: vec2(2048., 2048.),
timestep_alpha: 0.0,
})?,
);
panel.parser_state = gui_state_key; panel.parser_state = gui_state_key;
Ok(panel) Ok(panel)

View File

@@ -179,7 +179,10 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
.inspect_err(|e| log::error!("Could not create toast: {e:?}")) .inspect_err(|e| log::error!("Could not create toast: {e:?}"))
.ok()?; .ok()?;
panel.update_layout().context("layout update failed").ok()?; panel
.update_layout(app)
.context("layout update failed")
.ok()?;
Some(OverlayWindowConfig { Some(OverlayWindowConfig {
name: TOAST_NAME.clone(), name: TOAST_NAME.clone(),

View File

@@ -551,7 +551,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
align_to_hmd: false, align_to_hmd: false,
}; };
panel.update_layout()?; panel.update_layout(app)?;
Ok(OverlayWindowConfig { Ok(OverlayWindowConfig {
name: WATCH_NAME.into(), name: WATCH_NAME.into(),

View File

@@ -109,6 +109,9 @@ impl AppState {
audio::AudioSample::from_mp3(include_bytes!("res/toast.mp3"))?, audio::AudioSample::from_mp3(include_bytes!("res/toast.mp3"))?,
); );
let mut assets = Box::new(gui::asset::GuiAsset {});
audio_sample_player.register_wgui_samples(assets.as_mut())?;
let mut defaults = wgui::globals::Defaults::default(); let mut defaults = wgui::globals::Defaults::default();
{ {
@@ -147,7 +150,7 @@ impl AppState {
anchor: Affine3A::IDENTITY, anchor: Affine3A::IDENTITY,
anchor_grabbed: false, anchor_grabbed: false,
wgui_globals: WguiGlobals::new( wgui_globals: WguiGlobals::new(
Box::new(gui::asset::GuiAsset {}), assets,
defaults, defaults,
&WguiFontConfig::default(), &WguiFontConfig::default(),
get_config_file_path(&theme), get_config_file_path(&theme),