Загрузка данных


import customtkinter as ctk
import math

class CalculatorApp(ctk.CTk):
    def __init__(self):
        super().__init__()

        # Настройка окна
        self.title("Калькулятор")
        self.geometry("360x680")
        self.resizable(False, False)
        ctk.set_appearance_mode("dark")  # Темная тема как в Apple
        
        # Цвета (в стиле Apple Calculator)
        self.COLOR_BG = "#000000"
        self.COLOR_DISPLAY_BG = "#000000"
        self.COLOR_BTN_NUM = "#333333"
        self.COLOR_BTN_UTIL = "#A5A5A5"
        self.COLOR_BTN_OP = "#FF9F0A"
        self.COLOR_TEXT_NUM = "#FFFFFF"
        self.COLOR_TEXT_UTIL = "#000000"
        
        self.configure(fg_color=self.COLOR_BG)

        # Переменные состояния
        self.current = "0"
        self.previous = None
        self.operator = None
        self.waiting = False
        self.just_evaluated = False
        self.memory = 0.0
        self.history = []
        
        self.symbols = {"+": "+", "-": "−", "*": "×", "/": "÷"}

        self.setup_ui()

        # Привязка клавиатуры
        self.bind("<Key>", self.handle_keypress)

    def setup_ui(self):
        # --- Дисплей ---
        self.display_frame = ctk.CTkFrame(self, fg_color=self.COLOR_DISPLAY_BG)
        self.display_frame.pack(fill="x", padx=20, pady=(30, 10))
        
        self.mem_badge = ctk.CTkLabel(self.display_frame, text="M", font=("Arial", 14), text_color="#FF9F0A")
        self.mem_badge.pack(anchor="ne", padx=10, pady=0)
        self.mem_badge.pack_forget() # Скрываем по умолчанию

        self.expr_label = ctk.CTkLabel(self.display_frame, text="", font=("Arial", 18), text_color="#A5A5A5", anchor="e")
        self.expr_label.pack(fill="x", padx=10, pady=0)
        
        self.result_label = ctk.CTkLabel(self.display_frame, text="0", font=("Arial", 56), text_color="#FFFFFF", anchor="e")
        self.result_label.pack(fill="x", padx=10, pady=(0, 10))

        # --- Память ---
        self.mem_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.mem_frame.pack(fill="x", padx=15, pady=5)
        
        mem_buttons = [("MC", "mc"), ("MR", "mr"), ("M+", "m+"), ("M-", "m-")]
        for i, (text, cmd) in enumerate(mem_buttons):
            btn = ctk.CTkButton(self.mem_frame, text=text, font=("Arial", 14), fg_color="transparent", 
                                text_color="#A5A5A5", hover_color="#333333", corner_radius=20, width=60, height=30,
                                command=lambda c=cmd: self.memory_action(c))
            btn.grid(row=0, column=i, padx=5, sticky="ew")
            self.mem_frame.grid_columnconfigure(i, weight=1)

        # --- Клавиатура ---
        self.grid_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.grid_frame.pack(fill="both", expand=True, padx=15, pady=10)

        buttons_layout = [
            ("AC", "util", "clear"), ("⌫", "util", "back"), ("%", "util", "percent"), ("÷", "op", "/"),
            ("7", "num", "7"), ("8", "num", "8"), ("9", "num", "9"), ("×", "op", "*"),
            ("4", "num", "4"), ("5", "num", "5"), ("6", "num", "6"), ("−", "op", "-"),
            ("1", "num", "1"), ("2", "num", "2"), ("3", "num", "3"), ("+", "op", "+"),
            ("±", "util", "sign"), ("0", "num", "0"), (".", "num", "dot"), ("=", "op", "equals")
        ]

        for i, (text, btn_type, action) in enumerate(buttons_layout):
            row = i // 4
            col = i % 4
            
            fg_col = self.COLOR_BTN_NUM
            text_col = self.COLOR_TEXT_NUM
            
            if btn_type == "util":
                fg_col = self.COLOR_BTN_UTIL
                text_col = self.COLOR_TEXT_UTIL
            elif btn_type == "op":
                fg_col = self.COLOR_BTN_OP
                text_col = self.COLOR_TEXT_NUM
                
            btn = ctk.CTkButton(self.grid_frame, text=text, font=("Arial", 28), 
                                fg_color=fg_col, text_color=text_col, 
                                hover_color=self.adjust_color(fg_col), 
                                corner_radius=35, width=75, height=75,
                                command=lambda a=action, t=btn_type: self.button_click(a, t))
            
            btn.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
            
        for i in range(4):
            self.grid_frame.grid_columnconfigure(i, weight=1)
        for i in range(5):
            self.grid_frame.grid_rowconfigure(i, weight=1)

        # --- История ---
        self.history_frame = ctk.CTkFrame(self, fg_color="transparent", height=80)
        self.history_frame.pack(fill="x", padx=20, pady=(0, 20))
        self.render_history()

    def adjust_color(self, hex_color):
        # Простое затемнение цвета для эффекта наведения
        return hex_color + "CC" if hex_color != "transparent" else "#333333"

    def update_display(self):
        text = self.shown()
        self.result_label.configure(text=text)
        
        # Динамический размер шрифта
        if len(text) > 18:
            self.result_label.configure(font=("Arial", 32))
        elif len(text) > 12:
            self.result_label.configure(font=("Arial", 40))
        else:
            self.result_label.configure(font=("Arial", 56))
            
        if self.operator is not None and self.previous is not None:
            self.expr_label.configure(text=f"{self.fmt(self.previous)} {self.symbols[self.operator]}")
        else:
            self.expr_label.configure(text="")
            
        if self.memory != 0:
            self.mem_badge.pack(anchor="ne", padx=10, pady=0)
        else:
            self.mem_badge.pack_forget()

    def render_history(self):
        for widget in self.history_frame.winfo_children():
            widget.destroy()
            
        if not self.history:
            empty_lbl = ctk.CTkLabel(self.history_frame, text="История пуста", text_color="#555555", font=("Arial", 12))
            empty_lbl.pack(pady=10)
            return
            
        for item in self.history[:4]:
            btn = ctk.CTkButton(self.history_frame, text=f"{item['expr']} = {item['res']}", 
                                font=("Arial", 14), fg_color="transparent", text_color="#A5A5A5",
                                hover_color="#222222", anchor="w", height=25,
                                command=lambda raw=item['raw']: self.load_history(raw))
            btn.pack(fill="x", pady=1)

    def load_history(self, raw_val):
        self.current = raw_val
        self.previous = None
        self.operator = None
        self.waiting = False
        self.just_evaluated = True
        self.update_display()

    def fmt(self, n):
        if not math.isfinite(n):
            return "Ошибка"
        a = abs(n)
        if a != 0 and (a >= 1e12 or a < 1e-8):
            return f"{n:.6e}".replace(".", ",").replace("e+", "e").replace("e-", "e−")
        
        # Форматирование с пробелами для тысяч и запятой для дробей
        formatted = f"{n:,.10f}".rstrip('0').rstrip('.')
        if '.' in formatted:
            formatted = formatted.replace('.', ',')
        # Заменяем запятые (разделители тысяч) на пробелы
        return formatted.replace(',', ' ')

    def shown(self):
        if self.current == "Ошибка":
            return self.current
        if self.just_evaluated:
            return self.fmt(float(self.current))
        return self.current.replace('.', ',')

    def set_error(self):
        self.current = "Ошибка"
        self.previous = None
        self.operator = None
        self.waiting = False
        self.just_evaluated = True
        self.update_display()

    def input_digit(self, digit):
        if self.current == "Ошибка":
            self.current = "0"
            
        if self.waiting or self.just_evaluated:
            self.current = digit
            self.waiting = False
            self.just_evaluated = False
        else:
            if self.current == "0":
                self.current = digit
            elif len(self.current.replace("-", "").replace(".", "")) < 12:
                self.current += digit
        self.update_display()

    def input_dot(self):
        if self.current == "Ошибка":
            self.current = "0"
        if self.waiting or self.just_evaluated:
            self.current = "0."
            self.waiting = False
            self.just_evaluated = False
        elif "." not in self.current:
            self.current += "."
        self.update_display()

    def toggle_sign(self):
        if self.current in ["Ошибка", "0"]:
            return
        self.current = self.current[1:] if self.current.startswith("-") else "-" + self.current
        if self.current == "-0":
            self.current = "0"
        self.update_display()

    def percent(self):
        try:
            x = float(self.current)
        except ValueError:
            return
        
        if self.previous is not None and self.operator in ["+", "-"]:
            x = self.previous * x / 100
        else:
            x = x / 100
            
        x = round(x, 10)
        self.current = str(x)
        self.waiting = False
        self.just_evaluated = False
        self.update_display()

    def resolve(self, b):
        if self.previous is None or self.operator is None:
            return False
        try:
            if self.operator == "+":
                r = self.previous + b
            elif self.operator == "-":
                r = self.previous - b
            elif self.operator == "*":
                r = self.previous * b
            elif self.operator == "/":
                if b == 0:
                    self.set_error()
                    return False
                r = self.previous / b
            else:
                return False
                
            r = round(r, 10)
            if not math.isfinite(r) or abs(r) > 1e15:
                self.set_error()
                return False
                
            # Добавление в историю
            self.history.insert(0, {
                "expr": f"{self.fmt(self.previous)} {self.symbols[self.operator]} {self.fmt(b)}",
                "res": self.fmt(r),
                "raw": str(r)
            })
            self.history = self.history[:4]
            self.render_history()
            
            self.current = str(r)
            self.previous = None
            self.operator = None
            self.waiting = False
            self.just_evaluated = True
            self.update_display()
            return True
        except Exception:
            self.set_error()
            return False

    def set_operator(self, op):
        if self.current == "Ошибка":
            return
            
        if self.operator is not None and not self.waiting:
            if not self.resolve(float(self.current)):
                return
        else:
            self.just_evaluated = False
            
        self.previous = float(self.current)
        self.operator = op
        self.waiting = True
        self.update_display()

    def equals(self):
        if self.current == "Ошибка" or self.operator is None or self.previous is None:
            return
        b = self.previous if self.waiting else float(self.current)
        self.resolve(b)

    def backspace(self):
        if self.current == "Ошибка" or self.just_evaluated:
            self.clear_all()
            return
        if self.waiting:
            self.operator = None
            self.previous = None
            self.waiting = False
            self.update_display()
            return
            
        self.current = self.current[:-1] if len(self.current) > 1 else "0"
        if self.current in ["-", "-0"]:
            self.current = "0"
        self.update_display()

    def clear_all(self):
        self.current = "0"
        self.previous = None
        self.operator = None
        self.waiting = False
        self.just_evaluated = False
        self.update_display()

    def memory_action(self, action):
        try:
            x = float(self.current)
        except ValueError:
            return
            
        if action == "mc":
            self.memory = 0.0
        elif action == "mr":
            if math.isfinite(self.memory):
                self.current = str(self.memory)
                self.previous = None
                self.operator = None
                self.waiting = False
                self.just_evaluated = True
        elif action == "m+":
            self.memory = round(self.memory + x, 10)
        elif action == "m-":
            self.memory = round(self.memory - x, 10)
        self.update_display()

    def button_click(self, action, btn_type):
        if btn_type == "num" or (btn_type == "util" and action in ["sign", "dot"]):
            if action == "dot": self.input_dot()
            elif action == "sign": self.toggle_sign()
            else: self.input_digit(action)
        elif btn_type == "op":
            if action == "equals": self.equals()
            else: self.set_operator(action)
        elif btn_type == "util":
            if action == "clear": self.clear_all()
            elif action == "back": self.backspace()
            elif action == "percent": self.percent()

    def handle_keypress(self, event):
        k = event.char
        if k.isdigit():
            self.input_digit(k)
        elif k in [".", ","]:
            self.input_dot()
        elif k in ["+", "-", "*", "/"]:
            self.set_operator(k)
        elif k in ["\r", "="]:
            self.equals()
        elif event.keysym == "BackSpace":
            self.backspace()
        elif event.keysym in ["Escape", "Delete"]:
            self.clear_all()
        elif k == "%":
            self.percent()

if __name__ == "__main__":
    app = CalculatorApp()
    app.mainloop()