Skip to content

Commit a5bda41

Browse files
authored
Create Tip-Calculator.py
1 parent 420791b commit a5bda41

1 file changed

Lines changed: 286 additions & 0 deletions

File tree

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
import sys
2+
import os
3+
import threading
4+
import tkinter as tk
5+
from tkinter import ttk, messagebox, filedialog
6+
import sv_ttk
7+
8+
# =========================
9+
# Helpers
10+
# =========================
11+
def resource_path(file_name):
12+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
13+
return os.path.join(base_path, file_name)
14+
15+
def set_status(msg):
16+
status_var.set(msg)
17+
root.update_idletasks()
18+
19+
# =========================
20+
# App Setup
21+
# =========================
22+
root = tk.Tk()
23+
root.title("Interactive Tip Calculator")
24+
root.geometry("600x700")
25+
root.minsize(600, 700)
26+
sv_ttk.set_theme("light")
27+
28+
# =========================
29+
# Globals
30+
# =========================
31+
dark_mode_var = tk.BooleanVar(value=False)
32+
bill_var = tk.StringVar()
33+
tip_var = tk.StringVar(value="15")
34+
people_var = tk.StringVar(value="1")
35+
tip_per_person_var = tk.StringVar(value="$0.00")
36+
total_per_person_var = tk.StringVar(value="$0.00")
37+
calculation_history = [] # List of tuples: (bill, tip%, people, tip_pp, total_pp)
38+
39+
# =========================
40+
# Theme Toggle
41+
# =========================
42+
def toggle_theme():
43+
bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
44+
fg = "white" if dark_mode_var.get() else "black"
45+
root.configure(bg=bg)
46+
for w in ["TFrame", "TLabel", "TLabelframe", "TLabelframe.Label", "TCheckbutton"]:
47+
style.configure(w, background=bg, foreground=fg)
48+
for entry in [bill_entry, tip_entry, people_entry]:
49+
entry.configure(background=bg, foreground=fg)
50+
51+
# =========================
52+
# Tip Calculation with Animation
53+
# =========================
54+
def calculate_tip(real_time=False):
55+
try:
56+
bill = float(bill_var.get())
57+
tip_percent = float(tip_var.get())
58+
people = int(people_var.get())
59+
if bill < 0 or tip_percent < 0 or people < 1:
60+
raise ValueError
61+
except ValueError:
62+
if not real_time:
63+
messagebox.showerror("Invalid Input", "Please enter valid numeric values.")
64+
return
65+
66+
tip_total = bill * (tip_percent / 100)
67+
total_bill = bill + tip_total
68+
tip_per_person = tip_total / people
69+
total_per_person = total_bill / people
70+
71+
animate_split(tip_per_person, total_per_person)
72+
73+
if not real_time:
74+
add_to_history(bill, tip_percent, people, tip_per_person, total_per_person)
75+
set_status("Calculation completed!")
76+
77+
# =========================
78+
# Animation Function
79+
# =========================
80+
def animate_split(tip_final, total_final, steps=30, interval=15):
81+
tip_current = 0.0
82+
total_current = 0.0
83+
tip_step = tip_final / steps
84+
total_step = total_final / steps
85+
86+
def step_animation(count=0):
87+
nonlocal tip_current, total_current
88+
if count >= steps:
89+
tip_per_person_var.set(f"${tip_final:.2f}")
90+
total_per_person_var.set(f"${total_final:.2f}")
91+
return
92+
tip_current += tip_step
93+
total_current += total_step
94+
tip_per_person_var.set(f"${tip_current:.2f}")
95+
total_per_person_var.set(f"${total_current:.2f}")
96+
root.after(interval, lambda: step_animation(count + 1))
97+
98+
step_animation()
99+
100+
# =========================
101+
# Preset Tip Buttons
102+
# =========================
103+
def set_tip(value):
104+
tip_var.set(str(value))
105+
calculate_tip(real_time=True)
106+
107+
# =========================
108+
# History Management
109+
# =========================
110+
def add_to_history(bill, tip_percent, people, tip_pp, total_pp):
111+
calculation_history.append((bill, tip_percent, people, tip_pp, total_pp))
112+
preview = f"${bill:.2f} | {tip_percent}% | {people} person(s) → Tip: ${tip_pp:.2f}, Total: ${total_pp:.2f}"
113+
history_list.insert(tk.END, preview)
114+
115+
def export_history_txt():
116+
if not calculation_history:
117+
messagebox.showinfo("Empty History", "No calculations to export.")
118+
return
119+
120+
file_path = filedialog.asksaveasfilename(
121+
defaultextension=".txt",
122+
filetypes=[("Text Files", "*.txt")],
123+
title="Export Tip History"
124+
)
125+
if not file_path:
126+
return
127+
128+
try:
129+
with open(file_path, "w", encoding="utf-8") as f:
130+
f.write("Tip Calculator History\n")
131+
f.write("=" * 40 + "\n\n")
132+
for i, (bill, tip, people, tip_pp, total_pp) in enumerate(calculation_history, 1):
133+
f.write(f"{i}. Bill: ${bill:.2f}, Tip: {tip}%, People: {people}\n")
134+
f.write(f" Tip per person: ${tip_pp:.2f}, Total per person: ${total_pp:.2f}\n\n")
135+
set_status("History exported successfully")
136+
messagebox.showinfo("Export Successful", "Tip calculation history saved.")
137+
except Exception as e:
138+
messagebox.showerror("Export Failed", str(e))
139+
140+
# =========================
141+
# History Viewer with Animation
142+
# =========================
143+
def view_selected_history(event=None):
144+
selection = history_list.curselection()
145+
if not selection:
146+
messagebox.showinfo("No Selection", "Please select a calculation from the history.")
147+
return
148+
index = selection[0]
149+
bill, tip, people, tip_pp, total_pp = calculation_history[index]
150+
151+
history_window = tk.Toplevel(root)
152+
history_window.title(f"Calculation Details")
153+
history_window.geometry("500x400")
154+
history_window.rowconfigure(0, weight=1)
155+
history_window.columnconfigure(0, weight=1)
156+
history_window.transient(root)
157+
history_window.grab_set()
158+
159+
frame = ttk.Frame(history_window, padding=10)
160+
frame.grid(row=0, column=0, sticky="nsew")
161+
frame.rowconfigure(0, weight=1)
162+
frame.columnconfigure(0, weight=1)
163+
164+
text_widget = tk.Text(frame, wrap="word", font=("Segoe UI", 12))
165+
text_widget.grid(row=0, column=0, sticky="nsew")
166+
167+
scrollbar = ttk.Scrollbar(frame, orient="vertical", command=text_widget.yview)
168+
scrollbar.grid(row=0, column=1, sticky="ns")
169+
text_widget.configure(yscrollcommand=scrollbar.set)
170+
text_widget.configure(state="normal")
171+
text_widget.delete("1.0", tk.END)
172+
173+
lines = [
174+
f"Bill Amount: ${bill:.2f}",
175+
f"Tip Percentage: {tip}%",
176+
f"Number of People: {people}",
177+
f"Tip per Person: ${tip_pp:.2f}",
178+
f"Total per Person: ${total_pp:.2f}"
179+
]
180+
181+
def type_lines(idx=0):
182+
if idx >= len(lines):
183+
text_widget.configure(state="disabled")
184+
history_window.grab_release()
185+
return
186+
187+
line = lines[idx]
188+
char_idx = 0
189+
190+
def type_char():
191+
nonlocal char_idx
192+
if char_idx < len(line):
193+
text_widget.insert(tk.END, line[char_idx])
194+
text_widget.see(tk.END)
195+
char_idx += 1
196+
text_widget.after(20, type_char)
197+
else:
198+
text_widget.insert(tk.END, "\n")
199+
text_widget.after(100, lambda: type_lines(idx + 1))
200+
201+
type_char()
202+
203+
type_lines()
204+
history_window.focus()
205+
206+
# =========================
207+
# Styles
208+
# =========================
209+
style = ttk.Style()
210+
style.theme_use("clam")
211+
style.configure("Action.TButton", font=("Segoe UI", 11, "bold"), padding=8)
212+
213+
# =========================
214+
# Status Bar
215+
# =========================
216+
status_var = tk.StringVar(value="Ready")
217+
ttk.Label(root, textvariable=status_var, anchor="w").pack(side=tk.BOTTOM, fill="x")
218+
219+
# =========================
220+
# UI Layout
221+
# =========================
222+
main = ttk.Frame(root, padding=20)
223+
main.pack(expand=True, fill="both")
224+
main.columnconfigure(0, weight=1)
225+
226+
ttk.Label(main, text="Interactive Tip Calculator", font=("Segoe UI", 22, "bold")).grid(row=0, column=0, sticky="ew", pady=(0,10))
227+
228+
# Inputs
229+
ttk.Label(main, text="Bill Amount ($):", font=("Segoe UI", 12)).grid(row=1, column=0, sticky="w")
230+
bill_entry = ttk.Entry(main, textvariable=bill_var, font=("Segoe UI", 14))
231+
bill_entry.grid(row=2, column=0, sticky="ew", pady=2)
232+
233+
ttk.Label(main, text="Tip Percentage (%):", font=("Segoe UI", 12)).grid(row=3, column=0, sticky="w")
234+
tip_entry = ttk.Entry(main, textvariable=tip_var, font=("Segoe UI", 14))
235+
tip_entry.grid(row=4, column=0, sticky="ew", pady=2)
236+
237+
# Preset Tip Buttons
238+
preset_frame = ttk.Frame(main)
239+
preset_frame.grid(row=5, column=0, sticky="ew", pady=(0,10))
240+
preset_frame.columnconfigure((0,1,2), weight=1)
241+
ttk.Button(preset_frame, text="10%", command=lambda: set_tip(10)).grid(row=0, column=0, sticky="ew", padx=2)
242+
ttk.Button(preset_frame, text="15%", command=lambda: set_tip(15)).grid(row=0, column=1, sticky="ew", padx=2)
243+
ttk.Button(preset_frame, text="20%", command=lambda: set_tip(20)).grid(row=0, column=2, sticky="ew", padx=2)
244+
245+
ttk.Label(main, text="Number of People:", font=("Segoe UI", 12)).grid(row=6, column=0, sticky="w")
246+
people_entry = ttk.Entry(main, textvariable=people_var, font=("Segoe UI", 14))
247+
people_entry.grid(row=7, column=0, sticky="ew", pady=2)
248+
249+
# Output
250+
ttk.Label(main, text="Tip per Person:", font=("Segoe UI", 12)).grid(row=8, column=0, sticky="w", pady=(10,0))
251+
ttk.Label(main, textvariable=tip_per_person_var, font=("Segoe UI", 14, "bold")).grid(row=9, column=0, sticky="w")
252+
253+
ttk.Label(main, text="Total per Person:", font=("Segoe UI", 12)).grid(row=10, column=0, sticky="w", pady=(10,0))
254+
ttk.Label(main, textvariable=total_per_person_var, font=("Segoe UI", 14, "bold")).grid(row=11, column=0, sticky="w")
255+
256+
# Controls
257+
controls = ttk.Frame(main)
258+
controls.grid(row=12, column=0, sticky="ew", pady=12)
259+
controls.columnconfigure((0,1), weight=1)
260+
ttk.Button(controls, text="💰 Calculate Tip", command=calculate_tip, style="Action.TButton").grid(row=0, column=0, padx=4, sticky="ew")
261+
ttk.Button(controls, text="📤 Export History", command=export_history_txt, style="Action.TButton").grid(row=0, column=1, padx=4, sticky="ew")
262+
263+
# History Vault
264+
vault = ttk.LabelFrame(main, text="Calculation History Vault", padding=10)
265+
vault.grid(row=13, column=0, sticky="nsew", pady=10)
266+
main.rowconfigure(13, weight=1)
267+
history_list = tk.Listbox(vault, font=("Segoe UI", 10), height=7)
268+
history_list.pack(fill="both", expand=True)
269+
history_list.bind("<Double-Button-1>", view_selected_history)
270+
271+
# Options
272+
options_frame = ttk.Frame(main)
273+
options_frame.grid(row=14, column=0, sticky="w", pady=6)
274+
ttk.Checkbutton(options_frame, text="Dark Mode", variable=dark_mode_var, command=toggle_theme).pack(side="left", padx=(0,10))
275+
276+
# =========================
277+
# Real-Time Calculation Bindings
278+
# =========================
279+
bill_var.trace_add("write", lambda *args: calculate_tip(real_time=True))
280+
tip_var.trace_add("write", lambda *args: calculate_tip(real_time=True))
281+
people_var.trace_add("write", lambda *args: calculate_tip(real_time=True))
282+
283+
# =========================
284+
# Run App
285+
# =========================
286+
root.mainloop()

0 commit comments

Comments
 (0)