sequoia-dock 0.1.1

Sequoia GUI framework — headless dock model (IDE splits, tabs, floating)
Documentation
use sequoia_core::{Rect, Vec2};

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum DockDrop {
    Left,
    Right,
    Top,
    Bottom,

    #[default]
    Tab,

    Float,
}

impl DockDrop {
    pub const fn is_side(self) -> bool {
        matches!(self, Self::Left | Self::Right | Self::Top | Self::Bottom)
    }

    pub const fn is_tab(self) -> bool {
        matches!(self, Self::Tab)
    }

    pub const fn is_float(self) -> bool {
        matches!(self, Self::Float)
    }
}

pub const EDGE_FRACTION: f32 = 0.22;

pub fn drop_from_fraction(fraction: Vec2, edge: f32) -> DockDrop {
    let e = if edge.is_finite() {
        edge.clamp(0.0, 0.45)
    } else {
        EDGE_FRACTION
    };
    let fx = fraction.x.clamp(0.0, 1.0);
    let fy = fraction.y.clamp(0.0, 1.0);

    let dist_left = fx;
    let dist_right = 1.0 - fx;
    let dist_top = fy;
    let dist_bottom = 1.0 - fy;
    let min_h = dist_left.min(dist_right);
    let min_v = dist_top.min(dist_bottom);
    if min_h < e || min_v < e {
        if min_h < min_v {
            if dist_left < dist_right {
                DockDrop::Left
            } else {
                DockDrop::Right
            }
        } else if dist_top < dist_bottom {
            DockDrop::Top
        } else {
            DockDrop::Bottom
        }
    } else {
        DockDrop::Tab
    }
}

pub fn drop_from_fraction_or_float(fraction: Vec2, edge: f32, force_float: bool) -> DockDrop {
    if force_float {
        DockDrop::Float
    } else {
        drop_from_fraction(fraction, edge)
    }
}

pub fn preview_rect(host: Rect, drop: DockDrop, frac: f32) -> Rect {
    use sequoia_layout::{Dock, dock_preview_rect};
    match drop {
        DockDrop::Left => dock_preview_rect(host, Dock::Left, frac),
        DockDrop::Right => dock_preview_rect(host, Dock::Right, frac),
        DockDrop::Top => dock_preview_rect(host, Dock::Top, frac),
        DockDrop::Bottom => dock_preview_rect(host, Dock::Bottom, frac),
        DockDrop::Tab => host,
        DockDrop::Float => {
            let w = host.size.width.max(0.0);
            let h = host.size.height.max(0.0);
            let pw = w * 0.6;
            let ph = h * 0.6;
            Rect::from_xywh(
                host.origin.x + (w - pw) * 0.5,
                host.origin.y + (h - ph) * 0.5,
                pw,
                ph,
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn v(x: f32, y: f32) -> Vec2 {
        Vec2::new(x, y)
    }

    #[test]
    fn edges_win_over_center() {
        assert_eq!(
            drop_from_fraction(v(0.05, 0.5), EDGE_FRACTION),
            DockDrop::Left
        );
        assert_eq!(
            drop_from_fraction(v(0.95, 0.5), EDGE_FRACTION),
            DockDrop::Right
        );
        assert_eq!(
            drop_from_fraction(v(0.5, 0.05), EDGE_FRACTION),
            DockDrop::Top
        );
        assert_eq!(
            drop_from_fraction(v(0.5, 0.95), EDGE_FRACTION),
            DockDrop::Bottom
        );
        assert_eq!(
            drop_from_fraction(v(0.5, 0.5), EDGE_FRACTION),
            DockDrop::Tab
        );
    }

    #[test]
    fn bad_edge_falls_back() {
        assert_eq!(drop_from_fraction(v(0.5, 0.5), f32::NAN), DockDrop::Tab);
        assert_eq!(drop_from_fraction(v(0.05, 0.5), -1.0), DockDrop::Tab);
    }

    #[test]
    fn force_float_wins() {
        assert_eq!(
            drop_from_fraction_or_float(v(0.05, 0.5), EDGE_FRACTION, true),
            DockDrop::Float
        );
        assert_eq!(
            drop_from_fraction_or_float(v(0.05, 0.5), EDGE_FRACTION, false),
            DockDrop::Left
        );
    }

    #[test]
    fn preview_splits_host() {
        let host = Rect::from_xywh(0.0, 0.0, 400.0, 300.0);
        let l = preview_rect(host, DockDrop::Left, 0.5);
        assert_eq!((l.size.width, l.size.height), (200.0, 300.0));
        let b = preview_rect(host, DockDrop::Bottom, 0.25);
        assert!((b.size.height - 75.0).abs() < 0.01);
        let t = preview_rect(host, DockDrop::Tab, 0.5);
        assert_eq!(t, host);
        let f = preview_rect(host, DockDrop::Float, 0.5);
        assert!(f.size.width < host.size.width && f.size.height < host.size.height);
    }

    #[test]
    fn preview_frac_clamps() {
        let host = Rect::from_xywh(0.0, 0.0, 400.0, 300.0);
        let l = preview_rect(host, DockDrop::Left, f32::NAN);
        assert!((l.size.width - 200.0).abs() < 0.01);
        let l2 = preview_rect(host, DockDrop::Left, 99.0);
        assert!((l2.size.width - 400.0 * 0.85).abs() < 0.01);
    }
}