zaplib_widget 0.0.1

The widget toolkit for Zap
Documentation
use zaplib::*;

define_string_with_filename!(MAIN_SHADER);

#[derive(Clone)]
#[repr(C)]
struct DrawFloatSlider {
    base: DrawQuad,
    norm_value: f32,
    hover: f32,
    down: f32,
}

impl DrawFloatSlider {
    fn new(cx: &mut Cx) -> Self {
        Self {
            base: DrawQuad::with_slots(
                cx,
                cx.get_shader(StringHash::new(MAIN_SHADER), location_hash!()),
                f32::slots() + f32::slots() + f32::slots(),
            ),
            norm_value: Default::default(),
            hover: Default::default(),
            down: Default::default(),
        }
    }
}

pub enum FloatSliderEvent {
    Change { scaled_value: f32 },
    DoneChanging,
    None,
}

#[derive(Clone)]
pub struct FloatSlider {
    pub scaled_value: f32,
    pub norm_value: f32,
    pub animator: Animator,
    pub min: Option<f32>,
    pub max: Option<f32>,
    pub step: Option<f32>,
    pub _size: f32,
    slider: DrawFloatSlider,
    pub dragging: bool,
}

const ANIM_DEFAULT: Anim = Anim {
    duration: 0.2,
    tracks: &[
        // DrawFloatSlider::hover
        Track::Float { key_frames: &[(1.0, 0.0)], ease: Ease::DEFAULT },
        // DrawFloatSlider::down
        Track::Float { key_frames: &[(1.0, 0.0)], ease: Ease::DEFAULT },
    ],
    ..Anim::DEFAULT
};

const ANIM_HOVER: Anim = Anim {
    duration: 0.2,
    tracks: &[
        // DrawFloatSlider::hover
        Track::Float { key_frames: &[(0.0, 1.0)], ease: Ease::DEFAULT },
        // DrawFloatSlider::down
        Track::Float { key_frames: &[(1.0, 0.0)], ease: Ease::DEFAULT },
    ],
    ..Anim::DEFAULT
};

const ANIM_DOWN: Anim = Anim {
    duration: 0.2,
    tracks: &[
        // DrawFloatSlider::hover
        Track::Float { key_frames: &[(1.0, 1.0)], ease: Ease::DEFAULT },
        // DrawFloatSlider::down
        Track::Float { key_frames: &[(0.0, 0.0), (1.0, 1.0)], ease: Ease::DEFAULT },
    ],
    ..Anim::DEFAULT
};

impl FloatSlider {
    pub fn new(cx: &mut Cx) -> Self {
        Self {
            norm_value: 0.0,
            scaled_value: 0.0,
            animator: Animator::new(ANIM_DEFAULT),
            min: None,
            max: None,
            step: None,
            _size: 0.0,
            slider: DrawFloatSlider::new(cx),
            dragging: false,
        }
    }

    fn animate(&mut self, cx: &mut Cx) {
        self.slider.hover = self.animator.get_float(0);
        self.animator.get_float(0).write_shader_value(cx, self.slider.base.area(), "hover");
        self.slider.down = self.animator.get_float(1);
        self.animator.get_float(1).write_shader_value(cx, self.slider.base.area(), "down");
    }

