Skip to content

Commit

Permalink
Merge pull request #49 from NJUPT-SAST/feature/magic&cli
Browse files Browse the repository at this point in the history
Feature/magic&cli
  • Loading branch information
BQXBQX authored Oct 3, 2024
2 parents ea0fb0a + 7ee9a2f commit d01712b
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 119 deletions.
16 changes: 9 additions & 7 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
fn main() {
// println!("Hello, world!");
// let mut terminal = ratatui::init();
// terminal.clear()?;
// // let app_result = run(terminal);
// ratatui::restore();
// app_result
use std::io::Result;

use cli::utils::tui::{tui::Tui, tui_render::TuiRender};

fn main() -> Result<()> {
let mut terminal = ratatui::init();
let app_result = Tui::new().run(&mut terminal);
ratatui::restore();
app_result
}
75 changes: 75 additions & 0 deletions cli/src/utils/tui/component_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use ratatui::{
style::{
palette::{material::GREEN, tailwind::SLATE},
Color,
},
text::Line,
widgets::{ListItem, ListState},
};

const TEXT_FG_COLOR: Color = SLATE.c200;
const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;

#[derive(Debug, Default)]
pub struct ComponentList {
pub components: Vec<ComponentItem>,
pub state: ListState,
}

impl ComponentList {
pub fn new() -> Self {
ComponentList::from_iter([
(Status::UnSelect, "Button", "A button with magic animation"),
(
Status::Select,
"Card",
"A card with a shooting star background",
),
])
}
}

impl FromIterator<(Status, &'static str, &'static str)> for ComponentList {
fn from_iter<I: IntoIterator<Item = (Status, &'static str, &'static str)>>(iter: I) -> Self {
let components = iter
.into_iter()
.map(|(status, todo, info)| ComponentItem::new(status, todo, info))
.collect();
let state: ListState = ListState::default();
Self { components, state }
}
}

#[derive(Debug)]
pub struct ComponentItem {
pub title: String,
pub info: String,
pub status: Status,
}

#[derive(Debug)]
pub enum Status {
Select,
UnSelect,
}

impl ComponentItem {
fn new(status: Status, title: &str, info: &str) -> Self {
Self {
title: title.to_string(),
info: info.to_string(),
status,
}
}
}

impl From<&ComponentItem> for ListItem<'_> {
fn from(value: &ComponentItem) -> Self {
let line = match value.status {
Status::UnSelect => Line::styled(format!(" ☐ {}", value.title), TEXT_FG_COLOR),
Status::Select => Line::styled(format!(" ✓ {}", value.title), COMPLETED_TEXT_FG_COLOR),
};

ListItem::new(line)
}
}
6 changes: 4 additions & 2 deletions cli/src/utils/tui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod tui_base;
pub mod wrapper;
pub mod component_list;
pub mod split_area;
pub mod tui;
pub mod tui_render;
22 changes: 22 additions & 0 deletions cli/src/utils/tui/split_area.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::rc::Rc;

use ratatui::layout::{Constraint, Direction, Layout, Rect};

pub fn split_area(area: Rect) -> (Rc<[Rect]>, Rc<[Rect]>, Rc<[Rect]>) {
let comment = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Fill(1), Constraint::Length(1)])
.split(area);

let outer = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![Constraint::Percentage(30), Constraint::Percentage(70)])
.split(comment[0]);

let inner = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Percentage(35), Constraint::Percentage(65)])
.split(outer[1]);

(comment, outer, inner)
}
142 changes: 142 additions & 0 deletions cli/src/utils/tui/tui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
buffer::Buffer,
layout::Rect,
style::{palette::tailwind::SLATE, Color, Modifier, Style},
symbols::border,
widgets::{Block, List, ListItem, Paragraph, StatefulWidget, Widget, Wrap},
};

use super::{
component_list::{ComponentList, Status},
split_area::split_area,
tui_render::TuiRender,
};

const SELECTED_STYLE: Style = Style::new().bg(SLATE.c800).add_modifier(Modifier::BOLD);

#[derive(Debug, Default)]
pub struct Tui {
exit: bool,
component_list: ComponentList,
}

