From 4a96e14f1cd5d2431abf5aac38a3708b9005d1e5 Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Sun, 30 Aug 2026 20:05:28 -0600 Subject: [PATCH] docs: alinear CONTRIBUTING, README y system_design con el estado real del codigo (P0-P7) Corrige errores factuales (View sealed class, ViewBase en Share/View.cs, UseWinApi solo compile-time), documenta subsistemas omitidos (UiDefaults/DPI, Constants/Entities/Win32, Procedures separacion, LayoutEngine, Common, Windowing, Backend), expone API publica Share (jerarquia ventanas, 3 overloads IChildrenBuilder, primitivas UI), detalla Type-State/MasterRouter/HandleRegistry y Source Generators, limpia TODO LazyAssets y ortografia, y actualiza system_design con UiDefaults pipeline, AlternativeWindow y View(Action). Ref: #43 Refs: plan-master-99.md P0.1-P0.3, P1.1-P1.9, P2.1-P2.4, P3.1-P3.2, P4.1-P4.3, P6.1-P6.2, P7.1-P7.4 --- CONTRIBUTING.md | 51 +++++++++++++++++++++++++------------------- README.md | 37 ++++++++++++++++++++------------ src/system_design.md | 16 ++++++++++---- 3 files changed, 64 insertions(+), 40 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b0c4feb..b6b5a4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,22 +67,24 @@ El framework está organizado en módulos bajo `src/EasyWindowsApplication/`: | Módulo | Capa | Responsabilidad | |---|---|---| -| `Core` | `internal` (100%) | Punto de entrada (`WindowsApplication` → `Share/IApplicationLayoutPhase`), `Application`, `MasterRouter` (trampolín HWND-based), `HandleRegistry` (ConcurrentDictionary HWND→Router), `ControlBase`, `IconGenerator` | -| `Core/Windowing` | `internal` | `WindowImpl`, `AlternativeWindowImpl`, `Procedures` (CreateMainWindow/CreateAlternativeWindow con `[UnmanagedCallersOnly]`), `UserControl` | -| `Core/LayoutEngine` | `internal` | `LayoutEngine`, `ILayoutBuilder`, layouts (Grid, Stack, Dock), `ChildrenBuilderImpl` (usa `ControlActivatorRegistry`), `ViewBuilderImpl` | -| `Common` | `internal` | `ControlActivatorRegistry` + `IControlActivator` (registry genérico `Register/Create`, `EnsureInitialized()` + `ControlActivatorGenerator` `IIncrementalGenerator` con `InternalsVisibleTo`, MEF compile-time) — rompe ciclo `Common ↔ Core` sin `ModuleInitializer` (`CA2255`) | -| `Share` | `public` | API usuario: `IWindow`, `IAlternativeWindow`, `IWindowConfig`, `IChildrenBuilder` (dual overload `Action>` + `Func,View>`), `View` struct readonly (`Name/Margin/Width/Height/Background/Dock/Grid/OnClick/Text`), `LayoutLength`, `GridDefinitions`, `IResourcesDictionary`, etc. | -| `Share/Infrastructure` | `public` técnico | `[EditorBrowsable(Never)]`: `ControlAccess` (punto usado por Source Generator), `ViewBase` base para controles custom (ej. `IpAddress`) | -| `Win32ControlsModule` | Frontend `public` / Backend `internal` | Interfaces (`IButton`, `ILabel` …) heredando `IControl` + Backend (`Button`, `Label` → `Core.ControlBase`) + `Win32ControlActivator` (`IControlActivator`, `internal`, auto-registrado vía `ControlActivatorGenerator` + `EnsureInitialized()` en `Core/Application`) | -| `Generators` | `Analyzer` | `EasyBehaviorGenerator` (FQN `Share` + `GetTypeByMetadataName`) + `ControlActivatorGenerator` (descubre `IControlActivator` en `Compilation`, genera `ControlActivatorRegistrations.g.cs`, `InternalsVisibleTo`) | +| `Core` | `internal` (100%) | Núcleo del framework: `WindowsApplication` → `Share/IApplicationLayoutPhase`, `Application` (pipeline `Initialize()` en 5 fases), `MasterRouter` (trampolín `[UnmanagedCallersOnly]` + `HandleRegistry` `ConcurrentDictionary` + 6 diccionarios adicionales; ciclo `WM.CLOSE/SIZE/MOVE/ACTIVATE/SCROLL/DPI/ERASEBKGND/CTLCOLOR/DESTROY`), `HandleRegistry` (hwnd→control/window, parent→children), `ControlBase`, `IconGenerator`, `Procedures.RunMessageLoop()` (`GetMessageW/TranslateMessage/DispatchMessageW`), `Constants.cs` (227 líneas: `WS/WS_EX/CS/CW/WM/SPI/WMSIZE/WA/MONITOR/DT/SCROLLBAR/SIF/BN/EN/SW/GWL/ICC/SWP`), `Entities.cs` (`MSG/POINT/NMHDR/SCROLLINFO/…`), `Win32.cs` (~40 P/Invoke `[LibraryImport]` para Native AOT), `LayoutModels.cs` (`WindowModel/ContentModel/ViewModel`), `ResourcesDictionaryImpl.cs` (`SettingsBuilderImpl/UseWinApi` no-op + `AppConfigFile/Services` builders) | +| `Core/UiDefaults` | `internal` | Sistema centralizado de defaults con DPI scaling: `IDefaultUiValues`, `UiDefaultsProvider` (singleton `CoreFallbackDefaults` → `Set(Win32UiDefaults)` en `Application.Initialize()` antes de registrar ventanas), `FontSpec` (`SystemTheme`), `ControlUiDefaults`/`WindowUiDefaults`/`CoreUiDefaults` (`PreferredHeight/Padding/Background`), `DefaultUiValuesExtensions`. Acoplamiento crítico: `Button.MeasureContent()` lee `UiDefaultsProvider.Current.GetFor().PreferredHeight` y escala `96→dpiActual`; `ControlProcedures.GetDefaultFont()` lee `UiDefaultsProvider.Current.DefaultFont` → `CreateFontFromSpec` | +| `Core/Windowing` | `internal` | `WindowImpl`, `AlternativeWindowImpl`, `Procedures` (`CreateMainWindow` con `WS_OVERLAPPEDWINDOW` + `CreateAlternativeWindow` con `WS_POPUP|CAPTION|SYSMENU`, registro `HandleRegistry.RegisterRouter` + `WndProcTrampoline`), `UserControl`, `Win32.cs` (P/Invoke parcial `[LibraryImport]`), `Entities.cs` (`MSG/POINT/NMHDR/…`), `Enums.cs`, `Constants.cs` (stub: "Window-specific constants are in Core") | +| `Core/LayoutEngine` | `internal` | `LayoutEngine` (batching `DeferWindowPos` anti-flicker), `ILayoutStrategy`/`ILayoutable`, `LayoutBuilderImpl`, `ContentBuilderImpl`, `WindowConfigBuilder`, `ChildrenBuilderImpl` (usa `ControlActivatorRegistry`), `ViewBuilderImpl`, estrategias `GridLayoutStrategy`/`DockLayoutStrategy`/`HorizontalStackLayoutStrategy`/`VerticalStackLayoutStrategy` | +| `Common` | `internal` | `ControlActivatorRegistry` (`Register/Create`, `RegisterFactory/TryGetFactory/CreateFactory/TryGetFactoryForControl`, `Shared` + `EnsureInitialized()` + `partial RegisterGeneratedActivators()`), `IControlActivator` (`RegisterActivators`), `INativeHandleFactory` (`CreateHandle(parentHwnd, control, registry)`), `Win32Helpers` (`HIWORD/LOWORD` usado por `MasterRouter`). Rompe ciclo `Common ↔ Core` sin `ModuleInitializer` (`CA2255`); usa `IIncrementalGenerator` + `InternalsVisibleTo` | +| `Share` | `public` | API usuario: jerarquía ventanas `IBaseWindow` → `IWindow`/`IAlternativeWindow`/`IView`, `IWindowConfig`/`IContentBuilder`/`ILayout`/`ILayoutBuilderAfterWindow.AlternativeWindow()`/`IViewBuilder` (controles custom), `ViewBase` (`Share/View.cs`, base para controles custom ej. `IpAddress`) + `View sealed class where T : class, IControl` (`Name/Margin/Padding/Width/Height/Background/Dock/OnClick/Text` + `PendingName` y lista `Action`), `IChildrenBuilder` (3 overloads: `View(Action>)` + `View(Func,View>)` + `View(Action)`), primitivas `LayoutLength`/`GridDefinitions`/`Thickness`/`Color`/`LayoutOptions`/`DockPosition`/`WindowPositionOnScreen`/`ScrollBarVisibility`, eventos `CancelEventArgs/WindowMoved/Resized/Resizing`, `IResourcesDictionary`/`IApplicationLayoutPhase` (Type-State) | +| `Share/Infrastructure` | `public` técnico | `[EditorBrowsable(Never)]`: `ControlAccess` (punto usado por `EasyBehaviorGenerator` vía `ControlAccess.Get(name)` / `GetWindow`) — único tipo en este namespace | +| `Win32ControlsModule` | Frontend `public` / Backend `internal` | **Frontend:** 30 interfaces `IControl` → `IButton/ILabel/ICheckBox/IComboBox/…/ITreeView/IUpDown` + `ControlBuilderExtensions` + `StyleInterfaces` + clase `IpAddress : ViewBase` · **Backend:** `Button`/`Label` → `Core.ControlBase` (con `MeasureContent` + DPI scaling acoplado a `UiDefaultsProvider`), `Win32UiDefaults` (`IDefaultUiValues` concreto registrado en `Application.Initialize()`), `Win32NativeHandleFactory` (`INativeHandleFactory`), `Win32ControlActivator` (`IControlActivator`, `internal`, auto-registrado vía `ControlActivatorGenerator` + `EnsureInitialized()`), `Procedures.ControlProcedures` (capa GDI/User32: `GetDefaultFont/CreateFontFromSpec/GetDpiForWindowSafe/SendMessage/InvalidateDefaultFont/CreateControl`), `Win32.cs`/`Constants.cs`/`Entities.cs` del módulo | +| `Generators` | `Analyzer` | `EasyBehaviorGenerator` (detecta `.Name("…")` en `View/Window/AlternativeWindow`, genera `EasyBehaviorExtensions` + `EAWIN001` duplicados + `EAWIN002` gate `UseWinApi()`, FQN `Share` + `GetTypeByMetadataName`) + `ControlActivatorGenerator` (descubre `IControlActivator` vía `AllInterfaces`, genera `ControlActivatorRegistrations.g.cs` con `new Win32ControlActivator().RegisterActivators(Shared)`, `InternalsVisibleTo`) | ### Principios de organización -- **`Common`** — código compartido `internal` (no visible para consumidores). Contiene `ControlActivatorRegistry` (evita reflexión, factorías genéricas). -- **`Share`** — API `public` para consumidores. Todo lo que devuelve `WindowsApplication` (`IApplicationLayoutPhase` etc.) vive aquí. -- **`Share/Infrastructure`** — `public` por razones técnicas (`[EditorBrowsable(Never)]`). Solo `ControlAccess` y `ViewBase` permanecen aquí; `ControlBase`/`IconGenerator` migraron a `Core/`. -- **`Core/**`** — 100% `internal`. Nada `public` aquí (si aparece `public` es bug, debe ser `internal` o moverse a `Share`). Implementaciones puras. -- **`Win32ControlsModule/Frontend`** — `public` interfaces controles; `Backend` — `internal` implementaciones que heredan `Core.ControlBase` y se registran vía `IControlActivator`. +- **`Common`** — código compartido `internal` (no visible para consumidores). Contiene `ControlActivatorRegistry` + `INativeHandleFactory`/`Win32Helpers` (evita reflexión, factorías genéricas + `HIWORD/LOWORD`). +- **`Share`** — API `public` para consumidores. Todo lo que devuelve `WindowsApplication` (`IApplicationLayoutPhase` etc.) vive aquí. `ViewBase` y `View sealed class` están en `Share/View.cs` (no en `Infrastructure`). +- **`Share/Infrastructure`** — `public` por razones técnicas (`[EditorBrowsable(Never)]`). Solo `ControlAccess` permanece aquí; `ViewBase` está en `Share`, `ControlBase`/`IconGenerator` migraron a `Core/`. +- **`Core/**`** — 100% `internal`. Nada `public` aquí (si aparece `public` es bug, debe ser `internal` o moverse a `Share`). Implementaciones puras. `Core/Constants.cs` es el archivo central de constantes Win32 (227 líneas); `Core/Windowing/Constants.cs` es un stub. +- **`Core/UiDefaults`** — 100% `internal`. Singleton `UiDefaultsProvider` con 7 archivos (`IDefaultUiValues/FontSpec/ControlUiDefaults/WindowUiDefaults/CoreUiDefaults`). Se inicializa en `Application.Initialize()` antes de crear HWNDs; `Button.MeasureContent` y `ControlProcedures.GetDefaultFont` dependen de él (DPI scaling). +- **`Win32ControlsModule/Frontend`** — `public` interfaces controles (30 tipos); `Backend` — `internal` implementaciones que heredan `Core.ControlBase` y se registran vía `IControlActivator` + `Win32UiDefaults`/`Win32NativeHandleFactory`/`ControlProcedures`. --- @@ -96,7 +98,7 @@ Resources(...) |--> ASSETS (Qué es / Cómo se ve) --> Fuentes, Imágenes, |--> SERVICES (Qué hace / Con qué) --> Web APIs, DBs, Repositorios, Lógica de Negocio ``` - + ### Estructura de directorios Resources @@ -116,13 +118,15 @@ Resources/ │ └── appsettings.json # → SETTINGS por defecto, auto-detectado ``` +> **Nota `UseWinApi()` vs `UiDefaults`:** `SettingsBuilderImpl.UseWinApi()` es un **marcador compile-time** (`EAWIN002`): en runtime retorna `this` (no-op). El Source Generator lo usa para decidir si emite `EasyBehaviorExtensions` para controles `IControl` de `Win32ControlsModule`; sin `UseWinApi()` el compilador rechaza `.View` etc. El mecanismo **runtime** real es `Core/UiDefaults`: `Application.Initialize()` llama `UiDefaultsProvider.Set(new Win32UiDefaults())` antes de `InitCommonControlsEx` y de crear HWNDs. `ControlProcedures.GetDefaultFont()` consume `UiDefaultsProvider.Current.DefaultFont` (`FontSpec.SystemTheme` → `LOGFONT` → `HFONT` con DPI) y `Button.MeasureContent()` consume `UiDefaultsProvider.Current.GetFor().PreferredHeight` escalado `96→dpiActual`. No confundir gate de compilación con configuración de UI en runtime. + --- ## Source Generators El proyecto `EasyWindowsApplication.Generators` contiene `EasyBehaviorGenerator`, un `IIncrementalGenerator` que (FQN actualizados a `EasyWindowsApplication.Share.*` + robustez `GetTypeByMetadataName`): -1. **Detecta** llamadas `.Name("...")`** dentro de lambdas `View(...)` (ahora `View` es `Share/View` struct) y `Window(...)` / `AlternativeWindow(...)` en el `Layout`. +1. **Detecta** llamadas `.Name("...")`** dentro de lambdas `View(...)` (ahora `View` es `Share/View` `sealed class`) y `Window(...)` / `AlternativeWindow(...)` en el `Layout`. 2. **Genera** `EasyBehaviorExtensions` — un *extension property* para cada nombre, tipado con la interfaz del control (FQN `global::EasyWindowsApplication.Share.IWindow` / `IAlternativeWindow` / `global::EasyWindowsApplication.Share.IBehaviorBuilder`): ```csharp // Auto-generado (ejemplo) @@ -160,7 +164,7 @@ Los proyectos de plantilla incluyen un `Target` de MSBuild (`GenerateAppIcon`) q > **Nota**: SkiaSharp es dependencia de *build-time* solo (no se incluye en la app final). El developer puede reemplazar el procesador implementando `IEasyImageProcessor`. -> **Alineación de versiones**: el proyecto temporal `IcoGen` debe referenciar la **misma** versión de `Svg.Skia` que el framework (`EasyWindowsApplication.csproj` usa `*` flotante). La línea se declara como `Version="%2A"` en los tres `.csproj` con el target (`Sample`, `EasyWinApp`, `SimpleEasyWinApp`): `%2A` es el escapado MSBuild de `*`, necesario porque un `*` literal en un `Include` de ItemGroup se interpreta como *wildcard* y MSBuild descarta el item. El `IcoGen.csproj` generado recibe `Version="*"`, de modo que ambos resuelven al mismo `Svg.Skia` en el mismo build. Si se *pinea* el framework, hay que actualizar también las 3 líneas del `IcoGen`; un desajuste produce `System.IO.FileNotFoundException` al ejecutar `IcoGen` (código `-532462766`). +> **Alineación de versiones**: el proyecto temporal `IcoGen` debe referenciar la **misma** versión de `Svg.Skia` que el framework (`EasyWindowsApplication.csproj` usa `*` flotante). La línea se declara como `Version="%2A"` en los tres `.csproj` con el target (`Sample`, `EasyWinApp`, `SimpleEasyWinApp`): `%2A` es el escapado MSBuild de `*`, necesario porque un `*` literal en un `Include` de ItemGroup se interpreta como *wildcard* y MSBuild descarta el item. El `IcoGen.csproj` generado recibe `Version="*"`, de modo que ambos resuelven al mismo `Svg.Skia` en el mismo build. Si se fija la versión del framework, hay que actualizar también las 3 líneas del `IcoGen`; un desajuste produce `System.IO.FileNotFoundException` al ejecutar `IcoGen` (código `-532462766`). --- @@ -176,12 +180,15 @@ Los proyectos de plantilla incluyen un `Target` de MSBuild (`GenerateAppIcon`) q Documenta aquí las decisiones de arquitectura relevantes. Usa ADRs (Architecture Decision Records) en `docs/adr/`. -- **Fluent API con interfaces de fase** — El IntelliSense guía el orden `Resources → Layout → Behavior → Initialize` mediante interfaces (`IApplicationLayoutPhase`, `IApplicationPostLayoutPhase`, `IApplicationPostBehaviorPhase`) ahora en `Share/` (antes `CoreModule/Frontend`). `WindowsApplication` devuelve `IApplicationLayoutPhase` público. -- **Controles como interfaces** (`IButton`, `ILabel`, ...) — permite que el Source Generator genere tipos débilesmente acoplados y facilita testing/mocking. `Button`/`Label` heredan `Core.ControlBase` (antes `Share.Infrastructure.ControlBase`). -- **MasterRouter** — centraliza el bucle de mensajes con trampolín `[UnmanagedCallersOnly(CallConvs=[Stdcall])]` + lookup HWND-based (`HandleRegistry.ConcurrentDictionary`), `try/catch` que evita tumbar proceso, despacha eventos tipados (`WM.COMMAND` → `Click`). El delegado estático sobrescrito fue eliminado (evita GC). -- **ControlAccess / ControlActivatorRegistry / ControlActivatorGenerator** — `ControlAccess` sigue en `Share/Infrastructure` (usado por `EasyBehaviorGenerator`). `ControlActivatorRegistry` en `Common/` (genérico `Register/Create`, `Shared` + `EnsureInitialized()` + `partial RegisterGeneratedActivators()`) rompe acoplamiento `Core → Win32ControlsModule` sin `ModuleInitializer` (`CA2255` en `Library`). `ControlActivatorGenerator` (`IIncrementalGenerator` en `Generators`, descubre `IControlActivator` en `Compilation` vía `GetTypeByMetadataName` + `AllInterfaces`, genera `ControlActivatorRegistrations.g.cs` con `new Win32ControlActivator().RegisterActivators(Shared)` y futuros plugins; usa `` para mantener `internal`. -- **View struct** — `Share/View` readonly struct `where T : class, IControl` con `Instance` + fluents (`Name/Margin/Width/Height/Background/Dock/Grid/OnClick/Text`). Dual overload en `IChildrenBuilder`: `View(Action>)` + `View(Func,View>)` instanciando `View(control)` vía `ControlActivatorRegistry`. -- **`UseWinApi()` como bifurcación explícita** — gate **compile-time** (`EAWIN002`), consumidor Source Generator decide accessors. `IconGenerator` migró `Share/Infrastructure` → `Core/` (ahora `Core.IconGenerator`, IcoGen usa `EasyWindowsApplication.Core`). +- **Fluent API con interfaces de fase (Type-State)** — El IntelliSense guía el orden `Resources → Layout → Behavior → Initialize` mediante interfaces (`IApplicationLayoutPhase`, `IApplicationPostLayoutPhase`, `IApplicationPostBehaviorPhase`) ahora en `Share/` (antes `CoreModule/Frontend`). `WindowsApplication` devuelve `IApplicationLayoutPhase` público. `IApplicationPostLayoutPhase` expone tanto `Behavior()` como `Initialize()` para flujos mínimos (`Resources→Layout→Initialize` o `Layout→Initialize`). `Application.cs` tiene overloads sin parámetro `Layout()`/`Behavior()` que retornan `this` para encadenamiento condicional. `WindowsApplication` tiene `static constructor` que llama `ControlActivatorRegistry.EnsureInitialized()` (inicialización perezosa; segundo llamado en `Application.Initialize()` es no-op por flag `_inited`). +- **Controles como interfaces** (`IButton`, `ILabel`, ...) — permite que el Source Generator genere tipos débilesmente acoplados y facilita testing/mocking. `Button`/`Label` heredan `Core.ControlBase` (antes `Share.Infrastructure.ControlBase`) y contienen lógica DPI (`MeasureContent` escala `PreferredHeight` 96→dpi actual vía `UiDefaultsProvider`). +- **MasterRouter** — 376 líneas: centraliza el bucle de mensajes con trampolín `[UnmanagedCallersOnly(CallConvs=[Stdcall])]` + lookup HWND-based (`HandleRegistry.ConcurrentDictionary`), `try/catch` que evita tumbar proceso, ciclo completo `WM.CLOSE/ENTERSIZEMOVE/EXITSIZEMOVE/SIZE/MOVE/ACTIVATE/HSCROLL/VSCROLL/MOUSEWHEEL/SETTINGCHANGE/DPICHANGED/ERASEBKGND/CTLCOLOR/DESTROY` (typed dispatch `WM.COMMAND→Click` + raw handlers + cleanup `Unregister/PostQuitMessage` + `WM.ERASEBKGND` con `LayoutGroup` backgrounds + `WM.CTLCOLOR*` con control brushes), despacha eventos tipados (`WM.COMMAND` → `Click`). El delegado estático sobrescrito fue eliminado (evita GC). Paint/Theme/DPI/scroll/lifecycle separados del GDI. +- **HandleRegistry** — No es solo `ConcurrentDictionary`. Contiene `ConcurrentDictionary` (routers) + 5 diccionarios: `hwnd→WeakReference`, `name→WeakReference`, `name→IBaseWindow`, `hwnd→IBaseWindow`, `parent→List children`. Métodos: `TrackChildWindow/UnregisterWindowControls/RegisterWindow/UnregisterWindow/UnregisterWindowByHwnd/GetWindowByHwnd/GetByName/GetWindow/AllControlHandles`. +- **ControlAccess / ControlActivatorRegistry / ControlActivatorGenerator** — `ControlAccess` sigue en `Share/Infrastructure` (usado por `EasyBehaviorGenerator`). `ControlActivatorRegistry` en `Common/` (genérico `Register/Create` + `RegisterFactory/TryGetFactory/CreateFactory/CreateFactoryFor/TryGetFactoryForControl` + `INativeHandleFactory` + `Shared` + `EnsureInitialized()` + `partial RegisterGeneratedActivators()`) rompe acoplamiento `Core → Win32ControlsModule` sin `ModuleInitializer` (`CA2255` en `Library`). `ControlActivatorGenerator` (`IIncrementalGenerator` en `Generators`, descubre `IControlActivator` en `Compilation` vía `GetTypeByMetadataName` + `AllInterfaces`, genera `ControlActivatorRegistrations.g.cs` con `new Win32ControlActivator().RegisterActivators(Shared)` y futuros plugins; usa `` para mantener `internal`. +- **View sealed class** — `Share/View` `public sealed class where T : class, IControl` (no `struct`; semántica de referencia en heap, GC, identidad por referencia) con lista `Action` + fluents (`Name/Margin/Padding/Width/Height/Background/Dock/OnClick/Text`). Triple overload en `IChildrenBuilder`: `View(Action>)` + `View(Func,View>)` + `View(Action)` para controles custom sin tipo genérico, instanciando `View(control)` vía `ControlActivatorRegistry`. +- **`UseWinApi()` como bifurcación explícita** — gate **solo compile-time** (`EAWIN002`): `SettingsBuilderImpl.UseWinApi()` retorna `this` (no-op en runtime). El Source Generator decide si emite accessors tipados. El mecanismo runtime real es `Core/UiDefaults` (`UiDefaultsProvider.Set(new Win32UiDefaults())` en `Application.Initialize()` antes de `InitCommonControlsEx` y registro de ventanas). `UseWinApi()` y `UiDefaults` son conceptos separados. `IconGenerator` migró `Share/Infrastructure` → `Core/` (ahora `Core.IconGenerator`, IcoGen usa `EasyWindowsApplication.Core`). +- **UiDefaults y DPI** — `Core/UiDefaults/` (7 archivos) + `Backend/Win32UiDefaults` + `Backend/Controls.cs` (`Button.MeasureContent` escala `PreferredHeight` vía `UiDefaultsProvider.Current.GetFor()` y `GetDpiForWindow` 96→actual; `ControlProcedures.GetDefaultFont` lee `FontSpec` vía `UiDefaultsProvider.Current.DefaultFont` → `CreateFontFromSpec` → `GetDpiForSystemSafe`). Ver diagrama en [Arquitectura de módulos](#arquitectura-de-modulos). +- **Separación Procedures** — `Core/Procedures.cs` (`RunMessageLoop`: `while(GetMessageW>0){Translate/Dispatch}`) pertenece al núcleo; `Core/Windowing/Procedures.cs` (`CreateMainWindow/CreateAlternativeWindow` con `RegisterClassExW` + `WndProcTrampoline`) pertenece a windowing; `Win32ControlsModule/Backend/Procedures.cs` (`ControlProcedures`: `GetDefaultFont/CreateFontFromSpec/GetDpiForWindowSafe/SendMessage/CreateControl`) es la capa GDI/User32. Mezclar message loop con GDI es el error Win32 más común — esta separación debe preservarse. - **AlternativeWindow** — ahora funcional: `Procedures.CreateAlternativeWindow` registra clase `WS_POPUP|CAPTION|SYSMENU` y `HandleRegistry.RegisterRouter(hwnd, router)`; `Application.RegisterAlternative` crea hwnd real, aplica BackgroundBrush y `Show()`. --- diff --git a/README.md b/README.md index 9007104..b076bd0 100644 --- a/README.md +++ b/README.md @@ -88,17 +88,17 @@ El framework incluye **dos plantillas** oficiales: dotnet new install src\ProjectTemplates\ ``` -> NOTA: Si tienes previamente instalada una o varias de estas plantillas y existe una actualizacion, se recomienda desintalar todas y volver a instalar. +> NOTA: Si tienes previamente instalada una o varias de estas plantillas y existe una actualización, se recomienda desinstalar todas y volver a instalar. - **Desintalaion de plantillas** + **Desinstalación de plantillas** ```bash dotnet new uninstall RandAMediaLabGroup.EasyWindowsApplication.Templates dotnet new uninstall src\ProjectTemplates\EasyWinApp dotnet new uninstall src\ProjectTemplates\SimpleEasyWinApp - // Puedes desintalar todas las plantillas en un solo paso - dotnet new uninstall src\ProjectTemplates\ + // Puedes desinstalar todas las plantillas en un solo paso + dotnet new uninstall src\ProjectTemplates\ ``` 3. **Crea un nuevo proyecto:** @@ -153,14 +153,17 @@ WindowsApplication EasyWindowsApplication/src/ ├── EasyWindowsApplication/ # Framework principal │ ├── Core/ # Fases Fluent API, Resources, Behavior, MasterRouter (100% internal) -│ │ ├── Windowing/ # WindowImpl, AlternativeWindowImpl, Procedures (HWND-based WndProc) -│ │ └── LayoutEngine/ # ILayoutBuilder, Grid/Stack/Dock layouts, ViewBuilder -│ ├── Common/ # ControlActivatorRegistry + ControlActivatorGenerator (IIncrementalGenerator, InternalsVisibleTo) -│ ├── Share/ # API pública usuario (IWindow, ILayoutBuilder, View struct, Color, Thickness) -│ │ └── Infrastructure/ # Técnicos [EditorBrowsable(Never)]: ControlAccess -│ ├── Win32ControlsModule/ # IButton, ILabel... + Backend (Button, Label → Core.ControlBase) -│ └── WindowsApplication.cs # Punto de entrada Fluent API (devuelve Share/IApplicationLayoutPhase) -├── EasyWindowsApplication.Generators/ # Source Generator (FQN Share.* + GetTypeByMetadataName) +│ │ ├── Constants.cs / Entities.cs / Win32.cs # Interop Win32 central ([LibraryImport], MSG/POINT/WM/WS/…) +│ │ ├── Procedures.cs # RunMessageLoop (GetMessageW/Translate/Dispatch) +│ │ ├── UiDefaults/ # IDefaultUiValues + UiDefaultsProvider + FontSpec (DPI scaling) +│ │ ├── Windowing/ # WindowImpl, AlternativeWindowImpl, Procedures.CreateMainWindow/Alternative, Win32/Entities/Enums, UserControl +│ │ └── LayoutEngine/ # ILayoutBuilder, Grid/Stack/Dock layouts, ViewBuilder, ContentBuilder, ILayoutStrategy +│ ├── Common/ # ControlActivatorRegistry + IControlActivator + INativeHandleFactory + Win32Helpers (HIWORD/LOWORD) +│ ├── Share/ # API pública usuario (IBaseWindow→IWindow/IAlternativeWindow/IView, IWindowConfig, IChildrenBuilder con 3 overloads View, View sealed class + ViewBase, Color/Thickness/LayoutLength/GridDefinitions) +│ │ └── Infrastructure/ # Técnicos [EditorBrowsable(Never)]: ControlAccess (único tipo aquí) +│ ├── Win32ControlsModule/ # Frontend: IButton/ILabel/… (30 interfaces) + Backend: Button/Label→Core.ControlBase + Win32UiDefaults/Win32NativeHandleFactory/ControlProcedures (GDI/User32) +│ └── WindowsApplication.cs # Punto de entrada Fluent API (devuelve Share/IApplicationLayoutPhase, static ctor → EnsureInitialized) +├── EasyWindowsApplication.Generators/ # Source Generators (EasyBehaviorGenerator EAWIN001/EAWIN002 + ControlActivatorGenerator) └── ProjectTemplates/ ├── EasyWinApp/ # Plantilla `dotnet new easywinapp` └── SimpleEasyWinApp/ # Plantilla `dotnet new simpleeasywinapp` @@ -168,9 +171,15 @@ EasyWindowsApplication/src/ ## Cómo funciona -El **Source Generator** `EasyBehaviorGenerator` analiza tu `Layout` en tiempo de compilación: cada `.Name("BtnGuardar")` dentro de un `View` (struct `View` wrapper con `Action>` + `Func,View>`) o `Window` genera una propiedad tipada (`bh.BtnGuardar`) en `Behavior`. En runtime, el `MasterRouter` usa un trampolín `[UnmanagedCallersOnly]` con lookup HWND-based (`HandleRegistry.ConcurrentDictionary`) y despacha eventos tipados (`Click`, etc.); `ControlActivatorRegistry` en `Common/` (poblado en `Application.Initialize()` vía `ControlActivatorGenerator` — `IIncrementalGenerator` con `InternalsVisibleTo`, MEF compile-time) elimina el acoplamiento `Core → Win32Controls` sin `ModuleInitializer` (`CA2255`). +El **Source Generator** `EasyBehaviorGenerator` analiza tu `Layout` en tiempo de compilación: cada `.Name("BtnGuardar")` dentro de un `View` (`public sealed class View where T : class, IControl` con 3 overloads `Action>` + `Func,View>` + `View(Action)` para controles custom) o `Window`/`AlternativeWindow` genera una propiedad tipada (`bh.BtnGuardar`) en `Behavior`. En runtime, `Application.Initialize()` registra `UiDefaultsProvider.Set(new Win32UiDefaults())` antes de crear HWNDs — `Button.MeasureContent` y `ControlProcedures.GetDefaultFont` consumen esos defaults con DPI scaling (`96→dpiActual`); el `MasterRouter` (376 líneas) usa un trampolín `[UnmanagedCallersOnly]` con lookup HWND-based (`HandleRegistry.ConcurrentDictionary` + 5 diccionarios adicionales) y despacha el ciclo completo `CLOSE/SIZE/MOVE/SCROLL/DPI/ERASEBKGND/CTLCOLOR/DESTROY` + eventos tipados (`WM.COMMAND→Click`); `Core/Procedures.RunMessageLoop` (`GetMessageW/Translate/Dispatch`) está separado de `Win32ControlsModule/Backend/Procedures.ControlProcedures` (capa GDI/User32); `ControlActivatorRegistry` en `Common/` (`Register/Create` + `RegisterFactory/TryGetFactory` + `INativeHandleFactory`, poblado en `Application.Initialize()` vía `ControlActivatorGenerator` — `IIncrementalGenerator` con `InternalsVisibleTo`, MEF compile-time) elimina el acoplamiento `Core → Win32Controls` sin `ModuleInitializer` (`CA2255`). -Para usar los controles nativos del módulo Win32, activa `UseWinApi()` en Resources. **Es obligatorio**: si declaras un control (`View`) sin él, el compilador lo rechaza con `EAWIN002` y no se genera ningún accessor: +Para usar los controles nativos del módulo Win32, activa `UseWinApi()` en Resources. **Es obligatorio y solo compile-time** (`EAWIN002`): `SettingsBuilderImpl.UseWinApi()` retorna `this` (no-op en runtime); el Source Generator lo usa para decidir si emite accessors tipados. El mecanismo **runtime** real es `Core/UiDefaults` (`UiDefaultsProvider.Set(new Win32UiDefaults())` en `Application.Initialize()`): + +```text +Win32UiDefaults (IDefaultUiValues) → UiDefaultsProvider.Set() → ControlProcedures.GetDefaultFont() / Button.MeasureContent() (DPI 96→actual) +``` + +Si declaras un control (`View` donde `T : IControl`) sin `UseWinApi()`, el compilador lo rechaza con `EAWIN002` y no se genera ningún accessor: ```csharp WindowsApplication diff --git a/src/system_design.md b/src/system_design.md index 176ae4f..c12b1db 100644 --- a/src/system_design.md +++ b/src/system_design.md @@ -28,13 +28,15 @@ WindowsApplication.Resources(...).Layout(...).Initialize(); - Cuando se pone `.` despues de `Behavior(...)`, el IntelliSense debe mostrar `Initialize`. - Cuando se pone `.` despues de `Initialize()`, el IntelliSense no debe recomendar nada. +> **Pipeline `Initialize()`:** `Application.Initialize()` ejecuta en orden: `1) UiDefaultsProvider.Set(new Win32UiDefaults())` → `2) InitCommonControlsEx(STANDARD_CLASSES)` → `3) ControlActivatorRegistry.EnsureInitialized()` → `4) new MasterRouter(registry)` → `5) foreach window: CreateMainWindow/CreateAlternativeWindow + MaterializeContent + RegisterWindow` → `6) Behavior(registry)` → `7) Procedures.RunMessageLoop()`. `UiDefaults` debe ir primero porque `GetDefaultFont`/`MeasureContent` lo leen con DPI scaling. + # Flujo en **Resources** ```csharp WindowsApplication .Resources(rd => { rd.Setting(st => st - .UseWinApi() + .UseWinApi() // gate solo compile-time (EAWIN002); runtime es no-op → UiDefaultsProvider.Set(Win32UiDefaults) en Application.Initialize() .AppConfigFile(nm => nm.Path("./appsettings.json").WithAutoSave()) ); rd.Services(sr => sr.Singleton()); @@ -46,7 +48,7 @@ WindowsApplication .Dimensions(420, 280) .Content(c => c .Children(ch => ch - .View