234 lines
7.9 KiB
Rust
234 lines
7.9 KiB
Rust
/*
|
|
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
|
|
|
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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use crate::{CoreApi, ResourceManager};
|
|
use adw::prelude::*;
|
|
|
|
use glib::Propagation;
|
|
use gtk::{gdk::Key, EventControllerKey};
|
|
use otg_core::{
|
|
settings::{SettingsRequest, SettingsResponse},
|
|
CoreRequest, CoreResponse, GameReviewViewModel,
|
|
};
|
|
use sgf::GameRecord;
|
|
use std::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<AppView>,
|
|
|
|
// 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<RwLock<Option<SettingsView>>>,
|
|
|
|
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(
|
|
GameReviewViewModel::new(game_record),
|
|
self.resources.clone(),
|
|
);
|
|
|
|
let layout = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Vertical)
|
|
.build();
|
|
layout.append(&header);
|
|
layout.append(&game_review.widget());
|
|
|
|
// This controller ensures that navigational keypresses get sent to the game review so that
|
|
// they're not changing the cursor focus in the app.
|
|
let keypress_controller = EventControllerKey::new();
|
|
keypress_controller.connect_key_pressed({
|
|
move |s, key, _, _| {
|
|
println!("layout keypress: {}", key);
|
|
if s.forward(&game_review.widget()) {
|
|
Propagation::Stop
|
|
} else {
|
|
Propagation::Proceed
|
|
}
|
|
}
|
|
});
|
|
|
|
layout.add_controller(keypress_controller);
|
|
|
|
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()
|
|
}
|
|
}
|