1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
//! A simple character spinner for bars with no known size
use super::*;
use std::io;
/// A single character spinner with optional title that can be told to spin whenever it wants. It implements `Spinner` trait, and is the default spinner.
///
/// The spinner takes up a line, and renders it's spinner on the end of its title. Calling the `Spinner::bump()` function will cause the character sequence to advance.
///
/// # Usage
/// To use the spinner you can provide it a `Wheel` (see [[wheel]] module), or it implements the `Default` trait, creating a traditional looking spinner (`|/-\`)
///
/// ```rust
/// # use termprogress::prelude::*;
/// let mut spin = Spin::default(); //Default new spinner without a title.
/// ```
///
/// # How it looks
/// It renders in the terminal like:
/// `This is a spinner /`
///
/// # Thread `Sync`safety
/// This type is safely `Sync` (where `T` is), the behaviour is defined to prevent overlapping writes to `T`.
/// Though it is *advised* to not render a `Spin` from more than a single thread, you still safely can.
///
/// A display operation on one thread will cause any other threads attempting on to silently and safely abort their display attempt before anything is written to output.
pub struct Spin<T: ?Sized = DefaultOutputDevice>/*<T: ?Sized = DefaultOutputDevice>*/ //TODO: <- implement same as `Bar
{
title: String,
current: char,
chars: wheel::WheelIntoIter,
output: AtomicRefCell<T>,
}
impl Spin
{
/// Create a new spinner with title and wheel writing to `stdout`.
///
/// To give it the default wheel, you can pass `whl` `Default::default()` to use the default one.
#[inline]
pub fn with_title_default(title: &str, whl: wheel::Wheel) -> Self
{
Self::with_title(create_default_output_device(), title, whl)
}
/// Create a new blank spinner with a wheel writing to `stdout`.
///
/// # Example
/// ```rust
/// # use termprogress::prelude::*;
/// Spin::new_default(Default::default()); // Create a spinner with the default wheel ('|/-\') that writes to stdout.
/// ```
#[inline]
pub fn new_default(whl: wheel::Wheel) -> Self
{
Self::new(create_default_output_device(), whl)
}
}
impl<T> Spin<T>
{
/// Return the backing write object
#[inline]
pub fn into_inner(self) -> T
{
self.output.into_inner()
}
}
impl<T: ?Sized> Spin<T>
{
/// Get a mutable reference to the inner object
#[inline]
pub fn inner_mut(&mut self) -> &mut T
{
self.output.get_mut()
}
/// Get a shared reference to the inner object
///
/// # Returns
/// `None` is returned if a display operation is currently in progress on another thread.
///
/// ## Operation suspension
/// As long as the returned reference lives, **all** display operations will fail silently, on this thread and any other. This method should be avoided in favour of `&*inner_mut()` when exclusive ownership is avaliable.
#[inline]
pub fn inner(&self) -> Option<atomic_refcell::AtomicRef<'_, T>>
{
self.output.try_borrow().ok()
}
}
impl<T: io::Write> Spin<T>
{
/// Create a new spinner with title and wheel writing to `output`.
///
/// To give it the default wheel, you can pass `whl` `Default::default()` to use the default one.
pub fn with_title(output: T, title: &str, whl: wheel::Wheel) -> Self
{
let mut chars = whl.into_iter();
let current = chars.next().unwrap();
Self {
title: title.to_string(),
current,
chars,
output: AtomicRefCell::new(output)
}
}
/// Create a new blank spinner with a wheel writing to `output`.
///
/// # Example
/// ```rust
/// # use termprogress::prelude::*;
/// Spin::new(std::io::stdout(), Default::default()); // Create a spinner with the default wheel ('|/-\') that writes to stdout.
/// ```
pub fn new(output: T, whl: wheel::Wheel) -> Self
{
let mut chars = whl.into_iter();
let current = chars.next().unwrap();
Self {
title: String::new(),
current,
chars,
output: output.into()
}
}
/// Consume the spinner and complete it. Removes the spin character.
pub fn complete(self) -> io::Result<()> {
let mut output = self.output.into_inner();
writeln!(&mut output, "{} ", (8u8 as char))
}
/// Consume the spinner and complete it with a message. Removes the spin character and then prints the message.
pub fn complete_with(self, msg: &str) -> io::Result<()>
{
let mut output = self.output.into_inner();
writeln!(&mut output, "{}{}", (8u8 as char), msg)
}
}
impl Default for Spin
{
fn default() -> Self
{
Self {
title: String::new(),
chars: wheel::Wheel::default().into_iter(),
current: '|',
output: AtomicRefCell::new(create_default_output_device())
}
}
}
impl<T: ?Sized + io::Write> Display for Spin<T>
{
fn refresh(&self)
{
let Ok(mut output) = self.output.try_borrow_mut() else { return };
//TODO: What to do about I/O errors?
let _ = write!(&mut output, "\r{} {}", self.title, self.current)
.and_then(move |_| flush!(? output));
}
fn blank(&self)
{
let Ok(mut output) = self.output.try_borrow_mut() else { return };
//TODO: What to do about I/O errors?
let _ = output.write_all(b"\r")
.and_then(|_|
stackalloc::stackalloc(self.title.chars().count(), b' ',
|spaces| output.write_all(spaces)))
.and_then(|_| write!(&mut output, " \r"))
.and_then(move |_| flush!(? output));
}
fn get_title(&self) -> &str
{
&self.title[..]
}
fn set_title(&mut self, from: &str)
{
//self.blank(), with exclusive access
let mut output = self.output.get_mut();
let size = self.title.chars().count();
let _ = output.write_all(b"\r")
.and_then(|_|
stackalloc::stackalloc(size, b' ',
|spaces| output.write_all(spaces)))
.and_then(|_| write!(&mut output, " \r"))
.and_then(|_| flush!(? output));
self.title = from.to_string();
//self.refresh(), with exclusive access
let _ = write!(&mut output, "\r{} {}", self.title, self.current)
.and_then(move |_| flush!(? output));
}
fn update_dimensions(&mut self, _:usize){}
fn println(&self, string: &str)
{
self.blank();
if let Ok(mut output) = self.output.try_borrow_mut() {
//TODO: What to do about I/O errors?
let _ = writeln!(&mut output, "{}", string);
drop(output)
} else {
return;
}
self.refresh();
}
}
impl<T: ?Sized + io::Write> Spinner for Spin<T>
{
fn bump(&mut self)
{
self.current = self.chars.next().unwrap();
let mut output = self.output.get_mut();
let _ = write!(&mut output, "\r{} {}", self.title, self.current)
.and_then(move |_| flush!(? output));
}
}
impl<T: io::Write> WithTitle for Spin<T>
{
#[inline]
fn with_title(self, t: impl AsRef<str>) -> Self
{
Self {
title: t.as_ref().to_owned(),
..self
}
}
#[inline]
fn add_title(&mut self, t: impl AsRef<str>)
{
self.title = t.as_ref().to_owned();
// Self::with_title(t.as_ref(), Default::default())
}
#[inline] fn update(&mut self){}
#[inline] fn complete(self)
{
//TODO: What to do about I/O errors?
let _ = Spin::complete(self);
}
}