widgetry/
lib.rs

1//! # Widgets
2//!
3//! If none of these do what you need, implementing a new [`WidgetImpl`] isn't tough.
4//!
5//! TODO inline pictures of some of these
6//!
7//! * [`Autocomplete`] - select predefined value by combining text entry with menus
8//! * [`Button`] - clickable buttons with keybindings and tooltips
9//! * [`Toggle`] - checkboxes, switches, and other toggles
10//! * [`CompareTimes`] - a scatter plot specialized for comparing times
11//! * [`DragDrop`] - a reorderable row of draggable cards
12//! * [`DrawWithTooltips`] - draw static geometry, with mouse tooltips in certain regions
13//! * [`Dropdown`] - a button that expands into a menu
14//! * [`FanChart`] - visualize a range of values over time
15//! * [`Filler`] - just carve out space in the layout for something else
16//! * [`JustDraw`] (argh private) - just draw text, `GeomBatch`es, SVGs
17//! * [`LinePlot`] - visualize 2 variables with a line plot
18//! * [`Menu`] - select something from a menu, with keybindings
19//! * [`PersistentSplit`] - a button with a dropdown to change its state
20//! * [`ScatterPlot`] - visualize 2 variables with a scatter plot
21//! * [`Slider`] - horizontal and vertical sliders
22//! * [`Spinner`] - numeric input with up/down buttons
23//! * [`table::Table`] - rows and columns, supporting filtering and pagination
24//! * [`TextBox`] - single line text entry
25
26//#![warn(missing_docs)]
27#![allow(clippy::too_many_arguments, clippy::type_complexity)]
28#![allow(clippy::new_without_default)]
29
30#[macro_use]
31extern crate anyhow;
32#[macro_use]
33extern crate log;
34
35pub use crate::app_state::{DrawBaselayer, SharedAppState, SimpleState, State, Transition};
36pub use crate::backend::Drawable;
37pub use crate::canvas::{Canvas, CanvasSettings, HorizontalAlignment, VerticalAlignment};
38pub use crate::color::{Color, Fill, LinearGradient, Texture};
39pub use crate::drawing::{GfxCtx, Prerender};
40pub use crate::event::{hotkeys, lctrl, Event, Key, MultiKey};
41pub use crate::event_ctx::{EventCtx, UpdateType};
42pub use crate::geom::geom_batch_stack::{
43    Alignment as StackAlignment, Axis as StackAxis, GeomBatchStack,
44};
45pub use crate::geom::{GeomBatch, RewriteColor};
46pub use crate::input::UserInput;
47pub use crate::runner::{run, Settings};
48pub use crate::screen_geom::{ScreenDims, ScreenPt, ScreenRectangle};
49pub use crate::style::{ButtonStyle, OutlineStyle, Style};
50pub use crate::text::{Font, Line, Text, TextExt, TextSpan};
51pub use crate::tools::warper::Warper;
52pub use crate::tools::Cached;
53pub use crate::widgets::autocomplete::Autocomplete;
54pub(crate) use crate::widgets::button::Button;
55pub use crate::widgets::button::ButtonBuilder;
56pub use crate::widgets::compare_times::CompareTimes;
57pub use crate::widgets::drag_drop::DragDrop;
58pub(crate) use crate::widgets::dropdown::Dropdown;
59pub use crate::widgets::fan_chart::FanChart;
60pub use crate::widgets::filler::Filler;
61pub use crate::widgets::image::{Image, ImageSource};
62pub use crate::widgets::just_draw::DrawWithTooltips;
63pub(crate) use crate::widgets::just_draw::{DeferDraw, JustDraw};
64pub use crate::widgets::line_plot::LinePlot;
65pub use crate::widgets::menu::Menu;
66pub use crate::widgets::persistent_split::PersistentSplit;
67pub use crate::widgets::plots::{PlotOptions, Series};
68pub use crate::widgets::scatter_plot::ScatterPlot;
69pub use crate::widgets::slider::Slider;
70pub use crate::widgets::spinner::{RoundedF64, Spinner};
71pub use crate::widgets::stash::Stash;
72pub use crate::widgets::table;
73pub use crate::widgets::tabs::TabController;
74pub use crate::widgets::text_box::TextBox;
75pub use crate::widgets::toggle::Toggle;
76pub use crate::widgets::DEFAULT_CORNER_RADIUS;
77pub use crate::widgets::{
78    ClickOutcome, CornerRounding, EdgeInsets, Outcome, Panel, PanelBuilder, PanelDims, Widget,
79    WidgetImpl, WidgetOutput,
80};
81
82mod app_state;
83mod assets;
84#[cfg(any(feature = "native-backend", feature = "wasm-backend"))]
85mod backend_glow;
86#[cfg(feature = "native-backend")]
87mod backend_glow_native;
88#[cfg(feature = "wasm-backend")]
89mod backend_glow_wasm;
90mod canvas;
91mod color;
92mod drawing;
93mod event;
94mod event_ctx;
95mod geom;
96mod input;
97pub mod mapspace;
98mod runner;
99mod screen_geom;
100mod style;
101mod svg;
102mod text;
103pub mod tools;
104mod widgets;
105
106mod backend {
107    #[cfg(any(feature = "native-backend", feature = "wasm-backend"))]
108    pub use crate::backend_glow::*;
109}
110
111#[cfg(feature = "wasm-backend")]
112pub use crate::backend_glow_wasm::RenderOnly;
113
114/// Like [`std::include_bytes!`], but also returns its argument, the relative path to the bytes
115///
116/// returns a `(path, bytes): (&str, &[u8])` tuple
117#[macro_export]
118macro_rules! include_labeled_bytes {
119    ($file:expr) => {
120        ($file, include_bytes!($file))
121    };
122}
123
124#[derive(Clone, Copy, Debug)]
125pub enum ControlState {
126    Default,
127    Hovered,
128    Disabled,
129    // TODO: Pressing
130}
131
132/// Rules for how content should stretch to fill its bounds
133#[derive(Clone, Copy, Debug)]
134pub enum ContentMode {
135    /// Stretches content to fit its bounds exactly, breaking aspect ratio as necessary.
136    ScaleToFill,
137
138    /// Maintaining aspect ratio, content grows until it touches its bounds in one dimension.
139    /// This is the default ContentMode.
140    ///
141    /// If the aspect ratio of the bounds do not exactly match the aspect ratio of the content,
142    /// then there will be some empty space within the bounds to center the content.
143    ScaleAspectFit,
144
145    /// Maintaining aspect ratio, content grows until both bounds are met.
146    ///
147    /// If the aspect ratio of the bounds do not exactly match the aspect ratio of the content,
148    /// the content will overflow one dimension of its bounds.
149    ScaleAspectFill,
150}
151
152impl Default for ContentMode {
153    fn default() -> Self {
154        ContentMode::ScaleAspectFit
155    }
156}
157
158pub struct Choice<T> {
159    pub label: String,
160    pub data: T,
161    pub(crate) hotkey: Option<MultiKey>,
162    pub(crate) active: bool,
163    pub(crate) tooltip: Option<String>,
164    pub(crate) fg: Option<Color>,
165}
166
167impl<T> Choice<T> {
168    pub fn new<S: Into<String>>(label: S, data: T) -> Choice<T> {
169        Choice {
170            label: label.into(),
171            data,
172            hotkey: None,
173            active: true,
174            tooltip: None,
175            fg: None,
176        }
177    }
178
179    pub fn from(tuples: Vec<(String, T)>) -> Vec<Choice<T>> {
180        tuples
181            .into_iter()
182            .map(|(label, data)| Choice::new(label, data))
183            .collect()
184    }
185
186    pub fn key(mut self, key: Key) -> Choice<T> {
187        assert_eq!(self.hotkey, None);
188        self.hotkey = key.into();
189        self
190    }
191
192    pub fn multikey(mut self, mk: MultiKey) -> Choice<T> {
193        self.hotkey = Some(mk);
194        self
195    }
196
197    pub fn active(mut self, active: bool) -> Choice<T> {
198        self.active = active;
199        self
200    }
201
202    pub fn tooltip<I: Into<String>>(mut self, info: I) -> Choice<T> {
203        self.tooltip = Some(info.into());
204        self
205    }
206
207    pub fn fg(mut self, fg: Color) -> Choice<T> {
208        self.fg = Some(fg);
209        self
210    }
211
212    pub(crate) fn with_value<X>(&self, data: X) -> Choice<X> {
213        Choice {
214            label: self.label.clone(),
215            data,
216            hotkey: self.hotkey.clone(),
217            active: self.active,
218            tooltip: self.tooltip.clone(),
219            fg: self.fg,
220        }
221    }
222}
223
224impl Choice<String> {
225    pub fn string(label: &str) -> Choice<String> {
226        Choice::new(label.to_string(), label.to_string())
227    }
228
229    pub fn strings<I: Into<String>>(list: Vec<I>) -> Vec<Choice<String>> {
230        list.into_iter()
231            .map(|x| {
232                let x = x.into();
233                Choice::new(x.clone(), x)
234            })
235            .collect()
236    }
237}