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>
9.1 KiB
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
- Modularidad: Paneles independientes, faciles de testear
- Reutilizacion: Mismo panel en multiples ventanas
- Mantenibilidad: Bugs aislados a paneles especificos
- Escalabilidad: Nuevas ventanas = componer paneles existentes
- 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/