Skip to main content

polyscope_ui/
structure_ui.rs

1//! Structure-specific UI builders.
2
3use egui::Ui;
4
5/// Default built-in material names (used as fallback if no registry provided).
6const DEFAULT_MATERIALS: &[&str] = &[
7    "clay", "wax", "candy", "flat", "mud", "ceramic", "jade", "normal",
8];
9
10/// Builds a material selector `ComboBox`. Returns true if the material changed.
11/// `available_materials` is the list of all registered material names (built-in + custom).
12/// If empty, falls back to the default built-in list.
13pub fn build_material_selector(
14    ui: &mut Ui,
15    material: &mut String,
16    available_materials: &[&str],
17) -> bool {
18    let materials: &[&str] = if available_materials.is_empty() {
19        DEFAULT_MATERIALS
20    } else {
21        available_materials
22    };
23
24    let mut changed = false;
25    egui::ComboBox::from_label("Material")
26        .selected_text(material.as_str())
27        .show_ui(ui, |ui| {
28            for &mat in materials {
29                if ui
30                    .selectable_value(material, mat.to_string(), mat)
31                    .changed()
32                {
33                    changed = true;
34                }
35            }
36        });
37    changed
38}
39
40/// Builds UI for a point cloud.
41pub fn build_point_cloud_ui(
42    ui: &mut Ui,
43    num_points: usize,
44    point_radius: &mut f32,
45    base_color: &mut [f32; 3],
46    material: &mut String,
47    available_materials: &[&str],
48) -> bool {
49    let mut changed = false;
50
51    ui.label(format!("{num_points} points"));
52
53    if build_material_selector(ui, material, available_materials) {
54        changed = true;
55    }
56
57    egui::Grid::new("point_cloud_props")
58        .num_columns(2)
59        .show(ui, |ui| {
60            ui.label("Color:");
61            if ui.color_edit_button_rgb(base_color).changed() {
62                changed = true;
63            }
64            ui.end_row();
65
66            ui.label("Radius:");
67            if ui
68                .add(
69                    egui::DragValue::new(point_radius)
70                        .speed(0.001)
71                        .range(0.001..=0.5),
72                )
73                .changed()
74            {
75                changed = true;
76            }
77            ui.end_row();
78        });
79
80    changed
81}
82
83/// Builds UI for a surface mesh.
84pub fn build_surface_mesh_ui(
85    ui: &mut Ui,
86    num_vertices: usize,
87    num_faces: usize,
88    num_edges: usize,
89    shade_style: &mut u32,
90    surface_color: &mut [f32; 3],
91    transparency: &mut f32,
92    show_edges: &mut bool,
93    edge_width: &mut f32,
94    edge_color: &mut [f32; 3],
95    backface_policy: &mut u32,
96    material: &mut String,
97    available_materials: &[&str],
98) -> bool {
99    let mut changed = false;
100
101    ui.label(format!(
102        "{num_vertices} verts, {num_faces} faces, {num_edges} edges"
103    ));
104
105    if build_material_selector(ui, material, available_materials) {
106        changed = true;
107    }
108
109    ui.separator();
110
111    // Shade style
112    egui::ComboBox::from_label("Shading")
113        .selected_text(match *shade_style {
114            0 => "Smooth",
115            1 => "Flat",
116            _ => "Tri-Flat",
117        })
118        .show_ui(ui, |ui| {
119            if ui.selectable_value(shade_style, 0, "Smooth").changed() {
120                changed = true;
121            }
122            if ui.selectable_value(shade_style, 1, "Flat").changed() {
123                changed = true;
124            }
125            if ui.selectable_value(shade_style, 2, "Tri-Flat").changed() {
126                changed = true;
127            }
128        });
129
130    // Surface color & opacity
131    egui::Grid::new("mesh_color_grid")
132        .num_columns(2)
133        .show(ui, |ui| {
134            ui.label("Color:");
135            if ui.color_edit_button_rgb(surface_color).changed() {
136                changed = true;
137            }
138            ui.end_row();
139
140            // Opacity (displayed as 1.0 - transparency so slider semantics match the label:
141            // opacity 1 = fully opaque, opacity 0 = fully transparent)
142            ui.label("Opacity:");
143            let mut opacity = 1.0 - *transparency;
144            if ui.add(egui::Slider::new(&mut opacity, 0.0..=1.0)).changed() {
145                *transparency = 1.0 - opacity;
146                changed = true;
147            }
148            ui.end_row();
149        });
150
151    ui.separator();
152
153    // Wireframe
154    if ui.checkbox(show_edges, "Show edges").changed() {
155        changed = true;
156    }
157
158    if *show_edges {
159        ui.indent("edges", |ui| {
160            egui::Grid::new("mesh_edge_grid")
161                .num_columns(2)
162                .show(ui, |ui| {
163                    ui.label("Width:");
164                    if ui
165                        .add(egui::DragValue::new(edge_width).speed(0.1).range(0.1..=5.0))
166                        .changed()
167                    {
168                        changed = true;
169                    }
170                    ui.end_row();
171
172                    ui.label("Color:");
173                    if ui.color_edit_button_rgb(edge_color).changed() {
174                        changed = true;
175                    }
176                    ui.end_row();
177                });
178        });
179    }
180
181    ui.separator();
182
183    // Backface policy
184    egui::ComboBox::from_label("Backface")
185        .selected_text(match *backface_policy {
186            0 => "Identical",
187            1 => "Different",
188            2 => "Custom",
189            _ => "Cull",
190        })
191        .show_ui(ui, |ui| {
192            if ui
193                .selectable_value(backface_policy, 0, "Identical")
194                .changed()
195            {
196                changed = true;
197            }
198            if ui
199                .selectable_value(backface_policy, 1, "Different")
200                .changed()
201            {
202                changed = true;
203            }
204            if ui.selectable_value(backface_policy, 2, "Custom").changed() {
205                changed = true;
206            }
207            if ui.selectable_value(backface_policy, 3, "Cull").changed() {
208                changed = true;
209            }
210        });
211
212    changed
213}
214
215/// Builds UI for a curve network.
216pub fn build_curve_network_ui(
217    ui: &mut Ui,
218    num_nodes: usize,
219    num_edges: usize,
220    radius: &mut f32,
221    radius_is_relative: &mut bool,
222    color: &mut [f32; 3],
223    render_mode: &mut u32,
224    material: &mut String,
225    available_materials: &[&str],
226) -> bool {
227    let mut changed = false;
228
229    ui.label(format!("{num_nodes} nodes, {num_edges} edges"));
230
231    if build_material_selector(ui, material, available_materials) {
232        changed = true;
233    }
234
235    ui.separator();
236
237    egui::Grid::new("curve_network_grid")
238        .num_columns(2)
239        .show(ui, |ui| {
240            ui.label("Color:");
241            if ui.color_edit_button_rgb(color).changed() {
242                changed = true;
243            }
244            ui.end_row();
245
246            ui.label("Radius:");
247            if ui
248                .add(
249                    egui::DragValue::new(radius)
250                        .speed(0.001)
251                        .range(0.001..=10.0),
252                )
253                .changed()
254            {
255                changed = true;
256            }
257            ui.end_row();
258        });
259
260    // Render mode
261    egui::ComboBox::from_label("Render")
262        .selected_text(match *render_mode {
263            0 => "Lines",
264            _ => "Tubes",
265        })
266        .show_ui(ui, |ui| {
267            if ui.selectable_value(render_mode, 0, "Lines").changed() {
268                changed = true;
269            }
270            if ui.selectable_value(render_mode, 1, "Tubes").changed() {
271                changed = true;
272            }
273        });
274
275    // Radius is relative checkbox
276    if ui.checkbox(radius_is_relative, "Relative radius").changed() {
277        changed = true;
278    }
279
280    changed
281}