From 15e27a13fad8f7df58ecc5ab805c928043ad248c Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Fri, 14 Aug 2026 14:20:35 -0600 Subject: [PATCH 1/5] refactor: introduce Share/Infrastructure namespace for technical public types --- CONTRIBUTING.md | 11 ++++++----- README.md | 2 +- .../EasyBehaviorGenerator.cs | 8 ++++---- .../CoreModule/Backend/Application.cs | 2 ++ .../CoreModule/Backend/HandleRegistry.cs | 3 +++ src/EasyWindowsApplication/CoreModule/Backend/RECT.cs | 2 +- .../CoreModule/Frontend/BehaviorBuilderImpl.cs | 9 ++++++++- .../CoreModule/Frontend/IBehaviorBuilder.cs | 2 +- .../CoreModule/Frontend/IWin32State.cs | 2 +- .../{ => Share/Infrastructure}/ControlAccess.cs | 4 +++- .../Backend => Share/Infrastructure}/ControlBase.cs | 7 ++++++- .../Infrastructure}/IconGenerator.cs | 4 +++- .../Frontend => Share/Infrastructure}/View.cs | 7 +++++-- .../Win32ControlsModule/Backend/Controls.cs | 1 + .../Win32ControlsModule/Frontend/IpAddress.cs | 1 + .../WindowingModule/Backend/UserControl.cs | 1 + .../WindowingModule/Frontend/WindowsScroll.cs | 2 +- src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj | 2 +- .../SimpleEasyWinApp/SimpleEasyWinApp.csproj | 2 +- src/Sample/Sample.csproj | 2 +- src/creators_vision.md | 3 ++- 21 files changed, 53 insertions(+), 24 deletions(-) rename src/EasyWindowsApplication/{ => Share/Infrastructure}/ControlAccess.cs (84%) rename src/EasyWindowsApplication/{CoreModule/Backend => Share/Infrastructure}/ControlBase.cs (97%) rename src/EasyWindowsApplication/{BuildSupport => Share/Infrastructure}/IconGenerator.cs (94%) rename src/EasyWindowsApplication/{WindowingModule/Frontend => Share/Infrastructure}/View.cs (72%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee1a035..ad2055c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,15 +59,16 @@ El framework está organizado en módulos bajo `src/EasyWindowsApplication/`: |---|---|---| | `CoreModule` | Backend + Frontend | Punto de entrada (`WindowsApplication`), fases del Fluent API, Resources, Behavior, MasterRouter, HandleRegistry | | `LayoutModule` | Backend + Frontend | `ILayoutBuilder`, layouts (Grid, Stack, Dock), `IContentBuilder`, `IChildrenBuilder`, `IViewBuilder` | -| `Share` | Frontend (público) | Tipos públicos compartidos: `Color`, `Thickness`, `IContentBuilder`, `LayoutLength`, `LayoutOptions` | +| `Share` | Frontend (público) | Tipos públicos de la API de usuario: `Color`, `Thickness`, `IContentBuilder`, `LayoutLength`, `LayoutOptions` | +| `Share/Infrastructure` | Frontend (público técnico) | Tipos `public` **por razones técnicas** con `[EditorBrowsable(Never)]`: `ControlAccess`, `IconGenerator`, `ControlBase`, `View` | | `Win32ControlsModule` | Backend + Frontend | Interfaces de controles (`IButton`, `ILabel`, `IEdit`, ...) y sus implementaciones Win32 | | `WindowingModule` | Backend + Frontend | Ventanas (`IWindow`, `IAlternativeWindow`), posicionamiento (`WindowPositionOnScreen`) | -| `BuildSupport` | Frontend (público) | `IconGenerator` — pipeline SVG→ICO en build-time | ### Principios de organización -- **`Common`** — código compartido entre módulos, siempre `internal`. -- **`Share`** — código compartido entre módulos que también es **público** para consumidores del framework. +- **`Common`** — código compartido entre módulos, pero de acceso **`internal`** (no visible para consumidores). +- **`Share`** — código compartido entre módulos, pero de acceso **`public`** (expuesto a consumidores del framework). +- **`Share/Infrastructure`** — código compartido entre módulos, `public` **por razones técnicas** (lo alcanzan el código generado, los targets de build o la herencia interna), pero **NO** parte de la API de usuario → obligatorio `[EditorBrowsable(EditorBrowsableState.Never)]`. Si un archivo ahí no lo tiene, es un bug. - **`CoreModule/Frontend`** — interfaces de las fases del Fluent API (`IApplicationLayoutPhase` → `IApplicationPostLayoutPhase` → `IApplicationPostBehaviorPhase`) que guían por IntelliSense el orden correcto. - **`Backend`** — implementaciones `internal` de esas interfaces. @@ -156,7 +157,7 @@ Documenta aquí las decisiones de arquitectura relevantes. Usa ADRs (Architectur - **Fluent API con interfaces de fase** — El IntelliSense guía el orden `Resources → Layout → Behavior → Initialize` mediante interfaces (`IApplicationLayoutPhase`, `IApplicationPostLayoutPhase`, `IApplicationPostBehaviorPhase`). - **Controles como interfaces** (`IButton`, `ILabel`, ...) — permite que el Source Generator genere tipos débilesmente acoplados y facilita testing/mocking. - **MasterRouter** — centraliza el bucle de mensajes (`WndProc` → `DefWindowProcW`) y despacha eventos tipados (`WM.COMMAND` → `Click`). -- **ControlAccess** — punto de acceso público para resolver controles por nombre desde el Behavior (usado por las propiedades generadas). +- **ControlAccess** — punto de acceso público para resolver controles por nombre desde el Behavior (usado por las propiedades generadas). Vive en `Share/Infrastructure` (técnico, `[EditorBrowsable(Never)]`). `SetController` se cablea en `Application.Behavior(...)`. --- diff --git a/README.md b/README.md index 0a99f17..f04bdb8 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,9 @@ EasyWindowsApplication/src/ │ ├── CoreModule/ # Fases del Fluent API, Resources, Behavior │ ├── LayoutModule/ # ILayoutBuilder, Grid, Stack layouts │ ├── Share/ # Tipos públicos (Color, Thickness, IContentBuilder) +│ │ └── Infrastructure/ # Tipos públicos técnicos ([EditorBrowsable(Never)]) │ ├── Win32ControlsModule/ # IButton, ILabel, IEdit, ICheckBox, ... │ ├── WindowingModule/ # IWindow, WindowPositionOnScreen -│ ├── BuildSupport/ # IconGenerator (pipeline SVG→ICO en build-time) │ └── WindowsApplication.cs # Punto de entrada del Fluent API ├── EasyWindowsApplication.Generators/ # Source Generator (EasyBehaviorGenerator) └── ProjectTemplates/ diff --git a/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs b/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs index f795edc..5680fff 100644 --- a/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs +++ b/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs @@ -78,13 +78,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (item.IsWindowType) { sb.AppendLine($" public {item.TypeSymbol} {propName}"); - sb.AppendLine($" => global::EasyWindowsApplication.ControlAccess"); + sb.AppendLine($" => global::EasyWindowsApplication.Share.Infrastructure.ControlAccess"); sb.AppendLine($" .GetWindow<{item.TypeSymbol}>(\"{item.Name}\");"); } else { sb.AppendLine($" public {item.TypeSymbol} {propName}"); - sb.AppendLine($" => global::EasyWindowsApplication.ControlAccess"); + sb.AppendLine($" => global::EasyWindowsApplication.Share.Infrastructure.ControlAccess"); sb.AppendLine($" .Get<{item.TypeSymbol}>(\"{item.Name}\");"); } sb.AppendLine(); @@ -117,13 +117,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (item.IsWindowType) { sb.AppendLine($" internal static {item.TypeSymbol} {propName}"); - sb.AppendLine($" => global::EasyWindowsApplication.ControlAccess"); + sb.AppendLine($" => global::EasyWindowsApplication.Share.Infrastructure.ControlAccess"); sb.AppendLine($" .GetWindow<{item.TypeSymbol}>(\"{item.Name}\");"); } else { sb.AppendLine($" internal static {item.TypeSymbol} {propName}"); - sb.AppendLine($" => global::EasyWindowsApplication.ControlAccess"); + sb.AppendLine($" => global::EasyWindowsApplication.Share.Infrastructure.ControlAccess"); sb.AppendLine($" .Get<{item.TypeSymbol}>(\"{item.Name}\");"); } sb.AppendLine(); diff --git a/src/EasyWindowsApplication/CoreModule/Backend/Application.cs b/src/EasyWindowsApplication/CoreModule/Backend/Application.cs index a19f37a..92e4a90 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/Application.cs +++ b/src/EasyWindowsApplication/CoreModule/Backend/Application.cs @@ -2,6 +2,7 @@ using EasyWindowsApplication.LayoutModule.Backend; using EasyWindowsApplication.LayoutModule.Frontend; using EasyWindowsApplication.Share; +using EasyWindowsApplication.Share.Infrastructure; using EasyWindowsApplication.WindowingModule.Frontend; namespace EasyWindowsApplication.CoreModule.Backend; @@ -38,6 +39,7 @@ public IApplicationPostLayoutPhase Layout(Action configure) public IApplicationPostBehaviorPhase Behavior(Action configure) { BehaviorBuilder.Registry = _registry; + ControlAccess.SetController(BehaviorBuilder); configure(BehaviorBuilder); return this; } diff --git a/src/EasyWindowsApplication/CoreModule/Backend/HandleRegistry.cs b/src/EasyWindowsApplication/CoreModule/Backend/HandleRegistry.cs index 5cebbbc..c25b5e0 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/HandleRegistry.cs +++ b/src/EasyWindowsApplication/CoreModule/Backend/HandleRegistry.cs @@ -33,4 +33,7 @@ internal void RegisterWindow(IBaseWindow window) internal T? GetWindow(string name) where T : class, IBaseWindow => _windowByName.TryGetValue(name, out var w) ? w as T : null; + + internal IBaseWindow? GetWindow(string name) + => _windowByName.TryGetValue(name, out var w) ? w : null; } diff --git a/src/EasyWindowsApplication/CoreModule/Backend/RECT.cs b/src/EasyWindowsApplication/CoreModule/Backend/RECT.cs index 67f425a..cc6ac0a 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/RECT.cs +++ b/src/EasyWindowsApplication/CoreModule/Backend/RECT.cs @@ -3,7 +3,7 @@ namespace EasyWindowsApplication.CoreModule.Backend; [StructLayout(LayoutKind.Sequential)] -public struct RECT +internal struct RECT { public int Left; public int Top; diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs b/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs index 4a1e0fa..d3c0063 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs @@ -1,10 +1,11 @@ using EasyWindowsApplication.CoreModule.Backend; +using EasyWindowsApplication.Share.Infrastructure; using EasyWindowsApplication.Win32ControlsModule.Frontend; using EasyWindowsApplication.WindowingModule.Frontend; namespace EasyWindowsApplication.CoreModule.Frontend; -internal sealed class BehaviorBuilderImpl : IBehaviorBuilder +internal sealed class BehaviorBuilderImpl : IBehaviorBuilder, ControlAccess.IBehaviorServicesController { internal Action? Win32Configurator { get; private set; } internal HandleRegistry? Registry { get; set; } @@ -38,6 +39,12 @@ public T Get(string name) where T : View return (T)control; } + T ControlAccess.IBehaviorServicesController.Get(string name) + => (T)Registry!.GetByName(name)!; + + T ControlAccess.IBehaviorServicesController.GetWindow(string name) + => (T)Registry!.GetWindow(name)!; + internal void ApplyPending(HandleRegistry registry) { foreach (var (name, handler) in _pendingClicks) diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs index 2731e7c..3196f51 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs @@ -1,5 +1,5 @@ using System.ComponentModel; -using EasyWindowsApplication.WindowingModule.Frontend; +using EasyWindowsApplication.Share.Infrastructure; namespace EasyWindowsApplication.CoreModule.Frontend; diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs index 1b8af8b..0c0f607 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs @@ -1,4 +1,4 @@ -using EasyWindowsApplication.WindowingModule.Frontend; +using EasyWindowsApplication.Share.Infrastructure; namespace EasyWindowsApplication.CoreModule.Frontend; diff --git a/src/EasyWindowsApplication/ControlAccess.cs b/src/EasyWindowsApplication/Share/Infrastructure/ControlAccess.cs similarity index 84% rename from src/EasyWindowsApplication/ControlAccess.cs rename to src/EasyWindowsApplication/Share/Infrastructure/ControlAccess.cs index 0fe3d75..1ff78a5 100644 --- a/src/EasyWindowsApplication/ControlAccess.cs +++ b/src/EasyWindowsApplication/Share/Infrastructure/ControlAccess.cs @@ -1,8 +1,10 @@ +using System.ComponentModel; using EasyWindowsApplication.Win32ControlsModule.Frontend; using EasyWindowsApplication.WindowingModule.Frontend; -namespace EasyWindowsApplication; +namespace EasyWindowsApplication.Share.Infrastructure; +[EditorBrowsable(EditorBrowsableState.Never)] public static class ControlAccess { private static IBehaviorServicesController? _controller; diff --git a/src/EasyWindowsApplication/CoreModule/Backend/ControlBase.cs b/src/EasyWindowsApplication/Share/Infrastructure/ControlBase.cs similarity index 97% rename from src/EasyWindowsApplication/CoreModule/Backend/ControlBase.cs rename to src/EasyWindowsApplication/Share/Infrastructure/ControlBase.cs index b96e8f8..939f888 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/ControlBase.cs +++ b/src/EasyWindowsApplication/Share/Infrastructure/ControlBase.cs @@ -1,14 +1,19 @@ +using System.ComponentModel; using System.Runtime.InteropServices; +using EasyWindowsApplication.CoreModule.Backend; using EasyWindowsApplication.LayoutModule.Backend; using EasyWindowsApplication.LayoutModule.Frontend; using EasyWindowsApplication.Share; using EasyWindowsApplication.Win32ControlsModule.Frontend; using static EasyWindowsApplication.CoreModule.Backend.Win32; -namespace EasyWindowsApplication.CoreModule.Backend; +namespace EasyWindowsApplication.Share.Infrastructure; +[EditorBrowsable(EditorBrowsableState.Never)] public abstract class ControlBase : IControl, IClickEventSource, ILayoutable, IDockable { + internal ControlBase() { } + public nint Hwnd { get; internal set; } public string Name { get; set; } = ""; diff --git a/src/EasyWindowsApplication/BuildSupport/IconGenerator.cs b/src/EasyWindowsApplication/Share/Infrastructure/IconGenerator.cs similarity index 94% rename from src/EasyWindowsApplication/BuildSupport/IconGenerator.cs rename to src/EasyWindowsApplication/Share/Infrastructure/IconGenerator.cs index 4e1530b..b1c99eb 100644 --- a/src/EasyWindowsApplication/BuildSupport/IconGenerator.cs +++ b/src/EasyWindowsApplication/Share/Infrastructure/IconGenerator.cs @@ -1,8 +1,10 @@ +using System.ComponentModel; using SkiaSharp; using Svg.Skia; -namespace EasyWindowsApplication.BuildSupport; +namespace EasyWindowsApplication.Share.Infrastructure; +[EditorBrowsable(EditorBrowsableState.Never)] public static class IconGenerator { public static void GenerateIco(string svgPath, string icoPath, int[] sizes) diff --git a/src/EasyWindowsApplication/WindowingModule/Frontend/View.cs b/src/EasyWindowsApplication/Share/Infrastructure/View.cs similarity index 72% rename from src/EasyWindowsApplication/WindowingModule/Frontend/View.cs rename to src/EasyWindowsApplication/Share/Infrastructure/View.cs index c144397..569bbf5 100644 --- a/src/EasyWindowsApplication/WindowingModule/Frontend/View.cs +++ b/src/EasyWindowsApplication/Share/Infrastructure/View.cs @@ -1,10 +1,13 @@ -using EasyWindowsApplication.CoreModule.Backend; +using System.ComponentModel; using EasyWindowsApplication.Win32ControlsModule.Frontend; -namespace EasyWindowsApplication.WindowingModule.Frontend; +namespace EasyWindowsApplication.Share.Infrastructure; +[EditorBrowsable(EditorBrowsableState.Never)] public abstract class View : ControlBase where TSelf : View { + internal View() { } + public new TSelf OnMessage(uint msg, Win32MessageHandler handler) { base.OnMessage(msg, handler); diff --git a/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs b/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs index 0b6a54b..591b3cc 100644 --- a/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs +++ b/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs @@ -1,4 +1,5 @@ using EasyWindowsApplication.CoreModule.Backend; +using EasyWindowsApplication.Share.Infrastructure; using EasyWindowsApplication.Win32ControlsModule.Frontend; namespace EasyWindowsApplication.Win32ControlsModule.Backend; diff --git a/src/EasyWindowsApplication/Win32ControlsModule/Frontend/IpAddress.cs b/src/EasyWindowsApplication/Win32ControlsModule/Frontend/IpAddress.cs index f2e5305..908f150 100644 --- a/src/EasyWindowsApplication/Win32ControlsModule/Frontend/IpAddress.cs +++ b/src/EasyWindowsApplication/Win32ControlsModule/Frontend/IpAddress.cs @@ -1,3 +1,4 @@ +using EasyWindowsApplication.Share.Infrastructure; using EasyWindowsApplication.Win32ControlsModule.Backend; using EasyWindowsApplication.WindowingModule.Frontend; diff --git a/src/EasyWindowsApplication/WindowingModule/Backend/UserControl.cs b/src/EasyWindowsApplication/WindowingModule/Backend/UserControl.cs index d6a6130..18d4d07 100644 --- a/src/EasyWindowsApplication/WindowingModule/Backend/UserControl.cs +++ b/src/EasyWindowsApplication/WindowingModule/Backend/UserControl.cs @@ -1,5 +1,6 @@ using EasyWindowsApplication.CoreModule.Backend; using EasyWindowsApplication.LayoutModule.Backend; +using EasyWindowsApplication.Share.Infrastructure; namespace EasyWindowsApplication.WindowingModule.Backend; diff --git a/src/EasyWindowsApplication/WindowingModule/Frontend/WindowsScroll.cs b/src/EasyWindowsApplication/WindowingModule/Frontend/WindowsScroll.cs index 3a72428..176445e 100644 --- a/src/EasyWindowsApplication/WindowingModule/Frontend/WindowsScroll.cs +++ b/src/EasyWindowsApplication/WindowingModule/Frontend/WindowsScroll.cs @@ -2,7 +2,7 @@ namespace EasyWindowsApplication.WindowingModule.Frontend; -public sealed class WindowsScroll : IWindowsScrollConfig +internal sealed class WindowsScroll : IWindowsScrollConfig { public ScrollBarVisibility HorizontalScrollBarVisibility { get; set; } = ScrollBarVisibility.Default; public ScrollBarVisibility VerticalScrollBarVisibility { get; set; } = ScrollBarVisibility.Default; diff --git a/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj b/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj index 170cced..1f7f1ac 100644 --- a/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj +++ b/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj b/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj index 5c28f82..9d08de1 100644 --- a/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj +++ b/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/Sample/Sample.csproj b/src/Sample/Sample.csproj index e2e3875..5772094 100644 --- a/src/Sample/Sample.csproj +++ b/src/Sample/Sample.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/creators_vision.md b/src/creators_vision.md index 113ec5b..62a1c25 100644 --- a/src/creators_vision.md +++ b/src/creators_vision.md @@ -6,6 +6,7 @@ - `EasyWindowsApplication.Generators` es un proyecto de apoyo a la biblioteca `EasyWindowsApplication`. Mi idea es usar el poder de Source Generator para crear propiedades con el atributo `Name` declarados, donde el generador de código analiza en que contexto aparece en tiempo de compilación y genera una estructura fuertemente tipada (un strongly-typed alias). - `Common` es para código compartido y/o repetitivo entre los módulos, este siempre debe ser interno (internal). - `Share` es para código compartido y/o repetitivo entre los módulos (internamente), pero también externamente (public), para ser usados por los desarrolladores que emplean la biblioteca. +- `Share/Infrastructure` es para código compartido entre módulos que es `public` por razones técnicas (código generado, targets de build o herencia interna), pero NO parte de la API de usuario → debe llevar `[EditorBrowsable(EditorBrowsableState.Never)]`. - `WindowsApplication.cs` el punto de entrada del Fluent Api, debe ser simple, limpio, exponer lo que se necesita para la escribir el flujo evitando que se vuelva un fichero de codigo monolitico, esa es su unica funcion. - La aplicación se construye mediante una Fluent API declarativa dividida estrictamente en secciones secuenciales. Esto garantiza la separación de responsabilidades: ```csharp @@ -640,7 +641,7 @@ Un único fichero `Program.cs` (top-level statements) equivalente al ejemplo de | Paquete | Rol | |---|---| -| `EasyWindowsApplication` (NuGet) | Framework + BuildSupport (`IconGenerator`, `.targets` de icono incluido) | +| `EasyWindowsApplication` (NuGet) | Framework + `Share/Infrastructure` (`IconGenerator`, `.targets` de icono incluido) | | `SkiaSharp` (transitivo) | Solo build-time (generación de .ico desde SVG), `PrivateAssets=all` | | `EasyWindowsApplication.Templates` (NuGet) | Contiene los templates `dotnet new easywinapp` y `simpleeasywinapp` | From b7a489cc77e2239f3c4c157bda82feb9438118e6 Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Fri, 14 Aug 2026 14:20:39 -0600 Subject: [PATCH 2/5] refactor: remove parameterless fluent-phase overloads --- .../CoreModule/Frontend/IApplicationLayoutPhase.cs | 1 - .../CoreModule/Frontend/IApplicationPostLayoutPhase.cs | 1 - src/EasyWindowsApplication/WindowsApplication.cs | 4 ---- src/Sample/Program.cs | 1 + 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationLayoutPhase.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationLayoutPhase.cs index 87b3cb3..74078af 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationLayoutPhase.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationLayoutPhase.cs @@ -4,6 +4,5 @@ namespace EasyWindowsApplication.CoreModule.Frontend; public interface IApplicationLayoutPhase { - IApplicationPostLayoutPhase Layout(); IApplicationPostLayoutPhase Layout(Action configure); } \ No newline at end of file diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationPostLayoutPhase.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationPostLayoutPhase.cs index 6f927ed..7c5ffae 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationPostLayoutPhase.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/IApplicationPostLayoutPhase.cs @@ -2,7 +2,6 @@ namespace EasyWindowsApplication.CoreModule.Frontend; public interface IApplicationPostLayoutPhase { - IApplicationPostBehaviorPhase Behavior(); IApplicationPostBehaviorPhase Behavior(Action configure); void Initialize(); } \ No newline at end of file diff --git a/src/EasyWindowsApplication/WindowsApplication.cs b/src/EasyWindowsApplication/WindowsApplication.cs index 940af88..0b11dba 100644 --- a/src/EasyWindowsApplication/WindowsApplication.cs +++ b/src/EasyWindowsApplication/WindowsApplication.cs @@ -6,8 +6,6 @@ namespace EasyWindowsApplication; public static class WindowsApplication { - public static IApplicationLayoutPhase Resources() => new Application(); - public static IApplicationLayoutPhase Resources(Action configure) { var app = new Application(); @@ -15,8 +13,6 @@ public static IApplicationLayoutPhase Resources(Action con return app; } - public static IApplicationPostLayoutPhase Layout() => new Application(); - public static IApplicationPostLayoutPhase Layout(Action configure) => new Application().Layout(configure); } \ No newline at end of file diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index abb24d2..7059be0 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -2,6 +2,7 @@ using EasyWindowsApplication.Win32ControlsModule.Frontend; using EasyWindowsApplication.WindowingModule.Frontend; + int counter = 0; WindowsApplication From ddb9f1b5e2d1e35aae412441ea441b76b3998f56 Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Fri, 14 Aug 2026 17:51:12 -0600 Subject: [PATCH 3/5] refactor: empty IBehaviorBuilder marker, internalize Win32 plumbing --- README.md | 11 +++-- .../CoreModule/Backend/Application.cs | 2 - .../Frontend/BehaviorBuilderImpl.cs | 41 +------------------ .../CoreModule/Frontend/IBehaviorBuilder.cs | 15 +------ .../CoreModule/Frontend/IWin32State.cs | 2 +- src/Sample/Program.cs | 9 ++-- src/creators_vision.md | 2 +- 7 files changed, 14 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index f04bdb8..9237953 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ * **UX simplificada** — 4 bloques secuenciales (`Resources → Layout → Behavior → Initialize`) que reducen el esfuerzo de cableado y aceleran la entrega temprana. * **Fluent API declarativa** — El IntelliSense te guía de Resources a Layout a Behavior a Initialize, impidiendo equivocarte de orden. * **Source Generators** — Acceso tipado a controles por su nombre. Si te equivocas, falla en compilación, no en runtime. -* **Válvula de escape Win32** — `UseWinApi()` + `WithWin32State` para acceso directo al HWND y WndProc cuando lo necesites. +* **Válvula de escape Win32** — `UseWinApi()` habilita el uso del módulo de controles Win32 nativos de alto rendimiento (`Win32ControlsModule`) cuando lo necesites. ## Un vistazo al código @@ -159,19 +159,18 @@ EasyWindowsApplication/src/ El **Source Generator** analiza tu `Layout` en tiempo de compilación: cada `.Name("BtnGuardar")` dentro de un `View` o `Window` genera una propiedad tipada (`bh.BtnGuardar`) en `Behavior`. Al compilar, ya no necesitas localizar controles por string — si el nombre no coincide, el compilador te lo dice. En runtime, el `MasterRouter` centraliza el bucle de mensajes de Win32 y despacha eventos tipados (`Click`, etc.) de forma automatica. -Para acceso total al HWND y WndProc, activa `UseWinApi()` en Resources y usa `WithWin32State`: +Para usar los controles nativos del módulo Win32, activa `UseWinApi()` en Resources: ```csharp WindowsApplication .Resources(r => r.Setting(s => s.UseWinApi())) .Layout(...) - .Behavior(b => b.WithWin32State(ctrl => { - var btn = ctrl.Get("BtnGuardar"); - btn.OnMessage(WM.COMMAND, (w, l) => { /* tu código Win32 puro */ }); - })) + .Behavior(bh => { ... }) .Initialize(); ``` +> **Nota**: el acceso de bajo nivel al HWND/WndProc (`WithWin32State`) es plomería interna del framework; se cableará automáticamente según el patrón MVU cuando `UseWinApi()` esté activo. + ## ¿Desarrollas el framework? Toda la información para compilar desde el código fuente, entender la arquitectura de módulos, el pipeline de generación de iconos y las decisiones de diseño se encuentran en [`CONTRIBUTING.md`](CONTRIBUTING.md) y en la **Wiki del repositorio**. diff --git a/src/EasyWindowsApplication/CoreModule/Backend/Application.cs b/src/EasyWindowsApplication/CoreModule/Backend/Application.cs index 92e4a90..60d1427 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/Application.cs +++ b/src/EasyWindowsApplication/CoreModule/Backend/Application.cs @@ -63,8 +63,6 @@ public void Initialize() RegisterMain(window); } - BehaviorBuilder.ApplyPending(_registry); - if (BehaviorBuilder.Win32Configurator is not null) BehaviorBuilder.Win32Configurator(new Win32StateImpl(_registry)); diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs b/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs index d3c0063..820cd81 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs @@ -1,7 +1,5 @@ using EasyWindowsApplication.CoreModule.Backend; using EasyWindowsApplication.Share.Infrastructure; -using EasyWindowsApplication.Win32ControlsModule.Frontend; -using EasyWindowsApplication.WindowingModule.Frontend; namespace EasyWindowsApplication.CoreModule.Frontend; @@ -9,49 +7,12 @@ internal sealed class BehaviorBuilderImpl : IBehaviorBuilder, ControlAccess.IBeh { internal Action? Win32Configurator { get; private set; } internal HandleRegistry? Registry { get; set; } - private readonly List<(string Name, Action Handler)> _pendingClicks = new(); - public IBehaviorBuilder OnClick(string controlName, Action handler) - { - var control = Registry?.GetByName(controlName); - if (control is IClickEventSource clickable) - { - clickable.AddClickHandler(handler); - } - else - { - _pendingClicks.Add((controlName, handler)); - } - return this; - } - - public IBehaviorBuilder WithWin32State(Action configure) - { - Win32Configurator = configure; - return this; - } - - public T Get(string name) where T : View - { - var control = Registry?.GetByName(name); - if (control is null) - throw new InvalidOperationException($"Control '{name}' not found."); - return (T)control; - } + internal void SetWin32Configurator(Action configure) => Win32Configurator = configure; T ControlAccess.IBehaviorServicesController.Get(string name) => (T)Registry!.GetByName(name)!; T ControlAccess.IBehaviorServicesController.GetWindow(string name) => (T)Registry!.GetWindow(name)!; - - internal void ApplyPending(HandleRegistry registry) - { - foreach (var (name, handler) in _pendingClicks) - { - if (registry.GetByName(name) is IClickEventSource clickable) - clickable.AddClickHandler(handler); - } - _pendingClicks.Clear(); - } } diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs index 3196f51..0ffc43f 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/IBehaviorBuilder.cs @@ -1,16 +1,3 @@ -using System.ComponentModel; -using EasyWindowsApplication.Share.Infrastructure; - namespace EasyWindowsApplication.CoreModule.Frontend; -public interface IBehaviorBuilder -{ - [EditorBrowsable(EditorBrowsableState.Never)] - IBehaviorBuilder OnClick(string controlName, Action handler); - - [EditorBrowsable(EditorBrowsableState.Never)] - IBehaviorBuilder WithWin32State(Action configure); - - [EditorBrowsable(EditorBrowsableState.Never)] - T Get(string name) where T : View; -} +public interface IBehaviorBuilder { } diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs index 0c0f607..4fba86f 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs @@ -2,7 +2,7 @@ namespace EasyWindowsApplication.CoreModule.Frontend; -public interface IWin32State +internal interface IWin32State { T Get(string name) where T : View; } diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index 7059be0..333b816 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -22,11 +22,12 @@ ) ) ) - .Behavior(bh => bh - .BtnIncrement.OnClick(() => + .Behavior(bh => + { + bh.BtnIncrement.OnClick(() => { counter++; bh.BtnIncrement.Text = $"Click: {counter}"; - }) - ) + }); + }) .Initialize(); diff --git a/src/creators_vision.md b/src/creators_vision.md index 62a1c25..24e13c6 100644 --- a/src/creators_vision.md +++ b/src/creators_vision.md @@ -118,7 +118,7 @@ - **`Behavior`**: Enrutamiento de eventos y lógica. Gracias a los Source Generators, los nombres dados en el `Layout` (ej. `BtnGuardar`) aparecen aquí fuertemente tipados, eliminando los *Magic Strings*.Soporta dos modalidades: * Behavior (Implícito/Por defecto): Para un flujo moderno basado en estados y señales, ideal para la mayoría de aplicaciones. -* Behavior: Válvula de escape para desarrolladores avanzados. Expone el HWND y permite interceptar mensajes directamente en el WndProc (ej. WM_COMMAND, WM_PAINT) para cada control declarado en el Layout, ofreciendo el mismo nivel de control que C/C++ nativo. +* Behavior: Válvula de escape para desarrolladores avanzados. Expone el HWND y permite interceptar mensajes directamente en el WndProc (ej. WM_COMMAND, WM_PAINT) para cada control declarado en el Layout, ofreciendo el mismo nivel de control que C/C++ nativo. Es plomería interna (`IWin32State` es `internal`), se cablea automáticamente al activar `UseWinApi()`. - **`Initialize()`**: El nodo terminal que bloquea el hilo, cede el control al `CoreModule` y arranca el bucle de mensajes de Win32. En el flujo, es el nodo terminal de la Fluent API. No debe confundirse con un hook de ciclo de vida ni con las acciones del IDE (Build, Run). Su semántica es: en este punto la declaración está completa, todo ha sido definido y el sistema está habilitado para arrancar. Es el equivalente conceptual de `Build()` en el patrón Builder clásico, con la diferencia de que el nombre refleja que los Source Generators ya han actuado en tiempo de compilación, por lo que al llegar aquí no se construye nada, simplemente se lanza lo que ya está construido. - Plantilla para Visual Studio, esta debe servir como un "esqueleto" (scaffolding) para crear las carpetas y poner los archivos placeholder. From 303f783d484cf9f311d36a4af6ce0cc33ab384f6 Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Sun, 16 Aug 2026 11:09:27 -0600 Subject: [PATCH 4/5] fix: align Svg.Skia version of IcoGen subproject with framework --- CONTRIBUTING.md | 4 +++- .../EasyWinApp/EasyWinApp.csproj | 2 +- .../SimpleEasyWinApp/SimpleEasyWinApp.csproj | 2 +- src/Sample/Sample.csproj | 2 +- src/Sample/Sample.ico | Bin 19784 -> 20134 bytes 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad2055c..b5ea058 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -140,6 +140,8 @@ 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`). + --- ## Native AOT @@ -235,7 +237,7 @@ Setup manual (una sola vez): ### Notas para mantenedores -- **`Svg.Skia` usa `*`** en `EasyWindowsApplication.csproj`. Si un build empieza a fallar tras una actualización de dependencias, considera *pinnear* la versión. +- **`Svg.Skia` usa `*`** en `EasyWindowsApplication.csproj`. Si un build empieza a fallar tras una actualización de dependencias, considera *pinnear* la versión — en ese caso actualiza también las 3 líneas del proyecto `IcoGen` (ver [Pipeline de iconos SVG→ICO](#pipeline-de-iconos-svg-ico)). - **Tags de GitHub**: `v{x.y.z}` (estable) o `v{x.y.z}-preview`. Si el workflow falla al crear el tag por duplicado, elimínalo manualmente (`git push origin --delete v{tag}` o desde la interfaz) y vuelve a disparar el workflow. --- diff --git a/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj b/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj index 1f7f1ac..c986b96 100644 --- a/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj +++ b/src/ProjectTemplates/EasyWinApp/EasyWinApp.csproj @@ -51,7 +51,7 @@ <_IcoProjLine Include=" <Nullable>enable</Nullable>" /> <_IcoProjLine Include=" </PropertyGroup>" /> <_IcoProjLine Include=" <ItemGroup>" /> - <_IcoProjLine Include=" <PackageReference Include="Svg.Skia" Version="5.1.1" />" /> + <_IcoProjLine Include=" <PackageReference Include="Svg.Skia" Version="%2A" />" /> <_IcoProjLine Include=" <ProjectReference Include="$(_EasyWAProj)" />" /> <_IcoProjLine Include=" </ItemGroup>" /> <_IcoProjLine Include="</Project>" /> diff --git a/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj b/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj index 9d08de1..9b1a1a5 100644 --- a/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj +++ b/src/ProjectTemplates/SimpleEasyWinApp/SimpleEasyWinApp.csproj @@ -51,7 +51,7 @@ <_IcoProjLine Include=" <Nullable>enable</Nullable>" /> <_IcoProjLine Include=" </PropertyGroup>" /> <_IcoProjLine Include=" <ItemGroup>" /> - <_IcoProjLine Include=" <PackageReference Include="Svg.Skia" Version="5.1.1" />" /> + <_IcoProjLine Include=" <PackageReference Include="Svg.Skia" Version="%2A" />" /> <_IcoProjLine Include=" <ProjectReference Include="$(_EasyWAProj)" />" /> <_IcoProjLine Include=" </ItemGroup>" /> <_IcoProjLine Include="</Project>" /> diff --git a/src/Sample/Sample.csproj b/src/Sample/Sample.csproj index 5772094..d76f94f 100644 --- a/src/Sample/Sample.csproj +++ b/src/Sample/Sample.csproj @@ -51,7 +51,7 @@ <_IcoProjLine Include=" <Nullable>enable</Nullable>" /> <_IcoProjLine Include=" </PropertyGroup>" /> <_IcoProjLine Include=" <ItemGroup>" /> - <_IcoProjLine Include=" <PackageReference Include="Svg.Skia" Version="5.1.1" />" /> + <_IcoProjLine Include=" <PackageReference Include="Svg.Skia" Version="%2A" />" /> <_IcoProjLine Include=" <ProjectReference Include="$(_EasyWAProj)" />" /> <_IcoProjLine Include=" </ItemGroup>" /> <_IcoProjLine Include="</Project>" /> diff --git a/src/Sample/Sample.ico b/src/Sample/Sample.ico index fae7f03d7d37a4349aea504a605e1c0a43d263b8..0f483cf1cc404166d09729d55dad153c2259986e 100644 GIT binary patch literal 20134 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 my87AV5TA;A8Cy}!wEth=JvbG(pnn4sbwQ;7 From f7a8e3eddf633a79ed485cd338e9e6bf92f7d9f4 Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Sun, 16 Aug 2026 11:10:12 -0600 Subject: [PATCH 5/5] feat: gate UseWinApi() at compile-time (EAWIN002) and remove escape valve --- CONTRIBUTING.md | 8 +- README.md | 9 +- .../EasyBehaviorGenerator.cs | 111 +++++++++++++++++- .../CoreModule/Backend/Application.cs | 16 --- .../Backend/ResourcesDictionaryImpl.cs | 2 - .../Frontend/BehaviorBuilderImpl.cs | 3 - .../CoreModule/Frontend/IWin32State.cs | 8 -- .../SimpleEasyWinApp/Program.cs | 1 + src/Sample/Program.cs | 14 ++- 9 files changed, 130 insertions(+), 42 deletions(-) delete mode 100644 src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5ea058..a35f0c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,12 +119,17 @@ El proyecto `EasyWindowsApplication.Generators` contiene `EasyBehaviorGenerator` } ``` 3. **Valida** duplicados en tiempo de compilación (`EAWIN001`: "Name '{0}' is already used..."). -4. **Genera propiedades `internal static`** en la clase envolvente del `Behavior` (cuando se usa un lambda `.Behavior(bh => ...)`), para acceso directo sin el prefijo `bh.`. +4. **Valida el gate `UseWinApi()`** en tiempo de compilación (`EAWIN002`): si se declara un control (`View` cuyo tipo hereda de `IControl` de `Win32ControlsModule`) sin activar `UseWinApi()`, emite un error y **no genera** ningún accessor de controles. Las ventanas no se gatean. +5. **Genera propiedades `internal static`** en la clase envolvente del `Behavior` (cuando se usa un lambda `.Behavior(bh => ...)`), para acceso directo sin el prefijo `bh.`. ### Detección de candidatos - **Nombre**: invocaciones `.Name("string")` dentro de lambdas `View()` / `Window()` / `AlternativeWindow()`. - **Behavior**: invocaciones `.Behavior(bh => ...)` con un lambda (no method-group). Si se pasa un method-group, el enclosing-type no se detecta, pero las extensiones globales sí se generan. +- **`UseWinApi()`**: invocaciones `.UseWinApi()` (0 args) cuya verificación semántica confirme el símbolo `ISettingsBuilder.UseWinApi` (solo existe dentro de `.Resources(...).Setting(...)`). +- **Control**: invocaciones `View(lambda)` cuyo `T` hereda de `EasyWindowsApplication.Win32ControlsModule.Frontend.IControl`. + +> **Naming**: referenciar un control desde `Behavior` requiere `.Name()` explícito; sin `.Name()` no se genera accessor (el control sigue funcionando en el Layout por HWND). El auto-naming fue evaluado y **rechazado** (deriva semántica silenciosa al editar el Layout). --- @@ -160,6 +165,7 @@ Documenta aquí las decisiones de arquitectura relevantes. Usa ADRs (Architectur - **Controles como interfaces** (`IButton`, `ILabel`, ...) — permite que el Source Generator genere tipos débilesmente acoplados y facilita testing/mocking. - **MasterRouter** — centraliza el bucle de mensajes (`WndProc` → `DefWindowProcW`) y despacha eventos tipados (`WM.COMMAND` → `Click`). - **ControlAccess** — punto de acceso público para resolver controles por nombre desde el Behavior (usado por las propiedades generadas). Vive en `Share/Infrastructure` (técnico, `[EditorBrowsable(Never)]`). `SetController` se cablea en `Application.Behavior(...)`. +- **`UseWinApi()` como bifurcación explícita** — elegir controles nativos por HWND (comctl32) frente a los futuros controles GDI/GDI+ personalizados. Es un gate **compile-time** (`EAWIN002`), no un flag decorativo: el consumidor es el Source Generator, que decide qué accessors existen. Con los módulos MVU futuros, lo que reciba el lambda de `.Behavior(...)` dependerá del mismo flag. --- diff --git a/README.md b/README.md index 9237953..2308226 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ * **UX simplificada** — 4 bloques secuenciales (`Resources → Layout → Behavior → Initialize`) que reducen el esfuerzo de cableado y aceleran la entrega temprana. * **Fluent API declarativa** — El IntelliSense te guía de Resources a Layout a Behavior a Initialize, impidiendo equivocarte de orden. * **Source Generators** — Acceso tipado a controles por su nombre. Si te equivocas, falla en compilación, no en runtime. -* **Válvula de escape Win32** — `UseWinApi()` habilita el uso del módulo de controles Win32 nativos de alto rendimiento (`Win32ControlsModule`) cuando lo necesites. +* **Controles Win32 nativos** — `UseWinApi()` es el *gate* obligatorio para declarar controles de `Win32ControlsModule` en `Layout`: sin él el compilador lo rechaza (EAWIN002); con él, el generador emite los accessors tipados. ## Un vistazo al código @@ -24,6 +24,7 @@ using EasyWindowsApplication.WindowingModule.Frontend; int counter = 0; WindowsApplication + .Resources(rd => rd.Setting(st => st.UseWinApi())) .Layout(ly => ly .Window(iw => iw .Name("MainWindow") @@ -119,7 +120,7 @@ El framework incluye **dos plantillas** oficiales: La fase `Resources` registra todo lo que tu app necesita antes de dibujar: * **Assets** — imágenes, iconos (`.svg` → `.ico` multi-resolución) y fuentes. -* **Settings** — configuración embebida y persistencia (`UseWinApi`, `AppConfigFile`). +* **Settings** — configuración embebida y persistencia (`UseWinApi`, `AppConfigFile`). `UseWinApi` es el *gate* obligatorio para usar controles Win32 nativos. * **Services** — contenedor de inyección de dependencias. ```csharp @@ -159,7 +160,7 @@ EasyWindowsApplication/src/ El **Source Generator** analiza tu `Layout` en tiempo de compilación: cada `.Name("BtnGuardar")` dentro de un `View` o `Window` genera una propiedad tipada (`bh.BtnGuardar`) en `Behavior`. Al compilar, ya no necesitas localizar controles por string — si el nombre no coincide, el compilador te lo dice. En runtime, el `MasterRouter` centraliza el bucle de mensajes de Win32 y despacha eventos tipados (`Click`, etc.) de forma automatica. -Para usar los controles nativos del módulo Win32, activa `UseWinApi()` en Resources: +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: ```csharp WindowsApplication @@ -169,7 +170,7 @@ WindowsApplication .Initialize(); ``` -> **Nota**: el acceso de bajo nivel al HWND/WndProc (`WithWin32State`) es plomería interna del framework; se cableará automáticamente según el patrón MVU cuando `UseWinApi()` esté activo. +> **Nota**: el acceso de bajo nivel al HWND/WndProc es plomería interna del framework. Con los futuros módulos MVU, lo que reciba el lambda de `.Behavior(...)` dependerá de que `UseWinApi()` esté activo — resuelto en compile-time por el Source Generator. ## ¿Desarrollas el framework? diff --git a/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs b/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs index 5680fff..b1f4fa8 100644 --- a/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs +++ b/src/EasyWindowsApplication.Generators/EasyBehaviorGenerator.cs @@ -20,6 +20,14 @@ public sealed class EasyBehaviorGenerator : IIncrementalGenerator DiagnosticSeverity.Error, isEnabledByDefault: true); + private static readonly DiagnosticDescriptor UseWinApiRequiredRule = new( + id: "EAWIN002", + title: "UseWinApi required", + messageFormat: "UseWinApi() is required to use Win32 controls in Layout. Add .Resources(s => s.Setting(x => x.UseWinApi())).", + category: "EasyWindowsApplication", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + public void Initialize(IncrementalGeneratorInitializationContext context) { var namedItems = context.SyntaxProvider.CreateSyntaxProvider( @@ -37,9 +45,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var enclosingType = behaviorCalls.Collect(); - context.RegisterSourceOutput(collected.Combine(enclosingType), static (spc, pair) => + var useWinApi = context.SyntaxProvider.CreateSyntaxProvider( + predicate: static (node, _) => IsUseWinApiCandidate(node), + transform: static (ctx, _) => IsUseWinApiSymbol(ctx)) + .Where(static enabled => enabled) + .Collect() + .Select(static (items, _) => !items.IsDefaultOrEmpty); + + var controlItems = context.SyntaxProvider.CreateSyntaxProvider( + predicate: static (node, _) => IsViewControlCandidate(node), + transform: static (ctx, _) => ExtractControlLocation(ctx)) + .Where(static location => location is not null); + + var controlLocations = controlItems.Collect(); + + context.RegisterSourceOutput( + collected.Combine(enclosingType).Combine(useWinApi).Combine(controlLocations), + static (spc, quadruple) => { - var (items, enclosing) = pair; + var (((items, enclosing), usesWinApi), controls) = quadruple; + + if (!usesWinApi && !controls.IsDefaultOrEmpty) + { + foreach (var location in controls) + spc.ReportDiagnostic(Diagnostic.Create(UseWinApiRequiredRule, location)); + } + if (items.IsDefaultOrEmpty) return; var seen = new HashSet(); @@ -74,6 +105,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) foreach (var item in unique) { + if (!usesWinApi && !item.IsWindowType) continue; var propName = SanitizeName(item.Name); if (item.IsWindowType) { @@ -113,6 +145,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) foreach (var item in unique) { + if (!usesWinApi && !item.IsWindowType) continue; var propName = SanitizeName(item.Name); if (item.IsWindowType) { @@ -226,6 +259,80 @@ private static bool IsBehaviorCandidate(SyntaxNode node) return true; } + private static bool IsUseWinApiCandidate(SyntaxNode node) + { + if (node is not InvocationExpressionSyntax inv) + return false; + if (inv.Expression is not MemberAccessExpressionSyntax ma) + return false; + if (ma.Name.Identifier.Text != "UseWinApi") + return false; + if (inv.ArgumentList.Arguments.Count != 0) + return false; + return true; + } + + private static bool IsUseWinApiSymbol(GeneratorSyntaxContext context) + { + var inv = (InvocationExpressionSyntax)context.Node; + if (inv.Expression is not MemberAccessExpressionSyntax ma) + return false; + var symbol = context.SemanticModel.GetSymbolInfo(ma).Symbol; + return symbol is IMethodSymbol method + && method.Name == "UseWinApi" + && method.ContainingType is { } containingType + && containingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + == "global::EasyWindowsApplication.CoreModule.Frontend.ISettingsBuilder"; + } + + private static bool IsViewControlCandidate(SyntaxNode node) + { + if (node is not InvocationExpressionSyntax inv) + return false; + if (inv.Expression is not MemberAccessExpressionSyntax ma) + return false; + if (ma.Name is not GenericNameSyntax generic) + return false; + if (generic.Identifier.Text != "View") + return false; + if (generic.TypeArgumentList.Arguments.Count != 1) + return false; + if (inv.ArgumentList.Arguments.Count != 1) + return false; + if (inv.ArgumentList.Arguments[0].Expression is not LambdaExpressionSyntax) + return false; + return true; + } + + private static Location ExtractControlLocation(GeneratorSyntaxContext context) + { + var inv = (InvocationExpressionSyntax)context.Node; + if (inv.Expression is not MemberAccessExpressionSyntax ma) + return null; + if (ma.Name is not GenericNameSyntax generic) + return null; + + var typeSymbol = context.SemanticModel.GetTypeInfo(generic.TypeArgumentList.Arguments[0]).Type; + if (typeSymbol is null) + return null; + + var iControl = context.SemanticModel.Compilation.GetTypeByMetadataName( + "EasyWindowsApplication.Win32ControlsModule.Frontend.IControl"); + if (iControl is null) + return null; + + if (typeSymbol.Equals(iControl, SymbolEqualityComparer.Default)) + return inv.GetLocation(); + + foreach (var iface in typeSymbol.AllInterfaces) + { + if (iface.Equals(iControl, SymbolEqualityComparer.Default)) + return inv.GetLocation(); + } + + return null; + } + private static EnclosingTypeInfo ExtractEnclosingType(GeneratorSyntaxContext context) { var current = context.Node.Parent; diff --git a/src/EasyWindowsApplication/CoreModule/Backend/Application.cs b/src/EasyWindowsApplication/CoreModule/Backend/Application.cs index 60d1427..3f1e10f 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/Application.cs +++ b/src/EasyWindowsApplication/CoreModule/Backend/Application.cs @@ -63,9 +63,6 @@ public void Initialize() RegisterMain(window); } - if (BehaviorBuilder.Win32Configurator is not null) - BehaviorBuilder.Win32Configurator(new Win32StateImpl(_registry)); - Procedures.RunMessageLoop(); } @@ -91,17 +88,4 @@ private void RegisterAlternative(WindowModel window) _registry.RegisterWindow(new WindowingModule.Backend.AlternativeWindowImpl( 0, 0, window.Name, window.Title, window.Width, window.Height, window.Position)); } -} - -internal sealed class Win32StateImpl : IWin32State -{ - private readonly HandleRegistry _registry; - internal Win32StateImpl(HandleRegistry registry) => _registry = registry; - - public T Get(string name) where T : View - { - var control = _registry.GetByName(name) - ?? throw new InvalidOperationException($"Control '{name}' not found in the layout."); - return (T)control; - } } \ No newline at end of file diff --git a/src/EasyWindowsApplication/CoreModule/Backend/ResourcesDictionaryImpl.cs b/src/EasyWindowsApplication/CoreModule/Backend/ResourcesDictionaryImpl.cs index b2aa786..11e69dd 100644 --- a/src/EasyWindowsApplication/CoreModule/Backend/ResourcesDictionaryImpl.cs +++ b/src/EasyWindowsApplication/CoreModule/Backend/ResourcesDictionaryImpl.cs @@ -22,12 +22,10 @@ public IResourcesDictionary Services(Action configure) internal sealed class SettingsBuilderImpl : ISettingsBuilder { - internal bool IsWinApiEnabled { get; private set; } internal AppConfigFileConfig AppConfig { get; } = new(); public ISettingsBuilder UseWinApi() { - IsWinApiEnabled = true; return this; } diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs b/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs index 820cd81..300834b 100644 --- a/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs +++ b/src/EasyWindowsApplication/CoreModule/Frontend/BehaviorBuilderImpl.cs @@ -5,11 +5,8 @@ namespace EasyWindowsApplication.CoreModule.Frontend; internal sealed class BehaviorBuilderImpl : IBehaviorBuilder, ControlAccess.IBehaviorServicesController { - internal Action? Win32Configurator { get; private set; } internal HandleRegistry? Registry { get; set; } - internal void SetWin32Configurator(Action configure) => Win32Configurator = configure; - T ControlAccess.IBehaviorServicesController.Get(string name) => (T)Registry!.GetByName(name)!; diff --git a/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs b/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs deleted file mode 100644 index 4fba86f..0000000 --- a/src/EasyWindowsApplication/CoreModule/Frontend/IWin32State.cs +++ /dev/null @@ -1,8 +0,0 @@ -using EasyWindowsApplication.Share.Infrastructure; - -namespace EasyWindowsApplication.CoreModule.Frontend; - -internal interface IWin32State -{ - T Get(string name) where T : View; -} diff --git a/src/ProjectTemplates/SimpleEasyWinApp/Program.cs b/src/ProjectTemplates/SimpleEasyWinApp/Program.cs index abb24d2..d5f5044 100644 --- a/src/ProjectTemplates/SimpleEasyWinApp/Program.cs +++ b/src/ProjectTemplates/SimpleEasyWinApp/Program.cs @@ -5,6 +5,7 @@ int counter = 0; WindowsApplication + .Resources(rd => rd.Setting(st => st.UseWinApi())) .Layout(ly => ly .Window(iw => iw .Name("MainWindow") diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index 333b816..2e5e305 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -2,10 +2,10 @@ using EasyWindowsApplication.Win32ControlsModule.Frontend; using EasyWindowsApplication.WindowingModule.Frontend; - int counter = 0; WindowsApplication + .Resources(rd => rd.Setting(st => st.UseWinApi())) .Layout(ly => ly .Window(iw => iw .Name("MainWindow") @@ -13,12 +13,14 @@ .Dimensions(420, 280) .Position(WindowPositionOnScreen.Center) .Content(c => c - .Children(ch => ch - .View(btn => btn + .Children(ch => + { + ch.View(btn => btn .Name("BtnIncrement") .Text("Click me") - ) - ) + ); + ch.View(lb => lb.Name("LbShowIncrement").Text("0 Increment")); + }) ) ) ) @@ -27,7 +29,7 @@ bh.BtnIncrement.OnClick(() => { counter++; - bh.BtnIncrement.Text = $"Click: {counter}"; + bh.LbShowIncrement.Text = $"{counter} Increment"; }); }) .Initialize();