settings implementation

This commit is contained in:
galister
2026-01-07 02:16:43 +09:00
parent 51dbb6f14d
commit ce558855d4
21 changed files with 561 additions and 351 deletions

1
Cargo.lock generated
View File

@@ -6928,6 +6928,7 @@ dependencies = [
"rodio",
"rust-ini",
"serde",
"serde_json",
"smol",
"walkdir",
"wayvr-ipc",

View File

@@ -2,53 +2,37 @@
<include src="t_tab_title.xml" />
<include src="../t_group_box.xml" />
<template name="SettingsGroupBox">
<rectangle macro="group_box" id="${id}">
<GroupBoxTitle translation="${translation}" src_builtin="${icon}" />
</rectangle>
</template>
<template name="CheckBoxSetting">
<CheckBox id="${id}" text="${text}" translation="${translation}" checked="${checked}" tooltip="${tooltip}" />
</template>
<template name="SliderSetting">
<label text="${text}" translation="${translation}" />
<Slider id="${id}" width="250" height="24" min_value="${min}" max_value="${max}" step="${step}" value="${value}" tooltip="${tooltip}" />
</template>
<template name="SelectSetting">
<label text="${text}" translation="${translation}" tooltip="${tooltip}" />
<RadioGroup id="${id}" />
</template>
<template name="SelectOption">
<RadioBox text="${text}" translation="${translation}" value="${value}" tooltip="${tooltip}" />
</template>
<elements>
<TabTitle translation="SETTINGS" icon="dashboard/settings.svg" />
<div flex_wrap="wrap" justify_content="stretch" gap="4">
<!-- Home screen -->
<rectangle macro="group_box">
<GroupBoxTitle translation="HOME_SCREEN" src_builtin="dashboard/wayvr_dashboard.svg" />
<CheckBox id="cb_hide_username" translation="APP_SETTINGS.HIDE_USERNAME" />
</rectangle>
<!-- General settings -->
<rectangle macro="group_box">
<GroupBoxTitle translation="GENERAL_SETTINGS" src_builtin="dashboard/settings.svg" />
<CheckBox id="cb_am_pm_clock" text="AM/PM clock" />
<CheckBox id="cb_opaque_background" translation="APP_SETTINGS.OPAQUE_BACKGROUND" />
</rectangle>
<!-- Application launcher -->
<rectangle macro="group_box">
<GroupBoxTitle translation="APPLICATION_LAUNCHER" src_builtin="dashboard/apps.svg" />
<CheckBox id="cb_xwayland_by_default" translation="APP_SETTINGS.RUN_IN_XWAYLAND_MODE_BY_DEFAULT" />
</rectangle>
<!-- headset settings -->
<rectangle macro="group_box">
<GroupBoxTitle translation="APP_SETTINGS.HEADSET_SETTINGS" src_builtin="dashboard/vr.svg" />
<label translation="APP_SETTINGS.BRIGHTNESS" />
<Slider width="100" height="24" min_value="0.0" max_value="100.0" />
</rectangle>
<!-- wlx-overlay-s settings -->
<rectangle macro="group_box">
<GroupBoxTitle translation="APP_SETTINGS.WLX_OVERLAY_S_SETTINGS" src_builtin="dashboard/vr.svg" />
<CheckBox translation="APP_SETTINGS.WLX.NOTIFICATIONS_ENABLED" />
<CheckBox translation="APP_SETTINGS.WLX.NOTIFICATIONS_SOUND_ENABLED" />
<CheckBox translation="APP_SETTINGS.WLX.KEYBOARD_SOUND_ENABLED" />
<CheckBox translation="APP_SETTINGS.WLX.BLOCK_GAME_INPUT" />
<label translation="APP_SETTINGS.WLX.SPACE_DRAG_MULTIPLIER" />
<Slider width="100" height="24" min_value="0.0" max_value="3.0" />
<CheckBox translation="APP_SETTINGS.WLX.SPACE_DRAG_ROTATION_ENABLED" />
<CheckBox translation="APP_SETTINGS.WLX.SHOW_SKYBOX" />
<CheckBox translation="APP_SETTINGS.WLX.ENABLE_PASSTHROUGH" />
</rectangle>
</div>
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" />
<div>
<Button color="#AA3333" height="32" sprite_src_builtin="dashboard/refresh.svg" translation="APP_SETTINGS.RESTART_SOFTWARE" />
</div>
</elements>
</layout>
</layout>

View File

@@ -3,23 +3,68 @@
"RECENTER_PLAYSPACE": "Re-center playspace"
},
"APP_SETTINGS": {
"LOOK_AND_FEEL": "Look & Feel",
"OPAQUE_BACKGROUND": "Opaque background",
"HIDE_USERNAME": "Hide username",
"HIDE_GRAB_HELP": "Hide grab help",
"ANIMATION_SPEED": "UI Animation speed",
"ROUND_MULTIPLIER": "UI Edge roundness",
"SINGLE_SET_MODE": "Single set mode",
"USE_SKYBOX": "Enable skybox",
"USE_PASSTHROUGH": "Enable passthrough",
"CLOCK_12H": "12-hour clock",
"FEATURES": "Features",
"NOTIFICATIONS_ENABLED": "Enable notifications",
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
"SPACE_DRAG_MULTIPLIER": "Space drag multiplier",
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
"BLOCK_GAME_INPUT": "Block game input",
"BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignore watch when blocking input",
"CONTROLS": "Controls",
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
"LEFT_HANDED_MOUSE": "Left-handed mouse",
"ALLOW_SLIDING": "Stick interaction during grab",
"INVERT_SCROLL_DIRECTION_X": "Invert horizontal scroll direction",
"INVERT_SCROLL_DIRECTION_Y": "Invert vertical scroll direction",
"SCROLL_SPEED": "Scroll speed",
"LONG_PRESS_DURATION": "Long press duration",
"POINTER_LERP_FACTOR": "Pointer smoothing",
"XR_CLICK_SENSITIVITY": "XR click sensitivity",
"XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity",
"CLICK_FREEZE_TIME_MS": "Click freeze time (ms)",
"MISC": "Miscellaneous",
"XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default",
"UPRIGHT_SCREEN_FIX": "Upright screen fix",
"DOUBLE_CURSOR_FIX": "Double cursor fix",
"SCREEN_RENDER_DOWN": "Render screen at lower resolution",
"UPRIGHT_SCREEN_FIX_HELP": "Fixes upright screens on some desktops",
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
"SINGLE_SET_MODE_HELP": "Optimize the watch for working with a single set",
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Must be lower than click",
"CLICK_FREEZE_TIME_MS_HELP": "Helps with double-click precision",
"LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped",
"BLOCK_GAME_INPUT_HELP": "Blocks all input when an overlay is hovered",
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Do not block input when watch is hovered",
"USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough",
"USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it",
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
"__UNUSED": "---- Below this are unused values ----",
"BRIGHTNESS": "Brightness",
"HEADSET_SETTINGS": "Headset settings",
"HIDE_USERNAME": "Hide username",
"OPAQUE_BACKGROUND": "Opaque background",
"RESTART_SOFTWARE": "Restart software",
"RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "Run in XWayland mode by default",
"WLX": {
"BLOCK_GAME_INPUT": "Block game input",
"ENABLE_PASSTHROUGH": "Enable passthrough",
"KEYBOARD_SOUND_ENABLED": "Keyboard sound enabled",
"NOTIFICATIONS_ENABLED": "Notifications enabled",
"NOTIFICATIONS_SOUND_ENABLED": "Notifications sound enabled",
"SHOW_SKYBOX": "Show skybox",
"SPACE_DRAG_MULTIPLIER": "Space-drag multiplier",
"SPACE_DRAG_ROTATION_ENABLED": "Enable rotation in space-drag"
},
"WLX_OVERLAY_S_SETTINGS": "WlxOverlay-S settings"
"RESTART_SOFTWARE": "Restart software"
},
"APPLICATION_LAUNCHER": "Application launcher",
"APPLICATION_STARTED": "Application started",

