forked from sinelaw/fresh
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender.rs
More file actions
157 lines (140 loc) · 5.6 KB
/
Copy pathrender.rs
File metadata and controls
157 lines (140 loc) · 5.6 KB
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Toggle rendering functions
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use crate::primitives::display_width::str_width;
use super::{FocusState, ToggleColors, ToggleLayout, ToggleState};
fn pad_to_display_width(label: &str, width: u16) -> String {
let width = width as usize;
let padding = width.saturating_sub(str_width(label));
let mut padded = String::with_capacity(label.len() + padding);
padded.push_str(label);
padded.extend(std::iter::repeat_n(' ', padding));
padded
}
/// Render a toggle control
///
/// # Arguments
/// * `frame` - The ratatui frame to render to
/// * `area` - Rectangle where the toggle should be rendered
/// * `state` - The toggle state
/// * `colors` - Colors for rendering
///
/// # Returns
/// Layout information for hit testing
pub fn render_toggle(
frame: &mut Frame,
area: Rect,
state: &ToggleState,
colors: &ToggleColors,
) -> ToggleLayout {
render_toggle_aligned(frame, area, state, colors, None)
}
/// Render a toggle control with optional label width alignment
///
/// # Arguments
/// * `frame` - The ratatui frame to render to
/// * `area` - Rectangle where the toggle should be rendered
/// * `state` - The toggle state
/// * `colors` - Colors for rendering
/// * `label_width` - Optional minimum label width for alignment
///
/// # Returns
/// Layout information for hit testing
pub fn render_toggle_aligned(
frame: &mut Frame,
area: Rect,
state: &ToggleState,
colors: &ToggleColors,
label_width: Option<u16>,
) -> ToggleLayout {
if area.height == 0 || area.width < 4 {
return ToggleLayout {
checkbox_area: Rect::default(),
full_area: area,
};
}
// When focused/hovered the chip sits on top of the row's highlight bg
// (settings_selected_bg / menu_hover_bg). Use `focused_fg` for the
// checkmark too — themes guarantee `focused_fg` contrasts with
// `focused` (their bg), whereas `checkmark` is green-ish in most
// themes and collides with green-tinted highlights (e.g. Nostalgia).
let (bracket_color, check_color, label_color) = match state.focus {
FocusState::Normal => (colors.bracket, colors.checkmark, colors.label),
FocusState::Focused => (colors.focused_fg, colors.focused_fg, colors.focused_fg),
FocusState::Hovered => (colors.focused_fg, colors.focused_fg, colors.focused_fg),
FocusState::Disabled => (colors.disabled, colors.disabled, colors.disabled),
};
// Format: "Label: [v]" / "Label: [ ]" with optional padding.
let label_display_width = str_width(&state.label) as u16;
let actual_label_width = label_width
.unwrap_or(label_display_width)
.max(label_display_width);
let padded_label = pad_to_display_width(&state.label, actual_label_width);
// Compact checkbox glyph — matches the widget framework's
// `[v]` / `[ ]` convention so an empty checkbox is not visually
// confusable with an empty text input.
// checked: [v]
// unchecked: [ ]
// inherited: [-] (value is unset and falls back to a lower layer)
const CHIP_WIDTH: u16 = 3;
let line = if state.inherited {
// Neutral chip: the value is inherited/unset, so we deliberately avoid
// a definite checked/unchecked glyph that could be read as the user
// having set it off (issue #2345).
Line::from(vec![
Span::styled(padded_label, Style::default().fg(label_color)),
Span::styled(": ", Style::default().fg(label_color)),
Span::styled("[", Style::default().fg(bracket_color)),
Span::styled("-", Style::default().fg(bracket_color)),
Span::styled("]", Style::default().fg(bracket_color)),
])
} else if state.checked {
Line::from(vec![
Span::styled(padded_label, Style::default().fg(label_color)),
Span::styled(": ", Style::default().fg(label_color)),
Span::styled("[", Style::default().fg(bracket_color)),
Span::styled(
"v",
Style::default()
.fg(check_color)
.add_modifier(ratatui::style::Modifier::BOLD),
),
Span::styled("]", Style::default().fg(bracket_color)),
])
} else {
Line::from(vec![
Span::styled(padded_label, Style::default().fg(label_color)),
Span::styled(": ", Style::default().fg(label_color)),
Span::styled("[", Style::default().fg(bracket_color)),
Span::styled(" ", Style::default().fg(bracket_color)),
Span::styled("]", Style::default().fg(bracket_color)),
])
};
let paragraph = Paragraph::new(line);
frame.render_widget(paragraph, area);
// Chip position after label (label + ": ").
let label_overhead = actual_label_width.saturating_add(2);
let checkbox_start = area.x.saturating_add(label_overhead);
let chip_avail = area.width.saturating_sub(label_overhead.min(area.width));
let checkbox_area = Rect::new(checkbox_start, area.y, CHIP_WIDTH.min(chip_avail), 1);
// Full area is label + ": " + chip
let full_width = (actual_label_width + 2 + CHIP_WIDTH).min(area.width);
let full_area = Rect::new(area.x, area.y, full_width, 1);
ToggleLayout {
checkbox_area,
full_area,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pad_to_display_width_uses_terminal_columns() {
let padded = pad_to_display_width("你好", 6);
assert_eq!(str_width(&padded), 6);
assert_eq!(padded, "你好 ");
}
}