diff --git a/dash-frontend/assets/dashboard/drag.svg b/dash-frontend/assets/dashboard/drag.svg
new file mode 100644
index 00000000..9710d6bd
--- /dev/null
+++ b/dash-frontend/assets/dashboard/drag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dash-frontend/assets/dashboard/gravity.svg b/dash-frontend/assets/dashboard/gravity.svg
new file mode 100644
index 00000000..bec06901
--- /dev/null
+++ b/dash-frontend/assets/dashboard/gravity.svg
@@ -0,0 +1,46 @@
+
+
diff --git a/dash-frontend/assets/gui/tab/settings.xml b/dash-frontend/assets/gui/tab/settings.xml
index b6850851..8b7d33a8 100644
--- a/dash-frontend/assets/gui/tab/settings.xml
+++ b/dash-frontend/assets/gui/tab/settings.xml
@@ -13,13 +13,13 @@
-
-
+
+
-
-
+
+
@@ -56,6 +56,7 @@
+
diff --git a/dash-frontend/assets/gui/tab/settings_tab_space_drag.xml b/dash-frontend/assets/gui/tab/settings_tab_space_drag.xml
new file mode 100644
index 00000000..3f8f9cf7
--- /dev/null
+++ b/dash-frontend/assets/gui/tab/settings_tab_space_drag.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index 14b8d023..ccb3a192 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -55,6 +55,7 @@
"DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d",
"DOUBLE_CURSOR_FIX": "Double cursor fix",
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
+ "ENABLED": "Enabled",
"FEATURES": "Features",
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
"HANDSFREE_POINTER": "Handsfree mode",
@@ -72,9 +73,10 @@
"LONG_PRESS_DURATION": "Long press duration",
"LOOK_AND_FEEL": "Look & Feel",
"MISC": "Miscellaneous",
- "NO_SKYMAPS_FOUND": "No skymaps found",
"NOTIFICATIONS_ENABLED": "Enable notifications",
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
+ "NOT_SUPPORTED": "Not supported",
+ "NO_SKYMAPS_FOUND": "No skymaps found",
"OPAQUE_BACKGROUND": "Opaque background",
"OPTION": {
"AUTO": "Automatic",
@@ -100,11 +102,23 @@
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
"SCROLL_SPEED": "Scroll speed",
"SELECT_VARIANT": "Select variant",
+ "ENABLE_WATCH": "Enable watch",
"SETS_ON_WATCH": "Sets on watch",
"SKYBOX": "Skybox",
"SKYMAP_ALREADY_DOWNLOADED": "This skymap is already downloaded. Select desired action.",
+ "SPACE_DRAG": "Space drag",
"SPACE_DRAG_MULTIPLIER": "Space drag multiplier",
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
+ "SPACE_GRAVITY_DAMPING": "Damping",
+ "SPACE_GRAVITY_DAMPING_HELP": "Artificial drag to slow down movement. 0.1 - high drag, 1.0 - no drag",
+ "SPACE_GRAVITY_FLING_STRENGTH": "Fling strength",
+ "SPACE_GRAVITY_FLING_STRENGTH_HELP": "Intensity multiplier of gravitational launch force after space-drag.\n0.0 - no movement at all, 2.0 - double intensity",
+ "SPACE_GRAVITY_GRAVITY": "Gravity",
+ "SPACE_GRAVITY_GRAVITY_HELP": "Amount of downwards force. 0.0 - no gravity",
+ "SPACE_GRAVITY_GROUND_FRICTION": "Ground friction",
+ "SPACE_GRAVITY_GROUND_FRICTION_HELP": "Amount of friction slowing you down if you're touching the ground.\n0.0 - no friction (just like on ice), 1.0 - rough surface",
+ "SPACE_GRAVITY_FLOOR_HEIGHT": "Floor height",
+ "SPACE_GRAVITY_FLOOR_HEIGHT_HELP": "The Y position where the floor is. Gravity stops when you reach this height.",
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
"TROUBLESHOOTING": "Troubleshooting",
"UI_GRADIENT_INTENSITY": "UI Gradient intensity",
diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json
index 7e4ceef4..c45a2cdc 100644
--- a/dash-frontend/assets/lang/pl.json
+++ b/dash-frontend/assets/lang/pl.json
@@ -44,6 +44,7 @@
"USE_SKYBOX_HELP": "Wyświetlaj niebo, jeśli nie ma aplikacji sceny lub passthrough",
"USE_PASSTHROUGH_HELP": "Pozwól na passthrough, jeśli runtime XR to obsługuje",
"SCREEN_RENDER_DOWN_HELP": "Pomaga redukować aliasing na ekranach o wysokiej rozdzielczości",
+ "ENABLE_WATCH": "Włącz zegarek",
"SETS_ON_WATCH": "Lista zestawów na zegarku",
"TROUBLESHOOTING": "Rozwiązywanie problemów",
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
diff --git a/dash-frontend/src/tab/apps.rs b/dash-frontend/src/tab/apps.rs
index a55cf356..5a21b59a 100644
--- a/dash-frontend/src/tab/apps.rs
+++ b/dash-frontend/src/tab/apps.rs
@@ -295,8 +295,8 @@ impl AppList {
state: &Rc>,
parser_state: &mut ParserState,
) -> anyhow::Result<()> {
- // load 4 entries for a single frame at most
- for _ in 0..4 {
+ // load 30 entries for a single frame at most
+ for _ in 0..30 {
if let Some(entry) = self.entries_to_mount.pop_front() {
let globals = frontend.layout.state.globals.clone();
let button = self.mount_entry(frontend, parser_state, &doc_params(globals.clone()), &entry)?;
diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs
index ab32bdf1..38ef2bbf 100644
--- a/dash-frontend/src/tab/settings/mod.rs
+++ b/dash-frontend/src/tab/settings/mod.rs
@@ -39,29 +39,32 @@ mod tab_features;
mod tab_look_and_feel;
mod tab_misc;
mod tab_skybox;
+mod tab_space_drag;
mod tab_troubleshooting;
#[derive(Clone)]
enum TabNameEnum {
- LookAndFeel,
- Features,
- Controls,
- Misc,
AutostartApps,
- Troubleshooting,
+ Controls,
+ Features,
+ LookAndFeel,
+ Misc,
Skybox,
+ SpaceDrag,
+ Troubleshooting,
}
impl TabNameEnum {
fn from_string(s: &str) -> Option {
match s {
- "look_and_feel" => Some(TabNameEnum::LookAndFeel),
- "features" => Some(TabNameEnum::Features),
- "controls" => Some(TabNameEnum::Controls),
- "misc" => Some(TabNameEnum::Misc),
"autostart_apps" => Some(TabNameEnum::AutostartApps),
- "troubleshooting" => Some(TabNameEnum::Troubleshooting),
+ "controls" => Some(TabNameEnum::Controls),
+ "features" => Some(TabNameEnum::Features),
+ "look_and_feel" => Some(TabNameEnum::LookAndFeel),
+ "misc" => Some(TabNameEnum::Misc),
"skybox" => Some(TabNameEnum::Skybox),
+ "space_drag" => Some(TabNameEnum::SpaceDrag),
+ "troubleshooting" => Some(TabNameEnum::Troubleshooting),
_ => None,
}
}
@@ -69,18 +72,18 @@ impl TabNameEnum {
#[derive(Clone)]
enum Task {
- UpdateBool(SettingType, bool),
- UpdateFloat(SettingType, f32),
- UpdateInt(SettingType, i32),
- SettingUpdated(SettingType),
- OpenContextMenu(Vec2, Vec),
ClearPipewireTokens,
ClearSavedState,
DeleteAllConfigs,
+ OpenContextMenu(Vec2, Vec),
+ RemoveAutostartApp(Rc),
ResetPlayspace,
RestartSoftware,
- RemoveAutostartApp(Rc),
SetTab(TabNameEnum),
+ SettingUpdated(SettingType),
+ UpdateBool(SettingType, bool),
+ UpdateFloat(SettingType, f32),
+ UpdateInt(SettingType, i32),
}
struct SettingsMountParams<'a> {
@@ -90,10 +93,20 @@ struct SettingsMountParams<'a> {
feats: InterfaceFeats,
}
+struct SettingUpdatedParams<'a> {
+ layout: &'a mut Layout,
+ config: &'a GeneralConfig,
+ setting_type: SettingType,
+}
+
trait SettingsTab {
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
Ok(())
}
+
+ fn setting_updated(&mut self, _sup: &mut SettingUpdatedParams) -> anyhow::Result<()> {
+ Ok(())
+ }
}
pub struct TabSettings {
@@ -203,12 +216,21 @@ impl Tab for TabSettings {
changed = Some(ConfigChangeKind::OverlayConfig);
}
}
- Task::SettingUpdated(setting) => match setting {
- SettingType::UiAnimationSpeed | SettingType::UiGradientIntensity | SettingType::UiRoundMultiplier => {
- // todo: currently, wayvr restart is required to apply these changes (WguiTheme is Rc)
+ Task::SettingUpdated(setting) => {
+ if let Some(tab) = &mut self.current_tab {
+ tab.setting_updated(&mut SettingUpdatedParams {
+ layout: &mut frontend.layout,
+ config: frontend.interface.general_config(data),
+ setting_type: setting,
+ })?;
}
- _ => { /* do nothing */ }
- },
+ match setting {
+ SettingType::UiAnimationSpeed | SettingType::UiGradientIntensity | SettingType::UiRoundMultiplier => {
+ // todo: currently, wayvr restart is required to apply these changes (WguiTheme is Rc)
+ }
+ _ => { /* do nothing */ }
+ }
+ }
}
}
@@ -247,7 +269,7 @@ impl Tab for TabSettings {
// Sorted alphabetically
#[allow(clippy::enum_variant_names)]
-#[derive(Clone, Copy, AsRefStr, EnumString)]
+#[derive(Clone, Copy, Eq, PartialEq, AsRefStr, EnumString)]
enum SettingType {
AllowSliding,
BlockGameInput,
@@ -258,6 +280,7 @@ enum SettingType {
Clock12h,
DoubleCursorFix,
FocusFollowsMouseMode,
+ GridOpacity,
HandsfreePointer,
HideGrabHelp,
HideUsername,
@@ -274,9 +297,16 @@ enum SettingType {
PointerLerpFactor,
ScreenRenderDown,
ScrollSpeed,
+ EnableWatch,
SetsOnWatch,
SpaceDragMultiplier,
SpaceDragUnlocked,
+ SpaceGravityDamping,
+ SpaceGravityEnabled,
+ SpaceGravityFlingStrength,
+ SpaceGravityGravity,
+ SpaceGravityGroundFriction,
+ SpaceGravityFloorHeight,
SpaceRotateUnlocked,
UiAnimationSpeed,
UiGradientIntensity,
@@ -284,7 +314,6 @@ enum SettingType {
UprightScreenFix,
UsePassthrough,
UseSkybox,
- GridOpacity,
WatchViewAngleMax,
WatchViewAngleMin,
XrClickSensitivity,
@@ -302,29 +331,31 @@ impl SettingType {
pub fn mut_bool(self, config: &mut GeneralConfig) -> &mut bool {
match self {
- 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::SetsOnWatch => &mut config.sets_on_watch,
- 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::BlockPosesOnKbdInteraction => &mut config.block_poses_on_kbd_interaction,
- 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::DoubleCursorFix => &mut config.double_cursor_fix,
+ Self::EnableWatch => &mut config.enable_watch,
+ Self::FocusFollowsMouseMode => &mut config.focus_follows_mouse_mode,
+ Self::HideGrabHelp => &mut config.hide_grab_help,
Self::HideUsername => &mut config.hide_username,
+ Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x,
+ Self::InvertScrollDirectionY => &mut config.invert_scroll_direction_y,
+ Self::KeyboardSoundEnabled => &mut config.keyboard_sound_enabled,
+ Self::LeftHandedMouse => &mut config.left_handed_mouse,
+ Self::NotificationsEnabled => &mut config.notifications_enabled,
+ Self::NotificationsSoundEnabled => &mut config.notifications_sound_enabled,
Self::OpaqueBackground => &mut config.opaque_background,
+ Self::ScreenRenderDown => &mut config.screen_render_down,
+ Self::SetsOnWatch => &mut config.sets_on_watch,
+ Self::SpaceDragUnlocked => &mut config.space_drag_unlocked,
+ Self::SpaceGravityEnabled => &mut config.space_gravity_enabled,
+ Self::SpaceRotateUnlocked => &mut config.space_rotate_unlocked,
+ Self::UprightScreenFix => &mut config.upright_screen_fix,
+ Self::UsePassthrough => &mut config.use_passthrough,
+ Self::UseSkybox => &mut config.use_skybox,
Self::XwaylandByDefault => &mut config.xwayland_by_default,
_ => panic!("Requested bool for non-bool SettingType"),
}
@@ -332,18 +363,23 @@ impl SettingType {
pub fn mut_f32(self, config: &mut GeneralConfig) -> &mut f32 {
match self {
+ Self::GridOpacity => &mut config.grid_opacity,
+ Self::LongPressDuration => &mut config.long_press_duration,
+ Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
+ Self::ScrollSpeed => &mut config.scroll_speed,
+ Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
+ Self::SpaceGravityDamping => &mut config.space_gravity_damping,
+ Self::SpaceGravityFlingStrength => &mut config.space_gravity_fling_strength,
+ Self::SpaceGravityGravity => &mut config.space_gravity_gravity,
+ Self::SpaceGravityGroundFriction => &mut config.space_gravity_ground_friction,
+ Self::SpaceGravityFloorHeight => &mut config.space_gravity_floor_height,
Self::UiAnimationSpeed => &mut config.ui_animation_speed,
Self::UiGradientIntensity => &mut config.ui_gradient_intensity,
Self::UiRoundMultiplier => &mut config.ui_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,
- Self::GridOpacity => &mut config.grid_opacity,
Self::WatchViewAngleMax => &mut config.watch_view_angle_max,
Self::WatchViewAngleMin => &mut config.watch_view_angle_min,
+ Self::XrClickSensitivity => &mut config.xr_click_sensitivity,
+ Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release,
_ => panic!("Requested f32 for non-f32 SettingType"),
}
}
@@ -435,9 +471,16 @@ impl SettingType {
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
+ Self::EnableWatch => Ok("APP_SETTINGS.ENABLE_WATCH"),
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
+ Self::SpaceGravityDamping => Ok("APP_SETTINGS.SPACE_GRAVITY_DAMPING"),
+ Self::SpaceGravityEnabled => Ok("APP_SETTINGS.ENABLED"),
+ Self::SpaceGravityFlingStrength => Ok("APP_SETTINGS.SPACE_GRAVITY_FLING_STRENGTH"),
+ Self::SpaceGravityGravity => Ok("APP_SETTINGS.SPACE_GRAVITY_GRAVITY"),
+ Self::SpaceGravityGroundFriction => Ok("APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION"),
+ Self::SpaceGravityFloorHeight => Ok("APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT"),
Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
Self::UiAnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
Self::UiGradientIntensity => Ok("APP_SETTINGS.UI_GRADIENT_INTENSITY"),
@@ -466,13 +509,18 @@ impl SettingType {
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
+ Self::SpaceGravityDamping => Some("APP_SETTINGS.SPACE_GRAVITY_DAMPING_HELP"),
+ Self::SpaceGravityFlingStrength => Some("APP_SETTINGS.SPACE_GRAVITY_FLING_STRENGTH_HELP"),
+ Self::SpaceGravityFloorHeight => Some("APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT_HELP"),
+ Self::SpaceGravityGravity => Some("APP_SETTINGS.SPACE_GRAVITY_GRAVITY_HELP"),
+ Self::SpaceGravityGroundFriction => Some("APP_SETTINGS.SPACE_GRAVITY_GROUND_FRICTION_HELP"),
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
+ Self::WatchViewAngleMax => Some("APP_SETTINGS.WATCH_VIEW_ANGLE_HELP"),
+ Self::WatchViewAngleMin => Some("APP_SETTINGS.WATCH_VIEW_ANGLE_HELP"),
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
- Self::WatchViewAngleMin => Some("APP_SETTINGS.WATCH_VIEW_ANGLE_HELP"),
- Self::WatchViewAngleMax => Some("APP_SETTINGS.WATCH_VIEW_ANGLE_HELP"),
_ => None,
}
}
@@ -575,6 +623,9 @@ impl TabSettings {
TabNameEnum::Features => {
self.current_tab = Some(Box::new(tab_features::State::mount(settings_mount_params)?));
}
+ TabNameEnum::SpaceDrag => {
+ self.current_tab = Some(Box::new(tab_space_drag::State::mount(settings_mount_params)?));
+ }
TabNameEnum::Controls => {
self.current_tab = Some(Box::new(tab_controls::State::mount(settings_mount_params)?));
}
diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs
index 266a7334..a69113ba 100644
--- a/dash-frontend/src/tab/settings/tab_features.rs
+++ b/dash-frontend/src/tab/settings/tab_features.rs
@@ -13,15 +13,6 @@ impl State {
options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?;
options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?;
options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?;
- if !par.feats.openxr || par.feats.monado {
- // monado or openvr
- options_checkbox(par.mp, c, SettingType::SpaceDragUnlocked)?;
- options_slider_f32(par.mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5)?;
- }
- if par.feats.monado {
- // openvr can only ever rotate yaw
- options_checkbox(par.mp, c, SettingType::SpaceRotateUnlocked)?;
- }
if !par.feats.openxr || par.feats.monado {
// monado or openvr
options_checkbox(par.mp, c, SettingType::BlockGameInput)?;
diff --git a/dash-frontend/src/tab/settings/tab_look_and_feel.rs b/dash-frontend/src/tab/settings/tab_look_and_feel.rs
index 768536db..216e6688 100644
--- a/dash-frontend/src/tab/settings/tab_look_and_feel.rs
+++ b/dash-frontend/src/tab/settings/tab_look_and_feel.rs
@@ -22,6 +22,7 @@ impl State {
options_slider_f32(par.mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step
options_slider_f32(par.mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step
options_slider_f32(par.mp, c, SettingType::UiRoundMultiplier, 0.1, 5.0, 0.1)?;
+ options_checkbox(par.mp, c, SettingType::EnableWatch)?;
options_checkbox(par.mp, c, SettingType::SetsOnWatch)?;
options_checkbox(par.mp, c, SettingType::Clock12h)?;
Ok(State {})
diff --git a/dash-frontend/src/tab/settings/tab_space_drag.rs b/dash-frontend/src/tab/settings/tab_space_drag.rs
new file mode 100644
index 00000000..aa6bf8d0
--- /dev/null
+++ b/dash-frontend/src/tab/settings/tab_space_drag.rs
@@ -0,0 +1,138 @@
+use wgui::{
+ assets::AssetPath,
+ i18n::Translation,
+ layout::{Layout, LayoutTask, WidgetID},
+ parser::{Fetchable, ParseDocumentParams},
+};
+
+use crate::{
+ tab::settings::{
+ SettingType, SettingsMountParams, SettingsTab,
+ macros::{options_category, options_checkbox, options_slider_f32},
+ },
+ util::wgui_simple,
+};
+
+pub struct State {
+ id_space_gravity_parent: WidgetID,
+}
+
+fn set_visible(parent: WidgetID, layout: &mut Layout, n: bool) {
+ layout.tasks.push(LayoutTask::SetWidgetVisible(parent, n));
+}
+
+impl SettingsTab for State {
+ fn setting_updated(&mut self, sup: &mut super::SettingUpdatedParams) -> anyhow::Result<()> {
+ if sup.setting_type == SettingType::SpaceGravityEnabled {
+ set_visible(
+ self.id_space_gravity_parent,
+ sup.layout,
+ sup.config.space_gravity_enabled,
+ );
+ }
+ Ok(())
+ }
+}
+
+impl State {
+ pub fn mount(par: SettingsMountParams) -> anyhow::Result {
+ let c = options_category(par.mp, par.id_parent, "APP_SETTINGS.SPACE_DRAG", "dashboard/drag.svg")?;
+
+ let globals = par.mp.layout.state.globals.clone();
+
+ let tab_state = wgui::parser::parse_from_assets(
+ &ParseDocumentParams {
+ globals,
+ path: AssetPath::BuiltIn("gui/tab/settings_tab_space_drag.xml"),
+ extra: Default::default(),
+ },
+ par.mp.layout,
+ c,
+ )?;
+
+ let id_common_options_parent = tab_state.get_widget_id("common_options_parent")?;
+ let id_gravity_enabled_parent = tab_state.get_widget_id("gravity_enabled_parent")?;
+ let id_space_gravity_parent = tab_state.get_widget_id("space_gravity_parent")?;
+
+ if !par.feats.openxr || par.feats.monado {
+ // monado or openvr
+ options_checkbox(par.mp, id_common_options_parent, SettingType::SpaceDragUnlocked)?;
+
+ options_slider_f32(
+ par.mp,
+ id_common_options_parent,
+ SettingType::SpaceDragMultiplier,
+ -10.0,
+ 10.0,
+ 0.5,
+ )?;
+ }
+
+ if par.feats.monado {
+ // openvr can only ever rotate yaw
+ options_checkbox(par.mp, id_common_options_parent, SettingType::SpaceRotateUnlocked)?;
+ }
+
+ if par.feats.monado {
+ /* space gravity section */
+ options_checkbox(par.mp, id_gravity_enabled_parent, SettingType::SpaceGravityEnabled)?;
+
+ options_slider_f32(
+ par.mp,
+ id_space_gravity_parent,
+ SettingType::SpaceGravityGravity,
+ 0.0,
+ 10.0,
+ 0.5,
+ )?;
+ options_slider_f32(
+ par.mp,
+ id_space_gravity_parent,
+ SettingType::SpaceGravityDamping,
+ 0.1,
+ 1.0,
+ 0.01,
+ )?;
+ options_slider_f32(
+ par.mp,
+ id_space_gravity_parent,
+ SettingType::SpaceGravityFlingStrength,
+ 0.0,
+ 3.0,
+ 0.1,
+ )?;
+ options_slider_f32(
+ par.mp,
+ id_space_gravity_parent,
+ SettingType::SpaceGravityGroundFriction,
+ 0.0,
+ 1.0,
+ 0.01,
+ )?;
+ options_slider_f32(
+ par.mp,
+ id_space_gravity_parent,
+ SettingType::SpaceGravityFloorHeight,
+ -5.0,
+ 5.0,
+ 0.1,
+ )?;
+ } else {
+ wgui_simple::create_label(
+ par.mp.layout,
+ id_gravity_enabled_parent,
+ Translation::from_translation_key("APP_SETTINGS.NOT_SUPPORTED"),
+ )?;
+ }
+
+ set_visible(
+ id_space_gravity_parent,
+ par.mp.layout,
+ par.mp.config.space_gravity_enabled,
+ );
+
+ Ok(State {
+ id_space_gravity_parent,
+ })
+ }
+}
diff --git a/dash-frontend/src/util/popup_manager.rs b/dash-frontend/src/util/popup_manager.rs
index e7c62151..7f1ab24e 100644
--- a/dash-frontend/src/util/popup_manager.rs
+++ b/dash-frontend/src/util/popup_manager.rs
@@ -6,12 +6,11 @@ use std::{
use wgui::{
assets::AssetPath,
components::button::ComponentButton,
- event::{EventAlterables, StyleSetRequest},
+ event::EventAlterables,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState},
- taffy::Display,
widget::label::WidgetLabel,
};
use wlx_common::config::GeneralConfig;
@@ -232,15 +231,7 @@ impl State {
let popup = popup.upgrade().unwrap(); // safe
let popup = popup.borrow_mut();
let mounted_popup = popup.mounted_popup.as_ref().unwrap(); // safe;
-
- alterables.set_style(
- mounted_popup.id_root,
- StyleSetRequest::Display(if idx == self.popup_stack.len() - 1 {
- Display::Flex
- } else {
- Display::None
- }),
- );
+ alterables.set_widget_visible(mounted_popup.id_root, idx == self.popup_stack.len() - 1);
}
}
}
diff --git a/dash-frontend/src/views/running_games_list.rs b/dash-frontend/src/views/running_games_list.rs
index 6428f5d1..bc607b20 100644
--- a/dash-frontend/src/views/running_games_list.rs
+++ b/dash-frontend/src/views/running_games_list.rs
@@ -112,10 +112,7 @@ impl View {
fn fill_list(&mut self, layout: &mut Layout, games: Vec) -> anyhow::Result<()> {
if games.is_empty() {
// hide self
- layout.tasks.push(LayoutTask::SetWidgetStyle(
- self.parent_id,
- StyleSetRequest::Display(Display::None),
- ));
+ layout.tasks.push(LayoutTask::SetWidgetVisible(self.parent_id, false));
return Ok(());
}
diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs
index d1d078da..e64baf79 100644
--- a/uidev/src/testbed/testbed_generic.rs
+++ b/uidev/src/testbed/testbed_generic.rs
@@ -13,7 +13,6 @@ use wgui::{
checkbox::ComponentCheckbox,
},
drawing::Color,
- event::StyleSetRequest,
font_config::WguiFontConfig,
globals::WguiGlobals,
i18n::Translation,
@@ -134,14 +133,9 @@ impl TestbedGeneric {
let div_visibility = parser_state.fetch_widget(&layout.state, "div_visibility")?;
cb_visible.on_toggle(Box::new(move |common, evt| {
- common.alterables.set_style(
- div_visibility.id,
- StyleSetRequest::Display(if evt.checked {
- taffy::Display::Flex
- } else {
- taffy::Display::None
- }),
- );
+ common
+ .alterables
+ .set_widget_visible(div_visibility.id, evt.checked);
Ok(())
}));
diff --git a/wayvr/src/backend/mod.rs b/wayvr/src/backend/mod.rs
index b352a879..b1ded4f3 100644
--- a/wayvr/src/backend/mod.rs
+++ b/wayvr/src/backend/mod.rs
@@ -1,4 +1,5 @@
pub mod input;
+pub mod playspace_common;
#[cfg(feature = "openvr")]
pub mod openvr;
diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs
index d1e3870d..a44696b1 100644
--- a/wayvr/src/backend/openvr/mod.rs
+++ b/wayvr/src/backend/openvr/mod.rs
@@ -146,8 +146,12 @@ pub fn openvr_run(
let mut lines = LinePool::new(app.gfx.clone())?;
let pointer_lines = [lines.allocate(), lines.allocate()];
let mut current_lines = Vec::with_capacity(2);
+ let mut last_frame_time = Instant::now();
'main_loop: loop {
+ let now = Instant::now();
+ app.delta_time = (now.duration_since(last_frame_time).as_secs_f32()).clamp(0.001, 0.2); // 5 - 1000 fps
+ last_frame_time = now;
let _ = overlay_mgr.wait_frame_sync(frame_timeout);
if !RUNNING.load(Ordering::Relaxed) {
diff --git a/wayvr/src/backend/openvr/playspace.rs b/wayvr/src/backend/openvr/playspace.rs
index e73b1ef2..670dfd5d 100644
--- a/wayvr/src/backend/openvr/playspace.rs
+++ b/wayvr/src/backend/openvr/playspace.rs
@@ -6,7 +6,7 @@ use ovr_overlay::{
};
use crate::{
- backend::{input::InputState, task::PlayspaceTask},
+ backend::{input::InputState, playspace_common, task::PlayspaceTask},
state::AppState,
windowing::manager::OverlayWindowManager,
};
@@ -137,16 +137,7 @@ impl PlayspaceMover {
}
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
-
- overlays.values_mut().for_each(|overlay| {
- let Some(state) = overlay.config.active_state.as_mut() else {
- return;
- };
- if state.positioning.moves_with_space() {
- state.transform.translation += overlay_offset;
- overlay.config.dirty = true;
- }
- });
+ playspace_common::shift_overlays(overlays, overlay_offset);
data.pose.translation += relative_pos;
data.hand_pose = new_hand;
diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs
index f6ec7a44..ceecfc74 100644
--- a/wayvr/src/backend/openxr/mod.rs
+++ b/wayvr/src/backend/openxr/mod.rs
@@ -94,7 +94,7 @@ pub fn openxr_run(
app.monado_state_init();
- let mut playspace = app.monado_state.as_mut().and_then(|m| {
+ let mut playspace_mover = app.monado_state.as_mut().and_then(|m| {
playspace::PlayspaceMover::new(&mut m.ipc)
.map_err(|e| log::warn!("Will not use Monado playspace mover: {e}"))
.ok()
@@ -155,8 +155,12 @@ pub fn openxr_run(
let mut main_session_visible = false;
let mut environment_blend_mode = modes[0];
+ let mut last_frame_time = Instant::now();
'main_loop: loop {
+ let now = Instant::now();
+ app.delta_time = (now.duration_since(last_frame_time).as_secs_f32()).clamp(0.001, 0.2); // 5 - 1000 fps
+ last_frame_time = now;
let cur_frame = FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
if !RUNNING.load(Ordering::Relaxed) {
@@ -296,8 +300,8 @@ pub fn openxr_run(
.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard));
}
- if let Some(ref mut space_mover) = playspace {
- space_mover.update(&mut overlays, &mut app);
+ if let Some(ref mut playspace_mover) = playspace_mover {
+ playspace_mover.update(&mut overlays, &mut app);
}
for o in overlays.values_mut() {
@@ -346,9 +350,12 @@ pub fn openxr_run(
app.hid_provider.inner.commit();
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
+ if watch.config.active_state.is_none() {
+ watch.config.activate(&mut app);
+ }
let watch_state = watch.config.active_state.as_mut().unwrap();
let watch_transform = watch_state.transform;
- if watch_state.alpha < 0.05 {
+ if watch_state.alpha < 0.05 || !app.session.config.enable_watch {
//FIXME: Temporary workaround for Monado bug
watch_state.transform = Affine3A::from_scale(Vec3 {
x: 0.001,
@@ -481,8 +488,8 @@ pub fn openxr_run(
overlays.handle_task(&mut app, task)?;
}
TaskType::Playspace(task) => {
- if let Some(playspace) = playspace.as_mut() {
- playspace.handle_task(&mut app, task);
+ if let Some(playspace_mover) = playspace_mover.as_mut() {
+ playspace_mover.handle_task(&mut app, task);
}
}
TaskType::OpenXR(task) => {
@@ -510,7 +517,13 @@ pub fn openxr_run(
//FIXME: Temporary workaround for Monado bug
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
- watch.config.active_state.as_mut().unwrap().transform = watch_transform;
+
+ if let Some(state) = watch.config.active_state.as_mut() {
+ state.transform = watch_transform
+ }
+ if !app.session.config.enable_watch {
+ watch.config.deactivate();
+ }
} // main_loop
if let (Some(blocker), Some(monado)) = (blocker, app.monado_state.as_mut()) {
diff --git a/wayvr/src/backend/openxr/playspace.rs b/wayvr/src/backend/openxr/playspace.rs
index 0f6d95b5..f8aa9c63 100644
--- a/wayvr/src/backend/openxr/playspace.rs
+++ b/wayvr/src/backend/openxr/playspace.rs
@@ -3,7 +3,11 @@ use libmonado::{MndResult, Monado, Pose, ReferenceSpaceType};
use wgui::log::LogErr;
use crate::{
- backend::{input::InputState, task::PlayspaceTask},
+ backend::{
+ input::InputState,
+ playspace_common::{self, SpaceGravity, SpaceGravityUpdateParams},
+ task::PlayspaceTask,
+ },
state::AppState,
windowing::manager::OverlayWindowManager,
};
@@ -19,6 +23,7 @@ struct MoverData {
pub(super) struct PlayspaceMover {
drag: Option>,
rotate: Option>,
+ gravity: SpaceGravity,
}
impl PlayspaceMover {
@@ -35,6 +40,7 @@ impl PlayspaceMover {
Ok(Self {
drag: None,
rotate: None,
+ gravity: SpaceGravity::new(),
})
}
@@ -140,21 +146,27 @@ impl PlayspaceMover {
}
if let Some(mut data) = self.drag.take() {
- let pointer = &app.input_state.pointers[data.hand];
- if !pointer.now.space_drag {
- log::info!("End space drag");
- return;
- }
-
let new_hand = data
.pose
.transform_point3a(app.input_state.pointers[data.hand].raw_pose.translation);
-
let relative_pos = if app.session.config.space_drag_unlocked {
new_hand - data.hand_pose
} else {
vec3a(0., new_hand.y - data.hand_pose.y, 0.)
} * app.session.config.space_drag_multiplier;
+ let pointer = &app.input_state.pointers[data.hand];
+
+ if !pointer.now.space_drag {
+ self.gravity.mark_end_drag(
+ &app.session.config,
+ relative_pos,
+ data.pose.translation,
+ app.delta_time,
+ );
+
+ log::info!("End space drag");
+ return;
+ }
if relative_pos.length_squared() > 1000.0 {
log::warn!("Space drag too fast, ignoring");
@@ -162,16 +174,7 @@ impl PlayspaceMover {
}
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
-
- overlays.values_mut().for_each(|overlay| {
- let Some(state) = overlay.config.active_state.as_mut() else {
- return;
- };
- if state.positioning.moves_with_space() {
- state.transform.translation += overlay_offset;
- }
- overlay.config.dirty = true;
- });
+ playspace_common::shift_overlays(overlays, overlay_offset);
data.pose.translation += relative_pos;
data.hand_pose = new_hand;
@@ -207,6 +210,20 @@ impl PlayspaceMover {
}
}
}
+
+ if let Some(res) = self.gravity.update(SpaceGravityUpdateParams {
+ dt: app.delta_time,
+ dragging: self.drag.is_some(),
+ config: &app.session.config,
+ floor_height: app.session.config.space_gravity_floor_height,
+ }) {
+ apply_offset(
+ Affine3A::from_translation(res.playspace_pos.into()),
+ &mut monado.ipc,
+ );
+
+ playspace_common::shift_overlays(overlays, -res.playspace_pos_offset);
+ }
}
pub fn recenter(&mut self, input: &InputState, monado: &mut Monado) {
@@ -232,6 +249,8 @@ impl PlayspaceMover {
let _ = monado
.set_reference_space_offset(ReferenceSpaceType::Stage, pose)
.inspect_err(|e| log::warn!("Could not recenter due to libmonado error: {e:?}"));
+
+ self.gravity.reset();
}
pub fn reset_offset(&mut self, monado: &mut Monado) {
@@ -244,6 +263,7 @@ impl PlayspaceMover {
self.rotate = None;
}
+ self.gravity.reset();
apply_offset(Affine3A::IDENTITY, monado);
}
diff --git a/wayvr/src/backend/playspace_common.rs b/wayvr/src/backend/playspace_common.rs
new file mode 100644
index 00000000..ceac0e7e
--- /dev/null
+++ b/wayvr/src/backend/playspace_common.rs
@@ -0,0 +1,101 @@
+use glam::Vec3A;
+use wlx_common::config::GeneralConfig;
+
+use crate::windowing::manager::OverlayWindowManager;
+
+pub struct SpaceGravityUpdateParams<'a> {
+ pub dt: f32,
+ pub dragging: bool,
+ pub config: &'a GeneralConfig,
+ pub floor_height: f32,
+}
+
+pub struct SpaceGravity {
+ velocity: Vec3A,
+ space_pos: Vec3A,
+}
+
+pub fn shift_overlays(
+ overlays: &mut OverlayWindowManager,
+ overlay_offset: Vec3A,
+) {
+ overlays.values_mut().for_each(|overlay| {
+ let Some(state) = overlay.config.active_state.as_mut() else {
+ return;
+ };
+ if state.positioning.moves_with_space() {
+ state.transform.translation += overlay_offset;
+ }
+ overlay.config.dirty = true;
+ });
+}
+
+pub struct SpaceGravityUpdateResult {
+ pub playspace_pos: Vec3A,
+ pub playspace_pos_offset: Vec3A, // position difference compared to previous update() call
+}
+
+impl SpaceGravity {
+ pub fn new() -> Self {
+ Self {
+ velocity: Vec3A::default(),
+ space_pos: Vec3A::default(),
+ }
+ }
+
+ pub fn mark_end_drag(
+ &mut self,
+ config: &GeneralConfig,
+ hand_pos_diff: Vec3A,
+ space_pos: Vec3A,
+ dt: f32,
+ ) {
+ if config.space_gravity_enabled {
+ self.velocity = hand_pos_diff * config.space_gravity_fling_strength / dt;
+ self.space_pos = space_pos;
+ } else {
+ self.reset();
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.velocity = Vec3A::default();
+ self.space_pos = Vec3A::default();
+ }
+
+ pub fn update(&mut self, par: SpaceGravityUpdateParams) -> Option {
+ if par.dragging || !par.config.space_gravity_enabled {
+ return None;
+ }
+
+ let prev_pos = self.space_pos;
+
+ self.velocity.y += par.config.space_gravity_gravity * par.dt;
+
+ // terminal velocity
+ self.velocity.y = self.velocity.y.min(200.0);
+
+ self.velocity *= (par.config.space_gravity_damping).powf(par.dt * 10.0);
+
+ self.space_pos += self.velocity * par.dt;
+
+ self.space_pos.y = self.space_pos.y.min(par.floor_height);
+
+ if self.space_pos.y >= par.floor_height
+ /* at floor height or below */
+ {
+ // apply ground friction
+ self.velocity *= 1.0 - par.config.space_gravity_ground_friction * par.dt * 10.0;
+ }
+
+ if self.velocity.length_squared() > 0.00003 {
+ // Space position changed
+ return Some(SpaceGravityUpdateResult {
+ playspace_pos: self.space_pos,
+ playspace_pos_offset: self.space_pos - prev_pos,
+ });
+ }
+
+ None
+ }
+}
diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs
index a42526db..ebe40d5f 100644
--- a/wayvr/src/config.rs
+++ b/wayvr/src/config.rs
@@ -145,6 +145,7 @@ pub struct AutoSettings {
pub keyboard_sound_enabled: bool,
pub upright_screen_fix: bool,
pub double_cursor_fix: bool,
+ pub enable_watch: bool,
pub sets_on_watch: bool,
pub hide_grab_help: bool,
pub xr_click_sensitivity: f32,
@@ -165,6 +166,12 @@ pub struct AutoSettings {
pub screen_render_down: bool,
pub pointer_lerp_factor: f32,
pub space_drag_unlocked: bool,
+ pub space_gravity_damping: f32,
+ pub space_gravity_enabled: bool,
+ pub space_gravity_fling_strength: f32,
+ pub space_gravity_gravity: f32,
+ pub space_gravity_ground_friction: f32,
+ pub space_gravity_floor_height: f32,
pub space_rotate_unlocked: bool,
pub clock_12h: bool,
pub hide_username: bool,
@@ -200,6 +207,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
keyboard_sound_enabled: config.keyboard_sound_enabled,
upright_screen_fix: config.upright_screen_fix,
double_cursor_fix: config.double_cursor_fix,
+ enable_watch: config.enable_watch,
sets_on_watch: config.sets_on_watch,
hide_grab_help: config.hide_grab_help,
xr_click_sensitivity: config.xr_click_sensitivity,
@@ -220,6 +228,12 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
screen_render_down: config.screen_render_down,
pointer_lerp_factor: config.pointer_lerp_factor,
space_drag_unlocked: config.space_drag_unlocked,
+ space_gravity_damping: config.space_gravity_damping,
+ space_gravity_enabled: config.space_gravity_enabled,
+ space_gravity_fling_strength: config.space_gravity_fling_strength,
+ space_gravity_gravity: config.space_gravity_gravity,
+ space_gravity_ground_friction: config.space_gravity_ground_friction,
+ space_gravity_floor_height: config.space_gravity_floor_height,
space_rotate_unlocked: config.space_rotate_unlocked,
clock_12h: config.clock_12h,
hide_username: config.hide_username,
diff --git a/wayvr/src/gui/panel/mod.rs b/wayvr/src/gui/panel/mod.rs
index 7726ffcc..6d60d065 100644
--- a/wayvr/src/gui/panel/mod.rs
+++ b/wayvr/src/gui/panel/mod.rs
@@ -33,7 +33,6 @@ use wgui::{
self, CustomAttribsInfoOwned, Fetchable, ParseDocumentExtra, ParserState, parse_color_hex,
},
renderer_vk::{context::Context as WguiContext, text::custom_glyph::CustomGlyphData},
- taffy,
widget::{
EventResult, image::WidgetImage, label::WidgetLabel, rectangle::WidgetRectangle,
sprite::WidgetSprite,
@@ -552,15 +551,7 @@ pub fn apply_custom_command(
.parser_state
.get_widget_id(element)
.context("No widget with such id.")?;
-
- let display = if *visible {
- taffy::Display::Flex
- } else {
- taffy::Display::None
- };
-
- com.alterables
- .set_style(wid, wgui::event::StyleSetRequest::Display(display));
+ com.alterables.set_widget_visible(wid, *visible);
com.alterables.mark_redraw();
}
ModifyPanelCommand::SetValue(value_str) => {
diff --git a/wayvr/src/overlays/anchor.rs b/wayvr/src/overlays/anchor.rs
index 529c1680..8ca6c497 100644
--- a/wayvr/src/overlays/anchor.rs
+++ b/wayvr/src/overlays/anchor.rs
@@ -1,8 +1,7 @@
use glam::{Affine3A, Quat, Vec3};
use std::sync::{Arc, LazyLock};
-use wgui::event::{EventAlterables, StyleSetRequest};
+use wgui::event::EventAlterables;
use wgui::parser::Fetchable;
-use wgui::taffy;
use wlx_common::windowing::{OverlayWindowState, Positioning};
use crate::gui::panel::GuiPanel;
@@ -84,13 +83,7 @@ pub fn create_grab_help(app: &mut AppState) -> anyhow::Result>,
extentf: [f32; 2],
offsetf: [f32; 2],
+ transform: wlx_frame::Transform,
stereo: StereoMode,
}
@@ -64,6 +64,7 @@ impl ScreenPipeline {
app: &mut AppState,
stereo: StereoMode,
offsetf: [f32; 2],
+ transform: wlx_frame::Transform,
) -> anyhow::Result {
let extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
@@ -81,6 +82,7 @@ impl ScreenPipeline {
pipeline,
extentf,
offsetf,
+ transform,
stereo,
};
me.ensure_stereo(stereo);
@@ -96,6 +98,10 @@ impl ScreenPipeline {
self.pass.clear(); // ensure_depth will repopulate
}
+ pub const fn transform(&self) -> wlx_frame::Transform {
+ self.transform
+ }
+
fn ensure_depth(&mut self, app: &mut AppState, depth: usize) -> anyhow::Result<()> {
while self.pass.len() < depth {
self.pass.push(Self::create_pass(
@@ -111,20 +117,22 @@ impl ScreenPipeline {
}
for (eye, current) in self.pass.iter_mut().enumerate() {
- let verts = stereo_mode_to_verts(self.stereo, eye);
+ let verts = stereo_mode_to_verts(self.stereo, eye, self.transform);
current.buf_vert.write()?.copy_from_slice(&verts);
}
Ok(())
}
- pub fn set_extent(
+ pub fn set_layout(
&mut self,
app: &mut AppState,
extentf: [f32; 2],
offsetf: [f32; 2],
+ transform: wlx_frame::Transform,
) -> anyhow::Result<()> {
self.extentf = extentf;
self.offsetf = offsetf;
+ self.transform = transform;
self.pass.clear();
self.mouse = Self::create_mouse_pass(app, self.pipeline.clone(), extentf, offsetf)?;
@@ -241,13 +249,31 @@ impl ScreenPipeline {
}
}
-fn stereo_mode_to_verts(stereo: StereoMode, array_index: usize) -> [Vert2Uv; 4] {
+fn transform_uv(uv: [f32; 2], transform: wlx_frame::Transform) -> [f32; 2] {
+ let [u, v] = uv;
+ match transform {
+ wlx_frame::Transform::Normal | wlx_frame::Transform::Undefined => [u, v],
+ wlx_frame::Transform::Rotated90 => [v, 1.0 - u],
+ wlx_frame::Transform::Rotated180 => [1.0 - u, 1.0 - v],
+ wlx_frame::Transform::Rotated270 => [1.0 - v, u],
+ wlx_frame::Transform::Flipped => [1.0 - u, v],
+ wlx_frame::Transform::Flipped90 => [v, u],
+ wlx_frame::Transform::Flipped180 => [u, 1.0 - v],
+ wlx_frame::Transform::Flipped270 => [1.0 - v, 1.0 - u],
+ }
+}
+
+fn stereo_mode_to_verts(
+ stereo: StereoMode,
+ array_index: usize,
+ transform: wlx_frame::Transform,
+) -> [Vert2Uv; 4] {
let eye = match stereo {
StereoMode::RightLeft | StereoMode::BottomTop => (1 - array_index) as f32,
_ => array_index as f32,
};
- match stereo {
+ let mut verts = match stereo {
StereoMode::None => [
Vert2Uv {
in_pos: [0., 0.],
@@ -302,7 +328,13 @@ fn stereo_mode_to_verts(stereo: StereoMode, array_index: usize) -> [Vert2Uv; 4]
in_uv: [1., 0.5 + eye * 0.5],
},
],
+ };
+
+ for vert in &mut verts {
+ vert.in_uv = transform_uv(vert.in_uv, transform);
}
+
+ verts
}
static DMA_ALLOCATOR: OnceLock> = OnceLock::new();
@@ -460,7 +492,7 @@ impl WlxCaptureOut {
FrameMeta {
clear: WGfxClearMode::DontCare,
extent: extent_from_format(self.format, config),
- transform: affine_from_format(&self.format),
+ transform: Affine3A::IDENTITY,
format: self.image.format(),
stereo,
}
@@ -585,13 +617,14 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option {
+ WlxFrame::Implicit(transform) => {
log::trace!("{}: New Implicit frame", me.name);
- let Some((image, format)) = me.dma_exporter.as_ref().unwrap().get_current() else {
+ let Some((image, mut format)) = me.dma_exporter.as_ref().unwrap().get_current() else {
log::error!("{}: Implicit frame is missing!", me.name);
return None;
};
+ format.transform = transform;
Some(WlxCaptureOut {
image,
@@ -676,6 +709,14 @@ const fn receive_callback_dummy(_: &DummyDrmExporter, frame: WlxFrame) -> Option
}
fn extent_from_format(fmt: FrameFormat, config: &GeneralConfig) -> [u32; 2] {
+ let (width, height) = match fmt.transform {
+ wlx_frame::Transform::Rotated90
+ | wlx_frame::Transform::Rotated270
+ | wlx_frame::Transform::Flipped90
+ | wlx_frame::Transform::Flipped270 => (fmt.height, fmt.width),
+ _ => (fmt.width, fmt.height),
+ };
+
// screens above a certain resolution will have severe aliasing
let height_limit = if config.screen_render_down {
u32::from(config.screen_max_height.min(2560))
@@ -683,36 +724,11 @@ fn extent_from_format(fmt: FrameFormat, config: &GeneralConfig) -> [u32; 2] {
2560
};
- let h = fmt.height.min(height_limit);
- let w = (fmt.width as f32 / fmt.height as f32 * h as f32) as u32;
+ let h = height.min(height_limit);
+ let w = (width as f32 / height as f32 * h as f32) as u32;
[w, h]
}
-fn affine_from_format(format: &FrameFormat) -> Affine3A {
- const FLIP_X: Vec3 = Vec3 {
- x: -1.0,
- y: 1.0,
- z: 1.0,
- };
-
- match format.transform {
- wlx_frame::Transform::Rotated90 => Affine3A::from_rotation_z(-PI / 2.0),
- wlx_frame::Transform::Rotated180 => Affine3A::from_rotation_z(PI),
- wlx_frame::Transform::Rotated270 => Affine3A::from_rotation_z(PI / 2.0),
- wlx_frame::Transform::Flipped => Affine3A::from_scale(FLIP_X),
- wlx_frame::Transform::Flipped90 => {
- Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(-PI / 2.0)
- }
- wlx_frame::Transform::Flipped180 => {
- Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI)
- }
- wlx_frame::Transform::Flipped270 => {
- Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI / 2.0)
- }
- _ => Affine3A::IDENTITY,
- }
-}
-
macro_rules! new_wlx_capture {
($capture_queue:expr, $capture:expr) => {
if $capture_queue.is_none() {
diff --git a/wayvr/src/overlays/watch.rs b/wayvr/src/overlays/watch.rs
index 596d2a4d..33c19ad5 100644
--- a/wayvr/src/overlays/watch.rs
+++ b/wayvr/src/overlays/watch.rs
@@ -4,9 +4,7 @@ use glam::{Affine3A, Quat, Vec3, vec3};
use wgui::{
assets::AssetPath,
components::button::ComponentButton,
- event::StyleSetRequest,
parser::{Fetchable, ParseDocumentParams},
- taffy,
};
use wlx_common::{
common::LeftRight,
@@ -156,17 +154,17 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result {
angle_fade: true,
..OverlayWindowState::default()
},
- show_on_spawn: true,
+ show_on_spawn: app.session.config.enable_watch,
global: true,
..OverlayWindowConfig::from_backend(Box::new(panel))
})
}
fn sets_or_overlays(panel: &mut GuiPanel, app: &mut AppState) {
- let display = if app.session.config.sets_on_watch {
- [taffy::Display::None, taffy::Display::Flex]
+ let visible = if app.session.config.sets_on_watch {
+ [false, true]
} else {
- [taffy::Display::Flex, taffy::Display::None]
+ [true, false]
};
let widget = [
@@ -184,6 +182,6 @@ fn sets_or_overlays(panel: &mut GuiPanel, app: &mut AppState) {
panel
.layout
.alterables
- .set_style(widget[i], StyleSetRequest::Display(display[i]));
+ .set_widget_visible(widget[i], visible[i]);
}
}
diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs
index 2b0b0274..9a223ed2 100644
--- a/wayvr/src/overlays/wayvr.rs
+++ b/wayvr/src/overlays/wayvr.rs
@@ -19,7 +19,7 @@ use wgui::{
parser::Fetchable,
widget::{EventResult, label::WidgetLabel},
};
-use wlx_capture::frame::MouseMeta;
+use wlx_capture::frame::{MouseMeta, Transform};
use wlx_common::{
overlays::{BackendAttrib, BackendAttribValue, StereoMode},
windowing::{OverlayWindowState, Positioning},
@@ -348,10 +348,11 @@ impl OverlayBackend for WvrWindowBackend {
if let Some(pipeline) = self.pipeline.as_mut() {
if self.inner_extent != inner_extent {
- pipeline.set_extent(
+ pipeline.set_layout(
app,
[inner_extent[0] as _, inner_extent[1] as _],
[BORDER_SIZE as _, (BAR_SIZE + BORDER_SIZE) as _],
+ Transform::Normal,
)?;
self.apply_extent(app, &meta)?;
self.inner_extent = inner_extent;
@@ -362,6 +363,7 @@ impl OverlayBackend for WvrWindowBackend {
app,
self.stereo.unwrap_or(StereoMode::None),
[BORDER_SIZE as _, (BAR_SIZE + BORDER_SIZE) as _],
+ Transform::Normal,
)?;
self.apply_extent(app, &meta)?;
self.pipeline = Some(pipeline);
diff --git a/wayvr/src/res/config.yaml b/wayvr/src/res/config.yaml
index 55747a09..62e00286 100644
--- a/wayvr/src/res/config.yaml
+++ b/wayvr/src/res/config.yaml
@@ -85,6 +85,9 @@
# The settings are here for reference only.
# Probably don't include them in your config file.
+## The watch will be enabled
+#enable_watch: true
+
## The bottom of the watch will list sets instead of overlays.
#sets_on_watch: false
@@ -117,6 +120,21 @@
## can rotate in any axis. Imagine horizon mode².
#space_rotate_unlocked: false
+## Space gravity: downward acceleration speed
+#space_gravity_gravity: 2.0
+
+## Space gravity: velocity damping (0.98 = gentle slowdown, 0.5 = heavy drag)
+#space_gravity_damping: 0.98
+
+## Space gravity: multiplier for "throwing" yourself via space drag momentum
+#space_gravity_fling_strength: 1.0
+
+## Space gravity: floor height (Y position where gravity stops)
+#space_gravity_floor_height: 0.0
+
+## Space gravity: ground friction when touching the floor
+#space_gravity_ground_friction: 1.0
+
## Monado/WiVRn only. Use passthrough camera if the headset supports it.
## If disabled, the skybox will be shown.
#use_passthrough: true
diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs
index 49a5f3f1..ae4323f6 100644
--- a/wayvr/src/state.rs
+++ b/wayvr/src/state.rs
@@ -71,6 +71,8 @@ pub struct AppState {
#[cfg(feature = "openxr")]
pub monado_state: Option,
+
+ pub delta_time: f32,
}
#[allow(unused_mut)]
@@ -188,6 +190,8 @@ impl AppState {
#[cfg(feature = "openxr")]
monado_state: None,
+
+ delta_time: 1.0 / 120.0,
};
if let Some(error_toast) = hid_error {
diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs
index 5d8a168c..8b859ff0 100644
--- a/wayvr/src/windowing/manager.rs
+++ b/wayvr/src/windowing/manager.rs
@@ -288,6 +288,16 @@ where
self.sets_changed(app);
}
OverlayTask::SettingsChanged => {
+ if let Some(watch) = self.mut_by_id(self.watch_id) {
+ if app.session.config.enable_watch != watch.config.active_state.is_some() {
+ if watch.config.active_state.is_some() {
+ watch.config.deactivate();
+ } else {
+ watch.config.activate(app);
+ }
+ }
+ }
+
for o in self.overlays.values_mut() {
let _ = o
.config
diff --git a/wgui/src/components/editbox.rs b/wgui/src/components/editbox.rs
index c28650e6..14ea2de0 100644
--- a/wgui/src/components/editbox.rs
+++ b/wgui/src/components/editbox.rs
@@ -123,14 +123,7 @@ fn refresh_all(common: &mut CallbackDataCommon, data: &Data, state: &mut State)
}
// Cursor
- common.alterables.set_style(
- data.id_rect_cursor,
- StyleSetRequest::Display(if state.focused {
- taffy::Display::Flex
- } else {
- taffy::Display::None
- }),
- );
+ common.alterables.set_widget_visible(data.id_rect_cursor, state.focused);
state.first_refresh = false;
diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs
index f51f5021..56a8ae9b 100644
--- a/wgui/src/components/slider.rs
+++ b/wgui/src/components/slider.rs
@@ -346,8 +346,8 @@ impl State {
}
}
-const BODY_COLOR: drawing::Color = drawing::Color::new(0.6, 0.65, 0.7, 0.2);
-const BODY_BORDER_COLOR: drawing::Color = drawing::Color::new(0.4, 0.45, 0.5, 1.0);
+const BODY_COLOR: drawing::Color = drawing::Color::new(0.6, 0.65, 0.7, 0.1);
+const BODY_BORDER_COLOR: drawing::Color = drawing::Color::new(0.4, 0.45, 0.5, 0.6);
const HANDLE_BORDER_COLOR: drawing::Color = drawing::Color::new(0.85, 0.85, 0.85, 1.0);
const HANDLE_BORDER_COLOR_HOVERED: drawing::Color = drawing::Color::new(0.0, 0.0, 0.0, 1.0);
const HANDLE_COLOR: drawing::Color = drawing::Color::new(1.0, 1.0, 1.0, 1.0);
diff --git a/wgui/src/event.rs b/wgui/src/event.rs
index 5e09650e..ea6e38a9 100644
--- a/wgui/src/event.rs
+++ b/wgui/src/event.rs
@@ -138,6 +138,17 @@ impl EventAlterables {
self.style_set_requests.push((widget_id, request));
}
+ pub fn set_widget_visible(&mut self, widget_id: WidgetID, visible: bool) {
+ self.style_set_requests.push((
+ widget_id,
+ StyleSetRequest::Display(if visible {
+ taffy::Display::Flex
+ } else {
+ taffy::Display::None
+ }),
+ ));
+ }
+
pub fn mark_dirty(&mut self, widget_id: WidgetID) {
self.dirty_widgets.push(widget_id);
}
diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs
index 2e202007..45870def 100644
--- a/wgui/src/layout.rs
+++ b/wgui/src/layout.rs
@@ -11,7 +11,7 @@ use crate::{
drawing::{
self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack,
},
- event::{self, CallbackDataCommon, Event, EventAlterables},
+ event::{self, CallbackDataCommon, Event, EventAlterables, StyleSetRequest},
globals::WguiGlobals,
sound::WguiSoundType,
task::Tasks,
@@ -149,6 +149,7 @@ pub type LayoutDispatchFunc = Box anyhow:
pub enum LayoutTask {
RemoveWidget(WidgetID),
SetWidgetStyle(WidgetID, event::StyleSetRequest),
+ SetWidgetVisible(WidgetID, bool), // if true, sets Display to Flex; None, otherwise
ModifyLayoutState(LayoutModifyStateFunc),
PlaySound(WguiSoundType),
Dispatch(LayoutDispatchFunc),
@@ -746,6 +747,16 @@ impl Layout {
LayoutTask::SetWidgetStyle(widget_id, style_request) => {
self.set_style_request(widget_id, &style_request);
}
+ LayoutTask::SetWidgetVisible(widget_id, visible) => {
+ self.set_style_request(
+ widget_id,
+ &StyleSetRequest::Display(if visible {
+ taffy::Display::Flex
+ } else {
+ taffy::Display::None
+ }),
+ );
+ }
LayoutTask::SetFocus(weak) => {
if let Some(c) = weak.upgrade() {
self.set_focus(Some(&components::Component(c)))?;
diff --git a/wgui/src/parser/widget_label.rs b/wgui/src/parser/widget_label.rs
index 4ad72e19..4652c72b 100644
--- a/wgui/src/parser/widget_label.rs
+++ b/wgui/src/parser/widget_label.rs
@@ -36,10 +36,8 @@ pub fn parse_widget_label<'a>(
params.content = Translation::from_raw_text(value);
}
}
- "translation" => {
- if !value.is_empty() {
- params.content = Translation::from_translation_key(value);
- }
+ "translation" if !value.is_empty() => {
+ params.content = Translation::from_translation_key(value);
}
_ => {}
}
diff --git a/wlx-capture/src/frame.rs b/wlx-capture/src/frame.rs
index c0c7fc18..c1c9c7b9 100644
--- a/wlx-capture/src/frame.rs
+++ b/wlx-capture/src/frame.rs
@@ -16,7 +16,7 @@ pub enum WlxFrame {
Dmabuf(DmabufFrame),
MemFd(MemFdFrame),
MemPtr(MemPtrFrame),
- Implicit,
+ Implicit(Transform),
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
diff --git a/wlx-capture/src/wlr_screencopy.rs b/wlx-capture/src/wlr_screencopy.rs
index 4f694a68..8de78e3a 100644
--- a/wlx-capture/src/wlr_screencopy.rs
+++ b/wlx-capture/src/wlr_screencopy.rs
@@ -267,7 +267,8 @@ where
// copy_with_damage seems to not work here
proxy.copy(&wl_buffer);
- frame_buffer = Some((WlxFrame::Implicit, BufData::Dma { wl_buffer }));
+ frame_buffer =
+ Some((WlxFrame::Implicit(transform), BufData::Dma { wl_buffer }));
} else if let Some(ScreenCopyEvent::Buffer {
shm_format,
width,
diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs
index e673bda9..c9813913 100644
--- a/wlx-common/src/config.rs
+++ b/wlx-common/src/config.rs
@@ -165,6 +165,10 @@ const fn def_point3() -> f32 {
0.3
}
+const fn def_zero() -> f32 {
+ 0.0
+}
+
const fn def_osc_port() -> u16 {
9000
}
@@ -273,6 +277,9 @@ pub struct GeneralConfig {
#[serde(default = "def_false")]
pub double_cursor_fix: bool,
+ #[serde(default = "def_true")]
+ pub enable_watch: bool,
+
#[serde(default = "def_false")]
pub sets_on_watch: bool,
@@ -336,6 +343,24 @@ pub struct GeneralConfig {
#[serde(default = "def_false")]
pub space_rotate_unlocked: bool,
+ #[serde(default = "def_false")]
+ pub space_gravity_enabled: bool,
+
+ #[serde(default = "def_one")]
+ pub space_gravity_gravity: f32,
+
+ #[serde(default = "def_one")]
+ pub space_gravity_damping: f32,
+
+ #[serde(default = "def_one")]
+ pub space_gravity_fling_strength: f32,
+
+ #[serde(default = "def_one")]
+ pub space_gravity_ground_friction: f32,
+
+ #[serde(default = "def_zero")]
+ pub space_gravity_floor_height: f32,
+
#[serde(default)]
pub alt_click_down: Vec,