Skip to content

Commit

Permalink
feat: implement spinner (#13)
Browse files Browse the repository at this point in the history
* feat: implement spinner

* fix: spinner - fix docs
  • Loading branch information
roele authored Jan 22, 2024
1 parent 265f639 commit fc34061
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 0 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,26 @@ fn main() {
println!("yes: {}", yes);
}
```

## Spinner

Spinners are used to indicate that a process is running.
Run example with [`cargo run --example spinner`](./examples/spinner.rs).

![Spinner](./assets/spinner.gif)

```rust
use std::{thread::sleep, time::Duration};

use demand::{Spinner, SpinnerStyle};

fn main() {
Spinner::new("Loading Data...")
.style(SpinnerStyle::line())
.run(|| {
sleep(Duration::from_secs(2));
})
.expect("error running spinner");
println!("Done!");
}
```
Binary file added assets/spinner.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions assets/spinner.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# VHS documentation
#
# Output:
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
#
# Require:
# Require <string> Ensure a program is on the $PATH to proceed
#
# Settings:
# Set FontSize <number> Set the font size of the terminal
# Set FontFamily <string> Set the font family of the terminal
# Set Height <number> Set the height of the terminal
# Set Width <number> Set the width of the terminal
# Set LetterSpacing <float> Set the font letter spacing (tracking)
# Set LineHeight <float> Set the font line height
# Set LoopOffset <float>% Set the starting frame offset for the GIF loop
# Set Theme <json|string> Set the theme of the terminal
# Set Padding <number> Set the padding of the terminal
# Set Framerate <number> Set the framerate of the recording
# Set PlaybackSpeed <float> Set the playback speed of the recording
# Set MarginFill <file|#000000> Set the file or color the margin will be filled with.
# Set Margin <number> Set the size of the margin. Has no effect if MarginFill isn't set.
# Set BorderRadius <number> Set terminal border radius, in pixels.
# Set WindowBar <string> Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)
# Set WindowBarSize <number> Set window bar size, in pixels. Default is 40.
# Set TypingSpeed <time> Set the typing speed of the terminal. Default is 50ms.
#
# Sleep:
# Sleep <time> Sleep for a set amount of <time> in seconds
#
# Type:
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
# <time> delay between each character
#
# Keys:
# Escape[@<time>] [number] Press the Escape key
# Backspace[@<time>] [number] Press the Backspace key
# Delete[@<time>] [number] Press the Delete key
# Insert[@<time>] [number] Press the Insert key
# Down[@<time>] [number] Press the Down key
# Enter[@<time>] [number] Press the Enter key
# Space[@<time>] [number] Press the Space key
# Tab[@<time>] [number] Press the Tab key
# Left[@<time>] [number] Press the Left Arrow key
# Right[@<time>] [number] Press the Right Arrow key
# Up[@<time>] [number] Press the Up Arrow key
# Down[@<time>] [number] Press the Down Arrow key
# PageUp[@<time>] [number] Press the Page Up key
# PageDown[@<time>] [number] Press the Page Down key
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
#
# Display:
# Hide Hide the subsequent commands from the output
# Show Show the subsequent commands in the output

Output assets/spinner.gif

Set Shell "fish"
Set Padding 10
Set FontSize 16
Set Width 600
Set Height 300
Set TypingSpeed 100ms

Hide
Type "cargo build --example spinner && clear" Enter
Sleep 2s
Show

Type "target/debug/examples/spinner" Enter
Sleep 5s
13 changes: 13 additions & 0 deletions examples/spinner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::{thread::sleep, time::Duration};

use demand::{Spinner, SpinnerStyle};

fn main() {
Spinner::new("Loading Data...")
.style(SpinnerStyle::line())
.run(|| {
sleep(Duration::from_secs(2));
})
.expect("error running spinner");
println!("Done!");
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ pub use input::Input;
pub use multiselect::MultiSelect;
pub use option::DemandOption;
pub use select::Select;
pub use spinner::Spinner;
pub use spinner::SpinnerStyle;
pub use theme::Theme;

mod confirm;
mod input;
mod multiselect;
mod option;
mod select;
mod spinner;
mod theme;
180 changes: 180 additions & 0 deletions src/spinner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::{
io::{self, Write},
thread::sleep,
time::Duration,
};

use console::Term;
use termcolor::{Buffer, WriteColor};

use crate::Theme;

/// Show a spinner
///
/// # Example
/// ```rust
/// use demand::{Spinner,SpinnerStyle};
/// use std::time::Duration;
/// use std::thread::sleep;
///
/// let spinner = Spinner::new("Loading data...")
/// .style(SpinnerStyle::line())
/// .run(|| {
/// sleep(Duration::from_secs(2));
/// })
/// .expect("error running spinner");
/// ```
pub struct Spinner {
// The title of the spinner
pub title: String,
// The style of the spinner
pub style: SpinnerStyle,
/// The colors/style of the spinner
pub theme: Theme,

term: Term,
frame: usize,
height: usize,
}

impl Spinner {
/// Create a new spinner with the given title
pub fn new<S: Into<String>>(title: S) -> Self {
Self {
title: title.into(),
style: SpinnerStyle::line(),
theme: Theme::default(),
term: Term::stderr(),
frame: 0,
height: 0,
}
}

/// Set the style of the spinner
pub fn style(mut self, style: SpinnerStyle) -> Self {
self.style = style;
self
}

/// Set the theme of the dialog
pub fn theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}

/// Displays the dialog to the user and returns their response
pub fn run<F>(mut self, func: F) -> io::Result<()>
where
F: Fn() + Send + 'static,
{
let handle = std::thread::spawn(move || {
func();
});

self.term.hide_cursor()?;
loop {
self.clear()?;
let output = self.render()?;
self.height = output.lines().count() - 1;
self.term.write_all(output.as_bytes())?;
sleep(self.style.fps);
if handle.is_finished() {
self.clear()?;
self.term.show_cursor()?;
break;
}
}
Ok(())
}

/// Render the spinner and return the output
fn render(&mut self) -> io::Result<String> {
let mut out = Buffer::ansi();

if self.frame > self.style.chars.len() - 1 {
self.frame = 0
}

out.set_color(&self.theme.input_prompt)?;
write!(out, "{} ", self.style.chars[self.frame])?;
out.reset()?;

write!(out, "{}", self.title)?;

self.frame += 1;

Ok(std::str::from_utf8(out.as_slice()).unwrap().to_string())
}

fn clear(&mut self) -> io::Result<()> {
self.term.clear_to_end_of_screen()?;
self.term.clear_last_lines(self.height)?;
self.height = 0;
Ok(())
}
}

