packages feed

hmp3-ng-2.20.0: src/Elements.hs

-- Copyright (c) 2005-2008 Don Stewart - http://www.cse.unsw.edu.au/~dons
-- Copyright (c) 2019-2026 Galen Huntington
-- SPDX-License-Identifier: GPL-2.0-or-later

module Elements where

import Base
import Decoder (Frame(..))
import Keyboard (charToKey, historyKeys)
import State
import Text
import Paths_hmp3_ng (version)

import Data.List.NonEmpty qualified as NE
import System.Clock
import UI.HSCurses.Curses qualified as Curses


package :: String
package = "hmp3-ng"

fullVersion :: String
fullVersion  = package ++ " v" ++ showVersion version

-- | Version info
pVersion :: SText
pVersion = fromString fullVersion

commonModalWidth :: Int -> Int
commonModalWidth w = max (min w 3) $ round $ fromIntegral w * (0.8::Float)

showClock :: Fixed E2 -> SText
showClock t =
    let m, si, sd :: Int
        (m, s) = t `divMod'` 60
        si     = floor s
        sd     = floor (s*10) `mod` 10
    in mconcat [showInt m, ":", show02d si, ".", showInt sd]

-- | Human-friendly duration, with a flag to include seconds.
showDuration :: Bool -> TimeSpec -> SText
showDuration showSecs tm =
    render $ dropWhile ((==0) . fst) (init parts) `NE.prependList` pure (last parts)
  where
    render ((tv, tu) :| l) =
        mconcat $ showInt tv : tu : foldMap (\ (v, u) -> [show02d v, u]) l
    parts   = [(d, "d"), (h, "h"), (m, "m")] ++ [ (s, "s") | showSecs ]
    (ms, s) = fromIntegral (sec tm) `quotRem` 60
    (hs, m) = ms `quotRem` 60
    (d, h)  = hs `quotRem` 24

-- | The time used and time left
pTimes :: Int -> Maybe Frame -> SText
pTimes w clock
    | w - 4 < width elapsed = ""
    | True                  =
        mconcat $ ["  ", elapsed] ++ [gap <> "-" <> left | distance > 0]
  where
    elapsed  = showClock (maybe 0 (.elapsed) clock)
    left     = maybe "?:??.?" (showClock . (.left)) clock
    gap      = spaces distance
    distance = w - 5 - width elapsed - width left

-- | Progress out of total
progress :: Int -> Maybe Frame -> Int
progress w = maybe 0 \fr ->
    let total    = curr + toRational fr.left - ε
        curr     = toRational fr.elapsed
        ε        = 1 / 200
    in ceiling (curr * fromIntegral (w - 1) / total)

data Fit = Fit { wide :: !Bool, padL :: !Int, padR :: !Int, ctake :: !Int }
    deriving stock Show

-- | Given a width and size of left, center, and right elements, determine
-- whether left and right can fit, padding between, and amount of center to show
fitLCR :: Int -> (Int, Int, Int) -> Fit
fitLCR w (lsz, csz, rsz) = if
    | gap >= 2   -> let gapl = 1 `max` ((side - lsz) `min` (gap - 1))
                    in Fit True gapl (gap - gapl) csz
    | w-2 >= csz -> Fit False side (sides - side) csz
    | w > 1      -> Fit False 1 1 (w-2)
    | True       -> Fit False w 0 0
  where
    sides = w - csz
    side = sides `div` 2
    gap  = sides - lsz - rsz

layoutLCR :: Int -> (SText, String, SText) -> SText
layoutLCR w (left, centerS, right) = mconcat [
    if fit.wide then left else "",
    spaces fit.padL,
    fromString $ take fit.ctake centerS,
    spaces fit.padR,
    if fit.wide then right else ""
    ]
  where
    fit = fitLCR w (width left, length centerS, width right)


-- Modals

-- screen width -> (modal width, list of lines)
type ModalMaker = Int -> (Int, [SText])

helpModal :: [KeysHelp] -> ModalMaker
helpModal help swd = (wd, map showLine help) where
    wd = commonModalWidth swd
    showLine :: ([Char], SText) -> SText
    showLine (cs, ps) = toWidth clen cmds <> ps where
        clen = max 4 $ round $ fromIntegral wd * (0.2::Float)
        cmds = mconcat $ intersperse " " $ "" : map pprIt cs
        pprIt c = case c of
            '\n' -> "Enter"
            '\f' -> "^L"
            '\\' -> "\\"
            ' '  -> "Space"
            _ -> case charToKey c of
                Curses.KeyUp        -> "↑"
                Curses.KeyDown      -> "↓"
                Curses.KeyPPage     -> "PgUp"
                Curses.KeyNPage     -> "PgDn"
                Curses.KeyLeft      -> "←"
                Curses.KeyRight     -> "→"
                Curses.KeyEnd       -> "End"
                Curses.KeyHome      -> "Home"
                Curses.KeyBackspace -> "Backspace"
                _                   -> fromChar c

histModal :: HistDisplay -> ModalMaker
histModal []   _   = let s = "  No history  " in (width s, [s])
histModal hist swd = do
    let wd = commonModalWidth swd
        mtlen = maximum $ map (width . fst) hist
        tlen = min (mtlen + 1) $ wd `div` 3
    (wd, [
        let tstr = toMaxWidth tlen $ spaces (tlen - width time) <> time
        in mconcat [" ", fromChar c, " ", tstr, " ", song]
        | (c, (time, (_, song))) <- zip (toList historyKeys ++ repeat ' ') hist ])

exitModal :: ModalMaker
exitModal swd = (wd, ["", padl <> "Exit (y)?", ""]) where
    wd = commonModalWidth swd `min` 19
    padl = spaces ((wd - 9) `div` 2)