    pub fn app_load(cx: &mut Cx) {
        cx.register_shader(
            MAIN_SHADER,
            Some(GEOM_QUAD2D),
            &[STD_SHADER_PRELUDE, DRAWQUAD_SHADER_PRELUDE],
            &code_fragment!(
                r#"
                instance norm_value: float;
                instance hover: float;
                instance down: float;

                fn pixel() -> vec4 {
                    let df = Df::viewport(pos * rect_size);

                    let cy = rect_size.y * 0.5;
                    let height = 2.;
                    df.box(1., cy - 0.5 * height, rect_size.x - 1., height, 1.);

                    df.fill(#4);

                    let bheight = 15.;
                    let bwidth = 7.;

                    df.box((rect_size.x - bwidth) * norm_value, cy - 0.5 * bheight, bwidth, bheight, 1.);

                    let color = mix(mix(#7, #B, hover), #F, down);
                    df.fill(color);

                    return df.result;
                }"#
            ),
        )
    }

    pub fn handle_finger(&mut self, cx: &mut Cx, rel: Vec2) -> FloatSliderEvent {
        let norm_value = (rel.x / self._size).max(0.0).min(1.0);
        let mut scaled_value = norm_value * (self.max.unwrap_or(1.0) - self.min.unwrap_or(0.0)) + self.min.unwrap_or(0.0);
        if self.step.unwrap_or(0.0) > 0.0 {
            scaled_value = (scaled_value / self.step.unwrap_or(1.0)).round() * self.step.unwrap_or(1.0);
        }
        let mut changed = false;
        #[allow(clippy::float_cmp)]
        if scaled_value != self.scaled_value {
            self.scaled_value = scaled_value;
            self.norm_value = norm_value;
            self.norm_value.write_shader_value(cx, self.slider.base.area(), "norm_value");
            changed = true;
        }
        if changed {
            FloatSliderEvent::Change { scaled_value }
        } else {
            FloatSliderEvent::None
        }
    }

    pub fn handle_float_slider(&mut self, cx: &mut Cx, event: &mut Event) -> FloatSliderEvent {
        if self.animator.handle_animator(cx, event) {
            self.animate(cx);
        }

        match event.hits(cx, self.slider.base.area(), HitOpt::default()) {
            Event::FingerHover(fe) => {
                cx.set_hover_mouse_cursor(MouseCursor::Arrow);
                match fe.hover_state {
                    HoverState::In => {
                        self.animator.play_anim(cx, ANIM_HOVER);
                    }
                    HoverState::Out => {
                        self.animator.play_anim(cx, ANIM_DEFAULT);
                    }
                    _ => (),
                }
            }
            Event::FingerDown(fe) => {
                self.animator.play_anim(cx, ANIM_DOWN);
                cx.set_down_mouse_cursor(MouseCursor::Arrow);
                self.dragging = true;
                return self.handle_finger(cx, fe.rel);
                // lets check where we clicked!
            }
            Event::FingerUp(fe) => {
                if fe.is_over {
                    if fe.input_type.has_hovers() {
                        self.animator.play_anim(cx, ANIM_HOVER);
                    } else {
                        self.animator.play_anim(cx, ANIM_DEFAULT);
                    }
                } else {
                    self.animator.play_anim(cx, ANIM_DEFAULT);
                }
                self.dragging = false;
                return FloatSliderEvent::DoneChanging;
            }
            Event::FingerMove(fe) => return self.handle_finger(cx, fe.rel),
            Event::FingerScroll(fs) => {
                self.norm_value += fs.scroll.x / 1000.0;
                self.norm_value = self.norm_value.min(1.0).max(0.0);
                self.scaled_value =
                    self.norm_value * (self.max.unwrap_or(1.0) - self.min.unwrap_or(0.0)) + self.min.unwrap_or(0.0);
                self.norm_value.write_shader_value(cx, self.slider.base.area(), "norm_value");
                return FloatSliderEvent::Change { scaled_value: self.scaled_value };
            }
            _ => (),
        }
        FloatSliderEvent::None
    }

    pub fn draw_float_slider(
        &mut self,
        cx: &mut Cx,
        scaled_value: f32,
        min: Option<f32>,
        max: Option<f32>,
        step: Option<f32>,
        height_scale: f32,
    ) {
        if self.animator.process_animator(cx) {
            self.animate(cx);
        }

        if !self.dragging {
            self.scaled_value = scaled_value;
            self.min = min;
            self.max = max;
            self.step = step;
            self.norm_value = (scaled_value - min.unwrap_or(0.0)) / (max.unwrap_or(1.0) - min.unwrap_or(0.0));
        }

        let pad = 10.;

        self._size = cx.get_turtle_rect().size.x - 2. * pad;
        self.slider.norm_value = self.norm_value;
        self.slider.base.draw_quad_walk(
            cx,
            Walk {
                margin: Margin { l: pad, r: pad, ..Margin::default() },
                width: Width::Fill,
                height: Height::Fix(35.0 * height_scale),
            },
        );
    }
}