This document provides a technical overview of Pomodorust's architecture for contributors and maintainers.
Pomodorust is a native desktop Pomodoro timer built in Rust using the egui/eframe GUI framework. It follows a modular architecture with clear separation of concerns.
┌─────────────────────────────────────────────────────────────┐
│ main.rs │
│ (Entry point) │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PomodoRustApp │
│ (app.rs) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Session │ │ Config │ │ Theme │ │ Database │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│TimerView │ │StatsView │ │ Settings │
└──────────┘ └──────────┘ └──────────┘
pomodorust/
├── src/
│ ├── main.rs # Application entry point
│ ├── lib.rs # Library exports
│ ├── app.rs # Main application struct (PomodoRustApp)
│ ├── error.rs # Error types and handling
│ │
│ ├── core/ # Business logic
│ │ ├── mod.rs # Module exports
│ │ ├── timer.rs # Timer state machine
│ │ ├── session.rs # Pomodoro session management
│ │ └── preset.rs # Timer presets (Classic, Short, Long)
│ │
│ ├── data/ # Persistence layer
│ │ ├── mod.rs # Module exports
│ │ ├── config.rs # TOML configuration
│ │ ├── database.rs # SQLite database
│ │ ├── statistics.rs # Statistics aggregation
│ │ └── export.rs # CSV/JSON export
│ │
│ ├── ui/ # User interface
│ │ ├── mod.rs # Module exports
│ │ ├── theme.rs # Design system (colors, fonts, spacing)
│ │ ├── timer_view.rs # Main timer screen
│ │ ├── stats.rs # Statistics dashboard
│ │ ├── settings.rs # Settings panel
│ │ ├── titlebar.rs # Custom window titlebar
│ │ ├── animations.rs # Animation utilities
│ │ ├── layout.rs # Layout helpers
│ │ └── components/ # Reusable UI components
│ │ ├── mod.rs
│ │ ├── ascii_art.rs # ASCII spinners for retro themes
│ │ ├── button.rs # GradientButton, IconButton
│ │ ├── card.rs # Card container
│ │ ├── circular_progress.rs # Circular progress bar
│ │ ├── icons.rs # Icon definitions
│ │ └── slider.rs # Custom slider
│ │
│ ├── platform/ # Platform-specific code
│ │ ├── mod.rs # Cross-platform abstractions
│ │ ├── audio.rs # Audio playback (rodio)
│ │ └── windows.rs # Windows-specific APIs (DWM, registry)
│ │
│ └── utils/
│ └── mod.rs # Utility functions
│
├── assets/ # Static assets
│ ├── icon.png # Application icon
│ ├── soft_bell.mp3 # Notification sounds
│ ├── level_up.mp3
│ └── digital_alert.mp3
│
├── .github/
│ └── workflows/ # CI/CD pipelines
│
├── Cargo.toml # Dependencies
├── build.rs # Windows resource compilation
├── README.md
├── CHANGELOG.md
└── CONTRIBUTING.md
The Timer struct is a state machine with the following states:
pub enum TimerState {
Idle, // Timer not started
Running, // Timer counting down
Paused, // Timer paused
Completed, // Timer finished
}
pub enum TimerEvent {
Started, // Timer started
Resumed, // Timer resumed from pause
Paused, // Timer paused
Ticked, // Second elapsed
Completed, // Timer finished
}State transitions:
Idle ──[start]──► Running ──[complete]──► Completed
│ ▲ │
│ │ │
[pause] [resume] [reset]
│ │ │
▼ │ │
Paused ◄────────────────────┘
Session manages the Pomodoro cycle:
Work → Short Break → Work → Short Break → Work → Short Break → Work → Long Break
1 1 2 2 3 3 4 4
Key methods:
toggle()- Start/pause the timerskip()- Skip current sessionreset()- Reset to beginningupdate()- Called every frame, returns events
Configuration is stored in TOML format:
[timer]
work_duration = 25
short_break = 5
long_break = 15
sessions_before_long = 4
[sounds]
enabled = true
volume = 80
notification_sound = "SoftBell"
[appearance]
accent_color = "Blue"
window_opacity = 100
[system]
start_with_windows = false
notifications_enabled = true
[window]
always_on_top = falseLocation:
- Windows:
%APPDATA%\pomodorust\config.toml - macOS:
~/Library/Application Support/pomodorust/config.toml - Linux:
~/.config/pomodorust/config.toml
SQLite database with three tables:
-- Individual sessions
CREATE TABLE sessions (
id INTEGER PRIMARY KEY,
session_type TEXT NOT NULL, -- 'work', 'short_break', 'long_break'
planned_duration INTEGER,
actual_duration INTEGER,
completed INTEGER,
started_at TEXT,
ended_at TEXT
);
-- Daily aggregates
CREATE TABLE daily_stats (
date TEXT PRIMARY KEY,
total_work_seconds INTEGER,
total_pomodoros INTEGER,
total_breaks INTEGER
);
-- Streak tracking
CREATE TABLE streaks (
id INTEGER PRIMARY KEY,
type TEXT, -- 'current', 'longest'
count INTEGER,
last_date TEXT
);The Theme struct contains all design tokens:
pub struct Theme {
// Colors
pub bg_primary: Color32,
pub bg_secondary: Color32,
pub text_primary: Color32,
pub accent: AccentColor,
// Spacing
pub spacing_sm: f32,
pub spacing_md: f32,
// Rounding
pub rounding_md: f32,
// ...
}Nine accent colors available:
- Modern: Blue, Purple, Rose, Emerald, Amber, Cyan
- Retro: Matrix (green), RetroAmber, Synthwave (pink/cyan)
Each view returns an Option<Action>:
pub enum TimerAction {
Toggle,
Skip,
Reset,
OpenStats,
OpenSettings,
}
impl TimerView {
pub fn show(&mut self, ui: &mut Ui, ...) -> Option<TimerAction> {
// Render UI
// Return action if user clicked something
}
}The main app handles actions in handle_*_action() methods.
UI components are composable:
// Card component wraps content
Card::new().show(ui, theme, |ui| {
// Content goes here
duration_row(ui, theme, "Focus", &mut value, 1.0, 90.0);
});
// Buttons with gradients
GradientButton::new(icon, text)
.with_gradient(start_color, end_color)
.show(ui, theme);// Notification abstraction
pub fn show_notification(title: &str, body: &str);
// Autostart abstraction
pub fn set_autostart(enabled: bool) -> Result<(), Error>;
// Window effects
pub fn apply_window_effects(hwnd: isize);- DWM effects (dark mode frame, rounded corners)
- Registry autostart
- Window flashing on timer completion
- Native toast notifications
Uses rodio crate for cross-platform audio:
pub struct AudioPlayer {
stream: OutputStream,
handle: OutputStreamHandle,
volume: f32,
}
impl AudioPlayer {
pub fn play_notification(&self, sound: NotificationSound);
pub fn set_volume(&mut self, volume: f32);
}Sounds are embedded in binary via include_bytes!().
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ eframe │────►│ app.update()│────►│session.update│
│ (60 fps) │ │ │ │ │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌───────────────────┴───────────────────┐
│ │
▼ ▼
TimerEvent::Ticked TimerEvent::Completed
│ │
▼ ▼
Request repaint on_timer_completed()
│
┌──────────────┴──────────────┐
│ │ │
▼ ▼ ▼
Record to DB Play sound Show notification
Settings UI ──► SettingsState ──► SettingsAction::UpdateConfig
│ │
│ ▼
│ app.apply_config()
│ │
│ ┌──────────────┴──────────────┐
│ │ │ │
│ ▼ ▼ ▼
│ Update theme Update timer Update audio
│ │
└───────────────────────────────────────┘
│
▼
config.save()
All errors use the Error enum from error.rs:
#[derive(Debug)]
pub enum Error {
Config(ConfigError),
Database(DatabaseError),
Audio(AudioError),
// ...
}Errors are logged via tracing and shown to user when appropriate.
Current test coverage:
core/timer.rs- Unit tests for state machineerror.rs- Error type testsui/animations.rs- Easing function testsui/layout.rs- Layout calculation tests
Run tests:
cargo testcargo buildcargo build --releaseRelease profile settings (Cargo.toml):
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimization
codegen-units = 1 # Better optimization
panic = "abort" # Smaller binary
strip = true # Strip symbols- Core logic - Add to
core/if it's business logic - Persistence - Add to
data/if it needs storage - UI - Add to
ui/for visual components - Platform - Add to
platform/for OS-specific features
- Add field to appropriate struct in
config.rs - Add to
SettingsStateinsettings.rs - Update
from_config(),differs_from(),apply_to() - Add UI element in
SettingsView::show() - Handle in
app.rsapply_config()if needed
- Use
rustfmtfor formatting - Use
clippyfor linting - Document public APIs with
///comments - Keep functions small and focused
| Crate | Purpose |
|---|---|
| egui/eframe | GUI framework |
| rusqlite | SQLite database |
| serde | Serialization |
| toml | Config file format |
| rodio | Audio playback |
| chrono | Date/time handling |
| directories | Platform paths |
| tracing | Logging |
| windows | Windows API (Windows only) |
- Animation frame rate - Only request repaint when needed
- Database - Sync writes (may block briefly on completion)
- Audio - Separate thread via rodio
- Binary size - ~15MB with all optimizations
Planned improvements:
- Async database operations (tokio)
- Plugin system for extensibility
- IPC for CLI integration
- Trait-based platform abstraction
Last updated: 2026-01-19