236 lines
8.3 KiB
Rust
236 lines
8.3 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 cairo::Context;
|
|
use glib::Object;
|
|
use gtk::{prelude::*, subclass::prelude::*};
|
|
use otg_core::DepthTree;
|
|
use sgf::GameRecord;
|
|
use std::{cell::RefCell, rc::Rc};
|
|
use uuid::Uuid;
|
|
|
|
const WIDTH: i32 = 200;
|
|
const HEIGHT: i32 = 800;
|
|
|
|
#[derive(Default)]
|
|
pub struct ReviewTreePrivate {
|
|
record: Rc<RefCell<Option<GameRecord>>>,
|
|
tree: Rc<RefCell<Option<DepthTree>>>,
|
|
}
|
|
|
|
#[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();
|
|
|
|
// TODO: there can be more than one tree, especially in instructional files. Either unify
|
|
// them into a single tree in the GameTree, or draw all of them here.
|
|
*s.imp().tree.borrow_mut() = Some(DepthTree::from(&record.trees[0]));
|
|
*s.imp().record.borrow_mut() = Some(record);
|
|
|
|
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) {
|
|
let tree: &Option<DepthTree> = &self.imp().tree.borrow();
|
|
match tree {
|
|
Some(ref tree) => {
|
|
for node in tree.bfs_iter() {
|
|
// draw a circle given the coordinates of the nodes
|
|
// I don't know the indent. How do I keep track of that? Do I track the position of
|
|
// the parent? do I need to just make it more intrinsically a part of the position
|
|
// code?
|
|
ctx.set_source_rgb(0.7, 0.7, 0.7);
|
|
let (row, column) = node.position();
|
|
let y = (row as f64) * 20. + 10.;
|
|
let x = (column as f64) * 20. + 10.;
|
|
ctx.arc(x, y, 5., 0., 2. * std::f64::consts::PI);
|
|
let _ = ctx.stroke();
|
|
}
|
|
}
|
|
None => {
|
|
// if there is no tree present, then there's nothing to draw!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://llimllib.github.io/pymag-trees/
|
|
// I want to take advantage of the Wetherell Shannon algorithm, but I want some variations. In
|
|
// their diagram, they got a tree that looks like this.
|
|
//
|
|
// O
|
|
// |\
|
|
// O O
|
|
// |\ \ \
|
|
// O O O O
|
|
// |\ |\
|
|
// O O O O
|
|
//
|
|
// In the same circumstance, what I want is this:
|
|
//
|
|
// O--
|
|
// | \
|
|
// O O
|
|
// |\ |\
|
|
// O O O O
|
|
// |\
|
|
// O O
|
|
//
|
|
// In order to keep things from being overly smooshed, I want to ensure that if a branch overlaps
|
|
// with another branch, there is some extra drawing space. This might actually be similar to adding
|
|
// the principal that "A parent should be centered over its children".
|
|
//
|
|
// So, given a tree, I need to know how many children exist at each level. Then I build parents
|
|
// atop the children. At level 3, I have four children, and that happens to be the maximum width of
|
|
// the graph.
|
|
//
|
|
// A bottom-up traversal:
|
|
// - Figure out the number of nodes at each depth
|
|
|
|
/*
|
|
struct Tree {
|
|
width: Vec<usize>, // the total width of the tree at each depth
|
|
}
|
|
*/
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use sgf::{Color, GameNode, Move, MoveNode};
|
|
|
|
#[test]
|
|
fn it_calculates_width_for_single_node() {
|
|
let node = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
|
|
assert_eq!(node_width(&node), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn it_calculates_width_for_node_with_children() {
|
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_b = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
let node_c = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
let node_d = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
|
|
node_a.children.push(node_b);
|
|
node_a.children.push(node_c);
|
|
node_a.children.push(node_d);
|
|
|
|
assert_eq!(node_width(&GameNode::MoveNode(node_a)), 3);
|
|
}
|
|
|
|
// A
|
|
// B E
|
|
// C D
|
|
#[test]
|
|
fn it_calculates_width_with_one_deep_child() {
|
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
|
|
node_b.children.push(GameNode::MoveNode(node_c));
|
|
node_b.children.push(GameNode::MoveNode(node_d));
|
|
assert_eq!(node_width(&GameNode::MoveNode(node_b.clone())), 2);
|
|
|
|
node_a.children.push(GameNode::MoveNode(node_b));
|
|
node_a.children.push(GameNode::MoveNode(node_e));
|
|
assert_eq!(node_width(&GameNode::MoveNode(node_a)), 3);
|
|
}
|
|
|
|
// A
|
|
// B G H
|
|
// C I
|
|
// D E F
|
|
#[test]
|
|
fn it_calculates_a_complex_tree() {
|
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_f = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_g = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let mut node_h = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_i = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
|
|
node_c.children.push(GameNode::MoveNode(node_d));
|
|
node_c.children.push(GameNode::MoveNode(node_e));
|
|
node_c.children.push(GameNode::MoveNode(node_f));
|
|
assert_eq!(node_width(&GameNode::MoveNode(node_c.clone())), 3);
|
|
|
|
node_b.children.push(GameNode::MoveNode(node_c));
|
|
assert_eq!(node_width(&GameNode::MoveNode(node_b.clone())), 3);
|
|
|
|
node_h.children.push(GameNode::MoveNode(node_i));
|
|
|
|
node_a.children.push(GameNode::MoveNode(node_b));
|
|
node_a.children.push(GameNode::MoveNode(node_g));
|
|
node_a.children.push(GameNode::MoveNode(node_h));
|
|
// This should be 4 if I were collapsing levels correctly, but it is 5 until I return to
|
|
// figure that step out.
|
|
assert_eq!(node_width(&GameNode::MoveNode(node_a.clone())), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn a_nodes_children_get_separate_columns() {
|
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
|
let node_b = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
let node_c = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
let node_d = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
|
|
|
node_a.children.push(node_b.clone());
|
|
node_a.children.push(node_c.clone());
|
|
node_a.children.push(node_d.clone());
|
|
|
|
assert_eq!(
|
|
node_children_columns(&GameNode::MoveNode(node_a)),
|
|
vec![0, 1, 2]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn text_renderer() {
|
|
assert!(false);
|
|
}
|
|
}
|