/// The style of the spinner
///
/// # Example
/// ```rust
/// use demand::SpinnerStyle;
///
/// let style = SpinnerStyle::dots();
/// ```
pub struct SpinnerStyle {
chars: Vec<&'static str>,
fps: Duration,
}

impl SpinnerStyle {
// Create a new spinner type of dots
pub fn dots() -> Self {
Self {
chars: vec!["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
fps: Duration::from_millis(1000 / 10),
}
}
// Create a new spinner type of jump
pub fn jump() -> Self {
Self {
chars: vec!["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"],
fps: Duration::from_millis(1000 / 10),
}
}
// Create a new spinner type of line
pub fn line() -> Self {
Self {
chars: vec!["-", "\\", "|", "/"],
fps: Duration::from_millis(1000 / 10),
}
}
// Create a new spinner type of points
pub fn points() -> Self {
Self {
chars: vec!["∙∙∙", "●∙∙", "∙●∙", "∙∙●"],
fps: Duration::from_millis(1000 / 7),
}
}
// Create a new spinner type of meter
pub fn meter() -> Self {
Self {
chars: vec!["▱▱▱", "▰▱▱", "▰▰▱", "▰▰▰", "▰▰▱", "▰▱▱", "▱▱▱"],
fps: Duration::from_millis(1000 / 7),
}
}
// Create a new spinner type of mini dots
pub fn minidots() -> Self {
Self {
chars: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
fps: Duration::from_millis(1000 / 12),
}
}
// Create a new spinner type of ellipsis
pub fn ellipsis() -> Self {
Self {
chars: vec![" ", ". ", ".. ", "..."],
fps: Duration::from_millis(1000 / 3),
}
}
}

0 comments on commit fc34061

Please sign in to comment.