Reworked pages code

- added usage of `clone!` macro
- rewritten general and enhanced pages to follow new standard pattern
This commit is contained in:
Observer KRypt0n_ 2022-07-04 10:35:28 +02:00
parent 0524cf80d4
commit 8f65f6f3a8
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
4 changed files with 278 additions and 136 deletions

View file

@ -1,6 +1,9 @@
use gtk4::{self as gtk, prelude::*};
use libadwaita::{self as adw, prelude::*};
use gtk4::glib;
use gtk4::glib::clone;
use std::rc::Rc;
use std::cell::Cell;
@ -16,7 +19,7 @@ use crate::lib::game;
/// `AppWidgets::try_get` 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)]
#[derive(Clone, glib::Downgrade)]
pub struct AppWidgets {
pub window: adw::ApplicationWindow,
pub toast_overlay: adw::ToastOverlay,
@ -80,7 +83,7 @@ pub enum Actions {
/// In this example we store a counter here to know what should we increment or decrement
///
/// This must implement `Default` trait
#[derive(Debug, Default)]
#[derive(Debug, Default, glib::Downgrade)]
pub struct Values;
/// The main application structure
@ -94,7 +97,7 @@ pub struct Values;
///
/// 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)]
#[derive(Clone, glib::Downgrade)]
pub struct App {
widgets: AppWidgets,
values: Rc<Cell<Values>>
@ -117,25 +120,19 @@ impl App {
/// Add default events and values to the widgets
fn init_events(self) -> Self {
// Open preferences page
let self_copy = self.clone();
self.widgets.open_preferences.connect_clicked(move |_| {
self_copy.update(Actions::OpenPreferencesPage);
});
self.widgets.open_preferences.connect_clicked(clone!(@strong self as this => move |_| {
this.update(Actions::OpenPreferencesPage);
}));
// Go back button for preferences page
let self_copy = self.clone();
self.widgets.preferences_stack.preferences_go_back.connect_clicked(move |_| {
self_copy.update(Actions::PreferencesGoBack);
});
self.widgets.preferences_stack.preferences_go_back.connect_clicked(clone!(@strong self as this => move |_| {
this.update(Actions::PreferencesGoBack);
}));
// Launch game
let self_copy = self.clone();
self.widgets.launch_game.connect_clicked(move |_| {
self_copy.update(Actions::LaunchGame);
});
self.widgets.launch_game.connect_clicked(clone!(@strong self as this => move |_| {
this.update(Actions::LaunchGame);
}));
self
}

View file

@ -1,13 +1,23 @@
use gtk4::{self as gtk, prelude::*};
use libadwaita::{self as adw, prelude::*};
use gtk4::glib;
use gtk4::glib::clone;
use std::rc::Rc;
use std::cell::Cell;
use std::io::Error;
use crate::ui::get_object;
use crate::lib::config;
#[derive(Clone)]
pub struct Page {
/// This structure is used to describe widgets used in application
///
/// `AppWidgets::try_get` 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, glib::Downgrade)]
pub struct AppWidgets {
pub page: adw::PreferencesPage,
pub sync_combo: adw::ComboRow,
@ -19,8 +29,8 @@ pub struct Page {
pub gamemode_switcher: gtk::Switch
}
impl Page {
pub fn new() -> Result<Self, String> {
impl AppWidgets {
fn try_get() -> Result<Self, String> {
let builder = gtk::Builder::from_string(include_str!("../../../assets/ui/.dist/preferences_enhanced.ui"));
let result = Self {
@ -35,103 +45,155 @@ impl Page {
gamemode_switcher: get_object(&builder, "gamemode_switcher")?
};
// Wine sync selection
result.sync_combo.connect_selected_notify(|hud| {
if let Ok(mut config) = config::get() {
// TODO: show toast
config.game.wine.sync = config::WineSync::try_from(hud.selected()).unwrap();
Ok(result)
}
}
config::update(config).unwrap();
}
});
/// 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)]
pub enum Actions<T> {
OptionSelection(fn(crate::lib::config::Config, T) -> crate::lib::config::Config, T)
}
// Wine language selection
result.wine_lang.connect_selected_notify(|hud| {
if let Ok(mut config) = config::get() {
// TODO: show toast
config.game.wine.language = config::WineLang::try_from(hud.selected()).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, glib::Downgrade)]
pub struct Values;
config::update(config).unwrap();
}
});
/// 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, glib::Downgrade)]
pub struct App {
widgets: AppWidgets,
values: Rc<Cell<Values>>
}
// HUD selection
result.hud_combo.connect_selected_notify(|hud| {
if let Ok(mut config) = config::get() {
// TODO: show toast
config.game.enhancements.hud = config::HUD::try_from(hud.selected()).unwrap();
config::update(config).unwrap();
}
});
// FSR strength selection
result.fsr_combo.connect_selected_notify(|hud| {
if let Ok(mut config) = config::get() {
// TODO: show toast
// Ultra Quality = 5
// Quality = 4
// Balanced = 3
// Performance = 2
//
// Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88)
config.game.enhancements.fsr.strength = 5 - hud.selected();
config::update(config).unwrap();
}
});
// FSR switching
result.fsr_switcher.connect_state_notify(|switcher| {
if let Ok(mut config) = config::get() {
// TODO: show toast
config.game.enhancements.fsr.enabled = switcher.state();
config::update(config).unwrap();
}
});
// Gamemode switching
result.gamemode_switcher.connect_state_notify(|switcher| {
if let Ok(mut config) = config::get() {
// TODO: show toast
config.game.enhancements.gamemode = switcher.state();
config::update(config).unwrap();
}
});
impl App {
/// Create new application
pub fn new() -> Result<Self, String> {
let result = Self {
widgets: AppWidgets::try_get()?,
values: Default::default()
}.init_events();
Ok(result)
}
/// Add default events and values to the widgets
fn init_events(self) -> Self {
// Wine sync selection
self.widgets.sync_combo.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.wine.sync = value; config
}, config::WineSync::try_from(hud.selected()).unwrap()));
}));
// Wine language selection
self.widgets.wine_lang.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.wine.language = value; config
}, config::WineLang::try_from(hud.selected()).unwrap()));
}));
// HUD selection
self.widgets.hud_combo.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.hud = value; config
}, config::HUD::try_from(hud.selected()).unwrap()));
}));
// FSR strength selection
//
// Ultra Quality = 5
// Quality = 4
// Balanced = 3
// Performance = 2
//
// Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88)
self.widgets.fsr_combo.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.fsr.strength = value; config
}, 5 - hud.selected()));
}));
// FSR switching
self.widgets.fsr_switcher.connect_state_notify(clone!(@weak self as this => move |switcher| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.fsr.enabled = value; config
}, switcher.state()));
}));
// Gamemode switching
self.widgets.gamemode_switcher.connect_state_notify(clone!(@weak self as this => move |switcher| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.gamemode = value; config
}, switcher.state()));
}));
self
}
/// Update widgets state by calling some action
pub fn update<T>(&self, action: Actions<T>) {
let values = self.values.take();
match action {
Actions::OptionSelection(update, value) => {
if let Ok(config) = config::get() {
// TODO: show toast
config::update((update)(config, value)).unwrap();
}
}
}
self.values.set(values);
}
pub fn title() -> String {
String::from("Enhanced")
}
pub fn get_page(&self) -> adw::PreferencesPage {
self.widgets.page.clone()
}
/// This method is being called by the `PreferencesStack::update`
pub fn update(&self, status_page: &adw::StatusPage) -> Result<(), Error> {
pub fn prepare(&self, status_page: &adw::StatusPage) -> Result<(), Error> {
let config = config::get()?;
status_page.set_description(Some("Loading preferences..."));
// Update Wine sync
self.sync_combo.set_selected(config.game.wine.sync.into());
self.widgets.sync_combo.set_selected(config.game.wine.sync.into());
// Update wine language
self.wine_lang.set_selected(config.game.wine.language.into());
self.widgets.wine_lang.set_selected(config.game.wine.language.into());
// Update HUD
self.hud_combo.set_selected(config.game.enhancements.hud.into());
self.widgets.hud_combo.set_selected(config.game.enhancements.hud.into());
// FSR strength selection
self.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength);
self.widgets.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength);
// FSR switching
self.fsr_switcher.set_state(config.game.enhancements.fsr.enabled);
self.widgets.fsr_switcher.set_state(config.game.enhancements.fsr.enabled);
// Gamemode switching
self.fsr_switcher.set_state(config.game.enhancements.gamemode);
self.widgets.fsr_switcher.set_state(config.game.enhancements.gamemode);
Ok(())
}

