diff --git a/otg/core/src/types.rs b/otg/core/src/types.rs index cc9147b..6ba99e9 100644 --- a/otg/core/src/types.rs +++ b/otg/core/src/types.rs @@ -263,8 +263,7 @@ impl Default for DepthTree { pub struct SizeNode { /// 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. - #[allow(dead_code)] - game_node_id: nary_tree::NodeId, + pub game_node_id: nary_tree::NodeId, /// How deep into the tree is this node? depth: usize, @@ -285,6 +284,14 @@ impl DepthTree { &self.nodes[idx].content } + pub fn parent(&self, node: &Node) -> Option<&Node> { + 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 // to add the root node, but the constructor should handle that, anyway. fn add_node(&mut self, parent_idx: usize, node: T) -> usize { diff --git a/otg/gtk/src/components/review_tree.rs b/otg/gtk/src/components/review_tree.rs index dbd76d9..aa02952 100644 --- a/otg/gtk/src/components/review_tree.rs +++ b/otg/gtk/src/components/review_tree.rs @@ -21,6 +21,10 @@ use otg_core::GameReviewViewModel; const WIDTH: i32 = 200; const HEIGHT: i32 = 800; +const RADIUS: f64 = 7.5; +const HIGHLIGHT_WIDTH: f64 = 4.; +const SPACING: f64 = 30.; + #[derive(Clone)] pub struct ReviewTree { widget: gtk::ScrolledWindow, @@ -32,16 +36,19 @@ pub struct ReviewTree { impl ReviewTree { pub fn new(view: GameReviewViewModel) -> ReviewTree { let drawing_area = gtk::DrawingArea::new(); - let widget = gtk::ScrolledWindow::builder() - .child(&drawing_area) - .build(); + let widget = gtk::ScrolledWindow::builder().child(&drawing_area).build(); widget.set_width_request(WIDTH); 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({ let s = s.clone(); @@ -54,20 +61,55 @@ impl ReviewTree { } fn redraw(&self, ctx: &Context, _width: i32, _height: i32) { - self.view.map_tree(move |node, current| { - 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); + println!("redraw: {} {}", _width, _height); - if current == Some(node.node_id()) { - ctx.set_line_width(3.); - } else { + #[allow(deprecated)] + let context = WidgetExt::style_context(&self.widget); + #[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.); + 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(); } - 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(); + ctx.set_line_width(2.); + } }); }