zcatgui/docs/research/LEGO_PANELS_SYSTEM.md
reugenio 6ac3856ae2 feat: zcatgui v0.5.0 - Complete widget library + research docs
Widgets implemented (13 total):
- Label: Static text with alignment
- Button: With importance levels (primary/normal/danger)
- TextInput: Single-line text entry with cursor
- Checkbox: Boolean toggle
- Select: Dropdown selection
- List: Scrollable selectable list
- Focus: Focus manager with tab navigation
- Table: Editable table with dirty tracking, keyboard nav
- Split: HSplit/VSplit draggable panels
- Panel: Container with title bar, collapsible
- Modal: Dialogs (alert, confirm, inputDialog)
- AutoComplete: ComboBox with prefix/contains/fuzzy matching

Core improvements:
- InputState now tracks keyboard state (keys_down, key_events)
- Full keyboard navigation for Table widget

Research documentation:
- WIDGET_COMPARISON.md: zcatgui vs DVUI vs Gio vs zcatui
- SIMIFACTU_ADVANCEDTABLE.md: Analysis of 10K LOC table component
- LEGO_PANELS_SYSTEM.md: Modular panel composition architecture

Examples:
- widgets_demo.zig: All basic widgets showcase
- table_demo.zig: Table, Split, Panel demonstration

All tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 11:00:49 +01:00

9.1 KiB

Sistema Lego Panels de Simifactu

Fecha: 2025-12-09 Proposito: Documentar arquitectura Lego Panels para aplicar en zcatgui


Resumen

Lego Panels es una arquitectura de composicion modular de UI donde:

  • Cada panel es autonomo (maneja su propio estado, UI y logica)
  • Los paneles son reutilizables (mismo panel en diferentes ventanas)
  • Las ventanas se construyen componiendo paneles (no herencia)
  • La comunicacion usa patron Observer (paneles no se conocen entre si)

Resultados en Simifactu:

  • 83 modulos
  • ~112 lineas por archivo (target: 150 max)
  • 85-98% reutilizacion de codigo
  • 3x mas rapido crear ventanas nuevas

1. Principios Core

1.1 Panel Autonomo

Cada panel:

  • Tiene su propio estado interno
  • Construye su propia UI
  • Maneja sus propios eventos
  • No conoce a otros paneles
  • Se comunica via DataManager (observer)

1.2 Composicion vs Herencia

MAL: WindowA hereda de BaseWindow y override metodos
BIEN: WindowA compone PanelX + PanelY + PanelZ

1.3 Single Source of Truth

DataManager es el hub central:

  • Todas las entidades pasan por DataManager
  • Paneles observan cambios
  • Sin comunicacion directa panel-a-panel

2. Patrones de Composicion

2.1 Vertical Composite (2 paneles)

┌─────────────────────┐
│      Top Panel      │
├─────────────────────┤
│    Bottom Panel     │
└─────────────────────┘

Uso: Division simple top/bottom

2.2 Center Composite (3 paneles)

┌─────────────────────┐
│     WHO Detail      │
├─────────────────────┤
│  Document Detail    │
├─────────────────────┤
│      Lines          │
└─────────────────────┘

Uso: Detalle de documento (master-detail-lines)

2.3 Config Composite (HSplit + dynamic)

┌────────────┬────────────────────┐
│            │                    │
│ Categories │   Dynamic Editor   │
│   (nav)    │  (table/form/etc)  │
│            │                    │
└────────────┴────────────────────┘

Uso: Configuracion (lista izq + editor der cambiante)

2.4 Docs Composite (2 columnas)

┌────────────────┬───────────────────┐
│                │   WHO Compact     │
│   Doc List     ├───────────────────┤
│                │  Document Detail  │
└────────────────┴───────────────────┘

Uso: Lista de documentos con preview


3. Interfaz AutonomousPanel

type AutonomousPanel interface {
    // Identidad
    GetPanelID() string       // "who_list", "doc_detail"
    GetPanelType() string     // "list", "detail", "composite"
    GetEntityType() string    // "WHO", "Document", "Line"

    // Estado
    GetSelectedEntity() interface{}
    SetSelectedEntity(interface{}) error

    // UI
    BuildUI() fyne.CanvasObject
    Refresh()

    // Lifecycle
    Initialize() error
    Destroy() error
}

4. Patron Observer

4.1 Registro

// Panel se registra para recibir cambios de "Document"
dataManager.AddObserverForType("Document", myPanel)

4.2 Notificacion

// Cuando cambia un documento
dataManager.NotifyObserversWithChange(NewDataChange(
    entityType: "Document",
    changeType: "UPDATE",
    data: doc,
))

4.3 Recepcion

// Panel responde al cambio
func (p *MyPanel) OnDataChanged(timestamp time.Time) {
    // Refrescar UI si afecta mis datos
}

4.4 Dual Notification

OnDataChanged()    // Cambios UI locales
OnDataChangedDB()  // Cambios en DB (invalidar cache + recargar)

5. Aplicacion a zcatgui

5.1 Propuesta de Interfaz

