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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! Implements a list box for single or multiple selections from a list.
use std::fmt::Display;
use crate::{
core::{
crossterm::{
self,
event::Event,
style::{Attribute, Attributes, Color, ContentStyle},
},
render::{Renderer, SharedRenderer},
Widget,
},
preset::Evaluator,
widgets::{
listbox::{self, config::Config},
text::{self, Text},
},
Signal,
};
pub mod evaluate;
/// Represents the indices of various components in the listbox preset.
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum Index {
Title = 0,
Listbox = 1,
}
/// A component for creating and managing a selectable list of options.
pub struct Listbox {
/// Shared renderer for the prompt, allowing for rendering of UI components.
pub renderer: Option<SharedRenderer<Index>>,
/// Function to evaluate the input events and update the state of the prompt.
pub evaluator: Evaluator<Self>,
/// State for the title displayed above the selectable list.
pub title: text::State,
/// State for the selectable list itself.
pub listbox: listbox::State,
}
#[async_trait::async_trait]
impl crate::Prompt for Listbox {
async fn initialize(&mut self) -> anyhow::Result<()> {
let size = crossterm::terminal::size()?;
self.renderer = Some(SharedRenderer::new(
Renderer::try_new_with_graphemes(
[
(Index::Title, self.title.create_graphemes(size.0, size.1)),
(
Index::Listbox,
self.listbox.create_graphemes(size.0, size.1),
),
],
true,
)
.await?,
));
Ok(())
}
async fn evaluate(&mut self, event: &Event) -> anyhow::Result<Signal> {
let ret = (self.evaluator)(event, self).await;
let size = crossterm::terminal::size()?;
self.render(size.0, size.1).await?;
ret
}
type Return = String;
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
Ok(self.listbox.listbox.get().to_string())
}
}
impl Listbox {
/// Constructs a new `Listbox` instance
/// with a list of items to be displayed as selectable options.
///
/// # Arguments
///
/// * `items` - An iterator over items
/// that implement the `Display` trait, to be used as options.
pub fn new<T: Display, I: IntoIterator<Item = T>>(items: I) -> Self {
Self {
renderer: None,
evaluator: |event, ctx| Box::pin(evaluate::default(event, ctx)),
title: text::State {
config: text::config::Config {
style: Some(ContentStyle {
attributes: Attributes::from(Attribute::Bold),
..Default::default()
}),
..Default::default()
},
..Default::default()
},
listbox: listbox::State {
listbox: listbox::Listbox::from(items),
config: Config {
cursor: String::from("❯ "),
active_item_style: Some(ContentStyle {
foreground_color: Some(Color::DarkCyan),
..Default::default()
}),
inactive_item_style: Some(ContentStyle::default()),
lines: Default::default(),
},
},
}
}
/// Sets the title text displayed above the selectable list.
pub fn title<T: AsRef<str>>(mut self, text: T) -> Self {
self.title.text = Text::from(text);
self
}
/// Sets the style for the title text.
pub fn title_style(mut self, style: ContentStyle) -> Self {
self.title.config.style = Some(style);
self
}
/// Sets the cursor symbol used to indicate the current selection.
pub fn cursor<T: AsRef<str>>(mut self, cursor: T) -> Self {
self.listbox.config.cursor = cursor.as_ref().to_string();
self
}
/// Sets the style for active (currently selected) items.
pub fn active_item_style(mut self, style: ContentStyle) -> Self {
self.listbox.config.active_item_style = Some(style);
self
}
/// Sets the style for inactive (not currently selected) items.
pub fn inactive_item_style(mut self, style: ContentStyle) -> Self {
self.listbox.config.inactive_item_style = Some(style);
self
}
/// Sets the number of lines to be used for displaying the selectable list.
pub fn listbox_lines(mut self, lines: usize) -> Self {
self.listbox.config.lines = Some(lines);
self
}
/// Sets the evaluator function for handling input events.
pub fn evaluator(mut self, evaluator: Evaluator<Self>) -> Self {
self.evaluator = evaluator;
self
}
/// Render the prompt with the specified width and height.
async fn render(&mut self, width: u16, height: u16) -> anyhow::Result<()> {
match self.renderer.as_ref() {
Some(renderer) => {
renderer
.update([
(Index::Title, self.title.create_graphemes(width, height)),
(Index::Listbox, self.listbox.create_graphemes(width, height)),
])
.render()
.await
}
None => Err(anyhow::anyhow!("Renderer not initialized")),
}
}
}