#![forbid(unsafe_code)]
use egui::{Pos2, Rect};
mod behavior;
mod container;
mod tile;
mod tiles;
mod tree;
pub use behavior::{Behavior, EditAction};
pub use container::{Container, ContainerKind, Grid, GridLayout, Linear, LinearDir, Shares, Tabs};
pub use tile::{Tile, TileId};
pub use tiles::Tiles;
pub use tree::Tree;
#[must_use]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum UiResponse {
#[default]
None,
DragStarted,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SimplificationOptions {
pub prune_empty_tabs: bool,
pub prune_empty_containers: bool,
pub prune_single_child_tabs: bool,
pub prune_single_child_containers: bool,
pub all_panes_must_have_tabs: bool,
pub join_nested_linear_containers: bool,
}
impl SimplificationOptions {
pub const OFF: Self = Self {
prune_empty_tabs: false,
prune_empty_containers: false,
prune_single_child_tabs: false,
prune_single_child_containers: false,
all_panes_must_have_tabs: false,
join_nested_linear_containers: false,
};
}
impl Default for SimplificationOptions {
fn default() -> Self {
Self {
prune_empty_tabs: true,
prune_single_child_tabs: true,
prune_empty_containers: true,
prune_single_child_containers: true,
all_panes_must_have_tabs: false,
join_nested_linear_containers: true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ResizeState {
Idle,
Hovering,
Dragging,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ContainerInsertion {
Tabs(usize),
Horizontal(usize),
Vertical(usize),
Grid(usize),
}
impl ContainerInsertion {
fn index(self) -> usize {
match self {
ContainerInsertion::Tabs(index)
| ContainerInsertion::Horizontal(index)
| ContainerInsertion::Vertical(index)
| ContainerInsertion::Grid(index) => index,
}
}
fn kind(self) -> ContainerKind {
match self {
ContainerInsertion::Tabs(_) => ContainerKind::Tabs,
ContainerInsertion::Horizontal(_) => ContainerKind::Horizontal,
ContainerInsertion::Vertical(_) => ContainerKind::Vertical,
ContainerInsertion::Grid(_) => ContainerKind::Grid,
}
}
}
#[derive(Clone, Copy, Debug)]
struct InsertionPoint {
pub parent_id: TileId,
pub insertion: ContainerInsertion,
}
impl InsertionPoint {
pub fn new(parent_id: TileId, insertion: ContainerInsertion) -> Self {
Self {
parent_id,
insertion,
}
}
}
#[derive(PartialEq, Eq)]
enum GcAction {
Keep,
Remove,
}
#[must_use]
enum SimplifyAction {
Remove,
Keep,
Replace(TileId),
}
pub(crate) fn is_being_dragged(ctx: &egui::Context, tree_id: egui::Id, tile_id: TileId) -> bool {
let dragged_id = ctx.dragged_id().or(ctx.drag_stopped_id());
dragged_id == Some(tile_id.egui_id(tree_id))
}
fn cover_tile_if_dragged<Pane>(
tree: &Tree<Pane>,
behavior: &dyn Behavior<Pane>,
ui: &mut egui::Ui,
tile_id: TileId,
) {
if is_being_dragged(ui.ctx(), tree.id, tile_id) {
if let Some(child_rect) = tree.tiles.try_rect(tile_id) {
let overlay_color = behavior.dragged_overlay_color(ui.visuals());
ui.painter().rect_filled(child_rect, 0.0, overlay_color);
}
}
}
struct DropContext {
enabled: bool,
dragged_tile_id: Option<TileId>,
mouse_pos: Option<Pos2>,
best_insertion: Option<InsertionPoint>,
best_dist_sq: f32,
preview_rect: Option<Rect>,
}
impl DropContext {
fn on_tile<Pane>(
&mut self,
behavior: &mut dyn Behavior<Pane>,
style: &egui::Style,
parent_id: TileId,
rect: Rect,
tile: &Tile<Pane>,
) {
if !self.enabled {
return;
}
if tile.kind() != Some(ContainerKind::Horizontal) {
self.suggest_rect(
InsertionPoint::new(parent_id, ContainerInsertion::Horizontal(0)),
rect.split_left_right_at_fraction(0.5).0,
);
self.suggest_rect(
InsertionPoint::new(parent_id, ContainerInsertion::Horizontal(usize::MAX)),
rect.split_left_right_at_fraction(0.5).1,
);
}
if tile.kind() != Some(ContainerKind::Vertical) {
self.suggest_rect(
InsertionPoint::new(parent_id, ContainerInsertion::Vertical(0)),
rect.split_top_bottom_at_fraction(0.5).0,
);
self.suggest_rect(
InsertionPoint::new(parent_id, ContainerInsertion::Vertical(usize::MAX)),
rect.split_top_bottom_at_fraction(0.5).1,
);
}
self.suggest_rect(
InsertionPoint::new(parent_id, ContainerInsertion::Tabs(usize::MAX)),
rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(style))
.1,
);
}
fn suggest_rect(&mut self, insertion: InsertionPoint, preview_rect: Rect) {
if !self.enabled {
return;
}
let target_point = preview_rect.center();
if let Some(mouse_pos) = self.mouse_pos {
let dist_sq = mouse_pos.distance_sq(target_point);
if dist_sq < self.best_dist_sq {
self.best_dist_sq = dist_sq;
self.best_insertion = Some(insertion);
self.preview_rect = Some(preview_rect);
}
}
}
}