diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2021-05-03 07:41:25 +0800 |
---|---|---|
committer | Sebastian Thiel <sebastian.thiel@icloud.com> | 2021-05-03 07:41:25 +0800 |
commit | 1ddbeae87dc0c23edf412405d6a08696bc703c1b (patch) | |
tree | 97b6541f50855025e115722743431a07bcedaf9e | |
parent | e16a3e4908cdfed103c0c1d5e54c31f1c90d40df (diff) |
Remove tui-react, it now lives in https://github.com/Byron/tui-crates
-rw-r--r-- | tui-react/.gitignore | 1 | ||||
-rw-r--r-- | tui-react/Cargo.toml | 15 | ||||
-rw-r--r-- | tui-react/LICENSE | 21 | ||||
-rw-r--r-- | tui-react/README.md | 43 | ||||
-rw-r--r-- | tui-react/src/lib.rs | 156 | ||||
-rw-r--r-- | tui-react/src/list.rs | 78 | ||||
-rw-r--r-- | tui-react/src/terminal.rs | 181 |
7 files changed, 0 insertions, 495 deletions
diff --git a/tui-react/.gitignore b/tui-react/.gitignore deleted file mode 100644 index 5a44eef..0000000 --- a/tui-react/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/Cargo.lock diff --git a/tui-react/Cargo.toml b/tui-react/Cargo.toml deleted file mode 100644 index aa55aad..0000000 --- a/tui-react/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "tui-react" -version = "0.15.0" -authors = ["Sebastian Thiel <sthiel@thoughtworks.com>"] -edition = "2018" -repository = "https://github.com/Byron/dua-cli" -description = "TUI widgets using a react-like paradigm, allowing mutable component state and render properties." -readme = "README.md" -license = "MIT" - -[dependencies] -tui = { version = "0.15.0", default-features = false } -log = "0.4.6" -unicode-segmentation = "1.6.0" -unicode-width = "0.1.7" diff --git a/tui-react/LICENSE b/tui-react/LICENSE deleted file mode 100644 index 86c3928..0000000 --- a/tui-react/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Sebastian Thiel - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/tui-react/README.md b/tui-react/README.md deleted file mode 100644 index 0fe3e09..0000000 --- a/tui-react/README.md +++ /dev/null @@ -1,43 +0,0 @@ -**tui-react** is a library to enable components with state, and with properties provided per render - -Please note that this crate is early in development and build for the needs of **dua**. - -### How it works - -It uses the TUI infrastructure Terminal, but alters it to not enforce implementing the `Widget` trait. -It provides only a single, optional, trait called `TopLevelComponent`, which makes it convenient to -draw its implementors with `Terminal::render(..)`. However, since this enforces the absence of -refernces in your state, it's probably not suitable for most. - -Instead, any struct can implement `render` methods or functions, and freely write into the terminal. -That way, one can leverage everything Rust has to offer, which allows stateful components which -work in your favor. Thus, this crate does away with 'one size fits all' render implementations, -greatly adding to flexibility. - -State that one wants within the component for instance could be the scoll location. Alternatively, -one can configure windows by altering their public state. - -### What's the relation to TUI? - -This project coudln't exist without it, and is happy to provide an alternative set of components -for use in command-line applications. - - -### Why `tui-react`? - -I kept having a terrible feeling when managing state with tui widgets when writing **dua**, and -after trying many things, I realized what the problem actually was. It took me some time to -realize it's not possible to have stateful components in with TUI, and I admire the smarts -that went into the API design! After all, it effectively prohibited this! Amazing! - -That's why I implemented my own terminal and some key components, based on the ones provided -by TUI, which can serve as standard building blocks in a stateful world. - -Thus far, the experience was fantastic, and it feels much better than before. Let's see what -happens with it. - -### Changelog - -#### v0.4.1 - Simplify `block_width(…)` function - -#### v0.2.1 - add license file to crate diff --git a/tui-react/src/lib.rs b/tui-react/src/lib.rs deleted file mode 100644 index 9333fb5..0000000 --- a/tui-react/src/lib.rs +++ /dev/null @@ -1,156 +0,0 @@ -#![forbid(unsafe_code)] - -mod list; -mod terminal; - -pub use list::*; -pub use terminal::*; - -use std::iter::repeat; -use tui::{self, buffer::Buffer, layout::Rect, style::Color, style::Style}; -use unicode_segmentation::UnicodeSegmentation; -use unicode_width::UnicodeWidthStr; - -pub fn fill_background_to_right(mut s: String, entire_width: u16) -> String { - match (s.len(), entire_width as usize) { - (x, y) if x >= y => s, - (x, y) => { - s.extend(repeat(' ').take(y - x)); - s - } - } -} - -/// Helper method to quickly set the background of all cells inside the specified area. -pub fn fill_background(area: Rect, buf: &mut Buffer, color: Color) { - for y in area.top()..area.bottom() { - for x in area.left()..area.right() { - buf.get_mut(x, y).set_bg(color); - } - } -} - -pub fn draw_text_with_ellipsis_nowrap( - bound: Rect, - buf: &mut Buffer, - text: impl AsRef<str>, - style: impl Into<Option<Style>>, -) -> u16 { - let s = style.into(); - let t = text.as_ref(); - let mut graphemes = t.graphemes(true); - let mut total_width = 0; - { - let mut ellipsis_candidate_x = None; - let mut x_offset = 0; - for (g, mut x) in graphemes.by_ref().zip(bound.left()..bound.right()) { - let width = g.width(); - total_width += width; - - x += x_offset; - let cell = buf.get_mut(x, bound.y); - if x + 1 == bound.right() { - ellipsis_candidate_x = Some(x); - } - cell.symbol = g.into(); - if let Some(s) = s { - cell.set_style(s); - } - - x_offset += width.saturating_sub(1) as u16; - if x + x_offset >= bound.right() { - break; - } - let x = x as usize; - for x in x + 1..x + width { - let i = buf.index_of(x as u16, bound.y); - buf.content[i].reset(); - } - } - if let (Some(_), Some(x)) = (graphemes.next(), ellipsis_candidate_x) { - buf.get_mut(x, bound.y).symbol = "…".into(); - } - } - total_width as u16 -} - -pub fn draw_text_nowrap_fn( - bound: Rect, - buf: &mut Buffer, - t: impl AsRef<str>, - mut s: impl FnMut(&str, u16, u16) -> Style, -) { - if bound.width == 0 { - return; - } - for (g, x) in t.as_ref().graphemes(true).zip(bound.left()..bound.right()) { - let cell = buf.get_mut(x, bound.y); - cell.symbol = g.into(); - cell.set_style(s(&cell.symbol, x, bound.y)); - } -} - -pub mod util { - use unicode_segmentation::UnicodeSegmentation; - use unicode_width::UnicodeWidthStr; - - pub fn sanitize_offset(offset: u16, num_items: usize, num_displayable_lines: u16) -> u16 { - offset.min((num_items.saturating_sub(num_displayable_lines as usize)) as u16) - } - - #[derive(Default)] - pub struct GraphemeCountWriter(pub usize); - - impl std::io::Write for GraphemeCountWriter { - fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> { - self.0 += String::from_utf8_lossy(buf).graphemes(true).count(); - Ok(buf.len()) - } - - fn flush(&mut self) -> Result<(), std::io::Error> { - Ok(()) - } - } - - pub fn block_width(s: &str) -> u16 { - s.width() as u16 - } - - pub mod rect { - use tui::layout::Rect; - - /// A safe version of Rect::intersection that doesn't suffer from underflows - pub fn intersect(lhs: Rect, rhs: Rect) -> Rect { - let x1 = lhs.x.max(rhs.x); - let y1 = lhs.y.max(rhs.y); - let x2 = lhs.right().min(rhs.right()); - let y2 = lhs.bottom().min(rhs.bottom()); - Rect { - x: x1, - y: y1, - width: x2.saturating_sub(x1), - height: y2.saturating_sub(y1), - } - } - - pub fn offset_x(r: Rect, offset: u16) -> Rect { - Rect { - x: r.x + offset, - width: r.width.saturating_sub(offset), - ..r - } - } - - pub fn snap_to_right(bound: Rect, new_width: u16) -> Rect { - offset_x(bound, bound.width.saturating_sub(new_width)) - } - - pub fn line_bound(bound: Rect, line: usize) -> Rect { - Rect { - y: bound.y + line as u16, - height: 1, - ..bound - } - } - } -} diff --git a/tui-react/src/list.rs b/tui-react/src/list.rs deleted file mode 100644 index 0ffb3eb..0000000 --- a/tui-react/src/list.rs +++ /dev/null @@ -1,78 +0,0 @@ -use tui::{ - buffer::Buffer, - layout::Rect, - text::{Span, Spans, Text}, - widgets::{Block, Paragraph, Widget}, -}; - -#[derive(Default)] -pub struct List { - /// The index at which the list last started. Used for scrolling - pub offset: usize, -} - -impl List { - fn list_offset_for(&self, entry_in_view: Option<usize>, height: usize) -> usize { - match entry_in_view { - Some(pos) => match height as usize { - h if self.offset + h - 1 < pos => pos - h + 1, - _ if self.offset > pos => pos, - _ => self.offset, - }, - None => 0, - } - } -} - -#[derive(Default)] -pub struct ListProps<'b> { - pub block: Option<Block<'b>>, - pub entry_in_view: Option<usize>, -} - -impl List { - pub fn render<'a, 't>( - &mut self, - props: ListProps<'a>, - items: impl IntoIterator<Item = Vec<Span<'t>>>, - area: Rect, - buf: &mut Buffer, - ) { - let ListProps { - block, - entry_in_view, - } = props; - - let list_area = match block { - Some(b) => { - let inner_area = b.inner(area); - b.render(area, buf); - inner_area - } - None => area, - }; - self.offset = self.list_offset_for(entry_in_view, list_area.height as usize); - - if list_area.width < 1 || list_area.height < 1 { - return; - } - - for (i, vec_of_spans) in items - .into_iter() - .skip(self.offset) - .enumerate() - .take(list_area.height as usize) - { - let (x, y) = (list_area.left(), list_area.top() + i as u16); - Paragraph::new(Text::from(Spans::from(vec_of_spans))).render( - Rect { - x, - y, - width: list_area.width, - height: 1, - }, - buf, - ); - } - } -} diff --git a/tui-react/src/terminal.rs b/tui-react/src/terminal.rs deleted file mode 100644 index f38f8ea..0000000 --- a/tui-react/src/terminal.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! Derived from TUI-rs, license: MIT, Copyright (c) 2016 Florian Dehau -use log::error; -use std::{borrow::Borrow, io}; - -use tui::{backend::Backend, buffer::Buffer, layout::Rect}; - -/// A component meant to be rendered by `Terminal::render(...)`. -/// All other components don't have to implement this trait, and instead -/// provide a render method by convention, tuned towards their needs using whichever -/// generic types or lifetimes they need. -pub trait ToplevelComponent { - type Props; - - fn render(&mut self, props: impl Borrow<Self::Props>, area: Rect, buf: &mut Buffer); -} - -#[derive(Debug)] -pub struct Terminal<B> -where - B: Backend, -{ - pub backend: B, - buffers: [Buffer; 2], - current: usize, - hidden_cursor: bool, - known_size: Rect, -} - -impl<B> Drop for Terminal<B> -where - B: Backend, -{ - fn drop(&mut self) { - // Attempt to restore the cursor state - if self.hidden_cursor { - if let Err(err) = self.show_cursor() { - error!("Failed to show the cursor: {}", err); - } - } - } -} - -impl<B> Terminal<B> -where - B: Backend, -{ - pub fn new(backend: B) -> io::Result<Terminal<B>> { - let size = backend.size()?; - Ok(Terminal { - backend, - buffers: [Buffer::empty(size), Buffer::empty(size)], - current: 0, - hidden_cursor: false, - known_size: size, - }) - } - - pub fn current_buffer_mut(&mut self) -> &mut Buffer { - &mut self.buffers[self.current] - } - - pub fn reconcile_and_flush(&mut self) -> io::Result<()> { - let previous_buffer = &self.buffers[1 - self.current]; - let current_buffer = &self.buffers[self.current]; - let updates = previous_buffer.diff(current_buffer); - self.backend.draw(updates.into_iter()) - } - - pub fn resize(&mut self, area: Rect) -> io::Result<()> { - self.buffers[self.current].resize(area); - self.buffers[1 - self.current].reset(); - self.buffers[1 - self.current].resize(area); - self.known_size = area; - self.backend.clear() - } - - pub fn autoresize(&mut self) -> io::Result<()> { - let size = self.size()?; - if self.known_size != size { - self.resize(size)?; - } - Ok(()) - } - - /// Get ready for rendering and return the maximum display size as `Rect` - pub fn pre_render(&mut self) -> io::Result<Rect> { - // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets - // and the terminal (if growing), which may OOB. - self.autoresize()?; - Ok(self.known_size) - } - - pub fn post_render(&mut self) -> io::Result<()> { - self.reconcile_and_flush()?; - - self.buffers[1 - self.current].reset(); - self.current = 1 - self.current; - - self.backend.flush()?; - Ok(()) - } - - pub fn render<C>(&mut self, component: &mut C, props: impl Borrow<C::Props>) -> io::Result<()> - where - C: ToplevelComponent, - { - self.pre_render()?; - component.render(props, self.known_size, self.current_buffer_mut()); - self.post_render() - } - - pub fn hide_cursor(&mut self) -> io::Result<()> { - self.backend.hide_cursor()?; - self.hidden_cursor = true; - Ok(()) - } - pub fn show_cursor(&mut self) -> io::Result<()> { - self.backend.show_cursor()?; - self.hidden_cursor = false; - Ok(()) - } - pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> { - self.backend.get_cursor() - } - pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { - self.backend.set_cursor(x, y) - } - pub fn clear(&mut self) -> io::Result<()> { - self.backend.clear() - } - pub fn size(&self) -> io::Result<Rect> { - self.backend.size() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use tui::backend::TestBackend; - - #[derive(Default, Clone)] - struct ComplexProps { - x: usize, - y: String, - } - - #[derive(Default)] - struct StatefulComponent { - x: usize, - } - - #[derive(Default)] - struct StatelessComponent; - - impl ToplevelComponent for StatefulComponent { - type Props = usize; - - fn render(&mut self, props: impl Borrow<Self::Props>, _area: Rect, _buf: &mut Buffer) { - self.x += *props.borrow(); - } - } - - impl ToplevelComponent for StatelessComponent { - type Props = ComplexProps; - fn render(&mut self, _props: impl Borrow<Self::Props>, _area: Rect, _buf: &mut Buffer) { - // does not matter - we want to see it compiles essentially - } - } - - #[test] - fn it_does_render_with_simple_and_complex_props() { - let mut term = Terminal::new(TestBackend::new(20, 20)).unwrap(); - let mut c = StatefulComponent::default(); - - term.render(&mut c, 3usize).ok(); - assert_eq!(c.x, 3); - - let mut c = StatelessComponent::default(); - term.render(&mut c, ComplexProps::default()).ok(); - } -} |