Compare commits

..

3 Commits

Author SHA1 Message Date
Savanni D'Gerinel acdf9ec150 Add the window header bar 2023-12-18 19:08:32 -05:00
Savanni D'Gerinel 0ebdcd7c2a Add some commentary 2023-12-18 18:36:22 -05:00
Savanni D'Gerinel baf652173c Set up the main views for the window, as well as the redraw policy
Whenever we change views, we need to call the redraw function. That
function will handle dropping the old view and populating the new one.
2023-12-18 18:30:41 -05:00
4 changed files with 195 additions and 28 deletions

1
Cargo.lock generated
View File

@ -976,6 +976,7 @@ checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
name = "fitnesstrax" name = "fitnesstrax"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"emseries",
"ft-core", "ft-core",
"gio", "gio",
"glib", "glib",

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" ] }
emseries = { path = "../../emseries" }
ft-core = { path = "../core" } ft-core = { path = "../core" }
gio = { version = "0.18" } gio = { version = "0.18" }
glib = { version = "0.18" } glib = { version = "0.18" }

View File

@ -1,16 +1,200 @@
/*
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of FitnessTrax.
FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
*/
use adw::prelude::*;
use emseries::Series;
use ft_core::TraxRecord;
use gio::resources_lookup_data; use gio::resources_lookup_data;
use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER}; use glib::Object;
use std::env; use gtk::{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_DEV: &str = "com.luminescent-dreams.fitnesstrax.dev";
const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax"; 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/";
struct AppState {} /// The real, headless application. This is where all of the logic will reside.
#[derive(Clone)]
struct App {
database: Arc<RwLock<Option<Series<TraxRecord>>>>,
}
impl App {
pub fn new() -> Self {
Self {
database: Arc::new(RwLock::new(None)),
}
}
}
/// This is the view to show if the application has not yet been configured. It will walk the user
/// through the most critical setup steps so that we can move on to the other views in the app.
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<UnconfiguredViewPrivate>) @extends gtk::Box, gtk::Widget;
}
impl UnconfiguredView {
pub fn new() -> Self {
let s: Self = Object::builder().build();
// Replace this with the welcome screen that we set up in the fitnesstrax/unconfigured-page
// branch.
let label = gtk::Label::builder()
.label("Database is not configured.")
.build();
s.append(&label);
s
}
}
/// The historical view will show a window into the main database. It will show some version of
/// daily summaries, daily details, and will provide all functions the user may need for editing
/// records.
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<HistoricalViewPrivate>) @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
}
}
/// The application window, or the main window, is the main user interface for the app. Almost
/// everything occurs here.
struct AppWindow { struct AppWindow {
app: App,
window: adw::ApplicationWindow, window: adw::ApplicationWindow,
layout: gtk::Box,
current_view: RefCell<gtk::Widget>,
}
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() {
UnconfiguredView::new().upcast()
} else {
HistoricalView::new().upcast()
};
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);
let header = adw::HeaderBar::builder()
.title_widget(&gtk::Label::new(Some("FitnessTrax")))
.build();
let layout = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.build();
layout.append(&header);
layout.append(&current_view);
window.set_content(Some(&layout));
window.present();
let s = Self {
app,
window,
layout,
current_view: RefCell::new(current_view),
};
s
}
// Switch views.
//
// This function only replaces the old view with the one which matches the current view state.
// It is responsible for ensuring that the new view goes into the layout in the correct
// position.
fn change_view(&self, view: gtk::Widget) {
let mut current_view = self.current_view.borrow_mut();
self.layout.remove(&*current_view);
*current_view = view;
self.layout.append(&*current_view);
}
} }
fn main() { fn main() {
@ -31,10 +215,7 @@ fn main() {
println!("database path: {}", settings.string("series-path")); println!("database path: {}", settings.string("series-path"));
let app = adw::Application::builder() let app = App::new();
.application_id(app_id)
.resource_base_path(RESOURCE_BASE_PATH)
.build();
/* /*
let runtime = tokio::runtime::Builder::new_multi_thread() let runtime = tokio::runtime::Builder::new_multi_thread()
@ -43,32 +224,15 @@ fn main() {
.unwrap(); .unwrap();
*/ */
let 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();
app.connect_activate(move |app| { adw_app.connect_activate(move |adw_app| {
let stylesheet = String::from_utf8( AppWindow::new(adw_app, app.clone());
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();
}); });
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
ApplicationExtManual::run_with_args(&app, &args); ApplicationExtManual::run_with_args(&adw_app, &args);
} }

View File

@ -4,3 +4,4 @@ use emseries::DateTimeTz;
mod legacy; mod legacy;
mod types; mod types;
pub use types::TraxRecord;