Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(widgets)!: make widgets optionally reusable #122

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/custom_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct Label<'a> {
}

impl<'a> Widget for Label<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
buf.set_string(area.left(), area.top(), self.text, Style::default());
}
}
Expand Down
78 changes: 73 additions & 5 deletions src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
backend::{Backend, ClearType},
buffer::Buffer,
layout::Rect,
widgets::{StatefulWidget, Widget},
widgets::{AsStatefulWidgetRef, AsWidgetRef, StatefulWidget, Widget},
};
use std::io;

Expand Down Expand Up @@ -83,11 +83,14 @@ where
/// let mut frame = terminal.get_frame();
/// frame.render_widget(block, area);
/// ```
pub fn render_widget<W>(&mut self, widget: W, area: Rect)
pub fn render_widget<W, R>(&mut self, widget: R, area: Rect)
where
W: Widget,
R: AsWidgetRef<W>,
{
widget.render(area, self.terminal.current_buffer_mut());
widget
.as_widget_ref()
.render(area, self.terminal.current_buffer_mut());
}

/// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
Expand Down Expand Up @@ -115,11 +118,14 @@ where
/// let mut frame = terminal.get_frame();
/// frame.render_stateful_widget(list, area, &mut state);
/// ```
pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
pub fn render_stateful_widget<W, R>(&mut self, widget: R, area: Rect, state: &mut W::State)
where
W: StatefulWidget,
R: AsStatefulWidgetRef<W>,
{
widget.render(area, self.terminal.current_buffer_mut(), state);
widget
.as_widget_ref()
.render(area, self.terminal.current_buffer_mut(), state);
}

/// After drawing this frame, make the cursor visible and put it at the specified (x, y)
Expand Down Expand Up @@ -479,3 +485,65 @@ fn compute_inline_size<B: Backend>(
pos,
))
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{backend::TestBackend, style::Style};