/// Panel autonomo
pub const AutonomousPanel = struct {
    /// Identificador unico
    id: []const u8,
    /// Tipo de panel
    panel_type: PanelType,

    /// Build UI - retorna commands
    build_fn: *const fn(*Context) void,

    /// Refresh callback
    refresh_fn: ?*const fn(*AutonomousPanel) void = null,

    /// Estado interno (opaco)
    state: *anyopaque,

    /// Destructor
    deinit_fn: ?*const fn(*AutonomousPanel) void = null,
};

pub const PanelType = enum {
    list,       // Lista de items
    detail,     // Detalle de un item
    table,      // Tabla editable
    composite,  // Compuesto de otros paneles
};

5.2 Patron Composite

/// Composite vertical (2 paneles)
pub const VerticalComposite = struct {
    top: *AutonomousPanel,
    bottom: *AutonomousPanel,
    split_ratio: f32 = 0.5,

    pub fn build(self: *VerticalComposite, ctx: *Context) void {
        const split = widgets.split.vsplit(ctx, self.split_ratio);

        self.top.build_fn(ctx.withArea(split.first));
        self.bottom.build_fn(ctx.withArea(split.second));
    }
};

/// HSplit composite (lista + detalle)
pub const HSplitComposite = struct {
    left: *AutonomousPanel,
    right: *AutonomousPanel,
    split_ratio: f32 = 0.3,

    pub fn build(self: *HSplitComposite, ctx: *Context) void {
        const split = widgets.split.hsplit(ctx, self.split_ratio);

        self.left.build_fn(ctx.withArea(split.first));
        self.right.build_fn(ctx.withArea(split.second));
    }
};

5.3 DataManager Simplificado

/// Observer callback
pub const DataObserver = struct {
    on_data_changed: ?*const fn(entity_type: []const u8, data: ?*anyopaque) void = null,
    context: ?*anyopaque = null,
};

/// Data manager (singleton)
pub const DataManager = struct {
    observers: std.StringHashMap(std.ArrayList(DataObserver)),

    pub fn addObserver(self: *DataManager, entity_type: []const u8, observer: DataObserver) void {
        // ...
    }

    pub fn notifyChange(self: *DataManager, entity_type: []const u8, data: ?*anyopaque) void {
        if (self.observers.get(entity_type)) |observers| {
            for (observers.items) |obs| {
                if (obs.on_data_changed) |callback| {
                    callback(entity_type, data);
                }
            }
        }
    }
};

6. Ejemplo de Uso

6.1 Definir Panel Simple

const CustomerListPanel = struct {
    state: ListState,
    customers: []Customer,

    pub fn build(ctx: *Context) void {
        widgets.panel.panel(ctx, "Customers", .{});

        const result = widgets.list.list(ctx, &state, customers);
        if (result.selection_changed) {
            dataManager.notifyChange("Customer", customers[result.selected.?]);
        }
    }
};

6.2 Definir Panel Detalle

const CustomerDetailPanel = struct {
    state: FormState,
    customer: ?Customer,

    pub fn build(ctx: *Context) void {
        widgets.panel.panel(ctx, "Customer Detail", .{});

        if (self.customer) |c| {
            widgets.label.label(ctx, c.name);
            widgets.label.label(ctx, c.email);
        } else {
            widgets.label.label(ctx, "Select a customer");
        }
    }

    pub fn onDataChanged(entity_type: []const u8, data: ?*anyopaque) void {
        if (std.mem.eql(u8, entity_type, "Customer")) {
            self.customer = @ptrCast(data);
        }
    }
};

6.3 Componer en Ventana

pub fn main() !void {
    var list_panel = CustomerListPanel{};
    var detail_panel = CustomerDetailPanel{};

    // Registrar observers
    dataManager.addObserver("Customer", .{
        .on_data_changed = detail_panel.onDataChanged,
    });

    // Componer
    var composite = HSplitComposite{
        .left = &list_panel.asPanel(),
        .right = &detail_panel.asPanel(),
        .split_ratio = 0.3,
    };

    // Main loop
    while (running) {
        composite.build(&ctx);
        // ...
    }
}

7. Plan de Implementacion

Fase 1: Panel Interface (2 horas)

  • Definir AutonomousPanel trait/interface
  • Helpers para crear paneles

Fase 2: Composites Basicos (2 horas)

  • VerticalComposite
  • HSplitComposite
  • TabComposite (tabs)

Fase 3: DataManager Simple (3 horas)

  • Observer registration
  • Notify observers
  • Entity type filtering

Fase 4: Ejemplo Completo (2 horas)

  • Master-detail demo
  • Config-like layout demo

Total estimado: 9 horas


8. Beneficios Esperados

  1. Modularidad: Paneles independientes, faciles de testear
  2. Reutilizacion: Mismo panel en multiples ventanas
  3. Mantenibilidad: Bugs aislados a paneles especificos
  4. Escalabilidad: Nuevas ventanas = componer paneles existentes
  5. Decoupling: Sin dependencias directas entre paneles

9. Referencias

  • /mnt/cello2/arno/re/recode/go/simifactu-fyne/docs/arquitectura_canonica/01_filosofia_lego.md
  • /mnt/cello2/arno/re/recode/go/simifactu-fyne/docs/AUDITORIA_ARQUITECTURA_LEGO_V3_SEPT16.md
  • /mnt/cello2/arno/re/recode/go/simifactu-fyne/internal/ui/panels_v3/panels/