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>
378 lines
9.1 KiB
Markdown
378 lines
9.1 KiB
Markdown
# 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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
// Panel se registra para recibir cambios de "Document"
|
|
dataManager.AddObserverForType("Document", myPanel)
|
|
```
|
|
|
|
### 4.2 Notificacion
|
|
|
|
```go
|
|
// Cuando cambia un documento
|
|
dataManager.NotifyObserversWithChange(NewDataChange(
|
|
entityType: "Document",
|
|
changeType: "UPDATE",
|
|
data: doc,
|
|
))
|
|
```
|
|
|
|
### 4.3 Recepcion
|
|
|
|
```go
|
|
// Panel responde al cambio
|
|
func (p *MyPanel) OnDataChanged(timestamp time.Time) {
|
|
// Refrescar UI si afecta mis datos
|
|
}
|
|
```
|
|
|
|
### 4.4 Dual Notification
|
|
|
|
```go
|
|
OnDataChanged() // Cambios UI locales
|
|
OnDataChangedDB() // Cambios en DB (invalidar cache + recargar)
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Aplicacion a zcatgui
|
|
|
|
### 5.1 Propuesta de Interfaz
|
|
|
|
```zig
|
|
/// 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
|
|
|
|
```zig
|
|
/// 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
|
|
|
|
```zig
|
|
/// 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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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/`
|