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("", 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("", 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")), ("xʸ", 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")), ("x²", 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("", lambda _e, b=btn, c=bg: b.config(bg=self._lighten(c))) btn.bind("", lambda _e, b=btn, c=bg: b.config(bg=c)) # keyboard bindings self.bind("", self._on_key) self.bind("", lambda _: self._evaluate()) self.bind("", lambda _: self._evaluate()) self.bind("", lambda _: self._backspace()) self.bind("", lambda _: self._clear_all()) self.bind("", lambda _: self._clear_all()) self.bind("", 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"(?