Compare commits

...

8 Commits

Author SHA1 Message Date
AnnoyingRains
3dc32ed3ae Merge branch 'StardustXR:dev' into dev 2025-08-31 02:18:41 +10:00
Schmarni
01ec90c699 fix(hexagon_launcher): use the proper resource init function
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-08-30 18:15:47 +02:00
Schmarni
e60463081e Revert "fix hexagon_launcher crashing (#8)"
This reverts commit 62b0fe8240.
This fix only stopped hexagon_launcher from exiting properly upon
receiving a fatal error
2025-08-30 18:09:23 +02:00
AnnoyingRains
cbd99c2cc7 fix: nix 2025-08-31 01:40:41 +10:00
Leah Anderson
62b0fe8240 fix hexagon_launcher crashing (#8) 2025-07-05 13:38:39 -07:00
Schmarni
f5008ff5b5 refactor: update everything to latest fusion and to rust edition 2024 (#7)
Signed-off-by: Schmarni <marnistromer@gmail.com>
2025-04-07 13:59:41 -07:00
Cyberneticmelon
e28a8e8e1e Updated documentation 2025-04-03 17:40:37 -04:00
Nova
c5c4840452 feat: update to latest fusion 2025-02-21 14:13:32 -08:00
17 changed files with 572 additions and 447 deletions

721
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
[workspace] [workspace]
resolver = "2"
members = ["app_grid", "hexagon_launcher", "protostar", "single", "sirius"] members = ["app_grid", "hexagon_launcher", "protostar", "single", "sirius"]
[workspace.dependencies] [workspace.dependencies]

View File

@@ -1,11 +1,24 @@
# protostar # protostar
A collection of application launchers for Stardust XR
> [!IMPORTANT]
> Requires the [Stardust XR Server](https://github.com/StardustXR/server) to be running. For launching 2D applications, [Flatland](https://github.com/StardustXR/flatland) also needs to be running.
Prototype application launcher for Stardust XR If you installed the Stardust XR server via:
```note
sudo dnf group install stardust-xr
```
Or if you installed via the [installation script](https://github.com/cyberneticmelon/usefulscripts/blob/main/stardustxr_setup.sh), Protostar comes pre-installed
Protostar provides an easy to use crate to write applications launchers. See the [examples](examples) to learn more! # How to Use
Protostar itself can be used to build various kinds of app launchers, but two are built in. Most likely the one you will want to use will be `hexagon_launcher`. After launching, in flastcreen mode drag applications out of the app launcher, hold down `Shift + ~`
![updated_drag](https://github.com/StardustXR/website/blob/main/static/img/updated_flat_drag.GIF)
**Quest 3 Hand tracking**:
Pinch to drag and drop, grasp with full hand for grabbing, point and click with pointer finger to click or pinch from a distance
# TODO: ![hand_pinching](https://github.com/StardustXR/website/blob/main/static/img/hand_pinching.GIF)
1. Implement our own (more reliable) way to get icons ## Manual Installation
2. Untangle the code (eg: make it so that the user can decide sizes, models, etc... ) Clone the repository and after the server is running:
3. Implement [configuration files](https://docs.rs/confy/latest/confy/) in the examples ```sh
cargo run
```

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "app_grid" name = "app_grid"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
tokio = { version = "1.32.0", features = ["rt", "tokio-macros", "sync"] } tokio = { version = "1.32.0", features = ["rt", "tokio-macros", "sync"] }

View File

@@ -13,11 +13,11 @@ use stardust_xr_fusion::{
YAlign, YAlign,
}, },
fields::{Field, Shape}, fields::{Field, Shape},
node::NodeType, root::{ClientState, FrameInfo, RootAspect},
root::{ClientState, FrameInfo, RootAspect, RootHandler},
spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform}, spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform},
ClientHandle,
}; };
use stardust_xr_molecules::{Grabbable, GrabbableSettings}; use stardust_xr_molecules::{FrameSensitive, Grabbable, GrabbableSettings, UIElement};
use std::f32::consts::PI; use std::f32::consts::PI;
const APP_LIMIT: usize = 300; const APP_LIMIT: usize = 300;
@@ -32,16 +32,34 @@ async fn main() -> Result<()> {
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.pretty() .pretty()
.init(); .init();
let (client, event_loop) = Client::connect_with_async_loop().await?; let owned_client = Client::connect().await?;
let client = owned_client.handle();
let async_loop = owned_client.async_event_loop();
client client
.set_base_prefixes(&[directory_relative_path!("../res")]) .get_root()
.set_base_prefixes(&[directory_relative_path!("../res").to_string()])
.unwrap(); .unwrap();
let _root = client.get_root().alias().wrap(AppGrid::new(&client))?; let mut grid = AppGrid::new(&client);
let mut owned_client = async_loop.stop().await.unwrap();
let event_loop = owned_client.sync_event_loop(|handle, _| {
let Some(event) = handle.get_root().recv_root_event() else {
return;
};
match event {
stardust_xr_fusion::root::RootEvent::Ping { response } => response.send(Ok(())),
stardust_xr_fusion::root::RootEvent::Frame { info } => {
grid.frame(info);
}
stardust_xr_fusion::root::RootEvent::SaveState { response } => {
response.send(grid.save_state());
}
}
});
tokio::select! { tokio::select! {
_ = tokio::signal::ctrl_c() => (), _ = tokio::signal::ctrl_c() => (),
e = event_loop => e??, e = event_loop => e?,
}; };
Ok(()) Ok(())
} }
@@ -51,7 +69,7 @@ struct AppGrid {
//style: TextStyle, //style: TextStyle,
} }
impl AppGrid { impl AppGrid {
fn new(client: &Client) -> Self { fn new(client: &ClientHandle) -> Self {
let apps = get_desktop_files() let apps = get_desktop_files()
.filter_map(|d| parse_desktop_file(d).ok()) .filter_map(|d| parse_desktop_file(d).ok())
.filter(|d| !d.no_display) .filter(|d| !d.no_display)
@@ -74,7 +92,7 @@ impl AppGrid {
AppGrid { apps } AppGrid { apps }
} }
} }
impl RootHandler for AppGrid { impl AppGrid {
fn frame(&mut self, info: FrameInfo) { fn frame(&mut self, info: FrameInfo) {
for app in &mut self.apps { for app in &mut self.apps {
app.frame(&info); app.frame(&info);
@@ -198,7 +216,10 @@ impl App {
// } // }
fn frame(&mut self, info: &FrameInfo) { fn frame(&mut self, info: &FrameInfo) {
let _ = self.grabbable.update(info); if !self.grabbable.handle_events() {
return;
}
self.grabbable.frame(info);
if self.grabbable.grab_action().actor_stopped() { if self.grabbable.grab_action().actor_stopped() {
self.grabbable.cancel_angular_velocity(); self.grabbable.cancel_angular_velocity();
@@ -210,8 +231,8 @@ impl App {
// } // }
let application = self.application.clone(); let application = self.application.clone();
let space = self.content_parent().alias(); let space = self.content_parent().clone();
let root = self.root.alias(); let root = self.root.clone();
tokio::task::spawn(async move { tokio::task::spawn(async move {
let Ok(transform) = space.get_transform(&root).await else { let Ok(transform) = space.get_transform(&root).await else {

17
flake.lock generated
View File

@@ -1,17 +1,12 @@
{ {
"nodes": { "nodes": {
"crane": { "crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1712681629, "lastModified": 1755993354,
"narHash": "sha256-bMDXn4AkTXLCpoZbII6pDGoSeSe9gI87jxPsHRXgu/E=", "narHash": "sha256-FCRRAzSaL/+umLIm3RU3O/+fJ2ssaPHseI2SSFL8yZU=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "220387ac8e99cbee0ca4c95b621c4bc782b6a235", "rev": "25bd41b24426c7734278c2ff02e53258851db914",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -22,11 +17,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1712791164, "lastModified": 1756386758,
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=", "narHash": "sha256-1wxxznpW2CKvI9VdniaUnTT2Os6rdRJcRUf65ZK9OtE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5", "rev": "dfb2f12e899db4876308eba6d93455ab7da304cd",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -2,7 +2,6 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
crane = { crane = {
inputs.nixpkgs.follows = "nixpkgs";
url = "github:ipetkov/crane"; url = "github:ipetkov/crane";
}; };
}; };
@@ -13,9 +12,9 @@
forAllSystems = nixpkgs.lib.genAttrs supportedSystems; forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in { in {
packages = forAllSystems (system: let pkgs = nixpkgsFor.${system}; in { packages = forAllSystems (system: let pkgs = nixpkgsFor.${system}; craneLib = crane.mkLib pkgs; in {
default = crane.lib.${system}.buildPackage { default = craneLib.buildPackage {
pname = "protostar"; pname = "hexagon-launcher";
version = "0.1.0"; version = "0.1.0";
src = ./.; src = ./.;

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "hexagon_launcher" name = "hexagon_launcher"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
protostar = { path = "../protostar" } protostar = { path = "../protostar" }

View File

@@ -15,7 +15,7 @@ use stardust_xr_fusion::{
root::FrameInfo, root::FrameInfo,
spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform}, spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform},
}; };
use stardust_xr_molecules::{Grabbable, GrabbableSettings}; use stardust_xr_molecules::{FrameSensitive as _, Grabbable, GrabbableSettings, UIElement};
use std::f32::consts::PI; use std::f32::consts::PI;
use tween::{QuartInOut, Tweener}; use tween::{QuartInOut, Tweener};
@@ -160,7 +160,7 @@ impl App {
} }
Ok(App { Ok(App {
parent: parent.alias(), parent: parent.clone(),
position, position,
grabbable, grabbable,
_field: field, _field: field,
@@ -190,7 +190,9 @@ impl App {
} }
pub fn frame(&mut self, info: &FrameInfo, state: &State) { pub fn frame(&mut self, info: &FrameInfo, state: &State) {
let _ = self.grabbable.update(info); if self.grabbable.handle_events() {
self.grabbable.frame(info);
}
if let Some(grabbable_move) = &mut self.grabbable_move { if let Some(grabbable_move) = &mut self.grabbable_move {
if !grabbable_move.is_finished() { if !grabbable_move.is_finished() {
@@ -265,8 +267,8 @@ impl App {
self.grabbable_shrink = Some(Tweener::quart_in_out(APP_SIZE * 0.5, 0.0001, 0.25)); self.grabbable_shrink = Some(Tweener::quart_in_out(APP_SIZE * 0.5, 0.0001, 0.25));
let application = self.application.clone(); let application = self.application.clone();
let space = self.content_parent().alias(); let space = self.content_parent().clone();
let parent = self.parent.alias(); let parent = self.parent.clone();
//TODO: split the executable string for the args //TODO: split the executable string for the args
tokio::task::spawn(async move { tokio::task::spawn(async move {

View File

@@ -5,23 +5,24 @@ use app::App;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use glam::Quat; use glam::Quat;
use hex::{HEX_CENTER, HEX_DIRECTION_VECTORS}; use hex::{HEX_CENTER, HEX_DIRECTION_VECTORS};
use manifest_dir_macros::directory_relative_path; use protostar::xdg::{DesktopFile, get_desktop_files, parse_desktop_file};
use protostar::xdg::{get_desktop_files, parse_desktop_file, DesktopFile};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use stardust_xr_fusion::{ use stardust_xr_fusion::{
ClientHandle,
client::Client, client::Client,
core::values::{ core::values::{
color::{color_space::LinearRgb, rgba_linear, Rgba},
ResourceID, ResourceID,
color::{Rgba, color_space::LinearRgb, rgba_linear},
}, },
drawable::{MaterialParameter, Model, ModelPartAspect}, drawable::{MaterialParameter, Model, ModelPartAspect},
node::{NodeError, NodeType}, node::NodeError,
root::{ClientState, FrameInfo, RootAspect, RootHandler}, project_local_resources,
root::{ClientState, FrameInfo, RootAspect, RootEvent},
spatial::{Spatial, SpatialAspect, Transform}, spatial::{Spatial, SpatialAspect, Transform},
}; };
use stardust_xr_molecules::{ use stardust_xr_molecules::{
FrameSensitive, Grabbable, GrabbableSettings, PointerMode, UIElement,
button::{Button, ButtonSettings}, button::{Button, ButtonSettings},
Grabbable, GrabbableSettings, PointerMode,
}; };
use std::{f32::consts::PI, sync::Arc, time::Duration}; use std::{f32::consts::PI, sync::Arc, time::Duration};
@@ -41,19 +42,30 @@ async fn main() -> Result<()> {
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.pretty() .pretty()
.init(); .init();
let (client, event_loop) = Client::connect_with_async_loop().await?; let owned_client = Client::connect().await?;
client owned_client.setup_resources(&[&project_local_resources!("../res")])?;
.set_base_prefixes(&[directory_relative_path!("../res")]) let client = owned_client.handle();
.unwrap(); let async_loop = owned_client.async_event_loop();
let mut grid = AppHexGrid::new(&client).await;
let _root = client let mut owned_client = async_loop.stop().await.unwrap();
.get_root() let event_loop = owned_client.sync_event_loop(|handle, _| {
.alias() let Some(event) = handle.get_root().recv_root_event() else {
.wrap(AppHexGrid::new(&client).await)?; return;
};
match event {
RootEvent::Ping { response } => response.send(Ok(())),
RootEvent::Frame { info } => {
grid.frame(info);
}
RootEvent::SaveState { response } => {
response.send(grid.save_state());
}
}
});
tokio::select! { tokio::select! {
_ = tokio::signal::ctrl_c() => (), _ = tokio::signal::ctrl_c() => (),
e = event_loop => e??, e = event_loop => e?,
} }
Ok(()) Ok(())
} }
@@ -70,13 +82,14 @@ struct AppHexGrid {
state: State, state: State,
} }
impl AppHexGrid { impl AppHexGrid {
async fn new(client: &Arc<Client>) -> Self { async fn new(client: &Arc<ClientHandle>) -> Self {
let state = client.get_state().data().unwrap_or_default(); let client_state = client.get_root().get_state().await.unwrap();
let state = client_state.data().unwrap_or_default();
let movable_root = let movable_root =
Spatial::create(client.get_root(), Transform::identity(), false).unwrap(); Spatial::create(client.get_root(), Transform::identity(), false).unwrap();
let button = CenterButton::new(client, client.get_state()).unwrap(); let button = CenterButton::new(client, &client_state).unwrap();
tokio::time::sleep(Duration::from_millis(10)).await; // give it a bit of time to send the messages properly tokio::time::sleep(Duration::from_millis(10)).await; // give it a bit of time to send the messages properly
let mut desktop_files: Vec<DesktopFile> = get_desktop_files() let mut desktop_files: Vec<DesktopFile> = get_desktop_files()
@@ -120,7 +133,7 @@ impl AppHexGrid {
} }
} }
} }
impl RootHandler for AppHexGrid { impl AppHexGrid {
fn frame(&mut self, info: FrameInfo) { fn frame(&mut self, info: FrameInfo) {
self.button.frame(&info); self.button.frame(&info);
if self.button.button.pressed() { if self.button.button.pressed() {
@@ -173,7 +186,7 @@ struct CenterButton {
model: Model, model: Model,
} }
impl CenterButton { impl CenterButton {
fn new(client: &Arc<Client>, state: &ClientState) -> Result<Self, NodeError> { fn new(client: &Arc<ClientHandle>, state: &ClientState) -> Result<Self, NodeError> {
// (APP_SIZE + PADDING) / 2.0, // (APP_SIZE + PADDING) / 2.0,
let button = Button::create( let button = Button::create(
client.get_root(), client.get_root(),
@@ -224,7 +237,9 @@ impl CenterButton {
} }
fn frame(&mut self, info: &FrameInfo) { fn frame(&mut self, info: &FrameInfo) {
let _ = self.grabbable.update(info); if self.grabbable.handle_events() {
self.button.update(); self.grabbable.frame(info);
}
self.button.handle_events();
} }
} }

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "protostar" name = "protostar"
version = "0.4.0" version = "0.4.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
clap = { version = "4.1.3", features = ["derive"] } clap = { version = "4.1.3", features = ["derive"] }

View File

@@ -44,9 +44,9 @@ impl Application {
icon.and_then(|i| i.cached_process(preferred_px_size).ok()) icon.and_then(|i| i.cached_process(preferred_px_size).ok())
} }
pub fn launch(&self, launch_space: &impl SpatialRefAspect) -> NodeResult<()> { pub fn launch<T: SpatialRefAspect + Clone>(&self, launch_space: &T) -> NodeResult<()> {
let client = launch_space.node().client()?; let client = launch_space.node().client()?;
let launch_space = launch_space.alias(); let launch_space = launch_space.clone();
let executable = self let executable = self
.desktop_file .desktop_file
@@ -66,10 +66,16 @@ impl Application {
return; return;
}; };
for (k, v) in connection_env.into_iter() { for (k, v) in connection_env.into_iter() {
std::env::set_var(k, v); // this should be fine, probably?
unsafe {
std::env::set_var(k, v);
}
} }
std::env::set_var("STARDUST_STARTUP_TOKEN", startup_token); // this should be fine, probably?
unsafe {
std::env::set_var("STARDUST_STARTUP_TOKEN", startup_token);
}
// Strip/ignore field codes https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html // Strip/ignore field codes https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html
let re = Regex::new(r"%[fFuUdDnNickvm]").unwrap(); let re = Regex::new(r"%[fFuUdDnNickvm]").unwrap();

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "single" name = "single"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
protostar = { path = "../protostar" } protostar = { path = "../protostar" }

View File

@@ -4,7 +4,7 @@ use clap::Parser;
use color_eyre::{eyre::Result, Report}; use color_eyre::{eyre::Result, Report};
use manifest_dir_macros::directory_relative_path; use manifest_dir_macros::directory_relative_path;
use protostar::xdg::parse_desktop_file; use protostar::xdg::parse_desktop_file;
use stardust_xr_fusion::{client::Client, node::NodeType, root::RootAspect}; use stardust_xr_fusion::{client::Client, root::RootAspect};
use std::path::PathBuf; use std::path::PathBuf;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
@@ -25,20 +25,38 @@ async fn main() -> Result<()> {
.init(); .init();
color_eyre::install()?; color_eyre::install()?;
let args = Args::parse(); let args = Args::parse();
let (client, event_loop) = Client::connect_with_async_loop().await?; let owned_client = Client::connect().await?;
client.set_base_prefixes(&[directory_relative_path!("../res")])?; let client = owned_client.handle();
let async_loop = owned_client.async_event_loop();
client
.get_root()
.set_base_prefixes(&[directory_relative_path!("../res").to_string()])?;
let protostar = Single::create_from_desktop_file( let mut protostar = Single::create_from_desktop_file(
client.get_root(), client.get_root(),
[0.0, 0.0, 0.0], [0.0, 0.0, 0.0],
parse_desktop_file(args.desktop_file).map_err(Report::msg)?, parse_desktop_file(args.desktop_file).map_err(Report::msg)?,
)?; )?;
let _root = client.get_root().alias().wrap(protostar)?; let mut owned_client = async_loop.stop().await.unwrap();
let event_loop = owned_client.sync_event_loop(|handle, _| {
let Some(event) = handle.get_root().recv_root_event() else {
return;
};
match event {
stardust_xr_fusion::root::RootEvent::Ping { response } => response.send(Ok(())),
stardust_xr_fusion::root::RootEvent::Frame { info } => {
protostar.frame(info);
}
stardust_xr_fusion::root::RootEvent::SaveState { response } => {
response.send(protostar.save_state());
}
}
});
tokio::select! { tokio::select! {
_ = tokio::signal::ctrl_c() => (), _ = tokio::signal::ctrl_c() => (),
e = event_loop => e??, e = event_loop => e?,
}; };
Ok(()) Ok(())
} }

View File

@@ -12,10 +12,10 @@ use stardust_xr_fusion::{
}, },
fields::{Field, Shape}, fields::{Field, Shape},
node::NodeType, node::NodeType,
root::{ClientState, FrameInfo, RootHandler}, root::{ClientState, FrameInfo},
spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform}, spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform},
}; };
use stardust_xr_molecules::{Grabbable, GrabbableSettings}; use stardust_xr_molecules::{FrameSensitive, Grabbable, GrabbableSettings, UIElement};
use std::f32::consts::PI; use std::f32::consts::PI;
use tween::{QuartInOut, Tweener}; use tween::{QuartInOut, Tweener};
@@ -146,9 +146,11 @@ impl Single {
self.grabbable.content_parent() self.grabbable.content_parent()
} }
} }
impl RootHandler for Single { impl Single {
fn frame(&mut self, info: FrameInfo) { pub fn frame(&mut self, info: FrameInfo) {
let _ = self.grabbable.update(&info); if self.grabbable.handle_events() {
self.grabbable.frame(&info);
}
if let Some(grabbable_move) = &mut self.grabbable_move { if let Some(grabbable_move) = &mut self.grabbable_move {
if !grabbable_move.is_finished() { if !grabbable_move.is_finished() {
@@ -224,8 +226,8 @@ impl RootHandler for Single {
self.grabbable_shrink = Some(Tweener::quart_in_out(MODEL_SCALE, 0.0001, 0.25)); self.grabbable_shrink = Some(Tweener::quart_in_out(MODEL_SCALE, 0.0001, 0.25));
let application = self.application.clone(); let application = self.application.clone();
let space = self.content_parent().alias(); let space = self.content_parent().clone();
let root = self.root.alias(); let root = self.root.clone();
//TODO: split the executable string for the args //TODO: split the executable string for the args
tokio::task::spawn(async move { tokio::task::spawn(async move {
@@ -244,7 +246,7 @@ impl RootHandler for Single {
} }
} }
fn save_state(&mut self) -> color_eyre::eyre::Result<ClientState> { pub fn save_state(&mut self) -> color_eyre::eyre::Result<ClientState> {
ClientState::from_root(self.content_parent()) ClientState::from_root(self.content_parent())
} }
} }

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "sirius" name = "sirius"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
protostar = { path = "../protostar" } protostar = { path = "../protostar" }

View File

@@ -16,12 +16,12 @@ use stardust_xr_fusion::{
}, },
fields::{Field, Shape}, fields::{Field, Shape},
node::{NodeError, NodeType}, node::{NodeError, NodeType},
root::{ClientState, FrameInfo, RootAspect, RootHandler}, root::{ClientState, FrameInfo, RootAspect},
spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform}, spatial::{Spatial, SpatialAspect, SpatialRefAspect, Transform}, ClientHandle,
}; };
use stardust_xr_molecules::{ use stardust_xr_molecules::{
button::{Button, ButtonSettings}, button::{Button, ButtonSettings},
Grabbable, GrabbableSettings, FrameSensitive, Grabbable, GrabbableSettings, UIElement,
}; };
use std::{f32::consts::PI, path::PathBuf}; use std::{f32::consts::PI, path::PathBuf};
@@ -50,17 +50,33 @@ async fn main() -> Result<()> {
) )
} }
let (client, event_loop) = Client::connect_with_async_loop().await?; let owned_client = Client::connect().await?;
client.set_base_prefixes(&[directory_relative_path!("../res")])?; let client = owned_client.handle();
let async_loop = owned_client.async_event_loop();
let _wrapped_root = client client
.get_root() .get_root()
.alias() .set_base_prefixes(&[directory_relative_path!("../res").to_string()])?;
.wrap(Sirius::new(&client, args)?)?; let mut sirius = Sirius::new(&client, args)?;
let mut owned_client = async_loop.stop().await.unwrap();
let event_loop = owned_client.sync_event_loop(|handle, _| {
let Some(event) = handle.get_root().recv_root_event() else {
return;
};
match event {
stardust_xr_fusion::root::RootEvent::Ping { response } => response.send(Ok(())),
stardust_xr_fusion::root::RootEvent::Frame { info } => {
sirius.frame(info);
}
stardust_xr_fusion::root::RootEvent::SaveState { response } => {
response.send(sirius.save_state());
}
}
});
tokio::select! { tokio::select! {
_ = tokio::signal::ctrl_c() => (), _ = tokio::signal::ctrl_c() => (),
e = event_loop => e??, e = event_loop => e?,
} }
Ok(()) Ok(())
} }
@@ -78,7 +94,7 @@ struct Sirius {
grabbable: Grabbable, grabbable: Grabbable,
} }
impl Sirius { impl Sirius {
fn new(client: &Client, args: Args) -> Result<Self, NodeError> { fn new(client: &ClientHandle, args: Args) -> Result<Self, NodeError> {
let root = Spatial::create(client.get_root(), Transform::identity(), false).unwrap(); let root = Spatial::create(client.get_root(), Transform::identity(), false).unwrap();
let field = let field =
@@ -141,15 +157,16 @@ impl Sirius {
// } // }
// } // }
} }
impl RootHandler for Sirius { impl Sirius {
fn frame(&mut self, info: FrameInfo) { fn frame(&mut self, info: FrameInfo) {
for app in &mut self.clients { for app in &mut self.clients {
app.frame(&info); app.frame(&info);
} }
self.grabbable.update(&info).unwrap(); if self.grabbable.handle_events() {
self.button.update(); self.grabbable.frame(&info);
if self.button.pressed() { };
if self.button.handle_events() && self.button.pressed() {
println!("Touch started"); println!("Touch started");
self.state.visible = !self.state.visible; self.state.visible = !self.state.visible;
match self.state.visible { match self.state.visible {
@@ -343,7 +360,7 @@ impl App {
.ok() .ok()
}); });
Ok(App { Ok(App {
parent: parent.alias(), parent: parent.clone(),
position, position,
grabbable, grabbable,
_field: field, _field: field,
@@ -374,7 +391,10 @@ impl App {
} }
fn frame(&mut self, info: &FrameInfo) { fn frame(&mut self, info: &FrameInfo) {
let _ = self.grabbable.update(info); if !self.grabbable.handle_events() {
return;
}
self.grabbable.frame(info);
if let Some(grabbable_move) = &mut self.grabbable_move { if let Some(grabbable_move) = &mut self.grabbable_move {
if !grabbable_move.is_finished() { if !grabbable_move.is_finished() {
@@ -449,8 +469,8 @@ impl App {
self.grabbable_shrink = Some(Tweener::quart_in_out(APP_SIZE * 0.5, 0.0001, 0.25)); self.grabbable_shrink = Some(Tweener::quart_in_out(APP_SIZE * 0.5, 0.0001, 0.25));
let application = self.application.clone(); let application = self.application.clone();
let space = self.content_parent().alias(); let space = self.content_parent().clone();
let parent = self.parent.alias(); let parent = self.parent.clone();
//TODO: split the executable string for the args //TODO: split the executable string for the args
tokio::task::spawn(async move { tokio::task::spawn(async move {