Merge remote-tracking branch 'origin/main' into next-dash-interface

[skip ci]
This commit is contained in:
Aleksander
2025-12-23 17:01:16 +01:00
parent 848674c143
commit 9a606dbce5
63 changed files with 2457 additions and 309 deletions

View File

@@ -5551,19 +5551,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "wayvr_ipc"
version = "0.1.0"
source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=a72587d23f3bb8624d9aeb1f13c0a21e65350f51#a72587d23f3bb8624d9aeb1f13c0a21e65350f51"
dependencies = [
"anyhow",
"bytes",
"log",
"serde",
"serde_json",
"smallvec",
]
[[package]]
name = "web-sys"
version = "0.3.77"
@@ -6206,7 +6193,7 @@ dependencies = [
"uuid",
"wayland-client",
"wayland-egl",
"wayvr_ipc",
"wayvr-ipc",
"wgui",
"winit",
"wlx-capture",

View File

@@ -97,6 +97,7 @@ uuid = { version = "1.19.0", features = ["v4", "fast-rng"], optional = true }
wayland-client = { workspace = true, optional = true }
wayland-egl = { version = "0.32.8", optional = true }
bytes = { version = "1.11.0", optional = true }
wayvr-ipc = { path = "../wayvr-ipc", default-features = false, optional = true }
rust-embed = { workspace = true }
signal-hook = "0.3.18"
################################
@@ -121,5 +122,6 @@ wayvr = [
"dep:wayland-client",
"dep:wayland-egl",
"dep:bytes",
"dep:wayvr-ipc",
]
as-raw-xcb-connection = []

View File

@@ -15,7 +15,7 @@
</template>
<template name="TopButtonDanger">
<Button macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<Button macro="button_style" tooltip="${tooltip}" _long_release="${long_release}" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<sprite width="48" height="48" src="${src}" />
</Button>
</template>
@@ -39,7 +39,7 @@
<rectangle padding="16" gap="8" round="32" color="~color_bg" border="2" border_color="~color_accent" justify_content="center">
<div flex_direction="column" gap="8">
<div flex_direction="row" gap="4">
<TopButton sticky="1" id="top_lock" src="edit/lock_open.svg" tooltip="EDIT_MODE.LOCK_INTERACTION" press="::EditModeToggleLock" />
<TopButton sticky="0" id="top_lock" src="edit/lock_open.svg" tooltip="EDIT_MODE.LOCK_INTERACTION" press="::EditModeToggleLock" />
<TopButton sticky="1" id="top_grab" src="edit/disable-grab.svg" tooltip="EDIT_MODE.DISABLE_GRAB" press="::EditModeToggleGrab" />
<TopButton sticky="0" id="top_pos" src="edit/anchor.svg" tooltip="EDIT_MODE.POSITIONING" press="::EditModeTab pos" />
<TopButton sticky="0" id="top_alpha" src="edit/fade.svg" tooltip="EDIT_MODE.OPACITY" press="::EditModeTab alpha" />
@@ -48,7 +48,7 @@
<TopButton sticky="0" id="top_mouse" src="edit/normal.svg" tooltip="EDIT_MODE.MOUSE.TITLE" press="::EditModeTab mouse" />
<!-- TopButton sticky="0" id="top_move" src="edit/move-all.svg" tooltip="EDIT_MODE.MOVE_PRESS_AND_DRAG" / -->
<!-- TopButton sticky="0" id="top_resize" src="edit/resize.svg" tooltip="EDIT_MODE.RESIZE_PRESS_AND_DRAG" / -->
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" press="::EditModeDeletePress" release="::EditModeDeleteRelease" />
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" long_release="::EditModeDelete" />
<div width="8" height="100%" />
<TopButtonFaded src="watch/edit.svg" tooltip="EDIT_MODE.LEAVE" press="::EditToggle" />
</div>

View File

@@ -15,7 +15,7 @@
<template name="KeySpecial">
<div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect">
<sprite width="32" height="32" src="keyboard/${text}.svg" />
<sprite color="~color_text" width="32" height="32" src="keyboard/${text}.svg" />
</rectangle>
</div>
</template>

View File

@@ -34,8 +34,8 @@ All overlays are listed on bottom row.
</template>
<template name="Overlay">
<Button macro="button_style" id="overlay_${idx}" sticky="1"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::SingleSetOverlayToggle ${idx}"
<Button macro="button_style" id="overlay_${idx}"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::SingleSetOverlayToggle ${idx}" _long_release="::SingleSetOverlayReset ${idx}"
align_items="center"
height="40">
<sprite id="overlay_${idx}_sprite" src_builtin="${src}" width="32" height="32" />
@@ -111,13 +111,13 @@ All overlays are listed on bottom row.
<!-- Bottom buttons -->
<div id="toolbox-condensed" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<Button id="btn_dashboard" height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" sticky="1" >
<Button id="btn_dashboard" height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" >
<sprite color="~set_color" width="32" height="32" src="watch/wayvr_dashboard_mono.svg" />
</Button>
<Button id="btn_edit_mode" height="40" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="top">
<sprite color="~set_color" width="32" height="32" src="watch/edit.svg" />
</Button>
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" sticky="1" >
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" >
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
</Button>
<!-- Src here may be changed, but maintain `OverlayCategory` order: Panel, Screen, Mirror, WayVR -->

View File

@@ -30,7 +30,7 @@
</template>
<template name="Overlay">
<Button macro="button_style" id="overlay_${idx}" sticky="1"
<Button macro="button_style" id="overlay_${idx}"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::EditModeOverlayToggle ${idx}"
align_items="center"
height="40">
@@ -120,7 +120,7 @@
</div>
<div flex_direction="column" align_items="center" justify_content="center">
<div id="toolbox" gap="8" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" sticky="1" >
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" >
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
<label translation="EDIT_MODE.KEYBOARD" size="18" />
</Button>
@@ -136,11 +136,11 @@
<!-- Bottom buttons -->
<div flex_direction="row" gap="4">
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" sticky="1" >
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" >
<sprite color="~set_color" width="40" height="40" src="watch/wayvr_dashboard_mono.svg" />
</Button>
<div id="edit_delete" display="none">
<Button macro="button_style" _press="::EditModeDeleteDown" _release="::EditModeDeleteUp" tooltip="WATCH.LONG_PRESS_TO_DELETE_SET" tooltip_side="top" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<Button macro="button_style" _long_release="::EditModeDeleteSet" tooltip="WATCH.LONG_PRESS_TO_DELETE_SET" tooltip_side="top" border_color="~color_danger_translucent" color="~color_danger_5" color2="~color_danger_1">
<sprite color="~set_color" width="40" height="40" src="edit/delete.svg" />
</Button>
</div>

View File

@@ -45,7 +45,43 @@
"SPLIT_TOP_BOTTOM": "OBEN→UNTEN",
"SPLIT_BOTTOM_TOP": "UNTEN→OBEN"
},
"STEREO_3D_MODE_": {}
"STEREO_3D_MODE_": {},
"ALIGN_TO_HMD": "An HMD ausrichten",
"MOUSE": {
"TITLE": "Mauskorrekturen",
"WRONG_SCREEN_SELECTION_HELP": "Wenn der Cursor auf einem völlig anderen Bildschirm verschwindet,\nwurden die Bildschirme wahrscheinlich falsch ausgewählt. Siehe Lesezeichen.",
"NORMAL": "Normal",
"FLIPPED": "Umgedreht",
"FLIP90": "Um 90° gedreht",
"FLIP180": "Um 180° gedreht",
"FLIP270": "Um 270° gedreht",
"ROTATE90": "Um 90° gedreht",
"ROTATE180": "Um 180° gedreht",
"ROTATE270": "Um 270° gedreht"
}
},
"DISABLED": "Deaktiviert"
}
"DISABLED": "Deaktiviert",
"DEFAULT": "Standard",
"GRAB": {
"ADJUST_DISTANCE": "Entfernung anpassen",
"ADJUST_SIZE": "Größe anpassen",
"UNRESTRICTED_MOVEMENT": "Unbeschränkte Bewegung",
"GRABBING_WATCH": "Um die Hände zu vertauschen, bewegen Sie die Uhr vor sich und greifen Sie sie mit der anderen Hand.",
"GRABBING_STATIC": "Dieses Overlay ist statisch und bleibt an Ort und Stelle, ignoriert die Neu-Zentrierung.",
"GRABBING_ANCHORED": "Festgelegte Overlays bewegen sich alle zusammen. Trennen Sie ein einzelnes Fenster, indem Sie es mit der anderen Hand greifen, während Sie den Anker weiterhin greifen.",
"GRABBING_ANCHORED_EDIT": "Diese Overlay-Darstellung bleibt an der Mittelmarkierung verankert.",
"GRABBING_FLOATING": "Diese Overlay-Darstellung ist frei schwebend und bleibt an Ort und Stelle, es sei denn, sie wird neu zentriert.",
"GRABBING_FOLLOW": "Dieses Overlay wird dem Gerät folgen, an das es angehängt ist."
},
"TOAST": {
"DEFAULT_TITLE": "Benachrichtigung",
"ERROR": "Fehler",
"CANNOT_REMOVE_SET": "Satz kann nicht entfernt werden!",
"NO_SET_SELECTED": "Kein Set ausgewählt.",
"LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.",
"EMPTY_SET": "Leeres Set!",
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
"FIXING_FLOOR": "Boden wird in 5 Sekunden fixiert...",
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!"
}
}

