/* Copyright 2024, Savanni D'Gerinel This file is part of On the Grid. On the Grid 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. On the Grid 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 On the Grid. If not, see . */ use crate::{CoreApi, ResourceManager}; use adw::prelude::*; use otg_core::{ settings::{SettingsRequest, SettingsResponse}, CoreRequest, CoreResponse, }; use sgf::GameRecord; use std::{rc::Rc, sync::{Arc, RwLock}}; use crate::views::{GameReview, HomeView, SettingsView}; /* #[derive(Clone)] enum AppView { Home, } */ // An application window should generally contain // - an overlay widget // - the main content in a stack on the bottom panel of the overlay // - the settings and the about page in bins atop the overlay #[derive(Clone)] pub struct AppWindow { pub window: adw::ApplicationWindow, // content is a stack which contains the view models for the application. These are the main // elements that users want to interact with: the home page, the game library, a review, a game // itself, perhaps also chat rooms and player lists on other networks. stack contains the // widgets that need to be rendered. The two of these work together in order to ensure that // we can maintain the state of previous views. Since the two of these work together, they are // a candidate for extraction into a new widget or a new struct. stack: adw::NavigationView, // view_states: Vec, // Overlays are for transient content, such as about and settings, which can be accessed from // anywhere but shouldn't be part of the main application flow. overlay: gtk::Overlay, core: CoreApi, // Not liking this, but I have to keep track of the settings view model separately from // anything else. I'll have to look into this later. settings_view_model: Arc>>, resources: ResourceManager, } impl AppWindow { pub fn new(app: &adw::Application, core: CoreApi, resources: ResourceManager) -> Self { let window = Self::setup_window(app); let overlay = Self::setup_overlay(); let stack = adw::NavigationView::new(); // let view_states = vec![]; window.set_content(Some(&overlay)); overlay.set_child(Some(&stack)); let s = Self { window, stack, // view_states, overlay, core, settings_view_model: Default::default(), resources, }; let home = s.setup_home(); s.stack.push(&home); s } pub fn open_game_review(&self, game_record: GameRecord) { let header = adw::HeaderBar::new(); let game_review = GameReview::new(self.core.clone(), game_record, self.resources.clone()); let layout = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); layout.append(&header); layout.append(&game_review); let page = adw::NavigationPage::builder() .can_pop(true) .title("Game Review") .child(&layout) .build(); self.stack.push(&page); } pub fn open_settings(&self) { // This should return instantly and allow the UI to continue being functional. However, // some tests indicate that this may not actually be working correctly, and that a // long-running background thread may delay things. glib::spawn_future_local({ let s = self.clone(); async move { if let CoreResponse::Settings(SettingsResponse(settings)) = s .core .dispatch(CoreRequest::Settings(SettingsRequest::Get)) .await { let view_model = SettingsView::new( &s.window, settings, { let s = s.clone(); move |config| { glib::spawn_future_local({ let s = s.clone(); async move { s.core .dispatch(CoreRequest::Settings(SettingsRequest::Set( config, ))) .await } }); s.close_overlay(); } }, { let s = s.clone(); move || { s.close_overlay(); } }, ); s.overlay.add_overlay(&view_model); *s.settings_view_model.write().unwrap() = Some(view_model); } } }); } pub fn close_overlay(&self) { let mut view = self.settings_view_model.write().unwrap(); if let Some(ref mut settings) = *view { self.overlay.remove_overlay(settings); *view = None; } } fn setup_window(app: &adw::Application) -> adw::ApplicationWindow { adw::ApplicationWindow::builder() .application(app) .width_request(800) .height_request(500) .build() } fn setup_header() -> adw::HeaderBar { let header = adw::HeaderBar::builder() .title_widget(>k::Label::new(Some("On the Grid"))) .build(); let app_menu = gio::Menu::new(); let menu_item = gio::MenuItem::new(Some("Configuration"), Some("app.show_settings")); app_menu.append_item(&menu_item); let hamburger = gtk::MenuButton::builder() .icon_name("open-menu-symbolic") .build(); hamburger.set_menu_model(Some(&app_menu)); header.pack_end(&hamburger); header } fn setup_overlay() -> gtk::Overlay { gtk::Overlay::new() } fn setup_home(&self) -> adw::NavigationPage { let header = Self::setup_header(); let home = HomeView::new(self.core.clone(), { let s = self.clone(); move |game| s.open_game_review(game) }); let layout = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); layout.append(&header); layout.append(&home); adw::NavigationPage::builder() .can_pop(false) .title("Home") .child(&layout) .build() } }