Timer
The timer example shows a simple timer built with Bubble Tea.
- Preview
- Code

pkgs/artisanal/example/tui/examples/timer/main.dart
/// Timer example ported from Bubble Tea.
library;
import 'package:artisanal/bubbles.dart'
as tui
hide
CodeBlockCommentDelimiters,
CodeLanguageProfile,
Column,
CommonKeyBindings,
EditBuffer,
EditHistoryCoalescePredicate,
EditHistoryController,
EditHistoryMarkerBuilder,
EditHistoryStateEquals,
EditorCoreConfig,
EditorState,
GraphemePredicate,
GraphemeReader,
Help,
KeyBinding,
KeyMap,
PasteMsg,
Row,
Spinner,
SpinnerModel,
SpinnerTickMsg,
Spinners,
Text,
TextCommandResult,
TextCursorCommandResult,
TextDecorationLayerKey,
TextDecorationRange,
TextDiagnosticRange,
TextDiagnosticSeverity,
TextDocument,
TextDocumentChange,
TextDocumentEditResult,
TextEditResult,
TextExtmark,
TextExtmarkOptions,
TextExtmarkPositionRange,
TextExtmarksController,
TextHighlightRange,
TextHitResult,
TextLineCommandResult,
TextLineDecoration,
TextLineStateCommandExtensions,
TextLineStateSnapshot,
TextOffsetStateCommandExtensions,
TextOffsetStateDocumentEditingExtensions,
TextOffsetStateSnapshot,
TextPasteChunk,
TextPasteChunkStep,
TextPasteController,
TextPasteMode,
TextPastePlan,
TextPasteReference,
TextPasteReferenceStore,
TextPasteSession,
TextPatternDiagnosticRule,
TextPosition,
TextPositionDiagnosticRange,
TextSelection,
TextSyntaxBuildResult,
TextSyntaxChangeWindow,
TextSyntaxDecorationPatch,
TextSyntaxLineWindow,
TextSyntaxProvider,
TextSyntaxSession,
TextSyntaxSnapshot,
TextView,
TextViewLine,
TextViewport,
TextVisualCursorPosition,
UndoCommandDecoder,
UndoCommandJournalEntry,
UndoManager,
UndoableCommand;
import 'package:artisanal/bubbles.dart'
hide
CodeBlockCommentDelimiters,
CodeLanguageProfile,
Column,
CommonKeyBindings,
EditBuffer,
EditHistoryCoalescePredicate,
EditHistoryController,
EditHistoryMarkerBuilder,
EditHistoryStateEquals,
EditorCoreConfig,
EditorState,
GraphemePredicate,
GraphemeReader,
Help,
KeyBinding,
KeyMap,
PasteMsg,
Row,
Spinner,
SpinnerModel,
SpinnerTickMsg,
Spinners,
Text,
TextCommandResult,
TextCursorCommandResult,
TextDecorationLayerKey,
TextDecorationRange,
TextDiagnosticRange,
TextDiagnosticSeverity,
TextDocument,
TextDocumentChange,
TextDocumentEditResult,
TextEditResult,
TextExtmark,
TextExtmarkOptions,
TextExtmarkPositionRange,
TextExtmarksController,
TextHighlightRange,
TextHitResult,
TextLineCommandResult,
TextLineDecoration,
TextLineStateCommandExtensions,
TextLineStateSnapshot,
TextOffsetStateCommandExtensions,
TextOffsetStateDocumentEditingExtensions,
TextOffsetStateSnapshot,
TextPasteChunk,
TextPasteChunkStep,
TextPasteController,
TextPasteMode,
TextPastePlan,
TextPasteReference,
TextPasteReferenceStore,
TextPasteSession,
TextPatternDiagnosticRule,
TextPosition,
TextPositionDiagnosticRange,
TextSelection,
TextSyntaxBuildResult,
TextSyntaxChangeWindow,
TextSyntaxDecorationPatch,
TextSyntaxLineWindow,
TextSyntaxProvider,
TextSyntaxSession,
TextSyntaxSnapshot,
TextView,
TextViewLine,
TextViewport,
TextVisualCursorPosition,
UndoCommandDecoder,
UndoCommandJournalEntry,
UndoManager,
UndoableCommand;
import 'package:artisanal/tui.dart' as tui;
const _timeout = Duration(seconds: 5);
class TimerKeys extends tui.KeyMap {
TimerKeys()
: start = tui.KeyBinding.withHelp(['s'], 's', 'start/stop'),
stop = tui.KeyBinding.withHelp(['s'], 's', 'start/stop'),
reset = tui.KeyBinding.withHelp(['r'], 'r', 'reset'),
quit = tui.KeyBinding.withHelp(['q', 'ctrl+c'], 'q', 'quit') {
shortHelp = [start, reset, quit];
fullHelp = [
[start, reset, quit],
];
}
final tui.KeyBinding start;
final tui.KeyBinding stop;
final tui.KeyBinding reset;
final tui.KeyBinding quit;
}
class TimerExampleModel implements tui.Model {
TimerExampleModel({
required this.timer,
required this.keys,
required this.help,
this.quitting = false,
});
factory TimerExampleModel.initial() {
final timer = tui.TimerModel(
timeout: _timeout,
interval: const Duration(milliseconds: 100),
);
return TimerExampleModel(
timer: timer,
keys: TimerKeys(),
help: tui.HelpModel(),
);
}
final tui.TimerModel timer;
final TimerKeys keys;
final tui.HelpModel help;
final bool quitting;
tui.Cmd? init() => timer.start();
(tui.Model, tui.Cmd?) update(tui.Msg msg) {
switch (msg) {
case tui.TimerTickMsg():
case tui.TimerStartStopMsg():
final (newTimerModel, cmd) = timer.update(msg);
final tm = newTimerModel;
final quitCmd = tm.timedOut ? tui.Cmd.quit() : null;
return (
copyWith(timer: tm, quitting: quitting || tm.timedOut),
_batch([cmd, quitCmd]),
);
case tui.KeyMsg(key: final key):
if (key.matches([keys.quit])) {
return (copyWith(quitting: true), tui.Cmd.quit());
}
if (key.matches([keys.reset])) {
final resetTimer = tui.TimerModel(
timeout: _timeout,
interval: timer.interval,
);
return (
copyWith(timer: resetTimer, quitting: false),
resetTimer.start(),
);
}
if (key.matches([keys.start, keys.stop])) {
return (this, timer.toggle());
}
}
return (this, null);
}
TimerExampleModel copyWith({
tui.TimerModel? timer,
TimerKeys? keys,
tui.HelpModel? help,
bool? quitting,
}) {
return TimerExampleModel(
timer: timer ?? this.timer,
keys: keys ?? this.keys,
help: help ?? this.help,
quitting: quitting ?? this.quitting,
);
}
String view() {
var s = timer.view();
if (timer.timedOut) {
s = 'All done!';
}
if (!quitting) {
s = 'Exiting in $s';
s += '\n${help.shortHelpView([keys.start, keys.reset, keys.quit])}';
}
return '$s\n';
}
}
tui.Cmd? _batch(List<tui.Cmd?> cmds) {
final filtered = cmds.whereType<tui.Cmd>().toList();
return filtered.isEmpty ? null : tui.Cmd.batch(filtered);
}
Future<void> main() async {
await tui.runProgram(
TimerExampleModel.initial(),
options: const tui.ProgramOptions(altScreen: false, hideCursor: false),
);
}
Run it
dart run pkgs/artisanal/example/tui/examples/timer/main.dart