dash and wgui sounds
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -6801,6 +6801,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smol",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
"xdg 3.0.0",
|
||||
]
|
||||
|
||||
|
||||
BIN
dash-frontend/assets/sound/app_start.mp3
Normal file
BIN
dash-frontend/assets/sound/app_start.mp3
Normal file
Binary file not shown.
BIN
dash-frontend/assets/sound/startup.mp3
Normal file
BIN
dash-frontend/assets/sound/startup.mp3
Normal file
Binary file not shown.
@@ -8,19 +8,20 @@ use wgui::{
|
||||
font_config::WguiFontConfig,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutParams, WidgetID},
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams, LayoutUpdateResult, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
sound::WguiSoundType,
|
||||
task::Tasks,
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
|
||||
windowing::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||
};
|
||||
use wlx_common::{dash_interface::BoxDashInterface, timestep::Timestep};
|
||||
use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
|
||||
|
||||
use crate::{
|
||||
assets, settings,
|
||||
tab::{
|
||||
apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings,
|
||||
Tab, TabType,
|
||||
Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses,
|
||||
settings::TabSettings,
|
||||
},
|
||||
util::{
|
||||
desktop_finder::DesktopFinder,
|
||||
@@ -68,6 +69,13 @@ pub struct Frontend<T> {
|
||||
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 settings: Box<dyn settings::SettingsIO>,
|
||||
pub interface: BoxDashInterface<T>,
|
||||
@@ -164,15 +172,27 @@ impl<T: 'static> Frontend<T> {
|
||||
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();
|
||||
|
||||
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() {
|
||||
tab.update(self, data)?;
|
||||
tab.update(self, params.data)?;
|
||||
|
||||
self.current_tab = Some(tab);
|
||||
}
|
||||
@@ -180,13 +200,14 @@ impl<T: 'static> Frontend<T> {
|
||||
// process async runtime tasks
|
||||
while self.executor.try_tick() {}
|
||||
|
||||
self.tick(width, height, timestep_alpha)?;
|
||||
let res = self.tick(params)?;
|
||||
|
||||
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
|
||||
if self.ticks.is_multiple_of(1000) {
|
||||
self.update_time()?;
|
||||
@@ -197,11 +218,12 @@ impl<T: 'static> Frontend<T> {
|
||||
while self.timestep.on_tick() {
|
||||
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<()> {
|
||||
|
||||
@@ -68,7 +68,7 @@ pub struct CardProperties {
|
||||
pub device_name: String, // alsa_card.pci-0000_0c_00.4
|
||||
|
||||
#[serde(rename = "device.nick")]
|
||||
pub device_nick: String, // HD-Audio Generic
|
||||
pub device_nick: Option<String>, // HD-Audio Generic
|
||||
}
|
||||
|
||||
#[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)?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
1
uidev/assets/sound/wgui_button_press.mp3
Symbolic link
1
uidev/assets/sound/wgui_button_press.mp3
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../wlx-overlay-s/src/assets/sound/wgui_button_press.mp3
|
||||
1
uidev/assets/sound/wgui_button_release.mp3
Symbolic link
1
uidev/assets/sound/wgui_button_release.mp3
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../wlx-overlay-s/src/assets/sound/wgui_button_release.mp3
|
||||
1
uidev/assets/sound/wgui_mouse_enter.mp3
Symbolic link
1
uidev/assets/sound/wgui_mouse_enter.mp3
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../wlx-overlay-s/src/assets/sound/wgui_mouse_enter.mp3
|
||||
@@ -18,6 +18,7 @@ use vulkano::{
|
||||
sync::GpuFuture,
|
||||
};
|
||||
use wgui::{
|
||||
assets::AssetProvider,
|
||||
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
|
||||
gfx::{WGfx, cmd::WGfxClearMode},
|
||||
renderer_vk::{self},
|
||||
@@ -27,7 +28,7 @@ use winit::{
|
||||
event_loop::ControlFlow,
|
||||
keyboard::{KeyCode, PhysicalKey},
|
||||
};
|
||||
use wlx_common::timestep::Timestep;
|
||||
use wlx_common::{audio, timestep::Timestep};
|
||||
|
||||
use crate::{
|
||||
rate_limiter::RateLimiter,
|
||||
@@ -59,12 +60,15 @@ fn init_logging() {
|
||||
.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();
|
||||
Ok(match name.as_str() {
|
||||
"dashboard" => Box::new(TestbedDashboard::new()?),
|
||||
"" => Box::new(TestbedGeneric::new()?),
|
||||
_ => Box::new(TestbedAny::new(&name)?),
|
||||
"" => Box::new(TestbedGeneric::new(assets)?),
|
||||
_ => 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 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;
|
||||
|
||||
@@ -291,6 +297,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
width: (swapchain_size[0] as f32 / scale) as _,
|
||||
height: (swapchain_size[1] as f32 / scale) as _,
|
||||
timestep_alpha: timestep.alpha,
|
||||
audio_system: &mut audio_system,
|
||||
audio_sample_player: &mut audio_sample_player,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
use wgui::layout::Layout;
|
||||
use wgui::layout::{Layout, LayoutUpdateResult};
|
||||
use wlx_common::audio;
|
||||
|
||||
pub mod testbed_any;
|
||||
pub mod testbed_dashboard;
|
||||
pub mod testbed_generic;
|
||||
|
||||
pub struct TestbedUpdateParams {
|
||||
pub struct TestbedUpdateParams<'a> {
|
||||
pub width: f32,
|
||||
pub height: 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 {
|
||||
|
||||
@@ -9,7 +9,7 @@ use wgui::{
|
||||
assets::AssetPath,
|
||||
font_config::WguiFontConfig,
|
||||
globals::WguiGlobals,
|
||||
layout::{Layout, LayoutParams},
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
||||
parser::{ParseDocumentParams, ParserState},
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ pub struct 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") {
|
||||
AssetPath::FileOrBuiltIn(name)
|
||||
} else {
|
||||
@@ -29,7 +29,7 @@ impl TestbedAny {
|
||||
};
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
Box::new(assets::Asset {}),
|
||||
assets,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
@@ -48,11 +48,12 @@ impl TestbedAny {
|
||||
}
|
||||
|
||||
impl Testbed for TestbedAny {
|
||||
fn update(&mut self, params: TestbedUpdateParams) -> anyhow::Result<()> {
|
||||
self.layout.update(
|
||||
Vec2::new(params.width, params.height),
|
||||
params.timestep_alpha,
|
||||
)?;
|
||||
fn update(&mut self, mut params: TestbedUpdateParams) -> anyhow::Result<()> {
|
||||
let res = self.layout.update(&mut LayoutUpdateParams {
|
||||
size: Vec2::new(params.width, params.height),
|
||||
timestep_alpha: params.timestep_alpha,
|
||||
})?;
|
||||
params.process_layout_result(res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::testbed::{Testbed, TestbedUpdateParams};
|
||||
use dash_frontend::{
|
||||
frontend,
|
||||
frontend::{self, FrontendUpdateParams},
|
||||
settings::{self, SettingsIO},
|
||||
};
|
||||
use wgui::layout::Layout;
|
||||
@@ -70,13 +70,15 @@ impl TestbedDashboard {
|
||||
}
|
||||
|
||||
impl Testbed for TestbedDashboard {
|
||||
fn update(&mut self, params: TestbedUpdateParams) -> anyhow::Result<()> {
|
||||
self.frontend.update(
|
||||
&mut (), /* nothing */
|
||||
params.width,
|
||||
params.height,
|
||||
params.timestep_alpha,
|
||||
)
|
||||
fn update(&mut self, mut params: TestbedUpdateParams) -> anyhow::Result<()> {
|
||||
let res = self.frontend.update(FrontendUpdateParams {
|
||||
data: &mut (), /* nothing */
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
timestep_alpha: params.timestep_alpha,
|
||||
})?;
|
||||
params.process_layout_result(res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn layout(&mut self) -> &mut Layout {
|
||||
|
||||
@@ -17,7 +17,7 @@ use wgui::{
|
||||
font_config::WguiFontConfig,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutParams, Widget},
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams, Widget},
|
||||
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
||||
taffy,
|
||||
task::Tasks,
|
||||
@@ -73,11 +73,11 @@ fn handle_button_click(button: Rc<ComponentButton>, label: Widget, text: &'stati
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
Box::new(assets::Asset {}),
|
||||
assets,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
@@ -223,10 +223,12 @@ impl Testbed for TestbedGeneric {
|
||||
let data = self.data.clone();
|
||||
let mut data = data.borrow_mut();
|
||||
|
||||
self.layout.update(
|
||||
Vec2::new(params.width, params.height),
|
||||
params.timestep_alpha,
|
||||
)?;
|
||||
let res = self.layout.update(&mut LayoutUpdateParams {
|
||||
size: Vec2::new(params.width, params.height),
|
||||
timestep_alpha: params.timestep_alpha,
|
||||
})?;
|
||||
|
||||
params.process_layout_result(res);
|
||||
|
||||
for task in self.tasks.drain() {
|
||||
self.process_task(&task, &mut params, &mut data)?;
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::{
|
||||
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
|
||||
util::centered_matrix,
|
||||
},
|
||||
sound::WguiSoundType,
|
||||
widget::{
|
||||
self, ConstructEssentials, EventResult, WidgetData,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
@@ -270,6 +271,7 @@ fn register_event_mouse_enter(
|
||||
listeners.register(
|
||||
EventListenerKind::MouseEnter,
|
||||
Box::new(move |common, event_data, (), ()| {
|
||||
common.alterables.play_sound(WguiSoundType::ButtonMouseEnter);
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.mark_redraw();
|
||||
common
|
||||
@@ -332,6 +334,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
|
||||
);
|
||||
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.play_sound(WguiSoundType::ButtonPress);
|
||||
common.alterables.mark_redraw();
|
||||
|
||||
if state.hovered {
|
||||
@@ -362,6 +365,7 @@ fn register_event_mouse_release(
|
||||
}
|
||||
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.play_sound(WguiSoundType::ButtonRelease);
|
||||
common.alterables.mark_redraw();
|
||||
|
||||
if state.down {
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{
|
||||
animation::{self, Animation},
|
||||
i18n::I18n,
|
||||
layout::{LayoutState, LayoutTask, WidgetID},
|
||||
sound::WguiSoundType,
|
||||
stack::{ScissorStack, Transform, TransformStack},
|
||||
widget::{EventResult, WidgetData, WidgetObj},
|
||||
};
|
||||
@@ -140,6 +141,10 @@ impl EventAlterables {
|
||||
pub fn animate(&mut self, animation: 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> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
collections::{HashMap, HashSet},
|
||||
io::Write,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
@@ -11,6 +11,8 @@ use crate::{
|
||||
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
|
||||
event::{self, CallbackDataCommon, EventAlterables},
|
||||
globals::WguiGlobals,
|
||||
sound::WguiSoundType,
|
||||
task::Tasks,
|
||||
widget::{self, EventParams, EventResult, WidgetObj, WidgetState, WidgetStateFlags, div::WidgetDiv},
|
||||
};
|
||||
|
||||
@@ -119,25 +121,24 @@ pub struct ModifyLayoutStateData<'a> {
|
||||
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 enum LayoutTask {
|
||||
RemoveWidget(WidgetID),
|
||||
ModifyLayoutState(ModifyLayoutStateFunc),
|
||||
PlaySound(WguiSoundType),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
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 type LayoutTasks = Tasks<LayoutTask>;
|
||||
|
||||
pub struct Layout {
|
||||
pub state: LayoutState,
|
||||
@@ -146,6 +147,7 @@ pub struct Layout {
|
||||
|
||||
components_to_refresh_once: HashSet<Component>,
|
||||
registered_components_to_refresh: HashMap<taffy::NodeId, Component>,
|
||||
sounds_to_play_once: Vec<WguiSoundType>,
|
||||
|
||||
pub widgets_to_tick: Vec<WidgetID>,
|
||||
|
||||
@@ -266,15 +268,12 @@ impl Layout {
|
||||
}
|
||||
|
||||
pub fn get_parent(&self, widget_id: WidgetID) -> Option<(WidgetID, taffy::NodeId)> {
|
||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||
return None;
|
||||
};
|
||||
let node_id = self.state.nodes.get(widget_id)?;
|
||||
|
||||
self.state.tree.parent(*node_id).map(|parent_id| {
|
||||
let parent_widget_id = self.state.tree.get_node_context(parent_id).unwrap();
|
||||
(*parent_widget_id, parent_id)
|
||||
}
|
||||
)
|
||||
let parent_widget_id = self.state.tree.get_node_context(parent_id).unwrap();
|
||||
(*parent_widget_id, parent_id)
|
||||
})
|
||||
}
|
||||
|
||||
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(),
|
||||
widgets_to_tick: Vec::new(),
|
||||
tasks: LayoutTasks::new(),
|
||||
sounds_to_play_once: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -659,12 +659,17 @@ impl Layout {
|
||||
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();
|
||||
self.animations.process(&self.state, &mut alterables, timestep_alpha);
|
||||
self
|
||||
.animations
|
||||
.process(&self.state, &mut alterables, params.timestep_alpha);
|
||||
self.process_alterables(alterables)?;
|
||||
self.try_recompute_layout(size)?;
|
||||
Ok(())
|
||||
self.try_recompute_layout(params.size)?;
|
||||
|
||||
Ok(LayoutUpdateResult {
|
||||
sounds_to_play: std::mem::take(&mut self.sounds_to_play_once),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> anyhow::Result<()> {
|
||||
@@ -677,8 +682,7 @@ impl Layout {
|
||||
}
|
||||
|
||||
pub fn process_tasks(&mut self) -> anyhow::Result<()> {
|
||||
let tasks = self.tasks.clone();
|
||||
let mut tasks = tasks.0.borrow_mut();
|
||||
let mut tasks = self.tasks.drain();
|
||||
while let Some(task) = tasks.pop_front() {
|
||||
match task {
|
||||
LayoutTask::RemoveWidget(widget_id) => {
|
||||
@@ -687,6 +691,11 @@ impl Layout {
|
||||
LayoutTask::ModifyLayoutState(callback) => {
|
||||
(*callback)(ModifyLayoutStateData { layout: self })?;
|
||||
}
|
||||
LayoutTask::PlaySound(sound) => {
|
||||
if !self.sounds_to_play_once.contains(&sound) {
|
||||
self.sounds_to_play_once.push(sound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ pub mod i18n;
|
||||
pub mod layout;
|
||||
pub mod parser;
|
||||
pub mod renderer_vk;
|
||||
pub mod sound;
|
||||
pub mod stack;
|
||||
pub mod task;
|
||||
pub mod widget;
|
||||
|
||||
6
wgui/src/sound.rs
Normal file
6
wgui/src/sound.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum WguiSoundType {
|
||||
ButtonMouseEnter,
|
||||
ButtonPress,
|
||||
ButtonRelease,
|
||||
}
|
||||
@@ -16,6 +16,7 @@ serde = { workspace = true, features = ["rc"] }
|
||||
xdg.workspace = true
|
||||
chrono = "0.4.42"
|
||||
smol = "2.0.2"
|
||||
wgui = { path = "../wgui/" }
|
||||
rodio = { version = "0.21.1", default-features = false, features = [
|
||||
"playback",
|
||||
"mp3",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::{collections::HashMap, io::Cursor};
|
||||
|
||||
use rodio::Source;
|
||||
use wgui::{
|
||||
assets::{self, AssetProvider},
|
||||
sound::WguiSoundType,
|
||||
};
|
||||
|
||||
pub struct AudioSystem {
|
||||
audio_stream: Option<rodio::OutputStream>,
|
||||
@@ -15,6 +19,14 @@ pub struct SamplePlayer {
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -23,9 +35,39 @@ impl SamplePlayer {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
let Some(sample) = self.samples.get(sample_name) else {
|
||||
log::error!("failed to play sample by name {}", sample_name);
|
||||
@@ -34,6 +76,12 @@ impl SamplePlayer {
|
||||
|
||||
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 {
|
||||
|
||||
8
wlx-overlay-s/src/app_misc.rs
Normal file
8
wlx-overlay-s/src/app_misc.rs
Normal 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);
|
||||
}
|
||||
BIN
wlx-overlay-s/src/assets/sound/wgui_button_press.mp3
Normal file
BIN
wlx-overlay-s/src/assets/sound/wgui_button_press.mp3
Normal file
Binary file not shown.
BIN
wlx-overlay-s/src/assets/sound/wgui_button_release.mp3
Normal file
BIN
wlx-overlay-s/src/assets/sound/wgui_button_release.mp3
Normal file
Binary file not shown.
BIN
wlx-overlay-s/src/assets/sound/wgui_mouse_enter.mp3
Normal file
BIN
wlx-overlay-s/src/assets/sound/wgui_mouse_enter.mp3
Normal file
Binary file not shown.
@@ -13,7 +13,7 @@ use wgui::{
|
||||
MouseMotionEvent, MouseUpEvent, MouseWheelEvent,
|
||||
},
|
||||
gfx::cmd::WGfxClearMode,
|
||||
layout::{Layout, LayoutParams, WidgetID},
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams, WidgetID},
|
||||
parser::{CustomAttribsInfoOwned, Fetchable, ParserState},
|
||||
renderer_vk::context::Context as WguiContext,
|
||||
widget::{EventResult, label::WidgetLabel},
|
||||
@@ -22,6 +22,7 @@ use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
|
||||
use wlx_common::timestep::Timestep;
|
||||
|
||||
use crate::{
|
||||
app_misc,
|
||||
backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
|
||||
state::AppState,
|
||||
subsystem::hid::WheelDelta,
|
||||
@@ -211,8 +212,15 @@ impl<S: 'static> GuiPanel<S> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_layout(&mut self) -> anyhow::Result<()> {
|
||||
self.layout.update(self.max_size, 0.0)
|
||||
pub fn update_layout(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
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 {
|
||||
@@ -236,9 +244,9 @@ impl<S: 'static> 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 {
|
||||
self.update_layout()?;
|
||||
self.update_layout(app)?;
|
||||
self.interaction_transform = Some(ui_transform([
|
||||
self.layout.content_size.x 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<()> {
|
||||
self.context
|
||||
.update_viewport(&mut app.wgui_shared, rdr.extent, self.gui_scale)?;
|
||||
self.layout
|
||||
.update(self.max_size / self.gui_scale, self.timestep.alpha)?;
|
||||
|
||||
self.update_layout(app)?;
|
||||
let globals = self.layout.state.globals.clone(); // sorry
|
||||
let mut globals = globals.get();
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
clippy::cargo_common_metadata,
|
||||
clippy::option_if_let_else
|
||||
)]
|
||||
mod app_misc;
|
||||
mod backend;
|
||||
mod config;
|
||||
mod config_io;
|
||||
|
||||
@@ -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> {
|
||||
let mut panel = GuiPanel::new_from_template(app, "gui/anchor.xml", (), Default::default())?;
|
||||
panel.update_layout()?;
|
||||
panel.update_layout(app)?;
|
||||
|
||||
Ok(OverlayWindowConfig {
|
||||
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> {
|
||||
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_static = panel.parser_state.data.get_widget_id("grabbing_static")?;
|
||||
|
||||
@@ -42,7 +42,7 @@ pub fn create_custom(app: &mut AppState, name: Arc<str>) -> Option<OverlayWindow
|
||||
.ok()?;
|
||||
|
||||
panel
|
||||
.update_layout()
|
||||
.update_layout(app)
|
||||
.inspect_err(|e| log::warn!("Error layouting '{name}': {e:?}"))
|
||||
.ok()?;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use dash_frontend::{
|
||||
frontend,
|
||||
frontend::{self, FrontendUpdateParams},
|
||||
settings::{self, SettingsIO},
|
||||
};
|
||||
use glam::{Affine2, Affine3A, Vec2, vec2, vec3};
|
||||
@@ -27,6 +27,7 @@ use wlx_common::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app_misc,
|
||||
backend::{
|
||||
input::{Haptics, HoverResult, PointerHit, PointerMode},
|
||||
task::{OverlayTask, PlayspaceTask, TaskType},
|
||||
@@ -103,10 +104,13 @@ impl DashFrontend {
|
||||
let settings = SimpleSettingsIO::new();
|
||||
let interface = DashInterfaceLive::new();
|
||||
|
||||
let frontend = frontend::Frontend::new(frontend::InitParams {
|
||||
let mut frontend = frontend::Frontend::new(frontend::InitParams {
|
||||
settings: Box::new(settings),
|
||||
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)?;
|
||||
Ok(Self {
|
||||
inner: frontend,
|
||||
@@ -119,12 +123,14 @@ impl DashFrontend {
|
||||
}
|
||||
|
||||
fn update(&mut self, app: &mut AppState, timestep_alpha: f32) -> anyhow::Result<()> {
|
||||
self.inner.update(
|
||||
app,
|
||||
DASH_RES_VEC2.x / GUI_SCALE,
|
||||
DASH_RES_VEC2.y / GUI_SCALE,
|
||||
let res = self.inner.update(FrontendUpdateParams {
|
||||
data: app,
|
||||
width: DASH_RES_VEC2.x / GUI_SCALE,
|
||||
height: DASH_RES_VEC2.y / GUI_SCALE,
|
||||
timestep_alpha,
|
||||
)
|
||||
})?;
|
||||
app_misc::process_layout_result(app, res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_event(&mut self, event: &WguiEvent) -> EventResult {
|
||||
|
||||
@@ -167,7 +167,7 @@ impl OverlayBackend for EditModeBackendWrapper {
|
||||
|
||||
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.update_layout()?;
|
||||
self.panel.update_layout(app)?;
|
||||
}
|
||||
|
||||
self.can_render_inner = true;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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 wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
assets::AssetPath,
|
||||
drawing::{self, Color},
|
||||
event::{self, CallbackMetadata, EventListenerKind},
|
||||
layout::LayoutParams,
|
||||
layout::{LayoutParams, LayoutUpdateParams},
|
||||
parser::Fetchable,
|
||||
renderer_vk::util,
|
||||
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;
|
||||
|
||||
Ok(panel)
|
||||
|
||||
@@ -179,7 +179,10 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
|
||||
.inspect_err(|e| log::error!("Could not create toast: {e:?}"))
|
||||
.ok()?;
|
||||
|
||||
panel.update_layout().context("layout update failed").ok()?;
|
||||
panel
|
||||
.update_layout(app)
|
||||
.context("layout update failed")
|
||||
.ok()?;
|
||||
|
||||
Some(OverlayWindowConfig {
|
||||
name: TOAST_NAME.clone(),
|
||||
|
||||
@@ -551,7 +551,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
align_to_hmd: false,
|
||||
};
|
||||
|
||||
panel.update_layout()?;
|
||||
panel.update_layout(app)?;
|
||||
|
||||
Ok(OverlayWindowConfig {
|
||||
name: WATCH_NAME.into(),
|
||||
|
||||
@@ -109,6 +109,9 @@ impl AppState {
|
||||
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();
|
||||
|
||||
{
|
||||
@@ -147,7 +150,7 @@ impl AppState {
|
||||
anchor: Affine3A::IDENTITY,
|
||||
anchor_grabbed: false,
|
||||
wgui_globals: WguiGlobals::new(
|
||||
Box::new(gui::asset::GuiAsset {}),
|
||||
assets,
|
||||
defaults,
|
||||
&WguiFontConfig::default(),
|
||||
get_config_file_path(&theme),
|
||||
|
||||
Reference in New Issue
Block a user