monomer 1.1.0.0 → 1.1.1.0
raw patch · 10 files changed
+189/−73 lines, 10 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Monomer.Event.Core: ConvertEventsCfg :: Text -> Double -> Double -> Bool -> Bool -> ConvertEventsCfg
+ Monomer.Event.Core: [_cecDpr] :: ConvertEventsCfg -> Double
+ Monomer.Event.Core: [_cecEpr] :: ConvertEventsCfg -> Double
+ Monomer.Event.Core: [_cecInvertWheelX] :: ConvertEventsCfg -> Bool
+ Monomer.Event.Core: [_cecInvertWheelY] :: ConvertEventsCfg -> Bool
+ Monomer.Event.Core: [_cecOs] :: ConvertEventsCfg -> Text
+ Monomer.Event.Core: data ConvertEventsCfg
+ Monomer.Event.Core: instance GHC.Classes.Eq Monomer.Event.Core.ConvertEventsCfg
+ Monomer.Event.Core: instance GHC.Show.Show Monomer.Event.Core.ConvertEventsCfg
+ Monomer.Main.Lens: class HasInvertWheelX s a | s -> a
+ Monomer.Main.Lens: class HasInvertWheelY s a | s -> a
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasInvertWheelX (Monomer.Main.Types.AppConfig e) (GHC.Maybe.Maybe GHC.Types.Bool)
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasInvertWheelY (Monomer.Main.Types.AppConfig e) (GHC.Maybe.Maybe GHC.Types.Bool)
+ Monomer.Main.Lens: invertWheelX :: HasInvertWheelX s a => Lens' s a
+ Monomer.Main.Lens: invertWheelY :: HasInvertWheelY s a => Lens' s a
+ Monomer.Main.Types: [_apcInvertWheelX] :: AppConfig e -> Maybe Bool
+ Monomer.Main.Types: [_apcInvertWheelY] :: AppConfig e -> Maybe Bool
+ Monomer.Main.Types: appInvertWheelX :: Bool -> AppConfig e
+ Monomer.Main.Types: appInvertWheelY :: Bool -> AppConfig e
- Monomer.Core.Lens: _WidgetLens :: forall s_a1yEA a_a1nAe s_a1nAd. Prism (WidgetData s_a1yEA a_a1nAe) (WidgetData s_a1nAd a_a1nAe) (ALens' s_a1yEA a_a1nAe) (ALens' s_a1nAd a_a1nAe)
+ Monomer.Core.Lens: _WidgetLens :: forall s_a1zoa a_a1ojO s_a1ojN. Prism (WidgetData s_a1zoa a_a1ojO) (WidgetData s_a1ojN a_a1ojO) (ALens' s_a1zoa a_a1ojO) (ALens' s_a1ojN a_a1ojO)
- Monomer.Core.Lens: _WidgetValue :: forall s_a1nAd a_a1nAe. Prism' (WidgetData s_a1nAd a_a1nAe) a_a1nAe
+ Monomer.Core.Lens: _WidgetValue :: forall s_a1ojN a_a1ojO. Prism' (WidgetData s_a1ojN a_a1ojO) a_a1ojO
- Monomer.Event.Core: convertEvents :: Double -> Double -> Point -> [EventPayload] -> [SystemEvent]
+ Monomer.Event.Core: convertEvents :: ConvertEventsCfg -> Point -> [EventPayload] -> [SystemEvent]
- Monomer.Main.Types: AppConfig :: Maybe MainWindowState -> Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Int -> Maybe Double -> [FontDef] -> Maybe Theme -> [e] -> [e] -> [e] -> [Rect -> e] -> Maybe Button -> Maybe Button -> AppConfig e
+ Monomer.Main.Types: AppConfig :: Maybe MainWindowState -> Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Int -> Maybe Double -> [FontDef] -> Maybe Theme -> [e] -> [e] -> [e] -> [Rect -> e] -> Maybe Button -> Maybe Button -> Maybe Bool -> Maybe Bool -> AppConfig e
Files
- ChangeLog.md +44/−15
- examples/todo/Main.hs +14/−14
- monomer.cabal +1/−1
- src/Monomer/Event/Core.hs +26/−9
- src/Monomer/Main/Core.hs +4/−1
- src/Monomer/Main/Types.hs +29/−3
- src/Monomer/Widgets/Composite.hs +24/−19
- src/Monomer/Widgets/Container.hs +2/−2
- src/Monomer/Widgets/Containers/Scroll.hs +2/−6
- test/unit/Monomer/Widgets/CompositeSpec.hs +43/−3
ChangeLog.md view
@@ -1,33 +1,62 @@-### 1.1.0.0+## 1.1.1.0 +### Added++- `appInvertWheelX` and `appInvertWheelY` configuration options.++### Fixed++- Horizontal wheel/trackpad scrolling on Linux.+- Scroll: do not use direction argument to modify wheel/trackpad direction (event provides correct value).+- Only replace composite model with user model on init and merge.++## 1.1.0.0++### Added++- `sizeUpdater` helpers. Support multiple handlers in box, grid and stack.+- `dpr` field to `WidgetEnv`.+- `RunInRenderThread` to support initialization of low level OpenGL resources.+- OpenGL example.+- `containerDrawDecorations`. Simplify button/externalLink internals.+- `ThemeState` entries for future `optionButton` and `toggleButton` widgets.+- `singleDrawDecorations`. Make it consistent with Container.++### Fixed+ - Reduce memory usage by sharing wreq session among image widget instances.-- Add sizeUpdater helpers. Support multiple handlers in box, grid and stack.-- Add `dpr` field to `WidgetEnv`.-- Add `RunInRenderThread` to support initialization of low level OpenGL resources. - Set correct `WidgetEnv` viewport from scroll (it looked good because of scissoring). - Fix issue with scrollbars using child coordinates for detecting clicks.-- Add OpenGL example.-- Add `containerDrawDecorations`. Simplify button/externalLink internals.-- Add ThemeState entries for optionButton and toggleButton widgets.-- Add `singleDrawDecorations`. Make it consistent with Container. -### 1.0.0.3+## 1.0.0.3 -- Consume and forward all available messages from Producers on each cycle.+### Added+ - Add Nix and GitHub Actions support (thanks @smunix!).++### Fixed++- Consume and forward all available messages from Producers on each cycle. - Fix space leak when rebuilding the UI or handling events. -### 1.0.0.2+## 1.0.0.2 -- Use the recently published nanovg-0.8.0.0 from Hackage, instead of the version from the PR's commit.+### Added+ - Add `appRenderOnMainThread` option. -### 1.0.0.1+### Changed +- Use the recently published nanovg-0.8.0.0 from Hackage, instead of the version from the PR's commit.++## 1.0.0.1++### Fixed+ - Fix Haddocks for widget configuration types. -### 1.0.0.0+## 1.0.0.0 -Initial public release+### Initial public release - Supports Windows, Linux and macOS
examples/todo/Main.hs view
@@ -73,13 +73,15 @@ todoEdit :: TodoWenv -> TodoModel -> TodoNode todoEdit wenv model = editNode where sectionBg = wenv ^. L.theme . L.sectionColor+ isValidInput = model ^. activeTodo . description /= "" - saveTodoBtn = case model ^. action of- TodoAdding -> mainButton "Add" TodoAdd- TodoEditing idx -> mainButton "Save" (TodoSave idx)- _ -> spacer+ (saveAction, saveLabel) = case model ^. action of+ TodoEditing idx -> (TodoSave idx, "Save")+ _ -> (TodoAdd, "Add") - editNode = vstack [+ saveTodoBtn = mainButton saveLabel saveAction++ editFields = keystroke [("Enter", saveAction) | isValidInput] $ vstack [ hstack [ label "Task:", spacer,@@ -98,11 +100,15 @@ spacer, textDropdownS (activeTodo . status) todoStatuses ]- ],+ ]+ ]++ editNode = keystroke [("Esc", TodoCancel)] $ vstack [+ editFields, spacer, hstack [ filler,- saveTodoBtn `nodeEnabled` (model ^. activeTodo . description /= ""),+ saveTodoBtn `nodeEnabled` isValidInput, spacer, button "Cancel" TodoCancel ]@@ -123,10 +129,6 @@ `nodeVisible` not isEditing editLayer = content where- saveAction = case model ^. action of- TodoEditing idx -> TodoSave idx- _ -> TodoAdd- dualSlide content = outer where inner = animSlideIn_ [slideTop, duration 200] content `nodeKey` "animEditIn"@@ -134,9 +136,7 @@ `nodeKey` "animEditOut" content = vstack [- dualSlide $- keystroke [("Enter", saveAction), ("Esc", TodoCancel)] $- todoEdit wenv model,+ dualSlide (todoEdit wenv model), filler ] `styleBasic` [bgColor (grayDark & L.a .~ 0.5)]
monomer.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: monomer-version: 1.1.0.0+version: 1.1.1.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.
src/Monomer/Event/Core.hs view
@@ -9,6 +9,7 @@ Core functions for SDL event processing and conversion. -} module Monomer.Event.Core (+ ConvertEventsCfg(..), isActionEvent, convertEvents, translateEvent@@ -16,6 +17,7 @@ import Control.Applicative ((<|>)) import Data.Maybe (catMaybes, fromMaybe)+import Data.Text (Text) import qualified Data.Map.Strict as M import qualified SDL@@ -37,19 +39,27 @@ isActionEvent SDL.TextInputEvent{} = True isActionEvent _ = False +data ConvertEventsCfg = ConvertEventsCfg {+ _cecOs :: Text, -- ^ The host operating system.+ _cecDpr :: Double, -- ^ Device pixel rate.+ _cecEpr :: Double, -- ^ Event pixel rate.+ _cecInvertWheelX :: Bool, -- ^ Whether wheel/trackpad x direction should be inverted.+ _cecInvertWheelY :: Bool -- ^ Whether wheel/trackpad y direction should be inverted.+} deriving (Eq, Show)+ -- | Converts SDL events to Monomer's SystemEvent convertEvents- :: Double -- ^ Device pixel rate.- -> Double -- ^ Event pixel rate.+ :: ConvertEventsCfg -- ^ Settings for event conversion. -> Point -- ^ Mouse position. -> [SDL.EventPayload] -- ^ List of SDL events. -> [SystemEvent] -- ^ List of Monomer events.-convertEvents dpr epr mousePos events = catMaybes convertedEvents where+convertEvents cfg mousePos events = catMaybes convertedEvents where+ ConvertEventsCfg os dpr epr invertX invertY = cfg convertedEvents = fmap convertEvent events convertEvent evt = mouseMoveEvent mousePos evt <|> mouseClick mousePos evt- <|> mouseWheelEvent epr mousePos evt+ <|> mouseWheelEvent cfg mousePos evt <|> mouseMoveLeave mousePos evt <|> keyboardEvent evt <|> textEvent evt@@ -94,18 +104,25 @@ evt = Just $ Move (Point (-1) (-1)) mouseMoveLeave mousePos _ = Nothing -mouseWheelEvent :: Double -> Point -> SDL.EventPayload -> Maybe SystemEvent-mouseWheelEvent epr mousePos (SDL.MouseWheelEvent eventData) = systemEvent where+mouseWheelEvent :: ConvertEventsCfg -> Point -> SDL.EventPayload -> Maybe SystemEvent+mouseWheelEvent cfg pos (SDL.MouseWheelEvent eventData) = systemEvent where+ ConvertEventsCfg os dpr epr invertX invertY = cfg+ signX = if invertX then -1 else 1+ signY = if invertY then -1 else 1+ factorX+ | os == "Windows" || os == "Mac OS X" = -signX+ | otherwise = signX+ factorY = signY wheelDirection = case SDL.mouseWheelEventDirection eventData of SDL.ScrollNormal -> WheelNormal SDL.ScrollFlipped -> WheelFlipped SDL.V2 x y = SDL.mouseWheelEventPos eventData- wheelDelta = Point (fromIntegral x * epr) (fromIntegral y * epr)+ wheelDelta = Point (factorX * fromIntegral x * epr) (factorY * fromIntegral y * epr) systemEvent = case SDL.mouseWheelEventWhich eventData of- SDL.Mouse _ -> Just $ WheelScroll mousePos wheelDelta wheelDirection+ SDL.Mouse _ -> Just $ WheelScroll pos wheelDelta wheelDirection SDL.Touch -> Nothing-mouseWheelEvent epr mousePos _ = Nothing+mouseWheelEvent cfg mousePos _ = Nothing keyboardEvent :: SDL.EventPayload -> Maybe SystemEvent keyboardEvent (SDL.KeyboardEvent eventData) = Just keyAction where
src/Monomer/Main/Core.hs view
@@ -244,7 +244,10 @@ let windowResized = currWinSize /= windowSize && isWindowResized eventsPayload let windowExposed = isWindowExposed eventsPayload let mouseEntered = isMouseEntered eventsPayload- let baseSystemEvents = convertEvents dpr epr mousePos eventsPayload+ let invertX = fromMaybe False (_apcInvertWheelX config)+ let invertY = fromMaybe False (_apcInvertWheelY config)+ let convertCfg = ConvertEventsCfg _mlOS dpr epr invertX invertY+ let baseSystemEvents = convertEvents convertCfg mousePos eventsPayload -- when newSecond $ -- liftIO . putStrLn $ "Frames: " ++ show _mlFrameCount
src/Monomer/Main/Types.hs view
@@ -185,7 +185,11 @@ -- | Defines which mouse button is considered main. _apcMainButton :: Maybe Button, -- | Defines which mouse button is considered secondary or context button.- _apcContextButton :: Maybe Button+ _apcContextButton :: Maybe Button,+ -- | Whether wheel/trackpad horizontal movement should be inverted.+ _apcInvertWheelX :: Maybe Bool,+ -- | Whether wheel/trackpad vertical movement should be inverted.+ _apcInvertWheelY :: Maybe Bool } instance Default (AppConfig e) where@@ -204,7 +208,9 @@ _apcExitEvent = [], _apcResizeEvent = [], _apcMainButton = Nothing,- _apcContextButton = Nothing+ _apcContextButton = Nothing,+ _apcInvertWheelX = Nothing,+ _apcInvertWheelY = Nothing } instance Semigroup (AppConfig e) where@@ -223,7 +229,9 @@ _apcExitEvent = _apcExitEvent a1 ++ _apcExitEvent a2, _apcResizeEvent = _apcResizeEvent a1 ++ _apcResizeEvent a2, _apcMainButton = _apcMainButton a2 <|> _apcMainButton a1,- _apcContextButton = _apcContextButton a2 <|> _apcContextButton a1+ _apcContextButton = _apcContextButton a2 <|> _apcContextButton a1,+ _apcInvertWheelX = _apcInvertWheelX a2 <|> _apcInvertWheelX a1,+ _apcInvertWheelY = _apcInvertWheelY a2 <|> _apcInvertWheelY a1 } instance Monoid (AppConfig e) where@@ -337,4 +345,22 @@ appContextButton :: Button -> AppConfig e appContextButton btn = def { _apcContextButton = Just btn+}++{-|+Whether the horizontal wheel/trackpad movement should be inverted. In general+platform detection should do the right thing.+-}+appInvertWheelX :: Bool -> AppConfig e+appInvertWheelX invert = def {+ _apcInvertWheelX = Just invert+}++{-|+Whether the vertical wheel/trackpad movement should be inverted. In general+platform detection should do the right thing.+-}+appInvertWheelY :: Bool -> AppConfig e+appInvertWheelY invert = def {+ _apcInvertWheelY = Just invert }
src/Monomer/Widgets/Composite.hs view
@@ -416,7 +416,7 @@ -> WidgetResult sp ep compositeInit comp state wenv widgetComp = newResult where CompositeState{..} = state- !model = getModel comp wenv+ !model = getUserModel comp wenv !cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model -- Creates UI using provided function !builtRoot = _cmpUiBuilder comp cwenv model@@ -449,7 +449,7 @@ oldState = widgetGetState (oldComp ^. L.widget) wenv oldComp validState = fromMaybe state (useState oldState) CompositeState oldModel oldRoot oldWidgetKeys = validState- model = getModel comp wenv+ model = getUserModel comp wenv -- Creates new UI using provided function cwenv = convertWidgetEnv wenv oldWidgetKeys model tempRoot = cascadeCtx wenv newComp (_cmpUiBuilder comp cwenv model)@@ -504,7 +504,8 @@ -> WidgetResult sp ep compositeDispose comp state wenv widgetComp = result where CompositeState{..} = state- model = getModel comp wenv++ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model widget = _cpsRoot ^. L.widget newEvts = RaiseEvent <$> Seq.fromList (_cmpOnDispose comp)@@ -523,7 +524,7 @@ compositeGetInstanceTree comp state wenv node = instTree where CompositeState{..} = state widget = _cpsRoot ^. L.widget- model = getModel comp wenv+ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model cInstTree = widgetGetInstanceTree widget cwenv _cpsRoot instTree = WidgetInstanceNode {@@ -545,7 +546,7 @@ compositeFindNextFocus comp state wenv widgetComp dir start = nextFocus where CompositeState{..} = state widget = _cpsRoot ^. L.widget- model = getModel comp wenv+ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model nextFocus = widgetFindNextFocus widget cwenv _cpsRoot dir start @@ -565,7 +566,7 @@ where CompositeState{..} = state widget = _cpsRoot ^. L.widget- model = getModel comp wenv+ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model next = nextTargetStep widgetComp start validStep = isNothing next || next == Just 0@@ -585,7 +586,7 @@ | otherwise = Seq.empty where CompositeState{..} = state- model = getModel comp wenv+ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model info = widgetComp ^. L.info nextStep = nextTargetStep widgetComp path@@ -605,7 +606,7 @@ compositeHandleEvent comp state wenv widgetComp target evt = result where CompositeState{..} = state widget = _cpsRoot ^. L.widget- !model = getModel comp wenv+ !model = getCompositeModel state !cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model rootEnabled = _cpsRoot ^. L.info . L.enabled compVisible = widgetComp ^. L.info . L.visible@@ -637,7 +638,7 @@ | otherwise = fmap processEvent result where processEvent = toParentResult comp state wenv widgetComp cmpWidget = _cpsRoot ^. L.widget- !model = getModel comp wenv+ !model = getCompositeModel state !cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model result = widgetHandleMessage cmpWidget cwenv _cpsRoot target arg @@ -689,7 +690,7 @@ style = currentStyle wenv widgetComp carea = fromMaybe def (removeOuterBounds style viewport) widget = _cpsRoot ^. L.widget- model = getModel comp wenv+ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model WidgetResult newRoot newReqs = widgetResize widget cwenv _cpsRoot carea rszReq@@ -717,7 +718,7 @@ compositeRender comp state wenv widgetComp renderer = action where CompositeState{..} = state widget = _cpsRoot ^. L.widget- !model = getModel comp wenv+ !model = getCompositeModel state !cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model !action = widgetRender widget cwenv _cpsRoot renderer @@ -731,10 +732,8 @@ -> WidgetResult sp ep handleMsgEvent comp state wenv widgetComp event = newResult where CompositeState{..} = state- model- | isJust _cpsModel = fromJust _cpsModel- | otherwise = getModel comp wenv evtHandler = _cmpEventHandler comp+ !model = getCompositeModel state !cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model !response = evtHandler cwenv _cpsRoot model event !newReqs = evtResponseToRequest widgetComp _cpsWidgetKeyMap <$> response@@ -750,9 +749,7 @@ -> WidgetResult sp ep handleMsgUpdate comp state wenv widgetComp fnUpdate = result where CompositeState{..} = state- model- | isJust _cpsModel = fromJust _cpsModel- | otherwise = getModel comp wenv+ !model = getCompositeModel state !newModel = fnUpdate model !result | model == newModel = resultNode widgetComp@@ -825,12 +822,20 @@ !newResult = result & L.requests <>~ Seq.fromList newReqs -getModel+getUserModel :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => Composite s e sp ep -> WidgetEnv sp ep -> s-getModel comp wenv = widgetDataGet (_weModel wenv) (_cmpWidgetData comp)+getUserModel comp wenv = widgetDataGet (_weModel wenv) (_cmpWidgetData comp)++getCompositeModel+ :: (CompositeModel s, CompositeEvent e)+ => CompositeState s e+ -> s+getCompositeModel state = case _cpsModel state of+ Just model -> model+ _ -> error "Error calling getCompositeModel: widgetInit has not been invoked." toParentReq :: (CompositeModel s, CompParentModel sp)
src/Monomer/Widgets/Container.hs view
@@ -578,7 +578,7 @@ oldState = widgetGetState (oldNode ^. L.widget) wenv oldNode mergeRequired = case useState oldState of- Just state -> mergeRequiredHandler wenv newNode oldNode state+ Just ostate -> mergeRequiredHandler wenv newNode oldNode ostate Nothing -> True styledNode = initNodeStyle getBaseStyle wenv newNode@@ -623,7 +623,7 @@ & L.info . L.sizeReqW .~ oldInfo ^. L.sizeReqW & L.info . L.sizeReqH .~ oldInfo ^. L.sizeReqH result = case useState oldState of- Just state -> mergeHandler wenv tempNode oldNode state+ Just ostate -> mergeHandler wenv tempNode oldNode ostate Nothing -> resultNode tempNode mergeChildren
src/Monomer/Widgets/Containers/Scroll.hs view
@@ -510,12 +510,8 @@ result | needsUpdate = Just $ makeResult newState | otherwise = Nothing- stepX- | wheelDirection == WheelNormal = -wheelRate * wx- | otherwise = wheelRate * wx- stepY- | wheelDirection == WheelNormal = wheelRate * wy- | otherwise = -wheelRate * wy+ stepX = wheelRate * wx+ stepY = wheelRate * wy newState = state { _sstDeltaX = scrollAxisH (stepX + dx), _sstDeltaY = scrollAxisV (stepY + dy)
test/unit/Monomer/Widgets/CompositeSpec.hs view
@@ -40,6 +40,7 @@ import Monomer.Widgets.Containers.Stack import Monomer.Widgets.Containers.ZStack import Monomer.Widgets.Singles.Button+import Monomer.Widgets.Singles.Checkbox import Monomer.Widgets.Singles.Label import Monomer.Widgets.Singles.TextField import Monomer.Widgets.Util.Widget@@ -59,6 +60,10 @@ | ChildResize Rect deriving (Eq, Show) +data DeepEvt+ = DeepInit+ deriving (Eq, Show)+ data MainModel = MainModel { _tmClicks :: Int, _tmChild :: ChildModel@@ -72,15 +77,26 @@ data ChildModel = ChildModel { _cmClicks :: Int,- _cmMessage :: String+ _cmMessage :: String,+ _cmDeep :: DeepModel } deriving (Eq, Show) instance Default ChildModel where def = ChildModel { _cmClicks = 0,- _cmMessage = ""+ _cmMessage = "",+ _cmDeep = def } +newtype DeepModel = DeepModel {+ _dmClicked :: Bool+} deriving (Eq, Show)++instance Default DeepModel where+ def = DeepModel {+ _dmClicked = False+ }+ data TestModel = TestModel { _tmItems :: [Int], _tmText1 :: Text,@@ -104,6 +120,7 @@ makeLensesWith abbreviatedFields ''MainModel makeLensesWith abbreviatedFields ''ChildModel+makeLensesWith abbreviatedFields ''DeepModel makeLensesWith abbreviatedFields ''TestModel baseLens idx = L.children . ix 0 . L.children . ix idx . L.children . ix 0@@ -155,17 +172,35 @@ it "should not generate an event if clicked outside" $ do model [evtClick (Point 3000 3000)] ^. clicks `shouldBe` 0 model [evtClick (Point 3000 3000)] ^. child . clicks `shouldBe` 0+ model [evtClick (Point 3000 3000)] ^. child . deep . clicked `shouldBe` False it "should generate a main event when clicked in main button" $ do model [evtClick (Point 10 10)] ^. clicks `shouldBe` 1 model [evtClick (Point 10 10)] ^. child . clicks `shouldBe` 0+ model [evtClick (Point 10 10)] ^. child . deep . clicked `shouldBe` False it "should generate a child event when clicked in child button" $ do model [evtClick (Point 10 30)] ^. clicks `shouldBe` 0 model [evtClick (Point 10 30)] ^. child . clicks `shouldBe` 1+ model [evtClick (Point 10 30)] ^. child . deep . clicked `shouldBe` False + it "should update the nested model when the checkbox is clicked" $ do+ model [evtClick (Point 10 50)] ^. clicks `shouldBe` 0+ model [evtClick (Point 10 50)] ^. child . clicks `shouldBe` 0+ model [evtClick (Point 10 50)] ^. child . deep . clicked `shouldBe` True+ where wenv = mockWenv def++ handleDeep+ :: WidgetEnv DeepModel DeepEvt+ -> WidgetNode DeepModel DeepEvt+ -> DeepModel+ -> DeepEvt+ -> [EventResponse DeepModel DeepEvt ChildModel ChildEvt]+ handleDeep wenv node model evt = []+ buildDeep wenv model = checkbox clicked+ handleChild :: WidgetEnv ChildModel ChildEvt -> WidgetNode ChildModel ChildEvt@@ -173,7 +208,11 @@ -> ChildEvt -> [EventResponse ChildModel ChildEvt MainModel MainEvt] handleChild wenv node model evt = [Model (model & clicks %~ (+1))]- buildChild wenv model = button "Click" ChildBtnClicked+ buildChild wenv model = vstack [+ button "Click" ChildBtnClicked,+ composite "deep" deep buildDeep handleDeep+ ]+ handleEvent :: WidgetEnv MainModel MainEvt -> WidgetNode MainModel MainEvt@@ -185,6 +224,7 @@ button "Click" MainBtnClicked, composite "child" child buildChild handleChild ]+ cmpNode = composite "main" id buildUI handleEvent model es = nodeHandleEventModel wenv es cmpNode