Skip to main content

elegance/
lib.rs

1//! Elegance — opinionated, beautiful widgets for egui.
2//!
3//! Elegance is a small companion crate to [`egui`] that provides a cohesive
4//! design system inspired by modern web UIs: chunky rounded buttons in a
5//! handful of accent colors, crisp inputs with a focus ring, pill-shaped
6//! status indicators, cards, tabs, segmented buttons, and a matching colour
7//! palette. Four palettes ship built-in — two dark
8//! ([`Theme::slate`] and [`Theme::charcoal`]) and two light
9//! ([`Theme::frost`] and [`Theme::paper`]) — paired so you can toggle without
10//! a layout shift.
11//!
12//! # Getting started
13//!
14//! ```no_run
15//! use eframe::egui;
16//! use elegance::{Theme, Button, Card, Accent};
17//!
18//! fn main() -> eframe::Result<()> {
19//!     eframe::run_ui_native(
20//!         "elegance demo",
21//!         eframe::NativeOptions::default(),
22//!         |ui, _| {
23//!             Theme::slate().install(ui.ctx());
24//!             egui::CentralPanel::default().show_inside(ui, |ui| {
25//!                 Card::new().heading("Hello").show(ui, |ui| {
26//!                     if ui.add(Button::new("Click me").accent(Accent::Blue))
27//!                         .clicked()
28//!                     {
29//!                         println!("clicked!");
30//!                     }
31//!                 });
32//!             });
33//!         },
34//!     )
35//! }
36//! ```
37//!
38//! # Design
39//!
40//! All visuals are driven by a [`Theme`] value. Calling [`Theme::install`]
41//! once at startup configures [`egui::Style`] so that built-in widgets
42//! (labels, sliders, etc.) inherit the elegance look, and it stores the
43//! theme in `ctx` memory so elegance widgets can pick it up automatically.
44
45#![warn(missing_debug_implementations)]
46#![deny(missing_docs)]
47
48mod badge;
49mod button;
50mod callout;
51mod card;
52mod checkbox;
53mod collapsing;
54mod flash;
55mod indicator;
56mod input;
57mod log_bar;
58mod menu;
59mod modal;
60mod multi_terminal;
61mod pairing;
62mod pill;
63mod progress_bar;
64mod segmented;
65mod select;
66mod slider;
67mod spinner;
68mod switch;
69mod tabs;
70mod text_area;
71mod theme;
72mod theme_switcher;
73mod toast;
74
75pub use badge::{Badge, BadgeTone};
76pub use button::{Button, ButtonSize};
77pub use callout::{Callout, CalloutTone};
78pub use card::Card;
79pub use checkbox::Checkbox;
80pub use collapsing::CollapsingSection;
81pub use flash::{flash_error, flash_success, FlashKind, ResponseFlashExt, FLASH_DURATION};
82pub use indicator::{Indicator, IndicatorState};
83pub use input::TextInput;
84pub use log_bar::{LogBar, LogEntry, LogKind};
85pub use menu::{Menu, MenuItem};
86pub use modal::Modal;
87pub use multi_terminal::{
88    LineKind, MultiTerminal, TerminalEvent, TerminalLine, TerminalPane, TerminalStatus,
89};
90pub use pairing::{PairItem, Pairing};
91pub use pill::StatusPill;
92pub use progress_bar::ProgressBar;
93pub use segmented::SegmentedButton;
94pub use select::Select;
95pub use slider::Slider;
96pub use spinner::Spinner;
97pub use switch::Switch;
98pub use tabs::TabBar;
99pub use text_area::TextArea;
100pub use theme::{Accent, BuiltInTheme, Palette, Theme, Typography};
101pub use theme_switcher::ThemeSwitcher;
102pub use toast::{Toast, Toasts};
103
104/// Re-export of [`egui`] for convenience.
105pub use egui;
106
107/// Request a repaint such that the next paint comes ~`1/hz` seconds from now,
108/// independent of display refresh rate.
109///
110/// [`egui::Context::request_repaint_after`] internally subtracts `predicted_dt`
111/// from the requested delay to budget for the paint taking time. On a 60 Hz
112/// integration (egui's default) that subtraction is ~16.7 ms, so a naive
113/// `request_repaint_after(1/30 s)` lands on the very next vsync and produces
114/// ~60 Hz — double the rate you asked for. This helper adds `predicted_dt`
115/// back in so the effective cadence lands near `1/hz` on any refresh rate.
116///
117/// Typical use: throttle continuously-animating widgets (spinners, progress
118/// fills) to 20–30 Hz so they don't burn a full vsync budget on motion the
119/// eye can't resolve.
120#[track_caller]
121pub fn request_repaint_at_rate(ctx: &egui::Context, hz: f32) {
122    let pd = ctx.input(|i| i.predicted_dt);
123    if let Ok(d) = std::time::Duration::try_from_secs_f32(1.0 / hz + pd) {
124        ctx.request_repaint_after(d);
125    }
126}