Struct ratatui::widgets::ListItem

source ·
pub struct ListItem<'a> { /* private fields */ }
Expand description

A single item in a List

The item’s height is defined by the number of lines it contains. This can be queried using ListItem::height. Similarly, ListItem::width will return the maximum width of all lines.

You can set the style of an item with ListItem::style or using the Stylize trait. This Style will be combined with the Style of the inner Text. The Style of the Text will be added to the Style of the ListItem.

You can also align a ListItem by aligning its underlying Text and Lines. For that, see Text::alignment and Line::alignment. On a multiline Text, one Line can override the alignment by setting it explicitly.

§Examples

You can create ListItems from simple &str

let item = ListItem::new("Item 1");

Anything that can be converted to Text can be a ListItem.

let item1: ListItem = "Item 1".into();
let item2: ListItem = Line::raw("Item 2").into();

A ListItem styled with Stylize

let item = ListItem::new("Item 1").red().on_white();

If you need more control over the item’s style, you can explicitly style the underlying Text

let mut text = Text::default();
text.extend(["Item".blue(), Span::raw(" "), "1".bold().red()]);
let item = ListItem::new(text);

A right-aligned ListItem

ListItem::new(Text::from("foo").alignment(Alignment::Right));

Implementations§

source§

impl<'a> ListItem<'a>

source

pub fn new<T>(content: T) -> Self
where T: Into<Text<'a>>,

Creates a new ListItem

The content parameter accepts any value that can be converted into Text.

§Examples

You can create ListItems from simple &str

let item = ListItem::new("Item 1");

Anything that can be converted to Text can be a ListItem.

let item1: ListItem = "Item 1".into();
let item2: ListItem = Line::raw("Item 2").into();

You can also create multilines item

let item = ListItem::new("Multi-line\nitem");
§See also
Examples found in repository?
examples/list.rs (line 350)
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
    fn to_list_item(&self, index: usize) -> ListItem {
        let bg_color = match index % 2 {
            0 => NORMAL_ROW_COLOR,
            _ => ALT_ROW_COLOR,
        };
        let line = match self.status {
            Status::Todo => Line::styled(format!(" ☐ {}", self.todo), TEXT_COLOR),
            Status::Completed => Line::styled(
                format!(" ✓ {}", self.todo),
                (COMPLETED_TEXT_COLOR, bg_color),
            ),
        };

        ListItem::new(line).bg(bg_color)
    }
