import tkinter as tk from tkinter import ttk from typing import Dict, List from MaMa import MaMa class MaMaGUI: def __init__(self, mama: MaMa, delay: int = 400) -> None: # Autoplay speed self.delay = delay # Rather autoplay is activated self.do_autoplay = False # Run MaMa and create journal self.machine = mama self.journal: List[Dict[str, object]] = self.machine.run() if not self.journal: raise ValueError("Journal is empty.") self.journal_index = 0 # Bootstrap UI self.root = tk.Tk() self.root.title("MaMa GUI") container = ttk.Frame(self.root, padding=10) container.grid(sticky="nsew") container.columnconfigure((0, 1), weight=1) container.rowconfigure(0, weight=1) # Program display prog_frame = ttk.LabelFrame(container, text="Program", padding=5) prog_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) self.prog_list = tk.Listbox(prog_frame, width=70, height=24, font=("Consolas", 11)) self.prog_list.pack(fill="both", expand=True) # Stack display stack_frame = ttk.LabelFrame(container, text="Stack", padding=5) stack_frame.grid(row=0, column=1, sticky="nsew") stack_frame.rowconfigure(0, weight=1) self.stack_list = tk.Listbox(stack_frame, width=36, height=22, font=("Consolas", 11)) self.stack_list.grid(row=0, column=0, sticky="nsew") # Configuration display self.conf_label = ttk.Label(stack_frame, text="(s, p, S) = (-, -, [])", font=("Consolas", 11), anchor="e") self.conf_label.grid(row=1, column=0, sticky="ew", pady=(4, 0)) # Create and bind controls control = ttk.Frame(self.root, padding=5) control.grid(row=1, column=0, columnspan=2, sticky="ew") btns = [ ("▶ Start", self.__start), ("⏸ Stop", self.__stop), ("⏭ Next", self.__next_step), ("↩ Prev", self.__prev_step), ("⟲ Reset", self.__reset) ] for text, cmd in btns: ttk.Button(control, text=text, command=cmd).pack(side="left", padx=5) ttk.Label(control, text="Speed:").pack(side="left", padx=5) self.speed_scale = ttk.Scale(control, from_=2000, to=100, orient="horizontal", command=self.__update_speed) self.speed_scale.set(self.delay) self.speed_scale.pack(side="left", padx=5) # Global status bar self.status = ttk.Label(self.root, text="Ready", padding=5, anchor="w") self.status.grid(row=2, column=0, columnspan=2, sticky="ew") self.__update() def __update(self) -> None: # Load current journal entry state = self.journal[self.journal_index] stack = dict(state["stack"]) p_prog, p_stack = int(state["p_prog"]), int(state["p_stack"]) step, micro = int(state["step"]), str(state["micro"]) # Load code structure structure = self.machine.structure() self.prog_list.delete(0, tk.END) opened_macros: List[str] = [] macro_offset = 4 # Display code lines and unpack macros for addr, info in structure.items(): macros = info.get("macros", []) depth = len(macros) # Align macro headers at parent's instruction column if len(macros) > len(opened_macros): parent_depth = len(macros) - 1 header_indent = " " * parent_depth + " " * macro_offset self.prog_list.insert(tk.END, f"{header_indent}[{macros[-1]}]") self.prog_list.itemconfig(tk.END, {"bg": "#e6e6e6"}) instr_indent = " " * depth prefix = ">> " if addr == p_prog else " " line = f"{prefix}{instr_indent}{addr:>3}: {info['micro']}" self.prog_list.insert(tk.END, line) opened_macros = macros # Highlight current program line if addr == p_prog: self.prog_list.itemconfig(tk.END, {"bg": "#ffd966"}) # Visualize current stack self.stack_list.delete(0, tk.END) if stack: for i in range(p_stack, -1, -1): val = stack.get(i, 0) mark = "<-- top" if i == p_stack else "" self.stack_list.insert(tk.END, f"[{i:>2}] {val:>6} {mark}") if i == p_stack: self.stack_list.itemconfig(tk.END, {"bg": "#cfe2f3"}) else: self.stack_list.insert(tk.END, "Empty") # Update configuration and global stack self.conf_label.config(text=f"({p_stack}, {p_prog}, {list(stack.values())})") self.status.config(text=f"Step {step}/{len(self.journal)-1} | PC={p_prog} | SP={p_stack} | Micro: {micro}") # Configure auto step speed def __update_speed(self, value: str) -> None: self.delay = int(float(value)) # Start autoplay def __start(self) -> None: if not self.do_autoplay: self.do_autoplay = True self.__auto_step() # Stop autoplay def __stop(self) -> None: self.do_autoplay = False # Play an autoplay step def __auto_step(self) -> None: if self.do_autoplay and self.journal_index < len(self.journal) - 1: self.journal_index += 1 self.__update() self.root.after(self.delay, self.__auto_step) else: self.do_autoplay = False # Play next step def __next_step(self) -> None: if self.journal_index < len(self.journal) - 1: self.journal_index += 1 self.__update() # Play previous step def __prev_step(self) -> None: if self.journal_index > 0: self.journal_index -= 1 self.__update() # Reset machine def __reset(self) -> None: self.journal_index = 0 self.__update() # Run main loop def display(self) -> None: self.root.mainloop()