1
0
Fork 0
mirror of https://gitlab.com/niansa/colohalopp.git synced 2025-03-06 20:48:29 +01:00

Initial commit

This commit is contained in:
niansa 2023-05-27 23:32:32 +02:00
commit 93035b9061
13 changed files with 622 additions and 0 deletions

75
.gitignore vendored Normal file
View file

@ -0,0 +1,75 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
CMakeLists.txt.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
CMakeLists.txt.user*

21
CMakeLists.txt Normal file
View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.5)
project(colohalopp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(colohalopp STATIC
include/colorama/ansi.hpp
include/colorama/initialise.hpp
colorama.cpp
include/halo.hpp halo.cpp
include/spinners.hpp
include/log_symbols.hpp
)
target_include_directories(colohalopp PUBLIC include/)
target_link_libraries(colohalopp PRIVATE fmt)
add_executable(example example.cpp)
target_link_libraries(example PRIVATE colohalopp)

14
LICENSE.txt Normal file
View file

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# ColoHalo++
[Colorama](https://pypi.org/project/colorama/) and [Halo](https://pypi.org/project/halo/) loosely ported to C++

23
colorama.cpp Normal file
View file

@ -0,0 +1,23 @@
#include "colorama/initialise.hpp"
#ifdef _WIN32
#include <windows.h>
#endif
namespace colorama {
// Code ported from initialise.py
#ifdef _WIN32
void just_fix_windows_console(bool revert) {
HANDLE handleOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode;
GetConsoleMode(handleOut, &consoleMode);
static initial_consoleMode = consoleMode;
if (!revert)
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
else
consoleMode &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(handleOut, consoleMode);
}
#endif
}

22
example.cpp Normal file
View file

@ -0,0 +1,22 @@
#include <spinners.hpp>
#include <colorama/ansi.hpp>
#include <halo.hpp>
#include <iostream>
#include <chrono>
#include <thread>
int main() {
using namespace std::chrono_literals;
Halo halo({.text="Doing something very complicated...", .spinner=spinners::dots, .text_color=colorama::Fore::GREEN});
halo.start();
std::this_thread::sleep_for(5s);
halo.success("It worked!");
halo.settings.text = "Doing something error-prone...";
halo.settings.text_color = colorama::Fore::YELLOW;
std::this_thread::sleep_for(2s);
halo.stop();
halo.error("Oops!", colorama::Fore::RED);
}

113
halo.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "halo.hpp"
#include "colorama.hpp"
#include <iostream>
#include <chrono>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#endif
void Halo::set_cursor_hidden(bool hidden) {
#ifdef _WIN32
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(out, &cursorInfo);
cursorInfo.bVisible = !hidden;
SetConsoleCursorInfo(out, &cursorInfo);
#else
std::cout << "\033[?25" << (hidden?'l':'h') << std::flush;
#endif
}
void Halo::show_spinner_frame(std::string_view frame) {
// Set color
*settings.stream << settings.color;
// Show frame
*settings.stream << frame;
}
void Halo::show_spinner() {
// Increment frame index
assert(!settings.spinner.frames[0].empty());
if (++frame_idx > settings.spinner.get_frame_count()) {
frame_idx = 0;
}
// Show frame
show_spinner_frame(settings.spinner.frames[frame_idx]);
}
void Halo::show_custom_text(std::string_view text) {
// Set color
*settings.stream << settings.text_color;
// Calculate text length difference
ssize_t diff = last_text_len - text.size();
// Print text (TODO: Add animations)
*settings.stream << text;
last_text_len = text.size();
// Make sure to overwrite old text
if (diff > 0) {
std::string placeholder;
placeholder.resize(diff, ' ');
*settings.stream << placeholder << colorama::Cursor::BACK(diff);
}
}
void Halo::show_text() {
show_custom_text(settings.text);
}
void Halo::show() {
// Don't run if disabled
if (!enabled) return;
// Show spinner and text, in the correct order
*settings.stream << std::flush;
if (settings.placement == left) {
show_spinner();
*settings.stream << ' ';
}
show_text();
if (settings.placement == right) {
*settings.stream << ' ';
show_spinner();
}
*settings.stream << '\r' << std::flush;
// Reset color
*settings.stream << colorama::Fore::RESET;
}
void Halo::status(std::string_view frame, std::string_view message, colorama::Color text_color) {
// Set custom color
auto color_bak = settings.text_color;
if (text_color != neutral_color) {
settings.text_color = text_color;
}
// Show given spinner frame and text, in the correct order
*settings.stream << std::flush;
if (settings.placement == left) {
show_spinner_frame(frame);
*settings.stream << ' ';
}
show_custom_text(message);
if (settings.placement == right) {
*settings.stream << ' ';
show_spinner_frame(frame);
}
*settings.stream << std::endl;
// Reset color
*settings.stream << colorama::Fore::RESET;
// Restore color
settings.text_color = color_bak;
}
void Halo::run_thread() {
auto stop_req = wants_stop;
set_cursor_hidden(true);
while (!*stop_req) {
show();
std::this_thread::sleep_for(std::chrono::milliseconds(settings.interval));
}
set_cursor_hidden(false);
}

2
include/colorama.hpp Normal file
View file

@ -0,0 +1,2 @@
#include "colorama/ansi.hpp"
#include "colorama/initialise.hpp"

143
include/colorama/ansi.hpp Normal file
View file

@ -0,0 +1,143 @@
/*
* This library generates ANSI character codes to printing colors to terminals.
* See: http://en.wikipedia.org/wiki/ANSI_escape_code
*/
#pragma once
#include <string>
#include <string_view>
#include <array>
#include <fmt/compile.h>
namespace colorama {
// Some boilerplate code
namespace detail {
constexpr size_t Color_maxlen = 16;
using ColorBase = std::array<char, Color_maxlen>;
}
struct Color : public detail::ColorBase {
using detail::ColorBase::ColorBase;
constexpr operator const char *() const {
return data();
}
constexpr operator std::string_view() const {
return data();
}
operator std::string() const {
return data();
}
};
// Code ported from ansi.py
constexpr auto CSI = "\033[",
OSC = "\033]",
BEL = "\a";
constexpr Color code_to_chars(unsigned code) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}m"), CSI, code);
return fres;
}
constexpr Color set_title(std::string_view title) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}2;{}{}"), OSC, title, BEL);
return fres;
}
constexpr Color clean_screen(unsigned mode = 2) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}J"), CSI, mode);
return fres;
}
constexpr Color clear_line(unsigned mode = 2) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}K"), CSI, mode);
return fres;
}
namespace Cursor {
constexpr Color UP(unsigned n = 1) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}A"), CSI, n);
return fres;
}
constexpr Color DOWN(unsigned n = 1) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}B"), CSI, n);
return fres;
}
constexpr Color FORWARD(unsigned n = 1) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}C"), CSI, n);
return fres;
}
constexpr Color BACK(unsigned n = 1) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{}D"), CSI, n);
return fres;
}
constexpr Color POS(unsigned x = 1, unsigned y = 1) {
auto fres = Color();
fmt::format_to(fres.data(), FMT_COMPILE("{}{};{}H"), CSI, y, x);
return fres;
}
}
#define COLORAMA_CODE(name, code) constexpr Color name = code_to_chars(code)
namespace Fore {
COLORAMA_CODE(BLACK, 30);
COLORAMA_CODE(RED, 31);
COLORAMA_CODE(GREEN, 32);
COLORAMA_CODE(YELLOW, 33);
COLORAMA_CODE(BLUE, 34);
COLORAMA_CODE(MAGENTA, 35);
COLORAMA_CODE(CYAN, 36);
COLORAMA_CODE(WHITE, 37);
COLORAMA_CODE(RESET, 39);
COLORAMA_CODE(LIGHTBLACK_EX, 90);
COLORAMA_CODE(LIGHTRED_EX, 91);
COLORAMA_CODE(LIGHTGREEN_EX, 92);
COLORAMA_CODE(LIGHTYELLOW_EX, 93);
COLORAMA_CODE(LIGHTBLUE_EX, 94);
COLORAMA_CODE(LIGHTMAGENTA_EX, 95);
COLORAMA_CODE(LIGHTCYAN_EX, 96);
COLORAMA_CODE(LIGHTWHITE_EX, 97);
}
namespace Back {
COLORAMA_CODE(BLACK, 40);
COLORAMA_CODE(RED, 41);
COLORAMA_CODE(GREEN, 42);
COLORAMA_CODE(YELLOW, 43);
COLORAMA_CODE(BLUE, 44);
COLORAMA_CODE(MAGENTA, 45);
COLORAMA_CODE(CYAN, 46);
COLORAMA_CODE(WHITE, 47);
COLORAMA_CODE(RESET, 49);
COLORAMA_CODE(LIGHTBLACK_EX, 100);
COLORAMA_CODE(LIGHTRED_EX, 101);
COLORAMA_CODE(LIGHTGREEN_EX, 102);
COLORAMA_CODE(LIGHTYELLOW_EX, 103);
COLORAMA_CODE(LIGHTBLUE_EX, 104);
COLORAMA_CODE(LIGHTMAGENTA_EX, 105);
COLORAMA_CODE(LIGHTCYAN_EX, 106);
COLORAMA_CODE(LIGHTWHITE_EX, 107);
}
namespace Style {
COLORAMA_CODE(BRIGHT, 1);
COLORAMA_CODE(DIM, 2);
COLORAMA_CODE(NORMAL, 22);
COLORAMA_CODE(RESET_ALL, 0);
}
}

