use egui::{vec2, Rect};
use crate::{
is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction,
TileId, Tiles, Tree,
};
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Tabs {
pub children: Vec<TileId>,
pub active: Option<TileId>,
}
impl Tabs {
pub fn new(children: Vec<TileId>) -> Self {
let active = children.first().copied();
Self { children, active }
}
pub fn add_child(&mut self, child: TileId) {
self.children.push(child);
}
pub fn set_active(&mut self, child: TileId) {
self.active = Some(child);
}
pub fn is_active(&self, child: TileId) -> bool {
Some(child) == self.active
}
pub(super) fn layout<Pane>(
&mut self,
tiles: &mut Tiles<Pane>,
style: &egui::Style,
behavior: &mut dyn Behavior<Pane>,
rect: Rect,
) {
if let Some(active) = self.active {
if !tiles.is_visible(active) {
self.active = None;
}
}
if !self.children.iter().any(|&child| self.is_active(child)) {
self.active = self
.children
.iter()
.copied()
.find(|&child_id| tiles.is_visible(child_id));
}
let mut active_rect = rect;
active_rect.min.y += behavior.tab_bar_height(style);
if let Some(active) = self.active {
tiles.layout_tile(style, behavior, active_rect, active);
}
}
pub(super) fn ui<Pane>(
&mut self,
tree: &mut Tree<Pane>,
behavior: &mut dyn Behavior<Pane>,
drop_context: &mut DropContext,
ui: &mut egui::Ui,
rect: Rect,
tile_id: TileId,
) {
let next_active = self.tab_bar_ui(tree, behavior, ui, rect, drop_context, tile_id);
if let Some(active) = self.active {
tree.tile_ui(behavior, drop_context, ui, active);
crate::cover_tile_if_dragged(tree, behavior, ui, active);
}
self.active = next_active;
}
fn tab_bar_ui<Pane>(
&self,
tree: &mut Tree<Pane>,
behavior: &mut dyn Behavior<Pane>,
ui: &mut egui::Ui,
rect: Rect,
drop_context: &mut DropContext,
tile_id: TileId,
) -> Option<TileId> {
let mut next_active = self.active;
let tab_bar_height = behavior.tab_bar_height(ui.style());
let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0;
let mut ui = ui.child_ui(tab_bar_rect, *ui.layout());
let mut button_rects = ahash::HashMap::default();
let mut dragged_index = None;
ui.painter()
.rect_filled(ui.max_rect(), 0.0, behavior.tab_bar_color(ui.visuals()));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
behavior.top_bar_rtl_ui(&tree.tiles, ui, tile_id, self);
ui.spacing_mut().item_spacing.x = 0.0;
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
ui.set_clip_rect(ui.max_rect());
if !tree.is_root(tile_id) {
if ui
.interact(
ui.max_rect(),
ui.id().with("background"),
egui::Sense::drag(),
)
.on_hover_cursor(egui::CursorIcon::Grab)
.drag_started()
{
ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id()));
}
}
for (i, &child_id) in self.children.iter().enumerate() {
if !tree.is_visible(child_id) {
continue;
}
let is_being_dragged = is_being_dragged(ui.ctx(), child_id);
let selected = self.is_active(child_id);
let id = child_id.egui_id();
let response =
behavior.tab_ui(&tree.tiles, ui, id, child_id, selected, is_being_dragged);
let response = response.on_hover_cursor(egui::CursorIcon::Grab);
if response.clicked() {
next_active = Some(child_id);
}
if let Some(mouse_pos) = drop_context.mouse_pos {
if drop_context.dragged_tile_id.is_some()
&& response.rect.contains(mouse_pos)
{
next_active = Some(child_id);
}
}
button_rects.insert(child_id, response.rect);
if is_being_dragged {
dragged_index = Some(i);
}
}
});
});
let preview_thickness = 6.0;
let after_rect = |rect: Rect| {
let dragged_size = if let Some(dragged_index) = dragged_index {
button_rects[&self.children[dragged_index]].size()
} else {
rect.size() };
Rect::from_min_size(
rect.right_top() + vec2(ui.spacing().item_spacing.x, 0.0),
dragged_size,
)
};
super::linear::drop_zones(
preview_thickness,
&self.children,
dragged_index,
super::LinearDir::Horizontal,
|tile_id| button_rects.get(&tile_id).copied(),
|rect, i| {
drop_context.suggest_rect(
InsertionPoint::new(tile_id, ContainerInsertion::Tabs(i)),
rect,
);
},
after_rect,
);
next_active
}
pub(super) fn simplify_children(&mut self, mut simplify: impl FnMut(TileId) -> SimplifyAction) {
self.children.retain_mut(|child| match simplify(*child) {
SimplifyAction::Remove => false,
SimplifyAction::Keep => true,
SimplifyAction::Replace(new) => {
if self.active == Some(*child) {
self.active = Some(new);
}
*child = new;
true
}
});
}
pub(crate) fn remove_child(&mut self, needle: TileId) -> Option<usize> {
let index = self.children.iter().position(|&child| child == needle)?;
self.children.remove(index);
Some(index)
}
}