nothing works
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.gdb_history
|
||||||
4155
Cargo.lock
generated
Normal file
4155
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "wlx-overlay-s"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ash-window = "0.12.0"
|
||||||
|
chrono = "0.4.29"
|
||||||
|
cstr = "0.2.11"
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
fontconfig-rs = { version = "0.1.1", features = ["dlopen"] }
|
||||||
|
freetype-rs = "0.32.0"
|
||||||
|
glam = { version = "0.24.1", features = ["approx"] }
|
||||||
|
idmap = "0.2.21"
|
||||||
|
idmap-derive = "0.1.2"
|
||||||
|
input-linux = "0.6.0"
|
||||||
|
libc = "0.2.147"
|
||||||
|
log = "0.4.20"
|
||||||
|
once_cell = "1.18.0"
|
||||||
|
ovr_overlay = { features = ["ovr_input", "ovr_system"], path = "../ovr_overlay_oyasumi" }
|
||||||
|
png = "0.17.10"
|
||||||
|
raw-window-handle = "0.5.2"
|
||||||
|
regex = "1.9.5"
|
||||||
|
rodio = { version = "0.17.1", default-features = false, features = ["wav", "hound"] }
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9.25"
|
||||||
|
smallvec = "1.11.0"
|
||||||
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
|
tinyvec = "1.6.0"
|
||||||
|
vulkano = { version = "0.33.0", features = ["serde"] }
|
||||||
|
vulkano-shaders = "0.33.0"
|
||||||
|
vulkano-util = "0.33.0"
|
||||||
|
vulkano-win = "0.33.0"
|
||||||
|
winit = "0.28.6"
|
||||||
|
wlx-capture = { version = "0.1.0", path = "../wlx-capture" }
|
||||||
|
|
||||||
152
src/backend/common.rs
Normal file
152
src/backend/common.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use std::{collections::VecDeque, time::Instant};
|
||||||
|
|
||||||
|
use glam::{Affine3A, Vec2, Vec3, Vec3A};
|
||||||
|
use ovr_overlay::TrackedDeviceIndex;
|
||||||
|
|
||||||
|
pub struct InputState<TState, THand> {
|
||||||
|
pub hmd: Affine3A,
|
||||||
|
pub pointers: [Pointer<THand>; 2],
|
||||||
|
pub devices: Vec<TrackedDevice>,
|
||||||
|
pub(super) data: TState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TState, THand> InputState<TState, THand> {
|
||||||
|
pub fn pre_update(&mut self) {
|
||||||
|
self.pointers[0].before = self.pointers[0].now;
|
||||||
|
self.pointers[1].before = self.pointers[1].now;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_update(&mut self) {
|
||||||
|
for hand in &mut self.pointers {
|
||||||
|
if hand.now.click_modifier_right {
|
||||||
|
hand.interaction.mode = PointerMode::Right;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if hand.now.click_modifier_middle {
|
||||||
|
hand.interaction.mode = PointerMode::Middle;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hmd_up = self.hmd.transform_vector3a(Vec3A::Y);
|
||||||
|
let dot =
|
||||||
|
hmd_up.dot(hand.pose.transform_vector3a(Vec3A::X)) * (1.0 - 2.0 * hand.hand as f32);
|
||||||
|
|
||||||
|
hand.interaction.mode = if dot < -0.85 {
|
||||||
|
PointerMode::Right
|
||||||
|
} else if dot > 0.7 {
|
||||||
|
PointerMode::Middle
|
||||||
|
} else {
|
||||||
|
PointerMode::Left
|
||||||
|
};
|
||||||
|
|
||||||
|
let middle_click_orientation = false;
|
||||||
|
let right_click_orientation = false;
|
||||||
|
match hand.interaction.mode {
|
||||||
|
PointerMode::Middle => {
|
||||||
|
if !middle_click_orientation {
|
||||||
|
hand.interaction.mode = PointerMode::Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PointerMode::Right => {
|
||||||
|
if !right_click_orientation {
|
||||||
|
hand.interaction.mode = PointerMode::Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pointer<THand> {
|
||||||
|
pub hand: usize,
|
||||||
|
pub pose: Affine3A,
|
||||||
|
pub now: PointerState,
|
||||||
|
pub before: PointerState,
|
||||||
|
pub(super) interaction: InteractionState,
|
||||||
|
pub(super) data: THand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct PointerState {
|
||||||
|
pub scroll: f32,
|
||||||
|
pub click: bool,
|
||||||
|
pub grab: bool,
|
||||||
|
pub alt_click: bool,
|
||||||
|
pub show_hide: bool,
|
||||||
|
pub space_drag: bool,
|
||||||
|
pub click_modifier_right: bool,
|
||||||
|
pub click_modifier_middle: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InteractionState {
|
||||||
|
pub mode: PointerMode,
|
||||||
|
pub grabbed: Option<GrabData>,
|
||||||
|
pub clicked_id: Option<usize>,
|
||||||
|
pub hovered_id: Option<usize>,
|
||||||
|
pub release_actions: VecDeque<Box<dyn Fn()>>,
|
||||||
|
pub next_push: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InteractionState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
mode: PointerMode::Left,
|
||||||
|
grabbed: None,
|
||||||
|
clicked_id: None,
|
||||||
|
hovered_id: None,
|
||||||
|
release_actions: VecDeque::new(),
|
||||||
|
next_push: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PointerHit {
|
||||||
|
pub hand: usize,
|
||||||
|
pub mode: PointerMode,
|
||||||
|
pub primary: bool,
|
||||||
|
pub uv: Vec2,
|
||||||
|
pub dist: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RayHit {
|
||||||
|
idx: usize,
|
||||||
|
ray_pos: Vec3,
|
||||||
|
hit_pos: Vec3,
|
||||||
|
uv: Vec2,
|
||||||
|
dist: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct GrabData {
|
||||||
|
pub offset: Vec3,
|
||||||
|
pub grabbed_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub enum PointerMode {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Middle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TrackedDevice {
|
||||||
|
pub index: TrackedDeviceIndex,
|
||||||
|
pub valid: bool,
|
||||||
|
pub soc: Option<f32>,
|
||||||
|
pub charging: bool,
|
||||||
|
pub role: TrackedDeviceRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TrackedDeviceRole {
|
||||||
|
None,
|
||||||
|
Hmd,
|
||||||
|
LeftHand,
|
||||||
|
RightHand,
|
||||||
|
Tracker,
|
||||||
|
}
|
||||||
3
src/backend/mod.rs
Normal file
3
src/backend/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod common;
|
||||||
|
pub mod openvr;
|
||||||
|
pub mod openxr;
|
||||||
288
src/backend/openvr/input.rs
Normal file
288
src/backend/openvr/input.rs
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
use std::array;
|
||||||
|
|
||||||
|
use glam::Affine3A;
|
||||||
|
use ovr_overlay::{
|
||||||
|
input::{ActionHandle, ActionSetHandle, ActiveActionSet, InputManager, InputValueHandle},
|
||||||
|
sys::{
|
||||||
|
k_unMaxTrackedDeviceCount, ETrackedControllerRole, ETrackedDeviceClass,
|
||||||
|
ETrackedDeviceProperty, ETrackingUniverseOrigin, HmdMatrix34_t,
|
||||||
|
},
|
||||||
|
system::SystemManager,
|
||||||
|
TrackedDeviceIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::backend::common::{
|
||||||
|
InputState, InteractionState, Pointer, PointerState, TrackedDevice, TrackedDeviceRole,
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! result_str {
|
||||||
|
( $e:expr ) => {
|
||||||
|
match $e {
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
Err(y) => Err(y.description()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const SET_DEFAULT: &str = "/actions/default";
|
||||||
|
const INPUT_SOURCES: [&str; 2] = ["/user/hand/left", "/user/hand/right"];
|
||||||
|
const PATH_POSES: [&str; 2] = [
|
||||||
|
"/actions/default/in/LeftHand",
|
||||||
|
"/actions/default/in/RightHand",
|
||||||
|
];
|
||||||
|
const PATH_HAPTICS: [&str; 2] = [
|
||||||
|
"/actions/default/out/HapticsLeft",
|
||||||
|
"/actions/default/out/HapticsRight",
|
||||||
|
];
|
||||||
|
|
||||||
|
const PATH_CLICK: &str = "/actions/default/in/Click";
|
||||||
|
const PATH_GRAB: &str = "/actions/default/in/Grab";
|
||||||
|
const PATH_SCROLL: &str = "/actions/default/in/Scroll";
|
||||||
|
const PATH_ALT_CLICK: &str = "/actions/default/in/AltClick";
|
||||||
|
const PATH_SHOW_HIDE: &str = "/actions/default/in/ShowHide";
|
||||||
|
const PATH_SPACE_DRAG: &str = "/actions/default/in/SpaceDrag";
|
||||||
|
const PATH_CLICK_MODIFIER_RIGHT: &str = "/actions/default/in/ClickModifierRight";
|
||||||
|
const PATH_CLICK_MODIFIER_MIDDLE: &str = "/actions/default/in/ClickModifierMiddle";
|
||||||
|
|
||||||
|
const INPUT_ANY: InputValueHandle = InputValueHandle(ovr_overlay::sys::k_ulInvalidInputValueHandle);
|
||||||
|
|
||||||
|
pub(super) struct OpenVrInputState {
|
||||||
|
set_hnd: ActionSetHandle,
|
||||||
|
click_hnd: ActionHandle,
|
||||||
|
grab_hnd: ActionHandle,
|
||||||
|
scroll_hnd: ActionHandle,
|
||||||
|
alt_click_hnd: ActionHandle,
|
||||||
|
show_hide_hnd: ActionHandle,
|
||||||
|
space_drag_hnd: ActionHandle,
|
||||||
|
click_modifier_right_hnd: ActionHandle,
|
||||||
|
click_modifier_middle_hnd: ActionHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct OpenVrHandState {
|
||||||
|
has_pose: bool,
|
||||||
|
input_hnd: InputValueHandle,
|
||||||
|
pose_hnd: ActionHandle,
|
||||||
|
haptics_hnd: ActionHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState<OpenVrInputState, OpenVrHandState> {
|
||||||
|
pub fn new(input: &mut InputManager) -> Result<Self, &'static str> {
|
||||||
|
let set_hnd = result_str!(input.get_action_set_handle(SET_DEFAULT))?;
|
||||||
|
|
||||||
|
let click_hnd = result_str!(input.get_action_handle(PATH_CLICK))?;
|
||||||
|
let grab_hnd = result_str!(input.get_action_handle(PATH_GRAB))?;
|
||||||
|
let scroll_hnd = result_str!(input.get_action_handle(PATH_SCROLL))?;
|
||||||
|
let alt_click_hnd = result_str!(input.get_action_handle(PATH_ALT_CLICK))?;
|
||||||
|
let show_hide_hnd = result_str!(input.get_action_handle(PATH_SHOW_HIDE))?;
|
||||||
|
let space_drag_hnd = result_str!(input.get_action_handle(PATH_SPACE_DRAG))?;
|
||||||
|
let click_modifier_right_hnd =
|
||||||
|
result_str!(input.get_action_handle(PATH_CLICK_MODIFIER_RIGHT))?;
|
||||||
|
let click_modifier_middle_hnd =
|
||||||
|
result_str!(input.get_action_handle(PATH_CLICK_MODIFIER_MIDDLE))?;
|
||||||
|
|
||||||
|
let input_hnd: Vec<InputValueHandle> = INPUT_SOURCES
|
||||||
|
.iter()
|
||||||
|
.map(|path| Ok(result_str!(input.get_input_source_handle(path))?))
|
||||||
|
.collect::<Result<_, &'static str>>()?;
|
||||||
|
|
||||||
|
let pose_hnd: Vec<ActionHandle> = PATH_POSES
|
||||||
|
.iter()
|
||||||
|
.map(|path| Ok(result_str!(input.get_action_handle(path))?))
|
||||||
|
.collect::<Result<_, &'static str>>()?;
|
||||||
|
|
||||||
|
let haptics_hnd: Vec<ActionHandle> = PATH_HAPTICS
|
||||||
|
.iter()
|
||||||
|
.map(|path| Ok(result_str!(input.get_action_handle(path))?))
|
||||||
|
.collect::<Result<_, &'static str>>()?;
|
||||||
|
|
||||||
|
let hands: [Pointer<OpenVrHandState>; 2] = array::from_fn(|i| Pointer::<OpenVrHandState> {
|
||||||
|
hand: i,
|
||||||
|
now: PointerState::default(),
|
||||||
|
before: PointerState::default(),
|
||||||
|
pose: Affine3A::IDENTITY,
|
||||||
|
interaction: InteractionState::default(),
|
||||||
|
data: OpenVrHandState {
|
||||||
|
has_pose: false,
|
||||||
|
input_hnd: input_hnd[i],
|
||||||
|
pose_hnd: pose_hnd[i],
|
||||||
|
haptics_hnd: haptics_hnd[i],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(InputState {
|
||||||
|
hmd: Affine3A::IDENTITY,
|
||||||
|
pointers: hands,
|
||||||
|
devices: vec![],
|
||||||
|
data: OpenVrInputState {
|
||||||
|
set_hnd,
|
||||||
|
click_hnd,
|
||||||
|
grab_hnd,
|
||||||
|
scroll_hnd,
|
||||||
|
alt_click_hnd,
|
||||||
|
show_hide_hnd,
|
||||||
|
space_drag_hnd,
|
||||||
|
click_modifier_right_hnd,
|
||||||
|
click_modifier_middle_hnd,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, input: &mut InputManager, system: &mut SystemManager) {
|
||||||
|
let aas = ActiveActionSet {
|
||||||
|
0: ovr_overlay::sys::VRActiveActionSet_t {
|
||||||
|
ulActionSet: self.data.set_hnd.0,
|
||||||
|
ulRestrictedToDevice: 0,
|
||||||
|
ulSecondaryActionSet: 0,
|
||||||
|
unPadding: 0,
|
||||||
|
nPriority: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = input.update_actions(&mut [aas]);
|
||||||
|
|
||||||
|
let universe = ETrackingUniverseOrigin::TrackingUniverseStanding;
|
||||||
|
|
||||||
|
for i in 0..2 {
|
||||||
|
let hand = &mut self.pointers[i];
|
||||||
|
|
||||||
|
hand.data.has_pose = false;
|
||||||
|
|
||||||
|
let _ = input
|
||||||
|
.get_pose_action_data_relative_to_now(
|
||||||
|
hand.data.pose_hnd,
|
||||||
|
universe.clone(),
|
||||||
|
0.005,
|
||||||
|
INPUT_ANY,
|
||||||
|
)
|
||||||
|
.and_then(|pose| {
|
||||||
|
copy_from_hmd(&pose.0.pose.mDeviceToAbsoluteTracking, &mut hand.pose);
|
||||||
|
hand.data.has_pose = true;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
hand.now.click = input
|
||||||
|
.get_digital_action_data(self.data.click_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.grab = input
|
||||||
|
.get_digital_action_data(self.data.grab_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.alt_click = input
|
||||||
|
.get_digital_action_data(self.data.alt_click_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.show_hide = input
|
||||||
|
.get_digital_action_data(self.data.show_hide_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.space_drag = input
|
||||||
|
.get_digital_action_data(self.data.space_drag_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.click_modifier_right = input
|
||||||
|
.get_digital_action_data(self.data.click_modifier_right_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.click_modifier_middle = input
|
||||||
|
.get_digital_action_data(self.data.click_modifier_middle_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.bState)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
hand.now.scroll = input
|
||||||
|
.get_analog_action_data(self.data.scroll_hnd, hand.data.input_hnd)
|
||||||
|
.map(|x| x.0.x)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let devices = system.get_device_to_absolute_tracking_pose(universe, 0.005);
|
||||||
|
copy_from_hmd(&devices[0].mDeviceToAbsoluteTracking, &mut self.hmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_devices(&mut self, system: &mut SystemManager) {
|
||||||
|
self.devices.clear();
|
||||||
|
for i in 0..k_unMaxTrackedDeviceCount {
|
||||||
|
let index = TrackedDeviceIndex(i);
|
||||||
|
let maybe_role = match system.get_tracked_device_class(index) {
|
||||||
|
ETrackedDeviceClass::TrackedDeviceClass_HMD => Some(TrackedDeviceRole::Hmd),
|
||||||
|
ETrackedDeviceClass::TrackedDeviceClass_Controller => {
|
||||||
|
let sys_role = system.get_controller_role_for_tracked_device_index(index);
|
||||||
|
match sys_role {
|
||||||
|
ETrackedControllerRole::TrackedControllerRole_LeftHand => {
|
||||||
|
Some(TrackedDeviceRole::LeftHand)
|
||||||
|
}
|
||||||
|
ETrackedControllerRole::TrackedControllerRole_RightHand => {
|
||||||
|
Some(TrackedDeviceRole::RightHand)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ETrackedDeviceClass::TrackedDeviceClass_GenericTracker => {
|
||||||
|
Some(TrackedDeviceRole::Tracker)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(role) = maybe_role {
|
||||||
|
if let Some(device) = get_tracked_device(system, index, role) {
|
||||||
|
self.devices.push(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.devices.sort_by(|a, b| {
|
||||||
|
(a.role as u8)
|
||||||
|
.cmp(&(b.role as u8))
|
||||||
|
.then(a.index.0.cmp(&b.index.0))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tracked_device(
|
||||||
|
system: &mut SystemManager,
|
||||||
|
index: TrackedDeviceIndex,
|
||||||
|
role: TrackedDeviceRole,
|
||||||
|
) -> Option<TrackedDevice> {
|
||||||
|
let soc = system
|
||||||
|
.get_tracked_device_property(
|
||||||
|
index,
|
||||||
|
ETrackedDeviceProperty::Prop_DeviceBatteryPercentage_Float,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
let charging = if soc.is_some() {
|
||||||
|
system
|
||||||
|
.get_tracked_device_property(index, ETrackedDeviceProperty::Prop_DeviceIsCharging_Bool)
|
||||||
|
.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(TrackedDevice {
|
||||||
|
valid: true,
|
||||||
|
index,
|
||||||
|
soc,
|
||||||
|
charging,
|
||||||
|
role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_from_hmd(in_mat: &HmdMatrix34_t, out_mat: &mut Affine3A) {
|
||||||
|
out_mat.x_axis[0] = in_mat.m[0][0];
|
||||||
|
out_mat.x_axis[1] = in_mat.m[1][0];
|
||||||
|
out_mat.x_axis[2] = in_mat.m[2][0];
|
||||||
|
out_mat.y_axis[0] = in_mat.m[0][1];
|
||||||
|
out_mat.y_axis[1] = in_mat.m[1][1];
|
||||||
|
out_mat.y_axis[2] = in_mat.m[2][1];
|
||||||
|
out_mat.z_axis[0] = in_mat.m[0][2];
|
||||||
|
out_mat.z_axis[1] = in_mat.m[1][2];
|
||||||
|
out_mat.z_axis[2] = in_mat.m[2][2];
|
||||||
|
out_mat.w_axis[0] = in_mat.m[0][3];
|
||||||
|
out_mat.w_axis[1] = in_mat.m[1][3];
|
||||||
|
out_mat.w_axis[2] = in_mat.m[2][3];
|
||||||
|
}
|
||||||
100
src/backend/openvr/mod.rs
Normal file
100
src/backend/openvr/mod.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use std::{
|
||||||
|
path::Path,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::{error, info};
|
||||||
|
use ovr_overlay::{
|
||||||
|
sys::{ETrackedDeviceProperty, EVRApplicationType, EVREventType},
|
||||||
|
TrackedDeviceIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::common::InputState;
|
||||||
|
|
||||||
|
pub mod input;
|
||||||
|
pub mod overlay;
|
||||||
|
|
||||||
|
fn openvr_run() {
|
||||||
|
let app_type = EVRApplicationType::VRApplication_Overlay;
|
||||||
|
let Ok(context) = ovr_overlay::Context::init(app_type) else {
|
||||||
|
error!("Failed to initialize OpenVR");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut overlay = context.overlay_mngr();
|
||||||
|
let mut settings = context.settings_mngr();
|
||||||
|
let mut input = context.input_mngr();
|
||||||
|
let mut system = context.system_mngr();
|
||||||
|
let mut compositor = context.compositor_mngr();
|
||||||
|
|
||||||
|
let Ok(_) = input.set_action_manifest(Path::new("resources/actions.json")) else {
|
||||||
|
error!("Failed to set action manifest");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(mut input_state) = InputState::new(&mut input) else {
|
||||||
|
error!("Failed to initialize input");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(refresh_rate) = system.get_tracked_device_property::<f32>(TrackedDeviceIndex::HMD, ETrackedDeviceProperty::Prop_DisplayFrequency_Float) else {
|
||||||
|
error!("Failed to get display refresh rate");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let frame_time = (1000.0 / refresh_rate).floor() * 0.001;
|
||||||
|
let mut next_device_update = Instant::now();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
while let Some(event) = system.poll_next_event() {
|
||||||
|
match event.event_type {
|
||||||
|
EVREventType::VREvent_Quit => {
|
||||||
|
info!("Received quit event, shutting down.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EVREventType::VREvent_TrackedDeviceActivated
|
||||||
|
| EVREventType::VREvent_TrackedDeviceDeactivated
|
||||||
|
| EVREventType::VREvent_TrackedDeviceUpdated => {
|
||||||
|
next_device_update = Instant::now();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if next_device_update <= Instant::now() {
|
||||||
|
input_state.update_devices(&mut system);
|
||||||
|
next_device_update = Instant::now() + Duration::from_secs(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
input_state.pre_update();
|
||||||
|
input_state.update(&mut input, &mut system);
|
||||||
|
input_state.post_update();
|
||||||
|
|
||||||
|
// task scheduler
|
||||||
|
|
||||||
|
// after input
|
||||||
|
|
||||||
|
// interactions
|
||||||
|
|
||||||
|
// show overlays
|
||||||
|
|
||||||
|
// chaperone
|
||||||
|
|
||||||
|
// render overlays
|
||||||
|
|
||||||
|
// hide overlays
|
||||||
|
|
||||||
|
// close font handles
|
||||||
|
|
||||||
|
// playspace moved end frame
|
||||||
|
|
||||||
|
let mut seconds_since_vsync = 0f32;
|
||||||
|
std::thread::sleep(Duration::from_secs_f32(
|
||||||
|
if system.get_time_since_last_vsync(&mut seconds_since_vsync, &mut 0u64) {
|
||||||
|
frame_time - seconds_since_vsync
|
||||||
|
} else {
|
||||||
|
0.011
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/backend/openvr/overlay.rs
Normal file
15
src/backend/openvr/overlay.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use ovr_overlay::sys::VRVulkanTextureData_t;
|
||||||
|
|
||||||
|
use crate::overlays::OverlayData;
|
||||||
|
|
||||||
|
pub(super) struct OpenVrOverlayManager {
|
||||||
|
pub(super) overlays: Vec<OpenVrOverlay>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct OpenVrOverlay {
|
||||||
|
pub(super) visible: bool,
|
||||||
|
pub(super) color: [f32; 4],
|
||||||
|
overlay: OverlayData,
|
||||||
|
handle: u32,
|
||||||
|
ovr_texture: VRVulkanTextureData_t,
|
||||||
|
}
|
||||||
0
src/backend/openxr.rs
Normal file
0
src/backend/openxr.rs
Normal file
559
src/graphics.rs
Normal file
559
src/graphics.rs
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
use std::{sync::Arc, slice::Iter, io::Cursor, error::Error};
|
||||||
|
|
||||||
|
use log::{info,error};
|
||||||
|
use vulkano::{format::Format, device::{physical::PhysicalDeviceType, QueueFlags, DeviceExtensions, Device, DeviceCreateInfo, Features, QueueCreateInfo, Queue}, Version, VulkanLibrary, instance::{Instance, InstanceCreateInfo}, memory::allocator::{StandardMemoryAllocator, AllocationCreateInfo, MemoryUsage}, command_buffer::{allocator::StandardCommandBufferAllocator, CommandBufferUsage, AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, RenderingAttachmentInfo, RenderingInfo, PrimaryCommandBufferAbstract, CommandBufferExecFuture, SubpassContents, SecondaryAutoCommandBuffer, CommandBufferInheritanceInfo, CommandBufferInheritanceRenderPassType, CommandBufferInheritanceRenderingInfo}, descriptor_set::{allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet}, buffer::{Buffer, BufferCreateInfo, BufferUsage, BufferContents, Subbuffer, allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}}, sampler::{Filter, SamplerCreateInfo, SamplerAddressMode, Sampler}, pipeline::{GraphicsPipeline, Pipeline, graphics::{render_pass::PipelineRenderingCreateInfo, vertex_input::Vertex, input_assembly::InputAssemblyState, viewport::{ViewportState, Viewport}, color_blend::{ColorBlendState, AttachmentBlend}}, PipelineBindPoint}, image::{ImageViewAbstract, ImageUsage, SwapchainImage, ImageDimensions, ImmutableImage, MipmapsCount, StorageImage, ImageError, SubresourceData, ImageCreateFlags, AttachmentImage}, swapchain::{Surface, Swapchain, SwapchainCreateInfo, CompositeAlpha}, shader::ShaderModule, render_pass::{StoreOp, LoadOp}, sync::future::NowFuture};
|
||||||
|
use winit::{event_loop::EventLoop, window::{WindowBuilder, Window}};
|
||||||
|
use vulkano_win::VkSurfaceBuild;
|
||||||
|
use wlx_capture::frame::{DmabufFrame, DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(BufferContents, Vertex, Copy, Clone, Debug)]
|
||||||
|
pub struct Vert2Uv {
|
||||||
|
#[format(R32G32_SFLOAT)]
|
||||||
|
pub in_pos: [f32; 2],
|
||||||
|
#[format(R32G32_SFLOAT)]
|
||||||
|
pub in_uv: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const INDICES : [u16; 6] = [2, 1, 0, 1, 2, 3];
|
||||||
|
|
||||||
|
pub struct WlxGraphics {
|
||||||
|
pub instance: Arc<Instance>,
|
||||||
|
pub device: Arc<Device>,
|
||||||
|
pub queue: Arc<Queue>,
|
||||||
|
|
||||||
|
pub surface: Arc<Surface>,
|
||||||
|
|
||||||
|
pub memory_allocator: Arc<StandardMemoryAllocator>,
|
||||||
|
pub command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
||||||
|
pub descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
||||||
|
|
||||||
|
pub quad_indices: Subbuffer<[u16]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlxGraphics {
|
||||||
|
pub fn new() -> (Arc<Self>, EventLoop<()>) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let layers = vec!["VK_LAYER_KHRONOS_validation".to_owned()];
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let layers = vec![];
|
||||||
|
|
||||||
|
let library = VulkanLibrary::new().unwrap();
|
||||||
|
let required_extensions = vulkano_win::required_extensions(&library);
|
||||||
|
|
||||||
|
let instance = Instance::new(
|
||||||
|
library,
|
||||||
|
InstanceCreateInfo {
|
||||||
|
enabled_extensions: required_extensions,
|
||||||
|
enabled_layers: layers,
|
||||||
|
enumerate_portability: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut device_extensions = DeviceExtensions {
|
||||||
|
khr_swapchain: true,
|
||||||
|
khr_external_memory: true,
|
||||||
|
khr_external_memory_fd: true,
|
||||||
|
ext_external_memory_dma_buf: true,
|
||||||
|
ext_image_drm_format_modifier: true,
|
||||||
|
..DeviceExtensions::empty()
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO headless
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let surface = WindowBuilder::new()
|
||||||
|
.build_vk_surface(&event_loop, instance.clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let (physical_device, queue_family_index) = instance
|
||||||
|
.enumerate_physical_devices()
|
||||||
|
.unwrap()
|
||||||
|
.filter(|p| {
|
||||||
|
p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering
|
||||||
|
})
|
||||||
|
.filter(|p| {
|
||||||
|
p.supported_extensions().contains(&device_extensions)
|
||||||
|
})
|
||||||
|
.filter_map(|p| {
|
||||||
|
p.queue_family_properties()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.position(|(i, q)| {
|
||||||
|
q.queue_flags.intersects(QueueFlags::GRAPHICS)
|
||||||
|
&& p.surface_support(i as u32, &surface).unwrap_or(false)
|
||||||
|
})
|
||||||
|
.map(|i| (p, i as u32))
|
||||||
|
})
|
||||||
|
.min_by_key(|(p, _)| {
|
||||||
|
match p.properties().device_type {
|
||||||
|
PhysicalDeviceType::DiscreteGpu => 0,
|
||||||
|
PhysicalDeviceType::IntegratedGpu => 1,
|
||||||
|
PhysicalDeviceType::VirtualGpu => 2,
|
||||||
|
PhysicalDeviceType::Cpu => 3,
|
||||||
|
PhysicalDeviceType::Other => 4,
|
||||||
|
_ => 5,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("no suitable physical device found");
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Nice {} you have there.",
|
||||||
|
physical_device.properties().device_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
if physical_device.api_version() < Version::V1_3 {
|
||||||
|
device_extensions.khr_dynamic_rendering = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (device, mut queues) = Device::new(
|
||||||
|
physical_device,
|
||||||
|
DeviceCreateInfo {
|
||||||
|
enabled_extensions: device_extensions,
|
||||||
|
enabled_features: Features {
|
||||||
|
dynamic_rendering: true,
|
||||||
|
..Features::empty()
|
||||||
|
},
|
||||||
|
queue_create_infos: vec![QueueCreateInfo {
|
||||||
|
queue_family_index,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let queue = queues.next().unwrap();
|
||||||
|
|
||||||
|
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
|
||||||
|
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(device.clone(), Default::default()));
|
||||||
|
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(device.clone()));
|
||||||
|
|
||||||
|
let quad_indices = Buffer::from_iter(
|
||||||
|
&memory_allocator,
|
||||||
|
BufferCreateInfo {
|
||||||
|
usage: BufferUsage::INDEX_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo {
|
||||||
|
usage: MemoryUsage::Upload,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
INDICES.iter().cloned(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let me = Self {
|
||||||
|
instance,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
surface,
|
||||||
|
memory_allocator,
|
||||||
|
command_buffer_allocator,
|
||||||
|
descriptor_set_allocator,
|
||||||
|
quad_indices,
|
||||||
|
};
|
||||||
|
|
||||||
|
(Arc::new(me), event_loop)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_swapchain(&self, format: Option<Format>) -> (Arc<Swapchain>, Vec<Arc<SwapchainImage>>) {
|
||||||
|
let (min_image_count, composite_alpha, image_format) = if let Some(format) = format {
|
||||||
|
(1, CompositeAlpha::Opaque, format)
|
||||||
|
} else {
|
||||||
|
let surface_capabilities = self.device
|
||||||
|
.physical_device()
|
||||||
|
.surface_capabilities(&self.surface, Default::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let composite_alpha = surface_capabilities.supported_composite_alpha.into_iter().next().unwrap();
|
||||||
|
|
||||||
|
let image_format = Some(
|
||||||
|
self.device
|
||||||
|
.physical_device()
|
||||||
|
.surface_formats(&self.surface, Default::default())
|
||||||
|
.unwrap()[0]
|
||||||
|
.0,
|
||||||
|
);
|
||||||
|
(surface_capabilities.min_image_count, composite_alpha, image_format.unwrap())
|
||||||
|
};
|
||||||
|
let window = self.surface.object().unwrap().downcast_ref::<Window>().unwrap();
|
||||||
|
let swapchain = Swapchain::new(
|
||||||
|
self.device.clone(),
|
||||||
|
self.surface.clone(),
|
||||||
|
SwapchainCreateInfo {
|
||||||
|
min_image_count,
|
||||||
|
image_format: Some(image_format),
|
||||||
|
image_extent: window.inner_size().into(),
|
||||||
|
image_usage: ImageUsage::COLOR_ATTACHMENT,
|
||||||
|
composite_alpha,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
swapchain
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_verts(&self, width: f32, height: f32, x: f32, y: f32, w: f32, h: f32) -> Subbuffer<[Vert2Uv]> {
|
||||||
|
let rw = width;
|
||||||
|
let rh = height;
|
||||||
|
|
||||||
|
let x0 = x / rw;
|
||||||
|
let y0 = y / rh;
|
||||||
|
|
||||||
|
let x1 = w / rw + x0;
|
||||||
|
let y1 = h / rh + y0;
|
||||||
|
|
||||||
|
let vertices = [
|
||||||
|
Vert2Uv { in_pos: [x0, y0], in_uv: [0.0, 0.0] },
|
||||||
|
Vert2Uv { in_pos: [x0, y1], in_uv: [0.0, 1.0] },
|
||||||
|
Vert2Uv { in_pos: [x1, y0], in_uv: [1.0, 0.0] },
|
||||||
|
Vert2Uv { in_pos: [x1, y1], in_uv: [1.0, 1.0] },
|
||||||
|
];
|
||||||
|
self.upload_buffer(BufferUsage::VERTEX_BUFFER, vertices.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_buffer<T>(&self, usage: BufferUsage, contents: Iter<'_, T>) -> Subbuffer<[T]>
|
||||||
|
where T: BufferContents + Clone {
|
||||||
|
Buffer::from_iter(
|
||||||
|
&self.memory_allocator,
|
||||||
|
BufferCreateInfo {
|
||||||
|
usage,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo {
|
||||||
|
usage: MemoryUsage::Upload,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
contents.cloned(),
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dmabuf_texture(&self, frame: DmabufFrame) -> Result<Arc<StorageImage>, ImageError> {
|
||||||
|
let dimensions = ImageDimensions::Dim2d {
|
||||||
|
width: frame.format.width,
|
||||||
|
height: frame.format.height,
|
||||||
|
array_layers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let format = match frame.format.fourcc {
|
||||||
|
DRM_FORMAT_ABGR8888 => Format::R8G8B8A8_UNORM,
|
||||||
|
DRM_FORMAT_XBGR8888 => Format::R8G8B8A8_UNORM,
|
||||||
|
DRM_FORMAT_ARGB8888 => Format::B8G8R8A8_UNORM,
|
||||||
|
DRM_FORMAT_XRGB8888 => Format::B8G8R8A8_UNORM,
|
||||||
|
_ => panic!("Unsupported dmabuf format {:x}", frame.format.fourcc),
|
||||||
|
};
|
||||||
|
|
||||||
|
let planes = frame.planes
|
||||||
|
.iter()
|
||||||
|
.take(frame.num_planes)
|
||||||
|
.filter_map(|plane| {
|
||||||
|
let Some(fd) = plane.fd else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(SubresourceData {
|
||||||
|
fd,
|
||||||
|
offset: plane.offset as _,
|
||||||
|
row_pitch: plane.stride as _,
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
StorageImage::new_from_dma_buf_fd(
|
||||||
|
&self.memory_allocator,
|
||||||
|
self.device.clone(),
|
||||||
|
dimensions,
|
||||||
|
format,
|
||||||
|
ImageUsage::SAMPLED | ImageUsage::TRANSFER_SRC,
|
||||||
|
ImageCreateFlags::empty(),
|
||||||
|
[self.queue.queue_family_index()],
|
||||||
|
planes,
|
||||||
|
frame.format.modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn render_texture(&self, width: u32, height: u32, format: Format) -> Arc<AttachmentImage> {
|
||||||
|
let tex = AttachmentImage::with_usage(
|
||||||
|
&self.memory_allocator,
|
||||||
|
[width, height],
|
||||||
|
format,
|
||||||
|
ImageUsage::SAMPLED | ImageUsage::TRANSFER_SRC | ImageUsage::COLOR_ATTACHMENT,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
tex
|
||||||
|
}
|
||||||
|
pub fn create_pipeline(self: &Arc<Self>, vert: Arc<ShaderModule>, frag: Arc<ShaderModule>, format: Format) -> Arc<WlxPipeline> {
|
||||||
|
Arc::new(WlxPipeline::new(self.clone(), vert, frag, format))
|
||||||
|
}
|
||||||
|
pub fn create_command_buffer(self: &Arc<Self>, usage: CommandBufferUsage) -> WlxCommandBuffer<PrimaryAutoCommandBuffer> {
|
||||||
|
let command_buffer = AutoCommandBufferBuilder::primary(
|
||||||
|
&self.command_buffer_allocator,
|
||||||
|
self.queue.queue_family_index(),
|
||||||
|
usage,
|
||||||
|
).unwrap();
|
||||||
|
WlxCommandBuffer { graphics: self.clone(), command_buffer }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WlxCommandBuffer<T> {
|
||||||
|
graphics: Arc<WlxGraphics>,
|
||||||
|
command_buffer: AutoCommandBufferBuilder<T, Arc<StandardCommandBufferAllocator>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WlxCommandBuffer<T> {
|
||||||
|
pub fn inner(&self) -> &AutoCommandBufferBuilder<T, Arc<StandardCommandBufferAllocator>> {
|
||||||
|
&self.command_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_mut(&mut self) -> &mut AutoCommandBufferBuilder<T, Arc<StandardCommandBufferAllocator>> {
|
||||||
|
&mut self.command_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_inner(self) -> AutoCommandBufferBuilder<T, Arc<StandardCommandBufferAllocator>> {
|
||||||
|
self.command_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin(mut self, render_target: Arc<dyn ImageViewAbstract>) -> Self
|
||||||
|
{
|
||||||
|
self.command_buffer
|
||||||
|
.begin_rendering(RenderingInfo {
|
||||||
|
contents: SubpassContents::SecondaryCommandBuffers,
|
||||||
|
color_attachments: vec![Some(RenderingAttachmentInfo {
|
||||||
|
load_op: LoadOp::Clear,
|
||||||
|
store_op: StoreOp::Store,
|
||||||
|
clear_value: Some([0.0, 0.0, 0.0, 0.0].into()),
|
||||||
|
..RenderingAttachmentInfo::image_view(
|
||||||
|
render_target.clone(),
|
||||||
|
)
|
||||||
|
})],
|
||||||
|
..Default::default()
|
||||||
|
}).unwrap();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_ref(&mut self, pass: &WlxPass) -> &mut Self
|
||||||
|
{
|
||||||
|
let _ = self.command_buffer.execute_commands(pass.command_buffer.clone()).unwrap();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(mut self, pass: &WlxPass) -> Self
|
||||||
|
{
|
||||||
|
let _ = self.command_buffer.execute_commands(pass.command_buffer.clone());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture2d(&mut self, width: u32, height: u32, format: Format, data: Vec<u8>) -> Arc<ImmutableImage> {
|
||||||
|
let dimensions = ImageDimensions::Dim2d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
array_layers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
ImmutableImage::from_iter(
|
||||||
|
&self.graphics.memory_allocator,
|
||||||
|
data,
|
||||||
|
dimensions,
|
||||||
|
MipmapsCount::One,
|
||||||
|
format,
|
||||||
|
&mut self.command_buffer,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture2d_png(&mut self, bytes: Vec<u8>) -> Arc<ImmutableImage> {
|
||||||
|
let cursor = Cursor::new(bytes);
|
||||||
|
let decoder = png::Decoder::new(cursor);
|
||||||
|
let mut reader = decoder.read_info().unwrap();
|
||||||
|
let info = reader.info();
|
||||||
|
let width = info.width;
|
||||||
|
let height = info.height;
|
||||||
|
let mut image_data = Vec::new();
|
||||||
|
image_data.resize((info.width * info.height * 4) as usize, 0);
|
||||||
|
reader.next_frame(&mut image_data).unwrap();
|
||||||
|
self.texture2d(width, height, Format::R8G8B8A8_UNORM, image_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlxCommandBuffer<PrimaryAutoCommandBuffer> {
|
||||||
|
pub fn end_render_and_continue(&mut self) {
|
||||||
|
self.command_buffer.end_rendering().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_render(self) -> PrimaryAutoCommandBuffer {
|
||||||
|
let mut buf = self.command_buffer;
|
||||||
|
buf.end_rendering().unwrap();
|
||||||
|
|
||||||
|
buf.build().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end(self) -> PrimaryAutoCommandBuffer {
|
||||||
|
self.command_buffer.build().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_render_and_execute(self) -> CommandBufferExecFuture<NowFuture> {
|
||||||
|
let mut buf = self.command_buffer;
|
||||||
|
buf.end_rendering().unwrap();
|
||||||
|
let buf = buf.build().unwrap();
|
||||||
|
buf.execute(self.graphics.queue.clone()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_and_execute(self) -> CommandBufferExecFuture<NowFuture> {
|
||||||
|
let buf = self.command_buffer;
|
||||||
|
let buf = buf.build().unwrap();
|
||||||
|
buf.execute(self.graphics.queue.clone()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WlxPipeline {
|
||||||
|
pub graphics: Arc<WlxGraphics>,
|
||||||
|
pub pipeline: Arc<GraphicsPipeline>,
|
||||||
|
pub format: Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlxPipeline {
|
||||||
|
fn new(graphics: Arc<WlxGraphics>, vert: Arc<ShaderModule>, frag: Arc<ShaderModule>, format: Format) -> Self {
|
||||||
|
let vep = vert.entry_point("main").unwrap();
|
||||||
|
let fep = frag.entry_point("main").unwrap();
|
||||||
|
let pipeline = GraphicsPipeline::start()
|
||||||
|
.render_pass(PipelineRenderingCreateInfo {
|
||||||
|
color_attachment_formats: vec![Some(format)],
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.color_blend_state(ColorBlendState::default().blend(
|
||||||
|
AttachmentBlend::alpha()
|
||||||
|
))
|
||||||
|
.vertex_input_state(Vert2Uv::per_vertex())
|
||||||
|
.input_assembly_state(InputAssemblyState::new())
|
||||||
|
.vertex_shader(vep, ())
|
||||||
|
.viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
|
||||||
|
.fragment_shader(fep, ())
|
||||||
|
.build(graphics.device.clone())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self { graphics, pipeline, format}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> Arc<GraphicsPipeline> {
|
||||||
|
self.pipeline.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics(&self) -> Arc<WlxGraphics> {
|
||||||
|
self.graphics.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uniform_sampler(&self, set: usize, texture: Arc<dyn ImageViewAbstract>, filter: Filter) -> Arc<PersistentDescriptorSet> {
|
||||||
|
let sampler = Sampler::new(
|
||||||
|
self.graphics.device.clone(),
|
||||||
|
SamplerCreateInfo {
|
||||||
|
mag_filter: filter,
|
||||||
|
min_filter: filter,
|
||||||
|
address_mode: [SamplerAddressMode::Repeat; 3],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let layout = self.pipeline.layout().set_layouts().get(set).unwrap();
|
||||||
|
|
||||||
|
PersistentDescriptorSet::new(
|
||||||
|
&self.graphics.descriptor_set_allocator,
|
||||||
|
layout.clone(),
|
||||||
|
[WriteDescriptorSet::image_view_sampler(0, texture, sampler)],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uniform_buffer<T>(&self, set: usize, data: Vec<T>) -> Arc<PersistentDescriptorSet>
|
||||||
|
where T: BufferContents + Copy {
|
||||||
|
let uniform_buffer = SubbufferAllocator::new(
|
||||||
|
self.graphics.memory_allocator.clone(),
|
||||||
|
SubbufferAllocatorCreateInfo {
|
||||||
|
buffer_usage: BufferUsage::UNIFORM_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let uniform_buffer_subbuffer = {
|
||||||
|
let subbuffer = uniform_buffer.allocate_slice(data.len() as _).unwrap();
|
||||||
|
subbuffer.write().unwrap().copy_from_slice(data.as_slice());
|
||||||
|
subbuffer
|
||||||
|
};
|
||||||
|
|
||||||
|
let layout = self.pipeline.layout().set_layouts().get(set).unwrap();
|
||||||
|
PersistentDescriptorSet::new(
|
||||||
|
&self.graphics.descriptor_set_allocator,
|
||||||
|
layout.clone(),
|
||||||
|
[WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)]
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_pass(self: &Arc<Self>, dimensions: [f32; 2], vertex_buffer: Subbuffer<[Vert2Uv]>, index_buffer: Subbuffer<[u16]>, descriptor_sets: Vec<Arc<PersistentDescriptorSet>>) -> WlxPass {
|
||||||
|
WlxPass::new(self.clone(), dimensions, vertex_buffer, index_buffer, descriptor_sets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WlxPass
|
||||||
|
{
|
||||||
|
pipeline: Arc<WlxPipeline>,
|
||||||
|
vertex_buffer: Subbuffer<[Vert2Uv]>,
|
||||||
|
index_buffer: Subbuffer<[u16]>,
|
||||||
|
descriptor_sets: Vec<Arc<PersistentDescriptorSet>>,
|
||||||
|
pub command_buffer: Arc<SecondaryAutoCommandBuffer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlxPass
|
||||||
|
{
|
||||||
|
fn new(pipeline: Arc<WlxPipeline>, dimensions: [f32; 2], vertex_buffer: Subbuffer<[Vert2Uv]>, index_buffer: Subbuffer<[u16]>, descriptor_sets: Vec<Arc<PersistentDescriptorSet>>) -> Self {
|
||||||
|
let viewport = Viewport {
|
||||||
|
origin: [0.0, 0.0],
|
||||||
|
dimensions,
|
||||||
|
depth_range: 0.0..1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pipeline_inner = pipeline.inner().clone();
|
||||||
|
let mut command_buffer = AutoCommandBufferBuilder::secondary(
|
||||||
|
&pipeline.graphics.command_buffer_allocator,
|
||||||
|
pipeline.graphics.queue.queue_family_index(),
|
||||||
|
CommandBufferUsage::MultipleSubmit,
|
||||||
|
CommandBufferInheritanceInfo {
|
||||||
|
render_pass: Some(CommandBufferInheritanceRenderPassType::BeginRendering(
|
||||||
|
CommandBufferInheritanceRenderingInfo {
|
||||||
|
color_attachment_formats: vec![Some(pipeline.format)],
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
command_buffer.set_viewport(0, [viewport])
|
||||||
|
.bind_pipeline_graphics(pipeline_inner)
|
||||||
|
.bind_descriptor_sets(
|
||||||
|
PipelineBindPoint::Graphics,
|
||||||
|
pipeline.inner().layout().clone(),
|
||||||
|
0,
|
||||||
|
descriptor_sets.clone(),
|
||||||
|
)
|
||||||
|
.bind_vertex_buffers(0, vertex_buffer.clone())
|
||||||
|
.bind_index_buffer(index_buffer.clone())
|
||||||
|
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)
|
||||||
|
.or_else(|err| {
|
||||||
|
if let Some(source) = err.source() {
|
||||||
|
error!("Failed to draw: {}", source);
|
||||||
|
}
|
||||||
|
Err(err)
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pipeline,
|
||||||
|
vertex_buffer,
|
||||||
|
index_buffer,
|
||||||
|
descriptor_sets,
|
||||||
|
command_buffer: Arc::new(command_buffer.build().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1197
src/graphics.rs.bak2
Normal file
1197
src/graphics.rs.bak2
Normal file
File diff suppressed because it is too large
Load Diff
211
src/gui/font.rs
Normal file
211
src/gui/font.rs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
use std::{rc::Rc, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
|
use fontconfig::{FontConfig, OwnedPattern};
|
||||||
|
use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library};
|
||||||
|
use idmap::IdMap;
|
||||||
|
use log::debug;
|
||||||
|
use vulkano::{format::Format, command_buffer::CommandBufferUsage, image::ImmutableImage};
|
||||||
|
|
||||||
|
use crate::graphics::WlxGraphics;
|
||||||
|
|
||||||
|
const PRIMARY_FONT: &str = "LiberationSans";
|
||||||
|
|
||||||
|
pub struct FontCache {
|
||||||
|
fc: FontConfig,
|
||||||
|
ft: Library,
|
||||||
|
collections: IdMap<isize, FontCollection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontCollection {
|
||||||
|
fonts: Vec<Font>,
|
||||||
|
cp_map: IdMap<usize, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Font {
|
||||||
|
face: Face,
|
||||||
|
path: String,
|
||||||
|
index: isize,
|
||||||
|
size: isize,
|
||||||
|
glyphs: IdMap<usize, Rc<Glyph>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Glyph {
|
||||||
|
pub tex: Option<Arc<ImmutableImage>>,
|
||||||
|
pub top: f32,
|
||||||
|
pub left: f32,
|
||||||
|
pub width: f32,
|
||||||
|
pub height: f32,
|
||||||
|
pub advance: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontCache {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let ft = Library::init().expect("Failed to initialize freetype");
|
||||||
|
let fc = FontConfig::default();
|
||||||
|
|
||||||
|
FontCache {
|
||||||
|
fc,
|
||||||
|
ft,
|
||||||
|
collections: IdMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_text_size(&mut self, text: &str, size: isize, graphics: Arc<WlxGraphics>) -> (f32, f32) {
|
||||||
|
let sizef = size as f32;
|
||||||
|
|
||||||
|
let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5);
|
||||||
|
|
||||||
|
let mut max_w = sizef * 0.33;
|
||||||
|
for line in text.lines() {
|
||||||
|
let w: f32 = line
|
||||||
|
.chars()
|
||||||
|
.map(|c| self.get_glyph_for_cp(c as usize, size, graphics.clone()).advance)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
if w > max_w {
|
||||||
|
max_w = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(max_w, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_glyphs(&mut self, text: &str, size: isize, graphics: Arc<WlxGraphics>) -> Vec<Rc<Glyph>> {
|
||||||
|
let mut glyphs = Vec::new();
|
||||||
|
for line in text.lines() {
|
||||||
|
for c in line.chars() {
|
||||||
|
glyphs.push(self.get_glyph_for_cp(c as usize, size, graphics.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glyphs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_font_for_cp(&mut self, cp: usize, size: isize) -> usize {
|
||||||
|
if !self.collections.contains_key(size) {
|
||||||
|
self.collections.insert(
|
||||||
|
size,
|
||||||
|
FontCollection {
|
||||||
|
fonts: Vec::new(),
|
||||||
|
cp_map: IdMap::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let coll = self.collections.get_mut(size).unwrap();
|
||||||
|
|
||||||
|
if let Some(font) = coll.cp_map.get(cp) {
|
||||||
|
return *font;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern_str = format!("{PRIMARY_FONT}-{size}:style=bold:charset={cp:04x}");
|
||||||
|
|
||||||
|
let mut pattern =
|
||||||
|
OwnedPattern::from_str(&pattern_str).expect("Failed to create fontconfig pattern");
|
||||||
|
self.fc
|
||||||
|
.substitute(&mut pattern, fontconfig::MatchKind::Pattern);
|
||||||
|
pattern.default_substitute();
|
||||||
|
|
||||||
|
let pattern = pattern.font_match(&mut self.fc);
|
||||||
|
|
||||||
|
if let Some(path) = pattern.filename() {
|
||||||
|
debug!(
|
||||||
|
"Loading font: {} {}pt",
|
||||||
|
pattern.name().unwrap_or(path),
|
||||||
|
size
|
||||||
|
);
|
||||||
|
|
||||||
|
let font_idx = pattern.face_index().unwrap_or(0);
|
||||||
|
|
||||||
|
let face = self
|
||||||
|
.ft
|
||||||
|
.new_face(path, font_idx as _)
|
||||||
|
.expect("Failed to load font face");
|
||||||
|
face.set_char_size(size << 6, size << 6, 96, 96)
|
||||||
|
.expect("Failed to set font size");
|
||||||
|
|
||||||
|
let idx = coll.fonts.len();
|
||||||
|
for cp in 0..0xFFFF {
|
||||||
|
if coll.cp_map.contains_key(cp) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let g = face.get_char_index(cp);
|
||||||
|
if g > 0 {
|
||||||
|
coll.cp_map.insert(cp, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let zero_glyph = Rc::new(Glyph {
|
||||||
|
tex: None,
|
||||||
|
top: 0.,
|
||||||
|
left: 0.,
|
||||||
|
width: 0.,
|
||||||
|
height: 0.,
|
||||||
|
advance: size as f32 / 3.,
|
||||||
|
});
|
||||||
|
let mut glyphs = IdMap::new();
|
||||||
|
glyphs.insert(0, zero_glyph);
|
||||||
|
|
||||||
|
let font = Font {
|
||||||
|
face,
|
||||||
|
path: path.to_string(),
|
||||||
|
size,
|
||||||
|
index: font_idx as _,
|
||||||
|
glyphs,
|
||||||
|
};
|
||||||
|
coll.fonts.push(font);
|
||||||
|
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
coll.cp_map.insert(cp, 0);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_glyph_for_cp(&mut self, cp: usize, size: isize, graphics: Arc<WlxGraphics>) -> Rc<Glyph> {
|
||||||
|
let key = self.get_font_for_cp(cp, size);
|
||||||
|
|
||||||
|
let font = &mut self.collections[size].fonts[key];
|
||||||
|
|
||||||
|
if let Some(glyph) = font.glyphs.get(cp) {
|
||||||
|
return glyph.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if font.face.load_char(cp, LoadFlag::DEFAULT).is_err() {
|
||||||
|
return font.glyphs[0].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let glyph = font.face.glyph();
|
||||||
|
if glyph.render_glyph(freetype::RenderMode::Normal).is_err() {
|
||||||
|
return font.glyphs[0].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let bmp = glyph.bitmap();
|
||||||
|
let buf = bmp.buffer().to_vec();
|
||||||
|
if buf.len() == 0 {
|
||||||
|
return font.glyphs[0].clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let metrics = glyph.metrics();
|
||||||
|
|
||||||
|
let format = match bmp.pixel_mode() {
|
||||||
|
Ok(PixelMode::Gray) => Format::R8_UNORM,
|
||||||
|
Ok(PixelMode::Gray2) => Format::R16_SFLOAT,
|
||||||
|
Ok(PixelMode::Gray4) => Format::R32_SFLOAT,
|
||||||
|
_ => return font.glyphs[0].clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit);
|
||||||
|
let texture = cmd_buffer.texture2d(bmp.width() as _, bmp.rows() as _, format, buf);
|
||||||
|
let _ = cmd_buffer.end_and_execute();
|
||||||
|
|
||||||
|
let g = Glyph {
|
||||||
|
tex: Some(texture),
|
||||||
|
top: (metrics.horiBearingY >> 6i64) as _,
|
||||||
|
left: (metrics.horiBearingX >> 6i64) as _,
|
||||||
|
advance: (metrics.horiAdvance >> 6i64) as _,
|
||||||
|
width: bmp.width() as _,
|
||||||
|
height: bmp.rows() as _,
|
||||||
|
};
|
||||||
|
|
||||||
|
font.glyphs.insert(cp, Rc::new(g));
|
||||||
|
font.glyphs[cp].clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
669
src/gui/mod.rs
Normal file
669
src/gui/mod.rs
Normal file
@@ -0,0 +1,669 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use glam::{Vec2, Vec3};
|
||||||
|
use vulkano::{
|
||||||
|
command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer},
|
||||||
|
format::Format,
|
||||||
|
image::{view::ImageView, AttachmentImage},
|
||||||
|
sampler::Filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
graphics::{WlxCommandBuffer, WlxGraphics, WlxPass, WlxPipeline},
|
||||||
|
overlays::{
|
||||||
|
interactions::{InteractionHandler, PointerHit},
|
||||||
|
OverlayBackend, OverlayRenderer,
|
||||||
|
},
|
||||||
|
shaders::{frag_color, frag_glyph, frag_sprite, vert_common},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod font;
|
||||||
|
|
||||||
|
const RES_DIVIDER: usize = 4;
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
w: f32,
|
||||||
|
h: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a color from a HTML hex string
|
||||||
|
pub fn color_parse(html_hex: &str) -> Vec3 {
|
||||||
|
let mut color = Vec3::ZERO;
|
||||||
|
color.x = u8::from_str_radix(&html_hex[1..3], 16).unwrap() as f32 / 255.;
|
||||||
|
color.y = u8::from_str_radix(&html_hex[3..5], 16).unwrap() as f32 / 255.;
|
||||||
|
color.z = u8::from_str_radix(&html_hex[5..7], 16).unwrap() as f32 / 255.;
|
||||||
|
color
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CanvasBuilder<D, S> {
|
||||||
|
canvas: Canvas<D, S>,
|
||||||
|
|
||||||
|
pub fg_color: Vec3,
|
||||||
|
pub bg_color: Vec3,
|
||||||
|
pub font_size: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, S> CanvasBuilder<D, S> {
|
||||||
|
pub fn new(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
graphics: Arc<WlxGraphics>,
|
||||||
|
format: Format,
|
||||||
|
data: D,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
canvas: Canvas::new(width, height, graphics, format, data),
|
||||||
|
bg_color: Vec3::ZERO,
|
||||||
|
fg_color: Vec3::ONE,
|
||||||
|
font_size: 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Canvas<D, S> {
|
||||||
|
self.canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a panel with bg_color inherited from the canvas
|
||||||
|
pub fn panel(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control<D, S> {
|
||||||
|
let idx = self.canvas.controls.len();
|
||||||
|
self.canvas.controls.push(Control {
|
||||||
|
rect: Rect { x, y, w, h },
|
||||||
|
bg_color: self.bg_color,
|
||||||
|
on_render_bg: Some(Control::render_rect),
|
||||||
|
..Control::new()
|
||||||
|
});
|
||||||
|
&mut self.canvas.controls[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a label with fg_color, font_size inherited from the canvas
|
||||||
|
pub fn label(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc<str>) -> &mut Control<D, S> {
|
||||||
|
let idx = self.canvas.controls.len();
|
||||||
|
self.canvas.controls.push(Control {
|
||||||
|
rect: Rect { x, y, w, h },
|
||||||
|
text,
|
||||||
|
fg_color: self.fg_color,
|
||||||
|
size: self.font_size,
|
||||||
|
on_render_fg: Some(Control::render_text),
|
||||||
|
..Control::new()
|
||||||
|
});
|
||||||
|
&mut self.canvas.controls[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a label with fg_color, font_size inherited from the canvas
|
||||||
|
pub fn label_centered(
|
||||||
|
&mut self,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
w: f32,
|
||||||
|
h: f32,
|
||||||
|
text: Arc<str>,
|
||||||
|
) -> &mut Control<D, S> {
|
||||||
|
let idx = self.canvas.controls.len();
|
||||||
|
self.canvas.controls.push(Control {
|
||||||
|
rect: Rect { x, y, w, h },
|
||||||
|
text,
|
||||||
|
fg_color: self.fg_color,
|
||||||
|
size: self.font_size,
|
||||||
|
on_render_fg: Some(Control::render_text_centered),
|
||||||
|
..Control::new()
|
||||||
|
});
|
||||||
|
&mut self.canvas.controls[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a button with fg_color, bg_color, font_size inherited from the canvas
|
||||||
|
pub fn button(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc<str>) -> &mut Control<D, S> {
|
||||||
|
let idx = self.canvas.controls.len();
|
||||||
|
|
||||||
|
self.canvas.interactive_set_idx(x, y, w, h, idx);
|
||||||
|
self.canvas.controls.push(Control {
|
||||||
|
rect: Rect { x, y, w, h },
|
||||||
|
text,
|
||||||
|
fg_color: self.fg_color,
|
||||||
|
bg_color: self.bg_color,
|
||||||
|
size: self.font_size,
|
||||||
|
on_render_bg: Some(Control::render_rect),
|
||||||
|
on_render_fg: Some(Control::render_text_centered),
|
||||||
|
on_render_hl: Some(Control::render_highlight),
|
||||||
|
..Control::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
&mut self.canvas.controls[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_button(
|
||||||
|
&mut self,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
w: f32,
|
||||||
|
h: f32,
|
||||||
|
label: &Vec<String>,
|
||||||
|
) -> &mut Control<D, S> {
|
||||||
|
let idx = self.canvas.controls.len();
|
||||||
|
self.canvas.interactive_set_idx(x, y, w, h, idx);
|
||||||
|
|
||||||
|
self.canvas.controls.push(Control {
|
||||||
|
rect: Rect { x, y, w, h },
|
||||||
|
bg_color: self.bg_color,
|
||||||
|
on_render_bg: Some(Control::render_rect),
|
||||||
|
on_render_hl: Some(Control::render_highlight),
|
||||||
|
..Control::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
for (i, item) in label.iter().enumerate().take(label.len().min(2)) {
|
||||||
|
self.canvas.controls.push(Control {
|
||||||
|
rect: if i == 0 {
|
||||||
|
Rect {
|
||||||
|
x: x + 4.,
|
||||||
|
y: y + (self.font_size as f32) + 4.,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Rect {
|
||||||
|
x: x + w * 0.5,
|
||||||
|
y: y + h - (self.font_size as f32) + 4.,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: Arc::from(item.as_str()),
|
||||||
|
fg_color: self.fg_color,
|
||||||
|
size: self.font_size,
|
||||||
|
on_render_fg: Some(Control::render_text),
|
||||||
|
..Control::new()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
&mut self.canvas.controls[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CanvasData<D> {
|
||||||
|
pub data: D,
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
|
||||||
|
graphics: Arc<WlxGraphics>,
|
||||||
|
|
||||||
|
pipeline_color: Arc<WlxPipeline>,
|
||||||
|
pipeline_glyph: Arc<WlxPipeline>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Canvas<D, S> {
|
||||||
|
controls: Vec<Control<D, S>>,
|
||||||
|
canvas: CanvasData<D>,
|
||||||
|
|
||||||
|
hover_controls: [Option<usize>; 2],
|
||||||
|
pressed_controls: [Option<usize>; 2],
|
||||||
|
|
||||||
|
interact_map: Vec<Option<u8>>,
|
||||||
|
interact_stride: usize,
|
||||||
|
interact_rows: usize,
|
||||||
|
|
||||||
|
tex_fg: Arc<AttachmentImage>,
|
||||||
|
view_fg: Arc<ImageView<AttachmentImage>>,
|
||||||
|
tex_bg: Arc<AttachmentImage>,
|
||||||
|
view_bg: Arc<ImageView<AttachmentImage>>,
|
||||||
|
tex_final: Arc<AttachmentImage>,
|
||||||
|
view_final: Arc<ImageView<AttachmentImage>>,
|
||||||
|
|
||||||
|
pass_fg: WlxPass,
|
||||||
|
pass_bg: WlxPass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, S> Canvas<D, S> {
|
||||||
|
fn new(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
graphics: Arc<WlxGraphics>,
|
||||||
|
format: Format,
|
||||||
|
data: D,
|
||||||
|
) -> Self {
|
||||||
|
let pipeline_color = graphics.create_pipeline(
|
||||||
|
vert_common::load(graphics.device.clone()).unwrap(),
|
||||||
|
frag_color::load(graphics.device.clone()).unwrap(),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pipeline_glyph = graphics.create_pipeline(
|
||||||
|
vert_common::load(graphics.device.clone()).unwrap(),
|
||||||
|
frag_glyph::load(graphics.device.clone()).unwrap(),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
|
||||||
|
let vertex_buffer =
|
||||||
|
graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _);
|
||||||
|
|
||||||
|
let pipeline = graphics.create_pipeline(
|
||||||
|
vert_common::load(graphics.device.clone()).unwrap(),
|
||||||
|
frag_sprite::load(graphics.device.clone()).unwrap(),
|
||||||
|
format,
|
||||||
|
);
|
||||||
|
|
||||||
|
let tex_fg = graphics.render_texture(width as _, height as _, format);
|
||||||
|
let tex_bg = graphics.render_texture(width as _, height as _, format);
|
||||||
|
let tex_final = graphics.render_texture(width as _, height as _, format);
|
||||||
|
|
||||||
|
let view_fg = ImageView::new_default(tex_fg.clone()).unwrap();
|
||||||
|
let view_bg = ImageView::new_default(tex_bg.clone()).unwrap();
|
||||||
|
let view_final = ImageView::new_default(tex_final.clone()).unwrap();
|
||||||
|
|
||||||
|
let set_fg = pipeline.uniform_sampler(0, view_fg.clone(), Filter::Nearest);
|
||||||
|
let set_bg = pipeline.uniform_sampler(0, view_bg.clone(), Filter::Nearest);
|
||||||
|
let pass_fg = pipeline.create_pass(
|
||||||
|
[width as _, height as _],
|
||||||
|
vertex_buffer.clone(),
|
||||||
|
graphics.quad_indices.clone(),
|
||||||
|
vec![set_fg],
|
||||||
|
);
|
||||||
|
let pass_bg = pipeline.create_pass(
|
||||||
|
[width as _, height as _],
|
||||||
|
vertex_buffer.clone(),
|
||||||
|
graphics.quad_indices.clone(),
|
||||||
|
vec![set_bg],
|
||||||
|
);
|
||||||
|
|
||||||
|
let stride = width / RES_DIVIDER;
|
||||||
|
let rows = height / RES_DIVIDER;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
canvas: CanvasData {
|
||||||
|
data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
graphics,
|
||||||
|
pipeline_color,
|
||||||
|
pipeline_glyph,
|
||||||
|
},
|
||||||
|
controls: Vec::new(),
|
||||||
|
hover_controls: [None, None],
|
||||||
|
pressed_controls: [None, None],
|
||||||
|
interact_map: vec![None; stride * rows],
|
||||||
|
interact_stride: stride,
|
||||||
|
interact_rows: rows,
|
||||||
|
tex_fg,
|
||||||
|
view_fg,
|
||||||
|
tex_bg,
|
||||||
|
view_bg,
|
||||||
|
tex_final,
|
||||||
|
view_final,
|
||||||
|
pass_fg,
|
||||||
|
pass_bg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interactive_set_idx(&mut self, x: f32, y: f32, w: f32, h: f32, idx: usize) {
|
||||||
|
let (x, y, w, h) = (x as usize, y as usize, w as usize, h as usize);
|
||||||
|
|
||||||
|
let x_min = (x / RES_DIVIDER).max(0);
|
||||||
|
let y_min = (y / RES_DIVIDER).max(0);
|
||||||
|
let x_max = (x_min + (w / RES_DIVIDER)).min(self.interact_stride - 1);
|
||||||
|
let y_max = (y_min + (h / RES_DIVIDER)).min(self.interact_rows - 1);
|
||||||
|
|
||||||
|
for y in y_min..y_max {
|
||||||
|
for x in x_min..x_max {
|
||||||
|
self.interact_map[y * self.interact_stride + x] = Some(idx as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interactive_get_idx(&self, uv: Vec2) -> Option<usize> {
|
||||||
|
let x = (uv.x * self.canvas.width as f32) as usize;
|
||||||
|
let y = (uv.y * self.canvas.height as f32) as usize;
|
||||||
|
let x = (x / RES_DIVIDER).max(0).min(self.interact_stride - 1);
|
||||||
|
let y = (y / RES_DIVIDER).max(0).min(self.interact_rows - 1);
|
||||||
|
self.interact_map[y * self.interact_stride + x].map(|x| x as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_bg(&mut self, app: &mut AppState) {
|
||||||
|
let mut cmd_buffer = self
|
||||||
|
.canvas
|
||||||
|
.graphics
|
||||||
|
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||||
|
.begin(self.view_bg.clone());
|
||||||
|
for c in self.controls.iter_mut() {
|
||||||
|
if let Some(fun) = c.on_render_bg {
|
||||||
|
fun(c, &self.canvas, app, &mut cmd_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = cmd_buffer.end_render_and_execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_fg(&mut self, app: &mut AppState) {
|
||||||
|
let mut cmd_buffer = self
|
||||||
|
.canvas
|
||||||
|
.graphics
|
||||||
|
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||||
|
.begin(self.view_fg.clone());
|
||||||
|
for c in self.controls.iter_mut() {
|
||||||
|
if let Some(fun) = c.on_render_fg {
|
||||||
|
fun(c, &self.canvas, app, &mut cmd_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = cmd_buffer.end_render_and_execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_view(&self) -> Arc<ImageView<AttachmentImage>> {
|
||||||
|
self.view_final.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, S> InteractionHandler for Canvas<D, S> {
|
||||||
|
fn on_left(&mut self, _app: &mut AppState, hand: usize) {
|
||||||
|
self.hover_controls[hand] = None;
|
||||||
|
}
|
||||||
|
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) {
|
||||||
|
if let Some(i) = self.interactive_get_idx(hit.uv) {
|
||||||
|
self.hover_controls[hit.hand] = Some(i);
|
||||||
|
} else {
|
||||||
|
self.hover_controls[hit.hand] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
|
||||||
|
let idx = if pressed {
|
||||||
|
self.interactive_get_idx(hit.uv)
|
||||||
|
} else {
|
||||||
|
self.pressed_controls[hit.hand]
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(idx) = idx {
|
||||||
|
let c = &mut self.controls[idx];
|
||||||
|
if pressed {
|
||||||
|
if let Some(ref mut f) = c.on_press {
|
||||||
|
self.pressed_controls[hit.hand] = Some(idx);
|
||||||
|
f(c, &mut self.canvas.data, app);
|
||||||
|
}
|
||||||
|
} else if let Some(ref mut f) = c.on_release {
|
||||||
|
self.pressed_controls[hit.hand] = None;
|
||||||
|
f(c, &mut self.canvas.data, app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta: f32) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, S> OverlayRenderer for Canvas<D, S> {
|
||||||
|
fn init(&mut self, app: &mut AppState) {
|
||||||
|
self.render_bg(app);
|
||||||
|
|
||||||
|
self.render_fg(app);
|
||||||
|
}
|
||||||
|
fn pause(&mut self, _app: &mut AppState) {}
|
||||||
|
fn resume(&mut self, _app: &mut AppState) {}
|
||||||
|
fn render(&mut self, app: &mut AppState) {
|
||||||
|
let mut dirty = false;
|
||||||
|
|
||||||
|
for c in self.controls.iter_mut() {
|
||||||
|
if let Some(fun) = c.on_update {
|
||||||
|
fun(c, &mut self.canvas.data, app);
|
||||||
|
}
|
||||||
|
if c.dirty {
|
||||||
|
dirty = true;
|
||||||
|
c.dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd_buffer = self
|
||||||
|
.canvas
|
||||||
|
.graphics
|
||||||
|
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||||
|
.begin(self.view_final.clone());
|
||||||
|
|
||||||
|
if dirty {
|
||||||
|
self.render_fg(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static background
|
||||||
|
cmd_buffer.run_ref(&self.pass_bg);
|
||||||
|
|
||||||
|
for (i, c) in self.controls.iter_mut().enumerate() {
|
||||||
|
if let Some(render) = c.on_render_hl {
|
||||||
|
if let Some(test) = c.test_highlight {
|
||||||
|
if test(c, &mut self.canvas.data, app) {
|
||||||
|
render(c, &self.canvas, app, &mut cmd_buffer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.hover_controls.contains(&Some(i)) {
|
||||||
|
render(c, &self.canvas, app, &mut cmd_buffer, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mostly static text
|
||||||
|
cmd_buffer.run_ref(&self.pass_fg);
|
||||||
|
|
||||||
|
let _ = cmd_buffer.end_render_and_execute();
|
||||||
|
}
|
||||||
|
fn view(&mut self) -> Arc<dyn vulkano::image::ImageViewAbstract> {
|
||||||
|
self.view_final.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, S> OverlayBackend for Canvas<D, S> {}
|
||||||
|
|
||||||
|
pub struct Control<D, S> {
|
||||||
|
pub state: Option<S>,
|
||||||
|
rect: Rect,
|
||||||
|
fg_color: Vec3,
|
||||||
|
bg_color: Vec3,
|
||||||
|
text: Arc<str>,
|
||||||
|
size: isize,
|
||||||
|
dirty: bool,
|
||||||
|
pass_hl: Option<(WlxPass, WlxPass)>,
|
||||||
|
|
||||||
|
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
||||||
|
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
||||||
|
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
||||||
|
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> bool>,
|
||||||
|
|
||||||
|
on_render_bg: Option<
|
||||||
|
fn(&Self, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>),
|
||||||
|
>,
|
||||||
|
on_render_hl: Option<
|
||||||
|
fn(
|
||||||
|
&Self,
|
||||||
|
&CanvasData<D>,
|
||||||
|
&mut AppState,
|
||||||
|
&mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||||
|
bool,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
on_render_fg: Option<
|
||||||
|
fn(&Self, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>),
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, S> Control<D, S> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
rect: Rect {
|
||||||
|
x: 0.,
|
||||||
|
y: 0.,
|
||||||
|
w: 0.,
|
||||||
|
h: 0.,
|
||||||
|
},
|
||||||
|
fg_color: Vec3::ONE,
|
||||||
|
bg_color: Vec3::ZERO,
|
||||||
|
text: Arc::from(""),
|
||||||
|
dirty: false,
|
||||||
|
size: 24,
|
||||||
|
state: None,
|
||||||
|
on_update: None,
|
||||||
|
on_render_bg: None,
|
||||||
|
on_render_hl: None,
|
||||||
|
on_render_fg: None,
|
||||||
|
test_highlight: None,
|
||||||
|
on_press: None,
|
||||||
|
on_release: None,
|
||||||
|
pass_hl: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_text(&mut self, text: &str) {
|
||||||
|
if *self.text == *text {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.text = text.into();
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_text(&self) -> &str {
|
||||||
|
&self.text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_rect(
|
||||||
|
&self,
|
||||||
|
canvas: &CanvasData<D>,
|
||||||
|
_: &mut AppState,
|
||||||
|
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||||
|
) {
|
||||||
|
let pass = {
|
||||||
|
let vertex_buffer = canvas.graphics.upload_verts(
|
||||||
|
canvas.width as _,
|
||||||
|
canvas.height as _,
|
||||||
|
self.rect.x,
|
||||||
|
self.rect.y,
|
||||||
|
self.rect.w,
|
||||||
|
self.rect.h,
|
||||||
|
);
|
||||||
|
let set0 = canvas.pipeline_color.uniform_buffer(
|
||||||
|
0,
|
||||||
|
vec![self.bg_color.x, self.bg_color.y, self.bg_color.z, 1.],
|
||||||
|
);
|
||||||
|
canvas.pipeline_color.create_pass(
|
||||||
|
[canvas.width as _, canvas.height as _],
|
||||||
|
vertex_buffer,
|
||||||
|
canvas.graphics.quad_indices.clone(),
|
||||||
|
vec![set0],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd_buffer.run_ref(&pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_highlight(
|
||||||
|
&self,
|
||||||
|
canvas: &CanvasData<D>,
|
||||||
|
_: &mut AppState,
|
||||||
|
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||||
|
strong: bool,
|
||||||
|
) {
|
||||||
|
let vertex_buffer = canvas.graphics.upload_verts(
|
||||||
|
canvas.width as _,
|
||||||
|
canvas.height as _,
|
||||||
|
self.rect.x,
|
||||||
|
self.rect.y,
|
||||||
|
self.rect.w,
|
||||||
|
self.rect.h,
|
||||||
|
);
|
||||||
|
let set0 = canvas.pipeline_color.uniform_buffer(
|
||||||
|
0,
|
||||||
|
vec![
|
||||||
|
self.bg_color.x,
|
||||||
|
self.bg_color.y,
|
||||||
|
self.bg_color.z,
|
||||||
|
if strong { 0.5 } else { 0.3 },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let pass = canvas.pipeline_color.create_pass(
|
||||||
|
[canvas.width as _, canvas.height as _],
|
||||||
|
vertex_buffer.clone(),
|
||||||
|
canvas.graphics.quad_indices.clone(),
|
||||||
|
vec![set0],
|
||||||
|
);
|
||||||
|
|
||||||
|
cmd_buffer.run_ref(&pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_text(
|
||||||
|
&self,
|
||||||
|
canvas: &CanvasData<D>,
|
||||||
|
app: &mut AppState,
|
||||||
|
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||||
|
) {
|
||||||
|
let mut cur_y = self.rect.y;
|
||||||
|
for line in self.text.lines() {
|
||||||
|
let mut cur_x = self.rect.x;
|
||||||
|
for glyph in app.fc.get_glyphs(line, self.size, canvas.graphics.clone()) {
|
||||||
|
if let Some(tex) = glyph.tex.clone() {
|
||||||
|
let vertex_buffer = canvas.graphics.upload_verts(
|
||||||
|
canvas.width as _,
|
||||||
|
canvas.height as _,
|
||||||
|
cur_x + glyph.left,
|
||||||
|
cur_y - glyph.top,
|
||||||
|
glyph.width,
|
||||||
|
glyph.height,
|
||||||
|
);
|
||||||
|
let set0 = canvas.pipeline_glyph.uniform_sampler(
|
||||||
|
0,
|
||||||
|
ImageView::new_default(tex).unwrap(),
|
||||||
|
Filter::Nearest,
|
||||||
|
);
|
||||||
|
let set1 = canvas.pipeline_glyph.uniform_buffer(
|
||||||
|
1,
|
||||||
|
vec![self.fg_color.x, self.fg_color.y, self.fg_color.z, 1.],
|
||||||
|
);
|
||||||
|
let pass = canvas.pipeline_glyph.create_pass(
|
||||||
|
[canvas.width as _, canvas.height as _],
|
||||||
|
vertex_buffer,
|
||||||
|
canvas.graphics.quad_indices.clone(),
|
||||||
|
vec![set0, set1],
|
||||||
|
);
|
||||||
|
cmd_buffer.run_ref(&pass);
|
||||||
|
}
|
||||||
|
cur_x += glyph.advance;
|
||||||
|
}
|
||||||
|
cur_y += (self.size as f32) * 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_text_centered(
|
||||||
|
&self,
|
||||||
|
canvas: &CanvasData<D>,
|
||||||
|
app: &mut AppState,
|
||||||
|
cmd_buffer: &mut WlxCommandBuffer<PrimaryAutoCommandBuffer>,
|
||||||
|
) {
|
||||||
|
let (w, h) = app
|
||||||
|
.fc
|
||||||
|
.get_text_size(&self.text, self.size, canvas.graphics.clone());
|
||||||
|
|
||||||
|
let mut cur_y = self.rect.y + (self.rect.h) - (h * 0.5);
|
||||||
|
for line in self.text.lines() {
|
||||||
|
let mut cur_x = self.rect.x + (self.rect.w * 0.5) - (w * 0.5);
|
||||||
|
for glyph in app.fc.get_glyphs(line, self.size, canvas.graphics.clone()) {
|
||||||
|
if let Some(tex) = glyph.tex.clone() {
|
||||||
|
let vertex_buffer = canvas.graphics.upload_verts(
|
||||||
|
canvas.width as _,
|
||||||
|
canvas.height as _,
|
||||||
|
cur_x + glyph.left,
|
||||||
|
cur_y - glyph.top,
|
||||||
|
glyph.width,
|
||||||
|
glyph.height,
|
||||||
|
);
|
||||||
|
let set0 = canvas.pipeline_glyph.uniform_sampler(
|
||||||
|
0,
|
||||||
|
ImageView::new_default(tex).unwrap(),
|
||||||
|
Filter::Nearest,
|
||||||
|
);
|
||||||
|
let set1 = canvas.pipeline_glyph.uniform_buffer(
|
||||||
|
1,
|
||||||
|
vec![self.fg_color.x, self.fg_color.y, self.fg_color.z, 1.],
|
||||||
|
);
|
||||||
|
let pass = canvas.pipeline_glyph.create_pass(
|
||||||
|
[canvas.width as _, canvas.height as _],
|
||||||
|
vertex_buffer,
|
||||||
|
canvas.graphics.quad_indices.clone(),
|
||||||
|
vec![set0, set1],
|
||||||
|
);
|
||||||
|
cmd_buffer.run_ref(&pass);
|
||||||
|
}
|
||||||
|
cur_x += glyph.advance;
|
||||||
|
}
|
||||||
|
cur_y += (self.size as f32) * 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/image.png
Normal file
BIN
src/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
431
src/input.rs
Normal file
431
src/input.rs
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
use glam::Vec2;
|
||||||
|
use idmap::{idmap, IdMap};
|
||||||
|
use idmap_derive::IntegerId;
|
||||||
|
use input_linux::{
|
||||||
|
AbsoluteAxis, AbsoluteInfo, AbsoluteInfoSetup, EventKind, InputId, Key, RelativeAxis,
|
||||||
|
UInputHandle,
|
||||||
|
};
|
||||||
|
use libc::{input_event, timeval};
|
||||||
|
use log::{error, info};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::mem::transmute;
|
||||||
|
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
||||||
|
|
||||||
|
pub fn initialize_input() -> Box<dyn InputProvider> {
|
||||||
|
if let Some(uinput) = UInputProvider::try_new() {
|
||||||
|
info!("Initialized uinput.");
|
||||||
|
return Box::new(uinput);
|
||||||
|
}
|
||||||
|
error!("Could not create uinput provider. Keyboard/Mouse input will not work!");
|
||||||
|
error!("Check if you're in `input` group: `id -nG`");
|
||||||
|
Box::new(DummyProvider {})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InputProvider {
|
||||||
|
fn mouse_move(&mut self, pos: Vec2);
|
||||||
|
fn send_button(&self, button: u16, down: bool);
|
||||||
|
fn wheel(&self, delta: i32);
|
||||||
|
fn set_modifiers(&mut self, mods: u8);
|
||||||
|
fn send_key(&self, key: u16, down: bool);
|
||||||
|
fn set_desktop_extent(&mut self, extent: Vec2);
|
||||||
|
fn on_new_frame(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UInputProvider {
|
||||||
|
handle: UInputHandle<File>,
|
||||||
|
desktop_extent: Vec2,
|
||||||
|
mouse_moved: bool,
|
||||||
|
cur_modifiers: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DummyProvider;
|
||||||
|
|
||||||
|
pub const MOUSE_LEFT: u16 = 0x110;
|
||||||
|
pub const MOUSE_RIGHT: u16 = 0x111;
|
||||||
|
pub const MOUSE_MIDDLE: u16 = 0x112;
|
||||||
|
|
||||||
|
const MOUSE_EXTENT: f32 = 32768.;
|
||||||
|
|
||||||
|
const EV_SYN: u16 = 0x0;
|
||||||
|
const EV_KEY: u16 = 0x1;
|
||||||
|
const EV_REL: u16 = 0x2;
|
||||||
|
const EV_ABS: u16 = 0x3;
|
||||||
|
|
||||||
|
impl UInputProvider {
|
||||||
|
fn try_new() -> Option<Self> {
|
||||||
|
if let Ok(file) = File::create("/dev/uinput") {
|
||||||
|
let handle = UInputHandle::new(file);
|
||||||
|
|
||||||
|
let id = InputId {
|
||||||
|
bustype: 0x03,
|
||||||
|
vendor: 0x4711,
|
||||||
|
product: 0x0819,
|
||||||
|
version: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = b"WlxOverlay Keyboard-Mouse Hybrid Thing\0";
|
||||||
|
|
||||||
|
let abs_info = vec![
|
||||||
|
AbsoluteInfoSetup {
|
||||||
|
axis: input_linux::AbsoluteAxis::X,
|
||||||
|
info: AbsoluteInfo {
|
||||||
|
value: 0,
|
||||||
|
minimum: 0,
|
||||||
|
maximum: MOUSE_EXTENT as _,
|
||||||
|
fuzz: 0,
|
||||||
|
flat: 0,
|
||||||
|
resolution: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AbsoluteInfoSetup {
|
||||||
|
axis: input_linux::AbsoluteAxis::Y,
|
||||||
|
info: AbsoluteInfo {
|
||||||
|
value: 0,
|
||||||
|
minimum: 0,
|
||||||
|
maximum: MOUSE_EXTENT as _,
|
||||||
|
fuzz: 0,
|
||||||
|
flat: 0,
|
||||||
|
resolution: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if handle.set_evbit(EventKind::Key).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if handle.set_evbit(EventKind::Absolute).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if handle.set_evbit(EventKind::Relative).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for btn in MOUSE_LEFT..=MOUSE_MIDDLE {
|
||||||
|
let key: Key = unsafe { transmute(btn) };
|
||||||
|
if handle.set_keybit(key).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in VirtualKey::iter() {
|
||||||
|
let key: Key = unsafe { transmute(key as u16) };
|
||||||
|
if handle.set_keybit(key).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle.set_absbit(AbsoluteAxis::X).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if handle.set_absbit(AbsoluteAxis::Y).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if handle.set_relbit(RelativeAxis::Wheel).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle.create(&id, name, 0, &abs_info).is_ok() {
|
||||||
|
return Some(UInputProvider {
|
||||||
|
handle,
|
||||||
|
desktop_extent: Vec2::ZERO,
|
||||||
|
mouse_moved: false,
|
||||||
|
cur_modifiers: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputProvider for UInputProvider {
|
||||||
|
fn mouse_move(&mut self, pos: Vec2) {
|
||||||
|
if self.mouse_moved {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.mouse_moved = true;
|
||||||
|
|
||||||
|
let pos = pos * (MOUSE_EXTENT / self.desktop_extent);
|
||||||
|
|
||||||
|
let time = get_time();
|
||||||
|
let events = [
|
||||||
|
new_event(time, EV_ABS, AbsoluteAxis::X as _, pos.x as i32),
|
||||||
|
new_event(time, EV_ABS, AbsoluteAxis::Y as _, pos.y as i32),
|
||||||
|
new_event(time, EV_SYN, 0, 0),
|
||||||
|
];
|
||||||
|
if let Err(res) = self.handle.write(&events) {
|
||||||
|
error!("{}", res.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn send_button(&self, button: u16, down: bool) {
|
||||||
|
let time = get_time();
|
||||||
|
let events = [
|
||||||
|
new_event(time, EV_KEY, button, down as _),
|
||||||
|
new_event(time, EV_SYN, 0, 0),
|
||||||
|
];
|
||||||
|
if let Err(res) = self.handle.write(&events) {
|
||||||
|
error!("{}", res.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn wheel(&self, delta: i32) {
|
||||||
|
let time = get_time();
|
||||||
|
let events = [
|
||||||
|
new_event(time, EV_REL, RelativeAxis::Wheel as _, delta),
|
||||||
|
new_event(time, EV_SYN, 0, 0),
|
||||||
|
];
|
||||||
|
if let Err(res) = self.handle.write(&events) {
|
||||||
|
error!("{}", res.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn set_modifiers(&mut self, modifiers: u8) {
|
||||||
|
let changed = self.cur_modifiers ^ modifiers;
|
||||||
|
for i in 0..7 {
|
||||||
|
let m = 1 << i;
|
||||||
|
if changed & m != 0 {
|
||||||
|
let vk = MODS_TO_KEYS.get(m).unwrap()[0] as u16;
|
||||||
|
self.send_key(vk, modifiers & m != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cur_modifiers = modifiers;
|
||||||
|
}
|
||||||
|
fn send_key(&self, key: u16, down: bool) {
|
||||||
|
let time = get_time();
|
||||||
|
let events = [
|
||||||
|
new_event(time, EV_KEY, key - 8, down as _),
|
||||||
|
new_event(time, EV_SYN, 0, 0),
|
||||||
|
];
|
||||||
|
if let Err(res) = self.handle.write(&events) {
|
||||||
|
error!("{}", res.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn set_desktop_extent(&mut self, extent: Vec2) {
|
||||||
|
info!("Desktop extent: {:?}", extent);
|
||||||
|
self.desktop_extent = extent;
|
||||||
|
}
|
||||||
|
fn on_new_frame(&mut self) {
|
||||||
|
self.mouse_moved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputProvider for DummyProvider {
|
||||||
|
fn mouse_move(&mut self, _pos: Vec2) {}
|
||||||
|
fn send_button(&self, _button: u16, _down: bool) {}
|
||||||
|
fn wheel(&self, _delta: i32) {}
|
||||||
|
fn set_modifiers(&mut self, _modifiers: u8) {}
|
||||||
|
fn send_key(&self, _key: u16, _down: bool) {}
|
||||||
|
fn set_desktop_extent(&mut self, _extent: Vec2) {}
|
||||||
|
fn on_new_frame(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_time() -> timeval {
|
||||||
|
let mut time = timeval {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_usec: 0,
|
||||||
|
};
|
||||||
|
unsafe { libc::gettimeofday(&mut time, std::ptr::null_mut()) };
|
||||||
|
time
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn new_event(time: timeval, type_: u16, code: u16, value: i32) -> input_event {
|
||||||
|
input_event {
|
||||||
|
time,
|
||||||
|
type_,
|
||||||
|
code,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type KeyModifier = u8;
|
||||||
|
pub const SHIFT: KeyModifier = 0x01;
|
||||||
|
pub const CAPS_LOCK: KeyModifier = 0x02;
|
||||||
|
pub const CTRL: KeyModifier = 0x04;
|
||||||
|
pub const ALT: KeyModifier = 0x08;
|
||||||
|
pub const NUM_LOCK: KeyModifier = 0x10;
|
||||||
|
pub const SUPER: KeyModifier = 0x40;
|
||||||
|
pub const META: KeyModifier = 0x80;
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, IntegerId, EnumString, EnumIter)]
|
||||||
|
pub enum VirtualKey {
|
||||||
|
Escape = 9,
|
||||||
|
N1, // number row
|
||||||
|
N2,
|
||||||
|
N3,
|
||||||
|
N4,
|
||||||
|
N5,
|
||||||
|
N6,
|
||||||
|
N7,
|
||||||
|
N8,
|
||||||
|
N9,
|
||||||
|
N0,
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
|
BackSpace,
|
||||||
|
Tab,
|
||||||
|
Q,
|
||||||
|
W,
|
||||||
|
E,
|
||||||
|
R,
|
||||||
|
T,
|
||||||
|
Y,
|
||||||
|
U,
|
||||||
|
I,
|
||||||
|
O,
|
||||||
|
P,
|
||||||
|
Oem4, // [ {
|
||||||
|
Oem6, // ] }
|
||||||
|
Return,
|
||||||
|
LCtrl,
|
||||||
|
A,
|
||||||
|
S,
|
||||||
|
D,
|
||||||
|
F,
|
||||||
|
G,
|
||||||
|
H,
|
||||||
|
J,
|
||||||
|
K,
|
||||||
|
L,
|
||||||
|
Oem1, // ; :
|
||||||
|
Oem7, // ' "
|
||||||
|
Oem3, // ` ~
|
||||||
|
LShift,
|
||||||
|
Oem5, // \ |
|
||||||
|
Z,
|
||||||
|
X,
|
||||||
|
C,
|
||||||
|
V,
|
||||||
|
B,
|
||||||
|
N,
|
||||||
|
M,
|
||||||
|
Comma, // , <
|
||||||
|
Period, // . >
|
||||||
|
Oem2, // / ?
|
||||||
|
RShift,
|
||||||
|
KP_Multiply,
|
||||||
|
LAlt,
|
||||||
|
Space,
|
||||||
|
Caps,
|
||||||
|
F1,
|
||||||
|
F2,
|
||||||
|
F3,
|
||||||
|
F4,
|
||||||
|
F5,
|
||||||
|
F6,
|
||||||
|
F7,
|
||||||
|
F8,
|
||||||
|
F9,
|
||||||
|
F10,
|
||||||
|
NumLock,
|
||||||
|
Scroll,
|
||||||
|
KP_7, // KeyPad
|
||||||
|
KP_8,
|
||||||
|
KP_9,
|
||||||
|
KP_Subtract,
|
||||||
|
KP_4,
|
||||||
|
KP_5,
|
||||||
|
KP_6,
|
||||||
|
KP_Add,
|
||||||
|
KP_1,
|
||||||
|
KP_2,
|
||||||
|
KP_3,
|
||||||
|
KP_0,
|
||||||
|
KP_Decimal,
|
||||||
|
Oem102 = 94, // Optional key usually between LShift and Z
|
||||||
|
F11,
|
||||||
|
F12,
|
||||||
|
AbntC1,
|
||||||
|
Katakana,
|
||||||
|
Hiragana,
|
||||||
|
Henkan,
|
||||||
|
Kana,
|
||||||
|
Muhenkan,
|
||||||
|
KP_Enter = 104,
|
||||||
|
RCtrl,
|
||||||
|
KP_Divide,
|
||||||
|
Print,
|
||||||
|
Meta, // Right Alt aka AltGr
|
||||||
|
Home = 110,
|
||||||
|
Up,
|
||||||
|
Prior,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
End,
|
||||||
|
Down,
|
||||||
|
Next,
|
||||||
|
Insert,
|
||||||
|
Delete,
|
||||||
|
XF86AudioMute = 121,
|
||||||
|
XF86AudioLowerVolume,
|
||||||
|
XF86AudioRaiseVolume,
|
||||||
|
Pause = 127,
|
||||||
|
AbntC2 = 129,
|
||||||
|
Hangul,
|
||||||
|
Hanja,
|
||||||
|
LSuper = 133,
|
||||||
|
RSuper,
|
||||||
|
Menu,
|
||||||
|
Help = 146,
|
||||||
|
XF86MenuKB,
|
||||||
|
XF86Sleep = 150,
|
||||||
|
XF86Xfer = 155,
|
||||||
|
XF86Launch1,
|
||||||
|
XF86Launch2,
|
||||||
|
XF86WWW,
|
||||||
|
XF86Mail = 163,
|
||||||
|
XF86Favorites,
|
||||||
|
XF86MyComputer,
|
||||||
|
XF86Back,
|
||||||
|
XF86Forward,
|
||||||
|
XF86AudioNext = 171,
|
||||||
|
XF86AudioPlay,
|
||||||
|
XF86AudioPrev,
|
||||||
|
XF86AudioStop,
|
||||||
|
XF86HomePage = 180,
|
||||||
|
XF86Reload,
|
||||||
|
F13 = 191,
|
||||||
|
F14,
|
||||||
|
F15,
|
||||||
|
F16,
|
||||||
|
F17,
|
||||||
|
F18,
|
||||||
|
F19,
|
||||||
|
F20,
|
||||||
|
F21,
|
||||||
|
F22,
|
||||||
|
F23,
|
||||||
|
F24,
|
||||||
|
Hyper = 207,
|
||||||
|
XF86Launch3,
|
||||||
|
XF86Launch4,
|
||||||
|
XF86LaunchB,
|
||||||
|
XF86Search = 225,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static KEYS_TO_MODS: Lazy<IdMap<VirtualKey, KeyModifier>> = Lazy::new(|| {
|
||||||
|
idmap! {
|
||||||
|
VirtualKey::LShift => SHIFT,
|
||||||
|
VirtualKey::RShift => SHIFT,
|
||||||
|
VirtualKey::Caps => CAPS_LOCK,
|
||||||
|
VirtualKey::LCtrl => CTRL,
|
||||||
|
VirtualKey::RCtrl => CTRL,
|
||||||
|
VirtualKey::LAlt => ALT,
|
||||||
|
VirtualKey::NumLock => NUM_LOCK,
|
||||||
|
VirtualKey::LSuper => SUPER,
|
||||||
|
VirtualKey::RSuper => SUPER,
|
||||||
|
VirtualKey::Meta => META,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static MODS_TO_KEYS: Lazy<IdMap<KeyModifier, Vec<VirtualKey>>> = Lazy::new(|| {
|
||||||
|
idmap! {
|
||||||
|
SHIFT => vec![VirtualKey::LShift, VirtualKey::RShift],
|
||||||
|
CAPS_LOCK => vec![VirtualKey::Caps],
|
||||||
|
CTRL => vec![VirtualKey::LCtrl, VirtualKey::RCtrl],
|
||||||
|
ALT => vec![VirtualKey::LAlt],
|
||||||
|
NUM_LOCK => vec![VirtualKey::NumLock],
|
||||||
|
SUPER => vec![VirtualKey::LSuper, VirtualKey::RSuper],
|
||||||
|
META => vec![VirtualKey::Meta],
|
||||||
|
}
|
||||||
|
});
|
||||||
271
src/main.rs
Normal file
271
src/main.rs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
#[allow(dead_code)]
|
||||||
|
mod backend;
|
||||||
|
mod graphics;
|
||||||
|
mod gui;
|
||||||
|
mod input;
|
||||||
|
mod overlays;
|
||||||
|
mod ovr;
|
||||||
|
mod shaders;
|
||||||
|
mod state;
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::graphics::{Vert2Uv, WlxGraphics, INDICES};
|
||||||
|
use crate::input::initialize_input;
|
||||||
|
use crate::overlays::watch::create_watch;
|
||||||
|
use crate::{
|
||||||
|
shaders::{frag_sprite, vert_common},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
use env_logger::Env;
|
||||||
|
use log::{info, warn};
|
||||||
|
use vulkano::{
|
||||||
|
buffer::BufferUsage,
|
||||||
|
command_buffer::CommandBufferUsage,
|
||||||
|
image::{
|
||||||
|
view::{ImageView, ImageViewCreateInfo},
|
||||||
|
ImageAccess, ImageSubresourceRange, ImageViewType, SwapchainImage,
|
||||||
|
},
|
||||||
|
pipeline::graphics::viewport::Viewport,
|
||||||
|
sampler::Filter,
|
||||||
|
swapchain::{
|
||||||
|
acquire_next_image, AcquireError, SwapchainCreateInfo, SwapchainCreationError,
|
||||||
|
SwapchainPresentInfo,
|
||||||
|
},
|
||||||
|
sync::{self, FlushError, GpuFuture},
|
||||||
|
};
|
||||||
|
use winit::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::ControlFlow,
|
||||||
|
window::Window,
|
||||||
|
};
|
||||||
|
use wlx_capture::{frame::WlxFrame, wayland::WlxClient, wlr::WlrDmabufCapture, WlxCapture};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
info!(
|
||||||
|
"Welcome to {} version {}!",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
|
|
||||||
|
let (graphics, event_loop) = WlxGraphics::new();
|
||||||
|
let (mut swapchain, images) = graphics.create_swapchain(None);
|
||||||
|
|
||||||
|
let mut app = AppState {
|
||||||
|
fc: crate::gui::font::FontCache::new(),
|
||||||
|
session: crate::state::AppSession::load(),
|
||||||
|
tasks: VecDeque::with_capacity(16),
|
||||||
|
graphics: graphics.clone(),
|
||||||
|
format: swapchain.image_format(),
|
||||||
|
input: initialize_input(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let wl = WlxClient::new().unwrap();
|
||||||
|
let output_id = wl.outputs[0].id;
|
||||||
|
let mut capture = WlrDmabufCapture::new(wl, output_id).unwrap();
|
||||||
|
let rx = capture.init();
|
||||||
|
|
||||||
|
let vertices = [
|
||||||
|
Vert2Uv {
|
||||||
|
in_pos: [0., 0.],
|
||||||
|
in_uv: [0., 0.],
|
||||||
|
},
|
||||||
|
Vert2Uv {
|
||||||
|
in_pos: [0., 1.],
|
||||||
|
in_uv: [0., 1.],
|
||||||
|
},
|
||||||
|
Vert2Uv {
|
||||||
|
in_pos: [1., 0.],
|
||||||
|
in_uv: [1., 0.],
|
||||||
|
},
|
||||||
|
Vert2Uv {
|
||||||
|
in_pos: [1., 1.],
|
||||||
|
in_uv: [1., 1.],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let vertex_buffer = graphics.upload_buffer(BufferUsage::VERTEX_BUFFER, vertices.iter());
|
||||||
|
let index_buffer = graphics.upload_buffer(BufferUsage::INDEX_BUFFER, INDICES.iter());
|
||||||
|
|
||||||
|
let vs = vert_common::load(graphics.device.clone()).unwrap();
|
||||||
|
let fs = frag_sprite::load(graphics.device.clone()).unwrap();
|
||||||
|
|
||||||
|
let uploads = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit);
|
||||||
|
|
||||||
|
let mut watch = create_watch(&app, vec![]);
|
||||||
|
watch.init(&mut app);
|
||||||
|
watch.render(&mut app);
|
||||||
|
|
||||||
|
let pipeline1 = graphics.create_pipeline(vs.clone(), fs.clone(), swapchain.image_format());
|
||||||
|
let set1 = pipeline1.uniform_sampler(0, watch.view(), Filter::Nearest);
|
||||||
|
|
||||||
|
capture.request_new_frame();
|
||||||
|
|
||||||
|
let pipeline = graphics.create_pipeline(vs, fs, swapchain.image_format());
|
||||||
|
let set0;
|
||||||
|
loop {
|
||||||
|
if let Ok(frame) = rx.try_recv() {
|
||||||
|
match frame {
|
||||||
|
WlxFrame::Dmabuf(dmabuf_frame) => match graphics.dmabuf_texture(dmabuf_frame) {
|
||||||
|
Ok(tex) => {
|
||||||
|
let format = tex.format();
|
||||||
|
let view = ImageView::new(
|
||||||
|
tex,
|
||||||
|
ImageViewCreateInfo {
|
||||||
|
format: Some(format),
|
||||||
|
view_type: ImageViewType::Dim2d,
|
||||||
|
subresource_range: ImageSubresourceRange::from_parameters(
|
||||||
|
format, 1, 1,
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
set0 = pipeline.uniform_sampler(0, view, Filter::Nearest);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to create texture from dmabuf: {}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
warn!("Received non-dmabuf frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//let set1 = graphics.uniform_buffer(1, vec![1.0, 1.0, 1.0, 1.0]);
|
||||||
|
let image_extent_f32 = [
|
||||||
|
swapchain.image_extent()[0] as f32,
|
||||||
|
swapchain.image_extent()[1] as f32,
|
||||||
|
];
|
||||||
|
let image_extent2_f32 = [
|
||||||
|
swapchain.image_extent()[0] as f32 / 2.,
|
||||||
|
swapchain.image_extent()[1] as f32 / 2.,
|
||||||
|
];
|
||||||
|
let pass = pipeline.create_pass(
|
||||||
|
image_extent_f32,
|
||||||
|
vertex_buffer.clone(),
|
||||||
|
index_buffer.clone(),
|
||||||
|
vec![set0],
|
||||||
|
);
|
||||||
|
let pass2 = pipeline1.create_pass(image_extent2_f32, vertex_buffer, index_buffer, vec![set1]);
|
||||||
|
|
||||||
|
let mut viewport = Viewport {
|
||||||
|
origin: [0.0, 0.0],
|
||||||
|
dimensions: [1024.0, 1024.0],
|
||||||
|
depth_range: 0.0..1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut attachment_image_views = window_size_dependent_setup(&images, &mut viewport);
|
||||||
|
|
||||||
|
//let set1 = pipeline.uniform_buffer(1, vec![1.0, 0.0, 1.0, 1.0]);
|
||||||
|
|
||||||
|
let mut recreate_swapchain = false;
|
||||||
|
let mut previous_frame_end = //Some(sync::now(graphics.device.clone()).boxed());
|
||||||
|
Some(uploads.end_and_execute().boxed());
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::Resized(_),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
}
|
||||||
|
Event::RedrawEventsCleared => {
|
||||||
|
previous_frame_end.as_mut().unwrap().cleanup_finished();
|
||||||
|
|
||||||
|
if recreate_swapchain {
|
||||||
|
let window = graphics
|
||||||
|
.surface
|
||||||
|
.object()
|
||||||
|
.unwrap()
|
||||||
|
.downcast_ref::<Window>()
|
||||||
|
.unwrap();
|
||||||
|
let (new_swapchain, new_images) = match swapchain.recreate(SwapchainCreateInfo {
|
||||||
|
image_extent: window.inner_size().into(),
|
||||||
|
..swapchain.create_info()
|
||||||
|
}) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
|
||||||
|
Err(e) => panic!("failed to recreate swapchain: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
swapchain = new_swapchain;
|
||||||
|
attachment_image_views = window_size_dependent_setup(&new_images, &mut viewport);
|
||||||
|
recreate_swapchain = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (image_index, suboptimal, acquire_future) =
|
||||||
|
match acquire_next_image(swapchain.clone(), None) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(AcquireError::OutOfDate) => {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => panic!("failed to acquire next image: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if suboptimal {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = graphics
|
||||||
|
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||||
|
.begin(attachment_image_views[image_index as usize].clone())
|
||||||
|
.run(&pass)
|
||||||
|
.run(&pass2)
|
||||||
|
.end_render();
|
||||||
|
|
||||||
|
let future = previous_frame_end
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.join(acquire_future)
|
||||||
|
.then_execute(graphics.queue.clone(), cmd)
|
||||||
|
.unwrap()
|
||||||
|
.then_swapchain_present(
|
||||||
|
graphics.queue.clone(),
|
||||||
|
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
|
||||||
|
)
|
||||||
|
.then_signal_fence_and_flush();
|
||||||
|
|
||||||
|
match future {
|
||||||
|
Ok(future) => {
|
||||||
|
previous_frame_end = Some(future.boxed());
|
||||||
|
}
|
||||||
|
Err(FlushError::OutOfDate) => {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
previous_frame_end = Some(sync::now(graphics.device.clone()).boxed());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("failed to flush future: {e}");
|
||||||
|
previous_frame_end = Some(sync::now(graphics.device.clone()).boxed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is called once during initialization, then again whenever the window is resized.
|
||||||
|
fn window_size_dependent_setup(
|
||||||
|
images: &[Arc<SwapchainImage>],
|
||||||
|
viewport: &mut Viewport,
|
||||||
|
) -> Vec<Arc<ImageView<SwapchainImage>>> {
|
||||||
|
let dimensions = images[0].dimensions().width_height();
|
||||||
|
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
|
||||||
|
|
||||||
|
images
|
||||||
|
.iter()
|
||||||
|
.map(|image| ImageView::new_default(image.clone()).unwrap())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
159
src/main.rs.bak2
Normal file
159
src/main.rs.bak2
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
|
||||||
|
mod graphics;
|
||||||
|
//mod gui;
|
||||||
|
//mod interactions;
|
||||||
|
//mod overlay;
|
||||||
|
//mod state;
|
||||||
|
|
||||||
|
use core::slice;
|
||||||
|
|
||||||
|
use ash::vk;
|
||||||
|
use env_logger::Env;
|
||||||
|
use glam::f32::Vec4;
|
||||||
|
use log::{info, warn};
|
||||||
|
use wlx_capture::{wlr::WlrDmabufCapture, wayland::WlxClient, WlxCapture, frame::WlxFrame};
|
||||||
|
|
||||||
|
use crate::{graphics::{VkGraphics, VkDescriptor}};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
info!("Welcome to {} version {}!", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
// let mut app = AppState {
|
||||||
|
// fc: gui::font::FontCache::new(),
|
||||||
|
// session: state::AppSession::load(),
|
||||||
|
// };
|
||||||
|
|
||||||
|
let wl = WlxClient::new().unwrap();
|
||||||
|
let output_id = wl.outputs[0].id;
|
||||||
|
let mut capture = WlrDmabufCapture::new(wl, output_id).unwrap();
|
||||||
|
let rx = capture.init();
|
||||||
|
|
||||||
|
let gfx = VkGraphics::new();
|
||||||
|
let vert = gfx.create_shader(include_bytes!("shaders/vert-common.spv"), None);
|
||||||
|
let frag_sprite = gfx.create_shader(
|
||||||
|
include_bytes!("shaders/frag-sprite.spv"),
|
||||||
|
Some(vec![vk::DescriptorType::COMBINED_IMAGE_SAMPLER])
|
||||||
|
);
|
||||||
|
let frag_srgb = gfx.create_shader(
|
||||||
|
include_bytes!("shaders/frag-srgb.spv"),
|
||||||
|
Some(vec![vk::DescriptorType::COMBINED_IMAGE_SAMPLER])
|
||||||
|
);
|
||||||
|
let frag_glyph = gfx.create_shader(
|
||||||
|
include_bytes!("shaders/frag-glyph.spv"),
|
||||||
|
Some(vec![vk::DescriptorType::COMBINED_IMAGE_SAMPLER, vk::DescriptorType::UNIFORM_BUFFER])
|
||||||
|
);
|
||||||
|
let frag_color = gfx.create_shader(
|
||||||
|
include_bytes!("shaders/frag-color.spv"),
|
||||||
|
Some(vec![vk::DescriptorType::UNIFORM_BUFFER])
|
||||||
|
);
|
||||||
|
|
||||||
|
let pipeline = gfx.create_pipeline(vert, frag_color, gfx.swapchain_format);
|
||||||
|
|
||||||
|
let color_buf = gfx.create_buffer(vk::BufferUsageFlags::UNIFORM_BUFFER, &[Vec4::new(1.0, 0.0, 1.0, 1.0); 16]);
|
||||||
|
|
||||||
|
gfx.render_loop(|| {
|
||||||
|
let frame = gfx.create_frame();
|
||||||
|
|
||||||
|
unsafe { gfx.device.reset_command_pool(frame.command_pool, vk::CommandPoolResetFlags::RELEASE_RESOURCES) }.unwrap();
|
||||||
|
|
||||||
|
let (present_index, _) = unsafe {
|
||||||
|
gfx.swapchain_loader.acquire_next_image(
|
||||||
|
gfx.swapchain,
|
||||||
|
std::u64::MAX,
|
||||||
|
frame.present_semaphore,
|
||||||
|
vk::Fence::null()
|
||||||
|
).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder()
|
||||||
|
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
unsafe { gfx.device.begin_command_buffer(frame.command_buffer, &command_buffer_begin_info) }.unwrap();
|
||||||
|
|
||||||
|
let image = gfx.present_images[present_index as usize];
|
||||||
|
|
||||||
|
let image_memory_barrier = vk::ImageMemoryBarrier2::builder()
|
||||||
|
.src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)
|
||||||
|
.dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
.dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||||
|
.old_layout(vk::ImageLayout::UNDEFINED)
|
||||||
|
.new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.image(image)
|
||||||
|
.subresource_range(vk::ImageSubresourceRange::builder().aspect_mask(vk::ImageAspectFlags::COLOR).level_count(1).layer_count(1).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
unsafe { gfx.device.cmd_pipeline_barrier2(frame.command_buffer, &vk::DependencyInfo::builder().image_memory_barriers(slice::from_ref(&image_memory_barrier)).build()) };
|
||||||
|
|
||||||
|
let color_attachment = vk::RenderingAttachmentInfo::builder()
|
||||||
|
.image_view(gfx.present_image_views[present_index as usize])
|
||||||
|
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||||
|
.store_op(vk::AttachmentStoreOp::STORE)
|
||||||
|
.clear_value(vk::ClearValue {
|
||||||
|
color: vk::ClearColorValue {
|
||||||
|
float32: [0.0, 0.0, 0.0, 1.0]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let rendering_info = vk::RenderingInfo::builder()
|
||||||
|
.render_area(vk::Rect2D::builder().extent(vk::Extent2D::builder().width(1600).height(900).build()).build())
|
||||||
|
.layer_count(1)
|
||||||
|
.color_attachments(slice::from_ref(&color_attachment))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
unsafe { gfx.device.cmd_begin_rendering(frame.command_buffer, &rendering_info) };
|
||||||
|
|
||||||
|
let descriptor = VkDescriptor::Buffer(color_buf.clone());
|
||||||
|
pipeline.bind_descriptors(slice::from_ref(&descriptor));
|
||||||
|
pipeline.render(&frame, vk::Rect2D {
|
||||||
|
offset: vk::Offset2D { x: 0, y: 0 },
|
||||||
|
extent: vk::Extent2D { width: 1600, height: 900 }
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { gfx.device.cmd_end_rendering(frame.command_buffer) };
|
||||||
|
|
||||||
|
|
||||||
|
let image_memory_barrier = vk::ImageMemoryBarrier2::builder()
|
||||||
|
.src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
.src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||||
|
.dst_stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)
|
||||||
|
.old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
|
||||||
|
.image(image)
|
||||||
|
.subresource_range(vk::ImageSubresourceRange::builder().aspect_mask(vk::ImageAspectFlags::COLOR).level_count(1).layer_count(1).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gfx.device.cmd_pipeline_barrier2(frame.command_buffer, &vk::DependencyInfo::builder().image_memory_barriers(slice::from_ref(&image_memory_barrier)).build());
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { gfx.device.end_command_buffer(frame.command_buffer) }.unwrap();
|
||||||
|
|
||||||
|
let wait_semaphores = [frame.present_semaphore];
|
||||||
|
let wait_dst_stage_mask = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
||||||
|
|
||||||
|
let submit_info = vk::SubmitInfo::builder()
|
||||||
|
.wait_semaphores(&wait_semaphores)
|
||||||
|
.wait_dst_stage_mask(&wait_dst_stage_mask)
|
||||||
|
.command_buffers(slice::from_ref(&frame.command_buffer))
|
||||||
|
.signal_semaphores(slice::from_ref(&frame.render_semaphore));
|
||||||
|
|
||||||
|
unsafe { gfx.device.queue_submit(gfx.present_queue, slice::from_ref(&submit_info), frame.fence) }.unwrap();
|
||||||
|
|
||||||
|
let present_info = vk::PresentInfoKHR::builder()
|
||||||
|
.wait_semaphores(slice::from_ref(&frame.present_semaphore))
|
||||||
|
.swapchains(slice::from_ref(&gfx.swapchain))
|
||||||
|
.image_indices(slice::from_ref(&present_index))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
unsafe { gfx.swapchain_loader.queue_present(gfx.present_queue, &present_info).unwrap() };
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { gfx.device.device_wait_idle().unwrap() };
|
||||||
|
}
|
||||||
|
|
||||||
41
src/overlays/interactions.rs
Normal file
41
src/overlays/interactions.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use std::{collections::VecDeque, time::Instant};
|
||||||
|
|
||||||
|
use glam::{Affine3A, Vec2, Vec3};
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub const HAND_LEFT: usize = 0;
|
||||||
|
pub const HAND_RIGHT: usize = 1;
|
||||||
|
|
||||||
|
pub const POINTER_NORM: u16 = 0;
|
||||||
|
pub const POINTER_SHIFT: u16 = 1;
|
||||||
|
pub const POINTER_ALT: u16 = 2;
|
||||||
|
|
||||||
|
pub trait InteractionHandler {
|
||||||
|
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit);
|
||||||
|
fn on_left(&mut self, app: &mut AppState, hand: usize);
|
||||||
|
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
|
||||||
|
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Dummies & plumbing below ---
|
||||||
|
|
||||||
|
impl Default for PointerState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
click: false,
|
||||||
|
grab: false,
|
||||||
|
show_hide: false,
|
||||||
|
scroll: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DummyInteractionHandler;
|
||||||
|
|
||||||
|
impl InteractionHandler for DummyInteractionHandler {
|
||||||
|
fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
|
||||||
|
fn on_hover(&mut self, _app: &mut AppState, _hit: &PointerHit) {}
|
||||||
|
fn on_pointer(&mut self, _app: &mut AppState, _hit: &PointerHit, _pressed: bool) {}
|
||||||
|
fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta: f32) {}
|
||||||
|
}
|
||||||
354
src/overlays/keyboard.rs
Normal file
354
src/overlays/keyboard.rs
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
env::var,
|
||||||
|
fs,
|
||||||
|
io::Cursor,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Child, Command},
|
||||||
|
str::FromStr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gui::{color_parse, CanvasBuilder, Control},
|
||||||
|
input::{KeyModifier, VirtualKey, KEYS_TO_MODS},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
use glam::{vec2, vec3};
|
||||||
|
use log::error;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
use rodio::{Decoder, OutputStream, Source};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::OverlayData;
|
||||||
|
|
||||||
|
const PIXELS_PER_UNIT: f32 = 80.;
|
||||||
|
const BUTTON_PADDING: f32 = 4.;
|
||||||
|
|
||||||
|
pub fn create_keyboard(app: &AppState) -> OverlayData {
|
||||||
|
let size = vec2(
|
||||||
|
LAYOUT.row_size * PIXELS_PER_UNIT,
|
||||||
|
(LAYOUT.main_layout.len() as f32) * PIXELS_PER_UNIT,
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = KeyboardData {
|
||||||
|
modifiers: 0,
|
||||||
|
processes: vec![],
|
||||||
|
audio_stream: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut canvas = CanvasBuilder::new(
|
||||||
|
size.x as _,
|
||||||
|
size.y as _,
|
||||||
|
app.graphics.clone(),
|
||||||
|
app.format,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.bg_color = color_parse("#101010");
|
||||||
|
canvas.panel(0., 0., size.x, size.y);
|
||||||
|
|
||||||
|
canvas.font_size = 18;
|
||||||
|
canvas.bg_color = color_parse("#202020");
|
||||||
|
|
||||||
|
let unit_size = size.x / LAYOUT.row_size;
|
||||||
|
let h = unit_size - 2. * BUTTON_PADDING;
|
||||||
|
|
||||||
|
for row in 0..LAYOUT.key_sizes.len() {
|
||||||
|
let y = unit_size * (row as f32) + BUTTON_PADDING;
|
||||||
|
let mut sum_size = 0f32;
|
||||||
|
|
||||||
|
for col in 0..LAYOUT.key_sizes[row].len() {
|
||||||
|
let my_size = LAYOUT.key_sizes[row][col];
|
||||||
|
let x = unit_size * sum_size + BUTTON_PADDING;
|
||||||
|
let w = unit_size * my_size - 2. * BUTTON_PADDING;
|
||||||
|
|
||||||
|
if let Some(key) = LAYOUT.main_layout[row][col].as_ref() {
|
||||||
|
let mut maybe_state: Option<KeyButtonData> = None;
|
||||||
|
if let Ok(vk) = VirtualKey::from_str(key) {
|
||||||
|
if let Some(mods) = KEYS_TO_MODS.get(vk) {
|
||||||
|
maybe_state = Some(KeyButtonData::Modifier {
|
||||||
|
modifier: *mods,
|
||||||
|
sticky: false,
|
||||||
|
pressed: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
maybe_state = Some(KeyButtonData::Key { vk, pressed: false });
|
||||||
|
}
|
||||||
|
} else if let Some(macro_verbs) = LAYOUT.macros.get(key) {
|
||||||
|
maybe_state = Some(KeyButtonData::Macro {
|
||||||
|
verbs: key_events_for_macro(macro_verbs),
|
||||||
|
});
|
||||||
|
} else if let Some(exec_args) = LAYOUT.exec_commands.get(key) {
|
||||||
|
maybe_state = Some(KeyButtonData::Exec {
|
||||||
|
program: exec_args.first().unwrap().clone(),
|
||||||
|
args: exec_args.iter().skip(1).cloned().collect(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
error!("Unknown key: {}", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(state) = maybe_state {
|
||||||
|
let label = LAYOUT.label_for_key(key);
|
||||||
|
let button = canvas.key_button(x, y, w, h, &label);
|
||||||
|
button.state = Some(state);
|
||||||
|
button.on_press = Some(key_press);
|
||||||
|
button.on_release = Some(key_release);
|
||||||
|
button.test_highlight = Some(test_highlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sum_size += my_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let canvas = canvas.build();
|
||||||
|
|
||||||
|
OverlayData {
|
||||||
|
name: Arc::from("Kbd"),
|
||||||
|
show_hide: true,
|
||||||
|
width: LAYOUT.row_size * 0.05,
|
||||||
|
size: (size.x as _, size.y as _),
|
||||||
|
grabbable: true,
|
||||||
|
spawn_point: vec3(0., -0.5, -1.),
|
||||||
|
backend: Box::new(canvas),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_press(
|
||||||
|
control: &mut Control<KeyboardData, KeyButtonData>,
|
||||||
|
data: &mut KeyboardData,
|
||||||
|
app: &mut AppState,
|
||||||
|
) {
|
||||||
|
match control.state.as_mut() {
|
||||||
|
Some(KeyButtonData::Key { vk, pressed }) => {
|
||||||
|
data.key_click();
|
||||||
|
app.input.send_key(*vk as _, true);
|
||||||
|
*pressed = true;
|
||||||
|
}
|
||||||
|
Some(KeyButtonData::Modifier {
|
||||||
|
modifier,
|
||||||
|
sticky,
|
||||||
|
pressed,
|
||||||
|
}) => {
|
||||||
|
*sticky = data.modifiers & *modifier == 0;
|
||||||
|
data.modifiers |= *modifier;
|
||||||
|
data.key_click();
|
||||||
|
app.input.set_modifiers(data.modifiers);
|
||||||
|
*pressed = true;
|
||||||
|
}
|
||||||
|
Some(KeyButtonData::Macro { verbs }) => {
|
||||||
|
data.key_click();
|
||||||
|
for (vk, press) in verbs {
|
||||||
|
app.input.send_key(*vk as _, *press);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(KeyButtonData::Exec { program, args }) => {
|
||||||
|
// Reap previous processes
|
||||||
|
data.processes
|
||||||
|
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
|
||||||
|
|
||||||
|
data.key_click();
|
||||||
|
if let Ok(child) = Command::new(program).args(args).spawn() {
|
||||||
|
data.processes.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_release(
|
||||||
|
control: &mut Control<KeyboardData, KeyButtonData>,
|
||||||
|
data: &mut KeyboardData,
|
||||||
|
app: &mut AppState,
|
||||||
|
) {
|
||||||
|
match control.state.as_mut() {
|
||||||
|
Some(KeyButtonData::Key { vk, pressed }) => {
|
||||||
|
app.input.send_key(*vk as _, false);
|
||||||
|
*pressed = false;
|
||||||
|
}
|
||||||
|
Some(KeyButtonData::Modifier {
|
||||||
|
modifier,
|
||||||
|
sticky,
|
||||||
|
pressed,
|
||||||
|
}) => {
|
||||||
|
if !*sticky {
|
||||||
|
data.modifiers &= !*modifier;
|
||||||
|
app.input.set_modifiers(data.modifiers);
|
||||||
|
*pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_highlight(
|
||||||
|
control: &Control<KeyboardData, KeyButtonData>,
|
||||||
|
_data: &mut KeyboardData,
|
||||||
|
_app: &mut AppState,
|
||||||
|
) -> bool {
|
||||||
|
match control.state.as_ref() {
|
||||||
|
Some(KeyButtonData::Key { pressed, .. }) => *pressed,
|
||||||
|
Some(KeyButtonData::Modifier { pressed, .. }) => *pressed,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeyboardData {
|
||||||
|
modifiers: KeyModifier,
|
||||||
|
processes: Vec<Child>,
|
||||||
|
audio_stream: Option<OutputStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyboardData {
|
||||||
|
fn key_click(&mut self) {
|
||||||
|
let wav = include_bytes!("../res/421581.wav");
|
||||||
|
let cursor = Cursor::new(wav);
|
||||||
|
let source = Decoder::new_wav(cursor).unwrap();
|
||||||
|
self.audio_stream = None;
|
||||||
|
if let Ok((stream, handle)) = OutputStream::try_default() {
|
||||||
|
let _ = handle.play_raw(source.convert_samples());
|
||||||
|
self.audio_stream = Some(stream);
|
||||||
|
} else {
|
||||||
|
error!("Failed to play key click");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum KeyButtonData {
|
||||||
|
Key {
|
||||||
|
vk: VirtualKey,
|
||||||
|
pressed: bool,
|
||||||
|
},
|
||||||
|
Modifier {
|
||||||
|
modifier: KeyModifier,
|
||||||
|
sticky: bool,
|
||||||
|
pressed: bool,
|
||||||
|
},
|
||||||
|
Macro {
|
||||||
|
verbs: Vec<(VirtualKey, bool)>,
|
||||||
|
},
|
||||||
|
Exec {
|
||||||
|
program: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
static KEYBOARD_YAML: Lazy<PathBuf> = Lazy::new(|| {
|
||||||
|
let home = &var("HOME").unwrap();
|
||||||
|
[home, ".config/wlxoverlay/keyboard.yaml"].iter().collect() //TODO other paths
|
||||||
|
});
|
||||||
|
|
||||||
|
static LAYOUT: Lazy<Layout> = Lazy::new(Layout::load_from_disk);
|
||||||
|
|
||||||
|
static MACRO_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap());
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct Layout {
|
||||||
|
name: String,
|
||||||
|
row_size: f32,
|
||||||
|
key_sizes: Vec<Vec<f32>>,
|
||||||
|
main_layout: Vec<Vec<Option<String>>>,
|
||||||
|
exec_commands: HashMap<String, Vec<String>>,
|
||||||
|
macros: HashMap<String, Vec<String>>,
|
||||||
|
labels: HashMap<String, Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout {
|
||||||
|
fn load_from_disk() -> Layout {
|
||||||
|
let mut yaml = fs::read_to_string(KEYBOARD_YAML.as_path()).ok();
|
||||||
|
|
||||||
|
if yaml.is_none() {
|
||||||
|
yaml = Some(include_str!("../res/keyboard.yaml").to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut layout: Layout =
|
||||||
|
serde_yaml::from_str(&yaml.unwrap()).expect("Failed to parse keyboard.yaml");
|
||||||
|
layout.post_load();
|
||||||
|
|
||||||
|
layout
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_load(&mut self) {
|
||||||
|
for i in 0..self.key_sizes.len() {
|
||||||
|
let row = &self.key_sizes[i];
|
||||||
|
let width: f32 = row.iter().sum();
|
||||||
|
if (width - self.row_size).abs() > 0.001 {
|
||||||
|
panic!(
|
||||||
|
"Row {} has a width of {}, but the row size is {}",
|
||||||
|
i, width, self.row_size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..self.main_layout.len() {
|
||||||
|
let row = &self.main_layout[i];
|
||||||
|
let width = row.len();
|
||||||
|
if width != self.key_sizes[i].len() {
|
||||||
|
panic!(
|
||||||
|
"Row {} has {} keys, needs to have {} according to key_sizes",
|
||||||
|
i,
|
||||||
|
width,
|
||||||
|
self.key_sizes[i].len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_for_key(&self, key: &str) -> Vec<String> {
|
||||||
|
if let Some(label) = self.labels.get(key) {
|
||||||
|
return label.clone();
|
||||||
|
}
|
||||||
|
if key.is_empty() {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
if key.len() == 1 {
|
||||||
|
return vec![key.to_string().to_lowercase()];
|
||||||
|
}
|
||||||
|
let mut key = key;
|
||||||
|
if key.starts_with("KP_") {
|
||||||
|
key = &key[3..];
|
||||||
|
}
|
||||||
|
if key.contains('_') {
|
||||||
|
key = key.split('_').next().unwrap();
|
||||||
|
}
|
||||||
|
vec![format!(
|
||||||
|
"{}{}",
|
||||||
|
key.chars().next().unwrap().to_uppercase(),
|
||||||
|
&key[1..].to_lowercase()
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_events_for_macro(macro_verbs: &Vec<String>) -> Vec<(VirtualKey, bool)> {
|
||||||
|
let mut key_events = vec![];
|
||||||
|
for verb in macro_verbs {
|
||||||
|
if let Some(caps) = MACRO_REGEX.captures(verb) {
|
||||||
|
if let Ok(virtual_key) = VirtualKey::from_str(&caps[1]) {
|
||||||
|
if let Some(state) = caps.get(2) {
|
||||||
|
if state.as_str() == "UP" {
|
||||||
|
key_events.push((virtual_key, false));
|
||||||
|
} else if state.as_str() == "DOWN" {
|
||||||
|
key_events.push((virtual_key, true));
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"Unknown key state in macro: {}, looking for UP or DOWN.",
|
||||||
|
state.as_str()
|
||||||
|
);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key_events.push((virtual_key, true));
|
||||||
|
key_events.push((virtual_key, false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Unknown virtual key: {}", &caps[1]);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_events
|
||||||
|
}
|
||||||
145
src/overlays/mod.rs
Normal file
145
src/overlays/mod.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use glam::{Affine3A, Quat, Vec3};
|
||||||
|
use vulkano::image::ImageViewAbstract;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
use self::interactions::{DummyInteractionHandler, InteractionHandler, PointerHit};
|
||||||
|
|
||||||
|
pub mod interactions;
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod watch;
|
||||||
|
|
||||||
|
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
pub enum RelativeTo {
|
||||||
|
None,
|
||||||
|
Head,
|
||||||
|
Hand(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OverlayBackend: OverlayRenderer + InteractionHandler {}
|
||||||
|
|
||||||
|
pub struct OverlayData {
|
||||||
|
pub id: usize,
|
||||||
|
pub name: Arc<str>,
|
||||||
|
pub width: f32,
|
||||||
|
pub size: (i32, i32),
|
||||||
|
pub want_visible: bool,
|
||||||
|
pub show_hide: bool,
|
||||||
|
pub grabbable: bool,
|
||||||
|
pub transform: Affine3A,
|
||||||
|
pub spawn_point: Vec3,
|
||||||
|
pub spawn_rotation: Quat,
|
||||||
|
pub relative_to: RelativeTo,
|
||||||
|
pub interaction_transform: Affine3A,
|
||||||
|
pub backend: Box<dyn OverlayBackend>,
|
||||||
|
pub primary_pointer: Option<usize>,
|
||||||
|
}
|
||||||
|
impl Default for OverlayData {
|
||||||
|
fn default() -> OverlayData {
|
||||||
|
OverlayData {
|
||||||
|
id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed),
|
||||||
|
name: Arc::from(""),
|
||||||
|
width: 1.,
|
||||||
|
size: (0, 0),
|
||||||
|
want_visible: false,
|
||||||
|
show_hide: false,
|
||||||
|
grabbable: false,
|
||||||
|
relative_to: RelativeTo::None,
|
||||||
|
spawn_point: Vec3::NEG_Z,
|
||||||
|
spawn_rotation: Quat::IDENTITY,
|
||||||
|
transform: Affine3A::IDENTITY,
|
||||||
|
interaction_transform: Affine3A::IDENTITY,
|
||||||
|
backend: Box::new(SplitOverlayBackend::default()),
|
||||||
|
primary_pointer: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayData {
|
||||||
|
pub fn reset(&mut self, app: &mut AppState) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn init(&mut self, app: &mut AppState) {
|
||||||
|
self.backend.init(app);
|
||||||
|
}
|
||||||
|
pub fn render(&mut self, app: &mut AppState) {
|
||||||
|
self.backend.render(app);
|
||||||
|
}
|
||||||
|
pub fn view(&mut self) -> Arc<dyn ImageViewAbstract> {
|
||||||
|
self.backend.view()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OverlayRenderer {
|
||||||
|
fn init(&mut self, app: &mut AppState);
|
||||||
|
fn pause(&mut self, app: &mut AppState);
|
||||||
|
fn resume(&mut self, app: &mut AppState);
|
||||||
|
fn render(&mut self, app: &mut AppState);
|
||||||
|
fn view(&mut self) -> Arc<dyn ImageViewAbstract>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FallbackRenderer;
|
||||||
|
|
||||||
|
impl OverlayRenderer for FallbackRenderer {
|
||||||
|
fn init(&mut self, _app: &mut AppState) {}
|
||||||
|
fn pause(&mut self, _app: &mut AppState) {}
|
||||||
|
fn resume(&mut self, _app: &mut AppState) {}
|
||||||
|
fn render(&mut self, _app: &mut AppState) {}
|
||||||
|
fn view(&mut self) -> Arc<dyn ImageViewAbstract> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Boilerplate and dummies
|
||||||
|
|
||||||
|
pub struct SplitOverlayBackend {
|
||||||
|
pub renderer: Box<dyn OverlayRenderer>,
|
||||||
|
pub interaction: Box<dyn InteractionHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SplitOverlayBackend {
|
||||||
|
fn default() -> SplitOverlayBackend {
|
||||||
|
SplitOverlayBackend {
|
||||||
|
renderer: Box::new(FallbackRenderer),
|
||||||
|
interaction: Box::new(DummyInteractionHandler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayBackend for SplitOverlayBackend {}
|
||||||
|
impl OverlayRenderer for SplitOverlayBackend {
|
||||||
|
fn init(&mut self, app: &mut AppState) {
|
||||||
|
self.renderer.init(app);
|
||||||
|
}
|
||||||
|
fn pause(&mut self, app: &mut AppState) {
|
||||||
|
self.renderer.pause(app);
|
||||||
|
}
|
||||||
|
fn resume(&mut self, app: &mut AppState) {
|
||||||
|
self.renderer.resume(app);
|
||||||
|
}
|
||||||
|
fn render(&mut self, app: &mut AppState) {
|
||||||
|
self.renderer.render(app);
|
||||||
|
}
|
||||||
|
fn view(&mut self) -> Arc<dyn ImageViewAbstract> {
|
||||||
|
self.renderer.view()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InteractionHandler for SplitOverlayBackend {
|
||||||
|
fn on_left(&mut self, app: &mut AppState, hand: usize) {
|
||||||
|
self.interaction.on_left(app, hand);
|
||||||
|
}
|
||||||
|
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) {
|
||||||
|
self.interaction.on_hover(app, hit);
|
||||||
|
}
|
||||||
|
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) {
|
||||||
|
self.interaction.on_scroll(app, hit, delta);
|
||||||
|
}
|
||||||
|
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
|
||||||
|
self.interaction.on_pointer(app, hit, pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/overlays/screen.rs
Normal file
31
src/overlays/screen.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
pub struct ScreenInteractionData {
|
||||||
|
next_scroll: Instant,
|
||||||
|
next_move: Instant,
|
||||||
|
mouse_transform: Affine2,
|
||||||
|
}
|
||||||
|
impl ScreenInteractionData {
|
||||||
|
fn new(pos: Vec2, size: Vec2, transform: Transform) -> ScreenInteractionHandler {
|
||||||
|
let transform = match transform {
|
||||||
|
Transform::_90 | Transform::Flipped90 =>
|
||||||
|
Affine2::from_cols(vec2(0., size.y), vec2(-size.x, 0.), vec2(pos.x + size.x, pos.y)),
|
||||||
|
Transform::_180 | Transform::Flipped180 =>
|
||||||
|
Affine2::from_cols(vec2(-size.x, 0.), vec2(0., -size.y), vec2(pos.x + size.x, pos.y + size.y)),
|
||||||
|
Transform::_270 | Transform::Flipped270 =>
|
||||||
|
Affine2::from_cols(vec2(0., -size.y), vec2(size.x, 0.), vec2(pos.x, pos.y + size.y)),
|
||||||
|
_ =>
|
||||||
|
Affine2::from_cols(vec2(size.x, 0.), vec2(0., size.y), pos),
|
||||||
|
};
|
||||||
|
|
||||||
|
ScreenInteractionHandler {
|
||||||
|
next_scroll: Instant::now(),
|
||||||
|
next_move: Instant::now(),
|
||||||
|
mouse_transform: transform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScreenInteractionHandler {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
170
src/overlays/watch.rs
Normal file
170
src/overlays/watch.rs
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
|
use chrono::Local;
|
||||||
|
use glam::{Quat, Vec3};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gui::{color_parse, CanvasBuilder},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{OverlayData, RelativeTo};
|
||||||
|
|
||||||
|
pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(0., 0., 0.15);
|
||||||
|
pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963);
|
||||||
|
|
||||||
|
pub fn create_watch(state: &AppState, screens: Vec<(usize, Arc<str>)>) -> OverlayData {
|
||||||
|
let mut canvas = CanvasBuilder::new(400, 200, state.graphics.clone(), state.format, ());
|
||||||
|
let empty_str: Arc<str> = Arc::from("");
|
||||||
|
|
||||||
|
// Background
|
||||||
|
canvas.bg_color = color_parse("#353535");
|
||||||
|
canvas.panel(0., 0., 400., 200.);
|
||||||
|
|
||||||
|
// Time display
|
||||||
|
canvas.font_size = 46;
|
||||||
|
let clock = canvas.label(19., 100., 200., 50., empty_str.clone());
|
||||||
|
clock.on_update = Some(|control, _data, _app| {
|
||||||
|
let date = Local::now();
|
||||||
|
control.set_text(&format!("{}", &date.format("%H:%M")));
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.font_size = 14;
|
||||||
|
let date = canvas.label(20., 125., 200., 50., empty_str.clone());
|
||||||
|
date.on_update = Some(|control, _data, _app| {
|
||||||
|
let date = Local::now();
|
||||||
|
control.set_text(&format!("{}", &date.format("%x")));
|
||||||
|
});
|
||||||
|
|
||||||
|
let day_of_week = canvas.label(20., 150., 200., 50., empty_str);
|
||||||
|
day_of_week.on_update = Some(|control, _data, _app| {
|
||||||
|
let date = Local::now();
|
||||||
|
control.set_text(&format!("{}", &date.format("%A")));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Volume controls
|
||||||
|
canvas.bg_color = color_parse("#222222");
|
||||||
|
canvas.fg_color = color_parse("#AAAAAA");
|
||||||
|
canvas.font_size = 14;
|
||||||
|
|
||||||
|
canvas.bg_color = color_parse("#303030");
|
||||||
|
canvas.fg_color = color_parse("#353535");
|
||||||
|
|
||||||
|
let vol_up = canvas.button(327., 116., 46., 32., "+".into());
|
||||||
|
vol_up.on_press = Some(|_control, _data, _app| {
|
||||||
|
println!("Volume up!"); //TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
let vol_dn = canvas.button(327., 52., 46., 32., "-".into());
|
||||||
|
vol_dn.on_press = Some(|_control, _data, _app| {
|
||||||
|
println!("Volume down!"); //TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.bg_color = color_parse("#303030");
|
||||||
|
canvas.fg_color = color_parse("#353535");
|
||||||
|
|
||||||
|
let settings = canvas.button(2., 162., 36., 36., "☰".into());
|
||||||
|
settings.on_press = Some(|_control, _data, _app| {
|
||||||
|
println!("Settings!"); //TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.fg_color = color_parse("#CCBBAA");
|
||||||
|
canvas.bg_color = color_parse("#406050");
|
||||||
|
// Bottom row
|
||||||
|
let num_buttons = screens.len() + 1;
|
||||||
|
let button_width = 360. / num_buttons as f32;
|
||||||
|
let mut button_x = 40.;
|
||||||
|
|
||||||
|
let keyboard = canvas.button(button_x + 2., 162., button_width - 4., 36., "Kbd".into());
|
||||||
|
keyboard.state = Some(WatchButtonState {
|
||||||
|
pressed_at: Instant::now(),
|
||||||
|
scr_idx: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on_press = Some(|control, _data, _app| {
|
||||||
|
if let Some(state) = control.state.as_mut() {
|
||||||
|
state.pressed_at = Instant::now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keyboard.on_release = Some(|control, _data, app| {
|
||||||
|
if let Some(state) = control.state.as_ref() {
|
||||||
|
if Instant::now()
|
||||||
|
.saturating_duration_since(state.pressed_at)
|
||||||
|
.as_millis()
|
||||||
|
< 2000
|
||||||
|
{
|
||||||
|
app.tasks.push_back(Box::new(|_app, o| {
|
||||||
|
for overlay in o {
|
||||||
|
if &*overlay.name == "Kbd" {
|
||||||
|
overlay.want_visible = !overlay.want_visible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
app.tasks.push_back(Box::new(|app, o| {
|
||||||
|
for overlay in o {
|
||||||
|
if &*overlay.name == "Kbd" {
|
||||||
|
overlay.reset(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button_x += button_width;
|
||||||
|
|
||||||
|
canvas.bg_color = color_parse("#405060");
|
||||||
|
|
||||||
|
for (scr_idx, scr_name) in screens.into_iter() {
|
||||||
|
let button = canvas.button(button_x + 2., 162., button_width - 4., 36., scr_name);
|
||||||
|
button.state = Some(WatchButtonState {
|
||||||
|
pressed_at: Instant::now(),
|
||||||
|
scr_idx,
|
||||||
|
});
|
||||||
|
|
||||||
|
button.on_press = Some(|control, _data, _app| {
|
||||||
|
if let Some(state) = control.state.as_mut() {
|
||||||
|
state.pressed_at = Instant::now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button.on_release = Some(|control, _data, app| {
|
||||||
|
if let Some(state) = control.state.as_ref() {
|
||||||
|
let scr_idx = state.scr_idx;
|
||||||
|
if Instant::now()
|
||||||
|
.saturating_duration_since(state.pressed_at)
|
||||||
|
.as_millis()
|
||||||
|
< 2000
|
||||||
|
{
|
||||||
|
app.tasks.push_back(Box::new(move |_app, o| {
|
||||||
|
o[scr_idx].want_visible = !o[scr_idx].want_visible;
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
app.tasks.push_back(Box::new(move |app, o| {
|
||||||
|
o[scr_idx].reset(app);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button_x += button_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let relative_to = RelativeTo::Hand(state.session.watch_hand);
|
||||||
|
|
||||||
|
OverlayData {
|
||||||
|
name: "Watch".into(),
|
||||||
|
size: (400, 200),
|
||||||
|
width: 0.065,
|
||||||
|
backend: Box::new(canvas.build()),
|
||||||
|
want_visible: true,
|
||||||
|
relative_to,
|
||||||
|
spawn_point: state.session.watch_pos,
|
||||||
|
spawn_rotation: state.session.watch_rot,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WatchButtonState {
|
||||||
|
pressed_at: Instant,
|
||||||
|
scr_idx: usize,
|
||||||
|
}
|
||||||
64
src/ovr.rs
Normal file
64
src/ovr.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use vulkano::{
|
||||||
|
image::{
|
||||||
|
sys::{Image, RawImage},
|
||||||
|
ImageViewAbstract,
|
||||||
|
},
|
||||||
|
Handle, VulkanObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::graphics::WlxGraphics;
|
||||||
|
|
||||||
|
pub struct OpenVrState {
|
||||||
|
pub context: ovr_overlay::Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct OvrTextureData {
|
||||||
|
image_handle: u64,
|
||||||
|
device: u64,
|
||||||
|
physical: u64,
|
||||||
|
instance: u64,
|
||||||
|
queue: u64,
|
||||||
|
queue_family_index: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
format: u32,
|
||||||
|
sample_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OvrTextureData {
|
||||||
|
pub fn new(graphics: Arc<WlxGraphics>, view: Arc<dyn ImageViewAbstract>) -> OvrTextureData {
|
||||||
|
let image = view.image();
|
||||||
|
|
||||||
|
let device = graphics.device.handle().as_raw();
|
||||||
|
let physical = graphics.device.physical_device().handle().as_raw();
|
||||||
|
let instance = graphics.instance.handle().as_raw();
|
||||||
|
let queue = graphics.queue.handle().as_raw();
|
||||||
|
let queue_family_index = graphics.queue.queue_family_index();
|
||||||
|
|
||||||
|
let (width, height) = {
|
||||||
|
let dim = image.dimensions();
|
||||||
|
(dim.width() as u32, dim.height() as u32)
|
||||||
|
};
|
||||||
|
|
||||||
|
let sample_count = image.samples() as u32;
|
||||||
|
let format = image.format() as u32;
|
||||||
|
|
||||||
|
let image_handle = image.inner().image.handle().as_raw();
|
||||||
|
|
||||||
|
OvrTextureData {
|
||||||
|
image_handle,
|
||||||
|
device,
|
||||||
|
physical,
|
||||||
|
instance,
|
||||||
|
queue,
|
||||||
|
queue_family_index,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
format,
|
||||||
|
sample_count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/res/421581.wav
Normal file
BIN
src/res/421581.wav
Normal file
Binary file not shown.
BIN
src/res/660533.wav
Normal file
BIN
src/res/660533.wav
Normal file
Binary file not shown.
83
src/res/actions.json
Normal file
83
src/res/actions.json
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/Click",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "mandatory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/Grab",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "mandatory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/Scroll",
|
||||||
|
"type": "vector2",
|
||||||
|
"requirement": "mandatory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/ShowHide",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "mandatory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/AltClick",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/ClickModifierRight",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/ClickModifierMiddle",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/SpaceDrag",
|
||||||
|
"type": "boolean",
|
||||||
|
"requirement": "optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/LeftHand",
|
||||||
|
"type": "pose",
|
||||||
|
"requirement": "optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/in/RightHand",
|
||||||
|
"type": "pose",
|
||||||
|
"requirement": "optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/out/HapticsLeft",
|
||||||
|
"type": "vibration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "/actions/default/out/HapticsRight",
|
||||||
|
"type": "vibration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action_sets": [
|
||||||
|
{
|
||||||
|
"name": "/actions/default",
|
||||||
|
"usage": "leftright"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_bindings": [
|
||||||
|
{
|
||||||
|
"controller_type": "knuckles",
|
||||||
|
"binding_url": "actions_binding_knuckles.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"controller_type": "oculus_touch",
|
||||||
|
"binding_url": "actions_binding_oculus.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"controller_type": "vive_controller",
|
||||||
|
"binding_url": "actions_binding_vive.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"localization": []
|
||||||
|
}
|
||||||
173
src/res/actions_binding_knuckles.json
Normal file
173
src/res/actions_binding_knuckles.json
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
{
|
||||||
|
"action_manifest_version" : 0,
|
||||||
|
"app_key" : "galister.wlxoverlay",
|
||||||
|
"bindings" : {
|
||||||
|
"/actions/default" : {
|
||||||
|
"haptics" : [
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/out/hapticsleft",
|
||||||
|
"path" : "/user/hand/left/output/haptic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/out/hapticsright",
|
||||||
|
"path" : "/user/hand/right/output/haptic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"poses" : [
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/in/lefthand",
|
||||||
|
"path" : "/user/hand/left/pose/tip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/in/righthand",
|
||||||
|
"path" : "/user/hand/right/pose/tip"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources" : [
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"double" : {
|
||||||
|
"output" : "/actions/default/in/showhide"
|
||||||
|
},
|
||||||
|
"touch" : {
|
||||||
|
"output": "/actions/default/in/clickmodifierright"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "button",
|
||||||
|
"path" : "/user/hand/left/input/b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/a",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"touch": {
|
||||||
|
"output": "/actions/default/in/clickmodifiermiddle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/b",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"touch": {
|
||||||
|
"output": "/actions/default/in/clickmodifierright"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/a",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"touch": {
|
||||||
|
"output": "/actions/default/in/clickmodifiermiddle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/click"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "button",
|
||||||
|
"parameters" : {
|
||||||
|
"click_activate_threshold" : "0.35",
|
||||||
|
"click_deactivate_threshold" : "0.31"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/left/input/trigger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/click"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "button",
|
||||||
|
"parameters" : {
|
||||||
|
"click_activate_threshold" : "0.35",
|
||||||
|
"click_deactivate_threshold" : "0.31"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/right/input/trigger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/altclick"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "button",
|
||||||
|
"path" : "/user/hand/right/input/trackpad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/altclick"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "button",
|
||||||
|
"path" : "/user/hand/left/input/trackpad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"grab" : {
|
||||||
|
"output" : "/actions/default/in/grab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "grab",
|
||||||
|
"parameters" : {
|
||||||
|
"value_hold_threshold" : "1.3",
|
||||||
|
"value_release_threshold" : "1.1"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/left/input/grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"grab" : {
|
||||||
|
"output" : "/actions/default/in/grab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "grab",
|
||||||
|
"parameters" : {
|
||||||
|
"value_hold_threshold" : "1.3",
|
||||||
|
"value_release_threshold" : "1.1"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/right/input/grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"scroll" : {
|
||||||
|
"output" : "/actions/default/in/scroll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "scroll",
|
||||||
|
"parameters" : {
|
||||||
|
"scroll_mode" : "smooth"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/left/input/thumbstick"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"scroll" : {
|
||||||
|
"output" : "/actions/default/in/scroll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "scroll",
|
||||||
|
"parameters" : {
|
||||||
|
"scroll_mode" : "smooth"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/right/input/thumbstick"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"category" : "steamvr_input",
|
||||||
|
"controller_type" : "knuckles",
|
||||||
|
"description" : "Ver1",
|
||||||
|
"interaction_profile" : "",
|
||||||
|
"name" : "WlxOverlay configuration for Index Controller",
|
||||||
|
"options" : {
|
||||||
|
"mirror_actions" : false,
|
||||||
|
"simulated_controller_type" : "none"
|
||||||
|
},
|
||||||
|
"simulated_actions" : []
|
||||||
|
}
|
||||||
139
src/res/actions_binding_oculus.json
Normal file
139
src/res/actions_binding_oculus.json
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"action_manifest_version" : 0,
|
||||||
|
"app_key" : "galister.wlxoverlay",
|
||||||
|
"bindings" : {
|
||||||
|
"/actions/default" : {
|
||||||
|
"haptics" : [
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/out/hapticsleft",
|
||||||
|
"path" : "/user/hand/left/output/haptic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/out/hapticsright",
|
||||||
|
"path" : "/user/hand/right/output/haptic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"poses" : [
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/in/lefthand",
|
||||||
|
"path" : "/user/hand/left/pose/tip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/in/righthand",
|
||||||
|
"path" : "/user/hand/right/pose/tip"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources" : [
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"double" : {
|
||||||
|
"output" : "/actions/default/in/showhide"
|
||||||
|
},
|
||||||
|
"touch" : {
|
||||||
|
"output": "/actions/default/in/clickmodifierright"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "button",
|
||||||
|
"path" : "/user/hand/left/input/y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/x",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"touch": {
|
||||||
|
"output": "/actions/default/in/clickmodifiermiddle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/b",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"touch": {
|
||||||
|
"output": "/actions/default/in/clickmodifierright"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/a",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"touch": {
|
||||||
|
"output": "/actions/default/in/clickmodifiermiddle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/click"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": "button",
|
||||||
|
"path" : "/user/hand/left/input/trigger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/click"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": "button",
|
||||||
|
"path" : "/user/hand/right/input/trigger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/grab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": "button",
|
||||||
|
"path" : "/user/hand/left/input/grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"click" : {
|
||||||
|
"output" : "/actions/default/in/grab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": "button",
|
||||||
|
"path" : "/user/hand/right/input/grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"scroll" : {
|
||||||
|
"output" : "/actions/default/in/scroll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "scroll",
|
||||||
|
"parameters" : {
|
||||||
|
"scroll_mode" : "smooth"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/left/input/joystick"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs" : {
|
||||||
|
"scroll" : {
|
||||||
|
"output" : "/actions/default/in/scroll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode" : "scroll",
|
||||||
|
"parameters" : {
|
||||||
|
"scroll_mode" : "smooth"
|
||||||
|
},
|
||||||
|
"path" : "/user/hand/right/input/joystick"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"category" : "steamvr_input",
|
||||||
|
"controller_type" : "oculus_touch",
|
||||||
|
"description" : "Ver1",
|
||||||
|
"interaction_profile" : "",
|
||||||
|
"name" : "WlxOverlay configuration for Oculus Touch Controller",
|
||||||
|
"options" : {
|
||||||
|
"mirror_actions" : false,
|
||||||
|
"simulated_controller_type" : "none"
|
||||||
|
},
|
||||||
|
"simulated_actions" : []
|
||||||
|
}
|
||||||
126
src/res/actions_binding_vive.json
Normal file
126
src/res/actions_binding_vive.json
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"action_manifest_version" : 0,
|
||||||
|
"app_key" : "galister.wlxoverlay",
|
||||||
|
"bindings" : {
|
||||||
|
"/actions/default": {
|
||||||
|
"haptics" : [
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/out/hapticsleft",
|
||||||
|
"path" : "/user/hand/left/output/haptic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output" : "/actions/default/out/hapticsright",
|
||||||
|
"path" : "/user/hand/right/output/haptic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/grip",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"click": {
|
||||||
|
"output": "/actions/default/in/grab"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/grip",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"click": {
|
||||||
|
"output": "/actions/default/in/grab"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/trigger",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"click": {
|
||||||
|
"output": "/actions/default/in/click"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/trigger",
|
||||||
|
"mode": "trigger",
|
||||||
|
"inputs": {
|
||||||
|
"click": {
|
||||||
|
"output": "/actions/default/in/click"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/trackpad",
|
||||||
|
"mode": "scroll",
|
||||||
|
"parameters": {
|
||||||
|
"scroll_mode": "smooth"
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"scroll": {
|
||||||
|
"output": "/actions/default/in/scroll"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/trackpad",
|
||||||
|
"mode": "scroll",
|
||||||
|
"inputs": {
|
||||||
|
"scroll": {
|
||||||
|
"output": "/actions/default/in/scroll"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/application_menu",
|
||||||
|
"mode": "button",
|
||||||
|
"inputs": {
|
||||||
|
"double": {
|
||||||
|
"output": "/actions/default/in/showhide"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/right/input/trackpad",
|
||||||
|
"mode": "dpad",
|
||||||
|
"parameters": {
|
||||||
|
"sub_mode": "touch"
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"west": {
|
||||||
|
"output": "/actions/default/in/clickmodifiermiddle"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"output": "/actions/default/in/clickmodifierright"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/user/hand/left/input/trackpad",
|
||||||
|
"mode": "dpad",
|
||||||
|
"parameters": {
|
||||||
|
"sub_mode": "touch"
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"west": {
|
||||||
|
"output": "/actions/default/in/clickmodifiermiddle"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"output": "/actions/default/in/clickmodifierright"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"category" : "steamvr_input",
|
||||||
|
"controller_type" : "vive_controller",
|
||||||
|
"description" : "Ver1",
|
||||||
|
"interaction_profile" : "",
|
||||||
|
"name" : "WlxOverlay configuration for Vive Controller",
|
||||||
|
"options" : {
|
||||||
|
"mirror_actions" : false,
|
||||||
|
"simulated_controller_type" : "none"
|
||||||
|
},
|
||||||
|
"simulated_actions" : []
|
||||||
|
}
|
||||||
127
src/res/keyboard.yaml
Normal file
127
src/res/keyboard.yaml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
# This file contains all data needed to generate the keyboard.
|
||||||
|
# You can create any layout, as long as:
|
||||||
|
# - All keys are rectangular with 1 unit of height.
|
||||||
|
# This means:
|
||||||
|
# - We're limited to the flat & boring ANSI enter key.
|
||||||
|
# - Numpad + and Enter might not look so great.
|
||||||
|
|
||||||
|
# *** Important ***
|
||||||
|
# The keyboard layout uses virtual key codes, so they are layout-independent.
|
||||||
|
# For example, Q on a French layout actually results in A.
|
||||||
|
# If you're using a non-english layout, chances are you only need to edit the label section below.
|
||||||
|
|
||||||
|
# Not used for anything right now
|
||||||
|
name: "en-us_full"
|
||||||
|
|
||||||
|
# How many units of key size in each row? 1 = standard letter key size
|
||||||
|
row_size: 23
|
||||||
|
|
||||||
|
# Specifies the size of each key. The sum of any given row must equal RowSize
|
||||||
|
key_sizes:
|
||||||
|
- [1.5,0.5, 1, 1, 1, 1,0.5,1, 1, 1, 1,0.5,1, 1, 1, 1, 0.5, 1, 1, 1, 4.5]
|
||||||
|
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||||
|
- [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1]
|
||||||
|
- [1.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 4, 1, 1, 1, 1]
|
||||||
|
- [2.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1]
|
||||||
|
- [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1]
|
||||||
|
|
||||||
|
# The main (blue) layout of the keyboard.
|
||||||
|
# Accepted are:
|
||||||
|
# - virtual keys, for a full list go to https://github.com/galister/X11Overlay/blob/master/Types/VirtualKey.cs
|
||||||
|
# - ExecCommands (defined below)
|
||||||
|
# - Macros (defined below)
|
||||||
|
# - ~ (null) will leave an empty space with the corresponding size from key_sizes
|
||||||
|
main_layout:
|
||||||
|
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~]
|
||||||
|
- ["Oem3", "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "N0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"]
|
||||||
|
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
|
||||||
|
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
|
||||||
|
- ["LShift", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
|
||||||
|
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~]
|
||||||
|
|
||||||
|
# When using the purple pointer...
|
||||||
|
# None - No special functionality when using purple pointer
|
||||||
|
# Shift - Use same functionality as the orange pointer
|
||||||
|
# Ctrl - Use Main layout with Ctrl modifier
|
||||||
|
# Super - Use Main layout with Super (WinKey) modifier
|
||||||
|
# Meta - Use Main layout with Meta (AltGr) modifier
|
||||||
|
# Layout - Use the alt_layout defined below
|
||||||
|
alt_layout_mode: Ctrl
|
||||||
|
|
||||||
|
# The alt (fn) layout of the keyboard. Only takes effect when alt_layout_mode: Layout
|
||||||
|
# Accepted values are the same as for MainLayout.
|
||||||
|
# The default layout here is a dummy. Change it to fit your liking.
|
||||||
|
alt_layout:
|
||||||
|
- ["Escape", ~, "F1", "F2", "F3", "F4", ~, "F5", "F6", "F7", "F8", ~, "F9", "F10", "F11", "F12", ~, "Print", "Scroll", "Pause", ~, "COPY", "PASTE"]
|
||||||
|
- ["Oem3", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"]
|
||||||
|
- ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"]
|
||||||
|
- ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~]
|
||||||
|
- ["LShift", "Oem102", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"]
|
||||||
|
- ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~]
|
||||||
|
|
||||||
|
# Shell commands to be used in a layout.
|
||||||
|
# Value is an array of string arguments.
|
||||||
|
exec_commands:
|
||||||
|
STT: [ "whisper_stt", "--lang", "en" ]
|
||||||
|
|
||||||
|
# Series of keypresses to be used in a layout.
|
||||||
|
# Format: keyName [DOWN|UP]
|
||||||
|
# keyName must be a valid key from `xmodmap -pke`
|
||||||
|
# DOWN|UP: can be omitted for an implicit "keyName DOWN, keyName UP"
|
||||||
|
macros:
|
||||||
|
KILL: [ "LSuper DOWN", "LCtrl DOWN", "Escape", "LCtrl UP", "LSuper UP" ]
|
||||||
|
COPY: [ "LCtrl DOWN", "C", "LCtrl UP" ]
|
||||||
|
PASTE: [ "LCtrl DOWN", "V", "LCtrl UP" ]
|
||||||
|
|
||||||
|
# Custom labels to use.
|
||||||
|
# Key: element of MainLayout / AltLayout
|
||||||
|
# Value: Array of strings. 0th element is the upper row, 1st element is lower row.
|
||||||
|
# For empty labels, use [] (do not use ~)
|
||||||
|
labels:
|
||||||
|
"N1": ["1", "!"]
|
||||||
|
"N2": ["2", "@"]
|
||||||
|
"N3": ["3", "#"]
|
||||||
|
"N4": ["4", "$"]
|
||||||
|
"N5": ["5", "%"]
|
||||||
|
"N6": ["6", "^"]
|
||||||
|
"N7": ["7", "&"]
|
||||||
|
"N8": ["8", "*"]
|
||||||
|
"N9": ["9", "("]
|
||||||
|
"N0": ["0", ")"]
|
||||||
|
"Escape": ["Esc"]
|
||||||
|
"Prior": ["PgUp"]
|
||||||
|
"Next": ["PgDn"]
|
||||||
|
"NumLock": ["Num"]
|
||||||
|
"Space": []
|
||||||
|
"LAlt": ["Alt"]
|
||||||
|
"LCtrl": ["Ctrl"]
|
||||||
|
"RCtrl": ["Ctrl"]
|
||||||
|
"LSuper": ["Super"]
|
||||||
|
"RSuper": ["Super"]
|
||||||
|
"LShift": ["Shift"]
|
||||||
|
"RShift": ["Shift"]
|
||||||
|
"Insert": ["Ins"]
|
||||||
|
"Delete": ["Del"]
|
||||||
|
"Minus": ["-", "_"]
|
||||||
|
"Plus": ["=", "+"]
|
||||||
|
"BackSpace": ["<<"]
|
||||||
|
"Comma": [" ,", "<"]
|
||||||
|
"Period": [" .", ">"]
|
||||||
|
"Oem1": [" ;", ":"]
|
||||||
|
"Oem2": [" /", "?"]
|
||||||
|
"Oem3": ["`", "~"]
|
||||||
|
"Oem4": [" [", "{"]
|
||||||
|
"Oem5": [" \\", "|"]
|
||||||
|
"Oem6": [" ]", "}"]
|
||||||
|
"Oem7": [" '", "\""]
|
||||||
|
"Oem102": [" \\", "|"]
|
||||||
|
"KP_Divide": [" /"]
|
||||||
|
"KP_Add": [" +"]
|
||||||
|
"KP_Multiply": [" *"]
|
||||||
|
"KP_Decimal": [" ."]
|
||||||
|
"KP_Subtract": [" -"]
|
||||||
|
"KP_Enter": ["Ent"]
|
||||||
|
"XF86Favorites": ["Rofi"]
|
||||||
|
|
||||||
BIN
src/shaders/frag-color.spv
Normal file
BIN
src/shaders/frag-color.spv
Normal file
Binary file not shown.
BIN
src/shaders/frag-glyph.spv
Normal file
BIN
src/shaders/frag-glyph.spv
Normal file
Binary file not shown.
BIN
src/shaders/frag-sprite.spv
Normal file
BIN
src/shaders/frag-sprite.spv
Normal file
Binary file not shown.
BIN
src/shaders/frag-srgb.spv
Normal file
BIN
src/shaders/frag-srgb.spv
Normal file
Binary file not shown.
108
src/shaders/mod.rs
Normal file
108
src/shaders/mod.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
pub mod vert_common {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "vertex",
|
||||||
|
src: r"#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_pos;
|
||||||
|
layout (location = 1) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec2 out_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_uv = in_uv;
|
||||||
|
gl_Position = vec4(in_pos * 2. - 1., 0., 1.);
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod frag_color {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
src: r"#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform ColorBlock {
|
||||||
|
uniform vec4 in_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = in_color;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod frag_glyph {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
src: r"#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D in_texture;
|
||||||
|
|
||||||
|
layout (set = 1, binding = 0) uniform ColorBlock {
|
||||||
|
uniform vec4 in_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
float r = texture(in_texture, in_uv).r;
|
||||||
|
out_color = vec4(r,r,r,r) * in_color;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod frag_sprite {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
src: r"#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D in_texture;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 c = texture(in_texture, in_uv);
|
||||||
|
out_color.rgb = c.rgb;
|
||||||
|
out_color.a = min((c.r + c.g + c.b)*100.0, 1.0);
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod frag_srgb {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
src: r"#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D in_texture;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = texture(in_texture, in_uv);
|
||||||
|
|
||||||
|
bvec4 cutoff = lessThan(out_color, vec4(0.04045));
|
||||||
|
vec4 higher = pow((out_color + vec4(0.055))/vec4(1.055), vec4(2.4));
|
||||||
|
vec4 lower = out_color/vec4(12.92);
|
||||||
|
|
||||||
|
out_color = mix(higher, lower, cutoff);
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/shaders/src/color.frag
Normal file
15
src/shaders/src/color.frag
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform ColorBlock {
|
||||||
|
uniform vec4 in_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = in_color;
|
||||||
|
}
|
||||||
|
|
||||||
12
src/shaders/src/common.vert
Normal file
12
src/shaders/src/common.vert
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_pos;
|
||||||
|
layout (location = 1) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec2 out_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_uv = in_uv;
|
||||||
|
gl_Position = vec4(in_pos * 2. - 1., 0., 1.);
|
||||||
|
}
|
||||||
|
|
||||||
18
src/shaders/src/glyph.frag
Normal file
18
src/shaders/src/glyph.frag
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D in_texture;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 1) uniform ColorBlock {
|
||||||
|
uniform vec4 in_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
float r = texture(in_texture, in_uv).r;
|
||||||
|
out_color = vec4(r,r,r,r) * in_color;
|
||||||
|
}
|
||||||
|
|
||||||
14
src/shaders/src/sprite.frag
Normal file
14
src/shaders/src/sprite.frag
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D in_texture;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = texture(in_texture, in_uv);
|
||||||
|
out_color.a = 1.;
|
||||||
|
}
|
||||||
|
|
||||||
19
src/shaders/src/srgb.frag
Normal file
19
src/shaders/src/srgb.frag
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#version 310 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_uv;
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform sampler2D in_texture;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = texture(in_texture, in_uv);
|
||||||
|
|
||||||
|
bvec4 cutoff = lessThan(out_color, vec4(0.04045));
|
||||||
|
vec4 higher = pow((out_color + vec4(0.055))/vec4(1.055), vec4(2.4));
|
||||||
|
vec4 lower = out_color/vec4(12.92);
|
||||||
|
|
||||||
|
out_color = mix(higher, lower, cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
BIN
src/shaders/vert-common.spv
Normal file
BIN
src/shaders/vert-common.spv
Normal file
Binary file not shown.
103
src/state.rs
Normal file
103
src/state.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
use std::{collections::VecDeque, env::VarError, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use glam::{Quat, Vec3};
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
graphics::WlxGraphics, gui::font::FontCache, input::InputProvider, overlays::OverlayData,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(0., 0., 0.15);
|
||||||
|
pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963);
|
||||||
|
|
||||||
|
pub type Task = Box<dyn FnOnce(&mut AppState, &mut [OverlayData]) + Send>;
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
pub fc: FontCache,
|
||||||
|
//pub input: InputState,
|
||||||
|
pub session: AppSession,
|
||||||
|
pub tasks: VecDeque<Task>,
|
||||||
|
pub graphics: Arc<WlxGraphics>,
|
||||||
|
pub format: vulkano::format::Format,
|
||||||
|
pub input: Box<dyn InputProvider>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppSession {
|
||||||
|
pub config_path: String,
|
||||||
|
|
||||||
|
pub show_screens: Vec<String>,
|
||||||
|
pub show_keyboard: bool,
|
||||||
|
pub keyboard_volume: f32,
|
||||||
|
|
||||||
|
pub screen_flip_h: bool,
|
||||||
|
pub screen_flip_v: bool,
|
||||||
|
pub screen_invert_color: bool,
|
||||||
|
|
||||||
|
pub watch_hand: usize,
|
||||||
|
pub watch_pos: Vec3,
|
||||||
|
pub watch_rot: Quat,
|
||||||
|
|
||||||
|
pub primary_hand: usize,
|
||||||
|
|
||||||
|
pub capture_method: String,
|
||||||
|
|
||||||
|
pub color_norm: Vec3,
|
||||||
|
pub color_shift: Vec3,
|
||||||
|
pub color_alt: Vec3,
|
||||||
|
pub color_grab: Vec3,
|
||||||
|
|
||||||
|
pub click_freeze_time_ms: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppSession {
|
||||||
|
pub fn load() -> AppSession {
|
||||||
|
let config_path = std::env::var("XDG_CONFIG_HOME")
|
||||||
|
.or_else(|_| std::env::var("HOME").map(|home| format!("{}/.config", home)))
|
||||||
|
.or_else(|_| {
|
||||||
|
warn!("Err: $XDG_CONFIG_HOME and $HOME are not set, using /tmp/wlxoverlay");
|
||||||
|
Ok::<String, VarError>("/tmp".to_string())
|
||||||
|
})
|
||||||
|
.map(|config| Path::new(&config).join("wlxoverlay"))
|
||||||
|
.ok()
|
||||||
|
.and_then(|path| path.to_str().map(|path| path.to_string()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let _ = std::fs::create_dir(&config_path);
|
||||||
|
|
||||||
|
AppSession {
|
||||||
|
config_path,
|
||||||
|
show_screens: vec!["DP-3".to_string()],
|
||||||
|
keyboard_volume: 0.5,
|
||||||
|
show_keyboard: false,
|
||||||
|
screen_flip_h: false,
|
||||||
|
screen_flip_v: false,
|
||||||
|
screen_invert_color: false,
|
||||||
|
capture_method: "auto".to_string(),
|
||||||
|
primary_hand: 1,
|
||||||
|
watch_hand: 1,
|
||||||
|
watch_pos: WATCH_DEFAULT_POS,
|
||||||
|
watch_rot: WATCH_DEFAULT_ROT,
|
||||||
|
color_norm: Vec3 {
|
||||||
|
x: 0.,
|
||||||
|
y: 1.,
|
||||||
|
z: 1.,
|
||||||
|
},
|
||||||
|
color_shift: Vec3 {
|
||||||
|
x: 1.,
|
||||||
|
y: 1.,
|
||||||
|
z: 0.,
|
||||||
|
},
|
||||||
|
color_alt: Vec3 {
|
||||||
|
x: 1.,
|
||||||
|
y: 0.,
|
||||||
|
z: 1.,
|
||||||
|
},
|
||||||
|
color_grab: Vec3 {
|
||||||
|
x: 1.,
|
||||||
|
y: 0.,
|
||||||
|
z: 0.,
|
||||||
|
},
|
||||||
|
click_freeze_time_ms: 300,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user