Set up the game review page along with #229
|
@ -2,7 +2,7 @@ use crate::goban::{Coordinate, Goban};
|
||||||
use config::define_config;
|
use config::define_config;
|
||||||
use config_derive::ConfigOption;
|
use config_derive::ConfigOption;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{path::PathBuf, time::Duration};
|
use std::{path::PathBuf, time::Duration, fmt};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
define_config! {
|
define_config! {
|
||||||
|
@ -61,6 +61,15 @@ impl From<&sgf::Color> for Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Color {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
|
match self {
|
||||||
|
Color::Black => write!(formatter, "Black"),
|
||||||
|
Color::White => write!(formatter, "White"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Size {
|
pub struct Size {
|
||||||
pub width: u8,
|
pub width: u8,
|
||||||
|
|
|
@ -15,3 +15,15 @@
|
||||||
.preference-item > suffixes {
|
.preference-item > suffixes {
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-card {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-card___black {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-card___white {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
|
@ -32,8 +32,11 @@ pub use goban::Goban;
|
||||||
mod library;
|
mod library;
|
||||||
pub use library::Library;
|
pub use library::Library;
|
||||||
|
|
||||||
// mod player_card;
|
mod player_card;
|
||||||
// pub use player_card::PlayerCard;
|
pub use player_card::PlayerCard;
|
||||||
|
|
||||||
|
mod review_tree;
|
||||||
|
pub use review_tree::ReviewTree;
|
||||||
|
|
||||||
// mod playing_field;
|
// mod playing_field;
|
||||||
// pub use playing_field::PlayingField;
|
// pub use playing_field::PlayingField;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use otg_core::ui::PlayerCardElement;
|
use otg_core::Color;
|
||||||
|
use sgf::Player;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PlayerCardPrivate {
|
pub struct PlayerCardPrivate {
|
||||||
player_name: gtk::Label,
|
// player_name: gtk::Label,
|
||||||
clock: gtk::Label,
|
// clock: gtk::Label,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -24,16 +25,31 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerCard {
|
impl PlayerCard {
|
||||||
pub fn new(element: PlayerCardElement) -> PlayerCard {
|
pub fn new(
|
||||||
|
color: Color,
|
||||||
|
player: &Player, /* add on current number of captures and time remaining on the clock*/
|
||||||
|
) -> PlayerCard {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
|
// I forget really the BEM system, but I know that what I want here is the block name and
|
||||||
|
// the modifier. I don't need an element here. So I'm going to mock it up and look up the
|
||||||
|
// real standard in my notes later.
|
||||||
|
match color {
|
||||||
|
Color::Black => s.set_css_classes(&["player-card", "player-card___black"]),
|
||||||
|
Color::White => s.set_css_classes(&["player-card", "player-card___white"]),
|
||||||
|
}
|
||||||
s.set_orientation(gtk::Orientation::Vertical);
|
s.set_orientation(gtk::Orientation::Vertical);
|
||||||
|
/*
|
||||||
s.imp()
|
s.imp()
|
||||||
.player_name
|
.player_name
|
||||||
.set_text(&format!("{} ({})", element.name, element.rank));
|
.set_text(&format!("{} ({})", element.name, element.rank));
|
||||||
s.imp().clock.set_text(&element.clock);
|
*/
|
||||||
|
// s.imp().clock.set_text(&element.clock);
|
||||||
|
|
||||||
s.append(&s.imp().player_name);
|
let name: String = player.name.clone().unwrap_or(color.to_string());
|
||||||
s.append(&s.imp().clock);
|
let player_name = gtk::Label::new(Some(&name));
|
||||||
|
|
||||||
|
s.append(&player_name);
|
||||||
|
// s.append(&s.imp().clock);
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
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 cairo::Context;
|
||||||
|
use glib::Object;
|
||||||
|
use gtk::{ prelude::*, subclass::prelude::*, };
|
||||||
|
use sgf::GameRecord;
|
||||||
|
use std::{rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
|
const WIDTH: i32 = 200;
|
||||||
|
const HEIGHT: i32 = 800;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ReviewTreePrivate {
|
||||||
|
record: Rc<RefCell<Option<GameRecord>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ReviewTreePrivate {
|
||||||
|
const NAME: &'static str = "ReviewTree";
|
||||||
|
type Type = ReviewTree;
|
||||||
|
type ParentType = gtk::DrawingArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ReviewTreePrivate {}
|
||||||
|
impl WidgetImpl for ReviewTreePrivate {}
|
||||||
|
impl DrawingAreaImpl for ReviewTreePrivate {}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct ReviewTree(ObjectSubclass<ReviewTreePrivate>) @extends gtk::Widget, gtk::DrawingArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReviewTree {
|
||||||
|
pub fn new(record: GameRecord) -> Self {
|
||||||
|
let s: Self = Object::new();
|
||||||
|
|
||||||
|
s.set_width_request(WIDTH);
|
||||||
|
s.set_height_request(HEIGHT);
|
||||||
|
|
||||||
|
s.set_draw_func({
|
||||||
|
let s = s.clone();
|
||||||
|
move |_, ctx, width, height| {
|
||||||
|
s.redraw(ctx, width, height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redraw(&self, ctx: &Context, width: i32, height: i32) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,10 +22,11 @@ You should have received a copy of the GNU General Public License along with On
|
||||||
// I'll get all of the information about the game from the core, and then render everything in the
|
// 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.
|
// UI. So this will be a heavy lift on the UI side.
|
||||||
|
|
||||||
|
use crate::{components::{Goban, PlayerCard, ReviewTree}, CoreApi};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
use otg_core::Color;
|
||||||
use sgf::GameRecord;
|
use sgf::GameRecord;
|
||||||
use crate::{components::Goban, CoreApi};
|
|
||||||
|
|
||||||
pub struct GameReviewPrivate {}
|
pub struct GameReviewPrivate {}
|
||||||
|
|
||||||
|
@ -57,7 +58,9 @@ impl GameReview {
|
||||||
// It's actually really bad to be just throwing away errors. Panics make everyone unhappy.
|
// 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
|
// 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.
|
// renders the board and notifies the user of a problem that cannot be resolved.
|
||||||
let board_repr = otg_core::Goban::default().apply_moves(record.mainline()).unwrap();
|
let board_repr = otg_core::Goban::default()
|
||||||
|
.apply_moves(record.mainline())
|
||||||
|
.unwrap();
|
||||||
let board = Goban::new(board_repr);
|
let board = Goban::new(board_repr);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -67,7 +70,33 @@ impl GameReview {
|
||||||
s.attach(>k::Label::new(Some("chat")), 1, 2, 2, 2);
|
s.attach(>k::Label::new(Some("chat")), 1, 2, 2, 2);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
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.
|
||||||
|
let review_tree = ReviewTree::new(record.clone());
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
player_information_section.append(&PlayerCard::new(Color::Black, &record.black_player));
|
||||||
|
player_information_section.append(&PlayerCard::new(Color::White, &record.white_player));
|
||||||
|
|
||||||
s.append(&board);
|
s.append(&board);
|
||||||
|
sidebar.append(&player_information_section);
|
||||||
|
sidebar.append(&review_tree);
|
||||||
|
s.append(&sidebar);
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
mod date;
|
mod date;
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
pub use game::{GameNode, GameRecord, MoveNode};
|
pub use game::{GameNode, GameRecord, MoveNode, Player};
|
||||||
|
|
||||||
mod parser;
|
mod parser;
|
||||||
pub use parser::Move;
|
pub use parser::{parse_collection, Move};
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
pub use date::Date;
|
pub use date::Date;
|
||||||
pub use parser::parse_collection;
|
|
||||||
use std::{fs::File, io::Read};
|
use std::{fs::File, io::Read};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue