use crate::graph::{Edge, Position, Size};
use hecs::World;
use macroquad::color::{Color, BLACK, DARKGRAY, RED, WHITE};
use macroquad::input::{is_mouse_button_down, mouse_delta_position};
use macroquad::math::Vec2;
use macroquad::prelude::{clear_background, draw_circle, draw_line};
use macroquad::text::{draw_text, get_text_center};
use std::collections::HashMap;
const NODE_SIZE: f32 = 15.0;
pub struct ScrollableView {
pub offset: Vec2,
}
impl ScrollableView {
pub fn new() -> Self {
Self {
offset: Vec2::new(0.0, 0.0),
}
}
pub fn world_pos_to_screen_pos(&self, world_pos: &Position) -> Position {
Position {
x: world_pos.x - self.offset.x,
y: world_pos.y - self.offset.y,
}
}
pub fn update(&mut self) {
if is_mouse_button_down(macroquad::input::MouseButton::Left) {
let mouse_delta = mouse_delta_position();
self.offset.x += mouse_delta.x * 1000.0;
self.offset.y += mouse_delta.y * 1000.0;
}
}
}
pub(crate) fn render(
world: &mut World,
node_data: &HashMap<usize, Position>,
edge_data: &HashMap<usize, Edge>,
) {
clear_background(DARKGRAY);
render_edges(world, node_data, edge_data);
render_nodes(world);
}
pub(crate) fn view_port_update(world: &mut World) {
let mut scrollable_view_query = world.query::<&mut ScrollableView>();
match scrollable_view_query.iter().last() {
None => {}
Some((_e, scrollable_view)) => {
scrollable_view.update();
}
}
}
fn render_edges(
world: &mut World,
node_data: &HashMap<usize, Position>,
edge_data: &HashMap<usize, Edge>,
) {
for (_, edge) in edge_data {
let edge_source_node_id = edge.source_node_id;
let edge_destination_node_id = edge.destination_node_id;
if node_data.contains_key(&edge_source_node_id)
&& node_data.contains_key(&edge_destination_node_id)
{
match (
node_data.get(&edge_source_node_id),
node_data.get(&edge_destination_node_id),
) {
(Some(source_node_position), Some(destination_node_position)) => {
let source_node_position =
world_to_screen_position(world, source_node_position);
let destination_node_position =
world_to_screen_position(world, destination_node_position);
draw_arrow_line(
Vec2 {
x: source_node_position.x,
y: source_node_position.y,
},
Vec2 {
x: destination_node_position.x,
y: destination_node_position.y,
},
RED,
3.0,
edge.is_directed,
)
}
_ => {}
}
};
}
}
fn render_nodes(world: &mut World) {
for (_id, (position, size, label)) in &mut world.query::<(&Position, &Size, &String)>() {
let label = &label;
let center_of_text = get_text_center(label, None, 20, 1.0, 0.0);
let mut scrollable_view_query = world.query::<&ScrollableView>();
let (_, scrollable_view) = scrollable_view_query
.iter()
.next()
.expect("No scrollable view found");
let position = scrollable_view.world_pos_to_screen_pos(position);
draw_circle(position.x, position.y, size.radius, BLACK);
draw_text(
label,
position.x - center_of_text.x,
position.y - center_of_text.y / 2.0,
20.0,
WHITE,
);
}
}
fn world_to_screen_position(world: &mut World, source_node_position: &Position) -> Position {
let mut scrollable_view_query = world.query::<&ScrollableView>();
let (_, scrollable_view) = scrollable_view_query
.iter()
.next()
.expect("No scrollable view found");
let source_node_position = scrollable_view.world_pos_to_screen_pos(source_node_position);
source_node_position
}
fn render_arrow(
color: Color,
thickness: f32,
arrow_start: Vec2,
arrow_end: Vec2,
arrowhead_left: Vec2,
arrowhead_right: Vec2,
is_directed: bool,
) {
draw_line(
arrow_start.x,
arrow_start.y,
arrow_end.x,
arrow_end.y,
thickness,
color,
);
if (is_directed) {
draw_line(
arrow_end.x,
arrow_end.y,
arrowhead_left.x,
arrowhead_left.y,
thickness,
color,
);
draw_line(
arrow_end.x,
arrow_end.y,
arrowhead_right.x,
arrowhead_right.y,
thickness,
color,
);
}
}
fn calculate_arrow_positions(start_pos: Vec2, end_pos: Vec2) -> (Vec2, Vec2, Vec2, Vec2) {
let direction = (end_pos - start_pos).normalize();
let arrow_start_pos = start_pos + direction * NODE_SIZE;
let arrow_end_pos = end_pos - direction * NODE_SIZE;
let arrowhead_size = 15.0;
let perpendicular = Vec2::new(direction.y, -direction.x);
let arrowhead_left_end_pos =
arrow_end_pos - direction * arrowhead_size + perpendicular * (arrowhead_size * 0.5);
let arrowhead_right_end_pos =
arrow_end_pos - direction * arrowhead_size - perpendicular * (arrowhead_size * 0.5);
(
arrow_start_pos,
arrow_end_pos,
arrowhead_left_end_pos,
arrowhead_right_end_pos,
)
}
fn draw_arrow_line(
start_pos: Vec2,
end_pos: Vec2,
color: Color,
thickness: f32,
is_directed: bool,
) {
let (arrow_start_pos, arrow_end_pos, arrowhead_left_end_pos, arrowhead_right_end_pos) =
calculate_arrow_positions(start_pos, end_pos);
render_arrow(
color,
thickness,
arrow_start_pos,
arrow_end_pos,
arrowhead_left_end_pos,
arrowhead_right_end_pos,
is_directed,
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_calculate_arrow_positions() {
let (start_pos, end_pos) = (Vec2 { x: 0.0, y: 0.0 }, Vec2 { x: 100.0, y: 0.0 });
let (arrow_start_pos, arrow_end_pos, arrowhead_left_end_pos, arrowhead_right_end_pos) =
calculate_arrow_positions(start_pos, end_pos);
assert_eq!(arrow_start_pos, Vec2 { x: 15.0, y: 0.0 });
assert_eq!(arrow_end_pos, Vec2 { x: 85.0, y: 0.0 });
assert_eq!(arrowhead_left_end_pos, Vec2 { x: 70.0, y: -7.5 });
assert_eq!(arrowhead_right_end_pos, Vec2 { x: 70.0, y: 7.5 });
}
}