Implement game tree navigation #237

Merged
savanni merged 6 commits from savanni/tree-navigation into main 2024-05-23 13:04:25 +00:00
2 changed files with 66 additions and 17 deletions
Showing only changes of commit 9540a2c5bb - Show all commits

View File

@ -263,8 +263,7 @@ impl Default for DepthTree {
pub struct SizeNode { pub struct SizeNode {
/// Use this to map back to the node in the original game tree. This way we know how to /// Use this to map back to the node in the original game tree. This way we know how to
/// correspond from a node in the review tree back to there. /// correspond from a node in the review tree back to there.
#[allow(dead_code)] pub game_node_id: nary_tree::NodeId,
game_node_id: nary_tree::NodeId,
/// How deep into the tree is this node? /// How deep into the tree is this node?
depth: usize, depth: usize,
@ -285,6 +284,14 @@ impl DepthTree {
&self.nodes[idx].content &self.nodes[idx].content
} }
pub fn parent(&self, node: &Node<T>) -> Option<&Node<T>> {
if let Some(parent_idx) = node.parent {
self.nodes.get(parent_idx)
} else {
None
}
}
// Add a node to the parent specified by parent_idx. Return the new index. This cannot be used // Add a node to the parent specified by parent_idx. Return the new index. This cannot be used
// to add the root node, but the constructor should handle that, anyway. // to add the root node, but the constructor should handle that, anyway.
fn add_node(&mut self, parent_idx: usize, node: T) -> usize { fn add_node(&mut self, parent_idx: usize, node: T) -> usize {

View File

@ -21,6 +21,10 @@ use otg_core::GameReviewViewModel;
const WIDTH: i32 = 200; const WIDTH: i32 = 200;
const HEIGHT: i32 = 800; const HEIGHT: i32 = 800;
const RADIUS: f64 = 7.5;
const HIGHLIGHT_WIDTH: f64 = 4.;
const SPACING: f64 = 30.;
#[derive(Clone)] #[derive(Clone)]
pub struct ReviewTree { pub struct ReviewTree {
widget: gtk::ScrolledWindow, widget: gtk::ScrolledWindow,
@ -32,16 +36,19 @@ pub struct ReviewTree {
impl ReviewTree { impl ReviewTree {
pub fn new(view: GameReviewViewModel) -> ReviewTree { pub fn new(view: GameReviewViewModel) -> ReviewTree {
let drawing_area = gtk::DrawingArea::new(); let drawing_area = gtk::DrawingArea::new();
let widget = gtk::ScrolledWindow::builder() let widget = gtk::ScrolledWindow::builder().child(&drawing_area).build();
.child(&drawing_area)
.build();
widget.set_width_request(WIDTH); widget.set_width_request(WIDTH);
widget.set_height_request(HEIGHT); widget.set_height_request(HEIGHT);
drawing_area.set_height_request(view.tree_max_depth() as i32 * 20 + 40); // TODO: figure out the maximum width of the tree so that we can also set a width request
drawing_area.set_height_request(view.tree_max_depth() as i32 * SPACING as i32);
let s = Self { widget, drawing_area, view }; let s = Self {
widget,
drawing_area,
view,
};
s.drawing_area.set_draw_func({ s.drawing_area.set_draw_func({
let s = s.clone(); let s = s.clone();
@ -54,20 +61,55 @@ impl ReviewTree {
} }
fn redraw(&self, ctx: &Context, _width: i32, _height: i32) { fn redraw(&self, ctx: &Context, _width: i32, _height: i32) {
self.view.map_tree(move |node, current| { println!("redraw: {} {}", _width, _height);
ctx.set_source_rgb(0.7, 0.7, 0.7);
let (row, column) = node.data().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);
if current == Some(node.node_id()) { #[allow(deprecated)]
ctx.set_line_width(3.); let context = WidgetExt::style_context(&self.widget);
} else { #[allow(deprecated)]
let foreground_color = context.lookup_color("sidebar_fg_color").unwrap();
#[allow(deprecated)]
let accent_color = context.lookup_color("accent_color").unwrap();
self.view.map_tree(move |node, current| {
let parent = node.parent();
ctx.set_source_rgb(
foreground_color.red().into(),
foreground_color.green().into(),
foreground_color.blue().into(),
);
let (row, column) = node.data().position();
let y = (row as f64) * SPACING + RADIUS * 2.;
let x = (column as f64) * SPACING + RADIUS * 2.;
ctx.arc(x, y, RADIUS, 0., 2. * std::f64::consts::PI);
let _ = ctx.fill();
if let Some(parent) = parent {
ctx.set_line_width(1.); ctx.set_line_width(1.);
let (row, column) = parent.data().position();
let py = (row as f64) * SPACING + RADIUS * 2.;
let px = (column as f64) * SPACING + RADIUS * 2.;
ctx.move_to(px, py);
ctx.line_to(x, y);
let _ = ctx.stroke();
} }
if current == Some(node.data().game_node_id) {
ctx.set_line_width(HIGHLIGHT_WIDTH);
ctx.set_source_rgb(
accent_color.red().into(),
accent_color.green().into(),
accent_color.blue().into(),
);
ctx.arc(
x,
y,
RADIUS + HIGHLIGHT_WIDTH / 2.,
0.,
2. * std::f64::consts::PI,
);
let _ = ctx.stroke(); let _ = ctx.stroke();
ctx.set_line_width(2.);
}
}); });
} }