Launch progress
This commit is contained in:
@@ -13,6 +13,7 @@ use lyceris::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use tauri::Emitter as _;
|
||||||
|
|
||||||
use crate::GameVersion;
|
use crate::GameVersion;
|
||||||
|
|
||||||
@@ -22,7 +23,16 @@ pub static LAUNCHER_DIR: Lazy<ProjectDirs> = Lazy::new(|| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn launch_game(version: GameVersion, account: MinecraftAccount) -> Result<(), String> {
|
pub async fn launch_game(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
version: GameVersion,
|
||||||
|
account: MinecraftAccount,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
emit_progress(&app, 0.0);
|
||||||
|
|
||||||
|
app.emit("is-launching", "true").unwrap();
|
||||||
|
app.emit("launch-status", "Preparing").unwrap();
|
||||||
|
|
||||||
// Emitter uses `EventEmitter` inside of it
|
// Emitter uses `EventEmitter` inside of it
|
||||||
// and it uses tokio::Mutex for locking.
|
// and it uses tokio::Mutex for locking.
|
||||||
// That causes emitter methods to be async.
|
// That causes emitter methods to be async.
|
||||||
@@ -44,19 +54,18 @@ pub async fn launch_game(version: GameVersion, account: MinecraftAccount) -> Res
|
|||||||
// Java, libraries and assets are downloaded in parallel and
|
// Java, libraries and assets are downloaded in parallel and
|
||||||
// this event is triggered for each file.
|
// this event is triggered for each file.
|
||||||
emitter
|
emitter
|
||||||
.on(
|
.on(Event::MultipleDownloadProgress, {
|
||||||
Event::MultipleDownloadProgress,
|
let app = app.clone();
|
||||||
|(_, current, total, _): (String, u64, u64, String)| {
|
move |(_, current, total, _): (String, u64, u64, String)| {
|
||||||
|
emit_progress(&app, current as f32 / total as f32);
|
||||||
println!("Downloading {}/{}", current, total);
|
println!("Downloading {}/{}", current, total);
|
||||||
},
|
}
|
||||||
)
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Console event send when a line is printed to the console.
|
|
||||||
// It uses a seperated tokio thread to handle this operation.
|
|
||||||
emitter
|
emitter
|
||||||
.on(Event::Console, |line: String| {
|
.on(Event::Console, |line: String| {
|
||||||
println!("Line: {}", line);
|
println!("{}", line);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -78,6 +87,8 @@ pub async fn launch_game(version: GameVersion, account: MinecraftAccount) -> Res
|
|||||||
|
|
||||||
println!("Starting installation");
|
println!("Starting installation");
|
||||||
|
|
||||||
|
app.emit("launch-status", "Installing").unwrap();
|
||||||
|
|
||||||
// Install method also checks for broken files
|
// Install method also checks for broken files
|
||||||
// and downloads them again if they are broken.
|
// and downloads them again if they are broken.
|
||||||
install::install(&config, Some(&emitter))
|
install::install(&config, Some(&emitter))
|
||||||
@@ -86,7 +97,10 @@ pub async fn launch_game(version: GameVersion, account: MinecraftAccount) -> Res
|
|||||||
|
|
||||||
println!("Finished installing");
|
println!("Finished installing");
|
||||||
|
|
||||||
// This method never downloads any file and just runs the game.
|
emit_progress(&app, 1.0);
|
||||||
|
|
||||||
|
app.emit("launch-status", "Launching").unwrap();
|
||||||
|
|
||||||
launch(&config, Some(&emitter))
|
launch(&config, Some(&emitter))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
@@ -94,5 +108,14 @@ pub async fn launch_game(version: GameVersion, account: MinecraftAccount) -> Res
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
app.emit("is-launching", "false").unwrap();
|
||||||
|
app.emit("launch-status", "").unwrap();
|
||||||
|
emit_progress(&app, 0.0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_progress(app: &tauri::AppHandle, progress: f32) {
|
||||||
|
let percent = (progress * 100.0).round() as u32;
|
||||||
|
let _ = app.emit("launch-progress", percent);
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,22 +16,31 @@ interface PlaySectionProps {
|
|||||||
const PlaySection: React.FC<PlaySectionProps> = ({ version, account }) => {
|
const PlaySection: React.FC<PlaySectionProps> = ({ version, account }) => {
|
||||||
const [isLaunching, setIsLaunching] = useState(false);
|
const [isLaunching, setIsLaunching] = useState(false);
|
||||||
const [launchStatus, setLaunchStatus] = useState("");
|
const [launchStatus, setLaunchStatus] = useState("");
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const unlistenProgress = listen<number>("launch-progress", (event) => {
|
||||||
|
setProgress(event.payload);
|
||||||
|
});
|
||||||
|
|
||||||
const unlistenLaunchStatus = listen<string>("launch-status", (event) => {
|
const unlistenLaunchStatus = listen<string>("launch-status", (event) => {
|
||||||
setLaunchStatus(event.payload);
|
setLaunchStatus(event.payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
const unlistenIsLaunching = listen<string>("is-launching", (event) => {
|
const unlistenIsLaunching = listen<string>("is-launching", (event) => {
|
||||||
setIsLaunching(event.payload === "true");
|
setIsLaunching(event.payload === "true");
|
||||||
|
if (event.payload === "false") setProgress(0); // reset when done
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
unlistenProgress.then((f) => f());
|
||||||
unlistenLaunchStatus.then((f) => f());
|
unlistenLaunchStatus.then((f) => f());
|
||||||
unlistenIsLaunching.then((f) => f());
|
unlistenIsLaunching.then((f) => f());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
console.log(isLaunching);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
@@ -42,7 +51,7 @@ const PlaySection: React.FC<PlaySectionProps> = ({ version, account }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Centered Overlay */}
|
{/* Centered Overlay */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute -inset-1 bg-linear-to-r from-blue-600 to-cyan-500 rounded-lg blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
|
<div className="absolute -inset-1 bg-linear-to-r from-blue-600 to-cyan-500 rounded-lg blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
|
||||||
|
|
||||||
@@ -68,6 +77,25 @@ const PlaySection: React.FC<PlaySectionProps> = ({ version, account }) => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-64 transition-opacity duration-700 opacity-0 absolute mt-28",
|
||||||
|
isLaunching && "opacity-100",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="h-2 w-full bg-white/10 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-linear-to-r from-blue-500 to-cyan-400 transition-all duration-300"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs text-center mt-1 text-white/70">
|
||||||
|
{progress}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,3 +53,9 @@
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #3f3f46;
|
background: #3f3f46;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Define the fade-in animation */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user