pythonCalcByClaudeCode/calculator.py
anas-rashid 05fdd13cd6 Add scientific calculator with modern Catppuccin Mocha UI
Features: basic arithmetic, trig/inverse-trig, log/ln, factorial,
memory (MC/MR/M+/M−), DEG/RAD toggle, auto-resizing display,
hover effects, keyboard bindings, and clipboard copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 00:18:02 +02:00

364 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import tkinter as tk
import math
import re
# ── Catppuccin Mocha palette ──────────────────────────────────────────────────
BG = "#1e1e2e"
BASE = "#313244"
OVERLAY = "#45475a"
TEXT = "#cdd6f4"
SUBTEXT = "#6c7086"
BLUE = "#89b4fa"
PURPLE = "#cba6f7"
GREEN = "#a6e3a1"
RED = "#f38ba8"
YELLOW = "#f9e2af"
TEAL = "#94e2d5"
class Calculator(tk.Tk):
def __init__(self):
super().__init__()
self.title("Scientific Calculator")
self.resizable(False, False)
self.configure(bg=BG)
self.expression = ""
self.result_shown = False
self.memory = 0.0
self.angle_mode = "DEG" # or "RAD"
self._build_display()
self._build_buttons()
self._center_window()
# ── window centering ──────────────────────────────────────────────────────
def _center_window(self):
self.update_idletasks()
w, h = self.winfo_width(), self.winfo_height()
sw, sh = self.winfo_screenwidth(), self.winfo_screenheight()
self.geometry(f"+{(sw - w) // 2}+{(sh - h) // 2}")
# ── display ───────────────────────────────────────────────────────────────
def _build_display(self):
display_frame = tk.Frame(self, bg=BG, pady=12, padx=14)
display_frame.pack(fill="x")
# top bar: memory indicator (left) + angle-mode toggle (right)
top_bar = tk.Frame(display_frame, bg=BG)
top_bar.pack(fill="x")
self.mem_indicator = tk.Label(
top_bar, text="", bg=BG, fg=TEAL,
font=("Consolas", 10, "bold"), anchor="w",
)
self.mem_indicator.pack(side="left")
self.angle_lbl = tk.Label(
top_bar, text="DEG", bg=OVERLAY, fg=BLUE,
font=("Consolas", 10, "bold"),
padx=10, pady=3, cursor="hand2",
)
self.angle_lbl.pack(side="right")
self.angle_lbl.bind("<Button-1>", lambda _: self._toggle_angle())
# history line
self.history_var = tk.StringVar(value="")
tk.Label(
display_frame,
textvariable=self.history_var,
bg=BG, fg=SUBTEXT,
font=("Consolas", 12),
anchor="e", height=1,
).pack(fill="x")
# main display — font shrinks automatically for long text
self.display_var = tk.StringVar(value="0")
self.display_lbl = tk.Label(
display_frame,
textvariable=self.display_var,
bg=BG, fg=TEXT,
font=("Consolas", 32, "bold"),
anchor="e", height=2,
)
self.display_lbl.pack(fill="x")
# click display to copy result
self.display_lbl.bind("<Button-1>", lambda _: self._copy_result())
self.display_lbl.config(cursor="hand2")
# divider
tk.Frame(self, bg=OVERLAY, height=1).pack(fill="x")
# ── buttons ───────────────────────────────────────────────────────────────
def _build_buttons(self):
btn_frame = tk.Frame(self, bg=BG, padx=8, pady=8)
btn_frame.pack()
C = {
"digit": (BASE, TEXT),
"op": (OVERLAY, PURPLE),
"fn": (BASE, BLUE),
"equal": (PURPLE, BG),
"clear": (RED, BG),
"back": (OVERLAY, RED),
"const": (BASE, GREEN),
"mem": (BASE, TEAL),
}
layout = [
# ── Row 0 · memory ──────────────────────────────────────────────
("MC", 0, 0, 1, "mem", self._mem_clear),
("MR", 0, 1, 1, "mem", self._mem_recall),
("M+", 0, 2, 1, "mem", self._mem_add),
("M", 0, 3, 1, "mem", self._mem_sub),
# ── Row 1 · basic trig ──────────────────────────────────────────
("sin", 1, 0, 1, "fn", lambda: self._insert_fn("sin")),
("cos", 1, 1, 1, "fn", lambda: self._insert_fn("cos")),
("tan", 1, 2, 1, "fn", lambda: self._insert_fn("tan")),
("√x", 1, 3, 1, "fn", lambda: self._insert_fn("sqrt")),
# ── Row 2 · inverse trig + power ────────────────────────────────
("asin", 2, 0, 1, "fn", lambda: self._insert_fn("asin")),
("acos", 2, 1, 1, "fn", lambda: self._insert_fn("acos")),
("atan", 2, 2, 1, "fn", lambda: self._insert_fn("atan")),
("", 2, 3, 1, "fn", lambda: self._append("**")),
# ── Row 3 · logarithms + factorial + square ──────────────────────
("log", 3, 0, 1, "fn", lambda: self._insert_fn("log")),
("ln", 3, 1, 1, "fn", lambda: self._insert_fn("ln")),
("x!", 3, 2, 1, "fn", lambda: self._insert_fn("fact")),
("", 3, 3, 1, "fn", lambda: self._append("**2")),
# ── Row 4 · parens + constants ───────────────────────────────────
("(", 4, 0, 1, "op", lambda: self._append("(")),
(")", 4, 1, 1, "op", lambda: self._append(")")),
("π", 4, 2, 1, "const", lambda: self._append("π")),
("e", 4, 3, 1, "const", lambda: self._append("e")),
# ── Row 5 · clear / back ─────────────────────────────────────────
("AC", 5, 0, 1, "clear", self._clear_all),
("CE", 5, 1, 1, "back", self._clear_entry),
("", 5, 2, 1, "back", self._backspace),
("%", 5, 3, 1, "op", lambda: self._append("%")),
# ── Row 6 ────────────────────────────────────────────────────────
("7", 6, 0, 1, "digit", lambda: self._append("7")),
("8", 6, 1, 1, "digit", lambda: self._append("8")),
("9", 6, 2, 1, "digit", lambda: self._append("9")),
("÷", 6, 3, 1, "op", lambda: self._append("/")),
# ── Row 7 ────────────────────────────────────────────────────────
("4", 7, 0, 1, "digit", lambda: self._append("4")),
("5", 7, 1, 1, "digit", lambda: self._append("5")),
("6", 7, 2, 1, "digit", lambda: self._append("6")),
("×", 7, 3, 1, "op", lambda: self._append("*")),
# ── Row 8 ────────────────────────────────────────────────────────
("1", 8, 0, 1, "digit", lambda: self._append("1")),
("2", 8, 1, 1, "digit", lambda: self._append("2")),
("3", 8, 2, 1, "digit", lambda: self._append("3")),
("", 8, 3, 1, "op", lambda: self._append("-")),
# ── Row 9 ────────────────────────────────────────────────────────
("±", 9, 0, 1, "op", self._negate),
("0", 9, 1, 1, "digit", lambda: self._append("0")),
(".", 9, 2, 1, "digit", lambda: self._append(".")),
("+", 9, 3, 1, "op", lambda: self._append("+")),
# ── Row 10 · equals ──────────────────────────────────────────────
("=", 10, 0, 4, "equal", self._evaluate),
]
for (label, row, col, span, ckey, cmd) in layout:
bg, fg = C[ckey]
btn = tk.Button(
btn_frame,
text=label,
bg=bg, fg=fg,
activebackground=self._lighten(bg),
activeforeground=fg,
font=("Consolas", 14, "bold"),
relief="flat", bd=0,
width=5 if span == 1 else 23,
height=2,
cursor="hand2",
command=cmd,
)
btn.grid(row=row, column=col, columnspan=span,
padx=4, pady=3, sticky="ew")
# hover highlight
btn.bind("<Enter>", lambda _e, b=btn, c=bg: b.config(bg=self._lighten(c)))
btn.bind("<Leave>", lambda _e, b=btn, c=bg: b.config(bg=c))
# keyboard bindings
self.bind("<Key>", self._on_key)
self.bind("<Return>", lambda _: self._evaluate())
self.bind("<KP_Enter>", lambda _: self._evaluate())
self.bind("<BackSpace>", lambda _: self._backspace())
self.bind("<Escape>", lambda _: self._clear_all())
self.bind("<Delete>", lambda _: self._clear_all())
self.bind("<Control-c>", lambda _: self._copy_result())
# ── helpers ───────────────────────────────────────────────────────────────
@staticmethod
def _lighten(hex_color: str) -> str:
h = hex_color.lstrip("#")
r, g, b = (int(h[i:i+2], 16) for i in (0, 2, 4))
return f"#{min(255, r+35):02x}{min(255, g+35):02x}{min(255, b+35):02x}"
def _set_display(self, text: str):
text = text or "0"
n = len(text)
size = 18 if n > 20 else 22 if n > 14 else 26 if n > 9 else 32
self.display_lbl.config(font=("Consolas", size, "bold"))
self.display_var.set(text)
def _append(self, char: str):
if self.result_shown:
if char in "0123456789.":
self.expression = ""
self.result_shown = False
self.expression += char
self._set_display(self.expression)
def _insert_fn(self, fn: str):
self.result_shown = False
self.expression += fn + "("
self._set_display(self.expression)
def _clear_all(self):
self.expression = ""
self.history_var.set("")
self.result_shown = False
self._set_display("")
def _clear_entry(self):
self.expression = ""
self.result_shown = False
self._set_display("")
def _backspace(self):
if self.result_shown:
self._clear_all()
return
self.expression = self.expression[:-1]
self._set_display(self.expression)
def _negate(self):
if not self.expression or self.expression == "0":
return
if self.expression.startswith("-"):
self.expression = self.expression[1:]
else:
self.expression = "-" + self.expression
self._set_display(self.expression)
def _toggle_angle(self):
if self.angle_mode == "DEG":
self.angle_mode = "RAD"
self.angle_lbl.config(text="RAD", fg=YELLOW)
else:
self.angle_mode = "DEG"
self.angle_lbl.config(text="DEG", fg=BLUE)
def _copy_result(self):
self.clipboard_clear()
self.clipboard_append(self.display_var.get())
# ── memory ────────────────────────────────────────────────────────────────
def _update_mem_indicator(self):
self.mem_indicator.config(
text=f"M = {self.memory:.6g}" if self.memory != 0 else ""
)
def _mem_clear(self):
self.memory = 0.0
self._update_mem_indicator()
def _mem_recall(self):
val = f"{self.memory:.10g}"
if self.result_shown or not self.expression:
self.expression = val
else:
self.expression += val
self.result_shown = False
self._set_display(self.expression)
def _mem_add(self):
try:
self.memory += float(self._safe_eval(self.expression))
self._update_mem_indicator()
except Exception:
pass
def _mem_sub(self):
try:
self.memory -= float(self._safe_eval(self.expression))
self._update_mem_indicator()
except Exception:
pass
# ── keyboard ──────────────────────────────────────────────────────────────
def _on_key(self, event: tk.Event):
char = event.char
if char in "0123456789.+-*/()%":
self._append(char)
elif char == "^":
self._append("**")
# ── evaluate ──────────────────────────────────────────────────────────────
def _evaluate(self):
if not self.expression:
return
try:
result = self._safe_eval(self.expression)
self.history_var.set(self.expression + " =")
if isinstance(result, float) and result.is_integer():
display = str(int(result))
else:
display = f"{result:.10g}"
self.expression = display
self._set_display(display)
self.result_shown = True
except ZeroDivisionError:
self._show_error("Error: ÷ 0")
except ValueError as exc:
self._show_error(f"Error: {exc}")
except Exception:
self._show_error("Error")
def _show_error(self, msg: str):
self._set_display(msg)
self.expression = ""
self.result_shown = False
def _safe_eval(self, expr: str):
expr = expr.replace("π", str(math.pi))
# Replace standalone 'e' constant but not 'e' in scientific notation (e.g. 1e5)
expr = re.sub(r"(?<![0-9Ee])e(?![0-9+\-])", str(math.e), expr)
rad = self.angle_mode == "RAD"
def _to_rad(x): return x if rad else math.radians(x)
def _from_rad(x): return x if rad else math.degrees(x)
safe_ns = {
"__builtins__": {},
"sin": lambda x: math.sin(_to_rad(x)),
"cos": lambda x: math.cos(_to_rad(x)),
"tan": lambda x: math.tan(_to_rad(x)),
"asin": lambda x: _from_rad(math.asin(x)),
"acos": lambda x: _from_rad(math.acos(x)),
"atan": lambda x: _from_rad(math.atan(x)),
"sqrt": math.sqrt,
"log": math.log10,
"ln": math.log,
"fact": math.factorial,
"abs": abs,
}
return eval(expr, safe_ns) # noqa: S307
if __name__ == "__main__":
app = Calculator()
app.mainloop()