More examples
Hide additional examples
examples/demo2/tabs/email.rs (line 94)
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
fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
    let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
    let [tabs, inbox] = vertical.areas(area);
    let theme = THEME.email;
    Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
        .style(theme.tabs)
        .highlight_style(theme.tabs_selected)
        .select(0)
        .divider("")
        .render(tabs, buf);

    let highlight_symbol = ">>";
    let from_width = EMAILS
        .iter()
        .map(|e| e.from.width())
        .max()
        .unwrap_or_default();
    let items = EMAILS
        .iter()
        .map(|e| {
            let from = format!("{:width$}", e.from, width = from_width).into();
            ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
        })
        .collect_vec();
    let mut state = ListState::default().with_selected(Some(selected_index));
    StatefulWidget::render(
        List::new(items)
            .style(theme.inbox)
            .highlight_style(theme.selected_item)
            .highlight_symbol(highlight_symbol),
        inbox,
        buf,
        &mut state,
    );
    let mut scrollbar_state = ScrollbarState::default()
        .content_length(EMAILS.len())
        .position(selected_index);
    Scrollbar::default()
        .begin_symbol(None)
        .end_symbol(None)
        .track_symbol(None)
        .thumb_symbol("▐")
        .render(inbox, buf, &mut scrollbar_state);
}
examples/inline.rs (lines 261-273)
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
fn ui(f: &mut Frame, downloads: &Downloads) {
    let area = f.size();

    let block = Block::new().title(block::Title::from("Progress").alignment(Alignment::Center));
    f.render_widget(block, area);

    let vertical = Layout::vertical([Constraint::Length(2), Constraint::Length(4)]).margin(1);
    let horizontal = Layout::horizontal([Constraint::Percentage(20), Constraint::Percentage(80)]);
    let [progress_area, main] = vertical.areas(area);
    let [list_area, gauge_area] = horizontal.areas(main);

    // total progress
    let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
    #[allow(clippy::cast_precision_loss)]
    let progress = LineGauge::default()
        .gauge_style(Style::default().fg(Color::Blue))
        .label(format!("{done}/{NUM_DOWNLOADS}"))
        .ratio(done as f64 / NUM_DOWNLOADS as f64);
    f.render_widget(progress, progress_area);

    // in progress downloads
    let items: Vec<ListItem> = downloads
        .in_progress
        .values()
        .map(|download| {
            ListItem::new(Line::from(vec![
                Span::raw(symbols::DOT),
                Span::styled(
                    format!(" download {:>2}", download.id),
                    Style::default()
                        .fg(Color::LightGreen)
                        .add_modifier(Modifier::BOLD),
                ),
                Span::raw(format!(
                    " ({}ms)",
                    download.started_at.elapsed().as_millis()
                )),
            ]))
        })
        .collect();
    let list = List::new(items);
    f.render_widget(list, list_area);

    #[allow(clippy::cast_possible_truncation)]
    for (i, (_, download)) in downloads.in_progress.iter().enumerate() {
        let gauge = Gauge::default()
            .gauge_style(Style::default().fg(Color::Yellow))
            .ratio(download.progress / 100.0);
        if gauge_area.top().saturating_add(i as u16) > area.bottom() {
            continue;
        }
        f.render_widget(
            gauge,
            Rect {
                x: gauge_area.left(),
                y: gauge_area.top().saturating_add(i as u16),
                width: gauge_area.width,
                height: 1,
            },
        );
    }
}
examples/user_input.rs (line 268)
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
fn ui(f: &mut Frame, app: &App) {
    let vertical = Layout::vertical([
        Constraint::Length(1),
        Constraint::Length(3),
        Constraint::Min(1),
    ]);
    let [help_area, input_area, messages_area] = vertical.areas(f.size());

    let (msg, style) = match app.input_mode {
        InputMode::Normal => (
            vec![
                "Press ".into(),
                "q".bold(),
                " to exit, ".into(),
                "e".bold(),
                " to start editing.".bold(),
            ],
            Style::default().add_modifier(Modifier::RAPID_BLINK),
        ),
        InputMode::Editing => (
            vec![
                "Press ".into(),
                "Esc".bold(),
                " to stop editing, ".into(),
                "Enter".bold(),
                " to record the message".into(),
            ],
            Style::default(),
        ),
    };
    let text = Text::from(Line::from(msg)).patch_style(style);
    let help_message = Paragraph::new(text);
    f.render_widget(help_message, help_area);

    let input = Paragraph::new(app.input.as_str())
        .style(match app.input_mode {
            InputMode::Normal => Style::default(),
            InputMode::Editing => Style::default().fg(Color::Yellow),
        })
        .block(Block::bordered().title("Input"));
    f.render_widget(input, input_area);
    match app.input_mode {
        InputMode::Normal =>
            // Hide the cursor. `Frame` does this by default, so we don't need to do anything here
            {}

        InputMode::Editing => {
            // Make the cursor visible and ask ratatui to put it at the specified coordinates after
            // rendering
            #[allow(clippy::cast_possible_truncation)]
            f.set_cursor(
                // Draw the cursor at the current position in the input field.
                // This position is can be controlled via the left and right arrow key
                input_area.x + app.character_index as u16 + 1,
                // Move one line down, from the border to the input line
                input_area.y + 1,
            );
        }
    }

    let messages: Vec<ListItem> = app
        .messages
        .iter()
        .enumerate()
        .map(|(i, m)| {
            let content = Line::from(Span::raw(format!("{i}: {m}")));
            ListItem::new(content)
        })
        .collect();
    let messages = List::new(messages).block(Block::bordered().title("Messages"));
    f.render_widget(messages, messages_area);
}
examples/demo/ui.rs (line 110)
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) {
    let constraints = if app.show_chart {
        vec![Constraint::Percentage(50), Constraint::Percentage(50)]
    } else {
        vec![Constraint::Percentage(100)]
    };
    let chunks = Layout::horizontal(constraints).split(area);
    {
        let chunks = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(chunks[0]);
        {
            let chunks =
                Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
                    .split(chunks[0]);

            // Draw tasks
            let tasks: Vec<ListItem> = app
                .tasks
                .items
                .iter()
                .map(|i| ListItem::new(vec![text::Line::from(Span::raw(*i))]))
                .collect();
            let tasks = List::new(tasks)
                .block(Block::bordered().title("List"))
                .highlight_style(Style::default().add_modifier(Modifier::BOLD))
                .highlight_symbol("> ");
            f.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state);

            // Draw logs
            let info_style = Style::default().fg(Color::Blue);
            let warning_style = Style::default().fg(Color::Yellow);
            let error_style = Style::default().fg(Color::Magenta);
            let critical_style = Style::default().fg(Color::Red);
            let logs: Vec<ListItem> = app
                .logs
                .items
                .iter()
                .map(|&(evt, level)| {
                    let s = match level {
                        "ERROR" => error_style,
                        "CRITICAL" => critical_style,
                        "WARNING" => warning_style,
                        _ => info_style,
                    };
                    let content = vec![text::Line::from(vec![
                        Span::styled(format!("{level:<9}"), s),
                        Span::raw(evt),
                    ])];
                    ListItem::new(content)
                })
                .collect();
            let logs = List::new(logs).block(Block::bordered().title("List"));
            f.render_stateful_widget(logs, chunks[1], &mut app.logs.state);
        }

        let barchart = BarChart::default()
            .block(Block::bordered().title("Bar chart"))
            .data(&app.barchart)
            .bar_width(3)
            .bar_gap(2)
            .bar_set(if app.enhanced_graphics {
                symbols::bar::NINE_LEVELS
            } else {
                symbols::bar::THREE_LEVELS
            })
            .value_style(
                Style::default()
                    .fg(Color::Black)
                    .bg(Color::Green)
                    .add_modifier(Modifier::ITALIC),
            )
            .label_style(Style::default().fg(Color::Yellow))
            .bar_style(Style::default().fg(Color::Green));
        f.render_widget(barchart, chunks[1]);
    }
    if app.show_chart {
        let x_labels = vec![
            Span::styled(
                format!("{}", app.signals.window[0]),
                Style::default().add_modifier(Modifier::BOLD),
            ),
            Span::raw(format!(
                "{}",
                (app.signals.window[0] + app.signals.window[1]) / 2.0
            )),
            Span::styled(
                format!("{}", app.signals.window[1]),
                Style::default().add_modifier(Modifier::BOLD),
            ),
        ];
        let datasets = vec![
            Dataset::default()
                .name("data2")
                .marker(symbols::Marker::Dot)
                .style(Style::default().fg(Color::Cyan))
                .data(&app.signals.sin1.points),
            Dataset::default()
                .name("data3")
                .marker(if app.enhanced_graphics {
                    symbols::Marker::Braille
                } else {
                    symbols::Marker::Dot
                })
                .style(Style::default().fg(Color::Yellow))
                .data(&app.signals.sin2.points),
        ];
        let chart = Chart::new(datasets)
            .block(
                Block::bordered().title(Span::styled(
                    "Chart",
                    Style::default()
                        .fg(Color::Cyan)
                        .add_modifier(Modifier::BOLD),
                )),
            )
            .x_axis(
                Axis::default()
                    .title("X Axis")
                    .style(Style::default().fg(Color::Gray))
                    .bounds(app.signals.window)
                    .labels(x_labels),
            )
            .y_axis(
                Axis::default()
                    .title("Y Axis")
                    .style(Style::default().fg(Color::Gray))
                    .bounds([-20.0, 20.0])
                    .labels(vec![
                        Span::styled("-20", Style::default().add_modifier(Modifier::BOLD)),
                        Span::raw("0"),
                        Span::styled("20", Style::default().add_modifier(Modifier::BOLD)),
                    ]),
            );
        f.render_widget(chart, chunks[1]);
    }
}
source

