diff --git a/bun.lockb b/bun.lockb index 3b71ecd..0222652 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 07aa44c..0baad9e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "MIT", "dependencies": { "@tauri-apps/api": "^2", + "@tauri-apps/plugin-os": "^2.0.0", "@tauri-apps/plugin-shell": "^2", "@tauri-apps/plugin-store": "^2.1.0" }, diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b5918a9..a4ad99f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -719,6 +719,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fdeflate" version = "0.3.6" @@ -990,6 +1000,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" +dependencies = [ + "rustix", + "windows-targets 0.52.6", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1705,6 +1725,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.3" @@ -2162,6 +2188,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "os_pipe" version = "1.2.1" @@ -2696,6 +2733,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.18" @@ -2746,6 +2796,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-os", "tauri-plugin-shell", "tauri-plugin-store", ] @@ -3136,6 +3187,15 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3335,6 +3395,24 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-os" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc5f23a86f37687c7f4fecfdc706b279087bc44f7a46702f7307ff1551ee03a" +dependencies = [ + "gethostname", + "log", + "os_info", + "serde", + "serde_json", + "serialize-to-javascript", + "sys-locale", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", +] + [[package]] name = "tauri-plugin-shell" version = "2.0.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8337b86..cd4159f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,6 +20,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } tauri-plugin-shell = "2" +tauri-plugin-os = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" tauri-plugin-store = "2.0.0" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index fd59d27..1d19881 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,6 +2,28 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main"], - "permissions": ["core:default", "shell:allow-open", "store:default"] -} + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "shell:allow-open", + "store:default", + "core:window:allow-close", + "core:window:allow-is-closable", + "core:window:allow-is-decorated", + "core:window:allow-is-fullscreen", + "core:window:allow-is-maximizable", + "core:window:allow-is-maximized", + "core:window:allow-is-minimizable", + "core:window:allow-is-minimized", + "core:window:allow-is-focused", + "core:window:allow-maximize", + "core:window:allow-minimize", + "core:window:allow-start-dragging", + "core:window:allow-toggle-maximize", + "core:window:allow-unmaximize", + "core:window:allow-unminimize", + "os:default" + ] +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f91b35e..2335fbb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,3 +1,5 @@ +use tauri::WindowEvent; + // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ #[tauri::command] fn greet(name: &str) -> String { @@ -7,8 +9,13 @@ fn greet(name: &str) -> String { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() - .plugin(tauri_plugin_shell::init()) - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} + .on_window_event(|_, event| { + if let WindowEvent::Resized(_) = event { + std::thread::sleep(std::time::Duration::from_nanos(1)); + } + }) + .plugin(tauri_plugin_shell::init()) + .invoke_handler(tauri::generate_handler![greet]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c354705..9f59907 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,6 +3,7 @@ fn main() { tauri::Builder::default() + .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_store::Builder::default().build()) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index da2f4ad..ef865b6 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,9 +14,12 @@ { "title": "scope", "width": 800, - "height": 600 + "height": 600, + "decorations": false, + "transparent": true } ], + "macOSPrivateApi": true, "security": { "csp": null } diff --git a/src/components/windows_titlebar.svelte b/src/components/windows_titlebar.svelte new file mode 100644 index 0000000..0fec65b --- /dev/null +++ b/src/components/windows_titlebar.svelte @@ -0,0 +1,130 @@ + + +{#snippet button(kind: "close" | "maximize" | "restore" | "minimize")} + +{/snippet} + +
+
+ {@render contents()} +
+
+ {@render button("minimize")} + {#if maximized.value} + {@render button("restore")} + {:else} + {@render button("maximize")} + {/if} + {@render button("close")} +
+
+ + diff --git a/src/icons/arrow_left_into_bar_icon.svelte b/src/icons/arrow_left_into_bar_icon.svelte new file mode 100644 index 0000000..46cfd8a --- /dev/null +++ b/src/icons/arrow_left_into_bar_icon.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/src/icons/arrow_right_into_bar_icon.svelte b/src/icons/arrow_right_into_bar_icon.svelte new file mode 100644 index 0000000..9240777 --- /dev/null +++ b/src/icons/arrow_right_into_bar_icon.svelte @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/lib/platform/index.svelte.ts b/src/lib/platform/index.svelte.ts new file mode 100644 index 0000000..1672602 --- /dev/null +++ b/src/lib/platform/index.svelte.ts @@ -0,0 +1,33 @@ +import { getCurrentWindow } from "@tauri-apps/api/window" + +export function dragRegion(element: HTMLElement) { + element.dataset.tauriDragRegion = "" +} + +export const CURRENT_WINDOW = getCurrentWindow(); + +CURRENT_WINDOW.onFocusChanged(focus_change => { + focused.value = focus_change.payload; +}) + +CURRENT_WINDOW.isFocused().then(f => focused.value = f); +export let focused = $state({ value: false }); + +CURRENT_WINDOW.isDecorated().then(d => decorated.value = d); +export let decorated = $state({ value: false }); + +let old: any | undefined = undefined; + +CURRENT_WINDOW.onResized(async evt => { + if (evt.payload.height == old?.height && evt.payload.width == old?.width && evt.payload.type == old?.type) + return; + + old = { height: evt.payload.height, width: evt.payload.width, type: evt.payload.type }; + + maximized.value = await CURRENT_WINDOW.isMaximized(); + + console.log("Maximized", maximized.value); +}) + +CURRENT_WINDOW.isMaximized().then(m => maximized.value = m); +export let maximized = $state({ value: false }); \ No newline at end of file diff --git a/src/lib/rem_size.svelte.ts b/src/lib/rem_size.svelte.ts new file mode 100644 index 0000000..26c5c02 --- /dev/null +++ b/src/lib/rem_size.svelte.ts @@ -0,0 +1,17 @@ +function parse_rem_size(size: string | undefined): number { + if (size?.endsWith("px")) { + return parseInt(size) + } + + throw new Error("Failed to parse rem size: " + size); +} + +export const REM_SIZE = $state({ value: parse_rem_size(document.documentElement.computedStyleMap().get("font-size")?.toString()) }); + +let observer = new MutationObserver(_ => { + console.log("MUTATION") + + REM_SIZE.value = parse_rem_size(document.documentElement.computedStyleMap().get("font-size")?.toString()); +}) + +observer.observe(document.documentElement, { attributeFilter: [ "style" ], attributes: true, attributeOldValue: false, characterData: false, characterDataOldValue: false, childList: false, subtree: false }); \ No newline at end of file diff --git a/src/lib/theme.ts b/src/lib/theme.ts new file mode 100644 index 0000000..48a088f --- /dev/null +++ b/src/lib/theme.ts @@ -0,0 +1,63 @@ +export function theme(key: string) { + key = key.replace(/[A-Z]/g, m => "-" + m.toLowerCase()); + + return `var(--theme-${key})` +} + +export interface Theme { + iter_keys(): Generator<[string, string]> +} + +export class ThemeOverride implements Theme { + constructor(private base: Theme, private overrides: Record) {} + + *iter_keys() { + console.log(this.base); + + for (let [key, value] of this.base.iter_keys()) { + if (key in this.overrides) { + yield [key, this.overrides[key]] as [string, string]; + continue; + } + + yield [key, value] as [string, string]; + } + } +} + +export class DefaultTheme implements Theme { + *iter_keys() { + yield* Object.entries({ + "background": "#1a191c", + "foreground": "#25272b", + + "titlebar-windows-minimize-icon": "#cccccc", + "titlebar-windows-minimize-hover-background": "#373737", + "titlebar-windows-minimize-hover-icon": "#cccccc", + "titlebar-windows-minimize-hover-transition-speed": "66ms", + "titlebar-windows-minimize-active-background": "#545454", + "titlebar-windows-minimize-active-icon": "#cccccc", + + "titlebar-windows-maximize-icon": "#cccccc", + "titlebar-windows-maximize-hover-background": "#373737", + "titlebar-windows-maximize-hover-icon": "#cccccc", + "titlebar-windows-maximize-hover-transition-speed": "66ms", + "titlebar-windows-maximize-active-background": "#545454", + "titlebar-windows-maximize-active-icon": "#cccccc", + + "titlebar-windows-restore-icon": "#cccccc", + "titlebar-windows-restore-hover-background": "#373737", + "titlebar-windows-restore-hover-icon": "#cccccc", + "titlebar-windows-restore-hover-transition-speed": "66ms", + "titlebar-windows-restore-active-background": "#545454", + "titlebar-windows-restore-active-icon": "#cccccc", + + "titlebar-windows-close-icon": "#cccccc", + "titlebar-windows-close-hover-background": "#e81123", + "titlebar-windows-close-hover-icon": "#ffffff", + "titlebar-windows-close-hover-transition-speed": "66ms", + "titlebar-windows-close-active-background": "#94141e", + "titlebar-windows-close-active-icon": "#ffffff", + }); + } +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 881d2ba..d60ecde 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,303 @@ - - + + +{#snippet TitlebarContents()} +
+ +
+{/snippet} + +
+ + +
+
+ {#if !decorationsAbove} + {#if os === "windows"} + + {/if} + {:else} + {@render TitlebarContents()} + {/if} +
+ +
+ + +
+ {@render children()} +
+ + +
+
+
+ + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ea9a68c..5ca2a37 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -33,6 +33,6 @@ }); -
+

Scope

diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 99fb83f..5322ff8 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -20,7 +20,7 @@ }); -
+