use crate::canvas::scene_modification::{SceneModded, pan_bounds};
use crate::canvas::view::SceneView;
use egui::{PointerButton, Rangef, Rect, Sense, Ui, Vec2, pos2};
pub(crate) fn panning(ui: &Ui) -> bool {
ui.input(|i| i.pointer.button_down(PointerButton::Middle))
}
pub(crate) fn pan_view(view: &mut SceneView, by: Vec2) {
view.center -= by;
}
pub(crate) const SCROLLBAR_WIDTH: f32 = 10.0;
pub(crate) fn scrollbars(ui: &mut Ui, canvas: Rect, scene: &SceneModded, view: &mut SceneView) {
let bounds = pan_bounds(scene.scene_rect());
let visible = scene.visible(canvas, view);
let horizontal = Rect::from_min_max(
pos2(canvas.left(), canvas.bottom()),
pos2(canvas.right(), canvas.bottom() + SCROLLBAR_WIDTH),
);
let vertical = Rect::from_min_max(
pos2(canvas.right(), canvas.top()),
pos2(canvas.right() + SCROLLBAR_WIDTH, canvas.bottom()),
);
view.center.x += scrollbar(ui, horizontal, true, bounds.x_range(), visible.x_range());
view.center.y += scrollbar(ui, vertical, false, bounds.y_range(), visible.y_range());
}
fn thumb_range(bounds: Rangef, visible: Rangef) -> (f32, f32) {
let fraction = |value: f32| ((value - bounds.min) / bounds.span()).clamp(0.0, 1.0);
(fraction(visible.min), fraction(visible.max))
}
fn scrollbar(ui: &mut Ui, rect: Rect, horizontal: bool, bounds: Rangef, visible: Rangef) -> f32 {
if !ui.is_rect_visible(rect) || bounds.span() <= 0.0 {
return 0.0;
}
let track = if horizontal {
rect.width()
} else {
rect.height()
};
let along = |fraction: f32| {
if horizontal {
rect.left() + fraction * track
} else {
rect.top() + fraction * track
}
};
let (start, end) = thumb_range(bounds, visible);
let (from, to) = (along(start), along(end).max(along(start) + 8.0));
let thumb = if horizontal {
Rect::from_min_max(pos2(from, rect.top() + 2.0), pos2(to, rect.bottom() - 2.0))
} else {
Rect::from_min_max(pos2(rect.left() + 2.0, from), pos2(rect.right() - 2.0, to))
};
let response = ui.interact(
rect,
ui.id().with(("scrollbar", horizontal)),
Sense::click_and_drag(),
);
let visuals = ui.visuals();
ui.painter()
.rect_filled(rect, 0.0, visuals.extreme_bg_color.gamma_multiply(0.5));
ui.painter().rect_filled(
thumb,
2.0,
if response.dragged() {
visuals.widgets.active.bg_fill
} else if response.hovered() {
visuals.widgets.hovered.bg_fill
} else {
visuals.widgets.inactive.bg_fill
},
);
let dragged = if horizontal {
response.drag_delta().x
} else {
response.drag_delta().y
};
dragged / track * bounds.span()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_thumb_measures_the_view_against_the_bounds() {
let bounds = Rangef::new(-1000.0, 3000.0);
assert_eq!(thumb_range(bounds, bounds), (0.0, 1.0));
assert_eq!(
thumb_range(bounds, Rangef::new(-1000.0, 1000.0)),
(0.0, 0.5)
);
assert_eq!(thumb_range(bounds, Rangef::new(1000.0, 3000.0)), (0.5, 1.0));
assert_eq!(
thumb_range(bounds, Rangef::new(-9_000.0, 9_000.0)),
(0.0, 1.0),
"a view wider than the bounds fills the track rather than running off it"
);
}
#[test]
fn a_middle_drag_moves_the_view_against_the_pointer() {
let mut view = SceneView {
center: pos2(1000.0, 1000.0),
zoom: Some(2.0),
};
pan_view(&mut view, Vec2::new(5.0, -10.0));
assert_eq!(view.center, pos2(995.0, 1010.0));
}
}