1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! The anatomy of every selectable row in a list-like widget, built once so hover, selection and
//! slide look and behave the same everywhere.
//!
//! A row has three parts after its pillar:
//!
//! 1. **Fixed leading marks**: indentation, a multi-select check mark, an expand chevron. They
//! never move, so a mark stays exactly where the pointer clicks it.
//! 2. **The sliding part**: icon and label. When the row is hovered or selected and the theme
//! allows sliding, only this part moves one cell right.
//! 3. **Fixed trailing content**: detail, badge, shortcut, value, status, close mark. The caller
//! draws it anchored to the right edge; the row only keeps its cells free.
//!
//! The label column always keeps one spare cell for the slide, so a label is cut with `…` at the
//! same place whether its row rests or slides.
//!
//! [`List`](super::List), [`Menu`](super::Menu), [`Tree`](super::Tree),
//! [`Accordion`](super::Accordion), [`WidgetDock`](super::WidgetDock) and
//! [`TabRail`](super::TabRail) draw their rows with it; [`Table`](super::Table) uses its fixed
//! marks and its spare-cell rule for the first cell.
use crate::color::Rgb;
use crate::geometry::Rect;
use crate::style::{CellStyle, WidgetStyle};
use crate::text;
use crate::widget::PaintCx;
/// Cells before the fixed marks: the pillar and a space.
pub(crate) const LEAD: u16 = 2;
/// A glyph drawn in a row with its style; it takes its width plus one cell of air.
pub(crate) type Mark = (String, CellStyle);
/// What one row shows, part by part.
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct Parts<'a> {
/// Cells of indentation after [`LEAD`], e.g. a tree level; they never move.
pub(crate) indent: u16,
/// Leading marks that never move: a check mark, a chevron.
pub(crate) fixed: &'a [Mark],
/// Marks that slide with the label: icons.
pub(crate) sliding: &'a [Mark],
/// The label, drawn in the row style's text colour.
pub(crate) label: &'a str,
/// Cells at the right the caller fills with anchored content.
pub(crate) trailing: u16,
}
/// The mark of icon `key` in theme colour `color`, or in `fallback` (usually the row's text colour)
/// when the icon has none.
pub(crate) fn icon(cx: &PaintCx<'_>, key: &str, color: Option<&str>, fallback: Option<Rgb>) -> Mark {
let fg = color.map_or(fallback, |token| Some(cx.color(token)));
(cx.env().icons().glyph(key).into_owned(), CellStyle { fg, ..CellStyle::default() })
}
/// The fixed check mark of a multi-select row: the accent box when `checked`, a faint one when not.
pub(crate) fn check(cx: &PaintCx<'_>, checked: bool) -> Mark {
let (key, color) = if checked { ("select-on", cx.color("accent")) } else { ("select-off", cx.color("muted")) };
(cx.env().icons().glyph(key).into_owned(), CellStyle::fg(color))
}
/// Paints `text` anchored to the right end of `rect`, keeping the row's last cell free: the detail,
/// badge or count that never slides.
pub(crate) fn paint_trailing(cx: &mut PaintCx<'_>, rect: Rect, text: &str, style: CellStyle) {
let width = text::width(text);
cx.text(rect.right() - 1 - i32::from(width), rect.y, text, style, width);
}
/// Draws `marks` from `x` on row `y` within `budget` cells and returns the cells used.
pub(crate) fn paint_marks(cx: &mut PaintCx<'_>, x: i32, y: i32, marks: &[Mark], budget: u16) -> u16 {
let mut used = 0u16;
for (glyph, style) in marks {
let left = budget.saturating_sub(used);
if left == 0 {
break;
}
cx.text(x + i32::from(used), y, glyph, *style, left);
used = used.saturating_add(text::width(glyph).saturating_add(1)).min(budget);
}
used
}
/// Paints `style`'s surface and pillar over `rect`, then the row's `parts`. When `slide` is set
/// only the sliding part moves one cell right.
pub(crate) fn paint_parts(cx: &mut PaintCx<'_>, rect: Rect, style: &WidgetStyle, slide: bool, parts: &Parts<'_>) {
let text_style = style.text();
if let Some(bg) = text_style.bg {
cx.fill(rect, bg);
}
if let Some(pillar) = style.color("pillar") {
cx.pillar(rect.x, rect.y, pillar);
}
// Pillar, indentation, fixed marks, trailing content, the spare slide cell and the margin.
// Saturating: a deep indent or a trailing text can be wider than any screen.
let reserved = super::cells::sum([LEAD, parts.indent, parts.trailing, 1, 1]);
let mut budget = rect.width.saturating_sub(reserved);
let fixed_x = rect.x + i32::from(LEAD) + i32::from(parts.indent);
let used = paint_marks(cx, fixed_x, rect.y, parts.fixed, budget);
budget = budget.saturating_sub(used);
let mut x = fixed_x + i32::from(used) + i32::from(slide);
let used = paint_marks(cx, x, rect.y, parts.sliding, budget);
x += i32::from(used);
budget = budget.saturating_sub(used);
let shown = text::truncate(parts.label, budget).into_owned();
cx.text(x, rect.y, &shown, CellStyle { bg: None, ..text_style }, budget);
}
/// Paints a row with only sliding `marks` (icons) and `label`: the plain row of lists, menus and
/// tab rails.
pub(crate) fn paint(
cx: &mut PaintCx<'_>,
rect: Rect,
style: &WidgetStyle,
slide: bool,
marks: &[Mark],
label: &str,
trailing: u16,
) {
paint_parts(cx, rect, style, slide, &Parts { sliding: marks, label, trailing, ..Parts::default() });
}