Build rapid GUI applications in FSharp using the excellent Dear ImGui via the ImGui.NET wrapper.
Because it should be very easy to knock up and iterate on a simple UI with minimal dependencies and ceremony. No companion C# project with Xaml, no web servers, browsers and Javascript bundles. Just some functions that compose nicely and work largely as expected.
How about a quick little GUI window from your FSI session?
letcommonStatusBar()= Gui.statusBar "Status Bar"[
Gui.button "Quit" closeGui
Gui.text $"{ImGui.GetIO().Framerate} FPS"]()letcounter= ref 0letincr i =fun _ -> counter :=!counter + i
letdecr i =fun _ -> counter :=!counter - i
letpage()= Gui.app [
Gui.window "Window 1"[
Gui.text "Hello World!"
Gui.text $"Counter: {!counter}"
Gui.button "Down"(decr 1)++ Gui.button "Up"(incr 1)]
commonStatusBar
]()
startOrUpdateGuiWith "Demo" pageHow about hot reloading some changes as you rapidly iterate?
letnewPage()= Gui.app [
Gui.window "Updated!"[
Gui.text "Updated!"]
commonStatusBar
]()
setGuiBuilder newPageAnd boom!
More complex apps might benefit from the MVU architecture of Elmish. I didn't want to pollute this repo with dependencies on Elmish, but turns out that's not needed.
The normal Elmish message, model, update, init components:
typeModel={
Value:int
Checkbox:bool}typeMsg=| Incr ofint| Decr ofint| Checked ofboolletupdate(msg:Msg)(model:Model)=match msg with| Incr(amount)->{ model with Value = model.Value + amount }, Cmd.none
| Decr(amount)->{ model with Value = model.Value - amount }, Cmd.none
| Checked(flag)->{model with Checkbox = flag}, Cmd.none
letinit()={
Value =0
Checkbox =false}, Cmd.noneAnd optional viewmodel and model to viewmodel mapper to ensure model changes flow into the UI and ensure no messy two-way data binding as per the MVU way:
letviewModel={|
Checkbox = ref false
ValueString = ref ""|}letmapModelToViewModel(model:Model)= viewModel.Checkbox := model.Checkbox
viewModel.ValueString := model.Value.ToString()And then of course the view itself:
letview(model:Model)(dispatch:Msg ->unit)= mapModelToViewModel model
letgui= Gui.app [
Gui.window "Demo"[
Gui.sameLine [
Gui.text !viewModel.ValueString
Gui.button "+"(fun()-> Incr 1|> dispatch )
Gui.button "-"(fun()-> Decr 1|> dispatch )]|> Gui.alignText
Gui.checkbox "Test" viewModel.Checkbox (Checked >> dispatch)]]
startOrUpdateGuiWith "Demo" gui |> ignoreWire up with some helper function to ensure dispatching on GUI thread and away we go:
letguiDispatch innerDispatch =fun msg -> dispatchToGui (fun()-> innerDispatch msg)
Program.mkProgram init update view
|> Program.withSyncDispatch guiDispatch
|> Program.runExplore some other samples in the Samples Project.

