mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2024-11-29 01:19:06 +03:00
I don't wanna die, I don't wanna die
This commit is contained in:
parent
744f234acd
commit
0f6ebfff55
16 changed files with 982 additions and 188 deletions
265
assets/ui/first_run.blp
Normal file
265
assets/ui/first_run.blp
Normal file
|
@ -0,0 +1,265 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
Adw.ApplicationWindow window {
|
||||
default-width: 780;
|
||||
default-height: 560;
|
||||
|
||||
content: Gtk.Box {
|
||||
orientation: vertical;
|
||||
|
||||
Adw.HeaderBar {
|
||||
title-widget: Adw.WindowTitle {
|
||||
title: "An Anime Game Launcher";
|
||||
};
|
||||
}
|
||||
|
||||
Adw.Carousel carousel {
|
||||
allow-mouse-drag: false;
|
||||
|
||||
// First page (welcome message)
|
||||
|
||||
Gtk.Box first_page {
|
||||
orientation: vertical;
|
||||
hexpand: true;
|
||||
|
||||
Adw.PreferencesPage {
|
||||
Adw.PreferencesGroup {
|
||||
Gtk.Image {
|
||||
resource: "/org/app/assets/images/icon.png";
|
||||
|
||||
vexpand: true;
|
||||
margin-top: 16;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "An Anime Game Launcher";
|
||||
margin-top: 32;
|
||||
|
||||
styles ["title-1"]
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "Hi there! Welcome to the An Anime Game Launcher. We need to prepare some stuff and download default components before you could run the game";
|
||||
|
||||
wrap: true;
|
||||
justify: center;
|
||||
margin-top: 32;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
vexpand: true;
|
||||
valign: center;
|
||||
|
||||
Gtk.Box {
|
||||
orientation: horizontal;
|
||||
spacing: 8;
|
||||
halign: center;
|
||||
|
||||
Gtk.Button first_page_continue {
|
||||
label: "Continue";
|
||||
|
||||
styles ["suggested-action"]
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: "Advanced";
|
||||
sensitive: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second page (warning message)
|
||||
|
||||
Gtk.Box second_page {
|
||||
orientation: vertical;
|
||||
hexpand: true;
|
||||
|
||||
Adw.PreferencesPage {
|
||||
Adw.PreferencesGroup {
|
||||
Gtk.Label {
|
||||
label: "ToS violation warning";
|
||||
margin-top: 8;
|
||||
|
||||
styles ["title-1"]
|
||||
}
|
||||
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
margin-top: 32;
|
||||
spacing: 12;
|
||||
|
||||
Gtk.Label {
|
||||
label: "This launcher is an unofficial tool, in no way related to miHoYo nor COGNOSPHERE.";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "This tool is designed to facilitate playing Genshin Impact on Linux, and was built with the sole purpose of installing and running the game with less hassle.";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "It does so by using existing components and making the experience simple for the user.";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "However, some components used here likely break the miHoYo Terms of Service for Genshin Impact.";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "If you are using this launcher, your player account could become identified as TOS-non-compliant by miHoYo/COGNOSPHERE.";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "If this happens, as your account would be disobeying TOS, miHoYo/COGNOSPHERE are free to do what they want. Including banning.";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: "If you understand the risk of trying to play the game in an unofficial capacity, press OK and let's go researching the world of Teyvat!";
|
||||
|
||||
wrap: true;
|
||||
halign: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
vexpand: true;
|
||||
valign: center;
|
||||
|
||||
Gtk.Box {
|
||||
orientation: horizontal;
|
||||
spacing: 8;
|
||||
halign: center;
|
||||
|
||||
Gtk.Button second_page_continue {
|
||||
label: "Continue";
|
||||
|
||||
styles ["suggested-action"]
|
||||
}
|
||||
|
||||
Gtk.Button second_page_exit {
|
||||
label: "Exit";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third page (downloading components)
|
||||
|
||||
Gtk.Box third_page {
|
||||
orientation: vertical;
|
||||
hexpand: true;
|
||||
|
||||
Adw.PreferencesPage {
|
||||
Adw.PreferencesGroup {
|
||||
Gtk.Label {
|
||||
label: "Download default components";
|
||||
margin-top: 16;
|
||||
|
||||
styles ["title-1"]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
vexpand: true;
|
||||
valign: center;
|
||||
|
||||
Adw.ComboRow third_page_wine_version {
|
||||
title: "Wine version";
|
||||
|
||||
model: Gtk.StringList {
|
||||
strings [
|
||||
"Wine-GE-Proton 7-22"
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
Adw.ComboRow third_page_dxvk_version {
|
||||
title: "DXVK version";
|
||||
|
||||
model: Gtk.StringList {
|
||||
strings [
|
||||
"dxvk-1.10.2"
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup third_page_buttons_group {
|
||||
vexpand: true;
|
||||
valign: center;
|
||||
|
||||
Gtk.Box {
|
||||
orientation: horizontal;
|
||||
spacing: 8;
|
||||
halign: center;
|
||||
|
||||
Gtk.Button third_page_download {
|
||||
label: "Download";
|
||||
|
||||
styles ["suggested-action"]
|
||||
}
|
||||
|
||||
Gtk.Button third_page_exit {
|
||||
label: "Exit";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup third_page_progress_bar_group {
|
||||
vexpand: true;
|
||||
valign: center;
|
||||
visible: false;
|
||||
|
||||
Gtk.Box {
|
||||
halign: center;
|
||||
margin-top: 64;
|
||||
spacing: 20;
|
||||
|
||||
Gtk.ProgressBar third_page_progress_bar {
|
||||
text: "Downloading: 37% (3.7 of 10 GB)";
|
||||
show-text: true;
|
||||
|
||||
width-request: 260;
|
||||
fraction: 0.37;
|
||||
valign: center;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: "Pause";
|
||||
sensitive: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.CarouselIndicatorDots {
|
||||
carousel: carousel;
|
||||
height-request: 32;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -102,6 +102,7 @@ Adw.ApplicationWindow window {
|
|||
|
||||
Gtk.Button {
|
||||
label: "Pause";
|
||||
sensitive: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
34
src/lib/launcher/executors/create_prefix.rs
Normal file
34
src/lib/launcher/executors/create_prefix.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use std::io::Error;
|
||||
|
||||
use wait_not_await::Await;
|
||||
|
||||
use crate::ui::components::progress_bar::ProgressBar;
|
||||
|
||||
use crate::lib::wine_prefix::WinePrefix;
|
||||
use crate::lib::config::Config;
|
||||
|
||||
pub fn create_prefix(config: Config, progress_bar: ProgressBar) -> Await<Result<(), (String, Error)>> {
|
||||
Await::new(move || {
|
||||
// Create prefix if needed
|
||||
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||
|
||||
if !prefix.exists() {
|
||||
progress_bar.update(0.0, Some("Creating prefix..."));
|
||||
|
||||
match config.try_get_selected_wine_info() {
|
||||
Some(wine_version) => match prefix.update(&config.game.wine.builds, wine_version) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err((String::from("Failed to create prefix"), err))
|
||||
},
|
||||
None => {
|
||||
// TODO: download default wine
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
64
src/lib/launcher/executors/download_diff.rs
Normal file
64
src/lib/launcher/executors/download_diff.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::io::Error;
|
||||
|
||||
use anime_game_core::prelude::*;
|
||||
use wait_not_await::Await;
|
||||
|
||||
use crate::ui::components::progress_bar::ProgressBar;
|
||||
use crate::lib::prettify_bytes::prettify_bytes;
|
||||
|
||||
/*pub fn download_diff(diff: &VersionDiff, progress_bar: ProgressBar, suffix: Option<String>) -> Await<Result<(), (String, Error)>> {
|
||||
let (send, recv) = std::sync::mpsc::channel();
|
||||
|
||||
diff.install(move |state| {
|
||||
match state {
|
||||
InstallerUpdate::DownloadingStarted(_) => progress_bar.update(0.0, Some("Downloading...")),
|
||||
|
||||
InstallerUpdate::DownloadingProgress(curr, total) => {
|
||||
// To reduce amount of action requests
|
||||
// if curr % 10000 < 200 {
|
||||
let progress = curr as f64 / total as f64;
|
||||
|
||||
progress_bar.update(progress, Some(&format!(
|
||||
"Downloading{}: {:.2}% ({} of {})",
|
||||
if let Some(suffix) = suffix { format!(" {}", suffix) } else { String::new() },
|
||||
progress * 100.0,
|
||||
prettify_bytes(curr),
|
||||
prettify_bytes(total)
|
||||
)));
|
||||
// }
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingStarted(_) => progress_bar.update(0.0, Some("Unpacking...")),
|
||||
|
||||
InstallerUpdate::UnpackingProgress(curr, total) => {
|
||||
let progress = curr as f64 / total as f64;
|
||||
|
||||
progress_bar.update(progress, Some(&format!(
|
||||
"Unpacking{}: {:.2}% ({} of {})",
|
||||
if let Some(suffix) = suffix { format!(" {}", suffix) } else { String::new() },
|
||||
progress * 100.0,
|
||||
prettify_bytes(curr),
|
||||
prettify_bytes(total)
|
||||
)));
|
||||
}
|
||||
|
||||
InstallerUpdate::DownloadingFinished => (),
|
||||
|
||||
InstallerUpdate::UnpackingFinished => {
|
||||
send.send(Ok(()));
|
||||
},
|
||||
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
send.send(Err((String::from("Failed to download"), err.into())));
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError => {
|
||||
send.send(Err((String::from("Failed to unpack"), Error::last_os_error())));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Await::new(move || {
|
||||
recv.recv().unwrap()
|
||||
})
|
||||
}*/
|
32
src/lib/launcher/executors/mod.rs
Normal file
32
src/lib/launcher/executors/mod.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
mod create_prefix;
|
||||
mod download_diff;
|
||||
|
||||
pub use create_prefix::*;
|
||||
pub use download_diff::*;
|
||||
|
||||
use crate::lib::config;
|
||||
use crate::lib::wine_prefix::WinePrefix;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Component {
|
||||
Wine,
|
||||
DXVK,
|
||||
Prefix
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComponentsChain {
|
||||
pub chain: Vec<Component>
|
||||
}
|
||||
|
||||
impl ComponentsChain {
|
||||
pub fn get() -> std::io::Result<Self> {
|
||||
let config = config::get()?;
|
||||
|
||||
let wine_prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||
|
||||
|
||||
|
||||
todo!();
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub mod states;
|
||||
pub mod executors;
|
||||
|
|
|
@ -4,8 +4,11 @@ use libadwaita::{self as adw, prelude::*};
|
|||
use std::io::{Error, ErrorKind};
|
||||
|
||||
use anime_game_core::prelude::*;
|
||||
use wait_not_await::Await;
|
||||
|
||||
use crate::ui::components::progress_bar::ProgressBar;
|
||||
use crate::lib::config;
|
||||
use crate::lib::prettify_bytes::prettify_bytes;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LauncherState {
|
||||
|
@ -102,4 +105,171 @@ impl LauncherState {
|
|||
VersionDiff::NotInstalled { .. } => Self::GameNotInstalled(diff)
|
||||
})
|
||||
}
|
||||
|
||||
/*pub fn execute(&self, progress_bar: &ProgressBar) -> Await<Result<(), (&str, Error)>> {
|
||||
match self {
|
||||
Self::Launch => {
|
||||
// Display toast message if the game is failed to run
|
||||
/*if let Err(err) = game::run(false) {
|
||||
this.toast_error("Failed to run game", err);
|
||||
}*/
|
||||
|
||||
todo!();
|
||||
},
|
||||
|
||||
Self::PatchAvailable(_) => todo!(),
|
||||
|
||||
Self::VoiceUpdateAvailable(diff) |
|
||||
Self::VoiceNotInstalled(diff) |
|
||||
Self::GameUpdateAvailable(diff) |
|
||||
Self::GameNotInstalled(diff) => {
|
||||
// this.update(Actions::DownloadDiff(Rc::new(diff))).unwrap();
|
||||
|
||||
// Download wine version if not installed
|
||||
match WineVersion::latest() {
|
||||
Ok(wine) => match Installer::new(wine.uri) {
|
||||
Ok(mut installer) => {
|
||||
let (send, recv) = std::sync::mpsc::channel();
|
||||
let wine_title = wine.title.clone();
|
||||
|
||||
installer.install(&config.game.wine.builds, clone!(@strong this => move |state| {
|
||||
match state {
|
||||
InstallerUpdate::UnpackingFinished => {
|
||||
send.send(true).unwrap();
|
||||
}
|
||||
|
||||
InstallerUpdate::DownloadingError(_) |
|
||||
InstallerUpdate::UnpackingError => {
|
||||
send.send(false).unwrap();
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(wine_title.clone()))))).unwrap();
|
||||
}));
|
||||
|
||||
// Block thread until downloading finished
|
||||
if recv.recv().unwrap() {
|
||||
config.game.wine.selected = Some(wine.name);
|
||||
|
||||
config::update(config.clone());
|
||||
}
|
||||
|
||||
else {
|
||||
println!("I'm tired, Boss!");
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to init wine version installer", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to load wine versions list", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create prefix if needed
|
||||
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||
|
||||
if !prefix.exists() {
|
||||
this.update(Actions::UpdateProgress {
|
||||
fraction: Rc::new(0.0),
|
||||
title: Rc::new(String::from("Creating prefix..."))
|
||||
}).unwrap();
|
||||
|
||||
match config.try_get_selected_wine_info() {
|
||||
Some(wine_version) => {
|
||||
if let Err(err) = prefix.update(&config.game.wine.builds, wine_version) {
|
||||
toast_error(&this, "Failed to create wineprefix", err);
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => return
|
||||
}
|
||||
}
|
||||
|
||||
// Download and apply DXVK if not installed
|
||||
match DxvkVersion::latest() {
|
||||
Ok(dxvk) => match Installer::new(&dxvk.uri) {
|
||||
Ok(mut installer) => {
|
||||
let (send, recv) = std::sync::mpsc::channel();
|
||||
let dxvk_title = dxvk.name.clone();
|
||||
|
||||
installer.install(&config.game.dxvk.builds, clone!(@strong this => move |state| {
|
||||
match state {
|
||||
InstallerUpdate::UnpackingFinished => {
|
||||
send.send(true).unwrap();
|
||||
}
|
||||
|
||||
InstallerUpdate::DownloadingError(_) |
|
||||
InstallerUpdate::UnpackingError => {
|
||||
send.send(false).unwrap();
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(dxvk_title.clone()))))).unwrap();
|
||||
}));
|
||||
|
||||
// Block thread until downloading finished
|
||||
if recv.recv().unwrap() {
|
||||
config.game.dxvk.selected = Some(dxvk.name.clone());
|
||||
|
||||
config::update(config.clone());
|
||||
}
|
||||
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply DXVK
|
||||
this.update(Actions::UpdateProgress {
|
||||
fraction: Rc::new(100.0),
|
||||
title: Rc::new(String::from("Applying DXVK..."))
|
||||
}).unwrap();
|
||||
|
||||
match dxvk.apply(&config.game.dxvk.builds, &config.game.wine.prefix) {
|
||||
Ok(_) => {
|
||||
config.game.dxvk.selected = Some(dxvk.name);
|
||||
|
||||
config::update(config.clone());
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to apply DXVK", err);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to init wine version installer", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to load wine versions list", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
todo!();
|
||||
},
|
||||
|
||||
Self::GameOutdated(_) => (),
|
||||
Self::VoiceOutdated(_) => ()
|
||||
}
|
||||
|
||||
todo!();
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@ pub mod dxvk;
|
|||
pub mod wine;
|
||||
pub mod wine_prefix;
|
||||
pub mod launcher;
|
||||
pub mod prettify_bytes;
|
||||
|
|
17
src/lib/prettify_bytes.rs
Normal file
17
src/lib/prettify_bytes.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
pub fn prettify_bytes(bytes: u64) -> String {
|
||||
if bytes > 1024 * 1024 * 1024 {
|
||||
format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
|
||||
}
|
||||
|
||||
else if bytes > 1024 * 1024 {
|
||||
format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0)
|
||||
}
|
||||
|
||||
else if bytes > 1024 {
|
||||
format!("{:.2} KB", bytes as f64 / 1024.0)
|
||||
}
|
||||
|
||||
else {
|
||||
format!("{:.2} B", bytes)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WinePrefix {
|
||||
pub path: String
|
||||
}
|
||||
|
@ -17,8 +18,8 @@ impl WinePrefix {
|
|||
fn wineboot<T: ToString>(&self, runners_folder: T, runner: super::wine::Version, command: &str) -> std::io::Result<()> {
|
||||
let runners_folder = runners_folder.to_string();
|
||||
|
||||
let wineboot = format!("{}/{}", &runners_folder, runner.files.wineboot);
|
||||
let wineserver = format!("{}/{}", &runners_folder, runner.files.wineserver);
|
||||
let wineboot = format!("{}/{}/{}", &runners_folder, runner.name, runner.files.wineboot);
|
||||
let wineserver = format!("{}/{}/{}", &runners_folder, runner.name, runner.files.wineserver);
|
||||
|
||||
Command::new(wineboot)
|
||||
.env("WINESERVER", wineserver)
|
||||
|
|
30
src/main.rs
30
src/main.rs
|
@ -5,6 +5,9 @@ use gtk::{CssProvider, StyleContext, STYLE_PROVIDER_PRIORITY_APPLICATION};
|
|||
use gtk::gdk::Display;
|
||||
use gtk::glib::set_application_name;
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
pub mod ui;
|
||||
pub mod lib;
|
||||
|
||||
|
@ -27,13 +30,6 @@ async fn main() {
|
|||
// FIXME: doesn't work?
|
||||
set_application_name("An Anime Game Launcher");
|
||||
|
||||
// Create default launcher folder if needed
|
||||
let launcher_dir = lib::consts::launcher_dir().unwrap();
|
||||
|
||||
if !std::path::Path::new(&launcher_dir).exists() {
|
||||
std::fs::create_dir_all(launcher_dir).expect("Failed to create default launcher dir");
|
||||
}
|
||||
|
||||
// Create app
|
||||
let application = gtk::Application::new(
|
||||
Some(APP_ID),
|
||||
|
@ -53,10 +49,24 @@ async fn main() {
|
|||
STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
|
||||
// Load main window and show it
|
||||
let main = MainApp::new(app).expect("Failed to init MainApp");
|
||||
// Create default launcher folder if needed
|
||||
let launcher_dir = lib::consts::launcher_dir().unwrap();
|
||||
|
||||
main.show();
|
||||
if !Path::new(&launcher_dir).exists() || Path::new(&format!("{}/.first-run", launcher_dir)).exists() {
|
||||
fs::create_dir_all(&launcher_dir).expect("Failed to create default launcher dir");
|
||||
fs::write(format!("{}/.first-run", launcher_dir), "").expect("Failed to create .first-run file");
|
||||
|
||||
let first_run = FirstRunApp::new(app).expect("Failed to init FirstRunApp");
|
||||
|
||||
first_run.show();
|
||||
}
|
||||
|
||||
// Load main window and show it
|
||||
else {
|
||||
let main = MainApp::new(app).expect("Failed to init MainApp");
|
||||
|
||||
main.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Flush config from the memory to the file before closing the app
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod dxvk_row;
|
||||
pub mod wine_group;
|
||||
pub mod wine_row;
|
||||
pub mod progress_bar;
|
||||
|
|
82
src/ui/components/progress_bar.rs
Normal file
82
src/ui/components/progress_bar.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use gtk4::{self as gtk, prelude::*};
|
||||
use libadwaita::{self as adw, prelude::*};
|
||||
|
||||
use gtk::glib;
|
||||
|
||||
use std::io::Error;
|
||||
|
||||
use anime_game_core::prelude::*;
|
||||
use wait_not_await::Await;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProgressUpdateResult {
|
||||
Updated,
|
||||
Error(String, std::io::Error),
|
||||
Finished
|
||||
}
|
||||
|
||||
#[derive(Clone, glib::Downgrade)]
|
||||
pub struct ProgressBar {
|
||||
pub progress_bar: gtk::ProgressBar,
|
||||
pub default_group: adw::PreferencesGroup,
|
||||
pub progress_bar_group: adw::PreferencesGroup
|
||||
}
|
||||
|
||||
impl ProgressBar {
|
||||
pub fn new(progress_bar: gtk::ProgressBar, default_group: adw::PreferencesGroup, progress_bar_group: adw::PreferencesGroup) -> Self {
|
||||
Self {
|
||||
progress_bar,
|
||||
default_group,
|
||||
progress_bar_group
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.progress_bar.set_text(None);
|
||||
self.progress_bar.set_fraction(0.0);
|
||||
|
||||
self.default_group.hide();
|
||||
self.progress_bar_group.show();
|
||||
}
|
||||
|
||||
pub fn hide(&self) {
|
||||
self.default_group.show();
|
||||
self.progress_bar_group.hide();
|
||||
}
|
||||
|
||||
pub fn update(&self, fraction: f64, text: Option<&str>) {
|
||||
self.progress_bar.set_fraction(fraction);
|
||||
self.progress_bar.set_text(text);
|
||||
}
|
||||
|
||||
pub fn update_from_state(&self, state: InstallerUpdate) -> ProgressUpdateResult {
|
||||
match state {
|
||||
InstallerUpdate::DownloadingStarted(_) => self.show(),
|
||||
|
||||
InstallerUpdate::DownloadingProgress(curr, total) => {
|
||||
let progress = curr as f64 / total as f64;
|
||||
|
||||
self.update(progress, None);
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingProgress(curr, total) => {
|
||||
let progress = curr as f64 / total as f64;
|
||||
|
||||
self.update(progress, None);
|
||||
}
|
||||
|
||||
InstallerUpdate::DownloadingFinished => (),
|
||||
InstallerUpdate::UnpackingStarted(_) => (),
|
||||
|
||||
InstallerUpdate::DownloadingError(err) => return ProgressUpdateResult::Error(String::from("Failed to download"), err.into()),
|
||||
InstallerUpdate::UnpackingError => return ProgressUpdateResult::Error(String::from("Failed to unpack"), Error::last_os_error()),
|
||||
|
||||
InstallerUpdate::UnpackingFinished => return ProgressUpdateResult::Finished
|
||||
}
|
||||
|
||||
ProgressUpdateResult::Updated
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ProgressBar {}
|
||||
unsafe impl Sync for ProgressBar {}
|
275
src/ui/first_run.rs
Normal file
275
src/ui/first_run.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
use gtk4::{self as gtk, prelude::*};
|
||||
use libadwaita::{self as adw, prelude::*};
|
||||
|
||||
use gtk::glib;
|
||||
use gtk::glib::clone;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::Cell;
|
||||
|
||||
use anime_game_core::prelude::*;
|
||||
|
||||
use crate::ui::*;
|
||||
use crate::ui::components::progress_bar::*;
|
||||
|
||||
use crate::lib::wine::Version as WineVersion;
|
||||
use crate::lib::dxvk::Version as DxvkVersion;
|
||||
use crate::lib::wine_prefix::WinePrefix;
|
||||
use crate::lib::config;
|
||||
|
||||
/// This structure is used to describe widgets used in application
|
||||
///
|
||||
/// `AppWidgets::default` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets
|
||||
///
|
||||
/// This function does not implement events
|
||||
#[derive(Clone)]
|
||||
pub struct AppWidgets {
|
||||
pub window: adw::ApplicationWindow,
|
||||
pub carousel: adw::Carousel,
|
||||
|
||||
// First page
|
||||
pub first_page: gtk::Box,
|
||||
pub first_page_continue: gtk::Button,
|
||||
|
||||
// Second page
|
||||
pub second_page: gtk::Box,
|
||||
pub second_page_continue: gtk::Button,
|
||||
pub second_page_exit: gtk::Button,
|
||||
|
||||
// Third page
|
||||
pub third_page: gtk::Box,
|
||||
|
||||
pub third_page_wine_version: adw::ComboRow,
|
||||
pub third_page_dxvk_version: adw::ComboRow,
|
||||
|
||||
pub third_page_download: gtk::Button,
|
||||
pub third_page_exit: gtk::Button,
|
||||
|
||||
pub third_page_progress_bar: ProgressBar
|
||||
}
|
||||
|
||||
impl AppWidgets {
|
||||
pub fn try_get() -> Result<Self, String> {
|
||||
let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/first_run.ui"));
|
||||
|
||||
Ok(Self {
|
||||
window: get_object(&builder, "window")?,
|
||||
carousel: get_object(&builder, "carousel")?,
|
||||
|
||||
// First page
|
||||
first_page: get_object(&builder, "first_page")?,
|
||||
first_page_continue: get_object(&builder, "first_page_continue")?,
|
||||
|
||||
// Second page
|
||||
second_page: get_object(&builder, "second_page")?,
|
||||
second_page_continue: get_object(&builder, "second_page_continue")?,
|
||||
second_page_exit: get_object(&builder, "second_page_exit")?,
|
||||
|
||||
// Third page
|
||||
third_page: get_object(&builder, "third_page")?,
|
||||
|
||||
third_page_wine_version: get_object(&builder, "third_page_wine_version")?,
|
||||
third_page_dxvk_version: get_object(&builder, "third_page_dxvk_version")?,
|
||||
|
||||
third_page_download: get_object(&builder, "third_page_download")?,
|
||||
third_page_exit: get_object(&builder, "third_page_exit")?,
|
||||
|
||||
third_page_progress_bar: ProgressBar::new(
|
||||
get_object(&builder, "third_page_progress_bar")?,
|
||||
get_object(&builder, "third_page_buttons_group")?,
|
||||
get_object(&builder, "third_page_progress_bar_group")?
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This enum is used to describe an action inside of this application
|
||||
///
|
||||
/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action
|
||||
///
|
||||
/// Has to implement glib::Downgrade` trait
|
||||
#[derive(Debug, glib::Downgrade)]
|
||||
pub enum Actions {
|
||||
FirstPageContinue,
|
||||
SecondPageContinue,
|
||||
ThirdPageDownload,
|
||||
Exit
|
||||
}
|
||||
|
||||
impl Actions {
|
||||
pub fn into_fn<T: gtk::glib::IsA<gtk::Widget>>(&self, app: &App) -> Box<dyn Fn(&T)> {
|
||||
Box::new(clone!(@weak self as action, @strong app => move |_| {
|
||||
app.update(action).unwrap();
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// This enum is used to store some of this application data
|
||||
///
|
||||
/// In this example we store a counter here to know what should we increment or decrement
|
||||
///
|
||||
/// This must implement `Default` trait
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Values;
|
||||
|
||||
/// The main application structure
|
||||
///
|
||||
/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets
|
||||
///
|
||||
/// `Rc<Cell<Values>>` means this:
|
||||
/// - `Rc` addeds ability to reference the same value from various clones of the structure.
|
||||
/// This will guarantee us that inner `Cell<Values>` is the same for all the `App::clone()` values
|
||||
/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference.
|
||||
///
|
||||
/// So we have a shared reference to some value that can be changed without mutable reference.
|
||||
/// That's what we need and what we use in `App::update` method
|
||||
#[derive(Clone)]
|
||||
pub struct App {
|
||||
widgets: AppWidgets,
|
||||
values: Rc<Cell<Values>>,
|
||||
actions: Rc<Cell<Option<glib::Sender<Actions>>>>
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Create new application
|
||||
pub fn new(app: >k::Application) -> Result<Self, String> {
|
||||
// Get default widgets from ui file and add events to them
|
||||
let result = Self {
|
||||
widgets: AppWidgets::try_get()?,
|
||||
values: Default::default(),
|
||||
actions: Default::default()
|
||||
}.init_events().init_actions();
|
||||
|
||||
// Bind app to the window
|
||||
result.widgets.window.set_application(Some(app));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Add default events and values to the widgets
|
||||
fn init_events(self) -> Self {
|
||||
self.widgets.first_page_continue.connect_clicked(Actions::FirstPageContinue.into_fn(&self));
|
||||
self.widgets.second_page_continue.connect_clicked(Actions::SecondPageContinue.into_fn(&self));
|
||||
self.widgets.third_page_download.connect_clicked(Actions::ThirdPageDownload.into_fn(&self));
|
||||
|
||||
self.widgets.second_page_exit.connect_clicked(Actions::Exit.into_fn(&self));
|
||||
self.widgets.third_page_exit.connect_clicked(Actions::Exit.into_fn(&self));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add actions processors
|
||||
///
|
||||
/// Changes will happen in the main thread so you can call `update` method from separate thread
|
||||
pub fn init_actions(self) -> Self {
|
||||
let (sender, receiver) = glib::MainContext::channel::<Actions>(glib::PRIORITY_DEFAULT);
|
||||
|
||||
let this = self.clone();
|
||||
|
||||
receiver.attach(None, move |action| {
|
||||
// Some debug output
|
||||
println!("[update] action: {:?}", &action);
|
||||
|
||||
match action {
|
||||
Actions::FirstPageContinue => {
|
||||
this.widgets.carousel.scroll_to(&this.widgets.second_page, true);
|
||||
}
|
||||
|
||||
Actions::SecondPageContinue => {
|
||||
this.widgets.carousel.scroll_to(&this.widgets.third_page, true);
|
||||
}
|
||||
|
||||
Actions::ThirdPageDownload => {
|
||||
this.widgets.third_page_wine_version.set_sensitive(false);
|
||||
this.widgets.third_page_dxvk_version.set_sensitive(false);
|
||||
|
||||
this.widgets.third_page_progress_bar.show();
|
||||
|
||||
let this = this.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
let wine_version = WineVersion::latest().unwrap();
|
||||
let dxvk_version = DxvkVersion::latest().unwrap();
|
||||
|
||||
let mut wine_version_installer = Installer::new(&wine_version.uri).unwrap();
|
||||
|
||||
let progress_bar = this.widgets.third_page_progress_bar.clone();
|
||||
|
||||
wine_version_installer.install(&config.game.wine.builds, move |state| {
|
||||
match progress_bar.update_from_state(state) {
|
||||
ProgressUpdateResult::Updated => (),
|
||||
ProgressUpdateResult::Error(_, _) => todo!(),
|
||||
|
||||
ProgressUpdateResult::Finished => {
|
||||
let mut config = config::get().unwrap();
|
||||
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||
|
||||
config.game.wine.selected = Some(wine_version.name.clone());
|
||||
|
||||
config::update_raw(config.clone()).unwrap();
|
||||
|
||||
prefix.update(&config.game.wine.builds, wine_version.clone()).unwrap();
|
||||
|
||||
let mut dxvk_version_installer = Installer::new(&dxvk_version.uri).unwrap();
|
||||
|
||||
let dxvk_version = dxvk_version.clone();
|
||||
let progress_bar = progress_bar.clone();
|
||||
|
||||
dxvk_version_installer.install(&config.game.dxvk.builds, move |state| {
|
||||
match progress_bar.update_from_state(state) {
|
||||
ProgressUpdateResult::Updated => (),
|
||||
ProgressUpdateResult::Error(_, _) => todo!(),
|
||||
|
||||
ProgressUpdateResult::Finished => {
|
||||
let mut config = config::get().unwrap();
|
||||
|
||||
config.game.dxvk.selected = Some(dxvk_version.name.clone());
|
||||
|
||||
config::update_raw(config.clone()).unwrap();
|
||||
|
||||
println!("Done!!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Actions::Exit => {
|
||||
this.widgets.window.close();
|
||||
}
|
||||
}
|
||||
|
||||
glib::Continue(true)
|
||||
});
|
||||
|
||||
self.actions.set(Some(sender));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Update widgets state by calling some action
|
||||
pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError<Actions>> {
|
||||
let actions = self.actions.take();
|
||||
|
||||
let result = match &actions {
|
||||
Some(sender) => Ok(sender.send(action)?),
|
||||
None => Ok(())
|
||||
};
|
||||
|
||||
self.actions.set(actions);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Show application window
|
||||
pub fn show(&self) {
|
||||
self.widgets.window.show();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for App {}
|
185
src/ui/main.rs
185
src/ui/main.rs
|
@ -14,6 +14,7 @@ use crate::ui::*;
|
|||
|
||||
use super::preferences::PreferencesStack;
|
||||
use super::traits::toast_error::ToastError;
|
||||
use super::components::progress_bar::ProgressBar;
|
||||
|
||||
use crate::lib::config;
|
||||
use crate::lib::game;
|
||||
|
@ -22,24 +23,7 @@ use crate::lib::launcher::states::LauncherState;
|
|||
use crate::lib::wine_prefix::WinePrefix;
|
||||
use crate::lib::wine::Version as WineVersion;
|
||||
use crate::lib::dxvk::Version as DxvkVersion;
|
||||
|
||||
fn prettify_bytes(bytes: u64) -> String {
|
||||
if bytes > 1024 * 1024 * 1024 {
|
||||
format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
|
||||
}
|
||||
|
||||
else if bytes > 1024 * 1024 {
|
||||
format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0)
|
||||
}
|
||||
|
||||
else if bytes > 1024 {
|
||||
format!("{:.2} KB", bytes as f64 / 1024.0)
|
||||
}
|
||||
|
||||
else {
|
||||
format!("{:.2} B", bytes)
|
||||
}
|
||||
}
|
||||
use crate::lib::prettify_bytes::prettify_bytes;
|
||||
|
||||
fn toast_error(app: &App, msg: &str, err: Error) {
|
||||
app.update(Actions::ToastError(Rc::new((
|
||||
|
@ -70,15 +54,13 @@ pub struct AppWidgets {
|
|||
pub launch_game: gtk::Button,
|
||||
pub open_preferences: gtk::Button,
|
||||
|
||||
pub launch_game_group: adw::PreferencesGroup,
|
||||
pub progress_bar_group: adw::PreferencesGroup,
|
||||
pub progress_bar: gtk::ProgressBar,
|
||||
pub progress_bar: ProgressBar,
|
||||
|
||||
pub preferences_stack: PreferencesStack
|
||||
}
|
||||
|
||||
impl AppWidgets {
|
||||
fn try_get() -> Result<Self, String> {
|
||||
pub fn try_get() -> Result<Self, String> {
|
||||
let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/main.ui"));
|
||||
|
||||
let window = get_object::<adw::ApplicationWindow>(&builder, "window")?;
|
||||
|
@ -98,9 +80,11 @@ impl AppWidgets {
|
|||
launch_game: get_object(&builder, "launch_game")?,
|
||||
open_preferences: get_object(&builder, "open_preferences")?,
|
||||
|
||||
launch_game_group: get_object(&builder, "launch_game_group")?,
|
||||
progress_bar_group: get_object(&builder, "progress_bar_group")?,
|
||||
progress_bar: get_object(&builder, "progress_bar")?,
|
||||
progress_bar: ProgressBar::new(
|
||||
get_object(&builder, "progress_bar")?,
|
||||
get_object(&builder, "launch_game_group")?,
|
||||
get_object(&builder, "progress_bar_group")?
|
||||
),
|
||||
|
||||
preferences_stack: PreferencesStack::new(window, toast_overlay)?
|
||||
};
|
||||
|
@ -311,144 +295,6 @@ impl App {
|
|||
|
||||
this.update(Actions::ShowProgressBar).unwrap();
|
||||
|
||||
// Download wine version if not installed
|
||||
match WineVersion::latest() {
|
||||
Ok(wine) => match Installer::new(wine.uri) {
|
||||
Ok(mut installer) => {
|
||||
let (send, recv) = std::sync::mpsc::channel();
|
||||
let wine_title = wine.title.clone();
|
||||
|
||||
installer.install(&config.game.wine.builds, clone!(@strong this => move |state| {
|
||||
match state {
|
||||
InstallerUpdate::UnpackingFinished => {
|
||||
send.send(true).unwrap();
|
||||
}
|
||||
|
||||
InstallerUpdate::DownloadingError(_) |
|
||||
InstallerUpdate::UnpackingError => {
|
||||
send.send(false).unwrap();
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(wine_title.clone()))))).unwrap();
|
||||
}));
|
||||
|
||||
// Block thread until downloading finished
|
||||
if recv.recv().unwrap() {
|
||||
config.game.wine.selected = Some(wine.name);
|
||||
|
||||
config::update(config.clone());
|
||||
}
|
||||
|
||||
else {
|
||||
println!("I'm tired, Boss!");
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to init wine version installer", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to load wine versions list", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create prefix if needed
|
||||
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||
|
||||
if !prefix.exists() {
|
||||
this.update(Actions::UpdateProgress {
|
||||
fraction: Rc::new(0.0),
|
||||
title: Rc::new(String::from("Creating prefix..."))
|
||||
}).unwrap();
|
||||
|
||||
match config.try_get_selected_wine_info() {
|
||||
Some(wine_version) => {
|
||||
if let Err(err) = prefix.update(&config.game.wine.builds, wine_version) {
|
||||
toast_error(&this, "Failed to create wineprefix", err);
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => return
|
||||
}
|
||||
}
|
||||
|
||||
// Download and apply DXVK if not installed
|
||||
match DxvkVersion::latest() {
|
||||
Ok(dxvk) => match Installer::new(&dxvk.uri) {
|
||||
Ok(mut installer) => {
|
||||
let (send, recv) = std::sync::mpsc::channel();
|
||||
let dxvk_title = dxvk.name.clone();
|
||||
|
||||
installer.install(&config.game.dxvk.builds, clone!(@strong this => move |state| {
|
||||
match state {
|
||||
InstallerUpdate::UnpackingFinished => {
|
||||
send.send(true).unwrap();
|
||||
}
|
||||
|
||||
InstallerUpdate::DownloadingError(_) |
|
||||
InstallerUpdate::UnpackingError => {
|
||||
send.send(false).unwrap();
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(dxvk_title.clone()))))).unwrap();
|
||||
}));
|
||||
|
||||
// Block thread until downloading finished
|
||||
if recv.recv().unwrap() {
|
||||
config.game.dxvk.selected = Some(dxvk.name.clone());
|
||||
|
||||
config::update(config.clone());
|
||||
}
|
||||
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply DXVK
|
||||
this.update(Actions::UpdateProgress {
|
||||
fraction: Rc::new(100.0),
|
||||
title: Rc::new(String::from("Applying DXVK..."))
|
||||
}).unwrap();
|
||||
|
||||
match dxvk.apply(&config.game.dxvk.builds, &config.game.wine.prefix) {
|
||||
Ok(_) => {
|
||||
config.game.dxvk.selected = Some(dxvk.name);
|
||||
|
||||
config::update(config.clone());
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to apply DXVK", err);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to init wine version installer", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
toast_error(&this, "Failed to load wine versions list", err.into());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Download diff
|
||||
diff.install_to_by(config.game.path, config.launcher.temp, move |state| {
|
||||
this.update(Actions::UpdateProgressByState(Rc::new((state, None)))).unwrap();
|
||||
|
@ -464,6 +310,7 @@ impl App {
|
|||
}
|
||||
|
||||
Actions::UpdateProgressByState(state) => {
|
||||
todo!();
|
||||
// let (state, suffix) = (&*state).clone();
|
||||
|
||||
match &state.0 {
|
||||
|
@ -532,21 +379,15 @@ impl App {
|
|||
}
|
||||
|
||||
Actions::ShowProgressBar => {
|
||||
this.widgets.progress_bar.set_text(None);
|
||||
this.widgets.progress_bar.set_fraction(0.0);
|
||||
|
||||
this.widgets.launch_game_group.hide();
|
||||
this.widgets.progress_bar_group.show();
|
||||
this.widgets.progress_bar.show();
|
||||
}
|
||||
|
||||
Actions::UpdateProgress { fraction, title } => {
|
||||
this.widgets.progress_bar.set_text(Some(title.as_str()));
|
||||
this.widgets.progress_bar.set_fraction(*fraction);
|
||||
this.widgets.progress_bar.update(*fraction, Some(title.as_str()));
|
||||
}
|
||||
|
||||
Actions::HideProgressBar => {
|
||||
this.widgets.launch_game_group.show();
|
||||
this.widgets.progress_bar_group.hide();
|
||||
this.widgets.progress_bar.hide();
|
||||
}
|
||||
|
||||
Actions::ToastError(toast) => {
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use gtk4::{self as gtk, prelude::*};
|
||||
use libadwaita::{self as adw, prelude::*};
|
||||
|
||||
mod first_run;
|
||||
mod main;
|
||||
mod preferences;
|
||||
mod traits;
|
||||
|
||||
pub mod components;
|
||||
|
||||
pub use main::{
|
||||
App as MainApp,
|
||||
// AppState as MainAppState,
|
||||
};
|
||||
pub use first_run::App as FirstRunApp;
|
||||
pub use main::App as MainApp;
|
||||
|
||||
/// This function loads object from builder or panics if it doesn't exist
|
||||
pub fn get_object<T: IsA<gtk::glib::Object>>(builder: >k::Builder, name: &str) -> Result<T, String> {
|
||||
|
|
Loading…
Reference in a new issue