Skip to content

Commit

Permalink
Replace tui_react::Terminal with tui::Terminal
Browse files Browse the repository at this point in the history
This is an experiment in converting a program that used the Terminal
provided by tui-react into a program that uses the Terminal provided by
ratatui. My hypothesis was that the two were not significantly different
from eachother, and this seems to be true in this case.

I found that `Widget` trait used by ratatui's `Terminal` is sufficiently
powerful to pass arbitrary mutable state to rendering code. A relatively
simple `FunctionWidget` trampoline is used to achieve this.

The tui_react dependency is retained. The code still calls some
tui_react utility functions and uses its List widget instead of the
ratatui List widget. These utilities do not depend on the tui_react
Terminal and could, in theory, be split out of tui-react into a separate
crate.
  • Loading branch information
matta committed May 25, 2024
1 parent 66e0166 commit c22a718
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 44 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ include = ["src/**/*", "Cargo.*", "LICENSE", "README.md", "CHANGELOG.md", "!**/t

[features]
default = ["tui-crossplatform", "trash-move"]
tui-crossplatform = ["crosstermion/tui-react-crossterm", "tui", "tui-react", "open", "unicode-segmentation", "unicode-width"]

tui-crossplatform = ["crosstermion/tui-crossterm", "tui", "tui-react", "open", "unicode-segmentation", "unicode-width"]
trash-move = ["trash"]

[dependencies]
Expand Down
64 changes: 58 additions & 6 deletions src/interactive/app/eventloop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ use dua::{
WalkResult,
};
use std::path::PathBuf;
use tui::backend::Backend;
use tui_react::Terminal;
use tui::{backend::Backend, buffer::Buffer, layout::Rect, widgets::Widget, Terminal};

use super::state::{AppState, Cursor};
use super::tree_view::TreeView;
Expand Down Expand Up @@ -569,6 +568,55 @@ enum Refresh {
Selected,
}

/// A [`Widget`] that renders by calling a function.
///
/// The `FunctionWidget` struct holds a function that renders into a portion of
/// a [`Buffer`] designated by a [`Rect`].
///
/// This widget can be used to create custom UI elements that are defined by a
/// rendering function. and allows for rendering functions that do not implement
/// the [`Widget`] trait.
struct FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
render: F,
}

impl<F> FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
/// Creates a new [`FunctionWidget`] with the given rendering function.
///
/// The rendering function must have the signature `FnOnce(Rect, &mut
/// Buffer)`, where:
/// - [`Rect`] represents the available space for rendering.
/// - [`Buffer`] is the buffer to write the rendered content to.
///
/// The `FunctionWidget` can then be used to render the provided function in
/// a user interface.
fn new(function: F) -> FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
FunctionWidget { render: function }
}
}

/// Implements the [`Widget`] trait for [`FunctionWidget`].
///
/// The implementation simply calls the provided render function with the given
/// `Rect` and `Buffer`.
impl<F> Widget for FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
fn render(self, area: Rect, buf: &mut Buffer) {
(self.render)(area, buf);
}
}

