Skip to content

Commit

Permalink
scrollable widget with memory leak
Browse files Browse the repository at this point in the history
  • Loading branch information
achristmascarl committed Jul 3, 2024
1 parent 657a9d6 commit 1a166f9
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ lazy_static = "1.4.0"
libc = "0.2.148"
log = "0.4.20"
pretty_assertions = "1.4.0"
ratatui = { version = "0.26.0", features = ["serde", "macros"] }
ratatui = { version = "0.26.0", features = ["serde", "macros", "unstable-widget-ref"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
signal-hook = "0.3.17"
Expand Down
59 changes: 7 additions & 52 deletions src/components/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
config::{Config, KeyBindings},
database::{get_headers, parse_value, row_to_json, row_to_vec, DbError, Rows},
focus::Focus,
widgets::scrollable::{Scrollable, ScrollableState},
};

pub struct Data {
Expand Down Expand Up @@ -65,36 +66,17 @@ impl Component for Data {
.bottom_margin(1);
let value_rows = rows.iter().map(|r| Row::new(row_to_vec(r)).bottom_margin(1)).collect::<Vec<Row>>();
let buf_table = Table::default().rows(value_rows).header(header_row).style(Style::default()).column_spacing(1);
let max_height = 250;
let max_width = 500;
let scrollable = Scrollable::new(Box::new(buf_table.clone()), max_height, max_width).block(block);
let mut scrollable_state = ScrollableState::default();

if !state.table_buf_logged {
let max_height = 250;
let max_width = 500;
let mut buf = Buffer::empty(Rect::new(0, 0, max_width, max_height));
ratatui::widgets::Widget::render(buf_table.clone(), buf.area, &mut buf);
buf = clamp(buf);
let buf_height = buf.area.height;
let buf_width = buf.area.width;
for n in 0..buf_height {
let mut line: String = String::from("");
let cells = buf.content.to_vec()[((n * buf_width) as usize)..(((n + 1) * buf_width) as usize)].to_vec();
for cell in cells.iter() {
line += cell.clone().symbol();
}
// log::info!(
// "rendering line {}/{}, length {}, last symbol {}, last symbol is blank {}:",
// n,
// buf_height,
// line.len(),
// cells[cells.len() - 1].symbol(),
// cells[cells.len() - 1].symbol() == " "
// );
log::info!("{}", line.as_str());
}
scrollable.log();
state.table_buf_logged = true;
}

let table = buf_table.block(block);
f.render_widget(table, area);
f.render_stateful_widget(scrollable, area, &mut scrollable_state);
},
Some(Err(e)) => {
f.render_widget(Paragraph::new(format!("{:?}", e.to_string())).wrap(Wrap { trim: false }).block(block), area)
Expand All @@ -105,30 +87,3 @@ impl Component for Data {
Ok(())
}
}

pub fn clamp(buf: Buffer) -> Buffer {
let height = buf.area.height;
let width = buf.area.width;
log::info!("original height: {}, width: {}", height, width);
let mut used_height: u16 = 0;
let mut used_width: u16 = 0;
for i in (0..height).rev() {
let row = buf.content.to_vec()[((i * width) as usize)..(((i + 1) * width) as usize)].to_vec();
for j in (0..width).rev() {
let cell = row[j as usize].clone();
if cell.symbol() != " " {
used_height = std::cmp::max(used_height, i + 1);
used_width = std::cmp::max(used_width, j + 1);
}
}
}
let mut content: Vec<ratatui::buffer::Cell> = Vec::new();
for i in 0..used_height {
let row = buf.content.to_vec()[((i * width) as usize)..(((i + 1) * width) as usize)].to_vec();
for j in 0..used_width {
content.push(row[j as usize].clone().to_owned());
}
}
log::info!("clamped height: {}, width: {}", used_height, used_width);
Buffer { area: Rect::new(0, 0, used_width, used_height), content }
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod database;
pub mod focus;
pub mod tui;
pub mod utils;
pub mod widgets;

use clap::Parser;
use cli::Cli;
Expand Down
1 change: 1 addition & 0 deletions src/widgets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod scrollable;
124 changes: 124 additions & 0 deletions src/widgets/scrollable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use ratatui::{
prelude::*,
widgets::{Block, WidgetRef},
};

#[derive(Debug, Clone)]
pub struct Scrollable<'a> {
child_buffer: Buffer,
block: Option<Block<'a>>,
state: ScrollableState,
}

#[derive(Debug, Clone, Default)]
pub struct ScrollableState {
x_offset: u16,
y_offset: u16,
}

impl<'a> Scrollable<'a> {
pub fn new(child_widget: Box<dyn WidgetRef>, max_height: u16, max_width: u16) -> Self {
let mut buf = Buffer::empty(Rect::new(0, 0, max_width, max_height));
child_widget.render_ref(buf.area, &mut buf);
let child_buffer = clamp(buf);
let state = ScrollableState::default();
Self { child_buffer, state, block: None }
}

pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self
}

pub fn log(&self) {
let buf_height = self.child_buffer.area.height;
let buf_width = self.child_buffer.area.width;
for n in 0..buf_height {
let mut line: String = String::from("");
let cells =
self.child_buffer.content.to_vec()[((n * buf_width) as usize)..(((n + 1) * buf_width) as usize)].to_vec();
for cell in cells.iter() {
line += cell.clone().symbol();
}
log::info!("{}", line.as_str());
}
}

pub fn debug_log(&self) {
let buf_height = self.child_buffer.area.height;
let buf_width = self.child_buffer.area.width;
for n in 0..buf_height {
let mut line: String = String::from("");
let cells =
self.child_buffer.content.to_vec()[((n * buf_width) as usize)..(((n + 1) * buf_width) as usize)].to_vec();
for cell in cells.iter() {
line += cell.clone().symbol();
}
log::info!(
"rendering line {}/{}, length {}, last symbol {}, last symbol is blank {}:",
n,
buf_height,
line.len(),
cells[cells.len() - 1].symbol(),
cells[cells.len() - 1].symbol() == " "
);

log::info!("{}", line.as_str());
}
}
}

impl<'a> StatefulWidget for Scrollable<'a> {
type State = ScrollableState;

fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.block.render_ref(area, buf);
let render_area = self.block.inner_if_some(area);
if render_area.is_empty() {
return;
}
log::info!("render area: {}", render_area);
let area = render_area.intersection(buf.area);
log::info!("intersection area: {}", area);
let max_x = Ord::min(area.x.saturating_add(area.width), buf.area.right());
let max_y = Ord::min(area.y.saturating_add(area.height), buf.area.bottom());
let content_height = self.child_buffer.area.height;
let content_width = self.child_buffer.area.width;
for i in area.y..max_y {
let content_i = i - area.y;
let row = self.child_buffer.content.to_vec()[(((content_i + state.y_offset) * content_width) as usize)
..(((content_i + state.y_offset + 1) * content_width) as usize)]
.to_vec();
for j in area.x..max_x {
let content_j = j - area.x;
let cell = &row[(content_j + state.x_offset) as usize];
buf.get_mut(j, i).set_symbol(cell.symbol()).set_fg(cell.fg).set_bg(cell.bg).set_skip(cell.skip);
}
}
}
}

fn clamp(buf: Buffer) -> Buffer {
let height = buf.area.height;
let width = buf.area.width;
let mut used_height: u16 = 0;
let mut used_width: u16 = 0;
for i in (0..height).rev() {
let row = buf.content.to_vec()[((i * width) as usize)..(((i + 1) * width) as usize)].to_vec();
for j in (0..width).rev() {
let cell = row[j as usize].clone();
if cell.symbol() != " " {
used_height = std::cmp::max(used_height, i + 1);
used_width = std::cmp::max(used_width, j + 1);
}
}
}
let mut content: Vec<ratatui::buffer::Cell> = Vec::new();
for i in 0..used_height {
let row = buf.content.to_vec()[((i * width) as usize)..(((i + 1) * width) as usize)].to_vec();
for j in 0..used_width {
content.push(row[j as usize].clone().to_owned());
}
}
Buffer { area: Rect::new(0, 0, used_width, used_height), content }
}

0 comments on commit 1a166f9

Please sign in to comment.