nano-ui-0.1.0.0: lib/NanoUI/Frame/Node.hs
-- | Per-node queries shared by the paint, span, scroll and hit passes: the
-- font a node renders and measures in, and a scroll node's fields and content
-- viewport.
module NanoUI.Frame.Node
( resolveFontFor
, resolveTextFont
, nodeFontMetrics
, ScrollNode (..)
, readScrollNode
, scrollNodeViewport
) where
import Data.Text (Text)
import NanoUI.Context (Context (..))
import NanoUI.Draw.Types (TextFont (..))
import NanoUI.Font (FontMetrics, ScrollBarSlot, isDefaultNodeFont, measureTextIO)
import NanoUI.Frame.Scroll.Geometry
( ScrollConfig
, decodeScrollConfig
, scrollConfigNative2D
, scrollContentClip
, scrollViewportClip2D
)
import NanoUI.Layout.Arena
( DirTag
, NodeArena
, NodeIdx
, NodeType (..)
, getDirection
, getNodeFontSize
, getNodeType
, getNodeValue
, getPadding
, getScrollContentW
, getStyleIdx
)
import NanoUI.Layout.Solve (scrollBarSlotOf)
import NanoUI.Style (FontVariant (..), Padding)
import NanoUI.Types (Rect)
import NanoUI.WidgetText (textNodeFontStyle, textNodeFontVariant, textNodeFontWeight)
-- | Font for a node of type @nt@ with an explicit size and packed style: the
-- metrics, whether the host returned a native styled face (paint then skips
-- synthetic weight and slant), and the matching measure. Base sans and mono
-- resolve to the pre-read metrics; everything else defers to the host
-- resolver. INLINE: it runs for every text-bearing node painted, and inlining
-- lets the result triple fold away at each call site (measured: 30 MB less
-- allocation over the 3000-frame profile).
--
-- Only text and text-input nodes pack a font into their style. Other widgets
-- keep their own data in those bits (a radio's option index, a colour picker
-- part, a tab's look), so their style must not be read as a font.
{-# INLINE resolveFontFor #-}
resolveFontFor :: Context -> NodeType -> Float -> Int -> IO (FontMetrics, Bool, Text -> IO (Float, Float))
resolveFontFor ctx nt size packed
| isDefaultNodeFont size weight style variant =
pure $
if variant == FontMono
then (ctxMonoFontMetrics ctx, False, measureTextIO (ctxMonoFontMetrics ctx))
else (ctxFontMetrics ctx, False, ctxMeasureText ctx)
| otherwise = do
(fm, native) <- ctxResolveFont ctx size weight style variant
pure (fm, native, ctxResolveMeasure ctx size weight style variant)
where
si = if nt == NodeText || nt == NodeTextInput then packed else 0
variant = textNodeFontVariant si
weight = textNodeFontWeight si
style = textNodeFontStyle si
-- | The font a 'DrawTextStyled' op names, and whether the host draws its
-- weight and slant natively.
resolveTextFont :: Context -> TextFont -> IO (FontMetrics, Bool)
resolveTextFont ctx (TextFont size variant weight style _)
| isDefaultNodeFont size weight style variant =
pure (if variant == FontMono then ctxMonoFontMetrics ctx else ctxFontMetrics ctx, False)
| otherwise = ctxResolveFont ctx size weight style variant
-- | Metrics of the font node @idx@ is styled with.
nodeFontMetrics :: Context -> NodeIdx -> IO FontMetrics
nodeFontMetrics ctx idx = do
nt <- getNodeType (ctxNodeArena ctx) idx
si <- getStyleIdx (ctxNodeArena ctx) idx
size <- getNodeFontSize (ctxNodeArena ctx) idx
(fm, _, _) <- resolveFontFor ctx nt size si
pure fm
-- | What the scroll passes read off a scroll container: its bar slot, scroll
-- config, whether it scrolls natively in 2D, direction, padding, the content
-- extent along its main axis (the content height for 2D) and, for 2D, the
-- content width.
data ScrollNode = ScrollNode
{ snSlot :: !ScrollBarSlot
, snConfig :: !ScrollConfig
, sn2D :: !Bool
, snDir :: !DirTag
, snPad :: {-# UNPACK #-} !Padding
, snContentMain :: {-# UNPACK #-} !Float
, snContentW :: {-# UNPACK #-} !Float
}
{-# INLINE readScrollNode #-}
readScrollNode :: NodeArena -> NodeIdx -> IO ScrollNode
readScrollNode na idx = do
si <- getStyleIdx na idx
slot <- scrollBarSlotOf na idx
dir <- getDirection na idx
pad <- getPadding na idx
contentMain <- getNodeValue na idx
contentW <- getScrollContentW na idx
let cfg = decodeScrollConfig si
pure $! ScrollNode slot cfg (si /= 0 && scrollConfigNative2D cfg) dir pad contentMain contentW
-- | Content viewport of a scroll node placed at @x y w h@: its padding box
-- minus the live scrollbar gutters.
scrollNodeViewport :: ScrollNode -> Float -> Float -> Float -> Float -> Rect
scrollNodeViewport (ScrollNode slot cfg native2D dir pad contentMain contentW) x y w h
| native2D = scrollViewportClip2D slot cfg x y w h pad contentW contentMain
| otherwise = scrollContentClip slot cfg dir x y w h pad contentMain