diff --git a/assets/ui/main.blp b/assets/ui/main.blp index 81c087d..f30384d 100644 --- a/assets/ui/main.blp +++ b/assets/ui/main.blp @@ -79,7 +79,7 @@ Adw.ApplicationWindow window { spacing: 20; Gtk.ProgressBar progress_bar { - text: "Downloading: 37% (3.7 of 10 GB)\n"; + text: "Downloading: 37% (3.7 of 10 GB)"; show-text: true; width-request: 260; diff --git a/src/main.rs b/src/main.rs index a7beaba..b0e8326 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use gtk4::{self as gtk, prelude::*}; use libadwaita::{self as adw, prelude::*}; +use gtk::{CssProvider, StyleContext, gdk::Display, STYLE_PROVIDER_PRIORITY_APPLICATION}; + pub mod ui; pub mod lib; @@ -19,9 +21,21 @@ fn main() { // Init app window and show it application.connect_activate(|app| { - let app = MainApp::new(app).expect("Failed to init MainApp"); + // Apply CSS styles to the application + let provider = CssProvider::new(); - app.show(); + provider.load_from_data(include_bytes!("styles.css")); + + StyleContext::add_provider_for_display( + &Display::default().expect("Could not connect to a display"), + &provider, + STYLE_PROVIDER_PRIORITY_APPLICATION + ); + + // Load main window and show it + let main = MainApp::new(app).expect("Failed to init MainApp"); + + main.show(); }); // Run app diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..72a6ae7 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,3 @@ +progressbar > text { + margin-bottom: 4px; +} diff --git a/src/ui/main.rs b/src/ui/main.rs index e4ed01a..578cac6 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -71,13 +71,21 @@ impl AppWidgets { /// 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 -#[derive(Debug)] +#[derive(Debug, glib::Downgrade)] pub enum Actions { OpenPreferencesPage, PreferencesGoBack, LaunchGame } +impl Actions { + pub fn into_fn>(&self, app: &App) -> Box { + Box::new(clone!(@weak self as action, @strong app => move |_| { + app.update(action); + })) + } +} + /// 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 @@ -100,7 +108,8 @@ pub struct Values; #[derive(Clone, glib::Downgrade)] pub struct App { widgets: AppWidgets, - values: Rc> + values: Rc>, + actions: Rc>>> } impl App { @@ -108,8 +117,9 @@ impl App { pub fn new(app: >k::Application) -> Result { let result = Self { widgets: AppWidgets::try_get()?, - values: Default::default() - }.init_events(); + values: Default::default(), + actions: Default::default() + }.init_events().init_actions(); // Bind app to the window result.widgets.window.set_application(Some(app)); @@ -120,51 +130,72 @@ impl App { /// Add default events and values to the widgets fn init_events(self) -> Self { // Open preferences page - self.widgets.open_preferences.connect_clicked(clone!(@strong self as this => move |_| { - this.update(Actions::OpenPreferencesPage); - })); + self.widgets.open_preferences.connect_clicked(Actions::OpenPreferencesPage.into_fn(&self)); // Go back button for preferences page - self.widgets.preferences_stack.preferences_go_back.connect_clicked(clone!(@strong self as this => move |_| { - this.update(Actions::PreferencesGoBack); - })); + self.widgets.preferences_stack.preferences_go_back.connect_clicked(Actions::PreferencesGoBack.into_fn(&self)); // Launch game - self.widgets.launch_game.connect_clicked(clone!(@strong self as this => move |_| { - this.update(Actions::LaunchGame); + self.widgets.launch_game.connect_clicked(Actions::LaunchGame.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::(glib::PRIORITY_DEFAULT); + + receiver.attach(None, clone!(@strong self as this => move |action| { + let values = this.values.take(); + + // Some debug output + println!("[update] action: {:?}, values: {:?}", &action, &values); + + match action { + Actions::OpenPreferencesPage => { + this.widgets.leaflet.set_visible_child_name("preferences_page"); + + if let Err(err) = this.widgets.preferences_stack.update() { + this.toast_error("Failed to update preferences", err); + } + } + + Actions::PreferencesGoBack => { + this.widgets.leaflet.navigate(adw::NavigationDirection::Back); + } + + Actions::LaunchGame => { + // 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); + } + } + } + + this.values.set(values); + + glib::Continue(true) })); + self.actions.set(Some(sender)); + self } /// Update widgets state by calling some action - pub fn update(&self, action: Actions) { - let values = self.values.take(); + pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError> { + let actions = self.actions.take(); + + let result = match &actions { + Some(sender) => Ok(sender.send(action)?), + None => Ok(()) + }; - println!("[update] action: {:?}, counter: {:?}", &action, &values); + self.actions.set(actions); - match action { - Actions::OpenPreferencesPage => { - self.widgets.leaflet.set_visible_child_name("preferences_page"); - - if let Err(err) = self.widgets.preferences_stack.update() { - self.toast_error("Failed to update preferences", err); - } - } - - Actions::PreferencesGoBack => { - self.widgets.leaflet.navigate(adw::NavigationDirection::Back); - } - - Actions::LaunchGame => { - // Display toast message if the game is failed to run - if let Err(err) = game::run(false) { - self.toast_error("Failed to run game", err); - } - } - } - - self.values.set(values); + result } /// Show application window diff --git a/src/ui/preferences/general_page.rs b/src/ui/preferences/general_page.rs index 7d43632..32015d0 100644 --- a/src/ui/preferences/general_page.rs +++ b/src/ui/preferences/general_page.rs @@ -1,8 +1,9 @@ use gtk4::{self as gtk, prelude::*}; use libadwaita::{self as adw, prelude::*}; -use gtk4::glib; -use gtk4::glib::clone; +use gtk::glib; +use gtk::glib::clone; +use gtk::Align; use std::rc::Rc; use std::cell::Cell; @@ -30,7 +31,7 @@ pub struct AppWidgets { pub dxvk_vanilla: adw::ExpanderRow, pub dxvk_async: adw::ExpanderRow, - pub dxvk_components: Rc> + pub dxvk_components: Rc> } impl AppWidgets { @@ -58,38 +59,28 @@ impl AppWidgets { let mut components = Vec::new(); - for version in list.vanilla { - let row = adw::ActionRow::new(); - let button = gtk::Button::new(); + for (i, versions) in [list.vanilla, list.r#async].into_iter().enumerate() { + for version in versions { + let row = adw::ActionRow::new(); + let button = gtk::Button::new(); - row.set_title(&version.version); - row.set_visible(version.recommended); + row.set_title(&version.version); + row.set_visible(version.recommended); - button.set_icon_name("document-save-symbolic"); - button.set_valign(gtk::Align::Center); - button.add_css_class("flat"); + button.set_icon_name("document-save-symbolic"); + button.set_valign(gtk::Align::Center); + button.add_css_class("flat"); - row.add_suffix(&button); + row.add_suffix(&button); - result.dxvk_vanilla.add_row(&row); - components.push((row, version)); - } + match i { + 0 => result.dxvk_vanilla.add_row(&row), + 1 => result.dxvk_async.add_row(&row), + _ => () + } - for version in list.r#async { - let row = adw::ActionRow::new(); - let button = gtk::Button::new(); - - row.set_title(&version.version); - row.set_visible(version.recommended); - - button.set_icon_name("document-save-symbolic"); - button.set_valign(gtk::Align::Center); - button.add_css_class("flat"); - - row.add_suffix(&button); - - result.dxvk_async.add_row(&row); - components.push((row, version)); + components.push((row, button, version)); + } } result.dxvk_components = Rc::new(components); @@ -101,9 +92,17 @@ impl AppWidgets { /// 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 -#[derive(Debug)] +#[derive(Debug, glib::Downgrade)] pub enum Actions { + DownloadDXVK(Rc) +} +impl Actions { + pub fn into_fn>(&self, app: &App) -> Box { + Box::new(clone!(@weak self as action, @strong app => move |_| { + app.update(action); + })) + } } /// This enum is used to store some of this application data @@ -128,7 +127,8 @@ pub struct Values; #[derive(Clone, glib::Downgrade)] pub struct App { widgets: AppWidgets, - values: Rc> + values: Rc>, + actions: Rc>>> } impl App { @@ -136,8 +136,9 @@ impl App { pub fn new() -> Result { let result = Self { widgets: AppWidgets::try_get()?, - values: Default::default() - }.init_events(); + values: Default::default(), + actions: Default::default() + }.init_events().init_actions(); Ok(result) } @@ -145,8 +146,8 @@ impl App { /// Add default events and values to the widgets fn init_events(self) -> Self { // Set DXVK recommended only switcher event - self.widgets.dxvk_recommended_only.connect_state_notify(clone!(@weak self as this => move |switcher| { - for (component, version) in &*this.widgets.dxvk_components { + self.widgets.dxvk_recommended_only.connect_state_notify(clone!(@strong self as this => move |switcher| { + for (component, _, version) in &*this.widgets.dxvk_components { component.set_visible(if switcher.state() { version.recommended } else { @@ -155,18 +156,73 @@ impl App { } })); + // DXVK install/remove buttons + let components = (*self.widgets.dxvk_components).clone(); + + for (i, (_, button, _)) in components.into_iter().enumerate() { + button.connect_clicked(Actions::DownloadDXVK(Rc::new(i)).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::(glib::PRIORITY_DEFAULT); + + receiver.attach(None, clone!(@strong self as this => move |action| { + let values = this.values.take(); + + // Some debug output + println!("[update] action: {:?}, values: {:?}", &action, &values); + + match action { + Actions::DownloadDXVK(i) => { + println!("123"); + + let (row, button, version) = &this.widgets.dxvk_components[*i]; + + let progress_bar = gtk::ProgressBar::new(); + + progress_bar.set_text(Some("Downloading: 0%")); + progress_bar.set_show_text(true); + + progress_bar.set_width_request(200); + progress_bar.set_valign(Align::Center); + + button.set_visible(false); + + row.add_suffix(&progress_bar); + + row.remove(&progress_bar); + button.set_visible(true); + } + } + + this.values.set(values); + + glib::Continue(true) + })); + + self.actions.set(Some(sender)); + self } /// Update widgets state by calling some action - pub fn update(&self, action: Actions) { - /*let values = self.values.take(); + pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError> { + let actions = self.actions.take(); + + let result = match &actions { + Some(sender) => Ok(sender.send(action)?), + None => Ok(()) + }; - match action { - - } + self.actions.set(actions); - self.values.set(values);*/ + result } pub fn title() -> String { @@ -247,3 +303,5 @@ impl App { Ok(()) } } + +unsafe impl Send for App {}