Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Buttons, Menus & Inputs

Buttons

button   :: Text -> NanoUI Bool
button'  :: Text -> NanoUI Response
buttonWith :: (Layout -> Layout) -> Text -> NanoUI Bool
buttonContent :: NanoUI () -> NanoUI Bool
iconButton :: Svg -> Text -> NanoUI Bool

button returns True only on the frame it is activated, by left click or by Enter/Space while focused:

saveButton = whenM (button "Save Document") (liftIO saveFile)

Use the primed form when you need the response: hover state, a rectangle for an anchored popup, or a tooltip.

refreshButton = do
  resp <- button' "Refresh"
  tooltip resp "Poll the remote server for updates"
  when (respClicked resp) refresh

buttonContent puts any view inside a button. The content is for display; a click anywhere on the button activates it:

iconSaveButton =
  whenM (buttonContent $ rowWith (gap 6 . alignMid) $ do
            svgIcon 16 saveIcon
            label "Save") $
    saveDocument

iconButton puts an icon before the label; an empty label gives an icon-only square button.

menuItem draws a row and returns True when clicked. menuItemShortcut also displays a chord and binds it while the menu is open. menuSeparator and menuItemDisabled complete the row set.

A menu bar is a menuButton' plus a popup' anchored to its rectangle:

fileMenu = do
  fileOpen <- useVar False
  isOpen <- readVar fileOpen
  btn <- menuButton' "File" isOpen
  when (respClicked btn) (writeVar fileOpen (not isOpen))

  let cfg = (defaultPopupConfig (AnchorRect (respRect btn)))
              {cfgPlacement = PlacementBelow, cfgOffset = 0}
  (popupResp, _) <- popup' (constVar isOpen) cfg $
    columnWith (tight . gap 0) $ do
      whenM (menuItemShortcut "New" (cmdOrCtrl <> key 'n')) newDocument
      whenM (menuItem "Open...") openDocument
      menuSeparator
      whenM (menuItem "Exit") (writeVar fileOpen False >> quitUi)
  when (respClicked popupResp) (writeVar fileOpen False)

constVar supplies a read-only open flag derived from the menu state, and the code writes a click on the popup back to the real Var.

Important

Call overlays unconditionally. popup' draws nothing while closed, but wrapping it in when isOpen changes later widget ids each time the menu opens.

Bind a command chord outside the menu as well as on its row, so it works with the menu closed:

saveBindings :: NanoUI ()
saveBindings = do
  whenM (menuItemShortcut "Save" saveChord) saveDocument
  whenM (shortcut saveChord) saveDocument

Only the first binding of a chord in a frame fires, so the open menu’s row wins. A complete menu bar is built in Build a Notepad.

contextMenu attaches a right-click menu to any widget with a Response:

rowMenu = do
  (_, rowResp) <- mouseArea (fillW . gap 6) (label "Right-click for actions")
  void $ contextMenu rowResp $ do
    whenM (menuItem "Inspect") inspectRow
    whenM (menuItem "Copy") copyRow

Input widgets

Input widgets read and write the Var they display, and store caret and selection state under their widget id. To reset a field, write the Var; to react to an edit, use the primed form’s Response.

WidgetSignatureDescription
checkboxText -> Var Bool -> NanoUI ()Two-state box with a label
toggleButtonTone -> Text -> Var Bool -> NanoUI ()Two-state button, filled in the tone while on
radioFoldable f => f Text -> Var Int -> NanoUI ()Radio group over indexed options
boundedRadio(Bounded a, Enum a) => (a -> Text) -> Var a -> NanoUI ()Radio group over every enum value
selectFoldable f => f Text -> Var Int -> NanoUI ()Drop-down select by index
boundedSelect(Bounded a, Enum a) => (a -> Text) -> Var a -> NanoUI ()Drop-down over every enum value
comboBoxFoldable f => Text -> f Text -> Var Text -> NanoUI ()Placeholder, suggestions, free-text value
sliderFloat -> Float -> Var Float -> NanoUI ()Min, max, value
textInputVar Text -> NanoUI ()Single-line editable text
searchInputText -> Var Text -> NanoUI ()Placeholder + search field with a clear button
numericInputVar Double -> NanoUI ()Numeric field with steppers
colorPickerVar Color -> NanoUI ()Saturation/value field and hue bar

Every widget has the usual variants: primed (...') returns a Response, ...With takes a layout modifier, and ...Configured takes a config record. For example:

byteField = do
  byte <- useVar (0 :: Double)
  numericInputConfigured defaultNumericInputConfig
    {nicMin = 0, nicMax = 255, nicHex = True} byte

Text and search fields

queryField = do
  query <- useVar ""
  resp <- textInput' query
  when (respSubmitted resp) (search =<< readVar query)
  when (respChanged resp) (preview =<< readVar query)

respChanged fires on every edit; respSubmitted fires on Enter. For expensive work use searchInput', whose respChanged is debounced (300 ms by default) and fires immediately when the field is cleared:

packageSearch = do
  query <- useVar ""
  resp <- searchInput' "Find a package" query
  when (respChanged resp) (refreshResults =<< readVar query)

Choices

data Pace = Walk | Jog | Sprint
  deriving (Eq, Show, Enum, Bounded)

choices = do
  enabled <- useVar True
  checkbox "Enable notifications" enabled

  isBold <- useVar False
  toggleButton Accent "B" isBold

  pace <- useEnumVar Jog
  enumRadio pace
  enumSelect pace

select is index-based for data-driven options:

sizeSelect = do
  choice <- useVar 0
  select ["Small", "Medium", "Large"] choice

comboBox takes free text plus suggestions; its respChanged fires on commit; a model write alone does not set it.

Sliders, numbers, color

values :: Var Color -> NanoUI ()
values accent = do
  volume <- useVar (40 :: Float)
  slider 0 100 volume

  count <- useVar (0 :: Double)
  numericInput count

  colorPicker accent -- accent <- newVar (colorHex "#38bdf8") in setup

numericInput accepts digits and a leading minus when the range allows it; arrow keys step, and Shift steps ten times as far. nicDecimals controls display and accepted precision. colorPickerRGBA adds an alpha bar.