zcatgui/docs/REFACTORING_MODULAR_2025-12-29.md

6.7 KiB

Refactorización Modular 2025-12-29

Objetivo: Reducir archivos monolíticos (>700 LOC) a estructuras modulares mantenibles.


Resumen Ejecutivo

Módulo Antes Hub Reducción
autocomplete 910 LOC 571 LOC -37%
icon 805 LOC 515 LOC -36%
textarea Ya modular 404 LOC -
progress Ya modular ~300 LOC -

Archivos residuales eliminados: textarea.zig, progress.zig (duplicados de carpetas existentes)


Patrón de Modularización

Estructura Estándar

widget/
├── widget.zig        # Hub: API pública, re-exports, lógica principal
├── types.zig         # Tipos, enums, Config, Colors, Result
├── state.zig         # Estado del widget (si es stateful)
└── helpers.zig       # Funciones auxiliares específicas

Principios

  1. Hub Mínimo: El archivo principal solo contiene API pública y lógica core
  2. Tipos Separados: Todos los struct, enum, Config, Colors van a types.zig
  3. Estado Aislado: Estado mutable con sus métodos en state.zig
  4. Helpers Específicos: Funciones de dibujo, filtrado, etc. en su propio archivo
  5. Re-exports: El hub re-exporta todo para compatibilidad con código existente

autocomplete/ (910 → 571 LOC, -37%)

Estructura

src/widgets/autocomplete/
├── autocomplete.zig    (571 LOC) - Hub con API y lógica principal
├── state.zig           (200 LOC) - AutoCompleteState + buffer management
├── types.zig           (89 LOC)  - MatchMode, Config, Colors, Result
└── filtering.zig       (106 LOC) - matchesFilter, matchesPrefix, matchesFuzzy

Archivos Modificados

  • widgets.zig: Import actualizado a autocomplete/autocomplete.zig

Detalles de Extracción

state.zig:

pub const AutoCompleteState = struct {
    buffer: [256]u8 = [_]u8{0} ** 256,
    cursor: usize = 0,
    // ... métodos de gestión de buffer
};

types.zig:

pub const MatchMode = enum { prefix, contains, fuzzy };
pub const AutoCompleteConfig = struct { ... };
pub const AutoCompleteColors = struct { ... };
pub const AutoCompleteResult = struct { ... };

filtering.zig:

pub fn matchesFilter(text: []const u8, filter: []const u8, mode: MatchMode) bool;
pub fn matchesPrefix(text: []const u8, prefix: []const u8) bool;
pub fn matchesContains(text: []const u8, needle: []const u8) bool;
pub fn matchesFuzzy(text: []const u8, pattern: []const u8) bool;

icon/ (805 → 515 LOC, -36%)

Estructura

src/widgets/icon/
├── icon.zig            (515 LOC) - Hub con API y switch de ~50 iconos
├── types.zig           (146 LOC) - Size, IconType, Config, Colors
└── drawing_helpers.zig (93 LOC)  - drawLine, fillCircle, strokeCircle

Archivos Modificados

  • widgets.zig: Import actualizado a icon/icon.zig
  • iconbutton.zig: Import actualizado
  • appbar.zig: Import actualizado
  • navdrawer.zig: Import actualizado

Detalles de Extracción

types.zig:

pub const Size = enum {
    small,   // 12x12
    medium,  // 16x16
    large,   // 24x24
    xlarge,  // 32x32

    pub fn pixels(self: Size) u32 { ... }
};

pub const IconType = enum {
    // Navigation (~12)
    arrow_up, arrow_down, arrow_left, arrow_right,
    chevron_up, chevron_down, chevron_left, chevron_right,
    home, menu, more_horizontal, more_vertical,

    // Actions (~16)
    check, close, plus, minus, edit, delete, refresh,
    search, settings, filter, sort, copy, paste, cut, undo, redo,

    // Files (~8)
    file, folder, folder_open, document, image_file,
    download, upload, save,

    // Status (~9)
    info, warning, error_icon, success, question,
    star, star_filled, heart, heart_filled,

    // UI elements (~10)
    eye, eye_off, lock, unlock, user, users,
    calendar, clock, bell, mail,

    // Media (~5)
    play, pause, stop, volume, volume_off,

    // Misc (~8)
    grip, drag, expand, collapse, maximize, minimize, external_link,
};

pub const Config = struct {
    size: Size = .medium,
    custom_size: ?u32 = null,
    stroke_width: u32 = 2,
    filled: bool = false,
};

pub const Colors = struct {
    foreground: Style.Color = Style.Color.rgba(220, 220, 220, 255),
    background: ?Style.Color = null,

    pub fn fromTheme(theme: Style.Theme) Colors { ... }
};

drawing_helpers.zig:

/// Dibuja línea con grosor variable
pub fn drawLine(ctx: *Context, x1: i32, y1: i32, x2: i32, y2: i32,
                thickness: u32, color: Style.Color) void;

/// Rellena círculo
pub fn fillCircle(ctx: *Context, cx: i32, cy: i32, radius: u32, color: Style.Color) void;

/// Dibuja contorno de círculo (Bresenham)
pub fn strokeCircle(ctx: *Context, cx: i32, cy: i32, radius: u32,
                    thickness: u32, color: Style.Color) void;

fn setPixelThick(ctx: *Context, x: i32, y: i32, thickness: u32, color: Style.Color) void;

Limpieza de Archivos Residuales

Problema Detectado

Existían archivos duplicados:

  • src/widgets/textarea.zig (26KB) - archivo antiguo
  • src/widgets/textarea/ - carpeta con módulos

Lo mismo con progress.zig / progress/.

Solución

rm src/widgets/textarea.zig
rm src/widgets/progress.zig

Los imports en widgets.zig ya apuntaban a las carpetas, por lo que los archivos sueltos eran código muerto.


Módulos Ya Modulares (No Modificados)

textarea/ (404 LOC hub)

src/widgets/textarea/
├── textarea.zig  (404 LOC)
├── state.zig
├── render.zig
└── types.zig

progress/ (~300 LOC hub)

src/widgets/progress/
├── progress.zig
├── bar.zig
├── circle.zig
└── spinner.zig

Beneficios de la Modularización

  1. Mantenibilidad: Archivos <600 LOC son más fáciles de navegar
  2. Compilación: Cambios en types.zig no recompilan lógica principal
  3. Testing: Módulos aislados son más fáciles de testear
  4. Colaboración: Menos conflictos de merge en archivos grandes
  5. Documentación: Estructura refleja responsabilidades

Candidatos Futuros

Archivo LOC Prioridad
font_data.zig 1157 Baja (datos estáticos)
style.zig 858 Media
table.zig ~750 Media

Commits

chore: Eliminar archivos residuales textarea.zig y progress.zig
refactor(autocomplete): Modularizar en carpeta (910→571 LOC hub, -37%)
refactor(icon): Modularizar en carpeta (805→515 LOC hub, -36%)

Fecha: 2025-12-29 Autor: Claude (Opus 4.5) + R.Eugenio