Creación de interfaces de usuario en terminal con estilo Dracula
ncurses es una biblioteca de programación que proporciona una API para desarrollar interfaces de usuario en modo texto. Permite crear aplicaciones con menús, ventanas, colores y más, directamente en la terminal.
Nota: ncurses es la versión nueva de la biblioteca curses, que originalmente fue desarrollada para sistemas UNIX.
Para usar ncurses en Python, necesitamos el módulo curses
, que viene incluido en la biblioteca estándar en sistemas UNIX. Para Windows, puedes usar windows-curses
:
# Instalación en Windows (si es necesario)
pip install windows-curses
Antes de usar ncurses, necesitamos inicializar el sistema. La función curses.initscr()
hace esto:
import curses
def main(stdscr):
# Configuración inicial
curses.curs_set(0) # Ocultar cursor
stdscr.clear()
stdscr.refresh()
# Tu código aquí...
stdscr.getch() # Esperar entrada de usuario
if __name__ == "__main__":
curses.wrapper(main)
Mejor práctica: Siempre usa curses.wrapper()
que maneja adecuadamente la inicialización y limpieza, incluso si ocurren errores.
En ncurses trabajamos con:
ncurses usa un sistema de coordenadas (y, x) donde:
Las funciones principales para mostrar texto son:
Función | Descripción |
---|---|
addstr(y, x, text) |
Escribe texto en la posición (y, x) |
addch(y, x, char) |
Escribe un solo carácter en (y, x) |
insstr(y, x, text) |
Inserta texto desplazando el existente |
delch(y, x) |
Borra un carácter |
import curses
def main(stdscr):
curses.curs_set(0)
stdscr.clear()
# Mostrar texto en diferentes posiciones
stdscr.addstr(5, 10, "¡Hola, mundo con ncurses!")
stdscr.addstr(7, 10, "Presiona cualquier tecla para continuar...")
stdscr.refresh()
stdscr.getch()
curses.wrapper(main)
Puedes aplicar atributos al texto para cambiar su apariencia:
Atributo | Descripción |
---|---|
curses.A_BOLD |
Texto en negrita |
curses.A_UNDERLINE |
Texto subrayado |
curses.A_REVERSE |
Invertir colores de fondo/primer plano |
curses.A_BLINK |
Texto parpadeante |
def main(stdscr):
curses.curs_set(0)
stdscr.clear()
# Texto con diferentes atributos
stdscr.addstr(5, 10, "Texto normal")
stdscr.addstr(6, 10, "Texto en negrita", curses.A_BOLD)
stdscr.addstr(7, 10, "Texto subrayado", curses.A_UNDERLINE)
stdscr.addstr(8, 10, "Texto invertido", curses.A_REVERSE)
stdscr.addstr(9, 10, "Texto parpadeante", curses.A_BLINK)
stdscr.refresh()
stdscr.getch()
curses.wrapper(main)
ncurses permite trabajar con colores. Primero debemos inicializar el sistema de colores:
curses.start_color() # Inicializar sistema de colores
Los colores se definen en pares (fondo, texto). Puedes crear tus propios pares:
curses.init_pair(pair_number, foreground, background)
def init_colors():
# Definir colores Dracula
curses.init_color(100, 40, 42, 54) # Fondo: #282a36
curses.init_color(101, 248, 248, 242) # Texto: #f8f8f2
curses.init_color(102, 189, 147, 249) # Purpura: #bd93f9
curses.init_color(103, 255, 121, 198) # Rosa: #ff79c6
curses.init_color(104, 80, 250, 123) # Verde: #50fa7b
# Definir pares de color
curses.init_pair(1, 101, 100) # Texto normal
curses.init_pair(2, 102, 100) # Destacado 1
curses.init_pair(3, 103, 100) # Destacado 2
curses.init_pair(4, 104, 100) # Éxito
def main(stdscr):
curses.curs_set(0)
curses.start_color()
init_colors()
stdscr.bkgd(' ', curses.color_pair(1))
stdscr.clear()
stdscr.addstr(5, 10, "Título importante", curses.color_pair(2) | curses.A_BOLD)
stdscr.addstr(7, 10, "Mensaje informativo", curses.color_pair(1))
stdscr.addstr(9, 10, "Acción exitosa!", curses.color_pair(4))
stdscr.refresh()
stdscr.getch()
curses.wrapper(main)
ncurses incluye algunos colores básicos:
Constante | Color |
---|---|
curses.COLOR_BLACK |
Negro |
curses.COLOR_RED |
Rojo |
curses.COLOR_GREEN |
Verde |
curses.COLOR_YELLOW |
Amarillo |
curses.COLOR_BLUE |
Azul |
curses.COLOR_MAGENTA |
Magenta |
curses.COLOR_CYAN |
Cian |
curses.COLOR_WHITE |
Blanco |
Además de la ventana principal (stdscr
), puedes crear ventanas independientes:
newwin(height, width, begin_y, begin_x)
def main(stdscr):
curses.curs_set(0)
curses.start_color()
stdscr.clear()
# Crear una ventana secundaria
win = curses.newwin(10, 40, 5, 10)
win.border()
win.addstr(2, 2, "Esta es una ventana secundaria")
win.addstr(4, 2, "Puede tener su propio contenido")
win.refresh()
stdscr.addstr(16, 10, "Presiona cualquier tecla para salir...")
stdscr.refresh()
stdscr.getch()
curses.wrapper(main)
Método | Descripción |
---|---|
border([ls[, rs[, ts[, bs[, tl[, tr[, bl[, br]]]]]]]) |
Dibuja un borde alrededor de la ventana |
box([vertch, horch]) |
Dibuja un borde simple |
clear() |
Limpia la ventana |
refresh() |
Actualiza la ventana |
move(y, x) |
Mueve el cursor a (y, x) |
nodelay(bool) |
Hace que getch() sea no bloqueante |
La función principal para obtener entrada es getch()
:
def main(stdscr):
curses.curs_set(0)
stdscr.clear()
stdscr.addstr(5, 10, "Presiona una tecla (q para salir)")
while True:
key = stdscr.getch()
stdscr.addstr(7, 10, f"Tecla presionada: {key} (carácter: {chr(key) if 32 <= key <= 126 else '?'})")
stdscr.clrtoeol() # Limpiar hasta el final de la línea
if key == ord('q'):
break
stdscr.addstr(9, 10, "Saliendo...")
stdscr.refresh()
curses.napms(1000) # Esperar 1 segundo
curses.wrapper(main)
ncurses define constantes para teclas especiales:
Constante | Tecla |
---|---|
curses.KEY_UP |
Flecha arriba |
curses.KEY_DOWN |
Flecha abajo |
curses.KEY_LEFT |
Flecha izquierda |
curses.KEY_RIGHT |
Flecha derecha |
curses.KEY_HOME |
Home |
curses.KEY_END |
End |
curses.KEY_BACKSPACE |
Backspace |
def main(stdscr):
curses.curs_set(0)
stdscr.keypad(True) # Habilitar detección de teclas especiales
stdscr.clear()
y, x = 10, 10
stdscr.addstr(y, x, "★")
stdscr.addstr(20, 10, "Usa las flechas para mover la estrella. Q para salir")
while True:
key = stdscr.getch()
if key == ord('q'):
break
elif key == curses.KEY_UP and y > 0:
y -= 1
elif key == curses.KEY_DOWN and y < curses.LINES - 1:
y += 1
elif key == curses.KEY_LEFT and x > 0:
x -= 1
elif key == curses.KEY_RIGHT and x < curses.COLS - 1:
x += 1
stdscr.clear()
stdscr.addstr(y, x, "★")
stdscr.addstr(20, 10, "Usa las flechas para mover la estrella. Q para salir")
stdscr.refresh()
stdscr.addstr(22, 10, "Saliendo...")
stdscr.refresh()
curses.napms(500)
curses.wrapper(main)
Vamos a crear un editor de texto básico con ncurses:
import curses
class TextEditor:
def __init__(self, stdscr):
self.stdscr = stdscr
self.text = []
self.cursor_y = 0
self.cursor_x = 0
self.init_ui()
def init_ui(self):
curses.curs_set(1) # Cursor visible
self.stdscr.keypad(True)
self.stdscr.clear()
# Configurar colores
curses.start_color()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE)
self.status_win = curses.newwin(1, curses.COLS, curses.LINES-1, 0)
def draw_status(self):
self.status_win.clear()
status = f"Línea: {self.cursor_y+1}/{len(self.text)} | Col: {self.cursor_x+1}"
self.status_win.addstr(0, 0, status, curses.color_pair(2))
self.status_win.refresh()
def draw_text(self):
self.stdscr.clear()
for y, line in enumerate(self.text):
if y < curses.LINES - 2: # Dejar espacio para status
self.stdscr.addstr(y, 0, line)
self.stdscr.move(self.cursor_y, self.cursor_x)
self.stdscr.refresh()
def handle_input(self, key):
if key == curses.KEY_UP:
self.cursor_y = max(0, self.cursor_y - 1)
self.cursor_x = min(self.cursor_x, len(self.text[self.cursor_y]))
elif key == curses.KEY_DOWN:
self.cursor_y = min(len(self.text) - 1, self.cursor_y + 1)
self.cursor_x = min(self.cursor_x, len(self.text[self.cursor_y]))
elif key == curses.KEY_LEFT:
if self.cursor_x > 0:
self.cursor_x -= 1
elif self.cursor_y > 0:
self.cursor_y -= 1
self.cursor_x = len(self.text[self.cursor_y])
elif key == curses.KEY_RIGHT:
if self.cursor_x < len(self.text[self.cursor_y]):
self.cursor_x += 1
elif self.cursor_y < len(self.text) - 1:
self.cursor_y += 1
self.cursor_x = 0
elif key == curses.KEY_BACKSPACE or key == 127:
if self.cursor_x > 0:
line = self.text[self.cursor_y]
self.text[self.cursor_y] = line[:self.cursor_x-1] + line[self.cursor_x:]
self.cursor_x -= 1
elif self.cursor_y > 0:
current_line = self.text[self.cursor_y]
self.text.pop(self.cursor_y)
self.cursor_y -= 1
self.cursor_x = len(self.text[self.cursor_y])
self.text[self.cursor_y] += current_line
elif key == curses.KEY_ENTER or key == 10:
line = self.text[self.cursor_y]
new_line = line[self.cursor_x:]
self.text[self.cursor_y] = line[:self.cursor_x]
self.text.insert(self.cursor_y + 1, new_line)
self.cursor_y += 1
self.cursor_x = 0
elif 32 <= key <= 126: # Caracteres imprimibles
line = self.text[self.cursor_y]
self.text[self.cursor_y] = line[:self.cursor_x] + chr(key) + line[self.cursor_x:]
self.cursor_x += 1
def run(self):
if not self.text:
self.text.append("")
while True:
self.draw_text()
self.draw_status()
key = self.stdscr.getch()
if key == 27: # ESC
break
self.handle_input(key)
def main(stdscr):
editor = TextEditor(stdscr)
editor.run()
if __name__ == "__main__":
curses.wrapper(main)
ncurses es una biblioteca poderosa para crear aplicaciones de terminal interactivas. Con Python, podemos aprovechar su sintaxis clara para construir interfaces complejas de manera relativamente sencilla.
Algunos consejos finales:
curses.wrapper()
para manejo adecuado de erroresrefresh()
después de hacer cambios