1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use egui::NumExt as _;

use re_types::{blueprint::components::VisualBounds2D, datatypes::Range2D};
use re_ui::UiExt as _;
use re_viewer_context::{MaybeMutRef, ViewerContext};

pub fn multiline_edit_visual_bounds2d(
    _ctx: &ViewerContext<'_>,
    ui: &mut egui::Ui,
    value: &mut MaybeMutRef<'_, VisualBounds2D>,
) -> egui::Response {
    let mut any_edit = false;

    let response_x = ui.list_item().interactive(false).show_hierarchical(
        ui,
        re_ui::list_item::PropertyContent::new("x").value_fn(|ui, _| {
            if let Some(value) = value.as_mut() {
                any_edit |= range_mut_ui(ui, &mut value.x_range.0).changed();
            } else {
                range_ui(ui, value.x_range.0);
            }
        }),
    );

    let response_y = ui.list_item().interactive(false).show_hierarchical(
        ui,
        re_ui::list_item::PropertyContent::new("y").value_fn(|ui, _| {
            if let Some(value) = value.as_mut() {
                any_edit |= range_mut_ui(ui, &mut value.y_range.0).changed();
            } else {
                range_ui(ui, value.y_range.0);
            }
        }),
    );

    let mut response = response_x | response_y;
    if any_edit {
        response.mark_changed();
    }
    response
}

fn range_ui(ui: &mut egui::Ui, [start, end]: [f64; 2]) {
    ui.horizontal_centered(|ui| {
        ui.label(format!(
            "{} - {}",
            re_format::format_f64(start),
            re_format::format_f64(end)
        ));
    });
}

fn range_mut_ui(ui: &mut egui::Ui, [start, end]: &mut [f64; 2]) -> egui::Response {
    let speed_func = |start: f64, end: f64| ((end - start).abs() * 0.01).at_least(0.001);

    let speed = speed_func(*start, *end);

    ui.horizontal_centered(|ui| {
        let response_min = ui.add(
            egui::DragValue::new(start)
                .clamp_existing_to_range(false)
                .range(f64::MIN..=*end)
                .max_decimals(2)
                .speed(speed),
        );

        ui.label("-");

        let response_max = ui.add(
            egui::DragValue::new(end)
                .clamp_existing_to_range(false)
                .range(*start..=f64::MAX)
                .max_decimals(2)
                .speed(speed),
        );

        response_min | response_max
    })
    .inner
}

pub fn singleline_edit_visual_bounds2d(
    _ctx: &ViewerContext<'_>,
    ui: &mut egui::Ui,
    value: &mut MaybeMutRef<'_, VisualBounds2D>,
) -> egui::Response {
    if let Some(value) = value.as_mut() {
        // Not a lot of space in a single line, so edit width/height instead.
        let width = value.x_range.0[1] - value.x_range.0[0];
        let height = value.y_range.0[1] - value.y_range.0[0];

        let mut width_edit = width;
        let mut height_edit = height;

        let speed_func = |v: f64| (v.abs() * 0.01).at_least(0.001);

        let response_width = ui.add(
            egui::DragValue::new(&mut width_edit)
                .clamp_existing_to_range(false)
                .range(0.001..=f64::MAX)
                .max_decimals(1)
                .speed(speed_func(width)),
        );
        ui.label("×");
        let response_height = ui.add(
            egui::DragValue::new(&mut height_edit)
                .clamp_existing_to_range(false)
                .range(0.001..=f64::MAX)
                .max_decimals(1)
                .speed(speed_func(height)),
        );
        let response = response_height | response_width;

        // Empirically it's quite confusing to edit width/height separately for the visual bounds.
        // So we lock the aspect ratio.

        if response.changed() {
            let aspect_ratio = width / height;

            if width != width_edit {
                height_edit = width_edit / aspect_ratio;
            } else {
                width_edit = height_edit * aspect_ratio;
            }

            let d_width = width_edit - width;
            let d_height = height_edit - height;

            *value = Range2D {
                x_range: [
                    value.x_range.0[0] - d_width * 0.5,
                    value.x_range.0[1] + d_width * 0.5,
                ]
                .into(),
                y_range: [
                    value.y_range.0[0] - d_height * 0.5,
                    value.y_range.0[1] + d_height * 0.5,
                ]
                .into(),
            }
            .into();
        }

        response
    } else {
        let width = value.x_range.0[1] - value.x_range.0[0];
        let height = value.y_range.0[1] - value.y_range.0[0];
        ui.label(format!(
            "{} × {}",
            re_format::format_f64(width),
            re_format::format_f64(height)
        ))
    }
}