fpvsetup 0.1.0

Library and GUI tool for calculating optimal first-person 3D view parameters from monitor size and distance
Documentation
use crate::{
    build_unit_selector,
    layout::{LayoutGen, Position, Rect, Size},
    util::{friendly_ftoa, length_from_unit, PosExt, Repack, Unit, DEGREE_SIGN},
    Number::*,
    RcUi, ADDED_HEIGHT, GROUP_H_PADDING, GROUP_V_PADDING, LINE_V_PADDING,
};
use fltk::{frame::Frame, group::Group, input::FloatInput, menu::Choice, prelude::*};
use fpvsetup::MonitorConfiguration;
use std::{cmp::max, convert::TryInto, rc::Rc};
use uom::si::angle::degree;

#[derive(Clone)]
pub struct Focused {
    pub containing_group: Group,
    pub accurate_distance_label_1: Frame,
    pub accurate_distance_input: FloatInput,
    pub accurate_distance_label_2: Frame,
    pub accurate_distance_unit_selector: Choice,
    pub fov_output_label: Frame,
    pub fov_output: FloatInput,
}
impl Focused {
    pub fn new(ui: &RcUi) -> Self {
        let containing_group = Group::default().with_label("Focused");

        let accurate_distance_label_1 = Frame::default().with_label("Accurate scale");
        let mut accurate_distance_input = FloatInput::default();
        let r = Rc::clone(&ui);
        accurate_distance_input.set_callback(move || Self::update(&r));
        accurate_distance_input.set_trigger(CallbackTrigger::Changed);
        let accurate_distance_unit_selector =
            build_unit_selector(&accurate_distance_input, Some(Unit::Meters), Plural, false);
        let accurate_distance_label_2 = Frame::default().with_label("away from the camera");

        let fov_output_label = Frame::default().with_label("Camera field of view:");
        let mut fov_output = FloatInput::default();
        fov_output.set_readonly(true);

        containing_group.end();

        Self {
            containing_group,
            accurate_distance_label_1,
            accurate_distance_input,
            accurate_distance_label_2,
            accurate_distance_unit_selector,
            fov_output_label,
            fov_output,
        }
    }
    pub fn apply_layout(&mut self, layout: &FocusedLayout, pos: Position) {
        self.containing_group
            .set_rect(layout.containing_group.with_added_pos(pos));
        self.accurate_distance_label_1
            .set_rect(layout.accurate_distance_label_1.with_added_pos(pos));
        self.accurate_distance_input
            .set_rect(layout.accurate_distance_input.with_added_pos(pos));
        self.accurate_distance_label_2
            .set_rect(layout.accurate_distance_label_2.with_added_pos(pos));
        self.accurate_distance_unit_selector
            .set_rect(layout.accurate_distance_unit_selector.with_added_pos(pos));
        self.fov_output_label
            .set_rect(layout.fov_output_label.with_added_pos(pos));
        self.fov_output
            .set_rect(layout.fov_output.with_added_pos(pos));
    }
    pub fn update(ui: &RcUi) {
        let mut _u = ui.borrow_mut();
        let u = _u.as_mut().unwrap();
        let mp = &mut u.monitor_properties;
        let fo = &mut u.output_tabs.focused;
        let width = mp.width_input.value().parse::<f64>();
        let height = mp.height_input.value().parse::<f64>();
        let distance = mp.distance_input.value().parse::<f64>();
        let accurate_distance = fo.accurate_distance_input.value().parse::<f64>();
        if let (Ok(width), Ok(height), Ok(distance), Ok(accurate_distance)) =
            (width, height, distance, accurate_distance)
        {
            let width_unit = mp.width_unit_selector.value().try_into().unwrap();
            let height_unit = mp.height_unit_selector.value().try_into().unwrap();
            let distance_unit = mp.distance_unit_selector.value().try_into().unwrap();
            let accurate_distance_unit = fo
                .accurate_distance_unit_selector
                .value()
                .try_into()
                .unwrap();
            let width = length_from_unit(width, width_unit);
            let height = length_from_unit(height, height_unit);
            let distance = length_from_unit(distance, distance_unit);
            let accurate_distance = length_from_unit(accurate_distance, accurate_distance_unit);

            let monitor_conf = MonitorConfiguration {
                dimensions: fpvsetup::MonitorDimensions::WidthAndHeight { width, height },
                distance,
            };
            let fov = monitor_conf.monitor_fov_for_distance(accurate_distance, true);
            fo.fov_output.set_value(&format!(
                "{}{}",
                &friendly_ftoa(fov.get::<degree>()),
                DEGREE_SIGN,
            ));
        }
    }
}
impl LayoutGen<'_> for Focused {
    type Layout = FocusedLayout;
    type Arguments = ();

    fn generate_layout(&self, _: Self::Arguments) -> Self::Layout {
        const NUM_LINES: i32 = 2;

        let mut width_l1 = GROUP_H_PADDING * 2;
        let height_l1;

        let accurate_distance_label_1 = Rect(
            Position(GROUP_H_PADDING, GROUP_V_PADDING),
            self.accurate_distance_label_1.measure_label().repack(),
        );
        height_l1 = accurate_distance_label_1.h() + ADDED_HEIGHT;
        width_l1 += accurate_distance_label_1.w();

        let accurate_distance_input =
            Rect(accurate_distance_label_1.to_right(5), Size(70, height_l1));
        width_l1 += accurate_distance_input.w();

        let accurate_distance_unit_selector =
            Rect(accurate_distance_input.to_right(5), Size(105, height_l1));
        width_l1 += accurate_distance_unit_selector.w();

        let accurate_distance_label_2 = Rect(
            accurate_distance_unit_selector.to_right(5),
            self.accurate_distance_label_2.measure_label().repack(),
        );
        width_l1 += accurate_distance_label_2.w();

        let mut width_l2 = GROUP_H_PADDING * 2;
        let height_l2;

        let fov_output_label = Rect(
            accurate_distance_label_1.to_bottom(LINE_V_PADDING),
            self.fov_output_label.measure_label().repack(),
        );
        height_l2 = fov_output_label.h() + ADDED_HEIGHT;
        width_l2 += fov_output_label.w();

        let fov_output = Rect(fov_output_label.to_right(5), Size(70, height_l2));
        width_l2 += fov_output.w();

        let total_width = max(width_l1, width_l2);
        let total_height =
            height_l1 + height_l2 + LINE_V_PADDING * (NUM_LINES - 1) + GROUP_V_PADDING * 2;
        let total_size = Size(total_width, total_height);
        let containing_group = Rect(Position(0, 0), total_size);
        FocusedLayout {
            total_size,
            containing_group,
            accurate_distance_label_1,
            accurate_distance_input,
            accurate_distance_unit_selector,
            accurate_distance_label_2,
            fov_output_label,
            fov_output,
        }
    }
}

make_layout!(pub FocusedLayout, has
    containing_group,
    accurate_distance_label_1,
    accurate_distance_input,
    accurate_distance_unit_selector,
    accurate_distance_label_2,
    fov_output_label,
    fov_output,
);