sequoia-view 0.1.0

Sequoia GUI framework — view tree and runtime
Documentation
use std::rc::Rc;

use sequoia_core::{Color, Length, Point, Rect, Size};
use sequoia_layout::LayoutStyle;

use crate::any::AnyView;
use crate::dnd::{DragPayload, DropPosition};
use crate::view::{EventCtx, PaintCtx, View};
use crate::views::{Column, Row, Unit};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EdgeSide {
    Left,
    Right,
    Top,
    Bottom,
}

impl EdgeSide {
    pub const ALL: [EdgeSide; 4] = [
        EdgeSide::Left,
        EdgeSide::Right,
        EdgeSide::Top,
        EdgeSide::Bottom,
    ];

    pub fn is_vertical(self) -> bool {
        matches!(self, EdgeSide::Left | EdgeSide::Right)
    }

    pub fn is_horizontal(self) -> bool {
        !self.is_vertical()
    }
}

pub const EDGE_HINT_PX: f32 = 28.0;

pub const EDGE_GHOST_PX: f32 = 72.0;

pub const EDGE_LINE_PX: f32 = 3.0;

pub const UNDOCK_THRESHOLD_PX: f32 = 6.0;

pub const MIN_VISIBLE_W: f32 = 120.0;
pub const MIN_VISIBLE_H: f32 = 34.0;

pub fn edge_hint_at(pos: Point, window: Size, threshold: f32) -> Option<EdgeSide> {
    let t = if threshold.is_finite() && threshold > 0.0 {
        threshold
    } else {
        EDGE_HINT_PX
    };
    if pos.x < t {
        Some(EdgeSide::Left)
    } else if pos.x > window.width - t {
        Some(EdgeSide::Right)
    } else if pos.y < t {
        Some(EdgeSide::Top)
    } else if pos.y > window.height - t {
        Some(EdgeSide::Bottom)
    } else {
        None
    }
}

pub fn edge_ghost_rect(side: EdgeSide, window: Size, thickness: f32) -> Rect {
    let t = if thickness.is_finite() && thickness > 0.0 {
        thickness
    } else {
        EDGE_GHOST_PX
    };
    match side {
        EdgeSide::Left => Rect::from_xywh(0.0, 0.0, t, window.height),
        EdgeSide::Right => Rect::from_xywh((window.width - t).max(0.0), 0.0, t, window.height),
        EdgeSide::Top => Rect::from_xywh(0.0, 0.0, window.width, t),
        EdgeSide::Bottom => Rect::from_xywh(0.0, (window.height - t).max(0.0), window.width, t),
    }
}

pub fn clamp_card_top_left(global: Point, grab_offset: (f32, f32), window: Size) -> (f32, f32) {
    let nx = global.x - grab_offset.0;
    let ny = global.y - grab_offset.1;
    let max_x = (window.width - MIN_VISIBLE_W).max(0.0);
    let max_y = (window.height - MIN_VISIBLE_H).max(0.0);
    (nx.clamp(0.0, max_x), ny.clamp(0.0, max_y))
}

pub fn exceeded_undock(origin: Point, current: Point) -> bool {
    let dx = current.x - origin.x;
    let dy = current.y - origin.y;
    dx * dx + dy * dy >= UNDOCK_THRESHOLD_PX * UNDOCK_THRESHOLD_PX
}

pub fn main_first_index(side: EdgeSide) -> bool {
    matches!(side, EdgeSide::Right | EdgeSide::Bottom)
}

#[derive(Clone)]
pub struct EdgeGhost {
    side: EdgeSide,
    color: Color,
}

impl EdgeGhost {
    pub fn new(side: EdgeSide) -> Self {
        Self {
            side,

            color: Color::rgb8(0xFF, 0xFF, 0xFF),
        }
    }

    pub fn color(mut self, color: Color) -> Self {
        self.color = color;
        self
    }
}

