/* Polydoro - Pomodoro widget for polybar and friends * Copyright (C) 2025 Vidhu Kant Sharma * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ use bincode::{Encode, Decode}; use std::env; #[derive(Encode, Decode, PartialEq, Clone, Copy)] pub enum State { WorkIdle, BreakIdle, LongBreakIdle, Work, Break, LongBreak, WorkPaused, BreakPaused, LongBreakPaused, } #[derive(Encode, Decode)] pub struct Pomo { work_duration: u16, break_duration: u16, long_break_duration: u16, long_break_interval: u16, // only these are supposed to change pub secs_elapsed: u16, pub counter: u16, pub current_state: State, } impl Pomo { pub fn new() -> Self { Pomo { secs_elapsed: 0, counter: 1, current_state: State::WorkIdle, work_duration: get_envvar("WORK_DURATION", 25 * 60), break_duration: get_envvar("BREAK_DURATION", 5 * 60), long_break_duration: get_envvar("LONG_BREAK_DURATION", 20 * 60), long_break_interval: get_envvar("LONG_BREAK_INTERVAL", 4), } } pub fn tick(&mut self) { // only tick on running states if matches!(self.current_state, State::Work | State::Break | State::LongBreak) { self.secs_elapsed += 1; let duration = match self.current_state { State::Work => self.work_duration, State::Break => self.break_duration, State::LongBreak => self.long_break_duration, _ => 0, // will never hit this case }; if self.secs_elapsed >= duration { self.end_cycle(); } } } // start/unpause a cycle pub fn run(&mut self) { self.current_state = match self.current_state { State::WorkIdle => State::Work, State::BreakIdle => State::Break, State::LongBreakIdle => State::LongBreak, State::WorkPaused => State::Work, State::BreakPaused => State::Break, State::LongBreakPaused => State::LongBreak, _ => return // already running }; } pub fn pause(&mut self) { self.current_state = match self.current_state { State::Work => State::WorkPaused, State::Break => State::BreakPaused, State::LongBreak => State::LongBreakPaused, _ => return, // idle and paused don't have a place here }; } // conditionally pause/run pub fn toggle(&mut self) { match self.current_state { State::Work | State::Break | State::LongBreak => self.pause(), _ => self.run(), } } // also used in the skip command pub fn end_cycle(&mut self) { match self.current_state { State::Work => { self.secs_elapsed = 0; self.current_state = if self.counter == self.long_break_interval { State::LongBreakIdle } else { State::BreakIdle }; }, State::Break => { self.secs_elapsed = 0; self.current_state = State::WorkIdle; // because we move on to the next task after the break self.counter += 1; } State::LongBreak => self.hard_reset(), _ => return, // paused and idle state can't be ended } } // reset current cycle pub fn soft_reset(&mut self) { self.secs_elapsed = 0; self.current_state = match self.current_state { State::Work | State::WorkPaused => State::WorkIdle, State::Break | State::BreakPaused => State::BreakIdle, State::LongBreak | State::LongBreakPaused => State::LongBreakIdle, _ => self.current_state, // if already in an idle state, don't change (probably won't hit anyways) }; } // reset all cycles pub fn hard_reset(&mut self) { self.secs_elapsed = 0; self.counter = 1; self.current_state = State::WorkIdle; } // conditionally do a soft or a hard reset pub fn reset(&mut self) { match self.current_state { State::WorkIdle | State::BreakIdle | State::LongBreakIdle => self.hard_reset(), _ => self.soft_reset(), } } } fn get_envvar(key: &str, def: u16) -> u16 { env::var(key) .ok() .and_then(|v| v.parse::().ok()) .unwrap_or(def) }