diff --git a/Cargo.lock b/Cargo.lock index 3c04fd4..4b85df9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -976,6 +976,7 @@ checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" name = "fitnesstrax" version = "0.1.0" dependencies = [ + "emseries", "ft-core", "gio", "glib", diff --git a/fitnesstrax/app/Cargo.toml b/fitnesstrax/app/Cargo.toml index 005fc16..470ecea 100644 --- a/fitnesstrax/app/Cargo.toml +++ b/fitnesstrax/app/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] } +emseries = { path = "../../emseries" } ft-core = { path = "../core" } gio = { version = "0.18" } glib = { version = "0.18" } diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index 570c7c9..8875bef 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -1,16 +1,171 @@ +use adw::prelude::*; +use emseries::Series; +use ft_core::TraxRecord; use gio::resources_lookup_data; -use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER}; -use std::env; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*, STYLE_PROVIDER_PRIORITY_USER}; +use std::{ + cell::RefCell, + env, + sync::{Arc, RwLock}, +}; const APP_ID_DEV: &str = "com.luminescent-dreams.fitnesstrax.dev"; const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax"; const RESOURCE_BASE_PATH: &str = "/com/luminescent-dreams/fitnesstrax/"; -struct AppState {} +/// The real, headless application. This is where all of the logic will reside. +#[derive(Clone)] +struct App { + database: Arc>>>, +} +impl App { + pub fn new() -> Self { + Self { + database: Arc::new(RwLock::new(None)), + } + } +} + +pub struct UnconfiguredViewPrivate {} + +#[glib::object_subclass] +impl ObjectSubclass for UnconfiguredViewPrivate { + const NAME: &'static str = "UnconfiguredView"; + type Type = UnconfiguredView; + type ParentType = gtk::Box; + + fn new() -> Self { + Self {} + } +} + +impl ObjectImpl for UnconfiguredViewPrivate {} +impl WidgetImpl for UnconfiguredViewPrivate {} +impl BoxImpl for UnconfiguredViewPrivate {} + +glib::wrapper! { + pub struct UnconfiguredView(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} + +impl UnconfiguredView { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + let label = gtk::Label::builder() + .label("Database is not configured.") + .build(); + s.append(&label); + s + } +} + +pub struct HistoricalViewPrivate {} + +#[glib::object_subclass] +impl ObjectSubclass for HistoricalViewPrivate { + const NAME: &'static str = "HistoricalView"; + type Type = HistoricalView; + type ParentType = gtk::Box; + + fn new() -> Self { + Self {} + } +} + +impl ObjectImpl for HistoricalViewPrivate {} +impl WidgetImpl for HistoricalViewPrivate {} +impl BoxImpl for HistoricalViewPrivate {} + +glib::wrapper! { + pub struct HistoricalView(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} + +impl HistoricalView { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + let label = gtk::Label::builder() + .label("Database has been configured and now it is time to show data") + .build(); + s.append(&label); + s + } +} + +/// These are the possible states of the main application view. +enum MainView { + /// The application is not configured yet. This is a basic background widget to take up the + /// space when there is no data to be shown. + Unconfigured(UnconfiguredView), + + /// The Historical view shows a history of records and whatnot. + Historical(HistoricalView), +} + +/// The application window, or the main window, is the main user interface for the app. Almost +/// everything occurs here. struct AppWindow { + app: App, window: adw::ApplicationWindow, + current_view: RefCell, +} + +impl AppWindow { + /// Construct a new App Window. + /// + /// adw_app is an Adwaita application. Application windows need to have access to this, but + /// otherwise we don't use this. + /// + /// app is a core [App] object which encapsulates all of the basic logic. + fn new(adw_app: &adw::Application, app: App) -> AppWindow { + let window = adw::ApplicationWindow::builder() + .application(adw_app) + .width_request(800) + .height_request(600) + .build(); + + let current_view = if app.database.read().unwrap().is_none() { + MainView::Unconfigured(UnconfiguredView::new()) + } else { + MainView::Historical(HistoricalView::new()) + }; + + let stylesheet = String::from_utf8( + resources_lookup_data( + &format!("{}style.css", RESOURCE_BASE_PATH), + gio::ResourceLookupFlags::NONE, + ) + .expect("stylesheet must be available in the resources") + .to_vec(), + ) + .expect("to parse stylesheet"); + + let provider = gtk::CssProvider::new(); + provider.load_from_data(&stylesheet); + + let context = window.style_context(); + context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER); + + window.present(); + + let s = Self { + app, + window, + current_view: RefCell::new(current_view), + }; + s.redraw(); + s + } + + fn redraw(&self) { + match *self.current_view.borrow() { + MainView::Unconfigured(ref view) => self.window.set_content(Some(view)), + MainView::Historical(ref view) => self.window.set_content(Some(view)), + } + } } fn main() { @@ -31,10 +186,7 @@ fn main() { println!("database path: {}", settings.string("series-path")); - let app = adw::Application::builder() - .application_id(app_id) - .resource_base_path(RESOURCE_BASE_PATH) - .build(); + let app = App::new(); /* let runtime = tokio::runtime::Builder::new_multi_thread() @@ -43,32 +195,15 @@ fn main() { .unwrap(); */ - let app = adw::Application::builder() + let adw_app = adw::Application::builder() .application_id(app_id) .resource_base_path(RESOURCE_BASE_PATH) .build(); - app.connect_activate(move |app| { - let stylesheet = String::from_utf8( - resources_lookup_data( - &format!("{}style.css", RESOURCE_BASE_PATH), - gio::ResourceLookupFlags::NONE, - ) - .expect("stylesheet must be available in the resources") - .to_vec(), - ) - .expect("to parse stylesheet"); - - let provider = gtk::CssProvider::new(); - provider.load_from_data(&stylesheet); - - let window = adw::ApplicationWindow::new(app); - let context = window.style_context(); - context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER); - - window.present(); + adw_app.connect_activate(move |adw_app| { + AppWindow::new(adw_app, app.clone()); }); let args: Vec = env::args().collect(); - ApplicationExtManual::run_with_args(&app, &args); + ApplicationExtManual::run_with_args(&adw_app, &args); } diff --git a/fitnesstrax/core/src/lib.rs b/fitnesstrax/core/src/lib.rs index b591639..9d7103a 100644 --- a/fitnesstrax/core/src/lib.rs +++ b/fitnesstrax/core/src/lib.rs @@ -4,3 +4,4 @@ use emseries::DateTimeTz; mod legacy; mod types; +pub use types::TraxRecord;