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 accordion;
49mod avatar;
50mod badge;
51mod browser_tabs;
52mod button;
53mod callout;
54mod card;
55mod checkbox;
56mod collapsing;
57mod color_picker;
58mod context_menu;
59mod drawer;
60mod file_drop_zone;
61mod flash;
62mod gauge;
63mod indicator;
64mod input;
65mod knob;
66mod log_bar;
67mod menu;
68mod menu_bar;
69mod modal;
70mod pairing;
71mod pill;
72mod popover;
73mod progress_bar;
74mod progress_ring;
75mod range_slider;
76mod removable_chip;
77mod segmented;
78mod segmented_control;
79mod select;
80mod slider;
81mod sortable_list;
82mod spinner;
83mod stat_card;
84mod steps;
85mod switch;
86mod tabs;
87mod tag_input;
88mod text_area;
89mod theme;
90mod theme_switcher;
91mod toast;
92mod tooltip;
93
94pub use accordion::{Accordion, AccordionItem, AccordionUi};
95pub use avatar::{Avatar, AvatarGroup, AvatarPresence, AvatarSize, AvatarTone};
96pub use badge::{Badge, BadgeTone};
97pub use browser_tabs::{BrowserTab, BrowserTabs, BrowserTabsEvent};
98pub use button::{Button, ButtonSize};
99pub use callout::{Callout, CalloutTone};
100pub use card::Card;
101pub use checkbox::Checkbox;
102pub use collapsing::CollapsingSection;
103pub use color_picker::ColorPicker;
104pub use context_menu::ContextMenu;
105pub use drawer::{Drawer, DrawerSide};
106pub use file_drop_zone::{FileDropResponse, FileDropZone};
107pub use flash::{flash_error, flash_success, FlashKind, ResponseFlashExt, FLASH_DURATION};
108pub use gauge::{GaugeZones, LinearGauge, RadialGauge};
109pub use indicator::{Indicator, IndicatorState};
110pub use input::TextInput;
111pub use knob::{Knob, KnobScale, KnobSize};
112pub use log_bar::{LogBar, LogEntry, LogKind};
113pub use menu::{Menu, MenuItem, MenuSection, SubMenuItem};
114pub use menu_bar::{MenuBar, MenuBarUi};
115pub use modal::Modal;
116pub use pairing::{PairItem, Pairing};
117pub use pill::StatusPill;
118pub use popover::{Popover, PopoverSide};
119pub use progress_bar::ProgressBar;
120pub use progress_ring::ProgressRing;
121pub use range_slider::RangeSlider;
122pub use removable_chip::{RemovableChip, RemovableChipResponse};
123pub use segmented::SegmentedButton;
124pub use segmented_control::{Segment, SegmentDot, SegmentedControl, SegmentedSize};
125pub use select::Select;
126pub use slider::Slider;
127pub use sortable_list::{SortableItem, SortableList, SortableStatus};
128pub use spinner::Spinner;
129pub use stat_card::StatCard;
130pub use steps::{Steps, StepsStyle};
131pub use switch::Switch;
132pub use tabs::TabBar;
133pub use tag_input::{TagInput, TagInputResponse};
134pub use text_area::TextArea;
135pub use theme::{Accent, BuiltInTheme, Palette, Theme, Typography};
136pub use theme_switcher::ThemeSwitcher;
137pub use toast::{Toast, Toasts};
138pub use tooltip::{Tooltip, TooltipSide};
139
140/// Re-export of [`egui`] for convenience.
141pub use egui;
142
143/// Stable codepoints for the icon glyphs bundled in the Elegance Symbols
144/// font. All icons are sourced from [Lucide](https://lucide.dev) and are
145/// kept in sync via `scripts/update_lucide_glyphs.py`. Use these in
146/// [`egui::RichText`] when you want one of elegance's icons in your own
147/// UI.
148///
149/// ```no_run
150/// # use elegance::glyphs;
151/// # egui::__run_test_ui(|ui| {
152/// ui.label(egui::RichText::new(glyphs::UPLOAD).size(24.0));
153/// # });
154/// ```
155pub mod glyphs {
156 /// Upload-tray icon. Source: [Lucide `upload`](https://lucide.dev/icons/upload).
157 pub const UPLOAD: char = '\u{E000}';
158 /// Download-tray icon. Source: [Lucide `download`](https://lucide.dev/icons/download).
159 pub const DOWNLOAD: char = '\u{E001}';
160 /// Search / magnifier icon. Source: [Lucide `search`](https://lucide.dev/icons/search).
161 pub const SEARCH: char = '\u{E002}';
162 /// Pin icon. Source: [Lucide `pin`](https://lucide.dev/icons/pin).
163 pub const PIN: char = '\u{E003}';
164 /// Copy / duplicate icon. Source: [Lucide `copy`](https://lucide.dev/icons/copy).
165 pub const COPY: char = '\u{E004}';
166 /// Circular alert icon. Source: [Lucide `circle-alert`](https://lucide.dev/icons/circle-alert).
167 pub const CIRCLE_ALERT: char = '\u{E005}';
168 /// Network / hub icon. Source: [Lucide `network`](https://lucide.dev/icons/network).
169 pub const NETWORK: char = '\u{E006}';
170 /// Zoom-in (magnifier with `+`) icon. Source: [Lucide `zoom-in`](https://lucide.dev/icons/zoom-in).
171 pub const ZOOM_IN: char = '\u{E007}';
172 /// Zoom-out (magnifier with `-`) icon. Source: [Lucide `zoom-out`](https://lucide.dev/icons/zoom-out).
173 pub const ZOOM_OUT: char = '\u{E008}';
174 /// Power icon. Source: [Lucide `power`](https://lucide.dev/icons/power).
175 pub const POWER: char = '\u{E009}';
176 /// Check / done mark, mapped at standard U+2713 so plain `'✓'` literals
177 /// also pick up the elegance treatment.
178 /// Source: [Lucide `check`](https://lucide.dev/icons/check).
179 pub const CHECK: char = '\u{2713}';
180 /// Cross / dismiss mark, mapped at standard U+2717 so plain `'✗'` literals
181 /// also pick up the elegance treatment.
182 /// Source: [Lucide `x`](https://lucide.dev/icons/x).
183 pub const X: char = '\u{2717}';
184}
185
186/// Request a repaint such that the next paint comes ~`1/hz` seconds from now,
187/// independent of display refresh rate.
188///
189/// [`egui::Context::request_repaint_after`] internally subtracts `predicted_dt`
190/// from the requested delay to budget for the paint taking time. On a 60 Hz
191/// integration (egui's default) that subtraction is ~16.7 ms, so a naive
192/// `request_repaint_after(1/30 s)` lands on the very next vsync and produces
193/// ~60 Hz — double the rate you asked for. This helper adds `predicted_dt`
194/// back in so the effective cadence lands near `1/hz` on any refresh rate.
195///
196/// Typical use: throttle continuously-animating widgets (spinners, progress
197/// fills) to 20–30 Hz so they don't burn a full vsync budget on motion the
198/// eye can't resolve.
199#[track_caller]
200pub fn request_repaint_at_rate(ctx: &egui::Context, hz: f32) {
201 let pd = ctx.input(|i| i.predicted_dt);
202 if let Ok(d) = std::time::Duration::try_from_secs_f32(1.0 / hz + pd) {
203 ctx.request_repaint_after(d);
204 }
205}