diff --git a/sandpolis/src/client/mod.rs b/sandpolis/src/client/mod.rs index a58c1ec8..f95c77f9 100644 --- a/sandpolis/src/client/mod.rs +++ b/sandpolis/src/client/mod.rs @@ -1,24 +1,17 @@ -use crate::{core::database::Database, CommandLine}; +use crate::CommandLine; use anyhow::Result; use clap::Parser; -use self::ui::AppState; - pub mod ui; +// Prohibit using a local database on platforms that don't support +#[cfg(all(feature = "local-database", target_os = "android"))] +compile_error!("Platform does not support local-database"); + #[derive(Parser, Debug, Clone, Default)] pub struct ClientCommandLine {} pub async fn main(args: CommandLine) -> Result<()> { - let mut state = AppState { - db: Database::new(None, "test", "test").await?, - }; - - // Create server connection(s) - for server in args.server.unwrap_or(Vec::new()) { - state.db.add_server(&server, "test", "test").await?; - } - - crate::client::ui::run(state); + crate::client::ui::run(args).await?; Ok(()) } diff --git a/sandpolis/src/client/ui/input.rs b/sandpolis/src/client/ui/input.rs index a4b232f2..06fc7a84 100644 --- a/sandpolis/src/client/ui/input.rs +++ b/sandpolis/src/client/ui/input.rs @@ -95,16 +95,16 @@ pub fn handle_camera( // Update transforms. for mut camera_transform in cameras.iter_mut() { // Handle keyboard events. - if keyboard_input.pressed(KeyCode::KeyW) { + if keyboard_input.pressed(KeyCode::ArrowUp) { camera_transform.translation.y -= CAMERA_KEYBOARD_ZOOM_SPEED; } - if keyboard_input.pressed(KeyCode::KeyA) { + if keyboard_input.pressed(KeyCode::ArrowLeft) { camera_transform.translation.x -= CAMERA_KEYBOARD_ZOOM_SPEED; } - if keyboard_input.pressed(KeyCode::KeyS) { + if keyboard_input.pressed(KeyCode::ArrowDown) { camera_transform.translation.y += CAMERA_KEYBOARD_ZOOM_SPEED; } - if keyboard_input.pressed(KeyCode::KeyD) { + if keyboard_input.pressed(KeyCode::ArrowRight) { camera_transform.translation.x += CAMERA_KEYBOARD_ZOOM_SPEED; } @@ -165,11 +165,23 @@ pub fn handle_layer_change( mut timer: ResMut, mut windows: Query<&mut Window>, ) { + // TODO don't allow change while timer is running + #[cfg(feature = "layer-filesystem")] if keyboard_input.pressed(KeyCode::KeyF) { **current_layer = Layer::Filesystem; timer.reset(); } + #[cfg(feature = "layer-packages")] + if keyboard_input.pressed(KeyCode::KeyP) { + **current_layer = Layer::Packages; + timer.reset(); + } + #[cfg(feature = "layer-desktop")] + if keyboard_input.pressed(KeyCode::KeyD) { + **current_layer = Layer::Desktop; + timer.reset(); + } // Now show the current layer for a few seconds if !timer.tick(time.delta()).finished() { diff --git a/sandpolis/src/client/ui/layer/package.rs b/sandpolis/src/client/ui/layer/package.rs new file mode 100644 index 00000000..9b7f0ea7 --- /dev/null +++ b/sandpolis/src/client/ui/layer/package.rs @@ -0,0 +1,37 @@ +use bevy::prelude::*; +use bevy_egui::EguiContexts; + +use crate::{ + client::ui::{node::NodeId, CurrentLayer}, + core::Layer, +}; + +pub fn check_layer_active(current_layer: Res) -> bool { + return **current_layer == Layer::Packages; +} + +pub fn handle_layer( + commands: Commands, + mut contexts: EguiContexts, + mut nodes: Query<(&mut Transform, &NodeId), With>, + mut windows: Query<&mut Window>, + cameras: Query<&Transform, (With, Without)>, +) { + let window_size = windows.single_mut().size(); + let camera_transform = cameras.single(); + + for (transform, id) in nodes.iter_mut() { + let package_manager_info: PackageManagerInfo = todo!(); + egui::Window::new("Hello") + .movable(false) + .resizable(false) + .pivot(egui::Align2::CENTER_TOP) + .current_pos(egui::Pos2::new( + window_size.x / 2.0 + transform.translation.x - camera_transform.translation.x, + window_size.y / 2.0 + transform.translation.y + camera_transform.translation.y, + )) + .show(contexts.ctx_mut(), |ui| { + ui.label("world"); + }); + } +} diff --git a/sandpolis/src/client/ui/mod.rs b/sandpolis/src/client/ui/mod.rs index ae9a0a3e..691ec65a 100644 --- a/sandpolis/src/client/ui/mod.rs +++ b/sandpolis/src/client/ui/mod.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use bevy::{ color::palettes::basic::*, prelude::*, @@ -6,7 +7,10 @@ use bevy::{ use bevy_egui::{EguiContexts, EguiPlugin}; use bevy_rapier2d::prelude::*; -use crate::core::{database::Database, Layer}; +use crate::{ + core::{database::Database, Layer}, + CommandLine, +}; use self::{ input::{LayerChangeTimer, MousePressed}, @@ -19,7 +23,7 @@ pub mod node; #[derive(Resource)] pub struct AppState { - pub db: Database, + pub db: Option, } #[derive(Resource, Deref, DerefMut)] @@ -29,7 +33,14 @@ pub struct CurrentLayer(Layer); pub struct ZoomLevel(f32); /// Initialize and start rendering the UI. -pub fn run(state: AppState) { +pub async fn run(args: CommandLine) -> Result<()> { + let mut state = AppState { + #[cfg(feature = "local-database")] + db: Some(Database::new(None, "test", "test").await?), + #[cfg(not(feature = "local-database"))] + db: None, + }; + let mut app = App::new(); app.add_plugins( DefaultPlugins @@ -57,6 +68,7 @@ pub fn run(state: AppState) { .insert_resource(ZoomLevel(1.0)) .insert_resource(LayerChangeTimer(Timer::from_seconds(3.0, TimerMode::Once))) .insert_resource(state) + .insert_resource(args) .insert_resource(MousePressed(false)) .add_systems(Startup, setup) .add_systems( @@ -84,6 +96,7 @@ pub fn run(state: AppState) { app.insert_resource(Msaa::Off); app.run(); + Ok(()) } fn setup( @@ -97,12 +110,12 @@ fn setup( commands.spawn(Camera2dBundle::default()); // Spawn the local client - spawn_node( - &asset_server, - &mut commands, - state.db.metadata.id, - state.db.metadata.os_info.os_type(), - ); + // spawn_node( + // &asset_server, + // &mut commands, + // state.db.metadata.id, + // state.db.metadata.os_info.os_type(), + // ); } fn button_handler( @@ -144,3 +157,38 @@ fn handle_lifetime( } } } + +// #[derive(Component)] +// pub struct ConnectionTask(pub Task<()>); + +// pub fn prepare_connection_tasks( +// args: Res, +// pool: Res, +// mut commands: Commands, +// ) { +// while let Some(key) = queue.queue.pop() { +// if let Some(entity) = chunks.entity(key) { +// let task = pool.spawn(async move { +// // TODO no references +// // Create server connection(s) +// for server in args.server.unwrap_or(Vec::new()) { +// state.db.add_server(&server, "test", "test").await?; +// } +// }); + +// cmds.entity(entity).insert(ConnectionTask { 0: task }); +// } +// } +// } + +// pub fn apply_connection_tasks( +// mut query: Query<(Entity, &mut ConnectionTask)>, +// mut commands: Commands, +// ) { +// query.for_each_mut(|(entity, mut task)| { +// if futures::block_on(futures::poll_once(&mut task.0)) { +// cmds.entity(entity).remove::(); +// } +// return; +// }); +// } diff --git a/sandpolis/src/client/ui/node.rs b/sandpolis/src/client/ui/node.rs index 593dbaa9..ccbc7c80 100644 --- a/sandpolis/src/client/ui/node.rs +++ b/sandpolis/src/client/ui/node.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use bevy_egui::EguiContexts; use bevy_rapier2d::{ dynamics::RigidBody, geometry::{Collider, Restitution}, @@ -89,3 +90,32 @@ pub fn get_os_image(os_type: os_info::Type) -> String { } .to_string() } + +/// A `WindowStack` is a set of collapsible Windows that are rendered below a node. +#[derive(Component, Clone, Debug)] +pub struct WindowStack {} + +pub fn handle_window_stacks( + commands: Commands, + mut contexts: EguiContexts, + mut nodes: Query<(&mut Transform, (&NodeId, &WindowStack)), With>, + mut windows: Query<&mut Window>, + cameras: Query<&Transform, (With, Without)>, +) { + let window_size = windows.single_mut().size(); + let camera_transform = cameras.single(); + + for (transform, (id, window_stack)) in nodes.iter_mut() { + egui::Window::new("Hello") + .movable(false) + .resizable(false) + .pivot(egui::Align2::CENTER_TOP) + .current_pos(egui::Pos2::new( + window_size.x / 2.0 + transform.translation.x - camera_transform.translation.x, + window_size.y / 2.0 + transform.translation.y + camera_transform.translation.y, + )) + .show(contexts.ctx_mut(), |ui| { + ui.label("world"); + }); + } +} diff --git a/sandpolis/src/core/layer/package.rs b/sandpolis/src/core/layer/package.rs index b595995b..53423028 100644 --- a/sandpolis/src/core/layer/package.rs +++ b/sandpolis/src/core/layer/package.rs @@ -1,3 +1,5 @@ +use couch_rs::{document::TypedCouchDocument, types::document::DocumentId}; + // message RQ_InstallOrUpgradePackages { // repeated string package = 1; // } @@ -9,14 +11,38 @@ // message RQ_RefreshPackages { // } +use couch_rs::CouchDocument; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] pub enum PackageManager { Pacman, Apt, Nix, } -#[derive(CouchDocument)] +#[derive(Serialize, Deserialize, CouchDocument)] +#[cfg_attr(feature = "client", derive(bevy::prelude::Component))] +pub struct PackageManagerInfo { + #[serde(skip_serializing_if = "String::is_empty")] + pub _id: DocumentId, + #[serde(skip_serializing_if = "String::is_empty")] + pub _rev: String, + + /// Type of package manager + pub manager: PackageManager, + + pub package_count: u64, +} + +#[derive(Serialize, Deserialize, CouchDocument)] +#[cfg_attr(feature = "client", derive(bevy::prelude::Component))] pub struct Package { + #[serde(skip_serializing_if = "String::is_empty")] + pub _id: DocumentId, + #[serde(skip_serializing_if = "String::is_empty")] + pub _rev: String, + /// Canonical name/identifier pub name: String, diff --git a/sandpolis/src/lib.rs b/sandpolis/src/lib.rs index 82742eca..4a695180 100644 --- a/sandpolis/src/lib.rs +++ b/sandpolis/src/lib.rs @@ -8,6 +8,7 @@ pub mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); } +#[cfg_attr(feature = "client", derive(bevy::prelude::Resource))] #[derive(Parser, Debug, Clone, Default)] #[clap(author, version, about, long_about = None)] pub struct CommandLine {