mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2025-02-16 15:22:01 +03:00
feat(ui): added components downloading page to the first run window
\
This commit is contained in:
parent
aa182e6efb
commit
5d1633efe1
6 changed files with 346 additions and 18 deletions
|
@ -1,3 +1,4 @@
|
|||
#[inline]
|
||||
pub fn prettify_bytes(bytes: u64) -> String {
|
||||
if bytes > 1024 * 1024 * 1024 {
|
||||
format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
|
||||
|
|
|
@ -37,7 +37,7 @@ pub struct ProgressBar {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppMsg {
|
||||
pub enum ProgressBarMsg {
|
||||
Reset,
|
||||
UpdateCaption(Option<String>),
|
||||
|
||||
|
@ -51,7 +51,7 @@ pub enum AppMsg {
|
|||
#[relm4::component(async, pub)]
|
||||
impl SimpleAsyncComponent for ProgressBar {
|
||||
type Init = ProgressBarInit;
|
||||
type Input = AppMsg;
|
||||
type Input = ProgressBarMsg;
|
||||
type Output = ();
|
||||
|
||||
view! {
|
||||
|
@ -109,15 +109,15 @@ impl SimpleAsyncComponent for ProgressBar {
|
|||
|
||||
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
|
||||
match msg {
|
||||
AppMsg::Reset => {
|
||||
ProgressBarMsg::Reset => {
|
||||
self.fraction = 0.0;
|
||||
self.downloaded = None;
|
||||
self.caption = None;
|
||||
}
|
||||
|
||||
AppMsg::UpdateCaption(caption) => self.caption = caption,
|
||||
ProgressBarMsg::UpdateCaption(caption) => self.caption = caption,
|
||||
|
||||
AppMsg::UpdateProgress(curr, total) => {
|
||||
ProgressBarMsg::UpdateProgress(curr, total) => {
|
||||
self.fraction = curr as f64 / total as f64;
|
||||
|
||||
self.downloaded = Some((
|
||||
|
@ -126,7 +126,7 @@ impl SimpleAsyncComponent for ProgressBar {
|
|||
));
|
||||
}
|
||||
|
||||
AppMsg::UpdateFromState(state) => {
|
||||
ProgressBarMsg::UpdateFromState(state) => {
|
||||
match state {
|
||||
InstallerUpdate::CheckingFreeSpace(_) => self.caption = Some(tr("checking-free-space")),
|
||||
InstallerUpdate::DownloadingStarted(_) => self.caption = Some(tr("downloading")),
|
||||
|
@ -150,7 +150,7 @@ impl SimpleAsyncComponent for ProgressBar {
|
|||
}
|
||||
}
|
||||
|
||||
AppMsg::SetVisible(visible) => self.visible = visible
|
||||
ProgressBarMsg::SetVisible(visible) => self.visible = visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use adw::prelude::*;
|
|||
|
||||
use gtk::glib::clone;
|
||||
|
||||
use super::progress_bar::AppMsg as ProgressBarMsg;
|
||||
use super::progress_bar::ProgressBarMsg;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
use anime_launcher_sdk::anime_game_core::installer::installer::*;
|
||||
|
|
|
@ -211,6 +211,8 @@ impl SimpleAsyncComponent for DefaultPathsApp {
|
|||
prefix: config.game.wine.prefix,
|
||||
game: config.game.path,
|
||||
patch: config.patch.path,
|
||||
|
||||
#[allow(clippy::or_fun_call)]
|
||||
temp: config.launcher.temp.unwrap_or(PathBuf::from("/tmp"))
|
||||
};
|
||||
|
||||
|
|
|
@ -3,24 +3,67 @@ use relm4::component::*;
|
|||
|
||||
use adw::prelude::*;
|
||||
|
||||
use anime_launcher_sdk::components::wine;
|
||||
use anime_launcher_sdk::components::dxvk;
|
||||
use anime_launcher_sdk::components::*;
|
||||
use anime_launcher_sdk::anime_game_core::installer::prelude::*;
|
||||
use anime_launcher_sdk::config;
|
||||
use anime_launcher_sdk::wincompatlib::prelude::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::main::FirstRunAppMsg;
|
||||
use crate::ui::components::*;
|
||||
|
||||
fn get_installer(uri: &str, temp: Option<&PathBuf>, speed_limit: u64) -> anyhow::Result<Installer> {
|
||||
let mut installer = Installer::new(uri)?;
|
||||
|
||||
installer.downloader.set_downloading_speed(speed_limit)?;
|
||||
|
||||
if let Some(temp) = temp {
|
||||
installer.set_temp_folder(temp);
|
||||
}
|
||||
|
||||
Ok(installer)
|
||||
}
|
||||
|
||||
pub struct DownloadComponentsApp {
|
||||
progress_bar: AsyncController<ProgressBar>,
|
||||
|
||||
wine_combo: adw::ComboRow,
|
||||
dxvk_combo: adw::ComboRow,
|
||||
|
||||
wine_versions: Vec<wine::Version>,
|
||||
dxvk_versions: Vec<dxvk::Version>,
|
||||
|
||||
/// `None` - default,
|
||||
/// `Some(false)` - processing,
|
||||
/// `Some(true)` - done
|
||||
downloading_wine: Option<bool>,
|
||||
|
||||
/// `None` - default,
|
||||
/// `Some(false)` - processing,
|
||||
/// `Some(true)` - done
|
||||
creating_prefix: Option<bool>,
|
||||
|
||||
/// `None` - default,
|
||||
/// `Some(false)` - processing,
|
||||
/// `Some(true)` - done
|
||||
downloading_dxvk: Option<bool>,
|
||||
|
||||
/// `None` - default,
|
||||
/// `Some(false)` - processing,
|
||||
/// `Some(true)` - done
|
||||
applying_dxvk: Option<bool>,
|
||||
|
||||
downloading: bool
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DownloadComponentsAppMsg {
|
||||
Download,
|
||||
DownloadWine,
|
||||
CreatePrefix,
|
||||
DownloadDXVK,
|
||||
ApplyDXVK,
|
||||
Continue,
|
||||
Exit
|
||||
}
|
||||
|
||||
|
@ -51,7 +94,8 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
#[watch]
|
||||
set_visible: !model.downloading,
|
||||
|
||||
adw::ComboRow {
|
||||
#[local_ref]
|
||||
wine_combo -> adw::ComboRow {
|
||||
set_title: "Wine version",
|
||||
|
||||
#[watch]
|
||||
|
@ -61,12 +105,13 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
.as_slice()))
|
||||
},
|
||||
|
||||
adw::ComboRow {
|
||||
#[local_ref]
|
||||
dxvk_combo -> adw::ComboRow {
|
||||
set_title: "DXVK version",
|
||||
|
||||
#[watch]
|
||||
set_model: Some(>k::StringList::new(model.dxvk_versions.iter()
|
||||
.map(|version| version.version.as_ref())
|
||||
.map(|version| version.name.as_ref())
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice()))
|
||||
}
|
||||
|
@ -88,7 +133,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
set_label: "Download",
|
||||
set_css_classes: &["suggested-action", "pill"],
|
||||
|
||||
connect_clicked => DownloadComponentsAppMsg::Download
|
||||
connect_clicked => DownloadComponentsAppMsg::DownloadWine
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
|
@ -107,6 +152,58 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
#[watch]
|
||||
set_visible: model.downloading,
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: "Download wine",
|
||||
|
||||
#[watch]
|
||||
set_icon_name: match model.downloading_wine {
|
||||
Some(true) => Some("emblem-ok"),
|
||||
Some(false) => Some("process-working"),
|
||||
None => None
|
||||
}
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: "Create prefix",
|
||||
|
||||
#[watch]
|
||||
set_icon_name: match model.creating_prefix {
|
||||
Some(true) => Some("emblem-ok"),
|
||||
Some(false) => Some("process-working"),
|
||||
None => None
|
||||
}
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: "Download DXVK",
|
||||
|
||||
#[watch]
|
||||
set_icon_name: match model.downloading_dxvk {
|
||||
Some(true) => Some("emblem-ok"),
|
||||
Some(false) => Some("process-working"),
|
||||
None => None
|
||||
}
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: "Apply DXVK",
|
||||
|
||||
#[watch]
|
||||
set_icon_name: match model.applying_dxvk {
|
||||
Some(true) => Some("emblem-ok"),
|
||||
Some(false) => Some("process-working"),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
set_valign: gtk::Align::Start,
|
||||
set_vexpand: true,
|
||||
|
||||
#[watch]
|
||||
set_visible: model.downloading,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_halign: gtk::Align::Center,
|
||||
|
@ -122,7 +219,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
async fn init(
|
||||
_init: Self::Init,
|
||||
root: Self::Root,
|
||||
_sender: AsyncComponentSender<Self>,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let model = Self {
|
||||
progress_bar: ProgressBar::builder()
|
||||
|
@ -134,14 +231,25 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
})
|
||||
.detach(),
|
||||
|
||||
wine_combo: adw::ComboRow::new(),
|
||||
dxvk_combo: adw::ComboRow::new(),
|
||||
|
||||
wine_versions: wine::get_groups()[0].versions.clone().into_iter().filter(|version| version.recommended).collect(),
|
||||
dxvk_versions: dxvk::get_groups()[0].versions.clone().into_iter().filter(|version| version.recommended).collect(),
|
||||
|
||||
downloading_wine: None,
|
||||
creating_prefix: None,
|
||||
downloading_dxvk: None,
|
||||
applying_dxvk: None,
|
||||
|
||||
downloading: false
|
||||
};
|
||||
|
||||
model.progress_bar.widget().set_width_request(360);
|
||||
|
||||
let wine_combo = &model.wine_combo;
|
||||
let dxvk_combo = &model.dxvk_combo;
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
AsyncComponentParts { model, widgets }
|
||||
|
@ -150,10 +258,215 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender<Self>) {
|
||||
match msg {
|
||||
#[allow(unused_must_use)]
|
||||
DownloadComponentsAppMsg::Download => {
|
||||
DownloadComponentsAppMsg::DownloadWine => {
|
||||
self.downloading = true;
|
||||
self.downloading_wine = Some(false);
|
||||
|
||||
// sender.output(Self::Output::ScrollToChooseVoiceovers);
|
||||
let config = config::get().unwrap_or_default();
|
||||
|
||||
let wine = self.get_wine();
|
||||
let progress_bar_input = self.progress_bar.sender().clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
tracing::info!("Installing wine");
|
||||
|
||||
// Install wine
|
||||
match get_installer(&wine.uri, config.launcher.temp.as_ref(), config.launcher.speed_limit) {
|
||||
Ok(mut installer) => {
|
||||
// Create wine builds folder
|
||||
if config.game.wine.builds.exists() {
|
||||
std::fs::create_dir_all(&config.game.wine.builds)
|
||||
.expect("Failed to create wine builds directory");
|
||||
}
|
||||
|
||||
installer.install(&config.game.wine.builds, move |update| {
|
||||
match &update {
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
let err: std::io::Error = err.clone().into();
|
||||
|
||||
tracing::error!("Failed to download wine: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to download wine"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError(err) => {
|
||||
tracing::error!("Failed to unpack wine: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to unpack wine"),
|
||||
description: Some(err.clone())
|
||||
});
|
||||
}
|
||||
|
||||
// Create prefix
|
||||
InstallerUpdate::UnpackingFinished => sender.input(DownloadComponentsAppMsg::CreatePrefix),
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateFromState(update));
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to initialize wine installer: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to initialize wine installer"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
DownloadComponentsAppMsg::CreatePrefix => {
|
||||
self.downloading_wine = Some(true);
|
||||
self.creating_prefix = Some(false);
|
||||
|
||||
let config = config::get().unwrap_or_default();
|
||||
|
||||
tracing::info!("Creating wine prefix");
|
||||
|
||||
let wine = self.get_wine();
|
||||
|
||||
let wine = wine
|
||||
.to_wine(Some(config.game.wine.builds.join(&wine.name)))
|
||||
.with_loader(WineLoader::Current)
|
||||
.with_arch(WineArch::Win64);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
match wine.update_prefix(config.game.wine.prefix) {
|
||||
// Download DXVK
|
||||
Ok(_) => sender.input(DownloadComponentsAppMsg::DownloadDXVK),
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to create prefix: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to create prefix"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
DownloadComponentsAppMsg::DownloadDXVK => {
|
||||
self.creating_prefix = Some(true);
|
||||
self.downloading_dxvk = Some(false);
|
||||
|
||||
let config = config::get().unwrap_or_default();
|
||||
|
||||
let dxvk = self.get_dxvk();
|
||||
let progress_bar_input = self.progress_bar.sender().clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
tracing::info!("Installing wine");
|
||||
|
||||
// Install DXVK
|
||||
tracing::info!("Installing DXVK");
|
||||
|
||||
match get_installer(&dxvk.uri, config.launcher.temp.as_ref(), config.launcher.speed_limit) {
|
||||
Ok(mut installer) => {
|
||||
let progress_bar_input = progress_bar_input.clone();
|
||||
let sender = sender.clone();
|
||||
|
||||
// Create DXVK builds folder
|
||||
if config.game.dxvk.builds.exists() {
|
||||
std::fs::create_dir_all(&config.game.dxvk.builds)
|
||||
.expect("Failed to create DXVK builds directory");
|
||||
}
|
||||
|
||||
installer.install(&config.game.dxvk.builds, move |update| {
|
||||
match &update {
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
let err: std::io::Error = err.clone().into();
|
||||
|
||||
tracing::error!("Failed to download dxvk: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to download dxvk"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError(err) => {
|
||||
tracing::error!("Failed to unpack dxvk: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to unpack dxvk"),
|
||||
description: Some(err.clone())
|
||||
});
|
||||
}
|
||||
|
||||
// Apply DXVK
|
||||
InstallerUpdate::UnpackingFinished => {
|
||||
sender.input(DownloadComponentsAppMsg::ApplyDXVK);
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateFromState(update));
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to initialize dxvk installer: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to initialize dxvk installer"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
DownloadComponentsAppMsg::ApplyDXVK => {
|
||||
self.downloading_dxvk = Some(true);
|
||||
self.applying_dxvk = Some(false);
|
||||
|
||||
let config = config::get().unwrap_or_default();
|
||||
|
||||
tracing::info!("Applying DXVK");
|
||||
|
||||
let wine = self.get_wine();
|
||||
let dxvk = self.get_dxvk();
|
||||
|
||||
let wine = wine
|
||||
.to_wine(Some(config.game.wine.builds.join(&wine.name)))
|
||||
.with_loader(WineLoader::Current)
|
||||
.with_arch(WineArch::Win64)
|
||||
.with_prefix(config.game.wine.prefix);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
match wine.install_dxvk(config.game.dxvk.builds.join(dxvk.name), InstallParams::default()) {
|
||||
// Go to next page
|
||||
Ok(_) => sender.input(DownloadComponentsAppMsg::Continue),
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to apply DXVK: {err}");
|
||||
|
||||
sender.output(Self::Output::Toast {
|
||||
title: String::from("Failed to apply DXVK"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
DownloadComponentsAppMsg::Continue => {
|
||||
sender.output(Self::Output::ScrollToChooseVoiceovers);
|
||||
}
|
||||
|
||||
DownloadComponentsAppMsg::Exit => {
|
||||
|
@ -163,3 +476,13 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadComponentsApp {
|
||||
pub fn get_wine(&self) -> wine::Version {
|
||||
self.wine_versions[self.wine_combo.selected() as usize].clone()
|
||||
}
|
||||
|
||||
pub fn get_dxvk(&self) -> dxvk::Version {
|
||||
self.dxvk_versions[self.dxvk_combo.selected() as usize].clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,8 @@ impl SimpleComponent for FirstRunApp {
|
|||
MAIN_WINDOW = Some(widgets.window.clone());
|
||||
}
|
||||
|
||||
tracing::info!("First run window initialized");
|
||||
|
||||
ComponentParts { model, widgets } // will return soon
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue