Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
357 changes: 148 additions & 209 deletions src/cmd/submit.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod cmd;
mod models;
mod service;
mod utils;
mod views;

use crate::cmd::Cli;
use clap::Parser;
Expand Down
5 changes: 3 additions & 2 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ impl SubmissionModeItem {
}
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ModelState {
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum AppState {
#[default]
LeaderboardSelection,
GpuSelection,
SubmissionModeSelection,
Expand Down
11 changes: 2 additions & 9 deletions src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,8 @@ pub async fn submit_solution<P: AsRef<Path>>(
"status" => (),
"result" => {
let result_val: Value = serde_json::from_str(data)?;
let pretty_result = match result_val.get("results") {
Some(result_obj) => serde_json::to_string_pretty(result_obj)?,
None => {
return Err(anyhow!(
"Invalid 'result' event structure: missing 'results' field"
))
}
};
return Ok(pretty_result);
let reports = result_val.get("reports").unwrap();
return Ok(reports.to_string());
}
"error" => {
let error_val: Value = serde_json::from_str(data)?;
Expand Down
89 changes: 62 additions & 27 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,68 @@ pub fn get_popcorn_directives<P: AsRef<Path>>(filepath: P) -> Result<(PopcornDir
))
}

pub fn display_ascii_art() {
pub fn get_ascii_art() -> String {
let art = r#"
_ __ _ ______ _
| | / / | | | ___ \ | |
| |/ / ___ _ __ _ __ ___ | | | |_/ / ___ _| |_
| \ / _ \ '__| '_ \ / _ \| | | ___ \ / _ \| | __|
| |\ \ __/ | | | | | __/| | | |_/ /| (_) | | |_
\_| \_/\___|_| |_| |_|\___|_/ \____/ \___/|_|\__|

POPCORN CLI - GPU MODE

┌───────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ooOoo│ │ooOoo│ │ooOoo│ │▒
│ │oOOOo│ │oOOOo│ │oOOOo│ │▒
│ │ooOoo│ │ooOoo│ │ooOoo│ ┌────────┐ │▒
│ └─────┘ └─────┘ └─────┘ │████████│ │▒
│ │████████│ │▒
│ ┌────────────────────────┐ │████████│ │▒
│ │ │ │████████│ │▒
│ │ POPCORN GPU COMPUTE │ └────────┘ │▒
│ │ │ │▒
│ └────────────────────────┘ │▒
│ │▒
└───────────────────────────────────────┘▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
"#;
_ __ _ ______ _
| | / / | | | ___ \ | |
| |/ / ___ _ __ _ __ ___ | | | |_/ / ___ _| |_
| \ / _ \ '__| '_ \ / _ \| | | ___ \ / _ \| | __|
| |\ \ __/ | | | | | __/| | | |_/ /| (_) | | |_
\_| \_/\___|_| |_| |_|\___|_/ \____/ \___/|_|\__|

POPCORN CLI - GPU MODE

┌───────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ooOoo│ │ooOoo│ │ooOoo│ │▒
│ │oOOOo│ │oOOOo│ │oOOOo│ │▒
│ │ooOoo│ │ooOoo│ │ooOoo│ ┌────────┐ │▒
│ └─────┘ └─────┘ └─────┘ │████████│ │▒
│ │████████│ │▒
│ ┌────────────────────────┐ │████████│ │▒
│ │ │ │████████│ │▒
│ │ POPCORN GPU COMPUTE │ └────────┘ │▒
│ │ │ │▒
│ └────────────────────────┘ │▒
│ │▒
└───────────────────────────────────────┘▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
"#;

art.to_string()
}


pub fn display_ascii_art() {
let art = get_ascii_art();
println!("{}", art);
}

pub fn custom_wrap(initial_text: String, remaining_text: String, available_width: usize) -> Vec<String> {
let mut lines = vec![initial_text];
let mut current_line = String::with_capacity(available_width);
for word in remaining_text.split_whitespace() {
if word.len() > available_width {
if !current_line.is_empty() {
lines.push(current_line.clone());
current_line.clear();
}
lines.push(word.to_string());
} else if current_line.is_empty() {
current_line.push_str(word);
} else if current_line.len() + word.len() + 1 <= available_width {
current_line.push(' ');
current_line.push_str(word);
} else {
lines.push(current_line.clone());
current_line.clear();
current_line.push_str(word);
}
}

if !current_line.is_empty() {
lines.push(current_line);
}
lines
}
73 changes: 73 additions & 0 deletions src/views/loading_page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use ratatui::{
buffer::Buffer,
layout::{Alignment, Layout, Rect},
style::{Color, Stylize},
widgets::{Block, Gauge, Padding, Paragraph, StatefulWidget, Widget},
};

#[derive(Debug, Default, Clone)]
pub struct LoadingPageState {
pub loop_count: u16,
pub progress_column: u16,
pub progress_bar: f64,
}

#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct LoadingPage {
header_area: Rect,
gauge_area: Rect,
footer_area: Rect,
}

const GAUGE_COLOR: Color = ratatui::style::palette::tailwind::RED.c800;

impl StatefulWidget for &LoadingPage {
type State = LoadingPageState;

fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
use ratatui::layout::Constraint::Percentage;

let layout = Layout::vertical([Percentage(45), Percentage(10), Percentage(45)]);

let [_, gauge_area, footer_area] = layout.areas(area);

render_gauge(gauge_area, buf, state);
render_footer(footer_area, buf, state);
}
}

fn render_gauge(area: Rect, buf: &mut Buffer, state: &mut LoadingPageState) {
let blk = Block::default().padding(Padding::horizontal(20));
Gauge::default()
.block(blk)
.gauge_style(GAUGE_COLOR)
.ratio(state.progress_bar / 100.0)
.render(area, buf);
}

fn get_footer_text(state: &LoadingPageState) -> String {
let percentage = state.progress_bar;

if state.loop_count > 0 {
return "Did you know we have zero idea how long this will take?".to_string();
}

if percentage > 75.0 {
return "Almost there!".to_string();
} else if percentage > 35.0 {
return "Crunching numbers...".to_string();
} else {
return "This is taking a while, huh?".to_string();
}
}

fn render_footer(area: Rect, buf: &mut Buffer, state: &LoadingPageState) {
let blk = Block::default().padding(Padding::vertical(1));
let text = Paragraph::new(get_footer_text(state))
.alignment(Alignment::Center)
.fg(Color::White)
.bold()
.block(blk);

text.render(area, buf);
}
2 changes: 2 additions & 0 deletions src/views/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod result_page;
pub mod loading_page;
149 changes: 149 additions & 0 deletions src/views/result_page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use crate::utils;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::{
layout::{Alignment, Constraint, Layout, Margin, Rect},
prelude::Buffer,
style::{Color, Style},
symbols::scrollbar,
widgets::{Block, BorderType, Paragraph, Scrollbar, ScrollbarState, StatefulWidget, Widget},
};

#[derive(Default, Debug)]
pub struct ResultPageState {
pub vertical_scroll: u16,
pub vertical_scroll_state: ScrollbarState,
pub horizontal_scroll: u16,
pub horizontal_scroll_state: ScrollbarState,
pub ack: bool,
}

#[derive(Default, Debug)]
pub struct ResultPage {
result_text: Paragraph<'static>,
}

impl ResultPage {
pub fn new(result_text: String, state: &mut ResultPageState) -> Self {
let max_width = result_text
.lines()
.map(|line| line.len())
.max()
.unwrap_or(0);

let num_lines = result_text.lines().count();

state.vertical_scroll_state = state
.vertical_scroll_state
.content_length(num_lines);

state.horizontal_scroll_state = state.horizontal_scroll_state.content_length(max_width);

Self {
result_text: Paragraph::new(result_text),
}
}

fn render_left(&self, buf: &mut Buffer, left: Rect) {
let left_block = Block::bordered()
.border_type(BorderType::Plain)
.border_style(Style::default().fg(Color::Yellow));

let left_text = Paragraph::new(utils::get_ascii_art());

left_text.block(left_block).render(left, buf);
}

fn render_right(&self, buf: &mut Buffer, right: Rect, state: &mut ResultPageState) {
let right_block = Block::bordered()
.border_type(BorderType::Plain)
.border_style(Style::default().fg(Color::Yellow))
.title_bottom("Press q to quit...")
.title_style(Style::default().fg(Color::Red))
.title_alignment(Alignment::Right);

let result_text = self
.result_text
.clone()
.block(right_block)
.scroll((state.vertical_scroll as u16, state.horizontal_scroll as u16));
result_text.render(right, buf);
}

pub fn handle_key_event(&mut self, state: &mut ResultPageState) {
if event::poll(std::time::Duration::from_millis(50)).unwrap() {
if let Event::Key(key) = event::read().unwrap() {
if key.kind != KeyEventKind::Press {
return;
}
if key.code == KeyCode::Char('q') {
state.ack = true;
}

match key.code {
KeyCode::Char('j') | KeyCode::Down => {
state.vertical_scroll = state.vertical_scroll.saturating_add(1);
state.vertical_scroll_state = state
.vertical_scroll_state
.position(state.vertical_scroll as usize);
}
KeyCode::Char('k') | KeyCode::Up => {
state.vertical_scroll = state.vertical_scroll.saturating_sub(1);
state.vertical_scroll_state = state
.vertical_scroll_state
.position(state.vertical_scroll as usize);
}
KeyCode::Char('h') | KeyCode::Left => {
state.horizontal_scroll = state.horizontal_scroll.saturating_sub(1);
state.horizontal_scroll_state = state
.horizontal_scroll_state
.position(state.horizontal_scroll as usize);
}
KeyCode::Char('l') | KeyCode::Right => {
state.horizontal_scroll = state.horizontal_scroll.saturating_add(1);
state.horizontal_scroll_state = state
.horizontal_scroll_state
.position(state.horizontal_scroll as usize);
}
_ => {}
}
}
}
}
}

impl StatefulWidget for &ResultPage {
type State = ResultPageState;

fn render(self, area: Rect, buf: &mut Buffer, state: &mut ResultPageState) {
let layout = Layout::horizontal([Constraint::Percentage(45), Constraint::Percentage(55)]);
let [left, right] = layout.areas(area);

self.render_left(buf, left);
self.render_right(buf, right, state);

let vertical_scrollbar =
Scrollbar::new(ratatui::widgets::ScrollbarOrientation::VerticalLeft)
.symbols(scrollbar::VERTICAL);

let horizontal_scrollbar =
Scrollbar::new(ratatui::widgets::ScrollbarOrientation::HorizontalBottom)
.symbols(scrollbar::HORIZONTAL);

vertical_scrollbar.render(
right.inner(&Margin {
vertical: 1,
horizontal: 0,
}),
buf,
&mut state.vertical_scroll_state,
);
horizontal_scrollbar.render(
right.inner(&Margin {
vertical: 0,
horizontal: 1,
}),
buf,
&mut state.horizontal_scroll_state,
);
}
}