packages feed

nano-ui-0.1.0.0: test/integration/Cases/Styling.hs

module Cases.Styling
  ( runDisabledPointerTest
  , runDisabledFocusOrderTest
  , runDisabledLookTest
  , runStyledPaintTest
  , runStyledNestingTest
  , runStyledDamageTest
  ) where

import Control.Monad (forM_)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import NanoUI
import NanoUI.Context (Context (..))
import NanoUI.Testing
import Data.Text (Text)
import NanoUI.Testing.Assert (assert, assertEq)
import NanoUI.Testing.Harness (centerOf, clickPair, drawQuads, held, tabInp, warmup2, warmupDraw, withInputOff)

-- | A pointer press, drag and typing on a disabled widget change nothing and
-- give it no focus.
runDisabledPointerTest :: Context -> IORef Int -> IO ()
runDisabledPointerTest _ failed = do
  let inp = withInputOff 400 200
      check :: (Eq a, Show a) => String -> a -> (a -> NanoUI (Response, a)) -> IO ()
      check name initial widget = do
        ctx <- newContext
        ref <- newIORef initial
        let ui = column (disabledWhen True (held ref widget))
        (resp, _) <- warmup2 ctx inp ui
        let V2 cx cy = centerOf resp
            (press, release) = clickPair inp (V2 cx cy)
            dragged = press {inputMousePressed = False, inputMousePos = V2 (cx + 60) cy}
        mapM_ (\i -> runFrame ctx i ui) [press, dragged, release {inputMousePos = V2 (cx + 60) cy}]
        _ <- runFrame ctx inp {inputMousePos = V2 cx cy, inputChars = "x"} ui
        (after, _, _, _) <- runFrame ctx inp ui
        value <- readIORef ref
        focus <- readIORef (ctxFocusId ctx)
        assertEqNamed name value initial
        assert failed (not (respClicked (fst after)) && not (respHovered (fst after)))
        if focus /= respId resp then pure () else putStrLn ("  disabled " <> name <> ": took focus")
        assert failed (focus /= respId resp)
      assertEqNamed :: (Eq b, Show b) => String -> b -> b -> IO ()
      assertEqNamed name a b =
        if a == b then pure () else do
          putStrLn ("  disabled " <> name <> ": " <> show a <> " /= " <> show b)
          assertEq failed a b
  check "button" False (\_ -> (\r -> (r, respClicked r)) <$> button' "Go")
  check "checkbox" False (checkbox' "Check")
  check "toggle" False toggleSwitch'
  check "slider" (50 :: Float) (slider' 0 100)
  check "knob" (50 :: Float) (knob' 0 100)
  check "radio" (0 :: Int) (radio' ["One", "Two"])
  check "select" (0 :: Int) (select' ["One", "Two"])
  check "text input" ("initial" :: Text) textInput'
  check "numeric input" (5 :: Double) numericInput'

-- | Tab skips a disabled widget.
runDisabledFocusOrderTest :: Context -> IORef Int -> IO ()
runDisabledFocusOrderTest ctx failed = do
  let inp = withInputOff 300 200
      ui = column $ do
        a <- button' "A"
        _ <- disabledWhen True (button' "B")
        c <- button' "C"
        pure (a, c)
  (a, c) <- warmup2 ctx inp ui
  writeIORef (ctxFocusId ctx) (respId a)
  _ <- runFrame ctx (tabInp inp) ui
  focus <- readIORef (ctxFocusId ctx)
  assertEq failed focus (respId c)

-- | A disabled button paints its fill faded toward the window colour.
runDisabledLookTest :: Context -> IORef Int -> IO ()
runDisabledLookTest ctx failed = do
  theme <- getTheme ctx
  let inp = withInputOff 300 200
      enabledBg = styleBg (themeButton theme)
      fadedBg = styleBg (themeButton (disabledTheme theme))
  (_, draw) <- warmupDraw ctx inp (column (disabledWhen True (button "Off")))
  quads <- drawQuads draw
  assert failed (any ((== fadedBg) . snd) quads)
  assert failed (not (any ((== enabledBg) . snd) quads))
  assert failed (fadedBg /= enabledBg)

-- | A styled scope paints only the widgets inside it with its theme.
runStyledPaintTest :: Context -> IORef Int -> IO ()
runStyledPaintTest ctx failed = do
  let inp = withInputOff 300 200
      red = colorRGBA 200 30 40 255
      ui = column $ do
        inside <- styled (buttonStyle (background red)) (button' "Red")
        outside <- button' "Plain"
        pure (inside, outside)
  ((inside, outside), draw) <- warmupDraw ctx inp ui
  quads <- drawQuads draw
  let fills r = [c | (q, c) <- quads, rectIntersect q (respRect r) /= Nothing]
  assert failed (red `elem` fills inside)
  assert failed (red `notElem` fills outside)

-- | Nested scopes modify the theme around them, and 'uiTheme' reads it.
runStyledNestingTest :: Context -> IORef Int -> IO ()
runStyledNestingTest ctx failed = do
  base <- getTheme ctx
  let inp = withInputOff 300 200
      teal = colorRGBA 20 160 150 255
  (outerAccent, innerAccent, innerRadius, afterAccent) <-
    warmup2 ctx inp $ column $ do
      (o, (i, r)) <- styled (accentColor teal) $ do
        o <- themeAccent <$> uiTheme
        ir <- styled (buttonStyle (cornerRadius 9)) $ do
          t <- uiTheme
          pure (themeAccent t, styleCornerRadius (themeButton t))
        pure (o, ir)
      a <- themeAccent <$> uiTheme
      pure (o, i, r, a)
  assertEq failed outerAccent teal
  assertEq failed innerAccent teal
  assertEq failed innerRadius 9
  assertEq failed afterAccent (themeAccent base)
  -- A primary button inside a disabled scope is still faded.
  offTheme <- warmup2 ctx inp (column (disabledWhen True (styled primary uiTheme)))
  assertEq failed (styleBg (themeButton offTheme)) (styleBg (themeButton (disabledTheme (primary base))))

-- | Changing only a scope's theme repaints.
runStyledDamageTest :: Context -> IORef Int -> IO ()
runStyledDamageTest ctx failed = do
  let inp = withInputOff 300 200
      ui c = column (styled (buttonStyle (background c)) (button "B"))
      blue = colorRGBA 30 60 200 255
      green = colorRGBA 30 200 60 255
  _ <- warmup2 ctx inp (ui blue)
  _ <- takeDamage ctx
  forM_ [green, blue] $ \c -> do
    (_, _, draw, _) <- runFrame ctx inp (ui c)
    dmg <- takeDamage ctx
    assertEq failed dmg DamageFull
    quads <- drawQuads draw
    assert failed (any ((== c) . snd) quads)