|
| 1 | +from tkinter import * |
| 2 | + |
| 3 | +# ---------------------------- CONSTANTS & GLOBALS ------------------------------- # |
| 4 | +PINK = "#e2979c" |
| 5 | +GREEN = "#9bdeac" |
| 6 | +FONT_NAME = "Courier" |
| 7 | +DEFAULT_WORK_MIN = 25 |
| 8 | +DEFAULT_BREAK_MIN = 5 |
| 9 | + |
| 10 | +# Background color options |
| 11 | +bg_colors = { |
| 12 | + "Pink": "#e2979c", |
| 13 | + "Green": "#9bdeac", |
| 14 | + "Blue": "#1f75fe", |
| 15 | + "Yellow": "#ffcc00", |
| 16 | + "Purple": "#b19cd9" |
| 17 | +} |
| 18 | + |
| 19 | +# Global variables |
| 20 | +ROUND = 1 |
| 21 | +timer_mec = None |
| 22 | +total_time = 0 # Total seconds for the current session |
| 23 | +is_paused = False # Timer pause flag |
| 24 | +remaining_time = 0 # Remaining time (in seconds) when paused |
| 25 | +custom_work_min = DEFAULT_WORK_MIN |
| 26 | +custom_break_min = DEFAULT_BREAK_MIN |
| 27 | + |
| 28 | +# ---------------------------- BACKGROUND COLOR CHANGE FUNCTION ------------------------------- # |
| 29 | +def change_background(*args): |
| 30 | + selected = bg_color_var.get() |
| 31 | + new_color = bg_colors.get(selected, PINK) |
| 32 | + window.config(bg=new_color) |
| 33 | + canvas.config(bg=new_color) |
| 34 | + label.config(bg=new_color) |
| 35 | + tick_label.config(bg=new_color) |
| 36 | + work_label.config(bg=new_color) |
| 37 | + break_label.config(bg=new_color) |
| 38 | + |
| 39 | +# ---------------------------- NOTIFICATION FUNCTION ------------------------------- # |
| 40 | +def show_notification(message): |
| 41 | + notif = Toplevel(window) |
| 42 | + notif.overrideredirect(True) |
| 43 | + notif.config(bg=PINK) |
| 44 | + |
| 45 | + msg_label = Label(notif, text=message, font=(FONT_NAME, 12, "bold"), |
| 46 | + bg=GREEN, fg="white", padx=10, pady=5) |
| 47 | + msg_label.pack() |
| 48 | + |
| 49 | + window.update_idletasks() |
| 50 | + wx = window.winfo_rootx() |
| 51 | + wy = window.winfo_rooty() |
| 52 | + wwidth = window.winfo_width() |
| 53 | + wheight = window.winfo_height() |
| 54 | + |
| 55 | + notif.update_idletasks() |
| 56 | + nwidth = notif.winfo_width() |
| 57 | + nheight = notif.winfo_height() |
| 58 | + |
| 59 | + x = wx + (wwidth - nwidth) // 2 |
| 60 | + y = wy + wheight - nheight - 10 |
| 61 | + notif.geometry(f"+{x}+{y}") |
| 62 | + |
| 63 | + notif.after(3000, notif.destroy) |
| 64 | + |
| 65 | +# ---------------------------- TIMER FUNCTIONS ------------------------------- # |
| 66 | +def reset_timer(): |
| 67 | + global ROUND, timer_mec, total_time, is_paused, remaining_time |
| 68 | + ROUND = 1 |
| 69 | + is_paused = False |
| 70 | + remaining_time = 0 |
| 71 | + if timer_mec is not None: |
| 72 | + window.after_cancel(timer_mec) |
| 73 | + canvas.itemconfig(timer_text, text="00:00") |
| 74 | + label.config(text="Timer") |
| 75 | + tick_label.config(text="") |
| 76 | + total_time = 0 |
| 77 | + canvas.itemconfig(progress_arc, extent=0) |
| 78 | + start_button.config(state=NORMAL) |
| 79 | + pause_button.config(state=DISABLED) |
| 80 | + play_button.config(state=DISABLED) |
| 81 | + |
| 82 | +def start_timer(): |
| 83 | + global ROUND, total_time, is_paused |
| 84 | + canvas.itemconfig(progress_arc, extent=0) |
| 85 | + |
| 86 | + if ROUND % 2 == 1: # Work session |
| 87 | + total_time = custom_work_min * 60 |
| 88 | + label.config(text="Work", fg=GREEN) |
| 89 | + else: # Break session |
| 90 | + total_time = custom_break_min * 60 |
| 91 | + label.config(text="Break", fg=PINK) |
| 92 | + |
| 93 | + count_down(total_time) |
| 94 | + start_button.config(state=DISABLED) |
| 95 | + pause_button.config(state=NORMAL) |
| 96 | + play_button.config(state=DISABLED) |
| 97 | + is_paused = False |
| 98 | + |
| 99 | +def count_down(count): |
| 100 | + global timer_mec, remaining_time |
| 101 | + remaining_time = count |
| 102 | + minutes = count // 60 |
| 103 | + seconds = count % 60 |
| 104 | + if seconds < 10: |
| 105 | + seconds = f"0{seconds}" |
| 106 | + canvas.itemconfig(timer_text, text=f"{minutes}:{seconds}") |
| 107 | + |
| 108 | + if total_time > 0: |
| 109 | + progress = (total_time - count) / total_time |
| 110 | + canvas.itemconfig(progress_arc, extent=progress * 360) |
| 111 | + |
| 112 | + if count > 0 and not is_paused: |
| 113 | + timer_mec = window.after(1000, count_down, count - 1) |
| 114 | + elif count == 0: |
| 115 | + if ROUND % 2 == 1: |
| 116 | + show_notification("Work session complete! Time for a break.") |
| 117 | + else: |
| 118 | + show_notification("Break over! Back to work.") |
| 119 | + if ROUND % 2 == 0: |
| 120 | + tick_label.config(text=tick_label.cget("text") + "#") |
| 121 | + ROUND += 1 |
| 122 | + start_timer() |
| 123 | + |
| 124 | +def pause_timer(): |
| 125 | + global is_paused, timer_mec |
| 126 | + if not is_paused: |
| 127 | + is_paused = True |
| 128 | + if timer_mec is not None: |
| 129 | + window.after_cancel(timer_mec) |
| 130 | + pause_button.config(state=DISABLED) |
| 131 | + play_button.config(state=NORMAL) |
| 132 | + |
| 133 | +def resume_timer(): |
| 134 | + global is_paused |
| 135 | + if is_paused: |
| 136 | + is_paused = False |
| 137 | + count_down(remaining_time) |
| 138 | + play_button.config(state=DISABLED) |
| 139 | + pause_button.config(state=NORMAL) |
| 140 | + |
| 141 | +def set_custom_durations(): |
| 142 | + global custom_work_min, custom_break_min |
| 143 | + try: |
| 144 | + work_val = int(entry_work.get()) |
| 145 | + break_val = int(entry_break.get()) |
| 146 | + custom_work_min = work_val |
| 147 | + custom_break_min = break_val |
| 148 | + canvas.itemconfig(left_custom, text=f"{custom_work_min}m") |
| 149 | + canvas.itemconfig(right_custom, text=f"{custom_break_min}m") |
| 150 | + except ValueError: |
| 151 | + pass |
| 152 | + |
| 153 | +# ---------------------------- UI SETUP ------------------------------- # |
| 154 | +window = Tk() |
| 155 | +window.title("Pomodoro") |
| 156 | +window.config(padx=100, pady=50, bg=PINK) |
| 157 | + |
| 158 | +# Canvas setup with increased width for spacing |
| 159 | +canvas = Canvas(window, width=240, height=224, bg=PINK, highlightthickness=0) |
| 160 | +timer_text = canvas.create_text(120, 112, text="00:00", font=(FONT_NAME, 35, "bold"), fill="white") |
| 161 | +background_circle = canvas.create_arc(40, 32, 200, 192, start=0, extent=359.9, |
| 162 | + style="arc", outline="white", width=5) |
| 163 | +progress_arc = canvas.create_arc(40, 32, 200, 192, start=270, extent=0, |
| 164 | + style="arc", outline="green", width=5) |
| 165 | +# Updated positions for work and break time labels |
| 166 | +left_custom = canvas.create_text(20, 112, text=f"{custom_work_min}m", font=(FONT_NAME, 12, "bold"), fill="white") |
| 167 | +right_custom = canvas.create_text(220, 112, text=f"{custom_break_min}m", font=(FONT_NAME, 12, "bold"), fill="white") |
| 168 | + |
| 169 | +canvas.grid(column=1, row=1) |
| 170 | + |
| 171 | +label = Label(text="Timer", font=(FONT_NAME, 35, "bold"), bg=PINK, fg="green") |
| 172 | +label.grid(column=1, row=0) |
| 173 | + |
| 174 | +start_button = Button(text="Start", command=start_timer, highlightthickness=0) |
| 175 | +start_button.grid(column=0, row=2) |
| 176 | + |
| 177 | +reset_button = Button(text="Reset", command=reset_timer, highlightthickness=0) |
| 178 | +reset_button.grid(column=2, row=2) |
| 179 | + |
| 180 | +pause_button = Button(text="Pause", command=pause_timer, highlightthickness=0, state=DISABLED) |
| 181 | +pause_button.grid(column=0, row=3) |
| 182 | + |
| 183 | +play_button = Button(text="Play", command=resume_timer, highlightthickness=0, state=DISABLED) |
| 184 | +play_button.grid(column=2, row=3) |
| 185 | + |
| 186 | +tick_label = Label(text="", font=(FONT_NAME, 15, "bold"), bg=PINK, fg="green") |
| 187 | +tick_label.grid(column=1, row=4) |
| 188 | + |
| 189 | +# Custom durations (stacked vertically) |
| 190 | +work_label = Label(text="Work (min):", font=(FONT_NAME, 12, "bold"), bg=PINK, fg="white") |
| 191 | +work_label.grid(column=1, row=5, pady=(20, 0)) |
| 192 | +entry_work = Entry(width=5, font=(FONT_NAME, 12)) |
| 193 | +entry_work.grid(column=1, row=6, pady=(5, 10)) |
| 194 | +break_label = Label(text="Break (min):", font=(FONT_NAME, 12, "bold"), bg=PINK, fg="white") |
| 195 | +break_label.grid(column=1, row=7, pady=(5, 0)) |
| 196 | +entry_break = Entry(width=5, font=(FONT_NAME, 12)) |
| 197 | +entry_break.grid(column=1, row=8, pady=(5, 10)) |
| 198 | +set_button = Button(text="Set Durations", command=set_custom_durations, font=(FONT_NAME, 12)) |
| 199 | +set_button.grid(column=1, row=9, pady=(10, 20)) |
| 200 | + |
| 201 | +# OptionMenu for changing background color |
| 202 | +bg_color_var = StringVar(window) |
| 203 | +bg_color_var.set("Pink") |
| 204 | +bg_option = OptionMenu(window, bg_color_var, *bg_colors.keys(), command=change_background) |
| 205 | +bg_option.config(font=(FONT_NAME, 12)) |
| 206 | +bg_option.grid(column=1, row=10, pady=(10, 20)) |
| 207 | + |
| 208 | +window.mainloop() |
0 commit comments