View file

@ -0,0 +1,13 @@
namespace colorama {
void just_fix_windows_console(bool = false)
#ifdef _WIN32
;
#else
{}
#endif
inline void init() {just_fix_windows_console();}
inline void deinit() {just_fix_windows_console(true);}
[[deprecated("Calling reinit() is never required ")]]
inline void reinit() {deinit(); init();}
}

97
include/halo.hpp Normal file
View file

@ -0,0 +1,97 @@
#pragma once
#include "spinners.hpp"
#include "log_symbols.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <memory>
#include <functional>
#include <ctime>
#include <cassert>
#include <colorama/ansi.hpp>
class Halo {
static constexpr time_t no_interval_magic = 0xdead5610;
static constexpr colorama::Color neutral_color = colorama::Fore::RESET;
std::unique_ptr<std::thread> spinner_thread;
unsigned frame_idx;
size_t last_text_len = 0;
std::shared_ptr<bool> wants_stop;
bool enabled = true;
public:
enum Side {
left,
right
};
struct Settings {
std::string text;
spinners::Spinner spinner = spinners::dots;
Side placement = left;
colorama::Color color = colorama::Fore::CYAN;
colorama::Color text_color = colorama::Fore::RESET;
uint32_t interval = no_interval_magic;
std::ostream *stream = &std::clog;
} settings;
private:
static void set_cursor_hidden(bool);
void run_thread();
void show_spinner_frame(std::string_view);
void show_spinner();
void show_custom_text(std::string_view);
void show_text();
void show();
public:
Halo(const Settings& settings_) : settings(settings_) {
reset();
wants_stop = std::make_shared<bool>(false);
if (settings.interval == 0xdead5610) {
settings.interval = settings.spinner.interval;
}
}
~Halo() {
if (is_running()) {
stop();
}
}
void pause(bool paused = true) {
enabled = !paused;
set_cursor_hidden(is_running()?enabled:false);
}
void resume() {
pause(false);
}
void start() {
*wants_stop = false;
spinner_thread = std::make_unique<std::thread>([this] () {run_thread();});
}
void stop() {
assert(wants_stop);
*wants_stop = true;
spinner_thread->join();
spinner_thread.reset();
}
bool is_running() const {
return spinner_thread != nullptr;
}
void reset() {
frame_idx = settings.spinner.get_frame_count()-1;
}
void status(std::string_view, std::string_view message, colorama::Color text_color = neutral_color);
void info(std::string_view message, colorama::Color text_color = neutral_color) {status(LogSymbols::INFO, message, text_color);}
void success(std::string_view message, colorama::Color text_color = neutral_color) {status(LogSymbols::SUCCESS, message, text_color);}
void warning(std::string_view message, colorama::Color text_color = neutral_color) {status(LogSymbols::WARNING, message, text_color);}
void error(std::string_view message, colorama::Color text_color = neutral_color) {status(LogSymbols::ERROR, message, text_color);}
};

