repose-ui 0.31.12

UI widgets and libs for Repose
Documentation
#![allow(non_snake_case)]

use std::rc::Rc;

use repose_core::*;

use crate::Interactions;
use crate::hit_testing::{HitContext, register_hit};

#[derive(Clone, Copy)]
pub(crate) enum ScrollbarAxis {
    V,
    H,
}

pub(crate) fn push_scrollbar(
    scene: &mut Scene,
    hits: &mut Vec<HitRegion>,
    interactions: &Interactions,
    vid: u64,
    vp: repose_core::Rect,
    content_len: f32,
    offset: f32,
    z: f32,
    axis: ScrollbarAxis,
    style: &ScrollbarStyle,
    alpha: f32,
    set_offset: Option<Rc<dyn Fn(f32)>>,
    hit_context: &HitContext,
) {
    let vp_len = match axis {
        ScrollbarAxis::V => vp.h,
        ScrollbarAxis::H => vp.w,
    };
    if content_len <= vp_len + 0.5 {
        return;
    }

    let thick = style.thickness.to_px().0.max(0.5);
    let main_inset = style.track_inset.to_px().0.max(0.0);

    let (track_x, track_y, track_main, track_cross) = match axis {
        ScrollbarAxis::V => (
            vp.x + vp.w - thick,
            vp.y + main_inset,
            (vp.h - 2.0 * main_inset).max(0.0),
            thick,
        ),
        ScrollbarAxis::H => (
            vp.x + main_inset,
            vp.y + vp.h - thick,
            (vp.w - 2.0 * main_inset).max(0.0),
            thick,
        ),
    };
    if track_main <= 0.5 {
        return;
    }

    let ratio = (vp_len / content_len).clamp(0.0, 1.0);
    let thumb_len = style
        .fixed_thumb_length
        .map(|length| length.to_px().0)
        .unwrap_or_else(|| (track_main * ratio).max(style.min_thumb_length.to_px().0))
        .clamp(0.5, track_main);
    let tpos = (offset / (content_len - vp_len).max(1.0)).clamp(0.0, 1.0);
    let thumb_offset = tpos * (track_main - thumb_len);

    let (track_rect, thumb_rect) = match axis {
        ScrollbarAxis::V => (
            repose_core::Rect {
                x: track_x,
                y: track_y,
                w: track_cross,
                h: track_main,
            },
            repose_core::Rect {
                x: track_x,
                y: track_y + thumb_offset,
                w: track_cross,
                h: thumb_len,
            },
        ),
        ScrollbarAxis::H => (
            repose_core::Rect {
                x: track_x,
                y: track_y,
                w: track_main,
                h: track_cross,
            },
            repose_core::Rect {
                x: track_x + thumb_offset,
                y: track_y,
                w: thumb_len,
                h: track_cross,
            },
        ),
    };

    let tid = match axis {
        ScrollbarAxis::V => vid ^ 0x8000_0001,
        ScrollbarAxis::H => vid ^ 0x8000_0002,
    };
    let visual_state = ControlVisualState {
        alpha,
        enabled: true,
        hovered: interactions.hover == Some(tid),
        pressed: interactions.pressed.contains(&tid),
        dragged: false,
        focused: false,
    };
    let track_state = ControlVisualState {
        pressed: false,
        dragged: false,
        ..visual_state
    };
    if let Some(visual) = style.track_visuals.resolve(track_state) {
        visual.paint(scene, track_rect, track_state);
    } else {
        let radius = style
            .radius
            .map(|radius| radius.to_px().0)
            .unwrap_or(thick * 0.5);
        scene.nodes.push(SceneNode::Rect {
            rect: track_rect,
            brush: Brush::Solid({
                let color = locals::theme().scrollbar_track;
                Color(
                    color.0,
                    color.1,
                    color.2,
                    (color.3 as f32 * alpha.clamp(0.0, 1.0)) as u8,
                )
            }),
            radius: [Px(radius); 4],
        });
    }
    if let Some(visual) = style.thumb_visuals.resolve(visual_state) {
        visual.paint(scene, thumb_rect, visual_state);
    } else {
        let radius = style
            .radius
            .map(|radius| radius.to_px().0)
            .unwrap_or(thick * 0.5);
        scene.nodes.push(SceneNode::Rect {
            rect: thumb_rect,
            brush: Brush::Solid({
                let color = locals::theme().scrollbar_thumb;
                Color(
                    color.0,
                    color.1,
                    color.2,
                    (color.3 as f32 * alpha.clamp(0.0, 1.0)) as u8,
                )
            }),
            radius: [Px(radius); 4],
        });
    }

    if let Some(s) = set_offset {
        let track_start = match axis {
            ScrollbarAxis::V => track_y,
            ScrollbarAxis::H => track_x,
        };
        let max_scroll = (content_len - vp_len).max(1.0);

        let map = Rc::new(move |pos: f32| -> f32 {
            let max_p = (track_main - thumb_len).max(0.0);
            let p = ((pos - track_start) - thumb_len * 0.5).clamp(0.0, max_p);
            (if max_p > 0.0 { p / max_p } else { 0.0 }) * max_scroll
        });

        let on_pd = {
            let s = s.clone();
            let m = map.clone();
            let context = hit_context.clone();
            let vertical = matches!(axis, ScrollbarAxis::V);
            Rc::new(move |pe: PointerEvent| {
                let local = context.to_local(pe.position_in_window());
                s(m(if vertical { local.y } else { local.x }))
            })
        };
        let on_pm = if interactions.pressed.contains(&tid) {
            let s = s.clone();
            let m = map.clone();
            let context = hit_context.clone();
            let vertical = matches!(axis, ScrollbarAxis::V);
            Some(Rc::new(move |pe: PointerEvent| {
                let local = context.to_local(pe.position_in_window());
                s(m(if vertical { local.y } else { local.x }))
            }) as Rc<dyn Fn(PointerEvent)>)
        } else {
            None
        };
        let mut hit = HitRegion {
            id: tid,
            rect: thumb_rect,
            z_index: z + 1000.0,
            on_pointer_down: Some(on_pd),
            on_pointer_move: on_pm,
            on_pointer_up: Some(Rc::new(|_| {})),
            ..Default::default()
        };
        register_hit(&mut hit, hit_context, None);
        hits.push(hit);
    }
}