notifications
This commit is contained in:
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -1130,6 +1130,17 @@ version = "0.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus"
|
||||||
|
version = "0.9.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libdbus-sys",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@@ -1965,6 +1976,15 @@ version = "0.2.153"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libdbus-sys"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
@@ -4321,6 +4341,7 @@ dependencies = [
|
|||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"cstr",
|
"cstr",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
|
"dbus",
|
||||||
"flexi_logger",
|
"flexi_logger",
|
||||||
"fontconfig-rs",
|
"fontconfig-rs",
|
||||||
"freetype-rs",
|
"freetype-rs",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ chrono = "0.4.29"
|
|||||||
chrono-tz = "0.8.5"
|
chrono-tz = "0.8.5"
|
||||||
cstr = "0.2.11"
|
cstr = "0.2.11"
|
||||||
ctrlc = { version = "3.4.2", features = ["termination"] }
|
ctrlc = { version = "3.4.2", features = ["termination"] }
|
||||||
|
dbus = { version = "0.9.7", optional = true }
|
||||||
flexi_logger = "0.27.4"
|
flexi_logger = "0.27.4"
|
||||||
fontconfig-rs = "0.1.1"
|
fontconfig-rs = "0.1.1"
|
||||||
freetype-rs = "0.32.0"
|
freetype-rs = "0.32.0"
|
||||||
@@ -49,10 +50,11 @@ wlx-capture = { git = "https://github.com/galister/wlx-capture" }
|
|||||||
xdg = "2.5.2"
|
xdg = "2.5.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["openvr", "openxr", "osc", "x11", "wayland", "notifications"]
|
||||||
|
notifications = ["dep:dbus"]
|
||||||
openvr = ["dep:ovr_overlay", "dep:json"]
|
openvr = ["dep:ovr_overlay", "dep:json"]
|
||||||
openxr = ["dep:openxr"]
|
openxr = ["dep:openxr"]
|
||||||
osc = ["dep:rosc"]
|
osc = ["dep:rosc"]
|
||||||
x11 = ["wlx-capture/xshm"]
|
x11 = ["wlx-capture/xshm"]
|
||||||
wayland = ["wlx-capture/pipewire", "wlx-capture/wlr"]
|
wayland = ["wlx-capture/pipewire", "wlx-capture/wlr"]
|
||||||
default = ["openvr", "openxr", "osc", "x11", "wayland"]
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use thiserror::Error;
|
|||||||
use crate::{
|
use crate::{
|
||||||
overlays::{
|
overlays::{
|
||||||
keyboard::create_keyboard,
|
keyboard::create_keyboard,
|
||||||
toast::Toast,
|
|
||||||
watch::{create_watch, WATCH_NAME, WATCH_SCALE},
|
watch::{create_watch, WATCH_NAME, WATCH_SCALE},
|
||||||
},
|
},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
@@ -58,6 +57,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut watch = create_watch::<T>(app, &screens)?;
|
let mut watch = create_watch::<T>(app, &screens)?;
|
||||||
|
log::info!("Watch Rotation: {:?}", watch.state.spawn_rotation);
|
||||||
watch.state.want_visible = true;
|
watch.state.want_visible = true;
|
||||||
overlays.insert(watch.state.id, watch);
|
overlays.insert(watch.state.id, watch);
|
||||||
|
|
||||||
@@ -68,9 +68,9 @@ where
|
|||||||
|
|
||||||
let mut show_screens = app.session.config.show_screens.clone();
|
let mut show_screens = app.session.config.show_screens.clone();
|
||||||
if show_screens.is_empty() {
|
if show_screens.is_empty() {
|
||||||
screens.first().map(|s| {
|
if let Some(s) = screens.first() {
|
||||||
show_screens.push(s.state.name.clone());
|
show_screens.push(s.state.name.clone());
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for mut screen in screens {
|
for mut screen in screens {
|
||||||
@@ -173,7 +173,7 @@ impl PartialEq<AppTask> for AppTask {
|
|||||||
}
|
}
|
||||||
impl PartialOrd<AppTask> for AppTask {
|
impl PartialOrd<AppTask> for AppTask {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
Some(self.not_before.cmp(&other.not_before).reverse())
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Eq for AppTask {}
|
impl Eq for AppTask {}
|
||||||
@@ -194,7 +194,6 @@ pub enum TaskType {
|
|||||||
Box<dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send>,
|
Box<dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send>,
|
||||||
),
|
),
|
||||||
DropOverlay(OverlaySelector),
|
DropOverlay(OverlaySelector),
|
||||||
Toast(Toast),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TaskContainer {
|
pub struct TaskContainer {
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
|
||||||
|
#[cfg(feature = "notifications")]
|
||||||
|
pub mod notifications;
|
||||||
|
|
||||||
#[cfg(feature = "openvr")]
|
#[cfg(feature = "openvr")]
|
||||||
pub mod openvr;
|
pub mod openvr;
|
||||||
|
|
||||||
#[cfg(feature = "openxr")]
|
#[cfg(feature = "openxr")]
|
||||||
pub mod openxr;
|
pub mod openxr;
|
||||||
|
|
||||||
#[cfg(feature = "osc")]
|
#[cfg(feature = "osc")]
|
||||||
pub mod osc;
|
pub mod osc;
|
||||||
|
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
|
|||||||
153
src/backend/notifications.rs
Normal file
153
src/backend/notifications.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use dbus::{
|
||||||
|
blocking::Connection,
|
||||||
|
channel::{MatchingReceiver, Token},
|
||||||
|
message::MatchRule,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
|
||||||
|
use crate::{overlays::toast::Toast, state::AppState};
|
||||||
|
|
||||||
|
pub struct NotificationManager {
|
||||||
|
rx_toast: mpsc::Receiver<Toast>,
|
||||||
|
tx_toast: mpsc::SyncSender<Toast>,
|
||||||
|
dbus_data: Option<(Connection, Token)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotificationManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (tx_toast, rx_toast) = mpsc::sync_channel(10);
|
||||||
|
Self {
|
||||||
|
rx_toast,
|
||||||
|
tx_toast,
|
||||||
|
dbus_data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submit_pending(&self, app: &mut AppState) {
|
||||||
|
self.rx_toast.try_iter().for_each(|toast| {
|
||||||
|
toast.submit(app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_dbus(&mut self) {
|
||||||
|
let Ok(c) = Connection::new_session() else {
|
||||||
|
log::error!("Failed to connect to dbus. Desktop notifications will not work.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rule = MatchRule::new_method_call();
|
||||||
|
rule.member = Some("Notify".into());
|
||||||
|
rule.interface = Some("org.freedesktop.Notifications".into());
|
||||||
|
rule.path = Some("/org/freedesktop/Notifications".into());
|
||||||
|
rule.eavesdrop = true;
|
||||||
|
|
||||||
|
let sender = self.tx_toast.clone();
|
||||||
|
|
||||||
|
let token = c.start_receive(
|
||||||
|
rule,
|
||||||
|
Box::new(move |msg, _| {
|
||||||
|
if let Ok(toast) = parse_dbus(&msg) {
|
||||||
|
match sender.try_send(toast) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to send notification: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.dbus_data = Some((c, token));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_udp(&mut self) {
|
||||||
|
let sender = self.tx_toast.clone();
|
||||||
|
// NOTE: We're detaching the thread, as there's no simple way to gracefully stop it other than app shutdown.
|
||||||
|
let _ = std::thread::spawn(move || {
|
||||||
|
let socket = match std::net::UdpSocket::bind("127.0.0.1:42069") {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to bind notification socket: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; 1500];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ok((num_bytes, _)) = socket.recv_from(&mut buf) {
|
||||||
|
let json_str = match std::str::from_utf8(&buf[..num_bytes]) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to receive notification message: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let msg = match serde_json::from_str::<XsoMessage>(json_str) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to parse notification message: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let toast = Toast::new(msg.title, msg.content.unwrap_or_else(|| "".into()))
|
||||||
|
.with_timeout(msg.timeout)
|
||||||
|
.with_volume(msg.volume)
|
||||||
|
.with_opacity(msg.opacity);
|
||||||
|
|
||||||
|
match sender.try_send(toast) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to send notification: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for NotificationManager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some((c, token)) = self.dbus_data.take() {
|
||||||
|
let _ = c.stop_receive(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_dbus(msg: &dbus::Message) -> anyhow::Result<Toast> {
|
||||||
|
let mut args = msg.iter_init();
|
||||||
|
let app_name: String = args.read()?;
|
||||||
|
let _replaces_id: u32 = args.read()?;
|
||||||
|
let _app_icon: String = args.read()?;
|
||||||
|
let summary: String = args.read()?;
|
||||||
|
let body: String = args.read()?;
|
||||||
|
|
||||||
|
let title = if summary.is_empty() {
|
||||||
|
app_name
|
||||||
|
} else {
|
||||||
|
summary
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Toast::new(title.into(), body.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct XsoMessage {
|
||||||
|
messageType: i32,
|
||||||
|
index: i32,
|
||||||
|
volume: f32,
|
||||||
|
audioPath: Arc<str>,
|
||||||
|
timeout: f32,
|
||||||
|
title: Arc<str>,
|
||||||
|
content: Option<Arc<str>>,
|
||||||
|
icon: Option<Arc<str>>,
|
||||||
|
height: f32,
|
||||||
|
opacity: f32,
|
||||||
|
useBase64Icon: bool,
|
||||||
|
sourceApp: Option<Arc<str>>,
|
||||||
|
alwaysShow: bool,
|
||||||
|
}
|
||||||
@@ -129,12 +129,12 @@ impl OpenVrInputSource {
|
|||||||
app: &mut AppState,
|
app: &mut AppState,
|
||||||
) {
|
) {
|
||||||
let aas = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
|
let aas = ActiveActionSet(ovr_overlay::sys::VRActiveActionSet_t {
|
||||||
ulActionSet: self.set_hnd.0,
|
ulActionSet: self.set_hnd.0,
|
||||||
ulRestrictedToDevice: 0,
|
ulRestrictedToDevice: 0,
|
||||||
ulSecondaryActionSet: 0,
|
ulSecondaryActionSet: 0,
|
||||||
unPadding: 0,
|
unPadding: 0,
|
||||||
nPriority: 0,
|
nPriority: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = input.update_actions(&mut [aas]);
|
let _ = input.update_actions(&mut [aas]);
|
||||||
|
|
||||||
@@ -156,7 +156,6 @@ impl OpenVrInputSource {
|
|||||||
.map(|pose| {
|
.map(|pose| {
|
||||||
app_hand.pose = pose.0.pose.mDeviceToAbsoluteTracking.to_affine();
|
app_hand.pose = pose.0.pose.mDeviceToAbsoluteTracking.to_affine();
|
||||||
hand.has_pose = true;
|
hand.has_pose = true;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app_hand.now.click = input
|
app_hand.now.click = input
|
||||||
@@ -235,10 +234,9 @@ impl OpenVrInputSource {
|
|||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
get_tracked_device(system, device, role).map(|device| {
|
if let Some(device) = get_tracked_device(system, device, role) {
|
||||||
app.input_state.devices.push(device);
|
app.input_state.devices.push(device);
|
||||||
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.input_state.devices.sort_by(|a, b| {
|
app.input_state.devices.sort_by(|a, b| {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
|
|||||||
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
|
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
|
||||||
if let Ok(mut file) = File::open(&manifest_path) {
|
if let Ok(mut file) = File::open(&manifest_path) {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
if let Ok(_) = file.read_to_string(&mut buf) {
|
if file.read_to_string(&mut buf).is_ok() {
|
||||||
let manifest: json::JsonValue = json::parse(&buf)?;
|
let manifest: json::JsonValue = json::parse(&buf)?;
|
||||||
if manifest["applications"][0]["binary_path_linux"] == executable_path {
|
if manifest["applications"][0]["binary_path_linux"] == executable_path {
|
||||||
log::info!("Manifest already up to date");
|
log::info!("Manifest already up to date");
|
||||||
@@ -50,16 +50,20 @@ pub(super) fn install_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::Res
|
|||||||
bail!("Failed to create manifest file at {:?}", manifest_path);
|
bail!("Failed to create manifest file at {:?}", manifest_path);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(()) = manifest.write(&mut file) else {
|
if let Err(e) = manifest.write(&mut file) {
|
||||||
bail!("Failed to write manifest file at {:?}", manifest_path);
|
bail!(
|
||||||
|
"Failed to write manifest file at {:?}: {:?}",
|
||||||
|
manifest_path,
|
||||||
|
e
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(()) = app_mgr.add_application_manifest(&manifest_path, false) else {
|
if let Err(e) = app_mgr.add_application_manifest(&manifest_path, false) {
|
||||||
bail!("Failed to add manifest to OpenVR");
|
bail!("Failed to add manifest to OpenVR: {}", e.description());
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(()) = app_mgr.set_application_auto_launch(APP_KEY, true) else {
|
if let Err(e) = app_mgr.set_application_auto_launch(APP_KEY, true) {
|
||||||
bail!("Failed to set auto launch");
|
bail!("Failed to set auto launch: {}", e.description());
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -69,8 +73,8 @@ pub(super) fn uninstall_manifest(app_mgr: &mut ApplicationsManager) -> anyhow::R
|
|||||||
let manifest_path = CONFIG_ROOT_PATH.join("wlx-overlay-s.vrmanifest");
|
let manifest_path = CONFIG_ROOT_PATH.join("wlx-overlay-s.vrmanifest");
|
||||||
|
|
||||||
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
|
if let Ok(true) = app_mgr.is_application_installed(APP_KEY) {
|
||||||
let Ok(()) = app_mgr.remove_application_manifest(&manifest_path) else {
|
if let Err(e) = app_mgr.remove_application_manifest(&manifest_path) {
|
||||||
bail!("Failed to remove manifest from OpenVR");
|
bail!("Failed to remove manifest from OpenVR: {}", e.description());
|
||||||
};
|
};
|
||||||
log::info!("Uninstalled manifest");
|
log::info!("Uninstalled manifest");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use vulkano::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
input::interact,
|
input::interact,
|
||||||
|
notifications::NotificationManager,
|
||||||
openvr::{
|
openvr::{
|
||||||
input::{set_action_manifest, OpenVrInputSource},
|
input::{set_action_manifest, OpenVrInputSource},
|
||||||
lines::LinePool,
|
lines::LinePool,
|
||||||
@@ -91,6 +92,9 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
let _ = install_manifest(&mut app_mgr);
|
let _ = install_manifest(&mut app_mgr);
|
||||||
|
|
||||||
let mut overlays = OverlayContainer::<OpenVrOverlayData>::new(&mut state)?;
|
let mut overlays = OverlayContainer::<OpenVrOverlayData>::new(&mut state)?;
|
||||||
|
let mut notifications = NotificationManager::new();
|
||||||
|
notifications.run_dbus();
|
||||||
|
notifications.run_udp();
|
||||||
|
|
||||||
let mut space_mover = playspace::PlayspaceMover::new();
|
let mut space_mover = playspace::PlayspaceMover::new();
|
||||||
#[cfg(feature = "osc")]
|
#[cfg(feature = "osc")]
|
||||||
@@ -149,6 +153,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
next_device_update = Instant::now() + Duration::from_secs(30);
|
next_device_update = Instant::now() + Duration::from_secs(30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifications.submit_pending(&mut state);
|
||||||
|
|
||||||
state.tasks.retrieve_due(&mut due_tasks);
|
state.tasks.retrieve_due(&mut due_tasks);
|
||||||
while let Some(task) = due_tasks.pop_front() {
|
while let Some(task) = due_tasks.pop_front() {
|
||||||
match task {
|
match task {
|
||||||
@@ -167,6 +173,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::info!("Creating overlay: {}", state.name);
|
||||||
|
|
||||||
overlays.add(OverlayData {
|
overlays.add(OverlayData {
|
||||||
state,
|
state,
|
||||||
backend,
|
backend,
|
||||||
@@ -175,14 +183,11 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
}
|
}
|
||||||
TaskType::DropOverlay(sel) => {
|
TaskType::DropOverlay(sel) => {
|
||||||
if let Some(o) = overlays.mut_by_selector(&sel) {
|
if let Some(o) = overlays.mut_by_selector(&sel) {
|
||||||
|
log::info!("Dropping overlay: {}", o.state.name);
|
||||||
o.destroy(&mut overlay_mngr);
|
o.destroy(&mut overlay_mngr);
|
||||||
overlays.drop_by_selector(&sel);
|
overlays.drop_by_selector(&sel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TaskType::Toast(t) => {
|
|
||||||
// TODO toasts
|
|
||||||
log::info!("Toast: {} {}", t.title, t.body);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,8 +254,6 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
|
|
||||||
// close font handles?
|
// close font handles?
|
||||||
|
|
||||||
// playspace moved end frame
|
|
||||||
|
|
||||||
state.hid_provider.on_new_frame();
|
state.hid_provider.on_new_frame();
|
||||||
|
|
||||||
let mut seconds_since_vsync = 0f32;
|
let mut seconds_since_vsync = 0f32;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ pub(super) unsafe fn create_overlay_session(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn hmd_pose_from_views(views: &Vec<xr::View>) -> Affine3A {
|
pub(super) fn hmd_pose_from_views(views: &[xr::View]) -> Affine3A {
|
||||||
let pos = {
|
let pos = {
|
||||||
let pos0: Vec3 = unsafe { std::mem::transmute(views[0].pose.position) };
|
let pos0: Vec3 = unsafe { std::mem::transmute(views[0].pose.position) };
|
||||||
let pos1: Vec3 = unsafe { std::mem::transmute(views[1].pose.position) };
|
let pos1: Vec3 = unsafe { std::mem::transmute(views[1].pose.position) };
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use crate::{
|
|||||||
backend::{
|
backend::{
|
||||||
common::{OverlayContainer, TaskType},
|
common::{OverlayContainer, TaskType},
|
||||||
input::interact,
|
input::interact,
|
||||||
|
notifications::NotificationManager,
|
||||||
openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData},
|
openxr::{input::DoubleClickCounter, lines::LinePool, overlay::OpenXrOverlayData},
|
||||||
overlay::OverlayData,
|
overlay::OverlayData,
|
||||||
},
|
},
|
||||||
@@ -63,6 +64,10 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app_state)?;
|
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app_state)?;
|
||||||
let mut lines = LinePool::new(app_state.graphics.clone())?;
|
let mut lines = LinePool::new(app_state.graphics.clone())?;
|
||||||
|
|
||||||
|
let mut notifications = NotificationManager::new();
|
||||||
|
notifications.run_dbus();
|
||||||
|
notifications.run_udp();
|
||||||
|
|
||||||
#[cfg(feature = "osc")]
|
#[cfg(feature = "osc")]
|
||||||
let mut osc_sender =
|
let mut osc_sender =
|
||||||
crate::backend::osc::OscSender::new(app_state.session.config.osc_out_port).ok();
|
crate::backend::osc::OscSender::new(app_state.session.config.osc_out_port).ok();
|
||||||
@@ -179,6 +184,8 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
continue 'main_loop;
|
continue 'main_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifications.submit_pending(&mut app_state);
|
||||||
|
|
||||||
app_state.tasks.retrieve_due(&mut due_tasks);
|
app_state.tasks.retrieve_due(&mut due_tasks);
|
||||||
while let Some(task) = due_tasks.pop_front() {
|
while let Some(task) = due_tasks.pop_front() {
|
||||||
match task {
|
match task {
|
||||||
@@ -206,10 +213,6 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
TaskType::DropOverlay(sel) => {
|
TaskType::DropOverlay(sel) => {
|
||||||
overlays.drop_by_selector(&sel);
|
overlays.drop_by_selector(&sel);
|
||||||
}
|
}
|
||||||
TaskType::Toast(t) => {
|
|
||||||
// TODO toasts
|
|
||||||
log::info!("Toast: {} {}", t.title, t.body);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +224,9 @@ pub fn openxr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
|
|||||||
.input_state
|
.input_state
|
||||||
.pointers
|
.pointers
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| p.now.show_hide && !p.before.show_hide) && show_hide_counter.click() {
|
.any(|p| p.now.show_hide && !p.before.show_hide)
|
||||||
|
&& show_hide_counter.click()
|
||||||
|
{
|
||||||
overlays.show_hide(&mut app_state);
|
overlays.show_hide(&mut app_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,20 +36,23 @@ impl OverlayData<OpenXrOverlayData> {
|
|||||||
};
|
};
|
||||||
let extent = my_view.image().extent();
|
let extent = my_view.image().extent();
|
||||||
|
|
||||||
let data = self.data.swapchain.get_or_insert_with(|| {
|
let data = match self.data.swapchain {
|
||||||
let srd =
|
Some(ref mut data) => data,
|
||||||
create_swapchain_render_data(xr, command_buffer.graphics.clone(), extent).unwrap(); //TODO
|
None => {
|
||||||
|
let srd =
|
||||||
log::info!(
|
create_swapchain_render_data(xr, command_buffer.graphics.clone(), extent)?;
|
||||||
"{}: Created swapchain {}x{}, {} images, {} MB",
|
log::debug!(
|
||||||
self.state.name,
|
"{}: Created swapchain {}x{}, {} images, {} MB",
|
||||||
extent[0],
|
self.state.name,
|
||||||
extent[1],
|
extent[0],
|
||||||
srd.images.len(),
|
extent[1],
|
||||||
extent[0] * extent[1] * 4 * srd.images.len() as u32 / 1024 / 1024
|
srd.images.len(),
|
||||||
);
|
extent[0] * extent[1] * 4 * srd.images.len() as u32 / 1024 / 1024
|
||||||
srd
|
);
|
||||||
});
|
self.data.swapchain = Some(srd);
|
||||||
|
self.data.swapchain.as_mut().unwrap() //safe
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let sub_image = data.acquire_present_release(command_buffer, my_view, self.state.alpha)?;
|
let sub_image = data.acquire_present_release(command_buffer, my_view, self.state.alpha)?;
|
||||||
let posef = helpers::transform_to_posef(&self.state.transform);
|
let posef = helpers::transform_to_posef(&self.state.transform);
|
||||||
|
|||||||
@@ -101,9 +101,7 @@ impl OverlayState {
|
|||||||
self.transform = parent
|
self.transform = parent
|
||||||
* Affine3A::from_scale_rotation_translation(
|
* Affine3A::from_scale_rotation_translation(
|
||||||
Vec3::ONE * self.spawn_scale,
|
Vec3::ONE * self.spawn_scale,
|
||||||
self.spawn_rotation
|
self.spawn_rotation,
|
||||||
* Quat::from_rotation_x(f32::to_radians(-180.0))
|
|
||||||
* Quat::from_rotation_z(f32::to_radians(180.0)),
|
|
||||||
point.into(),
|
point.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -837,7 +837,7 @@ impl WlxCommandBuffer {
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.command_buffer.begin_render_pass(
|
self.command_buffer.begin_render_pass(
|
||||||
RenderPassBeginInfo {
|
RenderPassBeginInfo {
|
||||||
clear_values: vec![Some([0.0, 0.0, 0.0, 1.0].into())],
|
clear_values: vec![Some([0.0, 0.0, 0.0, 0.0].into())],
|
||||||
..RenderPassBeginInfo::framebuffer(pipeline.data.framebuffer.clone())
|
..RenderPassBeginInfo::framebuffer(pipeline.data.framebuffer.clone())
|
||||||
},
|
},
|
||||||
SubpassBeginInfo {
|
SubpassBeginInfo {
|
||||||
@@ -854,7 +854,7 @@ impl WlxCommandBuffer {
|
|||||||
color_attachments: vec![Some(RenderingAttachmentInfo {
|
color_attachments: vec![Some(RenderingAttachmentInfo {
|
||||||
load_op: AttachmentLoadOp::Clear,
|
load_op: AttachmentLoadOp::Clear,
|
||||||
store_op: AttachmentStoreOp::Store,
|
store_op: AttachmentStoreOp::Store,
|
||||||
clear_value: Some([0.0, 0.0, 0.0, 1.0].into()),
|
clear_value: Some([0.0, 0.0, 0.0, 0.0].into()),
|
||||||
..RenderingAttachmentInfo::image_view(render_target.clone())
|
..RenderingAttachmentInfo::image_view(render_target.clone())
|
||||||
})],
|
})],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -923,8 +923,7 @@ impl WlxCommandBuffer {
|
|||||||
let info = reader.info();
|
let info = reader.info();
|
||||||
let width = info.width;
|
let width = info.width;
|
||||||
let height = info.height;
|
let height = info.height;
|
||||||
let mut image_data = Vec::new();
|
let mut image_data = vec![0; (info.width * info.height * 4) as usize];
|
||||||
image_data.resize((info.width * info.height * 4) as usize, 0);
|
|
||||||
reader.next_frame(&mut image_data)?;
|
reader.next_frame(&mut image_data)?;
|
||||||
self.texture2d(width, height, Format::R8G8B8A8_UNORM, &image_data)
|
self.texture2d(width, height, Format::R8G8B8A8_UNORM, &image_data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ impl<D, S> CanvasBuilder<D, S> {
|
|||||||
y: f32,
|
y: f32,
|
||||||
w: f32,
|
w: f32,
|
||||||
h: f32,
|
h: f32,
|
||||||
label: &Vec<String>,
|
label: &[String],
|
||||||
) -> &mut Control<D, S> {
|
) -> &mut Control<D, S> {
|
||||||
let idx = self.canvas.controls.len();
|
let idx = self.canvas.controls.len();
|
||||||
self.canvas.interactive_set_idx(x, y, w, h, idx);
|
self.canvas.interactive_set_idx(x, y, w, h, idx);
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
use std::sync::Arc;
|
use std::{
|
||||||
|
ops::Add,
|
||||||
|
sync::{atomic::AtomicUsize, Arc},
|
||||||
|
};
|
||||||
|
|
||||||
|
use glam::vec3a;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
backend::{
|
||||||
|
common::{OverlaySelector, TaskType},
|
||||||
|
overlay::{OverlayBackend, OverlayState, RelativeTo},
|
||||||
|
},
|
||||||
|
gui::{color_parse, CanvasBuilder},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FONT_SIZE: isize = 16;
|
||||||
|
const PADDING: (f32, f32) = (25., 7.);
|
||||||
|
const PIXELS_TO_METERS: f32 = 1. / 2000.;
|
||||||
|
|
||||||
|
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
pub struct Toast {
|
pub struct Toast {
|
||||||
pub title: Arc<str>,
|
pub title: Arc<str>,
|
||||||
pub body: Arc<str>,
|
pub body: Arc<str>,
|
||||||
pub opacity: f32,
|
pub opacity: f32,
|
||||||
|
pub volume: f32,
|
||||||
pub timeout: f32,
|
pub timeout: f32,
|
||||||
pub sound: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -16,7 +36,7 @@ impl Toast {
|
|||||||
body,
|
body,
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
timeout: 3.0,
|
timeout: 3.0,
|
||||||
sound: false,
|
volume: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_timeout(mut self, timeout: f32) -> Self {
|
pub fn with_timeout(mut self, timeout: f32) -> Self {
|
||||||
@@ -27,8 +47,94 @@ impl Toast {
|
|||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn with_sound(mut self) -> Self {
|
pub fn with_volume(mut self, volume: f32) -> Self {
|
||||||
self.sound = true;
|
self.volume = volume;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn submit(self, app: &mut AppState) {
|
||||||
|
let auto_increment = AUTO_INCREMENT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let name: Arc<str> = format!("toast-{}", auto_increment).into();
|
||||||
|
let selector = OverlaySelector::Name(name.clone());
|
||||||
|
|
||||||
|
let destroy_at =
|
||||||
|
std::time::Instant::now().add(std::time::Duration::from_secs_f32(self.timeout));
|
||||||
|
|
||||||
|
app.tasks.enqueue(TaskType::CreateOverlay(
|
||||||
|
selector.clone(),
|
||||||
|
Box::new(move |app| new_toast(self, name, app)),
|
||||||
|
));
|
||||||
|
|
||||||
|
app.tasks
|
||||||
|
.enqueue_at(TaskType::DropOverlay(selector), destroy_at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_toast(
|
||||||
|
toast: Toast,
|
||||||
|
name: Arc<str>,
|
||||||
|
app: &mut AppState,
|
||||||
|
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
||||||
|
let title = if toast.title.len() > 0 {
|
||||||
|
toast.title
|
||||||
|
} else {
|
||||||
|
"Notification".into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut size = if toast.body.len() > 0 {
|
||||||
|
let (w0, _) = app
|
||||||
|
.fc
|
||||||
|
.get_text_size(&title, FONT_SIZE, app.graphics.clone())
|
||||||
|
.ok()?;
|
||||||
|
let (w1, h1) = app
|
||||||
|
.fc
|
||||||
|
.get_text_size(&toast.body, FONT_SIZE, app.graphics.clone())
|
||||||
|
.ok()?;
|
||||||
|
(w0.max(w1), h1 + 50.)
|
||||||
|
} else {
|
||||||
|
app.fc
|
||||||
|
.get_text_size(&title, FONT_SIZE, app.graphics.clone())
|
||||||
|
.ok()?
|
||||||
|
};
|
||||||
|
|
||||||
|
let og_width = size.0;
|
||||||
|
size.0 += PADDING.0 * 2.;
|
||||||
|
|
||||||
|
let mut canvas = CanvasBuilder::<(), ()>::new(
|
||||||
|
size.0 as _,
|
||||||
|
size.1 as _,
|
||||||
|
app.graphics.clone(),
|
||||||
|
app.graphics.native_format,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
canvas.font_size = FONT_SIZE;
|
||||||
|
canvas.fg_color = color_parse("#aaaaaa").unwrap(); // want panic
|
||||||
|
canvas.bg_color = color_parse("#333333").unwrap(); // want panic
|
||||||
|
canvas.panel(0., 0., size.0, size.1);
|
||||||
|
|
||||||
|
if toast.body.len() > 0 {
|
||||||
|
log::info!("{}: {}", title, toast.body);
|
||||||
|
canvas.label(PADDING.0, 54., og_width, size.1 - 54., toast.body);
|
||||||
|
|
||||||
|
canvas.fg_color = color_parse("#101010").unwrap(); // want panic
|
||||||
|
canvas.bg_color = color_parse("#666666").unwrap(); // want panic
|
||||||
|
canvas.panel(0., 0., size.0, 30.);
|
||||||
|
canvas.label_centered(PADDING.0, 16., og_width, FONT_SIZE as f32 + 2., title);
|
||||||
|
} else {
|
||||||
|
log::info!("Toast: {}", title);
|
||||||
|
canvas.label(0., 0., size.0, size.1, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = OverlayState {
|
||||||
|
name,
|
||||||
|
want_visible: true,
|
||||||
|
spawn_scale: size.0 * PIXELS_TO_METERS,
|
||||||
|
spawn_point: vec3a(0., -0.2, -0.5),
|
||||||
|
relative_to: RelativeTo::Head,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let backend = Box::new(canvas.build());
|
||||||
|
|
||||||
|
Some((state, backend))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ where
|
|||||||
canvas.font_size = font_size;
|
canvas.font_size = font_size;
|
||||||
let button = canvas.button(x, y, w, h, text.clone());
|
let button = canvas.button(x, y, w, h, text.clone());
|
||||||
button.state = Some(ElemState::Mirror { name, show_hide });
|
button.state = Some(ElemState::Mirror { name, show_hide });
|
||||||
button.on_press = Some(btn_mirror_dn::<O>);
|
button.on_press = Some(btn_mirror_dn);
|
||||||
button.on_scroll = Some(overlay_button_scroll);
|
button.on_scroll = Some(overlay_button_scroll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,14 +346,12 @@ enum ElemState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
fn btn_mirror_dn<O>(
|
fn btn_mirror_dn(
|
||||||
control: &mut Control<(), ElemState>,
|
control: &mut Control<(), ElemState>,
|
||||||
_: &mut (),
|
_: &mut (),
|
||||||
app: &mut AppState,
|
app: &mut AppState,
|
||||||
mode: PointerMode,
|
mode: PointerMode,
|
||||||
) where
|
) {
|
||||||
O: Default,
|
|
||||||
{
|
|
||||||
let ElemState::Mirror { name, show_hide } = control.state.as_ref().unwrap()
|
let ElemState::Mirror { name, show_hide } = control.state.as_ref().unwrap()
|
||||||
// want panic
|
// want panic
|
||||||
else {
|
else {
|
||||||
@@ -428,10 +426,12 @@ fn btn_func_dn(
|
|||||||
Box::new(|app, o| {
|
Box::new(|app, o| {
|
||||||
o.want_visible = false;
|
o.want_visible = false;
|
||||||
o.spawn_scale = 0.0;
|
o.spawn_scale = 0.0;
|
||||||
app.tasks.enqueue(TaskType::Toast(Toast::new(
|
Toast::new(
|
||||||
"Watch hidden".into(),
|
"Watch hidden".into(),
|
||||||
"Use show/hide button to restore.".into(),
|
"Use show/hide button to restore.".into(),
|
||||||
)))
|
)
|
||||||
|
.with_timeout(3.)
|
||||||
|
.submit(app);
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -441,7 +441,6 @@ fn btn_func_dn(
|
|||||||
Box::new(|app, o| {
|
Box::new(|app, o| {
|
||||||
if let RelativeTo::Hand(0) = o.relative_to {
|
if let RelativeTo::Hand(0) = o.relative_to {
|
||||||
o.relative_to = RelativeTo::Hand(1);
|
o.relative_to = RelativeTo::Hand(1);
|
||||||
o.spawn_rotation = app.session.watch_rot;
|
|
||||||
o.spawn_rotation = app.session.watch_rot
|
o.spawn_rotation = app.session.watch_rot
|
||||||
* Quat::from_rotation_x(PI)
|
* Quat::from_rotation_x(PI)
|
||||||
* Quat::from_rotation_z(PI);
|
* Quat::from_rotation_z(PI);
|
||||||
@@ -452,10 +451,9 @@ fn btn_func_dn(
|
|||||||
o.spawn_rotation = app.session.watch_rot;
|
o.spawn_rotation = app.session.watch_rot;
|
||||||
o.spawn_point = app.session.watch_pos.into();
|
o.spawn_point = app.session.watch_pos.into();
|
||||||
}
|
}
|
||||||
app.tasks.enqueue(TaskType::Toast(Toast::new(
|
Toast::new("Watch switched".into(), "Check your other hand".into())
|
||||||
"Watch switched".into(),
|
.with_timeout(3.)
|
||||||
"Check your other hand".into(),
|
.submit(app);
|
||||||
)))
|
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -571,7 +569,7 @@ fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut A
|
|||||||
} else {
|
} else {
|
||||||
if let Some(mut stdout) = proc.stdout.take() {
|
if let Some(mut stdout) = proc.stdout.take() {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
if let Ok(_) = stdout.read_to_string(&mut buf) {
|
if stdout.read_to_string(&mut buf).is_ok() {
|
||||||
control.set_text(&buf);
|
control.set_text(&buf);
|
||||||
} else {
|
} else {
|
||||||
log::error!("Failed to read stdout for child process");
|
log::error!("Failed to read stdout for child process");
|
||||||
@@ -730,10 +728,11 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut
|
|||||||
o.grabbable = o.recenter;
|
o.grabbable = o.recenter;
|
||||||
o.show_hide = o.recenter;
|
o.show_hide = o.recenter;
|
||||||
if !o.recenter {
|
if !o.recenter {
|
||||||
app.tasks.enqueue(TaskType::Toast(Toast::new(
|
Toast::new(
|
||||||
format!("{} is now locked in place!", o.name).into(),
|
format!("{} is now locked in place!", o.name).into(),
|
||||||
"Right-click again to toggle.".into(),
|
"Right-click again to toggle.".into(),
|
||||||
)))
|
)
|
||||||
|
.submit(app);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
@@ -744,10 +743,11 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut
|
|||||||
Box::new(|app, o| {
|
Box::new(|app, o| {
|
||||||
o.interactable = !o.interactable;
|
o.interactable = !o.interactable;
|
||||||
if !o.interactable {
|
if !o.interactable {
|
||||||
app.tasks.enqueue(TaskType::Toast(Toast::new(
|
Toast::new(
|
||||||
format!("{} is now non-interactable!", o.name).into(),
|
format!("{} is now non-interactable!", o.name).into(),
|
||||||
"Middle-click again to toggle.".into(),
|
"Middle-click again to toggle.".into(),
|
||||||
)))
|
)
|
||||||
|
.submit(app);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ pub mod frag_sprite {
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 c = texture(in_texture, in_uv);
|
out_color = texture(in_texture, in_uv);
|
||||||
out_color.rgb = c.rgb;
|
|
||||||
out_color.a = min((c.r + c.g + c.b)*100.0, 1.0);
|
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(-0.03, -0.01, 0.125);
|
pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(-0.03, -0.01, 0.125);
|
||||||
pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963);
|
pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(-0.7071066, 0.0007963618, 0.7071066, 0.0);
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub fc: FontCache,
|
pub fc: FontCache,
|
||||||
|
|||||||
Reference in New Issue
Block a user