View File

@@ -17,10 +17,10 @@ use wgui::{
use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
use crate::{
assets, settings,
assets,
tab::{
Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses,
settings::TabSettings,
apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings,
Tab, TabType,
},
util::{
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
@@ -41,7 +41,6 @@ pub struct Frontend<T> {
pub layout: Layout,
globals: WguiGlobals,
pub settings: Box<dyn settings::SettingsIO>,
pub interface: BoxDashInterface<T>,
// async runtime executor
@@ -79,7 +78,6 @@ pub struct FrontendUpdateResult {
}
pub struct InitParams<T> {
pub settings: Box<dyn settings::SettingsIO>,
pub interface: BoxDashInterface<T>,
}
@@ -104,7 +102,7 @@ pub enum FrontendTask {
}
impl<T: 'static> Frontend<T> {
pub fn new(params: InitParams<T>) -> anyhow::Result<Frontend<T>> {
pub fn new(params: InitParams<T>, data: &mut T) -> anyhow::Result<Frontend<T>> {
let mut assets = Box::new(assets::Asset {});
let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?;
@@ -159,7 +157,6 @@ impl<T: 'static> Frontend<T> {
id_rect_content,
},
timestep,
settings: params.settings,
interface: params.interface,
popup_manager,
toast_manager,
@@ -170,8 +167,8 @@ impl<T: 'static> Frontend<T> {
};
// init some things first
frontend.update_background()?;
frontend.update_time()?;
frontend.update_background(data)?;
frontend.update_time(data)?;
Frontend::register_widgets(&mut frontend)?;
@@ -232,7 +229,7 @@ impl<T: 'static> Frontend<T> {
fn tick(&mut self, params: FrontendUpdateParams<T>) -> anyhow::Result<FrontendUpdateResult> {
// fixme: timer events instead of this thing
if self.ticks.is_multiple_of(1000) {
self.update_time()?;
self.update_time(params.data)?;
}
{
@@ -253,7 +250,7 @@ impl<T: 'static> Frontend<T> {
})
}
fn update_time(&mut self) -> anyhow::Result<()> {
fn update_time(&mut self, data: &mut T) -> anyhow::Result<()> {
let mut c = self.layout.start_common();
let mut common = c.common();
@@ -266,12 +263,12 @@ impl<T: 'static> Frontend<T> {
let hours = now.hour();
let minutes = now.minute();
let text: String = if !self.settings.get().general.am_pm_clock {
format!("{hours:02}:{minutes:02}")
} else {
let text: String = if self.interface.general_config(data).clock_12h {
let hours_ampm = (hours + 11) % 12 + 1;
let suffix = if hours >= 12 { "PM" } else { "AM" };
format!("{hours_ampm:02}:{minutes:02} {suffix}")
} else {
format!("{hours:02}:{minutes:02}")
};
label.set_text(&mut common, Translation::from_raw_text(&text));
@@ -281,13 +278,15 @@ impl<T: 'static> Frontend<T> {
Ok(())
}
fn mount_popup(&mut self, params: MountPopupParams) -> anyhow::Result<()> {
fn mount_popup(&mut self, params: MountPopupParams, data: &mut T) -> anyhow::Result<()> {
let config = self.interface.general_config(data);
self.popup_manager.mount_popup(
self.globals.clone(),
self.settings.as_ref(),
&mut self.layout,
self.tasks.clone(),
params,
config,
)?;
Ok(())
}
@@ -299,7 +298,7 @@ impl<T: 'static> Frontend<T> {
Ok(())
}
fn update_background(&self) -> anyhow::Result<()> {
fn update_background(&mut self, data: &mut T) -> anyhow::Result<()> {
let Some(mut rect) = self
.layout
.state
@@ -309,10 +308,10 @@ impl<T: 'static> Frontend<T> {
anyhow::bail!("");
};
let (alpha1, alpha2) = if !self.settings.get().general.opaque_background {
(0.8666, 0.9333)
} else {
let (alpha1, alpha2) = if self.interface.general_config(data).opaque_background {
(1.0, 1.0)
} else {
(0.8666, 0.9333)
};
rect.params.color.a = alpha1;
@@ -324,9 +323,9 @@ impl<T: 'static> Frontend<T> {
fn process_task(&mut self, params: &mut FrontendUpdateParams<T>, task: FrontendTask) -> anyhow::Result<()> {
match task {
FrontendTask::SetTab(tab_type) => self.set_tab(params.data, tab_type)?,
FrontendTask::RefreshClock => self.update_time()?,
FrontendTask::RefreshBackground => self.update_background()?,
FrontendTask::MountPopup(params) => self.mount_popup(params)?,
FrontendTask::RefreshClock => self.update_time(params.data)?,
FrontendTask::RefreshBackground => self.update_background(params.data)?,
FrontendTask::MountPopup(popup_params) => self.mount_popup(popup_params, params.data)?,
FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?,
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
@@ -343,12 +342,12 @@ impl<T: 'static> Frontend<T> {
self.layout.remove_children(widget_content.id);
let tab: Box<dyn Tab<T>> = match tab_type {
TabType::Home => Box::new(TabHome::new(self, widget_content.id)?),
TabType::Home => Box::new(TabHome::new(self, widget_content.id, data)?),
TabType::Apps => Box::new(TabApps::new(self, widget_content.id, data)?),
TabType::Games => Box::new(TabGames::new(self, widget_content.id)?),
TabType::Monado => Box::new(TabMonado::new(self, widget_content.id)?),
TabType::Processes => Box::new(TabProcesses::new(self, widget_content.id)?),
TabType::Settings => Box::new(TabSettings::new(self, widget_content.id)?),
TabType::Settings => Box::new(TabSettings::new(self, widget_content.id, data)?),
};
self.current_tab = Some(tab);

View File

@@ -1,6 +1,5 @@
mod assets;
pub mod frontend;
pub mod settings;
mod tab;
mod util;
mod various;

View File

@@ -1,42 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize)]
pub struct HomeScreen {
pub hide_username: bool,
}
#[derive(Default, Serialize, Deserialize)]
pub struct General {
pub am_pm_clock: bool,
pub opaque_background: bool,
}
#[derive(Default, Serialize, Deserialize)]
pub struct Tweaks {
pub xwayland_by_default: bool,
}
#[derive(Default, Serialize, Deserialize)]
pub struct Settings {
pub home_screen: HomeScreen,
pub general: General,
pub tweaks: Tweaks,
}
impl Settings {
pub fn save(&self) -> String {
serde_json::to_string_pretty(&self).unwrap() /* want panic */
}
pub fn load(input: &str) -> anyhow::Result<Settings> {
Ok(serde_json::from_str::<Settings>(input)?)
}
}
pub trait SettingsIO {
fn get_mut(&mut self) -> &mut Settings;
fn get(&self) -> &Settings;
fn save_to_disk(&mut self);
fn read_from_disk(&mut self);
fn mark_as_dirty(&mut self);
}

View File

@@ -104,7 +104,7 @@ fn on_app_click(
layout: data.layout,
parent_id: data.id_content,
frontend_tasks: &frontend_tasks,
settings: data.settings,
config: data.config,
on_launched,
})?;

View File

@@ -9,10 +9,10 @@ use wgui::{
parser::{Fetchable, ParseDocumentParams, ParserState},
widget::label::WidgetLabel,
};
use wlx_common::config::GeneralConfig;
use crate::{
frontend::{Frontend, FrontendTask},
settings,
tab::{Tab, TabType},
various,
};
@@ -29,7 +29,7 @@ impl<T> Tab<T> for TabHome<T> {
}
}
fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, settings: &settings::Settings) {
fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, config: &GeneralConfig) {
let mut username = various::get_username();
// first character as uppercase
if let Some(first) = username.chars().next() {
@@ -37,7 +37,7 @@ fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, s
username.replace_range(0..1, &first);
}
let translated = if !settings.home_screen.hide_username {
let translated = if !config.hide_username {
common.i18n().translate_and_replace("HELLO_USER", ("{USER}", &username))
} else {
common.i18n().translate("HELLO").to_string()
@@ -48,7 +48,7 @@ fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, s
}
impl<T> TabHome<T> {
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: frontend.layout.state.globals.clone(),
@@ -61,7 +61,7 @@ impl<T> TabHome<T> {
let mut c = frontend.layout.start_common();
let widget_label = state.fetch_widget(&c.layout.state, "label_hello")?.widget;
configure_label_hello(&mut c.common(), widget_label, frontend.settings.get_mut());
configure_label_hello(&mut c.common(), widget_label, frontend.interface.general_config(data));
let btn_apps = state.fetch_component_as::<ComponentButton>("btn_apps")?;
let btn_games = state.fetch_component_as::<ComponentButton>("btn_games")?;

View File

@@ -1,21 +1,24 @@
use std::{marker::PhantomData, rc::Rc};
use std::{collections::HashMap, marker::PhantomData, rc::Rc};
use strum::AsRefStr;
use wgui::{
assets::AssetPath,
components::checkbox::ComponentCheckbox,
layout::WidgetID,
components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
layout::{Layout, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState},
task::Tasks,
};
use wlx_common::config::GeneralConfig;
use crate::{
frontend::{Frontend, FrontendTask},
settings,
tab::{Tab, TabType},
};
enum Task {
ToggleSetting(SettingType, bool),
UpdateBool(SettingType, bool),
UpdateFloat(SettingType, f32),
UpdateInt(SettingType, i32),
}
pub struct TabSettings<T> {
@@ -31,10 +34,22 @@ impl<T> Tab<T> for TabSettings<T> {
TabType::Settings
}
fn update(&mut self, frontend: &mut Frontend<T>, _data: &mut T) -> anyhow::Result<()> {
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
let config = frontend.interface.general_config(data);
for task in self.tasks.drain() {
match task {
Task::ToggleSetting(setting, n) => self.toggle_setting(frontend, setting, n),
Task::UpdateBool(setting, n) => {
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
*setting.mut_bool(config) = n;
}
Task::UpdateFloat(setting, n) => {
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
*setting.mut_f32(config) = n;
}
Task::UpdateInt(setting, n) => {
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
*setting.mut_i32(config) = n;
}
}
}
Ok(())
@@ -42,109 +57,375 @@ impl<T> Tab<T> for TabSettings<T> {
}
#[allow(clippy::enum_variant_names)]
#[derive(Clone)]
#[derive(Clone, Copy, AsRefStr)]
enum SettingType {
DashHideUsername,
DashAmPmClock,
DashOpaqueBackground,
DashXwaylandByDefault,
AnimationSpeed,
RoundMultiplier,
InvertScrollDirectionX,
InvertScrollDirectionY,
ScrollSpeed,
LongPressDuration,
NotificationsEnabled,
NotificationsSoundEnabled,
KeyboardSoundEnabled,
UprightScreenFix,
DoubleCursorFix,
SingleSetMode,
HideGrabHelp,
XrClickSensitivity,
XrClickSensitivityRelease,
AllowSliding,
ClickFreezeTimeMs,
FocusFollowsMouseMode,
LeftHandedMouse,
BlockGameInput,
BlockGameInputIgnoreWatch,
SpaceDragMultiplier,
UseSkybox,
UsePassthrough,
ScreenRenderDown,
PointerLerpFactor,
SpaceDragUnlocked,
SpaceRotateUnlocked,
Clock12h,
HideUsername,
OpaqueBackground,
XwaylandByDefault,
}
impl SettingType {
fn get_bool<'a>(&self, settings: &'a mut settings::Settings) -> &'a mut bool {
pub fn mut_bool<'a>(self, config: &'a mut GeneralConfig) -> &'a mut bool {
match self {
SettingType::DashHideUsername => &mut settings.home_screen.hide_username,
SettingType::DashAmPmClock => &mut settings.general.am_pm_clock,
SettingType::DashOpaqueBackground => &mut settings.general.opaque_background,
SettingType::DashXwaylandByDefault => &mut settings.tweaks.xwayland_by_default,
Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x,
Self::InvertScrollDirectionY => &mut config.invert_scroll_direction_y,
Self::NotificationsEnabled => &mut config.notifications_enabled,
Self::NotificationsSoundEnabled => &mut config.notifications_sound_enabled,
Self::KeyboardSoundEnabled => &mut config.keyboard_sound_enabled,
Self::UprightScreenFix => &mut config.upright_screen_fix,
Self::DoubleCursorFix => &mut config.double_cursor_fix,
Self::SingleSetMode => &mut config.single_set_mode,
Self::HideGrabHelp => &mut config.hide_grab_help,
Self::AllowSliding => &mut config.allow_sliding,
Self::FocusFollowsMouseMode => &mut config.focus_follows_mouse_mode,
Self::LeftHandedMouse => &mut config.left_handed_mouse,
Self::BlockGameInput => &mut config.block_game_input,
Self::BlockGameInputIgnoreWatch => &mut config.block_game_input_ignore_watch,
Self::UseSkybox => &mut config.use_skybox,
Self::UsePassthrough => &mut config.use_passthrough,
Self::ScreenRenderDown => &mut config.screen_render_down,
Self::SpaceDragUnlocked => &mut config.space_drag_unlocked,
Self::SpaceRotateUnlocked => &mut config.space_rotate_unlocked,
Self::Clock12h => &mut config.clock_12h,
Self::HideUsername => &mut config.hide_username,
Self::OpaqueBackground => &mut config.opaque_background,
Self::XwaylandByDefault => &mut config.xwayland_by_default,
_ => panic!("Requested bool for non-bool SettingType"),
}
}
pub fn mut_f32<'a>(self, config: &'a mut GeneralConfig) -> &'a mut f32 {
match self {
Self::AnimationSpeed => &mut config.animation_speed,
Self::RoundMultiplier => &mut config.round_multiplier,
Self::ScrollSpeed => &mut config.scroll_speed,
Self::LongPressDuration => &mut config.long_press_duration,
Self::XrClickSensitivity => &mut config.xr_click_sensitivity,
Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release,
Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
_ => panic!("Requested f32 for non-f32 SettingType"),
}
}
pub fn mut_i32<'a>(self, config: &'a mut GeneralConfig) -> &'a mut i32 {
match self {
Self::ClickFreezeTimeMs => &mut config.click_freeze_time_ms,
_ => panic!("Requested i32 for non-i32 SettingType"),
}
}
/// Ok is translation, Err is raw text
fn get_translation(self) -> Result<&'static str, &'static str> {
match self {
Self::AnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
Self::RoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
Self::SingleSetMode => Ok("APP_SETTINGS.SINGLE_SET_MODE"),
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
Self::AllowSliding => Ok("APP_SETTINGS.ALLOW_SLIDING"),
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
Self::BlockGameInput => Ok("APP_SETTINGS.BLOCK_GAME_INPUT"),
Self::BlockGameInputIgnoreWatch => Ok("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH"),
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"),
}
}
fn get_tooltip(self) -> Option<&'static str> {
match self {
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
Self::SingleSetMode => Some("APP_SETTINGS.SINGLE_SET_MODE_HELP"),
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
Self::FocusFollowsMouseMode => Some("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE_HELP"),
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
Self::BlockGameInput => Some("APP_SETTINGS.BLOCK_GAME_INPUT_HELP"),
Self::BlockGameInputIgnoreWatch => Some("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH_HELP"),
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
_ => None,
}
}
//TODO: incorporate this
fn requires_restart(self) -> bool {
match self {
Self::AnimationSpeed
| Self::RoundMultiplier
| Self::UprightScreenFix
| Self::DoubleCursorFix
| Self::SingleSetMode
| Self::UseSkybox
| Self::UsePassthrough
| Self::ScreenRenderDown => true,
_ => false,
}
}
fn get_frontend_task(self) -> Option<FrontendTask> {
match self {
Self::Clock12h => Some(FrontendTask::RefreshClock),
Self::OpaqueBackground => Some(FrontendTask::RefreshBackground),
_ => None,
}
}
}
fn init_setting_checkbox<T>(
frontend: &mut Frontend<T>,
tasks: &Tasks<Task>,
checkbox: Rc<ComponentCheckbox>,
setting: SettingType,
additional_frontend_task: Option<FrontendTask>,
) -> anyhow::Result<()> {
let mut c = frontend.layout.start_common();
checkbox.set_checked(&mut c.common(), *setting.get_bool(frontend.settings.get_mut()));
macro_rules! category {
($pe:expr, $root:expr, $translation:expr, $icon:expr) => {{
let id = $pe.idx.to_string();
$pe.idx += 1;
let tasks = tasks.clone();
let frontend_tasks = frontend.tasks.clone();
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("translation"), Rc::from($translation));
params.insert(Rc::from("icon"), Rc::from($icon));
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
checkbox.on_toggle(Box::new(move |_common, e| {
tasks.push(Task::ToggleSetting(setting.clone(), e.checked));
$pe
.parser_state
.instantiate_template($pe.doc_params, "SettingsGroupBox", $pe.layout, $root, params)?;
if let Some(task) = &additional_frontend_task {
frontend_tasks.push(task.clone());
$pe.parser_state.get_widget_id(&id)
}};
}
macro_rules! checkbox {
($mp:expr, $root:expr, $setting:expr) => {
let id = $mp.idx.to_string();
$mp.idx += 1;
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
match $setting.get_translation() {
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
};
if let Some(tooltip) = $setting.get_tooltip() {
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
}
Ok(())
}));
c.finish()?;
Ok(())
let checked = if *$setting.mut_bool($mp.config) { "1" } else { "0" };
params.insert(Rc::from("checked"), Rc::from(checked));
$mp
.parser_state
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, $root, params)?;
let checkbox = $mp.parser_state.fetch_component_as::<ComponentCheckbox>(&id)?;
checkbox.on_toggle(Box::new({
let tasks = $mp.tasks.clone();
move |_common, e| {
tasks.push(Task::UpdateBool($setting, e.checked));
Ok(())
}
}));
};
}
macro_rules! slider_f32 {
($mp:expr, $root:expr, $setting:expr, $min:expr, $max:expr, $step:expr) => {
let id = $mp.idx.to_string();
$mp.idx += 1;
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
match $setting.get_translation() {
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
};
if let Some(tooltip) = $setting.get_tooltip() {
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
}
let value = $setting.mut_f32($mp.config).to_string();
params.insert(Rc::from("value"), Rc::from(value));
params.insert(Rc::from("min"), Rc::from($min.to_string()));
params.insert(Rc::from("max"), Rc::from($max.to_string()));
params.insert(Rc::from("step"), Rc::from($step.to_string()));
$mp
.parser_state
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
slider.on_value_changed(Box::new({
let tasks = $mp.tasks.clone();
move |_common, e| {
tasks.push(Task::UpdateFloat($setting, e.value));
Ok(())
}
}));
};
}
macro_rules! slider_i32 {
($mp:expr, $root:expr, $setting:expr, $min:expr, $max:expr, $step:expr) => {
let id = $mp.idx.to_string();
$mp.idx += 1;
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
match $setting.get_translation() {
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
};
if let Some(tooltip) = $setting.get_tooltip() {
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
}
let value = $setting.mut_i32($mp.config).to_string();
params.insert(Rc::from("value"), Rc::from(value));
params.insert(Rc::from("min"), Rc::from($min.to_string()));
params.insert(Rc::from("max"), Rc::from($max.to_string()));
params.insert(Rc::from("step"), Rc::from($step.to_string()));
$mp
.parser_state
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
slider.on_value_changed(Box::new({
let tasks = $mp.tasks.clone();
move |_common, e| {
tasks.push(Task::UpdateInt($setting, e.value as i32));
Ok(())
}
}));
};
}
struct MacroParams<'a> {
layout: &'a mut Layout,
parser_state: &'a mut ParserState,
doc_params: &'a ParseDocumentParams<'a>,
config: &'a mut GeneralConfig,
tasks: Tasks<Task>,
idx: usize,
}
impl<T> TabSettings<T> {
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: frontend.layout.state.globals.clone(),
path: AssetPath::BuiltIn("gui/tab/settings.xml"),
extra: Default::default(),
},
&mut frontend.layout,
parent_id,
)?;
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
let doc_params = ParseDocumentParams {
globals: frontend.layout.state.globals.clone(),
path: AssetPath::BuiltIn("gui/tab/settings.xml"),
extra: Default::default(),
};
let mut parser_state = wgui::parser::parse_from_assets(&doc_params, &mut frontend.layout, parent_id)?;
let tasks = Tasks::new();
let root = parser_state.get_widget_id("settings_root")?;
init_setting_checkbox(
frontend,
&tasks,
state.data.fetch_component_as::<ComponentCheckbox>("cb_hide_username")?,
SettingType::DashHideUsername,
None,
)?;
let mut mp = MacroParams {
layout: &mut frontend.layout,
parser_state: &mut parser_state,
doc_params: &doc_params,
config: frontend.interface.general_config(data),
tasks: Tasks::default(),
idx: 9001,
};
init_setting_checkbox(
frontend,
&tasks,
state.data.fetch_component_as::<ComponentCheckbox>("cb_am_pm_clock")?,
SettingType::DashAmPmClock,
None,
)?;
let c = category!(mp, root, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/settings.svg")?;
checkbox!(mp, c, SettingType::OpaqueBackground);
checkbox!(mp, c, SettingType::HideUsername);
checkbox!(mp, c, SettingType::HideGrabHelp);
slider_f32!(mp, c, SettingType::AnimationSpeed, 0.5, 5.0, 0.1); // min, max, step
slider_f32!(mp, c, SettingType::RoundMultiplier, 0.5, 5.0, 0.1);
checkbox!(mp, c, SettingType::SingleSetMode);
checkbox!(mp, c, SettingType::UseSkybox);
checkbox!(mp, c, SettingType::UsePassthrough);
checkbox!(mp, c, SettingType::Clock12h);
init_setting_checkbox(
frontend,
&tasks,
state
.data
.fetch_component_as::<ComponentCheckbox>("cb_opaque_background")?,
SettingType::DashOpaqueBackground,
Some(FrontendTask::RefreshBackground),
)?;
let c = category!(mp, root, "APP_SETTINGS.FEATURES", "dashboard/settings.svg")?;
checkbox!(mp, c, SettingType::NotificationsEnabled);
checkbox!(mp, c, SettingType::NotificationsSoundEnabled);
checkbox!(mp, c, SettingType::KeyboardSoundEnabled);
checkbox!(mp, c, SettingType::SpaceDragUnlocked);
checkbox!(mp, c, SettingType::SpaceRotateUnlocked);
slider_f32!(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5);
checkbox!(mp, c, SettingType::BlockGameInput);
checkbox!(mp, c, SettingType::BlockGameInputIgnoreWatch);
init_setting_checkbox(
frontend,
&tasks,
state
.data
.fetch_component_as::<ComponentCheckbox>("cb_xwayland_by_default")?,
SettingType::DashXwaylandByDefault,
None,
)?;
let c = category!(mp, root, "APP_SETTINGS.CONTROLS", "dashboard/settings.svg")?;
checkbox!(mp, c, SettingType::FocusFollowsMouseMode);
checkbox!(mp, c, SettingType::LeftHandedMouse);
checkbox!(mp, c, SettingType::AllowSliding);
checkbox!(mp, c, SettingType::InvertScrollDirectionX);
checkbox!(mp, c, SettingType::InvertScrollDirectionY);
slider_f32!(mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1);
slider_f32!(mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1);
slider_f32!(mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1);
slider_f32!(mp, c, SettingType::XrClickSensitivity, 0.1, 1.0, 0.1);
slider_f32!(mp, c, SettingType::XrClickSensitivityRelease, 0.1, 1.0, 0.1);
slider_i32!(mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50);
let c = category!(mp, root, "APP_SETTINGS.MISC", "dashboard/settings.svg")?;
checkbox!(mp, c, SettingType::XwaylandByDefault);
checkbox!(mp, c, SettingType::UprightScreenFix);
checkbox!(mp, c, SettingType::DoubleCursorFix);
checkbox!(mp, c, SettingType::ScreenRenderDown);
Ok(Self {
state,
tasks,
tasks: mp.tasks,
state: parser_state,
marker: PhantomData,
})
}
fn toggle_setting(&mut self, frontend: &mut Frontend<T>, setting: SettingType, state: bool) {
*setting.get_bool(frontend.settings.get_mut()) = state;
}
}

