wip: wgui backend
This commit is contained in:
1936
Cargo.lock
generated
1936
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@@ -16,7 +16,7 @@ categories = ["games"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.89"
|
anyhow = { workspace = true }
|
||||||
ash = "^0.38.0" # must match vulkano
|
ash = "^0.38.0" # must match vulkano
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
chrono-tz = "0.10.0"
|
chrono-tz = "0.10.0"
|
||||||
@@ -24,17 +24,15 @@ clap = { version = "4.5.6", features = ["derive"] }
|
|||||||
config = "0.15.11"
|
config = "0.15.11"
|
||||||
ctrlc = { version = "3.4.4", features = ["termination"] }
|
ctrlc = { version = "3.4.4", features = ["termination"] }
|
||||||
dbus = { version = "0.9.7" }
|
dbus = { version = "0.9.7" }
|
||||||
fontconfig-rs = "0.1.1"
|
|
||||||
freetype-rs = "0.36.0" # latest version supported on ubuntu 22.04
|
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
glam = { version = "0.30.1", features = ["approx", "mint", "serde"] }
|
glam = { workspace = true, features = ["mint", "serde"] }
|
||||||
idmap = { version = "0.2.21", features = ["serde"] }
|
idmap = { version = "0.2.21", features = ["serde"] }
|
||||||
idmap-derive = "0.1.2"
|
idmap-derive = "0.1.2"
|
||||||
input-linux = "0.7.0"
|
input-linux = "0.7.0"
|
||||||
json = { version = "0.12.4", optional = true }
|
json = { version = "0.12.4", optional = true }
|
||||||
json5 = "0.4.1"
|
json5 = "0.4.1"
|
||||||
libc = "0.2.155"
|
libc = "0.2.155"
|
||||||
log = "0.4.21"
|
log = { workspace = true }
|
||||||
openxr = { git = "https://github.com/Ralith/openxrs", rev = "d0afdd3365bc1e14de28f6a3a21f457e788a702e", features = [
|
openxr = { git = "https://github.com/Ralith/openxrs", rev = "d0afdd3365bc1e14de28f6a3a21f457e788a702e", features = [
|
||||||
"linked",
|
"linked",
|
||||||
"mint",
|
"mint",
|
||||||
@@ -54,14 +52,12 @@ serde_json = "1.0.117"
|
|||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
smallvec = "1.13.2"
|
smallvec = "1.13.2"
|
||||||
strum = { version = "0.27.1", features = ["derive"] }
|
strum = { version = "0.27.1", features = ["derive"] }
|
||||||
sysinfo = { version = "0.34.2" }
|
sysinfo = { version = "0.35" }
|
||||||
thiserror = "2.0.3"
|
thiserror = "2.0"
|
||||||
vulkano = { version = "0.35.1" }
|
|
||||||
vulkano-shaders = { version = "0.35.0" }
|
|
||||||
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.5.3", default-features = false }
|
wlx-capture = { git = "https://github.com/galister/wlx-capture", tag = "v0.5.3", default-features = false }
|
||||||
libmonado = { version = "1.3.2", optional = true }
|
libmonado = { version = "1.3.2", optional = true }
|
||||||
winit = { version = "0.30.0", optional = true }
|
winit = { version = "0.30", optional = true }
|
||||||
xdg = "2.5.2"
|
xdg = "3.0"
|
||||||
log-panics = { version = "2.1.0", features = ["with-backtrace"] }
|
log-panics = { version = "2.1.0", features = ["with-backtrace"] }
|
||||||
serde_json5 = "0.2.1"
|
serde_json5 = "0.2.1"
|
||||||
xkbcommon = { version = "0.8.0" }
|
xkbcommon = { version = "0.8.0" }
|
||||||
@@ -74,6 +70,9 @@ image_dds = { version = "0.7.2", default-features = false, features = [
|
|||||||
mint = "0.5.9"
|
mint = "0.5.9"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
vulkano = { workspace = true }
|
||||||
|
vulkano-shaders = { workspace = true }
|
||||||
|
wgui = { path = "../wgui" }
|
||||||
|
|
||||||
################################
|
################################
|
||||||
#WayVR-only deps
|
#WayVR-only deps
|
||||||
@@ -92,6 +91,7 @@ wayland-egl = { version = "0.32.4", optional = true }
|
|||||||
interprocess = { version = "2.2.2", optional = true }
|
interprocess = { version = "2.2.2", optional = true }
|
||||||
bytes = { version = "1.9.0", optional = true }
|
bytes = { version = "1.9.0", optional = true }
|
||||||
wayvr_ipc = { git = "https://github.com/olekolek1000/wayvr-ipc.git", rev = "a72587d23f3bb8624d9aeb1f13c0a21e65350f51", default-features = false, optional = true }
|
wayvr_ipc = { git = "https://github.com/olekolek1000/wayvr-ipc.git", rev = "a72587d23f3bb8624d9aeb1f13c0a21e65350f51", default-features = false, optional = true }
|
||||||
|
rust-embed = "8.7.2"
|
||||||
################################
|
################################
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|||||||
@@ -137,9 +137,8 @@ where
|
|||||||
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||||
#[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
|
#[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
|
||||||
pub fn update(&mut self, app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
|
pub fn update(&mut self, app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
|
||||||
use crate::overlays::{
|
use crate::overlays::screen::{
|
||||||
screen::{create_screen_interaction, create_screen_renderer_wl, load_pw_token_config},
|
create_screen_interaction, create_screen_renderer_wl, load_pw_token_config,
|
||||||
watch::create_watch_canvas,
|
|
||||||
};
|
};
|
||||||
use glam::vec2;
|
use glam::vec2;
|
||||||
use wlx_capture::wayland::OutputChangeEvent;
|
use wlx_capture::wayland::OutputChangeEvent;
|
||||||
@@ -256,15 +255,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
if watch_dirty {
|
if watch_dirty {
|
||||||
let watch = self.mut_by_name(WATCH_NAME).unwrap(); // want panic
|
let _watch = self.mut_by_name(WATCH_NAME).unwrap(); // want panic
|
||||||
match create_watch_canvas(None, app) {
|
todo!();
|
||||||
Ok(canvas) => {
|
|
||||||
watch.backend = Box::new(canvas);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to create watch canvas: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(removed_overlays)
|
Ok(removed_overlays)
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ pub mod openvr;
|
|||||||
#[cfg(feature = "openxr")]
|
#[cfg(feature = "openxr")]
|
||||||
pub mod openxr;
|
pub mod openxr;
|
||||||
|
|
||||||
#[cfg(feature = "uidev")]
|
|
||||||
pub mod uidev;
|
|
||||||
|
|
||||||
#[cfg(feature = "osc")]
|
#[cfg(feature = "osc")]
|
||||||
pub mod osc;
|
pub mod osc;
|
||||||
|
|
||||||
|
|||||||
@@ -279,14 +279,14 @@ struct XsoMessage {
|
|||||||
messageType: i32,
|
messageType: i32,
|
||||||
index: Option<i32>,
|
index: Option<i32>,
|
||||||
volume: Option<f32>,
|
volume: Option<f32>,
|
||||||
audioPath: Option<Arc<str>>,
|
audioPath: Option<String>,
|
||||||
timeout: Option<f32>,
|
timeout: Option<f32>,
|
||||||
title: Arc<str>,
|
title: String,
|
||||||
content: Option<Arc<str>>,
|
content: Option<String>,
|
||||||
icon: Option<Arc<str>>,
|
icon: Option<String>,
|
||||||
height: Option<f32>,
|
height: Option<f32>,
|
||||||
opacity: Option<f32>,
|
opacity: Option<f32>,
|
||||||
useBase64Icon: Option<bool>,
|
useBase64Icon: Option<bool>,
|
||||||
sourceApp: Option<Arc<str>>,
|
sourceApp: Option<String>,
|
||||||
alwaysShow: Option<bool>,
|
alwaysShow: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,31 @@ use std::f32::consts::PI;
|
|||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ash::vk::SubmitInfo;
|
||||||
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
||||||
use idmap::IdMap;
|
use idmap::IdMap;
|
||||||
use ovr_overlay::overlay::OverlayManager;
|
use ovr_overlay::overlay::OverlayManager;
|
||||||
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
||||||
use vulkano::command_buffer::CommandBufferUsage;
|
use vulkano::{
|
||||||
use vulkano::format::Format;
|
command_buffer::{
|
||||||
use vulkano::image::view::ImageView;
|
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
|
||||||
use vulkano::image::ImageLayout;
|
},
|
||||||
|
format::Format,
|
||||||
|
image::view::ImageView,
|
||||||
|
image::{Image, ImageLayout},
|
||||||
|
sync::{
|
||||||
|
fence::{Fence, FenceCreateInfo},
|
||||||
|
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
|
||||||
|
},
|
||||||
|
VulkanObject,
|
||||||
|
};
|
||||||
|
use wgui::gfx::WGfx;
|
||||||
|
|
||||||
use crate::backend::overlay::{
|
use crate::backend::overlay::{
|
||||||
FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend,
|
FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend,
|
||||||
Z_ORDER_LINES,
|
Z_ORDER_LINES,
|
||||||
};
|
};
|
||||||
use crate::graphics::{CommandBuffers, WlxGraphics};
|
use crate::graphics::CommandBuffers;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
use super::overlay::OpenVrOverlayData;
|
use super::overlay::OpenVrOverlayData;
|
||||||
@@ -29,19 +40,17 @@ pub(super) struct LinePool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LinePool {
|
impl LinePool {
|
||||||
pub fn new(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> {
|
pub fn new(graphics: Arc<WGfx>) -> anyhow::Result<Self> {
|
||||||
let mut command_buffer = graphics.create_uploads_command_buffer(
|
let mut command_buffer =
|
||||||
graphics.transfer_queue.clone(),
|
graphics.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let buf = vec![255; 16];
|
let buf = vec![255; 16];
|
||||||
|
|
||||||
let texture = command_buffer.texture2d_raw(2, 2, Format::R8G8B8A8_UNORM, &buf)?;
|
let texture = command_buffer.upload_image(2, 2, Format::R8G8B8A8_UNORM, &buf)?;
|
||||||
command_buffer.build_and_execute_now()?;
|
command_buffer.build_and_execute_now()?;
|
||||||
|
|
||||||
graphics
|
transition_layout(
|
||||||
.transition_layout(
|
&graphics,
|
||||||
texture.clone(),
|
texture.clone(),
|
||||||
ImageLayout::ShaderReadOnlyOptimal,
|
ImageLayout::ShaderReadOnlyOptimal,
|
||||||
ImageLayout::TransferSrcOptimal,
|
ImageLayout::TransferSrcOptimal,
|
||||||
@@ -150,7 +159,7 @@ impl LinePool {
|
|||||||
data.after_input(overlay, app)?;
|
data.after_input(overlay, app)?;
|
||||||
if data.state.want_visible {
|
if data.state.want_visible {
|
||||||
if data.state.dirty {
|
if data.state.dirty {
|
||||||
data.upload_texture(overlay, &app.graphics);
|
data.upload_texture(overlay, &app.gfx);
|
||||||
data.state.dirty = false;
|
data.state.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,3 +210,55 @@ impl OverlayRenderer for StaticRenderer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transition_layout(
|
||||||
|
gfx: &WGfx,
|
||||||
|
image: Arc<Image>,
|
||||||
|
old_layout: ImageLayout,
|
||||||
|
new_layout: ImageLayout,
|
||||||
|
) -> anyhow::Result<Fence> {
|
||||||
|
let barrier = ImageMemoryBarrier {
|
||||||
|
src_stages: PipelineStages::ALL_TRANSFER,
|
||||||
|
src_access: AccessFlags::TRANSFER_WRITE,
|
||||||
|
dst_stages: PipelineStages::ALL_TRANSFER,
|
||||||
|
dst_access: AccessFlags::TRANSFER_READ,
|
||||||
|
old_layout,
|
||||||
|
new_layout,
|
||||||
|
subresource_range: image.subresource_range(),
|
||||||
|
..ImageMemoryBarrier::image(image)
|
||||||
|
};
|
||||||
|
|
||||||
|
let command_buffer = unsafe {
|
||||||
|
let mut builder = RecordingCommandBuffer::new(
|
||||||
|
gfx.command_buffer_allocator.clone(),
|
||||||
|
gfx.queue_gfx.queue_family_index(),
|
||||||
|
CommandBufferLevel::Primary,
|
||||||
|
CommandBufferBeginInfo {
|
||||||
|
usage: CommandBufferUsage::OneTimeSubmit,
|
||||||
|
inheritance_info: None,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
builder.pipeline_barrier(&DependencyInfo {
|
||||||
|
image_memory_barriers: smallvec::smallvec![barrier],
|
||||||
|
..Default::default()
|
||||||
|
})?;
|
||||||
|
builder.end()?
|
||||||
|
};
|
||||||
|
|
||||||
|
let fence = Fence::new(gfx.device.clone(), FenceCreateInfo::default())?;
|
||||||
|
|
||||||
|
let fns = gfx.device.fns();
|
||||||
|
unsafe {
|
||||||
|
(fns.v1_0.queue_submit)(
|
||||||
|
gfx.queue_gfx.handle(),
|
||||||
|
1,
|
||||||
|
[SubmitInfo::default().command_buffers(&[command_buffer.handle()])].as_ptr(),
|
||||||
|
fence.handle(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.result()?;
|
||||||
|
|
||||||
|
Ok(fence)
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use crate::{
|
|||||||
overlay::{OverlayData, ShouldRender},
|
overlay::{OverlayData, ShouldRender},
|
||||||
task::{SystemTask, TaskType},
|
task::{SystemTask, TaskType},
|
||||||
},
|
},
|
||||||
graphics::{CommandBuffers, WlxGraphics},
|
graphics::{init_openvr_graphics, CommandBuffers},
|
||||||
overlays::{
|
overlays::{
|
||||||
toast::{Toast, ToastTopic},
|
toast::{Toast, ToastTopic},
|
||||||
watch::{watch_fade, WATCH_NAME},
|
watch::{watch_fade, WATCH_NAME},
|
||||||
@@ -39,7 +39,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
use crate::{gui::modular::button::WayVRAction, overlays::wayvr::wayvr_action};
|
use crate::{backend::wayvr::WayVRAction, overlays::wayvr::wayvr_action};
|
||||||
|
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
@@ -95,8 +95,8 @@ pub fn openvr_run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut state = {
|
let mut state = {
|
||||||
let graphics = WlxGraphics::new_openvr(instance_extensions, device_extensions_fn)?;
|
let (gfx, gfx_extras) = init_openvr_graphics(instance_extensions, device_extensions_fn)?;
|
||||||
AppState::from_graphics(graphics)?
|
AppState::from_graphics(gfx, gfx_extras)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if show_by_default {
|
if show_by_default {
|
||||||
@@ -147,7 +147,7 @@ pub fn openvr_run(
|
|||||||
let mut next_device_update = Instant::now();
|
let mut next_device_update = Instant::now();
|
||||||
let mut due_tasks = VecDeque::with_capacity(4);
|
let mut due_tasks = VecDeque::with_capacity(4);
|
||||||
|
|
||||||
let mut lines = LinePool::new(state.graphics.clone())?;
|
let mut lines = LinePool::new(state.gfx.clone())?;
|
||||||
let pointer_lines = [lines.allocate(), lines.allocate()];
|
let pointer_lines = [lines.allocate(), lines.allocate()];
|
||||||
|
|
||||||
'main_loop: loop {
|
'main_loop: loop {
|
||||||
@@ -361,7 +361,7 @@ pub fn openvr_run(
|
|||||||
|
|
||||||
log::trace!("Rendering overlays");
|
log::trace!("Rendering overlays");
|
||||||
|
|
||||||
if let Some(mut future) = buffers.execute_now(state.graphics.graphics_queue.clone())? {
|
if let Some(mut future) = buffers.execute_now(state.gfx.queue_gfx.clone())? {
|
||||||
if let Err(e) = future.flush() {
|
if let Err(e) = future.flush() {
|
||||||
return Err(BackendError::Fatal(e.into()));
|
return Err(BackendError::Fatal(e.into()));
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ pub fn openvr_run(
|
|||||||
|
|
||||||
overlays
|
overlays
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics));
|
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.gfx));
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
if let Some(wayvr) = &state.wayvr {
|
if let Some(wayvr) = &state.wayvr {
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ use ovr_overlay::{
|
|||||||
pose::Matrix3x4,
|
pose::Matrix3x4,
|
||||||
sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t},
|
sys::{ETrackingUniverseOrigin, VRVulkanTextureData_t},
|
||||||
};
|
};
|
||||||
use vulkano::{image::view::ImageView, Handle, VulkanObject};
|
use vulkano::{
|
||||||
|
image::{view::ImageView, ImageUsage},
|
||||||
|
Handle, VulkanObject,
|
||||||
|
};
|
||||||
|
use wgui::gfx::WGfx;
|
||||||
|
|
||||||
use crate::{backend::overlay::OverlayData, graphics::WlxGraphics, state::AppState};
|
use crate::{backend::overlay::OverlayData, state::AppState};
|
||||||
|
|
||||||
use super::helpers::Affine3AConvert;
|
use super::helpers::Affine3AConvert;
|
||||||
|
|
||||||
@@ -65,10 +69,11 @@ impl OverlayData<OpenVrOverlayData> {
|
|||||||
let Some(meta) = self.backend.frame_meta() else {
|
let Some(meta) = self.backend.frame_meta() else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let image = app.graphics.render_texture(
|
let image = app.gfx.new_image(
|
||||||
meta.extent[0],
|
meta.extent[0],
|
||||||
meta.extent[1],
|
meta.extent[1],
|
||||||
app.graphics.native_format,
|
app.gfx.surface_format,
|
||||||
|
ImageUsage::TRANSFER_SRC | ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED,
|
||||||
)?;
|
)?;
|
||||||
self.data.image_view = Some(ImageView::new_default(image)?);
|
self.data.image_view = Some(ImageView::new_default(image)?);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@@ -91,7 +96,7 @@ impl OverlayData<OpenVrOverlayData> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
universe: ETrackingUniverseOrigin,
|
universe: ETrackingUniverseOrigin,
|
||||||
overlay: &mut OverlayManager,
|
overlay: &mut OverlayManager,
|
||||||
graphics: &WlxGraphics,
|
graphics: &WGfx,
|
||||||
) {
|
) {
|
||||||
if self.data.visible {
|
if self.data.visible {
|
||||||
if self.state.dirty {
|
if self.state.dirty {
|
||||||
@@ -228,7 +233,7 @@ impl OverlayData<OpenVrOverlayData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn upload_texture(&mut self, overlay: &mut OverlayManager, graphics: &WlxGraphics) {
|
pub(super) fn upload_texture(&mut self, overlay: &mut OverlayManager, graphics: &WGfx) {
|
||||||
let Some(handle) = self.data.handle else {
|
let Some(handle) = self.data.handle else {
|
||||||
log::debug!("{}: No overlay handle", self.state.name);
|
log::debug!("{}: No overlay handle", self.state.name);
|
||||||
return;
|
return;
|
||||||
@@ -267,8 +272,8 @@ impl OverlayData<OpenVrOverlayData> {
|
|||||||
m_pDevice: graphics.device.handle().as_raw() as *mut _,
|
m_pDevice: graphics.device.handle().as_raw() as *mut _,
|
||||||
m_pPhysicalDevice: graphics.device.physical_device().handle().as_raw() as *mut _,
|
m_pPhysicalDevice: graphics.device.physical_device().handle().as_raw() as *mut _,
|
||||||
m_pInstance: graphics.instance.handle().as_raw() as *mut _,
|
m_pInstance: graphics.instance.handle().as_raw() as *mut _,
|
||||||
m_pQueue: graphics.graphics_queue.handle().as_raw() as *mut _,
|
m_pQueue: graphics.queue_gfx.handle().as_raw() as *mut _,
|
||||||
m_nQueueFamilyIndex: graphics.graphics_queue.queue_family_index(),
|
m_nQueueFamilyIndex: graphics.queue_gfx.queue_family_index(),
|
||||||
};
|
};
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"{}: UploadTex {:?}, {}x{}, {:?}",
|
"{}: UploadTex {:?}, {}x{}, {:?}",
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ use std::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use vulkano::command_buffer::CommandBufferUsage;
|
use wgui::gfx::{pipeline::WGfxPipeline, WGfx};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::openxr::helpers,
|
backend::openxr::helpers,
|
||||||
graphics::{CommandBuffers, WlxGraphics, WlxPipeline},
|
graphics::{CommandBuffers, ExtentExt, Vert2Uv},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
use vulkano::{
|
||||||
|
command_buffer::CommandBufferUsage, pipeline::graphics::input_assembly::PrimitiveTopology,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -37,20 +41,18 @@ static COLORS: [[f32; 6]; 5] = {
|
|||||||
|
|
||||||
pub(super) struct LinePool {
|
pub(super) struct LinePool {
|
||||||
lines: IdMap<usize, LineContainer>,
|
lines: IdMap<usize, LineContainer>,
|
||||||
pipeline: Arc<WlxPipeline>,
|
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinePool {
|
impl LinePool {
|
||||||
pub(super) fn new(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> {
|
pub(super) fn new(app: &AppState) -> anyhow::Result<Self> {
|
||||||
let Ok(shaders) = graphics.shared_shaders.read() else {
|
let pipeline = app.gfx.create_pipeline(
|
||||||
anyhow::bail!("Failed to lock shared shaders for reading");
|
app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
|
||||||
};
|
app.gfx_extras.shaders.get("frag_color").unwrap().clone(), // want panic
|
||||||
|
app.gfx.surface_format,
|
||||||
let pipeline = graphics.create_pipeline(
|
|
||||||
shaders.get("vert_common").unwrap().clone(), // want panic
|
|
||||||
shaders.get("frag_color").unwrap().clone(), // want panic
|
|
||||||
graphics.native_format,
|
|
||||||
None,
|
None,
|
||||||
|
PrimitiveTopology::TriangleStrip,
|
||||||
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -59,14 +61,10 @@ impl LinePool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn allocate(
|
pub(super) fn allocate(&mut self, xr: &XrState, gfx: Arc<WGfx>) -> anyhow::Result<usize> {
|
||||||
&mut self,
|
|
||||||
xr: &XrState,
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
) -> anyhow::Result<usize> {
|
|
||||||
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
|
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
let srd = create_swapchain(xr, graphics, [1, 1, 1], SwapchainOpts::new())?;
|
let srd = create_swapchain(xr, gfx, [1, 1, 1], SwapchainOpts::new())?;
|
||||||
self.lines.insert(
|
self.lines.insert(
|
||||||
id,
|
id,
|
||||||
LineContainer {
|
LineContainer {
|
||||||
@@ -130,7 +128,7 @@ impl LinePool {
|
|||||||
|
|
||||||
pub(super) fn render(
|
pub(super) fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
graphics: Arc<WlxGraphics>,
|
app: &AppState,
|
||||||
buf: &mut CommandBuffers,
|
buf: &mut CommandBuffers,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
for line in self.lines.values_mut() {
|
for line in self.lines.values_mut() {
|
||||||
@@ -139,14 +137,19 @@ impl LinePool {
|
|||||||
|
|
||||||
let set0 = self
|
let set0 = self
|
||||||
.pipeline
|
.pipeline
|
||||||
.uniform_buffer(0, COLORS[inner.color].to_vec())?;
|
.uniform_buffer_upload(0, COLORS[inner.color].to_vec())?;
|
||||||
|
|
||||||
let pass = self
|
let pass = self.pipeline.create_pass(
|
||||||
.pipeline
|
tgt.extent_f32(),
|
||||||
.create_pass_for_target(tgt.clone(), vec![set0])?;
|
app.gfx_extras.quad_verts.clone(),
|
||||||
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![set0],
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut cmd_buffer =
|
let mut cmd_buffer = app
|
||||||
graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
.gfx
|
||||||
|
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
cmd_buffer.begin_rendering(tgt)?;
|
cmd_buffer.begin_rendering(tgt)?;
|
||||||
cmd_buffer.run_ref(&pass)?;
|
cmd_buffer.run_ref(&pass)?;
|
||||||
cmd_buffer.end_rendering()?;
|
cmd_buffer.end_rendering()?;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use crate::{
|
|||||||
overlay::{OverlayData, ShouldRender},
|
overlay::{OverlayData, ShouldRender},
|
||||||
task::{SystemTask, TaskType},
|
task::{SystemTask, TaskType},
|
||||||
},
|
},
|
||||||
graphics::{CommandBuffers, WlxGraphics},
|
graphics::{init_openxr_graphics, CommandBuffers},
|
||||||
overlays::{
|
overlays::{
|
||||||
toast::{Toast, ToastTopic},
|
toast::{Toast, ToastTopic},
|
||||||
watch::{watch_fade, WATCH_NAME},
|
watch::{watch_fade, WATCH_NAME},
|
||||||
@@ -33,7 +33,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
use crate::{gui::modular::button::WayVRAction, overlays::wayvr::wayvr_action};
|
use crate::{backend::wayvr::WayVRAction, overlays::wayvr::wayvr_action};
|
||||||
|
|
||||||
mod blocker;
|
mod blocker;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
@@ -71,8 +71,8 @@ pub fn openxr_run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut app = {
|
let mut app = {
|
||||||
let graphics = WlxGraphics::new_openxr(xr_instance.clone(), system)?;
|
let (gfx, gfx_extras) = init_openxr_graphics(xr_instance.clone(), system)?;
|
||||||
AppState::from_graphics(graphics)?
|
AppState::from_graphics(gfx, gfx_extras)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let environment_blend_mode = {
|
let environment_blend_mode = {
|
||||||
@@ -95,7 +95,7 @@ pub fn openxr_run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app, headless)?;
|
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app, headless)?;
|
||||||
let mut lines = LinePool::new(app.graphics.clone())?;
|
let mut lines = LinePool::new(&app)?;
|
||||||
|
|
||||||
let mut notifications = NotificationManager::new();
|
let mut notifications = NotificationManager::new();
|
||||||
notifications.run_dbus();
|
notifications.run_dbus();
|
||||||
@@ -120,10 +120,10 @@ pub fn openxr_run(
|
|||||||
&xr_instance,
|
&xr_instance,
|
||||||
system,
|
system,
|
||||||
&xr::vulkan::SessionCreateInfo {
|
&xr::vulkan::SessionCreateInfo {
|
||||||
instance: app.graphics.instance.handle().as_raw() as _,
|
instance: app.gfx.instance.handle().as_raw() as _,
|
||||||
physical_device: app.graphics.device.physical_device().handle().as_raw() as _,
|
physical_device: app.gfx.device.physical_device().handle().as_raw() as _,
|
||||||
device: app.graphics.device.handle().as_raw() as _,
|
device: app.gfx.device.handle().as_raw() as _,
|
||||||
queue_family_index: app.graphics.graphics_queue.queue_family_index(),
|
queue_family_index: app.gfx.queue_gfx.queue_family_index(),
|
||||||
queue_index: 0,
|
queue_index: 0,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@@ -151,8 +151,8 @@ pub fn openxr_run(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let pointer_lines = [
|
let pointer_lines = [
|
||||||
lines.allocate(&xr_state, app.graphics.clone())?,
|
lines.allocate(&xr_state, app.gfx.clone())?,
|
||||||
lines.allocate(&xr_state, app.graphics.clone())?,
|
lines.allocate(&xr_state, app.gfx.clone())?,
|
||||||
];
|
];
|
||||||
|
|
||||||
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
|
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
|
||||||
@@ -418,9 +418,9 @@ pub fn openxr_run(
|
|||||||
o.data.cur_visible = true;
|
o.data.cur_visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.render(app.graphics.clone(), &mut buffers)?;
|
lines.render(&app, &mut buffers)?;
|
||||||
|
|
||||||
let future = buffers.execute_now(app.graphics.graphics_queue.clone())?;
|
let future = buffers.execute_now(app.gfx.queue_gfx.clone())?;
|
||||||
if let Some(mut future) = future {
|
if let Some(mut future) = future {
|
||||||
if let Err(e) = future.flush() {
|
if let Err(e) = future.flush() {
|
||||||
return Err(BackendError::Fatal(e.into()));
|
return Err(BackendError::Fatal(e.into()));
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ impl OverlayData<OpenXrOverlayData> {
|
|||||||
let extent = meta.extent;
|
let extent = meta.extent;
|
||||||
self.data.swapchain = Some(create_swapchain(
|
self.data.swapchain = Some(create_swapchain(
|
||||||
xr,
|
xr,
|
||||||
app.graphics.clone(),
|
app.gfx.clone(),
|
||||||
extent,
|
extent,
|
||||||
SwapchainOpts::new(),
|
SwapchainOpts::new(),
|
||||||
)?);
|
)?);
|
||||||
|
|||||||
@@ -5,16 +5,17 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use glam::{Quat, Vec3A};
|
use glam::{Quat, Vec3A};
|
||||||
use openxr::{self as xr, CompositionLayerFlags};
|
use openxr as xr;
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
command_buffer::CommandBufferUsage, image::view::ImageView,
|
command_buffer::CommandBufferUsage,
|
||||||
pipeline::graphics::color_blend::AttachmentBlend,
|
image::view::ImageView,
|
||||||
|
pipeline::graphics::{color_blend::AttachmentBlend, input_assembly::PrimitiveTopology},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
|
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
|
||||||
config_io,
|
config_io,
|
||||||
graphics::{dds::WlxCommandBufferDds, CommandBuffers},
|
graphics::{dds::WlxCommandBufferDds, CommandBuffers, ExtentExt},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,10 +32,9 @@ pub(super) struct Skybox {
|
|||||||
|
|
||||||
impl Skybox {
|
impl Skybox {
|
||||||
pub fn new(app: &AppState) -> anyhow::Result<Self> {
|
pub fn new(app: &AppState) -> anyhow::Result<Self> {
|
||||||
let mut command_buffer = app.graphics.create_uploads_command_buffer(
|
let mut command_buffer = app
|
||||||
app.graphics.transfer_queue.clone(),
|
.gfx
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut maybe_image = None;
|
let mut maybe_image = None;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ impl Skybox {
|
|||||||
);
|
);
|
||||||
break 'custom_tex;
|
break 'custom_tex;
|
||||||
};
|
};
|
||||||
match command_buffer.texture2d_dds(f) {
|
match command_buffer.upload_image_dds(f) {
|
||||||
Ok(image) => {
|
Ok(image) => {
|
||||||
maybe_image = Some(image);
|
maybe_image = Some(image);
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ impl Skybox {
|
|||||||
|
|
||||||
if maybe_image.is_none() {
|
if maybe_image.is_none() {
|
||||||
let p = include_bytes!("../../res/table_mountain_2.dds");
|
let p = include_bytes!("../../res/table_mountain_2.dds");
|
||||||
maybe_image = Some(command_buffer.texture2d_dds(p.as_slice())?);
|
maybe_image = Some(command_buffer.upload_image_dds(p.as_slice())?);
|
||||||
}
|
}
|
||||||
|
|
||||||
command_buffer.build_and_execute_now()?;
|
command_buffer.build_and_execute_now()?;
|
||||||
@@ -92,30 +92,31 @@ impl Skybox {
|
|||||||
}
|
}
|
||||||
let opts = SwapchainOpts::new().immutable();
|
let opts = SwapchainOpts::new().immutable();
|
||||||
|
|
||||||
let Ok(shaders) = app.graphics.shared_shaders.read() else {
|
|
||||||
anyhow::bail!("Failed to lock shared shaders for reading");
|
|
||||||
};
|
|
||||||
|
|
||||||
let extent = self.view.image().extent();
|
let extent = self.view.image().extent();
|
||||||
let mut swapchain = create_swapchain(xr, app.graphics.clone(), extent, opts)?;
|
let mut swapchain = create_swapchain(xr, app.gfx.clone(), extent, opts)?;
|
||||||
let tgt = swapchain.acquire_wait_image()?;
|
let tgt = swapchain.acquire_wait_image()?;
|
||||||
let pipeline = app.graphics.create_pipeline(
|
let pipeline = app.gfx.create_pipeline(
|
||||||
shaders.get("vert_common").unwrap().clone(), // want panic
|
app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
|
||||||
shaders.get("frag_srgb").unwrap().clone(), // want panic
|
app.gfx_extras.shaders.get("frag_srgb").unwrap().clone(), // want panic
|
||||||
app.graphics.native_format,
|
app.gfx.surface_format,
|
||||||
None,
|
None,
|
||||||
|
PrimitiveTopology::TriangleStrip,
|
||||||
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let set0 =
|
let set0 = pipeline.uniform_sampler(0, self.view.clone(), app.gfx.texture_filter)?;
|
||||||
pipeline.uniform_sampler(0, self.view.clone(), app.graphics.texture_filtering)?;
|
let set1 = pipeline.uniform_buffer_upload(1, vec![1f32])?;
|
||||||
|
let pass = pipeline.create_pass(
|
||||||
let set1 = pipeline.uniform_buffer(1, vec![1f32])?;
|
tgt.extent_f32(),
|
||||||
|
app.gfx_extras.quad_verts.clone(),
|
||||||
let pass = pipeline.create_pass_for_target(tgt.clone(), vec![set0, set1])?;
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![set0, set1],
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut cmd_buffer = app
|
let mut cmd_buffer = app
|
||||||
.graphics
|
.gfx
|
||||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
cmd_buffer.begin_rendering(tgt)?;
|
cmd_buffer.begin_rendering(tgt)?;
|
||||||
cmd_buffer.run_ref(&pass)?;
|
cmd_buffer.run_ref(&pass)?;
|
||||||
cmd_buffer.end_rendering()?;
|
cmd_buffer.end_rendering()?;
|
||||||
@@ -135,30 +136,35 @@ impl Skybox {
|
|||||||
if self.grid.is_some() {
|
if self.grid.is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let Ok(shaders) = app.graphics.shared_shaders.read() else {
|
|
||||||
anyhow::bail!("Failed to lock shared shaders for reading");
|
|
||||||
};
|
|
||||||
|
|
||||||
let extent = [1024, 1024, 1];
|
let extent = [1024, 1024, 1];
|
||||||
let mut swapchain = create_swapchain(
|
let mut swapchain = create_swapchain(
|
||||||
xr,
|
xr,
|
||||||
app.graphics.clone(),
|
app.gfx.clone(),
|
||||||
extent,
|
extent,
|
||||||
SwapchainOpts::new().immutable(),
|
SwapchainOpts::new().immutable(),
|
||||||
)?;
|
)?;
|
||||||
let pipeline = app.graphics.create_pipeline(
|
let pipeline = app.gfx.create_pipeline(
|
||||||
shaders.get("vert_common").unwrap().clone(), // want panic
|
app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
|
||||||
shaders.get("frag_grid").unwrap().clone(), // want panic
|
app.gfx_extras.shaders.get("frag_grid").unwrap().clone(), // want panic
|
||||||
app.graphics.native_format,
|
app.gfx.surface_format,
|
||||||
Some(AttachmentBlend::alpha()),
|
Some(AttachmentBlend::alpha()),
|
||||||
|
PrimitiveTopology::TriangleStrip,
|
||||||
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let tgt = swapchain.acquire_wait_image()?;
|
let tgt = swapchain.acquire_wait_image()?;
|
||||||
let pass = pipeline.create_pass_for_target(tgt.clone(), vec![])?;
|
let pass = pipeline.create_pass(
|
||||||
|
tgt.extent_f32(),
|
||||||
|
app.gfx_extras.quad_verts.clone(),
|
||||||
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![],
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut cmd_buffer = app
|
let mut cmd_buffer = app
|
||||||
.graphics
|
.gfx
|
||||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
cmd_buffer.begin_rendering(tgt)?;
|
cmd_buffer.begin_rendering(tgt)?;
|
||||||
cmd_buffer.run_ref(&pass)?;
|
cmd_buffer.run_ref(&pass)?;
|
||||||
cmd_buffer.end_rendering()?;
|
cmd_buffer.end_rendering()?;
|
||||||
@@ -206,7 +212,7 @@ impl Skybox {
|
|||||||
self.sky.as_mut().unwrap().ensure_image_released()?;
|
self.sky.as_mut().unwrap().ensure_image_released()?;
|
||||||
|
|
||||||
let sky = xr::CompositionLayerEquirect2KHR::new()
|
let sky = xr::CompositionLayerEquirect2KHR::new()
|
||||||
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
||||||
.pose(pose)
|
.pose(pose)
|
||||||
.radius(10.0)
|
.radius(10.0)
|
||||||
.sub_image(self.sky.as_ref().unwrap().get_subimage())
|
.sub_image(self.sky.as_ref().unwrap().get_subimage())
|
||||||
@@ -218,7 +224,7 @@ impl Skybox {
|
|||||||
|
|
||||||
self.grid.as_mut().unwrap().ensure_image_released()?;
|
self.grid.as_mut().unwrap().ensure_image_released()?;
|
||||||
let grid = xr::CompositionLayerQuad::new()
|
let grid = xr::CompositionLayerQuad::new()
|
||||||
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
||||||
.pose(*GRID_POSE)
|
.pose(*GRID_POSE)
|
||||||
.size(xr::Extent2Df {
|
.size(xr::Extent2Df {
|
||||||
width: 10.0,
|
width: 10.0,
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ use vulkano::{
|
|||||||
image::{sys::RawImage, view::ImageView, ImageCreateInfo, ImageUsage},
|
image::{sys::RawImage, view::ImageView, ImageCreateInfo, ImageUsage},
|
||||||
Handle,
|
Handle,
|
||||||
};
|
};
|
||||||
|
use wgui::gfx::WGfx;
|
||||||
use crate::graphics::WlxGraphics;
|
|
||||||
|
|
||||||
use super::XrState;
|
use super::XrState;
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ impl SwapchainOpts {
|
|||||||
|
|
||||||
pub(super) fn create_swapchain(
|
pub(super) fn create_swapchain(
|
||||||
xr: &XrState,
|
xr: &XrState,
|
||||||
graphics: Arc<WlxGraphics>,
|
gfx: Arc<WGfx>,
|
||||||
extent: [u32; 3],
|
extent: [u32; 3],
|
||||||
opts: SwapchainOpts,
|
opts: SwapchainOpts,
|
||||||
) -> anyhow::Result<WlxSwapchain> {
|
) -> anyhow::Result<WlxSwapchain> {
|
||||||
@@ -43,7 +42,7 @@ pub(super) fn create_swapchain(
|
|||||||
let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo {
|
let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo {
|
||||||
create_flags,
|
create_flags,
|
||||||
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED,
|
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED,
|
||||||
format: graphics.native_format as _,
|
format: gfx.surface_format as _,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
width: extent[0],
|
width: extent[0],
|
||||||
height: extent[1],
|
height: extent[1],
|
||||||
@@ -60,10 +59,10 @@ pub(super) fn create_swapchain(
|
|||||||
// thanks @yshui
|
// thanks @yshui
|
||||||
let raw_image = unsafe {
|
let raw_image = unsafe {
|
||||||
RawImage::from_handle_borrowed(
|
RawImage::from_handle_borrowed(
|
||||||
graphics.device.clone(),
|
gfx.device.clone(),
|
||||||
vk_image,
|
vk_image,
|
||||||
ImageCreateInfo {
|
ImageCreateInfo {
|
||||||
format: graphics.native_format as _,
|
format: gfx.surface_format as _,
|
||||||
extent,
|
extent,
|
||||||
usage: ImageUsage::COLOR_ATTACHMENT,
|
usage: ImageUsage::COLOR_ATTACHMENT,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -301,8 +301,11 @@ pub struct FrameMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum ShouldRender {
|
pub enum ShouldRender {
|
||||||
|
/// The overlay is dirty and needs to be rendered.
|
||||||
Should,
|
Should,
|
||||||
|
/// The overlay is not dirty but is ready to be rendered.
|
||||||
Can,
|
Can,
|
||||||
|
/// The overlay is not ready to be rendered.
|
||||||
Unable,
|
Unable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use serde::Deserialize;
|
|||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
use crate::gui::modular::button::WayVRAction;
|
use crate::backend::wayvr::WayVRAction;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::OverlaySelector,
|
common::OverlaySelector,
|
||||||
|
|||||||
@@ -1,289 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use vulkano::{
|
|
||||||
image::{view::ImageView, ImageUsage},
|
|
||||||
swapchain::{
|
|
||||||
acquire_next_image, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
|
|
||||||
SwapchainPresentInfo,
|
|
||||||
},
|
|
||||||
sync::GpuFuture,
|
|
||||||
Validated, VulkanError,
|
|
||||||
};
|
|
||||||
use winit::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::ControlFlow,
|
|
||||||
window::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::load_custom_ui,
|
|
||||||
config_io,
|
|
||||||
graphics::{CommandBuffers, WlxGraphics},
|
|
||||||
gui::{
|
|
||||||
canvas::Canvas,
|
|
||||||
modular::{modular_canvas, ModularData},
|
|
||||||
},
|
|
||||||
hid::USE_UINPUT,
|
|
||||||
state::{AppState, ScreenMeta},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
input::{TrackedDevice, TrackedDeviceRole},
|
|
||||||
overlay::{OverlayID, OverlayRenderer},
|
|
||||||
};
|
|
||||||
|
|
||||||
static LAST_SIZE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
|
||||||
|
|
||||||
struct PreviewState {
|
|
||||||
canvas: Canvas<(), ModularData>,
|
|
||||||
swapchain: Arc<Swapchain>,
|
|
||||||
images: Vec<Arc<ImageView>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PreviewState {
|
|
||||||
fn new(
|
|
||||||
state: &mut AppState,
|
|
||||||
surface: Arc<Surface>,
|
|
||||||
window: Arc<Window>,
|
|
||||||
panel_name: &str,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
let config = load_custom_ui(panel_name)?;
|
|
||||||
|
|
||||||
let last_size = {
|
|
||||||
let size_u64 = LAST_SIZE.load(std::sync::atomic::Ordering::Relaxed);
|
|
||||||
[size_u64 as u32, (size_u64 >> 32) as u32]
|
|
||||||
};
|
|
||||||
|
|
||||||
if last_size != config.size {
|
|
||||||
let logical_size = LogicalSize::new(config.size[0], config.size[1]);
|
|
||||||
let _ = window.request_inner_size(logical_size);
|
|
||||||
window.set_min_inner_size(Some(logical_size));
|
|
||||||
window.set_max_inner_size(Some(logical_size));
|
|
||||||
LAST_SIZE.store(
|
|
||||||
((config.size[1] as u64) << 32) | config.size[0] as u64,
|
|
||||||
std::sync::atomic::Ordering::Relaxed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let inner_size = window.inner_size();
|
|
||||||
let swapchain_size = [inner_size.width, inner_size.height];
|
|
||||||
let (swapchain, images) = create_swapchain(&state.graphics, surface, swapchain_size)?;
|
|
||||||
|
|
||||||
let mut canvas = modular_canvas(config.size, &config.elements, state)?;
|
|
||||||
canvas.init(state)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
canvas,
|
|
||||||
swapchain,
|
|
||||||
images,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
pub fn uidev_run(panel_name: &str) -> anyhow::Result<()> {
|
|
||||||
let (graphics, event_loop, window, surface) = WlxGraphics::new_window()?;
|
|
||||||
window.set_resizable(false);
|
|
||||||
window.set_title("WlxOverlay UI Preview");
|
|
||||||
|
|
||||||
USE_UINPUT.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
|
|
||||||
let mut state = AppState::from_graphics(graphics.clone())?;
|
|
||||||
add_dummy_devices(&mut state);
|
|
||||||
add_dummy_screens(&mut state);
|
|
||||||
|
|
||||||
let mut preview = Some(PreviewState::new(
|
|
||||||
&mut state,
|
|
||||||
surface.clone(),
|
|
||||||
window.clone(),
|
|
||||||
panel_name,
|
|
||||||
)?);
|
|
||||||
|
|
||||||
let watch_path = config_io::get_config_root().join(format!("{panel_name}.yaml"));
|
|
||||||
let mut path_last_modified = watch_path.metadata()?.modified()?;
|
|
||||||
let mut recreate = false;
|
|
||||||
let mut last_draw = std::time::Instant::now();
|
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
event_loop.run(move |event, elwt| {
|
|
||||||
elwt.set_control_flow(ControlFlow::Poll);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
elwt.exit();
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::Resized(_),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
recreate = true;
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::RedrawRequested,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let new_modified = watch_path.metadata().unwrap().modified().unwrap();
|
|
||||||
if new_modified > path_last_modified {
|
|
||||||
recreate = true;
|
|
||||||
path_last_modified = new_modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
if recreate {
|
|
||||||
drop(preview.take());
|
|
||||||
preview = Some(
|
|
||||||
PreviewState::new(&mut state, surface.clone(), window.clone(), panel_name)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
recreate = false;
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let preview = preview.as_mut().unwrap();
|
|
||||||
let (image_index, _, acquire_future) =
|
|
||||||
match acquire_next_image(preview.swapchain.clone(), None)
|
|
||||||
.map_err(Validated::unwrap)
|
|
||||||
{
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(VulkanError::OutOfDate) => {
|
|
||||||
recreate = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(e) => panic!("failed to acquire next image: {e}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut canvas_cmd_buf = CommandBuffers::default();
|
|
||||||
let tgt = preview.images[image_index as usize].clone();
|
|
||||||
|
|
||||||
if let Err(e) = preview
|
|
||||||
.canvas
|
|
||||||
.render(&mut state, tgt, &mut canvas_cmd_buf, 1.0)
|
|
||||||
{
|
|
||||||
log::error!("failed to render canvas: {e}");
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
last_draw = std::time::Instant::now();
|
|
||||||
|
|
||||||
canvas_cmd_buf
|
|
||||||
.execute_after(
|
|
||||||
state.graphics.graphics_queue.clone(),
|
|
||||||
Box::new(acquire_future),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.then_swapchain_present(
|
|
||||||
graphics.graphics_queue.clone(),
|
|
||||||
SwapchainPresentInfo::swapchain_image_index(
|
|
||||||
preview.swapchain.clone(),
|
|
||||||
image_index,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then_signal_fence_and_flush()
|
|
||||||
.unwrap()
|
|
||||||
.wait(None)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::AboutToWait => {
|
|
||||||
if last_draw.elapsed().as_millis() > 100 {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_swapchain(
|
|
||||||
graphics: &WlxGraphics,
|
|
||||||
surface: Arc<Surface>,
|
|
||||||
extent: [u32; 2],
|
|
||||||
) -> anyhow::Result<(Arc<Swapchain>, Vec<Arc<ImageView>>)> {
|
|
||||||
let surface_capabilities = graphics
|
|
||||||
.device
|
|
||||||
.physical_device()
|
|
||||||
.surface_capabilities(&surface, SurfaceInfo::default())
|
|
||||||
.unwrap(); // want panic
|
|
||||||
|
|
||||||
let (swapchain, images) = Swapchain::new(
|
|
||||||
graphics.device.clone(),
|
|
||||||
surface,
|
|
||||||
SwapchainCreateInfo {
|
|
||||||
min_image_count: surface_capabilities.min_image_count.max(2),
|
|
||||||
image_format: graphics.native_format,
|
|
||||||
image_extent: extent,
|
|
||||||
image_usage: ImageUsage::COLOR_ATTACHMENT,
|
|
||||||
composite_alpha: surface_capabilities
|
|
||||||
.supported_composite_alpha
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.unwrap(), // want panic
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let image_views = images
|
|
||||||
.into_iter()
|
|
||||||
// want panic
|
|
||||||
.map(|image| ImageView::new_default(image).unwrap())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok((swapchain, image_views))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_dummy_devices(app: &mut AppState) {
|
|
||||||
app.input_state.devices.push(TrackedDevice {
|
|
||||||
role: TrackedDeviceRole::Hmd,
|
|
||||||
soc: Some(0.42),
|
|
||||||
charging: true,
|
|
||||||
});
|
|
||||||
app.input_state.devices.push(TrackedDevice {
|
|
||||||
role: TrackedDeviceRole::LeftHand,
|
|
||||||
soc: Some(0.72),
|
|
||||||
charging: false,
|
|
||||||
});
|
|
||||||
app.input_state.devices.push(TrackedDevice {
|
|
||||||
role: TrackedDeviceRole::RightHand,
|
|
||||||
soc: Some(0.73),
|
|
||||||
charging: false,
|
|
||||||
});
|
|
||||||
app.input_state.devices.push(TrackedDevice {
|
|
||||||
role: TrackedDeviceRole::Tracker,
|
|
||||||
soc: Some(0.65),
|
|
||||||
charging: false,
|
|
||||||
});
|
|
||||||
app.input_state.devices.push(TrackedDevice {
|
|
||||||
role: TrackedDeviceRole::Tracker,
|
|
||||||
soc: Some(0.67),
|
|
||||||
charging: false,
|
|
||||||
});
|
|
||||||
app.input_state.devices.push(TrackedDevice {
|
|
||||||
role: TrackedDeviceRole::Tracker,
|
|
||||||
soc: Some(0.69),
|
|
||||||
charging: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_dummy_screens(app: &mut AppState) {
|
|
||||||
app.screens.push(ScreenMeta {
|
|
||||||
name: "HDMI-A-1".into(),
|
|
||||||
id: OverlayID(0),
|
|
||||||
native_handle: 0,
|
|
||||||
});
|
|
||||||
app.screens.push(ScreenMeta {
|
|
||||||
name: "DP-2".into(),
|
|
||||||
id: OverlayID(0),
|
|
||||||
native_handle: 0,
|
|
||||||
});
|
|
||||||
app.screens.push(ScreenMeta {
|
|
||||||
name: "DP-3".into(),
|
|
||||||
id: OverlayID(0),
|
|
||||||
native_handle: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@ use comp::Application;
|
|||||||
use display::{Display, DisplayInitParams, DisplayVec};
|
use display::{Display, DisplayInitParams, DisplayVec};
|
||||||
use event_queue::SyncEventQueue;
|
use event_queue::SyncEventQueue;
|
||||||
use process::ProcessVec;
|
use process::ProcessVec;
|
||||||
|
use serde::Deserialize;
|
||||||
use server_ipc::WayVRServer;
|
use server_ipc::WayVRServer;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
@@ -36,6 +37,7 @@ use std::{
|
|||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use time::get_millis;
|
use time::get_millis;
|
||||||
use wayvr_ipc::{packet_client, packet_server};
|
use wayvr_ipc::{packet_client, packet_server};
|
||||||
@@ -717,3 +719,22 @@ impl WayVRState {
|
|||||||
Ok(handle)
|
Ok(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub enum WayVRDisplayClickAction {
|
||||||
|
ToggleVisibility,
|
||||||
|
Reset,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub enum WayVRAction {
|
||||||
|
AppClick {
|
||||||
|
catalog_name: Arc<str>,
|
||||||
|
app_name: Arc<str>,
|
||||||
|
},
|
||||||
|
DisplayClick {
|
||||||
|
display_name: Arc<str>,
|
||||||
|
action: WayVRDisplayClickAction,
|
||||||
|
},
|
||||||
|
ToggleDashboard,
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::config_io;
|
use crate::config_io;
|
||||||
use crate::gui::modular::ModularUiConfig;
|
|
||||||
use crate::overlays::toast::DisplayMethod;
|
use crate::overlays::toast::DisplayMethod;
|
||||||
use crate::overlays::toast::ToastTopic;
|
use crate::overlays::toast::ToastTopic;
|
||||||
use crate::state::LeftRight;
|
use crate::state::LeftRight;
|
||||||
use anyhow::bail;
|
|
||||||
use chrono::Offset;
|
use chrono::Offset;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use config::File;
|
use config::File;
|
||||||
@@ -384,14 +382,6 @@ where
|
|||||||
panic!("No usable config found.");
|
panic!("No usable config found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_custom_ui(name: &str) -> anyhow::Result<ModularUiConfig> {
|
|
||||||
let filename = format!("{name}.yaml");
|
|
||||||
let Some(yaml_data) = config_io::load(&filename) else {
|
|
||||||
bail!("Could not read file at {}", &filename);
|
|
||||||
};
|
|
||||||
Ok(serde_yaml::from_str(&yaml_data)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_config_with_conf_d<ConfigData>(
|
pub fn load_config_with_conf_d<ConfigData>(
|
||||||
root_config_filename: &str,
|
root_config_filename: &str,
|
||||||
ctype: config_io::ConfigRoot,
|
ctype: config_io::ConfigRoot,
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ pub enum ConfigRoot {
|
|||||||
const FALLBACK_CONFIG_PATH: &str = "/tmp/wlxoverlay";
|
const FALLBACK_CONFIG_PATH: &str = "/tmp/wlxoverlay";
|
||||||
|
|
||||||
static CONFIG_ROOT_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
static CONFIG_ROOT_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
if let Ok(xdg_dirs) = xdg::BaseDirectories::new() {
|
if let Some(mut dir) = xdg::BaseDirectories::new().get_config_home() {
|
||||||
let mut dir = xdg_dirs.get_config_home();
|
|
||||||
dir.push("wlxoverlay");
|
dir.push("wlxoverlay");
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ use crate::{
|
|||||||
backend::{
|
backend::{
|
||||||
overlay::Positioning,
|
overlay::Positioning,
|
||||||
task::{TaskContainer, TaskType},
|
task::{TaskContainer, TaskType},
|
||||||
wayvr,
|
wayvr::{self, WayVRAction},
|
||||||
},
|
},
|
||||||
config::load_config_with_conf_d,
|
config::load_config_with_conf_d,
|
||||||
config_io,
|
config_io,
|
||||||
gui::modular::button::WayVRAction,
|
|
||||||
overlays::wayvr::{executable_exists_in_path, WayVRData},
|
overlays::wayvr::{executable_exists_in_path, WayVRData},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ use vulkano::{
|
|||||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
|
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
|
||||||
DeviceSize,
|
DeviceSize,
|
||||||
};
|
};
|
||||||
|
use wgui::gfx::cmd::XferCommandBuffer;
|
||||||
use super::WlxUploadsBuffer;
|
|
||||||
|
|
||||||
pub trait WlxCommandBufferDds {
|
pub trait WlxCommandBufferDds {
|
||||||
fn texture2d_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
|
fn upload_image_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
|
||||||
where
|
where
|
||||||
R: Read;
|
R: Read;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WlxCommandBufferDds for WlxUploadsBuffer {
|
impl WlxCommandBufferDds for XferCommandBuffer {
|
||||||
fn texture2d_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
|
fn upload_image_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
|
||||||
where
|
where
|
||||||
R: Read,
|
R: Read,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,142 @@
|
|||||||
use std::{mem::MaybeUninit, sync::Arc};
|
use std::{
|
||||||
|
mem::MaybeUninit,
|
||||||
|
os::fd::{FromRawFd, IntoRawFd},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
device::Device,
|
device::Device,
|
||||||
image::{sys::RawImage, ImageCreateInfo, SubresourceLayout},
|
format::Format,
|
||||||
|
image::{sys::RawImage, Image, ImageCreateInfo, ImageTiling, ImageUsage, SubresourceLayout},
|
||||||
|
memory::{
|
||||||
|
allocator::{MemoryAllocator, MemoryTypeFilter},
|
||||||
|
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, ExternalMemoryHandleTypes,
|
||||||
|
MemoryAllocateInfo, MemoryImportInfo, MemoryPropertyFlags, ResourceMemory,
|
||||||
|
},
|
||||||
sync::Sharing,
|
sync::Sharing,
|
||||||
VulkanError, VulkanObject,
|
VulkanError, VulkanObject,
|
||||||
};
|
};
|
||||||
|
use wgui::gfx::WGfx;
|
||||||
|
use wlx_capture::frame::{
|
||||||
|
DmabufFrame, DrmFormat, FourCC, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ABGR8888,
|
||||||
|
DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR2101010, DRM_FORMAT_XBGR8888, DRM_FORMAT_XRGB8888,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DRM_FORMAT_MOD_INVALID: u64 = 0xff_ffff_ffff_ffff;
|
||||||
|
|
||||||
|
pub trait WGfxDmabuf {
|
||||||
|
fn dmabuf_texture_ex(
|
||||||
|
&self,
|
||||||
|
frame: DmabufFrame,
|
||||||
|
tiling: ImageTiling,
|
||||||
|
layouts: Vec<SubresourceLayout>,
|
||||||
|
modifiers: &[u64],
|
||||||
|
) -> anyhow::Result<Arc<Image>>;
|
||||||
|
|
||||||
|
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WGfxDmabuf for WGfx {
|
||||||
|
fn dmabuf_texture_ex(
|
||||||
|
&self,
|
||||||
|
frame: DmabufFrame,
|
||||||
|
tiling: ImageTiling,
|
||||||
|
layouts: Vec<SubresourceLayout>,
|
||||||
|
modifiers: &[u64],
|
||||||
|
) -> anyhow::Result<Arc<Image>> {
|
||||||
|
let extent = [frame.format.width, frame.format.height, 1];
|
||||||
|
let format = fourcc_to_vk(frame.format.fourcc)?;
|
||||||
|
|
||||||
|
let image = unsafe {
|
||||||
|
create_dmabuf_image(
|
||||||
|
self.device.clone(),
|
||||||
|
ImageCreateInfo {
|
||||||
|
format,
|
||||||
|
extent,
|
||||||
|
usage: ImageUsage::SAMPLED,
|
||||||
|
external_memory_handle_types: ExternalMemoryHandleTypes::DMA_BUF,
|
||||||
|
tiling,
|
||||||
|
drm_format_modifiers: modifiers.to_owned(),
|
||||||
|
drm_format_modifier_plane_layouts: layouts,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let requirements = image.memory_requirements()[0];
|
||||||
|
let memory_type_index = self
|
||||||
|
.memory_allocator
|
||||||
|
.find_memory_type_index(
|
||||||
|
requirements.memory_type_bits,
|
||||||
|
MemoryTypeFilter {
|
||||||
|
required_flags: MemoryPropertyFlags::DEVICE_LOCAL,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("failed to get memory type index"))?;
|
||||||
|
|
||||||
|
debug_assert!(self.device.enabled_extensions().khr_external_memory_fd);
|
||||||
|
debug_assert!(self.device.enabled_extensions().khr_external_memory);
|
||||||
|
debug_assert!(self.device.enabled_extensions().ext_external_memory_dma_buf);
|
||||||
|
|
||||||
|
// only do the 1st
|
||||||
|
unsafe {
|
||||||
|
let Some(fd) = frame.planes[0].fd else {
|
||||||
|
anyhow::bail!("DMA-buf plane has no FD");
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = std::fs::File::from_raw_fd(fd);
|
||||||
|
let new_file = file.try_clone()?;
|
||||||
|
let _ = file.into_raw_fd();
|
||||||
|
|
||||||
|
let memory = DeviceMemory::allocate_unchecked(
|
||||||
|
self.device.clone(),
|
||||||
|
MemoryAllocateInfo {
|
||||||
|
allocation_size: requirements.layout.size(),
|
||||||
|
memory_type_index,
|
||||||
|
dedicated_allocation: Some(DedicatedAllocation::Image(&image)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(MemoryImportInfo::Fd {
|
||||||
|
file: new_file,
|
||||||
|
handle_type: ExternalMemoryHandleType::DmaBuf,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mem_alloc = ResourceMemory::new_dedicated(memory);
|
||||||
|
match image.bind_memory_unchecked([mem_alloc]) {
|
||||||
|
Ok(image) => Ok(Arc::new(image)),
|
||||||
|
Err(e) => {
|
||||||
|
anyhow::bail!("Failed to bind memory to image: {}", e.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
|
||||||
|
let mut modifiers: Vec<u64> = vec![];
|
||||||
|
let mut tiling: ImageTiling = ImageTiling::Optimal;
|
||||||
|
let mut layouts: Vec<SubresourceLayout> = vec![];
|
||||||
|
|
||||||
|
if frame.format.modifier != DRM_FORMAT_MOD_INVALID {
|
||||||
|
(0..frame.num_planes).for_each(|i| {
|
||||||
|
let plane = &frame.planes[i];
|
||||||
|
layouts.push(SubresourceLayout {
|
||||||
|
offset: plane.offset.into(),
|
||||||
|
size: 0,
|
||||||
|
row_pitch: plane.stride as _,
|
||||||
|
array_pitch: None,
|
||||||
|
depth_pitch: None,
|
||||||
|
});
|
||||||
|
modifiers.push(frame.format.modifier);
|
||||||
|
});
|
||||||
|
tiling = ImageTiling::DrmFormatModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dmabuf_texture_ex(frame, tiling, layouts, &modifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
pub(super) unsafe fn create_dmabuf_image(
|
pub(super) unsafe fn create_dmabuf_image(
|
||||||
@@ -170,3 +300,51 @@ pub(super) unsafe fn create_dmabuf_image(
|
|||||||
|
|
||||||
RawImage::from_handle(device, handle, create_info)
|
RawImage::from_handle(device, handle, create_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
|
||||||
|
let possible_formats = [
|
||||||
|
DRM_FORMAT_ABGR8888.into(),
|
||||||
|
DRM_FORMAT_XBGR8888.into(),
|
||||||
|
DRM_FORMAT_ARGB8888.into(),
|
||||||
|
DRM_FORMAT_XRGB8888.into(),
|
||||||
|
DRM_FORMAT_ABGR2101010.into(),
|
||||||
|
DRM_FORMAT_XBGR2101010.into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut final_formats = vec![];
|
||||||
|
|
||||||
|
for &f in &possible_formats {
|
||||||
|
let Ok(vk_fmt) = fourcc_to_vk(f) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(props) = device.physical_device().format_properties(vk_fmt) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut fmt = DrmFormat {
|
||||||
|
fourcc: f,
|
||||||
|
modifiers: props
|
||||||
|
.drm_format_modifier_properties
|
||||||
|
.iter()
|
||||||
|
// important bit: only allow single-plane
|
||||||
|
.filter(|m| m.drm_format_modifier_plane_count == 1)
|
||||||
|
.map(|m| m.drm_format_modifier)
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
fmt.modifiers.push(DRM_FORMAT_MOD_INVALID); // implicit modifiers support
|
||||||
|
final_formats.push(fmt);
|
||||||
|
}
|
||||||
|
log::debug!("Supported DRM formats:");
|
||||||
|
for f in &final_formats {
|
||||||
|
log::debug!(" {} {:?}", f.fourcc, f.modifiers);
|
||||||
|
}
|
||||||
|
final_formats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fourcc_to_vk(fourcc: FourCC) -> anyhow::Result<Format> {
|
||||||
|
match fourcc.value {
|
||||||
|
DRM_FORMAT_ABGR8888 | DRM_FORMAT_XBGR8888 => Ok(Format::R8G8B8A8_UNORM),
|
||||||
|
DRM_FORMAT_ARGB8888 | DRM_FORMAT_XRGB8888 => Ok(Format::B8G8R8A8_UNORM),
|
||||||
|
DRM_FORMAT_ABGR2101010 | DRM_FORMAT_XBGR2101010 => Ok(Format::A2B10G10R10_UNORM_PACK32),
|
||||||
|
_ => anyhow::bail!("Unsupported format {}", fourcc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1245
src/graphics/mod.rs
1245
src/graphics/mod.rs
File diff suppressed because it is too large
Load Diff
12
src/gui/asset.rs
Normal file
12
src/gui/asset.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#[derive(rust_embed::Embed)]
|
||||||
|
#[folder = "src/gui/assets/"]
|
||||||
|
pub struct GuiAsset;
|
||||||
|
|
||||||
|
impl wgui::assets::AssetProvider for GuiAsset {
|
||||||
|
fn load_from_path(&mut self, path: &str) -> anyhow::Result<Vec<u8>> {
|
||||||
|
match GuiAsset::get(path) {
|
||||||
|
Some(data) => Ok(data.data.to_vec()),
|
||||||
|
None => anyhow::bail!("embedded file {} not found", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/gui/assets/test
Normal file
0
src/gui/assets/test
Normal file
@@ -1,267 +0,0 @@
|
|||||||
use glam::Vec4;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use vulkano::format::Format;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
graphics::WlxGraphics,
|
|
||||||
gui::{canvas::control::ControlRenderer, GuiColor, KeyCapType},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{control::Control, Canvas, Rect};
|
|
||||||
|
|
||||||
pub struct CanvasBuilder<D, S> {
|
|
||||||
canvas: Canvas<D, S>,
|
|
||||||
|
|
||||||
pub fg_color: GuiColor,
|
|
||||||
pub bg_color: GuiColor,
|
|
||||||
pub font_size: isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, S> CanvasBuilder<D, S> {
|
|
||||||
pub fn new(
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
format: Format,
|
|
||||||
data: D,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
canvas: Canvas::new(width, height, graphics, format, data)?,
|
|
||||||
bg_color: Vec4::ZERO,
|
|
||||||
fg_color: Vec4::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, radius: f32) -> &mut Control<D, S> {
|
|
||||||
let idx = self.canvas.controls.len();
|
|
||||||
self.canvas.controls.push(Control {
|
|
||||||
rect: Rect { x, y, w, h },
|
|
||||||
corner_radius: radius,
|
|
||||||
bg_color: self.bg_color,
|
|
||||||
on_render_bg: Some(Control::render_rounded_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,
|
|
||||||
radius: 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 },
|
|
||||||
corner_radius: radius,
|
|
||||||
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
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn label_centered(
|
|
||||||
&mut self,
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
w: f32,
|
|
||||||
h: f32,
|
|
||||||
radius: 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 },
|
|
||||||
corner_radius: radius,
|
|
||||||
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 sprite. Will not draw anything until set_sprite is called.
|
|
||||||
pub fn sprite(&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 },
|
|
||||||
corner_radius: 0.,
|
|
||||||
on_render_bg: Some(Control::render_sprite_bg),
|
|
||||||
..Control::new()
|
|
||||||
});
|
|
||||||
&mut self.canvas.controls[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a sprite that highlights on pointer hover. Will not draw anything until set_sprite is called.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn sprite_interactive(&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 },
|
|
||||||
corner_radius: 0.,
|
|
||||||
on_render_bg: Some(Control::render_sprite_bg),
|
|
||||||
on_render_hl: Some(Control::render_sprite_hl),
|
|
||||||
..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,
|
|
||||||
radius: 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 },
|
|
||||||
corner_radius: radius,
|
|
||||||
text,
|
|
||||||
fg_color: self.fg_color,
|
|
||||||
bg_color: self.bg_color,
|
|
||||||
size: self.font_size,
|
|
||||||
on_render_bg: Some(Control::render_rounded_rect),
|
|
||||||
on_render_fg: Some(Control::render_text_centered),
|
|
||||||
on_render_hl: Some(Control::render_highlight),
|
|
||||||
..Control::new()
|
|
||||||
});
|
|
||||||
|
|
||||||
&mut self.canvas.controls[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn key_button(
|
|
||||||
&mut self,
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
w: f32,
|
|
||||||
h: f32,
|
|
||||||
radius: f32,
|
|
||||||
cap_type: KeyCapType,
|
|
||||||
label: &[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 },
|
|
||||||
corner_radius: radius,
|
|
||||||
bg_color: self.bg_color,
|
|
||||||
on_render_bg: Some(Control::render_rounded_rect),
|
|
||||||
on_render_hl: Some(Control::render_highlight),
|
|
||||||
..Control::new()
|
|
||||||
});
|
|
||||||
|
|
||||||
let renders = match cap_type {
|
|
||||||
KeyCapType::Regular => {
|
|
||||||
let render: ControlRenderer<D, S> = Control::render_text_centered;
|
|
||||||
let rect = Rect {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
w,
|
|
||||||
h: h - self.font_size as f32,
|
|
||||||
};
|
|
||||||
vec![(render, rect, 1f32)]
|
|
||||||
}
|
|
||||||
KeyCapType::RegularAltGr => {
|
|
||||||
let render: ControlRenderer<D, S> = Control::render_text;
|
|
||||||
let rect0 = Rect {
|
|
||||||
x: x + 12.,
|
|
||||||
y: y + (self.font_size as f32) + 12.,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
};
|
|
||||||
let rect1 = Rect {
|
|
||||||
x: w.mul_add(0.5, x) + 12.,
|
|
||||||
y: y + h - (self.font_size as f32) + 8.,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
};
|
|
||||||
vec![(render, rect0, 1.0), (render, rect1, 0.8)]
|
|
||||||
}
|
|
||||||
KeyCapType::Reversed => {
|
|
||||||
let render: ControlRenderer<D, S> = Control::render_text_centered;
|
|
||||||
let rect0 = Rect {
|
|
||||||
x,
|
|
||||||
y: y + 2.0,
|
|
||||||
w,
|
|
||||||
h: h * 0.5,
|
|
||||||
};
|
|
||||||
let rect1 = Rect {
|
|
||||||
x,
|
|
||||||
y: h.mul_add(0.5, y) + 2.0,
|
|
||||||
w,
|
|
||||||
h: h * 0.5,
|
|
||||||
};
|
|
||||||
vec![(render, rect1, 1.0), (render, rect0, 0.8)]
|
|
||||||
}
|
|
||||||
KeyCapType::ReversedAltGr => {
|
|
||||||
let render: ControlRenderer<D, S> = Control::render_text;
|
|
||||||
let rect0 = Rect {
|
|
||||||
x: x + 12.,
|
|
||||||
y: y + (self.font_size as f32) + 8.,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
};
|
|
||||||
let rect1 = Rect {
|
|
||||||
x: x + 12.,
|
|
||||||
y: y + h - (self.font_size as f32) + 4.,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
};
|
|
||||||
let rect2 = Rect {
|
|
||||||
x: w.mul_add(0.5, x) + 8.,
|
|
||||||
y: y + h - (self.font_size as f32) + 4.,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
};
|
|
||||||
vec![
|
|
||||||
(render, rect1, 1.0),
|
|
||||||
(render, rect0, 0.8),
|
|
||||||
(render, rect2, 0.8),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (idx, (render, rect, alpha)) in renders.into_iter().enumerate() {
|
|
||||||
if idx >= label.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.canvas.controls.push(Control {
|
|
||||||
rect,
|
|
||||||
text: Arc::from(label[idx].as_str()),
|
|
||||||
fg_color: self.fg_color * alpha,
|
|
||||||
size: self.font_size,
|
|
||||||
on_render_fg: Some(render),
|
|
||||||
..Control::new()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
&mut self.canvas.controls[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
use glam::Vec4;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use vulkano::image::view::ImageView;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
backend::input::PointerMode, graphics::WlxCommandBuffer, gui::GuiColor, state::AppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{CanvasData, Rect};
|
|
||||||
|
|
||||||
pub type ControlRenderer<D, S> =
|
|
||||||
fn(&Control<D, S>, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>;
|
|
||||||
|
|
||||||
pub type ControlRendererHl<D, S> = fn(
|
|
||||||
&Control<D, S>,
|
|
||||||
&CanvasData<D>,
|
|
||||||
&mut AppState,
|
|
||||||
&mut WlxCommandBuffer,
|
|
||||||
Vec4,
|
|
||||||
) -> anyhow::Result<()>;
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub struct Control<D, S> {
|
|
||||||
pub state: Option<S>,
|
|
||||||
pub rect: Rect,
|
|
||||||
pub corner_radius: f32,
|
|
||||||
pub fg_color: GuiColor,
|
|
||||||
pub bg_color: GuiColor,
|
|
||||||
pub text: Arc<str>,
|
|
||||||
pub size: isize,
|
|
||||||
pub sprite: Option<Arc<ImageView>>,
|
|
||||||
pub sprite_st: Vec4,
|
|
||||||
pub(super) bg_dirty: bool,
|
|
||||||
pub(super) fg_dirty: bool,
|
|
||||||
|
|
||||||
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
|
||||||
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>,
|
|
||||||
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
|
|
||||||
pub on_scroll: Option<fn(&mut Self, &mut D, &mut AppState, f32, f32)>,
|
|
||||||
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> Option<Vec4>>,
|
|
||||||
|
|
||||||
pub(super) on_render_bg: Option<ControlRenderer<D, S>>,
|
|
||||||
pub(super) on_render_hl: Option<ControlRendererHl<D, S>>,
|
|
||||||
pub(super) on_render_fg: Option<ControlRenderer<D, S>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, S> Control<D, S> {
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
rect: Rect {
|
|
||||||
x: 0.,
|
|
||||||
y: 0.,
|
|
||||||
w: 0.,
|
|
||||||
h: 0.,
|
|
||||||
},
|
|
||||||
corner_radius: 0.,
|
|
||||||
fg_color: Vec4::ONE,
|
|
||||||
bg_color: Vec4::ZERO,
|
|
||||||
text: Arc::from(""),
|
|
||||||
sprite: None,
|
|
||||||
sprite_st: Vec4::new(1., 1., 0., 0.),
|
|
||||||
bg_dirty: true,
|
|
||||||
fg_dirty: true,
|
|
||||||
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,
|
|
||||||
on_scroll: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: &str) {
|
|
||||||
if *self.text == *text {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.text = text.into();
|
|
||||||
self.fg_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_sprite(&mut self, sprite: Arc<ImageView>) {
|
|
||||||
self.sprite.replace(sprite);
|
|
||||||
self.bg_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_sprite_st(&mut self, sprite_st: Vec4) {
|
|
||||||
if self.sprite_st == sprite_st {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.sprite_st = sprite_st;
|
|
||||||
self.bg_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_fg_color(&mut self, color: GuiColor) {
|
|
||||||
if self.fg_color == color {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.fg_color = color;
|
|
||||||
self.fg_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_rounded_rect(
|
|
||||||
&self,
|
|
||||||
canvas: &CanvasData<D>,
|
|
||||||
_: &mut AppState,
|
|
||||||
cmd_buffer: &mut WlxCommandBuffer,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
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 clamped_radius = self
|
|
||||||
.corner_radius
|
|
||||||
.min(self.rect.w / 2.0)
|
|
||||||
.min(self.rect.h / 2.0);
|
|
||||||
|
|
||||||
let skew_radius = [clamped_radius / self.rect.w, clamped_radius / self.rect.h];
|
|
||||||
|
|
||||||
let set0 = canvas.pipeline_bg_color.uniform_buffer(
|
|
||||||
0,
|
|
||||||
vec![
|
|
||||||
self.bg_color.x,
|
|
||||||
self.bg_color.y,
|
|
||||||
self.bg_color.z,
|
|
||||||
self.bg_color.w,
|
|
||||||
skew_radius[0],
|
|
||||||
skew_radius[1],
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
canvas.pipeline_bg_color.create_pass(
|
|
||||||
[canvas.width as _, canvas.height as _],
|
|
||||||
vertex_buffer,
|
|
||||||
canvas.graphics.quad_indices.clone(),
|
|
||||||
vec![set0],
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
cmd_buffer.run_ref(&pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn render_highlight(
|
|
||||||
&self,
|
|
||||||
canvas: &CanvasData<D>,
|
|
||||||
_: &mut AppState,
|
|
||||||
cmd_buffer: &mut WlxCommandBuffer,
|
|
||||||
color: GuiColor,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
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 clamped_radius = self
|
|
||||||
.corner_radius
|
|
||||||
.min(self.rect.w / 2.0)
|
|
||||||
.min(self.rect.h / 2.0);
|
|
||||||
|
|
||||||
let skew_radius = [clamped_radius / self.rect.w, clamped_radius / self.rect.h];
|
|
||||||
|
|
||||||
let set0 = canvas.pipeline_hl_color.uniform_buffer(
|
|
||||||
0,
|
|
||||||
vec![
|
|
||||||
color.x,
|
|
||||||
color.y,
|
|
||||||
color.z,
|
|
||||||
color.w,
|
|
||||||
skew_radius[0],
|
|
||||||
skew_radius[1],
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pass = canvas.pipeline_hl_color.create_pass(
|
|
||||||
[canvas.width as _, canvas.height as _],
|
|
||||||
vertex_buffer,
|
|
||||||
canvas.graphics.quad_indices.clone(),
|
|
||||||
vec![set0],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
cmd_buffer.run_ref(&pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn render_text(
|
|
||||||
&self,
|
|
||||||
canvas: &CanvasData<D>,
|
|
||||||
app: &mut AppState,
|
|
||||||
cmd_buffer: &mut WlxCommandBuffer,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
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_fg_glyph.uniform_sampler(
|
|
||||||
0,
|
|
||||||
ImageView::new_default(tex)?,
|
|
||||||
app.graphics.texture_filtering,
|
|
||||||
)?;
|
|
||||||
let set1 = canvas
|
|
||||||
.pipeline_fg_glyph
|
|
||||||
.uniform_buffer(1, self.fg_color.to_array().to_vec())?;
|
|
||||||
let pass = canvas.pipeline_fg_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;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn render_text_centered(
|
|
||||||
&self,
|
|
||||||
canvas: &CanvasData<D>,
|
|
||||||
app: &mut AppState,
|
|
||||||
cmd_buffer: &mut WlxCommandBuffer,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (w, h) = app
|
|
||||||
.fc
|
|
||||||
.get_text_size(&self.text, self.size, canvas.graphics.clone())?;
|
|
||||||
|
|
||||||
let mut cur_y =
|
|
||||||
(self.size as f32).mul_add(-0.25, h.mul_add(-0.5, self.rect.y + (self.rect.h)));
|
|
||||||
for line in self.text.lines() {
|
|
||||||
let mut cur_x = w.mul_add(-0.5, self.rect.w.mul_add(0.5, 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_fg_glyph.uniform_sampler(
|
|
||||||
0,
|
|
||||||
ImageView::new_default(tex)?,
|
|
||||||
app.graphics.texture_filtering,
|
|
||||||
)?;
|
|
||||||
let set1 = canvas
|
|
||||||
.pipeline_fg_glyph
|
|
||||||
.uniform_buffer(1, self.fg_color.to_array().to_vec())?;
|
|
||||||
let pass = canvas.pipeline_fg_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;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn render_sprite_bg(
|
|
||||||
&self,
|
|
||||||
canvas: &CanvasData<D>,
|
|
||||||
app: &mut AppState,
|
|
||||||
cmd_buffer: &mut WlxCommandBuffer,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let Some(view) = self.sprite.as_ref() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
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_bg_sprite.uniform_sampler(
|
|
||||||
0,
|
|
||||||
view.clone(),
|
|
||||||
app.graphics.texture_filtering,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let uniform = vec![
|
|
||||||
self.sprite_st.x,
|
|
||||||
self.sprite_st.y,
|
|
||||||
self.sprite_st.z,
|
|
||||||
self.sprite_st.w,
|
|
||||||
self.fg_color.x,
|
|
||||||
self.fg_color.y,
|
|
||||||
self.fg_color.z,
|
|
||||||
self.fg_color.w,
|
|
||||||
];
|
|
||||||
|
|
||||||
let set1 = canvas.pipeline_bg_sprite.uniform_buffer(1, uniform)?;
|
|
||||||
|
|
||||||
let pass = canvas.pipeline_bg_sprite.create_pass(
|
|
||||||
[canvas.width as _, canvas.height as _],
|
|
||||||
vertex_buffer,
|
|
||||||
canvas.graphics.quad_indices.clone(),
|
|
||||||
vec![set0, set1],
|
|
||||||
)?;
|
|
||||||
cmd_buffer.run_ref(&pass)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(super) fn render_sprite_hl(
|
|
||||||
&self,
|
|
||||||
canvas: &CanvasData<D>,
|
|
||||||
app: &mut AppState,
|
|
||||||
cmd_buffer: &mut WlxCommandBuffer,
|
|
||||||
color: GuiColor,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let Some(view) = self.sprite.as_ref() else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
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_hl_sprite.uniform_sampler(
|
|
||||||
0,
|
|
||||||
view.clone(),
|
|
||||||
app.graphics.texture_filtering,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let uniform = vec![
|
|
||||||
self.sprite_st.x,
|
|
||||||
self.sprite_st.y,
|
|
||||||
self.sprite_st.z,
|
|
||||||
self.sprite_st.w,
|
|
||||||
color.x,
|
|
||||||
color.y,
|
|
||||||
color.z,
|
|
||||||
color.w,
|
|
||||||
];
|
|
||||||
|
|
||||||
let set1 = canvas.pipeline_hl_sprite.uniform_buffer(1, uniform)?;
|
|
||||||
|
|
||||||
let pass = canvas.pipeline_hl_sprite.create_pass(
|
|
||||||
[canvas.width as _, canvas.height as _],
|
|
||||||
vertex_buffer,
|
|
||||||
canvas.graphics.quad_indices.clone(),
|
|
||||||
vec![set0, set1],
|
|
||||||
)?;
|
|
||||||
cmd_buffer.run_ref(&pass)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
pub mod builder;
|
|
||||||
pub mod control;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use glam::{Vec2, Vec4};
|
|
||||||
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
backend::{
|
|
||||||
input::{Haptics, InteractionHandler, PointerHit},
|
|
||||||
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
|
|
||||||
},
|
|
||||||
graphics::{CommandBuffers, WlxGraphics, WlxPipeline, BLEND_ALPHA},
|
|
||||||
state::AppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
const RES_DIVIDER: usize = 4;
|
|
||||||
|
|
||||||
pub struct Rect {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
w: f32,
|
|
||||||
h: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CanvasData<D> {
|
|
||||||
pub data: D,
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
|
|
||||||
pipeline_bg_color: Arc<WlxPipeline>,
|
|
||||||
pipeline_bg_sprite: Arc<WlxPipeline>,
|
|
||||||
pipeline_fg_glyph: Arc<WlxPipeline>,
|
|
||||||
pipeline_hl_color: Arc<WlxPipeline>,
|
|
||||||
pipeline_hl_sprite: Arc<WlxPipeline>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Canvas<D, S> {
|
|
||||||
controls: Vec<control::Control<D, S>>,
|
|
||||||
data: CanvasData<D>,
|
|
||||||
|
|
||||||
hover_controls: [Option<usize>; 2],
|
|
||||||
pressed_controls: [Option<usize>; 2],
|
|
||||||
|
|
||||||
interact_map: Vec<Option<u16>>,
|
|
||||||
interact_stride: usize,
|
|
||||||
interact_rows: usize,
|
|
||||||
|
|
||||||
pipeline_final: Arc<WlxPipeline>,
|
|
||||||
|
|
||||||
view_fore: Arc<ImageView>,
|
|
||||||
view_back: Arc<ImageView>,
|
|
||||||
|
|
||||||
format: Format,
|
|
||||||
|
|
||||||
back_dirty: bool,
|
|
||||||
high_dirty: bool,
|
|
||||||
fore_dirty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, S> Canvas<D, S> {
|
|
||||||
fn new(
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
format: Format,
|
|
||||||
data: D,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
let tex_fore = graphics.render_texture(width as _, height as _, format)?;
|
|
||||||
let tex_back = graphics.render_texture(width as _, height as _, format)?;
|
|
||||||
|
|
||||||
let view_fore = ImageView::new_default(tex_fore)?;
|
|
||||||
let view_back = ImageView::new_default(tex_back)?;
|
|
||||||
|
|
||||||
let Ok(shaders) = graphics.shared_shaders.read() else {
|
|
||||||
anyhow::bail!("Failed to lock shared shaders for reading");
|
|
||||||
};
|
|
||||||
|
|
||||||
let vert = shaders.get("vert_common").unwrap().clone(); // want panic
|
|
||||||
|
|
||||||
let pipeline_bg_color = graphics.create_pipeline(
|
|
||||||
vert.clone(),
|
|
||||||
shaders.get("frag_color").unwrap().clone(), // want panic
|
|
||||||
format,
|
|
||||||
Some(BLEND_ALPHA),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pipeline_fg_glyph = graphics.create_pipeline(
|
|
||||||
vert.clone(),
|
|
||||||
shaders.get("frag_glyph").unwrap().clone(), // want panic
|
|
||||||
format,
|
|
||||||
Some(BLEND_ALPHA),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pipeline_bg_sprite = graphics.create_pipeline(
|
|
||||||
vert.clone(),
|
|
||||||
shaders.get("frag_sprite2").unwrap().clone(), // want panic
|
|
||||||
format,
|
|
||||||
Some(BLEND_ALPHA),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pipeline_hl_color = graphics.create_pipeline(
|
|
||||||
vert.clone(),
|
|
||||||
shaders.get("frag_color").unwrap().clone(), // want panic
|
|
||||||
graphics.native_format,
|
|
||||||
Some(BLEND_ALPHA),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pipeline_hl_sprite = graphics.create_pipeline(
|
|
||||||
vert.clone(),
|
|
||||||
shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic
|
|
||||||
graphics.native_format,
|
|
||||||
Some(BLEND_ALPHA),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pipeline_final = graphics.create_pipeline(
|
|
||||||
vert,
|
|
||||||
shaders.get("frag_srgb").unwrap().clone(), // want panic
|
|
||||||
graphics.native_format,
|
|
||||||
Some(BLEND_ALPHA),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let stride = width / RES_DIVIDER;
|
|
||||||
let rows = height / RES_DIVIDER;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
data: CanvasData {
|
|
||||||
data,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
graphics: graphics.clone(),
|
|
||||||
pipeline_bg_color,
|
|
||||||
pipeline_bg_sprite,
|
|
||||||
pipeline_fg_glyph,
|
|
||||||
pipeline_hl_color,
|
|
||||||
pipeline_hl_sprite,
|
|
||||||
},
|
|
||||||
controls: Vec::new(),
|
|
||||||
hover_controls: [None, None],
|
|
||||||
pressed_controls: [None, None],
|
|
||||||
interact_map: vec![None; stride * rows],
|
|
||||||
interact_stride: stride,
|
|
||||||
interact_rows: rows,
|
|
||||||
pipeline_final,
|
|
||||||
view_fore,
|
|
||||||
view_back,
|
|
||||||
format,
|
|
||||||
back_dirty: false,
|
|
||||||
high_dirty: false,
|
|
||||||
fore_dirty: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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 u16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interactive_get_idx(&self, uv: Vec2) -> Option<usize> {
|
|
||||||
let x = (uv.x * self.data.width as f32) as usize;
|
|
||||||
let y = (uv.y * self.data.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn data_mut(&mut self) -> &mut D {
|
|
||||||
&mut self.data.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, S> InteractionHandler for Canvas<D, S> {
|
|
||||||
fn on_left(&mut self, _app: &mut AppState, pointer: usize) {
|
|
||||||
self.high_dirty = true;
|
|
||||||
|
|
||||||
self.hover_controls[pointer] = None;
|
|
||||||
}
|
|
||||||
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
|
|
||||||
// render on every frame if we are being hovered
|
|
||||||
self.high_dirty = true;
|
|
||||||
|
|
||||||
let old = self.hover_controls[hit.pointer];
|
|
||||||
if let Some(i) = self.interactive_get_idx(hit.uv) {
|
|
||||||
self.hover_controls[hit.pointer] = Some(i);
|
|
||||||
} else {
|
|
||||||
self.hover_controls[hit.pointer] = None;
|
|
||||||
}
|
|
||||||
if old == self.hover_controls[hit.pointer] {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Haptics {
|
|
||||||
intensity: 0.1,
|
|
||||||
duration: 0.01,
|
|
||||||
frequency: 5.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.pointer]
|
|
||||||
};
|
|
||||||
|
|
||||||
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.pointer] = Some(idx);
|
|
||||||
f(c, &mut self.data.data, app, hit.mode);
|
|
||||||
}
|
|
||||||
} else if let Some(ref mut f) = c.on_release {
|
|
||||||
self.pressed_controls[hit.pointer] = None;
|
|
||||||
f(c, &mut self.data.data, app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
|
|
||||||
let idx = self.hover_controls[hit.pointer];
|
|
||||||
|
|
||||||
if let Some(idx) = idx {
|
|
||||||
let c = &mut self.controls[idx];
|
|
||||||
if let Some(ref mut f) = c.on_scroll {
|
|
||||||
f(c, &mut self.data.data, app, delta_y, delta_x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, S> OverlayRenderer for Canvas<D, S> {
|
|
||||||
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
|
||||||
for c in &mut self.controls {
|
|
||||||
if let Some(fun) = c.on_update {
|
|
||||||
fun(c, &mut self.data.data, app);
|
|
||||||
}
|
|
||||||
if c.fg_dirty {
|
|
||||||
self.fore_dirty = true;
|
|
||||||
c.fg_dirty = false;
|
|
||||||
}
|
|
||||||
if c.bg_dirty {
|
|
||||||
self.back_dirty = true;
|
|
||||||
c.bg_dirty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.back_dirty || self.fore_dirty || self.high_dirty {
|
|
||||||
Ok(ShouldRender::Should)
|
|
||||||
} else {
|
|
||||||
Ok(ShouldRender::Can)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
app: &mut AppState,
|
|
||||||
tgt: Arc<ImageView>,
|
|
||||||
buf: &mut CommandBuffers,
|
|
||||||
alpha: f32,
|
|
||||||
) -> anyhow::Result<bool> {
|
|
||||||
self.high_dirty = false;
|
|
||||||
|
|
||||||
let mut cmd_buffer = self
|
|
||||||
.data
|
|
||||||
.graphics
|
|
||||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
|
||||||
|
|
||||||
if self.back_dirty {
|
|
||||||
cmd_buffer.begin_rendering(self.view_back.clone())?;
|
|
||||||
for c in &mut self.controls {
|
|
||||||
if let Some(fun) = c.on_render_bg {
|
|
||||||
fun(c, &self.data, app, &mut cmd_buffer)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd_buffer.end_rendering()?;
|
|
||||||
self.back_dirty = false;
|
|
||||||
}
|
|
||||||
if self.fore_dirty {
|
|
||||||
cmd_buffer.begin_rendering(self.view_fore.clone())?;
|
|
||||||
for c in &mut self.controls {
|
|
||||||
if let Some(fun) = c.on_render_fg {
|
|
||||||
fun(c, &self.data, app, &mut cmd_buffer)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd_buffer.end_rendering()?;
|
|
||||||
self.fore_dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let set0_fg = self.pipeline_final.uniform_sampler(
|
|
||||||
0,
|
|
||||||
self.view_fore.clone(),
|
|
||||||
app.graphics.texture_filtering,
|
|
||||||
)?;
|
|
||||||
let set0_bg = self.pipeline_final.uniform_sampler(
|
|
||||||
0,
|
|
||||||
self.view_back.clone(),
|
|
||||||
app.graphics.texture_filtering,
|
|
||||||
)?;
|
|
||||||
let set1 = self.pipeline_final.uniform_buffer(1, vec![alpha])?;
|
|
||||||
|
|
||||||
let pass_fore = self
|
|
||||||
.pipeline_final
|
|
||||||
.create_pass_for_target(tgt.clone(), vec![set0_fg, set1.clone()])?;
|
|
||||||
|
|
||||||
let pass_back = self
|
|
||||||
.pipeline_final
|
|
||||||
.create_pass_for_target(tgt.clone(), vec![set0_bg, set1])?;
|
|
||||||
|
|
||||||
cmd_buffer.begin_rendering(tgt)?;
|
|
||||||
cmd_buffer.run_ref(&pass_back)?;
|
|
||||||
|
|
||||||
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 let Some(hl_color) = test(c, &mut self.data.data, app) {
|
|
||||||
render(c, &self.data, app, &mut cmd_buffer, hl_color)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.hover_controls.contains(&Some(i)) {
|
|
||||||
render(
|
|
||||||
c,
|
|
||||||
&self.data,
|
|
||||||
app,
|
|
||||||
&mut cmd_buffer,
|
|
||||||
Vec4::new(1., 1., 1., 0.3),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mostly static text
|
|
||||||
cmd_buffer.run_ref(&pass_fore)?;
|
|
||||||
|
|
||||||
cmd_buffer.end_rendering()?;
|
|
||||||
buf.push(cmd_buffer.build()?);
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
|
||||||
Some(FrameMeta {
|
|
||||||
extent: [self.data.width as _, self.data.height as _, 1],
|
|
||||||
format: self.format,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, S> OverlayBackend for Canvas<D, S> {
|
|
||||||
fn set_renderer(&mut self, _renderer: Box<dyn OverlayRenderer>) {}
|
|
||||||
fn set_interaction(&mut self, _interaction: Box<dyn InteractionHandler>) {}
|
|
||||||
}
|
|
||||||
268
src/gui/font.rs
268
src/gui/font.rs
@@ -1,268 +0,0 @@
|
|||||||
use std::{rc::Rc, str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
use fontconfig::{FontConfig, OwnedPattern};
|
|
||||||
use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library};
|
|
||||||
use idmap::IdMap;
|
|
||||||
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::Image};
|
|
||||||
|
|
||||||
use crate::graphics::{WlxGraphics, WlxUploadsBuffer};
|
|
||||||
|
|
||||||
pub struct FontCache {
|
|
||||||
primary_font: Arc<str>,
|
|
||||||
fc: FontConfig,
|
|
||||||
ft: Library,
|
|
||||||
collections: IdMap<isize, FontCollection>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FontCollection {
|
|
||||||
fonts: Vec<Font>,
|
|
||||||
cp_map: IdMap<usize, usize>,
|
|
||||||
zero_glyph: Rc<Glyph>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Font {
|
|
||||||
face: Face,
|
|
||||||
glyphs: IdMap<usize, Rc<Glyph>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Glyph {
|
|
||||||
pub tex: Option<Arc<Image>>,
|
|
||||||
pub top: f32,
|
|
||||||
pub left: f32,
|
|
||||||
pub width: f32,
|
|
||||||
pub height: f32,
|
|
||||||
pub advance: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FontCache {
|
|
||||||
pub fn new(primary_font: Arc<str>) -> anyhow::Result<Self> {
|
|
||||||
let ft = Library::init()?;
|
|
||||||
let fc = FontConfig::default();
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
primary_font,
|
|
||||||
fc,
|
|
||||||
ft,
|
|
||||||
collections: IdMap::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_text_size(
|
|
||||||
&mut self,
|
|
||||||
text: &str,
|
|
||||||
size: isize,
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
) -> anyhow::Result<(f32, f32)> {
|
|
||||||
let sizef = size as f32;
|
|
||||||
|
|
||||||
let height = ((text.lines().count() as f32) - 1f32).mul_add(sizef * 1.5, sizef);
|
|
||||||
let mut cmd_buffer = None;
|
|
||||||
|
|
||||||
let mut max_w = sizef * 0.33;
|
|
||||||
for line in text.lines() {
|
|
||||||
let w: f32 = line
|
|
||||||
.chars()
|
|
||||||
.filter_map(|c| {
|
|
||||||
self.get_glyph_for_cp(c as usize, size, graphics.clone(), &mut cmd_buffer)
|
|
||||||
.map(|glyph| glyph.advance)
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
if w > max_w {
|
|
||||||
max_w = w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cmd_buffer) = cmd_buffer {
|
|
||||||
cmd_buffer.build_and_execute_now()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((max_w, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_glyphs(
|
|
||||||
&mut self,
|
|
||||||
text: &str,
|
|
||||||
size: isize,
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
) -> anyhow::Result<Vec<Rc<Glyph>>> {
|
|
||||||
let mut glyphs = Vec::new();
|
|
||||||
let mut cmd_buffer = None;
|
|
||||||
|
|
||||||
for line in text.lines() {
|
|
||||||
for c in line.chars() {
|
|
||||||
glyphs.push(self.get_glyph_for_cp(
|
|
||||||
c as usize,
|
|
||||||
size,
|
|
||||||
graphics.clone(),
|
|
||||||
&mut cmd_buffer,
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cmd_buffer) = cmd_buffer {
|
|
||||||
cmd_buffer.build_and_execute_now()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(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(),
|
|
||||||
zero_glyph: Rc::new(Glyph {
|
|
||||||
tex: None,
|
|
||||||
top: 0.,
|
|
||||||
left: 0.,
|
|
||||||
width: 0.,
|
|
||||||
height: 0.,
|
|
||||||
advance: size as f32 / 3.,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let coll = self.collections.get_mut(size).unwrap(); // safe because of the insert above
|
|
||||||
|
|
||||||
if let Some(font) = coll.cp_map.get(cp) {
|
|
||||||
return *font;
|
|
||||||
}
|
|
||||||
|
|
||||||
let primary_font = self.primary_font.clone();
|
|
||||||
let pattern_str = format!("{primary_font}:size={size}:charset={cp:04x}");
|
|
||||||
let mut pattern = OwnedPattern::from_str(&pattern_str).unwrap(); // safe because PRIMARY_FONT is const
|
|
||||||
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() {
|
|
||||||
let name = pattern.name().unwrap_or(path);
|
|
||||||
log::debug!("Loading font: {name} {size}pt");
|
|
||||||
|
|
||||||
let font_idx = pattern.face_index().unwrap_or(0);
|
|
||||||
|
|
||||||
let face = match self.ft.new_face(path, font_idx as _) {
|
|
||||||
Ok(face) => face,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Failed to load font at {path}: {e:?}");
|
|
||||||
coll.cp_map.insert(cp, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match face.set_char_size(size << 6, size << 6, 96, 96) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Failed to set font size: {e:?}");
|
|
||||||
coll.cp_map.insert(cp, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = coll.fonts.len();
|
|
||||||
for (cp, _) in face.chars() {
|
|
||||||
if coll.cp_map.contains_key(cp) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
coll.cp_map.insert(cp, idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !coll.cp_map.contains_key(cp) {
|
|
||||||
log::warn!("Got font '{name}' for CP 0x{cp:x}, but CP is not present in font!",);
|
|
||||||
coll.cp_map.insert(cp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, glyphs };
|
|
||||||
coll.fonts.push(font);
|
|
||||||
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
coll.cp_map.insert(cp, 0);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_glyph_for_cp(
|
|
||||||
&mut self,
|
|
||||||
cp: usize,
|
|
||||||
size: isize,
|
|
||||||
graphics: Arc<WlxGraphics>,
|
|
||||||
cmd_buffer: &mut Option<WlxUploadsBuffer>,
|
|
||||||
) -> anyhow::Result<Rc<Glyph>> {
|
|
||||||
let key = self.get_font_for_cp(cp, size);
|
|
||||||
|
|
||||||
let Some(font) = &mut self.collections[size].fonts.get_mut(key) else {
|
|
||||||
log::warn!("No font found for codepoint: 0x{cp:x}");
|
|
||||||
return Ok(self.collections[size].zero_glyph.clone());
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(glyph) = font.glyphs.get(cp) {
|
|
||||||
return Ok(glyph.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if font.face.load_char(cp, LoadFlag::DEFAULT).is_err() {
|
|
||||||
return Ok(self.collections[size].zero_glyph.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let glyph = font.face.glyph();
|
|
||||||
if glyph.render_glyph(freetype::RenderMode::Normal).is_err() {
|
|
||||||
return Ok(self.collections[size].zero_glyph.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let bmp = glyph.bitmap();
|
|
||||||
let buf = bmp.buffer().to_vec();
|
|
||||||
if buf.is_empty() {
|
|
||||||
return Ok(self.collections[size].zero_glyph.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 Ok(self.collections[size].zero_glyph.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if cmd_buffer.is_none() {
|
|
||||||
*cmd_buffer = Some(graphics.create_uploads_command_buffer(
|
|
||||||
graphics.transfer_queue.clone(),
|
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let texture = cmd_buffer.as_mut().unwrap().texture2d_raw(
|
|
||||||
bmp.width() as _,
|
|
||||||
bmp.rows() as _,
|
|
||||||
format,
|
|
||||||
&buf,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
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));
|
|
||||||
Ok(font.glyphs[cp].clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +1,3 @@
|
|||||||
use std::sync::LazyLock;
|
mod asset;
|
||||||
|
pub mod panel;
|
||||||
use glam::Vec4;
|
mod timestep;
|
||||||
|
|
||||||
pub mod canvas;
|
|
||||||
pub mod font;
|
|
||||||
pub mod modular;
|
|
||||||
|
|
||||||
pub type GuiColor = Vec4;
|
|
||||||
pub static FALLBACK_COLOR: LazyLock<GuiColor> = LazyLock::new(|| Vec4::new(1., 0., 1., 1.));
|
|
||||||
|
|
||||||
// Parses a color from a HTML hex string
|
|
||||||
pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> {
|
|
||||||
if html_hex.len() == 7 {
|
|
||||||
if let (Ok(r), Ok(g), Ok(b)) = (
|
|
||||||
u8::from_str_radix(&html_hex[1..3], 16),
|
|
||||||
u8::from_str_radix(&html_hex[3..5], 16),
|
|
||||||
u8::from_str_radix(&html_hex[5..7], 16),
|
|
||||||
) {
|
|
||||||
return Ok(Vec4::new(
|
|
||||||
f32::from(r) / 255.,
|
|
||||||
f32::from(g) / 255.,
|
|
||||||
f32::from(b) / 255.,
|
|
||||||
1.,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if html_hex.len() == 9 {
|
|
||||||
if let (Ok(r), Ok(g), Ok(b), Ok(a)) = (
|
|
||||||
u8::from_str_radix(&html_hex[1..3], 16),
|
|
||||||
u8::from_str_radix(&html_hex[3..5], 16),
|
|
||||||
u8::from_str_radix(&html_hex[5..7], 16),
|
|
||||||
u8::from_str_radix(&html_hex[7..9], 16),
|
|
||||||
) {
|
|
||||||
return Ok(Vec4::new(
|
|
||||||
f32::from(r) / 255.,
|
|
||||||
f32::from(g) / 255.,
|
|
||||||
f32::from(b) / 255.,
|
|
||||||
f32::from(a) / 255.,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anyhow::bail!(
|
|
||||||
"Invalid color string: '{}', must be 7 characters long (e.g. #FF00FF)",
|
|
||||||
&html_hex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum KeyCapType {
|
|
||||||
/// Label is in center of keycap
|
|
||||||
Regular,
|
|
||||||
/// Label on the top
|
|
||||||
/// AltGr symbol on bottom
|
|
||||||
RegularAltGr,
|
|
||||||
/// Primary symbol on bottom
|
|
||||||
/// Shift symbol on top
|
|
||||||
Reversed,
|
|
||||||
/// Primary symbol on bottom-left
|
|
||||||
/// Shift symbol on top-left
|
|
||||||
/// AltGr symbol on bottom-right
|
|
||||||
ReversedAltGr,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,867 +0,0 @@
|
|||||||
use std::{
|
|
||||||
f32::consts::PI,
|
|
||||||
ops::Add,
|
|
||||||
process::{self, Child},
|
|
||||||
sync::Arc,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use glam::{Quat, Vec4};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
backend::{
|
|
||||||
common::OverlaySelector,
|
|
||||||
input::PointerMode,
|
|
||||||
overlay::Positioning,
|
|
||||||
task::{ColorChannel, SystemTask, TaskType},
|
|
||||||
},
|
|
||||||
config::{save_layout, save_settings, AStrSetExt},
|
|
||||||
hid::VirtualKey,
|
|
||||||
overlays::{
|
|
||||||
toast::{error_toast, Toast, ToastTopic},
|
|
||||||
watch::WATCH_NAME,
|
|
||||||
},
|
|
||||||
state::AppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(any(not(feature = "wayvr"), not(feature = "osc")))]
|
|
||||||
use crate::overlays::toast::error_toast_str;
|
|
||||||
|
|
||||||
#[cfg(feature = "osc")]
|
|
||||||
use rosc::OscType;
|
|
||||||
|
|
||||||
use super::{ExecArgs, ModularControl, ModularData};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum PressRelease {
|
|
||||||
Release,
|
|
||||||
Press,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Copy)]
|
|
||||||
pub enum ViewAngleKind {
|
|
||||||
/// The cosine of the angle at which the watch becomes fully transparent
|
|
||||||
MinOpacity,
|
|
||||||
/// The cosine of the angle at which the watch becomes fully opaque
|
|
||||||
MaxOpacity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Copy)]
|
|
||||||
pub enum Axis {
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
Z,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum HighlightTest {
|
|
||||||
AllowSliding,
|
|
||||||
AutoRealign,
|
|
||||||
NotificationSounds,
|
|
||||||
Notifications,
|
|
||||||
RorateLock,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum SystemAction {
|
|
||||||
ToggleAllowSliding,
|
|
||||||
ToggleAutoRealign,
|
|
||||||
ToggleNotificationSounds,
|
|
||||||
ToggleNotifications,
|
|
||||||
ToggleRotateLock,
|
|
||||||
PlayspaceResetOffset,
|
|
||||||
PlayspaceFixFloor,
|
|
||||||
RecalculateExtent,
|
|
||||||
PersistConfig,
|
|
||||||
PersistLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum WatchAction {
|
|
||||||
/// Hide the watch until Show/Hide binding is used
|
|
||||||
Hide,
|
|
||||||
/// Switch the watch to the opposite controller
|
|
||||||
SwitchHands,
|
|
||||||
/// Change the fade behavior of the watch
|
|
||||||
ViewAngle {
|
|
||||||
kind: ViewAngleKind,
|
|
||||||
delta: f32,
|
|
||||||
},
|
|
||||||
Rotation {
|
|
||||||
axis: Axis,
|
|
||||||
delta: f32,
|
|
||||||
},
|
|
||||||
Position {
|
|
||||||
axis: Axis,
|
|
||||||
delta: f32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum OverlayAction {
|
|
||||||
/// Reset the overlay to be in front of the HMD with its original scale
|
|
||||||
Reset,
|
|
||||||
/// Toggle the visibility of the overlay
|
|
||||||
ToggleVisible,
|
|
||||||
/// Toggle the ability to grab and recenter the overlay
|
|
||||||
ToggleImmovable,
|
|
||||||
/// Toggle the ability of the overlay to reacto to laser pointer
|
|
||||||
ToggleInteraction,
|
|
||||||
/// Change the opacity of the overlay
|
|
||||||
Opacity { delta: f32 },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum WindowAction {
|
|
||||||
/// Create a new mirror window, or show/hide an existing one
|
|
||||||
ShowMirror,
|
|
||||||
/// Create a new UI window, or show/hide an existing one
|
|
||||||
ShowUi,
|
|
||||||
/// Destroy a previously created window, if it exists
|
|
||||||
Destroy,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum WayVRDisplayClickAction {
|
|
||||||
ToggleVisibility,
|
|
||||||
Reset,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
#[allow(dead_code)] // in case if WayVR feature is disabled
|
|
||||||
pub enum WayVRAction {
|
|
||||||
AppClick {
|
|
||||||
catalog_name: Arc<str>,
|
|
||||||
app_name: Arc<str>,
|
|
||||||
},
|
|
||||||
DisplayClick {
|
|
||||||
display_name: Arc<str>,
|
|
||||||
action: WayVRDisplayClickAction,
|
|
||||||
},
|
|
||||||
ToggleDashboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum ButtonAction {
|
|
||||||
Exec {
|
|
||||||
command: ExecArgs,
|
|
||||||
toast: Option<Arc<str>>,
|
|
||||||
},
|
|
||||||
VirtualKey {
|
|
||||||
keycode: VirtualKey,
|
|
||||||
action: PressRelease,
|
|
||||||
},
|
|
||||||
Watch {
|
|
||||||
action: WatchAction,
|
|
||||||
},
|
|
||||||
Overlay {
|
|
||||||
target: OverlaySelector,
|
|
||||||
action: OverlayAction,
|
|
||||||
},
|
|
||||||
// Ignored if "wayvr" feature is not enabled
|
|
||||||
WayVR {
|
|
||||||
action: WayVRAction,
|
|
||||||
},
|
|
||||||
Window {
|
|
||||||
target: Arc<str>,
|
|
||||||
action: WindowAction,
|
|
||||||
},
|
|
||||||
Toast {
|
|
||||||
message: Arc<str>,
|
|
||||||
body: Option<Arc<str>>,
|
|
||||||
seconds: Option<f32>,
|
|
||||||
},
|
|
||||||
ColorAdjust {
|
|
||||||
channel: ColorChannel,
|
|
||||||
delta: f32,
|
|
||||||
},
|
|
||||||
DragMultiplier {
|
|
||||||
delta: f32,
|
|
||||||
},
|
|
||||||
System {
|
|
||||||
action: SystemAction,
|
|
||||||
},
|
|
||||||
SendOscValue {
|
|
||||||
parameter: Arc<str>,
|
|
||||||
values: Option<Vec<OscValue>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
#[cfg(feature = "osc")]
|
|
||||||
pub enum OscValue {
|
|
||||||
Int { value: i32 },
|
|
||||||
Float { value: f32 },
|
|
||||||
String { value: String },
|
|
||||||
Bool { value: bool },
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
#[cfg(not(feature = "osc"))]
|
|
||||||
pub enum OscValue {
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct PressData {
|
|
||||||
last_down: Instant,
|
|
||||||
last_mode: PointerMode,
|
|
||||||
child: Option<Child>,
|
|
||||||
}
|
|
||||||
impl Clone for PressData {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
last_down: self.last_down,
|
|
||||||
last_mode: self.last_mode,
|
|
||||||
child: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Default for PressData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
last_down: Instant::now(),
|
|
||||||
last_mode: PointerMode::Left,
|
|
||||||
child: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Clone)]
|
|
||||||
pub struct ButtonData {
|
|
||||||
#[serde(skip)]
|
|
||||||
pub(super) press: PressData,
|
|
||||||
|
|
||||||
pub(super) click_down: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) click_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) long_click_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) right_down: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) right_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) long_right_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) middle_down: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) middle_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) long_middle_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) scroll_down: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) scroll_up: Option<Vec<ButtonAction>>,
|
|
||||||
pub(super) highlight: Option<HighlightTest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modular_button_init(button: &mut ModularControl, data: &ButtonData) {
|
|
||||||
button.state = Some(ModularData::Button(Box::new(data.clone())));
|
|
||||||
button.on_press = Some(modular_button_dn);
|
|
||||||
button.on_release = Some(modular_button_up);
|
|
||||||
button.on_scroll = Some(modular_button_scroll);
|
|
||||||
button.test_highlight = Some(modular_button_highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modular_button_dn(
|
|
||||||
button: &mut ModularControl,
|
|
||||||
_: &mut (),
|
|
||||||
app: &mut AppState,
|
|
||||||
mode: PointerMode,
|
|
||||||
) {
|
|
||||||
// want panic
|
|
||||||
let ModularData::Button(data) = button.state.as_mut().unwrap() else {
|
|
||||||
panic!("modular_button_dn: button state is not Button");
|
|
||||||
};
|
|
||||||
|
|
||||||
data.press.last_down = Instant::now();
|
|
||||||
data.press.last_mode = mode;
|
|
||||||
|
|
||||||
let actions = match mode {
|
|
||||||
PointerMode::Left => data.click_down.as_ref(),
|
|
||||||
PointerMode::Right => data.right_down.as_ref(),
|
|
||||||
PointerMode::Middle => data.middle_down.as_ref(),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(actions) = actions {
|
|
||||||
for action in actions {
|
|
||||||
handle_action(action, &mut data.press, app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modular_button_up(button: &mut ModularControl, _: &mut (), app: &mut AppState) {
|
|
||||||
// want panic
|
|
||||||
let ModularData::Button(data) = button.state.as_mut().unwrap() else {
|
|
||||||
panic!("modular_button_up: button state is not Button");
|
|
||||||
};
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
let duration = now - data.press.last_down;
|
|
||||||
let long_press = duration.as_secs_f32() > app.session.config.long_press_duration;
|
|
||||||
|
|
||||||
let actions = match data.press.last_mode {
|
|
||||||
PointerMode::Left => {
|
|
||||||
if long_press {
|
|
||||||
data.long_click_up.as_ref()
|
|
||||||
} else {
|
|
||||||
data.click_up.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PointerMode::Right => {
|
|
||||||
if long_press {
|
|
||||||
data.long_right_up.as_ref()
|
|
||||||
} else {
|
|
||||||
data.right_up.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PointerMode::Middle => {
|
|
||||||
if long_press {
|
|
||||||
data.long_middle_up.as_ref()
|
|
||||||
} else {
|
|
||||||
data.middle_up.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(actions) = actions {
|
|
||||||
for action in actions {
|
|
||||||
handle_action(action, &mut data.press, app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modular_button_scroll(
|
|
||||||
button: &mut ModularControl,
|
|
||||||
_: &mut (),
|
|
||||||
app: &mut AppState,
|
|
||||||
delta_y: f32,
|
|
||||||
_delta_x: f32,
|
|
||||||
) {
|
|
||||||
// want panic
|
|
||||||
let ModularData::Button(data) = button.state.as_mut().unwrap() else {
|
|
||||||
panic!("modular_button_scroll: button state is not Button");
|
|
||||||
};
|
|
||||||
|
|
||||||
let actions = if delta_y < 0.0 {
|
|
||||||
data.scroll_down.as_ref()
|
|
||||||
} else {
|
|
||||||
data.scroll_up.as_ref()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(actions) = actions {
|
|
||||||
for action in actions {
|
|
||||||
handle_action(action, &mut data.press, app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modular_button_highlight(
|
|
||||||
button: &ModularControl,
|
|
||||||
_: &mut (),
|
|
||||||
app: &mut AppState,
|
|
||||||
) -> Option<Vec4> {
|
|
||||||
// want panic
|
|
||||||
let ModularData::Button(data) = button.state.as_ref().unwrap() else {
|
|
||||||
panic!("modular_button_highlight: button state is not Button");
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(test) = &data.highlight {
|
|
||||||
let lit = match test {
|
|
||||||
HighlightTest::AllowSliding => app.session.config.allow_sliding,
|
|
||||||
HighlightTest::AutoRealign => app.session.config.realign_on_showhide,
|
|
||||||
HighlightTest::NotificationSounds => app.session.config.notifications_sound_enabled,
|
|
||||||
HighlightTest::Notifications => app.session.config.notifications_enabled,
|
|
||||||
HighlightTest::RorateLock => !app.session.config.space_rotate_unlocked,
|
|
||||||
};
|
|
||||||
|
|
||||||
if lit {
|
|
||||||
return Some(Vec4::new(1.0, 1.0, 1.0, 0.5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppState) {
|
|
||||||
match action {
|
|
||||||
ButtonAction::Exec { command, toast } => run_exec(command, toast.clone(), press, app),
|
|
||||||
ButtonAction::Watch { action } => run_watch(action, app),
|
|
||||||
ButtonAction::Overlay { target, action } => run_overlay(target, action, app),
|
|
||||||
ButtonAction::Window { target, action } => run_window(target, action, app),
|
|
||||||
ButtonAction::WayVR { action } => {
|
|
||||||
#[cfg(feature = "wayvr")]
|
|
||||||
{
|
|
||||||
app.tasks.enqueue(TaskType::WayVR(action.clone()));
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wayvr"))]
|
|
||||||
{
|
|
||||||
let _ = &action;
|
|
||||||
error_toast_str(app, "WayVR feature is not enabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ButtonAction::VirtualKey { keycode, action } => app
|
|
||||||
.hid_provider
|
|
||||||
.send_key(*keycode, matches!(*action, PressRelease::Press)),
|
|
||||||
ButtonAction::Toast {
|
|
||||||
message,
|
|
||||||
body,
|
|
||||||
seconds,
|
|
||||||
} => {
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
message.clone(),
|
|
||||||
body.clone().unwrap_or_else(|| "".into()),
|
|
||||||
)
|
|
||||||
.with_timeout(seconds.unwrap_or(5.))
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
ButtonAction::ColorAdjust { channel, delta } => {
|
|
||||||
let channel = *channel;
|
|
||||||
let delta = *delta;
|
|
||||||
app.tasks
|
|
||||||
.enqueue(TaskType::System(SystemTask::ColorGain(channel, delta)));
|
|
||||||
}
|
|
||||||
ButtonAction::System { action } => run_system(action, app),
|
|
||||||
ButtonAction::DragMultiplier { delta } => {
|
|
||||||
app.session.config.space_drag_multiplier += delta;
|
|
||||||
}
|
|
||||||
ButtonAction::SendOscValue { parameter, values } => {
|
|
||||||
#[cfg(feature = "osc")]
|
|
||||||
if let Some(ref mut sender) = app.osc_sender {
|
|
||||||
// convert OscValue to OscType
|
|
||||||
let mut converted: Vec<OscType> = Vec::new();
|
|
||||||
|
|
||||||
for value in values.as_ref().unwrap() {
|
|
||||||
let converted_value = match value {
|
|
||||||
OscValue::Bool { value } => OscType::Bool(*value),
|
|
||||||
OscValue::Int { value } => OscType::Int(*value),
|
|
||||||
OscValue::Float { value } => OscType::Float(*value),
|
|
||||||
OscValue::String { value } => OscType::String(value.to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
converted.push(converted_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = sender.send_single_param(parameter.to_string(), converted);
|
|
||||||
audio_thump(app); // play sound for feedback
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "osc"))]
|
|
||||||
{
|
|
||||||
let _ = ¶meter;
|
|
||||||
let _ = &values;
|
|
||||||
error_toast_str(app, "OSC feature is not enabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ENABLED_DISABLED: [&str; 2] = ["enabled", "disabled"];
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
fn run_system(action: &SystemAction, app: &mut AppState) {
|
|
||||||
match action {
|
|
||||||
SystemAction::ToggleAllowSliding => {
|
|
||||||
app.session.config.allow_sliding = !app.session.config.allow_sliding;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!(
|
|
||||||
"Sliding is {}.",
|
|
||||||
ENABLED_DISABLED[usize::from(app.session.config.allow_sliding)]
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
SystemAction::ToggleAutoRealign => {
|
|
||||||
app.session.config.realign_on_showhide = !app.session.config.realign_on_showhide;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!(
|
|
||||||
"Auto realign is {}.",
|
|
||||||
ENABLED_DISABLED[usize::from(app.session.config.realign_on_showhide)]
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
SystemAction::ToggleRotateLock => {
|
|
||||||
app.session.config.space_rotate_unlocked = !app.session.config.space_rotate_unlocked;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!(
|
|
||||||
"Space rotate axis lock now {}.",
|
|
||||||
ENABLED_DISABLED[usize::from(!app.session.config.space_rotate_unlocked)]
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
SystemAction::PlayspaceResetOffset => {
|
|
||||||
app.tasks
|
|
||||||
.enqueue(TaskType::System(SystemTask::ResetPlayspace));
|
|
||||||
}
|
|
||||||
SystemAction::PlayspaceFixFloor => {
|
|
||||||
let now = Instant::now();
|
|
||||||
let sec = Duration::from_secs(1);
|
|
||||||
for i in 0..5 {
|
|
||||||
let at = now.add(i * sec);
|
|
||||||
let display = 5 - i;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!("Fixing floor in {display}").into(),
|
|
||||||
"Place either controller on the floor.".into(),
|
|
||||||
)
|
|
||||||
.with_timeout(1.0)
|
|
||||||
.submit_at(app, at);
|
|
||||||
}
|
|
||||||
app.tasks
|
|
||||||
.enqueue_at(TaskType::System(SystemTask::FixFloor), now.add(5 * sec));
|
|
||||||
}
|
|
||||||
SystemAction::RecalculateExtent => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
SystemAction::ToggleNotifications => {
|
|
||||||
app.session.config.notifications_enabled = !app.session.config.notifications_enabled;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!(
|
|
||||||
"Notifications are {}.",
|
|
||||||
ENABLED_DISABLED[usize::from(app.session.config.notifications_enabled)]
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
SystemAction::ToggleNotificationSounds => {
|
|
||||||
app.session.config.notifications_sound_enabled =
|
|
||||||
!app.session.config.notifications_sound_enabled;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!(
|
|
||||||
"Notification sounds are {}.",
|
|
||||||
ENABLED_DISABLED[usize::from(app.session.config.notifications_sound_enabled)]
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
SystemAction::PersistConfig => {
|
|
||||||
if let Err(e) = save_settings(&app.session.config) {
|
|
||||||
error_toast(app, "Failed to save config", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SystemAction::PersistLayout => {
|
|
||||||
if let Err(e) = save_layout(&app.session.config) {
|
|
||||||
error_toast(app, "Failed to save layout", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_exec(args: &ExecArgs, toast: Option<Arc<str>>, press: &mut PressData, app: &mut AppState) {
|
|
||||||
if let Some(proc) = press.child.as_mut() {
|
|
||||||
match proc.try_wait() {
|
|
||||||
Ok(Some(code)) => {
|
|
||||||
if !code.success() {
|
|
||||||
error_toast(app, "Child process exited with code", code);
|
|
||||||
}
|
|
||||||
press.child = None;
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
log::warn!("Unable to launch child process: previous child not exited yet");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
press.child = None;
|
|
||||||
error_toast(app, "Error checking child process", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let args = args
|
|
||||||
.iter()
|
|
||||||
.map(std::convert::AsRef::as_ref)
|
|
||||||
.collect::<Vec<&str>>();
|
|
||||||
match process::Command::new(args[0]).args(&args[1..]).spawn() {
|
|
||||||
Ok(proc) => {
|
|
||||||
press.child = Some(proc);
|
|
||||||
if let Some(toast) = toast.as_ref() {
|
|
||||||
Toast::new(ToastTopic::System, toast.clone(), "".into()).submit(app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error_toast(app, &format!("Failed to spawn process {args:?}"), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
fn run_watch(data: &WatchAction, app: &mut AppState) {
|
|
||||||
match data {
|
|
||||||
WatchAction::Hide => {
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
OverlaySelector::Name(WATCH_NAME.into()),
|
|
||||||
Box::new(|app, o| {
|
|
||||||
if o.saved_transform.is_none() {
|
|
||||||
o.want_visible = false;
|
|
||||||
o.saved_transform = Some(o.transform);
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
"Watch hidden".into(),
|
|
||||||
"Use show/hide binding to restore.".into(),
|
|
||||||
)
|
|
||||||
.with_timeout(3.)
|
|
||||||
.submit(app);
|
|
||||||
} else {
|
|
||||||
o.want_visible = true;
|
|
||||||
o.saved_transform = None;
|
|
||||||
Toast::new(ToastTopic::System, "Watch restored".into(), "".into())
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
audio_thump(app);
|
|
||||||
}
|
|
||||||
WatchAction::SwitchHands => {
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
OverlaySelector::Name(WATCH_NAME.into()),
|
|
||||||
Box::new(|app, o| {
|
|
||||||
if matches!(o.positioning, Positioning::FollowHand { hand: 0, .. }) {
|
|
||||||
o.positioning = Positioning::FollowHand { hand: 1, lerp: 1.0 };
|
|
||||||
o.spawn_rotation = app.session.config.watch_rot
|
|
||||||
* Quat::from_rotation_x(PI)
|
|
||||||
* Quat::from_rotation_z(PI);
|
|
||||||
o.spawn_point = app.session.config.watch_pos;
|
|
||||||
o.spawn_point.x *= -1.;
|
|
||||||
} else {
|
|
||||||
o.positioning = Positioning::FollowHand { hand: 0, lerp: 1.0 };
|
|
||||||
o.spawn_rotation = app.session.config.watch_rot;
|
|
||||||
o.spawn_point = app.session.config.watch_pos;
|
|
||||||
}
|
|
||||||
o.dirty = true;
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
"Watch switched".into(),
|
|
||||||
"Check your other hand".into(),
|
|
||||||
)
|
|
||||||
.with_timeout(3.)
|
|
||||||
.submit(app);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
audio_thump(app);
|
|
||||||
}
|
|
||||||
WatchAction::ViewAngle { kind, delta } => match kind {
|
|
||||||
ViewAngleKind::MinOpacity => {
|
|
||||||
let diff = (app.session.config.watch_view_angle_max
|
|
||||||
- app.session.config.watch_view_angle_min)
|
|
||||||
+ delta;
|
|
||||||
|
|
||||||
app.session.config.watch_view_angle_min = (app.session.config.watch_view_angle_max
|
|
||||||
- diff)
|
|
||||||
.clamp(0.0, app.session.config.watch_view_angle_max - 0.05);
|
|
||||||
}
|
|
||||||
ViewAngleKind::MaxOpacity => {
|
|
||||||
let diff = app.session.config.watch_view_angle_max
|
|
||||||
- app.session.config.watch_view_angle_min;
|
|
||||||
|
|
||||||
app.session.config.watch_view_angle_max =
|
|
||||||
(app.session.config.watch_view_angle_max + delta).clamp(0.05, 1.0);
|
|
||||||
|
|
||||||
app.session.config.watch_view_angle_min = (app.session.config.watch_view_angle_max
|
|
||||||
- diff)
|
|
||||||
.clamp(0.0, app.session.config.watch_view_angle_max - 0.05);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
WatchAction::Rotation { axis, delta } => {
|
|
||||||
let rot = match axis {
|
|
||||||
Axis::X => Quat::from_rotation_x(delta.to_radians()),
|
|
||||||
Axis::Y => Quat::from_rotation_y(delta.to_radians()),
|
|
||||||
Axis::Z => Quat::from_rotation_z(delta.to_radians()),
|
|
||||||
};
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
OverlaySelector::Name(WATCH_NAME.into()),
|
|
||||||
Box::new(move |app, o| {
|
|
||||||
o.spawn_rotation *= rot;
|
|
||||||
app.session.config.watch_rot = o.spawn_rotation;
|
|
||||||
o.dirty = true;
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
WatchAction::Position { axis, delta } => {
|
|
||||||
let delta = *delta;
|
|
||||||
let axis = match axis {
|
|
||||||
Axis::X => 0,
|
|
||||||
Axis::Y => 1,
|
|
||||||
Axis::Z => 2,
|
|
||||||
};
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
OverlaySelector::Name(WATCH_NAME.into()),
|
|
||||||
Box::new(move |app, o| {
|
|
||||||
o.spawn_point[axis] += delta;
|
|
||||||
app.session.config.watch_pos = o.spawn_point;
|
|
||||||
o.dirty = true;
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
fn run_overlay(overlay: &OverlaySelector, action: &OverlayAction, app: &mut AppState) {
|
|
||||||
match action {
|
|
||||||
OverlayAction::Reset => {
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
overlay.clone(),
|
|
||||||
Box::new(|app, o| {
|
|
||||||
o.reset(app, true);
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!("{} has been reset!", o.name).into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
OverlayAction::ToggleVisible => {
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
overlay.clone(),
|
|
||||||
Box::new(|app, o| {
|
|
||||||
o.want_visible = !o.want_visible;
|
|
||||||
if o.recenter {
|
|
||||||
o.show_hide = o.want_visible;
|
|
||||||
o.reset(app, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state_dirty = false;
|
|
||||||
if !o.want_visible {
|
|
||||||
state_dirty |= app.session.config.show_screens.arc_rm(o.name.as_ref());
|
|
||||||
} else if o.want_visible {
|
|
||||||
state_dirty |= app.session.config.show_screens.arc_set(o.name.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if state_dirty {
|
|
||||||
match save_layout(&app.session.config) {
|
|
||||||
Ok(()) => log::debug!("Saved state"),
|
|
||||||
Err(e) => {
|
|
||||||
error_toast(app, "Failed to save state", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
OverlayAction::ToggleImmovable => {
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
overlay.clone(),
|
|
||||||
Box::new(|app, o| {
|
|
||||||
o.recenter = !o.recenter;
|
|
||||||
o.grabbable = o.recenter;
|
|
||||||
o.show_hide = o.recenter;
|
|
||||||
if o.recenter {
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!("{} is now unlocked!", o.name).into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
} else {
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!("{} is now locked in place!", o.name).into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
audio_thump(app);
|
|
||||||
}
|
|
||||||
OverlayAction::ToggleInteraction => {
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
overlay.clone(),
|
|
||||||
Box::new(|app, o| {
|
|
||||||
o.interactable = !o.interactable;
|
|
||||||
if o.interactable {
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!("{} is now interactable!", o.name).into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
} else {
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
format!("{} is now non-interactable!", o.name).into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.submit(app);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
audio_thump(app);
|
|
||||||
}
|
|
||||||
OverlayAction::Opacity { delta } => {
|
|
||||||
let delta = *delta;
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
overlay.clone(),
|
|
||||||
Box::new(move |_, o| {
|
|
||||||
o.alpha = (o.alpha + delta).clamp(0.1, 1.0);
|
|
||||||
o.dirty = true;
|
|
||||||
log::debug!("{}: alpha {}", o.name, o.alpha);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_window(window: &Arc<str>, action: &WindowAction, app: &mut AppState) {
|
|
||||||
use crate::overlays::custom;
|
|
||||||
|
|
||||||
match action {
|
|
||||||
WindowAction::ShowMirror => {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
app.tasks.enqueue(TaskType::CreateOverlay(
|
|
||||||
OverlaySelector::Name(window.clone()),
|
|
||||||
Box::new({
|
|
||||||
let name = window.clone();
|
|
||||||
move |app| {
|
|
||||||
Toast::new(
|
|
||||||
ToastTopic::System,
|
|
||||||
"Check your desktop for popup.".into(),
|
|
||||||
"".into(),
|
|
||||||
)
|
|
||||||
.with_sound(true)
|
|
||||||
.submit(app);
|
|
||||||
Some(crate::overlays::mirror::new_mirror(
|
|
||||||
name,
|
|
||||||
false,
|
|
||||||
&app.session,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
#[cfg(not(feature = "wayland"))]
|
|
||||||
log::warn!("Mirror not available without Wayland feature.");
|
|
||||||
}
|
|
||||||
WindowAction::ShowUi => {
|
|
||||||
app.tasks.enqueue(TaskType::CreateOverlay(
|
|
||||||
OverlaySelector::Name(window.clone()),
|
|
||||||
Box::new({
|
|
||||||
let name = window.clone();
|
|
||||||
move |app| custom::create_custom(app, name)
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
WindowAction::Destroy => {
|
|
||||||
app.tasks
|
|
||||||
.enqueue(TaskType::DropOverlay(OverlaySelector::Name(window.clone())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const THUMP_AUDIO_WAV: &[u8] = include_bytes!("../../res/380885.wav");
|
|
||||||
|
|
||||||
fn audio_thump(app: &mut AppState) {
|
|
||||||
app.audio.play(THUMP_AUDIO_WAV);
|
|
||||||
}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
use chrono::Local;
|
|
||||||
use chrono_tz::Tz;
|
|
||||||
use glam::Vec4;
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
|
||||||
io::Read,
|
|
||||||
process::{self, Stdio},
|
|
||||||
sync::Arc,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
gui::modular::FALLBACK_COLOR,
|
|
||||||
overlays::toast::{error_toast, error_toast_str},
|
|
||||||
state::AppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use super::{color_parse_or_default, ExecArgs, GuiColor, ModularControl, ModularData};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum TimezoneDef {
|
|
||||||
Idx(usize),
|
|
||||||
Str(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(tag = "source")]
|
|
||||||
pub enum LabelContent {
|
|
||||||
Static {
|
|
||||||
text: Arc<str>,
|
|
||||||
},
|
|
||||||
Exec {
|
|
||||||
command: ExecArgs,
|
|
||||||
interval: f32,
|
|
||||||
},
|
|
||||||
Clock {
|
|
||||||
format: Arc<str>,
|
|
||||||
timezone: Option<TimezoneDef>,
|
|
||||||
},
|
|
||||||
Timezone {
|
|
||||||
timezone: usize,
|
|
||||||
},
|
|
||||||
Timer {
|
|
||||||
format: Arc<str>,
|
|
||||||
},
|
|
||||||
Battery {
|
|
||||||
device: usize,
|
|
||||||
low_threshold: f32,
|
|
||||||
low_color: Arc<str>,
|
|
||||||
charging_color: Arc<str>,
|
|
||||||
},
|
|
||||||
DragMultiplier,
|
|
||||||
Ipd,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum LabelData {
|
|
||||||
Battery {
|
|
||||||
device: usize,
|
|
||||||
low_threshold: f32,
|
|
||||||
normal_color: GuiColor,
|
|
||||||
low_color: GuiColor,
|
|
||||||
charging_color: GuiColor,
|
|
||||||
},
|
|
||||||
Clock {
|
|
||||||
format: Arc<str>,
|
|
||||||
timezone: Option<Tz>,
|
|
||||||
},
|
|
||||||
Timer {
|
|
||||||
format: Arc<str>,
|
|
||||||
start: Instant,
|
|
||||||
},
|
|
||||||
Exec {
|
|
||||||
last_exec: Instant,
|
|
||||||
interval: f32,
|
|
||||||
command: Vec<Arc<str>>,
|
|
||||||
child: Option<process::Child>,
|
|
||||||
},
|
|
||||||
Ipd {
|
|
||||||
last_ipd: f32,
|
|
||||||
},
|
|
||||||
DragMultiplier,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modular_label_init(label: &mut ModularControl, content: &LabelContent, app: &AppState) {
|
|
||||||
let state = match content {
|
|
||||||
LabelContent::Battery {
|
|
||||||
device,
|
|
||||||
low_threshold,
|
|
||||||
low_color,
|
|
||||||
charging_color,
|
|
||||||
} => Some(LabelData::Battery {
|
|
||||||
device: *device,
|
|
||||||
low_threshold: *low_threshold,
|
|
||||||
normal_color: label.fg_color,
|
|
||||||
low_color: color_parse_or_default(low_color),
|
|
||||||
charging_color: color_parse_or_default(charging_color),
|
|
||||||
}),
|
|
||||||
LabelContent::Clock { format, timezone } => {
|
|
||||||
let tz_str = match timezone {
|
|
||||||
Some(TimezoneDef::Idx(idx)) => app.session.config.timezones.get(*idx).map_or_else(
|
|
||||||
|| {
|
|
||||||
log::error!("Timezone index out of range '{idx}'");
|
|
||||||
label.set_fg_color(*FALLBACK_COLOR);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
|tz| Some(tz.as_str()),
|
|
||||||
),
|
|
||||||
Some(TimezoneDef::Str(tz_str)) => Some(tz_str.as_ref()),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(LabelData::Clock {
|
|
||||||
format: format.clone(),
|
|
||||||
timezone: tz_str.and_then(|tz| {
|
|
||||||
tz.parse()
|
|
||||||
.map_err(|_| {
|
|
||||||
log::error!("Failed to parse timezone '{}'", &tz);
|
|
||||||
label.set_fg_color(*FALLBACK_COLOR);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
LabelContent::Timezone { timezone } => {
|
|
||||||
if let Some(tz) = app.session.config.timezones.get(*timezone) {
|
|
||||||
let pretty_tz = tz.split('/').next_back().map(|x| x.replace('_', " "));
|
|
||||||
|
|
||||||
if let Some(pretty_tz) = pretty_tz {
|
|
||||||
label.set_text(&pretty_tz);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log::error!("Timezone name not valid '{}'", &tz);
|
|
||||||
} else {
|
|
||||||
log::error!("Timezone index out of range '{}'", &timezone);
|
|
||||||
}
|
|
||||||
label.set_fg_color(*FALLBACK_COLOR);
|
|
||||||
label.set_text("Error");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
LabelContent::Timer { format } => Some(LabelData::Timer {
|
|
||||||
format: format.clone(),
|
|
||||||
start: Instant::now(),
|
|
||||||
}),
|
|
||||||
LabelContent::Exec { command, interval } => Some(LabelData::Exec {
|
|
||||||
last_exec: Instant::now(),
|
|
||||||
interval: *interval,
|
|
||||||
command: command.clone(),
|
|
||||||
child: None,
|
|
||||||
}),
|
|
||||||
LabelContent::Static { text } => {
|
|
||||||
label.set_text(text);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
LabelContent::Ipd => Some(LabelData::Ipd { last_ipd: -1. }),
|
|
||||||
LabelContent::DragMultiplier => Some(LabelData::DragMultiplier),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(state) = state {
|
|
||||||
label.state = Some(ModularData::Label(Box::new(state)));
|
|
||||||
label.on_update = Some(label_update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
pub(super) fn label_update(control: &mut ModularControl, _: &mut (), app: &mut AppState) {
|
|
||||||
// want panic
|
|
||||||
let ModularData::Label(data) = control.state.as_mut().unwrap() else {
|
|
||||||
panic!("Label control has no state");
|
|
||||||
};
|
|
||||||
match data.as_mut() {
|
|
||||||
LabelData::Battery {
|
|
||||||
device,
|
|
||||||
low_threshold,
|
|
||||||
normal_color,
|
|
||||||
low_color,
|
|
||||||
charging_color,
|
|
||||||
} => {
|
|
||||||
let device = app.input_state.devices.get(*device);
|
|
||||||
|
|
||||||
let tags = ["", "H", "L", "R", "T"];
|
|
||||||
|
|
||||||
if let Some(device) = device {
|
|
||||||
let (text, color) = device.soc.map_or_else(
|
|
||||||
|| (String::new(), Vec4::ZERO),
|
|
||||||
|soc| {
|
|
||||||
let text = format!(
|
|
||||||
"{}{}",
|
|
||||||
tags[device.role as usize],
|
|
||||||
(soc * 100.).min(99.) as u32
|
|
||||||
);
|
|
||||||
let color = if device.charging {
|
|
||||||
*charging_color
|
|
||||||
} else if soc < *low_threshold {
|
|
||||||
*low_color
|
|
||||||
} else {
|
|
||||||
*normal_color
|
|
||||||
};
|
|
||||||
(text, color)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
control.set_text(&text);
|
|
||||||
control.set_fg_color(color);
|
|
||||||
} else {
|
|
||||||
control.set_text("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LabelData::Clock { format, timezone } => {
|
|
||||||
let format = format.clone();
|
|
||||||
if let Some(tz) = timezone {
|
|
||||||
let date_time = Local::now().with_timezone(tz);
|
|
||||||
control.set_text(&format!("{}", &date_time.format(&format)));
|
|
||||||
} else {
|
|
||||||
let date_time = Local::now();
|
|
||||||
control.set_text(&format!("{}", &date_time.format(&format)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LabelData::Timer { format, start } => {
|
|
||||||
let mut format = format.clone().to_lowercase();
|
|
||||||
let duration = start.elapsed().as_secs();
|
|
||||||
format = format.replace("%s", &format!("{:02}", (duration % 60)));
|
|
||||||
format = format.replace("%m", &format!("{:02}", ((duration / 60) % 60)));
|
|
||||||
format = format.replace("%h", &format!("{:02}", ((duration / 60) / 60)));
|
|
||||||
control.set_text(&format);
|
|
||||||
}
|
|
||||||
LabelData::Exec {
|
|
||||||
last_exec,
|
|
||||||
interval,
|
|
||||||
command,
|
|
||||||
child,
|
|
||||||
} => {
|
|
||||||
if let Some(mut proc) = child.take() {
|
|
||||||
match proc.try_wait() {
|
|
||||||
Ok(Some(code)) => {
|
|
||||||
if code.success() {
|
|
||||||
if let Some(mut stdout) = proc.stdout.take() {
|
|
||||||
let mut buf = String::new();
|
|
||||||
if stdout.read_to_string(&mut buf).is_ok() {
|
|
||||||
control.set_text(&buf);
|
|
||||||
} else {
|
|
||||||
error_toast_str(
|
|
||||||
app,
|
|
||||||
"LabelData::Exec: Failed to read stdout for child process",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log::error!("No stdout for child process");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
error_toast(app, "LabelData::Exec: Child process exited with code", code);
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
*child = Some(proc);
|
|
||||||
// not exited yet
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*child = None;
|
|
||||||
error_toast(app, "Error checking child process", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if Instant::now()
|
|
||||||
.saturating_duration_since(*last_exec)
|
|
||||||
.as_secs_f32()
|
|
||||||
> *interval
|
|
||||||
{
|
|
||||||
*last_exec = Instant::now();
|
|
||||||
let args = command
|
|
||||||
.iter()
|
|
||||||
.map(std::convert::AsRef::as_ref)
|
|
||||||
.collect::<SmallVec<[&str; 8]>>();
|
|
||||||
|
|
||||||
match process::Command::new(args[0])
|
|
||||||
.args(&args[1..])
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
{
|
|
||||||
Ok(proc) => {
|
|
||||||
*child = Some(proc);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error_toast(app, &format!("Failed to spawn process {args:?}"), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LabelData::Ipd { last_ipd } => {
|
|
||||||
if (app.input_state.ipd - *last_ipd).abs() > 0.05 {
|
|
||||||
*last_ipd = app.input_state.ipd;
|
|
||||||
control.set_text(&format!("{:.1}", app.input_state.ipd));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LabelData::DragMultiplier => {
|
|
||||||
control.set_text(&format!("{:.1}", app.session.config.space_drag_multiplier));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,569 +0,0 @@
|
|||||||
pub mod button;
|
|
||||||
pub mod label;
|
|
||||||
|
|
||||||
use std::{fs::File, sync::Arc};
|
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
|
||||||
use button::{WayVRAction, WayVRDisplayClickAction};
|
|
||||||
|
|
||||||
use glam::Vec4;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
backend::common::OverlaySelector, config::AStrMapExt, config_io,
|
|
||||||
graphics::dds::WlxCommandBufferDds, state::AppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::{
|
|
||||||
button::{modular_button_init, ButtonAction, ButtonData, OverlayAction},
|
|
||||||
label::{modular_label_init, LabelContent, LabelData},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
canvas::{builder::CanvasBuilder, control::Control, Canvas},
|
|
||||||
color_parse, GuiColor, FALLBACK_COLOR,
|
|
||||||
};
|
|
||||||
|
|
||||||
type ModularControl = Control<(), ModularData>;
|
|
||||||
type ExecArgs = Vec<Arc<str>>;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ModularUiConfig {
|
|
||||||
pub width: f32,
|
|
||||||
pub size: [u32; 2],
|
|
||||||
pub spawn_pos: Option<[f32; 3]>,
|
|
||||||
pub elements: Vec<ModularElement>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct OverlayListTemplate {
|
|
||||||
click_down: Option<OverlayAction>,
|
|
||||||
click_up: Option<OverlayAction>,
|
|
||||||
long_click_up: Option<OverlayAction>,
|
|
||||||
right_down: Option<OverlayAction>,
|
|
||||||
right_up: Option<OverlayAction>,
|
|
||||||
long_right_up: Option<OverlayAction>,
|
|
||||||
middle_down: Option<OverlayAction>,
|
|
||||||
middle_up: Option<OverlayAction>,
|
|
||||||
long_middle_up: Option<OverlayAction>,
|
|
||||||
scroll_down: Option<OverlayAction>,
|
|
||||||
scroll_up: Option<OverlayAction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum ModularElement {
|
|
||||||
Panel {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
bg_color: Arc<str>,
|
|
||||||
},
|
|
||||||
Label {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
#[serde(flatten)]
|
|
||||||
data: LabelContent,
|
|
||||||
},
|
|
||||||
CenteredLabel {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
#[serde(flatten)]
|
|
||||||
data: LabelContent,
|
|
||||||
},
|
|
||||||
Sprite {
|
|
||||||
rect: [f32; 4],
|
|
||||||
sprite: Arc<str>,
|
|
||||||
sprite_st: Option<[f32; 4]>,
|
|
||||||
},
|
|
||||||
Button {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
bg_color: Arc<str>,
|
|
||||||
text: Arc<str>,
|
|
||||||
#[serde(flatten)]
|
|
||||||
data: Box<ButtonData>,
|
|
||||||
},
|
|
||||||
/// Convenience type to save you from having to create a bunch of labels
|
|
||||||
BatteryList {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
fg_color_low: Arc<str>,
|
|
||||||
fg_color_charging: Arc<str>,
|
|
||||||
low_threshold: f32,
|
|
||||||
num_devices: usize,
|
|
||||||
layout: ListLayout,
|
|
||||||
},
|
|
||||||
OverlayList {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
bg_color: Arc<str>,
|
|
||||||
layout: ListLayout,
|
|
||||||
#[serde(flatten)]
|
|
||||||
template: Box<OverlayListTemplate>,
|
|
||||||
},
|
|
||||||
// Ignored if "wayvr" feature is not enabled
|
|
||||||
WayVRLauncher {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
bg_color: Arc<str>,
|
|
||||||
catalog_name: Arc<str>,
|
|
||||||
},
|
|
||||||
// Ignored if "wayvr" feature is not enabled
|
|
||||||
WayVRDisplayList {
|
|
||||||
rect: [f32; 4],
|
|
||||||
corner_radius: Option<f32>,
|
|
||||||
font_size: isize,
|
|
||||||
fg_color: Arc<str>,
|
|
||||||
bg_color: Arc<str>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub enum ButtonFunc {
|
|
||||||
HideWatch,
|
|
||||||
SwitchWatchHand,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub enum ListLayout {
|
|
||||||
Horizontal,
|
|
||||||
Vertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ModularData {
|
|
||||||
Label(Box<LabelData>),
|
|
||||||
Button(Box<ButtonData>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines, clippy::many_single_char_names)]
|
|
||||||
pub fn modular_canvas(
|
|
||||||
size: [u32; 2],
|
|
||||||
elements: &[ModularElement],
|
|
||||||
state: &mut AppState,
|
|
||||||
) -> anyhow::Result<Canvas<(), ModularData>> {
|
|
||||||
let mut canvas = CanvasBuilder::new(
|
|
||||||
size[0] as _,
|
|
||||||
size[1] as _,
|
|
||||||
state.graphics.clone(),
|
|
||||||
state.graphics.native_format,
|
|
||||||
(),
|
|
||||||
)?;
|
|
||||||
let empty_str: Arc<str> = Arc::from("");
|
|
||||||
for elem in elements {
|
|
||||||
match elem {
|
|
||||||
ModularElement::Panel {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
bg_color,
|
|
||||||
} => {
|
|
||||||
canvas.bg_color = color_parse(bg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.panel(*x, *y, *w, *h, corner_radius.unwrap_or_default());
|
|
||||||
}
|
|
||||||
ModularElement::Label {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
fg_color,
|
|
||||||
data,
|
|
||||||
} => {
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
let label = canvas.label(
|
|
||||||
*x,
|
|
||||||
*y,
|
|
||||||
*w,
|
|
||||||
*h,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
empty_str.clone(),
|
|
||||||
);
|
|
||||||
modular_label_init(label, data, state);
|
|
||||||
}
|
|
||||||
ModularElement::CenteredLabel {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
fg_color,
|
|
||||||
data,
|
|
||||||
} => {
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
let label = canvas.label_centered(
|
|
||||||
*x,
|
|
||||||
*y,
|
|
||||||
*w,
|
|
||||||
*h,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
empty_str.clone(),
|
|
||||||
);
|
|
||||||
modular_label_init(label, data, state);
|
|
||||||
}
|
|
||||||
ModularElement::Sprite {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
sprite,
|
|
||||||
sprite_st,
|
|
||||||
} => match sprite_from_path(sprite.clone(), state) {
|
|
||||||
Ok(view) => {
|
|
||||||
let sprite = canvas.sprite(*x, *y, *w, *h);
|
|
||||||
sprite.fg_color = Vec4::ONE;
|
|
||||||
sprite.set_sprite(view);
|
|
||||||
|
|
||||||
let st = sprite_st
|
|
||||||
.map(|st| Vec4::from_slice(&st))
|
|
||||||
.unwrap_or_else(|| Vec4::new(1., 1., 0., 0.));
|
|
||||||
sprite.set_sprite_st(st);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Could not load custom UI sprite: {e:?}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ModularElement::Button {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
bg_color,
|
|
||||||
fg_color,
|
|
||||||
text,
|
|
||||||
data,
|
|
||||||
} => {
|
|
||||||
canvas.bg_color = color_parse(bg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
let button = canvas.button(
|
|
||||||
*x,
|
|
||||||
*y,
|
|
||||||
*w,
|
|
||||||
*h,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
text.clone(),
|
|
||||||
);
|
|
||||||
modular_button_init(button, data);
|
|
||||||
}
|
|
||||||
ModularElement::BatteryList {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
fg_color,
|
|
||||||
fg_color_low,
|
|
||||||
fg_color_charging,
|
|
||||||
low_threshold,
|
|
||||||
num_devices,
|
|
||||||
layout,
|
|
||||||
} => {
|
|
||||||
let num_buttons = *num_devices as f32;
|
|
||||||
let mut button_x = *x;
|
|
||||||
let mut button_y = *y;
|
|
||||||
let low_threshold = low_threshold * 0.01;
|
|
||||||
let (button_w, button_h) = match layout {
|
|
||||||
ListLayout::Horizontal => (*w / num_buttons, *h),
|
|
||||||
ListLayout::Vertical => (*w, *h / num_buttons),
|
|
||||||
};
|
|
||||||
|
|
||||||
let fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
canvas.fg_color = fg_color;
|
|
||||||
|
|
||||||
for i in 0..*num_devices {
|
|
||||||
let label = canvas.label_centered(
|
|
||||||
button_x + 2.,
|
|
||||||
button_y + 2.,
|
|
||||||
button_w - 4.,
|
|
||||||
button_h - 4.,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
empty_str.clone(),
|
|
||||||
);
|
|
||||||
modular_label_init(
|
|
||||||
label,
|
|
||||||
&LabelContent::Battery {
|
|
||||||
device: i,
|
|
||||||
low_threshold,
|
|
||||||
low_color: fg_color_low.clone(),
|
|
||||||
charging_color: fg_color_charging.clone(),
|
|
||||||
},
|
|
||||||
state,
|
|
||||||
);
|
|
||||||
|
|
||||||
button_x += match layout {
|
|
||||||
ListLayout::Horizontal => button_w,
|
|
||||||
ListLayout::Vertical => 0.,
|
|
||||||
};
|
|
||||||
button_y += match layout {
|
|
||||||
ListLayout::Horizontal => 0.,
|
|
||||||
ListLayout::Vertical => button_h,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ModularElement::OverlayList {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
layout,
|
|
||||||
template,
|
|
||||||
} => {
|
|
||||||
let num_buttons = state.screens.len() as f32;
|
|
||||||
let mut button_x = *x;
|
|
||||||
let mut button_y = *y;
|
|
||||||
let (button_w, button_h) = match layout {
|
|
||||||
ListLayout::Horizontal => (*w / num_buttons, *h),
|
|
||||||
ListLayout::Vertical => (*w, *h / num_buttons),
|
|
||||||
};
|
|
||||||
|
|
||||||
canvas.bg_color = color_parse(bg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
|
|
||||||
for screen in &state.screens {
|
|
||||||
let button = canvas.button(
|
|
||||||
button_x + 2.,
|
|
||||||
button_y + 2.,
|
|
||||||
button_w - 4.,
|
|
||||||
button_h - 4.,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
screen.name.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// cursed
|
|
||||||
let data = ButtonData {
|
|
||||||
click_down: template.click_down.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
click_up: template.click_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
long_click_up: template.long_click_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
right_down: template.right_down.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
right_up: template.right_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
long_right_up: template.long_right_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
middle_down: template.middle_down.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
middle_up: template.middle_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
long_middle_up: template.long_middle_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
scroll_down: template.scroll_down.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
scroll_up: template.scroll_up.as_ref().map(|f| {
|
|
||||||
vec![ButtonAction::Overlay {
|
|
||||||
target: OverlaySelector::Id(screen.id),
|
|
||||||
action: f.clone(),
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
modular_button_init(button, &data);
|
|
||||||
|
|
||||||
button_x += match layout {
|
|
||||||
ListLayout::Horizontal => button_w,
|
|
||||||
ListLayout::Vertical => 0.,
|
|
||||||
};
|
|
||||||
button_y += match layout {
|
|
||||||
ListLayout::Horizontal => 0.,
|
|
||||||
ListLayout::Vertical => button_h,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(unused_variables)] // needed in case if wayvr feature is not enabled
|
|
||||||
ModularElement::WayVRLauncher {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
catalog_name,
|
|
||||||
} => {
|
|
||||||
#[cfg(feature = "wayvr")]
|
|
||||||
{
|
|
||||||
if let Some(catalog) = state.session.wayvr_config.get_catalog(catalog_name) {
|
|
||||||
let mut button_x = *x;
|
|
||||||
let button_y = *y;
|
|
||||||
|
|
||||||
for app in &catalog.apps {
|
|
||||||
let button_w: f32 = *w / catalog.apps.len() as f32;
|
|
||||||
let button_h: f32 = *h;
|
|
||||||
|
|
||||||
canvas.bg_color = color_parse(bg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
|
|
||||||
let button = canvas.button(
|
|
||||||
button_x + 2.,
|
|
||||||
button_y + 2.,
|
|
||||||
button_w - 4.,
|
|
||||||
button_h - 4.,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
Arc::from(app.name.as_str()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = ButtonData {
|
|
||||||
click_up: Some(vec![ButtonAction::WayVR {
|
|
||||||
action: WayVRAction::AppClick {
|
|
||||||
catalog_name: catalog_name.clone(),
|
|
||||||
app_name: Arc::from(app.name.as_str()),
|
|
||||||
},
|
|
||||||
}]),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
modular_button_init(button, &data);
|
|
||||||
button_x += button_w;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::error!("WayVR catalog \"{catalog_name}\" not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wayvr"))]
|
|
||||||
{
|
|
||||||
log::error!("WayVR feature is not enabled, ignoring");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
ModularElement::WayVRDisplayList {
|
|
||||||
rect: [x, y, w, h],
|
|
||||||
corner_radius,
|
|
||||||
font_size,
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
} => {
|
|
||||||
#[cfg(feature = "wayvr")]
|
|
||||||
{
|
|
||||||
let mut button_x = *x;
|
|
||||||
let button_y = *y;
|
|
||||||
let displays = &state.session.wayvr_config.displays;
|
|
||||||
for (display_name, display) in displays {
|
|
||||||
let button_w: f32 = (*w / displays.len() as f32).min(80.0);
|
|
||||||
let button_h: f32 = *h;
|
|
||||||
|
|
||||||
canvas.bg_color = color_parse(bg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR);
|
|
||||||
canvas.font_size = *font_size;
|
|
||||||
|
|
||||||
let button = canvas.button(
|
|
||||||
button_x + 2.,
|
|
||||||
button_y + 2.,
|
|
||||||
button_w - 4.,
|
|
||||||
button_h - 4.,
|
|
||||||
corner_radius.unwrap_or_default(),
|
|
||||||
Arc::from(display_name.as_str()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = ButtonData {
|
|
||||||
click_up: Some(vec![ButtonAction::WayVR {
|
|
||||||
action: WayVRAction::DisplayClick {
|
|
||||||
display_name: Arc::from(display_name.as_str()),
|
|
||||||
action: WayVRDisplayClickAction::ToggleVisibility,
|
|
||||||
},
|
|
||||||
}]),
|
|
||||||
long_click_up: Some(vec![ButtonAction::WayVR {
|
|
||||||
action: WayVRAction::DisplayClick {
|
|
||||||
display_name: Arc::from(display_name.as_str()),
|
|
||||||
action: WayVRDisplayClickAction::Reset,
|
|
||||||
},
|
|
||||||
}]),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
modular_button_init(button, &data);
|
|
||||||
button_x += button_w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wayvr"))]
|
|
||||||
{
|
|
||||||
log::error!("WayVR feature is not enabled, ignoring")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(canvas.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn color_parse_or_default(color: &str) -> GuiColor {
|
|
||||||
color_parse(color).unwrap_or_else(|e| {
|
|
||||||
log::error!("Failed to parse color '{color}': {e}");
|
|
||||||
*FALLBACK_COLOR
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sprite_from_path(path: Arc<str>, app: &mut AppState) -> anyhow::Result<Arc<ImageView>> {
|
|
||||||
if let Some(view) = app.sprites.arc_get(&path) {
|
|
||||||
return Ok(view.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let real_path = config_io::get_config_root().join(&*path);
|
|
||||||
|
|
||||||
let Ok(f) = File::open(real_path) else {
|
|
||||||
anyhow::bail!("Could not open custom sprite at: {}", path);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut command_buffer = app.graphics.create_uploads_command_buffer(
|
|
||||||
app.graphics.transfer_queue.clone(),
|
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match command_buffer.texture2d_dds(f) {
|
|
||||||
Ok(image) => {
|
|
||||||
command_buffer.build_and_execute_now()?;
|
|
||||||
Ok(ImageView::new_default(image)?)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
anyhow::bail!("Could not use custom sprite at: {}\n{:?}", path, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
161
src/gui/panel.rs
Normal file
161
src/gui/panel.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use glam::vec2;
|
||||||
|
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
|
||||||
|
use wgui::{
|
||||||
|
event::{Event as WguiEvent, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
|
||||||
|
layout::Layout,
|
||||||
|
renderer_vk::context::Context as WguiContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
backend::{
|
||||||
|
input::{Haptics, InteractionHandler, PointerHit},
|
||||||
|
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
|
||||||
|
},
|
||||||
|
graphics::{CommandBuffers, ExtentExt},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{asset::GuiAsset, timestep::Timestep};
|
||||||
|
|
||||||
|
pub struct GuiPanel {
|
||||||
|
pub layout: Layout,
|
||||||
|
context: WguiContext,
|
||||||
|
timestep: Timestep,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuiPanel {
|
||||||
|
pub fn new_from_template(
|
||||||
|
app: &AppState,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
path: &str,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let mut me = Self::new_blank(app, width, height)?;
|
||||||
|
|
||||||
|
let parent = me.layout.root_widget;
|
||||||
|
let _res = wgui::parser::parse_from_assets(&mut me.layout, parent, path)?;
|
||||||
|
|
||||||
|
Ok(me)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_blank(app: &AppState, width: u32, height: u32) -> anyhow::Result<Self> {
|
||||||
|
let layout = Layout::new(Box::new(GuiAsset {}))?;
|
||||||
|
let context = WguiContext::new(app.gfx.clone(), app.gfx.surface_format, 1.0)?;
|
||||||
|
let mut timestep = Timestep::new();
|
||||||
|
timestep.set_tps(60.0);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
layout,
|
||||||
|
context,
|
||||||
|
timestep,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayBackend for GuiPanel {
|
||||||
|
fn set_renderer(&mut self, _: Box<dyn OverlayRenderer>) {
|
||||||
|
log::debug!("Attempted to replace renderer on GuiPanel!");
|
||||||
|
}
|
||||||
|
fn set_interaction(&mut self, _: Box<dyn InteractionHandler>) {
|
||||||
|
log::debug!("Attempted to replace interaction layer on GuiPanel!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteractionHandler for GuiPanel {
|
||||||
|
fn on_scroll(&mut self, _app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
|
||||||
|
self.layout
|
||||||
|
.push_event(&WguiEvent::MouseWheel(MouseWheelEvent {
|
||||||
|
shift: vec2(delta_x, delta_y),
|
||||||
|
pos: hit.uv,
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
|
||||||
|
self.layout
|
||||||
|
.push_event(&WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv }))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_left(&mut self, _app: &mut AppState, _pointer: usize) {
|
||||||
|
//TODO: is this needed?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_pointer(&mut self, _app: &mut AppState, hit: &PointerHit, pressed: bool) {
|
||||||
|
if pressed {
|
||||||
|
self.layout
|
||||||
|
.push_event(&WguiEvent::MouseDown(MouseDownEvent { pos: hit.uv }))
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
self.layout
|
||||||
|
.push_event(&WguiEvent::MouseUp(MouseUpEvent { pos: hit.uv }))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayRenderer for GuiPanel {
|
||||||
|
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
||||||
|
self.timestep.reset();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
||||||
|
while self.timestep.on_tick() {
|
||||||
|
self.layout.tick()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(if self.layout.check_toggle_needs_redraw() {
|
||||||
|
ShouldRender::Should
|
||||||
|
} else {
|
||||||
|
ShouldRender::Can
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
app: &mut AppState,
|
||||||
|
tgt: Arc<ImageView>,
|
||||||
|
buf: &mut CommandBuffers,
|
||||||
|
_alpha: f32,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
self.context.update_viewport(tgt.extent_u32arr(), 1.0)?;
|
||||||
|
self.layout.update(tgt.extent_vec2(), self.timestep.alpha);
|
||||||
|
|
||||||
|
let mut cmd_buf = app
|
||||||
|
.gfx
|
||||||
|
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cmd_buf.begin_rendering(tgt)?;
|
||||||
|
let primitives = wgui::drawing::draw(&self.layout)?;
|
||||||
|
self.context.draw(&app.gfx, &mut cmd_buf, &primitives)?;
|
||||||
|
cmd_buf.end_rendering()?;
|
||||||
|
buf.push(cmd_buf.build()?);
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
||||||
|
Some(FrameMeta {
|
||||||
|
extent: [self.width, self.height, 1],
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/gui/timestep.rs
Normal file
70
src/gui/timestep.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use std::{sync::LazyLock, time::Instant};
|
||||||
|
static TIME_START: LazyLock<Instant> = LazyLock::new(Instant::now);
|
||||||
|
|
||||||
|
pub fn get_micros() -> u64 {
|
||||||
|
TIME_START.elapsed().as_micros() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Timestep {
|
||||||
|
current_time_us: u64,
|
||||||
|
accumulator: f32,
|
||||||
|
time_micros: u64,
|
||||||
|
ticks: u32,
|
||||||
|
speed: f32,
|
||||||
|
pub alpha: f32,
|
||||||
|
delta: f32,
|
||||||
|
loopnum: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timestep {
|
||||||
|
pub fn new() -> Timestep {
|
||||||
|
let mut timestep = Timestep {
|
||||||
|
speed: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
timestep.reset();
|
||||||
|
timestep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_alpha(&mut self) {
|
||||||
|
self.alpha = (self.accumulator / self.delta).clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tps(&mut self, tps: f32) {
|
||||||
|
self.delta = 1000.0 / tps;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.current_time_us = get_micros();
|
||||||
|
self.accumulator = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_tick(&mut self) -> bool {
|
||||||
|
let newtime = get_micros();
|
||||||
|
let frametime = newtime - self.current_time_us;
|
||||||
|
self.time_micros += frametime;
|
||||||
|
self.current_time_us = newtime;
|
||||||
|
self.accumulator += frametime as f32 * self.speed / 1000.0;
|
||||||
|
self.calculate_alpha();
|
||||||
|
|
||||||
|
if self.accumulator >= self.delta {
|
||||||
|
self.accumulator -= self.delta;
|
||||||
|
self.loopnum += 1;
|
||||||
|
self.ticks += 1;
|
||||||
|
|
||||||
|
if self.loopnum > 5 {
|
||||||
|
// cannot keep up!
|
||||||
|
self.loopnum = 0;
|
||||||
|
self.accumulator = 0.0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.loopnum = 0;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,12 +113,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "uidev")]
|
|
||||||
if let Some(panel_name) = args.uidev.as_ref() {
|
|
||||||
crate::backend::uidev::uidev_run(panel_name.as_str())?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let running = Arc::new(AtomicBool::new(true));
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
let _ = ctrlc::set_handler({
|
let _ = ctrlc::set_handler({
|
||||||
let running = running.clone();
|
let running = running.clone();
|
||||||
|
|||||||
@@ -1,18 +1,61 @@
|
|||||||
use glam::Vec3A;
|
use glam::Vec3A;
|
||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::{Arc, LazyLock};
|
||||||
|
use wgui::parser::parse_color_hex;
|
||||||
|
use wgui::renderer_vk::text::{FontWeight, TextStyle};
|
||||||
|
use wgui::taffy;
|
||||||
|
use wgui::taffy::prelude::{length, percent};
|
||||||
|
use wgui::widget::rectangle::{Rectangle, RectangleParams};
|
||||||
|
use wgui::widget::text::{TextLabel, TextParams};
|
||||||
|
use wgui::widget::util::WLength;
|
||||||
|
|
||||||
use crate::backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_ANCHOR};
|
use crate::backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_ANCHOR};
|
||||||
use crate::config::{load_known_yaml, ConfigType};
|
use crate::gui::panel::GuiPanel;
|
||||||
use crate::gui::modular::{modular_canvas, ModularUiConfig};
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
pub static ANCHOR_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("anchor"));
|
pub static ANCHOR_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("anchor"));
|
||||||
|
|
||||||
pub fn create_anchor<O>(state: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
pub fn create_anchor<O>(app: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
||||||
where
|
where
|
||||||
O: Default,
|
O: Default,
|
||||||
{
|
{
|
||||||
let config = load_known_yaml::<ModularUiConfig>(ConfigType::Anchor);
|
let mut panel = GuiPanel::new_blank(app, 200, 200)?;
|
||||||
|
|
||||||
|
let (rect, _) = panel.layout.add_child(
|
||||||
|
panel.layout.root_widget,
|
||||||
|
Rectangle::create(RectangleParams {
|
||||||
|
color: wgui::drawing::Color::new(0., 0., 0., 0.),
|
||||||
|
border_color: parse_color_hex("#ffff00").unwrap(),
|
||||||
|
border: 2.0,
|
||||||
|
round: WLength::Percent(1.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: percent(1.0),
|
||||||
|
},
|
||||||
|
align_items: Some(taffy::AlignItems::Center),
|
||||||
|
justify_content: Some(taffy::JustifyContent::Center),
|
||||||
|
padding: length(4.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let _ = panel.layout.add_child(
|
||||||
|
rect,
|
||||||
|
TextLabel::create(TextParams {
|
||||||
|
content: "Center".into(),
|
||||||
|
style: TextStyle {
|
||||||
|
weight: Some(FontWeight::Bold),
|
||||||
|
size: Some(36.0),
|
||||||
|
color: parse_color_hex("#ffff00"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::style::Style::DEFAULT,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(OverlayData {
|
Ok(OverlayData {
|
||||||
state: OverlayState {
|
state: OverlayState {
|
||||||
@@ -21,12 +64,12 @@ where
|
|||||||
interactable: false,
|
interactable: false,
|
||||||
grabbable: false,
|
grabbable: false,
|
||||||
z_order: Z_ORDER_ANCHOR,
|
z_order: Z_ORDER_ANCHOR,
|
||||||
spawn_scale: config.width,
|
spawn_scale: 0.1,
|
||||||
spawn_point: Vec3A::NEG_Z * 0.5,
|
spawn_point: Vec3A::NEG_Z * 0.5,
|
||||||
positioning: Positioning::Static,
|
positioning: Positioning::Static,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(modular_canvas(config.size, &config.elements, state)?),
|
backend: Box::new(panel),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,49 +3,34 @@ use std::sync::Arc;
|
|||||||
use glam::Vec3A;
|
use glam::Vec3A;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::overlay::{ui_transform, OverlayBackend, OverlayState},
|
backend::overlay::{OverlayBackend, OverlayState},
|
||||||
config::{load_custom_ui, load_known_yaml, ConfigType},
|
gui::panel::GuiPanel,
|
||||||
gui::modular::{modular_canvas, ModularUiConfig},
|
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SETTINGS_NAME: &str = "settings";
|
const SETTINGS_NAME: &str = "settings";
|
||||||
|
|
||||||
pub fn create_custom(
|
pub fn create_custom(
|
||||||
state: &mut AppState,
|
app: &mut AppState,
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
||||||
let config = if &*name == SETTINGS_NAME {
|
|
||||||
load_known_yaml::<ModularUiConfig>(ConfigType::Settings)
|
|
||||||
} else {
|
|
||||||
match load_custom_ui(&name) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to load custom UI config for {name}: {e:?}");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let canvas = match modular_canvas(config.size, &config.elements, state) {
|
unreachable!();
|
||||||
Ok(canvas) => canvas,
|
|
||||||
Err(e) => {
|
let panel = GuiPanel::new_blank(&app, 200, 200).ok()?;
|
||||||
log::error!("Failed to create canvas for {name}: {e:?}");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let state = OverlayState {
|
let state = OverlayState {
|
||||||
name,
|
name,
|
||||||
want_visible: true,
|
want_visible: true,
|
||||||
interactable: true,
|
interactable: true,
|
||||||
grabbable: true,
|
grabbable: true,
|
||||||
spawn_scale: config.width,
|
spawn_scale: 0.1, //TODO: this
|
||||||
spawn_point: Vec3A::from_array(config.spawn_pos.unwrap_or([0., 0., -0.5])),
|
spawn_point: Vec3A::from_array([0., 0., -0.5]),
|
||||||
interaction_transform: ui_transform(config.size),
|
//interaction_transform: ui_transform(config.size),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let backend = Box::new(canvas);
|
let backend = Box::new(panel);
|
||||||
|
|
||||||
Some((state, backend))
|
Some((state, backend))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,26 @@ use crate::{
|
|||||||
},
|
},
|
||||||
config::{self, ConfigType},
|
config::{self, ConfigType},
|
||||||
graphics::CommandBuffers,
|
graphics::CommandBuffers,
|
||||||
gui::{
|
gui::panel::GuiPanel,
|
||||||
canvas::{builder::CanvasBuilder, control::Control, Canvas},
|
|
||||||
color_parse, KeyCapType,
|
|
||||||
},
|
|
||||||
hid::{
|
hid::{
|
||||||
get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META,
|
get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META,
|
||||||
NUM_LOCK, SHIFT, SUPER,
|
NUM_LOCK, SHIFT, SUPER,
|
||||||
},
|
},
|
||||||
state::{AppState, KeyboardFocus},
|
state::{AppState, KeyboardFocus},
|
||||||
};
|
};
|
||||||
use glam::{vec2, vec3a, Affine2, Vec4};
|
use glam::{vec2, vec3a, Affine2, Vec2Swizzles, Vec4};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use vulkano::image::view::ImageView;
|
use vulkano::image::view::ImageView;
|
||||||
|
use wgui::{
|
||||||
|
parser::parse_color_hex,
|
||||||
|
taffy::{self, prelude::length},
|
||||||
|
widget::{
|
||||||
|
div::Div,
|
||||||
|
rectangle::{Rectangle, RectangleParams},
|
||||||
|
util::WLength,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const PIXELS_PER_UNIT: f32 = 80.;
|
const PIXELS_PER_UNIT: f32 = 80.;
|
||||||
const BUTTON_PADDING: f32 = 4.;
|
const BUTTON_PADDING: f32 = 4.;
|
||||||
@@ -92,20 +98,28 @@ where
|
|||||||
processes: vec![],
|
processes: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut canvas = CanvasBuilder::new(
|
let padding = 4f32;
|
||||||
size.x as _,
|
|
||||||
size.y as _,
|
let mut panel = GuiPanel::new_blank(
|
||||||
app.graphics.clone(),
|
app,
|
||||||
app.graphics.native_format,
|
padding.mul_add(2.0, size.x) as u32,
|
||||||
data,
|
padding.mul_add(2.0, size.y) as u32,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
canvas.bg_color = color_parse("#181926").unwrap(); //safe
|
let (background, _) = panel.layout.add_child(
|
||||||
canvas.panel(0., 0., size.x, size.y, 12.);
|
panel.layout.root_widget,
|
||||||
|
Rectangle::create(RectangleParams {
|
||||||
canvas.font_size = 18;
|
color: wgui::drawing::Color::new(0., 0., 0., 0.6),
|
||||||
canvas.fg_color = color_parse("#cad3f5").unwrap(); //safe
|
round: WLength::Units(4.0),
|
||||||
canvas.bg_color = color_parse("#1e2030").unwrap(); //safe
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
flex_direction: taffy::FlexDirection::Column,
|
||||||
|
padding: length(padding),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let has_altgr = keymap
|
let has_altgr = keymap
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -115,17 +129,22 @@ where
|
|||||||
keymap = None;
|
keymap = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let unit_size = size.x / LAYOUT.row_size;
|
|
||||||
let h = 2.0f32.mul_add(-BUTTON_PADDING, unit_size);
|
|
||||||
|
|
||||||
for row in 0..LAYOUT.key_sizes.len() {
|
for row in 0..LAYOUT.key_sizes.len() {
|
||||||
let y = unit_size.mul_add(row as f32, BUTTON_PADDING);
|
let (div, _) = panel.layout.add_child(
|
||||||
let mut sum_size = 0f32;
|
background,
|
||||||
|
Div::create().unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
flex_direction: taffy::FlexDirection::Row,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
for col in 0..LAYOUT.key_sizes[row].len() {
|
for col in 0..LAYOUT.key_sizes[row].len() {
|
||||||
let my_size = LAYOUT.key_sizes[row][col];
|
let my_size = LAYOUT.key_sizes[row][col];
|
||||||
let x = unit_size.mul_add(sum_size, BUTTON_PADDING);
|
let my_size = taffy::Size {
|
||||||
let w = unit_size.mul_add(my_size, -(2. * BUTTON_PADDING));
|
width: length(PIXELS_PER_UNIT * my_size),
|
||||||
|
height: length(PIXELS_PER_UNIT),
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(key) = LAYOUT.main_layout[row][col].as_ref() {
|
if let Some(key) = LAYOUT.main_layout[row][col].as_ref() {
|
||||||
let mut label = Vec::with_capacity(2);
|
let mut label = Vec::with_capacity(2);
|
||||||
@@ -180,8 +199,7 @@ where
|
|||||||
} else if let Some(exec_args) = LAYOUT.exec_commands.get(key) {
|
} else if let Some(exec_args) = LAYOUT.exec_commands.get(key) {
|
||||||
if exec_args.is_empty() {
|
if exec_args.is_empty() {
|
||||||
log::error!("Keyboard: EXEC args empty for {key}");
|
log::error!("Keyboard: EXEC args empty for {key}");
|
||||||
continue;
|
} else {
|
||||||
}
|
|
||||||
let mut iter = exec_args.iter().cloned();
|
let mut iter = exec_args.iter().cloned();
|
||||||
if let Some(program) = iter.next() {
|
if let Some(program) = iter.next() {
|
||||||
maybe_state = Some(KeyButtonData::Exec {
|
maybe_state = Some(KeyButtonData::Exec {
|
||||||
@@ -191,6 +209,7 @@ where
|
|||||||
release_args: iter.collect(),
|
release_args: iter.collect(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::error!("Unknown key: {key}");
|
log::error!("Unknown key: {key}");
|
||||||
}
|
}
|
||||||
@@ -199,20 +218,38 @@ where
|
|||||||
if label.is_empty() {
|
if label.is_empty() {
|
||||||
label = LAYOUT.label_for_key(key);
|
label = LAYOUT.label_for_key(key);
|
||||||
}
|
}
|
||||||
let button = canvas.key_button(x, y, w, h, 12., cap_type, &label);
|
let _ = panel.layout.add_child(
|
||||||
button.state = Some(state);
|
div,
|
||||||
button.on_press = Some(key_press);
|
Rectangle::create(RectangleParams {
|
||||||
button.on_release = Some(key_release);
|
border_color: parse_color_hex("#dddddd").unwrap(),
|
||||||
button.test_highlight = Some(test_highlight);
|
border: 2.0,
|
||||||
|
round: WLength::Units(4.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
size: my_size,
|
||||||
|
min_size: my_size,
|
||||||
|
max_size: my_size,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
let _ = panel.layout.add_child(
|
||||||
|
div,
|
||||||
|
Div::create().unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
size: my_size,
|
||||||
|
min_size: my_size,
|
||||||
|
max_size: my_size,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sum_size += my_size;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let canvas = canvas.build();
|
|
||||||
|
|
||||||
let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
|
let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
|
||||||
* Affine2::from_scale(vec2(1., -size.x as f32 / size.y as f32));
|
* Affine2::from_scale(vec2(1., -size.x as f32 / size.y as f32));
|
||||||
|
|
||||||
@@ -230,120 +267,11 @@ where
|
|||||||
interaction_transform,
|
interaction_transform,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(KeyboardBackend { canvas }),
|
backend: Box::new(KeyboardBackend { panel }),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_press(
|
|
||||||
control: &mut Control<KeyboardData, KeyButtonData>,
|
|
||||||
data: &mut KeyboardData,
|
|
||||||
app: &mut AppState,
|
|
||||||
mode: PointerMode,
|
|
||||||
) {
|
|
||||||
match control.state.as_mut() {
|
|
||||||
Some(KeyButtonData::Key { vk, pressed }) => {
|
|
||||||
key_click(app);
|
|
||||||
|
|
||||||
data.modifiers |= match mode {
|
|
||||||
PointerMode::Right => SHIFT,
|
|
||||||
PointerMode::Middle => data.alt_modifier,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
set_modifiers(app, data.modifiers);
|
|
||||||
|
|
||||||
send_key(app, *vk, true);
|
|
||||||
*pressed = true;
|
|
||||||
}
|
|
||||||
Some(KeyButtonData::Modifier { modifier, sticky }) => {
|
|
||||||
*sticky = data.modifiers & *modifier == 0;
|
|
||||||
data.modifiers |= *modifier;
|
|
||||||
key_click(app);
|
|
||||||
set_modifiers(app, data.modifiers);
|
|
||||||
}
|
|
||||||
Some(KeyButtonData::Macro { verbs }) => {
|
|
||||||
key_click(app);
|
|
||||||
for (vk, press) in verbs {
|
|
||||||
send_key(app, *vk, *press);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(KeyButtonData::Exec { program, args, .. }) => {
|
|
||||||
// Reap previous processes
|
|
||||||
data.processes
|
|
||||||
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
|
|
||||||
|
|
||||||
key_click(app);
|
|
||||||
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 }) => {
|
|
||||||
send_key(app, *vk, false);
|
|
||||||
*pressed = false;
|
|
||||||
|
|
||||||
for m in &AUTO_RELEASE_MODS {
|
|
||||||
if data.modifiers & *m != 0 {
|
|
||||||
data.modifiers &= !*m;
|
|
||||||
set_modifiers(app, data.modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(KeyButtonData::Modifier { modifier, sticky }) => {
|
|
||||||
if !*sticky {
|
|
||||||
data.modifiers &= !*modifier;
|
|
||||||
set_modifiers(app, data.modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(KeyButtonData::Exec {
|
|
||||||
release_program,
|
|
||||||
release_args,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
// Reap previous processes
|
|
||||||
data.processes
|
|
||||||
.retain_mut(|child| !matches!(child.try_wait(), Ok(Some(_))));
|
|
||||||
|
|
||||||
if let Some(program) = release_program {
|
|
||||||
if let Ok(child) = Command::new(program).args(release_args).spawn() {
|
|
||||||
data.processes.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static PRESS_COLOR: Vec4 = Vec4::new(198. / 255., 160. / 255., 246. / 255., 0.5);
|
|
||||||
|
|
||||||
fn test_highlight(
|
|
||||||
control: &Control<KeyboardData, KeyButtonData>,
|
|
||||||
data: &mut KeyboardData,
|
|
||||||
_app: &mut AppState,
|
|
||||||
) -> Option<Vec4> {
|
|
||||||
let pressed = match control.state.as_ref() {
|
|
||||||
Some(KeyButtonData::Key { pressed, .. }) => *pressed,
|
|
||||||
Some(KeyButtonData::Modifier { modifier, .. }) => data.modifiers & *modifier != 0,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if pressed {
|
|
||||||
Some(PRESS_COLOR)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct KeyboardData {
|
struct KeyboardData {
|
||||||
modifiers: KeyModifier,
|
modifiers: KeyModifier,
|
||||||
alt_modifier: KeyModifier,
|
alt_modifier: KeyModifier,
|
||||||
@@ -501,15 +429,15 @@ fn key_events_for_macro(macro_verbs: &Vec<String>) -> Vec<(VirtualKey, bool)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct KeyboardBackend {
|
struct KeyboardBackend {
|
||||||
canvas: Canvas<KeyboardData, KeyButtonData>,
|
panel: GuiPanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayBackend for KeyboardBackend {
|
impl OverlayBackend for KeyboardBackend {
|
||||||
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
|
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
|
||||||
self.canvas.set_interaction(interaction);
|
self.panel.set_interaction(interaction);
|
||||||
}
|
}
|
||||||
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
|
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
|
||||||
self.canvas.set_renderer(renderer);
|
self.panel.set_renderer(renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +448,7 @@ impl InteractionHandler for KeyboardBackend {
|
|||||||
hit: &crate::backend::input::PointerHit,
|
hit: &crate::backend::input::PointerHit,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
) {
|
) {
|
||||||
self.canvas.on_pointer(app, hit, pressed);
|
self.panel.on_pointer(app, hit, pressed);
|
||||||
}
|
}
|
||||||
fn on_scroll(
|
fn on_scroll(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -529,26 +457,26 @@ impl InteractionHandler for KeyboardBackend {
|
|||||||
delta_y: f32,
|
delta_y: f32,
|
||||||
delta_x: f32,
|
delta_x: f32,
|
||||||
) {
|
) {
|
||||||
self.canvas.on_scroll(app, hit, delta_y, delta_x);
|
self.panel.on_scroll(app, hit, delta_y, delta_x);
|
||||||
}
|
}
|
||||||
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
|
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
|
||||||
self.canvas.on_left(app, pointer);
|
self.panel.on_left(app, pointer);
|
||||||
}
|
}
|
||||||
fn on_hover(
|
fn on_hover(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &mut AppState,
|
app: &mut AppState,
|
||||||
hit: &crate::backend::input::PointerHit,
|
hit: &crate::backend::input::PointerHit,
|
||||||
) -> Option<crate::backend::input::Haptics> {
|
) -> Option<crate::backend::input::Haptics> {
|
||||||
self.canvas.on_hover(app, hit)
|
self.panel.on_hover(app, hit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayRenderer for KeyboardBackend {
|
impl OverlayRenderer for KeyboardBackend {
|
||||||
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
self.canvas.init(app)
|
self.panel.init(app)
|
||||||
}
|
}
|
||||||
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
||||||
self.canvas.should_render(app)
|
self.panel.should_render(app)
|
||||||
}
|
}
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -557,17 +485,31 @@ impl OverlayRenderer for KeyboardBackend {
|
|||||||
buf: &mut CommandBuffers,
|
buf: &mut CommandBuffers,
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
self.canvas.render(app, tgt, buf, alpha)
|
self.panel.render(app, tgt, buf, alpha)
|
||||||
}
|
}
|
||||||
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
||||||
self.canvas.frame_meta()
|
self.panel.frame_meta()
|
||||||
}
|
}
|
||||||
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
self.canvas.data_mut().modifiers = 0;
|
|
||||||
set_modifiers(app, 0);
|
set_modifiers(app, 0);
|
||||||
self.canvas.pause(app)
|
self.panel.pause(app)
|
||||||
}
|
}
|
||||||
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
self.canvas.resume(app)
|
self.panel.resume(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum KeyCapType {
|
||||||
|
/// Label is in center of keycap
|
||||||
|
Regular,
|
||||||
|
/// Label on the top
|
||||||
|
/// AltGr symbol on bottom
|
||||||
|
RegularAltGr,
|
||||||
|
/// Primary symbol on bottom
|
||||||
|
/// Shift symbol on top
|
||||||
|
Reversed,
|
||||||
|
/// Primary symbol on bottom-left
|
||||||
|
/// Shift symbol on top-left
|
||||||
|
/// AltGr symbol on bottom-right
|
||||||
|
ReversedAltGr,
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
|
buffer::{BufferUsage, Subbuffer},
|
||||||
command_buffer::CommandBufferUsage,
|
command_buffer::CommandBufferUsage,
|
||||||
device::Queue,
|
device::Queue,
|
||||||
format::Format,
|
format::Format,
|
||||||
image::{sampler::Filter, view::ImageView, Image},
|
image::{sampler::Filter, view::ImageView, Image},
|
||||||
pipeline::graphics::color_blend::AttachmentBlend,
|
pipeline::graphics::{color_blend::AttachmentBlend, input_assembly::PrimitiveTopology},
|
||||||
};
|
};
|
||||||
|
use wgui::gfx::{cmd::XferCommandBuffer, pass::WGfxPass, pipeline::WGfxPipeline, WGfx};
|
||||||
use wlx_capture::frame as wlx_frame;
|
use wlx_capture::frame as wlx_frame;
|
||||||
|
|
||||||
use wlx_capture::{
|
use wlx_capture::{
|
||||||
@@ -56,7 +58,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
config::{def_pw_tokens, GeneralConfig, PwTokenMap},
|
config::{def_pw_tokens, GeneralConfig, PwTokenMap},
|
||||||
graphics::{fourcc_to_vk, CommandBuffers, WlxGraphics, WlxPipeline, WlxUploadsBuffer},
|
graphics::{
|
||||||
|
dmabuf::{fourcc_to_vk, WGfxDmabuf},
|
||||||
|
upload_quad_vertices, CommandBuffers, ExtentExt, Vert2Uv,
|
||||||
|
},
|
||||||
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
||||||
state::{AppSession, AppState, KeyboardFocus, ScreenMeta},
|
state::{AppSession, AppState, KeyboardFocus, ScreenMeta},
|
||||||
};
|
};
|
||||||
@@ -153,36 +158,44 @@ impl InteractionHandler for ScreenInteractionHandler {
|
|||||||
fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
|
fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
struct MousePass {
|
||||||
|
pass: WGfxPass<Vert2Uv>,
|
||||||
|
buf_vert: Subbuffer<[Vert2Uv]>,
|
||||||
|
}
|
||||||
|
|
||||||
struct ScreenPipeline {
|
struct ScreenPipeline {
|
||||||
mouse: Option<Arc<ImageView>>,
|
mouse: Option<MousePass>,
|
||||||
pipeline: Arc<WlxPipeline>,
|
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||||
|
buf_alpha: Subbuffer<[f32]>,
|
||||||
extentf: [f32; 2],
|
extentf: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreenPipeline {
|
impl ScreenPipeline {
|
||||||
fn new(extent: &[u32; 3], app: &mut AppState) -> anyhow::Result<Self> {
|
fn new(extent: &[u32; 3], app: &mut AppState) -> anyhow::Result<Self> {
|
||||||
let Ok(shaders) = app.graphics.shared_shaders.read() else {
|
let pipeline = app.gfx.create_pipeline(
|
||||||
return Err(anyhow::anyhow!("Could not lock shared shaders for reading"));
|
app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
|
||||||
};
|
app.gfx_extras.shaders.get("frag_screen").unwrap().clone(), // want panic
|
||||||
|
app.gfx.surface_format,
|
||||||
let pipeline = app.graphics.create_pipeline(
|
|
||||||
shaders.get("vert_common").unwrap().clone(), // want panic
|
|
||||||
shaders.get("frag_screen").unwrap().clone(), // want panic
|
|
||||||
app.graphics.native_format,
|
|
||||||
Some(AttachmentBlend::default()),
|
Some(AttachmentBlend::default()),
|
||||||
|
PrimitiveTopology::TriangleStrip,
|
||||||
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let buf_alpha = app
|
||||||
|
.gfx
|
||||||
|
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
|
||||||
|
|
||||||
let extentf = [extent[0] as f32, extent[1] as f32];
|
let extentf = [extent[0] as f32, extent[1] as f32];
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
mouse: None,
|
mouse: None,
|
||||||
pipeline,
|
pipeline,
|
||||||
|
buf_alpha,
|
||||||
extentf,
|
extentf,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_mouse_initialized(&mut self, uploads: &mut WlxUploadsBuffer) -> anyhow::Result<()> {
|
fn ensure_mouse_initialized(&mut self, cmd_xfer: &mut XferCommandBuffer) -> anyhow::Result<()> {
|
||||||
if self.mouse.is_some() {
|
if self.mouse.is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -195,9 +208,28 @@ impl ScreenPipeline {
|
|||||||
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
|
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mouse_tex =
|
let image =
|
||||||
uploads.texture2d_raw(4, 4, vulkano::format::Format::R8G8B8A8_UNORM, &mouse_bytes)?;
|
cmd_xfer.upload_image(4, 4, vulkano::format::Format::R8G8B8A8_UNORM, &mouse_bytes)?;
|
||||||
self.mouse = Some(ImageView::new_default(mouse_tex)?);
|
|
||||||
|
let view = ImageView::new_default(image)?;
|
||||||
|
|
||||||
|
let buf_vert = cmd_xfer
|
||||||
|
.graphics
|
||||||
|
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::VERTEX_BUFFER, 4)?;
|
||||||
|
|
||||||
|
let set0 = self.pipeline.uniform_sampler(0, view, Filter::Nearest)?;
|
||||||
|
|
||||||
|
let set1 = self.pipeline.buffer(1, self.buf_alpha.clone())?;
|
||||||
|
|
||||||
|
let pass = self.pipeline.create_pass(
|
||||||
|
self.extentf,
|
||||||
|
buf_vert.clone(),
|
||||||
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![set0, set1],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.mouse = Some(MousePass { pass, buf_vert });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,23 +245,31 @@ impl ScreenPipeline {
|
|||||||
let view = ImageView::new_default(image)?;
|
let view = ImageView::new_default(image)?;
|
||||||
let set0 = self
|
let set0 = self
|
||||||
.pipeline
|
.pipeline
|
||||||
.uniform_sampler(0, view, app.graphics.texture_filtering)?;
|
.uniform_sampler(0, view, app.gfx.texture_filter)?;
|
||||||
let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?;
|
|
||||||
let pass = self
|
self.buf_alpha.write()?[0] = alpha;
|
||||||
.pipeline
|
|
||||||
.create_pass_for_target(tgt.clone(), vec![set0, set1])?;
|
let set1 = self.pipeline.buffer(1, self.buf_alpha.clone())?;
|
||||||
|
let pass = self.pipeline.create_pass(
|
||||||
|
tgt.extent_f32(),
|
||||||
|
app.gfx_extras.quad_verts.clone(),
|
||||||
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![set0, set1],
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut cmd = app
|
let mut cmd = app
|
||||||
.graphics
|
.gfx
|
||||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
cmd.begin_rendering(tgt)?;
|
cmd.begin_rendering(tgt)?;
|
||||||
cmd.run_ref(&pass)?;
|
cmd.run_ref(&pass)?;
|
||||||
|
|
||||||
if let (Some(mouse), Some(mouse_view)) = (mouse, self.mouse.clone()) {
|
if let (Some(mouse), Some(pass)) = (mouse, self.mouse.as_mut()) {
|
||||||
let size = CURSOR_SIZE * self.extentf[1];
|
let size = CURSOR_SIZE * self.extentf[1];
|
||||||
let half_size = size * 0.5;
|
let half_size = size * 0.5;
|
||||||
|
|
||||||
let vertex_buffer = app.graphics.upload_verts(
|
upload_quad_vertices(
|
||||||
|
&mut pass.buf_vert,
|
||||||
self.extentf[0],
|
self.extentf[0],
|
||||||
self.extentf[1],
|
self.extentf[1],
|
||||||
mouse.x.mul_add(self.extentf[0], -half_size),
|
mouse.x.mul_add(self.extentf[0], -half_size),
|
||||||
@@ -238,20 +278,7 @@ impl ScreenPipeline {
|
|||||||
size,
|
size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let set0 = self
|
cmd.run_ref(&pass.pass)?;
|
||||||
.pipeline
|
|
||||||
.uniform_sampler(0, mouse_view, Filter::Nearest)?;
|
|
||||||
|
|
||||||
let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?;
|
|
||||||
|
|
||||||
let pass = self.pipeline.create_pass(
|
|
||||||
self.extentf,
|
|
||||||
vertex_buffer,
|
|
||||||
app.graphics.quad_indices.clone(),
|
|
||||||
vec![set0, set1],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
cmd.run_ref(&pass)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.end_rendering()?;
|
cmd.end_rendering()?;
|
||||||
@@ -296,7 +323,7 @@ impl ScreenRenderer {
|
|||||||
pub fn new_wlr_dmabuf(output: &WlxOutput, app: &AppState) -> Option<Self> {
|
pub fn new_wlr_dmabuf(output: &WlxOutput, app: &AppState) -> Option<Self> {
|
||||||
let client = WlxClient::new()?;
|
let client = WlxClient::new()?;
|
||||||
let capture = new_wlx_capture!(
|
let capture = new_wlx_capture!(
|
||||||
app.graphics.capture_queue,
|
app.gfx_extras.queue_capture,
|
||||||
WlrDmabufCapture::new(client, output.id)
|
WlrDmabufCapture::new(client, output.id)
|
||||||
);
|
);
|
||||||
Some(Self::new_raw(output.name.clone(), capture))
|
Some(Self::new_raw(output.name.clone(), capture))
|
||||||
@@ -306,7 +333,7 @@ impl ScreenRenderer {
|
|||||||
pub fn new_wlr_screencopy(output: &WlxOutput, app: &AppState) -> Option<Self> {
|
pub fn new_wlr_screencopy(output: &WlxOutput, app: &AppState) -> Option<Self> {
|
||||||
let client = WlxClient::new()?;
|
let client = WlxClient::new()?;
|
||||||
let capture = new_wlx_capture!(
|
let capture = new_wlx_capture!(
|
||||||
app.graphics.capture_queue,
|
app.gfx_extras.queue_capture,
|
||||||
WlrScreencopyCapture::new(client, output.id)
|
WlrScreencopyCapture::new(client, output.id)
|
||||||
);
|
);
|
||||||
Some(Self::new_raw(output.name.clone(), capture))
|
Some(Self::new_raw(output.name.clone(), capture))
|
||||||
@@ -340,7 +367,7 @@ impl ScreenRenderer {
|
|||||||
let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
|
let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
|
||||||
|
|
||||||
let capture = new_wlx_capture!(
|
let capture = new_wlx_capture!(
|
||||||
app.graphics.capture_queue,
|
app.gfx_extras.queue_capture,
|
||||||
PipewireCapture::new(name, node_id)
|
PipewireCapture::new(name, node_id)
|
||||||
);
|
);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -351,8 +378,10 @@ impl ScreenRenderer {
|
|||||||
|
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
pub fn new_xshm(screen: Arc<XshmScreen>, app: &AppState) -> Self {
|
pub fn new_xshm(screen: Arc<XshmScreen>, app: &AppState) -> Self {
|
||||||
let capture =
|
let capture = new_wlx_capture!(
|
||||||
new_wlx_capture!(app.graphics.capture_queue, XshmCapture::new(screen.clone()));
|
app.gfx_extras.queue_capture,
|
||||||
|
XshmCapture::new(screen.clone())
|
||||||
|
);
|
||||||
Self::new_raw(screen.name.clone(), capture)
|
Self::new_raw(screen.name.clone(), capture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,7 +389,7 @@ impl ScreenRenderer {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WlxCaptureIn {
|
pub struct WlxCaptureIn {
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
graphics: Arc<WlxGraphics>,
|
gfx: Arc<WGfx>,
|
||||||
queue: Arc<Queue>,
|
queue: Arc<Queue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,9 +407,9 @@ fn upload_image(
|
|||||||
format: Format,
|
format: Format,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Option<Arc<Image>> {
|
) -> Option<Arc<Image>> {
|
||||||
let mut upload = match me
|
let mut cmd_xfer = match me
|
||||||
.graphics
|
.gfx
|
||||||
.create_uploads_command_buffer(me.queue.clone(), CommandBufferUsage::OneTimeSubmit)
|
.create_xfer_command_buffer_with_queue(me.queue.clone(), CommandBufferUsage::OneTimeSubmit)
|
||||||
{
|
{
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -388,7 +417,7 @@ fn upload_image(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let image = match upload.texture2d_raw(width, height, format, data) {
|
let image = match cmd_xfer.upload_image(width, height, format, data) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{}: Could not create vkImage: {:?}", me.name, e);
|
log::error!("{}: Could not create vkImage: {:?}", me.name, e);
|
||||||
@@ -396,7 +425,7 @@ fn upload_image(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = upload.build_and_execute_now() {
|
if let Err(e) = cmd_xfer.build_and_execute_now() {
|
||||||
log::error!("{}: Could not execute upload: {:?}", me.name, e);
|
log::error!("{}: Could not execute upload: {:?}", me.name, e);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -413,7 +442,7 @@ fn receive_callback(me: &WlxCaptureIn, frame: wlx_frame::WlxFrame) -> Option<Wlx
|
|||||||
}
|
}
|
||||||
log::trace!("{}: New DMA-buf frame", me.name);
|
log::trace!("{}: New DMA-buf frame", me.name);
|
||||||
let format = frame.format;
|
let format = frame.format;
|
||||||
match me.graphics.dmabuf_texture(frame) {
|
match me.gfx.dmabuf_texture(frame) {
|
||||||
Ok(image) => Some(WlxCaptureOut {
|
Ok(image) => Some(WlxCaptureOut {
|
||||||
image,
|
image,
|
||||||
format,
|
format,
|
||||||
@@ -499,7 +528,7 @@ impl OverlayRenderer for ScreenRenderer {
|
|||||||
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
||||||
if !self.capture.is_ready() {
|
if !self.capture.is_ready() {
|
||||||
let supports_dmabuf = app
|
let supports_dmabuf = app
|
||||||
.graphics
|
.gfx
|
||||||
.device
|
.device
|
||||||
.enabled_extensions()
|
.enabled_extensions()
|
||||||
.ext_external_memory_dma_buf
|
.ext_external_memory_dma_buf
|
||||||
@@ -512,13 +541,13 @@ impl OverlayRenderer for ScreenRenderer {
|
|||||||
|
|
||||||
let dmabuf_formats = if !supports_dmabuf {
|
let dmabuf_formats = if !supports_dmabuf {
|
||||||
log::info!("Capture method does not support DMA-buf");
|
log::info!("Capture method does not support DMA-buf");
|
||||||
if app.graphics.capture_queue.is_none() {
|
if app.gfx_extras.queue_capture.is_none() {
|
||||||
log::warn!("Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance.");
|
log::warn!("Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance.");
|
||||||
}
|
}
|
||||||
&Vec::new()
|
&Vec::new()
|
||||||
} else if !allow_dmabuf {
|
} else if !allow_dmabuf {
|
||||||
log::info!("Not using DMA-buf capture due to {capture_method}");
|
log::info!("Not using DMA-buf capture due to {capture_method}");
|
||||||
if app.graphics.capture_queue.is_none() {
|
if app.gfx_extras.queue_capture.is_none() {
|
||||||
log::warn!("Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance.");
|
log::warn!("Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance.");
|
||||||
}
|
}
|
||||||
&Vec::new()
|
&Vec::new()
|
||||||
@@ -528,17 +557,17 @@ impl OverlayRenderer for ScreenRenderer {
|
|||||||
);
|
);
|
||||||
log::warn!("echo 'capture_method: pw_fallback' > ~/.config/wlxoverlay/conf.d/pw_fallback.yaml");
|
log::warn!("echo 'capture_method: pw_fallback' > ~/.config/wlxoverlay/conf.d/pw_fallback.yaml");
|
||||||
|
|
||||||
&app.graphics.drm_formats
|
&app.gfx_extras.drm_formats
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_data = WlxCaptureIn {
|
let user_data = WlxCaptureIn {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
graphics: app.graphics.clone(),
|
gfx: app.gfx.clone(),
|
||||||
queue: app
|
queue: app
|
||||||
.graphics
|
.gfx_extras
|
||||||
.capture_queue
|
.queue_capture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or_else(|| &app.graphics.transfer_queue)
|
.unwrap_or_else(|| &app.gfx.queue_xfer)
|
||||||
.clone(),
|
.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -560,10 +589,9 @@ impl OverlayRenderer for ScreenRenderer {
|
|||||||
if let (Some(capture), None) = (self.cur_frame.as_ref(), self.pipeline.as_ref()) {
|
if let (Some(capture), None) = (self.cur_frame.as_ref(), self.pipeline.as_ref()) {
|
||||||
self.pipeline = Some({
|
self.pipeline = Some({
|
||||||
let mut pipeline = ScreenPipeline::new(&capture.image.extent(), app)?;
|
let mut pipeline = ScreenPipeline::new(&capture.image.extent(), app)?;
|
||||||
let mut upload = app.graphics.create_uploads_command_buffer(
|
let mut upload = app
|
||||||
app.graphics.transfer_queue.clone(),
|
.gfx
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
)?;
|
|
||||||
pipeline.ensure_mouse_initialized(&mut upload)?;
|
pipeline.ensure_mouse_initialized(&mut upload)?;
|
||||||
upload.build_and_execute_now()?;
|
upload.build_and_execute_now()?;
|
||||||
pipeline
|
pipeline
|
||||||
@@ -931,7 +959,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
|
|||||||
let renderer = ScreenRenderer::new_raw(
|
let renderer = ScreenRenderer::new_raw(
|
||||||
m.name.clone(),
|
m.name.clone(),
|
||||||
new_wlx_capture!(
|
new_wlx_capture!(
|
||||||
app.graphics.capture_queue,
|
app.gfx_extras.queue_capture,
|
||||||
PipewireCapture::new(m.name.clone(), s.node_id)
|
PipewireCapture::new(m.name.clone(), s.node_id)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,19 @@ use std::{
|
|||||||
use glam::{vec3a, Quat};
|
use glam::{vec3a, Quat};
|
||||||
use idmap_derive::IntegerId;
|
use idmap_derive::IntegerId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use wgui::{
|
||||||
|
parser::parse_color_hex,
|
||||||
|
renderer_vk::text::{FontWeight, TextStyle},
|
||||||
|
taffy::{
|
||||||
|
self,
|
||||||
|
prelude::{auto, length, percent},
|
||||||
|
},
|
||||||
|
widget::{
|
||||||
|
rectangle::{Rectangle, RectangleParams},
|
||||||
|
text::{TextLabel, TextParams},
|
||||||
|
util::WLength,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
@@ -15,7 +28,7 @@ use crate::{
|
|||||||
overlay::{OverlayBackend, OverlayState, Positioning, Z_ORDER_TOAST},
|
overlay::{OverlayBackend, OverlayState, Positioning, Z_ORDER_TOAST},
|
||||||
task::TaskType,
|
task::TaskType,
|
||||||
},
|
},
|
||||||
gui::{canvas::builder::CanvasBuilder, color_parse},
|
gui::panel::GuiPanel,
|
||||||
state::{AppState, LeftRight},
|
state::{AppState, LeftRight},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,8 +53,8 @@ pub enum ToastTopic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Toast {
|
pub struct Toast {
|
||||||
pub title: Arc<str>,
|
pub title: String,
|
||||||
pub body: Arc<str>,
|
pub body: String,
|
||||||
pub opacity: f32,
|
pub opacity: f32,
|
||||||
pub timeout: f32,
|
pub timeout: f32,
|
||||||
pub sound: bool,
|
pub sound: bool,
|
||||||
@@ -50,7 +63,7 @@ pub struct Toast {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl Toast {
|
impl Toast {
|
||||||
pub const fn new(topic: ToastTopic, title: Arc<str>, body: Arc<str>) -> Self {
|
pub const fn new(topic: ToastTopic, title: String, body: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
@@ -117,6 +130,7 @@ impl Toast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
||||||
let current_method = app
|
let current_method = app
|
||||||
.session
|
.session
|
||||||
@@ -153,63 +167,79 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
|
|||||||
toast.title
|
toast.title
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut size = if toast.body.is_empty() {
|
let mut panel = GuiPanel::new_blank(app, 600, 200).ok()?;
|
||||||
let (w, h) = app
|
|
||||||
.fc
|
|
||||||
.get_text_size(&title, FONT_SIZE, app.graphics.clone())
|
|
||||||
.ok()?;
|
|
||||||
(w, h + 20.)
|
|
||||||
} else {
|
|
||||||
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.)
|
|
||||||
};
|
|
||||||
|
|
||||||
let og_width = size.0;
|
let (rect, _) = panel
|
||||||
size.0 += PADDING.0 * 2.;
|
.layout
|
||||||
|
.add_child(
|
||||||
let mut canvas = CanvasBuilder::<(), ()>::new(
|
panel.layout.root_widget,
|
||||||
size.0 as _,
|
Rectangle::create(RectangleParams {
|
||||||
size.1 as _,
|
color: parse_color_hex("#1e2030").unwrap(),
|
||||||
app.graphics.clone(),
|
border_color: parse_color_hex("#5e7090").unwrap(),
|
||||||
app.graphics.native_format,
|
border: 1.0,
|
||||||
(),
|
round: WLength::Units(4.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
align_items: Some(taffy::AlignItems::Center),
|
||||||
|
justify_content: Some(taffy::JustifyContent::Center),
|
||||||
|
padding: length(1.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
canvas.font_size = FONT_SIZE;
|
let _ = panel.layout.add_child(
|
||||||
canvas.fg_color = color_parse("#cad3f5").unwrap(); // want panic
|
rect,
|
||||||
canvas.bg_color = color_parse("#1e2030").unwrap(); // want panic
|
TextLabel::create(TextParams {
|
||||||
canvas.panel(0., 0., size.0, size.1, 16.);
|
content: title,
|
||||||
|
style: TextStyle {
|
||||||
|
color: parse_color_hex("#ffffff"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: auto(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if toast.body.is_empty() {
|
let _ = panel.layout.add_child(
|
||||||
canvas.label_centered(PADDING.0, 0., og_width, size.1, 16., title);
|
rect,
|
||||||
} else {
|
TextLabel::create(TextParams {
|
||||||
canvas.label(PADDING.0, 54., og_width, size.1 - 54., 3., toast.body);
|
content: toast.body,
|
||||||
|
style: TextStyle {
|
||||||
canvas.fg_color = color_parse("#b8c0e0").unwrap(); // want panic
|
weight: Some(FontWeight::Bold),
|
||||||
canvas.bg_color = color_parse("#24273a").unwrap(); // want panic
|
color: parse_color_hex("#eeeeee"),
|
||||||
canvas.panel(0., 0., size.0, 30., 16.);
|
..Default::default()
|
||||||
canvas.label_centered(PADDING.0, 16., og_width, FONT_SIZE as f32 + 2., 16., title);
|
},
|
||||||
}
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: auto(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let state = OverlayState {
|
let state = OverlayState {
|
||||||
name: TOAST_NAME.clone(),
|
name: TOAST_NAME.clone(),
|
||||||
want_visible: true,
|
want_visible: true,
|
||||||
spawn_scale: size.0 * PIXELS_TO_METERS,
|
spawn_scale: (panel.width as f32) * PIXELS_TO_METERS,
|
||||||
spawn_rotation,
|
spawn_rotation,
|
||||||
spawn_point,
|
spawn_point,
|
||||||
z_order: Z_ORDER_TOAST,
|
z_order: Z_ORDER_TOAST,
|
||||||
positioning,
|
positioning,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let backend = Box::new(canvas.build());
|
let backend = Box::new(panel);
|
||||||
|
|
||||||
Some((state, backend))
|
Some((state, backend))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,54 @@
|
|||||||
use glam::Vec3A;
|
use glam::Vec3A;
|
||||||
|
use wgui::{
|
||||||
|
parser::parse_color_hex,
|
||||||
|
taffy::{
|
||||||
|
self,
|
||||||
|
prelude::{length, percent},
|
||||||
|
},
|
||||||
|
widget::{
|
||||||
|
rectangle::{Rectangle, RectangleParams},
|
||||||
|
util::WLength,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::overlay::{ui_transform, OverlayData, OverlayState, Positioning, Z_ORDER_WATCH},
|
backend::overlay::{ui_transform, OverlayData, OverlayState, Positioning, Z_ORDER_WATCH},
|
||||||
config::{load_known_yaml, ConfigType},
|
gui::panel::GuiPanel,
|
||||||
gui::{
|
|
||||||
canvas::Canvas,
|
|
||||||
modular::{modular_canvas, ModularData, ModularUiConfig},
|
|
||||||
},
|
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const WATCH_NAME: &str = "watch";
|
pub const WATCH_NAME: &str = "watch";
|
||||||
|
|
||||||
pub fn create_watch<O>(state: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
pub fn create_watch<O>(app: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
||||||
where
|
where
|
||||||
O: Default,
|
O: Default,
|
||||||
{
|
{
|
||||||
let config = load_known_yaml::<ModularUiConfig>(ConfigType::Watch);
|
let mut panel = GuiPanel::new_blank(app, 400, 200)?;
|
||||||
|
|
||||||
|
let (_, _) = panel.layout.add_child(
|
||||||
|
panel.layout.root_widget,
|
||||||
|
Rectangle::create(RectangleParams {
|
||||||
|
color: wgui::drawing::Color::new(0., 0., 0., 0.5),
|
||||||
|
border_color: parse_color_hex("#00ffff").unwrap(),
|
||||||
|
border: 2.0,
|
||||||
|
round: WLength::Units(4.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
taffy::Style {
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: percent(1.0),
|
||||||
|
},
|
||||||
|
align_items: Some(taffy::AlignItems::Center),
|
||||||
|
justify_content: Some(taffy::JustifyContent::Center),
|
||||||
|
padding: length(4.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let positioning = Positioning::FollowHand {
|
let positioning = Positioning::FollowHand {
|
||||||
hand: state.session.config.watch_hand as _,
|
hand: app.session.config.watch_hand as _,
|
||||||
lerp: 1.0,
|
lerp: 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,27 +58,18 @@ where
|
|||||||
want_visible: true,
|
want_visible: true,
|
||||||
interactable: true,
|
interactable: true,
|
||||||
z_order: Z_ORDER_WATCH,
|
z_order: Z_ORDER_WATCH,
|
||||||
spawn_scale: config.width,
|
spawn_scale: 0.115, //TODO:configurable
|
||||||
spawn_point: state.session.config.watch_pos,
|
spawn_point: app.session.config.watch_pos,
|
||||||
spawn_rotation: state.session.config.watch_rot,
|
spawn_rotation: app.session.config.watch_rot,
|
||||||
interaction_transform: ui_transform(config.size),
|
interaction_transform: ui_transform([400, 200]),
|
||||||
positioning,
|
positioning,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(create_watch_canvas(Some(config), state)?),
|
backend: Box::new(panel),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_watch_canvas(
|
|
||||||
config: Option<ModularUiConfig>,
|
|
||||||
state: &mut AppState,
|
|
||||||
) -> anyhow::Result<Canvas<(), ModularData>> {
|
|
||||||
let config = config.unwrap_or_else(|| load_known_yaml::<ModularUiConfig>(ConfigType::Watch));
|
|
||||||
|
|
||||||
modular_canvas(config.size, &config.elements, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayData<D>)
|
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayData<D>)
|
||||||
where
|
where
|
||||||
D: Default,
|
D: Default,
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ use glam::{vec3a, Affine2, Vec3, Vec3A};
|
|||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
command_buffer::CommandBufferUsage,
|
command_buffer::CommandBufferUsage,
|
||||||
image::{view::ImageView, SubresourceLayout},
|
format::Format,
|
||||||
|
image::{view::ImageView, Image, ImageTiling, SubresourceLayout},
|
||||||
|
pipeline::graphics::input_assembly::PrimitiveTopology,
|
||||||
};
|
};
|
||||||
use wayvr_ipc::packet_server::{self, PacketServer, WvrStateChanged};
|
use wayvr_ipc::packet_server::{self, PacketServer, WvrStateChanged};
|
||||||
|
use wgui::gfx::{pipeline::WGfxPipeline, WGfx};
|
||||||
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
|
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -19,12 +22,11 @@ use crate::{
|
|||||||
wayvr::{
|
wayvr::{
|
||||||
self, display,
|
self, display,
|
||||||
server_ipc::{gen_args_vec, gen_env_vec},
|
server_ipc::{gen_args_vec, gen_env_vec},
|
||||||
WayVR,
|
WayVR, WayVRAction, WayVRDisplayClickAction,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
config_wayvr,
|
config_wayvr,
|
||||||
graphics::{CommandBuffers, WlxGraphics, WlxPipeline},
|
graphics::{dmabuf::WGfxDmabuf, CommandBuffers, ExtentExt, Vert2Uv},
|
||||||
gui::modular::button::{WayVRAction, WayVRDisplayClickAction},
|
|
||||||
state::{self, AppState, KeyboardFocus},
|
state::{self, AppState, KeyboardFocus},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,15 +177,15 @@ impl InteractionHandler for WayVRInteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ImageData {
|
struct ImageData {
|
||||||
vk_image: Arc<vulkano::image::Image>,
|
vk_image: Arc<Image>,
|
||||||
vk_image_view: Arc<vulkano::image::view::ImageView>,
|
vk_image_view: Arc<ImageView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WayVRRenderer {
|
pub struct WayVRRenderer {
|
||||||
pipeline: Arc<WlxPipeline>,
|
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||||
image: Option<ImageData>,
|
image: Option<ImageData>,
|
||||||
context: Rc<RefCell<WayVRContext>>,
|
context: Rc<RefCell<WayVRContext>>,
|
||||||
graphics: Arc<WlxGraphics>,
|
graphics: Arc<WGfx>,
|
||||||
resolution: [u16; 2],
|
resolution: [u16; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,21 +196,19 @@ impl WayVRRenderer {
|
|||||||
display: wayvr::display::DisplayHandle,
|
display: wayvr::display::DisplayHandle,
|
||||||
resolution: [u16; 2],
|
resolution: [u16; 2],
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let Ok(shaders) = app.graphics.shared_shaders.read() else {
|
let pipeline = app.gfx.create_pipeline(
|
||||||
anyhow::bail!("Failed to lock shared shaders for reading");
|
app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
|
||||||
};
|
app.gfx_extras.shaders.get("frag_srgb").unwrap().clone(), // want panic
|
||||||
|
app.gfx.surface_format,
|
||||||
let pipeline = app.graphics.create_pipeline(
|
|
||||||
shaders.get("vert_common").unwrap().clone(), // want panic
|
|
||||||
shaders.get("frag_srgb").unwrap().clone(), // want panic
|
|
||||||
app.graphics.native_format,
|
|
||||||
None,
|
None,
|
||||||
|
PrimitiveTopology::TriangleStrip,
|
||||||
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
pipeline,
|
pipeline,
|
||||||
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display))),
|
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display))),
|
||||||
graphics: app.graphics.clone(),
|
graphics: app.gfx.clone(),
|
||||||
image: None,
|
image: None,
|
||||||
resolution,
|
resolution,
|
||||||
})
|
})
|
||||||
@@ -574,15 +574,14 @@ impl WayVRRenderer {
|
|||||||
&mut self,
|
&mut self,
|
||||||
data: &wayvr::egl_data::RenderSoftwarePixelsData,
|
data: &wayvr::egl_data::RenderSoftwarePixelsData,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut upload = self.graphics.create_uploads_command_buffer(
|
let mut upload = self
|
||||||
self.graphics.transfer_queue.clone(),
|
.graphics
|
||||||
CommandBufferUsage::OneTimeSubmit,
|
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
)?;
|
|
||||||
|
|
||||||
let tex = upload.texture2d_raw(
|
let tex = upload.upload_image(
|
||||||
u32::from(data.width),
|
u32::from(data.width),
|
||||||
u32::from(data.height),
|
u32::from(data.height),
|
||||||
vulkano::format::Format::R8G8B8A8_UNORM,
|
Format::R8G8B8A8_UNORM,
|
||||||
&data.data,
|
&data.data,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -644,7 +643,7 @@ impl WayVRRenderer {
|
|||||||
|
|
||||||
let tex = self.graphics.dmabuf_texture_ex(
|
let tex = self.graphics.dmabuf_texture_ex(
|
||||||
frame,
|
frame,
|
||||||
vulkano::image::ImageTiling::DrmFormatModifier,
|
ImageTiling::DrmFormatModifier,
|
||||||
layouts,
|
layouts,
|
||||||
&data.mod_info.modifiers,
|
&data.mod_info.modifiers,
|
||||||
)?;
|
)?;
|
||||||
@@ -732,18 +731,22 @@ impl OverlayRenderer for WayVRRenderer {
|
|||||||
let set0 = self.pipeline.uniform_sampler(
|
let set0 = self.pipeline.uniform_sampler(
|
||||||
0,
|
0,
|
||||||
image.vk_image_view.clone(),
|
image.vk_image_view.clone(),
|
||||||
app.graphics.texture_filtering,
|
app.gfx.texture_filter,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?;
|
let set1 = self.pipeline.uniform_buffer_upload(1, vec![alpha])?;
|
||||||
|
|
||||||
let pass = self
|
let pass = self.pipeline.create_pass(
|
||||||
.pipeline
|
tgt.extent_f32(),
|
||||||
.create_pass_for_target(tgt.clone(), vec![set0, set1])?;
|
app.gfx_extras.quad_verts.clone(),
|
||||||
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![set0, set1],
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut cmd_buffer = app
|
let mut cmd_buffer = app
|
||||||
.graphics
|
.gfx
|
||||||
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
cmd_buffer.begin_rendering(tgt)?;
|
cmd_buffer.begin_rendering(tgt)?;
|
||||||
cmd_buffer.run_ref(&pass)?;
|
cmd_buffer.run_ref(&pass)?;
|
||||||
cmd_buffer.end_rendering()?;
|
cmd_buffer.end_rendering()?;
|
||||||
|
|||||||
24
src/shaders/color.frag
Normal file
24
src/shaders/color.frag
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#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;
|
||||||
|
uniform vec2 corner_radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color.r = corner_radius.r;
|
||||||
|
out_color = in_color;
|
||||||
|
|
||||||
|
vec2 uv_circ = ((1. - corner_radius) - (abs(in_uv + vec2(-0.5)) * 2.))/corner_radius;
|
||||||
|
float dist = length(uv_circ);
|
||||||
|
|
||||||
|
out_color.a = mix(out_color.a, 0.,
|
||||||
|
float(dist > 1.)
|
||||||
|
* float(uv_circ.x < 0.)
|
||||||
|
* float(uv_circ.y < 0.));
|
||||||
|
}
|
||||||
19
src/shaders/grid.frag
Normal file
19
src/shaders/grid.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;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
float fade = max(1.0 - 2.0 * length(in_uv.xy + vec2(-0.5, -0.5)), 0.0);
|
||||||
|
float grid;
|
||||||
|
|
||||||
|
if (fract(in_uv.x / 0.0005) < 0.01 || fract(in_uv.y / 0.0005) < 0.01) {
|
||||||
|
grid = 1.0;
|
||||||
|
} else {
|
||||||
|
grid = 0.0;
|
||||||
|
}
|
||||||
|
out_color = vec4(1.0, 1.0, 1.0, grid * fade);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,266 +1,34 @@
|
|||||||
pub mod vert_common {
|
pub mod vert_quad {
|
||||||
vulkano_shaders::shader! {
|
vulkano_shaders::shader! {
|
||||||
ty: "vertex",
|
ty: "vertex",
|
||||||
src: r"#version 310 es
|
path: "src/shaders/quad.vert"
|
||||||
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 {
|
pub mod frag_color {
|
||||||
vulkano_shaders::shader! {
|
vulkano_shaders::shader! {
|
||||||
ty: "fragment",
|
ty: "fragment",
|
||||||
src: r"#version 310 es
|
path: "src/shaders/color.frag",
|
||||||
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;
|
|
||||||
uniform vec2 corner_radius;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
out_color.r = corner_radius.r;
|
|
||||||
out_color = in_color;
|
|
||||||
|
|
||||||
vec2 uv_circ = ((1. - corner_radius) - (abs(in_uv + vec2(-0.5)) * 2.))/corner_radius;
|
|
||||||
float dist = length(uv_circ);
|
|
||||||
|
|
||||||
out_color.a = mix(out_color.a, 0.,
|
|
||||||
float(dist > 1.)
|
|
||||||
* float(uv_circ.x < 0.)
|
|
||||||
* float(uv_circ.y < 0.));
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//layout (location = 1) in float corner_radius;
|
|
||||||
//out_color = in_color;
|
|
||||||
// Some equation that determines whether to keep the pixel
|
|
||||||
// Use Lerp not if
|
|
||||||
//out_color.a = 0;
|
|
||||||
|
|
||||||
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_sprite2 {
|
|
||||||
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 UniBlock {
|
|
||||||
uniform vec4 st;
|
|
||||||
uniform vec4 mul;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
out_color = texture(in_texture, (in_uv * st.xy) + st.zw) * mul;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod frag_sprite2_hl {
|
|
||||||
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 UniBlock {
|
|
||||||
uniform vec4 st;
|
|
||||||
uniform vec4 mul;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
out_color = texture(in_texture, (in_uv * st.xy) + st.zw).a * mul;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
out_color = texture(in_texture, in_uv);
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod frag_grid {
|
pub mod frag_grid {
|
||||||
vulkano_shaders::shader! {
|
vulkano_shaders::shader! {
|
||||||
ty: "fragment",
|
ty: "fragment",
|
||||||
src: r"#version 310 es
|
path: "src/shaders/grid.frag",
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout (location = 0) in vec2 in_uv;
|
|
||||||
layout (location = 0) out vec4 out_color;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
float fade = max(1.0 - 2.0 * length(in_uv.xy + vec2(-0.5, -0.5)), 0.0);
|
|
||||||
float grid;
|
|
||||||
|
|
||||||
if (fract(in_uv.x / 0.0005) < 0.01 || fract(in_uv.y / 0.0005) < 0.01) {
|
|
||||||
grid = 1.0;
|
|
||||||
} else {
|
|
||||||
grid = 0.0;
|
|
||||||
}
|
|
||||||
out_color = vec4(1.0, 1.0, 1.0, grid * fade);
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod frag_screen {
|
pub mod frag_screen {
|
||||||
vulkano_shaders::shader! {
|
vulkano_shaders::shader! {
|
||||||
ty: "fragment",
|
ty: "fragment",
|
||||||
src: r"#version 310 es
|
path: "src/shaders/screen.frag",
|
||||||
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 AlphaBlock {
|
|
||||||
uniform float alpha;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
out_color = texture(in_texture, in_uv);
|
|
||||||
out_color.a = alpha;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod frag_srgb {
|
pub mod frag_srgb {
|
||||||
vulkano_shaders::shader! {
|
vulkano_shaders::shader! {
|
||||||
ty: "fragment",
|
ty: "fragment",
|
||||||
src: r"#version 310 es
|
path: "src/shaders/srgb.frag",
|
||||||
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 AlphaBlock {
|
|
||||||
uniform float alpha;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
out_color.a *= alpha;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod frag_swapchain {
|
|
||||||
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 AlphaBlock {
|
|
||||||
uniform float alpha;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
out_color = texture(in_texture, in_uv);
|
|
||||||
out_color.a *= alpha;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod frag_line {
|
|
||||||
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;
|
|
||||||
uniform vec2 unused;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
out_color = in_color;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/shaders/quad.vert
Normal file
11
src/shaders/quad.vert
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#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.);
|
||||||
|
}
|
||||||
17
src/shaders/screen.frag
Normal file
17
src/shaders/screen.frag
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#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 AlphaBlock {
|
||||||
|
uniform float alpha;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = texture(in_texture, in_uv);
|
||||||
|
out_color.a = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
24
src/shaders/srgb.frag
Normal file
24
src/shaders/srgb.frag
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#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 AlphaBlock {
|
||||||
|
uniform float alpha;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
out_color.a *= alpha;
|
||||||
|
}
|
||||||
|
|
||||||
60
src/state.rs
60
src/state.rs
@@ -1,4 +1,3 @@
|
|||||||
use anyhow::bail;
|
|
||||||
use glam::Affine3A;
|
use glam::Affine3A;
|
||||||
use idmap::IdMap;
|
use idmap::IdMap;
|
||||||
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
||||||
@@ -6,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::{io::Cursor, sync::Arc};
|
use std::{io::Cursor, sync::Arc};
|
||||||
use vulkano::image::view::ImageView;
|
use vulkano::image::view::ImageView;
|
||||||
|
use wgui::gfx::WGfx;
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
use {
|
use {
|
||||||
@@ -21,14 +21,9 @@ use crate::{
|
|||||||
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
||||||
config::{AStrMap, GeneralConfig},
|
config::{AStrMap, GeneralConfig},
|
||||||
config_io,
|
config_io,
|
||||||
graphics::WlxGraphics,
|
graphics::WGfxExtras,
|
||||||
gui::font::FontCache,
|
|
||||||
hid::HidProvider,
|
hid::HidProvider,
|
||||||
overlays::toast::{DisplayMethod, ToastTopic},
|
overlays::toast::{DisplayMethod, ToastTopic},
|
||||||
shaders::{
|
|
||||||
frag_color, frag_glyph, frag_grid, frag_line, frag_screen, frag_sprite, frag_sprite2,
|
|
||||||
frag_sprite2_hl, frag_srgb, frag_swapchain, vert_common,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
@@ -40,10 +35,12 @@ pub enum KeyboardFocus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub fc: FontCache,
|
|
||||||
pub session: AppSession,
|
pub session: AppSession,
|
||||||
pub tasks: TaskContainer,
|
pub tasks: TaskContainer,
|
||||||
pub graphics: Arc<WlxGraphics>,
|
|
||||||
|
pub gfx: Arc<WGfx>,
|
||||||
|
pub gfx_extras: WGfxExtras,
|
||||||
|
|
||||||
pub input_state: InputState,
|
pub input_state: InputState,
|
||||||
pub hid_provider: Box<dyn HidProvider>,
|
pub hid_provider: Box<dyn HidProvider>,
|
||||||
pub audio: AudioOutput,
|
pub audio: AudioOutput,
|
||||||
@@ -61,47 +58,8 @@ pub struct AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn from_graphics(graphics: Arc<WlxGraphics>) -> anyhow::Result<Self> {
|
pub fn from_graphics(gfx: Arc<WGfx>, gfx_extras: WGfxExtras) -> anyhow::Result<Self> {
|
||||||
// insert shared resources
|
// insert shared resources
|
||||||
{
|
|
||||||
let Ok(mut shaders) = graphics.shared_shaders.write() else {
|
|
||||||
bail!("Failed to lock shared shaders");
|
|
||||||
};
|
|
||||||
|
|
||||||
let shader = vert_common::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("vert_common", shader);
|
|
||||||
|
|
||||||
let shader = frag_color::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_color", shader);
|
|
||||||
|
|
||||||
let shader = frag_line::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_line", shader);
|
|
||||||
|
|
||||||
let shader = frag_srgb::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_srgb", shader);
|
|
||||||
|
|
||||||
let shader = frag_glyph::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_glyph", shader);
|
|
||||||
|
|
||||||
let shader = frag_grid::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_grid", shader);
|
|
||||||
|
|
||||||
let shader = frag_sprite::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_sprite", shader);
|
|
||||||
|
|
||||||
let shader = frag_sprite2::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_sprite2", shader);
|
|
||||||
|
|
||||||
let shader = frag_sprite2_hl::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_sprite2_hl", shader);
|
|
||||||
|
|
||||||
let shader = frag_screen::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_screen", shader);
|
|
||||||
|
|
||||||
let shader = frag_swapchain::load(graphics.device.clone())?;
|
|
||||||
shaders.insert("frag_swapchain", shader);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
let mut tasks = TaskContainer::new();
|
let mut tasks = TaskContainer::new();
|
||||||
|
|
||||||
@@ -124,10 +82,10 @@ impl AppState {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
fc: FontCache::new(session.config.primary_font.clone())?,
|
|
||||||
session,
|
session,
|
||||||
tasks,
|
tasks,
|
||||||
graphics,
|
gfx,
|
||||||
|
gfx_extras,
|
||||||
input_state: InputState::new(),
|
input_state: InputState::new(),
|
||||||
hid_provider: crate::hid::initialize(),
|
hid_provider: crate::hid::initialize(),
|
||||||
audio: AudioOutput::new(),
|
audio: AudioOutput::new(),
|
||||||
|
|||||||
Reference in New Issue
Block a user