View File

@@ -39,12 +39,12 @@
"WRONG_SCREEN_SELECTION_HELP": "If the cursor moves on a completely different screen,\nthe screens were likely selected wrong. See readme.",
"NORMAL": "Normal",
"ROTATE90": "Rotated 90°",
"ROTATE180": "Rotated 170°",
"ROTATE180": "Rotated 180°",
"ROTATE270": "Rotated 270°",
"FLIPPED": "Flipped",
"ROTATE90": "Flipped 90°",
"ROTATE180": "Flipped 170°",
"ROTATE270": "Flipped 270°"
"FLIP90": "Flipped 90°",
"FLIP180": "Flipped 180°",
"FLIP270": "Flipped 270°"
}
},
"GRAB": {
@@ -58,6 +58,17 @@
"GRABBING_FLOATING": "This overlay is Floating and will stay in place, unless recentered.",
"GRABBING_FOLLOW": "This overlay will follow the device it is attached to."
},
"TOAST": {
"DEFAULT_TITLE": "Notification",
"ERROR": "Error",
"CANNOT_REMOVE_SET": "Cannot remove set!",
"NO_SET_SELECTED": "No set is selected.",
"LAST_EXISTING_SET": "This is the last existing set.",
"EMPTY_SET": "Empty set!",
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!",
"FIXING_FLOOR": "Fixing floor in 5 seconds...",
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
},
"WATCH": {
"ADD_NEW_SET": "Add a new set",
"CLEANUP_MIRRORS": "Remove mirrors that are\nnot currently visible",

View File

@@ -45,7 +45,43 @@
"SPLIT_TOP_BOTTOM": "ARRIBA→ABAJO",
"SPLIT_BOTTOM_TOP": "ABAJO→ARRIBA"
},
"STEREO_3D_MODE_": {}
"STEREO_3D_MODE_": {},
"ALIGN_TO_HMD": "Alinear al HMD",
"MOUSE": {
"TITLE": "Correcciones del ratón",
"WRONG_SCREEN_SELECTION_HELP": "Si el cursor se mueve en una pantalla completamente diferente,\nes probable que las pantallas se hayan seleccionado incorrectamente. Consulta el archivo readme.",
"NORMAL": "Normal",
"FLIPPED": "Invertido",
"FLIP90": "Girado 90°",
"FLIP180": "Invertido 180°",
"FLIP270": "Invertido 270°",
"ROTATE90": "Rotado 90°",
"ROTATE180": "Rotado 180°",
"ROTATE270": "Rotado 270°"
}
},
"DISABLED": "Deshabilitado"
"DISABLED": "Deshabilitado",
"DEFAULT": "Por defecto",
"GRAB": {
"ADJUST_DISTANCE": "Ajustar distancia",
"ADJUST_SIZE": "Ajustar tamaño",
"UNRESTRICTED_MOVEMENT": "Movimiento sin restricciones",
"GRABBING_WATCH": "Para intercambiar las manos, mueve el reloj al frente y agrárralo con la otra mano.",
"GRABBING_STATIC": "Esta superposición es estática y permanecerá en su lugar, ignorando el recentrado.",
"GRABBING_ANCHORED": "Las superposiciones ancladas se mueven todas juntas. Separa una ventana individual agarrándola con la otra mano mientras aún sujetas el ancla.",
"GRABBING_ANCHORED_EDIT": "Esta superposición permanecerá anclada al marcador central.",
"GRABBING_FLOATING": "Esta superposición es flotante y permanecerá en su lugar a menos que se recentre.",
"GRABBING_FOLLOW": "Esta superposición seguirá el dispositivo al que está adjunta."
},
"TOAST": {
"DEFAULT_TITLE": "Notificación",
"ERROR": "Error",
"CANNOT_REMOVE_SET": "¡No se puede eliminar el conjunto!",
"NO_SET_SELECTED": "No se ha seleccionado ningún conjunto.",
"LAST_EXISTING_SET": "Este es el último conjunto existente.",
"EMPTY_SET": "¡Conjunto vacío!",
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
"FIXING_FLOOR": "Fijando el suelo en 5 segundos...",
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!"
}
}

View File

@@ -16,28 +16,27 @@
"LONG_PRESS_TO_DELETE_SET": "長押しでセットを削除"
},
"EDIT_MODE": {
"ADJUST_CURVATURE": "曲率調整する",
"ALPHA_BLEND_MODE": "アルファブレンドモード",
"BLENDING_ADDITIVE": "加算",
"ADJUST_CURVATURE": "曲率調整",
"ALPHA_BLEND_MODE": "アルファブレンド",
"BLENDING_ADDITIVE": "加算モード",
"CURVATURE": "曲率",
"DELETE": "削除",
"HINT_POINT_WINDOW": "ウィンドウをタップしてそのパラメータを変更ます。\n完了したら、右側のボタンで編集モードを終了してください。",
"LEAVE": "編集モードを終了",
"LOCK_INTERACTION": "インタラクションをロック",
"HINT_POINT_WINDOW": "ウィンドウをタップしてそのパラメータを変更できます。\n右側のボタンで編集モードを終了できます。",
"LEAVE": "編集モードを終了する",
"LOCK_INTERACTION": "インタラクションを無効化",
"DISABLE_GRAB": "グラブを無効化",
"MOVE_PRESS_AND_DRAG": "移動(押してドラッグ)",
"OPACITY": "不透明度",
"POSITIONING": "位置調整",
"POSITIONING": "位置付け",
"RESIZE_PRESS_AND_DRAG": "サイズ変更(押してドラッグ)",
"POS_STATIC": "固定:その場に留まり、再センタリングされません。",
"POS_ANCHORED": "アンカー:他のセット要素と一緒に移動します。デフォルト。",
"POS_FLOATING": "フローティング:その場に留ま、表示されるときに再センタリングされます。",
"POS_STATIC": "固定:置かれた場所に留まり、再センタリングされません。",
"POS_ANCHORED": "アンカー:セット内の他のウィンドウと共に移動します。デフォルト。",
"POS_FLOATING": "フローティング:置かれた場所に留まるが、表示されるに再センタリングされます。",
"POS_HMD": "HMD に追従する。",
"POS_HAND_L": "左手に追従する。",
"POS_HAND_R": "右手に追従する。",
"INTERPOLATION": "補間",
"INTERPOLATION": "スムージング",
"KEYBOARD": "キーボード",
"STEREO_3D_MODE_A": {},
"STEREO_3D_MODE": {
"TITLE": "ステレオ3Dモード",
"SPLIT_LEFT_RIGHT": "左→右",
@@ -45,7 +44,42 @@
"SPLIT_TOP_BOTTOM": "上→下",
"SPLIT_BOTTOM_TOP": "下→上"
},
"STEREO_3D_MODE_": {}
"ALIGN_TO_HMD": "常にHMDの方を向く",
"MOUSE": {
"TITLE": "マウスの修正",
"WRONG_SCREEN_SELECTION_HELP": "カーソルが全く別のスクリーンで動く場合、\nスクリーンが正しく選択されていない可能性があります。\nマニュアルをご確認ください。",
"NORMAL": "通常",
"FLIPPED": "ミラー",
"FLIP90": "ミラー 90°",
"FLIP180": "ミラー 180°",
"FLIP270": "ミラー 270°",
"ROTATE90": "回転 90°",
"ROTATE180": "回転 180°",
"ROTATE270": "回転 270°"
}
},
"DISABLED": "無効"
"DISABLED": "無効",
"DEFAULT": "デフォルト",
"GRAB": {
"ADJUST_DISTANCE": "距離を調整",
"ADJUST_SIZE": "サイズ調整",
"UNRESTRICTED_MOVEMENT": "配置制限なし",
"GRABBING_WATCH": "手を入れ替えるにはウォッチを前に移動させて、もう一方の手で掴んでください。",
"GRABBING_STATIC": "このオーバーレイは固定であり、リセンターを無視してその場に留まります。",
"GRABBING_ANCHORED": "アンカーされたオーバーレイはすべて一緒に移動します。もう一方の手で掴んでアンカーを掴んだまま、単一のウィンドウを分離できます。",
"GRABBING_ANCHORED_EDIT": "このオーバーレイは、センター・マーカーに固定されます。",
"GRABBING_FLOATING": "このオーバーレイはフローティングのため、リセンターまではその場所に留まります。",
"GRABBING_FOLLOW": "このオーバーレイは、接続されているデバイスに追従します。"
},
"TOAST": {
"DEFAULT_TITLE": "通知",
"ERROR": "エラー",
"CANNOT_REMOVE_SET": "セットを削除できません!",
"NO_SET_SELECTED": "セットが選択されていません。",
"LAST_EXISTING_SET": "これが最後の設定です。",
"EMPTY_SET": "空のセットです!",
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
"FIXING_FLOOR": "5秒後にフロアを固定します...",
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!"
}
}

