ori-core 0.1.0-alpha.1

Core library for Ori, a declarative UI framework for Rust.
Documentation
use std::f32::consts::PI;

use glam::Vec2;
use ori_graphics::{Curve, Quad, Rect};
use ori_macro::Build;

use crate::{
    BoxConstraints, Context, DrawContext, Event, EventContext, LayoutContext, PointerEvent,
    SharedSignal, Style, View,
};

#[derive(Clone, Debug, Build)]
pub struct Knob {
    #[bind]
    value: SharedSignal<f32>,
    #[prop]
    max: f32,
    #[prop]
    min: f32,
    #[prop]
    label: String,
}

impl Default for Knob {
    fn default() -> Self {
        Self {
            value: SharedSignal::new(0.0),
            max: 1.0,
            min: 0.0,
            label: String::new(),
        }
    }
}

impl View for Knob {
    type State = Option<Vec2>;

    fn build(&self) -> Self::State {
        None
    }

    fn style(&self) -> Style {
        Style::new("knob")
    }

    fn event(&self, state: &mut Self::State, cx: &mut EventContext, event: &Event) {
        if let Some(pointer_event) = event.get::<PointerEvent>() {
            if pointer_event.is_press() && cx.hovered() {
                cx.activate();
            } else if pointer_event.is_release() && cx.active() {
                *state = None;
                cx.deactivate();
            }

            if cx.active() {
                if let Some(prev_position) = *state {
                    let delta = pointer_event.position - prev_position;
                    let delta = delta.x - delta.y * delta.length();
                    let range = self.max - self.min;
                    let delta = delta / cx.rect().width() * range * 0.15;
                    let value = self.value.get();
                    let new_value = f32::clamp(*value + delta, self.min, self.max);
                    self.value.set(new_value);
                }

                *state = Some(pointer_event.position);
                cx.request_redraw();
            }
        }
    }

    fn layout(&self, _state: &mut Self::State, cx: &mut LayoutContext, bc: BoxConstraints) -> Vec2 {
        let size = cx.style_range("size", bc.min.max_element()..bc.max.min_element());
        bc.constrain(Vec2::splat(size))
    }

    fn draw(&self, _state: &mut Self::State, cx: &mut DrawContext) {
        let size = cx.rect().size().min_element();

        let diameter = size * 0.7;

        let circle = Quad {
            rect: Rect::center_size(cx.rect().center(), Vec2::splat(diameter)),
            background: cx.style("background-color"),
            border_radius: [diameter * 0.5; 4],
            border_width: cx.style_range("border-width", 0.0..diameter * 0.5),
            border_color: cx.style("border-color"),
        };

        cx.draw(circle);

        let curve = Curve::arc(cx.rect().center(), diameter * 0.65, -PI * 1.25, PI * 0.25);
        let mesh = curve.rounded_mesh(diameter * 0.075, cx.style("background-color"));
        cx.draw(mesh);

        let range = self.max - self.min;
        let value = self.value.get();
        let angle = (*value - self.min) / range;
        let angle = -PI * 1.25 + angle * PI * 1.5;

        let curve = Curve::arc(cx.rect().center(), diameter * 0.65, -PI * 1.25, angle);
        let mesh = curve.rounded_mesh(diameter * 0.075, cx.style("color"));
        cx.draw(mesh);

        let mut arm = Curve::new();
        arm.add_point(cx.rect().center());
        arm.add_point(cx.rect().center() + Vec2::new(angle.cos(), angle.sin()) * diameter * 0.65);

        let mesh = arm.rounded_mesh(diameter * 0.075, cx.style("color"));
        cx.draw(mesh);
    }
}