Synced project structure with krypt0nn/gtk-example-app

This commit is contained in:
Observer KRypt0n_ 2022-07-09 17:57:42 +02:00
parent 8f65f6f3a8
commit fd6e729cd4
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
5 changed files with 188 additions and 82 deletions

View file

@ -79,7 +79,7 @@ Adw.ApplicationWindow window {
spacing: 20; spacing: 20;
Gtk.ProgressBar progress_bar { Gtk.ProgressBar progress_bar {
text: "Downloading: 37% (3.7 of 10 GB)\n"; text: "Downloading: 37% (3.7 of 10 GB)";
show-text: true; show-text: true;
width-request: 260; width-request: 260;

View file

@ -1,6 +1,8 @@
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 gtk::{CssProvider, StyleContext, gdk::Display, STYLE_PROVIDER_PRIORITY_APPLICATION};
pub mod ui; pub mod ui;
pub mod lib; pub mod lib;
@ -19,9 +21,21 @@ 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).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 // Run app

3
src/styles.css Normal file
View file

@ -0,0 +1,3 @@
progressbar > text {
margin-bottom: 4px;
}

View file

@ -71,13 +71,21 @@ impl AppWidgets {
/// This enum is used to describe an action inside of this application /// 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 /// 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 { pub enum Actions {
OpenPreferencesPage, OpenPreferencesPage,
PreferencesGoBack, PreferencesGoBack,
LaunchGame LaunchGame
} }
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);
}))
}
}
/// This enum is used to store some of this application data /// 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 /// 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)] #[derive(Clone, glib::Downgrade)]
pub struct App { pub struct App {
widgets: AppWidgets, widgets: AppWidgets,
values: Rc<Cell<Values>> values: Rc<Cell<Values>>,
actions: Rc<Cell<Option<glib::Sender<Actions>>>>
} }
impl App { impl App {
@ -108,8 +117,9 @@ impl App {
pub fn new(app: &gtk::Application) -> Result<Self, String> { pub fn new(app: &gtk::Application) -> Result<Self, String> {
let result = Self { let result = Self {
widgets: AppWidgets::try_get()?, widgets: AppWidgets::try_get()?,
values: Default::default() values: Default::default(),
}.init_events(); actions: Default::default()
}.init_events().init_actions();
// Bind app to the window // Bind app to the window
result.widgets.window.set_application(Some(app)); result.widgets.window.set_application(Some(app));
@ -120,51 +130,72 @@ impl App {
/// Add default events and values to the widgets /// Add default events and values to the widgets
fn init_events(self) -> Self { fn init_events(self) -> Self {
// Open preferences page // Open preferences page
self.widgets.open_preferences.connect_clicked(clone!(@strong self as this => move |_| { self.widgets.open_preferences.connect_clicked(Actions::OpenPreferencesPage.into_fn(&self));
this.update(Actions::OpenPreferencesPage);
}));
// Go back button for preferences page // Go back button for preferences page
self.widgets.preferences_stack.preferences_go_back.connect_clicked(clone!(@strong self as this => move |_| { self.widgets.preferences_stack.preferences_go_back.connect_clicked(Actions::PreferencesGoBack.into_fn(&self));
this.update(Actions::PreferencesGoBack);
}));
// Launch game // Launch game
self.widgets.launch_game.connect_clicked(clone!(@strong self as this => move |_| { self.widgets.launch_game.connect_clicked(Actions::LaunchGame.into_fn(&self));
this.update(Actions::LaunchGame);
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);
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 self
} }
/// Update widgets state by calling some action /// Update widgets state by calling some action
pub fn update(&self, action: Actions) { pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError<Actions>> {
let values = self.values.take(); 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 { result
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 /// Show application window

View file

@ -1,8 +1,9 @@
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 gtk4::glib; use gtk::glib;
use gtk4::glib::clone; use gtk::glib::clone;
use gtk::Align;
use std::rc::Rc; use std::rc::Rc;
use std::cell::Cell; use std::cell::Cell;
@ -30,7 +31,7 @@ pub struct AppWidgets {
pub dxvk_vanilla: adw::ExpanderRow, pub dxvk_vanilla: adw::ExpanderRow,
pub dxvk_async: adw::ExpanderRow, pub dxvk_async: adw::ExpanderRow,
pub dxvk_components: Rc<Vec<(adw::ActionRow, dxvk::Version)>> pub dxvk_components: Rc<Vec<(adw::ActionRow, gtk::Button, dxvk::Version)>>
} }
impl AppWidgets { impl AppWidgets {
@ -58,38 +59,28 @@ impl AppWidgets {
let mut components = Vec::new(); let mut components = Vec::new();
for version in list.vanilla { for (i, versions) in [list.vanilla, list.r#async].into_iter().enumerate() {
let row = adw::ActionRow::new(); for version in versions {
let button = gtk::Button::new(); let row = adw::ActionRow::new();
let button = gtk::Button::new();
row.set_title(&version.version); row.set_title(&version.version);
row.set_visible(version.recommended); row.set_visible(version.recommended);
button.set_icon_name("document-save-symbolic"); button.set_icon_name("document-save-symbolic");
button.set_valign(gtk::Align::Center); button.set_valign(gtk::Align::Center);
button.add_css_class("flat"); button.add_css_class("flat");
row.add_suffix(&button); row.add_suffix(&button);
result.dxvk_vanilla.add_row(&row); match i {
components.push((row, version)); 0 => result.dxvk_vanilla.add_row(&row),
} 1 => result.dxvk_async.add_row(&row),
_ => ()
}
for version in list.r#async { components.push((row, button, version));
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));
} }
result.dxvk_components = Rc::new(components); result.dxvk_components = Rc::new(components);
@ -101,9 +92,17 @@ impl AppWidgets {
/// This enum is used to describe an action inside of this application /// 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 /// 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 { pub enum Actions {
DownloadDXVK(Rc<usize>)
}
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);
}))
}
} }
/// This enum is used to store some of this application data /// This enum is used to store some of this application data
@ -128,7 +127,8 @@ pub struct Values;
#[derive(Clone, glib::Downgrade)] #[derive(Clone, glib::Downgrade)]
pub struct App { pub struct App {
widgets: AppWidgets, widgets: AppWidgets,
values: Rc<Cell<Values>> values: Rc<Cell<Values>>,
actions: Rc<Cell<Option<glib::Sender<Actions>>>>
} }
impl App { impl App {
@ -136,8 +136,9 @@ impl App {
pub fn new() -> Result<Self, String> { pub fn new() -> Result<Self, String> {
let result = Self { let result = Self {
widgets: AppWidgets::try_get()?, widgets: AppWidgets::try_get()?,
values: Default::default() values: Default::default(),
}.init_events(); actions: Default::default()
}.init_events().init_actions();
Ok(result) Ok(result)
} }
@ -145,8 +146,8 @@ impl App {
/// Add default events and values to the widgets /// Add default events and values to the widgets
fn init_events(self) -> Self { fn init_events(self) -> Self {
// Set DXVK recommended only switcher event // Set DXVK recommended only switcher event
self.widgets.dxvk_recommended_only.connect_state_notify(clone!(@weak self as this => move |switcher| { self.widgets.dxvk_recommended_only.connect_state_notify(clone!(@strong self as this => move |switcher| {
for (component, version) in &*this.widgets.dxvk_components { for (component, _, version) in &*this.widgets.dxvk_components {
component.set_visible(if switcher.state() { component.set_visible(if switcher.state() {
version.recommended version.recommended
} else { } 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::<Actions>(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 self
} }
/// Update widgets state by calling some action /// Update widgets state by calling some action
pub fn update(&self, action: Actions) { pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError<Actions>> {
/*let values = self.values.take(); 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 { pub fn title() -> String {
@ -247,3 +303,5 @@ impl App {
Ok(()) Ok(())
} }
} }
unsafe impl Send for App {}