Synced project structure with krypt0nn/gtk-example-app

This commit is contained in:
Observer KRypt0n_ 2022-07-03 17:28:07 +02:00
parent 9d5c0cc9ee
commit e2754a4eec
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
3 changed files with 147 additions and 67 deletions

View file

@ -19,15 +19,9 @@ fn main() {
// Init app window and show it // Init app window and show it
application.connect_activate(|app| { application.connect_activate(|app| {
let app = MainApp::new(app).unwrap(); let app = MainApp::new(app).expect("Failed to init MainApp");
let app_copy = app.clone(); app.show();
app.open_preferences.connect_clicked(move |_| {
app_copy.open_preferences_page();
});
app.window.show();
}); });
// Run app // Run app

View file

@ -1,28 +1,31 @@
use gtk4::{self as gtk, prelude::*}; use gtk4::{self as gtk, prelude::*};
use libadwaita::{self as adw, prelude::*}; use libadwaita::{self as adw, prelude::*};
use super::get_object; use std::rc::Rc;
use std::cell::Cell;
use crate::ui::*;
use super::preferences::PreferencesStack; use super::preferences::PreferencesStack;
use super::ToastError; use super::ToastError;
use crate::lib::game; use crate::lib::game;
pub enum AppState { /// This structure is used to describe widgets used in application
Launch, ///
Progress { /// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets
title: String, ///
progress: f64 /// This function does not implement events
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct App { pub struct AppWidgets {
pub window: adw::ApplicationWindow, pub window: adw::ApplicationWindow,
pub toast_overlay: adw::ToastOverlay,
pub leaflet: adw::Leaflet, pub leaflet: adw::Leaflet,
pub launch_game: adw::SplitButton, pub launch_game: adw::SplitButton,
pub launch_game_debug: gtk::Button, pub launch_game_debug: gtk::Button,
pub open_preferences: gtk::Button, pub open_preferences: gtk::Button,
pub toast_overlay: adw::ToastOverlay,
pub launch_game_group: adw::PreferencesGroup, pub launch_game_group: adw::PreferencesGroup,
pub progress_bar_group: adw::PreferencesGroup, pub progress_bar_group: adw::PreferencesGroup,
@ -31,22 +34,22 @@ pub struct App {
pub preferences_stack: PreferencesStack pub preferences_stack: PreferencesStack
} }
impl App { impl AppWidgets {
pub fn new(app: &gtk::Application) -> Result<Self, String> { fn try_get() -> Result<Self, String> {
// Create builder from UI file
let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/main.ui")); let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/main.ui"));
let window = get_object::<adw::ApplicationWindow>(&builder, "window")?; let window = get_object::<adw::ApplicationWindow>(&builder, "window")?;
let toast_overlay = get_object::<adw::ToastOverlay>(&builder, "toast_overlay")?; let toast_overlay = get_object::<adw::ToastOverlay>(&builder, "toast_overlay")?;
// Parse objects from builder
let result = Self { let result = Self {
window: window.clone(), window: window.clone(),
toast_overlay: toast_overlay.clone(),
leaflet: get_object(&builder, "leaflet")?, leaflet: get_object(&builder, "leaflet")?,
launch_game: get_object(&builder, "launch_game")?, launch_game: get_object(&builder, "launch_game")?,
launch_game_debug: get_object(&builder, "launch_game_debug")?, launch_game_debug: get_object(&builder, "launch_game_debug")?,
open_preferences: get_object(&builder, "open_preferences")?, open_preferences: get_object(&builder, "open_preferences")?,
toast_overlay: toast_overlay.clone(),
launch_game_group: get_object(&builder, "launch_game_group")?, launch_game_group: get_object(&builder, "launch_game_group")?,
progress_bar_group: get_object(&builder, "progress_bar_group")?, progress_bar_group: get_object(&builder, "progress_bar_group")?,
@ -56,68 +59,151 @@ impl App {
}; };
// Add preferences page to the leaflet // Add preferences page to the leaflet
let leaflet = result.leaflet.clone();
result.leaflet.append(&result.preferences_stack.preferences).set_name(Some("preferences_page")); result.leaflet.append(&result.preferences_stack.preferences).set_name(Some("preferences_page"));
// Go back button for preferences page Ok(result)
result.preferences_stack.preferences_go_back.connect_clicked(move |_| { }
leaflet.navigate(adw::NavigationDirection::Back); }
});
// Launch game /// This enum is used to describe an action inside of this application
let app_copy = result.clone(); ///
/// 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 {
OpenPreferencesPage,
PreferencesGoBack,
LaunchGame
}
result.launch_game.connect_clicked(move |_| { /// This enum is used to store some of this application data
// Display toast message if the game is failed to run ///
if let Err(err) = game::run(false) { /// In this example we store a counter here to know what should we increment or decrement
app_copy.toast_error("Failed to run game", err); ///
} /// This must implement `Default` trait
}); #[derive(Debug, Default)]
pub struct Values;
// Launch game in debug mode /// The main application structure
/*let app_copy = result.clone(); ///
/// `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>>
}
result.launch_game_debug.connect_clicked(move |_| { impl App {
// Display toast message if the game is failed to run /// Create new application
if let Err(err) = game::run(true) { pub fn new(app: &gtk::Application) -> Result<Self, String> {
app_copy.toast_error("Failed to run game", err); let result = Self {
} widgets: AppWidgets::try_get()?,
});*/ values: Default::default()
}.init_events();
// Bind app to the window // Bind app to the window
result.window.set_application(Some(app)); result.widgets.window.set_application(Some(app));
Ok(result) Ok(result)
} }
pub fn open_preferences_page(&self) { /// Add default events and values to the widgets
self.leaflet.set_visible_child_name("preferences_page"); fn init_events(self) -> Self {
// Open preferences page
let self_copy = self.clone();
if let Err(err) = self.preferences_stack.update() { self.widgets.open_preferences.connect_clicked(move |_| {
self.toast_error("Failed to update preferences", err); self_copy.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);
});
// Launch game
let self_copy = self.clone();
self.widgets.launch_game.connect_clicked(move |_| {
self_copy.update(Actions::LaunchGame);
});
self
} }
pub fn update_state(&self, state: AppState) { /// Update widgets state by calling some action
match state { pub fn update(&self, action: Actions) {
AppState::Launch => { let values = self.values.take();
self.launch_game_group.set_visible(true);
self.progress_bar_group.set_visible(false);
},
AppState::Progress { title, progress } => {
self.launch_game_group.set_visible(false);
self.progress_bar_group.set_visible(true);
self.progress_bar.set_text(Some(&title)); println!("[update] action: {:?}, counter: {:?}", &action, &values);
self.progress_bar.set_fraction(progress);
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);
}
/// Show application window
pub fn show(&self) {
self.widgets.window.show();
} }
} }
impl ToastError for App { impl ToastError for App {
fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay) { fn get_toast_widgets(&self) -> (adw::ApplicationWindow, adw::ToastOverlay) {
(self.window.clone(), self.toast_overlay.clone()) (self.widgets.window.clone(), self.widgets.toast_overlay.clone())
} }
} }
/*
pub enum AppState {
Launch,
Progress {
title: String,
progress: f64
}
}
pub fn update_state(&self, state: AppState) {
match state {
AppState::Launch => {
self.launch_game_group.set_visible(true);
self.progress_bar_group.set_visible(false);
},
AppState::Progress { title, progress } => {
self.launch_game_group.set_visible(false);
self.progress_bar_group.set_visible(true);
self.progress_bar.set_text(Some(&title));
self.progress_bar.set_fraction(progress);
}
}
}
*/

View file

@ -7,7 +7,7 @@ mod toast_error;
pub use main::{ pub use main::{
App as MainApp, App as MainApp,
AppState as MainAppState, // AppState as MainAppState,
}; };
pub use toast_error::ToastError; pub use toast_error::ToastError;