View file

@ -1,6 +1,11 @@
use gtk4::{self as gtk, prelude::*};
use libadwaita::{self as adw, prelude::*};
use gtk4::glib;
use gtk4::glib::clone;
use std::rc::Rc;
use std::cell::Cell;
use std::io::Error;
use anime_game_core::prelude::*;
@ -9,8 +14,13 @@ use crate::ui::get_object;
use crate::lib::config;
use crate::lib::dxvk;
#[derive(Clone)]
pub struct Page {
/// This structure is used to describe widgets used in application
///
/// `AppWidgets::try_get` 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, glib::Downgrade)]
pub struct AppWidgets {
pub page: adw::PreferencesPage,
pub game_version: gtk::Label,
@ -18,14 +28,16 @@ pub struct Page {
pub dxvk_recommended_only: gtk::Switch,
pub dxvk_vanilla: adw::ExpanderRow,
pub dxvk_async: adw::ExpanderRow
pub dxvk_async: adw::ExpanderRow,
pub dxvk_components: Rc<Vec<(adw::ActionRow, dxvk::Version)>>
}
impl Page {
pub fn new() -> Result<Self, String> {
impl AppWidgets {
fn try_get() -> Result<Self, String> {
let builder = gtk::Builder::from_string(include_str!("../../../assets/ui/.dist/preferences_general.ui"));
let result = Self {
let mut result = Self {
page: get_object(&builder, "general_page")?,
game_version: get_object(&builder, "game_version")?,
@ -33,7 +45,9 @@ impl Page {
dxvk_recommended_only: get_object(&builder, "dxvk_recommended_only")?,
dxvk_vanilla: get_object(&builder, "dxvk_vanilla")?,
dxvk_async: get_object(&builder, "dxvk_async")?
dxvk_async: get_object(&builder, "dxvk_async")?,
dxvk_components: Default::default()
};
// Update DXVK list
@ -77,55 +91,122 @@ impl Page {
result.dxvk_async.add_row(&row);
components.push((row, version));
}
result.dxvk_components = Rc::new(components);
Ok(result)
}
}
/// 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)]
pub enum Actions {
}
/// 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, glib::Downgrade)]
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, glib::Downgrade)]
pub struct App {
widgets: AppWidgets,
values: Rc<Cell<Values>>
}
impl App {
/// Create new application
pub fn new() -> Result<Self, String> {
let result = Self {
widgets: AppWidgets::try_get()?,
values: Default::default()
}.init_events();
Ok(result)
}
/// Add default events and values to the widgets
fn init_events(self) -> Self {
// Set DXVK recommended only switcher event
result.dxvk_recommended_only.connect_state_notify(move |switcher| {
for (component, version) in &components {
self.widgets.dxvk_recommended_only.connect_state_notify(clone!(@weak self as this => move |switcher| {
for (component, version) in &*this.widgets.dxvk_components {
component.set_visible(if switcher.state() {
version.recommended
} else {
true
});
}
});
}));
Ok(result)
self
}
/// Update widgets state by calling some action
pub fn update(&self, action: Actions) {
/*let values = self.values.take();
match action {
}
self.values.set(values);*/
}
pub fn title() -> String {
String::from("General")
}
pub fn get_page(&self) -> adw::PreferencesPage {
self.widgets.page.clone()
}
/// This method is being called by the `PreferencesStack::update`
pub fn update(&self, status_page: &adw::StatusPage) -> Result<(), Error> {
pub fn prepare(&self, status_page: &adw::StatusPage) -> Result<(), Error> {
let config = config::get()?;
let game = Game::new(config.game.path);
self.game_version.set_tooltip_text(None);
self.patch_version.set_tooltip_text(None);
self.widgets.game_version.set_tooltip_text(None);
self.widgets.patch_version.set_tooltip_text(None);
// Update game version
status_page.set_description(Some("Updating game info..."));
match game.try_get_diff()? {
VersionDiff::Latest(version) => {
self.game_version.set_label(&version.to_string());
self.widgets.game_version.set_label(&version.to_string());
},
VersionDiff::Diff { current, latest, .. } => {
self.game_version.set_label(&current.to_string());
self.game_version.set_css_classes(&["warning"]);
self.widgets.game_version.set_label(&current.to_string());
self.widgets.game_version.set_css_classes(&["warning"]);
self.game_version.set_tooltip_text(Some(&format!("Game update available: {} -> {}", current, latest)));
self.widgets.game_version.set_tooltip_text(Some(&format!("Game update available: {} -> {}", current, latest)));
},
VersionDiff::Outdated { current, latest } => {
self.game_version.set_label(&current.to_string());
self.game_version.set_css_classes(&["error"]);
self.widgets.game_version.set_label(&current.to_string());
self.widgets.game_version.set_css_classes(&["error"]);
self.game_version.set_tooltip_text(Some(&format!("Game is too outdated and can't be updated. Latest version: {}", latest)));
self.widgets.game_version.set_tooltip_text(Some(&format!("Game is too outdated and can't be updated. Latest version: {}", latest)));
},
VersionDiff::NotInstalled { .. } => {
self.game_version.set_label("not installed");
self.game_version.set_css_classes(&[]);
self.widgets.game_version.set_label("not installed");
self.widgets.game_version.set_css_classes(&[]);
}
}
@ -134,32 +215,32 @@ impl Page {
match Patch::try_fetch(config.patch.servers)? {
Patch::NotAvailable => {
self.patch_version.set_label("not available");
self.patch_version.set_css_classes(&["error"]);
self.widgets.patch_version.set_label("not available");
self.widgets.patch_version.set_css_classes(&["error"]);
self.patch_version.set_tooltip_text(Some("Patch is not available"));
self.widgets.patch_version.set_tooltip_text(Some("Patch is not available"));
},
Patch::Outdated { current, latest, .. } => {
self.patch_version.set_label("outdated");
self.patch_version.set_css_classes(&["warning"]);
self.widgets.patch_version.set_label("outdated");
self.widgets.patch_version.set_css_classes(&["warning"]);
self.patch_version.set_tooltip_text(Some(&format!("Patch is outdated ({} -> {})", current, latest)));
self.widgets.patch_version.set_tooltip_text(Some(&format!("Patch is outdated ({} -> {})", current, latest)));
},
Patch::Preparation { .. } => {
self.patch_version.set_label("preparation");
self.patch_version.set_css_classes(&["warning"]);
self.widgets.patch_version.set_label("preparation");
self.widgets.patch_version.set_css_classes(&["warning"]);
self.patch_version.set_tooltip_text(Some("Patch is in preparation state and will be available later"));
self.widgets.patch_version.set_tooltip_text(Some("Patch is in preparation state and will be available later"));
},
Patch::Testing { version, .. } => {
self.patch_version.set_label(&version.to_string());
self.patch_version.set_css_classes(&["warning"]);
self.widgets.patch_version.set_label(&version.to_string());
self.widgets.patch_version.set_css_classes(&["warning"]);
self.patch_version.set_tooltip_text(Some("Patch is in testing phase"));
self.widgets.patch_version.set_tooltip_text(Some("Patch is in testing phase"));
},
Patch::Available { version, .. } => {
self.patch_version.set_label(&version.to_string());
self.patch_version.set_css_classes(&["success"]);
self.widgets.patch_version.set_label(&version.to_string());
self.widgets.patch_version.set_css_classes(&["success"]);
}
}

View file

@ -1,6 +1,8 @@
use gtk4::{self as gtk, prelude::*};
use libadwaita::{self as adw, prelude::*};
use gtk4::glib;
use std::io::Error;
use crate::ui::get_object;
@ -10,11 +12,11 @@ mod general_page;
mod enhanced_page;
pub mod pages {
pub use super::general_page::Page as GeneralPage;
pub use super::enhanced_page::Page as EnhancedPage;
pub use super::general_page::App as GeneralPage;
pub use super::enhanced_page::App as EnhancedPage;
}
#[derive(Clone)]
#[derive(Clone, glib::Downgrade)]
pub struct PreferencesStack {
pub window: adw::ApplicationWindow,
pub toast_overlay: adw::ToastOverlay,
@ -51,8 +53,8 @@ impl PreferencesStack {
enhanced_page: pages::EnhancedPage::new()?
};
result.stack.add_titled(&result.general_page.page, None, &pages::GeneralPage::title());
result.stack.add_titled(&result.enhanced_page.page, None, &pages::EnhancedPage::title());
result.stack.add_titled(&result.general_page.get_page(), None, &pages::GeneralPage::title());
result.stack.add_titled(&result.enhanced_page.get_page(), None, &pages::EnhancedPage::title());
Ok(result)
}
@ -67,8 +69,8 @@ impl PreferencesStack {
self.status_page.set_description(None);
self.flap.set_visible(false);
self.general_page.update(&self.status_page)?;
self.enhanced_page.update(&self.status_page)?;
self.general_page.prepare(&self.status_page)?;
self.enhanced_page.prepare(&self.status_page)?;
self.status_page.set_visible(false);
self.flap.set_visible(true);