struct TestWidget(&'static str);

impl Widget for TestWidget {
fn render(&self, area: Rect, buf: &mut Buffer) {
buf.set_string(area.left(), area.top(), self.0, Style::default());
}
}

struct TestStatefulWidget(&'static str);

impl StatefulWidget for TestStatefulWidget {
type State = u8;
fn render(&self, area: Rect, buf: &mut Buffer, state: &mut u8) {
let message = format!("{}: {state}", self.0);
*state += 1;
buf.set_string(area.left(), area.top(), message, Style::default());
}
}

#[test]
fn test_render_widget() -> io::Result<()> {
let backend = TestBackend::new(10, 6);
let mut terminal = Terminal::new(backend)?;
let mut state = 0;
terminal.draw(|frame| {
frame.render_widget(&TestWidget("borrow"), Rect::new(0, 0, 10, 1));
frame.render_widget(&TestWidget("borrow"), Rect::new(0, 1, 10, 1));
frame.render_widget(TestWidget("own"), Rect::new(0, 2, 10, 1));
frame.render_stateful_widget(
TestStatefulWidget("borrow"),
Rect::new(0, 3, 10, 1),
&mut state,
);
frame.render_stateful_widget(
TestStatefulWidget("borrow"),
Rect::new(0, 4, 10, 1),
&mut state,
);
frame.render_stateful_widget(
TestStatefulWidget("own"),
Rect::new(0, 5, 10, 1),
&mut state,
);
})?;
terminal.backend.assert_buffer(&Buffer::with_lines(vec![
"borrow ",
"borrow ",
"own ",
"borrow: 0 ",
"borrow: 1 ",
"own: 2 ",
]));
assert_eq!(state, 3);
Ok(())
}
}
4 changes: 2 additions & 2 deletions src/widgets/barchart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ impl<'a> BarChart<'a> {
}

impl<'a> Widget for BarChart<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);

let chart_area = match self.block.take() {
let chart_area = match &self.block {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
Expand Down
6 changes: 3 additions & 3 deletions src/widgets/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ impl<'a> Block<'a> {
}

impl<'a> Widget for Block<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
if area.area() == 0 {
return;
}
Expand Down Expand Up @@ -312,7 +312,7 @@ impl<'a> Widget for Block<'a> {
}

// Title
if let Some(title) = self.title {
if let Some(title) = &self.title {
let left_border_dx = u16::from(self.borders.intersects(Borders::LEFT));
let right_border_dx = u16::from(self.borders.intersects(Borders::RIGHT));

Expand All @@ -337,7 +337,7 @@ impl<'a> Widget for Block<'a> {
area.top()
};

buf.set_line(title_x, title_y, &title, title_area_width);
buf.set_line(title_x, title_y, title, title_area_width);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/calendar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ impl<'a, S: DateStyler> Monthly<'a, S> {
}

impl<'a, S: DateStyler> Widget for Monthly<'a, S> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
// Block is used for borders and such
// Draw that first, and use the blank area inside the block for our own purposes
let mut area = match self.block.take() {
let mut area = match &self.block {
None => area,
Some(b) => {
let inner = b.inner(area);
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,8 @@ impl<'a, F> Widget for Canvas<'a, F>
where
F: Fn(&mut Context),
{
fn render(mut self, area: Rect, buf: &mut Buffer) {
let canvas_area = match self.block.take() {
fn render(&self, area: Rect, buf: &mut Buffer) {
let canvas_area = match &self.block {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
Expand Down
16 changes: 8 additions & 8 deletions src/widgets/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ impl<'a> Chart<'a> {
}

fn render_x_labels(
&mut self,
&self,
buf: &mut Buffer,
layout: &ChartLayout,
chart_area: Rect,
Expand Down Expand Up @@ -461,7 +461,7 @@ impl<'a> Chart<'a> {
}

fn render_y_labels(
&mut self,
&self,
buf: &mut Buffer,
layout: &ChartLayout,
chart_area: Rect,
Expand All @@ -486,7 +486,7 @@ impl<'a> Chart<'a> {
}

impl<'a> Widget for Chart<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
if area.area() == 0 {
return;
}
Expand All @@ -496,7 +496,7 @@ impl<'a> Widget for Chart<'a> {
// axis names).
let original_style = buf.get(area.left(), area.top()).style();

let chart_area = match self.block.take() {
let chart_area = match &self.block {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
Expand Down Expand Up @@ -580,7 +580,7 @@ impl<'a> Widget for Chart<'a> {
}

if let Some((x, y)) = layout.title_x {
let title = self.x_axis.title.unwrap();
let title = self.x_axis.title.as_ref().unwrap();
let width = graph_area.right().saturating_sub(x);
buf.set_style(
Rect {
Expand All @@ -591,11 +591,11 @@ impl<'a> Widget for Chart<'a> {
},
original_style,
);
buf.set_line(x, y, &title, width);
buf.set_line(x, y, title, width);
}

if let Some((x, y)) = layout.title_y {
let title = self.y_axis.title.unwrap();
let title = self.y_axis.title.as_ref().unwrap();
let width = graph_area.right().saturating_sub(x);
buf.set_style(
Rect {
Expand All @@ -606,7 +606,7 @@ impl<'a> Widget for Chart<'a> {
},
original_style,
);
buf.set_line(x, y, &title, width);
buf.set_line(x, y, title, width);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/clear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
pub struct Clear;

impl Widget for Clear {
fn render(self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {

Check warning on line 30 in src/widgets/clear.rs

View check run for this annotation

Codecov / codecov/patch

src/widgets/clear.rs#L30

Added line #L30 was not covered by tests
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
buf.get_mut(x, y).reset();
Expand Down
13 changes: 8 additions & 5 deletions src/widgets/gauge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ impl<'a> Gauge<'a> {
}

impl<'a> Widget for Gauge<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let gauge_area = match self.block.take() {
let gauge_area = match &self.block {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
Expand All @@ -111,7 +111,9 @@ impl<'a> Widget for Gauge<'a> {
// label is put at the center of the gauge_area
let label = {
let pct = f64::round(self.ratio * 100.0);
self.label.unwrap_or_else(|| Span::from(format!("{pct}%")))
self.label
.clone()
.unwrap_or_else(|| Span::from(format!("{pct}%")))
};
let clamped_label_width = gauge_area.width.min(label.width() as u16);
let label_col = gauge_area.left() + (gauge_area.width - clamped_label_width) / 2;
Expand Down Expand Up @@ -233,9 +235,9 @@ impl<'a> LineGauge<'a> {
}

impl<'a> Widget for LineGauge<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let gauge_area = match self.block.take() {
let gauge_area = match &self.block {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
Expand All @@ -251,6 +253,7 @@ impl<'a> Widget for LineGauge<'a> {
let ratio = self.ratio;
let label = self
.label
.clone()
.unwrap_or_else(move || Line::from(format!("{:.0}%", ratio * 100.0)));
let (col, row) = buf.set_line(
gauge_area.left(),
Expand Down
22 changes: 11 additions & 11 deletions src/widgets/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ impl<'a> List<'a> {
impl<'a> StatefulWidget for List<'a> {
type State = ListState;

fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
fn render(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
buf.set_style(area, self.style);
let list_area = match self.block.take() {
let list_area = match &self.block {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
Expand Down Expand Up @@ -230,7 +230,7 @@ impl<'a> StatefulWidget for List<'a> {
let has_selection = state.selected.is_some();
for (i, item) in self
.items
.iter_mut()
.iter()
.enumerate()
.skip(state.offset)
.take(end - start)
Expand Down Expand Up @@ -284,7 +284,7 @@ impl<'a> StatefulWidget for List<'a> {
}

impl<'a> Widget for List<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
fn render(&self, area: Rect, buf: &mut Buffer) {
let mut state = ListState::default();
StatefulWidget::render(self, area, buf, &mut state);
}
Expand Down Expand Up @@ -417,7 +417,7 @@ mod tests {
/// helper method to render a widget to an empty buffer with the default state
fn render_widget(widget: List<'_>, width: u16, height: u16) -> Buffer {
let mut buffer = Buffer::empty(Rect::new(0, 0, width, height));
Widget::render(widget, buffer.area, &mut buffer);
Widget::render(&widget, buffer.area, &mut buffer);
buffer
}

Expand All @@ -429,7 +429,7 @@ mod tests {
height: u16,
) -> Buffer {
let mut buffer = Buffer::empty(Rect::new(0, 0, width, height));
StatefulWidget::render(widget, buffer.area, &mut buffer, state);
StatefulWidget::render(&widget, buffer.area, &mut buffer, state);
buffer
}

Expand All @@ -440,19 +440,19 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));

// attempt to render into an area of the buffer with 0 width
Widget::render(list.clone(), Rect::new(0, 0, 0, 3), &mut buffer);
Widget::render(&list.clone(), Rect::new(0, 0, 0, 3), &mut buffer);
assert_buffer_eq!(buffer, Buffer::empty(buffer.area));

// attempt to render into an area of the buffer with 0 height
Widget::render(list.clone(), Rect::new(0, 0, 15, 0), &mut buffer);
Widget::render(&list.clone(), Rect::new(0, 0, 15, 0), &mut buffer);
assert_buffer_eq!(buffer, Buffer::empty(buffer.area));

let list = List::new(items)
.highlight_symbol(">>")
.block(Block::default().borders(Borders::all()));
// attempt to render into an area of the buffer with zero height after
// setting the block borders
Widget::render(list, Rect::new(0, 0, 15, 2), &mut buffer);
Widget::render(&list, Rect::new(0, 0, 15, 2), &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
Expand All @@ -469,7 +469,7 @@ mod tests {
let list = List::new(items.to_owned()).highlight_symbol(">>");
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));

Widget::render(list, buffer.area, &mut buffer);
Widget::render(&list, buffer.area, &mut buffer);

let expected = Buffer::with_lines(expected_lines);
assert_buffer_eq!(buffer, expected);
Expand All @@ -483,7 +483,7 @@ mod tests {
let mut state = ListState::default().with_selected(selected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));

StatefulWidget::render(list, buffer.area, &mut buffer, &mut state);
StatefulWidget::render(&list, buffer.area, &mut buffer, &mut state);

let expected = Buffer::with_lines(expected_lines);
assert_buffer_eq!(buffer, expected);
Expand Down
Loading