Create the asynchronous communication channel between the UI and the core app loop. #126

Merged
savanni merged 4 commits from fitnesstrax/async-handlers into main 2023-12-22 20:16:56 +00:00
3 changed files with 612 additions and 446 deletions
Showing only changes of commit 3ca8bf64cc - Show all commits

989
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] } adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] }
async-channel = "2.1.1"
emseries = { path = "../../emseries" } emseries = { path = "../../emseries" }
ft-core = { path = "../core" } ft-core = { path = "../core" }
gio = { version = "0.18" } gio = { version = "0.18" }

View File

@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
mod ui; mod ui;
use adw::prelude::*; use adw::prelude::*;
use async_channel::{Receiver, Sender};
use emseries::{EmseriesReadError, Series}; use emseries::{EmseriesReadError, Series};
use ft_core::TraxRecord; use ft_core::TraxRecord;
use gio::resources_lookup_data; use gio::resources_lookup_data;
@ -36,11 +37,16 @@ const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax";
const RESOURCE_BASE_PATH: &str = "/com/luminescent-dreams/fitnesstrax/"; const RESOURCE_BASE_PATH: &str = "/com/luminescent-dreams/fitnesstrax/";
/// A set of events that can occur at the global application level. These events should represent #[derive(Debug)]
/// significant state changes that should go through a central dispatcher. enum AppInvocation {
enum Events { OpenDatabase(PathBuf),
DatabaseChanged(Series<TraxRecord>),
} }
#[derive(Debug)]
enum AppResponse {
DatabaseChanged,
}
// Note that I have not yet figured out the communication channel or how the central dispatcher // Note that I have not yet figured out the communication channel or how the central dispatcher
// should work. There's a dance between the App and the AppWindow that I haven't figured out yet. // should work. There's a dance between the App and the AppWindow that I haven't figured out yet.
@ -222,7 +228,7 @@ impl HistoricalView {
/// everything occurs here. /// everything occurs here.
#[derive(Clone)] #[derive(Clone)]
struct AppWindow { struct AppWindow {
app: App, app_tx: Sender<AppInvocation>,
window: adw::ApplicationWindow, window: adw::ApplicationWindow,
layout: gtk::Box, layout: gtk::Box,
current_view: Rc<RefCell<gtk::Widget>>, current_view: Rc<RefCell<gtk::Widget>>,
@ -235,7 +241,7 @@ impl AppWindow {
/// otherwise we don't use this. /// otherwise we don't use this.
/// ///
/// app is a core [App] object which encapsulates all of the basic logic. /// app is a core [App] object which encapsulates all of the basic logic.
fn new(adw_app: &adw::Application, app: App) -> AppWindow { fn new(adw_app: &adw::Application, app_tx: Sender<AppInvocation>) -> AppWindow {
let window = adw::ApplicationWindow::builder() let window = adw::ApplicationWindow::builder()
.application(adw_app) .application(adw_app)
.width_request(800) .width_request(800)
@ -275,15 +281,14 @@ impl AppWindow {
window.present(); window.present();
let s = Self { let s = Self {
app: app.clone(), app_tx,
window, window,
layout, layout,
current_view: Rc::new(RefCell::new(initial_view.upcast())), current_view: Rc::new(RefCell::new(initial_view.upcast())),
}; };
let initial_view = if app.database.read().unwrap().is_none() { let initial_view = if true {
WelcomeView::new({ WelcomeView::new({
let app = app.clone();
let s = s.clone(); let s = s.clone();
Box::new(move |path: PathBuf| { Box::new(move |path: PathBuf| {
// The user has selected a path. Perhaps the path is new, perhaps it already // The user has selected a path. Perhaps the path is new, perhaps it already
@ -295,8 +300,20 @@ impl AppWindow {
// //
// If the file does not exist, create a new one. Again, show the user an error if // If the file does not exist, create a new one. Again, show the user an error if
// some kind of error occurs. // some kind of error occurs.
app.open_db(&path); // app.open_db(&path);
s.change_view(HistoricalView::new().upcast()); // s.change_view(HistoricalView::new().upcast());
// let s = s.clone();
/*
glib::spawn_future_local(async move {
s.app_tx
.send(AppInvocation::OpenDatabase(path))
.await
.unwrap();
});
*/
s.app_tx
.send_blocking(AppInvocation::OpenDatabase(path))
.unwrap();
}) })
}) })
.upcast() .upcast()
@ -342,20 +359,33 @@ fn main() {
let app = App::new(settings); let app = App::new(settings);
/*
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
*/
let adw_app = adw::Application::builder() let adw_app = adw::Application::builder()
.application_id(app_id) .application_id(app_id)
.resource_base_path(RESOURCE_BASE_PATH) .resource_base_path(RESOURCE_BASE_PATH)
.build(); .build();
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
adw_app.connect_activate(move |adw_app| { adw_app.connect_activate(move |adw_app| {
AppWindow::new(adw_app, app.clone()); let (gtk_tx, gtk_rx) = async_channel::unbounded::<AppResponse>();
let (app_tx, app_rx) = async_channel::unbounded::<AppInvocation>();
AppWindow::new(adw_app, app_tx.clone());
glib::spawn_future_local(async move {
while let Ok(response) = gtk_rx.recv().await {
println!("response received: {:?}", response);
}
});
runtime.spawn(async move {
while let Ok(invocation) = app_rx.recv().await {
println!("Received an invocation: {:?}", invocation);
}
});
}); });
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();