pub struct Model {
pub interval: Duration,
/* private fields */
}Expand description
A high-precision stopwatch for measuring elapsed time in Bubble Tea applications.
The Model represents a single stopwatch instance that can be started, stopped,
paused, and reset through Bubble Tea’s message system. Each stopwatch maintains
its own elapsed time, running state, and unique identifier for use in
multi-stopwatch applications.
§Core Functionality
- Timing: Accumulates elapsed time with configurable tick intervals
- State Management: Tracks running/stopped state independently
- Identity: Each instance has a unique ID for message routing
- Precision: Uses Rust’s
Durationfor sub-second accuracy
§Examples
Basic usage:
use bubbletea_widgets::stopwatch::{new, Model};
use std::time::Duration;
let mut stopwatch = new();
assert_eq!(stopwatch.elapsed(), Duration::ZERO);
assert!(!stopwatch.running());Custom interval for high-precision timing:
use bubbletea_widgets::stopwatch::new_with_interval;
use std::time::Duration;
let high_precision = new_with_interval(Duration::from_millis(10));
assert_eq!(high_precision.interval, Duration::from_millis(10));Integration with Bubble Tea:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{Model as BubbleTeaModel, Cmd, Msg};
use std::time::Duration;
struct TimerApp {
stopwatch: StopwatchModel,
}
impl BubbleTeaModel for TimerApp {
fn init() -> (Self, Option<Cmd>) {
let stopwatch = new();
let start_cmd = stopwatch.start();
(TimerApp { stopwatch }, Some(start_cmd))
}
fn update(&mut self, msg: Msg) -> Option<Cmd> {
self.stopwatch.update(msg)
}
fn view(&self) -> String {
format!("Timer: {} ({})",
self.stopwatch.view(),
if self.stopwatch.running() { "running" } else { "stopped" }
)
}
}§Thread Safety
Model is Clone and can be shared across threads, but individual instances
should be updated from a single thread to maintain timing accuracy.
Fields§
§interval: DurationTime interval between ticks when running.
This determines how frequently the elapsed time is updated and how precise the timing measurements will be. Shorter intervals provide higher precision but consume more CPU resources.
§Default
New stopwatches default to 1-second intervals (Duration::from_secs(1))
which is suitable for most applications displaying elapsed time to users.
§Precision Trade-offs
Duration::from_millis(10): High precision, higher CPU usageDuration::from_secs(1): Good balance for UI displayDuration::from_secs(5): Low precision, minimal CPU usage
Implementations§
Source§impl Model
impl Model
Sourcepub fn id(&self) -> i64
pub fn id(&self) -> i64
Returns the unique identifier of this stopwatch instance.
Each stopwatch has a globally unique ID that’s used for message routing when multiple stopwatches are running in the same application. This ID is automatically assigned during construction and never changes.
§Returns
A unique i64 identifier for this stopwatch
§Examples
use bubbletea_widgets::stopwatch::new;
let stopwatch1 = new();
let stopwatch2 = new();
// Each stopwatch has a unique ID
assert_ne!(stopwatch1.id(), stopwatch2.id());
assert!(stopwatch1.id() > 0);
assert!(stopwatch2.id() > 0);Using ID for message filtering:
use bubbletea_widgets::stopwatch::{new, StartStopMsg};
use bubbletea_rs::Msg;
let stopwatch = new();
let my_id = stopwatch.id();
// Create a start command for this specific stopwatch
let start_cmd = stopwatch.start(); // Generates appropriate StartStopMsg§Thread Safety
The ID is assigned using atomic operations and is safe to access from multiple threads.
Sourcepub fn running(&self) -> bool
pub fn running(&self) -> bool
Returns whether the stopwatch is currently running and accumulating time.
A running stopwatch actively updates its elapsed time at each tick interval. A stopped stopwatch preserves its current elapsed time without further updates.
§Returns
true if the stopwatch is running, false if stopped/paused
§Examples
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
assert!(!stopwatch.running()); // Initially stoppedChecking state after operations:
use bubbletea_widgets::stopwatch::{new, StartStopMsg};
let mut stopwatch = new();
assert!(!stopwatch.running());
// After processing a start command, it would be running
let start_cmd = stopwatch.start(); // Generates StartStopMsg internally
// Process the command through your Bubble Tea app to set running = true§State Persistence
The running state persists across:
- Clone operations
- Tick message processing
- Reset operations (reset preserves running state)
Only StartStopMsg messages change the running state.
Sourcepub fn init(&self) -> Cmd
pub fn init(&self) -> Cmd
Initializes the stopwatch by generating a start command.
This method is typically called when setting up the stopwatch in a Bubble Tea application’s initialization phase. It generates a command that will start the stopwatch when processed by the update loop.
§Returns
A Cmd that will start the stopwatch when executed
§Examples
Using in a Bubble Tea application:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{Model as BubbleTeaModel, Cmd, Msg};
struct App {
stopwatch: StopwatchModel,
}
impl BubbleTeaModel for App {
fn init() -> (Self, Option<Cmd>) {
let stopwatch = new();
let init_cmd = stopwatch.init(); // Generates start command
(App { stopwatch }, Some(init_cmd))
}
}Manual initialization:
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
let init_cmd = stopwatch.init();
// Execute this command in your Bubble Tea runtime§Note
This method is equivalent to calling start() and is provided for
consistency with the Bubble Tea component lifecycle.
Sourcepub fn start(&self) -> Cmd
pub fn start(&self) -> Cmd
Generates a command to start the stopwatch.
Creates a command that, when processed, will start the stopwatch and begin accumulating elapsed time. If the stopwatch is already running, this command has no additional effect.
§Returns
A Cmd that will start the stopwatch when executed by the Bubble Tea runtime
§Examples
Basic start operation:
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
let start_cmd = stopwatch.start();
// Execute this command in your update loop to start timingIntegration with application logic:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{KeyMsg, Cmd, Msg};
use crossterm::event::{KeyCode, KeyModifiers};
fn handle_keypress(stopwatch: &StopwatchModel, key: KeyMsg) -> Option<Cmd> {
match key.key {
KeyCode::Char('s') => {
if key.modifiers.is_empty() {
Some(stopwatch.start()) // Start on 's' key
} else {
None
}
}
_ => None,
}
}§State Transition
- Stopped → Running: Begins accumulating elapsed time
- Running → Running: No change (idempotent)
§Message Flow
This method generates a StartStopMsg with running: true that will be
processed by the update() method to change the stopwatch state.
Sourcepub fn stop(&self) -> Cmd
pub fn stop(&self) -> Cmd
Generates a command to stop/pause the stopwatch.
Creates a command that, when processed, will stop the stopwatch and pause
elapsed time accumulation. The current elapsed time is preserved and can
be resumed later with start(). If the stopwatch is already stopped,
this command has no additional effect.
§Returns
A Cmd that will stop the stopwatch when executed by the Bubble Tea runtime
§Examples
Basic stop operation:
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
let stop_cmd = stopwatch.stop();
// Execute this command to pause timingStop-watch pattern:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{KeyMsg, Cmd};
use crossterm::event::KeyCode;
fn handle_spacebar(stopwatch: &StopwatchModel) -> Cmd {
if stopwatch.running() {
stopwatch.stop() // Pause if running
} else {
stopwatch.start() // Resume if stopped
}
}§State Transition
- Running → Stopped: Pauses elapsed time accumulation
- Stopped → Stopped: No change (idempotent)
§Time Preservation
Stopping a stopwatch preserves the current elapsed time:
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
// Imagine stopwatch has been running and shows some elapsed time
let stopwatch = new();
// let elapsed_before_stop = stopwatch.elapsed();
// After processing stop command:
// assert_eq!(stopwatch.elapsed(), elapsed_before_stop); // Time preserved
// assert!(!stopwatch.running()); // But no longer accumulatingSourcepub fn toggle(&self) -> Cmd
pub fn toggle(&self) -> Cmd
Generates a command to toggle the stopwatch’s running state.
This is a convenience method that starts the stopwatch if it’s currently stopped, or stops it if it’s currently running. Useful for implementing play/pause functionality with a single key or button.
§Returns
A Cmd that will toggle the stopwatch state when executed:
- If running: generates a stop command
- If stopped: generates a start command
§Examples
Basic toggle functionality:
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
assert!(!stopwatch.running());
let toggle_cmd = stopwatch.toggle(); // Will generate start commandImplementing spacebar toggle:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{KeyMsg, Cmd};
use crossterm::event::{KeyCode, KeyModifiers};
fn handle_key(stopwatch: &StopwatchModel, key: KeyMsg) -> Option<Cmd> {
match key.key {
KeyCode::Char(' ') if key.modifiers == KeyModifiers::NONE => {
Some(stopwatch.toggle()) // Spacebar toggles play/pause
}
_ => None,
}
}§State Transitions
- Stopped → Running: Equivalent to
start() - Running → Stopped: Equivalent to
stop()
§Equivalent Implementation
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
let toggle_cmd = if stopwatch.running() {
stopwatch.stop()
} else {
stopwatch.start()
};Sourcepub fn reset(&self) -> Cmd
pub fn reset(&self) -> Cmd
Generates a command to reset the stopwatch’s elapsed time to zero.
Creates a command that, when processed, will clear the accumulated elapsed time while preserving the running state. A running stopwatch will continue timing from zero, while a stopped stopwatch will remain stopped with zero elapsed time.
§Returns
A Cmd that will reset the elapsed time when executed by the Bubble Tea runtime
§Examples
Basic reset operation:
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
let reset_cmd = stopwatch.reset();
// Execute this command to clear elapsed timeReset with state preservation:
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
// Imagine a running stopwatch with accumulated time
let stopwatch = new();
let was_running = stopwatch.running();
let reset_cmd = stopwatch.reset();
// After processing reset command:
// assert_eq!(stopwatch.elapsed(), Duration::ZERO); // Time cleared
// assert_eq!(stopwatch.running(), was_running); // State preservedImplementing a reset button:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{KeyMsg, Cmd};
use crossterm::event::{KeyCode, KeyModifiers};
fn handle_key(stopwatch: &StopwatchModel, key: KeyMsg) -> Option<Cmd> {
match key.key {
KeyCode::Char('r') if key.modifiers == KeyModifiers::NONE => {
Some(stopwatch.reset()) // 'r' key resets timer
}
_ => None,
}
}§Behavior Details
- Elapsed time: Set to
Duration::ZERO - Running state: Unchanged (preserved)
- Internal timing: Reset for accurate subsequent measurements
- ID and interval: Unchanged
§Use Cases
- Lap timing (reset while continuing)
- Error recovery (clear invalid measurements)
- User-initiated restart
- Preparation for new timing session
Sourcepub fn update(&mut self, msg: Msg) -> Option<Cmd>
pub fn update(&mut self, msg: Msg) -> Option<Cmd>
Processes messages and updates the stopwatch state.
This method handles all incoming messages for the stopwatch, updating its
internal state and scheduling follow-up commands as needed. It processes
three types of messages: StartStopMsg, ResetMsg, and TickMsg.
§Arguments
msg- The message to process, typically from the Bubble Tea runtime
§Returns
Some(Cmd)if a follow-up command should be executedNoneif no further action is needed
§Message Types
§StartStopMsg
Changes the running state and schedules the next tick if starting.
§ResetMsg
Clears elapsed time to zero without affecting running state.
§TickMsg
Increments elapsed time and schedules the next tick if running.
§Examples
Basic usage in a Bubble Tea application:
use bubbletea_widgets::stopwatch::new;
let mut stopwatch = new();
// Start the stopwatch using the public API
let start_cmd = stopwatch.start();
// In a real app, you'd send this command through bubbletea
assert!(!stopwatch.running()); // Initially not runningHandling multiple message types:
use bubbletea_widgets::stopwatch::{new, ResetMsg};
use std::time::Duration;
let mut stopwatch = new();
// Start the stopwatch using the public API
let start_cmd = stopwatch.start();
// Reset to zero
let reset = ResetMsg { id: stopwatch.id() };
stopwatch.update(Box::new(reset));
assert_eq!(stopwatch.elapsed(), Duration::ZERO);§Message Filtering
Messages are filtered by ID to ensure they’re intended for this stopwatch:
use bubbletea_widgets::stopwatch::new;
let mut stopwatch = new();
// Messages with wrong IDs don't affect state
assert!(!stopwatch.running()); // Initially not running§Thread Safety
This method should be called from a single thread to maintain timing accuracy, though the stopwatch can be cloned and used across threads.
Sourcepub fn view(&self) -> String
pub fn view(&self) -> String
Returns a human-readable string representation of the elapsed time.
Formats the accumulated elapsed time using Go-compatible duration formatting. The output format adapts to the magnitude of the elapsed time for optimal readability across different time scales.
§Returns
A formatted string representing the elapsed time
§Format Examples
"0s"for zero duration"150ms"for sub-second durations"2.5s"for fractional seconds"45s"for whole seconds under a minute"2m30s"for minutes with seconds"5m"for whole minutes without seconds
§Examples
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
let stopwatch = new();
assert_eq!(stopwatch.view(), "0s"); // Initially zeroDisplaying elapsed time in UI:
use bubbletea_widgets::stopwatch::{new, Model as StopwatchModel};
use bubbletea_rs::{Model as BubbleTeaModel, Cmd, Msg};
struct TimerDisplay {
stopwatch: StopwatchModel,
}
impl BubbleTeaModel for TimerDisplay {
fn init() -> (Self, Option<Cmd>) {
(TimerDisplay { stopwatch: new() }, None)
}
fn view(&self) -> String {
format!("Timer: {}", self.stopwatch.view())
}
}§Performance
String formatting is optimized for common time ranges and involves minimal allocations. Suitable for real-time UI updates.
§Consistency
The format matches Go’s time.Duration.String() output for cross-language
compatibility in applications that interoperate with Go services.
Sourcepub fn elapsed(&self) -> Duration
pub fn elapsed(&self) -> Duration
Returns the total elapsed time as a Duration.
Provides access to the raw elapsed time for precise calculations, comparisons, or custom formatting. This is the accumulated time since the stopwatch was started, minus any time it was stopped.
§Returns
The total elapsed time as a Duration
§Examples
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
let stopwatch = new();
assert_eq!(stopwatch.elapsed(), Duration::ZERO); // Initially zeroPrecise timing calculations:
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
let stopwatch = new();
let elapsed = stopwatch.elapsed();
// Convert to different units
let millis = elapsed.as_millis();
let secs = elapsed.as_secs_f64();
let nanos = elapsed.as_nanos();Performance measurement:
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
let mut stopwatch = new();
// Start timing using the public API
let start_cmd = stopwatch.start();
// In a real app, you'd send this command through bubbletea
let elapsed = stopwatch.elapsed();
assert!(elapsed >= Duration::ZERO);
// Check if operation was fast enough
if elapsed < Duration::from_millis(100) {
println!("Operation completed quickly: {:?}", elapsed);
}Comparison with thresholds:
use bubbletea_widgets::stopwatch::new;
use std::time::Duration;
let stopwatch = new();
let elapsed = stopwatch.elapsed();
let threshold = Duration::from_secs(30);
if elapsed > threshold {
println!("Timer has exceeded 30 seconds");
}§Precision
The returned Duration has the same precision as Rust’s Duration type,
which supports nanosecond-level timing on most platforms.
Trait Implementations§
Source§impl Default for Model
impl Default for Model
Source§fn default() -> Self
fn default() -> Self
Creates a new stopwatch with default settings.
Equivalent to calling new(), providing a stopwatch with:
- Zero elapsed time
- Stopped state
- 1-second tick interval
- Unique ID
§Examples
use bubbletea_widgets::stopwatch::Model;
use std::time::Duration;
let stopwatch = Model::default();
assert_eq!(stopwatch.elapsed(), Duration::ZERO);
assert!(!stopwatch.running());
assert_eq!(stopwatch.interval, Duration::from_secs(1));Using with struct initialization:
use bubbletea_widgets::stopwatch::Model as StopwatchModel;
#[derive(Default)]
struct App {
timer: StopwatchModel,
}
let app = App::default(); // Uses stopwatch defaultSource§impl Model for Model
impl Model for Model
Source§fn init() -> (Self, Option<Cmd>)
fn init() -> (Self, Option<Cmd>)
Creates a new stopwatch and starts it automatically.
This implementation provides default behavior for using a stopwatch as a standalone Bubble Tea component. The returned stopwatch will begin timing immediately when the application starts.
§Returns
A tuple containing:
- A new stopwatch model with default settings
- A command to start the stopwatch
§Examples
Using stopwatch as a standalone component:
use bubbletea_widgets::stopwatch::new;
let stopwatch = new();
let _init_cmd = stopwatch.init();
assert!(!stopwatch.running()); // Will be running after cmd executionAuto Trait Implementations§
impl Freeze for Model
impl RefUnwindSafe for Model
impl Send for Model
impl Sync for Model
impl Unpin for Model
impl UnwindSafe for Model
Blanket Implementations§
Source§impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
Source§fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
Source§fn adapt_into(self) -> D
fn adapt_into(self) -> D
Source§impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
Source§fn arrays_from(colors: C) -> T
fn arrays_from(colors: C) -> T
Source§impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
Source§fn arrays_into(self) -> C
fn arrays_into(self) -> C
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
Source§type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
parameters when converting.Source§fn cam16_into_unclamped(
self,
parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>,
) -> T
fn cam16_into_unclamped( self, parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>, ) -> T
self into C, using the provided parameters.Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
Source§fn components_from(colors: C) -> T
fn components_from(colors: C) -> T
Source§impl<T> FromAngle<T> for T
impl<T> FromAngle<T> for T
Source§fn from_angle(angle: T) -> T
fn from_angle(angle: T) -> T
angle.Source§impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
Source§fn from_stimulus(other: U) -> T
fn from_stimulus(other: U) -> T
other into Self, while performing the appropriate scaling,
rounding and clamping.Source§impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
Source§fn into_angle(self) -> U
fn into_angle(self) -> U
T.Source§impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
Source§type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
parameters when converting.Source§fn into_cam16_unclamped(
self,
parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>,
) -> T
fn into_cam16_unclamped( self, parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>, ) -> T
self into C, using the provided parameters.Source§impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
Source§fn into_color(self) -> U
fn into_color(self) -> U
Source§impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
Source§fn into_color_unclamped(self) -> U
fn into_color_unclamped(self) -> U
Source§impl<T> IntoStimulus<T> for T
impl<T> IntoStimulus<T> for T
Source§fn into_stimulus(self) -> T
fn into_stimulus(self) -> T
self into T, while performing the appropriate scaling,
rounding and clamping.Source§impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
Source§type Error = <C as TryFromComponents<T>>::Error
type Error = <C as TryFromComponents<T>>::Error
try_into_colors fails to cast.Source§fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
Source§impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
Source§fn try_into_color(self) -> Result<U, OutOfBounds<U>>
fn try_into_color(self) -> Result<U, OutOfBounds<U>>
OutOfBounds error is returned which contains
the unclamped color. Read more