From 3f5ce430f985f22b688849a30b386f5f189af668 Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Thu, 23 Feb 2023 20:37:02 +0200 Subject: [PATCH] feat(ui): initial work on adding first run window Added blank first run window with welcome page. On first start launcher will create launcher folder and `.first-run` file inside if needed. If file exists - launcher will open first run window instead of the main one (to prevent further data loadings in `init` functions) --- src/main.rs | 48 +++++++++---- src/ui/first_run/main.rs | 140 ++++++++++++++++++++++++++++++++++++ src/ui/first_run/mod.rs | 2 + src/ui/first_run/welcome.rs | 87 ++++++++++++++++++++++ src/ui/main.rs | 14 ++-- src/ui/mod.rs | 1 + src/ui/preferences/main.rs | 4 +- 7 files changed, 274 insertions(+), 22 deletions(-) create mode 100644 src/ui/first_run/main.rs create mode 100644 src/ui/first_run/mod.rs create mode 100644 src/ui/first_run/welcome.rs diff --git a/src/main.rs b/src/main.rs index 0920008..337a513 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use relm4::prelude::*; use anime_launcher_sdk::config; use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::genshin::prelude::*; +use anime_launcher_sdk::consts::launcher_dir; use tracing_subscriber::prelude::*; use tracing_subscriber::filter::*; @@ -37,19 +38,35 @@ lazy_static::lazy_static! { pub static ref GAME: Game = Game::new(&CONFIG.game.path); + /// Path to launcher folder. Standard is `$HOME/.local/share/anime-game-launcher` + pub static ref LAUNCHER_FOLDER: PathBuf = launcher_dir().unwrap_or_default(); + /// Path to `debug.log` file. Standard is `$HOME/.local/share/anime-game-launcher/debug.log` - pub static ref DEBUG_FILE: PathBuf = anime_launcher_sdk::consts::launcher_dir().unwrap_or_default().join("debug.log"); + pub static ref DEBUG_FILE: PathBuf = LAUNCHER_FOLDER.join("debug.log"); /// Path to `background` file. Standard is `$HOME/.local/share/anime-game-launcher/background` - pub static ref BACKGROUND_FILE: PathBuf = anime_launcher_sdk::consts::launcher_dir().unwrap_or_default().join("background"); + pub static ref BACKGROUND_FILE: PathBuf = LAUNCHER_FOLDER.join("background"); /// Path to `.keep-background` file. Used to mark launcher that it shouldn't update background picture /// /// Standard is `$HOME/.local/share/anime-game-launcher/.keep-background` - pub static ref KEEP_BACKGROUND_FILE: PathBuf = anime_launcher_sdk::consts::launcher_dir().unwrap_or_default().join(".keep-background"); + pub static ref KEEP_BACKGROUND_FILE: PathBuf = LAUNCHER_FOLDER.join(".keep-background"); + + /// Path to `.first-run` file. Used to mark launcher that it should run FirstRun window + /// + /// Standard is `$HOME/.local/share/anime-game-launcher/.first-run` + pub static ref FIRST_RUN_FILE: PathBuf = LAUNCHER_FOLDER.join(".first-run"); } fn main() { + // Create launcher folder if it isn't + if !LAUNCHER_FOLDER.exists() { + std::fs::create_dir_all(LAUNCHER_FOLDER.as_path()).expect("Failed to create launcher folder"); + + // This one is kinda critical buy well, I can't do something with it + std::fs::write(FIRST_RUN_FILE.as_path(), "").expect("Failed to create .first-run file"); + } + // Force debug output let force_debug = std::env::args().any(|arg| &arg == "--debug"); @@ -98,13 +115,6 @@ fn main() { gtk::glib::set_application_name("An Anime Game Launcher"); gtk::glib::set_program_name(Some("An Anime Game Launcher")); - // Set UI language - unsafe { - i18n::LANG = config::get().unwrap().launcher.language.parse().unwrap(); - - tracing::info!("Set UI language to {}", i18n::LANG); - } - // Create the app let app = RelmApp::new(APP_ID); @@ -120,7 +130,21 @@ fn main() { background-size: cover; }} ", BACKGROUND_FILE.to_string_lossy())); + + // Run FirstRun window if .first-run file persist + if FIRST_RUN_FILE.exists() { + app.run::(()); + } - // Run the app - app.run::(()); + // Run the app if everything's ready + else { + // Set UI language + unsafe { + i18n::LANG = config::get().unwrap().launcher.language.parse().unwrap(); + + tracing::info!("Set UI language to {}", i18n::LANG); + } + + app.run::(()); + } } diff --git a/src/ui/first_run/main.rs b/src/ui/first_run/main.rs new file mode 100644 index 0000000..5bf2c88 --- /dev/null +++ b/src/ui/first_run/main.rs @@ -0,0 +1,140 @@ +use relm4::prelude::*; +use relm4::component::*; + +use gtk::prelude::*; +use adw::prelude::*; + +use crate::i18n::tr; + +use super::welcome::*; + +static mut MAIN_WINDOW: Option = None; + +pub struct FirstRunApp { + welcome: AsyncController, + + toast_overlay: adw::ToastOverlay, + carousel: adw::Carousel +} + +#[derive(Debug, Clone)] +pub enum FirstRunAppMsg { + ScrollToTosWarning, + + Toast { + title: String, + description: Option + } +} + +#[relm4::component(pub)] +impl SimpleComponent for FirstRunApp { + type Init = (); + type Input = FirstRunAppMsg; + type Output = (); + + view! { + window = adw::Window { + set_title: Some("Welcome"), // TODO: update this based on currently open page + set_default_size: (780, 560), + + #[local_ref] + toast_overlay -> adw::ToastOverlay { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + + adw::HeaderBar { + add_css_class: "flat" + }, + + #[local_ref] + carousel -> adw::Carousel { + set_allow_mouse_drag: false, + + append = model.welcome.widget(), + }, + + adw::CarouselIndicatorDots { + set_carousel: Some(&carousel), + set_height_request: 32 + } + } + } + } + } + + fn init( + _parent: Self::Init, + root: &Self::Root, + _sender: ComponentSender, + ) -> ComponentParts { + tracing::info!("Initializing first run window"); + + let toast_overlay = adw::ToastOverlay::new(); + let carousel = adw::Carousel::new(); + + let model = Self { + welcome: WelcomeApp::builder() + .launch(()) + .detach(), + + toast_overlay, + carousel + }; + + let toast_overlay = &model.toast_overlay; + let carousel = &model.carousel; + + let widgets = view_output!(); + + unsafe { + MAIN_WINDOW = Some(widgets.window.clone()); + } + + ComponentParts { model, widgets } // will return soon + } + + fn update(&mut self, msg: Self::Input, sender: ComponentSender) { + tracing::debug!("Called first run window event: {:?}", msg); + + match msg { + FirstRunAppMsg::ScrollToTosWarning => { + self.carousel.scroll_to(self.welcome.widget(), true); + } + + FirstRunAppMsg::Toast { title, description } => unsafe { + let toast = adw::Toast::new(&title); + + toast.set_timeout(5); + + if let Some(description) = description { + toast.set_button_label(Some(&tr("details"))); + + let dialog = adw::MessageDialog::new(MAIN_WINDOW.as_ref(), Some(&title), Some(&description)); + + dialog.add_response("close", &tr("close")); + dialog.add_response("save", &tr("save")); + + dialog.set_response_appearance("save", adw::ResponseAppearance::Suggested); + + #[allow(unused_must_use)] + dialog.connect_response(Some("save"), |_, _| { + let result = std::process::Command::new("xdg-open") + .arg(crate::DEBUG_FILE.as_os_str()) + .output(); + + if let Err(err) = result { + tracing::error!("Failed to open debug file: {}", err); + } + }); + + toast.connect_button_clicked(move |_| { + dialog.show(); + }); + } + + self.toast_overlay.add_toast(&toast); + } + } + } +} diff --git a/src/ui/first_run/mod.rs b/src/ui/first_run/mod.rs new file mode 100644 index 0000000..fb56dea --- /dev/null +++ b/src/ui/first_run/mod.rs @@ -0,0 +1,2 @@ +pub mod main; +pub mod welcome; diff --git a/src/ui/first_run/welcome.rs b/src/ui/first_run/welcome.rs new file mode 100644 index 0000000..68d4361 --- /dev/null +++ b/src/ui/first_run/welcome.rs @@ -0,0 +1,87 @@ +use relm4::prelude::*; +use relm4::component::*; + +use adw::prelude::*; + +use crate::*; +use super::main::FirstRunAppMsg; + +pub struct WelcomeApp; + +#[derive(Debug, Clone)] +pub enum WelcomeAppMsg { + Continue +} + +#[relm4::component(async, pub)] +impl SimpleAsyncComponent for WelcomeApp { + type Init = (); + type Input = WelcomeAppMsg; + type Output = FirstRunAppMsg; + + view! { + adw::PreferencesPage { + set_hexpand: true, + + add = &adw::PreferencesGroup { + gtk::Image { + set_resource: Some("/org/app/images/icon.png"), + set_height_request: 128, + set_margin_top: 16 + }, + + gtk::Label { + set_label: "An Anime Game Launcher", + set_margin_top: 32, + add_css_class: "title-1" + }, + + gtk::Label { + set_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", + + set_justify: gtk::Justification::Center, + set_wrap: true, + set_margin_top: 32 + } + }, + + add = &adw::PreferencesGroup { + set_valign: gtk::Align::Center, + set_vexpand: true, + + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_halign: gtk::Align::Center, + set_spacing: 8, + + gtk::Button { + set_label: "Continue", + add_css_class: "suggested-action", + + connect_clicked => WelcomeAppMsg::Continue + } + } + } + } + } + + async fn init( + _init: Self::Init, + root: Self::Root, + _sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let model = Self; + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender) { + match msg { + #[allow(unused_must_use)] + WelcomeAppMsg::Continue => { + sender.output(Self::Output::ScrollToTosWarning); + } + } + } +} diff --git a/src/ui/main.rs b/src/ui/main.rs index 40b951f..5e440a6 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -346,15 +346,13 @@ impl SimpleComponent for App { // TODO: reduce code somehow group.add_action::(&RelmAction::new_stateless(clone!(@strong sender => move |_| { - if let Some(dir) = anime_launcher_sdk::consts::launcher_dir() { - if let Err(err) = std::process::Command::new("xdg-open").arg(dir).spawn() { - sender.input(AppMsg::Toast { - title: tr("launcher-folder-opening-error"), - description: Some(err.to_string()) - }); + if let Err(err) = std::process::Command::new("xdg-open").arg(LAUNCHER_FOLDER.as_path()).spawn() { + sender.input(AppMsg::Toast { + title: tr("launcher-folder-opening-error"), + description: Some(err.to_string()) + }); - tracing::error!("Failed to open launcher folder: {err}"); - } + tracing::error!("Failed to open launcher folder: {err}"); } }))); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 099de29..e5365c9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,3 +2,4 @@ pub mod main; pub mod about; pub mod preferences; pub mod components; +pub mod first_run; diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index 9b44ab4..2d5a16c 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -106,12 +106,12 @@ impl SimpleAsyncComponent for PreferencesApp { #[allow(unused_must_use)] PreferencesAppMsg::UpdateGameDiff(diff) => { self.general.sender().send(GeneralAppMsg::UpdateGameDiff(diff)); - }, + } #[allow(unused_must_use)] PreferencesAppMsg::UpdatePatch(patch) => { self.general.sender().send(GeneralAppMsg::UpdatePatch(patch)); - }, + } PreferencesAppMsg::Toast { title, description } => unsafe { let toast = adw::Toast::new(&title);