use crate::config::FlowConfig;
use crate::types::changes::NodeChange;
use crate::types::node::{InternalNode, NodeId};
use crate::types::position::Transform;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeHandleKind {
TopLeft,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
}
impl ResizeHandleKind {
pub const ALL: [ResizeHandleKind; 8] = [
ResizeHandleKind::TopLeft,
ResizeHandleKind::Top,
ResizeHandleKind::TopRight,
ResizeHandleKind::Right,
ResizeHandleKind::BottomRight,
ResizeHandleKind::Bottom,
ResizeHandleKind::BottomLeft,
ResizeHandleKind::Left,
];
pub fn anchor(self) -> (f32, f32) {
match self {
ResizeHandleKind::TopLeft => (0.0, 0.0),
ResizeHandleKind::Top => (0.5, 0.0),
ResizeHandleKind::TopRight => (1.0, 0.0),
ResizeHandleKind::Right => (1.0, 0.5),
ResizeHandleKind::BottomRight => (1.0, 1.0),
ResizeHandleKind::Bottom => (0.5, 1.0),
ResizeHandleKind::BottomLeft => (0.0, 1.0),
ResizeHandleKind::Left => (0.0, 0.5),
}
}
pub fn cursor(self) -> egui::CursorIcon {
match self {
ResizeHandleKind::TopLeft | ResizeHandleKind::BottomRight => {
egui::CursorIcon::ResizeNwSe
}
ResizeHandleKind::TopRight | ResizeHandleKind::BottomLeft => {
egui::CursorIcon::ResizeNeSw
}
ResizeHandleKind::Top | ResizeHandleKind::Bottom => egui::CursorIcon::ResizeVertical,
ResizeHandleKind::Left | ResizeHandleKind::Right => egui::CursorIcon::ResizeHorizontal,
}
}
pub fn affects_origin(self) -> (bool, bool) {
let moves_left = matches!(
self,
ResizeHandleKind::TopLeft | ResizeHandleKind::Left | ResizeHandleKind::BottomLeft
);
let moves_top = matches!(
self,
ResizeHandleKind::TopLeft | ResizeHandleKind::Top | ResizeHandleKind::TopRight
);
(moves_left, moves_top)
}
pub fn affects_size(self) -> (bool, bool) {
let w = !matches!(self, ResizeHandleKind::Top | ResizeHandleKind::Bottom);
let h = !matches!(self, ResizeHandleKind::Left | ResizeHandleKind::Right);
(w, h)
}
}
#[derive(Debug, Clone)]
pub(crate) struct ResizeState {
pub(crate) node_id: NodeId,
pub(crate) handle: ResizeHandleKind,
pub(crate) initial_pos: egui::Pos2,
pub(crate) initial_size: egui::Vec2,
}
pub(crate) const HANDLE_SIZE: f32 = 8.0;
const MIN_NODE_SIZE: f32 = 20.0;
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_and_handle_resize<D>(
ui: &mut egui::Ui,
painter: &egui::Painter,
node_id: &NodeId,
node_rect: egui::Rect, node: &InternalNode<D>,
transform: &Transform,
config: &FlowConfig,
resize_state: Option<ResizeState>,
) -> (
Option<ResizeState>,
Vec<NodeChange<D>>,
Option<egui::CursorIcon>,
) {
let mut changes: Vec<NodeChange<D>> = Vec::new();
let mut cursor_override: Option<egui::CursorIcon> = None;
if let Some(ref rs) = resize_state {
if rs.node_id == *node_id {
let delta = ui.input(|i| i.pointer.delta());
let released = ui.input(|i| i.pointer.primary_released());
cursor_override = Some(rs.handle.cursor());
if released {
return (None, changes, cursor_override);
}
if delta != egui::Vec2::ZERO {
let total_delta_screen = ui
.input(|i| i.pointer.press_origin().zip(i.pointer.hover_pos()))
.map(|(origin, hover)| hover - origin)
.unwrap_or(egui::Vec2::ZERO);
let flow_delta = egui::vec2(
total_delta_screen.x / transform.scale,
total_delta_screen.y / transform.scale,
);
let (moves_left, moves_top) = rs.handle.affects_origin();
let (scales_w, scales_h) = rs.handle.affects_size();
let mut new_x = rs.initial_pos.x;
let mut new_y = rs.initial_pos.y;
let mut new_w = rs.initial_size.x;
let mut new_h = rs.initial_size.y;
if scales_w {
if moves_left {
let delta_w = -flow_delta.x;
new_w = (rs.initial_size.x + delta_w).max(MIN_NODE_SIZE);
new_x = rs.initial_pos.x + rs.initial_size.x - new_w;
} else {
new_w = (rs.initial_size.x + flow_delta.x).max(MIN_NODE_SIZE);
}
}
if scales_h {
if moves_top {
let delta_h = -flow_delta.y;
new_h = (rs.initial_size.y + delta_h).max(MIN_NODE_SIZE);
new_y = rs.initial_pos.y + rs.initial_size.y - new_h;
} else {
new_h = (rs.initial_size.y + flow_delta.y).max(MIN_NODE_SIZE);
}
}
if (new_x - rs.initial_pos.x).abs() > 0.1 || (new_y - rs.initial_pos.y).abs() > 0.1
{
changes.push(NodeChange::Position {
id: node_id.clone(),
position: Some(egui::pos2(new_x, new_y)),
dragging: Some(true),
});
}
changes.push(NodeChange::Dimensions {
id: node_id.clone(),
dimensions: Some(crate::types::position::Dimensions {
width: new_w,
height: new_h,
}),
});
return (resize_state, changes, cursor_override);
}
return (resize_state, changes, cursor_override);
}
}
let handle_color = config.node_selected_border_color;
let handle_bg = egui::Color32::WHITE;
let mut new_state: Option<ResizeState> = None;
for kind in ResizeHandleKind::ALL {
let (ax, ay) = kind.anchor();
let center = egui::pos2(
node_rect.min.x + node_rect.width() * ax,
node_rect.min.y + node_rect.height() * ay,
);
let rect = egui::Rect::from_center_size(center, egui::vec2(HANDLE_SIZE, HANDLE_SIZE));
painter.rect_filled(rect, 2.0, handle_bg);
painter.rect_stroke(
rect,
2.0,
egui::Stroke::new(1.5, handle_color),
egui::StrokeKind::Middle,
);
let handle_id = ui.id().with(format!("resize_{:?}_{}", node_id, kind as u8));
let resp = ui.interact(rect, handle_id, egui::Sense::drag());
if resp.hovered() {
cursor_override = Some(kind.cursor());
}
if resp.drag_started() && new_state.is_none() {
let flow_pos = node.internals.position_absolute;
let flow_w = node.width();
let flow_h = node.height();
new_state = Some(ResizeState {
node_id: node_id.clone(),
handle: kind,
initial_pos: flow_pos,
initial_size: egui::vec2(flow_w, flow_h),
});
cursor_override = Some(kind.cursor());
}
}
(new_state.or(resize_state), changes, cursor_override)
}
pub(crate) fn should_show_resize_handles<D>(
node_lookup: &std::collections::HashMap<NodeId, InternalNode<D>>,
) -> Option<NodeId> {
let selected: Vec<&NodeId> = node_lookup
.iter()
.filter(|(_, n)| n.node.selected && !n.node.hidden)
.map(|(id, _)| id)
.collect();
if selected.len() == 1 {
Some(selected[0].clone())
} else {
None
}
}