use crate::canvas::box_scale::drag_handle;
use crate::canvas::shapes::Shapes;
use egui::emath::TSTransform;
use egui::{Color32, Pos2, Rect, Response, Stroke, Ui, Vec2, pos2, vec2};
const ROTATE_HANDLE_PX: f32 = 24.0;
pub(crate) const SNAP_DEG: f32 = 15.0;
pub(crate) fn rotation_handle(
ui: &mut Ui,
overlay: &mut Shapes,
scene_transform: &TSTransform,
shown_box: Rect,
) -> Response {
let top = *scene_transform * pos2(shown_box.center().x, shown_box.min.y);
let handle = top - vec2(0.0, ROTATE_HANDLE_PX);
overlay.line_segment(
[top, handle],
Stroke::new(1.0, Color32::from_white_alpha(160)),
);
let id = ui.id().with("rotation handle");
drag_handle(ui, overlay, handle, Color32::from_rgb(255, 210, 90), id)
.on_hover_text("Turn the selection; shift snaps to 15°")
}
#[derive(Debug, Clone)]
pub(crate) struct RotateDrag<P> {
pub(crate) centre: Pos2,
pub(crate) start_angle: f32,
pub(crate) start_box: Rect,
pub(crate) start: Vec<P>,
}
impl<P> RotateDrag<P> {
pub(crate) fn new(start_box: Rect, pointer: Pos2, start: Vec<P>) -> Self {
let centre = start_box.center();
Self {
centre,
start_angle: angle_of(pointer - centre),
start_box,
start,
}
}
pub(crate) fn angle(&self, pointer: Pos2, snap: bool) -> f32 {
turned_by(self.centre, self.start_angle, pointer, snap)
}
}
fn angle_of(offset: Vec2) -> f32 {
offset.y.atan2(offset.x)
}
fn turned_by(centre: Pos2, start_angle: f32, pointer: Pos2, snap: bool) -> f32 {
let angle = angle_of(pointer - centre) - start_angle;
if !snap {
return angle;
}
let step = SNAP_DEG.to_radians();
(angle / step).round() * step
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::FRAC_PI_2;
#[test]
fn a_quarter_turn_of_the_pointer_turns_the_selection_a_quarter() {
let centre = pos2(10.0, 10.0);
let start = angle_of(pos2(20.0, 10.0) - centre);
let angle = turned_by(centre, start, pos2(10.0, 20.0), false);
assert!((angle - FRAC_PI_2).abs() < 1e-6, "{angle}");
}
#[test]
fn shift_snaps_to_fifteen_degrees() {
let centre = Pos2::ZERO;
let pointer = pos2(20f32.to_radians().cos(), 20f32.to_radians().sin());
let angle = turned_by(centre, 0.0, pointer, true);
assert!((angle - 15f32.to_radians()).abs() < 1e-6, "{angle}");
}
}