Add UI button actions to send arbitrary OSC values. (#140)
* osc: start adding osc buttons a button action that sends an osc parameter. struggling with borrows in openxr.rs and openvr.rs when getting the osc sender. * osc: fix osc sender buttons by passing a ref to the device list to send_params instead of the entire app state. * osc: fix warnings * osc: conditionally use OscSender crate in state.rs * osc: fix button.rs compile errors without osc/wayvr features * osc: add other types: int, bool, string. play thump noise when sent. * osc: fix build without osc feature i just want to use OscType directly, but since it doesn't derive serde::Deserialize, i can't just have one OscType action or list of actions... * merge typed actions to one action, support multiple values. i added a new struct OscValue that has Deserialize, and now the action just converts that to the corresponding OscType when sending the parameters. perhaps not the most elegant solution, but it's the best i can think of without modifying the rosc crate. * run `cargo fmt`
This commit is contained in:
@@ -124,10 +124,6 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
let mut playspace = playspace::PlayspaceMover::new();
|
||||
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
let mut osc_sender =
|
||||
crate::backend::osc::OscSender::new(state.session.config.osc_out_port).ok();
|
||||
|
||||
set_action_manifest(&mut input_mgr)?;
|
||||
|
||||
let mut input_source = OpenVrInputSource::new(&mut input_mgr)?;
|
||||
@@ -333,8 +329,8 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
}
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
if let Some(ref mut sender) = osc_sender {
|
||||
let _ = sender.send_params(&overlays, &state);
|
||||
if let Some(ref mut sender) = state.osc_sender {
|
||||
let _ = sender.send_params(&overlays, &state.input_state.devices);
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
|
||||
@@ -109,10 +109,6 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
.ok()
|
||||
});
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
let mut osc_sender =
|
||||
crate::backend::osc::OscSender::new(app_state.session.config.osc_out_port).ok();
|
||||
|
||||
let (session, mut frame_wait, mut frame_stream) = unsafe {
|
||||
let raw_session = helpers::create_overlay_session(
|
||||
&xr_instance,
|
||||
@@ -315,8 +311,8 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
}
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
if let Some(ref mut sender) = osc_sender {
|
||||
let _ = sender.send_params(&overlays, &app_state);
|
||||
if let Some(ref mut sender) = app_state.osc_sender {
|
||||
let _ = sender.send_params(&overlays, &app_state.input_state.devices);
|
||||
};
|
||||
|
||||
let (_, views) = xr_state.session.locate_views(
|
||||
|
||||
@@ -8,12 +8,9 @@ use rosc::{OscMessage, OscPacket, OscType};
|
||||
|
||||
use crate::overlays::{keyboard::KEYBOARD_NAME, watch::WATCH_NAME};
|
||||
|
||||
use crate::{
|
||||
backend::input::TrackedDeviceRole,
|
||||
state::AppState,
|
||||
};
|
||||
use crate::backend::input::TrackedDeviceRole;
|
||||
|
||||
use super::common::OverlayContainer;
|
||||
use super::{common::OverlayContainer, input::TrackedDevice};
|
||||
|
||||
pub struct OscSender {
|
||||
last_sent_overlay: Instant,
|
||||
@@ -53,7 +50,11 @@ impl OscSender {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_params<D>(&mut self, overlays: &OverlayContainer<D>, app: &AppState) -> anyhow::Result<()>
|
||||
pub fn send_params<D>(
|
||||
&mut self,
|
||||
overlays: &OverlayContainer<D>,
|
||||
devices: &Vec<TrackedDevice>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: Default,
|
||||
{
|
||||
@@ -96,7 +97,6 @@ impl OscSender {
|
||||
"/avatar/parameters/openOverlayCount".into(),
|
||||
vec![OscType::Int(num_overlays)],
|
||||
)?;
|
||||
|
||||
}
|
||||
|
||||
// send battery levels every 10 seconds
|
||||
@@ -108,13 +108,13 @@ impl OscSender {
|
||||
let mut tracker_total_bat = 0.0;
|
||||
let mut controller_total_bat = 0.0;
|
||||
|
||||
for device in &app.input_state.devices {
|
||||
for device in devices {
|
||||
let tracker_param;
|
||||
|
||||
// soc is the battery level (set to device status.charge)
|
||||
let level = device.soc.unwrap_or(-1.0);
|
||||
let parameter = match device.role {
|
||||
TrackedDeviceRole::None => {continue}
|
||||
TrackedDeviceRole::None => continue,
|
||||
TrackedDeviceRole::Hmd => {
|
||||
// legacy OVR Toolkit style (int)
|
||||
// as of 20 Nov 2024 OVR Toolkit uses int 0-100, but this may change in a future update.
|
||||
@@ -158,7 +158,9 @@ impl OscSender {
|
||||
// send average controller and tracker battery parameters
|
||||
self.send_message(
|
||||
format!("/avatar/parameters/averageControllerBattery").into(),
|
||||
vec![OscType::Float(controller_total_bat / controller_count as f32)],
|
||||
vec![OscType::Float(
|
||||
controller_total_bat / controller_count as f32,
|
||||
)],
|
||||
)?;
|
||||
self.send_message(
|
||||
format!("/avatar/parameters/averageTrackerBattery").into(),
|
||||
@@ -168,4 +170,14 @@ impl OscSender {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_single_param(
|
||||
&mut self,
|
||||
parameter: String,
|
||||
values: Vec<OscType>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.send_message(parameter, values)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,12 @@ use crate::{
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "wayvr"))]
|
||||
#[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)]
|
||||
@@ -180,6 +183,26 @@ pub enum ButtonAction {
|
||||
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 {
|
||||
@@ -391,6 +414,34 @@ fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppStat
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
src/state.rs
12
src/state.rs
@@ -14,6 +14,9 @@ use {
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
use crate::backend::osc::OscSender;
|
||||
|
||||
use crate::{
|
||||
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
||||
config::{AStrMap, GeneralConfig},
|
||||
@@ -49,6 +52,9 @@ pub struct AppState {
|
||||
pub sprites: AStrMap<Arc<ImageView>>,
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
pub osc_sender: Option<OscSender>,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub wayvr: Option<Rc<RefCell<WayVRData>>>, // Dynamically created if requested
|
||||
}
|
||||
@@ -102,6 +108,9 @@ impl AppState {
|
||||
.wayvr_config
|
||||
.post_load(&session.config, &mut tasks)?;
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
let osc_sender = crate::backend::osc::OscSender::new(session.config.osc_out_port).ok();
|
||||
|
||||
Ok(AppState {
|
||||
fc: FontCache::new(session.config.primary_font.clone())?,
|
||||
session,
|
||||
@@ -115,6 +124,9 @@ impl AppState {
|
||||
sprites: AStrMap::new(),
|
||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
osc_sender,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
wayvr,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user