sovereignty over app lifecycle
This commit is contained in:
668
src-tauri/Cargo.lock
generated
668
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,16 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = ["macos-private-api", "unstable"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
tauri-plugin-global-shortcut = "2"
|
||||||
|
tauri-plugin-positioner = "2"
|
||||||
|
reqwest = { version = "0.12.23", features = ["json", "native-tls"] }
|
||||||
|
ts-rs = "11.0.1"
|
||||||
|
|
||||||
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
|
tauri-plugin-global-shortcut = "2"
|
||||||
|
tauri-plugin-positioner = "2"
|
||||||
|
|||||||
41
src-tauri/src/app.rs
Normal file
41
src-tauri/src/app.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use tauri::Manager;
|
||||||
|
use tauri_plugin_positioner::WindowExt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
get_app_handle,
|
||||||
|
services::overlay::{overlay_fullscreen, SCENE_WINDOW_LABEL},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn start_fdoll() {
|
||||||
|
initialize_session().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn initialize_session() {
|
||||||
|
let webview_window = tauri::WebviewWindowBuilder::new(
|
||||||
|
get_app_handle(),
|
||||||
|
SCENE_WINDOW_LABEL,
|
||||||
|
tauri::WebviewUrl::App("/scene".into()),
|
||||||
|
)
|
||||||
|
.title("Friendolls Scene")
|
||||||
|
.inner_size(600.0, 500.0)
|
||||||
|
.resizable(false)
|
||||||
|
.decorations(false)
|
||||||
|
.transparent(true)
|
||||||
|
.shadow(false)
|
||||||
|
.visible(true)
|
||||||
|
.skip_taskbar(true)
|
||||||
|
.always_on_top(true)
|
||||||
|
.visible_on_all_workspaces(true)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to display scene screen");
|
||||||
|
|
||||||
|
webview_window
|
||||||
|
.move_window(tauri_plugin_positioner::Position::Center)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let window = get_app_handle().get_window(webview_window.label()).unwrap();
|
||||||
|
overlay_fullscreen(&window).unwrap();
|
||||||
|
window.set_ignore_cursor_events(true).unwrap();
|
||||||
|
|
||||||
|
println!("Scene window initialized.");
|
||||||
|
}
|
||||||
4
src-tauri/src/core/mod.rs
Normal file
4
src-tauri/src/core/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod models;
|
||||||
|
pub mod services;
|
||||||
|
pub mod state;
|
||||||
|
pub mod utilities;
|
||||||
8
src-tauri/src/core/models/app_config.rs
Normal file
8
src-tauri/src/core/models/app_config.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct AppConfig {
|
||||||
|
pub api_base_url: Option<String>,
|
||||||
|
}
|
||||||
1
src-tauri/src/core/models/mod.rs
Normal file
1
src-tauri/src/core/models/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod app_config;
|
||||||
1
src-tauri/src/core/services/mod.rs
Normal file
1
src-tauri/src/core/services/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
26
src-tauri/src/core/state.rs
Normal file
26
src-tauri/src/core/state.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// in app-core/src/state.rs
|
||||||
|
use crate::{core::models::app_config::AppConfig, lock_w};
|
||||||
|
use reqwest::Client;
|
||||||
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub app_config: Option<AppConfig>,
|
||||||
|
pub http_client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global application state
|
||||||
|
// FDOLL = Multiplayer Todo App
|
||||||
|
// Read / write this state via the `lock_r!` / `lock_w!` macros from `fdoll-core::utilities`
|
||||||
|
pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
|
||||||
|
LazyLock::new(|| Arc::new(RwLock::new(AppState::default())));
|
||||||
|
|
||||||
|
pub fn init_fdoll_state() {
|
||||||
|
{
|
||||||
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
guard.app_config = Some(AppConfig {
|
||||||
|
api_base_url: Some("http://sandbox:3000".to_string()),
|
||||||
|
});
|
||||||
|
guard.http_client = reqwest::Client::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src-tauri/src/core/utilities.rs
Normal file
29
src-tauri/src/core/utilities.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! lock_r {
|
||||||
|
($rwlock:expr) => {{
|
||||||
|
match $rwlock.read() {
|
||||||
|
Ok(guard) => guard,
|
||||||
|
Err(_) => panic!(
|
||||||
|
"Failed to acquire read lock on {} at {}:{}",
|
||||||
|
stringify!($rwlock),
|
||||||
|
file!(),
|
||||||
|
line!()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! lock_w {
|
||||||
|
($rwlock:expr) => {{
|
||||||
|
match $rwlock.write() {
|
||||||
|
Ok(guard) => guard,
|
||||||
|
Err(_) => panic!(
|
||||||
|
"Failed to acquire write lock on {} at {}:{}",
|
||||||
|
stringify!($rwlock),
|
||||||
|
file!(),
|
||||||
|
line!()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
@@ -1,8 +1,50 @@
|
|||||||
|
static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
mod core;
|
||||||
|
mod services;
|
||||||
|
|
||||||
|
/// Tauri app handle
|
||||||
|
pub fn get_app_handle<'a>() -> &'a tauri::AppHandle<tauri::Wry> {
|
||||||
|
APP_HANDLE
|
||||||
|
.get()
|
||||||
|
.expect("get_app_handle called but app is still not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_fdoll() -> Result<(), tauri::Error> {
|
||||||
|
core::state::init_fdoll_state();
|
||||||
|
tokio::spawn(async move { app::start_fdoll().await });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_app_events(event: tauri::RunEvent) {
|
||||||
|
match event {
|
||||||
|
tauri::RunEvent::ExitRequested { api, code, .. } => {
|
||||||
|
if code.is_none() {
|
||||||
|
api.prevent_exit();
|
||||||
|
} else {
|
||||||
|
println!("exit code: {:?}", code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.plugin(tauri_plugin_positioner::init())
|
||||||
|
// .plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.invoke_handler(tauri::generate_handler![])
|
.invoke_handler(tauri::generate_handler![])
|
||||||
.run(tauri::generate_context!())
|
.setup(|app| {
|
||||||
.expect("error while running tauri application");
|
APP_HANDLE
|
||||||
|
.set(app.handle().to_owned())
|
||||||
|
.expect("Failed to init app handle.");
|
||||||
|
setup_fdoll().expect("Failed to setup app.");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.build(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application")
|
||||||
|
.run(|_, event| register_app_events(event));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
friendolls_desktop_lib::run()
|
friendolls_desktop_lib::run()
|
||||||
}
|
}
|
||||||
|
|||||||
1
src-tauri/src/services/mod.rs
Normal file
1
src-tauri/src/services/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod overlay;
|
||||||
25
src-tauri/src/services/overlay.rs
Normal file
25
src-tauri/src/services/overlay.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::get_app_handle;
|
||||||
|
|
||||||
|
pub static SCENE_WINDOW_LABEL: &str = "scene";
|
||||||
|
|
||||||
|
pub fn overlay_fullscreen(window: &tauri::Window) -> Result<(), tauri::Error> {
|
||||||
|
// Get the primary monitor
|
||||||
|
let monitor = get_app_handle().primary_monitor()?.unwrap();
|
||||||
|
|
||||||
|
// Get the work area (usable space, excluding menu bar/dock/notch)
|
||||||
|
let work_area = monitor.work_area();
|
||||||
|
|
||||||
|
// Set window position to top-left of the work area
|
||||||
|
window.set_position(tauri::PhysicalPosition {
|
||||||
|
x: work_area.position.x,
|
||||||
|
y: work_area.position.y,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Set window size to match work area size
|
||||||
|
window.set_size(tauri::PhysicalSize {
|
||||||
|
width: work_area.size.width,
|
||||||
|
height: work_area.size.height,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -10,16 +10,11 @@
|
|||||||
"frontendDist": "../build"
|
"frontendDist": "../build"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [],
|
||||||
{
|
|
||||||
"title": "friendolls-desktop",
|
|
||||||
"width": 800,
|
|
||||||
"height": 600
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
}
|
},
|
||||||
|
"macOSPrivateApi": true
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
|
|||||||
135
src/app.css
135
src/app.css
@@ -1,75 +1,76 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "daisyui";
|
@plugin "daisyui";
|
||||||
|
|
||||||
|
|
||||||
@plugin "daisyui/theme" {
|
@plugin "daisyui/theme" {
|
||||||
name: "proper";
|
name: "proper";
|
||||||
default: true;
|
default: true;
|
||||||
prefersdark: false;
|
prefersdark: false;
|
||||||
color-scheme: "light";
|
color-scheme: "light";
|
||||||
--color-base-100: oklch(96% 0.001 286.375);
|
--color-base-100: oklch(96% 0.001 286.375);
|
||||||
--color-base-200: oklch(92% 0.004 286.32);
|
--color-base-200: oklch(92% 0.004 286.32);
|
||||||
--color-base-300: oklch(87% 0.006 286.286);
|
--color-base-300: oklch(87% 0.006 286.286);
|
||||||
--color-base-content: oklch(44% 0.017 285.786);
|
--color-base-content: oklch(44% 0.017 285.786);
|
||||||
--color-primary: oklch(71% 0.143 215.221);
|
--color-primary: oklch(71% 0.143 215.221);
|
||||||
--color-primary-content: oklch(98% 0.019 200.873);
|
--color-primary-content: oklch(98% 0.019 200.873);
|
||||||
--color-secondary: oklch(67% 0.182 276.935);
|
--color-secondary: oklch(67% 0.182 276.935);
|
||||||
--color-secondary-content: oklch(93% 0.034 272.788);
|
--color-secondary-content: oklch(93% 0.034 272.788);
|
||||||
--color-accent: oklch(93% 0.032 255.585);
|
--color-accent: oklch(93% 0.032 255.585);
|
||||||
--color-accent-content: oklch(38% 0.063 188.416);
|
--color-accent-content: oklch(38% 0.063 188.416);
|
||||||
--color-neutral: oklch(96% 0.001 286.375);
|
--color-neutral: oklch(96% 0.001 286.375);
|
||||||
--color-neutral-content: oklch(0% 0 0);
|
--color-neutral-content: oklch(0% 0 0);
|
||||||
--color-info: oklch(58% 0.158 241.966);
|
--color-info: oklch(58% 0.158 241.966);
|
||||||
--color-info-content: oklch(100% 0 0);
|
--color-info-content: oklch(100% 0 0);
|
||||||
--color-success: oklch(76% 0.233 130.85);
|
--color-success: oklch(76% 0.233 130.85);
|
||||||
--color-success-content: oklch(98% 0.031 120.757);
|
--color-success-content: oklch(98% 0.031 120.757);
|
||||||
--color-warning: oklch(66% 0.179 58.318);
|
--color-warning: oklch(66% 0.179 58.318);
|
||||||
--color-warning-content: oklch(100% 0 0);
|
--color-warning-content: oklch(100% 0 0);
|
||||||
--color-error: oklch(70% 0.191 22.216);
|
--color-error: oklch(70% 0.191 22.216);
|
||||||
--color-error-content: oklch(100% 0 0);
|
--color-error-content: oklch(100% 0 0);
|
||||||
--radius-selector: 0.5rem;
|
--radius-selector: 0.5rem;
|
||||||
--radius-field: 0.5rem;
|
--radius-field: 0.5rem;
|
||||||
--radius-box: 0.5rem;
|
--radius-box: 0.5rem;
|
||||||
--size-selector: 0.25rem;
|
--size-selector: 0.25rem;
|
||||||
--size-field: 0.25rem;
|
--size-field: 0.25rem;
|
||||||
--border: 1px;
|
--border: 1px;
|
||||||
--depth: 1;
|
--depth: 1;
|
||||||
--noise: 1;
|
--noise: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@plugin "daisyui/theme" {
|
@plugin "daisyui/theme" {
|
||||||
name: "properdark";
|
name: "properdark";
|
||||||
default: false;
|
default: false;
|
||||||
prefersdark: true;
|
prefersdark: true;
|
||||||
color-scheme: "dark";
|
color-scheme: "dark";
|
||||||
--color-base-100: oklch(26% 0 0);
|
--color-base-100: oklch(26% 0 0);
|
||||||
--color-base-200: oklch(37% 0 0);
|
--color-base-200: oklch(37% 0 0);
|
||||||
--color-base-300: oklch(43% 0 0);
|
--color-base-300: oklch(43% 0 0);
|
||||||
--color-base-content: oklch(100% 0 0);
|
--color-base-content: oklch(100% 0 0);
|
||||||
--color-primary: oklch(86% 0.127 207.078);
|
--color-primary: oklch(86% 0.127 207.078);
|
||||||
--color-primary-content: oklch(39% 0.07 227.392);
|
--color-primary-content: oklch(39% 0.07 227.392);
|
||||||
--color-secondary: oklch(87% 0.065 274.039);
|
--color-secondary: oklch(87% 0.065 274.039);
|
||||||
--color-secondary-content: oklch(35% 0.144 278.697);
|
--color-secondary-content: oklch(35% 0.144 278.697);
|
||||||
--color-accent: oklch(36.2% 0.076 265.6);
|
--color-accent: oklch(36.2% 0.076 265.6);
|
||||||
--color-accent-content: oklch(97% 0.014 254.604);
|
--color-accent-content: oklch(97% 0.014 254.604);
|
||||||
--color-neutral: oklch(37% 0.01 67.558);
|
--color-neutral: oklch(37% 0.01 67.558);
|
||||||
--color-neutral-content: oklch(100% 0 0);
|
--color-neutral-content: oklch(100% 0 0);
|
||||||
--color-info: oklch(64.4% 0.12 237.3);
|
--color-info: oklch(64.4% 0.12 237.3);
|
||||||
--color-info-content: oklch(100% 0 0);
|
--color-info-content: oklch(100% 0 0);
|
||||||
--color-success: oklch(76% 0.233 130.85);
|
--color-success: oklch(76% 0.233 130.85);
|
||||||
--color-success-content: oklch(0% 0 0);
|
--color-success-content: oklch(0% 0 0);
|
||||||
--color-warning: oklch(87% 0.169 91.605);
|
--color-warning: oklch(87% 0.169 91.605);
|
||||||
--color-warning-content: oklch(0% 0 0);
|
--color-warning-content: oklch(0% 0 0);
|
||||||
--color-error: oklch(63% 0.237 25.331);
|
--color-error: oklch(63% 0.237 25.331);
|
||||||
--color-error-content: oklch(100% 0 0);
|
--color-error-content: oklch(100% 0 0);
|
||||||
--radius-selector: 0.5rem;
|
--radius-selector: 0.5rem;
|
||||||
--radius-field: 0.5rem;
|
--radius-field: 0.5rem;
|
||||||
--radius-box: 0.5rem;
|
--radius-box: 0.5rem;
|
||||||
--size-selector: 0.25rem;
|
--size-selector: 0.25rem;
|
||||||
--size-field: 0.25rem;
|
--size-field: 0.25rem;
|
||||||
--border: 1px;
|
--border: 1px;
|
||||||
--depth: 1;
|
--depth: 1;
|
||||||
--noise: 1;
|
--noise: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
22
src/app.html
22
src/app.html
@@ -1,13 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Friendolls</title>
|
<title>Friendolls</title>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
7
src/routes/+layout.svelte
Normal file
7
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="size-full bg-transparent">
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@@ -3,3 +3,4 @@
|
|||||||
// See: https://svelte.dev/docs/kit/single-page-apps
|
// See: https://svelte.dev/docs/kit/single-page-apps
|
||||||
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
|
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
|
import "../app.css";
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<script lang="ts">
|
|
||||||
import "../app.css";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main class="card-body">
|
<main class="card-body">
|
||||||
<button class="btn btn-primary">Hello TailwindCSS!</button>
|
<button class="btn btn-primary">Hello TailwindCSS!</button>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
10
src/routes/scene/+page.svelte
Normal file
10
src/routes/scene/+page.svelte
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="w-svw h-svh p-4">
|
||||||
|
<div
|
||||||
|
class="size-max mx-auto bg-base-100 border-base-200 border px-4 py-3 rounded-xl"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col text-center">
|
||||||
|
<p class="text-xl">Friendolls</p>
|
||||||
|
<p class="text-sm opacity-50">Scene Screen</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user