Skip to content

Commit

Permalink
Merge pull request #13 from preiter93/truncate-last-element
Browse files Browse the repository at this point in the history
Truncate last element
  • Loading branch information
preiter93 authored Feb 29, 2024
2 parents f0454ce + d48a1a5 commit b437025
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
0.8.2 - ?
===================
- Truncate last element correctly
- Add tests

Released
--------
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tui-widget-list"
version = "0.8.1"
version = "0.8.2"
edition = "2021"
authors = ["preiter <[email protected]>"]
description = "Widget List for TUI/Ratatui"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@ pub fn render(f: &mut Frame) {

For more examples see [tui-widget-list](https://github.com/preiter93/tui-widget-list/tree/main/examples).

![](resources/demo.gif)
![](resources/demo.gif?v1)

License: MIT
Binary file modified resources/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
//!
//! For more examples see [tui-widget-list](https://github.com/preiter93/tui-widget-list/tree/main/examples).
//!
//!![](resources/demo.gif)
//!![](resources/demo.gif?v1)
pub mod state;
pub mod traits;
pub mod widget;
Expand Down
215 changes: 182 additions & 33 deletions src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,44 +195,68 @@ fn render_item<T: ListableWidget>(
num_items: usize,
scroll_direction: &ScrollAxis,
) {
// Check if the first element is truncated and needs special handling
let item_size = item.size(scroll_direction) as u16;
if pos == 0 && num_items > 1 && area.height < item_size {
// Create an intermediate buffer for rendering the truncated element
let (width, height) = match scroll_direction {
ScrollAxis::Vertical => (area.width, item_size),
ScrollAxis::Horizontal => (item_size, area.height),
};
let mut hidden_buffer = Buffer::empty(Rect {
x: area.left(),
y: area.top(),
width,
height,
});
item.render(hidden_buffer.area, &mut hidden_buffer);

// Copy the visible part from the intermediate buffer to the main buffer
match scroll_direction {
ScrollAxis::Vertical => {
let offset = item_size.saturating_sub(area.height);
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
*buf.get_mut(x, y) = hidden_buffer.get(x, y + offset).clone();
}

// Check if the item needs to be truncated
if area.height < item_size {
// Determine if truncation should happen at the top or the bottom
let truncate_top = pos == 0 && num_items > 1;
render_and_truncate(item, area, buf, scroll_direction, truncate_top);
} else {
item.render(area, buf);
}
}

/// Renders a listable widget within a specified area of a buffer, potentially truncating the widget content based on scrolling direction.
/// `truncate_top` indicates whether to truncate the content from the top or bottom.
fn render_and_truncate<T: ListableWidget>(
item: T,
area: Rect,
buf: &mut Buffer,
scroll_direction: &ScrollAxis,
truncate_top: bool,
) {
let item_size = item.size(scroll_direction) as u16;
// Create an intermediate buffer for rendering the truncated element
let (width, height) = match scroll_direction {
ScrollAxis::Vertical => (area.width, item_size),
ScrollAxis::Horizontal => (item_size, area.height),
};
let mut hidden_buffer = Buffer::empty(Rect {
x: area.left(),
y: area.top(),
width,
height,
});
item.render(hidden_buffer.area, &mut hidden_buffer);

// Copy the visible part from the intermediate buffer to the main buffer
match scroll_direction {
ScrollAxis::Vertical => {
let offset = if truncate_top {
item_size.saturating_sub(area.height)
} else {
0
};
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
*buf.get_mut(x, y) = hidden_buffer.get(x, y + offset).clone();
}
}
ScrollAxis::Horizontal => {
let offset = item_size.saturating_sub(area.width);
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
*buf.get_mut(x, y) = hidden_buffer.get(x + offset, y).clone();
}
}
ScrollAxis::Horizontal => {
let offset = if truncate_top {
item_size.saturating_sub(area.width)
} else {
0
};
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
*buf.get_mut(x, y) = hidden_buffer.get(x + offset, y).clone();
}
}
};
} else {
item.render(area, buf);
}
}
};
}

/// Represents the scroll axis of a list.
Expand All @@ -245,3 +269,128 @@ pub enum ScrollAxis {
/// Indicates horizontal scrolling.
Horizontal,
}

#[cfg(test)]
mod test {
use super::*;
use ratatui::widgets::Borders;

struct TestItem {}
impl Widget for TestItem {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
Block::default().borders(Borders::ALL).render(area, buf);
}
}
impl ListableWidget for TestItem {
fn size(&self, scroll_direction: &ScrollAxis) -> usize {
match scroll_direction {
ScrollAxis::Vertical => 3,
ScrollAxis::Horizontal => 3,
}
}
}

fn init(height: u16) -> (Rect, Buffer, List<'static, TestItem>, ListState) {
let area = Rect::new(0, 0, 5, height);
(
area,
Buffer::empty(area),
List::new(vec![TestItem {}, TestItem {}, TestItem {}]),
ListState::default(),
)
}

#[test]
fn not_truncated() {
// given
let (area, mut buf, list, mut state) = init(9);

// when
list.render(area, &mut buf, &mut state);

// then
assert_buffer_eq(
buf,
Buffer::with_lines(vec![
"┌───┐",
"│ │",
"└───┘",
"┌───┐",
"│ │",
"└───┘",
"┌───┐",
"│ │",
"└───┘",
]),
)
}

#[test]
fn bottom_is_truncated() {
// given
let (area, mut buf, list, mut state) = init(8);

// when
list.render(area, &mut buf, &mut state);

// then
assert_buffer_eq(
buf,
Buffer::with_lines(vec![
"┌───┐",
"│ │",
"└───┘",
"┌───┐",
"│ │",
"└───┘",
"┌───┐",
"│ │",
]),
)
}

#[test]
fn top_is_truncated() {
// given
let (area, mut buf, list, mut state) = init(8);
state.select(Some(2));

// when
list.render(area, &mut buf, &mut state);

// then
assert_buffer_eq(
buf,
Buffer::with_lines(vec![
"│ │",
"└───┘",
"┌───┐",
"│ │",
"└───┘",
"┌───┐",
"│ │",
"└───┘",
]),
)
}

fn assert_buffer_eq(actual: Buffer, expected: Buffer) {
if actual.area != expected.area {
panic!(
"buffer areas not equal expected: {:?} actual: {:?}",
expected, actual
);
}
let diff = expected.diff(&actual);
if !diff.is_empty() {
panic!(
"buffer contents not equal\nexpected: {:?}\nactual: {:?}",
expected, actual,
);
}
assert_eq!(actual, expected, "buffers not equal");
}
}

0 comments on commit b437025

Please sign in to comment.