use egui::{Id, Pos2, Rect, Response, Sense, Ui, UiBuilder};
#[derive(Clone, Copy)]
pub(crate) enum CellSize {
Absolute(f32),
Remainder,
}
pub(crate) enum CellDirection {
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Default)]
pub(crate) struct StripLayoutFlags {
pub(crate) clip: bool,
pub(crate) striped: bool,
pub(crate) hovered: bool,
pub(crate) selected: bool,
pub(crate) sizing_pass: bool,
}
pub struct StripLayout<'l> {
pub(crate) ui: &'l mut Ui,
direction: CellDirection,
pub(crate) rect: Rect,
pub(crate) cursor: Pos2,
max: Pos2,
cell_layout: egui::Layout,
sense: Sense,
}
impl<'l> StripLayout<'l> {
pub(crate) fn new(
ui: &'l mut Ui,
direction: CellDirection,
cell_layout: egui::Layout,
sense: Sense,
) -> Self {
let rect = ui.available_rect_before_wrap();
let pos = rect.left_top();
Self {
ui,
direction,
rect,
cursor: pos,
max: pos,
cell_layout,
sense,
}
}
fn cell_rect(&self, width: &CellSize, height: &CellSize) -> Rect {
Rect {
min: self.cursor,
max: Pos2 {
x: match width {
CellSize::Absolute(width) => self.cursor.x + width,
CellSize::Remainder => self.rect.right(),
},
y: match height {
CellSize::Absolute(height) => self.cursor.y + height,
CellSize::Remainder => self.rect.bottom(),
},
},
}
}
fn set_pos(&mut self, rect: Rect) {
self.max.x = self.max.x.max(rect.right());
self.max.y = self.max.y.max(rect.bottom());
match self.direction {
CellDirection::Horizontal => {
self.cursor.x = rect.right() + self.ui.spacing().item_spacing.x;
}
CellDirection::Vertical => {
self.cursor.y = rect.bottom() + self.ui.spacing().item_spacing.y;
}
}
}
pub(crate) fn empty(&mut self, width: CellSize, height: CellSize) {
self.set_pos(self.cell_rect(&width, &height));
}
pub(crate) fn add(
&mut self,
flags: StripLayoutFlags,
width: CellSize,
height: CellSize,
child_ui_id_salt: Id,
add_cell_contents: impl FnOnce(&mut Ui),
) -> (Rect, Response) {
let max_rect = self.cell_rect(&width, &height);
let item_spacing = self.ui.spacing().item_spacing;
let gapless_rect = max_rect.expand2(0.5 * item_spacing);
if flags.striped {
self.ui.painter().rect_filled(
gapless_rect,
egui::Rounding::ZERO,
self.ui.visuals().faint_bg_color,
);
}
if flags.selected {
self.ui.painter().rect_filled(
gapless_rect,
egui::Rounding::ZERO,
self.ui.visuals().selection.bg_fill,
);
}
if flags.hovered && !flags.selected && self.sense.interactive() {
self.ui.painter().rect_filled(
gapless_rect,
egui::Rounding::ZERO,
self.ui.visuals().widgets.hovered.bg_fill,
);
}
let mut child_ui = self.cell(flags, max_rect, child_ui_id_salt, add_cell_contents);
let used_rect = child_ui.min_rect();
child_ui.set_min_size(max_rect.size());
let allocation_rect = if self.ui.is_sizing_pass() {
used_rect
} else if flags.clip {
max_rect
} else {
max_rect.union(used_rect)
};
self.set_pos(allocation_rect);
self.ui.advance_cursor_after_rect(allocation_rect);
let response = child_ui.response();
(used_rect, response)
}
pub fn end_line(&mut self) {
match self.direction {
CellDirection::Horizontal => {
self.cursor.y = self.max.y + self.ui.spacing().item_spacing.y;
self.cursor.x = self.rect.left();
}
CellDirection::Vertical => {
self.cursor.x = self.max.x + self.ui.spacing().item_spacing.x;
self.cursor.y = self.rect.top();
}
}
}
pub(crate) fn skip_space(&mut self, delta: egui::Vec2) {
let before = self.cursor;
self.cursor += delta;
let rect = Rect::from_two_pos(before, self.cursor);
self.ui.allocate_rect(rect, Sense::hover());
}
fn cell(
&mut self,
flags: StripLayoutFlags,
max_rect: Rect,
child_ui_id_salt: egui::Id,
add_cell_contents: impl FnOnce(&mut Ui),
) -> Ui {
let mut ui_builder = UiBuilder::new()
.id_salt(child_ui_id_salt)
.ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
.max_rect(max_rect)
.layout(self.cell_layout)
.sense(self.sense);
if flags.sizing_pass {
ui_builder = ui_builder.sizing_pass();
}
let mut child_ui = self.ui.new_child(ui_builder);
if flags.clip {
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
let clip_rect = max_rect.expand2(margin);
child_ui.shrink_clip_rect(clip_rect);
if !child_ui.is_sizing_pass() {
child_ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
}
}
if flags.selected {
let stroke_color = child_ui.style().visuals.selection.stroke.color;
child_ui.style_mut().visuals.override_text_color = Some(stroke_color);
}
add_cell_contents(&mut child_ui);
child_ui
}
pub fn allocate_rect(&mut self) -> Response {
let mut rect = self.rect;
rect.set_right(self.max.x);
rect.set_bottom(self.max.y);
self.ui.allocate_rect(rect, Sense::hover())
}
}