2024-03-23 15:21:10 +00:00
|
|
|
/*
|
|
|
|
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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Game review consists of the board, some information about the players, the game tree, and any
|
|
|
|
// commentary on the current move. This requires four major components, some of which are easier
|
|
|
|
// than others. The game board has to be kept in sync with the game tree, so there's a
|
|
|
|
// communication channel there.
|
|
|
|
//
|
|
|
|
// I'll get all of the information about the game from the core, and then render everything in the
|
|
|
|
// UI. So this will be a heavy lift on the UI side.
|
|
|
|
|
2024-05-07 11:53:15 +00:00
|
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
components::{Goban, PlayerCard, ReviewTree},
|
|
|
|
ResourceManager,
|
|
|
|
};
|
|
|
|
use glib::Propagation;
|
|
|
|
use gtk::{gdk::Key, prelude::*, EventControllerKey};
|
2024-04-09 21:03:41 +00:00
|
|
|
use otg_core::{Color, GameReviewViewModel};
|
2024-03-23 15:21:10 +00:00
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
/*
|
2024-03-26 12:53:24 +00:00
|
|
|
#[derive(Default)]
|
2024-04-09 21:03:41 +00:00
|
|
|
pub struct GameReviewPrivate {
|
|
|
|
model: Rc<RefCell<Option<GameReviewViewModel>>>,
|
|
|
|
}
|
2024-03-23 15:21:10 +00:00
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
impl ObjectSubclass for GameReviewPrivate {
|
|
|
|
const NAME: &'static str = "GameReview";
|
|
|
|
type Type = GameReview;
|
2024-03-24 01:57:56 +00:00
|
|
|
type ParentType = gtk::Box;
|
2024-03-23 15:21:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectImpl for GameReviewPrivate {}
|
|
|
|
impl WidgetImpl for GameReviewPrivate {}
|
2024-03-24 01:57:56 +00:00
|
|
|
impl BoxImpl for GameReviewPrivate {}
|
2024-03-23 15:21:10 +00:00
|
|
|
|
|
|
|
glib::wrapper! {
|
2024-03-24 01:57:56 +00:00
|
|
|
pub struct GameReview(ObjectSubclass<GameReviewPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Accessible;
|
2024-03-23 15:21:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl GameReview {
|
2024-04-05 14:47:16 +00:00
|
|
|
pub fn new(_api: CoreApi, record: GameRecord, resources: ResourceManager) -> Self {
|
2024-03-23 15:21:10 +00:00
|
|
|
let s: Self = Object::builder().build();
|
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
|
|
|
|
s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2024-05-07 11:53:15 +00:00
|
|
|
#[derive(Clone)]
|
2024-04-09 21:03:41 +00:00
|
|
|
pub struct GameReview {
|
|
|
|
widget: gtk::Box,
|
2024-05-07 11:53:15 +00:00
|
|
|
goban: Rc<RefCell<Option<Goban>>>,
|
2024-05-07 12:49:49 +00:00
|
|
|
review_tree: Rc<RefCell<Option<ReviewTree>>>,
|
2024-04-09 21:03:41 +00:00
|
|
|
|
|
|
|
resources: ResourceManager,
|
2024-05-07 11:53:15 +00:00
|
|
|
view: Rc<RefCell<GameReviewViewModel>>,
|
2024-04-09 21:03:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl GameReview {
|
|
|
|
pub fn new(view: GameReviewViewModel, resources: ResourceManager) -> Self {
|
|
|
|
let widget = gtk::Box::builder().build();
|
|
|
|
|
2024-05-07 11:53:15 +00:00
|
|
|
let view = Rc::new(RefCell::new(view));
|
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
let s = Self {
|
|
|
|
widget,
|
2024-05-07 12:49:49 +00:00
|
|
|
goban: Default::default(),
|
|
|
|
review_tree: Default::default(),
|
2024-04-09 21:03:41 +00:00
|
|
|
resources,
|
|
|
|
view,
|
|
|
|
};
|
|
|
|
|
2024-05-07 11:53:15 +00:00
|
|
|
let keypress_controller = EventControllerKey::new();
|
|
|
|
keypress_controller.connect_key_pressed({
|
|
|
|
let s = s.clone();
|
|
|
|
move |_, key, _, _| {
|
|
|
|
let mut view = s.view.borrow_mut();
|
|
|
|
match key {
|
|
|
|
Key::Down => view.next_move(),
|
|
|
|
Key::Up => view.previous_move(),
|
|
|
|
Key::Left => view.previous_variant(),
|
|
|
|
Key::Right => view.next_variant(),
|
|
|
|
_ => {
|
|
|
|
return Propagation::Proceed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match *s.goban.borrow_mut() {
|
|
|
|
Some(ref mut goban) => goban.set_board_state(view.game_view()),
|
|
|
|
None => {}
|
|
|
|
};
|
2024-05-07 12:49:49 +00:00
|
|
|
match *s.review_tree.borrow() {
|
|
|
|
Some(ref tree) => tree.queue_draw(),
|
|
|
|
None => {}
|
|
|
|
}
|
2024-05-07 11:53:15 +00:00
|
|
|
Propagation::Stop
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
s.widget.add_controller(keypress_controller);
|
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
s.render();
|
|
|
|
|
|
|
|
s
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render(&self) {
|
2024-03-24 20:16:50 +00:00
|
|
|
// It's actually really bad to be just throwing away errors. Panics make everyone unhappy.
|
|
|
|
// This is not a fatal error, so I'll replace this `unwrap` call with something that
|
|
|
|
// renders the board and notifies the user of a problem that cannot be resolved.
|
2024-05-07 11:53:15 +00:00
|
|
|
let board_repr = self.view.borrow().game_view();
|
2024-04-09 21:03:41 +00:00
|
|
|
let board = Goban::new(board_repr, self.resources.clone());
|
2024-03-24 15:03:40 +00:00
|
|
|
|
2024-03-24 01:57:56 +00:00
|
|
|
/*
|
2024-03-23 15:21:10 +00:00
|
|
|
s.attach(&board, 0, 0, 2, 2);
|
|
|
|
s.attach(>k::Label::new(Some("white player")), 0, 2, 1, 1);
|
|
|
|
s.attach(>k::Label::new(Some("black player")), 0, 2, 1, 2);
|
|
|
|
s.attach(>k::Label::new(Some("chat")), 1, 2, 2, 2);
|
2024-03-24 01:57:56 +00:00
|
|
|
*/
|
|
|
|
|
2024-03-25 12:18:36 +00:00
|
|
|
let sidebar = gtk::Box::builder()
|
|
|
|
.orientation(gtk::Orientation::Vertical)
|
|
|
|
.spacing(4)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
// The review tree needs to know the record for being able to render all of the nodes. Once
|
|
|
|
// keyboard input is being handled, the tree will have to be updated on each keystroke in
|
|
|
|
// order to show the user where they are within the game record.
|
2024-05-07 11:53:15 +00:00
|
|
|
let review_tree = ReviewTree::new(self.view.borrow().clone());
|
2024-03-25 12:18:36 +00:00
|
|
|
|
|
|
|
// I think most keyboard focus is going to end up being handled here in GameReview, as
|
|
|
|
// keystrokes need to affect both the goban and the review tree simultanesouly. Possibly
|
|
|
|
// also the player information, seeing as moving to a new position may mean that the score
|
|
|
|
// and time remaining can be updated.
|
|
|
|
|
|
|
|
let player_information_section = gtk::Box::builder()
|
|
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
|
|
.spacing(4)
|
|
|
|
.build();
|
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
player_information_section
|
2024-05-07 11:53:15 +00:00
|
|
|
.append(&PlayerCard::new(Color::Black, &self.view.borrow().black_player()));
|
2024-04-09 21:03:41 +00:00
|
|
|
player_information_section
|
2024-05-07 11:53:15 +00:00
|
|
|
.append(&PlayerCard::new(Color::White, &self.view.borrow().white_player()));
|
2024-03-25 12:18:36 +00:00
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
self.widget.append(&board);
|
2024-03-25 12:18:36 +00:00
|
|
|
sidebar.append(&player_information_section);
|
2024-04-09 21:03:41 +00:00
|
|
|
sidebar.append(&review_tree.widget());
|
|
|
|
self.widget.append(&sidebar);
|
2024-05-07 11:53:15 +00:00
|
|
|
|
|
|
|
*self.goban.borrow_mut() = Some(board);
|
2024-05-07 12:49:49 +00:00
|
|
|
*self.review_tree.borrow_mut() = Some(review_tree);
|
2024-05-07 11:53:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn redraw(&self) {
|
2024-04-09 21:03:41 +00:00
|
|
|
}
|
2024-03-23 15:21:10 +00:00
|
|
|
|
2024-04-09 21:03:41 +00:00
|
|
|
pub fn widget(&self) -> gtk::Widget {
|
|
|
|
self.widget.clone().upcast::<gtk::Widget>()
|
2024-03-23 15:21:10 +00:00
|
|
|
}
|
|
|
|
}
|