View File

@@ -14,11 +14,9 @@ use wgui::{
taffy::Display,
widget::label::WidgetLabel,
};
use wlx_common::config::GeneralConfig;
use crate::{
frontend::{FrontendTask, FrontendTasks},
settings::SettingsIO,
};
use crate::frontend::{FrontendTask, FrontendTasks};
pub struct PopupManagerParams {
pub parent_id: WidgetID,
@@ -58,7 +56,7 @@ pub struct PopupManager {
pub struct PopupContentFuncData<'a> {
pub layout: &'a mut Layout,
pub settings: &'a dyn SettingsIO,
pub config: &'a GeneralConfig,
pub handle: PopupHandle,
pub id_content: WidgetID,
}
@@ -123,10 +121,10 @@ impl PopupManager {
pub fn mount_popup(
&mut self,
globals: WguiGlobals,
settings: &dyn SettingsIO,
layout: &mut Layout,
frontend_tasks: FrontendTasks,
params: MountPopupParams,
config: &GeneralConfig,
) -> anyhow::Result<()> {
let doc_params = &ParseDocumentParams {
globals: globals.clone(),
@@ -180,7 +178,7 @@ impl PopupManager {
layout,
handle: popup_handle.clone(),
id_content,
settings,
config,
})?;
Ok(())

View File

@@ -12,12 +12,9 @@ use wgui::{
task::Tasks,
widget::label::WidgetLabel,
};
use wlx_common::{dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
use wlx_common::{config::GeneralConfig, dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
use crate::{
frontend::{FrontendTask, FrontendTasks, SoundType},
settings::SettingsIO,
};
use crate::frontend::{FrontendTask, FrontendTasks, SoundType};
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
enum ResMode {
@@ -89,7 +86,7 @@ pub struct Params<'a> {
pub entry: DesktopEntry,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
pub settings: &'a dyn SettingsIO,
pub config: &'a GeneralConfig,
pub frontend_tasks: &'a FrontendTasks,
pub on_launched: Box<dyn Fn()>,
}
@@ -144,7 +141,7 @@ impl View {
)?;
}
let compositor_mode = if params.settings.get().tweaks.xwayland_by_default {
let compositor_mode = if params.config.xwayland_by_default {
CompositorMode::Cage
} else {
CompositorMode::Native

View File

@@ -1,57 +1,8 @@
use crate::testbed::{Testbed, TestbedUpdateParams};
use dash_frontend::{
frontend::{self, FrontendUpdateParams},
settings::{self, SettingsIO},
};
use dash_frontend::frontend::{self, FrontendUpdateParams};
use wgui::layout::Layout;
use wlx_common::dash_interface_emulated::DashInterfaceEmulated;
struct SimpleSettingsIO {
settings: settings::Settings,
}
impl SimpleSettingsIO {
fn new() -> Self {
let mut res = Self {
settings: settings::Settings::default(),
};
res.read_from_disk();
res
}
}
// just a simple impl of a config io for dashboard frontend
// use ~/.config later
impl settings::SettingsIO for SimpleSettingsIO {
fn get_mut(&mut self) -> &mut settings::Settings {
&mut self.settings
}
fn get(&self) -> &dash_frontend::settings::Settings {
&self.settings
}
fn save_to_disk(&mut self) {
log::info!("saving settings");
let data = self.settings.save();
std::fs::write("/tmp/testbed_settings.json", data).unwrap();
}
fn read_from_disk(&mut self) {
log::info!("loading settings");
if let Ok(res) = std::fs::read("/tmp/testbed_settings.json") {
let data = String::from_utf8(res).unwrap();
self.settings = settings::Settings::load(&data).unwrap();
}
}
fn mark_as_dirty(&mut self) {
// just save it, at least for now
// save_to_disk should be called later in time or at exit, not instantly
self.save_to_disk();
}
}
pub struct TestbedDashboard {
frontend: frontend::Frontend<()>,
}
@@ -61,10 +12,12 @@ impl TestbedDashboard {
let settings = SimpleSettingsIO::new();
let interface = DashInterfaceEmulated::new();
let frontend = frontend::Frontend::new(frontend::InitParams {
settings: Box::new(settings),
interface: Box::new(interface),
})?;
let frontend = frontend::Frontend::new(
frontend::InitParams {
interface: Box::new(interface),
},
(),
)?;
Ok(Self { frontend })
}
}

View File

@@ -1,13 +1,13 @@
use crate::{
assets::AssetPath,
components::{Component, button, tooltip},
components::{button, tooltip, Component},
drawing::Color,
i18n::Translation,
layout::WidgetID,
parser::{
AttribPair, ParserContext, ParserFile, parse_check_f32, parse_check_i32, parse_children, parse_f32,
print_invalid_attrib, process_component,
parse_check_f32, parse_check_i32, parse_children, parse_f32, print_invalid_attrib, process_component,
style::{parse_color_opt, parse_round, parse_style, parse_text_style},
AttribPair, ParserContext, ParserFile,
},
widget::util::WLength,
};
@@ -41,10 +41,14 @@ pub fn parse_component_button<'a>(
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"text" => {
translation = Some(Translation::from_raw_text(value));
if !value.is_empty() {
translation = Some(Translation::from_raw_text(value));
}
}
"translation" => {
translation = Some(Translation::from_translation_key(value));
if !value.is_empty() {
translation = Some(Translation::from_translation_key(value));
}
}
"round" => {
parse_round(value, &mut round, ctx.doc_params.globals.get().defaults.rounding_mult);
@@ -77,8 +81,8 @@ pub fn parse_component_button<'a>(
sprite_src = Some(asset_path);
}
}
"tooltip" => tooltip = Some(Translation::from_translation_key(value)),
"tooltip_str" => tooltip = Some(Translation::from_raw_text(value)),
"tooltip" if !value.is_empty() => tooltip = Some(Translation::from_translation_key(value)),
"tooltip_str" if !value.is_empty() => tooltip = Some(Translation::from_raw_text(value)),
"tooltip_side" => {
tooltip_side = match value {
"left" => Some(tooltip::TooltipSide::Left),

View File

@@ -1,9 +1,9 @@
use crate::{
components::{Component, checkbox, radio_group::ComponentRadioGroup},
components::{checkbox, radio_group::ComponentRadioGroup, Component},
i18n::Translation,
layout::WidgetID,
parser::{
AttribPair, Fetchable, ParserContext, parse_check_f32, parse_check_i32, process_component, style::parse_style,
parse_check_f32, parse_check_i32, process_component, style::parse_style, AttribPair, Fetchable, ParserContext,
},
};
@@ -29,10 +29,14 @@ pub fn parse_component_checkbox(
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
match key {
"text" => {
translation = Translation::from_raw_text(value);
if !value.is_empty() {
translation = Translation::from_raw_text(value);
}
}
"translation" => {
translation = Translation::from_translation_key(value);
if !value.is_empty() {
translation = Translation::from_translation_key(value);
}
}
"value" => {
component_value = Some(value.into());

View File

@@ -13,6 +13,7 @@ idmap = { workspace = true, features = ["serde"] }
idmap-derive.workspace = true
log.workspace = true
serde = { workspace = true, features = ["rc"] }
serde_json.workspace = true
xdg.workspace = true
chrono = "0.4.42"
smol = "2.0.2"

View File

@@ -23,11 +23,11 @@ pub const fn def_pw_tokens() -> PwTokenMap {
AStrMap::new()
}
const fn def_mouse_move_interval_ms() -> u32 {
const fn def_mouse_move_interval_ms() -> i32 {
10 // 100fps
}
const fn def_click_freeze_time_ms() -> u32 {
const fn def_click_freeze_time_ms() -> i32 {
300
}
@@ -110,7 +110,7 @@ pub struct GeneralConfig {
pub attribs: AStrMap<Vec<BackendAttribValue>>,
#[serde(default = "def_click_freeze_time_ms")]
pub click_freeze_time_ms: u32,
pub click_freeze_time_ms: i32,
#[serde(default = "def_false")]
pub invert_scroll_direction_x: bool,
@@ -125,7 +125,7 @@ pub struct GeneralConfig {
pub long_press_duration: f32,
#[serde(default = "def_mouse_move_interval_ms")]
pub mouse_move_interval_ms: u32,
pub mouse_move_interval_ms: i32,
#[serde(default = "def_true")]
pub notifications_enabled: bool,
@@ -240,4 +240,13 @@ pub struct GeneralConfig {
#[serde(default)]
pub last_set: u32,
#[serde(default)]
pub hide_username: bool,
#[serde(default)]
pub opaque_background: bool,
#[serde(default)]
pub xwayland_by_default: bool,
}

View File

@@ -3,7 +3,7 @@ use wayvr_ipc::{
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
};
use crate::desktop_finder::DesktopFinder;
use crate::{config::GeneralConfig, desktop_finder::DesktopFinder};
pub trait DashInterface<T> {
fn window_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrWindow>>;
@@ -15,6 +15,7 @@ pub trait DashInterface<T> {
fn process_terminate(&mut self, data: &mut T, handle: WvrProcessHandle) -> anyhow::Result<()>;
fn recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()>;
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
fn general_config<'a>(&'a mut self, data: &'a mut T) -> &'a mut GeneralConfig;
}
pub type BoxDashInterface<T> = Box<dyn DashInterface<T>>;

View File

@@ -3,7 +3,7 @@ use wayvr_ipc::{
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
};
use crate::{dash_interface::DashInterface, desktop_finder::DesktopFinder, gen_id};
use crate::{config::GeneralConfig, dash_interface::DashInterface, desktop_finder::DesktopFinder, gen_id};
#[derive(Debug)]
pub struct EmuProcess {
@@ -55,6 +55,7 @@ pub struct DashInterfaceEmulated {
processes: EmuProcessVec,
windows: EmuWindowVec,
desktop_finder: DesktopFinder,
general_config: GeneralConfig,
}
impl DashInterfaceEmulated {
@@ -73,10 +74,14 @@ impl DashInterfaceEmulated {
let mut desktop_finder = DesktopFinder::new();
desktop_finder.refresh();
// Use serde defaults
let general_config = serde_json::from_str("{}").unwrap();
Self {
processes,
windows,
desktop_finder,
general_config,
}
}
}
@@ -169,4 +174,8 @@ impl DashInterface<()> for DashInterfaceEmulated {
fn desktop_finder<'a>(&'a mut self, _: &'a mut ()) -> &'a mut DesktopFinder {
&mut self.desktop_finder
}
fn general_config<'a>(&'a mut self, _: &'a mut ()) -> &'a mut crate::config::GeneralConfig {
&mut self.general_config
}
}

View File

@@ -107,7 +107,7 @@ impl BlitMethod {
}
pub struct Config {
pub click_freeze_time_ms: u32,
pub click_freeze_time_ms: i32,
pub keyboard_repeat_delay_ms: u32,
pub keyboard_repeat_rate: u32,
pub auto_hide_delay: Option<u32>, // if None, auto-hide is disabled

View File

@@ -1,7 +1,4 @@
use dash_frontend::{
frontend::{self, FrontendTask, FrontendUpdateParams},
settings::{self, SettingsIO},
};
use dash_frontend::frontend::{self, FrontendTask, FrontendUpdateParams};
use glam::{Affine2, Affine3A, Vec2, vec2, vec3};
use wayvr_ipc::{
packet_client::WvrProcessLaunchParams,
@@ -26,7 +23,6 @@ use wlx_common::{
};
use crate::{
app_misc,
backend::{
input::{Haptics, HoverResult, PointerHit, PointerMode},
task::{OverlayTask, PlayspaceTask, TaskType},
@@ -53,43 +49,6 @@ pub const DASH_NAME: &str = "Dashboard";
const DASH_RES_U32A: [u32; 2] = [1920, 1080];
const DASH_RES_VEC2: Vec2 = vec2(DASH_RES_U32A[0] as _, DASH_RES_U32A[1] as _);
//FIXME: replace with proper impl
struct SimpleSettingsIO {
settings: settings::Settings,
}
impl SimpleSettingsIO {
fn new() -> Self {
let mut res = Self {
settings: settings::Settings::default(),
};
res.read_from_disk();
res
}
}
impl settings::SettingsIO for SimpleSettingsIO {
fn get_mut(&mut self) -> &mut settings::Settings {
&mut self.settings
}
fn get(&self) -> &dash_frontend::settings::Settings {
&self.settings
}
fn save_to_disk(&mut self) {
log::info!("saving settings");
let data = self.settings.save();
std::fs::write("/tmp/testbed_settings.json", data).unwrap();
}
fn read_from_disk(&mut self) {
log::info!("loading settings");
if let Ok(res) = std::fs::read("/tmp/testbed_settings.json") {
let data = String::from_utf8(res).unwrap();
self.settings = settings::Settings::load(&data).unwrap();
}
}
fn mark_as_dirty(&mut self) {
self.save_to_disk();
}
}
pub struct DashFrontend {
inner: frontend::Frontend<AppState>,
initialized: bool,
@@ -103,13 +62,14 @@ const GUI_SCALE: f32 = 2.0;
impl DashFrontend {
fn new(app: &mut AppState) -> anyhow::Result<Self> {
let settings = SimpleSettingsIO::new();
let interface = DashInterfaceLive::new();
let frontend = frontend::Frontend::new(frontend::InitParams {
settings: Box::new(settings),
interface: Box::new(interface),
})?;
let frontend = frontend::Frontend::new(
frontend::InitParams {
interface: Box::new(interface),
},
app,
)?;
frontend
.tasks
@@ -448,4 +408,11 @@ impl DashInterface<AppState> for DashInterfaceLive {
) -> &'a mut wlx_common::desktop_finder::DesktopFinder {
&mut data.desktop_finder
}
fn general_config<'a>(
&'a mut self,
data: &'a mut AppState,
) -> &'a mut wlx_common::config::GeneralConfig {
&mut data.session.config
}
}

View File

@@ -266,7 +266,7 @@ impl OverlayBackend for ScreenBackend {
{
let pos = self.mouse_transform.transform_point2(hit.uv);
app.hid_provider.inner.mouse_move(pos);
set_next_move(u64::from(app.session.config.mouse_move_interval_ms));
set_next_move(app.session.config.mouse_move_interval_ms as _);
}
HoverResult {
consume: true,
@@ -290,7 +290,7 @@ impl OverlayBackend for ScreenBackend {
}
if pressed {
set_next_move(u64::from(app.session.config.click_freeze_time_ms));
set_next_move(app.session.config.click_freeze_time_ms as _);
}
app.hid_provider.inner.send_button(btn, pressed);