use crate::controls::MacControlSize;
use crate::layout::subpixel_snap_rect;
use crate::symbols::MacSymbol;
use crate::theme::MacTheme;
use egui::{CornerRadius, FontId, Id, Pos2, Rect, Response, Sense, Stroke, StrokeKind, Ui, Vec2};
use std::hash::Hash;
#[derive(Debug, Clone)]
pub struct MacTableActionResponse {
pub response: Response,
pub add_clicked: bool,
pub remove_clicked: bool,
pub action_clicked: bool,
}
pub struct MacTableActionBar<'a> {
id: Id,
can_add: bool,
can_remove: bool,
with_action_menu: bool,
fill_remaining_strip: bool,
control_size: MacControlSize,
fixed_rect: Option<Rect>,
enabled: bool,
theme: Option<&'a MacTheme>,
}
impl<'a> MacTableActionBar<'a> {
pub fn new(id_source: impl Hash) -> Self {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
id_source.hash(&mut hasher);
Self {
id: Id::NULL.with(std::hash::Hasher::finish(&hasher)),
can_add: true,
can_remove: true,
with_action_menu: false,
fill_remaining_strip: true,
control_size: MacControlSize::Regular,
fixed_rect: None,
enabled: true,
theme: None,
}
}
pub fn can_add(mut self, can_add: bool) -> Self {
self.can_add = can_add;
self
}
pub fn can_remove(mut self, can_remove: bool) -> Self {
self.can_remove = can_remove;
self
}
pub fn with_action_menu(mut self, with_action_menu: bool) -> Self {
self.with_action_menu = with_action_menu;
self
}
pub fn fill_remaining_strip(mut self, fill_remaining: bool) -> Self {
self.fill_remaining_strip = fill_remaining;
self
}
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 button_center(rect: Rect, index: usize, button_width: f32) -> Pos2 {
Pos2::new(rect.min.x + ((index as f32) + 0.5) * button_width, rect.center().y)
}
pub fn action_button_center(rect: Rect, button_width: f32, action_button_width: f32) -> Pos2 {
Pos2::new(
rect.min.x + button_width * 2.0 + action_button_width * 0.5,
rect.center().y,
)
}
pub fn show(self, ui: &mut Ui) -> MacTableActionResponse {
let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
let theme: &MacTheme = &theme_cow;
let ab = theme.table_action_bar();
let (bar_h, btn_w, act_w, glyph_sz, arm_half) = match self.control_size {
MacControlSize::Regular | MacControlSize::Large => (
ab.bar_height.points(), ab.button_width.points(), ab.action_button_width.points(),
ab.glyph_size.points(), ab.glyph_arm_half.points(),
),
MacControlSize::Small | MacControlSize::CompactInfobox => (
ab.small_bar_height.points(), ab.small_button_width.points(), ab.small_action_button_width.points(),
ab.small_glyph_size.points(), ab.small_glyph_arm_half.points(),
),
MacControlSize::Mini => (
ab.mini_bar_height.points(), ab.mini_button_width.points(), ab.mini_action_button_width.points(),
ab.small_glyph_size.points(), ab.small_glyph_arm_half.points(),
),
};
let cluster_w = if self.with_action_menu { btn_w * 2.0 + act_w } else { btn_w * 2.0 };
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);
(snapped, ui.interact(snapped, self.id.with("bar"), sense))
} else {
let width = if self.fill_remaining_strip { ui.available_width().max(cluster_w) } else { cluster_w };
let (r, resp) = ui.allocate_exact_size(Vec2::new(width, bar_h), sense);
(subpixel_snap_rect(r, ppp), resp)
};
let visual_rect = theme.clamp_nib_visual_rect(rect, bar_h, ppp);
let add_rect = Rect::from_min_size(visual_rect.min, Vec2::new(btn_w, visual_rect.height())).intersect(visual_rect);
let rem_rect = Rect::from_min_size(Pos2::new(visual_rect.min.x + btn_w, visual_rect.min.y), Vec2::new(btn_w, visual_rect.height())).intersect(visual_rect);
let act_rect = Rect::from_min_size(Pos2::new(visual_rect.min.x + btn_w * 2.0, visual_rect.min.y), Vec2::new(act_w, visual_rect.height())).intersect(visual_rect);
let (mut add_clicked, mut remove_clicked, mut action_clicked) = (false, false, false);
if enabled && response.clicked() {
if let Some(pos) = response.interact_pointer_pos() {
if self.can_add && add_rect.is_positive() && add_rect.contains(pos) {
add_clicked = true;
} else if self.can_remove && rem_rect.is_positive() && rem_rect.contains(pos) {
remove_clicked = true;
} else if self.with_action_menu && act_rect.is_positive() && act_rect.contains(pos) {
action_clicked = true;
}
}
if add_clicked || remove_clicked || action_clicked {
response.mark_changed();
}
}
let alpha = if enabled { 1.0 } else { theme.disabled_opacity() };
let painter = ui.painter_at(visual_rect);
let radius = CornerRadius::same(ab.corner_radius.points() as u8);
let border_w = ab.border_width.points();
let border_stroke = Stroke::new(border_w, theme.separator().gamma_multiply(alpha));
painter.rect_filled(visual_rect, radius, theme.box_background().gamma_multiply(alpha));
let active_cluster_rect = Rect::from_min_size(
visual_rect.min,
Vec2::new(cluster_w.min(visual_rect.width()), visual_rect.height()),
);
painter.rect_filled(active_cluster_rect, radius, theme.control().gamma_multiply(alpha));
let hover_pos = ui.input(|i| i.pointer.hover_pos());
let pointer_down = response.is_pointer_button_down_on();
for (seg_r, seg_ok) in [
(add_rect, enabled && self.can_add),
(rem_rect, enabled && self.can_remove),
(act_rect, enabled && self.with_action_menu),
] {
if seg_ok && seg_r.is_positive() && hover_pos.is_some_and(|p| seg_r.contains(p)) {
let hi = if pointer_down {
theme.selected_control_inactive()
} else {
theme.control_background()
};
painter.rect_filled(seg_r, CornerRadius::ZERO, hi);
}
}
let div_xs = [
visual_rect.min.x + btn_w,
visual_rect.min.x + btn_w * 2.0,
visual_rect.min.x + cluster_w,
];
let div_count = if self.with_action_menu { 3 } else { 2 };
for &x in &div_xs[..div_count] {
if x < visual_rect.max.x - border_w {
painter.line_segment(
[Pos2::new(x, visual_rect.top()), Pos2::new(x, visual_rect.bottom())],
border_stroke,
);
}
}
painter.rect_stroke(visual_rect, radius, border_stroke, StrokeKind::Inside);
let surface = theme.control();
let base_fg = theme.label_on(surface);
let glyph_w = ab.glyph_stroke_width.points();
if add_rect.width() >= btn_w * 0.5 {
let add_alpha = if enabled && self.can_add { 1.0 } else { theme.disabled_opacity() };
let add_stroke = Stroke::new(glyph_w, base_fg.gamma_multiply(add_alpha));
let ac = add_rect.center();
painter.line_segment([Pos2::new(ac.x - arm_half, ac.y), Pos2::new(ac.x + arm_half, ac.y)], add_stroke);
painter.line_segment([Pos2::new(ac.x, ac.y - arm_half), Pos2::new(ac.x, ac.y + arm_half)], add_stroke);
}
if rem_rect.width() >= btn_w * 0.5 {
let rem_alpha = if enabled && self.can_remove { 1.0 } else { theme.disabled_opacity() };
let rem_stroke = Stroke::new(glyph_w, base_fg.gamma_multiply(rem_alpha));
let rc = rem_rect.center();
painter.line_segment([Pos2::new(rc.x - arm_half, rc.y), Pos2::new(rc.x + arm_half, rc.y)], rem_stroke);
}
if self.with_action_menu && act_rect.width() >= act_w * 0.5 {
let act_fg = base_fg.gamma_multiply(alpha);
let galley = theme.layout_tracked_galley(
ui,
MacSymbol::Gear.as_str(),
FontId::proportional(glyph_sz),
act_fg,
f32::INFINITY,
);
let gap = ab.action_chevron_gap.points();
let chw = ab.action_chevron_half_width.points();
let ch = ab.action_chevron_height.points();
let total_w = galley.size().x + gap + chw * 2.0;
let start_x = act_rect.center().x - total_w * 0.5;
let gear_pos = Pos2::new(start_x, act_rect.center().y - galley.size().y * 0.5);
painter.galley(gear_pos, galley, act_fg);
let (cx, cy) = (start_x + total_w - chw, act_rect.center().y);
let chev_stroke = Stroke::new(glyph_w, act_fg);
painter.line_segment([Pos2::new(cx - chw, cy - ch * 0.5), Pos2::new(cx, cy + ch * 0.5)], chev_stroke);
painter.line_segment([Pos2::new(cx, cy + ch * 0.5), Pos2::new(cx + chw, cy - ch * 0.5)], chev_stroke);
}
MacTableActionResponse { response, add_clicked, remove_clicked, action_clicked }
}
}