View File

@@ -43,7 +43,43 @@
"SPLIT_RIGHT_LEFT": "PRAWO→LEWO",
"SPLIT_TOP_BOTTOM": "GÓRA→DÓŁ",
"TITLE": "Tryb stereoskopowy 3D"
},
"ALIGN_TO_HMD": "Dopasuj do HMD",
"MOUSE": {
"TITLE": "Naprawa myszy",
"WRONG_SCREEN_SELECTION_HELP": "Jeśli kursor porusza się na zupełnie innym ekranie,\nekrany zostały prawdopodobnie nieprawidłowo wybrane. Zobacz dokument README.",
"NORMAL": "Normalny",
"ROTATE90": "Obrócony o 90°",
"ROTATE180": "Obrócony o 180°",
"ROTATE270": "Obrócony o 270°",
"FLIPPED": "Odwrócony",
"FLIP90": "Odwrócony o 90°",
"FLIP180": "Odwrócony o 180°",
"FLIP270": "Odwrócony o 270°"
}
},
"DISABLED": "Wyłączone"
"DISABLED": "Wyłączone",
"DEFAULT": "Domyślny",
"GRAB": {
"ADJUST_DISTANCE": "Dostosuj odległość",
"ADJUST_SIZE": "Dostosuj rozmiar",
"UNRESTRICTED_MOVEMENT": "Nieograniczony ruch",
"GRABBING_WATCH": "Aby zamienić ręce, przenieś zegarek na przód i chwyć go drugą ręką.",
"GRABBING_STATIC": "Ta nakładka jest statyczna i pozostanie na swoim miejscu, ignorując centrowanie.",
"GRABBING_ANCHORED": "Przymocowane nakładki poruszają się razem. Aby oddzielić pojedyncze okno, chwyć je drugą ręką, podczas gdy nadal trzymasz kotwicę.",
"GRABBING_ANCHORED_EDIT": "Ta nakładka pozostanie zakotwiczona do znacznika centralnego.",
"GRABBING_FLOATING": "Ta nakładka jest zawieszony i pozostanie na miejscu, chyba że zostanie wycentrowany.",
"GRABBING_FOLLOW": "Ta nakładka będzie śledzić urządzenie, do którego jest dołączona."
},
"TOAST": {
"DEFAULT_TITLE": "Powiadomienie",
"ERROR": "Błąd",
"CANNOT_REMOVE_SET": "Nie można usunąć zestawu!",
"NO_SET_SELECTED": "Nie wybrano żadnego zestawu.",
"LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.",
"EMPTY_SET": "Pusty zestaw!",
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
"FIXING_FLOOR": "Naprawianie podłogi za 5 sekund...",
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!"
}
}

View File

