mod primitives;
use web_sys::{CanvasRenderingContext2d, Element, HtmlCanvasElement};
use crate::frontend::{
tiling::{Tile, TileType},
util::{
web_idl::{get_element_by_id, main_canvas_with_context, window},
PixelPoint, PixelRect,
},
view::View,
};
pub fn setup() {
CanvasRenderer::new().setup();
}
pub fn render(view: &mut View) {
CanvasRenderer::new().render(view);
}
struct CanvasRenderer {
main_area: Element,
main_canvas: HtmlCanvasElement,
main_context: CanvasRenderingContext2d,
}
impl CanvasRenderer {
fn render(&self, view: &mut View) {
let canvas_width = self.main_canvas.width();
let canvas_height = self.main_canvas.height();
view.set_view_size(canvas_width, canvas_height);
self.main_context
.clear_rect(0., 0., canvas_width as f64, canvas_height as f64);
self.main_context
.set_font(&format!("{}px sans-serif", view.scheme().font_size));
self.main_context.set_text_align("center");
self.main_context.set_text_baseline("middle");
self.main_context.save();
let view_offset = view.view_offset();
self.main_context
.translate(-view_offset.x as f64, -view_offset.y as f64)
.unwrap();
self.render_background(view);
let labellings =
if let Some((property_index, subproperty_index)) = view.selected_subproperty_index() {
view.snapshot()
.subproperty_labellings(property_index, subproperty_index)
} else {
None
};
let lesser_visible_point = view_offset;
let greater_visible_point = lesser_visible_point
+ PixelPoint {
x: self.main_canvas.width() as i64,
y: self.main_canvas.height() as i64,
};
let viewport = PixelRect::new(lesser_visible_point, greater_visible_point);
let tiles_in_view: Vec<_> = view.tiles_in_rect(viewport).collect();
console_log!("Tiles in view: {:?}", tiles_in_view);
for tile in tiles_in_view {
let tile_type = view
.tile_type(tile)
.expect("Tile in view should be populated");
match tile_type {
TileType::Node(node_id) => {
let node = view
.snapshot()
.state_space
.nodes
.get(node_id)
.expect("Tiling should have a node");
let node_tile_info = view.node_tile_info(*node_id).unwrap();
let labelling = if let (Some(labellings), Ok(state_id)) =
(labellings, (*node_id).try_into())
{
labellings.get(&state_id).copied()
} else {
None
};
let is_selected = if let Some(selected_node_id) = view.selected_node_id() {
selected_node_id == *node_id
} else {
false
};
self.render_node(view, tile, *node_id, labelling, is_selected);
if !node.incoming.is_empty() {
self.render_arrow_end(view, tile);
}
if node_tile_info.self_loop {
self.render_self_loop(view, tile);
}
if !node.outgoing.is_empty() {
self.render_arrow_start(view, tile, node_tile_info.successor_x_offset);
}
self.render_arrow_split(view, tile, 0, node_tile_info.predecessor_split_len);
self.render_arrow_split(
view,
tile,
node_tile_info.successor_x_offset as i64,
node_tile_info.successor_split_len,
);
}
TileType::IncomingReference(head_node_ids, tail_node_id) => {
self.render_reference(
view,
tile,
head_node_ids.iter().copied(),
*tail_node_id,
false,
);
self.render_arrow_start(view, tile, 1);
}
TileType::OutgoingReference(head_node_id, tail_node_id) => {
self.render_arrow_end(view, tile);
self.render_reference(
view,
tile,
std::iter::once(*head_node_id),
*tail_node_id,
true,
);
}
}
}
self.main_context.restore();
}
fn visible_tile_range(
&self,
view: &View,
) -> (std::ops::RangeInclusive<i64>, std::ops::RangeInclusive<i64>) {
let lesser_visible_point = view.view_offset();
let greater_visible_point = lesser_visible_point
+ PixelPoint {
x: self.main_canvas.width() as i64,
y: self.main_canvas.height() as i64,
};
let lesser_tile = view.tile_at_global_point(lesser_visible_point);
let greater_tile = view.tile_at_global_point(greater_visible_point);
let x_range = lesser_tile.x..=greater_tile.x;
let y_range = lesser_tile.y..=greater_tile.y;
(x_range, y_range)
}
fn render_background(&self, view: &View) {
self.main_context.save();
self.main_context.set_fill_style_str("#FAFAFA");
self.main_context.set_stroke_style_str("#DDD");
let (range_x, range_y) = self.visible_tile_range(view);
for tile_x in range_x {
for tile_y in range_y.clone() {
let tile_rect = view.tile_rect(Tile {
x: tile_x,
y: tile_y,
});
self.main_context.begin_path();
self.main_context.rect(
tile_rect.left_x() as f64,
tile_rect.top_y() as f64,
tile_rect.width() as f64,
tile_rect.height() as f64,
);
self.main_context.fill();
self.main_context.stroke();
}
}
self.main_context.restore();
}
fn new() -> CanvasRenderer {
let main_area = get_element_by_id("main_area");
let (main_canvas, main_context) = main_canvas_with_context();
CanvasRenderer {
main_area,
main_canvas,
main_context,
}
}
fn setup(&self) {
let pixel_ratio = window().device_pixel_ratio();
let main_area_rect = self.main_area.get_bounding_client_rect();
let width = main_area_rect.width();
let height = main_area_rect.height();
let pr_width = (width * pixel_ratio) as u32;
let pr_height = (height * pixel_ratio) as u32;
self.main_canvas.set_width(pr_width);
self.main_canvas.set_height(pr_height);
let width = pr_width as f64 / pixel_ratio;
let height = pr_height as f64 / pixel_ratio;
let canvas_style = self.main_canvas.style();
canvas_style
.set_property("width", &format!("{}px", width))
.unwrap();
canvas_style
.set_property("height", &format!("{}px", height))
.unwrap();
self.main_context.reset_transform().unwrap();
self.main_context.translate(0.5, 0.5).unwrap();
}
}