feat: add cryptography fundamentals lab scripts
This commit is contained in:
511
XOR_visualizer.py
Normal file
511
XOR_visualizer.py
Normal file
@@ -0,0 +1,511 @@
|
||||
"""
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ XOR Bit Visualizer — Fundamentos de Criptografía ║
|
||||
║ Herramienta pedagógica interactiva ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
|
||||
Requiere Python 3.x (tkinter incluido en la stdlib)
|
||||
Ejecutar: python xor_visualizer.py
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import threading
|
||||
import time
|
||||
|
||||
# ── Paleta de colores ─────────────────────────────────────────────────────────
|
||||
C = {
|
||||
"bg": "#0d0d1a",
|
||||
"panel": "#13132b",
|
||||
"panel2": "#1a1a35",
|
||||
"border": "#2a2a50",
|
||||
"text": "#dde1f0",
|
||||
"muted": "#555580",
|
||||
"a_col": "#38bdf8", # color para operando A (azul cielo)
|
||||
"b_col": "#fb923c", # color para operando B / clave (naranja)
|
||||
"r_col": "#4ade80", # color para resultado (verde)
|
||||
"bit1_bg": "#312e81", # fondo bit = 1 (índigo)
|
||||
"bit0_bg": "#1e1e3a", # fondo bit = 0
|
||||
"flip_bg": "#7f1d1d", # fondo bit que cambió (rojo oscuro)
|
||||
"same_bg": "#14532d", # fondo bit sin cambio (verde oscuro)
|
||||
"flip_fg": "#fca5a5", # texto bit que cambió
|
||||
"same_fg": "#86efac", # texto bit sin cambio
|
||||
"accent": "#818cf8", # violeta
|
||||
"btn": "#4f46e5",
|
||||
"btn_ok": "#15803d",
|
||||
"btn_warn": "#b45309",
|
||||
}
|
||||
|
||||
MONO = ("Consolas", 10)
|
||||
MONO_LG = ("Consolas", 13, "bold")
|
||||
MONO_XL = ("Consolas", 18, "bold")
|
||||
|
||||
|
||||
# ── Utilidades binarias ───────────────────────────────────────────────────────
|
||||
def to_bin_grouped(text: str) -> str:
|
||||
"""Convierte texto a binario, un byte por carácter, separados por espacio."""
|
||||
return " ".join(format(ord(c), "08b") for c in text)
|
||||
|
||||
|
||||
def xor_with_key(text: str, key: str) -> str:
|
||||
"""XOR del texto con la clave (clave cíclica si es más corta)."""
|
||||
return "".join(chr(ord(c) ^ ord(key[i % len(key)])) for i, c in enumerate(text))
|
||||
|
||||
|
||||
# ── Clase principal ───────────────────────────────────────────────────────────
|
||||
class XORVisualizer:
|
||||
def __init__(self, root: tk.Tk):
|
||||
self.root = root
|
||||
self.root.title("XOR Bit Visualizer — Criptografía")
|
||||
self.root.configure(bg=C["bg"])
|
||||
self.root.geometry("1180x720")
|
||||
self.root.minsize(900, 600)
|
||||
|
||||
self._animating = False
|
||||
self._current_result = "" # resultado guardado para descifrado demo
|
||||
|
||||
self._build_ui()
|
||||
# Valores iniciales
|
||||
self.var_a.set("Hola")
|
||||
self.var_k.set("K")
|
||||
|
||||
# ── Construcción de la interfaz ───────────────────────────────────────────
|
||||
def _build_ui(self):
|
||||
self._build_title()
|
||||
|
||||
# Zona central (3 paneles I/O + bit-viz)
|
||||
center = tk.Frame(self.root, bg=C["bg"])
|
||||
center.pack(fill=tk.BOTH, expand=True, padx=14, pady=(0, 8))
|
||||
|
||||
self._build_io_row(center)
|
||||
self._build_bitviz(center)
|
||||
self._build_bottom(center)
|
||||
|
||||
# ── Barra de título ───────────────────────────────────────────────────────
|
||||
def _build_title(self):
|
||||
bar = tk.Frame(self.root, bg=C["panel"], pady=10)
|
||||
bar.pack(fill=tk.X)
|
||||
|
||||
tk.Label(bar, text="⊕ XOR Bit Visualizer",
|
||||
font=("Consolas", 15, "bold"),
|
||||
bg=C["panel"], fg=C["accent"]).pack(side=tk.LEFT, padx=18)
|
||||
|
||||
tk.Label(bar, text="Herramienta Pedagógica · Fundamentos de Criptografía",
|
||||
font=MONO, bg=C["panel"], fg=C["muted"]).pack(side=tk.RIGHT, padx=18)
|
||||
|
||||
# ── Fila superior: A | XOR-op | B | → | Resultado ─────────────────────────
|
||||
def _build_io_row(self, parent):
|
||||
row = tk.Frame(parent, bg=C["bg"])
|
||||
row.pack(fill=tk.X, pady=(10, 6))
|
||||
|
||||
# ---- Panel A ----
|
||||
self.var_a = tk.StringVar()
|
||||
self.bin_a = self._io_panel(row, "OPERANDO A (texto plano)",
|
||||
self.var_a, C["a_col"], editable=True)
|
||||
self.var_a.trace_add("write", self._on_a_change)
|
||||
|
||||
# ---- Símbolo ⊕ ----
|
||||
op = tk.Frame(row, bg=C["bg"])
|
||||
op.pack(side=tk.LEFT, padx=4)
|
||||
tk.Label(op, text="⊕", font=("Consolas", 30, "bold"),
|
||||
bg=C["bg"], fg=C["accent"]).pack(expand=True)
|
||||
|
||||
# ---- Panel K (clave) ----
|
||||
self.var_k = tk.StringVar()
|
||||
self.bin_k = self._io_panel(row, "CLAVE B (key — cíclica)",
|
||||
self.var_k, C["b_col"], editable=True)
|
||||
self.var_k.trace_add("write", self._on_k_change)
|
||||
|
||||
# ---- Botones centrales ----
|
||||
btn_col = tk.Frame(row, bg=C["bg"], padx=6)
|
||||
btn_col.pack(side=tk.LEFT)
|
||||
|
||||
self.btn_xor = self._btn(btn_col, "Cifrar A ⊕ B →",
|
||||
C["btn"], self._start_xor)
|
||||
self.btn_xor.pack(fill=tk.X, pady=(0, 6))
|
||||
|
||||
self.btn_dec = self._btn(btn_col, "⟳ Descifrar (R ⊕ B)",
|
||||
C["btn_warn"], self._start_decrypt)
|
||||
self.btn_dec.pack(fill=tk.X, pady=(0, 6))
|
||||
self.btn_dec.config(state=tk.DISABLED)
|
||||
|
||||
self._btn(btn_col, "Reset", C["muted"], self._reset).pack(fill=tk.X)
|
||||
|
||||
# ---- Panel Resultado ----
|
||||
self.var_r = tk.StringVar()
|
||||
self.bin_r = self._io_panel(row, "RESULTADO A ⊕ B",
|
||||
self.var_r, C["r_col"], editable=False)
|
||||
|
||||
# ── Panel I/O genérico ────────────────────────────────────────────────────
|
||||
def _io_panel(self, parent, title, str_var, color, editable=True):
|
||||
outer = tk.Frame(parent, bg=color, padx=2, pady=2)
|
||||
outer.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
|
||||
|
||||
inner = tk.Frame(outer, bg=C["panel"], padx=10, pady=8)
|
||||
inner.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Cabecera
|
||||
tk.Label(inner, text=title, font=("Consolas", 9, "bold"),
|
||||
bg=C["panel"], fg=color).pack(anchor=tk.W)
|
||||
|
||||
# ASCII
|
||||
tk.Label(inner, text="ASCII", font=("Consolas", 8),
|
||||
bg=C["panel"], fg=C["muted"]).pack(anchor=tk.W, pady=(6, 0))
|
||||
|
||||
entry = tk.Entry(inner, textvariable=str_var, font=MONO_LG,
|
||||
bg=C["bg"], fg=color, insertbackground=color,
|
||||
relief=tk.FLAT, bd=4,
|
||||
state=tk.NORMAL if editable else tk.DISABLED)
|
||||
entry.pack(fill=tk.X, pady=(2, 8))
|
||||
|
||||
# Binario
|
||||
tk.Label(inner, text="BINARIO (8 bits / carácter)", font=("Consolas", 8),
|
||||
bg=C["panel"], fg=C["muted"]).pack(anchor=tk.W)
|
||||
|
||||
bin_box = tk.Text(inner, font=("Consolas", 9), bg=C["bg"], fg=color,
|
||||
relief=tk.FLAT, bd=4, height=3, wrap=tk.WORD,
|
||||
state=tk.DISABLED)
|
||||
bin_box.pack(fill=tk.BOTH, pady=(2, 0))
|
||||
|
||||
return bin_box # retornamos para actualizarlo desde código
|
||||
|
||||
# ── Visualización bit a bit ───────────────────────────────────────────────
|
||||
def _build_bitviz(self, parent):
|
||||
frame = tk.LabelFrame(parent, text=" Visualización bit a bit ",
|
||||
font=("Consolas", 9, "bold"),
|
||||
bg=C["panel2"], fg=C["accent"],
|
||||
relief=tk.FLAT,
|
||||
highlightbackground=C["border"],
|
||||
highlightthickness=1)
|
||||
frame.pack(fill=tk.BOTH, expand=True, pady=(4, 6))
|
||||
|
||||
self.canvas = tk.Canvas(frame, bg=C["panel2"],
|
||||
height=130, highlightthickness=0)
|
||||
hbar = ttk.Scrollbar(frame, orient=tk.HORIZONTAL,
|
||||
command=self.canvas.xview)
|
||||
self.canvas.configure(xscrollcommand=hbar.set)
|
||||
hbar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
self.canvas.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)
|
||||
|
||||
# Placeholder inicial
|
||||
self.canvas.create_text(20, 65, anchor=tk.W,
|
||||
text="Escribe texto y clave, luego presiona «Cifrar A ⊕ B»",
|
||||
font=MONO, fill=C["muted"])
|
||||
|
||||
# ── Zona inferior: tabla de verdad + propiedades + leyenda ────────────────
|
||||
def _build_bottom(self, parent):
|
||||
row = tk.Frame(parent, bg=C["bg"])
|
||||
row.pack(fill=tk.X)
|
||||
|
||||
self._build_truth_table(row)
|
||||
self._build_properties(row)
|
||||
self._build_legend(row)
|
||||
|
||||
def _build_truth_table(self, parent):
|
||||
frame = tk.LabelFrame(parent, text=" Tabla de Verdad XOR ",
|
||||
font=("Consolas", 9, "bold"),
|
||||
bg=C["panel2"], fg=C["accent"], relief=tk.FLAT)
|
||||
frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 8))
|
||||
|
||||
headers = ["A", "B", "A ⊕ B"]
|
||||
for j, h in enumerate(headers):
|
||||
tk.Label(frame, text=h, font=("Consolas", 10, "bold"),
|
||||
bg=C["panel2"], fg=C["accent"],
|
||||
padx=14, pady=4).grid(row=0, column=j)
|
||||
|
||||
rows = [("0", "0", "0"), ("0", "1", "1"),
|
||||
("1", "0", "1"), ("1", "1", "0")]
|
||||
for i, (a, b, r) in enumerate(rows, 1):
|
||||
for j, val in enumerate([a, b]):
|
||||
tk.Label(frame, text=val, font=("Consolas", 11),
|
||||
bg=C["panel2"], fg=C["text"],
|
||||
padx=14, pady=3).grid(row=i, column=j)
|
||||
fg = C["r_col"] if r == "1" else C["muted"]
|
||||
tk.Label(frame, text=r, font=("Consolas", 11, "bold"),
|
||||
bg=C["panel2"], fg=fg,
|
||||
padx=14, pady=3).grid(row=i, column=2)
|
||||
|
||||
def _build_properties(self, parent):
|
||||
frame = tk.LabelFrame(parent, text=" Propiedades algebraicas del XOR ",
|
||||
font=("Consolas", 9, "bold"),
|
||||
bg=C["panel2"], fg=C["accent"], relief=tk.FLAT)
|
||||
frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 8))
|
||||
|
||||
props = [
|
||||
("Identidad", "A ⊕ 0 = A"),
|
||||
("Auto-inverso", "A ⊕ A = 0"),
|
||||
("Conmutatividad", "A ⊕ B = B ⊕ A"),
|
||||
("Asociatividad", "(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)"),
|
||||
("★ Descifrado", "(A ⊕ B) ⊕ B = A ← ¡fundamento del cifrado!"),
|
||||
]
|
||||
for i, (name, expr) in enumerate(props):
|
||||
fg_name = C["b_col"] if "★" in name else C["muted"]
|
||||
tk.Label(frame, text=f"{name}:", font=("Consolas", 9, "bold"),
|
||||
bg=C["panel2"], fg=fg_name, anchor=tk.W,
|
||||
padx=10, pady=3).grid(row=i, column=0, sticky=tk.W)
|
||||
tk.Label(frame, text=expr, font=("Consolas", 9),
|
||||
bg=C["panel2"], fg=C["accent"], anchor=tk.W,
|
||||
padx=6, pady=3).grid(row=i, column=1, sticky=tk.W)
|
||||
|
||||
def _build_legend(self, parent):
|
||||
frame = tk.LabelFrame(parent, text=" Leyenda bits ",
|
||||
font=("Consolas", 9, "bold"),
|
||||
bg=C["panel2"], fg=C["accent"], relief=tk.FLAT)
|
||||
frame.pack(side=tk.LEFT, fill=tk.Y)
|
||||
|
||||
items = [
|
||||
(C["bit1_bg"], C["text"], "■ Bit = 1"),
|
||||
(C["bit0_bg"], C["muted"], "■ Bit = 0"),
|
||||
(C["flip_bg"], C["flip_fg"], "■ Bit modificado (XOR=1)"),
|
||||
(C["same_bg"], C["same_fg"], "■ Bit sin cambio (XOR=0)"),
|
||||
]
|
||||
for i, (bg, fg, label) in enumerate(items):
|
||||
tk.Label(frame, text=label, font=("Consolas", 9),
|
||||
bg=bg, fg=fg, padx=10, pady=5,
|
||||
anchor=tk.W).pack(fill=tk.X, padx=6, pady=2)
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||
@staticmethod
|
||||
def _btn(parent, text, color, cmd):
|
||||
return tk.Button(parent, text=text, font=("Consolas", 9, "bold"),
|
||||
bg=color, fg="white", relief=tk.FLAT,
|
||||
padx=10, pady=6, cursor="hand2", command=cmd)
|
||||
|
||||
def _set_text_widget(self, widget, text):
|
||||
widget.config(state=tk.NORMAL)
|
||||
widget.delete("1.0", tk.END)
|
||||
widget.insert("1.0", text)
|
||||
widget.config(state=tk.DISABLED)
|
||||
|
||||
def _set_entry(self, widget, var, text):
|
||||
widget.config(state=tk.NORMAL)
|
||||
var.set(text)
|
||||
widget.config(state=tk.DISABLED)
|
||||
|
||||
# ── Callbacks de cambio en entradas ──────────────────────────────────────
|
||||
def _on_a_change(self, *_):
|
||||
self._set_text_widget(self.bin_a, to_bin_grouped(self.var_a.get()))
|
||||
self._clear_result()
|
||||
|
||||
def _on_k_change(self, *_):
|
||||
self._set_text_widget(self.bin_k, to_bin_grouped(self.var_k.get()))
|
||||
self._clear_result()
|
||||
|
||||
def _clear_result(self):
|
||||
self._set_text_widget(self.bin_r, "")
|
||||
self.var_r.set("")
|
||||
self._current_result = ""
|
||||
self.canvas.delete("all")
|
||||
self.canvas.create_text(20, 65, anchor=tk.W,
|
||||
text="Escribe texto y clave, luego presiona «Cifrar A ⊕ B»",
|
||||
font=MONO, fill=C["muted"])
|
||||
self.btn_dec.config(state=tk.DISABLED)
|
||||
|
||||
def _reset(self):
|
||||
if self._animating:
|
||||
return
|
||||
self.var_a.set("")
|
||||
self.var_k.set("")
|
||||
self._clear_result()
|
||||
|
||||
# ── Lógica de animación XOR ───────────────────────────────────────────────
|
||||
def _start_xor(self):
|
||||
if self._animating:
|
||||
return
|
||||
text = self.var_a.get()
|
||||
key = self.var_k.get()
|
||||
if not text or not key:
|
||||
self._flash_warning("⚠ Escribe texto y clave antes de aplicar XOR")
|
||||
return
|
||||
self._run_animation(text, key,
|
||||
result_target_var=self.var_r,
|
||||
result_target_bin=self.bin_r,
|
||||
store_as_result=True)
|
||||
|
||||
def _start_decrypt(self):
|
||||
"""Demuestra que (A⊕B)⊕B = A (propiedad de descifrado)."""
|
||||
if self._animating or not self._current_result:
|
||||
return
|
||||
key = self.var_k.get()
|
||||
if not key:
|
||||
return
|
||||
self._run_animation(self._current_result, key,
|
||||
result_target_var=self.var_a,
|
||||
result_target_bin=self.bin_a,
|
||||
store_as_result=False,
|
||||
decrypt_mode=True)
|
||||
|
||||
def _run_animation(self, text, key, *,
|
||||
result_target_var, result_target_bin,
|
||||
store_as_result, decrypt_mode=False):
|
||||
self._animating = True
|
||||
self.btn_xor.config(state=tk.DISABLED, text="Calculando…")
|
||||
self.btn_dec.config(state=tk.DISABLED)
|
||||
|
||||
t = threading.Thread(
|
||||
target=self._animate_worker,
|
||||
args=(text, key, result_target_var, result_target_bin,
|
||||
store_as_result, decrypt_mode),
|
||||
daemon=True
|
||||
)
|
||||
t.start()
|
||||
|
||||
def _animate_worker(self, text, key,
|
||||
var_out, bin_out,
|
||||
store_as_result, decrypt_mode):
|
||||
partial_result = ""
|
||||
bytes_a, bytes_b, bytes_r = [], [], []
|
||||
|
||||
for i, ch in enumerate(text):
|
||||
k = key[i % len(key)]
|
||||
a_byte = format(ord(ch), "08b")
|
||||
b_byte = format(ord(k), "08b")
|
||||
r_char = chr(ord(ch) ^ ord(k))
|
||||
r_byte = format(ord(r_char), "08b")
|
||||
|
||||
partial_result += r_char
|
||||
bytes_a.append(a_byte)
|
||||
bytes_b.append(b_byte)
|
||||
bytes_r.append(r_byte)
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
pr = partial_result # captura para closure
|
||||
ba = list(bytes_a)
|
||||
bb = list(bytes_b)
|
||||
br = list(bytes_r)
|
||||
|
||||
self.root.after(0, lambda pr=pr, ba=ba, bb=bb, br=br:
|
||||
self._update_result(pr, ba, bb, br, var_out, bin_out))
|
||||
|
||||
self.root.after(0, lambda: self._finish(partial_result,
|
||||
store_as_result, decrypt_mode))
|
||||
|
||||
def _update_result(self, partial, bytes_a, bytes_b, bytes_r,
|
||||
var_out, bin_out):
|
||||
# ASCII display (editable temporalmente)
|
||||
bin_out.config(state=tk.NORMAL)
|
||||
bin_out.delete("1.0", tk.END)
|
||||
bin_out.insert("1.0", " ".join(bytes_r))
|
||||
bin_out.config(state=tk.DISABLED)
|
||||
|
||||
printable = "".join(
|
||||
c if 32 <= ord(c) < 127 else f"[{ord(c):02X}]"
|
||||
for c in partial
|
||||
)
|
||||
var_out.set(printable)
|
||||
|
||||
self._draw_bits(bytes_a, bytes_b, bytes_r)
|
||||
|
||||
def _finish(self, result, store_as_result, decrypt_mode):
|
||||
self._animating = False
|
||||
self.btn_xor.config(state=tk.NORMAL, text="Cifrar A ⊕ B →")
|
||||
|
||||
if store_as_result:
|
||||
self._current_result = result
|
||||
self.btn_dec.config(state=tk.NORMAL)
|
||||
else:
|
||||
# Fin del descifrado: restaurar texto original en var_a
|
||||
printable = "".join(
|
||||
c if 32 <= ord(c) < 127 else f"[{ord(c):02X}]"
|
||||
for c in result
|
||||
)
|
||||
self.var_a.set(printable)
|
||||
self._set_text_widget(self.bin_a, to_bin_grouped(result))
|
||||
if self._current_result:
|
||||
self.btn_dec.config(state=tk.NORMAL)
|
||||
|
||||
# ── Dibujo del canvas bit a bit ───────────────────────────────────────────
|
||||
def _draw_bits(self, bytes_a, bytes_b, bytes_r):
|
||||
cv = self.canvas
|
||||
cv.delete("all")
|
||||
|
||||
CW, CH = 20, 26 # ancho y alto de cada celda de bit
|
||||
GAP = 2 # espacio entre celdas
|
||||
SEP = 8 # separador entre bytes
|
||||
ROW_GAP = 6 # espacio entre filas
|
||||
OX, OY = 48, 6 # origen
|
||||
|
||||
row_meta = [
|
||||
("A", C["a_col"]),
|
||||
("B", C["b_col"]),
|
||||
("⊕", C["r_col"]),
|
||||
]
|
||||
|
||||
# Etiquetas de fila
|
||||
for r, (label, color) in enumerate(row_meta):
|
||||
y_ctr = OY + r * (CH + ROW_GAP) + CH // 2
|
||||
cv.create_text(OX - 10, y_ctr, text=label, anchor=tk.E,
|
||||
font=("Consolas", 9, "bold"), fill=color)
|
||||
|
||||
for ci, (ba, bb, br) in enumerate(zip(bytes_a, bytes_b, bytes_r)):
|
||||
byte_x = OX + ci * (8 * (CW + GAP) + SEP)
|
||||
|
||||
for bi in range(8):
|
||||
x = byte_x + bi * (CW + GAP)
|
||||
a_bit = ba[bi]; b_bit = bb[bi]; r_bit = br[bi]
|
||||
bits = [a_bit, b_bit, r_bit]
|
||||
|
||||
for ri, bit in enumerate(bits):
|
||||
y = OY + ri * (CH + ROW_GAP)
|
||||
label_color = row_meta[ri][1]
|
||||
|
||||
if ri < 2:
|
||||
# Operandos A y B
|
||||
bg = C["bit1_bg"] if bit == "1" else C["bit0_bg"]
|
||||
fg = label_color if bit == "1" else C["muted"]
|
||||
else:
|
||||
# Resultado — resaltar si el bit cambió
|
||||
flipped = (a_bit != r_bit)
|
||||
if flipped:
|
||||
bg = C["flip_bg"]
|
||||
fg = C["flip_fg"]
|
||||
else:
|
||||
bg = C["same_bg"]
|
||||
fg = C["same_fg"]
|
||||
|
||||
cv.create_rectangle(x, y, x + CW, y + CH,
|
||||
fill=bg, outline=C["bg"], width=1)
|
||||
cv.create_text(x + CW // 2, y + CH // 2, text=bit,
|
||||
font=("Consolas", 9, "bold"), fill=fg)
|
||||
|
||||
# Separador entre bytes (línea vertical)
|
||||
if ci < len(bytes_a) - 1:
|
||||
sep_x = byte_x + 8 * (CW + GAP) + SEP // 2
|
||||
cv.create_line(sep_x, OY,
|
||||
sep_x, OY + 3 * (CH + ROW_GAP) - ROW_GAP,
|
||||
fill=C["border"], width=1, dash=(3, 3))
|
||||
|
||||
# Etiqueta del carácter encima
|
||||
char_label_x = byte_x + 4 * (CW + GAP) - CW // 2
|
||||
# (espacio muy limitado; omitimos para no aglomerar)
|
||||
|
||||
# Actualizar scrollregion
|
||||
total_w = OX + len(bytes_a) * (8 * (CW + GAP) + SEP) + 20
|
||||
cv.configure(scrollregion=(0, 0, total_w, 130))
|
||||
|
||||
# ── Flash de advertencia ──────────────────────────────────────────────────
|
||||
def _flash_warning(self, msg):
|
||||
win = tk.Toplevel(self.root)
|
||||
win.title("")
|
||||
win.configure(bg="#7f1d1d")
|
||||
win.resizable(False, False)
|
||||
tk.Label(win, text=msg, font=MONO, bg="#7f1d1d",
|
||||
fg="#fca5a5", padx=24, pady=14).pack()
|
||||
win.after(2000, win.destroy)
|
||||
win.grab_set()
|
||||
|
||||
|
||||
# ── Punto de entrada ──────────────────────────────────────────────────────────
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = XORVisualizer(root)
|
||||
|
||||
# Centrar la ventana en pantalla
|
||||
root.update_idletasks()
|
||||
w, h = root.winfo_width(), root.winfo_height()
|
||||
sw = root.winfo_screenwidth()
|
||||
sh = root.winfo_screenheight()
|
||||
root.geometry(f"{w}x{h}+{(sw - w)//2}+{(sh - h)//2}")
|
||||
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user