From 5677b9beba49acc1fc6feb46ccd07182bed6e994 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 14 Jan 2026 16:38:07 +0800 Subject: feat: implement Mod Loader API for Fabric and Forge integration --- ui/src/lib/modLoaderApi.ts | 108 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 ui/src/lib/modLoaderApi.ts (limited to 'ui/src/lib') diff --git a/ui/src/lib/modLoaderApi.ts b/ui/src/lib/modLoaderApi.ts new file mode 100644 index 0000000..9d0d09d --- /dev/null +++ b/ui/src/lib/modLoaderApi.ts @@ -0,0 +1,108 @@ +/** + * Mod Loader API service for Fabric and Forge integration. + * This module provides functions to interact with the Tauri backend + * for mod loader version management. + */ + +import { invoke } from "@tauri-apps/api/core"; +import type { + FabricGameVersion, + FabricLoaderVersion, + FabricLoaderEntry, + InstalledFabricVersion, + ForgeVersion, + InstalledForgeVersion, +} from "../types"; + +// ==================== Fabric API ==================== + +/** + * Get all Minecraft versions supported by Fabric. + */ +export async function getFabricGameVersions(): Promise { + return invoke("get_fabric_game_versions"); +} + +/** + * Get all available Fabric loader versions. + */ +export async function getFabricLoaderVersions(): Promise { + return invoke("get_fabric_loader_versions"); +} + +/** + * Get Fabric loaders available for a specific Minecraft version. + */ +export async function getFabricLoadersForVersion( + gameVersion: string +): Promise { + return invoke("get_fabric_loaders_for_version", { + gameVersion, + }); +} + +/** + * Install Fabric loader for a specific Minecraft version. + */ +export async function installFabric( + gameVersion: string, + loaderVersion: string +): Promise { + return invoke("install_fabric", { + gameVersion, + loaderVersion, + }); +} + +/** + * List all installed Fabric versions. + */ +export async function listInstalledFabricVersions(): Promise { + return invoke("list_installed_fabric_versions"); +} + +/** + * Check if Fabric is installed for a specific version combination. + */ +export async function isFabricInstalled( + gameVersion: string, + loaderVersion: string +): Promise { + return invoke("is_fabric_installed", { + gameVersion, + loaderVersion, + }); +} + +// ==================== Forge API ==================== + +/** + * Get all Minecraft versions supported by Forge. + */ +export async function getForgeGameVersions(): Promise { + return invoke("get_forge_game_versions"); +} + +/** + * Get Forge versions available for a specific Minecraft version. + */ +export async function getForgeVersionsForGame( + gameVersion: string +): Promise { + return invoke("get_forge_versions_for_game", { + gameVersion, + }); +} + +/** + * Install Forge for a specific Minecraft version. + */ +export async function installForge( + gameVersion: string, + forgeVersion: string +): Promise { + return invoke("install_forge", { + gameVersion, + forgeVersion, + }); +} -- cgit v1.2.3-70-g09d2 From eed52135e7d6ffbbbd64070cf567bcf08653c7d5 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 14 Jan 2026 18:15:31 +0800 Subject: feat: Enhance UI components and add visual effects - Updated Sidebar component styles for improved aesthetics and usability. - Refactored VersionsView component with a new layout and enhanced version filtering. - Improved DownloadMonitor and GameConsole components for better performance and visual consistency. - Added new settings for GPU acceleration and visual effects in settings store. - Introduced ParticleBackground component with customizable effects (Constellation and Saturn). - Implemented ConstellationEffect and SaturnEffect classes for dynamic background animations. --- src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 3 +- src-tauri/src/core/config.rs | 8 + src-tauri/src/main.rs | 1 + ui/package.json | 1 + ui/pnpm-lock.yaml | 10 + ui/src/App.svelte | 145 +++++++++++--- ui/src/components/BottomBar.svelte | 79 ++++---- ui/src/components/HomeView.svelte | 57 ++++-- ui/src/components/ModLoaderSelector.svelte | 214 ++++++++++----------- ui/src/components/ParticleBackground.svelte | 57 ++++++ ui/src/components/SettingsView.svelte | 282 ++++++++++++++++++++-------- ui/src/components/Sidebar.svelte | 76 ++++---- ui/src/components/VersionsView.svelte | 184 +++++++++--------- ui/src/lib/DownloadMonitor.svelte | 2 +- ui/src/lib/GameConsole.svelte | 2 +- ui/src/lib/effects/ConstellationEffect.ts | 163 ++++++++++++++++ ui/src/lib/effects/SaturnEffect.ts | 194 +++++++++++++++++++ ui/src/stores/settings.svelte.ts | 3 + ui/src/types/index.ts | 4 + 20 files changed, 1080 insertions(+), 406 deletions(-) create mode 100644 ui/src/components/ParticleBackground.svelte create mode 100644 ui/src/lib/effects/ConstellationEffect.ts create mode 100644 ui/src/lib/effects/SaturnEffect.ts (limited to 'ui/src/lib') diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d7319a7..1d4ccc5 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,6 +23,7 @@ sha1 = "0.10" hex = "0.4" zip = "2.2.2" serde_urlencoded = "0.7.1" +tauri-plugin-dialog = "2.5.0" [build-dependencies] tauri-build = { version = "2.0", features = [] } diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 894b905..4d8b907 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -12,6 +12,7 @@ "core:app:allow-version", "core:path:default", "core:window:default", - "shell:allow-open" + "shell:allow-open", + "dialog:default" ] } diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs index d6d594f..27e0011 100644 --- a/src-tauri/src/core/config.rs +++ b/src-tauri/src/core/config.rs @@ -13,6 +13,10 @@ pub struct LauncherConfig { pub width: u32, pub height: u32, pub download_threads: u32, // concurrent download threads (1-128) + pub custom_background_path: Option, + pub enable_gpu_acceleration: bool, + pub enable_visual_effects: bool, + pub active_effect: String, } impl Default for LauncherConfig { @@ -24,6 +28,10 @@ impl Default for LauncherConfig { width: 854, height: 480, download_threads: 32, + custom_background_path: None, + enable_gpu_acceleration: false, + enable_visual_effects: true, + active_effect: "constellation".to_string(), } } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ba16f7a..f7a391a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1004,6 +1004,7 @@ async fn install_forge( fn main() { tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .manage(core::auth::AccountState::new()) .manage(MsRefreshTokenState::new()) diff --git a/ui/package.json b/ui/package.json index 0806781..03cc405 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@tauri-apps/api": "^2.9.1", + "@tauri-apps/plugin-dialog": "^2.5.0", "@tauri-apps/plugin-shell": "^2.3.4" } } diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index d48c01e..23d4ee2 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@tauri-apps/api': specifier: ^2.9.1 version: 2.9.1 + '@tauri-apps/plugin-dialog': + specifier: ^2.5.0 + version: 2.5.0 '@tauri-apps/plugin-shell': specifier: ^2.3.4 version: 2.3.4 @@ -296,6 +299,9 @@ packages: '@tauri-apps/api@2.9.1': resolution: {integrity: sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==} + '@tauri-apps/plugin-dialog@2.5.0': + resolution: {integrity: sha512-I0R0ygwRd9AN8Wj5GnzCogOlqu2+OWAtBd0zEC4+kQCI32fRowIyuhPCBoUv4h/lQt2bM39kHlxPHD5vDcFjiA==} + '@tauri-apps/plugin-shell@2.3.4': resolution: {integrity: sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA==} @@ -808,6 +814,10 @@ snapshots: '@tauri-apps/api@2.9.1': {} + '@tauri-apps/plugin-dialog@2.5.0': + dependencies: + '@tauri-apps/api': 2.9.1 + '@tauri-apps/plugin-shell@2.3.4': dependencies: '@tauri-apps/api': 2.9.1 diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 3750f11..d93374e 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -1,6 +1,7 @@
- + +
+ {#if settingsState.settings.custom_background_path} + Background + +
+ {:else if settingsState.settings.enable_visual_effects} + +
- -
- - -
- -
- - -
{#if uiState.currentView === "home"} - - {:else if uiState.currentView === "versions"} - - {:else if uiState.currentView === "settings"} - + {/if} + +
+ {/if} + + +
+
+ + +
+ + + + +
+ + +
- -
+ +
+ +
+ {#if uiState.currentView === "home"} + + {:else if uiState.currentView === "versions"} + + {:else if uiState.currentView === "settings"} + + {/if} +
+ + +
+
+ +
+
+ + + +
+
+
- - + + {#if uiState.showConsole} + +
+
+ + +
+
+ {/if}
+ + diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte index dcad9e8..dd218f3 100644 --- a/ui/src/components/BottomBar.svelte +++ b/ui/src/components/BottomBar.svelte @@ -5,18 +5,19 @@
-
+ +
authState.openLoginModal()} role="button" tabindex="0" onkeydown={(e) => e.key === "Enter" && authState.openLoginModal()} >
{#if authState.currentAccount} {:else} - ? + ? {/if}
-
- {authState.currentAccount ? authState.currentAccount.username : "Click to Login"} +
+ {authState.currentAccount ? authState.currentAccount.username : "Login"}
-
+
- {authState.currentAccount ? "Ready" : "Guest"} + {authState.currentAccount ? "Ready to play" : "Guest Mode"}
+ +
+
-
+ +
Selected Version - +
+ +
+
diff --git a/ui/src/components/HomeView.svelte b/ui/src/components/HomeView.svelte index e876c14..036c03a 100644 --- a/ui/src/components/HomeView.svelte +++ b/ui/src/components/HomeView.svelte @@ -1,26 +1,47 @@ - -
-
+
+ + +
+
-
-

+ +
- MINECRAFT -

-
- JAVA EDITION - Release 1.20.4 + MINECRAFT + + +
+
+ Java Edition +
+
+ Latest Release 1.21 +
+
+
+ + +
+ +
+ Ready to play. Select version below or hit Launch. +
diff --git a/ui/src/components/ModLoaderSelector.svelte b/ui/src/components/ModLoaderSelector.svelte index 06eb6ae..4a59916 100644 --- a/ui/src/components/ModLoaderSelector.svelte +++ b/ui/src/components/ModLoaderSelector.svelte @@ -112,134 +112,134 @@ } -
-

Mod Loader

+
+
+

Select Mod Loader

+
- -
+ +
- {#if selectedLoader === "vanilla"} -

- Launch the selected Minecraft version without any mod loaders. -

- {:else if !selectedGameVersion} -

- Select a Minecraft version first to see available {selectedLoader} versions. -

- {:else if isLoading} -
- - - - - Loading {selectedLoader} versions... -
- {:else if error} -

{error}

- {:else if selectedLoader === "fabric"} -
-
- - -
- -
- {:else if selectedLoader === "forge"} -
- {#if forgeVersions.length === 0} -

- No Forge versions available for Minecraft {selectedGameVersion} -

- {:else} + +
+ {#if selectedLoader === "vanilla"} +
+ No mod loader selected.
Pure vanilla experience. +
+ + {:else if !selectedGameVersion} +
+ ⚠️ Please select a base Minecraft version first. +
+ + {:else if isLoading} +
+
+ Loading {selectedLoader} versions... +
+ + {:else if error} +
+ {error} +
+ + {:else if selectedLoader === "fabric"} +
- - + +
+ +
+
+ - {/if} -
- {/if} +
+ + {:else if selectedLoader === "forge"} +
+ {#if forgeVersions.length === 0} +
+ No Forge versions available for {selectedGameVersion} +
+ {:else} +
+ +
+ +
+
+
+ + + {/if} +
+ {/if} +
diff --git a/ui/src/components/ParticleBackground.svelte b/ui/src/components/ParticleBackground.svelte new file mode 100644 index 0000000..080f1f2 --- /dev/null +++ b/ui/src/components/ParticleBackground.svelte @@ -0,0 +1,57 @@ + + + diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte index 801970b..4c92220 100644 --- a/ui/src/components/SettingsView.svelte +++ b/ui/src/components/SettingsView.svelte @@ -1,150 +1,274 @@ -
-

Settings

+
+
+

Settings

+
+ +
-
- -
- -
- - + +
+

+ Appearance +

+ +
+
+ + +
+ +
+ {#if settingsState.settings.custom_background_path} + Background Preview + {:else} +
+
Default Gradient
+ {/if} +
+ + +
+ + + {#if settingsState.settings.custom_background_path} + + {/if} +
+
+

+ Select an image from your computer to replace the default gradient background. + Supported formats: PNG, JPG, WEBP, GIF. +

+
+ + +
+
+
+

Visual Effects

+

Enable particle effects and animated gradients. (Default: On)

+
+ +
+ + {#if settingsState.settings.enable_visual_effects} +
+
+

Theme Effect

+

Select the active visual theme.

+
+ +
+ {/if} + +
+
+

GPU Acceleration

+

Enable GPU acceleration for the interface. (Default: Off, Requires Restart)

+
+ +
+
+
+ + +
+

+ Java Environment +

+
+
+ +
+ + +
+
{#if settingsState.javaInstallations.length > 0}
-

Detected Java Installations:

+

Detected Installations

{#each settingsState.javaInstallations as java} {/each}
{/if} - -

- The command or path to the Java Runtime Environment. Click "Auto Detect" to find installed Java versions. -

+
-
-

Memory Allocation (RAM)

- +
+

+ Memory Allocation (RAM) +

- +
- +
-
-

Game Window Size

+
+

+ Game Window Size +

- +
- +
-
-

Download Settings

-
- - -

- Number of concurrent download threads (1-128). Higher values increase download speed but use more bandwidth and system resources. Default: 32 -

-
+
+

+ Network +

+
+ + +

Higher values usually mean faster downloads but use more CPU/Network.

+
-
+
diff --git a/ui/src/components/Sidebar.svelte b/ui/src/components/Sidebar.svelte index a4f4e35..7976f6a 100644 --- a/ui/src/components/Sidebar.svelte +++ b/ui/src/components/Sidebar.svelte @@ -3,64 +3,56 @@ diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte index 1ea4878..00ac281 100644 --- a/ui/src/components/VersionsView.svelte +++ b/ui/src/components/VersionsView.svelte @@ -73,15 +73,15 @@ function getVersionBadge(type: string) { switch (type) { case "release": - return { text: "Release", class: "bg-green-600" }; + return { text: "Release", class: "bg-emerald-500/20 text-emerald-300 border-emerald-500/30" }; case "snapshot": - return { text: "Snapshot", class: "bg-yellow-600" }; + return { text: "Snapshot", class: "bg-amber-500/20 text-amber-300 border-amber-500/30" }; case "fabric": - return { text: "Fabric", class: "bg-blue-600" }; + return { text: "Fabric", class: "bg-indigo-500/20 text-indigo-300 border-indigo-500/30" }; case "forge": - return { text: "Forge", class: "bg-orange-600" }; + return { text: "Forge", class: "bg-orange-500/20 text-orange-300 border-orange-500/30" }; default: - return { text: type, class: "bg-zinc-600" }; + return { text: type, class: "bg-zinc-500/20 text-zinc-300 border-zinc-500/30" }; } } @@ -114,101 +114,92 @@ }); -
-

Versions

+
+
+

Version Manager

+
Select a version to play or modify
+
-
+
-
- +
+
- +
+ 🔍 + +
- -
- - - - + +
+ {#each ['all', 'release', 'snapshot', 'modded'] as filter} + + {/each}
- -
+ +
{#if gameState.versions.length === 0} -
Loading versions...
+
+ Fetching manifest... +
{:else if filteredVersions().length === 0} -
- {#if normalizedQuery.length > 0} - No versions found matching "{searchQuery}" - {:else} - No versions in this category - {/if} +
+ 👻 + No matching versions found
{:else} {#each filteredVersions() as version} {@const badge = getVersionBadge(version.type)} + {@const isSelected = gameState.selectedVersion === version.id} {/each} @@ -217,32 +208,29 @@
-
- - {#if gameState.selectedVersion} -
-

Selected

-

- {gameState.selectedVersion} -

-
- {/if} - - - - - -
-

💡 Tip

-

- Select a vanilla Minecraft version, then use the Mod Loader panel to - install Fabric or Forge. Installed modded versions will appear in the - list with colored badges. -

+
+ +
+
+ +

Current Selection

+ {#if gameState.selectedVersion} +

+ {gameState.selectedVersion} +

+ {:else} +

None selected

+ {/if}
+ + +
+ +
+
diff --git a/ui/src/lib/DownloadMonitor.svelte b/ui/src/lib/DownloadMonitor.svelte index 52c935c..860952c 100644 --- a/ui/src/lib/DownloadMonitor.svelte +++ b/ui/src/lib/DownloadMonitor.svelte @@ -156,7 +156,7 @@ {#if visible}

Downloads

diff --git a/ui/src/lib/GameConsole.svelte b/ui/src/lib/GameConsole.svelte index 281dc85..8d5e0ce 100644 --- a/ui/src/lib/GameConsole.svelte +++ b/ui/src/lib/GameConsole.svelte @@ -75,7 +75,7 @@ {#if visible} -
+
Logs diff --git a/ui/src/lib/effects/ConstellationEffect.ts b/ui/src/lib/effects/ConstellationEffect.ts new file mode 100644 index 0000000..2cc702e --- /dev/null +++ b/ui/src/lib/effects/ConstellationEffect.ts @@ -0,0 +1,163 @@ + +export class ConstellationEffect { + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private width: number = 0; + private height: number = 0; + private particles: Particle[] = []; + private animationId: number = 0; + private mouseX: number = -1000; + private mouseY: number = -1000; + + // Configuration + private readonly particleCount = 100; + private readonly connectionDistance = 150; + private readonly particleSpeed = 0.5; + + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.ctx = canvas.getContext("2d", { alpha: true })!; + + // Bind methods + this.animate = this.animate.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + + // Initial setup + this.resize(window.innerWidth, window.innerHeight); + this.initParticles(); + + // Mouse interaction + window.addEventListener("mousemove", this.handleMouseMove); + + // Start animation + this.animate(); + } + + resize(width: number, height: number) { + const dpr = window.devicePixelRatio || 1; + this.width = width; + this.height = height; + + this.canvas.width = width * dpr; + this.canvas.height = height * dpr; + this.canvas.style.width = `${width}px`; + this.canvas.style.height = `${height}px`; + + this.ctx.scale(dpr, dpr); + + // Re-initialize if screen size changes significantly to maintain density + if (this.particles.length === 0) { + this.initParticles(); + } + } + + private initParticles() { + this.particles = []; + // Adjust density based on screen area + const area = this.width * this.height; + const density = Math.floor(area / 15000); // 1 particle per 15000px² + const count = Math.min(Math.max(density, 50), 200); // Clamp between 50 and 200 + + for (let i = 0; i < count; i++) { + this.particles.push(new Particle(this.width, this.height, this.particleSpeed)); + } + } + + private handleMouseMove(e: MouseEvent) { + const rect = this.canvas.getBoundingClientRect(); + this.mouseX = e.clientX - rect.left; + this.mouseY = e.clientY - rect.top; + } + + animate() { + this.ctx.clearRect(0, 0, this.width, this.height); + + // Update and draw particles + this.particles.forEach(p => { + p.update(this.width, this.height); + p.draw(this.ctx); + }); + + // Draw lines + this.drawConnections(); + + this.animationId = requestAnimationFrame(this.animate); + } + + private drawConnections() { + this.ctx.lineWidth = 1; + + for (let i = 0; i < this.particles.length; i++) { + const p1 = this.particles[i]; + + // Connect to mouse if close + const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); + if (distMouse < this.connectionDistance + 50) { + const alpha = 1 - (distMouse / (this.connectionDistance + 50)); + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(this.mouseX, this.mouseY); + this.ctx.stroke(); + + // Gently attract to mouse + if (distMouse > 10) { + p1.x += (this.mouseX - p1.x) * 0.005; + p1.y += (this.mouseY - p1.y) * 0.005; + } + } + + // Connect to other particles + for (let j = i + 1; j < this.particles.length; j++) { + const p2 = this.particles[j]; + const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); + + if (dist < this.connectionDistance) { + const alpha = 1 - (dist / this.connectionDistance); + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(p2.x, p2.y); + this.ctx.stroke(); + } + } + } + } + + destroy() { + cancelAnimationFrame(this.animationId); + window.removeEventListener("mousemove", this.handleMouseMove); + } +} + +class Particle { + x: number; + y: number; + vx: number; + vy: number; + size: number; + + constructor(w: number, h: number, speed: number) { + this.x = Math.random() * w; + this.y = Math.random() * h; + this.vx = (Math.random() - 0.5) * speed; + this.vy = (Math.random() - 0.5) * speed; + this.size = Math.random() * 2 + 1; + } + + update(w: number, h: number) { + this.x += this.vx; + this.y += this.vy; + + // Bounce off walls + if (this.x < 0 || this.x > w) this.vx *= -1; + if (this.y < 0 || this.y > h) this.vy *= -1; + } + + draw(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + } +} diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts new file mode 100644 index 0000000..8a1c11f --- /dev/null +++ b/ui/src/lib/effects/SaturnEffect.ts @@ -0,0 +1,194 @@ +// Optimized Saturn Effect for low-end hardware +// Uses TypedArrays for memory efficiency and reduced particle density + +export class SaturnEffect { + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private width: number = 0; + private height: number = 0; + + // Data-oriented design for performance + // xyz: Float32Array where [i*3, i*3+1, i*3+2] corresponds to x, y, z + private xyz: Float32Array | null = null; + // types: Uint8Array where 0 = planet, 1 = ring + private types: Uint8Array | null = null; + private count: number = 0; + + private animationId: number = 0; + private angle: number = 0; + private scaleFactor: number = 1; + + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.ctx = canvas.getContext('2d', { + alpha: true, + desynchronized: false // default is usually fine, 'desynchronized' can help latency but might flicker + })!; + + // Initial resize will set up everything + this.resize(window.innerWidth, window.innerHeight); + this.initParticles(); + + this.animate = this.animate.bind(this); + this.animate(); + } + + resize(width: number, height: number) { + const dpr = window.devicePixelRatio || 1; + this.width = width; + this.height = height; + + this.canvas.width = width * dpr; + this.canvas.height = height * dpr; + this.canvas.style.width = `${width}px`; + this.canvas.style.height = `${height}px`; + + this.ctx.scale(dpr, dpr); + + // Dynamic scaling based on screen size + const minDim = Math.min(width, height); + this.scaleFactor = minDim * 0.45; + } + + initParticles() { + // Significantly reduced particle count for CPU optimization + // Planet: 1800 -> 1000 + // Rings: 5000 -> 2500 + // Total approx 3500 vs 6800 previously (approx 50% reduction) + const planetCount = 1000; + const ringCount = 2500; + this.count = planetCount + ringCount; + + // Use TypedArrays for better memory locality + this.xyz = new Float32Array(this.count * 3); + this.types = new Uint8Array(this.count); + + let idx = 0; + + // 1. Planet + for (let i = 0; i < planetCount; i++) { + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos((Math.random() * 2) - 1); + const r = 1.0; + + // x, y, z + this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); + this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); + this.xyz[idx * 3 + 2] = r * Math.cos(phi); + + this.types[idx] = 0; // 0 for planet + idx++; + } + + // 2. Rings + const ringInner = 1.4; + const ringOuter = 2.3; + + for (let i = 0; i < ringCount; i++) { + const angle = Math.random() * Math.PI * 2; + const dist = Math.sqrt(Math.random() * (ringOuter*ringOuter - ringInner*ringInner) + ringInner*ringInner); + + // x, y, z + this.xyz[idx * 3] = dist * Math.cos(angle); + this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; + this.xyz[idx * 3 + 2] = dist * Math.sin(angle); + + this.types[idx] = 1; // 1 for ring + idx++; + } + } + + animate() { + this.ctx.clearRect(0, 0, this.width, this.height); + + // Normal blending + this.ctx.globalCompositeOperation = 'source-over'; + + // Slower rotation (from 0.0015 to 0.0005) + this.angle += 0.0005; + + const cx = this.width * 0.6; + const cy = this.height * 0.5; + + // Pre-calculate rotation matrices + const rotationY = this.angle; + const rotationX = 0.4; + const rotationZ = 0.15; + + const sinY = Math.sin(rotationY); + const cosY = Math.cos(rotationY); + const sinX = Math.sin(rotationX); + const cosX = Math.cos(rotationX); + const sinZ = Math.sin(rotationZ); + const cosZ = Math.cos(rotationZ); + + const fov = 1500; + const scaleFactor = this.scaleFactor; + + if (!this.xyz || !this.types) return; + + for (let i = 0; i < this.count; i++) { + const x = this.xyz[i * 3]; + const y = this.xyz[i * 3 + 1]; + const z = this.xyz[i * 3 + 2]; + + // Apply Scale + const px = x * scaleFactor; + const py = y * scaleFactor; + const pz = z * scaleFactor; + + // 1. Rotate Y + const x1 = px * cosY - pz * sinY; + const z1 = pz * cosY + px * sinY; + // y1 = py + + // 2. Rotate X + const y2 = py * cosX - z1 * sinX; + const z2 = z1 * cosX + py * sinX; + // x2 = x1 + + // 3. Rotate Z + const x3 = x1 * cosZ - y2 * sinZ; + const y3 = y2 * cosZ + x1 * sinZ; + const z3 = z2; + + const scale = fov / (fov + z3); + + if (z3 > -fov) { + const x2d = cx + x3 * scale; + const y2d = cy + y3 * scale; + + // Size calculation - slightly larger dots to compensate for lower count + // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 + const type = this.types[i]; + const sizeBase = type === 0 ? 2.4 : 1.5; + const size = sizeBase * scale; + + // Opacity + let alpha = (scale * scale * scale); + if (alpha > 1) alpha = 1; + if (alpha < 0.15) continue; // Skip very faint particles for performance + + // Optimization: Planet color vs Ring color + if (type === 0) { + // Planet: Warn White + this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; + } else { + // Ring: Cool White + this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; + } + + // Render as squares (fillRect) instead of circles (arc) + // This is significantly faster for software rendering and reduces GPU usage. + this.ctx.fillRect(x2d, y2d, size, size); + } + } + + this.animationId = requestAnimationFrame(this.animate); + } + + destroy() { + cancelAnimationFrame(this.animationId); + } +} + diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts index 397b9a6..c59bf3c 100644 --- a/ui/src/stores/settings.svelte.ts +++ b/ui/src/stores/settings.svelte.ts @@ -10,6 +10,9 @@ export class SettingsState { width: 854, height: 480, download_threads: 32, + enable_gpu_acceleration: false, + enable_visual_effects: true, + active_effect: "constellation", }); javaInstallations = $state([]); isDetectingJava = $state(false); diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 933aab5..fa48075 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -30,6 +30,10 @@ export interface LauncherConfig { width: number; height: number; download_threads: number; + custom_background_path?: string; + enable_gpu_acceleration: boolean; + enable_visual_effects: boolean; + active_effect: string; } export interface JavaInstallation { -- cgit v1.2.3-70-g09d2 From 1b92b559a37166796d431ade745b31c4f73bc2ed Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:20:41 +0100 Subject: fix(ui): correct styling bugs and code quality issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix missing h- prefix in VersionsView height class (-40 → h-40) - Correct typo in SaturnEffect comment (Warn → Warm White) - Remove double space in App.svelte class attribute --- ui/src/App.svelte | 2 +- ui/src/components/VersionsView.svelte | 2 +- ui/src/lib/effects/SaturnEffect.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ui/src/lib') diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 1c465b1..968f6c5 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -140,7 +140,7 @@ {#if uiState.showConsole} -
+
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte index 8f3a568..99cc296 100644 --- a/ui/src/components/VersionsView.svelte +++ b/ui/src/components/VersionsView.svelte @@ -158,7 +158,7 @@ Fetching manifest...
{:else if filteredVersions().length === 0} -
+
👻 No matching versions found
diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts index 8a1c11f..a370936 100644 --- a/ui/src/lib/effects/SaturnEffect.ts +++ b/ui/src/lib/effects/SaturnEffect.ts @@ -171,7 +171,7 @@ export class SaturnEffect { // Optimization: Planet color vs Ring color if (type === 0) { - // Planet: Warn White + // Planet: Warm White this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; } else { // Ring: Cool White -- cgit v1.2.3-70-g09d2 From 485e8290e1ceaaeb82c7decd1330c8ca1b1f28bd Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Thu, 15 Jan 2026 16:57:20 +0800 Subject: feat: Enhance GameConsole with advanced log filtering, searching, and export/upload functionalities for improved user experience --- ui/src/lib/GameConsole.svelte | 366 ++++++++++++++++++++++++++++++++---------- 1 file changed, 281 insertions(+), 85 deletions(-) (limited to 'ui/src/lib') diff --git a/ui/src/lib/GameConsole.svelte b/ui/src/lib/GameConsole.svelte index 8d5e0ce..1b1ab53 100644 --- a/ui/src/lib/GameConsole.svelte +++ b/ui/src/lib/GameConsole.svelte @@ -1,107 +1,303 @@ -{#if visible} -
-
-
- Logs -
- LAUNCHER - GAME - ERROR -
+
+ +
+
+

Console

+ + + + + +
+ +
+ +
+ +
+ +
+ + +
+ + + {#if searchQuery} + + {/if} +
-
- - - + + +
+ + {filteredLogs.length} / {logsState.logs.length} + + + + +
+
-
- {#each logs as log} -
- {log.timestamp} - [{log.type === 'launcher' ? 'LAUNCHER' : log.type === 'stderr' ? 'ERROR' : 'GAME'}] - {log.line} -
+ + +
+ {#each filteredLogs as log (log.id)} +
+ + {log.timestamp.split('.')[0]} + + +
+ {log.source} + {getLevelLabel(log.level)} +
+ + +
+ {@html highlightText(log.message, searchQuery)} +
+
{/each} - {#if logs.length === 0} -
Waiting for output... Click "Show Logs" and start a game to see logs here.
+ + {#if filteredLogs.length === 0} +
+ {#if logsState.logs.length === 0} + Waiting for logs... + {:else} + No logs match current filters. + {/if} +
{/if}
+ + + {#if !autoScroll} + + {/if}
-{/if} + + -- cgit v1.2.3-70-g09d2 From 31077dbd39a25eecd24a1dca0f8c9d1879265277 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Thu, 15 Jan 2026 17:36:36 +0800 Subject: feat: Implement custom dropdown components for version selection in BottomBar and ModLoaderSelector, enhancing user interaction and UI consistency --- ui/src/app.css | 97 ++++++++++++++----- ui/src/components/BottomBar.svelte | 97 ++++++++++++++++--- ui/src/components/CustomSelect.svelte | 136 ++++++++++++++++++++++++++ ui/src/components/ModLoaderSelector.svelte | 150 ++++++++++++++++++++++++----- ui/src/components/SettingsView.svelte | 33 ++++--- ui/src/lib/GameConsole.svelte | 19 ++-- 6 files changed, 443 insertions(+), 89 deletions(-) create mode 100644 ui/src/components/CustomSelect.svelte (limited to 'ui/src/lib') diff --git a/ui/src/app.css b/ui/src/app.css index 5d0404b..82aa72f 100644 --- a/ui/src/app.css +++ b/ui/src/app.css @@ -14,43 +14,48 @@ select { padding-right: 2rem; } -/* Custom scrollbar for dropdowns and lists */ -select, -.custom-select { - scrollbar-width: thin; - scrollbar-color: #3f3f46 #18181b; -} - -/* Webkit scrollbar for select (when expanded, browser-dependent) */ -select::-webkit-scrollbar { - width: 8px; +/* Option styling - works in WebView/Chromium */ +select option { + background-color: #18181b; + color: #e4e4e7; + padding: 12px 16px; + font-size: 13px; + border: none; } -select::-webkit-scrollbar-track { - background: #18181b; +select option:hover, +select option:focus { + background-color: #3730a3 !important; + color: white !important; } -select::-webkit-scrollbar-thumb { - background-color: #3f3f46; - border-radius: 4px; +select option:checked { + background: linear-gradient(0deg, #4f46e5 0%, #4f46e5 100%); + color: white; + font-weight: 500; } -select::-webkit-scrollbar-thumb:hover { - background-color: #52525b; +select option:disabled { + color: #52525b; + background-color: #18181b; } -/* Option styling (limited browser support but good for Tauri/WebView) */ -select option { +/* Optgroup styling */ +select optgroup { background-color: #18181b; - color: #e4e4e7; - padding: 8px 12px; + color: #a1a1aa; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 8px 12px 4px; } -select option:hover, -select option:focus, -select option:checked { - background: linear-gradient(#3730a3, #3730a3); - color: white; +/* Select focus state */ +select:focus { + outline: none; + border-color: #6366f1; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); } /* ==================== Custom Scrollbar (Global) ==================== */ @@ -118,3 +123,43 @@ input[type="number"]::-webkit-inner-spin-button { input[type="number"] { -moz-appearance: textfield; } + +/* ==================== Checkbox Styling ==================== */ + +input[type="checkbox"] { + appearance: none; + width: 16px; + height: 16px; + border: 1px solid #3f3f46; + border-radius: 4px; + background-color: #18181b; + cursor: pointer; + position: relative; + transition: all 0.15s ease; +} + +input[type="checkbox"]:hover { + border-color: #52525b; +} + +input[type="checkbox"]:checked { + background-color: #4f46e5; + border-color: #4f46e5; +} + +input[type="checkbox"]:checked::after { + content: ''; + position: absolute; + left: 5px; + top: 2px; + width: 4px; + height: 8px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +input[type="checkbox"]:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); +} diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte index 198d4e6..abb0b23 100644 --- a/ui/src/components/BottomBar.svelte +++ b/ui/src/components/BottomBar.svelte @@ -2,7 +2,39 @@ import { authState } from "../stores/auth.svelte"; import { gameState } from "../stores/game.svelte"; import { uiState } from "../stores/ui.svelte"; - import { Terminal, ChevronDown, Play, User } from 'lucide-svelte'; + import { Terminal, ChevronDown, Play, User, Check } from 'lucide-svelte'; + + let isVersionDropdownOpen = $state(false); + let dropdownRef: HTMLDivElement; + + let versionOptions = $derived( + gameState.versions.length === 0 + ? [{ id: "loading", type: "loading", label: "Loading..." }] + : gameState.versions.map(v => ({ + ...v, + label: `${v.id}${v.type !== 'release' ? ` (${v.type})` : ''}` + })) + ); + + function selectVersion(id: string) { + if (id !== "loading") { + gameState.selectedVersion = id; + isVersionDropdownOpen = false; + } + } + + function handleClickOutside(e: MouseEvent) { + if (dropdownRef && !dropdownRef.contains(e.target as Node)) { + isVersionDropdownOpen = false; + } + } + + $effect(() => { + if (isVersionDropdownOpen) { + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + } + });
-
- -
- -
+
+ {/if}
diff --git a/ui/src/components/CustomSelect.svelte b/ui/src/components/CustomSelect.svelte new file mode 100644 index 0000000..2e89c75 --- /dev/null +++ b/ui/src/components/CustomSelect.svelte @@ -0,0 +1,136 @@ + + +
+ + + + + {#if isOpen} +
+ {#each options as option} + + {/each} +
+ {/if} +
diff --git a/ui/src/components/ModLoaderSelector.svelte b/ui/src/components/ModLoaderSelector.svelte index d0c1b59..cb949c5 100644 --- a/ui/src/components/ModLoaderSelector.svelte +++ b/ui/src/components/ModLoaderSelector.svelte @@ -6,7 +6,7 @@ ForgeVersion, ModLoaderType, } from "../types"; - import { Loader2, Download, AlertCircle, Check } from 'lucide-svelte'; + import { Loader2, Download, AlertCircle, Check, ChevronDown } from 'lucide-svelte'; interface Props { selectedGameVersion: string; @@ -23,10 +23,15 @@ // Fabric state let fabricLoaders = $state([]); let selectedFabricLoader = $state(""); + let isFabricDropdownOpen = $state(false); // Forge state let forgeVersions = $state([]); let selectedForgeVersion = $state(""); + let isForgeDropdownOpen = $state(false); + + let fabricDropdownRef = $state(null); + let forgeDropdownRef = $state(null); // Load mod loader versions when game version changes $effect(() => { @@ -111,6 +116,44 @@ loadModLoaderVersions(); } } + + function handleFabricClickOutside(e: MouseEvent) { + if (fabricDropdownRef && !fabricDropdownRef.contains(e.target as Node)) { + isFabricDropdownOpen = false; + } + } + + function handleForgeClickOutside(e: MouseEvent) { + if (forgeDropdownRef && !forgeDropdownRef.contains(e.target as Node)) { + isForgeDropdownOpen = false; + } + } + + $effect(() => { + if (isFabricDropdownOpen) { + document.addEventListener('click', handleFabricClickOutside); + return () => document.removeEventListener('click', handleFabricClickOutside); + } + }); + + $effect(() => { + if (isForgeDropdownOpen) { + document.addEventListener('click', handleForgeClickOutside); + return () => document.removeEventListener('click', handleForgeClickOutside); + } + }); + + let selectedFabricLabel = $derived( + fabricLoaders.find(l => l.version === selectedFabricLoader) + ? `${selectedFabricLoader}${fabricLoaders.find(l => l.version === selectedFabricLoader)?.stable ? ' (stable)' : ''}` + : selectedFabricLoader || 'Select version' + ); + + let selectedForgeLabel = $derived( + forgeVersions.find(v => v.version === selectedForgeVersion) + ? `${selectedForgeVersion}${forgeVersions.find(v => v.version === selectedForgeVersion)?.recommended ? ' (Recommended)' : ''}` + : selectedForgeVersion || 'Select version' + );
@@ -163,18 +206,48 @@ -
- + {selectedFabricLabel} + + + + {#if isFabricDropdownOpen} +
+ {#each fabricLoaders as loader} + + {/each} +
+ {/if}
@@ -199,19 +272,48 @@ -
- + {selectedForgeLabel} + + + + {#if isForgeDropdownOpen} +
+ {#each forgeVersions as version} + + {/each} +
+ {/if}
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte index 732f857..76d441b 100644 --- a/ui/src/components/SettingsView.svelte +++ b/ui/src/components/SettingsView.svelte @@ -1,11 +1,22 @@