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
}
}