use crate::controls::MacControlSize;
use crate::layout::subpixel_snap_rect;
use crate::theme::MacTheme;
use egui::{Key, Pos2, Rect, Response, Sense, Stroke, Ui, Vec2, Widget};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum MacOriginAnchor {
TopLeft = 0,
TopCenter = 1,
TopRight = 2,
MiddleLeft = 3,
#[default]
Center = 4,
MiddleRight = 5,
BottomLeft = 6,
BottomCenter = 7,
BottomRight = 8,
}
impl MacOriginAnchor {
pub const ALL: [Self; 9] = [
Self::TopLeft, Self::TopCenter, Self::TopRight,
Self::MiddleLeft, Self::Center, Self::MiddleRight,
Self::BottomLeft, Self::BottomCenter, Self::BottomRight,
];
pub const fn index(self) -> usize { self as usize }
pub const fn from_index(index: usize) -> Self {
match index {
0 => Self::TopLeft,
1 => Self::TopCenter,
2 => Self::TopRight,
3 => Self::MiddleLeft,
4 => Self::Center,
5 => Self::MiddleRight,
6 => Self::BottomLeft,
7 => Self::BottomCenter,
8 => Self::BottomRight,
_ => Self::Center,
}
}
pub const fn row(self) -> usize { (self as usize) / 3 }
pub const fn col(self) -> usize { (self as usize) % 3 }
pub const fn from_row_col(row: usize, col: usize) -> Self {
let r = if row > 2 { 2 } else { row };
let c = if col > 2 { 2 } else { col };
Self::from_index(r * 3 + c)
}
}
enum OriginBinding<'a> {
Anchor(&'a mut MacOriginAnchor),
Index(&'a mut usize),
}
impl OriginBinding<'_> {
fn get(&self) -> MacOriginAnchor {
match self {
Self::Anchor(a) => **a,
Self::Index(idx) => MacOriginAnchor::from_index(**idx),
}
}
fn set(&mut self, anchor: MacOriginAnchor) {
match self {
Self::Anchor(a) => **a = anchor,
Self::Index(idx) => **idx = anchor.index(),
}
}
}
pub struct MacOriginMatrix<'a> {
binding: OriginBinding<'a>,
control_size: MacControlSize,
fixed_rect: Option<Rect>,
enabled: bool,
theme: Option<&'a MacTheme>,
}
impl<'a> MacOriginMatrix<'a> {
pub fn new(selected: &'a mut MacOriginAnchor) -> Self {
Self {
binding: OriginBinding::Anchor(selected),
control_size: MacControlSize::Regular,
fixed_rect: None,
enabled: true,
theme: None,
}
}
pub fn from_index(selected_index: &'a mut usize) -> Self {
Self {
binding: OriginBinding::Index(selected_index),
control_size: MacControlSize::Regular,
fixed_rect: None,
enabled: true,
theme: None,
}
}
pub fn control_size(mut self, size: MacControlSize) -> Self {
self.control_size = size;
self
}
pub fn fixed_rect(mut self, rect: Rect) -> Self {
self.fixed_rect = Some(rect);
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn theme(mut self, theme: &'a MacTheme) -> Self {
self.theme = Some(theme);
self
}
pub fn node_center(rect: Rect, anchor: MacOriginAnchor, cell_step: f32) -> Pos2 {
let c = rect.center();
let dx = (anchor.col() as f32 - 1.0) * cell_step;
let dy = (anchor.row() as f32 - 1.0) * cell_step;
Pos2::new(c.x + dx, c.y + dy)
}
pub fn show(self, ui: &mut Ui) -> Response {
ui.add(self)
}
}
impl Widget for MacOriginMatrix<'_> {
fn ui(mut self, ui: &mut Ui) -> Response {
let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
let theme: &MacTheme = &theme_cow;
let om = theme.origin_matrix();
let (grid_sz, cell_step) = match self.control_size {
MacControlSize::Regular | MacControlSize::Large => (om.grid_size.points(), om.cell_step.points()),
MacControlSize::Small | MacControlSize::CompactInfobox => (om.small_grid_size.points(), om.small_cell_step.points()),
MacControlSize::Mini => (om.mini_grid_size.points(), om.mini_cell_step.points()),
};
let enabled = self.enabled && ui.is_enabled();
let sense = if enabled { Sense::click() } else { Sense::hover() };
let ppp = ui.ctx().pixels_per_point();
let (rect, mut response) = if let Some(fixed) = self.fixed_rect {
let snapped = subpixel_snap_rect(fixed, ppp);
let id = ui.next_auto_id().with("mac_origin_matrix_fixed");
ui.skip_ahead_auto_ids(1);
(snapped, ui.interact(snapped, id, sense))
} else {
let (r, resp) = ui.allocate_exact_size(Vec2::splat(grid_sz), sense);
(subpixel_snap_rect(r, ppp), resp)
};
let mut active = self.binding.get();
self.binding.set(active);
if enabled && response.clicked() {
let click_pos = response.interact_pointer_pos().unwrap_or(rect.center());
let (mut best, mut best_dist) = (active, f32::INFINITY);
for candidate in MacOriginAnchor::ALL {
let d = Self::node_center(rect, candidate, cell_step).distance(click_pos);
if d < best_dist {
best_dist = d;
best = candidate;
}
}
if best_dist <= cell_step {
let changed = best != active;
active = best;
self.binding.set(active);
response.request_focus();
if changed {
response.mark_changed();
}
}
}
if enabled && response.has_focus() {
let (mut r, mut c) = (active.row(), active.col());
ui.input(|i| {
if i.key_pressed(Key::ArrowLeft) { c = c.saturating_sub(1); }
if i.key_pressed(Key::ArrowRight) { c = (c + 1).min(2); }
if i.key_pressed(Key::ArrowUp) { r = r.saturating_sub(1); }
if i.key_pressed(Key::ArrowDown) { r = (r + 1).min(2); }
});
let next = MacOriginAnchor::from_row_col(r, c);
if next != active {
active = next;
self.binding.set(active);
response.mark_changed();
}
}
let alpha = if enabled { 1.0 } else { theme.disabled_opacity() };
let painter = ui.painter_at(rect);
let visual_rect = theme.clamp_nib_visual_rect(rect, grid_sz, ppp);
if enabled && response.has_focus() {
theme.paint_focus_ring(&painter, visual_rect, om.node_radius.points());
}
let surface = theme.active_surface_at(ui, rect.center());
let line_col = theme.tertiary_label_on(surface).gamma_multiply(alpha);
let line_stroke = Stroke::new(om.line_stroke_width.points(), line_col);
for edge in [0usize, 2usize] {
let r_start = Self::node_center(rect, MacOriginAnchor::from_row_col(edge, 0), cell_step);
let r_end = Self::node_center(rect, MacOriginAnchor::from_row_col(edge, 2), cell_step);
painter.line_segment([r_start, r_end], line_stroke);
let c_start = Self::node_center(rect, MacOriginAnchor::from_row_col(0, edge), cell_step);
let c_end = Self::node_center(rect, MacOriginAnchor::from_row_col(2, edge), cell_step);
painter.line_segment([c_start, c_end], line_stroke);
}
let hover_pos = ui.input(|i| i.pointer.hover_pos());
let widget_hovered = enabled && (response.hovered() || hover_pos.is_some_and(|hp| rect.contains(hp)));
for anchor in MacOriginAnchor::ALL {
let pos = Self::node_center(rect, anchor, cell_step);
if anchor == active {
painter.circle_filled(
pos,
om.active_node_radius.points(),
theme.control_accent().gamma_multiply(alpha),
);
} else {
let hovered = widget_hovered && hover_pos.is_some_and(|hp| hp.distance(pos) <= cell_step * 0.5);
let rad = if hovered { om.hover_node_radius.points() } else { om.node_radius.points() };
let fill_col = if hovered { theme.label_on(surface) } else { theme.secondary_label_on(surface) };
painter.circle_filled(pos, rad, fill_col.gamma_multiply(alpha));
}
}
response
}
}