| 1 |
1 |
|
//! The pieces every terminal app draws, drawn once.
|
| 2 |
2 |
|
//!
|
|
3 |
+ |
//! # Called `widget` until 0.19.0
|
|
4 |
+ |
//!
|
|
5 |
+ |
//! Renamed because `makeover-layout` 0.20.0 took the word for something else,
|
|
6 |
+ |
//! and the two meanings do not sit together. A `Region::Widget` there is
|
|
7 |
+ |
//! host-agnostic: a named assembly of primitives that every renderer draws its
|
|
8 |
+ |
//! own way. What is in this module is the opposite end — renderer-local, the
|
|
9 |
+ |
//! answer to *what a meter looks like in cells*, taking a description plus what
|
|
10 |
+ |
//! only a terminal knows.
|
|
11 |
+ |
//!
|
|
12 |
+ |
//! One word for both would have made the tier unreadable in the crate that
|
|
13 |
+ |
//! implements it. This half moved because the other half is the ecosystem-facing
|
|
14 |
+ |
//! one: a second or third party naming a widget is naming the layout kind, and
|
|
15 |
+ |
//! nothing outside this tree ever needed a word for a drawing routine.
|
|
16 |
+ |
//!
|
|
17 |
+ |
//! `WidgetStyle` went with it and is `PieceStyle`.
|
|
18 |
+ |
//!
|
| 3 |
19 |
|
//! Arrived in 0.16.0 out of `quasi-tui`, which had written all of them and was
|
| 4 |
20 |
|
//! the second consumer to do so. A meter, a badge, a control, a figure and a
|
| 5 |
21 |
|
//! form field are what a screen is made of below the level [`table`](crate::table)
|
| 8 |
24 |
|
//!
|
| 9 |
25 |
|
//! # What these take, and what they leave alone
|
| 10 |
26 |
|
//!
|
| 11 |
|
- |
//! Each takes a `makeover-layout` description, a [`WidgetStyle`], and whatever
|
|
27 |
+ |
//! Each takes a `makeover-layout` description, a [`PieceStyle`], and whatever
|
| 12 |
28 |
|
//! the *host* knows that a description never carries. That last part is the
|
| 13 |
29 |
|
//! shape worth copying: [`field`] takes what is currently typed in the box as a
|
| 14 |
30 |
|
//! separate argument, because [`Field`] deliberately does not carry a value and
|
| 19 |
35 |
|
//! is on, so every drawing here takes `focused` as an argument and the caller
|
| 20 |
36 |
|
//! is what counts. What focus *looks like* is this crate's answer and not the
|
| 21 |
37 |
|
//! caller's, which is the point of it being here: see
|
| 22 |
|
- |
//! [`WidgetStyle::focused`].
|
|
38 |
+ |
//! [`PieceStyle::focused`].
|
| 23 |
39 |
|
//!
|
| 24 |
40 |
|
//! # What they do not do
|
| 25 |
41 |
|
//!
|
| 51 |
67 |
|
/// two-colour terminal is the case where a `Style` carrying a foreground is a
|
| 52 |
68 |
|
/// foreground that will not land, and bold-and-reversed is what is left.
|
| 53 |
69 |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 54 |
|
- |
pub struct WidgetStyle {
|
|
70 |
+ |
pub struct PieceStyle {
|
| 55 |
71 |
|
/// Ordinary content, and what [`Tone::Neutral`] reads as.
|
| 56 |
72 |
|
pub content: Style,
|
| 57 |
73 |
|
/// Content one step back: a field's label, a quoted run.
|
| 100 |
116 |
|
pub required_marker: &'static str,
|
| 101 |
117 |
|
}
|
| 102 |
118 |
|
|
| 103 |
|
- |
impl Default for WidgetStyle {
|
|
119 |
+ |
impl Default for PieceStyle {
|
| 104 |
120 |
|
/// Modifiers only, no foreground: what survives a terminal with two
|
| 105 |
121 |
|
/// colours.
|
| 106 |
122 |
|
fn default() -> Self {
|
| 127 |
143 |
|
}
|
| 128 |
144 |
|
}
|
| 129 |
145 |
|
|
| 130 |
|
- |
impl WidgetStyle {
|
|
146 |
+ |
impl PieceStyle {
|
| 131 |
147 |
|
/// The house widgets, from a loaded theme.
|
| 132 |
148 |
|
///
|
| 133 |
149 |
|
/// The lift this module exists for. `quasi-tui` carried every line of this
|
| 254 |
270 |
|
/// taken assembled, which is what [`Meter::label`] carrying the noun alone is
|
| 255 |
271 |
|
/// for: a terminal at one line and a tooltip want different sentence orders.
|
| 256 |
272 |
|
#[must_use]
|
| 257 |
|
- |
pub fn meter(style: &WidgetStyle, meter: &Meter<'_>) -> Line<'static> {
|
|
273 |
+ |
pub fn meter(style: &PieceStyle, meter: &Meter<'_>) -> Line<'static> {
|
| 258 |
274 |
|
let cells = u32::from(style.meter_cells);
|
| 259 |
275 |
|
let filled = meter
|
| 260 |
276 |
|
.done
|
| 298 |
314 |
|
/// interaction, not for a drawing.
|
| 299 |
315 |
|
#[must_use]
|
| 300 |
316 |
|
pub fn token(
|
| 301 |
|
- |
style: &WidgetStyle,
|
|
317 |
+ |
style: &PieceStyle,
|
| 302 |
318 |
|
label: &str,
|
| 303 |
319 |
|
kind: Token,
|
| 304 |
320 |
|
tone: Tone,
|
| 328 |
344 |
|
/// it would be an affordance that lies. Whether it is reachable at all is the
|
| 329 |
345 |
|
/// caller's count to keep — ask [`Act::disabled`].
|
| 330 |
346 |
|
#[must_use]
|
| 331 |
|
- |
pub fn act(style: &WidgetStyle, act: &Act<'_>, focused: bool) -> Line<'static> {
|
|
347 |
+ |
pub fn act(style: &PieceStyle, act: &Act<'_>, focused: bool) -> Line<'static> {
|
| 332 |
348 |
|
let painted = if act.disabled() {
|
| 333 |
349 |
|
style.muted
|
| 334 |
350 |
|
} else {
|
| 347 |
363 |
|
/// carries as a primary-versus-secondary button. A form's submit is the case
|
| 348 |
364 |
|
/// this exists for.
|
| 349 |
365 |
|
#[must_use]
|
| 350 |
|
- |
pub fn filled_act(style: &WidgetStyle, label: &str, focused: bool) -> Line<'static> {
|
|
366 |
+ |
pub fn filled_act(style: &PieceStyle, label: &str, focused: bool) -> Line<'static> {
|
| 351 |
367 |
|
Line::from(Span::styled(
|
| 352 |
368 |
|
format!("[ {label} ]"),
|
| 353 |
369 |
|
style.focused(focused, style.filled),
|
| 365 |
381 |
|
/// The tone lands on the value and its change rather than on the caption, which
|
| 366 |
382 |
|
/// is what [`Figure::tone`] means: the figure is an ordinary fact and it is the
|
| 367 |
383 |
|
/// movement that reads as good or bad.
|
| 368 |
|
- |
pub fn figure(style: &WidgetStyle, figure: &Figure<'_>, area: Rect, buf: &mut Buffer) -> u16 {
|
|
384 |
+ |
pub fn figure(style: &PieceStyle, figure: &Figure<'_>, area: Rect, buf: &mut Buffer) -> u16 {
|
| 369 |
385 |
|
let value = match figure.change {
|
| 370 |
386 |
|
Some(change) => format!("{} {change}", figure.value),
|
| 371 |
387 |
|
None => figure.value.to_owned(),
|
| 385 |
401 |
|
/// field is nothing at all, which is the one field kind a terminal and a webview
|
| 386 |
402 |
|
/// agree on completely.
|
| 387 |
403 |
|
#[must_use]
|
| 388 |
|
- |
pub fn field_height(style: &WidgetStyle, field: &Field<'_>, width: u16) -> u16 {
|
|
404 |
+ |
pub fn field_height(style: &PieceStyle, field: &Field<'_>, width: u16) -> u16 {
|
| 389 |
405 |
|
if !field.kind.visible() {
|
| 390 |
406 |
|
return 0;
|
| 391 |
407 |
|
}
|
| 407 |
423 |
|
/// `focused` marks the box rather than the label, because the box is where the
|
| 408 |
424 |
|
/// typing lands.
|
| 409 |
425 |
|
pub fn field(
|
| 410 |
|
- |
style: &WidgetStyle,
|
|
426 |
+ |
style: &PieceStyle,
|
| 411 |
427 |
|
field: &Field<'_>,
|
| 412 |
428 |
|
held: Held<'_>,
|
| 413 |
429 |
|
focused: bool,
|
| 480 |
496 |
|
}
|
| 481 |
497 |
|
|
| 482 |
498 |
|
/// The label, marked where the field is compulsory.
|
| 483 |
|
- |
fn label_of(style: &WidgetStyle, field: &Field<'_>) -> String {
|
|
499 |
+ |
fn label_of(style: &PieceStyle, field: &Field<'_>) -> String {
|
| 484 |
500 |
|
if field.required {
|
| 485 |
501 |
|
format!("{} {}", field.label, style.required_marker)
|
| 486 |
502 |
|
} else {
|
| 501 |
517 |
|
/// bar for this and gets it without asking; a terminal has one cell of reversed
|
| 502 |
518 |
|
/// video, put on the first column, which is where the first character lands.
|
| 503 |
519 |
|
fn empty_well(
|
| 504 |
|
- |
style: &WidgetStyle,
|
|
520 |
+ |
style: &PieceStyle,
|
| 505 |
521 |
|
placeholder: &str,
|
| 506 |
522 |
|
well: Style,
|
| 507 |
523 |
|
focused: bool,
|
| 537 |
553 |
|
|
| 538 |
554 |
|
/// The style the drawings are read against: one distinguishable modifier
|
| 539 |
555 |
|
/// per role, so a test can say which style landed without a colour.
|
| 540 |
|
- |
fn style() -> WidgetStyle {
|
| 541 |
|
- |
WidgetStyle {
|
|
556 |
+ |
fn style() -> PieceStyle {
|
|
557 |
+ |
PieceStyle {
|
| 542 |
558 |
|
content: Style::new().add_modifier(Modifier::BOLD),
|
| 543 |
559 |
|
muted: Style::new().add_modifier(Modifier::DIM),
|
| 544 |
560 |
|
danger: Style::new().add_modifier(Modifier::CROSSED_OUT),
|
| 545 |
|
- |
..WidgetStyle::default()
|
|
561 |
+ |
..PieceStyle::default()
|
| 546 |
562 |
|
}
|
| 547 |
563 |
|
}
|
| 548 |
564 |
|
|
| 831 |
847 |
|
fn the_default_style_carries_no_colour_at_all() {
|
| 832 |
848 |
|
// A two-colour terminal is the case where a foreground will not land,
|
| 833 |
849 |
|
// so the default is modifiers only rather than a placeholder palette.
|
| 834 |
|
- |
let style = WidgetStyle::default();
|
|
850 |
+ |
let style = PieceStyle::default();
|
| 835 |
851 |
|
for painted in [style.content, style.danger, style.page, style.action] {
|
| 836 |
852 |
|
assert_eq!(painted.fg, None);
|
| 837 |
853 |
|
assert_eq!(painted.bg, None);
|