Implement game tree navigation #237
|
@ -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 {
|
||||||
|
|
|
@ -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.);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue