egui_probe/
lib.rs

1//! # Egui Probe
2//!
3//! Effortlessly create UI widgets to display and modify value types using a derive macro with rich customization via attributes. This library is exclusively for the [egui](https://github.com/emilk/egui) UI framework.
4//!
5//! ## Features
6//!
7//! - 🪄 **Derive Macro**: Automatically generate UI widgets for your types.
8//! - 🎨 **Rich Customization**: Customize the generated widgets using attributes.
9//! - 🚀 **Seamless Integration**: Designed to work seamlessly with egui.
10//!
11//! ## Getting Started
12//!
13//! Add `egui_probe` to your `Cargo.toml`:
14//!
15//! ```toml
16//! [dependencies]
17//! egui_probe = "0.5.2"
18//! ```
19//!
20//! ## Usage
21//!
22//! Derive `EguiProbe` for your types and use attributes to customize the UI:
23//!
24#![cfg_attr(feature = "derive", doc = "```")]
25#![cfg_attr(
26    not(feature = "derive"),
27    doc = "```ignore\n// This example requires the `derive` feature."
28)]
29//! use egui_probe::{EguiProbe, Probe, angle};
30//! use eframe::App;
31//!
32//! #[derive(EguiProbe)]
33//! struct DemoValue {
34//!     boolean: bool,
35//!
36//!     #[egui_probe(toggle_switch)]
37//!     boolean_toggle: bool,
38//!
39//!     float: f32,
40//!
41//!     #[egui_probe(range = 22..=55)]
42//!     range: usize,
43//!
44//!     #[egui_probe(as angle)]
45//!     angle: f32,
46//!
47//!     #[egui_probe(name = "renamed ^_^")]
48//!     renamed: u8,
49//!
50//!     inner: InnerValue,
51//! }
52//!
53//! #[derive(Default, EguiProbe)]
54//! struct InnerValue {
55//!     line: String,
56//!
57//!     #[egui_probe(multiline)]
58//!     multi_line: String,
59//! }
60//!
61//! struct EguiProbeDemoApp {
62//!     value: DemoValue,
63//! }
64//!
65//! impl App for EguiProbeDemoApp {
66//!     fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
67//!         egui::CentralPanel::default().show(ctx, |ui| {
68//!             Probe::new(&mut self.value).show(ui);
69//!         });
70//!     }
71//! }
72//! ```
73//!
74//! ## Attributes
75//!
76//! - `#[egui_probe(toggle_switch)]`: Render a boolean as a toggle switch.
77//! - `#[egui_probe(range = 22..=55)]`: Specify a range for numeric values.
78//! - `#[egui_probe(as angle)]`: Render a float as an angle.
79//! - `#[egui_probe(name = "custom name")]`: Rename the field in the UI.
80//! - `#[egui_probe(multiline)]`: Render a string as a multiline text box.
81//!
82//! ## License
83//!
84//! This project is licensed under either of
85//!
86//! - MIT License
87//! - Apache License, Version 2.0
88//!
89//! at your option.
90//!
91//! ## Contributing
92//!
93//! Contributions are welcome! Please open an issue or submit a pull request.
94//!
95//! Enjoy building your UI with Egui Probe! 🚀
96#![allow(clippy::inline_always, clippy::use_self)]
97
98mod algebra;
99mod array;
100mod boolean;
101mod collections;
102mod color;
103#[cfg(feature = "hashbrown")]
104mod hashbrown;
105mod map;
106mod num;
107mod option;
108mod set;
109#[cfg(any(feature = "smallvec1", feature = "smallvec2"))]
110mod small_vec;
111mod text;
112mod ui;
113mod vec;
114mod widget;
115
116pub use egui;
117
118pub use self::{
119    boolean::toggle_switch,
120    collections::DeleteMe,
121    option::option_probe_with,
122    widget::{Probe, ProbeLayout},
123};
124
125#[derive(Clone, Copy, Debug)]
126pub enum BooleanStyle {
127    Checkbox,
128    ToggleSwitch,
129}
130
131impl Default for BooleanStyle {
132    #[inline]
133    fn default() -> Self {
134        Self::Checkbox
135    }
136}
137
138#[derive(Clone, Copy, Debug)]
139pub enum VariantsStyle {
140    Inlined,
141    ComboBox,
142}
143
144impl Default for VariantsStyle {
145    #[inline]
146    fn default() -> Self {
147        Self::ComboBox
148    }
149}
150
151/// Controls the style of probbing UI.
152#[derive(Clone, Copy, Debug)]
153pub struct Style {
154    pub boolean: BooleanStyle,
155    pub variants: VariantsStyle,
156    pub field_indent_size: Option<f32>,
157    pub add_button_char: Option<char>,
158    pub remove_button_char: Option<char>,
159}
160
161impl Default for Style {
162    #[inline]
163    fn default() -> Self {
164        Style {
165            boolean: BooleanStyle::default(),
166            variants: VariantsStyle::default(),
167            field_indent_size: None,
168            add_button_char: None,
169            remove_button_char: None,
170        }
171    }
172}
173
174impl Style {
175    #[must_use]
176    pub fn add_button_text(&self) -> String {
177        self.add_button_char.unwrap_or('+').to_string()
178    }
179
180    #[must_use]
181    pub fn remove_button_text(&self) -> String {
182        self.remove_button_char.unwrap_or('-').to_string()
183    }
184}
185
186/// Provides ability to show probbing UI to values.
187pub trait EguiProbe {
188    /// Shows probbing UI to edit the value.
189    fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response;
190
191    /// Shows probbing UI to edit the inner values.
192    ///
193    /// It should add pairs of widgets to the UI for each record.
194    /// If record has sub-records it should flatten them.
195    #[inline(always)]
196    fn iterate_inner(
197        &mut self,
198        ui: &mut egui::Ui,
199        f: &mut dyn FnMut(&str, &mut egui::Ui, &mut dyn EguiProbe),
200    ) {
201        let _ = (ui, f);
202    }
203}
204
205impl<P> EguiProbe for &mut P
206where
207    P: EguiProbe,
208{
209    #[inline(always)]
210    fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
211        P::probe(*self, ui, style)
212    }
213
214    #[inline(always)]
215    fn iterate_inner(
216        &mut self,
217        ui: &mut egui::Ui,
218        f: &mut dyn FnMut(&str, &mut egui::Ui, &mut dyn EguiProbe),
219    ) {
220        P::iterate_inner(*self, ui, f);
221    }
222}
223
224impl<P> EguiProbe for Box<P>
225where
226    P: EguiProbe,
227{
228    #[inline(always)]
229    fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
230        P::probe(&mut *self, ui, style)
231    }
232
233    #[inline(always)]
234    fn iterate_inner(
235        &mut self,
236        ui: &mut egui::Ui,
237        f: &mut dyn FnMut(&str, &mut egui::Ui, &mut dyn EguiProbe),
238    ) {
239        P::iterate_inner(&mut *self, ui, f);
240    }
241}
242
243#[derive(Clone, Copy)]
244#[repr(transparent)]
245pub struct EguiProbeFn<F>(pub F);
246
247impl<F> EguiProbe for EguiProbeFn<F>
248where
249    F: FnMut(&mut egui::Ui, &Style) -> egui::Response,
250{
251    #[inline(always)]
252    fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
253        (self.0)(ui, style)
254    }
255}
256
257/// Wrap a function into probe-able.
258#[inline(always)]
259pub const fn probe_fn<F>(f: F) -> EguiProbeFn<F> {
260    EguiProbeFn(f)
261}
262
263#[inline(always)]
264pub fn angle(value: &mut f32) -> impl EguiProbe + '_ {
265    probe_fn(move |ui: &mut egui::Ui, _style: &Style| ui.drag_angle(value))
266}
267
268pub mod customize {
269    use std::ops::RangeFull;
270
271    use super::{
272        EguiProbe, Style,
273        boolean::ToggleSwitch,
274        collections::EguiProbeFrozen,
275        color::{
276            EguiProbeRgb, EguiProbeRgba, EguiProbeRgbaPremultiplied, EguiProbeRgbaUnmultiplied,
277        },
278        egui,
279        num::{EguiProbeRange, StepUnset},
280        probe_fn,
281        text::EguiProbeMultiline,
282    };
283
284    #[inline(always)]
285    pub fn probe_with<'a, T, F>(mut f: F, value: &'a mut T) -> impl EguiProbe + 'a
286    where
287        F: FnMut(&mut T, &mut egui::Ui, &Style) -> egui::Response + 'a,
288    {
289        probe_fn(move |ui: &mut egui::Ui, style: &Style| f(value, ui, style))
290    }
291
292    #[inline(always)]
293    pub fn probe_as<'a, T, F, R>(f: F, value: &'a mut T) -> impl EguiProbe + 'a
294    where
295        F: FnOnce(&'a mut T) -> R,
296        R: EguiProbe + 'a,
297    {
298        f(value)
299    }
300
301    #[inline(always)]
302    pub const fn probe_range<'a, T, R>(range: R, value: &'a mut T) -> EguiProbeRange<'a, T, R>
303    where
304        EguiProbeRange<'a, T, R>: EguiProbe,
305    {
306        EguiProbeRange {
307            value,
308            range,
309            step: StepUnset,
310        }
311    }
312
313    #[inline(always)]
314    pub const fn probe_range_step<'a, T, R, S>(
315        range: R,
316        step: S,
317        value: &'a mut T,
318    ) -> EguiProbeRange<'a, T, R, S>
319    where
320        EguiProbeRange<'a, T, R, S>: EguiProbe,
321    {
322        EguiProbeRange { value, range, step }
323    }
324
325    #[inline(always)]
326    pub const fn probe_step<'a, T, S>(
327        step: S,
328        value: &'a mut T,
329    ) -> EguiProbeRange<'a, T, RangeFull, S>
330    where
331        EguiProbeRange<'a, T, RangeFull, S>: EguiProbe,
332    {
333        EguiProbeRange {
334            value,
335            range: ..,
336            step,
337        }
338    }
339
340    #[inline(always)]
341    pub const fn probe_multiline<'a, T>(string: &'a mut T) -> EguiProbeMultiline<'a, T>
342    where
343        EguiProbeMultiline<'a, T>: EguiProbe,
344    {
345        EguiProbeMultiline { string }
346    }
347
348    #[inline(always)]
349    pub fn probe_toggle_switch<'a, T>(value: &'a mut T) -> impl EguiProbe + 'a
350    where
351        ToggleSwitch<'a, T>: EguiProbe,
352    {
353        ToggleSwitch(value)
354    }
355
356    #[inline(always)]
357    pub fn probe_frozen<'a, T>(value: &'a mut T) -> impl EguiProbe + 'a
358    where
359        EguiProbeFrozen<'a, T>: EguiProbe,
360    {
361        EguiProbeFrozen { value }
362    }
363
364    #[inline(always)]
365    pub fn probe_rgb<'a, T>(value: &'a mut T) -> impl EguiProbe + 'a
366    where
367        EguiProbeRgb<'a, T>: EguiProbe,
368    {
369        EguiProbeRgb { value }
370    }
371
372    #[inline(always)]
373    pub fn probe_rgba<'a, T>(value: &'a mut T) -> impl EguiProbe + 'a
374    where
375        EguiProbeRgba<'a, T>: EguiProbe,
376    {
377        EguiProbeRgba { value }
378    }
379
380    #[inline(always)]
381    pub fn probe_rgba_premultiplied<'a, T>(value: &'a mut T) -> impl EguiProbe + 'a
382    where
383        EguiProbeRgbaPremultiplied<'a, T>: EguiProbe,
384    {
385        EguiProbeRgbaPremultiplied { value }
386    }
387
388    #[inline(always)]
389    pub fn probe_rgba_unmultiplied<'a, T>(value: &'a mut T) -> impl EguiProbe + 'a
390    where
391        EguiProbeRgbaUnmultiplied<'a, T>: EguiProbe,
392    {
393        EguiProbeRgbaUnmultiplied { value }
394    }
395}
396
397#[cfg(feature = "derive")]
398pub use egui_probe_proc::EguiProbe;
399
400#[cfg(feature = "derive")]
401extern crate self as egui_probe;
402
403#[cfg(feature = "derive")]
404#[doc(hidden)]
405pub mod private {
406    pub use super::customize::*;
407    pub use core::stringify;
408}
409
410#[cfg(feature = "derive")]
411#[test]
412fn test_all_attributes() {
413    #![allow(unused)]
414
415    trait A {}
416
417    #[derive(EguiProbe)]
418    #[egui_probe(where T: EguiProbe)]
419    struct TypeAttributes<T> {
420        a: T,
421    }
422
423    struct NoProbe;
424
425    #[derive(EguiProbe)]
426    #[egui_probe(rename_all = Train-Case)]
427    struct FieldAttributes {
428        #[egui_probe(skip)]
429        skipped: NoProbe,
430
431        #[egui_probe(name = "renamed")]
432        a: u8,
433
434        #[egui_probe(with |_, ui, _| ui.label("a label"))]
435        b: u8,
436
437        #[egui_probe(as angle)]
438        c: f32,
439
440        #[egui_probe(range = 0..=100)]
441        d: u8,
442
443        #[egui_probe(multiline)]
444        e: String,
445
446        #[egui_probe(multiline)]
447        f: Option<String>,
448
449        #[egui_probe(toggle_switch)]
450        g: bool,
451
452        #[egui_probe(toggle_switch)]
453        h: Option<bool>,
454
455        #[egui_probe(frozen)]
456        i: Vec<u8>,
457
458        #[egui_probe(rgb)]
459        j: egui::Color32,
460
461        #[egui_probe(rgba)]
462        k: egui::Color32,
463
464        #[egui_probe(rgba_premultiplied)]
465        l: [u8; 4],
466
467        #[egui_probe(rgba_unmultiplied)]
468        m: [f32; 4],
469    }
470
471    #[derive(EguiProbe)]
472    #[egui_probe(tags combobox)]
473    enum EnumAttributes {
474        #[egui_probe(name = "renamed")]
475        A,
476
477        #[egui_probe(transparent)]
478        B {
479            #[egui_probe(skip)]
480            skipped: (),
481
482            b: f32,
483        },
484    }
485}