use std::fmt;
use egui::{emath::Rot2, vec2, Pos2, Rect, Ui, Vec2};
use super::SnarlStyle;
pub struct Viewport {
pub rect: Rect,
pub scale: f32,
pub offset: Vec2,
}
impl Viewport {
#[inline(always)]
pub fn screen_pos_to_graph(&self, pos: Pos2) -> Pos2 {
(pos + self.offset - self.rect.center().to_vec2()) / self.scale
}
#[inline(always)]
pub fn graph_pos_to_screen(&self, pos: Pos2) -> Pos2 {
pos * self.scale - self.offset + self.rect.center().to_vec2()
}
#[inline(always)]
pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 {
size * self.scale
}
#[inline(always)]
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
size / self.scale
}
#[inline(always)]
pub fn graph_size_to_screen(&self, size: f32) -> f32 {
size * self.scale
}
#[inline(always)]
pub fn screen_size_to_graph(&self, size: f32) -> f32 {
size / self.scale
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
pub struct Grid {
pub spacing: Vec2,
#[cfg_attr(feature = "egui-probe", egui_probe(as egui_probe::angle))]
pub angle: f32,
}
const DEFAULT_GRID_SPACING: Vec2 = vec2(50.0, 50.0);
macro_rules! default_grid_spacing {
() => {
stringify!(vec2(50.0, 50.0))
};
}
const DEFAULT_GRID_ANGLE: f32 = 1.0;
macro_rules! default_grid_angle {
() => {
stringify!(1.0)
};
}
impl Default for Grid {
fn default() -> Self {
Self {
spacing: DEFAULT_GRID_SPACING,
angle: DEFAULT_GRID_ANGLE,
}
}
}
impl Grid {
pub const fn new(spacing: Vec2, angle: f32) -> Self {
Self { spacing, angle }
}
fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) {
let bg_stroke = style.get_bg_pattern_stroke(viewport.scale, ui);
let spacing = vec2(self.spacing.x.max(1.0), self.spacing.y.max(1.0));
let rot = Rot2::from_angle(self.angle);
let rot_inv = rot.inverse();
let graph_viewport = Rect::from_min_max(
viewport.screen_pos_to_graph(viewport.rect.min),
viewport.screen_pos_to_graph(viewport.rect.max),
);
let pattern_bounds = graph_viewport.rotate_bb(rot_inv);
let min_x = (pattern_bounds.min.x / spacing.x).ceil();
let max_x = (pattern_bounds.max.x / spacing.x).floor();
for x in 0..=(max_x - min_x) as i64 {
#[allow(clippy::cast_possible_truncation)]
let x = (x as f32 + min_x) * spacing.x;
let top = (rot * vec2(x, pattern_bounds.min.y)).to_pos2();
let bottom = (rot * vec2(x, pattern_bounds.max.y)).to_pos2();
let top = viewport.graph_pos_to_screen(top);
let bottom = viewport.graph_pos_to_screen(bottom);
ui.painter().line_segment([top, bottom], bg_stroke);
}
let min_y = (pattern_bounds.min.y / spacing.y).ceil();
let max_y = (pattern_bounds.max.y / spacing.y).floor();
for y in 0..=(max_y - min_y) as i64 {
#[allow(clippy::cast_possible_truncation)]
let y = (y as f32 + min_y) * spacing.y;
let top = (rot * vec2(pattern_bounds.min.x, y)).to_pos2();
let bottom = (rot * vec2(pattern_bounds.max.x, y)).to_pos2();
let top = viewport.graph_pos_to_screen(top);
let bottom = viewport.graph_pos_to_screen(bottom);
ui.painter().line_segment([top, bottom], bg_stroke);
}
}
}
tiny_fn::tiny_fn! {
pub struct CustomBackground = Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui);
}
impl<const INLINE_SIZE: usize> Default for CustomBackground<'_, INLINE_SIZE> {
fn default() -> Self {
Self::new(|_, _, _| {})
}
}
#[cfg(feature = "egui-probe")]
impl<const INLINE_SIZE: usize> egui_probe::EguiProbe for CustomBackground<'_, INLINE_SIZE> {
fn probe(
&mut self,
ui: &mut egui_probe::egui::Ui,
_style: &egui_probe::Style,
) -> egui_probe::egui::Response {
ui.weak("Custom")
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
pub enum BackgroundPattern {
NoPattern,
#[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
Grid(Grid),
#[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
Custom(#[cfg_attr(feature = "serde", serde(skip))] CustomBackground<'static>),
}
impl PartialEq for BackgroundPattern {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(BackgroundPattern::Grid(l), BackgroundPattern::Grid(r)) => *l == *r,
_ => false,
}
}
}
impl fmt::Debug for BackgroundPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BackgroundPattern::Grid(grid) => f
.debug_tuple("BackgroundPattern::Grid")
.field(grid)
.finish(),
BackgroundPattern::Custom(_) => f.write_str("BackgroundPattern::Custom"),
BackgroundPattern::NoPattern => f.write_str("BackgroundPattern::NoPattern"),
}
}
}
impl Default for BackgroundPattern {
fn default() -> Self {
BackgroundPattern::new()
}
}
impl BackgroundPattern {
#[doc = default_grid_spacing!()]
#[doc = default_grid_angle!()]
pub const fn new() -> Self {
Self::Grid(Grid::new(DEFAULT_GRID_SPACING, DEFAULT_GRID_ANGLE))
}
pub const fn grid(spacing: Vec2, angle: f32) -> Self {
Self::Grid(Grid::new(spacing, angle))
}
pub fn custom<F>(f: F) -> Self
where
F: Fn(&SnarlStyle, &Viewport, &mut Ui) + 'static,
{
Self::Custom(CustomBackground::new(f))
}
pub(super) fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) {
match self {
BackgroundPattern::Grid(g) => g.draw(style, viewport, ui),
BackgroundPattern::Custom(c) => c.call(style, viewport, ui),
BackgroundPattern::NoPattern => {}
}
}
}