From 57aadd759722985b36788bbe90730bafa28d4522 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 25 Mar 2024 08:18:36 -0400 Subject: [PATCH] Create the drawing area for the review tree --- otg/core/src/types.rs | 11 ++++- otg/gtk/resources/style.css | 12 +++++ otg/gtk/src/components/mod.rs | 7 ++- otg/gtk/src/components/player_card.rs | 30 ++++++++++--- otg/gtk/src/components/review_tree.rs | 65 +++++++++++++++++++++++++++ otg/gtk/src/views/game_review.rs | 33 +++++++++++++- sgf/src/lib.rs | 6 +-- 7 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 otg/gtk/src/components/review_tree.rs diff --git a/otg/core/src/types.rs b/otg/core/src/types.rs index 89ed73c..36979ce 100644 --- a/otg/core/src/types.rs +++ b/otg/core/src/types.rs @@ -2,7 +2,7 @@ use crate::goban::{Coordinate, Goban}; use config::define_config; use config_derive::ConfigOption; use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, time::Duration}; +use std::{path::PathBuf, time::Duration, fmt}; use thiserror::Error; 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)] pub struct Size { pub width: u8, diff --git a/otg/gtk/resources/style.css b/otg/gtk/resources/style.css index 713aadc..1cebe6b 100644 --- a/otg/gtk/resources/style.css +++ b/otg/gtk/resources/style.css @@ -15,3 +15,15 @@ .preference-item > suffixes { margin: 4px; } + +.player-card { + border-radius: 5px; +} + +.player-card___black { + background-color: black; +} + +.player-card___white { + background-color: white; +} diff --git a/otg/gtk/src/components/mod.rs b/otg/gtk/src/components/mod.rs index 2cc4847..2a63815 100644 --- a/otg/gtk/src/components/mod.rs +++ b/otg/gtk/src/components/mod.rs @@ -32,8 +32,11 @@ pub use goban::Goban; mod library; pub use library::Library; -// mod player_card; -// pub use player_card::PlayerCard; +mod player_card; +pub use player_card::PlayerCard; + +mod review_tree; +pub use review_tree::ReviewTree; // mod playing_field; // pub use playing_field::PlayingField; diff --git a/otg/gtk/src/components/player_card.rs b/otg/gtk/src/components/player_card.rs index 76b55c1..709a50c 100644 --- a/otg/gtk/src/components/player_card.rs +++ b/otg/gtk/src/components/player_card.rs @@ -1,11 +1,12 @@ use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use otg_core::ui::PlayerCardElement; +use otg_core::Color; +use sgf::Player; #[derive(Default)] pub struct PlayerCardPrivate { - player_name: gtk::Label, - clock: gtk::Label, + // player_name: gtk::Label, + // clock: gtk::Label, } #[glib::object_subclass] @@ -24,16 +25,31 @@ glib::wrapper! { } 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(); + // 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.imp() .player_name .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); - s.append(&s.imp().clock); + let name: String = player.name.clone().unwrap_or(color.to_string()); + let player_name = gtk::Label::new(Some(&name)); + + s.append(&player_name); + // s.append(&s.imp().clock); s } } diff --git a/otg/gtk/src/components/review_tree.rs b/otg/gtk/src/components/review_tree.rs new file mode 100644 index 0000000..f0e3c86 --- /dev/null +++ b/otg/gtk/src/components/review_tree.rs @@ -0,0 +1,65 @@ +/* +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 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>>, +} + +#[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) @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) { + } +} diff --git a/otg/gtk/src/views/game_review.rs b/otg/gtk/src/views/game_review.rs index 857a0d8..17b38e0 100644 --- a/otg/gtk/src/views/game_review.rs +++ b/otg/gtk/src/views/game_review.rs @@ -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 // UI. So this will be a heavy lift on the UI side. +use crate::{components::{Goban, PlayerCard, ReviewTree}, CoreApi}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; +use otg_core::Color; use sgf::GameRecord; -use crate::{components::Goban, CoreApi}; pub struct GameReviewPrivate {} @@ -57,7 +58,9 @@ impl GameReview { // 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. - 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); /* @@ -67,7 +70,33 @@ impl GameReview { 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); + sidebar.append(&player_information_section); + sidebar.append(&review_tree); + s.append(&sidebar); s } diff --git a/sgf/src/lib.rs b/sgf/src/lib.rs index c205984..5f23da1 100644 --- a/sgf/src/lib.rs +++ b/sgf/src/lib.rs @@ -1,16 +1,16 @@ mod date; mod game; -pub use game::{GameNode, GameRecord, MoveNode}; +pub use game::{GameNode, GameRecord, MoveNode, Player}; mod parser; -pub use parser::Move; +pub use parser::{parse_collection, Move}; mod types; pub use types::*; pub use date::Date; -pub use parser::parse_collection; + use std::{fs::File, io::Read}; use thiserror::Error;