18
include/log_symbols.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <string_view>
namespace LogSymbols {
#ifdef _WIN32
constexpr std::string_view INFO = "",
SUCCESS = "v",
WARNING = "!!",
ERROR = "×";
#else
constexpr std::string_view INFO = "",
SUCCESS = "",
WARNING = "",
ERROR = "";
#endif
}

79
include/spinners.hpp Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include <string_view>
#include <array>
#include <ctime>
namespace spinners {
struct Spinner {
time_t interval; // In milliseconds
std::array<std::string_view, 32> frames; // Max 32 frames
constexpr size_t get_frame_count() const {
size_t counter = 0;
while (counter < frames.size() && !frames[counter].empty()) counter++;
return counter-1;
}
};
constexpr Spinner dots {80, {"\u280b", "\u2819", "\u2839", "\u2838", "\u283c", "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"}};
constexpr Spinner dots2 {80, {"\u28fe", "\u28fd", "\u28fb", "\u28bf", "\u287f", "\u28df", "\u28ef", "\u28f7"}};
constexpr Spinner dots3 {80, {"\u280b", "\u2819", "\u281a", "\u281e", "\u2816", "\u2826", "\u2834", "\u2832", "\u2833", "\u2813"}};
constexpr Spinner dots4 {80, {"\u2804", "\u2806", "\u2807", "\u280b", "\u2819", "\u2838", "\u2830", "\u2820", "\u2830", "\u2838", "\u2819", "\u280b", "\u2807", "\u2806"}};
constexpr Spinner dots5 {80, {"\u280b", "\u2819", "\u281a", "\u2812", "\u2802", "\u2802", "\u2812", "\u2832", "\u2834", "\u2826", "\u2816", "\u2812", "\u2810", "\u2810", "\u2812", "\u2813", "\u280b"}};
constexpr Spinner dots6 {80, {"\u2801", "\u2809", "\u2819", "\u281a", "\u2812", "\u2802", "\u2802", "\u2812", "\u2832", "\u2834", "\u2824", "\u2804", "\u2804", "\u2824", "\u2834", "\u2832", "\u2812", "\u2802", "\u2802", "\u2812", "\u281a", "\u2819", "\u2809", "\u2801"}};
constexpr Spinner dots7 {80, {"\u2808", "\u2809", "\u280b", "\u2813", "\u2812", "\u2810", "\u2810", "\u2812", "\u2816", "\u2826", "\u2824", "\u2820", "\u2820", "\u2824", "\u2826", "\u2816", "\u2812", "\u2810", "\u2810", "\u2812", "\u2813", "\u280b", "\u2809", "\u2808"}};
constexpr Spinner dots8 {80, {"\u2801", "\u2801", "\u2809", "\u2819", "\u281a", "\u2812", "\u2802", "\u2802", "\u2812", "\u2832", "\u2834", "\u2824", "\u2804", "\u2804", "\u2824", "\u2820", "\u2820", "\u2824", "\u2826", "\u2816", "\u2812", "\u2810", "\u2810", "\u2812", "\u2813", "\u280b", "\u2809", "\u2808", "\u2808"}};
constexpr Spinner dots9 {80, {"\u28b9", "\u28ba", "\u28bc", "\u28f8", "\u28c7", "\u2867", "\u2857", "\u284f"}};
constexpr Spinner dots10 {80, {"\u2884", "\u2882", "\u2881", "\u2841", "\u2848", "\u2850", "\u2860"}};
constexpr Spinner dots11 {100, {"\u2801", "\u2802", "\u2804", "\u2840", "\u2880", "\u2820", "\u2810", "\u2808"}};
constexpr Spinner line {130, {"-", "\\", "|", "/"}};
constexpr Spinner line2 {100, {"\u2802", "-", "\u2013", "\u2014", "\u2013", "-"}};
constexpr Spinner pipe {100, {"\u2524", "\u2518", "\u2534", "\u2514", "\u251c", "\u250c", "\u252c", "\u2510"}};
constexpr Spinner simpleDots {400, {". ", ".. ", "...", " "}};
constexpr Spinner simpleDotsScrolling {200, {". ", ".. ", "...", " ..", " .", " "}};
constexpr Spinner star {70, {"\u2736", "\u2738", "\u2739", "\u273a", "\u2739", "\u2737"}};
constexpr Spinner star2 {80, {"+", "x", "*"}};
constexpr Spinner flip {70, {"_", "_", "_", "-", "`", "`", "'", "\u00b4", "-", "_", "_", "_"}};
constexpr Spinner hamburger {100, {"\u2631", "\u2632", "\u2634"}};
constexpr Spinner growVertical {120, {"\u2581", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2586", "\u2585", "\u2584", "\u2583"}};
constexpr Spinner growHorizontal {120, {"\u258f", "\u258e", "\u258d", "\u258c", "\u258b", "\u258a", "\u2589", "\u258a", "\u258b", "\u258c", "\u258d", "\u258e"}};
constexpr Spinner balloon {140, {" ", ".", "o", "O", "@", "*", " "}};
constexpr Spinner balloon2 {120, {".", "o", "O", "\u00b0", "O", "o", "."}};
constexpr Spinner noise {100, {"\u2593", "\u2592", "\u2591"}};
constexpr Spinner bounce {120, {"\u2801", "\u2802", "\u2804", "\u2802"}};
constexpr Spinner boxBounce {120, {"\u2596", "\u2598", "\u259d", "\u2597"}};
constexpr Spinner boxBounce2 {100, {"\u258c", "\u2580", "\u2590", "\u2584"}};
constexpr Spinner triangle {50, {"\u25e2", "\u25e3", "\u25e4", "\u25e5"}};
constexpr Spinner arc {100, {"\u25dc", "\u25e0", "\u25dd", "\u25de", "\u25e1", "\u25df"}};
constexpr Spinner circle {120, {"\u25e1", "\u2299", "\u25e0"}};
constexpr Spinner squareCorners {180, {"\u25f0", "\u25f3", "\u25f2", "\u25f1"}};
constexpr Spinner circleQuarters {120, {"\u25f4", "\u25f7", "\u25f6", "\u25f5"}};
constexpr Spinner circleHalves {50, {"\u25d0", "\u25d3", "\u25d1", "\u25d2"}};
constexpr Spinner squish {100, {"\u256b", "\u256a"}};
constexpr Spinner toggle {250, {"\u22b6", "\u22b7"}};
constexpr Spinner toggle2 {80, {"\u25ab", "\u25aa"}};
constexpr Spinner toggle3 {120, {"\u25a1", "\u25a0"}};
constexpr Spinner toggle4 {100, {"\u25a0", "\u25a1", "\u25aa", "\u25ab"}};
constexpr Spinner toggle5 {100, {"\u25ae", "\u25af"}};
constexpr Spinner toggle6 {300, {"\u101d", "\u1040"}};
constexpr Spinner toggle7 {80, {"\u29be", "\u29bf"}};
constexpr Spinner toggle8 {100, {"\u25cd", "\u25cc"}};
constexpr Spinner toggle9 {100, {"\u25c9", "\u25ce"}};
constexpr Spinner toggle10 {100, {"\u3282", "\u3280", "\u3281"}};
constexpr Spinner toggle11 {50, {"\u29c7", "\u29c6"}};
constexpr Spinner toggle12 {120, {"\u2617", "\u2616"}};
constexpr Spinner toggle13 {80, {"=", "*", "-"}};
constexpr Spinner arrow {100, {"\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"}};
constexpr Spinner arrow2 {80, {"\u2b06\ufe0f ", "\u2197\ufe0f ", "\u27a1\ufe0f ", "\u2198\ufe0f ", "\u2b07\ufe0f ", "\u2199\ufe0f ", "\u2b05\ufe0f ", "\u2196\ufe0f "}};
constexpr Spinner arrow3 {120, {"\u25b9\u25b9\u25b9\u25b9\u25b9", "\u25b8\u25b9\u25b9\u25b9\u25b9", "\u25b9\u25b8\u25b9\u25b9\u25b9", "\u25b9\u25b9\u25b8\u25b9\u25b9", "\u25b9\u25b9\u25b9\u25b8\u25b9", "\u25b9\u25b9\u25b9\u25b9\u25b8"}};
constexpr Spinner bouncingBar {80, {"[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"}};
constexpr Spinner bouncingBall {80, {"( \u25cf )", "( \u25cf )", "( \u25cf )", "( \u25cf )", "( \u25cf)", "( \u25cf )", "( \u25cf )", "( \u25cf )", "( \u25cf )", "(\u25cf )"}};
constexpr Spinner pong {80, {"\u2590\u2802 \u258c", "\u2590\u2808 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2840 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2840 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c", "\u2590 \u2802\u258c", "\u2590 \u2820\u258c", "\u2590 \u2840\u258c", "\u2590 \u2820 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2840 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c", "\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c", "\u2590 \u2840 \u258c", "\u2590\u2820 \u258c"}};
constexpr Spinner shark {120, {"\u2590|\\____________\u258c", "\u2590_|\\___________\u258c", "\u2590__|\\__________\u258c", "\u2590___|\\_________\u258c", "\u2590____|\\________\u258c", "\u2590_____|\\_______\u258c", "\u2590______|\\______\u258c", "\u2590_______|\\_____\u258c", "\u2590________|\\____\u258c", "\u2590_________|\\___\u258c", "\u2590__________|\\__\u258c", "\u2590___________|\\_\u258c", "\u2590____________|\\\u258c", "\u2590____________/|\u258c", "\u2590___________/|_\u258c", "\u2590__________/|__\u258c", "\u2590_________/|___\u258c", "\u2590________/|____\u258c", "\u2590_______/|_____\u258c", "\u2590______/|______\u258c", "\u2590_____/|_______\u258c", "\u2590____/|________\u258c", "\u2590___/|_________\u258c", "\u2590__/|__________\u258c", "\u2590_/|___________\u258c", "\u2590/|____________\u258c"}};
constexpr Spinner dqpb {100, {"d", "q", "p", "b"}};
constexpr Spinner grenade {80, {"\u060c ", "\u2032 ", " \u00b4 ", " \u203e ", " \u2e0c", " \u2e0a", " |", " \u204e", " \u2055", " \u0df4 ", " \u2053", " ", " ", " "}};
constexpr Spinner point {125, {"\u2219\u2219\u2219", "\u25cf\u2219\u2219", "\u2219\u25cf\u2219", "\u2219\u2219\u25cf", "\u2219\u2219\u2219"}};
constexpr Spinner layer {150, {"-", "=", "\u2261"}};
}