use egui::{
Color32, Pos2, Stroke,
epaint::{CubicBezierShape, PathStroke},
};
fn swap_xy(p: Pos2) -> Pos2 {
Pos2::new(p.y, p.x)
}
fn horizontal_ctrl(from: Pos2, to: Pos2, zoom: f32) -> [Pos2; 4] {
let dx = (to.x - from.x).abs();
let handle = (dx * 0.5).clamp(24.0 * zoom, 160.0 * zoom);
[
from,
Pos2::new(from.x + handle, from.y),
Pos2::new(to.x - handle, to.y),
to,
]
}
fn shape(points: [Pos2; 4], stroke: Stroke) -> CubicBezierShape {
CubicBezierShape::from_points_stroke(
points,
false,
Color32::TRANSPARENT,
PathStroke::from(stroke),
)
}
fn project_t(a: Pos2, b: Pos2, p: Pos2) -> f32 {
let ab = b - a;
let len2 = ab.length_sq();
if len2 <= f32::EPSILON {
return 0.0;
}
((p - a).dot(ab) / len2).clamp(0.0, 1.0)
}
fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 {
let l = |x: u8, y: u8| {
(x as f32 + (y as f32 - x as f32) * t)
.round()
.clamp(0.0, 255.0) as u8
};
Color32::from_rgba_unmultiplied(
l(a.r(), b.r()),
l(a.g(), b.g()),
l(a.b(), b.b()),
l(a.a(), b.a()),
)
}
fn grad_shape(
points: [Pos2; 4],
from: Pos2,
to: Pos2,
width: f32,
c_from: Color32,
c_to: Color32,
) -> CubicBezierShape {
let stroke = PathStroke::new_uv(width, move |_bbox, p| {
lerp_color(c_from, c_to, project_t(from, to, p))
});
CubicBezierShape::from_points_stroke(points, false, Color32::TRANSPARENT, stroke)
}
pub fn link_curve(from: Pos2, to: Pos2, zoom: f32, stroke: Stroke) -> CubicBezierShape {
shape(horizontal_ctrl(from, to, zoom), stroke)
}
pub fn link_curve_grad(
from: Pos2,
to: Pos2,
zoom: f32,
width: f32,
c_from: Color32,
c_to: Color32,
) -> CubicBezierShape {
grad_shape(
horizontal_ctrl(from, to, zoom),
from,
to,
width,
c_from,
c_to,
)
}
pub fn link_curve_vertical(from: Pos2, to: Pos2, zoom: f32, stroke: Stroke) -> CubicBezierShape {
let ctrl = horizontal_ctrl(swap_xy(from), swap_xy(to), zoom).map(swap_xy);
shape(ctrl, stroke)
}