namespace Mibo.Elmish open Microsoft.Xna.Framework open Microsoft.Xna.Framework.Graphics open Mibo.Input // ───────────────────────────────────────────────────────────────────────────── // MonoGame-specific Program wrapper and builder extensions. // // The backend-neutral Program builder (mkProgram, withConfig, withRenderer, // withTick, withFixedStep, withDispatchMode, withSubscription, withAssets, // withAssetsBasePath, withInput, withServiceRegistration) lives in Mibo.Core. // // MonoGameProgram wraps a Core Program and adds a slot for device-level // configuration callbacks (Game * GraphicsDeviceManager -> unit) that the // Core type cannot hold without leaking MonoGame types. The host (MiboGame) // runs these callbacks in its constructor, after the Core GameConfig but // before Initialize, so settings like GraphicsProfile, vsync, and fullscreen // take effect at device-creation time. // ───────────────────────────────────────────────────────────────────────────── /// /// MonoGame-specific wrapper around a Core , /// carrying device-level configuration that the backend-neutral Core type cannot hold. /// /// /// Build one with , add /// MonoGame-specific configuration with /// and/or /// , then pass the result /// to . /// /// /// /// let program = /// Program.mkProgram init update /// |> Program.withConfig(fun cfg -> /// { cfg with Width = 1280; Height = 720; Title = "My Game" }) /// |> Program.withInput /// |> Program.withTick Tick /// /// let mgProgram = /// program /// |> MonoGameProgram.ofProgram /// |> MonoGameProgram.withConfig(fun (game, graphics) -> /// graphics.SynchronizeWithVerticalRetrace <- false) /// /// use game = new MiboGame<_, _>(mgProgram) /// game.Run() /// /// type MonoGameProgram<'Model, 'Msg> = { /// The backend-neutral Core program. Program: Program<'Model, 'Msg> /// /// Device-level configuration callbacks invoked in the /// constructor, after the Core /// callbacks and before /// Initialize/GraphicsDevice creation. /// DeviceConfig: (Game * GraphicsDeviceManager -> unit) list } /// MonoGame-specific program builder extensions. module MonoGameProgram = /// /// Wraps a Core into a /// with no device-level /// configuration. /// let ofProgram (program: Program<'Model, 'Msg>) : MonoGameProgram<'Model, 'Msg> = { Program = program; DeviceConfig = [] } /// /// Adds a device-level configuration callback that receives the Game /// instance and GraphicsDeviceManager for low-level setup. /// /// /// Invoked in the constructor, /// after the Core callbacks /// and before Initialize / GraphicsDevice creation — so /// settings like GraphicsProfile and /// PreferredBackBufferWidth/Height take effect. /// Use this for properties that require direct /// GraphicsDeviceManager/Game access: /// GraphicsProfile, SynchronizeWithVerticalRetrace (vsync), /// IsFullScreen, HardwareModeSwitch, /// Window.AllowUserResizing, Content.RootDirectory, etc. /// /// /// /// program /// |> MonoGameProgram.ofProgram /// |> MonoGameProgram.withConfig(fun (game, graphics) -> /// graphics.GraphicsProfile <- GraphicsProfile.HiDef /// graphics.SynchronizeWithVerticalRetrace <- false /// game.IsMouseVisible <- true) /// /// let withConfig (configure: Game * GraphicsDeviceManager -> unit) (program: MonoGameProgram<'Model, 'Msg>) : MonoGameProgram<'Model, 'Msg> = { program with DeviceConfig = configure :: program.DeviceConfig } /// /// Configures the game to register an service /// backed by MonoGame's polling API. /// /// /// This registers automatically (equivalent to /// ). /// The mapper is registered as a service via a /// callback that /// MiboGame runs before Init, so the Core Program type never /// references a backend factory. /// The service is registered, not driven: nothing polls /// Update() automatically — call it yourself each frame if you read /// CurrentState, or prefer the subscription path below, which the /// input deltas drive on their own. /// If you want to stay fully "Elmish" (no service access), consider using /// instead and handle a single message /// (adaptive programs: ). /// /// /// /// program /// |> MonoGameProgram.ofProgram /// |> MonoGameProgram.withInputMapper inputMap /// /// let withInputMapper<'Model, 'Msg, 'Action when 'Action: comparison> (initialMap: InputMap<'Action>) (program: MonoGameProgram<'Model, 'Msg>) : MonoGameProgram<'Model, 'Msg> = let coreProgram = program.Program |> Program.withInput |> Program.withServiceRegistration(fun ctx -> let mapper = InputMapper.createService initialMap GameContext.register> mapper ctx) { program with Program = { coreProgram with HasInputMapper = true } }