using AcDream.App.Input; using Silk.NET.Maths; using Silk.NET.OpenGL; namespace AcDream.App.Rendering; internal interface IFramebufferViewportTarget { void ResizeViewport(int width, int height); } internal sealed class SilkFramebufferViewportTarget(GL gl) : IFramebufferViewportTarget { private readonly GL _gl = gl ?? throw new ArgumentNullException(nameof(gl)); public void ResizeViewport(int width, int height) => _gl.Viewport(0, 0, (uint)width, (uint)height); } internal interface IFramebufferCameraTarget { void SetAspect(float aspect); } internal sealed class CameraFramebufferTarget(CameraController camera) : IFramebufferCameraTarget { private readonly CameraController _camera = camera ?? throw new ArgumentNullException(nameof(camera)); public void SetAspect(float aspect) => _camera.SetAspect(aspect); } internal interface IFramebufferDevToolsTarget { void ResetLayout(int width, int height); } internal sealed class DevToolsFramebufferTarget(DevToolsFramePresenter presenter) : IFramebufferDevToolsTarget { private readonly DevToolsFramePresenter _presenter = presenter ?? throw new ArgumentNullException(nameof(presenter)); public void ResetLayout(int width, int height) => _presenter.ResetLayout( width, height, DevToolsPanelLayoutCondition.Always); } /// /// The one framebuffer-size target. Logical gameplay/UI dimensions continue /// to come from Window.Size; this owner only applies physical framebuffer /// viewport/aspect changes in the frozen acdream host order. /// internal sealed class FramebufferResizeController { private readonly ViewportAspectState _viewportAspect; private IFramebufferViewportTarget? _viewport; private IFramebufferCameraTarget? _camera; private IFramebufferDevToolsTarget? _devTools; public FramebufferResizeController(ViewportAspectState viewportAspect) => _viewportAspect = viewportAspect ?? throw new ArgumentNullException(nameof(viewportAspect)); public void BindViewport(IFramebufferViewportTarget viewport) { ArgumentNullException.ThrowIfNull(viewport); BindOnce(ref _viewport, viewport, "viewport"); } public void BindCamera(IFramebufferCameraTarget camera) { ArgumentNullException.ThrowIfNull(camera); BindOnce(ref _camera, camera, "camera"); } public void BindDevTools(IFramebufferDevToolsTarget devTools) { ArgumentNullException.ThrowIfNull(devTools); BindOnce(ref _devTools, devTools, "developer tools"); } public void UnbindDevTools(IFramebufferDevToolsTarget devTools) { ArgumentNullException.ThrowIfNull(devTools); if (ReferenceEquals(_devTools, devTools)) _devTools = null; } public void Resize(Vector2D newSize) => Resize(newSize.X, newSize.Y); public void Resize(int width, int height) { if (width <= 0 || height <= 0) return; // Frozen order: GL viewport, shared aspect publication, camera, then // optional ImGui layout reset. Late binding never replays an earlier // resize transition. _viewport?.ResizeViewport(width, height); _viewportAspect.Update(width, height); _camera?.SetAspect(width / (float)height); _devTools?.ResetLayout(width, height); } private static void BindOnce(ref T? slot, T value, string name) where T : class { if (slot is not null && !ReferenceEquals(slot, value)) throw new InvalidOperationException( $"The framebuffer {name} target is already bound."); slot = value; } }