watch: highlight buttons of active overlays
This commit is contained in:
@@ -34,7 +34,7 @@ All overlays are listed on bottom row.
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="Overlay">
|
<template name="Overlay">
|
||||||
<Button macro="button_style" id="overlay_${idx}"
|
<Button macro="button_style" id="overlay_${idx}" sticky="1"
|
||||||
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::SingleSetOverlayToggle ${idx}"
|
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::SingleSetOverlayToggle ${idx}"
|
||||||
align_items="center"
|
align_items="center"
|
||||||
height="40">
|
height="40">
|
||||||
@@ -111,13 +111,13 @@ All overlays are listed on bottom row.
|
|||||||
|
|
||||||
<!-- Bottom buttons -->
|
<!-- Bottom buttons -->
|
||||||
<div id="toolbox-condensed" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
|
<div id="toolbox-condensed" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
|
||||||
<Button height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top">
|
<Button id="btn_dashboard" height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" sticky="1" >
|
||||||
<sprite color="~set_color" width="32" height="32" src="watch/wayvr_dashboard_mono.svg" />
|
<sprite color="~set_color" width="32" height="32" src="watch/wayvr_dashboard_mono.svg" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button id="btn_edit_mode" height="40" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="top">
|
<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" />
|
<sprite color="~set_color" width="32" height="32" src="watch/edit.svg" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd">
|
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" sticky="1" >
|
||||||
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
|
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
|
||||||
</Button>
|
</Button>
|
||||||
<!-- Src here may be changed, but maintain `OverlayCategory` order: Panel, Screen, Mirror, WayVR -->
|
<!-- Src here may be changed, but maintain `OverlayCategory` order: Panel, Screen, Mirror, WayVR -->
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="Overlay">
|
<template name="Overlay">
|
||||||
<Button macro="button_style" id="overlay_${idx}"
|
<Button macro="button_style" id="overlay_${idx}" sticky="1"
|
||||||
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::EditModeOverlayToggle ${idx}"
|
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::EditModeOverlayToggle ${idx}"
|
||||||
align_items="center"
|
align_items="center"
|
||||||
height="40">
|
height="40">
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div flex_direction="column" align_items="center" justify_content="center">
|
<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">
|
<div id="toolbox" gap="8" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
|
||||||
<Button height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd">
|
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" sticky="1" >
|
||||||
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
|
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
|
||||||
<label translation="EDIT_MODE.KEYBOARD" size="18" />
|
<label translation="EDIT_MODE.KEYBOARD" size="18" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
<!-- Bottom buttons -->
|
<!-- Bottom buttons -->
|
||||||
<div flex_direction="row" gap="4">
|
<div flex_direction="row" gap="4">
|
||||||
<Button macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top">
|
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" sticky="1" >
|
||||||
<sprite color="~set_color" width="40" height="40" src="watch/wayvr_dashboard_mono.svg" />
|
<sprite color="~set_color" width="40" height="40" src="watch/wayvr_dashboard_mono.svg" />
|
||||||
</Button>
|
</Button>
|
||||||
<div id="edit_delete" display="none">
|
<div id="edit_delete" display="none">
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3};
|
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3};
|
||||||
use idmap::DirectIdMap;
|
use idmap::{DirectIdMap, ordered::Keys};
|
||||||
|
use slotmap::SecondaryMap;
|
||||||
use wgui::{
|
use wgui::{
|
||||||
components::button::ComponentButton,
|
components::button::ComponentButton,
|
||||||
event::{CallbackDataCommon, EventAlterables, EventCallback, StyleSetRequest},
|
event::{CallbackDataCommon, EventAlterables, EventCallback, StyleSetRequest},
|
||||||
@@ -33,7 +34,7 @@ use crate::{
|
|||||||
overlays::edit::LongPressButtonState,
|
overlays::edit::LongPressButtonState,
|
||||||
state::AppState,
|
state::AppState,
|
||||||
windowing::{
|
windowing::{
|
||||||
OverlaySelector, Z_ORDER_WATCH,
|
OverlayID, OverlaySelector, Z_ORDER_WATCH,
|
||||||
backend::{OverlayEventData, OverlayMeta},
|
backend::{OverlayEventData, OverlayMeta},
|
||||||
manager::MAX_OVERLAY_SETS,
|
manager::MAX_OVERLAY_SETS,
|
||||||
window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
|
window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
|
||||||
@@ -60,11 +61,14 @@ struct WatchState {
|
|||||||
set_buttons: Vec<Rc<ComponentButton>>,
|
set_buttons: Vec<Rc<ComponentButton>>,
|
||||||
overlay_buttons: Vec<OverlayButton>,
|
overlay_buttons: Vec<OverlayButton>,
|
||||||
overlay_metas: Vec<OverlayMeta>,
|
overlay_metas: Vec<OverlayMeta>,
|
||||||
|
overlay_indices: SecondaryMap<OverlayID, usize>,
|
||||||
edit_mode_widgets: Vec<(WidgetID, bool)>,
|
edit_mode_widgets: Vec<(WidgetID, bool)>,
|
||||||
edit_add_widget: WidgetID,
|
edit_add_widget: WidgetID,
|
||||||
device_role_icons: DirectIdMap<TrackedDeviceRole, CustomGlyphData>,
|
device_role_icons: DirectIdMap<TrackedDeviceRole, CustomGlyphData>,
|
||||||
overlay_cat_icons: DirectIdMap<OverlayCategory, CustomGlyphData>,
|
overlay_cat_icons: DirectIdMap<OverlayCategory, CustomGlyphData>,
|
||||||
devices: Vec<(WidgetID, WidgetID)>,
|
devices: Vec<(WidgetID, WidgetID)>,
|
||||||
|
keyboard_oid: OverlayID,
|
||||||
|
dashboard_oid: OverlayID,
|
||||||
num_sets: usize,
|
num_sets: usize,
|
||||||
delete: LongPressButtonState,
|
delete: LongPressButtonState,
|
||||||
}
|
}
|
||||||
@@ -310,6 +314,14 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
|||||||
.parser_state
|
.parser_state
|
||||||
.fetch_component_as::<ComponentButton>("btn_edit_mode")
|
.fetch_component_as::<ComponentButton>("btn_edit_mode")
|
||||||
.ok();
|
.ok();
|
||||||
|
let btn_keyboard = panel
|
||||||
|
.parser_state
|
||||||
|
.fetch_component_as::<ComponentButton>("btn_keyboard")
|
||||||
|
.ok();
|
||||||
|
let btn_dashboard = panel
|
||||||
|
.parser_state
|
||||||
|
.fetch_component_as::<ComponentButton>("btn_dashboard")
|
||||||
|
.ok();
|
||||||
|
|
||||||
panel.on_notify = Some(Box::new(move |panel, app, event_data| {
|
panel.on_notify = Some(Box::new(move |panel, app, event_data| {
|
||||||
let mut alterables = EventAlterables::default();
|
let mut alterables = EventAlterables::default();
|
||||||
@@ -379,7 +391,31 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
OverlayEventData::OverlaysChanged(metas) => {
|
OverlayEventData::OverlaysChanged(metas) => {
|
||||||
panel.state.overlay_metas = metas;
|
panel.state.overlay_metas.clear();
|
||||||
|
for meta in metas {
|
||||||
|
match meta.category {
|
||||||
|
OverlayCategory::Keyboard => {
|
||||||
|
panel.state.keyboard_oid = meta.id;
|
||||||
|
if let Some(btn_keyboard) = btn_keyboard.as_ref() {
|
||||||
|
btn_keyboard.set_sticky_state(&mut com, meta.visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OverlayCategory::Dashboard => {
|
||||||
|
if let Some(btn_dashboard) = btn_dashboard.as_ref() {
|
||||||
|
btn_dashboard.set_sticky_state(&mut com, meta.visible);
|
||||||
|
}
|
||||||
|
panel.state.dashboard_oid = meta.id
|
||||||
|
}
|
||||||
|
OverlayCategory::Internal => {}
|
||||||
|
_ => panel.state.overlay_metas.push(meta),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.state.overlay_indices.clear();
|
||||||
|
for (idx, meta) in panel.state.overlay_metas.iter().enumerate() {
|
||||||
|
panel.state.overlay_indices.insert(meta.id, idx);
|
||||||
|
}
|
||||||
|
|
||||||
for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
|
for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
|
||||||
let display = if let Some(meta) = panel.state.overlay_metas.get(idx) {
|
let display = if let Some(meta) = panel.state.overlay_metas.get(idx) {
|
||||||
let name = btn
|
let name = btn
|
||||||
@@ -406,6 +442,8 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
|||||||
sprite.set_content(Some(glyph.clone()));
|
sprite.set_content(Some(glyph.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
btn.button.set_sticky_state(&mut com, meta.visible);
|
||||||
|
|
||||||
taffy::Display::Flex
|
taffy::Display::Flex
|
||||||
} else {
|
} else {
|
||||||
taffy::Display::None
|
taffy::Display::None
|
||||||
@@ -414,6 +452,39 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
|||||||
.set_style(btn.button.get_rect(), StyleSetRequest::Display(display));
|
.set_style(btn.button.get_rect(), StyleSetRequest::Display(display));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
OverlayEventData::VisibleOverlaysChanged(overlays) => {
|
||||||
|
for meta in panel.state.overlay_metas.iter_mut() {
|
||||||
|
meta.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut keyboard_visible = false;
|
||||||
|
let mut dashboard_visible = false;
|
||||||
|
|
||||||
|
for visible in overlays.iter() {
|
||||||
|
if let Some(idx) = panel.state.overlay_indices.get(*visible)
|
||||||
|
&& let Some(o) = panel.state.overlay_metas.get_mut(*idx)
|
||||||
|
{
|
||||||
|
o.visible = true;
|
||||||
|
} else if panel.state.keyboard_oid == *visible {
|
||||||
|
keyboard_visible = true;
|
||||||
|
} else if panel.state.dashboard_oid == *visible {
|
||||||
|
dashboard_visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
|
||||||
|
let Some(meta) = panel.state.overlay_metas.get(idx) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
btn.button.set_sticky_state(&mut com, meta.visible);
|
||||||
|
}
|
||||||
|
if let Some(btn_keyboard) = btn_keyboard.as_ref() {
|
||||||
|
btn_keyboard.set_sticky_state(&mut com, keyboard_visible);
|
||||||
|
}
|
||||||
|
if let Some(btn_dashboard) = btn_dashboard.as_ref() {
|
||||||
|
btn_dashboard.set_sticky_state(&mut com, dashboard_visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
OverlayEventData::DevicesChanged => {
|
OverlayEventData::DevicesChanged => {
|
||||||
for (i, (div, s)) in panel.state.devices.iter().enumerate() {
|
for (i, (div, s)) in panel.state.devices.iter().enumerate() {
|
||||||
if let Some(dev) = app.input_state.devices.get(i)
|
if let Some(dev) = app.input_state.devices.get(i)
|
||||||
|
|||||||
@@ -811,7 +811,7 @@ pub fn create_wayvr_display_overlay(
|
|||||||
)?);
|
)?);
|
||||||
|
|
||||||
let category = if name == DASHBOARD_DISPLAY_NAME {
|
let category = if name == DASHBOARD_DISPLAY_NAME {
|
||||||
OverlayCategory::Internal
|
OverlayCategory::Dashboard
|
||||||
} else {
|
} else {
|
||||||
OverlayCategory::WayVR
|
OverlayCategory::WayVR
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ pub struct OverlayMeta {
|
|||||||
pub id: OverlayID,
|
pub id: OverlayID,
|
||||||
pub name: Arc<str>,
|
pub name: Arc<str>,
|
||||||
pub category: OverlayCategory,
|
pub category: OverlayCategory,
|
||||||
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
@@ -114,6 +115,7 @@ pub enum OverlayEventData {
|
|||||||
NumSetsChanged(usize),
|
NumSetsChanged(usize),
|
||||||
EditModeChanged(bool),
|
EditModeChanged(bool),
|
||||||
OverlaysChanged(Vec<OverlayMeta>),
|
OverlaysChanged(Vec<OverlayMeta>),
|
||||||
|
VisibleOverlaysChanged(Vec<OverlayID>),
|
||||||
DevicesChanged,
|
DevicesChanged,
|
||||||
OverlayGrabbed {
|
OverlayGrabbed {
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
|
|||||||
@@ -248,7 +248,12 @@ where
|
|||||||
}
|
}
|
||||||
OverlayTask::Modify(sel, f) => {
|
OverlayTask::Modify(sel, f) => {
|
||||||
if let Some(o) = self.mut_by_selector(&sel) {
|
if let Some(o) = self.mut_by_selector(&sel) {
|
||||||
|
let was_visible = o.config.is_active();
|
||||||
f(app, &mut o.config);
|
f(app, &mut o.config);
|
||||||
|
|
||||||
|
if was_visible != o.config.is_active() {
|
||||||
|
let _ = self.visible_overlays_changed(app);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Overlay not found for task: {sel:?}");
|
log::warn!("Overlay not found for task: {sel:?}");
|
||||||
}
|
}
|
||||||
@@ -477,7 +482,10 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(overlay.config.category, OverlayCategory::Internal) {
|
if matches!(
|
||||||
|
overlay.config.category,
|
||||||
|
OverlayCategory::Internal | OverlayCategory::Dashboard
|
||||||
|
) {
|
||||||
// watch, anchor, toast, dashboard
|
// watch, anchor, toast, dashboard
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -524,7 +532,7 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
let internal = ret_val.as_ref().is_some_and(|o| {
|
let internal = ret_val.as_ref().is_some_and(|o| {
|
||||||
matches!(
|
matches!(
|
||||||
o.config.category,
|
o.config.category,
|
||||||
OverlayCategory::Internal | OverlayCategory::Keyboard
|
OverlayCategory::Internal | OverlayCategory::Keyboard | OverlayCategory::Dashboard
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -577,10 +585,7 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
|
|
||||||
let name = overlay.config.name.clone();
|
let name = overlay.config.name.clone();
|
||||||
let global = overlay.config.global;
|
let global = overlay.config.global;
|
||||||
let internal = matches!(
|
let internal = matches!(overlay.config.category, OverlayCategory::Internal);
|
||||||
overlay.config.category,
|
|
||||||
OverlayCategory::Internal | OverlayCategory::Keyboard
|
|
||||||
);
|
|
||||||
let show_on_spawn = overlay.config.show_on_spawn;
|
let show_on_spawn = overlay.config.show_on_spawn;
|
||||||
|
|
||||||
let oid = self.overlays.insert(overlay);
|
let oid = self.overlays.insert(overlay);
|
||||||
@@ -611,6 +616,9 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
if !internal && let Err(e) = self.overlays_changed(app) {
|
if !internal && let Err(e) = self.overlays_changed(app) {
|
||||||
log::error!("Error while adding overlay: {e:?}");
|
log::error!("Error while adding overlay: {e:?}");
|
||||||
}
|
}
|
||||||
|
if !internal && let Err(e) = self.visible_overlays_changed(app) {
|
||||||
|
log::error!("Error while adding overlay: {e:?}");
|
||||||
|
}
|
||||||
oid
|
oid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,6 +679,10 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
.backend
|
.backend
|
||||||
.notify(app, OverlayEventData::ActiveSetChanged(new_set))
|
.notify(app, OverlayEventData::ActiveSetChanged(new_set))
|
||||||
.unwrap(); // TODO: handle this
|
.unwrap(); // TODO: handle this
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.visible_overlays_changed(app)
|
||||||
|
.inspect_err(|e| log::error!("VisibleOverlaysChanged: {e:?}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,21 +695,23 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
} else {
|
} else {
|
||||||
self.switch_to_set(app, None, false);
|
self.switch_to_set(app, None, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.visible_overlays_changed(app)
|
||||||
|
.inspect_err(|e| log::error!("VisibleOverlaysChanged: {e:?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlays_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
fn overlays_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
let mut meta = Vec::with_capacity(self.overlays.len());
|
let mut meta = Vec::with_capacity(self.overlays.len());
|
||||||
for (id, data) in &self.overlays {
|
for (id, data) in &self.overlays {
|
||||||
if matches!(
|
if matches!(data.config.category, OverlayCategory::Internal) {
|
||||||
data.config.category,
|
|
||||||
OverlayCategory::Internal | OverlayCategory::Keyboard
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
meta.push(OverlayMeta {
|
meta.push(OverlayMeta {
|
||||||
id,
|
id,
|
||||||
name: data.config.name.clone(),
|
name: data.config.name.clone(),
|
||||||
category: data.config.category,
|
category: data.config.category,
|
||||||
|
visible: data.config.is_active(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,6 +725,28 @@ impl<T> OverlayWindowManager<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visible_overlays_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
|
let mut vis = Vec::with_capacity(self.overlays.len());
|
||||||
|
|
||||||
|
for (id, data) in &self.overlays {
|
||||||
|
if data.config.active_state.is_none()
|
||||||
|
|| matches!(data.config.category, OverlayCategory::Internal)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vis.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(watch) = self.mut_by_id(self.watch_id) {
|
||||||
|
watch
|
||||||
|
.config
|
||||||
|
.backend
|
||||||
|
.notify(app, OverlayEventData::VisibleOverlaysChanged(vis))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn devices_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
pub fn devices_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
if let Some(watch) = self.mut_by_id(self.watch_id) {
|
if let Some(watch) = self.mut_by_id(self.watch_id) {
|
||||||
watch
|
watch
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ impl<T> OverlayWindowData<T> {
|
|||||||
pub enum OverlayCategory {
|
pub enum OverlayCategory {
|
||||||
Internal,
|
Internal,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
|
Dashboard,
|
||||||
Panel,
|
Panel,
|
||||||
Screen,
|
Screen,
|
||||||
Mirror,
|
Mirror,
|
||||||
|
|||||||
Reference in New Issue
Block a user