use crate::theme::MacTheme;
use egui::{CornerRadius, Id, Pos2, Rect, Response, Stroke, StrokeKind, Ui, Vec2};
use std::hash::Hash;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MacPopoverArrowEdge {
#[default]
Top,
Bottom,
Left,
Right,
None,
}
pub struct MacPopover<'a> {
id: Option<Id>,
theme: Option<&'a MacTheme>,
arrow_edge: MacPopoverArrowEdge,
arrow_offset: Option<f32>,
scale: f32,
alpha: f32,
}
impl<'a> Default for MacPopover<'a> {
fn default() -> Self {
Self {
id: None,
theme: None,
arrow_edge: MacPopoverArrowEdge::Top,
arrow_offset: None,
scale: 1.0,
alpha: 1.0,
}
}
}
impl<'a> MacPopover<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id_source: impl Hash + std::fmt::Debug) -> Self {
self.id = Some(Id::NULL.with(id_source));
self
}
pub fn theme(mut self, theme: &'a MacTheme) -> Self {
self.theme = Some(theme);
self
}
pub fn arrow_edge(mut self, edge: MacPopoverArrowEdge) -> Self {
self.arrow_edge = edge;
self
}
pub fn arrow_offset(mut self, offset: f32) -> Self {
self.arrow_offset = Some(offset);
self
}
pub fn scale(mut self, scale: f32) -> Self {
self.scale = scale;
self
}
pub fn alpha(mut self, alpha: f32) -> Self {
self.alpha = alpha;
self
}
pub fn show<R>(
self,
ui: &mut Ui,
rect: Rect,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> (Response, R) {
let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
let theme: &MacTheme = &theme_cow;
let metrics = theme.popover_metrics();
let corner_radius = metrics.corner_radius.points();
let border_width = metrics.border_width.points();
let arrow_w = metrics.arrow_base_width.points();
let arrow_h = metrics.arrow_height.points();
let center = rect.center();
let scaled_size = rect.size() * self.scale.max(f32::EPSILON);
let active_rect = Rect::from_center_size(center, scaled_size);
let popover_id = self.id.unwrap_or_else(|| {
ui.scope_id().with((
"mac_popover",
rect.min.x.to_bits(),
rect.min.y.to_bits(),
self.arrow_edge as u8,
))
});
let resp = ui.interact(active_rect, popover_id, egui::Sense::click_and_drag());
if ui.rect_contains_pointer(active_rect) {
ui.input_mut(|i| {
i.smooth_scroll_delta = Vec2::ZERO;
});
}
let bg_color = theme.window_background().gamma_multiply(self.alpha);
let border_color = theme.separator().gamma_multiply(self.alpha);
let stroke = Stroke::new(border_width, border_color);
let painter = ui.painter_at(active_rect);
painter.rect(
active_rect,
CornerRadius::same(corner_radius as u8),
bg_color,
stroke,
StrokeKind::Inside,
);
if self.arrow_edge != MacPopoverArrowEdge::None {
let arrow_points = match self.arrow_edge {
MacPopoverArrowEdge::Top => {
let ax = self.arrow_offset.unwrap_or(active_rect.center().x);
[Pos2::new(ax - arrow_w * 0.5, active_rect.min.y), Pos2::new(ax, active_rect.min.y - arrow_h), Pos2::new(ax + arrow_w * 0.5, active_rect.min.y)]
}
MacPopoverArrowEdge::Bottom => {
let ax = self.arrow_offset.unwrap_or(active_rect.center().x);
[Pos2::new(ax - arrow_w * 0.5, active_rect.max.y), Pos2::new(ax, active_rect.max.y + arrow_h), Pos2::new(ax + arrow_w * 0.5, active_rect.max.y)]
}
MacPopoverArrowEdge::Left => {
let ay = self.arrow_offset.unwrap_or(active_rect.center().y);
[Pos2::new(active_rect.min.x, ay - arrow_w * 0.5), Pos2::new(active_rect.min.x - arrow_h, ay), Pos2::new(active_rect.min.x, ay + arrow_w * 0.5)]
}
MacPopoverArrowEdge::Right | MacPopoverArrowEdge::None => {
let ay = self.arrow_offset.unwrap_or(active_rect.center().y);
[Pos2::new(active_rect.max.x, ay - arrow_w * 0.5), Pos2::new(active_rect.max.x + arrow_h, ay), Pos2::new(active_rect.max.x, ay + arrow_w * 0.5)]
}
};
painter.add(egui::Shape::convex_polygon(arrow_points.to_vec(), bg_color, Stroke::NONE));
painter.line_segment([arrow_points[0], arrow_points[1]], stroke);
painter.line_segment([arrow_points[1], arrow_points[2]], stroke);
painter.line_segment([arrow_points[0], arrow_points[2]], Stroke::new(border_width * 2.0, bg_color));
}
let mut child_ui = ui.new_child(
egui::UiBuilder::new()
.id_salt((popover_id, "mac_popover_content"))
.max_rect(active_rect.shrink(corner_radius.min(metrics.inner_padding.points())))
.layout(egui::Layout::top_down(egui::Align::Min)),
);
let ret = add_contents(&mut child_ui);
(resp, ret)
}
}
#[derive(Default)]
pub struct MacPopoverSurface<'a> {
id: Option<Id>,
theme: Option<&'a MacTheme>,
fixed_rect: Option<Rect>,
corner_radius: Option<f32>,
}
impl<'a> MacPopoverSurface<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id_source: impl Hash + std::fmt::Debug) -> Self {
self.id = Some(Id::NULL.with(id_source));
self
}
pub fn theme(mut self, theme: &'a MacTheme) -> Self {
self.theme = Some(theme);
self
}
pub fn fixed_rect(mut self, rect: Rect) -> Self {
self.fixed_rect = Some(rect);
self
}
pub fn corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = Some(radius);
self
}
pub fn paint_at(self, ui: &mut Ui, rect: Rect) -> Response {
self.fixed_rect(rect).show(ui, |_| {}).response
}
pub fn show<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> egui::InnerResponse<R> {
let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
let theme: &MacTheme = &theme_cow;
let metrics = theme.popover_metrics();
let rect = self.fixed_rect.unwrap_or_else(|| {
let size = Vec2::new(metrics.full_popover_width.points(), metrics.full_popover_height.points());
ui.allocate_exact_size(size, egui::Sense::hover()).0
});
let surface_id = self.id.unwrap_or_else(|| {
ui.scope_id().with(("mac_popover_surface", rect.min.x.to_bits(), rect.min.y.to_bits()))
});
let response = ui.interact(rect, surface_id, egui::Sense::click_and_drag());
let rounding = self.corner_radius.unwrap_or_else(|| metrics.surface_corner_radius.points());
let border_w = metrics.surface_border_width.points();
let bg_fill = theme.popover_background();
let border_stroke = Stroke::new(border_w, theme.popover_border());
ui.painter_at(rect).rect(
rect,
CornerRadius::same(rounding as u8),
bg_fill,
border_stroke,
StrokeKind::Inside,
);
let pad = metrics.inner_padding.points();
let content_rect = rect.shrink2(Vec2::new(pad.min(rect.width() * 0.5), pad.min(rect.height() * 0.5)));
let mut child_ui = ui.new_child(
egui::UiBuilder::new()
.id_salt((surface_id, "mac_popover_surface_content"))
.max_rect(content_rect)
.layout(egui::Layout::top_down(egui::Align::Min)),
);
child_ui.visuals_mut().panel_fill = bg_fill;
let surface_slot = theme.register_surface_rect_slot(ui.ctx(), rect, bg_fill);
let inner = add_contents(&mut child_ui);
theme.update_surface_rect_slot(ui.ctx(), surface_slot, rect);
egui::InnerResponse::new(inner, response)
}
}