pub fn style<S: Into<Style>>(self, style: S) -> Self

Sets the item style

style accepts any type that is convertible to Style (e.g. Style, Color, or your own type that implements Into<Style>).

This Style can be overridden by the Style of the Text content.

This is a fluent setter method which must be chained or used as it consumes self

§Example
let item = ListItem::new("Item 1").style(Style::new().red().italic());

ListItem also implements the Styled trait, which means you can use style shorthands from the Stylize trait to set the style of the widget more concisely.

let item = ListItem::new("Item 1").red().italic();
source

pub fn height(&self) -> usize

Returns the item height

§Examples

One line item

let item = ListItem::new("Item 1");
assert_eq!(item.height(), 1);

Two lines item (note the \n)

let item = ListItem::new("Multi-line\nitem");
assert_eq!(item.height(), 2);
source

pub fn width(&self) -> usize

Returns the max width of all the lines

§Examples
let item = ListItem::new("12345");
assert_eq!(item.width(), 5);
let item = ListItem::new("12345\n1234567");
assert_eq!(item.width(), 7);

Trait Implementations§

source§

impl<'a> Clone for ListItem<'a>

source§

fn clone(&self) -> ListItem<'a>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'a> Debug for ListItem<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a, T> From<T> for ListItem<'a>
where T: Into<Text<'a>>,

source§

fn from(value: T) -> Self

Converts to this type from the input type.
source§

impl<'a> Hash for ListItem<'a>

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl<'a> PartialEq for ListItem<'a>

source§

