packages feed

monomer 1.2.0.0 → 1.3.0.0

raw patch · 45 files changed

+783/−166 lines, 45 filesdep ~aesondep ~lensdep ~sdl2

Dependency ranges changed: aeson, lens, sdl2, text, time, wuss

Files

ChangeLog.md view
@@ -1,3 +1,35 @@+## 1.3.0.0++### Added++- Export `drawArrowUp` from `Drawing` module.+- The `image` widget now supports a `fitEither` option ([PR #56](https://github.com/fjvallarino/monomer/pull/56)). Thanks @Kyarigwo!+- The `scroll` widget now raises `onChange` events, providing the current `ScrollStatus` ([PR #51](https://github.com/fjvallarino/monomer/pull/51)).+- The `grid`, `stack`, `labeledCheckbox` and `labeledRadio` widgets now support a `childSpacing`/`childSpacing_` option+  ([PR #67](https://github.com/fjvallarino/monomer/pull/67)). Thanks @Dretch!++### Fixed++- Widgets that receive polymorphic types now append the handled type to their `WidgetType`. This+  is done to avoid issues if the handled type is later changed ([#46](https://github.com/fjvallarino/monomer/issues/46)).+- If the `WidgetType` of the root item in a `Composite` changes during `merge`, initialize the new widget instead+  of merging with the old one ([#50](https://github.com/fjvallarino/monomer/issues/50)).+- The arrow position in `dropdown` is now correct when a dropdown is taller than one line ([PR #55](https://github.com/fjvallarino/monomer/pull/55)). Thanks @Dretch!+- The middle button click is now handled by `convertEvents`, and in turn reported to widgets ([#63](https://github.com/fjvallarino/monomer/issues/63)).+- Add tolerance to width comparison in text clipping functions ([#54](https://github.com/fjvallarino/monomer/issues/54)).+- Call `pumpEvents` before `pollEvents`. The `pumpEvents` call is implied by `pollEvent`, but starting on+  SDL2 `2.0.20` it seems to be required to call it explicitly ([#66](https://github.com/fjvallarino/monomer/issues/66)). Thanks @JD95!++### Changed++- Relaxed upper bounds of dependencies for Stackage inclusion.++## 1.2.0.1++### Fixed++- The `scroll` and `split` widgets now avoid unexpected behaviour when visibility is toggled.+ ## 1.2.0.0  ### Added
README.md view
@@ -77,6 +77,11 @@ If possible, keep them small and focused. If you are planning on making a large change, please submit an issue first so we can agree on a solution. +## Questions?++If you are not sure how something works or you have a usage question, feel free+to open an issue!+ ## License  This library is licensed under the [BSD-3 license](LICENSE).@@ -88,5 +93,5 @@  ## Acknowledgments -- Thanks to [Ghislaine Guerin](https://github.com/ghislaineguerin) for UX advise.+- Thanks to [Ghislaine Guerin](https://github.com/ghislaineguerin) for UX advice. - Thanks to [Mikko Mononen](https://github.com/memononen) for the amazing [nanovg](https://github.com/memononen/nanovg) library.
monomer.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           monomer-version:        1.2.0.0+version:        1.3.0.0 synopsis:       A GUI library for writing native Haskell applications. description:    Monomer is an easy to use, cross platform, GUI library for writing native                 Haskell applications.@@ -168,16 +168,16 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14@@ -203,7 +203,7 @@   build-depends:       JuicyPixels >=3.2.9 && <3.5     , OpenGL ==3.0.*-    , aeson >=1.4 && <2.1+    , aeson >=1.4 && <2.3     , async >=2.1 && <2.3     , attoparsec >=0.12 && <0.15     , base >=4.11 && <5@@ -215,17 +215,17 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14@@ -258,18 +258,18 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , random >=1.1 && <1.3     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14@@ -301,18 +301,18 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , random >=1.1 && <1.3     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14@@ -333,7 +333,7 @@   build-depends:       JuicyPixels >=3.2.9 && <3.5     , OpenGL ==3.0.*-    , aeson >=1.4 && <2.1+    , aeson >=1.4 && <2.3     , async >=2.1 && <2.3     , attoparsec >=0.12 && <0.15     , base >=4.11 && <5@@ -345,24 +345,24 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , safe ==0.3.*     , scientific ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14     , websockets ==0.12.*     , wreq >=0.5.2 && <0.6-    , wuss ==1.1.*+    , wuss >=1.1 && <2.3   default-language: Haskell2010  executable todo@@ -389,17 +389,17 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14@@ -437,16 +437,16 @@     , extra >=1.6 && <1.9     , formatting >=6.0 && <8.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , random >=1.1 && <1.3     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10     , time >=1.8 && <1.13     , transformers >=0.5 && <0.7@@ -528,18 +528,18 @@     , formatting >=6.0 && <8.0     , hspec >=2.4 && <3.0     , http-client >=0.6 && <0.9-    , lens >=4.16 && <5.1+    , lens >=4.16 && <6     , monomer     , mtl >=2.1 && <2.3     , nanovg >=0.8 && <1.0     , process ==1.6.*     , safe ==0.3.*-    , sdl2 >=2.4.0 && <2.6+    , sdl2 >=2.5.0 && <2.6     , silently ==1.2.*     , stm ==2.5.*-    , text ==1.2.*+    , text >=1.2 && <2.1     , text-show >=3.7 && <3.10-    , time >=1.8 && <1.13+    , time >=1.8 && <1.16     , transformers >=0.5 && <0.7     , unordered-containers >=0.2.8 && <0.3     , vector >=0.12 && <0.14
src/Monomer/Core/Combinators.hs view
@@ -265,6 +265,10 @@ class CmbFitHeight t where   fitHeight :: t +-- | Either fitWidth or fitHeight such that image does not overflow viewport+class CmbFitEither t where+  fitEither :: t+ -- | Applies nearest filtering when stretching an image. class CmbImageNearest t where   imageNearest :: t@@ -694,3 +698,11 @@ -- | Set radius for the bottom right corner. class CmbRadiusBR t where   radiusBR :: Double -> t++-- | Set the spacing between the children of the container.+class CmbChildSpacing t where+  -- | Set the spacing to the default amount (may vary by container).+  childSpacing :: t+  childSpacing = childSpacing_ 10+  -- | Set the spacing to the specified amount.+  childSpacing_ :: Double -> t
src/Monomer/Event/Core.hs view
@@ -85,6 +85,7 @@     button = case SDL.mouseButtonEventButton eventData of       SDL.ButtonLeft -> Just BtnLeft       SDL.ButtonRight -> Just BtnRight+      SDL.ButtonMiddle -> Just BtnMiddle       _ -> Nothing      action = case SDL.mouseButtonEventMotion eventData of
src/Monomer/Graphics/Text.hs view
@@ -328,7 +328,7 @@   targetW = width - tw    dropHelper (idx, w) g-    | _glpW g + w <= dw = (idx + 1, _glpW g + w)+    | isSafeLE (_glpW g + w) dw = (idx + 1, _glpW g + w)     | otherwise = (idx, w)   (dropChars, _) = foldl' dropHelper (0, targetW) (Seq.reverse _tlGlyphs) @@ -359,11 +359,11 @@   fontSpcH = styleFontSpaceH style    takeHelper (idx, w) g-    | _glpW g + w <= width = (idx + 1, _glpW g + w)+    | isSafeLE (_glpW g + w) width = (idx + 1, _glpW g + w)     | otherwise = (idx, w)    (takeChars, _) = foldl' takeHelper (0, 0) _tlGlyphs-  validGlyphs = Seq.takeWhileL (\g -> _glpXMax g <= width) _tlGlyphs+  validGlyphs = Seq.takeWhileL (\g -> isSafeLE (_glpXMax g) width) _tlGlyphs   newText     | trim == KeepSpaces = T.take (length validGlyphs) _tlText     | otherwise = T.dropWhileEnd (== ' ') $ T.take (length validGlyphs) _tlText@@ -395,7 +395,7 @@   -> (Seq GlyphPos, Seq GlyphGroup) fitExtraGroups Empty _ _ _ = (Empty, Empty) fitExtraGroups (g :<| gs) !width !prevGMax !keepTailSpaces-  | gW + wDiff <= width || keepSpace = (g <> newFit, newRest)+  | isSafeLE (gW + wDiff) width || keepSpace = (g <> newFit, newRest)   | otherwise = (Empty, g :<| gs)   where     gW = getGlyphsWidth g@@ -444,3 +444,6 @@  isSpace :: Char -> Bool isSpace = (== ' ')++isSafeLE :: Double -> Double -> Bool+isSafeLE width target = width <= target || abs (target - width) < 0.001
src/Monomer/Main/Core.hs view
@@ -220,7 +220,7 @@   let MainLoopArgs{..} = loopArgs    startTicks <- fmap fromIntegral SDL.ticks-  events <- SDL.pollEvents+  events <- SDL.pumpEvents >> SDL.pollEvents    windowSize <- use L.windowSize   dpr <- use L.dpr
src/Monomer/Main/Platform.hs view
@@ -23,6 +23,7 @@ ) where  import Control.Monad (void)+import Control.Monad.Extra (whenJust) import Control.Monad.State import Data.Maybe import Data.Text (Text)@@ -90,8 +91,8 @@         | platform `elem` ["Windows", "Linux"] = (scaleFactor, 1 / scaleFactor)         | otherwise = (scaleFactor * contentRatio, 1 / scaleFactor) -- macOS -  when (isJust (_apcWindowTitle config)) $-    SDL.windowTitle window $= fromJust (_apcWindowTitle config)+  whenJust (_apcWindowTitle config) $ \title ->+    SDL.windowTitle window $= title    when windowFullscreen $     SDL.setWindowMode window SDL.FullscreenDesktop
src/Monomer/Widgets/Composite.hs view
@@ -528,8 +528,8 @@   -- The same model is provided as old since nothing else is available, but   -- mergeRequired may be using data from a closure   modelChanged = _cmpMergeRequired comp (fromJust oldModel) model-  visibleChg = oldComp ^. L.info . L.visible /= newComp ^. L.info . L.visible-  enabledChg = oldComp ^. L.info . L.enabled /= newComp ^. L.info . L.enabled+  visibleChg = nodeVisibleChanged oldComp newComp+  enabledChg = nodeEnabledChanged oldComp newComp   flagsChanged = visibleChg || enabledChg   themeChanged = wenv ^. L.themeChanged   mergeRequired@@ -914,10 +914,14 @@ mergeChild comp state wenv newModel widgetRoot widgetComp = newResult where   CompositeState{..} = state   cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap newModel+  widgetId = _cpsRoot ^. L.info . L.widgetId   builtRoot = cascadeCtx wenv widgetComp (_cmpUiBuilder comp cwenv newModel)-      & L.info . L.widgetId .~ _cpsRoot ^. L.info . L.widgetId+      & L.info . L.widgetId .~ widgetId   builtWidget = builtRoot ^. L.widget-  mergedResult = widgetMerge builtWidget cwenv builtRoot widgetRoot+  initRequired = not (nodeMatches widgetRoot builtRoot)+  mergedResult+    | initRequired = widgetInit builtWidget cwenv builtRoot+    | otherwise = widgetMerge builtWidget cwenv builtRoot widgetRoot   !mergedState = state {     _cpsModel = Just newModel,     _cpsRoot = mergedResult ^. L.node,@@ -926,6 +930,7 @@   !result = toParentResult comp mergedState wenv widgetComp mergedResult   !newReqs = widgetDataSet (_cmpWidgetData comp) newModel     ++ fmap ($ newModel) (_cmpOnChangeReq comp)+    ++ [ResizeWidgets widgetId | initRequired]   !newResult = result     & L.requests <>~ Seq.fromList newReqs 
src/Monomer/Widgets/Container.hs view
@@ -317,11 +317,19 @@   -> (SizeReq, SizeReq)    -- ^ The horizontal and vertical requirements.  {-|-Resizes the widget to the provided size. If the widget state does not depend-on the viewport size, this function does not need to be overriden.+Assigns space to children according to the Container's logic, returning a 'Seq'+of 'Rect's in the same order of the child widgets. If the container has a single+child it is not generally necessary to override this function. A new version of+the Container may be returned if state needs to be updated.  Examples can be found in "Monomer.Widgets.Containers.Grid" and "Monomer.Widgets.Containers.Stack".++Note: if the Container's state depends on the current size, visibility should be+considered; since invisible nodes receive zero space, it may affect calculations+and cause unexpected behavior when the widget is made visible again. Examples+can be found in "Monomer.Widgets.Containers.Scroll" and+"Monomer.Widgets.Containers.Split". -} type ContainerResizeHandler s e   = WidgetEnv s e                  -- ^ The widget environment.
src/Monomer/Widgets/Containers/Base/LabeledItem.hs view
@@ -26,7 +26,7 @@ import qualified Data.Sequence as Seq  import Monomer.Core-import Monomer.Core.Combinators+import Monomer.Core.Combinators as Cmb  import Monomer.Widgets.Container import Monomer.Widgets.Containers.Stack@@ -43,22 +43,24 @@   :: WidgetEvent e   => WidgetType   -> RectSide+  -> Maybe Double   -> Text   -> LabelCfg s e   -> WidgetNode s e   -> WidgetNode s e-labeledItem wtype textSide caption labelCfg itemNode = labeledNode where-  widget = makeLabeledItem textSide caption labelCfg itemNode+labeledItem wtype textSide childSpacing caption labelCfg itemNode = labeledNode where+  widget = makeLabeledItem textSide childSpacing caption labelCfg itemNode   labeledNode = defaultWidgetNode wtype widget  makeLabeledItem   :: WidgetEvent e   => RectSide+  -> Maybe Double   -> Text   -> LabelCfg s e   -> WidgetNode s e   -> Widget s e-makeLabeledItem textSide caption labelCfg itemNode = widget where+makeLabeledItem textSide childSpacing caption labelCfg itemNode = widget where   widget = createContainer () def {     containerInit = init,     containerMerge = merge,@@ -83,10 +85,12 @@       & L.info . L.style <>~ itemStyle      childNode-      | textSide == SideLeft = hstack [ labelNode, spacer, styledNode ]-      | textSide == SideRight = hstack [ styledNode, spacer, labelNode ]-      | textSide == SideTop = vstack [ labelNode, spacer, styledNode ]-      | otherwise = vstack [ styledNode, spacer, labelNode ]+      | textSide == SideLeft = hstack_ stackCfg [ labelNode, styledNode ]+      | textSide == SideRight = hstack_ stackCfg [ styledNode, labelNode ]+      | textSide == SideTop = vstack_ stackCfg [ labelNode, styledNode ]+      | otherwise = vstack_ stackCfg [ styledNode, labelNode ]+    stackCfg =+      [maybe Cmb.childSpacing Cmb.childSpacing_ childSpacing]     newNode = node       & L.children .~ Seq.singleton childNode @@ -110,8 +114,8 @@     where       labelIdx         | textSide `elem` [SideLeft, SideTop] = 0-        | otherwise = 2-      targetIdx = 2 - labelIdx+        | otherwise = 1+      targetIdx = 1 - labelIdx       newPath = Seq.take (length target - 1) target |> targetIdx       labelNode = node ^. L.children . ix 0 . L.children ^?! ix labelIdx       targetNode = node ^. L.children . ix 0 . L.children ^?! ix targetIdx
src/Monomer/Widgets/Containers/Box.hs view
@@ -397,8 +397,8 @@     contentArea = fromMaybe def (removeOuterBounds style viewport)     Rect cx cy cw ch = contentArea -    contentW = snd $ assignStackAreas True contentArea children-    contentH = snd $ assignStackAreas False contentArea children+    contentW = snd $ assignStackAreas True contentArea 0 children+    contentH = snd $ assignStackAreas False contentArea 0 children      raChild = Rect cx cy (min cw contentW) (min ch contentH)     ah = fromMaybe ACenter (_boxAlignH config)
src/Monomer/Widgets/Containers/Dropdown.hs view
@@ -18,6 +18,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StrictData #-}  module Monomer.Widgets.Containers.Dropdown (@@ -40,8 +41,9 @@ import Data.Maybe import Data.Sequence (Seq(..), (<|), (|>)) import Data.Text (Text)-import Data.Typeable (cast)+import Data.Typeable (Typeable, Proxy, cast, typeRep) import GHC.Generics+import TextShow  import qualified Data.Sequence as Seq @@ -223,22 +225,21 @@  -- | Creates a dropdown providing a WidgetData instance and config. dropdownD_-  :: (WidgetModel s, WidgetEvent e, Traversable t, DropdownItem a)+  :: forall s e t a . (WidgetModel s, WidgetEvent e, Traversable t, DropdownItem a)   => WidgetData s a         -- ^ The 'WidgetData' to retrieve the value from.   -> t a                    -- ^ The list of selectable items.   -> (a -> WidgetNode s e)  -- ^ Function to create the header (always visible).   -> (a -> WidgetNode s e)  -- ^ Function to create the list (collapsable).   -> [DropdownCfg s e a]    -- ^ The config options.   -> WidgetNode s e         -- ^ The created dropdown.-dropdownD_ widgetData items makeMain makeRow configs = makeNode widget where+dropdownD_ widgetData items makeMain makeRow configs = newNode where   config = mconcat configs   newState = DropdownState False def   newItems = foldl' (|>) Empty items+  wtype = WidgetType ("dropdown-" <> showt (typeRep (undefined :: Proxy a)))   widget = makeDropdown widgetData newItems makeMain makeRow config newState--makeNode :: Widget s e -> WidgetNode s e-makeNode widget = defaultWidgetNode "dropdown" widget-  & L.info . L.focusable .~ True+  newNode = defaultWidgetNode wtype widget+    & L.info . L.focusable .~ True  makeDropdown   :: (WidgetModel s, WidgetEvent e, DropdownItem a)@@ -505,8 +506,7 @@       Rect x y w h = contentArea       size = style ^. L.text . non def . L.fontSize . non def       arrowW = unFontSize size / 2-      dh = (h - arrowW) / 2-      arrowRect = Rect (x + w - dh * 2) (y + dh * 1.25) arrowW (arrowW / 2)+      arrowRect = Rect (x + w - arrowW) (y + h / 2 - arrowW / 3) arrowW (arrowW / 2)    renderOverlay renderer wenv overlayNode = renderAction where     widget = overlayNode ^. L.widget
src/Monomer/Widgets/Containers/Grid.hs view
@@ -24,7 +24,7 @@ ) where  import Control.Applicative ((<|>))-import Control.Lens ((&), (^.), (.~))+import Control.Lens ((&), (^.), (.~), (%~)) import Data.Default import Data.List (foldl') import Data.Maybe@@ -40,25 +40,34 @@ {-| Configuration options for grid: +- 'childSpacing': spacing between the child widgets. - 'sizeReqUpdater': allows modifying the 'SizeReq' generated by the grid. -}-newtype GridCfg = GridCfg {+data GridCfg = GridCfg {+  _grcChildSpacing :: Maybe Double,   _grcSizeReqUpdater :: [SizeReqUpdater] }  instance Default GridCfg where   def = GridCfg {+    _grcChildSpacing = Nothing,     _grcSizeReqUpdater = []   }  instance Semigroup GridCfg where   (<>) s1 s2 = GridCfg {+    _grcChildSpacing = _grcChildSpacing s2 <|> _grcChildSpacing s1,     _grcSizeReqUpdater = _grcSizeReqUpdater s1 <> _grcSizeReqUpdater s2   }  instance Monoid GridCfg where   mempty = def +instance CmbChildSpacing GridCfg where+  childSpacing_ spacing = def {+    _grcChildSpacing = Just spacing+  }+ instance CmbSizeReqUpdater GridCfg where   sizeReqUpdater updater = def {     _grcSizeReqUpdater = [updater]@@ -95,6 +104,7 @@   }    isVertical = not isHorizontal+  childSpacing = fromMaybe 0 (_grcChildSpacing config)    getSizeReq wenv node children = newSizeReq where     sizeReqFns = _grcSizeReqUpdater config@@ -105,12 +115,13 @@    getDimSizeReq mainAxis accesor vchildren     | Seq.null vreqs = fixedSize 0-    | mainAxis = foldl1 sizeReqMergeSum (Seq.replicate nreqs maxSize)+    | mainAxis = foldl1 sizeReqMergeSum (Seq.replicate nreqs maxSize) & L.fixed %~ (+ totalSpacing)     | otherwise = maxSize     where       vreqs = accesor <$> vchildren       nreqs = Seq.length vreqs       ~maxSize = foldl1 sizeReqMergeMax vreqs+      totalSpacing = fromIntegral (nreqs - 1) * childSpacing    resize wenv node viewport children = resized where     style = currentStyle wenv node@@ -118,18 +129,25 @@     Rect l t w h = contentArea     vchildren = Seq.filter (_wniVisible . _wnInfo) children +    totalSpacingW = fromIntegral (max 0 (cols - 1)) * childSpacing+    totalSpacingH = fromIntegral (max 0 (rows - 1)) * childSpacing+     cols = if isHorizontal then length vchildren else 1     rows = if isHorizontal then 1 else length vchildren -    cw = if cols > 0 then w / fromIntegral cols else 0-    ch = if rows > 0 then h / fromIntegral rows else 0+    cw = if cols > 0 then (w - totalSpacingW) / fromIntegral cols else 0+    ch = if rows > 0 then (h - totalSpacingH) / fromIntegral rows else 0      cx i-      | rows > 0 = l + fromIntegral (i `div` rows) * cw-      | otherwise = 0+      | rows == 0 = 0+      | isHorizontal = l + fromIntegral i * cw + spacingOffset i+      | otherwise = l     cy i-      | cols > 0 = t + fromIntegral (i `div` cols) * ch-      | otherwise = 0+      | cols == 0 = 0+      | isVertical = t + fromIntegral i * ch + spacingOffset i+      | otherwise = t+    spacingOffset i =+      fromIntegral i * childSpacing      foldHelper (currAreas, index) child = (newAreas, newIndex) where       (newIndex, newViewport)
src/Monomer/Widgets/Containers/Scroll.hs view
@@ -22,12 +22,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE StrictData #-}  module Monomer.Widgets.Containers.Scroll (   -- * Configuration   ScrollCfg,+  ScrollStatus(..),   ScrollMessage(..),   scrollOverlay,   scrollOverlay_,@@ -68,6 +68,23 @@   | ScrollBoth   deriving (Eq, Show) +data ScrollStatus = ScrollStatus {+  scrollDeltaX :: Double,+  scrollDeltaY :: Double,+  scrollRect :: Rect,+  scrollVpSize :: Size,+  scrollChildSize :: Size+} deriving (Eq, Show)++instance Default ScrollStatus where+  def = ScrollStatus {+    scrollDeltaX = 0,+    scrollDeltaY = 0,+    scrollRect = def,+    scrollVpSize = def,+    scrollChildSize = def+  }+ data ActiveBar   = HBar   | VBar@@ -76,6 +93,12 @@ {-| Configuration options for scroll: +- 'scrollOverlay': whether scroll bar should be on top of content or by the+  side.+- 'scrollInvisible': shortcut for setting invisible style. Useful with scroll+  overlay, since it allows scrolling without taking up space or hiding content.+- 'scrollFollowFocus': whether to auto scroll when focusing a non visible item.+- 'scrollStyle': the base style of the scroll bar. - 'wheelRate': rate at which wheel movement causes scrolling. - 'barColor': the color of the bar (container of the thumb). - 'barHoverColor': the color of the bar when mouse is on top.@@ -84,12 +107,8 @@ - 'thumbHoverColor': the color of the thumb when mouse is on top. - 'thumbWidth': the width of the thumb. - 'thumbRadius': the radius of the corners of the thumb.-- 'scrollOverlay': whether scroll bar should be on top of content or by the-  side.-- 'scrollInvisible': shortcut for setting invisible style. Useful with scroll-  overlay, since it allows scrolling without taking up space or hiding content.-- 'scrollFollowFocus': whether to auto scroll when focusing a non visible item.-- 'scrollStyle': the base style of the scroll bar.+- 'onChange': event to raise when the viewport changes.+- 'onChangeReq': 'WidgetRequest' to generate when the viewport changes. -} data ScrollCfg s e = ScrollCfg {   _scScrollType :: Maybe ScrollType,@@ -104,7 +123,8 @@   _scStyle :: Maybe (ALens' ThemeState StyleState),   _scBarWidth :: Maybe Double,   _scThumbWidth :: Maybe Double,-  _scThumbRadius :: Maybe Double+  _scThumbRadius :: Maybe Double,+  _scOnChangeReq :: [ScrollStatus -> WidgetRequest s e] }  instance Default (ScrollCfg s e) where@@ -121,7 +141,8 @@     _scStyle = Nothing,     _scBarWidth = Nothing,     _scThumbWidth = Nothing,-    _scThumbRadius = Nothing+    _scThumbRadius = Nothing,+    _scOnChangeReq = []   }  instance Semigroup (ScrollCfg s e) where@@ -138,7 +159,8 @@     _scStyle = _scStyle t2 <|> _scStyle t1,     _scBarWidth = _scBarWidth t2 <|> _scBarWidth t1,     _scThumbWidth = _scThumbWidth t2 <|> _scThumbWidth t1,-    _scThumbRadius = _scThumbRadius t2 <|> _scThumbRadius t1+    _scThumbRadius = _scThumbRadius t2 <|> _scThumbRadius t1,+    _scOnChangeReq = _scOnChangeReq t2 <|> _scOnChangeReq t1   }  instance Monoid (ScrollCfg s e) where@@ -185,6 +207,16 @@     _scThumbRadius = Just r   } +instance WidgetEvent e => CmbOnChange (ScrollCfg s e) ScrollStatus e where+  onChange fn = def {+    _scOnChangeReq = [RaiseEvent . fn]+  }++instance CmbOnChangeReq (ScrollCfg s e) s e ScrollStatus where+  onChangeReq req = def {+    _scOnChangeReq = [req]+  }+ -- | Scroll bars will be displayed on top of the content. scrollOverlay :: ScrollCfg s e scrollOverlay = scrollOverlay_ True@@ -559,6 +591,7 @@     rect = moveRect offset targetRect     Rect rx ry rw rh = rect     Rect cx cy _ _ = contentArea+    childVp = Rect cx cy maxVpW maxVpH      diffL = cx - rx     diffR = cx + maxVpW - (rx + rw)@@ -566,11 +599,11 @@     diffB = cy + maxVpH - (ry + rh)      stepX-      | rectInRectH rect contentArea = dx+      | rectInRectH rect childVp = dx       | abs diffL <= abs diffR = diffL + dx       | otherwise = diffR + dx     stepY-      | rectInRectV rect contentArea = dy+      | rectInRectV rect childVp = dy       | abs diffT <= abs diffB = diffT + dy       | otherwise = diffB + dy @@ -579,7 +612,7 @@       _sstDeltaY = scrollAxisV stepY     }     result-      | rectInRect rect contentArea = Nothing+      | rectInRect rect childVp = Nothing       | otherwise = Just $ rebuildWidget wenv node newState    scrollReset wenv node = result where@@ -613,9 +646,14 @@     }    rebuildWidget wenv node newState = result where+    updateReqs+      | childPosChanged state newState = scrollInfoReqs node config newState+      | otherwise = []+     newNode = node       & L.widget .~ makeScroll config newState     result = resultNode newNode+      & L.requests .~ Seq.fromList updateReqs    getSizeReq :: ContainerGetSizeReqHandler s e   getSizeReq wenv node children = sizeReq where@@ -676,10 +714,21 @@       _sstChildSize = Size areaW areaH,       _sstScissor = scissor     }-    newNode = resultNode $ node+    newNode = node       & L.widget .~ makeScroll config newState-    result = (newNode, Seq.singleton cViewport)+      -- For scrollInfoReqs only, since parent will set viewport later+      & L.info . L.viewport .~ viewport +    updateReqs+      | childPosChanged state newState = scrollInfoReqs newNode config newState+      | otherwise = []+    visibleResult = resultNode newNode+      & L.requests .~ Seq.fromList updateReqs++    result+      | node ^. L.info . L.visible = (visibleResult, Seq.singleton cViewport)+      | otherwise = (resultNode node, Seq.singleton cViewport)+   renderAfter wenv node renderer = do     when hScrollRequired $       drawRect renderer hScrollRect barColorH Nothing@@ -729,6 +778,27 @@       thumbColorV         | vMouseInThumb || draggingV = thumbHCol         | otherwise = thumbBCol++childPosChanged :: ScrollState -> ScrollState -> Bool+childPosChanged state newState = _sstVpSize state /= _sstVpSize newState+  || _sstChildSize state /= _sstChildSize newState+  || _sstDeltaX state /= _sstDeltaX newState+  || _sstDeltaY state /= _sstDeltaY newState++scrollInfoReqs+  :: WidgetNode s e+  -> ScrollCfg s e+  -> ScrollState+  -> [WidgetRequest s e]+scrollInfoReqs node config state = reqs where+  info = ScrollStatus {+    scrollDeltaX = _sstDeltaX state,+    scrollDeltaY = _sstDeltaY state,+    scrollRect = node ^. L.info . L.viewport,+    scrollVpSize = _sstVpSize state,+    scrollChildSize = _sstChildSize state+  }+  reqs = fmap ($ info) (_scOnChangeReq config)  scrollCurrentStyle :: WidgetEnv s e -> WidgetNode s e -> StyleState scrollCurrentStyle wenv node
src/Monomer/Widgets/Containers/SelectList.hs view
@@ -15,6 +15,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE StrictData #-}+{-# LANGUAGE ScopedTypeVariables #-}  module Monomer.Widgets.Containers.SelectList (   -- * Configuration@@ -38,7 +39,8 @@ import Data.Maybe import Data.Sequence (Seq(..), (<|), (|>)) import Data.Text (Text)-import Data.Typeable (Typeable, cast)+import Data.Typeable (Typeable, Proxy, cast, typeRep)+import TextShow  import qualified Data.Map as Map import qualified Data.Sequence as Seq@@ -235,21 +237,22 @@  -- | Creates a dropdown providing a 'WidgetData' instance and config. selectListD_-  :: (WidgetModel s, WidgetEvent e, Traversable t, SelectListItem a)+  :: forall s e t a . (WidgetModel s, WidgetEvent e, Traversable t, SelectListItem a)   => WidgetData s a         -- ^ The 'WidgetData' to retrieve the value from.   -> t a                    -- ^ The list of selectable items.   -> SelectListMakeRow s e a  -- ^ Function to create the list items.   -> [SelectListCfg s e a]  -- ^ The config options.   -> WidgetNode s e         -- ^ The created dropdown.-selectListD_ widgetData items makeRow configs = makeNode widget where+selectListD_ widgetData items makeRow configs = makeNode wtype widget where   config = mconcat configs   newItems = foldl' (|>) Empty items   newState = SelectListState newItems (-1) 0+  wtype = WidgetType ("selectList-" <> showt (typeRep (undefined :: Proxy a)))   widget = makeSelectList widgetData newItems makeRow config newState -makeNode :: Widget s e -> WidgetNode s e-makeNode widget = scroll_ [scrollStyle L.selectListStyle] childNode where-  childNode = defaultWidgetNode "selectList" widget+makeNode :: WidgetType -> Widget s e -> WidgetNode s e+makeNode wtype widget = scroll_ [scrollStyle L.selectListStyle] childNode where+  childNode = defaultWidgetNode wtype widget     & L.info . L.focusable .~ True  makeSelectList
src/Monomer/Widgets/Containers/Split.hs view
@@ -270,7 +270,7 @@     style = currentStyle wenv node     contentArea = fromMaybe def (removeOuterBounds style viewport)     Rect rx ry rw rh = contentArea-    (areas, newSize) = assignStackAreas isHorizontal contentArea children+    (areas, newSize) = assignStackAreas isHorizontal contentArea 0 children     oldHandlePos = _spsHandlePos state      sizeReq1 = sizeReq $ Seq.index children 0@@ -320,7 +320,9 @@       & L.node . L.widget .~ makeSplit isHorizontal config newState       & L.requests .~ Seq.fromList (requestPos ++ reqOnChange)     newVps = Seq.fromList [rect1, rect2]-    resized = (result, newVps)+    resized+      | node ^. L.info . L.visible = (result, newVps)+      | otherwise = (resultNode node, newVps)    getValidHandlePos maxDim vp handleXY children = addPoint origin newPoint where     Rect rx ry _ _ = vp
src/Monomer/Widgets/Containers/Stack.hs view
@@ -26,7 +26,7 @@ ) where  import Control.Applicative ((<|>))-import Control.Lens ((&), (^.), (.~))+import Control.Lens ((&), (^.), (.~), (%~)) import Data.Default import Data.Foldable (toList) import Data.List (foldl')@@ -43,24 +43,28 @@ {-| Configuration options for stack: +- 'childSpacing': spacing between the child widgets. - 'ignoreEmptyArea': when the widgets do not use all the available space,   ignoring the unassigned space allows for mouse events to pass through. This is   useful in zstack layers. - 'sizeReqUpdater': allows modifying the 'SizeReq' generated by the stack. -} data StackCfg = StackCfg {+  _stcChildSpacing :: Maybe Double,   _stcIgnoreEmptyArea :: Maybe Bool,   _stcSizeReqUpdater :: [SizeReqUpdater] }  instance Default StackCfg where   def = StackCfg {+    _stcChildSpacing = Nothing,     _stcIgnoreEmptyArea = Nothing,     _stcSizeReqUpdater = []   }  instance Semigroup StackCfg where   (<>) s1 s2 = StackCfg {+    _stcChildSpacing = _stcChildSpacing s2 <|> _stcChildSpacing s1,     _stcIgnoreEmptyArea = _stcIgnoreEmptyArea s2 <|> _stcIgnoreEmptyArea s1,     _stcSizeReqUpdater = _stcSizeReqUpdater s1 <> _stcSizeReqUpdater s2   }@@ -68,6 +72,11 @@ instance Monoid StackCfg where   mempty = def +instance CmbChildSpacing StackCfg where+  childSpacing_ spacing = def {+    _stcChildSpacing = Just spacing+  }+ instance CmbIgnoreEmptyArea StackCfg where   ignoreEmptyArea_ ignore = def {     _stcIgnoreEmptyArea = Just ignore@@ -120,6 +129,7 @@    isVertical = not isHorizontal   ignoreEmptyArea = fromMaybe False (_stcIgnoreEmptyArea config)+  childSpacing = fromMaybe 0 (_stcChildSpacing config)    getSizeReq wenv node children = newSizeReq where     sizeReqFns = _stcSizeReqUpdater config@@ -130,15 +140,16 @@    getDimSizeReq mainAxis accesor vchildren     | Seq.null vreqs = fixedSize 0-    | mainAxis = foldl1 sizeReqMergeSum vreqs+    | mainAxis = foldl1 sizeReqMergeSum vreqs & L.fixed %~ (+ totalSpacing)     | otherwise = foldl1 sizeReqMergeMax vreqs     where       vreqs = accesor <$> vchildren+      totalSpacing = fromIntegral (Seq.length vchildren - 1) * childSpacing    resize wenv node viewport children = resized where     style = currentStyle wenv node     contentArea = fromMaybe def (removeOuterBounds style viewport)-    (newVps, newDim) = assignStackAreas isHorizontal contentArea children+    (newVps, newDim) = assignStackAreas isHorizontal contentArea childSpacing children     newCa       | isHorizontal = contentArea & L.w .~ newDim       | otherwise = contentArea & L.h .~ newDim@@ -153,9 +164,10 @@ assignStackAreas   :: Bool                 -- ^ True if horizontal, False for vertical.   -> Rect                 -- ^ The available space to assign.+  -> Double               -- ^ The spacing between adjacent children.   -> Seq (WidgetNode s e) -- ^ The widgets that will be assigned space.   -> (Seq Rect, Double)   -- ^ The assigned areas and used space in main axis.-assignStackAreas isHorizontal contentArea children = result where+assignStackAreas isHorizontal contentArea childSpacing children = result where   Rect x y w h = contentArea   mainSize = if isHorizontal then w else h   mainStart = if isHorizontal then x else y@@ -173,9 +185,12 @@     newExtraFac = cExtraFac + sizeReqExtra req * sizeReqFactor req     newStep = (newFixed, newFlex, newFlexFac, newExtraFac) +  visibleSpacings = max 0 (Seq.length vchildren - 1)+  totalSpacing = fromIntegral visibleSpacings * childSpacing+   (fixed, flex, flexFac, extraFac) = foldl' sumSizes def reqs-  flexAvail = min flex (mainSize - fixed)-  extraAvail = max 0 (mainSize - fixed - flexAvail)+  flexAvail = min flex (mainSize - fixed - totalSpacing)+  extraAvail = max 0 (mainSize - fixed - totalSpacing - flexAvail)    -- flexCoeff can only be negative   flexCoeff@@ -185,12 +200,15 @@     | extraAvail > 0 && extraFac > 0 = extraAvail / extraFac     | otherwise = 0 -  foldHelper (accum, offset) child = (newAccum, newOffset) where+  foldHelper (accum, offset, vIndex) child = (newAccum, newOffset, newVIndex) where     newRect = resizeChild isHorizontal contentArea flexCoeff extraCoeff offset child     newAccum = accum |> newRect-    newOffset = offset + rectSelector newRect+    newOffset = offset + rectSelector newRect + spacing+    newVIndex = vIndex + if visible then 1 else 0+    spacing = if vIndex < Seq.length vchildren - 1 && visible then childSpacing else 0+    visible = _wniVisible (_wnInfo child) -  (areas, usedDim) = foldl' foldHelper (Seq.empty, mainStart) children+  (areas, usedDim, _) = foldl' foldHelper (Seq.empty, mainStart, 0) children   result = (areas, usedDim - mainStart)  resizeChild :: Bool -> Rect -> Factor -> Factor -> Double -> WidgetNode s e -> Rect
src/Monomer/Widgets/Singles/DateField.hs view
@@ -20,7 +20,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE UndecidableInstances #-}  module Monomer.Widgets.Singles.DateField (@@ -49,6 +48,7 @@ import Data.Text (Text) import Data.Time import Data.Typeable (Typeable, typeOf)+import TextShow  import qualified Data.Attoparsec.Text as A import qualified Data.Text as T@@ -395,7 +395,8 @@     _ifcOnBlurReq = _dfcOnBlurReq config,     _ifcOnChangeReq = _dfcOnChangeReq config   }-  newNode = inputField_ "dateField" inputConfig+  wtype = WidgetType ("dateField-" <> showt (typeOf initialValue))+  newNode = inputField_ wtype inputConfig  handleWheel   :: FormattableDate a
src/Monomer/Widgets/Singles/Dial.hs view
@@ -37,8 +37,9 @@ import Data.Default import Data.Maybe import Data.Text (Text)-import Data.Typeable (Typeable)+import Data.Typeable (Typeable, typeOf) import GHC.Generics+import TextShow  import qualified Data.Sequence as Seq @@ -212,8 +213,9 @@ dialD_ widgetData minVal maxVal configs = dialNode where   config = mconcat configs   state = DialState 0 0+  wtype = WidgetType ("dial-" <> showt (typeOf minVal))   widget = makeDial widgetData minVal maxVal config state-  dialNode = defaultWidgetNode "dial" widget+  dialNode = defaultWidgetNode wtype widget     & L.info . L.focusable .~ True  makeDial
src/Monomer/Widgets/Singles/Image.hs view
@@ -69,6 +69,7 @@   | FitFill   | FitWidth   | FitHeight+  | FitEither   deriving (Eq, Show)  -- | Posible errors when loading an image.@@ -174,6 +175,11 @@     _imcFit = Just FitHeight   } +instance CmbFitEither (ImageCfg e) where+  fitEither  = def {+    _imcFit = Just FitEither+  }+ instance CmbTransparency (ImageCfg e) where   transparency alpha = def {     _imcTransparency = Just alpha@@ -423,16 +429,22 @@ fitImage viewport imageSize imgFlags imgFit alignH alignV = case imgFit of   FitNone -> alignImg iw ih   FitFill -> alignImg w h-  FitWidth-    | ImageRepeatY `elem` imgFlags -> alignImg w ih-    | otherwise -> alignImg w (w * ih / iw)-  FitHeight-    | ImageRepeatX `elem` imgFlags -> alignImg iw h-    | otherwise -> alignImg (h * iw / ih) h+  FitWidth -> fitWidth+  FitHeight -> fitHeight+  FitEither+    | w * ih > h * iw -> fitHeight+    | otherwise -> fitWidth   where     Rect x y w h = viewport     Size iw ih = imageSize     alignImg nw nh = alignInRect viewport (Rect x y nw nh) alignH alignV+    fitWidth+      | ImageRepeatY `elem` imgFlags = alignImg w ih+      | otherwise = alignImg w (w * ih / iw)+    fitHeight+      | ImageRepeatX `elem` imgFlags = alignImg iw h+      | otherwise = alignImg (h * iw / ih) h+  handleImageLoad :: ImageCfg e -> WidgetEnv s e -> Text -> IO ImageMessage handleImageLoad config wenv path = do
src/Monomer/Widgets/Singles/LabeledCheckbox.hs view
@@ -43,6 +43,10 @@ {-| Configuration options for labeledCheckbox: +- General++    - 'childSpacing': the spacing between the checkbox and the text.+ - Text related      - 'textLeft': places the label to the left of the checkbox.@@ -70,6 +74,7 @@ -} data LabeledCheckboxCfg s e = LabeledCheckboxCfg {   _lchTextSide :: Maybe RectSide,+  _lchChildSpacing :: Maybe Double,   _lchLabelCfg :: LabelCfg s e,   _lchCheckboxCfg :: CheckboxCfg s e }@@ -77,6 +82,7 @@ instance Default (LabeledCheckboxCfg s e) where   def = LabeledCheckboxCfg {     _lchTextSide = Nothing,+    _lchChildSpacing = Nothing,     _lchLabelCfg = def,     _lchCheckboxCfg = def   }@@ -84,6 +90,7 @@ instance Semigroup (LabeledCheckboxCfg s e) where   (<>) t1 t2 = LabeledCheckboxCfg {     _lchTextSide = _lchTextSide t2 <|> _lchTextSide t1,+    _lchChildSpacing = _lchChildSpacing t2 <|> _lchChildSpacing t1,     _lchLabelCfg = _lchLabelCfg t1 <> _lchLabelCfg t2,     _lchCheckboxCfg = _lchCheckboxCfg t1 <> _lchCheckboxCfg t2   }@@ -91,6 +98,11 @@ instance Monoid (LabeledCheckboxCfg s e) where   mempty = def +instance CmbChildSpacing (LabeledCheckboxCfg s e) where+  childSpacing_ spacing = def {+    _lchChildSpacing = Just spacing+  }+ instance CmbTextLeft (LabeledCheckboxCfg s e) where   textLeft_ False = def   textLeft_ True = def {@@ -240,6 +252,7 @@ labeledCheckboxD_ caption widgetData configs = newNode where   config = mconcat configs   labelSide = fromMaybe SideLeft (_lchTextSide config)+  childSpacing = _lchChildSpacing config   labelCfg = _lchLabelCfg config   widget = checkboxD_ widgetData [_lchCheckboxCfg config]-  newNode = labeledItem "labeledCheckbox" labelSide caption labelCfg widget+  newNode = labeledItem "labeledCheckbox" labelSide childSpacing caption labelCfg widget
src/Monomer/Widgets/Singles/LabeledRadio.hs view
@@ -10,6 +10,7 @@ clickable label. Each instance of the radio will be associated with a single value. -}+{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE Strict #-}@@ -30,6 +31,8 @@ import Data.Default import Data.Maybe import Data.Text (Text)+import Data.Typeable (typeOf)+import TextShow  import qualified Data.Sequence as Seq @@ -43,6 +46,10 @@ {-| Configuration options for labeledRadio: +- General++    - 'childSpacing': the spacing between the radio and the text.+ - Text related      - 'textLeft': places the label to the left of the radio.@@ -70,6 +77,7 @@ -} data LabeledRadioCfg s e a = LabeledRadioCfg {   _lchTextSide :: Maybe RectSide,+  _lchChildSpacing :: Maybe Double,   _lchLabelCfg :: LabelCfg s e,   _lchRadioCfg :: RadioCfg s e a }@@ -77,6 +85,7 @@ instance Default (LabeledRadioCfg s e a) where   def = LabeledRadioCfg {     _lchTextSide = Nothing,+    _lchChildSpacing = Nothing,     _lchLabelCfg = def,     _lchRadioCfg = def   }@@ -84,6 +93,7 @@ instance Semigroup (LabeledRadioCfg s e a) where   (<>) t1 t2 = LabeledRadioCfg {     _lchTextSide = _lchTextSide t2 <|> _lchTextSide t1,+    _lchChildSpacing = _lchChildSpacing t2 <|> _lchChildSpacing t1,     _lchLabelCfg = _lchLabelCfg t1 <> _lchLabelCfg t2,     _lchRadioCfg = _lchRadioCfg t1 <> _lchRadioCfg t2   }@@ -91,6 +101,11 @@ instance Monoid (LabeledRadioCfg s e a) where   mempty = def +instance CmbChildSpacing (LabeledRadioCfg s e a) where+  childSpacing_ spacing = def {+    _lchChildSpacing = Just spacing+  }+ instance CmbTextLeft (LabeledRadioCfg s e a) where   textLeft_ False = def   textLeft_ True = def {@@ -185,7 +200,7 @@  -- | Creates a labeled radio using the given lens. labeledRadio-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => Text   -> a   -> ALens' s a@@ -194,7 +209,7 @@  -- | Creates a labeled radio using the given lens. Accepts config. labeledRadio_-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => Text   -> a   -> ALens' s a@@ -205,7 +220,7 @@  -- | Creates a labeled radio using the given value and 'onChange' event handler. labeledRadioV-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => Text   -> a   -> a@@ -219,7 +234,7 @@ Accepts config. -} labeledRadioV_-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => Text   -> a   -> a@@ -232,7 +247,7 @@  -- | Creates a labeled radio providing a 'WidgetData' instance and config. labeledRadioD_-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => Text   -> a   -> WidgetData s a@@ -241,6 +256,8 @@ labeledRadioD_ caption option widgetData configs = newNode where   config = mconcat configs   labelSide = fromMaybe SideLeft (_lchTextSide config)+  childSpacing = _lchChildSpacing config   labelCfg = _lchLabelCfg config+  wtype = WidgetType ("labeledRadio-" <> showt (typeOf option))   widget = radioD_ option widgetData [_lchRadioCfg config]-  newNode = labeledItem "labeledRadio" labelSide caption labelCfg widget+  newNode = labeledItem wtype labelSide childSpacing caption labelCfg widget
src/Monomer/Widgets/Singles/NumericField.hs view
@@ -42,6 +42,7 @@ import Data.Text (Text) import Data.Text.Read (signed, rational) import Data.Typeable (Typeable, typeOf)+import TextShow  import qualified Data.Attoparsec.Text as A import qualified Data.Text as T@@ -347,7 +348,8 @@     _ifcOnBlurReq = _nfcOnBlurReq config,     _ifcOnChangeReq = _nfcOnChangeReq config   }-  newNode = inputField_ "numericField" inputConfig+  wtype = WidgetType ("numericField-" <> showt (typeOf initialValue))+  newNode = inputField_ wtype inputConfig  handleWheel   :: FormattableNumber a
src/Monomer/Widgets/Singles/OptionButton.hs view
@@ -31,6 +31,7 @@ @ -} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -39,6 +40,7 @@  module Monomer.Widgets.Singles.OptionButton (   -- * Configuration+  OptionButtonValue,   OptionButtonCfg,   optionButtonOffStyle,   -- * Constructors@@ -57,6 +59,8 @@ import Data.Default import Data.Maybe import Data.Text (Text)+import Data.Typeable (Typeable, typeOf)+import TextShow  import qualified Data.Sequence as Seq @@ -65,6 +69,9 @@  import qualified Monomer.Lens as L +-- | Constraints for numeric types accepted by the optionButton widget.+type OptionButtonValue a = (Eq a, Typeable a)+ {-| Configuration options for optionButton: @@ -192,7 +199,7 @@  -- | Creates an optionButton using the given lens. optionButton-  :: Eq a+  :: OptionButtonValue a   => Text   -> a   -> ALens' s a@@ -201,7 +208,7 @@  -- | Creates an optionButton using the given lens. Accepts config. optionButton_-  :: Eq a+  :: OptionButtonValue a   => Text   -> a   -> ALens' s a@@ -212,7 +219,7 @@  -- | Creates an optionButton using the given value and 'onChange' event handler. optionButtonV-  :: (Eq a, WidgetEvent e)+  :: (OptionButtonValue a, WidgetEvent e)   => Text   -> a   -> a@@ -224,7 +231,7 @@ -- | Creates an optionButton using the given value and 'onChange' event handler. --   Accepts config. optionButtonV_-  :: (Eq a, WidgetEvent e)+  :: (OptionButtonValue a, WidgetEvent e)   => Text   -> a   -> a@@ -238,7 +245,7 @@  -- | Creates an optionButton providing a 'WidgetData' instance and config. optionButtonD_-  :: Eq a+  :: OptionButtonValue a   => Text   -> a   -> WidgetData s a@@ -247,12 +254,13 @@ optionButtonD_ caption option widgetData configs = optionButtonNode where   config = mconcat configs   makeWithStyle = makeOptionButton L.optionBtnOnStyle L.optionBtnOffStyle+  wtype = WidgetType ("optionButton-" <> showt (typeOf option))   widget = makeWithStyle widgetData caption (== option) (const option) config-  optionButtonNode = defaultWidgetNode "optionButton" widget+  optionButtonNode = defaultWidgetNode wtype widget     & L.info . L.focusable .~ True  makeOptionButton-  :: Eq a+  :: OptionButtonValue a   => Lens' ThemeState StyleState   -> Lens' ThemeState StyleState   -> WidgetData s a
src/Monomer/Widgets/Singles/Radio.hs view
@@ -16,12 +16,14 @@ the look of a regular button. -} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE StrictData #-}  module Monomer.Widgets.Singles.Radio (   -- * Configuration+  RadioValue,   RadioCfg,   -- * Constructors   radio,@@ -37,11 +39,16 @@ import Data.Default import Data.Maybe import Data.Text (Text)+import Data.Typeable (Typeable, typeOf)+import TextShow  import Monomer.Widgets.Single  import qualified Monomer.Lens as L +-- | Constraints for numeric types accepted by the radio widget.+type RadioValue a = (Eq a, Typeable a)+ {-| Configuration options for radio: @@ -115,12 +122,12 @@   }  -- | Creates a radio using the given lens.-radio :: (Eq a, WidgetEvent e) => a -> ALens' s a -> WidgetNode s e+radio :: (RadioValue a, WidgetEvent e) => a -> ALens' s a -> WidgetNode s e radio option field = radio_ option field def  -- | Creates a radio using the given lens. Accepts config. radio_-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => a   -> ALens' s a   -> [RadioCfg s e a]@@ -128,13 +135,18 @@ radio_ option field configs = radioD_ option (WidgetLens field) configs  -- | Creates a radio using the given value and 'onChange' event handler.-radioV :: (Eq a, WidgetEvent e) => a -> a -> (a -> e) -> WidgetNode s e+radioV+  :: (RadioValue a, WidgetEvent e)+  => a+  -> a+  -> (a -> e)+  -> WidgetNode s e radioV option value handler = radioV_ option value handler def  -- | Creates a radio using the given value and 'onChange' event handler. --   Accepts config. radioV_-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => a   -> a   -> (a -> e)@@ -147,18 +159,24 @@  -- | Creates a radio providing a 'WidgetData' instance and config. radioD_-  :: (Eq a, WidgetEvent e)+  :: (RadioValue a, WidgetEvent e)   => a   -> WidgetData s a   -> [RadioCfg s e a]   -> WidgetNode s e radioD_ option widgetData configs = radioNode where   config = mconcat configs+  wtype = WidgetType ("radio-" <> showt (typeOf option))   widget = makeRadio widgetData option config-  radioNode = defaultWidgetNode "radio" widget+  radioNode = defaultWidgetNode wtype widget     & L.info . L.focusable .~ True -makeRadio :: (Eq a, WidgetEvent e) => WidgetData s a -> a -> RadioCfg s e a -> Widget s e+makeRadio+  :: (RadioValue a, WidgetEvent e)+  => WidgetData s a+  -> a+  -> RadioCfg s e a+  -> Widget s e makeRadio !field !option !config = widget where   widget = createSingle () def {     singleGetBaseStyle = getBaseStyle,
src/Monomer/Widgets/Singles/Slider.hs view
@@ -43,8 +43,9 @@ import Data.Default import Data.Maybe import Data.Text (Text)-import Data.Typeable (Typeable)+import Data.Typeable (Typeable, typeOf) import GHC.Generics+import TextShow  import qualified Data.Sequence as Seq @@ -53,7 +54,7 @@  import qualified Monomer.Lens as L --- | Constraints for numeric types accepted by slider.+-- | Constraints for numeric types accepted by the slider widget. type SliderValue a = (Eq a, Show a, Real a, FromFractional a, Typeable a)  {-|@@ -306,8 +307,9 @@ sliderD_ isHz widgetData minVal maxVal configs = sliderNode where   config = mconcat configs   state = SliderState 0 0+  wtype = WidgetType ("slider-" <> showt (typeOf minVal))   widget = makeSlider isHz widgetData minVal maxVal config state-  sliderNode = defaultWidgetNode "slider" widget+  sliderNode = defaultWidgetNode wtype widget     & L.info . L.focusable .~ True  makeSlider
src/Monomer/Widgets/Singles/TimeField.hs view
@@ -20,7 +20,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE UndecidableInstances #-}  module Monomer.Widgets.Singles.TimeField (@@ -46,6 +45,7 @@ import Data.Text (Text) import Data.Time import Data.Typeable (Typeable, typeOf)+import TextShow  import qualified Data.Attoparsec.Text as A import qualified Data.Text as T@@ -372,7 +372,8 @@     _ifcOnBlurReq = _tfcOnBlurReq config,     _ifcOnChangeReq = _tfcOnChangeReq config   }-  newNode = inputField_ "timeField" inputConfig+  wtype = WidgetType ("timeField-" <> showt (typeOf initialValue))+  newNode = inputField_ wtype inputConfig  handleWheel   :: FormattableTime a
src/Monomer/Widgets/Singles/ToggleButton.hs view
@@ -12,7 +12,7 @@ 'Monomer.Widgets.Singles.LabeledCheckbox', with a different visual representation. -See 'OptionButton' for detailed notes.+See 'Monomer.Widgets.Singles.OptionButton' for detailed notes. -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-}
src/Monomer/Widgets/Util/Drawing.hs view
@@ -19,18 +19,20 @@   drawInRotation,   drawInAlpha,   drawTextLine,+  drawLine,   drawRect,   drawRectBorder,   drawArc,   drawArcBorder,   drawEllipse,   drawEllipseBorder,+  drawArrowUp,   drawArrowDown,   drawTimesX,   drawStyledAction,   drawStyledAction_,   drawRoundedRect,-  drawRectRoundedBorder+  drawRoundedRectBorder ) where  import Control.Applicative ((<|>))@@ -201,7 +203,7 @@ drawRectBorder renderer rect border Nothing =   drawRectSimpleBorder renderer rect border drawRectBorder renderer rect border (Just radius) =-  drawRectRoundedBorder renderer rect border radius+  drawRoundedRectBorder renderer rect border radius  -- | Draws a filled arc, delimited by a rect and within the given angles. drawArc@@ -277,6 +279,27 @@     contentRect = subtractFromRect rect w w w w     w = width / 2 +-- | Draws a triangular arrow pointing up, delimited by the given rect.+drawArrowUp+  :: Renderer      -- ^ The renderer.+  -> Rect          -- ^ The rect delimiting the arrow.+  -> Maybe Color   -- ^ The color. If Nothing, the arrow will not be drawn.+  -> IO ()         -- ^ The resulting action.+drawArrowUp renderer rect Nothing = return ()+drawArrowUp renderer rect (Just color) = do+  beginPath renderer+  setFillColor renderer color+  moveTo renderer p1+  renderLineTo renderer p2+  renderLineTo renderer p3+  renderLineTo renderer p1+  fill renderer+  where+    Rect x y w h = rect+    p1 = Point (x + w / 2) y+    p2 = Point (x + w) (y + h)+    p3 = Point x (y + h)+ -- | Draws a triangular arrow pointing down, delimited by the given rect. drawArrowDown   :: Renderer      -- ^ The renderer.@@ -451,8 +474,8 @@     (g1, g2) = cornerGradientPoints ocorner icorner  -- | Draws the border of a rounded rect. Borders' widths may not match.-drawRectRoundedBorder :: Renderer -> Rect -> Border -> Radius -> IO ()-drawRectRoundedBorder renderer rect border radius =+drawRoundedRectBorder :: Renderer -> Rect -> Border -> Radius -> IO ()+drawRoundedRectBorder renderer rect border radius =   let     Rect xl yt w h = rect     Border borL borR borT borB = border
test/unit/Monomer/TestUtil.hs view
@@ -40,6 +40,7 @@  data InitWidget   = WInit+  | WInitKeepFirst   | WNoInit   deriving (Eq, Show) @@ -217,17 +218,37 @@   -> WidgetNode s e   -> MonomerCtx s e nodeHandleEventCtx wenv evts node = ctx where-  ctx = snd $ nodeHandleEvents wenv WInit evts node+  ctx = nodeHandleEventCtx_ wenv WInit evts node +nodeHandleEventCtx_+  :: (Eq s)+  => WidgetEnv s e+  -> InitWidget+  -> [SystemEvent]+  -> WidgetNode s e+  -> MonomerCtx s e+nodeHandleEventCtx_ wenv init evts node = ctx where+  ctx = snd $ nodeHandleEvents wenv init evts node+ nodeHandleEventModel   :: (Eq s)   => WidgetEnv s e   -> [SystemEvent]   -> WidgetNode s e   -> s-nodeHandleEventModel wenv evts node = _weModel wenv2 where-  (wenv2, _, _) = fst $ nodeHandleEvents wenv WInit evts node+nodeHandleEventModel wenv evts node = model where+  model = nodeHandleEventModel_ wenv WInit evts node +nodeHandleEventModel_+  :: (Eq s)+  => WidgetEnv s e+  -> InitWidget+  -> [SystemEvent]+  -> WidgetNode s e+  -> s+nodeHandleEventModel_ wenv init evts node = _weModel wenv2 where+  (wenv2, _, _) = fst $ nodeHandleEvents wenv init evts node+ nodeHandleEventRoot   :: (Eq s)   => WidgetEnv s e@@ -235,8 +256,18 @@   -> WidgetNode s e   -> WidgetNode s e nodeHandleEventRoot wenv evts node = newRoot where-  (_, newRoot, _) = fst $ nodeHandleEvents wenv WInit evts node+  newRoot = nodeHandleEventRoot_ wenv WInit evts node +nodeHandleEventRoot_+  :: (Eq s)+  => WidgetEnv s e+  -> InitWidget+  -> [SystemEvent]+  -> WidgetNode s e+  -> WidgetNode s e+nodeHandleEventRoot_ wenv init evts node = newRoot where+  (_, newRoot, _) = fst $ nodeHandleEvents wenv init evts node+ nodeHandleEventReqs   :: (Eq s)   => WidgetEnv s e@@ -244,17 +275,37 @@   -> WidgetNode s e   -> Seq (WidgetRequest s e) nodeHandleEventReqs wenv evts node = reqs where-  (_, _, reqs) = fst $ nodeHandleEvents wenv WInit evts node+  reqs = nodeHandleEventReqs_ wenv WInit evts node +nodeHandleEventReqs_+  :: (Eq s)+  => WidgetEnv s e+  -> InitWidget+  -> [SystemEvent]+  -> WidgetNode s e+  -> Seq (WidgetRequest s e)+nodeHandleEventReqs_ wenv init evts node = reqs where+  (_, _, reqs) = fst $ nodeHandleEvents wenv init evts node+ nodeHandleEventEvts   :: (Eq s)   => WidgetEnv s e   -> [SystemEvent]   -> WidgetNode s e   -> Seq e-nodeHandleEventEvts wenv evts node = eventsFromReqs reqs where-  (_, _, reqs) = fst $ nodeHandleEvents wenv WInit evts node+nodeHandleEventEvts wenv evts node = newEvts where+  newEvts = nodeHandleEventEvts_ wenv WInit evts node +nodeHandleEventEvts_+  :: (Eq s)+  => WidgetEnv s e+  -> InitWidget+  -> [SystemEvent]+  -> WidgetNode s e+  -> Seq e+nodeHandleEventEvts_ wenv init evts node = eventsFromReqs reqs where+  (_, _, reqs) = fst $ nodeHandleEvents wenv init evts node+ nodeHandleEvents   :: (Eq s)   => WidgetEnv s e@@ -263,9 +314,10 @@   -> WidgetNode s e   -> (HandlerStep s e, MonomerCtx s e) nodeHandleEvents wenv init evts node = result where-  steps-    | init == WInit = tail $ nodeHandleEvents_ wenv init [evts] node-    | otherwise = nodeHandleEvents_ wenv init [evts] node+  steps = case init of+    WInit -> tail $ nodeHandleEvents_ wenv init [evts] node+    WInitKeepFirst -> nodeHandleEvents_ wenv WInit [evts] node+    _ -> nodeHandleEvents_ wenv init [evts] node   result = foldl1 stepper steps   stepper step1 step2 = result where     ((_, _, reqs1), _) = step1
test/unit/Monomer/Widgets/CompositeSpec.hs view
@@ -140,17 +140,20 @@ handleEvent :: Spec handleEvent = describe "handleEvent" $ do   handleEventBasic+  handleEventNewRoot   handleEventChild   handleEventResize   handleEventLocalKey  handleEventBasic :: Spec handleEventBasic = describe "handleEventBasic" $ do-  it "should not generate an event if clicked outside" $+  it "should not generate an event if clicked outside" $ do     model [evtClick (Point 3000 3000)] ^. clicks `shouldBe` 0+    reqs [evtClick (Point 3000 3000)] `shouldBe` Seq.Empty -  it "should generate a user provided event when clicked" $+  it "should generate a user provided event when clicked" $ do     model [evtClick (Point 10 10)] ^. clicks `shouldBe` 1+    reqs [evtClick (Point 10 10)] `shouldSatisfy` (== 3) . length    where     wenv = mockWenv def@@ -166,6 +169,31 @@     buildUI wenv model = button "Click" MainBtnClicked     cmpNode = composite "main" id buildUI handleEvent     model es = nodeHandleEventModel wenv es cmpNode+    reqs es = nodeHandleEventReqs wenv es cmpNode++handleEventNewRoot :: Spec+handleEventNewRoot = describe "handleEventNewRoot" $ do+  it "should generate a resize request when the widgetType of the root widget changes" $ do+    reqs [evtClick (Point 10 10)] `shouldSatisfy` (== 4) . length+    reqs [evtClick (Point 10 10)] `shouldSatisfy` (== 1) . length . Seq.filter isResizeWidgets++  where+    wenv = mockWenv def+    handleEvent+      :: WidgetEnv MainModel MainEvt+      -> WidgetNode MainModel MainEvt+      -> MainModel+      -> MainEvt+      -> [EventResponse MainModel MainEvt MainModel MainEvt]+    handleEvent wenv node model evt = case evt of+      MainBtnClicked -> [Model (model & clicks %~ (+1))]+      _ -> []+    buildUI wenv model+      | even (model ^. clicks) = button "Click" MainBtnClicked+      | otherwise = label "Test"+    cmpNode = composite "main" id buildUI handleEvent+    model es = nodeHandleEventModel wenv es cmpNode+    reqs es = nodeHandleEventReqs wenv es cmpNode  handleEventChild :: Spec handleEventChild = describe "handleEventChild" $ do
test/unit/Monomer/Widgets/Containers/DropdownSpec.hs view
@@ -67,6 +67,7 @@   handleEvent   handleEventValue   getSizeReq+  testWidgetType  handleEvent :: Spec handleEvent = describe "handleEvent" $ do@@ -164,3 +165,13 @@     labelItem l = label_ (showt l) [resizeFactorW 0.01]     ddNode = dropdown selectedItem testItems labelItem labelItem     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv ddNode++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "dropdown-TestItem"++  where+    labelItem l = label_ (showt l) []+    node :: WidgetNode TestModel TestEvt+    node = dropdown selectedItem testItems labelItem labelItem
test/unit/Monomer/Widgets/Containers/GridSpec.hs view
@@ -24,6 +24,7 @@ import Monomer.TestUtil import Monomer.Widgets.Containers.Grid import Monomer.Widgets.Singles.Label+import Monomer.Widgets.Singles.Spacer  import qualified Monomer.Lens as L @@ -40,6 +41,7 @@   getSizeReqMixedH   getSizeReqMixedV   getSizeReqUpdater+  getSizeReqChildSpacing  getSizeReqEmpty :: Spec getSizeReqEmpty = describe "empty" $ do@@ -136,11 +138,33 @@     vgridNode = vgrid_ [sizeReqUpdater (fixedToMinW 2), sizeReqUpdater (fixedToMaxH 3)] [label "Label"]     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv vgridNode +getSizeReqChildSpacing :: Spec+getSizeReqChildSpacing = describe "with childSpacing" $ do+  it "should not add spacing when empty" $+    testGridSizeReq [] `shouldBe` fixedSize 0++  it "should not add spacing when singleton" $+    testGridSizeReq [spacer3] `shouldBe` fixedSize 3++  it "should add spacing between children" $+    testGridSizeReq [spacer3, spacer3] `shouldBe` fixedSize 8+  +  it "should not add spacing between invisible children" $ do+    testGridSizeReq [spacer3, spacer3 `nodeVisible` False] `shouldBe` fixedSize 3+  +  where+    wenv = mockWenv ()+    spacer3 = spacer_ [width 3, resizeFactor 0]+    testGridSizeReq children = sizeReqH where+      (_sizeReqW, sizeReqH) = nodeGetSizeReq wenv vgridNode+      vgridNode = vgrid_ [childSpacing_ 2] children+ resize :: Spec resize = describe "resize" $ do   resizeEmpty   resizeItemsH   resizeItemsV+  resizeChildSpacing  resizeEmpty :: Spec resizeEmpty = describe "empty" $ do@@ -205,3 +229,21 @@     newNode = nodeInit wenv gridNode     viewport = newNode ^. L.info . L.viewport     childrenVp = (^. L.info . L.viewport) <$> newNode ^. L.children++resizeChildSpacing :: Spec+resizeChildSpacing = describe "with childSpacing" $ do++  it "should add spacing between children" $+    testGridChildLocations [spacer, spacer] `shouldBe`+      Seq.fromList [Rect 0 0 640 215, Rect 0 265 640 215]+  +  it "should not add spacing between invisible children" $+    testGridChildLocations [spacer, spacer `nodeVisible` False, spacer] `shouldBe`+      Seq.fromList [Rect 0 0 640 215, Rect 0 0 0 0, Rect 0 265 640 215]+  +  where+    wenv = mockWenv ()+    testGridChildLocations children = childLocations where+      vgridNode = vgrid_ [childSpacing_ 50] children+      newNode = nodeInit wenv vgridNode+      childLocations = roundRectUnits . _wniViewport . _wnInfo <$> newNode ^. L.children
test/unit/Monomer/Widgets/Containers/ScrollSpec.hs view
@@ -13,6 +13,7 @@ import Control.Lens ((&), (^.), (^?), (^?!), (.~), _Just, ix) import Data.Default import Data.Text (Text)+import Data.Sequence (Seq(..)) import Test.Hspec  import qualified Data.Sequence as Seq@@ -38,6 +39,7 @@   | Button2   | Button3   | Button4+  | OnScrollChange ScrollStatus   deriving (Eq, Show)  spec :: Spec@@ -52,6 +54,7 @@   handleChildrenFocus   handleNestedWheel   handleMessageReset+  raiseOnChange  handleBarClick :: Spec handleBarClick = describe "handleBarClick" $ do@@ -180,6 +183,63 @@       ]) `nodeKey` "mainScroll"     cmpNode = composite "main" id buildUI handleEvent     events es = nodeHandleEventEvts wenv es cmpNode++raiseOnChange :: Spec+raiseOnChange = describe "raiseOnChange" $ do+  it "should raise the event on init" $ do+    let evts es = nodeHandleEventEvts_ wenv WInitKeepFirst es scrollNode+    let (OnScrollChange status) :<| Empty = evts []++    scrollDeltaX status `shouldBe` 0+    scrollDeltaY status `shouldBe` 0+    validateStatus status++  it "should raise the event when horizontal bar is clicked" $ do+    let (OnScrollChange status) :<| Empty = evts [evtPress midHBar]++    scrollDeltaX status `shouldBe` -648+    scrollDeltaY status `shouldBe` 0+    validateStatus status++  it "should raise the event when vertical bar is clicked" $ do+    let (OnScrollChange status) :<| Empty = evts [evtPress midVBar]++    scrollDeltaX status `shouldBe` 0+    scrollDeltaY status `shouldBe` -488+    validateStatus status++  it "should raise the event when wheel is scrolled" $ do+    let evtWheel = WheelScroll (Point 320 240) (Point 0 (-20)) WheelNormal+    let (OnScrollChange status) :<| Empty = evts [evtWheel]++    scrollDeltaX status `shouldBe` 0+    scrollDeltaY status `shouldBe` -200+    validateStatus status++  where+    validateStatus status = do+      scrollRect status `shouldBe` Rect 0 0 640 480+      scrollVpSize status `shouldBe` Size 632 472+      scrollChildSize status `shouldBe` Size 1280 960++    wenv = mockWenv ()+      & L.theme .~ darkTheme+      & L.windowSize .~ Size 640 480+    midHBar = Point 630 476+    midVBar = Point 636 470+    st = [width 640, height 480]+    stackNode = vstack [+        hstack [+          button "Button 1" Button1 `styleBasic` st,+          button "Button 2" Button2 `styleBasic` st+        ],+        hstack [+          button "Button 3" Button3 `styleBasic` st,+          button "Button 4" Button4 `styleBasic` st+        ]+      ]+    scrollNode = scroll_ [onChange OnScrollChange] stackNode+    evts es = nodeHandleEventEvts wenv es scrollNode  forwardStyle :: Spec forwardStyle = describe "forwardStyle" $ do
test/unit/Monomer/Widgets/Containers/SelectListSpec.hs view
@@ -13,7 +13,7 @@  module Monomer.Widgets.Containers.SelectListSpec (spec) where -import Control.Lens ((&), (^.), (.~), _1)+import Control.Lens ((&), (^.), (^?!), (.~), _1, ix) import Control.Lens.TH (abbreviatedFields, makeLensesWith) import Data.Default import Data.Functor ((<&>))@@ -68,6 +68,7 @@   handleEvent   handleEventValue   getSizeReq+  testWidgetType  handleEvent :: Spec handleEvent = describe "handleEvent" $ do@@ -143,3 +144,12 @@     wenv = mockWenvEvtUnit (TestModel testItem0)     slNode = selectList selectedItem testItems (label . showt)     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv slNode++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^?! L.children . ix 0 . L.info . L.widgetType `shouldBe` "selectList-TestItem"++  where+    node :: WidgetNode TestModel TestEvt+    node = selectList selectedItem testItems (label . showt)
test/unit/Monomer/Widgets/Containers/StackSpec.hs view
@@ -39,6 +39,7 @@   getSizeReqEmpty   getSizeReqItems   getSizeReqUpdater+  getSizeReqChildSpacing  getSizeReqEmpty :: Spec getSizeReqEmpty = describe "empty" $ do@@ -92,6 +93,27 @@     (sizeReqW1, sizeReqH1) = nodeGetSizeReq wenv vstackNode1     (sizeReqW2, sizeReqH2) = nodeGetSizeReq wenv vstackNode2 +getSizeReqChildSpacing :: Spec+getSizeReqChildSpacing = describe "with childSpacing" $ do+  it "should not add spacing when empty" $+    testStackSizeReq [] `shouldBe` fixedSize 0++  it "should not add spacing when singleton" $+    testStackSizeReq [spacer3] `shouldBe` fixedSize 3++  it "should add spacing between children" $+    testStackSizeReq [spacer3, spacer3] `shouldBe` fixedSize 8+  +  it "should not add spacing between invisible children" $ do+    testStackSizeReq [spacer3, spacer3 `nodeVisible` False] `shouldBe` fixedSize 3+  +  where+    wenv = mockWenv ()+    spacer3 = spacer_ [width 3, resizeFactor 0]+    testStackSizeReq children = sizeReqH where+      (_sizeReqW, sizeReqH) = nodeGetSizeReq wenv vstackNode+      vstackNode = vstack_ [childSpacing_ 2] children+ resize :: Spec resize = describe "resize" $ do   resizeEmpty@@ -105,6 +127,7 @@   resizeNoSpaceV   resizeSpacerFlexH   resizeSpacerFixedH+  resizeChildSpacing  resizeEmpty :: Spec resizeEmpty = describe "empty" $ do@@ -365,3 +388,22 @@     newNode = nodeInit wenv hstackNode     viewport = newNode ^. L.info . L.viewport     childrenVp = roundRectUnits . _wniViewport . _wnInfo <$> newNode ^. L.children++resizeChildSpacing :: Spec+resizeChildSpacing = describe "with childSpacing" $ do++  it "should add spacing between children" $+    testStackChildLocations [spacer3, spacer3] `shouldBe`+      Seq.fromList [Rect 0 0 640 3, Rect 0 5 640 3]+  +  it "should not add spacing between invisible children" $+    testStackChildLocations [spacer3, spacer3 `nodeVisible` False, spacer3] `shouldBe`+      Seq.fromList [Rect 0 0 640 3, Rect 0 0 0 0, Rect 0 5 640 3]+  +  where+    wenv = mockWenv ()+    spacer3 = spacer_ [width 3, resizeFactor 0]+    testStackChildLocations children = childLocations where+      vstackNode = vstack_ [childSpacing_ 2] children+      newNode = nodeInit wenv vstackNode+      childLocations = roundRectUnits . _wniViewport . _wnInfo <$> newNode ^. L.children
test/unit/Monomer/Widgets/Singles/DateFieldSpec.hs view
@@ -74,6 +74,7 @@   handleEventMouseDragDate   handleShiftFocus   getSizeReqDate+  testWidgetType  handleEventDate :: Spec handleEventDate = describe "handleEventDate" $ do@@ -282,3 +283,12 @@     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (dateField dateValue)     dateResize = dateField_ dateValue [resizeOnChange]     (sizeReqW2, sizeReqH2) = nodeGetSizeReq wenv dateResize++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "dateField-Day"++  where+    node :: WidgetNode DateModel TestEvt+    node = dateField dateValue
test/unit/Monomer/Widgets/Singles/DialSpec.hs view
@@ -12,6 +12,7 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}  module Monomer.Widgets.Singles.DialSpec (spec) where @@ -56,6 +57,7 @@   handleWheelVal   handleShiftFocus   getSizeReq+  testWidgetType  handleKeyboard :: Spec handleKeyboard = describe "handleKeyboard" $ do@@ -235,3 +237,12 @@     wenv = mockWenvEvtUnit (TestModel 0)       & L.theme .~ darkTheme     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (dial dialVal 0 100)++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "dial-Double"++  where+    node :: WidgetNode TestModel TestEvt+    node = dial dialVal 0 100
test/unit/Monomer/Widgets/Singles/LabeledRadioSpec.hs view
@@ -56,6 +56,7 @@   handleEvent   handleEventValue   getSizeReq+  testWidgetType  handleEvent :: Spec handleEvent = describe "handleEvent" $ do@@ -127,3 +128,12 @@     wenv = mockWenvEvtUnit (TestModel Apple)       & L.theme .~ darkTheme     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (labeledRadio "Test" Apple fruit)++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "labeledRadio-Fruit"++  where+    node :: WidgetNode TestModel FruitEvt+    node = labeledRadio "Test" Apple fruit
test/unit/Monomer/Widgets/Singles/NumericFieldSpec.hs view
@@ -66,6 +66,7 @@   handleEventValueIntegral   handleEventMouseDragIntegral   getSizeReqIntegral+  testIntegralWidgetType  handleEventIntegral :: Spec handleEventIntegral = describe "handleEventIntegral" $ do@@ -259,6 +260,15 @@     numericResize = numericField_ integralValue [resizeOnChange]     (sizeReqW2, sizeReqH2) = nodeGetSizeReq wenv numericResize +testIntegralWidgetType :: Spec+testIntegralWidgetType = describe "testIntegralWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "numericField-Int"++  where+    node :: WidgetNode IntegralModel TestEvt+    node = numericField integralValue+ -- ------------------------------ -- Fractional field -- ------------------------------@@ -269,6 +279,7 @@   handleEventMouseDragFractional   handleShiftFocusFractional   getSizeReqFractional+  testFractionalWidgetType  handleEventFractional :: Spec handleEventFractional = describe "handleEventFractional" $ do@@ -484,3 +495,12 @@     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (numericField fractionalValue)     numericResize = numericField_ fractionalValue [resizeOnChange]     (sizeReqW2, sizeReqH2) = nodeGetSizeReq wenv numericResize++testFractionalWidgetType :: Spec+testFractionalWidgetType = describe "testFractionalWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "numericField-Maybe Double"++  where+    node :: WidgetNode FractionalModel TestEvt+    node = numericField fractionalValue
test/unit/Monomer/Widgets/Singles/OptionButtonSpec.hs view
@@ -58,6 +58,7 @@   handleEvent   handleEventValue   getSizeReq+  testWidgetType  handleEvent :: Spec handleEvent = describe "handleEvent" $ do@@ -114,3 +115,12 @@   where     wenv = mockWenvEvtUnit (TestModel Apple)     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (optionButton "Apple" Apple fruit)++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "optionButton-Fruit"++  where+    node :: WidgetNode TestModel FruitEvt+    node = optionButton "Test" Apple fruit
test/unit/Monomer/Widgets/Singles/RadioSpec.hs view
@@ -56,6 +56,7 @@   handleEvent   handleEventValue   getSizeReq+  testWidgetType  handleEvent :: Spec handleEvent = describe "handleEvent" $ do@@ -115,3 +116,12 @@     wenv = mockWenvEvtUnit (TestModel Apple)       & L.theme .~ darkTheme     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (radio Apple fruit)++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "radio-Fruit"++  where+    node :: WidgetNode TestModel FruitEvt+    node = radio Apple fruit
test/unit/Monomer/Widgets/Singles/SliderSpec.hs view
@@ -62,6 +62,7 @@   getSizeReqH   getSizeReqV   getSizeReqThumb+  testWidgetType  handleKeyboardH :: Spec handleKeyboardH = describe "handleKeyboardH" $ do@@ -405,3 +406,12 @@     wenv = mockWenvEvtUnit (TestModel 0)       & L.theme .~ darkTheme     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (vslider_ sliderVal 0 100 [thumbVisible])++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "slider-Double"++  where+    node :: WidgetNode TestModel TestEvt+    node = hslider sliderVal 0 100
test/unit/Monomer/Widgets/Singles/TimeFieldSpec.hs view
@@ -74,6 +74,7 @@   handleEventMouseDragTime   handleShiftFocus   getSizeReqTime+  testWidgetType  handleEventTime :: Spec handleEventTime = describe "handleEventTime" $ do@@ -291,3 +292,12 @@     (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (timeField timeValue)     timeResize = timeField_ timeValue [resizeOnChange]     (sizeReqW2, sizeReqH2) = nodeGetSizeReq wenv timeResize++testWidgetType :: Spec+testWidgetType = describe "testWidgetType" $ do+  it "should set the correct widgetType" $+    node ^. L.info . L.widgetType `shouldBe` "timeField-TimeOfDay"++  where+    node :: WidgetNode TimeModel TestEvt+    node = timeField timeValue