Initial commit
This commit is contained in:
commit
78ed43eb57
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
29
.github/workflows/rust.yml
vendored
Normal file
29
.github/workflows/rust.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Lint
|
||||
run: cargo clippy
|
||||
- name: Format
|
||||
run: cargo fmt
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Run examples
|
||||
run: cargo run --example simple
|
||||
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "spinoff"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
author = "ad4m"
|
||||
description = "🔨 Simple to use Rust library for displaying spinners in the terminal."
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/ad4mx/spinoff"
|
||||
repository = "https://github.com/ad4mx/spinoff"
|
||||
readme = "README.md"
|
||||
keywords = ["spinner", "spin", "loader", "term", "terminal"]
|
||||
categories = ["command-line-interface"]
|
||||
include = ["src/**/*", "README.md"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
maplit = "1.0.2"
|
||||
yansi = "0.5.1"
|
||||
strum = { version = "0.24.0", features = ["derive"] }
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 ad4m
|
||||
|
||||
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.
|
40
README.md
Normal file
40
README.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# spinoff
|
||||
`spinoff` is a simple to use library for displaying spinners in the terminal, with plenty of features and options.
|
||||
|
||||
[![Cargo version](https://img.shields.io/crates/v/spinoff.svg)](https://crates.io/crates/spinoff) [![Crates.io](https://img.shields.io/crates/l/spinoff.svg)] [![Crates.io](https://img.shields.io/crates/d/spinoff.svg)](https://crates.io/crates/spinoff)
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use spinoff::Spinners;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
let spinner = spinoff::new(Spinners::Dots, "Loading...", "Blue".into()); // Can also be Some("blue") or None
|
||||
sleep(Duration::from_secs(3));
|
||||
spinner.success("Done!");
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
* All documentation can be found on the [Docs.rs page](https://docs.rs/spinoff/latest/spinoff/).
|
||||
* If you want to see all the available `Spinner` options, check the [`Spinners`](src/utils/spinner_enum.rs) enum.
|
||||
|
||||
## Examples
|
||||
|
||||
To run some of the included examples, use:
|
||||
```bash
|
||||
cargo run --example all_spinners
|
||||
```
|
||||
|
||||
```bash
|
||||
cargo run --example simple
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Any contributions to this crate are highly appreciated. If you have any ideas/suggestions/bug fixes, please open an issue or pull request.
|
||||
|
||||
## License
|
||||
|
||||
This crate is licensed under the [MIT license](LICENSE).
|
12
examples/all_spinners.rs
Normal file
12
examples/all_spinners.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use spinoff::Spinners;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use strum::IntoEnumIterator;
|
||||
fn main() {
|
||||
for spinner in Spinners::iter() {
|
||||
let spin = spinoff::new(spinner, "", None);
|
||||
sleep(Duration::from_secs(1));
|
||||
spin.clear();
|
||||
}
|
||||
println!("Done!");
|
||||
}
|
8
examples/simple.rs
Normal file
8
examples/simple.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use spinoff::Spinners;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
fn main() {
|
||||
let sp = spinoff::new(Spinners::Dots, "Loading...", None);
|
||||
sleep(Duration::from_millis(800));
|
||||
sp.success("Success!");
|
||||
}
|
9
examples/stop_and_persist.rs
Normal file
9
examples/stop_and_persist.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use spinoff::Spinners;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let sp = spinoff::new(Spinners::Dots, "Loading...", None);
|
||||
sleep(Duration::from_secs(5));
|
||||
sp.stop_and_persist("🍕", "Pizza!", "yellow".into());
|
||||
}
|
281
src/lib.rs
Normal file
281
src/lib.rs
Normal file
|
@ -0,0 +1,281 @@
|
|||
//! ## spinoff
|
||||
//!
|
||||
//! `spinoff` is a simple library for displaying spinners in the terminal.
|
||||
//! ### Usage
|
||||
//!
|
||||
//! ```rust
|
||||
//! use spinoff::Spinners;
|
||||
//! use std::thread::sleep;
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! let sp = spinoff::new(Spinners::Dots, "Loading...", None);
|
||||
//! sleep(Duration::from_millis(800));
|
||||
//! sp.success("Success!");
|
||||
//!
|
||||
//! ```
|
||||
//! ### Spinners
|
||||
//! There are over 80+ spinners available in the `Spinners` enum.
|
||||
//!
|
||||
//! ### Colors
|
||||
//! You can also color your spinners without any hassle. Simply pass a color to the `color` option.
|
||||
//! There are 6 colors available: `blue`, `green`, `red`, `yellow`, `cyan`, `white`.
|
||||
//! Don't want any of that? Simply pass `None` to the `color` option.
|
||||
//!
|
||||
#![allow(clippy::needless_return)]
|
||||
mod utils;
|
||||
use crate::utils::printer::{delete_last_line, init_color};
|
||||
use crate::utils::spinner_data::SPINNER_FRAMES;
|
||||
pub use crate::utils::spinner_enum::Spinners;
|
||||
use std::io::{stdout, Write};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
// Using this type for better readability.
|
||||
type StringLiteral = &'static str;
|
||||
|
||||
/// All methods for the spinner are implemented in this struct.
|
||||
/// This struct has an `Arc<AtomicBool>` field, which is later used in the `stop` type methods to stop the thread printing the spinner.
|
||||
/// The `msg` field is needed for the `clear` and `stop` methods for knowing how many spaces to print to clean up the previous spinner.
|
||||
|
||||
pub struct Spinner {
|
||||
still_spinning: Arc<AtomicBool>,
|
||||
msg: StringLiteral,
|
||||
}
|
||||
/// Create a new spinner.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `spinner_type` - The spinner to use.
|
||||
/// * `msg` - The message to display.
|
||||
/// * `color` - The color of the spinner.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", "blue".into()); // Color can also be `None` or `Some("blue")`
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.clear();
|
||||
/// ```
|
||||
/// # Panics
|
||||
/// * `Invalid color` - If the color is not one of the following: `blue`, `green`, `red`, `yellow`, `cyan`, `white` or `None`.
|
||||
/// * `Invalid spinner` - If the spinner is not one of those belonging to the `Spinners` enum.
|
||||
///
|
||||
/// # Notes
|
||||
/// * The spinner immediately starts spinning upon creation.
|
||||
pub fn new(spinner_type: Spinners, msg: StringLiteral, color: Option<StringLiteral>) -> Spinner {
|
||||
let spinner_data = SPINNER_FRAMES
|
||||
.get(&spinner_type)
|
||||
.expect("invalid spinner: expected variant of Spinners enum");
|
||||
let mut stdout = stdout();
|
||||
let mut i = 0;
|
||||
let still_spinning = Arc::new(AtomicBool::new(true));
|
||||
// Clone the atomic bool so that we can use it in the thread and return the original one later.
|
||||
let still_spinning_cloned = still_spinning.clone();
|
||||
// We use atomic bools to make the thread stop itself when the `spinner.stop()` method is called.
|
||||
thread::spawn(move || {
|
||||
while still_spinning_cloned.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let text = format!(
|
||||
"\r{} {}",
|
||||
init_color(color, spinner_data.frames[i].to_string()),
|
||||
msg
|
||||
);
|
||||
stdout.write_all(text.as_bytes()).unwrap();
|
||||
stdout.flush().unwrap();
|
||||
thread::sleep(std::time::Duration::from_millis(
|
||||
spinner_data.interval as u64,
|
||||
));
|
||||
i = (i + 1) % spinner_data.frames.len();
|
||||
}
|
||||
});
|
||||
// Return a Spinner struct so we can implement methods on it instead of `spinoff::stop()` etc.
|
||||
let spinner = Spinner {
|
||||
still_spinning,
|
||||
msg,
|
||||
};
|
||||
return spinner;
|
||||
}
|
||||
|
||||
impl Spinner {
|
||||
/// Stop the spinner.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.stop();
|
||||
/// ```
|
||||
/// # Notes
|
||||
/// * The spinner will be deleted after this method is called, the message will remain though.
|
||||
/// * This method also sets the `still_spinning` atomic bool to `false`, which stops the spinner thread.
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
///
|
||||
pub fn stop(self) {
|
||||
self.still_spinning
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
print!("\r");
|
||||
println!("{} ", self.msg);
|
||||
}
|
||||
|
||||
/// Stops the spinner and prints a message on a new line.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.stop_with_message("Bye", "red".into());
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// * The method will panic if the color is not one of the following: `blue`, `green`, `red`, `yellow`, `cyan`, `white` or `None`.
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
///
|
||||
pub fn stop_with_message(self, msg: StringLiteral, color: Option<StringLiteral>) {
|
||||
self.stop();
|
||||
println!("{}", init_color(color, msg.into()));
|
||||
}
|
||||
|
||||
/// Deletes the spinner and message and prints a new line with a symbol and message.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.stop_and_persist("🍕", "Pizza!", None);
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// * The method will panic if the color is not one of the following: `blue`, `green`, `red`, `yellow`, `cyan`, `white` or `None`.
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the success message.
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
pub fn stop_and_persist(self, symbol: StringLiteral, msg: StringLiteral, color: Option<StringLiteral>) {
|
||||
self.clear();
|
||||
println!("{} {}", init_color(color, symbol.into()), &msg);
|
||||
}
|
||||
|
||||
/// Deletes the last line of the terminal and prints a success symbol with a message.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.success("Success!");
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the success message.
|
||||
pub fn success(self, msg: StringLiteral) {
|
||||
self.stop_and_persist("✔", msg, "green".into());
|
||||
}
|
||||
|
||||
/// Deletes the last line of the terminal and prints a failure symbol with a message.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.fail("Failed!");
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the failure message.
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
///
|
||||
pub fn fail(self, msg: StringLiteral) {
|
||||
self.stop_and_persist("✖", msg, "red".into());
|
||||
}
|
||||
|
||||
/// Deletes the last line of the terminal and prints a warning symbol with a message.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.warn("Look out!");
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the warning message.
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
pub fn warn(self, msg: StringLiteral) {
|
||||
self.stop_and_persist("⚠ ", msg, "yellow".into());
|
||||
}
|
||||
|
||||
/// Deletes the last line of the terminal and prints a new spinner.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let mut sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp = sp.update(Spinners::Dots2, "Goodbye".into(), None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.stop();
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the new spinner instance.
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
///
|
||||
pub fn update(self, spinner: Spinners, msg: StringLiteral, color: Option<StringLiteral>) -> Spinner {
|
||||
self.still_spinning
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
delete_last_line(self.msg);
|
||||
self::new(spinner, msg, color)
|
||||
}
|
||||
/// Deletes the last line of the terminal.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use spinoff::Spinners;
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let mut sp = spinoff::new(Spinners::Dots, "Hello", None);
|
||||
/// sleep(Duration::from_millis(800));
|
||||
/// sp.clear();
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the `delete` method call.
|
||||
/// * This method cannot be called if the spinner is already stopped.
|
||||
pub fn clear(self) {
|
||||
self.still_spinning
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
delete_last_line(self.msg);
|
||||
}
|
||||
}
|
3
src/utils/mod.rs
Normal file
3
src/utils/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod printer;
|
||||
pub mod spinner_data;
|
||||
pub mod spinner_enum;
|
25
src/utils/printer.rs
Normal file
25
src/utils/printer.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use yansi::Paint;
|
||||
|
||||
use crate::StringLiteral;
|
||||
|
||||
pub fn delete_last_line(msg: StringLiteral) {
|
||||
print!("\r");
|
||||
for _ in 0..(msg.len() + 30) {
|
||||
print!(" ");
|
||||
}
|
||||
print!("\r");
|
||||
}
|
||||
|
||||
/// Accepts a color option and spinner, returns a Paint<String> object (e.g. `Paint::green(spinner)`)
|
||||
pub fn init_color(color: Option<&str>, spinner: String) -> Paint<String> {
|
||||
match color {
|
||||
Some("blue") => Paint::blue(spinner),
|
||||
Some("green") => Paint::green(spinner),
|
||||
Some("red") => Paint::red(spinner),
|
||||
Some("yellow") => Paint::yellow(spinner),
|
||||
Some("cyan") => Paint::cyan(spinner),
|
||||
Some("white") => Paint::new(spinner),
|
||||
None => Paint::new(spinner),
|
||||
_ => panic!("invalid color: expected one of the following: blue, green, red, yellow, cyan, white or None"),
|
||||
}
|
||||
}
|
1190
src/utils/spinner_data.rs
Normal file
1190
src/utils/spinner_data.rs
Normal file
File diff suppressed because it is too large
Load diff
89
src/utils/spinner_enum.rs
Normal file
89
src/utils/spinner_enum.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use strum::EnumIter;
|
||||
/// An enum of all the available spinners.
|
||||
/// Contains around 80+ spinners.
|
||||
/// Each variant in this enum is assigned to a HashMap holding it's frames and interval count.
|
||||
#[derive(Debug, Eq, Hash, PartialEq, EnumIter)]
|
||||
pub enum Spinners {
|
||||
Dots,
|
||||
Dots2,
|
||||
Dots3,
|
||||
Dots4,
|
||||
Dots5,
|
||||
Dots6,
|
||||
Dots7,
|
||||
Dots8,
|
||||
Dots9,
|
||||
Dots10,
|
||||
Dots11,
|
||||
Dots12,
|
||||
Dots8Bit,
|
||||
Line,
|
||||
Line2,
|
||||
Pipe,
|
||||
SimpleDots,
|
||||
SimpleDotsScrolling,
|
||||
Star,
|
||||
Star2,
|
||||
Flip,
|
||||
Hamburger,
|
||||
GrowVertical,
|
||||
GrowHorizontal,
|
||||
Balloon,
|
||||
Balloon2,
|
||||
Noise,
|
||||
Bounce,
|
||||
BoxBounce,
|
||||
BoxBounce2,
|
||||
Triangle,
|
||||
Arc,
|
||||
Circle,
|
||||
SquareCorners,
|
||||
CircleQuarters,
|
||||
CircleHalves,
|
||||
Squish,
|
||||
Toggle,
|
||||
Toggle2,
|
||||
Toggle3,
|
||||
Toggle4,
|
||||
Toggle5,
|
||||
Toggle6,
|
||||
Toggle7,
|
||||
Toggle8,
|
||||
Toggle9,
|
||||
Toggle10,
|
||||
Toggle11,
|
||||
Toggle12,
|
||||
Toggle13,
|
||||
Arrow,
|
||||
Arrow2,
|
||||
Arrow3,
|
||||
BouncingBar,
|
||||
BouncingBall,
|
||||
Smiley,
|
||||
Monkey,
|
||||
Hearts,
|
||||
Clock,
|
||||
Earth,
|
||||
Material,
|
||||
Moon,
|
||||
Runner,
|
||||
Pong,
|
||||
Shark,
|
||||
Dqpb,
|
||||
Weather,
|
||||
Christmas,
|
||||
Grenade,
|
||||
Point,
|
||||
Layer,
|
||||
BetaWave,
|
||||
FingerDance,
|
||||
FistBump,
|
||||
SoccerHeader,
|
||||
Mindblown,
|
||||
Speaker,
|
||||
OrangePulse,
|
||||
BluePulse,
|
||||
OrangeBluePulse,
|
||||
TimeTravel,
|
||||
Aesthetic,
|
||||
}
|
Loading…
Reference in a new issue