impl View for EdgeGhost {
    fn body(&self) -> impl View + 'static {
        Unit
    }

    fn children_override(&self) -> Option<Vec<AnyView>> {
        let line_color = self.color;
        let bar: AnyView = match self.side {
            EdgeSide::Left => AnyView::new(
                Row::new()
                    .fill_size()
                    .child(
                        crate::views::Panel::new()
                            .background(line_color)
                            .width(EDGE_LINE_PX)
                            .fill_height(),
                    )
                    .child_flex(Unit, 1.0),
            ),
            EdgeSide::Right => AnyView::new(
                Row::new().fill_size().child_flex(Unit, 1.0).child(
                    crate::views::Panel::new()
                        .background(line_color)
                        .width(EDGE_LINE_PX)
                        .fill_height(),
                ),
            ),
            EdgeSide::Top => AnyView::new(
                Column::new()
                    .fill_size()
                    .child(
                        crate::views::Panel::new()
                            .background(line_color)
                            .fill_width()
                            .height(EDGE_LINE_PX),
                    )
                    .child_flex(Unit, 1.0),
            ),
            EdgeSide::Bottom => AnyView::new(
                Column::new().fill_size().child_flex(Unit, 1.0).child(
                    crate::views::Panel::new()
                        .background(line_color)
                        .fill_width()
                        .height(EDGE_LINE_PX),
                ),
            ),
        };

        Some(vec![bar])
    }

    fn layout_style(&self) -> LayoutStyle {
        let wide = Length::Px(EDGE_GHOST_PX);
        let full = Length::Fill(1.0);
        let (w, h) = match self.side {
            EdgeSide::Left | EdgeSide::Right => (wide, full),
            EdgeSide::Top | EdgeSide::Bottom => (full, wide),
        };
        LayoutStyle {
            width: w,
            height: h,
            ..LayoutStyle::default()
        }
    }

    fn hit_testable(&self) -> bool {
        false
    }
}

pub struct EdgeSlab {
    side: EdgeSide,
    accepts: Rc<dyn Fn(&DragPayload) -> bool>,
    on_over: Rc<dyn Fn()>,
    on_drop: Rc<dyn Fn(DropPosition, &mut EventCtx)>,
}

impl Clone for EdgeSlab {
    fn clone(&self) -> Self {
        Self {
            side: self.side,
            accepts: self.accepts.clone(),
            on_over: self.on_over.clone(),
            on_drop: self.on_drop.clone(),
        }
    }
}

impl EdgeSlab {
    pub fn new(side: EdgeSide) -> Self {
        Self {
            side,
            accepts: Rc::new(|_| false),
            on_over: Rc::new(|| {}),
            on_drop: Rc::new(|_, _| {}),
        }
    }

    pub fn get_side(&self) -> EdgeSide {
        self.side
    }

    pub fn accepts(mut self, f: impl Fn(&DragPayload) -> bool + 'static) -> Self {
        self.accepts = Rc::new(f);
        self
    }

    pub fn on_drag_over(mut self, f: impl Fn() + 'static) -> Self {
        self.on_over = Rc::new(f);
        self
    }

    pub fn on_drop_with(mut self, f: impl Fn(DropPosition, &mut EventCtx) + 'static) -> Self {
        self.on_drop = Rc::new(f);
        self
    }
}

impl View for EdgeSlab {
    fn body(&self) -> impl View + 'static {
        Unit
    }

    fn children_override(&self) -> Option<Vec<AnyView>> {
        None
    }

    fn layout_style(&self) -> LayoutStyle {
        let wide = Length::Px(EDGE_GHOST_PX);
        let full = Length::Fill(1.0);
        let (w, h) = match self.side {
            EdgeSide::Left | EdgeSide::Right => (wide, full),
            EdgeSide::Top | EdgeSide::Bottom => (full, wide),
        };
        LayoutStyle {
            width: w,
            height: h,
            ..LayoutStyle::default()
        }
    }

    fn accepts_drop(&self, payload: &DragPayload) -> bool {
        (self.accepts)(payload)
    }

    fn on_drag_over(&self, _pos: DropPosition, _ctx: &mut EventCtx) {
        (self.on_over)();
    }

    fn on_drop(&self, pos: DropPosition, ctx: &mut EventCtx) {
        (self.on_drop)(pos, ctx);
    }

    fn paint_self(&self, _ctx: &mut PaintCtx) {}
}

#[allow(dead_code)]
fn _assert_send_sync() {
    fn is<T: Clone + 'static>() {}
    is::<EdgeSide>();
    is::<EdgeGhost>();
}