impl Tui {
pub fn new() -> Self {
Self {
exit: false,
component_list: ComponentList::new(),
}
}
fn exit(&mut self) {
self.exit = true;
}

fn select_next(&mut self) {
self.component_list.state.select_next();
}

fn select_previous(&mut self) {
self.component_list.state.select_previous();
}

fn select_first(&mut self) {
self.component_list.state.select_first();
}

fn select_last(&mut self) {
self.component_list.state.select_last();
}

fn toggle_status(&mut self) {
if let Some(i) = self.component_list.state.selected() {
self.component_list.components[i].status =
match self.component_list.components[i].status {
Status::Select => Status::UnSelect,
Status::UnSelect => Status::Select,
};
}
}

fn render_list(&mut self, area: Rect, buf: &mut Buffer) {
let block = Block::bordered()
.title(" Select Components ")
.border_set(border::THICK);

let items: Vec<ListItem> = self
.component_list
.components
.iter()
.enumerate()
.map(|(_, component_item)| ListItem::from(component_item))
.collect();

let list = List::new(items)
.block(block)
.highlight_style(SELECTED_STYLE)
.highlight_symbol(">")
.highlight_spacing(ratatui::widgets::HighlightSpacing::Always);

StatefulWidget::render(list, area, buf, &mut self.component_list.state);
}

fn render_selected_item(&self, area: Rect, buf: &mut Buffer) {
let info = if let Some(select_index) = self.component_list.state.selected() {
self.component_list.components[select_index].info.clone()
} else {
"Nothing selected...".to_string()
};

let block = Block::bordered().title(" Info ").border_set(border::THICK);

Paragraph::new(info)
.block(block)
.wrap(Wrap { trim: false })
.render(area, buf);
}
}

impl Widget for &mut Tui {
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) {
let (comment, outer, inner) = split_area(area);

Paragraph::new("Use 'j/k' to move, 'g/G' to go top/bottom, Space to toggle.")
.style(Style::default().fg(Color::Red))
.centered()
.render(comment[1], buf);

let block = Block::bordered().title(" State ").border_set(border::THICK);
Paragraph::new("")
.centered()
.block(block)
.render(inner[1], buf);

self.render_list(outer[0], buf);

self.render_selected_item(inner[0], buf);
}
}

impl TuiRender for Tui {
fn draw(&mut self, frame: &mut ratatui::Frame) {
frame.render_widget(self, frame.area());
}

fn handle_key_event(&mut self, key_event: KeyEvent) {
match key_event.code {
KeyCode::Char('q') | KeyCode::Esc => self.exit(),
KeyCode::Char('j') | KeyCode::Down => self.select_next(),
KeyCode::Char('k') | KeyCode::Up => self.select_previous(),
KeyCode::Char('g') | KeyCode::Home => self.select_first(),
KeyCode::Char('G') | KeyCode::End => self.select_last(),
KeyCode::Char(' ') | KeyCode::Right => {
self.toggle_status();
}
_ => {}
}
}

fn get_exit(&self) -> bool {
self.exit
}
}
13 changes: 0 additions & 13 deletions cli/src/utils/tui/tui_base.rs

This file was deleted.

29 changes: 29 additions & 0 deletions cli/src/utils/tui/tui_render.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use crossterm::event::{self, Event, KeyEvent, KeyEventKind};
use ratatui::{DefaultTerminal, Frame};
pub trait TuiRender {
fn handle_events(&mut self) -> Result<(), std::io::Error> {
match event::read()? {
// it's important to check that the event is a key press event as
// crossterm also emits key release and repeat events on Windows.
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
self.handle_key_event(key_event)
}
_ => {}
};
Ok(())
}

fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<(), std::io::Error> {
while !self.get_exit() {
terminal.draw(|frame| self.draw(frame))?;
self.handle_events()?;
}
Ok(())
}

fn draw(&mut self, frame: &mut Frame);

fn handle_key_event(&mut self, key_event: KeyEvent);

fn get_exit(&self) -> bool;
}
57 changes: 0 additions & 57 deletions cli/src/utils/tui/wrapper.rs

This file was deleted.

Loading

1 comment on commit d01712b

@vercel
Copy link

@vercel vercel bot commented on d01712b Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

sast-ui – ./

sast-ui-git-main-sast.vercel.app
sast-ui-sast.vercel.app

Please sign in to comment.