gradient_tui_fork 0.19.0

this is a fork, please use the official repo
Documentation
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::time::{Duration, Instant};
use std::{error::Error, io};
use tui::{
    backend::{Backend, CrosstermBackend},
    gradient::BorderGradients,
    layout::{Constraint, Direction, Layout},
    style::{Color, Modifier, Style},
    text::{Span, Spans},
    widgets::{Block, Borders, Tabs},
    Frame, Terminal,
};

use colorgrad;

struct App<'a> {
    pub titles: Vec<&'a str>,
    pub index: usize,
    pub gradients: BorderGradients,
    pub increment: u8,
    pub direction: Dir,
}

#[derive(PartialEq, Eq)]
enum Dir {
    Add,
    Substract,
}

impl<'a> App<'a> {
    fn new() -> App<'a> {
        App {
            titles: vec!["Tab0", "Tab1", "Tab2", "Tab3"],
            index: 0,
            gradients: BorderGradients::default(),
            increment: 0,
            direction: Dir::Add,
        }
    }

    pub fn next(&mut self) {
        self.index = (self.index + 1) % self.titles.len();
    }

    pub fn previous(&mut self) {
        if self.index > 0 {
            self.index -= 1;
        } else {
            self.index = self.titles.len() - 1;
        }
    }

    pub fn on_tick(&mut self) {
        if self.direction == Dir::Add {
            if let Some(s) = self.increment.checked_add(5) {
                self.increment = s;
            } else {
                self.direction = Dir::Substract;
                self.increment = 250;
            }
        } else {
            if let Some(s) = self.increment.checked_sub(5) {
                self.increment = s;
            } else {
                self.direction = Dir::Add;
                self.increment = 0;
            }
        }
        let curr_style = match (self.increment % 2 as u8) {
            0 => Style::default().fg(Color::Red),
            _ => Style::default().fg(Color::Blue),
        };
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    // setup terminal
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;
    let tick_rate = Duration::from_millis(250);

    // create app and run it
    let app = App::new();
    let res = run_app(&mut terminal, app, tick_rate);

    // restore terminal
    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    if let Err(err) = res {
        println!("{:?}", err)
    }

    Ok(())
}

fn run_app<B: Backend>(
    terminal: &mut Terminal<B>,
    mut app: App,
    tick_rate: Duration,
) -> io::Result<()> {
    let mut last_tick = Instant::now();
    loop {
        terminal.draw(|f| ui(f, &app))?;

        let timeout = tick_rate
            .checked_sub(last_tick.elapsed())
            .unwrap_or_else(|| Duration::from_secs(0));
        if crossterm::event::poll(timeout)? {
            if let Event::Key(key) = event::read()? {
                if let KeyCode::Char('q') = key.code {
                    return Ok(());
                }
            }
        }
        if last_tick.elapsed() >= tick_rate {
            app.on_tick();
            last_tick = Instant::now();
        }
    }
}

enum NewGradient {
    Color(Color),
}

impl From<colorgrad::Color> for NewGradient {
    fn from(color: colorgrad::Color) -> Self {
        let c = color.to_rgba8();
        let new_c = Color::Rgb(c[0], c[1], c[2]);
        NewGradient::Color(new_c)
    }
}

impl Into<Color> for NewGradient {
    fn into(self) -> Color {
        match self {
            NewGradient::Color(c) => c,
        }
    }
}

fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
    let size = f.size();
    let gradient = colorgrad::CustomGradient::new()
        .colors(&[
            colorgrad::Color::from_rgba8(255, 255, 255, 0),
            colorgrad::Color::from_rgba8(0, 0, app.increment, 0),
            colorgrad::Color::from_rgba8(255, 255, 255, 0),
        ])
        .build()
        .ok();

    let colors = gradient
        .expect("where colors?")
        .colors(f.size().width as usize)
        .iter()
        .map(|c| c.clone().into())
        .collect::<Vec<NewGradient>>();
    let color_vec: Vec<Color> = colors.into_iter().map(|c| c.into()).collect();

    let gradients = BorderGradients {
        bottom: Some(color_vec.clone()),
        top: Some(color_vec),
        ..Default::default()
    };

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .margin(5)
        .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
        .split(size);

    let block = Block::default().style(Style::default().bg(Color::Black).fg(Color::Black));
    f.render_widget(block, size);
    let titles = app
        .titles
        .iter()
        .map(|t| {
            let (first, rest) = t.split_at(1);
            Spans::from(vec![
                Span::styled(first, Style::default().fg(Color::Yellow)),
                Span::styled(rest, Style::default().fg(Color::Green)),
            ])
        })
        .collect();
    let tabs = Tabs::new(titles)
        .block(Block::default().borders(Borders::ALL).title("Tabs"))
        .select(app.index)
        .style(Style::default().fg(Color::Cyan))
        .highlight_style(
            Style::default()
                .add_modifier(Modifier::BOLD)
                .bg(Color::Black),
        );

    f.render_widget(tabs, chunks[0]);
    let inner = match app.index {
        0 => Block::default()
            .title("Inner 0")
            .borders(Borders::ALL)
            .border_gradients(gradients),
        1 => Block::default().title("Inner 1").borders(Borders::ALL),
        2 => Block::default().title("Inner 2").borders(Borders::ALL),
        3 => Block::default().title("Inner 3").borders(Borders::ALL),
        _ => unreachable!(),
    };
    f.render_widget(inner, chunks[1]);
}