use crate::theme::MacTheme;
use egui::{
Align, Align2, Color32, CornerRadius, FontId, Label, Layout, Pos2, Rect, RichText, Sense,
Stroke, StrokeKind, Ui, UiBuilder, Vec2,
};
pub use crate::tokens::CANVAS_FOOTER_HEIGHT;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MacCanvasFooterAction {
SelectZoom(f32),
BreadcrumbClicked(usize),
}
pub struct MacCanvasFooter<'a> {
breadcrumbs: &'a [&'a str],
cursor_pos: Option<Pos2>,
zoom_factor: f32,
theme: Option<&'a MacTheme>,
height: f32,
}
impl<'a> MacCanvasFooter<'a> {
pub fn new(breadcrumbs: &'a [&'a str], cursor_pos: Option<Pos2>, zoom_factor: f32) -> Self {
Self {
breadcrumbs,
cursor_pos,
zoom_factor,
theme: None,
height: CANVAS_FOOTER_HEIGHT,
}
}
pub fn theme(mut self, theme: &'a MacTheme) -> Self {
self.theme = Some(theme);
self
}
pub fn height(mut self, h: f32) -> Self {
self.height = h;
self
}
pub fn get_height(&self) -> f32 {
self.height
}
pub fn show(self, ui: &mut Ui, container_width: f32) -> Option<MacCanvasFooterAction> {
let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
let theme: &MacTheme = &theme_cow;
let fm = theme.footer_metrics();
let footer_height = self.height;
let border_w = theme.popover_metrics().border_width.points();
let (rect, _response) =
ui.allocate_exact_size(Vec2::new(container_width, footer_height), Sense::hover());
ui.painter()
.rect_filled(rect, CornerRadius::ZERO, theme.window_background());
ui.painter().line_segment(
[rect.left_top(), rect.right_top()],
Stroke::new(border_w, theme.separator()),
);
let item_spacing = theme.stack_view().default_spacing.points();
let font_id = FontId::proportional(fm.font_size.points());
let text_color = theme.secondary_label();
let on_accent = theme.on_accent_text();
let mut action = None;
let zoom_presets = [(2.0f32, "200%"), (1.0f32, "100%"), (0.5f32, "50%")];
let preset_btn_width = fm.preset_width.points();
let preset_btn_height = fm.preset_height.points();
let preset_corner_r = fm.preset_corner_radius.points() as u8;
let preset_font_pt = fm.preset_font_size.points();
let zoom_gap = fm.preset_spacing.points();
let zoom_cluster_width =
(preset_btn_width * (zoom_presets.len() as f32)) + (zoom_gap * 2.0);
let zoom_left =
(rect.right() - item_spacing - zoom_cluster_width).max(rect.left() + item_spacing);
let zoom_area = Rect::from_min_max(
Pos2::new(zoom_left, rect.top()),
Pos2::new(rect.right() - item_spacing, rect.bottom()),
);
if zoom_area.width() >= zoom_cluster_width - border_w {
let mut zoom_ui = ui.new_child(
UiBuilder::new()
.id_salt("footer_zoom_ui")
.max_rect(zoom_area)
.layout(Layout::right_to_left(Align::Center)),
);
for (z_idx, &(preset, label)) in zoom_presets.iter().enumerate() {
let (btn_rect, resp) = zoom_ui.allocate_exact_size(
Vec2::new(preset_btn_width, preset_btn_height),
Sense::click(),
);
let is_selected = (self.zoom_factor - preset).abs() <= f32::EPSILON;
let bg = if is_selected {
theme.control_accent()
} else if resp.hovered() {
theme.control()
} else {
Color32::TRANSPARENT
};
let fg = if is_selected {
on_accent
} else {
theme.label()
};
zoom_ui.painter().rect(
btn_rect,
CornerRadius::same(preset_corner_r),
bg,
Stroke::NONE,
StrokeKind::Inside,
);
zoom_ui.painter().text(
btn_rect.center(),
Align2::CENTER_CENTER,
label,
FontId::proportional(preset_font_pt),
fg,
);
if resp.clicked() {
action = Some(MacCanvasFooterAction::SelectZoom(preset));
}
if z_idx + 1 < zoom_presets.len() {
zoom_ui.add_space(zoom_gap);
}
}
}
let mut crumb_end_x = rect.left() + item_spacing;
let coord_full_w = fm.divider_trailing_offset.points();
let coord_compact_w = fm.coords_width.points();
let crumb_ratio = theme.split_view().default_ratio.points();
if !self.breadcrumbs.is_empty() {
let max_avail_for_crumbs =
(zoom_left - crumb_end_x - item_spacing * 2.0 - coord_full_w).max(0.0);
let crumb_max_w = (container_width * crumb_ratio).min(max_avail_for_crumbs);
if crumb_max_w > item_spacing {
let crumb_rect = Rect::from_min_size(
Pos2::new(crumb_end_x, rect.top()),
Vec2::new(crumb_max_w, footer_height),
);
let mut crumb_ui = ui.new_child(
UiBuilder::new()
.id_salt("footer_crumb_ui")
.max_rect(crumb_rect)
.layout(Layout::left_to_right(Align::Center)),
);
crumb_ui.set_clip_rect(crumb_rect.intersect(ui.clip_rect()));
crumb_ui.spacing_mut().item_spacing.x = zoom_gap * 2.0;
for (idx, crumb) in self.breadcrumbs.iter().enumerate() {
if idx > 0 {
crumb_ui.add(
Label::new(
RichText::new("›")
.font(font_id.clone())
.color(theme.placeholder_text()),
)
.selectable(false),
);
}
let seg_resp = crumb_ui.add(
Label::new(
RichText::new(*crumb)
.font(font_id.clone())
.color(text_color),
)
.sense(Sense::click())
.truncate(),
);
if seg_resp.hovered() {
ui.painter().line_segment(
[
Pos2::new(seg_resp.rect.left(), seg_resp.rect.bottom()),
Pos2::new(seg_resp.rect.right(), seg_resp.rect.bottom()),
],
Stroke::new(border_w, theme.label()),
);
}
if seg_resp.clicked() {
action = Some(MacCanvasFooterAction::BreadcrumbClicked(idx));
}
}
crumb_end_x += crumb_max_w;
}
}
if let Some(pos) = self.cursor_pos {
let center_min_x = crumb_end_x + item_spacing;
let center_max_x = zoom_left - item_spacing;
let avail_w = center_max_x - center_min_x;
let coord_font_pt = fm.font_size.points();
if avail_w >= coord_compact_w {
let (coord_text, needed_w) = if avail_w >= coord_full_w {
(
format!("X: {:.1} pt Y: {:.1} pt", pos.x, pos.y),
coord_full_w,
)
} else {
(format!("{:.0}, {:.0} pt", pos.x, pos.y), coord_compact_w)
};
let center_x = rect
.center()
.x
.clamp(center_min_x + needed_w * 0.5, center_max_x - needed_w * 0.5);
let clip_rect = Rect::from_min_max(
Pos2::new(center_min_x, rect.top()),
Pos2::new(center_max_x, rect.bottom()),
);
ui.painter_at(clip_rect).text(
Pos2::new(center_x, rect.center().y),
Align2::CENTER_CENTER,
coord_text,
FontId::monospace(coord_font_pt),
theme.secondary_label(),
);
}
}
action
}
}