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