use super::Edge;
use super::EdgeBuilder;
use super::EditableEdge;
use super::EdgeRouting;
use super::EditableNode;
use super::GraphNode;
use super::Node;
use super::NodeBuilder;
use super::RenderingOptions;
use crate::prelude::*;
use crate::utils::GlyphParser;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum ControlState {
Disabled,
Normal,
Focused,
}
enum Direction {
Left,
Right,
Top,
Bottom,
}
impl Direction {
fn compare_point(&self, rect: &Rect) -> Point {
match self {
Direction::Left => Point::new(rect.left(), rect.center_y()),
Direction::Right => Point::new(rect.right(), rect.center_y()),
Direction::Top => Point::new(rect.center_x(), rect.top()),
Direction::Bottom => Point::new(rect.center_x(), rect.bottom()),
}
}
}
impl ControlState {
fn new(control: &ControlBase) -> Self {
if !control.is_enabled() {
ControlState::Disabled
} else if control.has_focus() {
ControlState::Focused
} else {
ControlState::Normal
}
}
#[inline(always)]
fn node_attr(&self, theme: &Theme) -> CharAttribute {
match self {
ControlState::Disabled => theme.text.inactive,
ControlState::Normal | ControlState::Focused => theme.button.regular.text.normal,
}
}
#[inline(always)]
fn edge_attr(&self, theme: &Theme) -> CharAttribute {
match self {
ControlState::Disabled => theme.lines.inactive,
ControlState::Normal => theme.lines.normal,
ControlState::Focused => theme.lines.focused,
}
}
#[inline(always)]
fn hovered_node_attr(&self, theme: &Theme) -> CharAttribute {
theme.button.regular.text.hovered
}
#[inline(always)]
fn current_node_attr(&self, theme: &Theme) -> CharAttribute {
theme.button.regular.text.focused
}
#[inline(always)]
fn multiselect_selected_label_attr(theme: &Theme) -> CharAttribute {
theme.button.regular.text.pressed_or_selected
}
}
fn closest_points(r1: &Rect, r2: &Rect) -> (Point, Point, OrthogonalDirection, Option<Direction>) {
let h = if r1.right() + 2 <= r2.left() {
2
} else if r1.left() >= r2.right() + 2 {
1
} else {
0
};
let v = if r1.bottom() + 2 <= r2.top() {
2
} else if r1.top() >= r2.bottom() + 2 {
1
} else {
0
};
let value: u8 = (h << 2) | v;
match value {
0 => {
(r1.center(), r2.center(), OrthogonalDirection::Auto, None)
}
0b_00_01 => {
(
Point::new(r1.center_x(), r1.top()),
Point::new(r2.center_x(), r2.bottom()),
OrthogonalDirection::VerticalUntilMiddle,
Some(Direction::Bottom),
)
}
0b_00_10 => {
(
Point::new(r1.center_x(), r1.bottom()),
Point::new(r2.center_x(), r2.top()),
OrthogonalDirection::VerticalUntilMiddle,
Some(Direction::Top),
)
}
0b_01_00 => {
(
Point::new(r1.left(), r1.center_y()),
Point::new(r2.right(), r2.center_y()),
OrthogonalDirection::HorizontalUntilMiddle,
Some(Direction::Right),
)
}
0b_01_01 => {
if (r1.left() - r2.right()).abs() < (r1.top() - r2.bottom()).abs() {
(
Point::new(r1.left(), r1.center_y()),
Point::new(r2.right(), r2.center_y()),
OrthogonalDirection::HorizontalUntilMiddle,
Some(Direction::Right),
)
} else {
(
Point::new(r1.center_x(), r1.top()),
Point::new(r2.center_x(), r2.bottom()),
OrthogonalDirection::VerticalUntilMiddle,
Some(Direction::Bottom),
)
}
}
0b_01_10 => {
if (r1.left() - r2.right()).abs() < (r1.bottom() - r2.top()).abs() {
(
Point::new(r1.left(), r1.center_y()),
Point::new(r2.right(), r2.center_y()),
OrthogonalDirection::HorizontalUntilMiddle,
Some(Direction::Right),
)
} else {
(
Point::new(r1.center_x(), r1.bottom()),
Point::new(r2.center_x(), r2.top()),
OrthogonalDirection::VerticalUntilMiddle,
Some(Direction::Top),
)
}
}
0b_10_00 => {
(
Point::new(r1.right(), r1.center_y()),
Point::new(r2.left(), r2.center_y()),
OrthogonalDirection::HorizontalUntilMiddle,
Some(Direction::Left),
)
}
0b_10_01 => {
if (r1.right() - r2.left()).abs() < (r1.top() - r2.bottom()).abs() {
(
Point::new(r1.right(), r1.center_y()),
Point::new(r2.left(), r2.center_y()),
OrthogonalDirection::HorizontalUntilMiddle,
Some(Direction::Left),
)
} else {
(
Point::new(r1.center_x(), r1.top()),
Point::new(r2.center_x(), r2.bottom()),
OrthogonalDirection::VerticalUntilMiddle,
Some(Direction::Bottom),
)
}
}
0b_10_10 => {
if (r1.right() - r2.left()).abs() < (r1.bottom() - r2.top()).abs() {
(
Point::new(r1.right(), r1.center_y()),
Point::new(r2.left(), r2.center_y()),
OrthogonalDirection::HorizontalUntilMiddle,
Some(Direction::Left),
)
} else {
(
Point::new(r1.center_x(), r1.bottom()),
Point::new(r2.center_x(), r2.top()),
OrthogonalDirection::VerticalUntilMiddle,
Some(Direction::Top),
)
}
}
_ => {
unreachable!("The combination {value} [h={h}, v={v}] is not possible !");
}
}
}
pub struct Graph<T>
where
T: GraphNode,
{
pub(super) nodes: Vec<Node<T>>,
pub(super) edges: Vec<Edge>,
surface_size: Size,
surface: Surface,
pub(super) current_node: usize,
hovered_node: Option<usize>,
pending_edge_preview: Option<(usize, Point)>,
repr_buffer: String,
pub(super) rendering_options: RenderingOptions,
}
impl<T> Graph<T>
where
T: GraphNode,
{
pub(super) fn update_edges_in_out(&mut self) {
for n in &mut self.nodes {
n.edges_in.clear();
n.edges_out.clear();
}
for (idx, e) in self.edges.iter().enumerate() {
if e.directed {
self.nodes[e.from_node_id as usize].edges_out.push(idx as u32);
self.nodes[e.to_node_id as usize].edges_in.push(idx as u32);
} else {
self.nodes[e.from_node_id as usize].edges_out.push(idx as u32);
self.nodes[e.from_node_id as usize].edges_in.push(idx as u32);
self.nodes[e.to_node_id as usize].edges_out.push(idx as u32);
self.nodes[e.to_node_id as usize].edges_in.push(idx as u32);
}
}
}
pub fn new(nodes: Vec<Node<T>>, edges: Vec<Edge>) -> Self {
let mut g = Self {
nodes,
edges,
surface_size: Size::new(1, 1),
surface: Surface::new(200, 200),
current_node: 0,
hovered_node: None,
pending_edge_preview: None,
repr_buffer: String::with_capacity(128),
rendering_options: RenderingOptions::new(),
};
let nodes_count = g.nodes.len() as u32;
g.edges.retain(|e| (e.from_node_id < nodes_count) && (e.to_node_id < nodes_count));
g.update_edges_in_out();
g
}
pub fn with_slices(nodes: &[T], edges: &[(u32, u32)], directed: bool) -> Self
where
T: GraphNode + Clone,
{
let v: Vec<Node<T>> = nodes.iter().map(|n| NodeBuilder::new(n.clone()).build()).collect();
let e: Vec<Edge> = edges
.iter()
.map(|link| EdgeBuilder::new(link.0, link.1).directed(directed).build())
.collect();
Self::new(v, e)
}
pub fn with_slices_and_border(nodes: &[T], edges: &[(u32, u32)], border: LineType, directed: bool) -> Self
where
T: GraphNode + Clone,
{
let v: Vec<Node<T>> = nodes.iter().map(|n| NodeBuilder::new(n.clone()).border(border).build()).collect();
let e: Vec<Edge> = edges
.iter()
.map(|link| EdgeBuilder::new(link.0, link.1).directed(directed).build())
.collect();
Self::new(v, e)
}
pub fn current_node(&self) -> Option<&Node<T>> {
if self.current_node < self.nodes.len() {
Some(&self.nodes[self.current_node])
} else {
None
}
}
pub fn nodes_count(&self) -> usize {
self.nodes.len()
}
pub fn node(&self, index: usize) -> Option<&Node<T>> {
self.nodes.get(index)
}
fn update_surface_size(&mut self, pack: bool) {
if self.nodes.is_empty() {
self.surface_size = Size::new(1, 1);
return;
}
let r = self.nodes[0].rect;
let mut tl = r.top_left();
let mut br = r.bottom_right();
for n in &self.nodes {
tl.x = tl.x.min(n.rect.left());
tl.y = tl.y.min(n.rect.top());
br.x = br.x.max(n.rect.right());
br.y = br.y.max(n.rect.bottom());
}
let dx = 2 - tl.x; let dy = 1 - tl.y; if pack {
for n in &mut self.nodes {
n.rect += (dx, dy);
}
self.surface_size = Size::new(((br.x - tl.x + 1 + 4) as u32).max(1), ((br.y - tl.y + 1 + 2) as u32).max(1));
} else {
let dx = dx.max(0);
let dy = dy.max(0);
if dx > 0 || dy > 0 {
for n in &mut self.nodes {
n.rect += (dx, dy);
}
}
self.surface_size = Size::new(((br.x + (dx - 2) + 1 + 4) as u32).max(1), ((br.y + (dy - 1) + 1 + 2) as u32).max(1));
}
}
pub(super) fn resize_graph(&mut self, pack: bool) {
self.update_surface_size(pack);
self.surface.resize(self.surface_size);
}
pub(super) fn clear_selection(&mut self, control: &ControlBase) {
let mut need_repaint = false;
for n in &mut self.nodes {
need_repaint |= n.selected;
n.selected = false;
}
if need_repaint {
self.repaint(control);
}
}
pub(super) fn clear_filter(&mut self, control: &ControlBase) {
let mut need_repaint = false;
for n in &mut self.nodes {
if n.filtered {
n.filtered = false;
need_repaint = true;
}
}
if need_repaint {
self.repaint(control);
}
}
pub(super) fn filter(&mut self, text: &str, control: &ControlBase) -> usize {
if text.is_empty() {
self.clear_filter(control);
return self.nodes.len();
}
let mut need_repaint = false;
let mut count = 0;
for n in &mut self.nodes {
self.repr_buffer.clear();
if n.obj.write_label(&mut self.repr_buffer, Size::new(u32::MAX, u32::MAX)).is_ok() {
if self.repr_buffer.index_ignoring_case(text).is_some() {
if n.filtered {
n.filtered = false;
need_repaint = true;
}
count += 1;
} else if !n.filtered {
n.filtered = true;
need_repaint = true;
}
} else if !n.filtered {
n.filtered = true;
need_repaint = true;
}
}
if (count > 0) && (self.current_node < self.nodes.len()) && self.nodes[self.current_node].filtered {
let len = self.nodes.len();
for i in 0..self.nodes.len() {
let idx = (self.current_node + i) % len;
if !self.nodes[idx].filtered {
self.current_node = idx;
break;
}
}
need_repaint = true;
}
if need_repaint {
self.repaint(control);
}
count
}
pub(super) fn goto_next_match(&mut self, control: &ControlBase) {
if self.nodes.is_empty() {
return;
}
let len = self.nodes.len();
for i in 1..=len {
let idx = (self.current_node + i) % len;
if !self.nodes[idx].filtered {
self.set_current_node(idx, control);
break;
}
}
}
pub(super) fn goto_previous_match(&mut self, control: &ControlBase) {
if self.nodes.is_empty() {
return;
}
let len = self.nodes.len();
for i in 1..=len {
let idx = (self.current_node + len - i) % len;
if !self.nodes[idx].filtered {
self.set_current_node(idx, control);
break;
}
}
}
#[inline(always)]
pub(super) fn size(&self) -> Size {
self.surface_size
}
pub(super) fn update_rendering_options(&mut self, new_options: &RenderingOptions, control: &ControlBase) {
if self.rendering_options != *new_options {
let multiselect_changed = self.rendering_options.multiselect_ui != new_options.multiselect_ui;
let prev_multiselect_ui = self.rendering_options.multiselect_ui;
self.rendering_options = *new_options;
if multiselect_changed {
self.refresh_node_sizes_for_multiselect_ui(prev_multiselect_ui);
}
self.repaint(control);
}
}
fn refresh_node_sizes_for_multiselect_ui(&mut self, layout_before: bool) {
let m = self.rendering_options.multiselect_ui;
for node in &mut self.nodes {
let sz = node.label_content_size(layout_before);
node.resize(sz, m);
}
self.resize_graph(false);
}
pub(super) fn mouse_pos_to_index(&self, x: i32, y: i32) -> Option<usize> {
for (idx, n) in self.nodes.iter().enumerate() {
if n.contains(x, y) {
return Some(idx);
}
}
None
}
pub(super) fn current_node_id(&self) -> Option<usize> {
if self.current_node < self.nodes.len() {
Some(self.current_node)
} else {
None
}
}
pub(super) fn hovered_node_id(&self) -> Option<usize> {
self.hovered_node
}
pub(super) fn update_hover_at(&mut self, gpoint: Point) {
self.hovered_node = self.mouse_pos_to_index(gpoint.x, gpoint.y);
}
pub(super) fn set_edge_preview(&mut self, preview: Option<(usize, Point)>, control: &ControlBase) {
if self.pending_edge_preview == preview {
return;
}
self.pending_edge_preview = preview;
self.repaint(control);
}
fn draw_pending_edge_preview(&mut self, state: ControlState, theme: &Theme) {
let Some((from_ix, end)) = self.pending_edge_preview else {
return;
};
if from_ix >= self.nodes.len() {
return;
}
let attr = match state {
ControlState::Disabled => theme.lines.inactive,
ControlState::Normal => theme.lines.hovered,
ControlState::Focused => theme.lines.hovered,
};
let mouse_rect = Rect::new(end.x, end.y, end.x, end.y);
let (p1, p2, _, _) = closest_points(&self.nodes[from_ix].rect, &mouse_rect);
let lt = self.rendering_options.edge_line_type;
self.surface.draw_line(p1.x, p1.y, p2.x, p2.y, lt, attr);
}
fn draw_edge(&mut self, index: u32, attr: CharAttribute) {
let e = &self.edges[index as usize];
let (p1, p2, orto_dir, entry_dir) = closest_points(&self.nodes[e.from_node_id as usize].rect, &self.nodes[e.to_node_id as usize].rect);
let line_type = e.line_type.unwrap_or(self.rendering_options.edge_line_type);
match self.rendering_options.edge_routing {
EdgeRouting::Direct => self.surface.draw_line(p1.x, p1.y, p2.x, p2.y, line_type, attr),
EdgeRouting::Orthogonal => self.surface.draw_orthogonal_line(p1.x, p1.y, p2.x, p2.y, line_type, orto_dir, attr),
}
if e.directed && self.rendering_options.show_arrow_heads {
match entry_dir {
Some(Direction::Left) => self.surface.write_char(p2.x - 1, p2.y, Character::with_char(SpecialChar::TriangleRight)),
Some(Direction::Right) => self.surface.write_char(p2.x + 1, p2.y, Character::with_char(SpecialChar::TriangleLeft)),
Some(Direction::Top) => self.surface.write_char(p2.x, p2.y - 1, Character::with_char(SpecialChar::TriangleDown)),
Some(Direction::Bottom) => self.surface.write_char(p2.x, p2.y + 1, Character::with_char(SpecialChar::TriangleUp)),
None => (),
}
}
}
fn draw_edges_from_node(&mut self, node_index: usize, attr: CharAttribute) {
if node_index >= self.nodes.len() {
return;
}
let len = self.nodes[node_index].edges_out.len();
for i in 0..len {
let index = self.nodes[node_index].edges_out[i];
self.draw_edge(index, attr);
}
}
fn draw_edges_to_node(&mut self, node_index: usize, attr: CharAttribute) {
if node_index >= self.nodes.len() {
return;
}
let len = self.nodes[node_index].edges_in.len();
for i in 0..len {
let index = self.nodes[node_index].edges_in[i];
self.draw_edge(index, attr);
}
}
fn draw_edges_from_current(&mut self, attr: CharAttribute) {
self.draw_edges_from_node(self.current_node, attr);
}
fn draw_edges_to_current(&mut self, attr: CharAttribute) {
self.draw_edges_to_node(self.current_node, attr);
}
pub(super) fn repaint(&mut self, control: &ControlBase) {
let ch = Character::new(0 as char, Color::Transparent, Color::Transparent, CharFlags::None);
for c in &mut self.surface.chars {
*c = ch;
}
let state = ControlState::new(control);
let theme = control.theme();
let text_attr = state.node_attr(theme);
let edge_attr = state.edge_attr(theme);
let len = self.edges.len() as u32;
if state == ControlState::Focused {
for index in 0..len {
self.draw_edge(index, self.edges[index as usize].attribute.unwrap_or(edge_attr));
}
} else {
for index in 0..len {
self.draw_edge(index, edge_attr);
}
}
if state == ControlState::Focused {
let attr = theme.lines.hovered;
let ms = self.rendering_options.multiselect_ui;
let hi_out = self.rendering_options.highlight_edges_out;
let hi_in = self.rendering_options.highlight_edges_in;
if hi_out || hi_in {
if ms {
let selected: Vec<usize> =
(0..self.nodes.len()).filter(|&i| self.nodes[i].selected).collect();
if hi_out {
for idx in &selected {
self.draw_edges_from_node(*idx, attr);
}
}
if hi_in {
for idx in &selected {
self.draw_edges_to_node(*idx, attr);
}
}
} else if self.current_node < self.nodes.len() {
if hi_out {
self.draw_edges_from_current(attr);
}
if hi_in {
self.draw_edges_to_current(attr);
}
}
}
}
self.draw_pending_edge_preview(state, theme);
let ms = self.rendering_options.multiselect_ui;
for node in &self.nodes {
self.repr_buffer.clear();
if state == ControlState::Focused {
let attr = if node.filtered {
ControlState::Disabled.node_attr(theme)
} else if ms && node.selected {
node.text_attr.unwrap_or(ControlState::multiselect_selected_label_attr(theme))
} else {
node.text_attr.unwrap_or(text_attr)
};
node.paint(&mut self.surface, attr, &mut self.repr_buffer, ms);
} else {
node.paint(&mut self.surface, text_attr, &mut self.repr_buffer, ms);
};
}
let len = self.nodes.len();
let hover_node_id = self.hovered_node.unwrap_or(usize::MAX);
if (state != ControlState::Disabled) && (hover_node_id < len) {
let node = &self.nodes[hover_node_id];
let attr = state.hovered_node_attr(theme);
self.repr_buffer.clear();
node.paint(&mut self.surface, attr, &mut self.repr_buffer, ms);
}
if (state == ControlState::Focused) && (self.current_node < len) {
let node = &self.nodes[self.current_node];
let attr = state.current_node_attr(theme);
self.repr_buffer.clear();
node.paint(&mut self.surface, attr, &mut self.repr_buffer, ms);
}
}
pub(super) fn paint_node(&mut self, control: &ControlBase, index: usize) {
let len = self.nodes.len();
if index >= len {
return;
}
let state = ControlState::new(control);
let theme = control.theme();
let node = &self.nodes[index];
let attr = match state {
ControlState::Disabled => state.node_attr(theme),
ControlState::Normal => {
let hover_node_id = self.hovered_node.unwrap_or(usize::MAX);
if hover_node_id == index {
state.hovered_node_attr(theme)
} else {
state.node_attr(theme)
}
}
ControlState::Focused => {
if index == self.current_node {
state.current_node_attr(theme)
} else {
let hover_node_id = self.hovered_node.unwrap_or(usize::MAX);
if hover_node_id == index {
state.hovered_node_attr(theme)
} else if node.filtered {
ControlState::Disabled.node_attr(theme)
} else if self.rendering_options.multiselect_ui && node.selected {
node.text_attr.unwrap_or(ControlState::multiselect_selected_label_attr(theme))
} else {
node.text_attr.unwrap_or(state.node_attr(theme))
}
}
}
};
self.repr_buffer.clear();
node.paint(
&mut self.surface,
attr,
&mut self.repr_buffer,
self.rendering_options.multiselect_ui,
);
}
pub(super) fn reset_hover(&mut self, control: &ControlBase) {
let index = self.hovered_node.unwrap_or(usize::MAX);
if self.hovered_node.is_some() {
self.hovered_node = None;
self.paint_node(control, index);
}
}
pub(super) fn process_mouse_over(&mut self, control: &ControlBase, point: Point) -> bool {
let new_idx = self.mouse_pos_to_index(point.x, point.y);
if new_idx == self.hovered_node {
return false;
}
self.reset_hover(control);
self.hovered_node = new_idx;
if let Some(idx) = new_idx {
self.paint_node(control, idx);
}
true
}
pub(super) fn surface(&self) -> &Surface {
&self.surface
}
pub(super) fn selected_drag_anchors(&self) -> Vec<(usize, Point)> {
(0..self.nodes.len())
.filter(|&i| self.nodes[i].selected)
.map(|i| (i, self.nodes[i].rect.top_left()))
.collect()
}
pub(super) fn multiselect_selection_fingerprint(&self) -> u64 {
let mut h = 0u64;
for (i, n) in self.nodes.iter().enumerate() {
if n.selected {
h ^= (i as u64).wrapping_mul(0x9e3779b97f4a7c15);
}
}
h
}
pub(super) fn apply_multiselect_plain_click(&mut self, id: usize, control: &ControlBase) {
if id >= self.nodes.len() {
return;
}
if self.rendering_options.multiselect_ui {
for n in &mut self.nodes {
n.selected = false;
}
self.nodes[id].selected = true;
self.current_node = id;
self.repaint(control);
} else {
self.set_current_node(id, control);
}
}
pub(super) fn toggle_multiselect_selected(&mut self, id: usize, control: &ControlBase) {
if !self.rendering_options.multiselect_ui || id >= self.nodes.len() {
return;
}
self.nodes[id].selected = !self.nodes[id].selected;
if self.nodes[id].selected {
self.current_node = id;
} else if let Some(other) = (0..self.nodes.len()).find(|&i| self.nodes[i].selected) {
self.current_node = other;
} else {
self.current_node = id;
}
self.repaint(control);
}
pub(super) fn move_nodes_with_press_delta(
&mut self,
anchors: &[(usize, Point)],
origin: Point,
data: Point,
control: &ControlBase,
) -> bool {
if anchors.is_empty() {
return false;
}
let dx = data.x - origin.x;
let dy = data.y - origin.y;
let mut changed = false;
for &(id, tl) in anchors {
if id >= self.nodes.len() {
continue;
}
let nx = tl.x + dx;
let ny = tl.y + dy;
let node = &mut self.nodes[id];
if node.rect.top_left().x != nx || node.rect.top_left().y != ny {
node.rect.set_left(nx, true);
node.rect.set_top(ny, true);
changed = true;
}
}
if !changed {
return false;
}
let mut resized = false;
for &(id, _) in anchors {
if id >= self.nodes.len() {
continue;
}
let node = &self.nodes[id];
let x = node.rect.left();
let y = node.rect.top();
if node.rect.right() >= (self.surface_size.width as i32)
|| node.rect.bottom() >= (self.surface_size.height as i32)
|| x < 0
|| y < 0
{
resized = true;
break;
}
}
if resized {
self.resize_graph(false);
}
self.repaint(control);
resized
}
fn move_selected_nodes_with(&mut self, dx: i32, dy: i32, control: &ControlBase) -> bool {
let mut changed = false;
for i in 0..self.nodes.len() {
if !self.nodes[i].selected {
continue;
}
let tl = self.nodes[i].rect.top_left();
let nx = tl.x + dx;
let ny = tl.y + dy;
if tl.x != nx || tl.y != ny {
let node = &mut self.nodes[i];
node.rect.set_left(nx, true);
node.rect.set_top(ny, true);
changed = true;
}
}
if !changed {
return false;
}
let mut resized = false;
for i in 0..self.nodes.len() {
if !self.nodes[i].selected {
continue;
}
let node = &self.nodes[i];
let x = node.rect.left();
let y = node.rect.top();
if node.rect.right() >= (self.surface_size.width as i32)
|| node.rect.bottom() >= (self.surface_size.height as i32)
|| x < 0
|| y < 0
{
resized = true;
break;
}
}
if resized {
self.resize_graph(false);
}
self.repaint(control);
true
}
fn ctrl_arrow_move_to(&mut self, dx: i32, dy: i32, control: &ControlBase) {
if self.rendering_options.multiselect_ui {
if !self.move_selected_nodes_with(dx, dy, control) {
self.move_node_with(self.current_node, dx, dy, control);
}
} else {
self.move_node_with(self.current_node, dx, dy, control);
}
}
pub(super) fn move_node_to(&mut self, id: usize, x: i32, y: i32, control: &ControlBase) -> bool {
if id >= self.nodes.len() {
return false;
}
let node = &mut self.nodes[id];
let tl = node.rect.top_left();
if (tl.x == x) && (tl.y == y) {
return false;
}
node.rect.set_left(x, true);
node.rect.set_top(y, true);
let mut resized = false;
if node.rect.right() >= (self.surface_size.width as i32) || node.rect.bottom() >= (self.surface_size.height as i32) || x < 0 || y < 0 {
self.resize_graph(false);
resized = true;
}
self.repaint(control);
resized
}
fn move_node_with(&mut self, id: usize, dx: i32, dy: i32, control: &ControlBase) {
if id >= self.nodes.len() {
return;
}
let tl = self.nodes[id].rect.top_left();
self.move_node_to(id, tl.x + dx, tl.y + dy, control);
}
pub(super) fn set_current_node(&mut self, index: usize, control: &ControlBase) {
if index != self.current_node {
if self.rendering_options.highlight_edges_in || self.rendering_options.highlight_edges_out {
self.current_node = index;
self.repaint(control);
} else {
let old_index = self.current_node;
self.current_node = index;
self.paint_node(control, old_index);
self.paint_node(control, index);
}
}
}
fn next_node(&self, dir: Direction) -> Option<usize> {
if self.nodes.is_empty() {
return None;
}
let r = self.nodes[self.current_node].rect;
let c = dir.compare_point(&r);
let mut best = None;
let mut best_dist = u64::MAX;
for (index, n) in self.nodes.iter().enumerate() {
if index == self.current_node {
continue;
}
let dp = match dir {
Direction::Left => {
if r.right() > n.rect.right() {
Some(Direction::Right.compare_point(&n.rect))
} else {
None
}
}
Direction::Right => {
if r.left() < n.rect.left() {
Some(Direction::Left.compare_point(&n.rect))
} else {
None
}
}
Direction::Top => {
if r.bottom() > n.rect.bottom() {
Some(Direction::Bottom.compare_point(&n.rect))
} else {
None
}
}
Direction::Bottom => {
if r.top() < n.rect.top() {
Some(Direction::Top.compare_point(&n.rect))
} else {
None
}
}
};
if let Some(nc) = dp {
let dist = ((nc.x - c.x) * (nc.x - c.x)) as u64 + ((nc.y - c.y) * (nc.y - c.y)) as u64;
if dist < best_dist {
best = Some(index);
best_dist = dist;
}
}
}
best
}
fn move_to_node_with_direction(&mut self, dir: Direction, control: &ControlBase) {
if let Some(next_index) = self.next_node(dir) {
self.set_current_node(next_index, control);
}
}
pub(crate) fn apply_ctrl_a_visible_selection_toggle(&mut self) -> Option<usize> {
if !self.rendering_options.multiselect_ui || self.nodes.is_empty() {
return None;
}
let fv = (0..self.nodes.len()).find(|&i| !self.nodes[i].filtered)?;
let all_visible_selected = (0..self.nodes.len())
.filter(|&i| !self.nodes[i].filtered)
.all(|i| self.nodes[i].selected);
for n in &mut self.nodes {
if !n.filtered {
n.selected = !all_visible_selected;
}
}
Some(fv)
}
pub(super) fn process_key_events(&mut self, key: Key, control: &ControlBase) -> bool {
match key.value() {
key!("Left") => self.move_to_node_with_direction(Direction::Left, control),
key!("Right") => self.move_to_node_with_direction(Direction::Right, control),
key!("Up") => self.move_to_node_with_direction(Direction::Top, control),
key!("Down") => self.move_to_node_with_direction(Direction::Bottom, control),
key!("Ctrl+Left") => self.ctrl_arrow_move_to(-1, 0, control),
key!("Ctrl+Right") => self.ctrl_arrow_move_to(1, 0, control),
key!("Ctrl+Up") => self.ctrl_arrow_move_to(0, -1, control),
key!("Ctrl+Down") => self.ctrl_arrow_move_to(0, 1, control),
key!("Ctrl+Tab") => {
if !self.nodes.is_empty() {
self.set_current_node((self.current_node + 1) % self.nodes.len(), control);
}
}
key!("Ctrl+Shift+Tab") => {
if !self.nodes.is_empty() {
self.set_current_node((self.current_node + self.nodes.len() - 1) % self.nodes.len(), control);
}
}
key!("Space") => {
if !self.rendering_options.multiselect_ui || self.nodes.is_empty() || self.current_node >= self.nodes.len() {
return false;
}
let cn = self.current_node;
self.toggle_multiselect_selected(cn, control);
}
key!("Ctrl+A") => {
let Some(fv) = self.apply_ctrl_a_visible_selection_toggle() else {
return false;
};
self.current_node = fv;
self.repaint(control);
}
_ => return false,
}
true }
pub(super) fn node_description(&mut self, id: usize) -> Option<&str> {
if id >= self.nodes.len() {
return None;
}
self.repr_buffer.clear();
if self.nodes[id].obj.write_description(&mut self.repr_buffer).is_err() {
return None;
}
if self.repr_buffer.is_empty() {
None
} else {
Some(&self.repr_buffer)
}
}
}
impl<T> Default for Graph<T>
where
T: GraphNode,
{
fn default() -> Self {
Self {
nodes: Default::default(),
edges: Default::default(),
surface_size: Default::default(),
surface: Surface::new(1, 1),
current_node: 0,
hovered_node: None,
pending_edge_preview: None,
repr_buffer: String::new(),
rendering_options: RenderingOptions::new(),
}
}
}
pub struct EditableGraph<'a, T>
where
T: GraphNode + 'a,
{
graph: &'a mut Graph<T>,
pub(super) current_node: usize,
pub(super) changed_nodes: bool,
pub(super) changed_edges: bool,
pub(super) changed_graph: bool,
pub(super) changed_current_node: bool,
}
impl<'a, T> EditableGraph<'a, T>
where
T: GraphNode + 'a,
{
pub(crate) fn new(graph: &'a mut Graph<T>) -> Self {
let current_node = graph.current_node;
Self {
graph,
current_node,
changed_nodes: false,
changed_edges: false,
changed_graph: false,
changed_current_node: false,
}
}
#[inline(always)]
pub fn node(&mut self, index: usize) -> Option<EditableNode<'_, T>> {
if index >= self.graph.nodes.len() {
return None;
}
Some(EditableNode::new(&mut self.graph.nodes[index], &mut self.changed_nodes))
}
#[inline(always)]
pub fn nodes_count(&self) -> usize {
self.graph.nodes.len()
}
#[inline(always)]
pub fn add_node(&mut self, node: Node<T>) -> usize {
self.graph.nodes.push(node);
self.changed_graph = true;
self.graph.nodes.len() - 1
}
pub fn delete_node(&mut self, index: usize) {
if index >= self.graph.nodes.len() {
return;
}
let idx = index as u32;
self.graph.edges.retain(|e| e.from_node_id != idx && e.to_node_id != idx);
for e in &mut self.graph.edges {
if e.from_node_id > idx {
e.from_node_id = e.from_node_id.saturating_sub(1);
}
if e.to_node_id > idx {
e.to_node_id = e.to_node_id.saturating_sub(1);
}
}
self.graph.nodes.remove(index);
if self.current_node >= index {
self.current_node = self.current_node.saturating_sub(1);
self.changed_current_node = true;
}
let len = self.graph.nodes.len();
if len == 0 {
self.current_node = 0;
self.changed_current_node = true;
} else {
if self.current_node >= len {
self.current_node = len - 1;
self.changed_current_node = true;
}
if self.graph.rendering_options.multiselect_ui {
let cur = self.current_node;
if !self.graph.nodes[cur].selected {
if let Some(i) = (0..len).find(|&i| self.graph.nodes[i].selected) {
self.current_node = i;
self.changed_current_node = true;
}
}
}
}
self.changed_graph = true;
}
#[inline(always)]
pub fn edge(&mut self, index: usize) -> Option<EditableEdge<'_>> {
if index >= self.graph.edges.len() {
return None;
}
Some(EditableEdge::new(&mut self.graph.edges[index], &mut self.changed_edges))
}
#[inline(always)]
pub fn edges_count(&self) -> usize {
self.graph.edges.len()
}
#[inline(always)]
pub fn add_edge(&mut self, edge: Edge) -> bool {
let cnt = self.graph.nodes.len() as u32;
if edge.from_node_id >= cnt || edge.to_node_id >= cnt {
return false;
}
self.graph.edges.push(edge);
self.changed_graph = true;
true
}
pub fn delete_edge(&mut self, index: usize) {
if index >= self.graph.edges.len() {
return;
}
self.graph.edges.remove(index);
self.changed_graph = true;
}
#[inline(always)]
pub fn set_current_node(&mut self, index: usize) {
if (index != self.current_node) && (index < self.graph.nodes.len()) {
self.current_node = index;
self.changed_current_node = true;
}
}
#[inline(always)]
pub fn current_node(&self) -> usize {
self.current_node
}
}