nano-ui
Safe HaskellNone
LanguageGHC2024

NanoUI.Testing

Description

Deterministic frame execution and render inspection for tests and tools. Application code should use a backend's runner, such as runSdlApp in NanoUI.Backend.Sdl, instead of this module.

Synopsis

Frame

runFrameEff :: forall (es :: [Effect]) a. IOE :> es => (forall x. Eff es x -> IO x) -> Context -> Input -> Eff (Ui ': es) a -> IO (a, [FrameMsg], DrawData, Bool) Source #

runFrameReduce :: (Typeable msg, Eq model) => (msg -> model -> model) -> Context -> Input -> model -> (model -> NanoUI a) -> IO (a, model, [msg], DrawData, Bool) Source #

runFrameReduceEff :: forall (es :: [Effect]) msg model a. (IOE :> es, Typeable msg, Eq model) => (forall x. Eff es x -> IO x) -> (msg -> model -> model) -> Context -> Input -> model -> (model -> Eff (Ui ': es) a) -> IO (a, model, [msg], DrawData, Bool) Source #

colorPickerSvSquare :: Rect -> Rect Source #

The square the saturation / value field fills, centered in its node.

foldSpanArena :: SpanArena -> (Rect -> Text -> Color -> Color -> Rect -> IO ()) -> IO () Source #

Context

newPixelContext :: IO Context Source #

A headless context for tests: 16px monospace metrics, the measure cache on, text kept out of the vertex buffer, and the default theme.

setHost :: Typeable a => Context -> a -> IO () Source #

askHost :: forall a (es :: [Effect]). (Typeable a, Ui :> es) => Eff es (Maybe a) Source #

withTheme :: Context -> Theme -> IO Context Source #

Configure a context's theme. Goes through setTheme so a theme swapped between frames invalidates the caches keyed on it, drawing-op caches included, instead of leaving widgets painting the previous theme.

setWakeLoop :: Context -> IO () -> IO () Source #

data DamageRequest Source #

Explicit damage invalidation request queued during frame evaluation.

Constructors

ReqWidget !WidgetId !DamageBounds

Invalidate widget layout bounds (old & new)

ReqKey !Int !DamageBounds

Invalidate widget bounds by integer key

ReqRect !Rect

Invalidate an explicit window-space rectangle

ReqPeers ![WidgetId] !DamageBounds

Invalidate a collection of widgets

ReqFull

Force full window invalidation

Instances

Instances details
Eq DamageRequest Source # 
Instance details

Defined in NanoUI.Context.Types

Show DamageRequest Source # 
Instance details

Defined in NanoUI.Context.Types

setScrollOffset :: Context -> WidgetId -> Float -> IO () Source #

Move a scroller to an offset along its main axis. Cancels a glide in flight: whoever sets an offset outright owns it.

anyAnimating :: Context -> IO Bool Source #

Whether the frame loop has to keep drawing: an animation is running, or a scroller is still gliding onto its target.

Images

atlasTextureId :: Int Source #

GPU texture id shared by every packed image so draw cmds batch.

Messages

data FrameMsg where Source #

Constructors

FrameMsg :: forall a. Typeable a => a -> FrameMsg 

reduceMessages :: (Foldable f, Typeable msg) => (msg -> model -> model) -> model -> f FrameMsg -> model Source #

reduceUpdates :: (Foldable f, Typeable model) => model -> f FrameMsg -> model Source #

Draw

data DrawOp Source #

Constructors

FillRect !Rect !Color 
FillRoundedRect !Rect !Float !Color 
FillTriangle !Float !Float !Float !Float !Float !Float !Color 
FillCircle !Float !Float !Float !Color 
Stroke !Float !Float !Float !Float !Float !Color 
StrokeRoundedRect !Rect !Float !Float !Color 
StrokeCircle !Float !Float !Float !Float !Color 
StrokeLineAA !Float !Float !Float !Float !Float !Color 
FillQuadGradient !Rect !Color !Color !Color !Color 
DrawImageRect !Rect !Int !Float !Float !Float !Float !Color 
DrawText !Float !Float !Float !Float !Text !Color

Pen at (x, y) is the alignment point. ax 0..1 is left..right. ay 0..1 is bottom..top. ay < 0 means baseline (x is left, y is the baseline). Glyph size is the host font (drawTextBox).

DrawTextStyled !Float !Float !TextFont !Text !Color

Text in a font of its own, its line box's top left corner at (x, y).

Instances

Instances details
Eq DrawOp Source # 
Instance details

Defined in NanoUI.Draw.Types

Methods

(==) :: DrawOp -> DrawOp -> Bool #

(/=) :: DrawOp -> DrawOp -> Bool #

drawTextBox :: FontMetrics -> Float -> Float -> Float -> Float -> Text -> Rect Source #

Pixel box for a DrawText using host advances. diagrams text has no envelope, so plot sizing uses this instead of fontSizeL.

data Layer Source #

Instances

Instances details
Eq Layer Source # 
Instance details

Defined in NanoUI.Draw.Types

Methods

(==) :: Layer -> Layer -> Bool #

(/=) :: Layer -> Layer -> Bool #

Bounded Layer Source # 
Instance details

Defined in NanoUI.Draw.Types

Enum Layer Source # 
Instance details

Defined in NanoUI.Draw.Types

Show Layer Source # 
Instance details

Defined in NanoUI.Draw.Types

Methods

showsPrec :: Int -> Layer -> ShowS #

show :: Layer -> String #

showList :: [Layer] -> ShowS #

data Damage Source #

Constructors

DamageFull 
DamageClip Rect 

Instances

Instances details
Eq Damage Source # 
Instance details

Defined in NanoUI.Types

Methods

(==) :: Damage -> Damage -> Bool #

(/=) :: Damage -> Damage -> Bool #

Show Damage Source # 
Instance details

Defined in NanoUI.Types

Effectful

data Eff (es :: [Effect]) a #

The Eff monad provides the implementation of a computation that performs an arbitrary set of effects. In Eff es a, es is a type-level list that contains all the effects that the computation may perform. For example, a computation that produces an Integer by consuming a String from the global environment and acting upon a single mutable value of type Bool would have the following type:

(Reader String :> es, State Bool :> es) => Eff es Integer

Abstracting over the list of effects with (:>):

  • Allows the computation to be used in functions that may perform other effects.
  • Allows the effects to be handled in any order.

Instances

Instances details
IOE :> es => MonadBaseControl IO (Eff es) #

Instance included for compatibility with existing code.

Usage of withEffToIO is preferrable as it allows specifying the UnliftStrategy on a case-by-case basis and has better error reporting.

Note: the unlifting strategy for liftBaseWith is taken from the IOE context (see unliftStrategy).

Instance details

Defined in Effectful.Internal.Monad

Methods

liftBaseWith :: (RunInBase (Eff es) IO -> IO a) -> Eff es a #

restoreM :: StM (Eff es) a -> Eff es a #

(Show e, Error e :> es, MonadError e (Eff es)) => MonadError e (Eff es) #

Instance included for compatibility with existing code.

Instance details

Defined in Effectful.Internal.MTL

Methods

throwError :: e -> Eff es a #

catchError :: Eff es a -> (e -> Eff es a) -> Eff es a #

(Reader r :> es, MonadReader r (Eff es)) => MonadReader r (Eff es) #

Instance included for compatibility with existing code.

Instance details

Defined in Effectful.Internal.MTL

Methods

ask :: Eff es r #

local :: (r -> r) -> Eff es a -> Eff es a #

reader :: (r -> a) -> Eff es a #

(State s :> es, MonadState s (Eff es)) => MonadState s (Eff es) #

Instance included for compatibility with existing code.

Instance details

Defined in Effectful.Internal.MTL

Methods

get :: Eff es s #

put :: s -> Eff es () #

state :: (s -> (a, s)) -> Eff es a #

(Monoid w, Writer w :> es, MonadWriter w (Eff es)) => MonadWriter w (Eff es) #

Instance included for compatibility with existing code.

Instance details

Defined in Effectful.Internal.MTL

Methods

writer :: (a, w) -> Eff es a #

tell :: w -> Eff es () #

listen :: Eff es a -> Eff es (a, w) #

pass :: Eff es (a, w -> w) -> Eff es a #

IOE :> es => MonadBase IO (Eff es) #

Instance included for compatibility with existing code.

Usage of liftIO is preferrable as it's a standard.

Instance details

Defined in Effectful.Internal.Monad

Methods

liftBase :: IO α -> Eff es α #

MonadCatch (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

catch :: (HasCallStack, Exception e) => Eff es a -> (e -> Eff es a) -> Eff es a #

catchNoPropagate :: Exception e => Eff es a -> (ExceptionWithContext e -> Eff es a) -> Eff es a #

MonadMask (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

mask :: HasCallStack => ((forall a. Eff es a -> Eff es a) -> Eff es b) -> Eff es b #

uninterruptibleMask :: HasCallStack => ((forall a. Eff es a -> Eff es a) -> Eff es b) -> Eff es b #

generalBracket :: HasCallStack => Eff es a -> (a -> ExitCase b -> Eff es c) -> (a -> Eff es b) -> Eff es (b, c) #

MonadThrow (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

throwM :: (HasCallStack, Exception e) => e -> Eff es a #

rethrowM :: Exception e => ExceptionWithContext e -> Eff es a #

NonDet :> es => Alternative (Eff es) #

Since: effectful-core-2.2.0.0

Instance details

Defined in Effectful.Internal.Monad

Methods

empty :: Eff es a #

(<|>) :: Eff es a -> Eff es a -> Eff es a #

some :: Eff es a -> Eff es [a] #

many :: Eff es a -> Eff es [a] #

Applicative (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

pure :: a -> Eff es a #

(<*>) :: Eff es (a -> b) -> Eff es a -> Eff es b #

liftA2 :: (a -> b -> c) -> Eff es a -> Eff es b -> Eff es c #

(*>) :: Eff es a -> Eff es b -> Eff es b #

(<*) :: Eff es a -> Eff es b -> Eff es a #

Functor (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

fmap :: (a -> b) -> Eff es a -> Eff es b #

(<$) :: a -> Eff es b -> Eff es a #

Monad (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

(>>=) :: Eff es a -> (a -> Eff es b) -> Eff es b #

(>>) :: Eff es a -> Eff es b -> Eff es b #

return :: a -> Eff es a #

NonDet :> es => MonadPlus (Eff es) #

Since: effectful-core-2.2.0.0

Instance details

Defined in Effectful.Internal.Monad

Methods

mzero :: Eff es a #

mplus :: Eff es a -> Eff es a -> Eff es a #

Fail :> es => MonadFail (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

fail :: HasCallStack => String -> Eff es a #

MonadFix (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

mfix :: (a -> Eff es a) -> Eff es a #

IOE :> es => MonadIO (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

liftIO :: IO a -> Eff es a #

Prim :> es => PrimMonad (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

Associated Types

type PrimState (Eff es) 
Instance details

Defined in Effectful.Internal.Monad

Methods

primitive :: (State# (PrimState (Eff es)) -> (# State# (PrimState (Eff es)), a #)) -> Eff es a #

IOE :> es => MonadUnliftIO (Eff es) #

Instance included for compatibility with existing code.

Usage of withEffToIO is preferrable as it allows specifying the UnliftStrategy on a case-by-case basis and has better error reporting.

Note: the unlifting strategy for withRunInIO is taken from the IOE context (see unliftStrategy).

Instance details

Defined in Effectful.Internal.Monad

Methods

withRunInIO :: ((forall a. Eff es a -> IO a) -> IO b) -> Eff es b #

Monoid a => Monoid (Eff es a) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

mempty :: Eff es a #

mappend :: Eff es a -> Eff es a -> Eff es a #

mconcat :: [Eff es a] -> Eff es a #

Semigroup a => Semigroup (Eff es a) # 
Instance details

Defined in Effectful.Internal.Monad

Methods

(<>) :: Eff es a -> Eff es a -> Eff es a #

sconcat :: NonEmpty (Eff es a) -> Eff es a #

stimes :: Integral b => b -> Eff es a -> Eff es a #

type PrimState (Eff es) # 
Instance details

Defined in Effectful.Internal.Monad

type StM (Eff es) a # 
Instance details

Defined in Effectful.Internal.Monad

type StM (Eff es) a = a

runEff :: HasCallStack => Eff '[IOE] a -> IO a #

Run an Eff computation with side effects.

For running pure computations see runPureEff.

data IOE (a :: Type -> Type) b #

Run arbitrary IO computations via MonadIO or MonadUnliftIO.

Note: it is not recommended to use this effect in application code as it is too liberal. Ideally, this is only used in handlers of more fine-grained effects.

Instances

Instances details
type DispatchOf IOE # 
Instance details

Defined in Effectful.Internal.Monad

newtype StaticRep IOE # 
Instance details

Defined in Effectful.Internal.Monad

class (e :: Effect) :> (es :: [Effect]) #

A constraint that requires that a particular effect e is a member of the type-level list es. This is used to parameterize an Eff computation over an arbitrary list of effects, so long as e is somewhere in the list.

For example, a computation that only needs access to a mutable value of type Integer would have the following type:

State Integer :> es => Eff es ()

Instances

Instances details
(TypeError (('Text "There is no handler for '" ':<>: 'ShowType e) ':<>: 'Text "' in the context") :: Constraint) => e :> ('[] :: [Effect]) # 
Instance details

Defined in Effectful.Internal.Effect

Methods

reifyIndex :: Int #

e :> (e ': es) # 
Instance details

Defined in Effectful.Internal.Effect

Methods

reifyIndex :: Int #

e :> es => e :> (x ': es) # 
Instance details

Defined in Effectful.Internal.Effect

Methods

reifyIndex :: Int #

askContext :: forall (es :: [Effect]). Ui :> es => Eff es Context Source #

askInput :: forall (es :: [Effect]). Ui :> es => Eff es Input Source #

data Ui (a :: Type -> Type) b Source #

Instances

Instances details
type DispatchOf Ui Source # 
Instance details

Defined in NanoUI.Monad

data StaticRep Ui Source # 
Instance details

Defined in NanoUI.Monad

uiIO :: forall (es :: [Effect]) a. Ui :> es => IO a -> Eff es a Source #

Compact

data Compact a #

A Compact contains fully evaluated, pure, immutable data.

Compact serves two purposes:

  • Data stored in a Compact has no garbage collection overhead. The garbage collector considers the whole Compact to be alive if there is a reference to any object within it.
  • A Compact can be serialized, stored, and deserialized again. The serialized data can only be deserialized by the exact binary that created it, but it can be stored indefinitely before deserialization.

Compacts are self-contained, so compacting data involves copying it; if you have data that lives in two Compacts, each will have a separate copy of the data.

The cost of compaction is fully evaluating the data + copying it. However, because compact does not stop-the-world, retaining internal sharing during the compaction process is very costly. The user can choose whether to compact or compactWithSharing.

When you have a Compact a, you can get a pointer to the actual object in the region using getCompact. The Compact type serves as handle on the region itself; you can use this handle to add data to a specific Compact with compactAdd or compactAddWithSharing (giving you a new handle which corresponds to the same compact region, but points to the newly added object in the region). At the moment, due to technical reasons, it's not possible to get the Compact a if you only have an a, so make sure you hold on to the handle as necessary.

Data in a compact doesn't ever move, so compacting data is also a way to pin arbitrary data structures in memory.

There are some limitations on what can be compacted:

  • Functions. Compaction only applies to data.
  • Pinned ByteArray# objects cannot be compacted. This is for a good reason: the memory is pinned so that it can be referenced by address (the address might be stored in a C data structure, for example), so we can't make a copy of it to store in the Compact.
  • Objects with mutable pointer fields (e.g. IORef, MutableArray) also cannot be compacted, because subsequent mutation would destroy the property that a compact is self-contained.

If compaction encounters any of the above, a CompactionFailed exception will be thrown by the compaction operation.

askCompact :: forall a (es :: [Effect]). (Typeable a, Ui :> es) => Eff es (Maybe a) Source #

Text measurement

textIndexAtX :: FontMetrics -> Text -> Float -> Int Source #

The character index whose caret is nearest x: from the shaped carets when the text was prepared by a shaping host, which handles clusters and right-to-left runs, and otherwise from the same advances and kerning as pushText, so the caret lands where the glyph to its left was drawn.

caretX :: FontMetrics -> Text -> Int -> Float Source #

Where the caret before character i of txt sits: a shaped caret when the snapshot was prepared for txt, else the width of the characters before it.

selectionSpans :: FontMetrics -> Text -> Int -> Int -> [(Float, Float)] Source #

The horizontal extents covering characters lo to hi: one span for left-to-right text, and a span per direction run where a selection crosses right-to-left text.