pub fn draw_window<B>(
window: &mut MainWindow,
props: MainWindowProps<'_>,
Expand All @@ -578,10 +626,14 @@ pub fn draw_window<B>(
where
B: Backend,
{
let area = terminal.pre_render()?;
window.render(props, area, terminal, cursor);
terminal.post_render()?;

terminal.draw(|frame| {
frame.render_widget(
FunctionWidget::new(|area, buf| {
window.render(props, area, buf, cursor);
}),
frame.size(),
);
})?;
Ok(())
}

Expand Down
3 changes: 1 addition & 2 deletions src/interactive/app/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use crate::interactive::{
use crosstermion::input::Key;
use dua::traverse::TreeIndex;
use std::{fs, io, path::PathBuf};
use tui::backend::Backend;
use tui_react::Terminal;
use tui::{backend::Backend, Terminal};

use super::state::{AppState, FocussedPane::*};

Expand Down
3 changes: 1 addition & 2 deletions src/interactive/app/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ use dua::{
traverse::{Traversal, TraversalStats},
ByteFormat, WalkOptions, WalkResult,
};
use tui::prelude::Backend;
use tui_react::Terminal;
use tui::{backend::Backend, Terminal};

use crate::interactive::widgets::MainWindow;

Expand Down
3 changes: 1 addition & 2 deletions src/interactive/app/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use std::{
io::ErrorKind,
path::{Path, PathBuf},
};
use tui::backend::TestBackend;
use tui_react::Terminal;
use tui::{backend::TestBackend, Terminal};

use crate::interactive::{app::tests::FIXTURE_PATH, terminal::TerminalApp};

Expand Down
26 changes: 11 additions & 15 deletions src/interactive/widgets/glob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ use crosstermion::input::Key;
use dua::traverse::{Tree, TreeIndex};
use petgraph::Direction;
use std::borrow::Borrow;
use tui::backend::Backend;
use tui::prelude::Buffer;
use tui::{
buffer::Buffer,
layout::Rect,
style::Style,
text::{Line, Span, Text},
widgets::{Block, Borders, Paragraph, Widget},
};
use tui_react::util::{block_width, rect};
use tui_react::{draw_text_nowrap_fn, Terminal};
use tui_react::{
draw_text_nowrap_fn,
util::{block_width, rect},
};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

Expand Down Expand Up @@ -100,15 +101,13 @@ impl GlobPane {
new_cursor_pos.clamp(0, self.input.graphemes(true).count())
}

pub fn render<B>(
pub fn render(
&mut self,
props: impl Borrow<GlobPaneProps>,
area: Rect,
terminal: &mut Terminal<B>,
buffer: &mut Buffer,
cursor: &mut Cursor,
) where
B: Backend,
{
) {
let GlobPaneProps {
border_style,
has_focus,
Expand All @@ -120,18 +119,15 @@ impl GlobPane {
.border_style(*border_style)
.borders(Borders::ALL);
let inner_block_area = block.inner(area);
block.render(area, terminal.current_buffer_mut());
block.render(area, buffer);

let spans = vec![Span::from(&self.input)];
Paragraph::new(Text::from(Line::from(spans)))
.style(Style::default())
.render(
margin_left_right(inner_block_area, 1),
terminal.current_buffer_mut(),
);
.render(margin_left_right(inner_block_area, 1), buffer);

if *has_focus {
draw_top_right_help(area, title, terminal.current_buffer_mut());
draw_top_right_help(area, title, buffer);

cursor.show = true;
cursor.x = inner_block_area.x
Expand Down
24 changes: 10 additions & 14 deletions src/interactive/widgets/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ use crate::interactive::{
DisplayOptions,
};
use std::borrow::Borrow;
use tui::backend::Backend;
use tui::buffer::Buffer;
use tui::{
layout::{Constraint, Direction, Layout, Rect},
style::Modifier,
style::{Color, Style},
};
use tui_react::Terminal;
use Constraint::*;
use FocussedPane::*;

Expand All @@ -36,15 +35,13 @@ pub struct MainWindow {
}

impl MainWindow {
pub fn render<'a, B>(
pub fn render<'a>(
&mut self,
props: impl Borrow<MainWindowProps<'a>>,
area: Rect,
terminal: &mut Terminal<B>,
buffer: &mut Buffer,
cursor: &mut Cursor,
) where
B: Backend,
{
) {
let MainWindowProps {
current_path,
entries_traversed,
Expand All @@ -59,7 +56,7 @@ impl MainWindow {
let (header_area, content_area, footer_area) = main_window_layout(area);

let header_bg_color = header_background_color(self.is_anything_marked(), state.focussed);
Header.render(header_bg_color, header_area, terminal.current_buffer_mut());
Header.render(header_bg_color, header_area, buffer);

let (entries_area, help_pane, mark_pane) = {
let (left_pane, right_pane) = content_layout(content_area);
Expand Down Expand Up @@ -90,15 +87,15 @@ impl MainWindow {
border_style: mark_style,
format: display.byte_format,
};
pane.render(props, mark_area, terminal.current_buffer_mut());
pane.render(props, mark_area, buffer);
}

if let Some((help_area, pane)) = help_pane {
let props = HelpPaneProps {
border_style: help_style,
has_focus: matches!(state.focussed, Help),
};
pane.render(props, help_area, terminal.current_buffer_mut());
pane.render(props, help_area, buffer);
}

let marked = self.mark_pane.as_ref().map(|p| p.marked());
Expand All @@ -113,15 +110,14 @@ impl MainWindow {
sort_mode: state.sorting,
show_columns: &state.show_columns,
};
self.entries_pane
.render(props, entries_area, terminal.current_buffer_mut());
self.entries_pane.render(props, entries_area, buffer);

if let Some((glob_area, pane)) = glob_pane {
let props = GlobPaneProps {
border_style: glob_style,
has_focus: matches!(state.focussed, Glob),
};
pane.render(props, glob_area, terminal, cursor);
pane.render(props, glob_area, buffer, cursor);
}

Footer.render(
Expand All @@ -135,7 +131,7 @@ impl MainWindow {
sort_mode: state.sorting,
},
footer_area,
terminal.current_buffer_mut(),
buffer,
);
}

Expand Down

0 comments on commit c22a718

Please sign in to comment.