@@ -1,4 +1,4 @@
use glam::{Affine3A, Quat, Vec3, Vec3A};
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3a};
use ovr_overlay::{
chaperone_setup::ChaperoneSetupManager,
compositor::CompositorManager,
@@ -58,12 +58,12 @@ impl PlayspaceMover {
&mut self,
chaperone_mgr: &mut ChaperoneSetupManager,
overlays: &mut OverlayWindowManager<OpenVrOverlayData>,
state: &AppState,
app: &AppState,
) {
let universe = self.universe.clone();
if let Some(data) = self.rotate.as_mut() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_rotate {
self.rotate = None;
log::info!("End space rotate");
@@ -71,7 +71,7 @@ impl PlayspaceMover {
}
let new_hand =
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].raw_pose));
Quat::from_affine3(&(data.pose * app.input_state.pointers[data.hand].raw_pose));
let dq = new_hand * data.hand_pose.conjugate();
let rel_y = f32::atan2(
@@ -80,8 +80,8 @@ impl PlayspaceMover {
);
let mut space_transform = Affine3A::from_rotation_y(rel_y);
let offset = (space_transform.transform_vector3a(state.input_state.hmd.translation)
- state.input_state.hmd.translation)
let offset = (space_transform.transform_vector3a(app.input_state.hmd.translation)
- app.input_state.hmd.translation)
* -1.0;
let mut overlay_transform = Affine3A::from_rotation_y(-rel_y);
@@ -97,7 +97,7 @@ impl PlayspaceMover {
set_working_copy(&universe, chaperone_mgr, &data.pose);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_rotate {
let Some(mat) = get_working_copy(&universe, chaperone_mgr) else {
log::warn!("Can't space rotate - failed to get zero pose");
@@ -117,7 +117,7 @@ impl PlayspaceMover {
}
if let Some(data) = self.drag.as_mut() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_drag {
self.drag = None;
log::info!("End space drag");
@@ -126,9 +126,13 @@ impl PlayspaceMover {
let new_hand = data
.pose
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos =
(new_hand - data.hand_pose) * state.session.config.space_drag_multiplier;
.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;
if relative_pos.length_squared() > 1000.0 {
log::warn!("Space drag too fast, ignoring");
@@ -156,7 +160,7 @@ impl PlayspaceMover {
set_working_copy(&universe, chaperone_mgr, &data.pose);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_drag {
let Some(mat) = get_working_copy(&universe, chaperone_mgr) else {
log::warn!("Can't space drag - failed to get zero pose");

View File

@@ -11,7 +11,6 @@ use std::{
};
use wgui::gfx::{
WGfx,
cmd::WGfxClearMode,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
@@ -49,8 +48,6 @@ static COLORS: [[f32; 6]; 5] = {
pub(super) struct LinePool {
lines: IdMap<usize, LineContainer>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
pass: WGfxPass<Vert2Uv>,
buf_color: Subbuffer<[f32]>,
}
impl LinePool {
@@ -61,13 +58,22 @@ impl LinePool {
WPipelineCreateInfo::new(app.gfx.surface_format),
)?;
Ok(Self {
lines: IdMap::new(),
pipeline,
})
}
pub(super) fn allocate(&mut self, xr: &XrState, app: &AppState) -> anyhow::Result<usize> {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let buf_color = app
.gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 6)?;
let set0 = pipeline.buffer(0, buf_color.clone())?;
let set0 = self.pipeline.buffer(0, buf_color.clone())?;
let pass = pipeline.create_pass(
let pass = self.pipeline.create_pass(
[1.0, 1.0],
app.gfx_extras.quad_verts.clone(),
0..4,
@@ -76,22 +82,13 @@ impl LinePool {
&Default::default(),
)?;
Ok(Self {
lines: IdMap::new(),
pipeline,
pass,
buf_color,
})
}
pub(super) fn allocate(&mut self, xr: &XrState, gfx: Arc<WGfx>) -> anyhow::Result<usize> {
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
let srd = create_swapchain(xr, gfx, [1, 1, 1], SwapchainOpts::new())?;
let srd = create_swapchain(xr, app.gfx.clone(), [1, 1, 1], SwapchainOpts::new())?;
self.lines.insert(
id,
LineContainer {
swapchain: srd,
buf_color,
pass,
maybe_line: None,
},
);
@@ -165,13 +162,13 @@ impl LinePool {
.next()
.unwrap();
self.buf_color.write()?[0..6].copy_from_slice(&COLORS[inner.color]);
line.buf_color.write()?[0..6].copy_from_slice(&COLORS[inner.color]);
let mut cmd_buffer = app
.gfx
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
cmd_buffer.begin_rendering(tgt, WGfxClearMode::DontCare)?;
cmd_buffer.run_ref(&self.pass)?;
cmd_buffer.run_ref(&line.pass)?;
cmd_buffer.end_rendering()?;
futures.execute(cmd_buffer.queue.clone(), cmd_buffer.build()?)?;
@@ -217,5 +214,7 @@ pub(super) struct Line {
struct LineContainer {
swapchain: WlxSwapchain,
buf_color: Subbuffer<[f32]>,
pass: WGfxPass<Vert2Uv>,
maybe_line: Option<Line>,
}

View File

@@ -149,8 +149,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
};
let pointer_lines = [
lines.allocate(&xr_state, app.gfx.clone())?,
lines.allocate(&xr_state, app.gfx.clone())?,
lines.allocate(&xr_state, &mut app)?,
lines.allocate(&xr_state, &mut app)?,
];
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
@@ -238,6 +238,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
if !session_running {
std::thread::sleep(Duration::from_millis(100));
log::trace!("session not running: continue");
continue 'main_loop;
}
@@ -387,10 +388,12 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
for o in overlays.values_mut() {
o.data.cur_visible = false;
let Some(alpha) = o.config.active_state.as_ref().map(|x| x.alpha) else {
log::trace!("{}: hidden, skip render", o.config.name);
continue;
};
if !o.data.init {
log::trace!("{}: init", o.config.name);
o.init(&mut app)?;
o.data.init = true;
}
@@ -400,8 +403,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
ShouldRender::Can => (o.data.last_alpha - alpha).abs() > f32::EPSILON,
ShouldRender::Unable => false, //try show old image if exists
};
log::trace!("{}: should_render returned: {should_render}", o.config.name);
if should_render {
log::trace!("{}: render new frame", o.config.name);
let meta = o.config.backend.frame_meta().unwrap(); // want panic
let wsi = o.ensure_swapchain_acquire(&app, &xr_state, meta.extent)?;
let tgt = RenderTarget { views: wsi.views };
@@ -410,7 +415,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
o.data.last_alpha = alpha;
futures.execute_results(rdr.end()?)?;
} else if o.data.swapchain.is_none() {
log::trace!("{}: not showing due to missing swapchain", o.config.name);
continue;
} else {
log::trace!("{}: showing stale frame", o.config.name);
}
o.data.cur_visible = true;
}

View File

@@ -1,4 +1,4 @@
use glam::{Affine3A, Quat, Vec3A};
use glam::{Affine3A, Quat, Vec3A, vec3a};
use libmonado::{Monado, Pose, ReferenceSpaceType};
use crate::{
@@ -60,10 +60,10 @@ impl PlayspaceMover {
pub fn update(
&mut self,
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
state: &AppState,
app: &AppState,
monado: &mut Monado,
) {
for pointer in &state.input_state.pointers {
for pointer in &app.input_state.pointers {
if pointer.now.space_reset {
if !pointer.before.space_reset {
log::info!("Space reset");
@@ -74,7 +74,7 @@ impl PlayspaceMover {
}
if let Some(mut data) = self.rotate.take() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_rotate {
self.last_transform = data.pose;
log::info!("End space rotate");
@@ -82,10 +82,10 @@ impl PlayspaceMover {
}
let new_hand =
Quat::from_affine3(&(data.pose * state.input_state.pointers[data.hand].raw_pose));
Quat::from_affine3(&(data.pose * app.input_state.pointers[data.hand].raw_pose));
let dq = new_hand * data.hand_pose.conjugate();
let mut space_transform = if state.session.config.space_rotate_unlocked {
let mut space_transform = if app.session.config.space_rotate_unlocked {
Affine3A::from_quat(dq)
} else {
let rel_y = f32::atan2(
@@ -95,8 +95,8 @@ impl PlayspaceMover {
Affine3A::from_rotation_y(rel_y)
};
let offset = (space_transform.transform_vector3a(state.input_state.hmd.translation)
- state.input_state.hmd.translation)
let offset = (space_transform.transform_vector3a(app.input_state.hmd.translation)
- app.input_state.hmd.translation)
* -1.0;
space_transform.translation = offset;
@@ -107,7 +107,7 @@ impl PlayspaceMover {
apply_offset(data.pose, monado);
self.rotate = Some(data);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_rotate {
let hand_pose = Quat::from_affine3(&(self.last_transform * pointer.raw_pose));
self.rotate = Some(MoverData {
@@ -123,7 +123,7 @@ impl PlayspaceMover {
}
if let Some(mut data) = self.drag.take() {
let pointer = &state.input_state.pointers[data.hand];
let pointer = &app.input_state.pointers[data.hand];
if !pointer.now.space_drag {
self.last_transform = data.pose;
log::info!("End space drag");
@@ -132,9 +132,13 @@ impl PlayspaceMover {
let new_hand = data
.pose
.transform_point3a(state.input_state.pointers[data.hand].raw_pose.translation);
let relative_pos =
(new_hand - data.hand_pose) * state.session.config.space_drag_multiplier;
.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;
if relative_pos.length_squared() > 1000.0 {
log::warn!("Space drag too fast, ignoring");
@@ -159,7 +163,7 @@ impl PlayspaceMover {
apply_offset(data.pose, monado);
self.drag = Some(data);
} else {
for (i, pointer) in state.input_state.pointers.iter().enumerate() {
for (i, pointer) in app.input_state.pointers.iter().enumerate() {
if pointer.now.space_drag {
let hand_pos = self
.last_transform
@@ -234,7 +238,7 @@ impl PlayspaceMover {
let y1 = input.pointers[0].raw_pose.translation.y;
let y2 = input.pointers[1].raw_pose.translation.y;
let delta = y1.min(y2) - 0.03;
let delta = y1.min(y2) - 0.05;
pose.position.y += delta;

View File

@@ -75,6 +75,7 @@ pub fn dds_to_vk(dds_fmt: ImageFormat) -> anyhow::Result<Format> {
ImageFormat::Rgba8UnormSrgb => Ok(Format::R8G8B8A8_SRGB),
ImageFormat::Rgba16Float => Ok(Format::R16G16B16A16_SFLOAT),
ImageFormat::Rgba32Float => Ok(Format::R32G32B32A32_SFLOAT),
ImageFormat::Bgr8Unorm => Ok(Format::B8G8R8_UNORM),
ImageFormat::Bgra8Unorm => Ok(Format::B8G8R8A8_UNORM),
ImageFormat::Bgra8UnormSrgb => Ok(Format::B8G8R8A8_SRGB),
// DXT1

View File

@@ -81,6 +81,17 @@ Supported events:
<button _press="..." _release="..." />
```
Laser-color-specific variants are also available
- `_press_left` & `_release_left` for blue laser
- `_press_right` & `_release_right` for orange laser
- `_press_middle` & `_release_middle` for purple laser
Release after short/long press (length controlled by config `long_press_duration`)
- `_short_release` & `_long_release` for any laser
- `_short_release_left` & `_long_release_left` for blue laser
- `_short_release_right` & `_long_release_right` for orange laser
- `_short_release_middle` & `_long_release_middle` for purple laser
#### Supported button actions
##### `::ShellExec <command> [args ..]`

View File

@@ -10,7 +10,9 @@ use std::{
use anyhow::Context;
use wgui::{
components::button::ComponentButton,
event::{self, EventCallback, EventListenerKind},
event::{
self, CallbackData, CallbackMetadata, EventCallback, EventListenerKind, MouseButtonIndex,
},
i18n::Translation,
layout::Layout,
parser::CustomAttribsInfoOwned,
@@ -31,18 +33,159 @@ use crate::{
#[cfg(feature = "wayvr")]
use crate::backend::wayvr::WayVRAction;
pub const BUTTON_EVENTS: [(&str, EventListenerKind); 2] = [
("_press", EventListenerKind::MousePress),
("_release", EventListenerKind::MouseRelease),
pub const BUTTON_EVENTS: [(
&str,
EventListenerKind,
fn(&mut CallbackData) -> bool,
fn(&ComponentButton, &AppState) -> bool,
); 16] = [
(
"_press",
EventListenerKind::MousePress,
button_any,
short_duration,
),
(
"_release",
EventListenerKind::MouseRelease,
button_any,
ignore_duration,
),
(
"_press_left",
EventListenerKind::MousePress,
button_left,
ignore_duration,
),
(
"_release_left",
EventListenerKind::MouseRelease,
button_left,
ignore_duration,
),
(
"_press_right",
EventListenerKind::MousePress,
button_right,
ignore_duration,
),
(
"_release_right",
EventListenerKind::MouseRelease,
button_right,
ignore_duration,
),
(
"_press_middle",
EventListenerKind::MousePress,
button_middle,
ignore_duration,
),
(
"_release_middle",
EventListenerKind::MouseRelease,
button_middle,
ignore_duration,
),
(
"_short_release",
EventListenerKind::MouseRelease,
button_any,
short_duration,
),
(
"_short_release_left",
EventListenerKind::MouseRelease,
button_left,
short_duration,
),
(
"_short_release_right",
EventListenerKind::MouseRelease,
button_right,
short_duration,
),
(
"_short_release_middle",
EventListenerKind::MouseRelease,
button_middle,
short_duration,
),
(
"_long_release",
EventListenerKind::MouseRelease,
button_any,
long_duration,
),
(
"_long_release_left",
EventListenerKind::MouseRelease,
button_left,
long_duration,
),
(
"_long_release_right",
EventListenerKind::MouseRelease,
button_right,
long_duration,
),
(
"_long_release_middle",
EventListenerKind::MouseRelease,
button_middle,
long_duration,
),
];
fn button_any(_: &mut CallbackData) -> bool {
true
}
fn button_left(data: &mut CallbackData) -> bool {
if let CallbackMetadata::MouseButton(b) = data.metadata
&& let MouseButtonIndex::Left = b.index
{
true
} else {
false
}
}
fn button_right(data: &mut CallbackData) -> bool {
if let CallbackMetadata::MouseButton(b) = data.metadata
&& let MouseButtonIndex::Right = b.index
{
true
} else {
false
}
}
fn button_middle(data: &mut CallbackData) -> bool {
if let CallbackMetadata::MouseButton(b) = data.metadata
&& let MouseButtonIndex::Middle = b.index
{
true
} else {
false
}
}
fn ignore_duration(_btn: &ComponentButton, _app: &AppState) -> bool {
true
}
fn long_duration(btn: &ComponentButton, app: &AppState) -> bool {
btn.get_time_since_last_pressed().as_secs_f32() > app.session.config.long_press_duration
}
fn short_duration(btn: &ComponentButton, app: &AppState) -> bool {
btn.get_time_since_last_pressed().as_secs_f32() < app.session.config.long_press_duration
}
pub(super) fn setup_custom_button<S: 'static>(
layout: &mut Layout,
attribs: &CustomAttribsInfoOwned,
_app: &AppState,
button: Rc<ComponentButton>,
) {
for (name, kind) in &BUTTON_EVENTS {
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
@@ -52,9 +195,15 @@ pub(super) fn setup_custom_button<S: 'static>(
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, S> = match command {
#[cfg(feature = "wayvr")]
"::DashToggle" => Box::new(move |_common, _data, app, _| {
"::DashToggle" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::WayVR(WayVRAction::ToggleDashboard));
Ok(EventResult::Consumed)
@@ -65,7 +214,11 @@ pub(super) fn setup_custom_button<S: 'static>(
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleSet(set_idx)));
Ok(EventResult::Consumed)
@@ -77,7 +230,11 @@ pub(super) fn setup_custom_button<S: 'static>(
return;
};
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(arg.clone()),
Box::new(move |app, owc| {
@@ -91,54 +248,84 @@ pub(super) fn setup_custom_button<S: 'static>(
Ok(EventResult::Consumed)
})
}
"::EditToggle" => Box::new(move |_common, _data, app, _| {
"::EditToggle" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode));
Ok(EventResult::Consumed)
}),
#[cfg(feature = "wayland")]
"::NewMirror" => Box::new(move |_common, _data, app, _| {
let name = crate::overlays::mirror::new_mirror_name();
"::NewMirror" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let name = crate::overlays::screen::mirror::new_mirror_name();
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create(
OverlaySelector::Name(name.clone()),
Box::new(move |app| {
Some(crate::overlays::mirror::new_mirror(name, &app.session))
Some(crate::overlays::screen::mirror::new_mirror(
name,
&app.session,
))
}),
)));
Ok(EventResult::Consumed)
}),
"::CleanupMirrors" => Box::new(move |_common, _data, app, _| {
"::CleanupMirrors" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors));
Ok(EventResult::Consumed)
}),
"::PlayspaceReset" => Box::new(move |_common, _data, app, _| {
"::PlayspaceReset" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Playspace(PlayspaceTask::Reset));
Ok(EventResult::Consumed)
}),
"::PlayspaceRecenter" => Box::new(move |_common, _data, app, _| {
"::PlayspaceRecenter" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Playspace(PlayspaceTask::Recenter));
Ok(EventResult::Consumed)
}),
"::PlayspaceFixFloor" => Box::new(move |_common, _data, app, _| {
for i in 0..5 {
Toast::new(
ToastTopic::System,
format!("Fixing floor in {}", 5 - i),
"Touch your controller to the floor!".into(),
)
.with_timeout(1.)
.with_sound(true)
.submit_at(app, Instant::now() + Duration::from_secs(i));
"::PlayspaceFixFloor" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
Toast::new(
ToastTopic::System,
"TOAST.FIXING_FLOOR".into(),
"TOAST.ONE_CONTROLLER_ON_FLOOR".into(),
)
.with_timeout(5.)
.with_sound(true)
.submit(app);
app.tasks.enqueue_at(
TaskType::Playspace(PlayspaceTask::FixFloor),
Instant::now() + Duration::from_secs(5),
);
Ok(EventResult::Consumed)
}),
"::Shutdown" => Box::new(move |_common, _data, _app, _| {
"::Shutdown" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
RUNNING.store(false, Ordering::Relaxed);
Ok(EventResult::Consumed)
}),
@@ -155,7 +342,11 @@ pub(super) fn setup_custom_button<S: 'static>(
log::error!("{command} has bad/missing arguments");
return;
};
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.hid_provider.send_key_routed(key, down);
Ok(EventResult::Consumed)
})
@@ -182,7 +373,11 @@ pub(super) fn setup_custom_button<S: 'static>(
}),
);
Box::new(move |_common, _data, _app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let _ = shell_on_action(&state).inspect_err(|e| log::error!("{e:?}"));
Ok(EventResult::Consumed)
})
@@ -206,7 +401,11 @@ pub(super) fn setup_custom_button<S: 'static>(
osc_args.push(osc_arg);
}
Box::new(move |_common, _data, app, _| {
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(sender) = app.osc_sender.as_mut() else {
log::error!("OscSend: sender is not available.");
return Ok(EventResult::Consumed);

View File

@@ -70,7 +70,8 @@ pub type OnCustomIdFunc<S> = Box<
) -> anyhow::Result<()>,
>;
pub type OnCustomAttribFunc = Box<dyn Fn(&mut Layout, &CustomAttribsInfoOwned, &AppState)>;
pub type OnCustomAttribFunc =
Box<dyn Fn(&mut Layout, &ParserState, &CustomAttribsInfoOwned, &AppState)>;
pub struct NewGuiPanelParams<S> {
pub on_custom_id: Option<OnCustomIdFunc<S>>, // used only in `new_from_template`
@@ -155,7 +156,7 @@ impl<S: 'static> GuiPanel<S> {
}
if let Some(on_custom_attrib) = &params.on_custom_attrib {
on_custom_attrib(&mut layout, elem, app);
on_custom_attrib(&mut layout, &parser_state, elem, app);
}
}

View File

@@ -1,7 +1,10 @@
use std::rc::Rc;
use anyhow::Context;
use glam::FloatExt;
use wgui::{
animation::{Animation, AnimationEasing},
components::button::ComponentButton,
event::CallbackDataCommon,
layout::WidgetID,
parser::Fetchable,
@@ -15,6 +18,7 @@ pub(super) struct InteractLockHandler {
id: WidgetID,
color: wgui::drawing::Color,
interactable: bool,
button: Option<Rc<ComponentButton>>,
}
impl InteractLockHandler {
@@ -27,10 +31,13 @@ impl InteractLockHandler {
.get_as::<WidgetRectangle>(id)
.context("Element with id=\"shadow\" must be a <rectangle>")?;
let button = panel.parser_state.fetch_component_as("top_lock")?;
Ok(Self {
id,
color: shadow_rect.params.color,
interactable: true,
button: Some(button),
})
}
@@ -42,6 +49,10 @@ impl InteractLockHandler {
.get_as::<WidgetRectangle>(self.id)
.unwrap(); // can only fail if set_up_rect has issues
if let Some(button) = self.button.as_ref() {
button.set_sticky_state(common, !interactable);
}
let globals = common.state.globals.get();
if interactable {
set_anim_color(&mut rect, 0.0, self.color, globals.defaults.danger_color);
@@ -54,16 +65,20 @@ impl InteractLockHandler {
&mut self,
common: &mut CallbackDataCommon,
app: &mut AppState,
anim_mult: f32,
) -> Box<ModifyOverlayTask> {
let defaults = app.wgui_globals.get().defaults.clone();
let rect_color = self.color;
self.interactable = !self.interactable;
if let Some(button) = self.button.as_ref() {
button.set_sticky_state(common, !self.interactable);
}
let anim = if self.interactable {
Animation::new(
self.id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
@@ -79,7 +94,7 @@ impl InteractLockHandler {
} else {
Animation::new(
self.id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();

View File

@@ -47,21 +47,9 @@ mod sprite_tab;
mod stereo;
pub mod tab;
pub(super) struct LongPressButtonState {
pub(super) pressed: Instant,
}
impl Default for LongPressButtonState {
fn default() -> Self {
Self {
pressed: Instant::now(),
}
}
}
struct EditModeState {
tasks: Rc<RefCell<TaskContainer>>,
id: Rc<RefCell<OverlayID>>,
delete: LongPressButtonState,
tabs: ButtonPaneTabSwitcher,
lock: InteractLockHandler,
pos: SpriteTabHandler<PosTabState>,
@@ -272,9 +260,6 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
let state = EditModeState {
id: Rc::new(RefCell::new(OverlayID::null())),
tasks: Rc::new(RefCell::new(TaskContainer::new())),
delete: LongPressButtonState {
pressed: Instant::now(),
},
tabs: ButtonPaneTabSwitcher::default(),
lock: InteractLockHandler::default(),
pos: SpriteTabHandler::default(),
@@ -282,8 +267,16 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
mouse: SpriteTabHandler::default(),
};
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
for (name, kind) in &BUTTON_EVENTS {
let anim_mult = app.wgui_globals.defaults().animation_mult;
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) =
parser.fetch_component_from_widget_id_as::<ComponentButton>(attribs.widget_id)
else {
return;
};
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
@@ -293,15 +286,25 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, EditModeState> = match command {
"::EditModeToggleLock" => Box::new(move |common, _data, app, state| {
"::EditModeToggleLock" => Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.lock.toggle(common, app);
let task = state.lock.toggle(common, app, anim_mult);
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
Ok(EventResult::Consumed)
}),
"::EditModeToggleGrab" => Box::new(move |_common, _data, app, state| {
"::EditModeToggleGrab" => Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
sel,
@@ -314,14 +317,22 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}),
"::EditModeTab" => {
let tab_name = args.next().unwrap().to_owned();
Box::new(move |common, _data, _app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
state.tabs.tab_button_clicked(common, &tab_name);
Ok(EventResult::Consumed)
})
}
"::EditModeSetPos" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.pos.button_clicked(common, &key);
app.tasks
@@ -331,7 +342,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}
"::EditModeSetStereo" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.stereo.button_clicked(common, &key);
app.tasks
@@ -341,7 +356,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}
"::EditModeSetMouse" => {
let key = args.next().unwrap().to_owned();
Box::new(move |common, _data, app, state| {
Box::new(move |common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.mouse.button_clicked(common, &key);
app.tasks
@@ -349,15 +368,11 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
Ok(EventResult::Consumed)
})
}
"::EditModeDeletePress" => Box::new(move |_common, _data, _app, state| {
state.delete.pressed = Instant::now();
// TODO: animate to light up button after 2s
Ok(EventResult::Consumed)
}),
"::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| {
if state.delete.pressed.elapsed() < Duration::from_secs(1) {
"::EditModeDelete" => Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(*state.id.borrow()),
Box::new(move |_app, owc| {

View File

@@ -1,7 +1,7 @@
use std::{collections::HashMap, rc::Rc};
use crate::{gui::panel::GuiPanel, state::AppState, subsystem::hid::XkbKeymap};
use glam::{FloatExt, Mat4, Vec2, Vec3, vec2};
use glam::{FloatExt, Mat4, Vec2, vec2, vec3};
use wgui::{
animation::{Animation, AnimationEasing},
assets::AssetPath,
@@ -39,11 +39,13 @@ pub(super) fn create_keyboard_panel(
let globals = app.wgui_globals.clone();
let accent_color = globals.get().defaults.accent_color;
let anim_mult = globals.defaults().animation_mult;
let (background, _) = panel.layout.add_child(
panel.layout.content_root_widget,
WidgetRectangle::create(WidgetRectangleParams {
color: wgui::drawing::Color::new(0., 0., 0., 0.75),
round: WLength::Units(16.0),
color: globals.defaults().bg_color,
round: WLength::Units((16.0 * globals.defaults().rounding_mult).max(0.)),
border: 2.0,
border_color: accent_color,
..Default::default()
@@ -169,6 +171,8 @@ pub(super) fn create_keyboard_panel(
})
};
let width_mul = 1. / my_size_f32;
panel.add_event_listener(
widget_id,
EventListenerKind::MouseEnter,
@@ -176,7 +180,14 @@ pub(super) fn create_keyboard_panel(
let k = key_state.clone();
move |common, data, _app, _state| {
common.alterables.trigger_haptics();
on_enter_anim(k.clone(), common, data, accent_color);
on_enter_anim(
k.clone(),
common,
data,
accent_color,
anim_mult,
width_mul,
);
Ok(EventResult::Pass)
}
}),
@@ -188,7 +199,14 @@ pub(super) fn create_keyboard_panel(
let k = key_state.clone();
move |common, data, _app, _state| {
common.alterables.trigger_haptics();
on_leave_anim(k.clone(), common, data, accent_color);
on_leave_anim(
k.clone(),
common,
data,
accent_color,
anim_mult,
width_mul,
);
Ok(EventResult::Pass)
}
}),
@@ -254,11 +272,14 @@ pub(super) fn create_keyboard_panel(
const BUTTON_HOVER_SCALE: f32 = 0.1;
fn get_anim_transform(pos: f32, widget_size: Vec2) -> Mat4 {
util::centered_matrix(
widget_size,
&Mat4::from_scale(Vec3::splat(BUTTON_HOVER_SCALE.mul_add(pos, 1.0))),
)
fn get_anim_transform(pos: f32, widget_size: Vec2, width_mult: f32) -> Mat4 {
let scale = vec3(
(BUTTON_HOVER_SCALE * width_mult).mul_add(pos, 1.0),
BUTTON_HOVER_SCALE.mul_add(pos, 1.0),
1.0,
);
util::centered_matrix(widget_size, &Mat4::from_scale(scale))
}
fn set_anim_color(
@@ -291,15 +312,18 @@ fn on_enter_anim(
common: &mut event::CallbackDataCommon,
data: &event::CallbackData,
accent_color: drawing::Color,
anim_mult: f32,
width_mult: f32,
) {
common.alterables.animate(Animation::new(
data.widget_id,
10,
(10. * anim_mult) as _,
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(&key_state, rect, data.pos, accent_color);
data.data.transform = get_anim_transform(data.pos, data.widget_boundary.size);
data.data.transform =
get_anim_transform(data.pos, data.widget_boundary.size, width_mult);
common.alterables.mark_redraw();
}),
));
@@ -310,15 +334,18 @@ fn on_leave_anim(
common: &mut event::CallbackDataCommon,
data: &event::CallbackData,
accent_color: drawing::Color,
anim_mult: f32,
width_mult: f32,
) {
common.alterables.animate(Animation::new(
data.widget_id,
15,
(15. * anim_mult) as _,
AnimationEasing::OutQuad,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(&key_state, rect, 1.0 - data.pos, accent_color);
data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_boundary.size);
data.data.transform =
get_anim_transform(1.0 - data.pos, data.widget_boundary.size, width_mult);
common.alterables.mark_redraw();
}),
));

View File

@@ -2,8 +2,6 @@ pub mod anchor;
pub mod custom;
pub mod edit;
pub mod keyboard;
#[cfg(feature = "wayland")]
pub mod mirror;
pub mod screen;
pub mod toast;
pub mod watch;

View File

@@ -215,6 +215,7 @@ impl OverlayBackend for ScreenBackend {
Ok(ShouldRender::Can)
}
} else {
log::trace!("{}: backend ready, but no image received.", self.name);
Ok(ShouldRender::Unable)
}
}

View File

@@ -8,7 +8,10 @@ use std::{
use futures::{Future, FutureExt};
use glam::{Affine2, Affine3A, Quat, Vec3, vec3};
use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen};
use wlx_capture::{
WlxCapture,
pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen},
};
use wlx_common::{
overlays::{BackendAttrib, BackendAttribValue},
windowing::OverlayWindowState,
@@ -19,6 +22,7 @@ use crate::{
input::{HoverResult, PointerHit},
task::{OverlayTask, TaskType},
},
overlays::screen::capture::{MainThreadWlxCapture, new_wlx_capture},
state::{AppSession, AppState},
subsystem::hid::WheelDelta,
windowing::{
@@ -31,7 +35,7 @@ use crate::{
},
};
use super::screen::backend::ScreenBackend;
use super::backend::ScreenBackend;
type PinnedSelectorFuture = core::pin::Pin<
Box<dyn Future<Output = Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError>>>,
>;
@@ -76,13 +80,21 @@ impl OverlayBackend for MirrorBackend {
match maybe_pw_result {
Ok(pw_result) => {
log::debug!(
"{}: PipeWire result streams: {:?}",
self.name,
&pw_result.streams
);
let node_id = pw_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
log::info!("{}: PipeWire node selected: {}", self.name.clone(), node_id);
let capture = PipewireCapture::new(self.name.clone(), node_id);
log::info!("{}: PipeWire node selected: {}", self.name, node_id);
let capture = new_wlx_capture!(
app.gfx_extras.queue_capture,
PipewireCapture::new(self.name.clone(), node_id)
);
self.renderer = Some(ScreenBackend::new_raw(
self.name.clone(),
app.xr_backend,
Box::new(capture),
capture,
));
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(self.name.clone()),

View File

@@ -15,6 +15,8 @@ use crate::{
pub mod backend;
mod capture;
#[cfg(feature = "wayland")]
pub mod mirror;
#[cfg(feature = "pipewire")]
pub mod pw;
#[cfg(feature = "wayland")]

View File

@@ -42,6 +42,12 @@ impl ScreenBackend {
false,
)?;
log::debug!(
"{}: PipeWire result streams: {:?}",
output.name,
&select_screen_result.streams
);
let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
let capture = new_wlx_capture!(

View File

@@ -16,7 +16,6 @@ use wlx_common::{
use crate::{
backend::task::{OverlayTask, TaskType},
gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomIdFunc},
overlays::watch::{WATCH_POS, WATCH_ROT},
state::AppState,
windowing::{OverlaySelector, Z_ORDER_TOAST, window::OverlayWindowConfig},
};
@@ -121,35 +120,29 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
Positioning::FollowHead { lerp: 0.1 },
),
ToastDisplayMethod::Watch => {
//FIXME: properly follow watch
let watch_pos = WATCH_POS + vec3(-0.005, -0.05, 0.02);
let watch_rot = WATCH_ROT;
let relative_to = /*match app.session.config.watch_hand {
LeftRight::Left =>*/ Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 1.0,
align_to_hmd: true,
/*
},
LeftRight::Right => {
watch_pos.x = -watch_pos.x;
watch_rot = watch_rot * Quat::from_rotation_x(PI) * Quat::from_rotation_z(PI);
Positioning::FollowHand {
hand: LeftRight::Right,
lerp: 1.0,
}
}*/
let relative_to = Positioning::FollowHand {
hand: LeftRight::Left,
lerp: 0.1,
align_to_hmd: true,
};
(watch_pos, watch_rot, relative_to)
(vec3(0., 0., 0.), Quat::IDENTITY, relative_to)
}
};
let title = if toast.title.is_empty() {
Translation::from_translation_key("TOAST.DEFAULT_TITLE")
} else if matches!(toast.topic, ToastTopic::System | ToastTopic::Error) {
Translation::from_translation_key(&toast.title)
} else {
Translation::from_raw_text(&toast.title)
};
let body = if matches!(toast.topic, ToastTopic::System) {
Translation::from_translation_key(&toast.body)
} else {
Translation::from_raw_text(&toast.body)
};
let on_custom_id: OnCustomIdFunc<()> =
Box::new(move |id, widget, _doc_params, layout, _parser_state, ()| {
if &*id == "toast_title" {
@@ -168,7 +161,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
.get_as::<WidgetLabel>(widget)
.context("toast.xml: missing element with id: toast_body")?;
let mut globals = layout.state.globals.get();
label.set_text_simple(&mut globals, Translation::from_raw_text(&toast.body));
label.set_text_simple(&mut globals, body.clone());
}
Ok(())
});
@@ -206,7 +199,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
}
fn msg_err(app: &mut AppState, message: &str) {
Toast::new(ToastTopic::System, "Error".into(), message.into())
Toast::new(ToastTopic::Error, "TOAST.ERROR".into(), message.into())
.with_timeout(3.)
.submit(app);
}

View File

@@ -1,11 +1,7 @@
use std::{
collections::HashMap,
rc::Rc,
time::{Duration, Instant},
};
use std::{collections::HashMap, rc::Rc, time::Duration};
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3};
use idmap::{DirectIdMap, ordered::Keys};
use idmap::DirectIdMap;
use slotmap::SecondaryMap;
use wgui::{
components::button::ComponentButton,
@@ -31,7 +27,6 @@ use crate::{
panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
timer::GuiTimer,
},
overlays::edit::LongPressButtonState,
state::AppState,
windowing::{
OverlayID, OverlaySelector, Z_ORDER_WATCH,
@@ -70,7 +65,6 @@ struct WatchState {
keyboard_oid: OverlayID,
dashboard_oid: OverlayID,
num_sets: usize,
delete: LongPressButtonState,
}
#[allow(clippy::significant_drop_tightening)]
@@ -78,8 +72,14 @@ struct WatchState {
pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
let state = WatchState::default();
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
for (name, kind) in &BUTTON_EVENTS {
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) =
parser.fetch_component_from_widget_id_as::<ComponentButton>(attribs.widget_id)
else {
return;
};
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
@@ -89,20 +89,23 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, WatchState> = match command {
"::EditModeDeleteDown" => Box::new(move |_common, _data, _app, state| {
state.delete.pressed = Instant::now();
Ok(EventResult::Consumed)
}),
"::EditModeDeleteUp" => Box::new(move |_common, _data, app, state| {
if state.delete.pressed.elapsed() < Duration::from_secs(1) {
return Ok(EventResult::Consumed);
"::EditModeDeleteSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet));
Ok(EventResult::Consumed)
}),
"::EditModeAddSet" => Box::new(move |_common, _data, app, _state| {
"::EditModeAddSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet));
Ok(EventResult::Consumed)
}),
@@ -112,7 +115,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, _data, app, state| {
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
@@ -137,7 +144,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, _data, app, state| {
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
@@ -150,6 +161,29 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
Ok(EventResult::Consumed)
})
}
"::SingleSetOverlayReset" => {
let arg = args.next().unwrap_or_default();
let Ok(idx) = arg.parse::<usize>() else {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
};
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(overlay.id),
Box::new(|app, owc| owc.activate(app)),
)));
Ok(EventResult::Consumed)
})
}
_ => return,
};

View File

@@ -28,8 +28,12 @@
## How fast to drag when the space drag feature is activated
#space_drag_multiplier: 1.0
## When enabled, the space_rotate binding can be used
## to rotate in any axis. Imagine horizon mode².
## Setting this to false will make space_drag
## only affect the vertical axis (Y).
#space_drag_unlocked: true
## Monado/WiVRn only. When enabled, the space_rotate binding
## can rotate in any axis. Imagine horizon mode².
#space_rotate_unlocked: false
## Monado/WiVRn only. Use passthrough camera if the headset supports it.
@@ -87,9 +91,17 @@
#theme_path: "theme"
## These can be used to control the color theme of WlxOverlay-S.
#color_text: "#ffffff"
#color_accent: "#008cff"
#color_danger: "#ff3300"
#color_faded: "#668299"
#color_background: "#010206"
## Multiplier for animation speed. 2.0 → double speed, 0.5 → half speed
#animation_speed: 1.0
## Adjust this between 0..1 for a more rectangular feel.
#round_multiplier: 1.0
## Path to custom skybox texture, relative to `~/.config/wlxoverlay`
#skybox_texture: ""
@@ -163,6 +175,10 @@
#xr_click_sensitivity: 0.7
#xr_click_sensitivity_release: 0.5
## How many seconds to buttons need to be held
## before it's considered a long press
#long_press_duration: 1.0
## Change speed of scrolling
# 0.5 → half the speed
# 2.0 → twice the speed

View File

@@ -3,7 +3,7 @@ use idmap::IdMap;
use smallvec::{SmallVec, smallvec};
use std::sync::Arc;
use wgui::{
font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
renderer_vk::context::SharedContext as WSharedContext,
};
use wlx_common::{
@@ -96,26 +96,21 @@ impl AppState {
let theme = session.config.theme_path.clone();
let mut defaults = wgui::globals::Defaults::default();
defaults.accent_color = session
.config
.color_accent
.as_ref()
.and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.accent_color);
defaults.danger_color = session
.config
.color_danger
.as_ref()
.and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.danger_color);
fn apply_color(default: &mut drawing::Color, value: &Option<String>) {
if let Some(parsed) = value.as_ref().and_then(|c| parse_color_hex(c)) {
*default = parsed;
}
}
defaults.faded_color = session
.config
.color_faded
.as_ref()
.and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.faded_color);
apply_color(&mut defaults.text_color, &session.config.color_text);
apply_color(&mut defaults.accent_color, &session.config.color_accent);
apply_color(&mut defaults.danger_color, &session.config.color_danger);
apply_color(&mut defaults.faded_color, &session.config.color_faded);
apply_color(&mut defaults.bg_color, &session.config.color_background);
defaults.animation_mult = 1. / session.config.animation_speed;
defaults.rounding_mult = session.config.round_multiplier;
let dbus = DbusConnector::default();
@@ -205,6 +200,7 @@ impl AppSession {
let mut toast_topics = IdMap::new();
toast_topics.insert(ToastTopic::System, ToastDisplayMethod::Center);
toast_topics.insert(ToastTopic::Error, ToastDisplayMethod::Center);
toast_topics.insert(ToastTopic::DesktopNotification, ToastDisplayMethod::Center);
toast_topics.insert(ToastTopic::XSNotification, ToastDisplayMethod::Center);

View File

@@ -27,6 +27,7 @@ pub struct FrameMeta {
pub clear: WGfxClearMode,
}
#[derive(Debug, Clone, Copy)]
pub enum ShouldRender {
/// The overlay is dirty and needs to be rendered.
Should,

View File

@@ -34,7 +34,7 @@ use crate::{
},
};
pub const MAX_OVERLAY_SETS: usize = 7;
pub const MAX_OVERLAY_SETS: usize = 6;
pub struct OverlayWindowManager<T> {
wrappers: EditWrapperManager,
@@ -49,6 +49,7 @@ pub struct OverlayWindowManager<T> {
watch_id: OverlayID,
edit_mode: bool,
dropped_overlays: VecDeque<OverlayWindowData<T>>,
initialized: bool,
}
impl<T> OverlayWindowManager<T>
@@ -66,6 +67,7 @@ where
watch_id: OverlayID::null(), // set down below
edit_mode: false,
dropped_overlays: VecDeque::with_capacity(8),
initialized: false,
};
let mut wayland = false;
@@ -148,6 +150,8 @@ where
.notify(app, ev)?;
}
me.initialized = true;
Ok(me)
}
@@ -199,8 +203,8 @@ where
let Some(set) = self.current_set else {
Toast::new(
ToastTopic::System,
"Can't remove set".into(),
"No set is selected!".into(),
"TOAST.CANNOT_REMOVE_SET".into(),
"TOAST.NO_SET_SELECTED".into(),
)
.with_timeout(5.)
.with_sound(true)
@@ -211,8 +215,8 @@ where
if self.sets.len() <= 1 {
Toast::new(
ToastTopic::System,
"Can't remove set".into(),
"This is the last existing set!".into(),
"TOAST.CANNOT_REMOVE_SET".into(),
"TOAST.LAST_EXISTING_SET".into(),
)
.with_timeout(5.)
.with_sound(true)
@@ -658,6 +662,7 @@ impl<T> OverlayWindowManager<T> {
return;
}
let mut num_overlays = 0;
let ws = &mut self.sets[new_set];
for (id, data) in self.overlays.iter_mut().filter(|(_, d)| !d.config.global) {
if let Some(state) = ws.overlays.remove(id) {
@@ -666,10 +671,28 @@ impl<T> OverlayWindowManager<T> {
if !keep_transforms {
data.config.reset(app, false);
}
if !matches!(
data.config.category,
OverlayCategory::Internal
| OverlayCategory::Keyboard
| OverlayCategory::Dashboard
) {
num_overlays += 1;
}
}
}
ws.overlays.clear();
self.restore_set = new_set;
if !self.edit_mode && self.initialized && num_overlays < 1 {
Toast::new(
ToastTopic::System,
"TOAST.EMPTY_SET".into(),
"TOAST.LETS_ADD_OVERLAYS".into(),
)
.with_timeout(3.)
.submit(app);
}
}
self.current_set = new_set;