fn eq(&self, other: &ListItem<'a>) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl<'a> Styled for ListItem<'a>

§

type Item = ListItem<'a>

source§

fn style(&self) -> Style

Returns the style of the object.
source§

fn set_style<S: Into<Style>>(self, style: S) -> Self::Item

Sets the style of the object. Read more
source§

impl<'a> Eq for ListItem<'a>

source§

impl<'a> StructuralPartialEq for ListItem<'a>

Auto Trait Implementations§

§

impl<'a> Freeze for ListItem<'a>

§

impl<'a> RefUnwindSafe for ListItem<'a>

§

impl<'a> Send for ListItem<'a>

§

impl<'a> Sync for ListItem<'a>

§

impl<'a> Unpin for ListItem<'a>

§

impl<'a> UnwindSafe for ListItem<'a>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> IntoEither for T

source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

impl<T> Same for T

§

type Output = T

Should always be Self
source§

impl<'a, T, U> Stylize<'a, T> for U
where U: Styled<Item = T>,

source§

fn bg(self, color: Color) -> T

source§

fn fg<S>(self, color: S) -> T
where S: Into<Color>,

source§

fn add_modifier(self, modifier: Modifier) -> T

source§

fn remove_modifier(self, modifier: Modifier) -> T

source§

fn reset(self) -> T

source§

fn black(self) -> T

Sets the foreground color to black.
source§

fn on_black(self) -> T

Sets the background color to black.
source§

fn red(self) -> T

Sets the foreground color to red.
source§

fn on_red(self) -> T

Sets the background color to red.
source§

fn green(self) -> T

Sets the foreground color to green.
source§

fn on_green(self) -> T

Sets the background color to green.
source§

fn yellow(self) -> T

Sets the foreground color to yellow.
source§

fn on_yellow(self) -> T

Sets the background color to yellow.
source§

fn blue(self) -> T

Sets the foreground color to blue.
source§

fn on_blue(self) -> T

Sets the background color to blue.
source§

fn magenta(self) -> T

Sets the foreground color to magenta.
source§

fn on_magenta(self) -> T

Sets the background color to magenta.
source§

fn cyan(self) -> T

Sets the foreground color to cyan.
source§

fn on_cyan(self) -> T

Sets the background color to cyan.
source§

fn gray(self) -> T

Sets the foreground color to gray.
source§

fn on_gray(self) -> T

Sets the background color to gray.
source§

fn dark_gray(self) -> T

Sets the foreground color to dark_gray.
source§

fn on_dark_gray(self) -> T

Sets the background color to dark_gray.
source§

fn light_red(self) -> T

Sets the foreground color to light_red.
source§

fn on_light_red(self) -> T

Sets the background color to light_red.
source§

fn light_green(self) -> T

Sets the foreground color to light_green.
source§

fn on_light_green(self) -> T

Sets the background color to light_green.
source§

fn light_yellow(self) -> T

Sets the foreground color to light_yellow.
source§

fn on_light_yellow(self) -> T

Sets the background color to light_yellow.
source§

fn light_blue(self) -> T

Sets the foreground color to light_blue.
source§

fn on_light_blue(self) -> T

Sets the background color to light_blue.
source§

fn light_magenta(self) -> T

Sets the foreground color to light_magenta.
source§

fn on_light_magenta(self) -> T

Sets the background color to light_magenta.
source§

fn light_cyan(self) -> T

Sets the foreground color to light_cyan.
source§

fn on_light_cyan(self) -> T

Sets the background color to light_cyan.
source§

fn white(self) -> T

Sets the foreground color to white.
source§

fn on_white(self) -> T

Sets the background color to white.
source§

fn bold(self) -> T

Adds the BOLD modifier.
source§

fn not_bold(self) -> T

Removes the BOLD modifier.
source§

fn dim(self) -> T

Adds the DIM modifier.
source§

fn not_dim(self) -> T

Removes the DIM modifier.
source§

fn italic(self) -> T

Adds the ITALIC modifier.
source§

fn not_italic(self) -> T

Removes the ITALIC modifier.
source§

fn underlined(self) -> T

Adds the UNDERLINED modifier.
source§

fn not_underlined(self) -> T

Removes the UNDERLINED modifier.
Adds the SLOW_BLINK modifier.
Removes the SLOW_BLINK modifier.
Adds the RAPID_BLINK modifier.
Removes the RAPID_BLINK modifier.
source§

fn reversed(self) -> T

Adds the REVERSED modifier.
source§

fn not_reversed(self) -> T

Removes the REVERSED modifier.
source§

fn hidden(self) -> T

Adds the HIDDEN modifier.
source§

fn not_hidden(self) -> T

Removes the HIDDEN modifier.
source§

fn crossed_out(self) -> T

Adds the CROSSED_OUT modifier.
source§

fn not_crossed_out(self) -> T

Removes the CROSSED_OUT modifier.
source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.