monomer 1.3.0.0 → 1.4.0.0
raw patch · 107 files changed
+2694/−784 lines, 107 filesdep −HUnitdep −OpenGLdep −directoryPVP ok
version bump matches the API change (PVP)
Dependencies removed: HUnit, OpenGL, directory, safe, scientific, silently, unordered-containers
API changes (from Hackage documentation)
- Monomer.Core.Lens: instance Monomer.Core.Lens.HasTimestamp (Monomer.Core.WidgetTypes.WidgetEnv s e) Monomer.Core.WidgetTypes.Timestamp
- Monomer.Core.Lens: instance Monomer.Core.Lens.HasTs Monomer.Core.WidgetTypes.WidgetId GHC.Types.Int
- Monomer.Core.WidgetTypes: type Timestamp = Int
- Monomer.Main.Lens: class HasRenderChannel s a | s -> a
- Monomer.Main.Lens: instance Monomer.Main.Lens.HasMs Monomer.Main.Types.RenderSchedule GHC.Types.Int
- Monomer.Main.Lens: instance Monomer.Main.Lens.HasRenderChannel (Monomer.Main.Types.MonomerCtx s e) (Control.Concurrent.STM.TChan.TChan (Monomer.Main.Types.RenderMsg s e))
- Monomer.Main.Lens: instance Monomer.Main.Lens.HasStart Monomer.Main.Types.RenderSchedule GHC.Types.Int
- Monomer.Main.Lens: renderChannel :: HasRenderChannel s a => Lens' s a
- Monomer.Main.Types: [_mcRenderChannel] :: MonomerCtx s e -> TChan (RenderMsg s e)
- Monomer.Widgets.Animation.Fade: instance Monomer.Core.Combinators.CmbDuration (Monomer.Widgets.Animation.Fade.FadeCfg e) GHC.Types.Int
- Monomer.Widgets.Animation.Slide: instance Monomer.Core.Combinators.CmbDuration (Monomer.Widgets.Animation.Slide.SlideCfg e) GHC.Types.Int
- Monomer.Widgets.Composite: customModelBuilder :: CompositeCustomModelBuilder s sp -> CompositeCfg s e sp ep
- Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbMergeRequired (Monomer.Widgets.Composite.CompositeCfg s e sp ep) s
- Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbOnChangeReq (Monomer.Widgets.Composite.CompositeCfg s e sp ep) sp ep s
- Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbOnDispose (Monomer.Widgets.Composite.CompositeCfg s e sp ep) e
- Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbOnInit (Monomer.Widgets.Composite.CompositeCfg s e sp ep) e
- Monomer.Widgets.Composite: instance Monomer.Core.WidgetTypes.WidgetEvent ep => Monomer.Core.Combinators.CmbOnChange (Monomer.Widgets.Composite.CompositeCfg s e sp ep) s ep
- Monomer.Widgets.Composite: type CompositeCustomModelBuilder s sp = sp " Parent model." -> s " Old custom composite model." -> s " New custom composite model." -> s " Custom composite model."
- Monomer.Widgets.Containers.Box: instance Monomer.Core.Combinators.CmbMergeRequired (Monomer.Widgets.Containers.Box.BoxCfg s e) s
- Monomer.Widgets.Containers.Confirm: instance GHC.Classes.Eq e => GHC.Classes.Eq (Monomer.Widgets.Containers.Confirm.ConfirmEvt e)
- Monomer.Widgets.Containers.Confirm: instance GHC.Show.Show e => GHC.Show.Show (Monomer.Widgets.Containers.Confirm.ConfirmEvt e)
- Monomer.Widgets.Containers.SelectList: instance Monomer.Core.Combinators.CmbMergeRequired (Monomer.Widgets.Containers.SelectList.SelectListCfg s e a) (Data.Sequence.Internal.Seq a)
- Monomer.Widgets.Singles.DateField: instance GHC.Classes.Eq Monomer.Widgets.Singles.DateField.DateFormat
- Monomer.Widgets.Singles.DateField: instance GHC.Show.Show Monomer.Widgets.Singles.DateField.DateFormat
- Monomer.Widgets.Singles.DateField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.DateField.DateFieldCfg s e a) GHC.Types.Int
- Monomer.Widgets.Singles.NumericField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.NumericField.NumericFieldCfg s e a) GHC.Types.Int
- Monomer.Widgets.Singles.TextArea: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.TextArea.TextAreaCfg s e) GHC.Types.Int
- Monomer.Widgets.Singles.TextField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.TextField.TextFieldCfg s e) GHC.Types.Int
- Monomer.Widgets.Singles.TimeField: instance GHC.Classes.Eq Monomer.Widgets.Singles.TimeField.TimeFormat
- Monomer.Widgets.Singles.TimeField: instance GHC.Show.Show Monomer.Widgets.Singles.TimeField.TimeFormat
- Monomer.Widgets.Singles.TimeField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.TimeField.TimeFieldCfg s e a) GHC.Types.Int
- Monomer.Widgets.Util.Widget: findWidgetIdFromPath :: WidgetEnv s e -> Path -> Maybe WidgetId
+ Monomer.Common.BasicTypes: rectFromPoints :: Point -> Point -> Rect
+ Monomer.Core.Combinators: class CmbIgnoreParentEvts t
+ Monomer.Core.Combinators: class CmbOnDisposeReq t s e | t -> s e
+ Monomer.Core.Combinators: class CmbOnInitReq t s e | t -> s e
+ Monomer.Core.Combinators: class CmbReadOnly t
+ Monomer.Core.Combinators: class CmbTextLineBreak t
+ Monomer.Core.Combinators: class CmbThumbMinSize t
+ Monomer.Core.Combinators: ignoreParentEvts :: CmbIgnoreParentEvts t => t
+ Monomer.Core.Combinators: ignoreParentEvts_ :: CmbIgnoreParentEvts t => Bool -> t
+ Monomer.Core.Combinators: onDisposeReq :: CmbOnDisposeReq t s e => WidgetRequest s e -> t
+ Monomer.Core.Combinators: onInitReq :: CmbOnInitReq t s e => WidgetRequest s e -> t
+ Monomer.Core.Combinators: readOnly :: CmbReadOnly t => t
+ Monomer.Core.Combinators: readOnly_ :: CmbReadOnly t => Bool -> t
+ Monomer.Core.Combinators: styleActiveSet :: CmbStyleActive t => t -> [StyleState] -> t
+ Monomer.Core.Combinators: styleBasicSet :: CmbStyleBasic t => t -> [StyleState] -> t
+ Monomer.Core.Combinators: styleDisabledSet :: CmbStyleDisabled t => t -> [StyleState] -> t
+ Monomer.Core.Combinators: styleFocusHoverSet :: CmbStyleFocusHover t => t -> [StyleState] -> t
+ Monomer.Core.Combinators: styleFocusSet :: CmbStyleFocus t => t -> [StyleState] -> t
+ Monomer.Core.Combinators: styleHoverSet :: CmbStyleHover t => t -> [StyleState] -> t
+ Monomer.Core.Combinators: textLineBreak :: CmbTextLineBreak t => LineBreak -> t
+ Monomer.Core.Combinators: thumbMinSize :: CmbThumbMinSize t => Double -> t
+ Monomer.Core.Lens: appStartTs :: HasAppStartTs s a => Lens' s a
+ Monomer.Core.Lens: class HasAppStartTs s a | s -> a
+ Monomer.Core.Lens: class HasLineBreak s a | s -> a
+ Monomer.Core.Lens: class HasScrollThumbMinSize s a | s -> a
+ Monomer.Core.Lens: instance Monomer.Core.Lens.HasAppStartTs (Monomer.Core.WidgetTypes.WidgetEnv s e) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.Lens: instance Monomer.Core.Lens.HasLineBreak Monomer.Core.StyleTypes.TextStyle (GHC.Maybe.Maybe Monomer.Core.StyleTypes.LineBreak)
+ Monomer.Core.Lens: instance Monomer.Core.Lens.HasScrollThumbMinSize Monomer.Core.ThemeTypes.ThemeState GHC.Types.Double
+ Monomer.Core.Lens: instance Monomer.Core.Lens.HasTimestamp (Monomer.Core.WidgetTypes.WidgetEnv s e) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.Lens: instance Monomer.Core.Lens.HasTs Monomer.Core.WidgetTypes.WidgetId Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.Lens: lineBreak :: HasLineBreak s a => Lens' s a
+ Monomer.Core.Lens: scrollThumbMinSize :: HasScrollThumbMinSize s a => Lens' s a
+ Monomer.Core.SizeReq: clearExtraH :: SizeReqUpdater
+ Monomer.Core.SizeReq: clearExtraW :: SizeReqUpdater
+ Monomer.Core.Style: instance Monomer.Core.Combinators.CmbTextLineBreak Monomer.Core.StyleTypes.StyleState
+ Monomer.Core.Style: instance Monomer.Core.Combinators.CmbTextLineBreak Monomer.Core.StyleTypes.TextStyle
+ Monomer.Core.StyleTypes: OnCharacters :: LineBreak
+ Monomer.Core.StyleTypes: OnSpaces :: LineBreak
+ Monomer.Core.StyleTypes: [_txsLineBreak] :: TextStyle -> Maybe LineBreak
+ Monomer.Core.StyleTypes: data LineBreak
+ Monomer.Core.StyleTypes: instance Data.Default.Class.Default Monomer.Core.StyleTypes.LineBreak
+ Monomer.Core.StyleTypes: instance GHC.Classes.Eq Monomer.Core.StyleTypes.LineBreak
+ Monomer.Core.StyleTypes: instance GHC.Classes.Ord Monomer.Core.StyleTypes.LineBreak
+ Monomer.Core.StyleTypes: instance GHC.Enum.Enum Monomer.Core.StyleTypes.LineBreak
+ Monomer.Core.StyleTypes: instance GHC.Generics.Generic Monomer.Core.StyleTypes.LineBreak
+ Monomer.Core.StyleTypes: instance GHC.Show.Show Monomer.Core.StyleTypes.LineBreak
+ Monomer.Core.StyleUtil: styleTextLineBreak :: StyleState -> LineBreak
+ Monomer.Core.ThemeTypes: [_thsScrollThumbMinSize] :: ThemeState -> Double
+ Monomer.Core.Util: currentTimeMs :: Integral a => WidgetEnv s e -> a
+ Monomer.Core.Util: findChildBranchByPath :: WidgetEnv s e -> WidgetNode s e -> Path -> Seq WidgetNodeInfo
+ Monomer.Core.Util: findChildNodeInfoByPath :: WidgetEnv s e -> WidgetNode s e -> Path -> Maybe WidgetNodeInfo
+ Monomer.Core.Util: findParentNodeInfoByType :: WidgetEnv s e -> Path -> WidgetType -> Maybe WidgetNodeInfo
+ Monomer.Core.Util: findWidgetIdFromPath :: WidgetEnv s e -> Path -> Maybe WidgetId
+ Monomer.Core.Util: nodeInfoFromKey :: WidgetEnv s e -> WidgetKey -> Maybe WidgetNodeInfo
+ Monomer.Core.Util: nodeInfoFromPath :: WidgetEnv s e -> Path -> Maybe WidgetNodeInfo
+ Monomer.Core.Util: widgetIdFromPath :: WidgetEnv s e -> Path -> Maybe WidgetId
+ Monomer.Core.WidgetTypes: Millisecond :: Word64 -> Millisecond
+ Monomer.Core.WidgetTypes: [_weAppStartTs] :: WidgetEnv s e -> Millisecond
+ Monomer.Core.WidgetTypes: [unMilliseconds] :: Millisecond -> Word64
+ Monomer.Core.WidgetTypes: instance Data.Default.Class.Default Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Classes.Eq Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Classes.Ord Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Enum.Bounded Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Enum.Enum Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Generics.Generic Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Num.Num Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Read.Read Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Real.Integral Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Real.Real Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance GHC.Show.Show Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: instance TextShow.Classes.TextShow Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Core.WidgetTypes: newtype Millisecond
+ Monomer.Event.Keyboard: isKeyPad0 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad1 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad2 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad3 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad4 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad5 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad6 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad7 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad8 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPad9 :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPadDivide :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPadEnter :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPadMinus :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPadMultiply :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPadPeriod :: KeyCode -> Bool
+ Monomer.Event.Keyboard: isKeyPadPlus :: KeyCode -> Bool
+ Monomer.Event.Keyboard: keyEnter :: KeyCode
+ Monomer.Event.Keyboard: keyPad0 :: KeyCode
+ Monomer.Event.Keyboard: keyPad1 :: KeyCode
+ Monomer.Event.Keyboard: keyPad2 :: KeyCode
+ Monomer.Event.Keyboard: keyPad3 :: KeyCode
+ Monomer.Event.Keyboard: keyPad4 :: KeyCode
+ Monomer.Event.Keyboard: keyPad5 :: KeyCode
+ Monomer.Event.Keyboard: keyPad6 :: KeyCode
+ Monomer.Event.Keyboard: keyPad7 :: KeyCode
+ Monomer.Event.Keyboard: keyPad8 :: KeyCode
+ Monomer.Event.Keyboard: keyPad9 :: KeyCode
+ Monomer.Event.Keyboard: keyPadDivide :: KeyCode
+ Monomer.Event.Keyboard: keyPadEnter :: KeyCode
+ Monomer.Event.Keyboard: keyPadMinus :: KeyCode
+ Monomer.Event.Keyboard: keyPadMultiply :: KeyCode
+ Monomer.Event.Keyboard: keyPadPeriod :: KeyCode
+ Monomer.Event.Keyboard: keyPadPlus :: KeyCode
+ Monomer.Graphics.FFI: fmSetScale :: FMContext -> Double -> IO ()
+ Monomer.Graphics.FFI: fmSetScale'_ :: FMContext -> CFloat -> IO ()
+ Monomer.Graphics.Lens: instance Monomer.Common.Lens.HasX Monomer.Graphics.Types.GlyphPos GHC.Types.Double
+ Monomer.Graphics.Types: [_glpX] :: GlyphPos -> {-# UNPACK #-} !Double
+ Monomer.Graphics.Types: [computeGlyphsPos_] :: FontManager -> Double -> Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos
+ Monomer.Graphics.Types: [computeTextMetrics_] :: FontManager -> Double -> Font -> FontSize -> TextMetrics
+ Monomer.Graphics.Types: [computeTextSize_] :: FontManager -> Double -> Font -> FontSize -> FontSpace -> Text -> Size
+ Monomer.Main.Lens: class HasDisableAutoScale s a | s -> a
+ Monomer.Main.Lens: class HasRenderMethod s a | s -> a
+ Monomer.Main.Lens: class HasWindowIcon s a | s -> a
+ Monomer.Main.Lens: disableAutoScale :: HasDisableAutoScale s a => Lens' s a
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasDisableAutoScale (Monomer.Main.Types.AppConfig e) (GHC.Maybe.Maybe GHC.Types.Bool)
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasMs Monomer.Main.Types.RenderSchedule Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasRenderMethod (Monomer.Main.Types.MonomerCtx s e) (Data.Either.Either Monomer.Graphics.Types.Renderer (Control.Concurrent.STM.TChan.TChan (Monomer.Main.Types.RenderMsg s e)))
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasStart Monomer.Main.Types.RenderSchedule Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Main.Lens: instance Monomer.Main.Lens.HasWindowIcon (Monomer.Main.Types.AppConfig e) (GHC.Maybe.Maybe Data.Text.Internal.Text)
+ Monomer.Main.Lens: renderMethod :: HasRenderMethod s a => Lens' s a
+ Monomer.Main.Lens: windowIcon :: HasWindowIcon s a => Lens' s a
+ Monomer.Main.Types: MsgInit :: WidgetEnv s e -> WidgetNode s e -> RenderMsg s e
+ Monomer.Main.Types: RenderSetupMakeCurrentFailed :: String -> RenderSetupResult
+ Monomer.Main.Types: RenderSetupMulti :: RenderSetupResult
+ Monomer.Main.Types: RenderSetupSingle :: RenderSetupResult
+ Monomer.Main.Types: [_apcDisableAutoScale] :: AppConfig e -> Maybe Bool
+ Monomer.Main.Types: [_apcWindowIcon] :: AppConfig e -> Maybe Text
+ Monomer.Main.Types: [_mcRenderMethod] :: MonomerCtx s e -> Either Renderer (TChan (RenderMsg s e))
+ Monomer.Main.Types: appDisableAutoScale :: Bool -> AppConfig e
+ Monomer.Main.Types: appWindowIcon :: Text -> AppConfig e
+ Monomer.Main.Types: data RenderSetupResult
+ Monomer.Main.Types: instance GHC.Classes.Eq Monomer.Main.Types.RenderSetupResult
+ Monomer.Main.Types: instance GHC.Show.Show Monomer.Main.Types.RenderSetupResult
+ Monomer.Widgets.Animation.Fade: instance Monomer.Core.Combinators.CmbDuration (Monomer.Widgets.Animation.Fade.FadeCfg e) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Animation.Slide: instance Monomer.Core.Combinators.CmbDuration (Monomer.Widgets.Animation.Slide.SlideCfg e) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Composite: compositeMergeEvents :: WidgetEvent e => MergeEventsHandler s e sp -> CompositeCfg s e sp ep
+ Monomer.Widgets.Composite: compositeMergeModel :: MergeModelHandler s e sp -> CompositeCfg s e sp ep
+ Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbMergeRequired (Monomer.Widgets.Composite.CompositeCfg s e sp ep) (Monomer.Core.WidgetTypes.WidgetEnv s e) s
+ Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbOnChangeReq (Monomer.Widgets.Composite.CompositeCfg s e sp ep) s e s
+ Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbOnDisposeReq (Monomer.Widgets.Composite.CompositeCfg s e sp ep) s e
+ Monomer.Widgets.Composite: instance Monomer.Core.Combinators.CmbOnInitReq (Monomer.Widgets.Composite.CompositeCfg s e sp ep) s e
+ Monomer.Widgets.Composite: instance Monomer.Core.WidgetTypes.WidgetEvent e => Monomer.Core.Combinators.CmbOnChange (Monomer.Widgets.Composite.CompositeCfg s e sp ep) s e
+ Monomer.Widgets.Composite: instance Monomer.Core.WidgetTypes.WidgetEvent e => Monomer.Core.Combinators.CmbOnDispose (Monomer.Widgets.Composite.CompositeCfg s e sp ep) e
+ Monomer.Widgets.Composite: instance Monomer.Core.WidgetTypes.WidgetEvent e => Monomer.Core.Combinators.CmbOnInit (Monomer.Widgets.Composite.CompositeCfg s e sp ep) e
+ Monomer.Widgets.Composite: type MergeEventsHandler s e sp = WidgetEnv s e " Widget environment." -> WidgetNode s e " New widget node." -> WidgetNode s e " Old widget node." -> sp " Parent model." -> s " Old composite model." -> s " New composite model." -> [e] " The list of events."
+ Monomer.Widgets.Composite: type MergeModelHandler s e sp = WidgetEnv s e " Widget environment." -> sp " Parent model." -> s " Old composite model." -> s " New composite model." -> s " Updated composite model."
+ Monomer.Widgets.Container: type ContainerCreateContainerFromModel s e a = WidgetEnv s e " The widget environment." -> WidgetNode s e " The widget node." -> a " The previous model." -> Maybe (Container s e a) " An updated Container instance."
+ Monomer.Widgets.Containers.Box: instance Monomer.Core.Combinators.CmbMergeRequired (Monomer.Widgets.Containers.Box.BoxCfg s e) (Monomer.Core.WidgetTypes.WidgetEnv s e) s
+ Monomer.Widgets.Containers.Confirm: data InnerConfirmEvt e
+ Monomer.Widgets.Containers.Confirm: instance GHC.Classes.Eq e => GHC.Classes.Eq (Monomer.Widgets.Containers.Confirm.InnerConfirmEvt e)
+ Monomer.Widgets.Containers.Confirm: instance GHC.Show.Show e => GHC.Show.Show (Monomer.Widgets.Containers.Confirm.InnerConfirmEvt e)
+ Monomer.Widgets.Containers.Keystroke: instance GHC.Classes.Eq Monomer.Widgets.Containers.Keystroke.KeyEntry
+ Monomer.Widgets.Containers.Keystroke: instance GHC.Show.Show Monomer.Widgets.Containers.Keystroke.KeyEntry
+ Monomer.Widgets.Containers.Keystroke: instance Monomer.Widgets.Containers.Keystroke.HasKsErrors Monomer.Widgets.Containers.Keystroke.KeyStroke [Data.Text.Internal.Text]
+ Monomer.Widgets.Containers.Keystroke: instance Monomer.Widgets.Containers.Keystroke.HasKsKeysText Monomer.Widgets.Containers.Keystroke.KeyStroke (Data.Set.Internal.Set Data.Text.Internal.Text)
+ Monomer.Widgets.Containers.Scroll: instance Monomer.Core.Combinators.CmbThumbMinSize (Monomer.Widgets.Containers.Scroll.ScrollCfg s e)
+ Monomer.Widgets.Containers.SelectList: instance Monomer.Core.Combinators.CmbMergeRequired (Monomer.Widgets.Containers.SelectList.SelectListCfg s e a) (Monomer.Core.WidgetTypes.WidgetEnv s e) (Data.Sequence.Internal.Seq a)
+ Monomer.Widgets.Singles.Base.InputField: [_ifcReadOnly] :: InputFieldCfg s e a -> Bool
+ Monomer.Widgets.Singles.Button: buttonD_ :: WidgetEvent e => Text -> [ButtonCfg s e] -> WidgetNode s e
+ Monomer.Widgets.Singles.Button: instance Monomer.Core.Combinators.CmbIgnoreParentEvts (Monomer.Widgets.Singles.Button.ButtonCfg s e)
+ Monomer.Widgets.Singles.Button: mainButtonD_ :: WidgetEvent e => Text -> [ButtonCfg s e] -> WidgetNode s e
+ Monomer.Widgets.Singles.ColorPicker: data ColorPickerEvt
+ Monomer.Widgets.Singles.DateField: data DateFieldFormat
+ Monomer.Widgets.Singles.DateField: dateAcceptText :: DateTextConverter a => DateFieldFormat -> Char -> Maybe a -> Maybe a -> Text -> (Bool, Bool, Maybe a)
+ Monomer.Widgets.Singles.DateField: dateFieldD_ :: (FormattableDate a, WidgetEvent e) => WidgetData s a -> [DateFieldCfg s e a] -> WidgetNode s e
+ Monomer.Widgets.Singles.DateField: dateFromDay :: DateTextConverter a => Day -> a
+ Monomer.Widgets.Singles.DateField: dateFromText :: DateTextConverter a => DateFieldFormat -> Char -> Text -> Maybe a
+ Monomer.Widgets.Singles.DateField: dateToDay :: DateTextConverter a => a -> Maybe Day
+ Monomer.Widgets.Singles.DateField: dateToText :: DateTextConverter a => DateFieldFormat -> Char -> a -> Text
+ Monomer.Widgets.Singles.DateField: instance GHC.Classes.Eq Monomer.Widgets.Singles.DateField.DateFieldFormat
+ Monomer.Widgets.Singles.DateField: instance GHC.Show.Show Monomer.Widgets.Singles.DateField.DateFieldFormat
+ Monomer.Widgets.Singles.DateField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.DateField.DateFieldCfg s e a) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Singles.DateField: instance Monomer.Core.Combinators.CmbReadOnly (Monomer.Widgets.Singles.DateField.DateFieldCfg s e a)
+ Monomer.Widgets.Singles.NumericField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.NumericField.NumericFieldCfg s e a) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Singles.NumericField: instance Monomer.Core.Combinators.CmbReadOnly (Monomer.Widgets.Singles.NumericField.NumericFieldCfg s e a)
+ Monomer.Widgets.Singles.NumericField: numericAcceptText :: NumericTextConverter a => Maybe a -> Maybe a -> Int -> Text -> (Bool, Bool, Maybe a)
+ Monomer.Widgets.Singles.NumericField: numericFieldD_ :: forall s e a. (FormattableNumber a, WidgetEvent e) => WidgetData s a -> [NumericFieldCfg s e a] -> WidgetNode s e
+ Monomer.Widgets.Singles.NumericField: numericFromFractional :: (NumericTextConverter a, Real b, Fractional b) => b -> a
+ Monomer.Widgets.Singles.NumericField: numericFromText :: NumericTextConverter a => Text -> Maybe a
+ Monomer.Widgets.Singles.NumericField: numericToFractional :: (NumericTextConverter a, Fractional b) => a -> Maybe b
+ Monomer.Widgets.Singles.NumericField: numericToText :: NumericTextConverter a => Int -> a -> Text
+ Monomer.Widgets.Singles.OptionButton: instance Monomer.Core.Combinators.CmbOnClickReq (Monomer.Widgets.Singles.OptionButton.OptionButtonCfg s e a) s e
+ Monomer.Widgets.Singles.OptionButton: instance Monomer.Core.WidgetTypes.WidgetEvent e => Monomer.Core.Combinators.CmbOnClick (Monomer.Widgets.Singles.OptionButton.OptionButtonCfg s e a) e
+ Monomer.Widgets.Singles.Radio: instance Monomer.Core.Combinators.CmbOnClickReq (Monomer.Widgets.Singles.Radio.RadioCfg s e a) s e
+ Monomer.Widgets.Singles.Radio: instance Monomer.Core.WidgetTypes.WidgetEvent e => Monomer.Core.Combinators.CmbOnClick (Monomer.Widgets.Singles.Radio.RadioCfg s e a) e
+ Monomer.Widgets.Singles.TextArea: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.TextArea.TextAreaCfg s e) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Singles.TextArea: instance Monomer.Core.Combinators.CmbReadOnly (Monomer.Widgets.Singles.TextArea.TextAreaCfg s e)
+ Monomer.Widgets.Singles.TextArea: textAreaD_ :: WidgetEvent e => WidgetData s Text -> [TextAreaCfg s e] -> WidgetNode s e
+ Monomer.Widgets.Singles.TextField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.TextField.TextFieldCfg s e) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Singles.TextField: instance Monomer.Core.Combinators.CmbReadOnly (Monomer.Widgets.Singles.TextField.TextFieldCfg s e)
+ Monomer.Widgets.Singles.TimeField: class TimeTextConverter a
+ Monomer.Widgets.Singles.TimeField: data TimeFieldFormat
+ Monomer.Widgets.Singles.TimeField: instance GHC.Classes.Eq Monomer.Widgets.Singles.TimeField.TimeFieldFormat
+ Monomer.Widgets.Singles.TimeField: instance GHC.Show.Show Monomer.Widgets.Singles.TimeField.TimeFieldFormat
+ Monomer.Widgets.Singles.TimeField: instance Monomer.Core.Combinators.CmbCaretMs (Monomer.Widgets.Singles.TimeField.TimeFieldCfg s e a) Monomer.Core.WidgetTypes.Millisecond
+ Monomer.Widgets.Singles.TimeField: instance Monomer.Core.Combinators.CmbReadOnly (Monomer.Widgets.Singles.TimeField.TimeFieldCfg s e a)
+ Monomer.Widgets.Singles.TimeField: timeAcceptText :: TimeTextConverter a => TimeFieldFormat -> Maybe a -> Maybe a -> Text -> (Bool, Bool, Maybe a)
+ Monomer.Widgets.Singles.TimeField: timeFieldD_ :: (FormattableTime a, WidgetEvent e) => WidgetData s a -> [TimeFieldCfg s e a] -> WidgetNode s e
+ Monomer.Widgets.Singles.TimeField: timeFromText :: TimeTextConverter a => TimeFieldFormat -> Text -> Maybe a
+ Monomer.Widgets.Singles.TimeField: timeFromTimeOfDay' :: TimeTextConverter a => TimeOfDay -> a
+ Monomer.Widgets.Singles.TimeField: timeToText :: TimeTextConverter a => TimeFieldFormat -> a -> Text
+ Monomer.Widgets.Singles.TimeField: timeToTimeOfDay' :: TimeTextConverter a => a -> Maybe TimeOfDay
- Monomer.Core.Combinators: class CmbMergeRequired t s | t -> s
+ Monomer.Core.Combinators: class CmbMergeRequired t w s | t -> w s
- Monomer.Core.Combinators: mergeRequired :: CmbMergeRequired t s => (s -> s -> Bool) -> t
+ Monomer.Core.Combinators: mergeRequired :: CmbMergeRequired t w s => (w -> s -> s -> Bool) -> t
- Monomer.Core.Lens: _WidgetLens :: forall s_a1J2b a_a1xuz s_a1xuy. Prism (WidgetData s_a1J2b a_a1xuz) (WidgetData s_a1xuy a_a1xuz) (ALens' s_a1J2b a_a1xuz) (ALens' s_a1xuy a_a1xuz)
+ Monomer.Core.Lens: _WidgetLens :: forall s_a1NwM a_a1ART s_a1ARS. Prism (WidgetData s_a1NwM a_a1ART) (WidgetData s_a1ARS a_a1ART) (ALens' s_a1NwM a_a1ART) (ALens' s_a1ARS a_a1ART)
- Monomer.Core.Lens: _WidgetValue :: forall s_a1xuy a_a1xuz. Prism' (WidgetData s_a1xuy a_a1xuz) a_a1xuz
+ Monomer.Core.Lens: _WidgetValue :: forall s_a1ARS a_a1ART. Prism' (WidgetData s_a1ARS a_a1ART) a_a1ART
- Monomer.Core.StyleTypes: TextStyle :: Maybe Font -> Maybe FontSize -> Maybe FontSpace -> Maybe FontSpace -> Maybe Color -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe AlignTH -> Maybe AlignTV -> TextStyle
+ Monomer.Core.StyleTypes: TextStyle :: Maybe Font -> Maybe FontSize -> Maybe FontSpace -> Maybe FontSpace -> Maybe Color -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe AlignTH -> Maybe AlignTV -> Maybe LineBreak -> TextStyle
- Monomer.Core.ThemeTypes: ThemeState :: StyleState -> StyleState -> StyleState -> StyleState -> Double -> StyleState -> StyleState -> Rational -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Double -> Bool -> Bool -> Color -> Color -> Double -> Double -> Double -> Rational -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Maybe Double -> Double -> Rational -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Map String StyleState -> Map String Color -> ThemeState
+ Monomer.Core.ThemeTypes: ThemeState :: StyleState -> StyleState -> StyleState -> StyleState -> Double -> StyleState -> StyleState -> Rational -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Double -> Bool -> Bool -> Color -> Color -> Double -> Double -> Double -> Double -> Rational -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Maybe Double -> Double -> Rational -> Double -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> StyleState -> Map String StyleState -> Map String Color -> ThemeState
- Monomer.Core.WidgetTypes: RenderEvery :: WidgetId -> Int -> Maybe Int -> WidgetRequest s e
+ Monomer.Core.WidgetTypes: RenderEvery :: WidgetId -> Millisecond -> Maybe Int -> WidgetRequest s e
- Monomer.Core.WidgetTypes: WidgetEnv :: Text -> Double -> FontManager -> (Path -> Seq WidgetNodeInfo) -> Button -> Button -> Theme -> Size -> MVar (Map Text WidgetShared) -> WidgetKeyMap s e -> Maybe Path -> Path -> Maybe Path -> Maybe (Path, WidgetDragMsg) -> Maybe (Path, Point) -> Maybe (Path, CursorIcon) -> s -> InputStatus -> Timestamp -> Bool -> (Point -> Bool) -> LayoutDirection -> Rect -> Point -> WidgetEnv s e
+ Monomer.Core.WidgetTypes: WidgetEnv :: Text -> Double -> Millisecond -> FontManager -> (Path -> Seq WidgetNodeInfo) -> Button -> Button -> Theme -> Size -> MVar (Map Text WidgetShared) -> WidgetKeyMap s e -> Maybe Path -> Path -> Maybe Path -> Maybe (Path, WidgetDragMsg) -> Maybe (Path, Point) -> Maybe (Path, CursorIcon) -> s -> InputStatus -> Millisecond -> Bool -> (Point -> Bool) -> LayoutDirection -> Rect -> Point -> WidgetEnv s e
- Monomer.Core.WidgetTypes: WidgetId :: Int -> Path -> WidgetId
+ Monomer.Core.WidgetTypes: WidgetId :: Millisecond -> Path -> WidgetId
- Monomer.Core.WidgetTypes: [_weTimestamp] :: WidgetEnv s e -> Timestamp
+ Monomer.Core.WidgetTypes: [_weTimestamp] :: WidgetEnv s e -> Millisecond
- Monomer.Core.WidgetTypes: [_widTs] :: WidgetId -> Int
+ Monomer.Core.WidgetTypes: [_widTs] :: WidgetId -> Millisecond
- Monomer.Graphics.Types: FontManager :: (Font -> FontSize -> TextMetrics) -> (Font -> FontSize -> FontSpace -> Text -> Size) -> (Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos) -> FontManager
+ Monomer.Graphics.Types: FontManager :: (Font -> FontSize -> TextMetrics) -> (Double -> Font -> FontSize -> TextMetrics) -> (Font -> FontSize -> FontSpace -> Text -> Size) -> (Double -> Font -> FontSize -> FontSpace -> Text -> Size) -> (Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos) -> (Double -> Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos) -> FontManager
- Monomer.Graphics.Types: GlyphPos :: {-# UNPACK #-} !Char -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> GlyphPos
+ Monomer.Graphics.Types: GlyphPos :: {-# UNPACK #-} !Char -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> GlyphPos
- 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 -> Maybe Bool -> AppConfig e
+ Monomer.Main.Types: AppConfig :: Maybe MainWindowState -> Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Text -> Maybe Bool -> Maybe Int -> Maybe Double -> Maybe Bool -> [FontDef] -> Maybe Theme -> [e] -> [e] -> [e] -> [Rect -> e] -> Maybe Button -> Maybe Button -> Maybe Bool -> Maybe Bool -> Maybe Bool -> AppConfig e
- Monomer.Main.Types: MonomerCtx :: s -> ~Window -> Size -> Double -> Double -> TChan (RenderMsg s e) -> InputStatus -> [(WidgetId, CursorIcon)] -> WidgetId -> Maybe WidgetId -> Maybe WidgetId -> Maybe DragAction -> Maybe (Path, Point) -> Seq WidgetTask -> Map WidgetId Path -> Map CursorIcon Cursor -> Bool -> Seq WidgetId -> Bool -> Map WidgetId RenderSchedule -> Bool -> MonomerCtx s e
+ Monomer.Main.Types: MonomerCtx :: s -> ~Window -> Size -> Double -> Double -> Either Renderer (TChan (RenderMsg s e)) -> InputStatus -> [(WidgetId, CursorIcon)] -> WidgetId -> Maybe WidgetId -> Maybe WidgetId -> Maybe DragAction -> Maybe (Path, Point) -> Seq WidgetTask -> Map WidgetId Path -> Map CursorIcon Cursor -> Bool -> Seq WidgetId -> Bool -> Map WidgetId RenderSchedule -> Bool -> MonomerCtx s e
- Monomer.Main.Types: RenderSchedule :: WidgetId -> Int -> Int -> Maybe Int -> RenderSchedule
+ Monomer.Main.Types: RenderSchedule :: WidgetId -> Millisecond -> Millisecond -> Maybe Int -> RenderSchedule
- Monomer.Main.Types: [_rsMs] :: RenderSchedule -> Int
+ Monomer.Main.Types: [_rsMs] :: RenderSchedule -> Millisecond
- Monomer.Main.Types: [_rsStart] :: RenderSchedule -> Int
+ Monomer.Main.Types: [_rsStart] :: RenderSchedule -> Millisecond
- Monomer.Widgets.Composite: compositeMergeReqs :: MergeReqsHandler s e -> CompositeCfg s e sp ep
+ Monomer.Widgets.Composite: compositeMergeReqs :: MergeReqsHandler s e sp -> CompositeCfg s e sp ep
- Monomer.Widgets.Composite: compositeV :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => WidgetType -> s -> (s -> ep) -> UIBuilder s e -> EventHandler s e sp ep -> WidgetNode sp ep
+ Monomer.Widgets.Composite: compositeV :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => WidgetType -> s -> (s -> e) -> UIBuilder s e -> EventHandler s e sp ep -> WidgetNode sp ep
- Monomer.Widgets.Composite: compositeV_ :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => WidgetType -> s -> (s -> ep) -> UIBuilder s e -> EventHandler s e sp ep -> [CompositeCfg s e sp ep] -> WidgetNode sp ep
+ Monomer.Widgets.Composite: compositeV_ :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => WidgetType -> s -> (s -> e) -> UIBuilder s e -> EventHandler s e sp ep -> [CompositeCfg s e sp ep] -> WidgetNode sp ep
- Monomer.Widgets.Composite: type MergeReqsHandler s e = WidgetEnv s e " Widget environment." -> WidgetNode s e " New widget node." -> WidgetNode s e " Old widget node." -> s " The current model." -> [WidgetRequest s e] " The list of requests."
+ Monomer.Widgets.Composite: type MergeReqsHandler s e sp = WidgetEnv s e " Widget environment." -> WidgetNode s e " New widget node." -> WidgetNode s e " Old widget node." -> sp " Parent model." -> s " Old composite model." -> s " New composite model." -> [WidgetRequest s e] " The list of requests."
- Monomer.Widgets.Composite: type MergeRequired s = s " Old composite model." -> s " New composite model" -> Bool " True if merge is required."
+ Monomer.Widgets.Composite: type MergeRequired s e = WidgetEnv s e " Widget environment." -> s " Old composite model." -> s " New composite model" -> Bool " True if merge is required."
- Monomer.Widgets.Containers.Confirm: confirm :: (WidgetModel s, WidgetEvent e) => e -> e -> WidgetNode () (ConfirmEvt e) -> WidgetNode s e
+ Monomer.Widgets.Containers.Confirm: confirm :: (WidgetModel s, WidgetEvent e) => e -> e -> WidgetNode () (InnerConfirmEvt e) -> WidgetNode s e
- Monomer.Widgets.Containers.Confirm: confirm_ :: (WidgetModel s, WidgetEvent e) => e -> e -> [ConfirmCfg] -> WidgetNode () (ConfirmEvt e) -> WidgetNode s e
+ Monomer.Widgets.Containers.Confirm: confirm_ :: (WidgetModel s, WidgetEvent e) => e -> e -> [ConfirmCfg] -> WidgetNode () (InnerConfirmEvt e) -> WidgetNode s e
- Monomer.Widgets.Containers.Tooltip: tooltipDelay :: Int -> TooltipCfg
+ Monomer.Widgets.Containers.Tooltip: tooltipDelay :: Millisecond -> TooltipCfg
- Monomer.Widgets.Singles.Base.InputField: InputFieldCfg :: Maybe Text -> a -> WidgetData s a -> Maybe (WidgetData s Bool) -> [Bool -> e] -> Bool -> Double -> Maybe Double -> Maybe Int -> Maybe Char -> Bool -> Bool -> (Text -> Maybe a) -> (a -> Text) -> (Text -> Bool) -> (Text -> Bool) -> Maybe (ALens' ThemeState StyleState) -> Maybe (InputWheelHandler a) -> Maybe (InputDragHandler a) -> Maybe CursorIcon -> [Path -> WidgetRequest s e] -> [Path -> WidgetRequest s e] -> [a -> WidgetRequest s e] -> InputFieldCfg s e a
+ Monomer.Widgets.Singles.Base.InputField: InputFieldCfg :: Maybe Text -> a -> WidgetData s a -> Maybe (WidgetData s Bool) -> [Bool -> e] -> Bool -> Double -> Maybe Double -> Maybe Millisecond -> Maybe Char -> Bool -> Bool -> Bool -> (Text -> Maybe a) -> (a -> Text) -> (Text -> Bool) -> (Text -> Bool) -> Maybe (ALens' ThemeState StyleState) -> Maybe (InputWheelHandler a) -> Maybe (InputDragHandler a) -> Maybe CursorIcon -> [Path -> WidgetRequest s e] -> [Path -> WidgetRequest s e] -> [a -> WidgetRequest s e] -> InputFieldCfg s e a
- Monomer.Widgets.Singles.Base.InputField: InputFieldState :: Seq TextLine -> a -> !Text -> !Int -> Maybe Int -> a -> Seq GlyphPos -> !Double -> Rect -> TextMetrics -> Seq (HistoryStep a) -> Int -> Int -> InputFieldState a
+ Monomer.Widgets.Singles.Base.InputField: InputFieldState :: Seq TextLine -> a -> !Text -> !Int -> Maybe Int -> a -> Seq GlyphPos -> !Double -> Rect -> TextMetrics -> Seq (HistoryStep a) -> Int -> Millisecond -> InputFieldState a
- Monomer.Widgets.Singles.Base.InputField: [_ifcCaretMs] :: InputFieldCfg s e a -> Maybe Int
+ Monomer.Widgets.Singles.Base.InputField: [_ifcCaretMs] :: InputFieldCfg s e a -> Maybe Millisecond
- Monomer.Widgets.Singles.Base.InputField: [_ifsFocusStart] :: InputFieldState a -> Int
+ Monomer.Widgets.Singles.Base.InputField: [_ifsFocusStart] :: InputFieldState a -> Millisecond
- Monomer.Widgets.Util.Widget: delayedMessage :: Typeable i => WidgetNode s e -> i -> Int -> WidgetRequest s e
+ Monomer.Widgets.Util.Widget: delayedMessage :: Typeable i => WidgetNode s e -> i -> Millisecond -> WidgetRequest s e
- Monomer.Widgets.Util.Widget: delayedMessage_ :: Typeable i => WidgetId -> Path -> i -> Int -> WidgetRequest s e
+ Monomer.Widgets.Util.Widget: delayedMessage_ :: Typeable i => WidgetId -> Path -> i -> Millisecond -> WidgetRequest s e
Files
- ChangeLog.md +61/−0
- cbits/fontmanager.c +8/−3
- cbits/fontmanager.h +3/−0
- examples/books/Main.hs +2/−1
- examples/generative/Main.hs +1/−0
- examples/opengl/Main.hs +1/−0
- examples/ticker/BinanceTypes.hs +9/−7
- examples/ticker/Main.hs +14/−13
- examples/todo/Main.hs +2/−1
- examples/todo/TodoTypes.hs +1/−1
- examples/tutorial/Tutorial01_Basics.hs +1/−0
- examples/tutorial/Tutorial02_Styling.hs +1/−0
- examples/tutorial/Tutorial03_LifeCycle.hs +5/−3
- examples/tutorial/Tutorial04_Tasks.hs +2/−1
- examples/tutorial/Tutorial05_Producers.hs +1/−0
- examples/tutorial/Tutorial06_Composite.hs +1/−0
- examples/tutorial/Tutorial07_CustomWidget.hs +1/−0
- examples/tutorial/Tutorial08_Themes.hs +1/−0
- monomer.cabal +11/−31
- src/Monomer.hs +1/−1
- src/Monomer/Common/BasicTypes.hs +8/−0
- src/Monomer/Core/Combinators.hs +152/−15
- src/Monomer/Core/SizeReq.hs +43/−6
- src/Monomer/Core/Style.hs +6/−0
- src/Monomer/Core/StyleTypes.hs +15/−3
- src/Monomer/Core/StyleUtil.hs +85/−25
- src/Monomer/Core/ThemeTypes.hs +7/−4
- src/Monomer/Core/Themes/BaseTheme.hs +33/−15
- src/Monomer/Core/Util.hs +64/−9
- src/Monomer/Core/WidgetTypes.hs +35/−13
- src/Monomer/Event/Core.hs +1/−0
- src/Monomer/Event/Keyboard.hs +103/−0
- src/Monomer/Graphics/FFI.chs +7/−1
- src/Monomer/Graphics/FontManager.hs +38/−27
- src/Monomer/Graphics/NanoVGRenderer.hs +4/−0
- src/Monomer/Graphics/RemixIcon.hs +1/−1
- src/Monomer/Graphics/Text.hs +17/−22
- src/Monomer/Graphics/Types.hs +26/−5
- src/Monomer/Helper.hs +9/−1
- src/Monomer/Main/Core.hs +128/−64
- src/Monomer/Main/Handlers.hs +23/−12
- src/Monomer/Main/Platform.hs +40/−15
- src/Monomer/Main/Types.hs +90/−13
- src/Monomer/Main/Util.hs +2/−3
- src/Monomer/Widgets.hs +4/−3
- src/Monomer/Widgets/Animation/Fade.hs +4/−4
- src/Monomer/Widgets/Animation/Slide.hs +4/−4
- src/Monomer/Widgets/Composite.hs +151/−90
- src/Monomer/Widgets/Container.hs +7/−6
- src/Monomer/Widgets/Containers/Alert.hs +18/−0
- src/Monomer/Widgets/Containers/Base/LabeledItem.hs +5/−0
- src/Monomer/Widgets/Containers/Box.hs +31/−5
- src/Monomer/Widgets/Containers/Confirm.hs +56/−28
- src/Monomer/Widgets/Containers/Draggable.hs +10/−1
- src/Monomer/Widgets/Containers/DropTarget.hs +10/−2
- src/Monomer/Widgets/Containers/Dropdown.hs +15/−3
- src/Monomer/Widgets/Containers/Grid.hs +17/−3
- src/Monomer/Widgets/Containers/Keystroke.hs +97/−29
- src/Monomer/Widgets/Containers/Scroll.hs +77/−28
- src/Monomer/Widgets/Containers/SelectList.hs +16/−7
- src/Monomer/Widgets/Containers/Split.hs +12/−1
- src/Monomer/Widgets/Containers/Stack.hs +29/−4
- src/Monomer/Widgets/Containers/ThemeSwitch.hs +17/−3
- src/Monomer/Widgets/Containers/Tooltip.hs +13/−8
- src/Monomer/Widgets/Containers/ZStack.hs +11/−3
- src/Monomer/Widgets/Singles/Base/InputField.hs +33/−24
- src/Monomer/Widgets/Singles/Button.hs +54/−6
- src/Monomer/Widgets/Singles/Checkbox.hs +8/−3
- src/Monomer/Widgets/Singles/ColorPicker.hs +19/−11
- src/Monomer/Widgets/Singles/DateField.hs +50/−24
- src/Monomer/Widgets/Singles/Dial.hs +7/−2
- src/Monomer/Widgets/Singles/ExternalLink.hs +6/−2
- src/Monomer/Widgets/Singles/Icon.hs +4/−0
- src/Monomer/Widgets/Singles/Image.hs +6/−0
- src/Monomer/Widgets/Singles/Label.hs +13/−1
- src/Monomer/Widgets/Singles/LabeledCheckbox.hs +12/−2
- src/Monomer/Widgets/Singles/LabeledRadio.hs +13/−3
- src/Monomer/Widgets/Singles/NumericField.hs +42/−16
- src/Monomer/Widgets/Singles/OptionButton.hs +45/−17
- src/Monomer/Widgets/Singles/Radio.hs +34/−8
- src/Monomer/Widgets/Singles/SeparatorLine.hs +10/−2
- src/Monomer/Widgets/Singles/Slider.hs +6/−2
- src/Monomer/Widgets/Singles/Spacer.hs +18/−1
- src/Monomer/Widgets/Singles/TextArea.hs +64/−41
- src/Monomer/Widgets/Singles/TextDropdown.hs +8/−2
- src/Monomer/Widgets/Singles/TextField.hs +25/−3
- src/Monomer/Widgets/Singles/TimeField.hs +49/−23
- src/Monomer/Widgets/Singles/ToggleButton.hs +12/−7
- src/Monomer/Widgets/Util/Widget.hs +3/−10
- test/unit/Monomer/Common/CursorIconSpec.hs +1/−1
- test/unit/Monomer/Core/StyleUtilSpec.hs +78/−0
- test/unit/Monomer/TestUtil.hs +21/−9
- test/unit/Monomer/Widgets/CompositeSpec.hs +87/−3
- test/unit/Monomer/Widgets/Containers/BoxSpec.hs +2/−2
- test/unit/Monomer/Widgets/Containers/KeystrokeSpec.hs +56/−3
- test/unit/Monomer/Widgets/Containers/ScrollSpec.hs +49/−4
- test/unit/Monomer/Widgets/Singles/DateFieldSpec.hs +25/−0
- test/unit/Monomer/Widgets/Singles/LabelSpec.hs +1/−1
- test/unit/Monomer/Widgets/Singles/NumericFieldSpec.hs +25/−0
- test/unit/Monomer/Widgets/Singles/OptionButtonSpec.hs +12/−1
- test/unit/Monomer/Widgets/Singles/RadioSpec.hs +12/−1
- test/unit/Monomer/Widgets/Singles/TextAreaSpec.hs +19/−0
- test/unit/Monomer/Widgets/Singles/TextFieldSpec.hs +45/−0
- test/unit/Monomer/Widgets/Singles/TimeFieldSpec.hs +25/−0
- test/unit/Monomer/Widgets/Singles/ToggleButtonSpec.hs +12/−1
- test/unit/Monomer/Widgets/Util/TextSpec.hs +11/−0
- test/unit/Spec.hs +4/−0
ChangeLog.md view
@@ -1,3 +1,64 @@+## 1.4.0.0++### Breaking changes++- Added `style...Set` family of functions ([PR #104](https://github.com/fjvallarino/monomer/pull/104)).+- `Composite`'s `onChange` event is now sent to its `handleEvent` function, not to its parent; the type of the+ generated event was updated to reflect this change. The rationale is that since `onInit` is sent to+ `handleEvent`, having `onChange` sent to its parent was confusing. At the same time there was not an easy way+ in `handleEvent` to know when the model changed. Widgets that want to report model changes to its parent can+ use `Report`/`RequestParent`; an example can be found in `ColorPicker` ([PR #71](https://github.com/fjvallarino/monomer/pull/71)).+- `Timestamp` is now a newtype. Enforce use of this type instead of `Int` when appropriate ([PR #103](https://github.com/fjvallarino/monomer/pull/103)).+- `Timestamp` was renamed to `Millisecond`. The rationale is that since both timestamps and durations are used frequently in calculations (and in the context of Monomer timestamps and durations indeed represent time in milliseconds), having separate types for Timestamp and Duration caused more harm than good ([PR #107](https://github.com/fjvallarino/monomer/pull/107)).+- `compositeMergeModel` (previously `customModelBuilder`) now receives `WidgetEnv` as its first parameter ([PR #114](https://github.com/fjvallarino/monomer/pull/114)).+- `compositeMergeReqs` now receives `parentModel` and `oldModel` too ([PR #114](https://github.com/fjvallarino/monomer/pull/114)).+- `mergeRequired` now receives an extra value as its first parameter, usually `WidgetEnv` ([PR #122](https://github.com/fjvallarino/monomer/pull/122)).++### Fixed++- Properly handle `SetFocusOnKey` for `textArea` ([#80](https://github.com/fjvallarino/monomer/issues/80)).+- Lens tutorial sample code ([PR #95](https://github.com/fjvallarino/monomer/pull/95) and [PR #98](https://github.com/fjvallarino/monomer/pull/98)). Thanks @Clindbergh!+- ColorPicker's numericFields vertical alignment ([PR #108](https://github.com/fjvallarino/monomer/pull/108)).+- Differences in glyphs positions used by `FontManager` and nanovg; temporary workaround ([PR #105](https://github.com/fjvallarino/monomer/pull/105)).+- `nodeInfoFromKey` relies on `nodeInfoFromPath` to retrieve information instead of fetching it directly from `WidgetEnv`'s `widgetKeyMap`, which can be stale ([PR #110](https://github.com/fjvallarino/monomer/pull/110)).+- Glyph positioning issues in `FontManager`; removed workaround added in #105 ([PR #125](https://github.com/fjvallarino/monomer/pull/125)).+- Will attempt to fall back to rendering on the main thread if threaded rendering setup fails ([PR #131](https://github.com/fjvallarino/monomer/pull/131)).+- Space leak in StyleUtil's mergeNodeStyleState ([PR #132](https://github.com/fjvallarino/monomer/pull/132)).++### Added++- Utility functions `rectFromPoints`, `nodeInfoFromKey`, `nodeInfoFromPath` and `findParentNodeInfoByType`.+- Allow setting the window icon via AppConfig ([PR #79](https://github.com/fjvallarino/monomer/pull/79)). Thanks @Dretch!+- Support for breaking text lines at character boundaries ([PR #86](https://github.com/fjvallarino/monomer/pull/86)). Thanks @toku-sa-n!+- Read-only mode for `textField`, `numericField`, `dateField`, `timeField` and `textArea` ([PR #93](https://github.com/fjvallarino/monomer/pull/93)). Thanks @Dretch!+- The `scroll` widget now supports a `thumbMinSize` configuration option that allows setting a minimum thumb size ([PR #100](https://github.com/fjvallarino/monomer/pull/100)).+- New field `_weAppStartTs` in `WidgetEnv`, complementary to `_weTimestamp`, representing the time in milliseconds when the application started. Added utility function `currentTimeMs` that returns their sum with a polymorphic type ([PR #103](https://github.com/fjvallarino/monomer/pull/103)).+- Several sizeReq helpers ([PR #106](https://github.com/fjvallarino/monomer/pull/106)).+- `compositeMergeEvents`, for completeness ([PR #114](https://github.com/fjvallarino/monomer/pull/114)).+- Support for symbols and other keys in `keystroke` ([PR #117](https://github.com/fjvallarino/monomer/pull/117)).+- New constructor (`buttonD_`) and `ignoreParentEvts` configuration option to `button` ([PR #123](https://github.com/fjvallarino/monomer/pull/123)).+- Allow disabling auto scale detection with `appDisableAutoScale` ([PR #128](https://github.com/fjvallarino/monomer/pull/128)).++### Changed++- The `keystroke` widget now supports the `Backspace` key ([PR #74](https://github.com/fjvallarino/monomer/pull/74)).+- `style...` family of functions now combine new attributes with the existing ones ([PR #104](https://github.com/fjvallarino/monomer/pull/104)).+- `radio` and `optionButton` now only trigger `onChange` when their value changes. `onClick` was can be used to replicate the previous `onChange` behavior ([PR #134](https://github.com/fjvallarino/monomer/pull/134)).++### Renamed++- Utility functions for retrieving `WidgetNode` information ([PR #75](https://github.com/fjvallarino/monomer/pull/75))+ - `findWidgetByPath` -> `findChildNodeInfoByPath`.+ - `findWidgetBranchByPath` -> `findChildBranchByPath`.+ - `findWidgetIdFromPath` -> `widgetIdFromPath`.+- Composite merge related ([PR #114](https://github.com/fjvallarino/monomer/pull/114))+ - `customModelBuilder` -> `compositeMergeModel`+ - `CompositeCustomModelBuilder` -> `MergeModelHandler`.++### Removed++- Dependencies on `OpenGL`, `Safe`, `scientific`, `unordered-containers`, `directory`, `HUnit` and `silently` ([PR #70](https://github.com/fjvallarino/monomer/pull/70)).+ ## 1.3.0.0 ### Added
cbits/fontmanager.c view
@@ -37,6 +37,7 @@ // Initialize font manager context ctx->fs = fonsCreateInternal(&fontParams); ctx->dpr = dpr;+ ctx->scale = 1; ctx->fontSize = 16.0f; ctx->letterSpacing = 0.0f; ctx->lineHeight = 1.0f;@@ -52,6 +53,10 @@ return fonsAddFont(ctx->fs, name, filename, 0); } +void fmSetScale(FMcontext* ctx, float scale) {+ ctx->scale = scale;+}+ void fmFontFace(FMcontext* ctx, const char* font) { ctx->fontId = fonsGetFontByName(ctx->fs, font);@@ -79,7 +84,7 @@ void fmTextMetrics(FMcontext* ctx, float* ascender, float* descender, float* lineh) {- float scale = ctx->dpr;+ float scale = ctx->dpr * ctx->scale; float invscale = 1.0f / scale; if (ctx->fontId == FONS_INVALID) return;@@ -101,7 +106,7 @@ float fmTextBounds(FMcontext* ctx, float x, float y, const char* string, const char* end, float* bounds) {- float scale = ctx->dpr;+ float scale = ctx->dpr * ctx->scale; float invscale = 1.0f / scale; float width; @@ -127,7 +132,7 @@ int fmTextGlyphPositions(FMcontext* ctx, float x, float y, const char* string, const char* end, FMGglyphPosition* positions, int maxPositions) {- float scale = ctx->dpr;+ float scale = ctx->dpr * ctx->scale; float invscale = 1.0f / scale; FONStextIter iter, prevIter; FONSquad q;
cbits/fontmanager.h view
@@ -15,6 +15,7 @@ struct FMcontext { struct FONScontext* fs; float dpr;+ float scale; float fontSize; float letterSpacing; float lineHeight;@@ -37,6 +38,8 @@ FMcontext* fmInit(float dpr); int fmCreateFont(FMcontext* ctx, const char* name, const char* filename);++void fmSetScale(FMcontext* ctx, float scale); void fmFontFace(FMcontext* ctx, const char* font);
examples/books/Main.hs view
@@ -119,7 +119,7 @@ countLabel = label caption `styleBasic` [padding 10] where caption = "Books (" <> showt (length $ model ^. books) <> ")" - booksChanged old new = old ^. books /= new ^. books+ booksChanged wenv old new = old ^. books /= new ^. books widgetTree = zstack [ vstack [@@ -190,6 +190,7 @@ where config = [ appWindowTitle "Book search",+ appWindowIcon "./assets/images/icon.bmp", appTheme customDarkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appFontDef "Medium" "./assets/fonts/Roboto-Medium.ttf",
examples/generative/Main.hs view
@@ -112,6 +112,7 @@ model = GenerativeModel CirclesGrid False def def config = [ appWindowTitle "Generative",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Remix" "./assets/fonts/remixicon.ttf", appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf",
examples/opengl/Main.hs view
@@ -94,6 +94,7 @@ where config = [ appWindowTitle "OpenGL",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appInitEvent AppInit
examples/ticker/BinanceTypes.hs view
@@ -22,12 +22,14 @@ import Control.Lens.TH import Data.Aeson import Data.Default+import Data.Fixed import Data.Foldable (asum) import Data.Maybe import Data.Map (Map)-import Data.Scientific import Data.Text (Text, pack) +type FixedFloat = Pico+ data ServerRequest = ServerRequest { _srqRequestId :: Int, _srqMethod :: Text,@@ -68,12 +70,12 @@ data Ticker = Ticker { _tckSymbolPair :: Text, _tckTs :: Int,- _tckOpen :: Scientific,- _tckClose :: Scientific,- _tckHigh :: Scientific,- _tckLow :: Scientific,- _tckVolume :: Scientific,- _tckTrades :: Scientific+ _tckOpen :: FixedFloat,+ _tckClose :: FixedFloat,+ _tckHigh :: FixedFloat,+ _tckLow :: FixedFloat,+ _tckVolume :: FixedFloat,+ _tckTrades :: FixedFloat } deriving (Eq, Show) instance FromJSON Ticker where
examples/ticker/Main.hs view
@@ -25,9 +25,9 @@ import Data.Default import Data.Foldable (foldl') import Data.Maybe-import Data.Scientific import Data.Text (Text) +import qualified Formatting as F import qualified Data.Map as M import qualified Data.Text as T import qualified Network.Wreq as W@@ -46,10 +46,10 @@ tickerPct :: Ticker -> TickerNode tickerPct t = pctLabel where- diff = toRealFloat $ 100 * (t ^. close - t ^. open)- pct = diff / toRealFloat (t ^. open)+ diff = 100 * (t ^. close - t ^. open)+ pct = diff / t ^. open - pctText = formatTickerPct (fromFloatDigits pct) <> "%"+ pctText = formatTickerPct pct <> "%" pctColor | abs pct < 0.01 = rgbHex "#428FE0" | pct > 0 = rgbHex "#51A39A"@@ -170,7 +170,7 @@ handleSubscription :: AppEnv -> [Text] -> Text -> IO TickerEvt handleSubscription env pairs action = do- liftIO . atomically $ writeTChan (env^.channel) req+ atomically $ writeTChan (env ^. channel) req return TickerIgnore where subscription pair = T.toLower pair <> "@miniTicker"@@ -213,8 +213,8 @@ groupTickers :: TChan Ticker -> (TickerEvt -> IO a) -> IO () groupTickers channel sendMsg = void . forkIO . forever $ do- ticker <- liftIO . atomically $ readTChan channel- tickers <- collectJustM . liftIO . atomically $ tryReadTChan channel+ ticker <- atomically $ readTChan channel+ tickers <- collectJustM . atomically $ tryReadTChan channel sendMsg $ TickerUpdate (ticker : tickers) threadDelay $ 500 * 1000@@ -227,11 +227,11 @@ forM_ serverMsg $ \case MsgResponse resp -> sendMsg $ TickerResponse resp MsgError err -> sendMsg $ TickerError err- MsgTicker ticker -> liftIO . atomically $ writeTChan groupChannel ticker+ MsgTicker ticker -> atomically $ writeTChan groupChannel ticker sendWs :: (Show a, ToJSON a) => TChan a -> WS.Connection -> IO () sendWs channel connection = forever $ do- msg <- liftIO . atomically $ readTChan channel+ msg <- atomically $ readTChan channel WS.sendTextData connection (encode msg) main :: IO ()@@ -243,6 +243,7 @@ where config = [ appWindowTitle "Ticker",+ appWindowIcon "./assets/images/icon.bmp", appTheme customDarkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appFontDef "Remix" "./assets/fonts/remixicon.ttf",@@ -271,11 +272,11 @@ xs <- collectJustM action return (x : xs) -formatTickerValue :: Scientific -> Text-formatTickerValue = T.pack . formatScientific Fixed (Just 8)+formatTickerValue :: FixedFloat -> Text+formatTickerValue = F.sformat (F.fixed 8) -formatTickerPct :: Scientific -> Text-formatTickerPct = T.pack . formatScientific Fixed (Just 2)+formatTickerPct :: FixedFloat -> Text+formatTickerPct = F.sformat (F.fixed 2) initialList :: [Text] initialList = ["BTCUSDT", "ETHBTC", "BNBBTC", "ADABTC", "DOTBTC", "XRPBTC",
examples/todo/Main.hs view
@@ -221,7 +221,7 @@ addNewTodo :: WidgetEnv s e -> TodoModel -> TodoModel addNewTodo wenv model = newModel where newTodo = model ^. activeTodo- & todoId .~ wenv ^. L.timestamp+ & todoId .~ currentTimeMs wenv newModel = model & todos .~ (newTodo : model ^. todos) @@ -245,6 +245,7 @@ where config = [ appWindowTitle "Todo list",+ appWindowIcon "./assets/images/icon.bmp", appTheme customDarkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appFontDef "Medium" "./assets/fonts/Roboto-Medium.ttf",
examples/todo/TodoTypes.hs view
@@ -31,7 +31,7 @@ deriving (Eq, Show, Enum) data Todo = Todo {- _todoId :: Int,+ _todoId :: Millisecond, _todoType :: TodoType, _status :: TodoStatus, _description :: Text
examples/tutorial/Tutorial01_Basics.hs view
@@ -62,6 +62,7 @@ where config = [ appWindowTitle "Tutorial 01 - Basics",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appInitEvent AppInit
examples/tutorial/Tutorial02_Styling.hs view
@@ -106,6 +106,7 @@ where config = [ appWindowTitle "Tutorial 02 - Styling",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appFontDef "Medium" "./assets/fonts/Roboto-Medium.ttf",
examples/tutorial/Tutorial03_LifeCycle.hs view
@@ -22,7 +22,7 @@ import qualified Monomer.Lens as L data ListItem = ListItem {- _ts :: Int,+ _ts :: Millisecond, _text :: Text } deriving (Eq, Show) @@ -59,7 +59,8 @@ keystroke [("Enter", AddItem)] $ hstack [ label "Description:", spacer,- textField_ newItemText [placeholder "Write here!"], -- `nodeKey` "description",+ textField_ newItemText [placeholder "Write here!"]+ `nodeKey` "description", spacer, button "Add" AddItem `styleBasic` [paddingH 5]@@ -89,7 +90,7 @@ & items .~ removeIdx idx (model ^. items)] _ -> [] where- newItem = ListItem (wenv ^. L.timestamp) (model ^. newItemText)+ newItem = ListItem (currentTimeMs wenv) (model ^. newItemText) removeIdx :: Int -> [a] -> [a] removeIdx idx lst = part1 ++ drop 1 part2 where@@ -101,6 +102,7 @@ where config = [ appWindowTitle "Tutorial 03 - Merging",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appInitEvent AppInit
examples/tutorial/Tutorial04_Tasks.hs view
@@ -52,7 +52,7 @@ `nodeVisible` (model ^. selected == idx) imageSet = hstack [ numberedImage "https://picsum.photos/id/1020/800/600" 1,- numberedImage "https://picsum.photos/id/1047/800/600" 2,+ numberedImage "https://picsum.photos/id/1043/800/600" 2, numberedImage "https://picsum.photos/id/1047/800/600" 3, numberedImage "https://picsum.photos/id/1025/800/600" 4, numberedImage "https://picsum.photos/id/1080/800/600" 5,@@ -87,6 +87,7 @@ where config = [ appWindowTitle "Tutorial 04 - Tasks",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appFontDef "Bold" "./assets/fonts/Roboto-Bold.ttf"
examples/tutorial/Tutorial05_Producers.hs view
@@ -80,6 +80,7 @@ where config = [ appWindowTitle "Tutorial 05 - Producers",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appFontDef "Bold" "./assets/fonts/Roboto-Bold.ttf",
examples/tutorial/Tutorial06_Composite.hs view
@@ -150,6 +150,7 @@ where config = [ appWindowTitle "Tutorial 06 - Composite",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appInitEvent AppInit
examples/tutorial/Tutorial07_CustomWidget.hs view
@@ -168,6 +168,7 @@ where config = [ appWindowTitle "Tutorial 07 - Custom Widget",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf" ]
examples/tutorial/Tutorial08_Themes.hs view
@@ -115,6 +115,7 @@ where config = [ appWindowTitle "Tutorial 08 - Themes",+ appWindowIcon "./assets/images/icon.bmp", appTheme darkTheme, appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf", appInitEvent AppInit
monomer.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: monomer-version: 1.3.0.0+version: 1.4.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.@@ -156,7 +156,7 @@ c2hs build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ , OpenGLRaw ==3.3.* , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15 , base >=4.11 && <5@@ -172,14 +172,12 @@ , mtl >=2.1 && <2.3 , nanovg >=0.8 && <1.0 , process ==1.6.*- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , time >=1.8 && <1.16 , transformers >=0.5 && <0.7- , unordered-containers >=0.2.8 && <0.3 , vector >=0.12 && <0.14 , wreq >=0.5.2 && <0.6 if os(windows)@@ -202,7 +200,7 @@ ghc-options: -threaded build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ , OpenGLRaw ==3.3.* , aeson >=1.4 && <2.3 , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15@@ -220,14 +218,12 @@ , mtl >=2.1 && <2.3 , nanovg >=0.8 && <1.0 , process ==1.6.*- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , time >=1.8 && <1.16 , transformers >=0.5 && <0.7- , unordered-containers >=0.2.8 && <0.3 , vector >=0.12 && <0.14 , wreq >=0.5.2 && <0.6 default-language: Haskell2010@@ -246,7 +242,7 @@ ghc-options: -threaded build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ , OpenGLRaw ==3.3.* , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15 , base >=4.11 && <5@@ -264,14 +260,12 @@ , nanovg >=0.8 && <1.0 , process ==1.6.* , random >=1.1 && <1.3- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , time >=1.8 && <1.16 , transformers >=0.5 && <0.7- , unordered-containers >=0.2.8 && <0.3 , vector >=0.12 && <0.14 , wreq >=0.5.2 && <0.6 default-language: Haskell2010@@ -288,7 +282,6 @@ ghc-options: -threaded build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.* , OpenGLRaw ==3.3.* , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15@@ -307,14 +300,12 @@ , nanovg >=0.8 && <1.0 , process ==1.6.* , random >=1.1 && <1.3- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , time >=1.8 && <1.16 , transformers >=0.5 && <0.7- , unordered-containers >=0.2.8 && <0.3 , vector >=0.12 && <0.14 , wreq >=0.5.2 && <0.6 default-language: Haskell2010@@ -332,7 +323,7 @@ ghc-options: -threaded build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ , OpenGLRaw ==3.3.* , aeson >=1.4 && <2.3 , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15@@ -350,15 +341,12 @@ , mtl >=2.1 && <2.3 , nanovg >=0.8 && <1.0 , process ==1.6.*- , safe ==0.3.*- , scientific ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , 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@@ -377,7 +365,7 @@ ghc-options: -threaded build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ , OpenGLRaw ==3.3.* , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15 , base >=4.11 && <5@@ -394,14 +382,12 @@ , mtl >=2.1 && <2.3 , nanovg >=0.8 && <1.0 , process ==1.6.*- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , time >=1.8 && <1.16 , transformers >=0.5 && <0.7- , unordered-containers >=0.2.8 && <0.3 , vector >=0.12 && <0.14 , wreq >=0.5.2 && <0.6 default-language: Haskell2010@@ -425,7 +411,7 @@ ghc-options: -threaded build-depends: JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ , OpenGLRaw ==3.3.* , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15 , base >=4.11 && <5@@ -443,14 +429,12 @@ , nanovg >=0.8 && <1.0 , process ==1.6.* , random >=1.1 && <1.3- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6 , stm ==2.5.* , 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 , wreq >=0.5.2 && <0.6 default-language: Haskell2010@@ -461,6 +445,7 @@ other-modules: Monomer.Common.CursorIconSpec Monomer.Core.SizeReqSpec+ Monomer.Core.StyleUtilSpec Monomer.Graphics.UtilSpec Monomer.TestEventUtil Monomer.TestUtil@@ -512,9 +497,8 @@ OverloadedStrings ghc-options: -fwarn-incomplete-patterns build-depends:- HUnit ==1.6.*- , JuicyPixels >=3.2.9 && <3.5- , OpenGL ==3.0.*+ JuicyPixels >=3.2.9 && <3.5+ , OpenGLRaw ==3.3.* , async >=2.1 && <2.3 , attoparsec >=0.12 && <0.15 , base >=4.11 && <5@@ -522,7 +506,6 @@ , bytestring-to-vector ==0.3.* , containers >=0.5.11 && <0.7 , data-default >=0.5 && <0.8- , directory ==1.3.* , exceptions ==0.10.* , extra >=1.6 && <1.9 , formatting >=6.0 && <8.0@@ -533,15 +516,12 @@ , mtl >=2.1 && <2.3 , nanovg >=0.8 && <1.0 , process ==1.6.*- , safe ==0.3.* , sdl2 >=2.5.0 && <2.6- , silently ==1.2.* , stm ==2.5.* , text >=1.2 && <2.1 , text-show >=3.7 && <3.10 , time >=1.8 && <1.16 , transformers >=0.5 && <0.7- , unordered-containers >=0.2.8 && <0.3 , vector >=0.12 && <0.14 , wreq >=0.5.2 && <0.6 default-language: Haskell2010
src/Monomer.hs view
@@ -10,7 +10,7 @@ that should be imported by applications. To start using the library, it is recommended to check the-<https://github.com/fjvallarino/monomer#documentation tutorials>:+<https://github.com/fjvallarino/monomer#documentation tutorials>. If you don't want to use all the helper modules, or you don't want to import them unqualified, the modules you will need to import to create an application
src/Monomer/Common/BasicTypes.hs view
@@ -165,6 +165,14 @@ px2 = max rx . min (rx + rw) $ px py2 = max ry . min (ry + rh) $ py +-- | Returns a rect using the provided points as boundaries+rectFromPoints :: Point -> Point -> Rect+rectFromPoints (Point x1 y1) (Point x2 y2) = Rect x y w h where+ x = min x1 x2+ y = min y1 y2+ w = abs (x2 - x1)+ h = abs (y2 - y1)+ -- | Adds individual x, y, w and h coordinates to a rect. addToRect :: Rect -> Double -> Double -> Double -> Double -> Maybe Rect addToRect (Rect x y w h) l r t b = newRect where
src/Monomer/Core/Combinators.hs view
@@ -30,16 +30,40 @@ {-| Given two values, usually model, checks if merge is required for a given widget.-The first parameter corresponds to the old value, and the second to the new.++The first parameter usually corresponds to the current 'WidgetEnv', the second+to the old value/model, and the third to the new/model.++This is used, for example, by _composite_ and _box_. -}-class CmbMergeRequired t s | t -> s where- mergeRequired :: (s -> s -> Bool) -> t+class CmbMergeRequired t w s | t -> w s where+ mergeRequired :: (w -> s -> s -> Bool) -> t --- | Listener for the validation status of a field using a lens.+{-|+Listener for the validation status of a user input field using a lens.++Allows associating a flag to know if the input of a field with validation+settings is valid. This can be used with _textField_, _numericField_,+_dateField_ and _timeField_.++The flag can be used for styling the component according to the current status.+Beyond styling, its usage is needed for validation purposes. Taking+_numericField_ as an example, one can bind a 'Double' record field to it and set+a valid range from 0 to 100. When the user inputs 100, the record field will+reflect the correct value. If the user adds a 0 (the numericField showing 1000),+the record field will still have 100 because it's the last valid value. Since+there is not a way of indicating errors when using primitive types (a 'Double'+is just a number), we can rely on the flag to check its validity.+-} class CmbValidInput t s | t -> s where validInput :: ALens' s Bool -> t --- | Listener for the validation status of a field using an event handler.+{-|+Listener for the validation status of a user input field using an event handler,+avoiding the need of a lens.++Check 'CmbValidInput' for details.+-} class CmbValidInputV t e | t -> e where validInputV :: (Bool -> e) -> t @@ -49,6 +73,16 @@ selectOnFocus = selectOnFocus_ True selectOnFocus_ :: Bool -> t +{-|+Defines whether a widget prevents the user changing the value. Note that, in+contrast to a disabled widget, a read-only widget can still be focused and+still allows selecting and copying the value.+-}+class CmbReadOnly t where+ readOnly :: t+ readOnly = readOnly_ True+ readOnly_ :: Bool -> t+ -- | Defines whether a widget changes its size when the model changes. class CmbResizeOnChange t where resizeOnChange :: t@@ -249,6 +283,10 @@ textThroughline = textThroughline_ True textThroughline_ :: Bool -> t +-- | How to break texts into lines.+class CmbTextLineBreak t where+ textLineBreak :: LineBreak -> t+ -- | Does not apply any kind of resizing to fit to container. class CmbFitNone t where fitNone :: t@@ -302,8 +340,8 @@ thumbHoverColor :: Color -> t {-|-The thumb factor. For example, in slider this makes the thumb proportional-to the width of the slider.+The thumb factor. For example, in slider this makes the thumb proportional to+the width of the slider. -} class CmbThumbFactor t where thumbFactor :: Double -> t@@ -318,30 +356,71 @@ thumbVisible = thumbVisible_ True thumbVisible_ :: Bool -> t --- | The width color of a thumb, for example in a scroll.+-- | The width of a thumb, for example in a scroll. class CmbThumbWidth t where thumbWidth :: Double -> t +-- | The minimum size of a thumb, for example in a scroll.+class CmbThumbMinSize t where+ thumbMinSize :: Double -> t+ -- | Whether to show an alpha channel, for instance in color selector. class CmbShowAlpha t where showAlpha :: t showAlpha = showAlpha_ True showAlpha_ :: Bool -> t --- | Whether to ignore children events.+{-|+Whether to ignore children events.++By default low-level events (keyboard, mouse, clipboard, etc) traverse the whole+branch where the target widget is located in the widget tree, giving the chance+to each widget along the line to respond to the event.++In some cases it is desirable to restrict which widgets can handle an event. Two+different 'WidgetRequest's, which can be returned during event handling, exist+for this:++- 'IgnoreChildrenEvents': parent widgets always have the priority. If a widget+ returns this 'WidgetRequest' during event handling, its children widgets+ response will be ignored. For example, the _keystroke_ widget can be+ configured to return this when a keystroke combination matches.+- 'IgnoreParentEvents': if no parent widget requested 'IgnoreChildrenEvents', a+ widget can respond with 'IgnoreParentEvents' to have its response being the+ only one taking place. This is used, for example, by the _textArea_ widget to+ handle the tab key; without this, the default handler would pass focus to the+ next widget down the line.++Some of the stock widgets allow configuring this behavior (e.g, keystroke and+button).+-} class CmbIgnoreChildrenEvts t where ignoreChildrenEvts :: t ignoreChildrenEvts = ignoreChildrenEvts_ True ignoreChildrenEvts_ :: Bool -> t +-- | Whether to ignore parent events. Check 'CmbIgnoreChildrenEvts'.+class CmbIgnoreParentEvts t where+ ignoreParentEvts :: t+ ignoreParentEvts = ignoreParentEvts_ True+ ignoreParentEvts_ :: Bool -> t+ -- | On init event. class CmbOnInit t e | t -> e where onInit :: e -> t +-- | On init WidgetRequest.+class CmbOnInitReq t s e | t -> s e where+ onInitReq :: WidgetRequest s e -> t+ -- | On dispose event. class CmbOnDispose t e | t -> e where onDispose :: e -> t +-- | On dispose WidgetRequest.+class CmbOnDisposeReq t s e | t -> s e where+ onDisposeReq :: WidgetRequest s e -> t+ -- | On resize event. class CmbOnResize t e a | t -> e a where onResize :: (a -> e) -> t@@ -515,35 +594,93 @@ -- Style infixl 5 `styleBasic`+infixl 5 `styleBasicSet`+ infixl 5 `styleHover`+infixl 5 `styleHoverSet`+ infixl 5 `styleFocus`+infixl 5 `styleFocusSet`+ infixl 5 `styleFocusHover`+infixl 5 `styleFocusHoverSet`+ infixl 5 `styleActive`+infixl 5 `styleActiveSet`+ infixl 5 `styleDisabled`+infixl 5 `styleDisabledSet` --- | Basic style combinator, used mainly infix for widgets as a list.+{-|+Basic style combinator, mainly used infix with widgets.++Represents the default style of a widget. It serves as the base for all the+other style states when an attribute is not overriden.+-} class CmbStyleBasic t where+ -- | Merges the new basic style states with the existing ones. styleBasic :: t -> [StyleState] -> t+ -- | Sets the new basic style states overriding the existing ones.+ styleBasicSet :: t -> [StyleState] -> t --- | Hover style combinator, used mainly infix for widgets as a list.+{-|+Hover style combinator, mainly used infix with widgets.++Used when the widget is hovered with a pointing device.+-} class CmbStyleHover t where+ -- | Merges the new hover style states with the existing ones. styleHover :: t -> [StyleState] -> t+ -- | Sets the new hover style states overriding the existing ones.+ styleHoverSet :: t -> [StyleState] -> t --- | Focus style combinator, used mainly infix for widgets as a list.+{-|+Focus style combinator, mainly used infix with widgets.++Used when the widget has keyboard focus.+-} class CmbStyleFocus t where+ -- | Merges the new focus style states with the existing ones. styleFocus :: t -> [StyleState] -> t+ -- | Sets the new focus style states overriding the existing ones.+ styleFocusSet :: t -> [StyleState] -> t --- | Focus Hover style combinator, used mainly infix for widgets as a list.+{-|+Focus Hover style combinator, mainly used infix with widgets.++Used when the widget is both focused and hovered. In this situation the+attributes defined in focus and hover will be combined, with focus attributes+taking precedence. This style state allows for better control in cases when the+combination of focus and hover styles do not match expectations.+-} class CmbStyleFocusHover t where+ -- | Merges the new focus hover style states with the existing ones. styleFocusHover :: t -> [StyleState] -> t+ -- | Sets the new focus hover style states overriding the existing ones.+ styleFocusHoverSet :: t -> [StyleState] -> t --- | Active style combinator, used mainly infix for widgets as a list.+{-|+Active style combinator, mainly used infix with widgets.++Used when a mouse press was started in the widget and the pointer is inside its+boundaries.+-} class CmbStyleActive t where+ -- | Merges the new active style states with the existing ones. styleActive :: t -> [StyleState] -> t+ -- | Sets the new active style states overriding the existing ones.+ styleActiveSet :: t -> [StyleState] -> t --- | Disabled style combinator, used mainly infix for widgets as a list.+{-|+Disabled style combinator, mainly used infix with widgets.++Used when the _nodeEnabled_ attribute has been set to False.+-} class CmbStyleDisabled t where+ -- | Merges the new disabled style states with the existing ones. styleDisabled :: t -> [StyleState] -> t+ -- | Sets the new disabled style states overriding the existing ones.+ styleDisabledSet :: t -> [StyleState] -> t -- | Ignore theme settings and start with blank style. class CmbIgnoreTheme t where
src/Monomer/Core/SizeReq.hs view
@@ -13,6 +13,8 @@ module Monomer.Core.SizeReq ( SizeReqUpdater(..), clearExtra,+ clearExtraW,+ clearExtraH, fixedToMinW, fixedToMinH, fixedToMaxW,@@ -53,12 +55,29 @@ clearExtra :: SizeReqUpdater clearExtra (reqW, reqH) = (reqW & L.extra .~ 0, reqH & L.extra .~ 0) +-- | Clears the horizontal extra field of a pair of SizeReqs.+clearExtraW :: SizeReqUpdater+clearExtraW (reqW, reqH) = (reqW & L.extra .~ 0, reqH)++-- | Clears the vertical extra field of a pair of SizeReqs.+clearExtraH :: SizeReqUpdater+clearExtraH (reqW, reqH) = (reqW, reqH & L.extra .~ 0)++-- | Switches a SizeReq pair from fixed size to minimum size.+fixedToMin+ :: Double -- ^ The resize factor.+ -> SizeReqUpdater -- ^ The updated SizeReq.+fixedToMin fs (reqW, reqH) = (newReqW, newReqH) where+ (fixedW, fixedH) = (reqW ^. L.fixed, reqH ^. L.fixed)+ newReqW = SizeReq fixedW 0 fixedW fs+ newReqH = SizeReq fixedH 0 fixedH fs+ -- | Switches a SizeReq pair from fixed width to minimum width. fixedToMinW :: Double -- ^ The resize factor. -> SizeReqUpdater -- ^ The updated SizeReq.-fixedToMinW fw (SizeReq fixed _ _ _, reqH) = (newReqH, reqH) where- newReqH = SizeReq fixed 0 fixed fw+fixedToMinW fw (SizeReq fixed _ _ _, reqH) = (newReqW, reqH) where+ newReqW = SizeReq fixed 0 fixed fw -- | Switches a SizeReq pair from fixed height to minimum height. fixedToMinH@@ -67,12 +86,21 @@ fixedToMinH fh (reqW, SizeReq fixed _ _ _) = (reqW, newReqH) where newReqH = SizeReq fixed 0 fixed fh +-- | Switches a SizeReq pair from fixed size to maximum size.+fixedToMax+ :: Double -- ^ The resize factor.+ -> SizeReqUpdater -- ^ The updated SizeReq.+fixedToMax fs (reqW, reqH) = (newReqW, newReqH) where+ (fixedW, fixedH) = (reqW ^. L.fixed, reqH ^. L.fixed)+ newReqW = SizeReq 0 fixedW 0 fs+ newReqH = SizeReq 0 fixedH 0 fs+ -- | Switches a SizeReq pair from fixed width to maximum width. fixedToMaxW :: Double -- ^ The resize factor. -> SizeReqUpdater -- ^ The updated SizeReq.-fixedToMaxW fw (SizeReq fixed _ _ _, reqH) = (newReqH, reqH) where- newReqH = SizeReq 0 fixed 0 fw+fixedToMaxW fw (SizeReq fixed _ _ _, reqH) = (newReqW, reqH) where+ newReqW = SizeReq 0 fixed 0 fw -- | Switches a SizeReq pair from fixed height to maximum height. fixedToMaxH@@ -81,12 +109,21 @@ fixedToMaxH fh (reqW, SizeReq fixed _ _ _) = (reqW, newReqH) where newReqH = SizeReq 0 fixed 0 fh +-- | Switches a SizeReq pair from fixed size to expand size.+fixedToExpand+ :: Double -- ^ The resize factor.+ -> SizeReqUpdater -- ^ The updated SizeReq.+fixedToExpand fs (reqW, reqH) = (newReqW, newReqH) where+ (fixedW, fixedH) = (reqW ^. L.fixed, reqH ^. L.fixed)+ newReqW = SizeReq 0 fixedW fixedW fs+ newReqH = SizeReq 0 fixedH fixedH fs+ -- | Switches a SizeReq pair from fixed width to expand width. fixedToExpandW :: Double -- ^ The resize factor. -> SizeReqUpdater -- ^ The updated SizeReq.-fixedToExpandW fw (SizeReq fixed _ _ _, reqH) = (newReqH, reqH) where- newReqH = SizeReq 0 fixed fixed fw+fixedToExpandW fw (SizeReq fixed _ _ _, reqH) = (newReqW, reqH) where+ newReqW = SizeReq 0 fixed fixed fw -- | Switches a SizeReq pair from fixed height to expand height. fixedToExpandH
src/Monomer/Core/Style.hs view
@@ -177,6 +177,9 @@ instance CmbTextThroughline TextStyle where textThroughline_ through = def & L.throughline ?~ through +instance CmbTextLineBreak TextStyle where+ textLineBreak break = def & L.lineBreak ?~ break+ -- Padding instance CmbPadding Padding where@@ -360,6 +363,9 @@ instance CmbTextThroughline StyleState where textThroughline_ False = def textThroughline_ True = def & L.text ?~ textThroughline++instance CmbTextLineBreak StyleState where+ textLineBreak break = def & L.text ?~ textLineBreak break -- Padding instance CmbPadding StyleState where
src/Monomer/Core/StyleTypes.hs view
@@ -306,6 +306,15 @@ instance Monoid Radius where mempty = def +-- | Defines how to break texts into lines.+data LineBreak+ = OnSpaces+ | OnCharacters+ deriving (Eq, Ord, Enum, Show, Generic)++instance Default LineBreak where+ def = OnSpaces+ -- | Text related definitions. data TextStyle = TextStyle { _txsFont :: Maybe Font, -- ^ The font type.@@ -317,7 +326,8 @@ _txsOverline :: Maybe Bool, -- ^ True if overline should be displayed. _txsThroughline :: Maybe Bool, -- ^ True if throughline should be displayed. _txsAlignH :: Maybe AlignTH, -- ^ Horizontal alignment.- _txsAlignV :: Maybe AlignTV -- ^ Vertical alignment.+ _txsAlignV :: Maybe AlignTV, -- ^ Vertical alignment.+ _txsLineBreak :: Maybe LineBreak -- ^ Line break option. } deriving (Eq, Show, Generic) instance Default TextStyle where@@ -331,7 +341,8 @@ _txsOverline = Nothing, _txsThroughline = Nothing, _txsAlignH = Nothing,- _txsAlignV = Nothing+ _txsAlignV = Nothing,+ _txsLineBreak = Nothing } instance Semigroup TextStyle where@@ -345,7 +356,8 @@ _txsOverline = _txsOverline ts2 <|> _txsOverline ts1, _txsThroughline = _txsThroughline ts2 <|> _txsThroughline ts1, _txsAlignH = _txsAlignH ts2 <|> _txsAlignH ts1,- _txsAlignV = _txsAlignV ts2 <|> _txsAlignV ts1+ _txsAlignV = _txsAlignV ts2 <|> _txsAlignV ts1,+ _txsLineBreak = _txsLineBreak ts2 <|> _txsLineBreak ts1 } instance Monoid TextStyle where
src/Monomer/Core/StyleUtil.hs view
@@ -8,8 +8,10 @@ Helper functions for style types. -}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE Strict #-} module Monomer.Core.StyleUtil (@@ -25,6 +27,7 @@ styleFontColor, styleTextAlignH, styleTextAlignV,+ styleTextLineBreak, styleBgColor, styleFgColor, styleSndColor,@@ -42,7 +45,7 @@ mapStyleStates ) where -import Control.Lens ((&), (^.), (^?), (.~), (+~), (%~), (?~), _Just, non)+import Control.Lens import Data.Default import Data.Maybe import Data.Text (Text)@@ -58,64 +61,88 @@ instance CmbStyleBasic Style where styleBasic oldStyle states = newStyle where+ newStyle = oldStyle & L.basic <>~ maybeConcat states++ styleBasicSet oldStyle states = newStyle where newStyle = oldStyle & L.basic .~ maybeConcat states instance CmbStyleHover Style where styleHover oldStyle states = newStyle where+ newStyle = oldStyle & L.hover <>~ maybeConcat states++ styleHoverSet oldStyle states = newStyle where newStyle = oldStyle & L.hover .~ maybeConcat states instance CmbStyleFocus Style where styleFocus oldStyle states = newStyle where+ newStyle = oldStyle & L.focus <>~ maybeConcat states++ styleFocusSet oldStyle states = newStyle where newStyle = oldStyle & L.focus .~ maybeConcat states instance CmbStyleFocusHover Style where styleFocusHover oldStyle states = newStyle where+ newStyle = oldStyle & L.focusHover <>~ maybeConcat states++ styleFocusHoverSet oldStyle states = newStyle where newStyle = oldStyle & L.focusHover .~ maybeConcat states instance CmbStyleActive Style where styleActive oldStyle states = newStyle where+ newStyle = oldStyle & L.active <>~ maybeConcat states++ styleActiveSet oldStyle states = newStyle where newStyle = oldStyle & L.active .~ maybeConcat states instance CmbStyleDisabled Style where styleDisabled oldStyle states = newStyle where+ newStyle = oldStyle & L.disabled <>~ maybeConcat states++ styleDisabledSet oldStyle states = newStyle where newStyle = oldStyle & L.disabled .~ maybeConcat states instance CmbStyleBasic (WidgetNode s e) where- styleBasic node states = node & L.info . L.style .~ newStyle where- state = mconcat states- oldStyle = node ^. L.info . L.style- newStyle = oldStyle & L.basic ?~ state+ styleBasic node states = newNode where+ newNode = mergeNodeStyleState L.basic node states + styleBasicSet node states = newNode where+ newNode = setNodeStyleState L.basic node states+ instance CmbStyleHover (WidgetNode s e) where- styleHover node states = node & L.info . L.style .~ newStyle where- state = mconcat states- oldStyle = node ^. L.info . L.style- newStyle = oldStyle & L.hover ?~ state+ styleHover node states = newNode where+ newNode = mergeNodeStyleState L.hover node states + styleHoverSet node states = newNode where+ newNode = setNodeStyleState L.hover node states+ instance CmbStyleFocus (WidgetNode s e) where- styleFocus node states = node & L.info . L.style .~ newStyle where- state = mconcat states- oldStyle = node ^. L.info . L.style- newStyle = oldStyle & L.focus ?~ state+ styleFocus node states = newNode where+ newNode = mergeNodeStyleState L.focus node states + styleFocusSet node states = newNode where+ newNode = setNodeStyleState L.focus node states+ instance CmbStyleFocusHover (WidgetNode s e) where- styleFocusHover node states = node & L.info . L.style .~ newStyle where- state = mconcat states- oldStyle = node ^. L.info . L.style- newStyle = oldStyle & L.focusHover ?~ state+ styleFocusHover node states = newNode where+ newNode = mergeNodeStyleState L.focusHover node states + styleFocusHoverSet node states = newNode where+ newNode = setNodeStyleState L.focusHover node states+ instance CmbStyleActive (WidgetNode s e) where- styleActive node states = node & L.info . L.style .~ newStyle where- state = mconcat states- oldStyle = node ^. L.info . L.style- newStyle = oldStyle & L.active ?~ state+ styleActive node states = newNode where+ newNode = mergeNodeStyleState L.active node states + styleActiveSet node states = newNode where+ newNode = setNodeStyleState L.active node states+ instance CmbStyleDisabled (WidgetNode s e) where- styleDisabled node states = node & L.info . L.style .~ newStyle where- state = mconcat states- oldStyle = node ^. L.info . L.style- newStyle = oldStyle & L.disabled ?~ state+ styleDisabled node states = newNode where+ newNode = mergeNodeStyleState L.disabled node states + styleDisabledSet node states = newNode where+ newNode = setNodeStyleState L.disabled node states+ infixl 5 `nodeKey` infixl 5 `nodeEnabled` infixl 5 `nodeVisible`@@ -177,6 +204,11 @@ styleTextAlignV style = fromMaybe def alignV where alignV = style ^? L.text . _Just . L.alignV . _Just +-- | Returns the line break option of the given style state, or the+styleTextLineBreak :: StyleState -> LineBreak+styleTextLineBreak style = fromMaybe def lineBreak where+ lineBreak = style ^? L.text . _Just . L.lineBreak . _Just+ -- | Returns the background color of the given style state, or the default. styleBgColor :: StyleState -> Color styleBgColor style = fromMaybe def bgColor where@@ -306,6 +338,34 @@ br = maybe 0 _bsWidth (_brdRight border) bt = maybe 0 _bsWidth (_brdTop border) bb = maybe 0 _bsWidth (_brdBottom border)++mergeNodeStyleState+ :: Lens' Style (Maybe StyleState)+ -> WidgetNode s e+ -> [StyleState]+ -> WidgetNode s e+mergeNodeStyleState field node states = newNode where+ oldStyle = node ^. L.info . L.style+ oldState = oldStyle ^. field . non def+ !mcatStates = mconcat states+ !newStates = oldState <> mcatStates+ !newStyle = oldStyle+ & field ?~ newStates+ !newNode = node+ & L.info . L.style .~ newStyle++setNodeStyleState+ :: Lens' Style (Maybe StyleState)+ -> WidgetNode s e+ -> [StyleState]+ -> WidgetNode s e+setNodeStyleState field node states = newNode where+ oldStyle = node ^. L.info . L.style+ !newStates = mconcat states+ !newStyle = oldStyle+ & field ?~ newStates+ !newNode = node+ & L.info . L.style .~ newStyle justDef :: (Default a) => Maybe a -> a justDef val = fromMaybe def val
src/Monomer/Core/ThemeTypes.hs view
@@ -84,6 +84,7 @@ _thsScrollThumbColor :: Color, _thsScrollBarWidth :: Double, _thsScrollThumbWidth :: Double,+ _thsScrollThumbMinSize :: Double, _thsScrollThumbRadius :: Double, _thsScrollWheelRate :: Rational, _thsSeparatorLineWidth :: Double,@@ -140,6 +141,7 @@ _thsScrollThumbColor = darkGray, _thsScrollBarWidth = 10, _thsScrollThumbWidth = 8,+ _thsScrollThumbMinSize = 25, _thsScrollThumbRadius = 0, _thsScrollWheelRate = 10, _thsSeparatorLineWidth = 1,@@ -192,10 +194,11 @@ _thsRadioWidth = _thsRadioWidth t2, _thsScrollOverlay = _thsScrollOverlay t2, _thsScrollFollowFocus = _thsScrollFollowFocus t2,- _thsScrollBarColor = _thsScrollBarColor t2,- _thsScrollThumbColor = _thsScrollThumbColor t2,- _thsScrollBarWidth = _thsScrollBarWidth t2,- _thsScrollThumbWidth = _thsScrollThumbWidth t2,+ _thsScrollBarColor = _thsScrollBarColor t2,+ _thsScrollThumbColor = _thsScrollThumbColor t2,+ _thsScrollBarWidth = _thsScrollBarWidth t2,+ _thsScrollThumbWidth = _thsScrollThumbWidth t2,+ _thsScrollThumbMinSize = _thsScrollThumbMinSize t2, _thsScrollThumbRadius = _thsScrollThumbRadius t2, _thsScrollWheelRate = _thsScrollWheelRate t2, _thsSeparatorLineWidth = _thsSeparatorLineWidth t2,
src/Monomer/Core/Themes/BaseTheme.hs view
@@ -131,13 +131,29 @@ normalFont = def & L.font ?~ Font "Regular" & L.fontSize ?~ FontSize 16- & L.fontSpaceV ?~ FontSpace 4+ & L.fontSpaceV ?~ FontSpace 2 titleFont :: TextStyle titleFont = def & L.font ?~ Font "Bold" & L.fontSize ?~ FontSize 20+ & L.fontSpaceV ?~ FontSpace 2 +dialogMsgBodyFont :: BaseThemeColors -> TextStyle+dialogMsgBodyFont themeMod = fontStyle where+ fontStyle = normalFont+ & L.fontColor ?~ dialogText themeMod++externalLinkFont :: BaseThemeColors -> TextStyle+externalLinkFont themeMod = fontStyle where+ fontStyle = normalFont+ & L.fontColor ?~ externalLinkBasic themeMod++labelFont :: BaseThemeColors -> TextStyle+labelFont themeMod = fontStyle <> textLeft where+ fontStyle = normalFont+ & L.fontColor ?~ labelText themeMod+ btnStyle :: BaseThemeColors -> StyleState btnStyle themeMod = def & L.text ?~ (normalFont & L.fontColor ?~ btnText themeMod) <> textCenter@@ -153,15 +169,18 @@ & L.border ?~ border 1 (btnMainBgBasic themeMod) textInputStyle :: BaseThemeColors -> StyleState-textInputStyle themeMod = def- & L.text ?~ (normalFont & L.fontColor ?~ inputText themeMod)- & L.bgColor ?~ inputBgBasic themeMod- & L.fgColor ?~ inputFgBasic themeMod- & L.sndColor ?~ (inputSndBasic themeMod & L.a .~ 0.6)- & L.hlColor ?~ inputSelBasic themeMod- & L.border ?~ border 1 (inputBorder themeMod)- & L.radius ?~ radius 4- & L.padding ?~ padding 8+textInputStyle themeMod = style where+ textStyle = normalFont+ & L.fontColor ?~ inputText themeMod+ style = def+ & L.text ?~ textStyle+ & L.bgColor ?~ inputBgBasic themeMod+ & L.fgColor ?~ inputFgBasic themeMod+ & L.sndColor ?~ (inputSndBasic themeMod & L.a .~ 0.6)+ & L.hlColor ?~ inputSelBasic themeMod+ & L.border ?~ border 1 (inputBorder themeMod)+ & L.radius ?~ radius 4+ & L.padding ?~ padding 8 numericInputStyle :: BaseThemeColors -> StyleState numericInputStyle themeMod = textInputStyle themeMod@@ -226,8 +245,7 @@ & L.dialogCloseIconStyle . L.sizeReqH ?~ width 16 & L.dialogButtonsStyle . L.padding ?~ padding 20 <> paddingT 10 & L.dialogMsgBodyStyle . L.padding ?~ padding 20- & L.dialogMsgBodyStyle . L.text- ?~ (normalFont & L.fontColor ?~ dialogText themeMod)+ & L.dialogMsgBodyStyle . L.text ?~ dialogMsgBodyFont themeMod & L.dialogMsgBodyStyle . L.sizeReqW ?~ maxWidth 600 & L.dropdownStyle .~ textInputStyle themeMod & L.dropdownStyle . L.fgColor ?~ inputIconFg themeMod@@ -236,9 +254,8 @@ & L.dropdownListStyle . L.bgColor ?~ slMainBg themeMod & L.dropdownItemStyle .~ selectListItemStyle themeMod & L.dropdownItemSelectedStyle .~ selectListItemSelectedStyle themeMod- & L.externalLinkStyle . L.text ?~ (normalFont & L.fontColor ?~ externalLinkBasic themeMod)- & L.labelStyle . L.text- ?~ (normalFont & L.fontColor ?~ labelText themeMod) <> textLeft+ & L.externalLinkStyle . L.text ?~ externalLinkFont themeMod+ & L.labelStyle . L.text ?~ labelFont themeMod & L.numericFieldStyle .~ numericInputStyle themeMod & L.optionBtnOnStyle .~ btnMainStyle themeMod & L.optionBtnOffStyle .~ btnStyle themeMod@@ -255,6 +272,7 @@ & L.scrollThumbColor .~ scrollThumbBasic themeMod & L.scrollBarWidth .~ 8 & L.scrollThumbWidth .~ 8+ & L.scrollThumbMinSize .~ 25 & L.scrollThumbRadius .~ 4 & L.scrollWheelRate .~ 10 & L.separatorLineWidth .~ 1
src/Monomer/Core/Util.hs view
@@ -8,11 +8,12 @@ Helper functions for Core types. -}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} module Monomer.Core.Util where -import Control.Lens ((&), (^.), (.~), (?~))+import Control.Lens ((&), (^.), (^?), (.~), (?~), _Just) import Data.Maybe import Data.Text (Text) import Data.Typeable (cast)@@ -29,32 +30,78 @@ import qualified Monomer.Core.Lens as L --- | Returns the path associated to a given key, if any.+-- | Returns the 'Path' associated to a given 'WidgetKey', if any. The search is+-- restricted to the parent _Composite_. pathFromKey :: WidgetEnv s e -> WidgetKey -> Maybe Path pathFromKey wenv key = fmap (^. L.info . L.path) node where node = Map.lookup key (wenv ^. L.widgetKeyMap) --- | Returns the widgetId associated to a given key, if any.+-- | Returns the 'WidgetId' associated to a given 'WidgetKey', if any. The+-- search is restricted to the parent _Composite_. widgetIdFromKey :: WidgetEnv s e -> WidgetKey -> Maybe WidgetId widgetIdFromKey wenv key = fmap (^. L.info . L.widgetId) node where node = Map.lookup key (wenv ^. L.widgetKeyMap) --- | Returns the node info associated to a given path.-findWidgetByPath+-- | Returns the 'WidgetNodeInfo' associated to the given 'WidgetKey', if any.+-- The search is restricted to the parent _Composite_.+nodeInfoFromKey :: WidgetEnv s e -> WidgetKey -> Maybe WidgetNodeInfo+nodeInfoFromKey wenv key = path >>= nodeInfoFromPath wenv where+ path = pathFromKey wenv key++-- | Returns the 'WidgetId' associated to the given 'Path', if any.+widgetIdFromPath :: WidgetEnv s e -> Path -> Maybe WidgetId+widgetIdFromPath wenv path = mwni ^? _Just . L.widgetId where+ branch = wenv ^. L.findBranchByPath $ path+ mwni = Seq.lookup (length branch - 1) branch++{-# DEPRECATED findWidgetIdFromPath "Use 'widgetIdFromPath' instead." #-}+findWidgetIdFromPath :: WidgetEnv s e -> Path -> Maybe WidgetId+findWidgetIdFromPath = widgetIdFromPath++-- | Returns the 'WidgetNodeInfo' associated to the given 'Path', if any.+nodeInfoFromPath :: WidgetEnv s e -> Path -> Maybe WidgetNodeInfo+nodeInfoFromPath wenv path = mwni where+ branch = wenv ^. L.findBranchByPath $ path+ mwni = Seq.lookup (length branch - 1) branch++-- | Returns the 'WidgetNodeInfo' associated to a given 'Path'. The path will be+-- searched for starting from the provided 'WidgetNode'.+findChildNodeInfoByPath :: WidgetEnv s e -> WidgetNode s e -> Path -> Maybe WidgetNodeInfo-findWidgetByPath wenv node target = mnode where+findChildNodeInfoByPath wenv node target = mnode where branch = widgetFindBranchByPath (node ^. L.widget) wenv node target mnode = case Seq.lookup (length branch - 1) branch of Just child | child ^. L.path == target -> Just child _ -> Nothing --- | Returns the complete node info branch associated to a given path.-findWidgetBranchByPath+{-# DEPRECATED findWidgetByPath "Use 'findChildNodeInfoByPath' instead." #-}+findWidgetByPath+ :: WidgetEnv s e -> WidgetNode s e -> Path -> Maybe WidgetNodeInfo+findWidgetByPath = findChildNodeInfoByPath++-- | Returns the 'WidgetNodeInfo' branch associated to a given 'Path'. The path+-- will be searched for starting from the provided 'WidgetNode'.+findChildBranchByPath :: WidgetEnv s e -> WidgetNode s e -> Path -> Seq WidgetNodeInfo-findWidgetBranchByPath wenv node target = branch where+findChildBranchByPath wenv node target = branch where branch = widgetFindBranchByPath (node ^. L.widget) wenv node target +{-# DEPRECATED findWidgetBranchByPath "Use 'findChildBranchByPath' instead." #-}+findWidgetBranchByPath+ :: WidgetEnv s e -> WidgetNode s e -> Path -> Seq WidgetNodeInfo+findWidgetBranchByPath = findChildBranchByPath++-- | Returns the first parent 'WidgetNodeInfo' of the 'Path' that matches the+-- given 'WidgetType'.+findParentNodeInfoByType+ :: WidgetEnv s e -> Path -> WidgetType -> Maybe WidgetNodeInfo+findParentNodeInfoByType wenv path wtype = wniParent where+ isMatch wni = wni ^. L.widgetType == wtype+ branch = wenv ^. L.findBranchByPath $ path+ matches = Seq.filter isMatch branch+ wniParent = Seq.lookup (length matches - 1) matches+ -- | Helper functions that associates False to Vertical and True to Horizontal. getLayoutDirection :: Bool -> LayoutDirection getLayoutDirection False = LayoutVertical@@ -285,6 +332,14 @@ -- | Checks if the platform is macOS isMacOS :: WidgetEnv s e -> Bool isMacOS wenv = _weOs wenv == "Mac OS X"++{-|+Returns the current time in milliseconds. Adds appStartTs and timestamp fields+from 'WidgetEnv' and converts the result to the expected 'Integral' type.+-}+currentTimeMs :: Integral a => WidgetEnv s e -> a+currentTimeMs wenv = fromIntegral ts where+ ts = wenv ^. L.appStartTs + wenv ^. L.timestamp -- | Returns a string description of a node and its children. widgetTreeDesc :: Int -> WidgetNode s e -> String
src/Monomer/Core/WidgetTypes.hs view
@@ -10,7 +10,9 @@ -} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-}+{-# Language GeneralizedNewtypeDeriving #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE Strict #-} @@ -24,7 +26,9 @@ import Data.String (IsString(..)) import Data.Text (Text) import Data.Typeable (Typeable, typeOf)+import Data.Word (Word64) import GHC.Generics+import TextShow import qualified Data.Text as T @@ -34,9 +38,18 @@ import Monomer.Event.Types import Monomer.Graphics.Types --- | Time ellapsed since startup-type Timestamp = Int+{-|+Time expressed in milliseconds. Useful for representing the time of events,+length of intervals, start time of the application and ellapsed time since its+start. +It can be converted from/to other numeric types using the standard functions.+-}+newtype Millisecond = Millisecond {+ unMilliseconds :: Word64+} deriving newtype (Eq, Ord, Enum, Bounded, Num, Real, Integral, Read, Show, Default, TextShow)+ deriving (Generic)+ -- | Type constraints for a valid model type WidgetModel s = Typeable s -- | Type constraints for a valid event@@ -94,8 +107,8 @@ requests (tasks, clipboard, etc). -} data WidgetId = WidgetId {- _widTs :: Int, -- ^ The timestamp when the instance was created.- _widPath :: Path -- ^ The path at creation time.+ _widTs :: Millisecond, -- ^ The timestamp when the instance was created.+ _widPath :: Path -- ^ The path at creation time. } deriving (Eq, Show, Ord, Generic) instance Default WidgetId where@@ -139,7 +152,7 @@ {-| WidgetRequests are the way a widget can perform side effects, such as changing cursor icons, get/set the clipboard and perform asynchronous tasks. These-requests are included as part of a WidgetResult in different points in the+requests are included as part of a 'WidgetResult' in different points in the lifecycle of a widget. -} data WidgetRequest s e@@ -160,7 +173,7 @@ | SetFocus WidgetId -- | Requests the clipboard contents. It will be received as a SystemEvent. | GetClipboard WidgetId- -- | Sets the clipboard to the given ClipboardData.+ -- | Sets the clipboard to the given 'ClipboardData'. | SetClipboard ClipboardData -- | Sets the viewport that should be remain visible when an on-screen -- keyboard is displayed. Required for mobile.@@ -188,11 +201,11 @@ -- in order to reduce CPU usage. Widgets are responsible for requesting -- rendering at points of interest. Mouse (except mouse move) and keyboard -- events automatically generate render requests, but the result of a- -- WidgetTask or WidgetProducer does not.+ -- 'RunTask' or 'RunProducer' does not. | RenderOnce -- | Useful if a widget requires periodic rendering. An optional maximum -- number of frames can be provided.- | RenderEvery WidgetId Int (Maybe Int)+ | RenderEvery WidgetId Millisecond (Maybe Int) -- | Stops a previous periodic rendering request. | RenderStop WidgetId {-|@@ -293,6 +306,8 @@ _weOs :: Text, -- | Device pixel rate. _weDpr :: Double,+ -- | The timestamp in milliseconds when the application started.+ _weAppStartTs :: Millisecond, -- | Provides helper funtions for calculating text size. _weFontManager :: FontManager, -- | Returns the node info, and its parents', given a path from root.@@ -307,7 +322,11 @@ _weWindowSize :: Size, -- | The active map of shared data. _weWidgetShared :: MVar (Map Text WidgetShared),- -- | The active map of WidgetKey -> WidgetNode, if any.+ {-+ The active map of WidgetKey -> WidgetNode, if any. This map is restricted to+ to the parent 'Composite'. Do not use this map directly, rely instead on the+ 'widgetIdFromKey', 'nodeInfoFromKey' and 'nodeInfoFromPath' utility functions.+ -} _weWidgetKeyMap :: WidgetKeyMap s e, -- | The currently hovered path, if any. _weHoveredPath :: Maybe Path,@@ -325,9 +344,12 @@ _weModel :: s, -- | The input status, mainly mouse and keyboard. _weInputStatus :: InputStatus,- -- | The timestamp when this cycle started.- _weTimestamp :: Timestamp, {-|+ The timestamp in milliseconds when this event/message cycle started. This+ value starts from zero each time the application is run.+ -}+ _weTimestamp :: Millisecond,+ {-| Whether the theme changed in this cycle. Should be considered when a widget avoids merging as optimization, as the styles may have changed. -}@@ -404,8 +426,8 @@ {-| An instance of the widget in the widget tree, without specific type information. This allows querying for widgets that may be nested in Composites, which are not-visible as a regular "WidgetNode" because of possible type mismatches (see-"WidgetKeyMap").+visible as a regular 'WidgetNode' because of possible type mismatches (see+'WidgetKeyMap'). -} data WidgetInstanceNode = WidgetInstanceNode { -- | Information about the instance.
src/Monomer/Event/Core.hs view
@@ -39,6 +39,7 @@ isActionEvent SDL.TextInputEvent{} = True isActionEvent _ = False +-- | Configuration options for converting from an SDL event to a 'SystemEvent'. data ConvertEventsCfg = ConvertEventsCfg { _cecOs :: Text, -- ^ The host operating system. _cecDpr :: Double, -- ^ Device pixel rate.
src/Monomer/Event/Keyboard.hs view
@@ -46,6 +46,9 @@ keyUnknown :: KeyCode keyUnknown = getKeyCode SDL.KeycodeUnknown +keyEnter :: KeyCode+keyEnter = getKeyCode SDL.KeycodeReturn+ keyReturn :: KeyCode keyReturn = getKeyCode SDL.KeycodeReturn @@ -337,6 +340,56 @@ keyZ :: KeyCode keyZ = getKeyCode SDL.KeycodeZ +-- Key pad+keyPadDivide :: KeyCode+keyPadDivide = getKeyCode SDL.KeycodeKPDivide++keyPadMultiply :: KeyCode+keyPadMultiply = getKeyCode SDL.KeycodeKPMultiply++keyPadMinus :: KeyCode+keyPadMinus = getKeyCode SDL.KeycodeKPMinus++keyPadPlus :: KeyCode+keyPadPlus = getKeyCode SDL.KeycodeKPPlus++keyPadEnter :: KeyCode+keyPadEnter = getKeyCode SDL.KeycodeKPEnter++keyPadPeriod :: KeyCode+keyPadPeriod = getKeyCode SDL.KeycodeKPPeriod++-- Key pad numbers+keyPad0 :: KeyCode+keyPad0 = getKeyCode SDL.KeycodeKP0++keyPad1 :: KeyCode+keyPad1 = getKeyCode SDL.KeycodeKP1++keyPad2 :: KeyCode+keyPad2 = getKeyCode SDL.KeycodeKP2++keyPad3 :: KeyCode+keyPad3 = getKeyCode SDL.KeycodeKP3++keyPad4 :: KeyCode+keyPad4 = getKeyCode SDL.KeycodeKP4++keyPad5 :: KeyCode+keyPad5 = getKeyCode SDL.KeycodeKP5++keyPad6 :: KeyCode+keyPad6 = getKeyCode SDL.KeycodeKP6++keyPad7 :: KeyCode+keyPad7 = getKeyCode SDL.KeycodeKP7++keyPad8 :: KeyCode+keyPad8 = getKeyCode SDL.KeycodeKP8++keyPad9 :: KeyCode+keyPad9 = getKeyCode SDL.KeycodeKP9+ -- -- Mod keys@@ -657,3 +710,53 @@ isKeyZ :: KeyCode -> Bool isKeyZ = (== keyZ)++-- Key pad+isKeyPadDivide :: KeyCode -> Bool+isKeyPadDivide = (== keyPadDivide)++isKeyPadMultiply :: KeyCode -> Bool+isKeyPadMultiply = (== keyPadMultiply)++isKeyPadMinus :: KeyCode -> Bool+isKeyPadMinus = (== keyPadMinus)++isKeyPadPlus :: KeyCode -> Bool+isKeyPadPlus = (== keyPadPlus)++isKeyPadEnter :: KeyCode -> Bool+isKeyPadEnter = (== keyPadEnter)++isKeyPadPeriod :: KeyCode -> Bool+isKeyPadPeriod = (== keyPadPeriod)++-- Key pad numbers+isKeyPad0 :: KeyCode -> Bool+isKeyPad0 = (== keyPad0)++isKeyPad1 :: KeyCode -> Bool+isKeyPad1 = (== keyPad1)++isKeyPad2 :: KeyCode -> Bool+isKeyPad2 = (== keyPad2)++isKeyPad3 :: KeyCode -> Bool+isKeyPad3 = (== keyPad3)++isKeyPad4 :: KeyCode -> Bool+isKeyPad4 = (== keyPad4)++isKeyPad5 :: KeyCode -> Bool+isKeyPad5 = (== keyPad5)++isKeyPad6 :: KeyCode -> Bool+isKeyPad6 = (== keyPad6)++isKeyPad7 :: KeyCode -> Bool+isKeyPad7 = (== keyPad7)++isKeyPad8 :: KeyCode -> Bool+isKeyPad8 = (== keyPad8)++isKeyPad9 :: KeyCode -> Bool+isKeyPad9 = (== keyPad9)
src/Monomer/Graphics/FFI.chs view
@@ -37,10 +37,11 @@ #include "fontmanager.h" --- | Vector of 4 strict elements+-- | Vector of 4 strict elements. data V4 a = V4 !a !a !a !a deriving (Show, Read, Eq, Ord) +-- | Bounds of a block of text. newtype Bounds = Bounds (V4 CFloat) deriving (Show, Read, Eq, Ord)@@ -62,6 +63,7 @@ pokeElemOff p' 2 c pokeElemOff p' 3 d +-- | Position of a glyph in a text string. data GlyphPosition = GlyphPosition { -- | Pointer of the glyph in the input string. str :: !(Ptr CChar),@@ -98,9 +100,11 @@ {#pointer *FMGglyphPosition as GlyphPositionPtr -> GlyphPosition#} +-- | Reads Bounds from a pointer. peekBounds :: Ptr CFloat -> IO Bounds peekBounds = peek . castPtr +-- | Allocates space for Bounds. allocaBounds :: (Ptr CFloat -> IO b) -> IO b allocaBounds f = alloca (\(p :: Ptr Bounds) -> f (castPtr p)) @@ -121,6 +125,8 @@ {# fun unsafe fmInit {`Double'} -> `FMContext' #} {# fun unsafe fmCreateFont {`FMContext', withCString*`Text', withCString*`Text'} -> `Int' #}++{# fun unsafe fmSetScale {`FMContext', `Double'} -> `()' #} {# fun unsafe fmFontFace {`FMContext', withCString*`Text'} -> `()' #}
src/Monomer/Graphics/FontManager.hs view
@@ -31,23 +31,26 @@ -- | Creates a font manager instance. makeFontManager- :: [FontDef] -- ^ The font definitions.- -> Double -- ^ The device pixel rate.+ :: [FontDef] -- ^ The font definitions.+ -> Double -- ^ The device pixel rate. -> IO FontManager -- ^ The created renderer. makeFontManager fonts dpr = do- ctx <- fmInit 1 --dpr+ ctx <- fmInit dpr validFonts <- foldM (loadFont ctx) [] fonts when (null validFonts) $ putStrLn "Could not find any valid fonts. Text size calculations will fail."- - return $ newManager ctx dpr -newManager :: FMContext -> Double -> FontManager-newManager ctx dpr = FontManager {..} where- computeTextMetrics font fontSize = unsafePerformIO $ do- setFont ctx dpr font fontSize def+ return $ newManager ctx++newManager :: FMContext -> FontManager+newManager ctx = FontManager {..} where+ computeTextMetrics font fontSize =+ computeTextMetrics_ 1 font fontSize++ computeTextMetrics_ scale font fontSize = unsafePerformIO $ do+ setFont ctx scale font fontSize def (asc, desc, lineh) <- fmTextMetrics ctx lowerX <- Seq.lookup 0 <$> fmTextGlyphPositions ctx 0 0 "x" let heightLowerX = case lowerX of@@ -55,24 +58,30 @@ Nothing -> realToFrac asc return $ TextMetrics {- _txmAsc = asc / dpr,- _txmDesc = desc / dpr,- _txmLineH = lineh / dpr,- _txmLowerX = realToFrac heightLowerX / dpr+ _txmAsc = asc,+ _txmDesc = desc,+ _txmLineH = lineh,+ _txmLowerX = realToFrac heightLowerX } - computeTextSize font fontSize fontSpaceH text = unsafePerformIO $ do- setFont ctx dpr font fontSize fontSpaceH+ computeTextSize font fontSize fontSpaceH text =+ computeTextSize_ 1 font fontSize fontSpaceH text++ computeTextSize_ scale font fontSize fontSpaceH text = unsafePerformIO $ do+ setFont ctx scale font fontSize fontSpaceH (x1, y1, x2, y2) <- if text /= "" then fmTextBounds ctx 0 0 text else do (asc, desc, lineh) <- fmTextMetrics ctx return (0, 0, 0, lineh) - return $ Size (realToFrac (x2 - x1) / dpr) (realToFrac (y2 - y1) / dpr)+ return $ Size (realToFrac (x2 - x1)) (realToFrac (y2 - y1)) - computeGlyphsPos font fontSize fontSpaceH text = unsafePerformIO $ do- setFont ctx dpr font fontSize fontSpaceH+ computeGlyphsPos font fontSize fontSpaceH text =+ computeGlyphsPos_ 1 font fontSize fontSpaceH text++ computeGlyphsPos_ scale font fontSize fontSpaceH text = unsafePerformIO $ do+ setFont ctx scale font fontSize fontSpaceH glyphs <- if text /= "" then fmTextGlyphPositions ctx 0 0 text else return Seq.empty@@ -81,12 +90,13 @@ where toGlyphPos chr glyph = GlyphPos { _glpGlyph = chr,- _glpXMin = realToFrac (glyphPosMinX glyph) / dpr,- _glpXMax = realToFrac (glyphPosMaxX glyph) / dpr,- _glpYMin = realToFrac (glyphPosMinY glyph) / dpr,- _glpYMax = realToFrac (glyphPosMaxY glyph) / dpr,- _glpW = realToFrac (glyphPosMaxX glyph - glyphPosMinX glyph) / dpr,- _glpH = realToFrac (glyphPosMaxY glyph - glyphPosMinY glyph) / dpr+ _glpX = realToFrac (glyphX glyph),+ _glpXMin = realToFrac (glyphPosMinX glyph),+ _glpXMax = realToFrac (glyphPosMaxX glyph),+ _glpYMin = realToFrac (glyphPosMinY glyph),+ _glpYMax = realToFrac (glyphPosMaxY glyph),+ _glpW = realToFrac (glyphPosMaxX glyph - glyphPosMinX glyph),+ _glpH = realToFrac (glyphPosMaxY glyph - glyphPosMinY glyph) } loadFont :: FMContext -> [Text] -> FontDef -> IO [Text]@@ -97,7 +107,8 @@ else putStrLn ("Failed to load font: " ++ T.unpack name) >> return fonts setFont :: FMContext -> Double -> Font -> FontSize -> FontSpace -> IO ()-setFont ctx dpr (Font name) (FontSize size) (FontSpace spaceH) = do+setFont ctx scale (Font name) (FontSize size) (FontSpace spaceH) = do+ fmSetScale ctx scale fmFontFace ctx name- fmFontSize ctx $ realToFrac $ size * dpr- fmTextLetterSpacing ctx $ realToFrac $ spaceH * dpr+ fmFontSize ctx $ realToFrac size+ fmTextLetterSpacing ctx $ realToFrac spaceH
src/Monomer/Graphics/NanoVGRenderer.hs view
@@ -113,6 +113,10 @@ newRenderer :: VG.Context -> Double -> IORef Env -> Renderer newRenderer c rdpr envRef = Renderer {..} where+ {-+ rdpr is used to let nanovg know the real device pixel rate.+ dpr is set to 1 to disable all NanoVGRenderer internal calculations.+ -} dpr = 1 beginFrame w h = do
src/Monomer/Graphics/RemixIcon.hs view
@@ -10,7 +10,7 @@ representative name. These code points can be used in labels or buttons to show icons instead of regular text. -Make sure to load the remixicon.ttf font in your application and set 'textFont'+Make sure to load the remixicon.ttf font in your application and set _textFont_ in the corresponding widget. Existing icons can be browsed in https://remixicon.com.
src/Monomer/Graphics/Text.hs view
@@ -135,10 +135,11 @@ fSize = styleFontSize style fSpcH = styleFontSpaceH style fSpcV = styleFontSpaceV style+ break = styleTextLineBreak style lineH = _txmLineH metrics !metrics = computeTextMetrics fontMgr font fSize- fitToWidth = fitLineToW fontMgr font fSize fSpcH fSpcV metrics+ fitToWidth = fitLineToW fontMgr font fSize fSpcH fSpcV metrics break helper acc line = (cLines <> newLines, newTop) where (cLines, cTop) = acc@@ -261,22 +262,25 @@ -> FontSpace -> FontSpace -> TextMetrics+ -> LineBreak -> Double -> Double -> TextTrim -> Text -> Seq TextLine-fitLineToW fontMgr font fSize fSpcH fSpcV metrics top width trim text = res where+fitLineToW fontMgr font fSize fSpcH fSpcV metrics break top width trim text = res where spaces = T.replicate 4 " " newText = T.replace "\t" spaces text !glyphs = computeGlyphsPos fontMgr font fSize fSpcH newText -- Do not break line on trailing spaces, they are removed in the next step -- In the case of KeepSpaces, lines with only spaces (empty looking) are valid keepTailSpaces = trim == TrimSpaces- groups = fitGroups (splitGroups glyphs) width keepTailSpaces+ groups+ | break == OnCharacters = splitGroups break width glyphs+ | otherwise = fitGroups (splitGroups break width glyphs) width keepTailSpaces resetGroups- | trim == TrimSpaces = fmap (resetGlyphs . trimGlyphs) groups- | otherwise = fmap resetGlyphs groups+ | trim == TrimSpaces = fmap trimGlyphs groups+ | otherwise = groups buildLine = buildTextLine font fSize fSpcH fSpcV metrics top res | text /= "" = Seq.mapWithIndex buildLine resetGroups@@ -413,26 +417,17 @@ isSpaceGroup Empty = False isSpaceGroup (g :<| gs) = isSpace (_glpGlyph g) -splitGroups :: Seq GlyphPos -> Seq GlyphGroup-splitGroups Empty = Empty-splitGroups glyphs = group <| splitGroups rest where+splitGroups :: LineBreak -> Double -> Seq GlyphPos -> Seq GlyphGroup+splitGroups _ _ Empty = Empty+splitGroups break width glyphs = group <| splitGroups break width rest where g :<| gs = glyphs groupWordFn = not . isWordDelimiter . _glpGlyph+ groupWidthFn g2 = _glpXMax g2 - _glpXMin g <= width+ atWord = break == OnSpaces (group, rest)- | isWordDelimiter (_glpGlyph g) = (Seq.singleton g, gs)- | otherwise = Seq.spanl groupWordFn glyphs--resetGlyphs :: Seq GlyphPos -> Seq GlyphPos-resetGlyphs Empty = Empty-resetGlyphs gs@(g :<| _) = resetGlyphsPos gs (_glpXMin g)--resetGlyphsPos :: Seq GlyphPos -> Double -> Seq GlyphPos-resetGlyphsPos Empty _ = Empty-resetGlyphsPos (g :<| gs) offset = newG <| resetGlyphsPos gs offset where- newG = g {- _glpXMin = _glpXMin g - offset,- _glpXMax = _glpXMax g - offset- }+ | atWord && isWordDelimiter (_glpGlyph g) = (Seq.singleton g, gs)+ | atWord = Seq.spanl groupWordFn glyphs+ | otherwise = Seq.spanl groupWidthFn glyphs trimGlyphs :: Seq GlyphPos -> Seq GlyphPos trimGlyphs glyphs = newGlyphs where
src/Monomer/Graphics/Types.hs view
@@ -140,6 +140,7 @@ -- | Information of a text glyph instance. data GlyphPos = GlyphPos { _glpGlyph :: {-# UNPACK #-} !Char, -- ^ The represented character.+ _glpX :: {-# UNPACK #-} !Double, -- ^ The x coordinate used for rendering. _glpXMin :: {-# UNPACK #-} !Double, -- ^ The min x coordinate. _glpXMax :: {-# UNPACK #-} !Double, -- ^ The max x coordinate. _glpYMin :: {-# UNPACK #-} !Double, -- ^ The min x coordinate.@@ -151,6 +152,7 @@ instance Default GlyphPos where def = GlyphPos { _glpGlyph = ' ',+ _glpX = 0, _glpXMin = 0, _glpXMax = 0, _glpYMin = 0,@@ -197,8 +199,8 @@ data TextLine = TextLine { _tlFont :: !Font, -- ^ The font name. _tlFontSize :: !FontSize, -- ^ The font size.- _tlFontSpaceH :: !FontSpace, -- ^ The font spacing.- _tlFontSpaceV :: !FontSpace, -- ^ The vertical line spacing.+ _tlFontSpaceH :: !FontSpace, -- ^ The font spacing.+ _tlFontSpaceV :: !FontSpace, -- ^ The vertical line spacing. _tlMetrics :: !TextMetrics, -- ^ The text metrics for the given font/size. _tlText :: !Text, -- ^ The represented text. _tlSize :: !Size, -- ^ The size the formatted text takes.@@ -228,20 +230,39 @@ -- | The definition of a loaded image. data ImageDef = ImageDef {- _idfName :: Text, -- ^ The logic name of the image.+ _idfName :: Text, -- ^ The logic name of the image. _idfSize :: Size, -- ^ The dimensions of the image. _idfImgData :: BS.ByteString, -- ^ The image data as RGBA 4-bytes blocks. _idfFlags :: [ImageFlag] -- ^ The image flags. } deriving (Eq, Show, Generic) --- | Text metrics related functions.+{-|+Text metrics related functions.++Two different versions of each function exist:++- Default one, without underscore, does not apply scaling.+- Version with a trailing underscore, that receives an extra scale argument.++In case the text is going to be rendered with a scale factor applied on+'Renderer' (by calling 'setScale'), it is recommended to apply the scale here+too (otherwise there will be differences in size and positioning). In most use+cases these functions will never be called, preferring the non underscore+versions.+-} data FontManager = FontManager { -- | Returns the text metrics of a given font and size. computeTextMetrics :: Font -> FontSize -> TextMetrics,+ -- | Returns the text metrics of a given font and size, applying scale.+ computeTextMetrics_ :: Double -> Font -> FontSize -> TextMetrics, -- | Returns the size of the line of text given font and size. computeTextSize :: Font -> FontSize -> FontSpace -> Text -> Size,+ -- | Returns the size of the line of text given font and size, applying scale.+ computeTextSize_ :: Double -> Font -> FontSize -> FontSpace -> Text -> Size, -- | Returns the glyphs of the line of text given font and size.- computeGlyphsPos :: Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos+ computeGlyphsPos :: Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos,+ -- | Returns the glyphs of the line of text given font and size, applying scale.+ computeGlyphsPos_ :: Double -> Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos } -- | Low level rendering definitions.
src/Monomer/Helper.hs view
@@ -9,6 +9,8 @@ Helper functions used across the library. They do not belong to any specific module and are not directly exported. -}+{-# LANGUAGE BangPatterns #-}+ module Monomer.Helper where import Control.Exception (SomeException, catch)@@ -20,7 +22,8 @@ -- | Concats a list of Monoids or returns Nothing if empty. maybeConcat :: Monoid a => [a] -> Maybe a maybeConcat [] = Nothing-maybeConcat lst = Just (mconcat lst)+maybeConcat lst = Just merged where+ !merged = mconcat lst -- | Runs an action until Nothing is returned, collecting the results in a list. collectJustM :: MonadIO m => m (Maybe a) -> m [a]@@ -68,3 +71,8 @@ -- | Catches any exception thrown by the provided action catchAny :: IO a -> (SomeException -> IO a) -> IO a catchAny = catch++-- | Returns Just the first item if the list is not empty, Nothing otherwise.+headMay :: [a] -> Maybe a+headMay [] = Nothing+headMay (x : _) = Just x
src/Monomer/Main/Core.hs view
@@ -19,11 +19,11 @@ startApp ) where -import Control.Concurrent (MVar, forkIO, forkOS, newMVar, threadDelay)+import Control.Concurrent import Control.Concurrent.STM.TChan (TChan, newTChanIO, readTChan, writeTChan)+import Control.Exception import Control.Lens ((&), (^.), (.=), (.~), use) import Control.Monad (unless, void, when)-import Control.Monad.Catch import Control.Monad.Extra import Control.Monad.State import Control.Monad.STM (atomically)@@ -32,9 +32,12 @@ import Data.Map (Map) import Data.List (foldl') import Data.Text (Text)+import Data.Time+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)+import Graphics.GL import qualified Data.Map as Map-import qualified Graphics.Rendering.OpenGL as GL+import qualified Data.Text as T import qualified SDL import qualified Data.Sequence as Seq @@ -71,18 +74,16 @@ data MainLoopArgs sp e ep = MainLoopArgs { _mlOS :: Text,- _mlRenderer :: Maybe Renderer, _mlTheme :: Theme,- _mlAppStartTs :: Int,+ _mlAppStartTs :: Millisecond, _mlMaxFps :: Int,- _mlLatestRenderTs :: Int,- _mlFrameStartTs :: Int,- _mlFrameAccumTs :: Int,+ _mlLatestRenderTs :: Millisecond,+ _mlFrameStartTs :: Millisecond,+ _mlFrameAccumTs :: Millisecond, _mlFrameCount :: Int, _mlExitEvents :: [e], _mlWidgetRoot :: WidgetNode sp ep,- _mlWidgetShared :: MVar (Map Text WidgetShared),- _mlChannel :: TChan (RenderMsg sp ep)+ _mlWidgetShared :: MVar (Map Text WidgetShared) } data RenderState s e = RenderState {@@ -134,7 +135,8 @@ dpr <- use L.dpr winSize <- use L.windowSize - let useRenderThread = fromMaybe True (_apcUseRenderThread config)+ let useRenderThreadFlag = fromMaybe True (_apcUseRenderThread config)+ let useRenderThread = useRenderThreadFlag && rtsSupportsBoundThreads let maxFps = fromMaybe 60 (_apcMaxFps config) let fonts = _apcFonts config let theme = fromMaybe def (_apcTheme config)@@ -142,18 +144,16 @@ let mainBtn = fromMaybe BtnLeft (_apcMainButton config) let contextBtn = fromMaybe BtnRight (_apcContextButton config) - startTs <- fmap fromIntegral SDL.ticks+ appStartTs <- getCurrentTimestamp model <- use L.mainModel os <- liftIO getPlatform widgetSharedMVar <- liftIO $ newMVar Map.empty- renderer <- if useRenderThread- then return Nothing- else liftIO $ Just <$> makeRenderer fonts dpr fontManager <- liftIO $ makeFontManager fonts dpr let wenv = WidgetEnv { _weOs = os, _weDpr = dpr,+ _weAppStartTs = appStartTs, _weFontManager = fontManager, _weFindBranchByPath = const Seq.empty, _weMainButton = mainBtn,@@ -170,7 +170,7 @@ _weMainBtnPress = Nothing, _weModel = model, _weInputStatus = def,- _weTimestamp = startTs,+ _weTimestamp = 0, _weThemeChanged = False, _weInTopLayer = const True, _weLayoutDirection = LayoutNone,@@ -180,33 +180,66 @@ let pathReadyRoot = widgetRoot & L.info . L.path .~ rootPath & L.info . L.widgetId .~ WidgetId (wenv ^. L.timestamp) rootPath+ let makeMainThreadRenderer = do+ renderer <- liftIO $ makeRenderer fonts dpr+ L.renderMethod .= Left renderer+ return RenderSetupSingle + setupRes <- if useRenderThread+ then do+ stpChan <- liftIO newTChanIO++ liftIO . void . forkOS $+ {-+ The wenv and widgetRoot values are not used, since they are replaced+ during MsgInit. Kept to avoid issues with the Strict pragma.+ -}+ startRenderThread stpChan channel window glCtx fonts dpr wenv widgetRoot++ setupRes <- liftIO . atomically $ readTChan stpChan++ case setupRes of+ RenderSetupMakeCurrentFailed msg -> do+ liftIO . putStrLn $ "Setup of the rendering thread failed: " ++ msg+ liftIO . putStrLn $ "Falling back to rendering in the main thread. "+ ++ "The content may not be updated while resizing the window."++ makeMainThreadRenderer+ _ -> do+ return RenderSetupMulti+ else do+ makeMainThreadRenderer+ handleResourcesInit (newWenv, newRoot, _) <- handleWidgetInit wenv pathReadyRoot + {-+ Deferred initialization step to account for Widgets that rely on OpenGL. They+ need the Renderer to be setup before handleWidgetInit is called, and it is+ safer to initialize the watcher after this happens.+ -}+ case setupRes of+ RenderSetupMulti -> do+ liftIO . atomically $ writeTChan channel (MsgInit newWenv newRoot)+ liftIO $ watchWindowResize channel+ _ -> return ()+ let loopArgs = MainLoopArgs { _mlOS = os,- _mlRenderer = renderer, _mlTheme = theme, _mlMaxFps = maxFps,- _mlAppStartTs = 0,+ _mlAppStartTs = appStartTs, _mlLatestRenderTs = 0,- _mlFrameStartTs = startTs,+ _mlFrameStartTs = 0, _mlFrameAccumTs = 0, _mlFrameCount = 0, _mlExitEvents = exitEvents, _mlWidgetRoot = newRoot,- _mlWidgetShared = widgetSharedMVar,- _mlChannel = channel+ _mlWidgetShared = widgetSharedMVar } L.mainModel .= _weModel newWenv - when useRenderThread $ do- liftIO $ watchWindowResize channel- liftIO . void . forkOS $- startRenderThread channel window glCtx fonts dpr newWenv newRoot- mainLoop window fontManager config loopArgs mainLoop@@ -219,7 +252,7 @@ mainLoop window fontManager config loopArgs = do let MainLoopArgs{..} = loopArgs - startTicks <- fmap fromIntegral SDL.ticks+ startTs <- getEllapsedTimestampSince _mlAppStartTs events <- SDL.pumpEvents >> SDL.pollEvents windowSize <- use L.windowSize@@ -237,7 +270,7 @@ currWinSize <- liftIO $ getViewportSize window dpr let Size rw rh = windowSize- let ts = startTicks - _mlFrameStartTs+ let ts = startTs - _mlFrameStartTs let eventsPayload = fmap SDL.eventPayload events let quit = SDL.QuitEvent `elem` eventsPayload @@ -264,8 +297,9 @@ let wenv = WidgetEnv { _weOs = _mlOS, _weDpr = dpr,+ _weAppStartTs = _mlAppStartTs, _weFontManager = fontManager,- _weFindBranchByPath = findWidgetBranchByPath wenv _mlWidgetRoot,+ _weFindBranchByPath = findChildBranchByPath wenv _mlWidgetRoot, _weMainButton = mainBtn, _weContextButton = contextBtn, _weTheme = _mlTheme,@@ -280,7 +314,7 @@ _weMainBtnPress = mainPress, _weModel = currentModel, _weInputStatus = inputStatus,- _weTimestamp = startTicks,+ _weTimestamp = startTs, _weThemeChanged = False, _weInTopLayer = const True, _weLayoutDirection = LayoutNone,@@ -305,37 +339,38 @@ handleResizeWidgets (seWenv, seRoot, Seq.empty) else return (seWenv, seRoot, Seq.empty) - endTicks <- fmap fromIntegral SDL.ticks+ endTs <- getEllapsedTimestampSince _mlAppStartTs -- Rendering- renderCurrentReq <- checkRenderCurrent startTicks _mlLatestRenderTs+ renderCurrentReq <- checkRenderCurrent startTs _mlLatestRenderTs - let useRenderThread = fromMaybe True (_apcUseRenderThread config) let renderEvent = any isActionEvent eventsPayload let winRedrawEvt = windowResized || windowExposed let renderNeeded = winRedrawEvt || renderEvent || renderCurrentReq - when (renderNeeded && useRenderThread) $- liftIO . atomically $ writeTChan _mlChannel (MsgRender newWenv newRoot)+ when renderNeeded $ do+ renderMethod <- use L.renderMethod - when (renderNeeded && not useRenderThread) $ do- let renderer = fromJust _mlRenderer- let bgColor = newWenv ^. L.theme . L.clearColor+ case renderMethod of+ Right renderChan -> do+ liftIO . atomically $ writeTChan renderChan (MsgRender newWenv newRoot)+ Left renderer -> do+ let bgColor = newWenv ^. L.theme . L.clearColor - liftIO $ renderWidgets window dpr renderer bgColor newWenv newRoot+ liftIO $ renderWidgets window dpr renderer bgColor newWenv newRoot + -- Used in the next rendering cycle L.renderRequested .= windowResized let fps = realToFrac _mlMaxFps let frameLength = round (1000000 / fps)- let remainingMs = endTicks - startTicks- let tempDelay = abs (frameLength - remainingMs * 1000)+ let remainingMs = endTs - startTs+ let tempDelay = abs (frameLength - fromIntegral remainingMs * 1000) let nextFrameDelay = min frameLength tempDelay- let latestRenderTs = if renderNeeded then startTicks else _mlLatestRenderTs+ let latestRenderTs = if renderNeeded then startTs else _mlLatestRenderTs let newLoopArgs = loopArgs {- _mlAppStartTs = _mlAppStartTs + ts, _mlLatestRenderTs = latestRenderTs,- _mlFrameStartTs = startTicks,+ _mlFrameStartTs = startTs, _mlFrameAccumTs = if newSecond then 0 else _mlFrameAccumTs + ts, _mlFrameCount = if newSecond then 0 else _mlFrameCount + 1, _mlWidgetRoot = newRoot@@ -350,9 +385,18 @@ unless shouldQuit (mainLoop window fontManager config newLoopArgs) +{-+Attempts to initialize a GL context in a separate OS thread to handle rendering+actions. This allows for continuous content updates when the user resizes the+window.++In case the setup fails, it notifies the parent process so it can fall back to+rendering in the main thread.+-} startRenderThread :: (Eq s, WidgetEvent e)- => TChan (RenderMsg s e)+ => TChan RenderSetupResult+ -> TChan (RenderMsg s e) -> SDL.Window -> SDL.GLContext -> [FontDef]@@ -360,12 +404,23 @@ -> WidgetEnv s e -> WidgetNode s e -> IO ()-startRenderThread channel window glCtx fonts dpr wenv root = do- SDL.glMakeCurrent window glCtx- renderer <- liftIO $ makeRenderer fonts dpr- fontMgr <- liftIO $ makeFontManager fonts dpr+startRenderThread setupChan msgChan window glCtx fonts dpr wenv root = do+ resp <- try $ SDL.glMakeCurrent window glCtx - waitRenderMsg channel window renderer fontMgr state+ case resp of+ Right{} -> do+ renderer <- liftIO $ makeRenderer fonts dpr+ fontMgr <- liftIO $ makeFontManager fonts dpr++ atomically $ writeTChan setupChan RenderSetupMulti++ waitRenderMsg msgChan window renderer fontMgr state+ Left (SDL.SDLCallFailed _ _ err) -> do+ let msg = T.unpack err+ atomically $ writeTChan setupChan (RenderSetupMakeCurrentFailed msg)+ Left e -> do+ let msg = displayException e+ atomically $ writeTChan setupChan (RenderSetupMakeCurrentFailed msg) where state = RenderState dpr wenv root @@ -377,10 +432,10 @@ -> FontManager -> RenderState s e -> IO ()-waitRenderMsg channel window renderer fontMgr state = do- msg <- liftIO . atomically $ readTChan channel+waitRenderMsg msgChan window renderer fontMgr state = do+ msg <- atomically $ readTChan msgChan newState <- handleRenderMsg window renderer fontMgr state msg- waitRenderMsg channel window renderer fontMgr newState+ waitRenderMsg msgChan window renderer fontMgr newState handleRenderMsg :: (Eq s, WidgetEvent e)@@ -390,6 +445,9 @@ -> RenderState s e -> RenderMsg s e -> IO (RenderState s e)+handleRenderMsg window renderer fontMgr state (MsgInit newWenv newRoot) = do+ let RenderState dpr _ _ = state+ return (RenderState dpr newWenv newRoot) handleRenderMsg window renderer fontMgr state (MsgRender tmpWenv newRoot) = do let RenderState dpr _ _ = state let newWenv = tmpWenv@@ -434,13 +492,10 @@ Size dwW dwH <- getDrawableSize window Size vpW vpH <- getViewportSize window dpr - let position = GL.Position 0 0- let size = GL.Size (round dwW) (round dwH)-- GL.viewport GL.$= (position, size)+ glViewport 0 0 (round dwW) (round dwH) - GL.clearColor GL.$= clearColor4- GL.clear [GL.ColorBuffer]+ glClearColor r g b a+ glClear GL_COLOR_BUFFER_BIT beginFrame renderer vpW vpH widgetRender (widgetRoot ^. L.widget) wenv widgetRoot renderer@@ -459,8 +514,7 @@ r = fromIntegral (clearColor ^. L.r) / 255 g = fromIntegral (clearColor ^. L.g) / 255 b = fromIntegral (clearColor ^. L.b) / 255- a = clearColor ^. L.a- clearColor4 = GL.Color4 r g b (realToFrac a)+ a = realToFrac (clearColor ^. L.a) watchWindowResize :: TChan (RenderMsg s e) -> IO () watchWindowResize channel = do@@ -473,7 +527,7 @@ atomically $ writeTChan channel (MsgResize newSize) _ -> return () -checkRenderCurrent :: (MonomerM s e m) => Int -> Int -> m Bool+checkRenderCurrent :: (MonomerM s e m) => Millisecond -> Millisecond -> m Bool checkRenderCurrent currTs renderTs = do renderCurrent <- use L.renderRequested schedule <- use L.renderSchedule@@ -483,14 +537,14 @@ requiresRender = renderScheduleReq currTs renderTs renderNext schedule = any requiresRender schedule -renderScheduleReq :: Int -> Int -> RenderSchedule -> Bool+renderScheduleReq :: Millisecond -> Millisecond -> RenderSchedule -> Bool renderScheduleReq currTs renderTs schedule = required where RenderSchedule _ start ms _ = schedule stepCount = floor (fromIntegral (currTs - start) / fromIntegral ms) stepTs = start + ms * stepCount required = renderTs < stepTs -renderScheduleActive :: Int -> RenderSchedule -> Bool+renderScheduleActive :: Millisecond -> RenderSchedule -> Bool renderScheduleActive currTs schedule = scheduleActive where RenderSchedule _ start ms count = schedule stepCount = floor (fromIntegral (currTs - start) / fromIntegral ms)@@ -507,3 +561,13 @@ isMouseEntered :: [SDL.EventPayload] -> Bool isMouseEntered eventsPayload = not status where status = null [ e | e@SDL.WindowGainedMouseFocusEvent {} <- eventsPayload ]++getCurrentTimestamp :: MonadIO m => m Millisecond+getCurrentTimestamp = toMs <$> liftIO getCurrentTime+ where+ toMs = floor . (1e3 *) . nominalDiffTimeToSeconds . utcTimeToPOSIXSeconds++getEllapsedTimestampSince :: MonadIO m => Millisecond -> m Millisecond+getEllapsedTimestampSince start = do+ ts <- getCurrentTimestamp+ return (ts - start)
src/Monomer/Main/Handlers.hs view
@@ -39,7 +39,6 @@ import Data.Sequence (Seq(..), (|>)) import Data.Text (Text) import Data.Typeable (Typeable, typeOf)-import Safe (headMay) import SDL (($=)) import qualified Data.Map as Map@@ -52,7 +51,8 @@ import Monomer.Core import Monomer.Event-import Monomer.Helper (seqStartsWith)+import Monomer.Graphics+import Monomer.Helper (headMay, seqStartsWith) import Monomer.Main.Types import Monomer.Main.Util @@ -89,7 +89,7 @@ let (curWenv, curRoot, curReqs) = curStep let target = fromMaybe focused evtTarget let curWidget = curRoot ^. L.widget- let targetWni = evtTarget >>= findWidgetByPath curWenv curRoot+ let targetWni = evtTarget >>= findChildNodeInfoByPath curWenv curRoot let targetWid = (^. L.widgetId) <$> targetWni when (isOnEnter evt) $@@ -108,7 +108,7 @@ & L.hoveredPath .~ hoveredPath & L.mainBtnPress .~ mainBtnPress & L.inputStatus .~ inputStatus- let findBranchByPath path = findWidgetBranchByPath tmpWenv curRoot path+ let findBranchByPath path = findChildBranchByPath tmpWenv curRoot path let newWenv = tmpWenv & L.findBranchByPath .~ findBranchByPath (wenv2, root2, reqs2) <- handleSystemEvent newWenv curRoot evt target@@ -260,7 +260,7 @@ -> m (HandlerStep s e) -- ^ Updated state/"HandlerStep". handleResizeWidgets previousStep = do windowSize <- use L.windowSize- resizeCheckFn <- makeResizeChechFn+ resizeCheckFn <- makeResizeCheckFn let viewport = Rect 0 0 (windowSize ^. L.w) (windowSize ^. L.h) let (wenv, root, reqs) = previousStep@@ -277,7 +277,7 @@ return (wenv2, root2, reqs <> reqs2) where- makeResizeChechFn = do+ makeResizeCheckFn = do resizeRequests <- use L.resizeRequests paths <- mapM getWidgetIdPath resizeRequests let parts = Set.fromDistinctAscList . drop 1 . toList . Seq.inits@@ -518,7 +518,7 @@ handleRenderEvery :: MonomerM s e m => WidgetId- -> Int+ -> Millisecond -> Maybe Int -> HandlerStep s e -> m (HandlerStep s e)@@ -548,9 +548,12 @@ handleRemoveRendererImage :: MonomerM s e m => Text -> HandlerStep s e -> m (HandlerStep s e) handleRemoveRendererImage name previousStep = do- renderChannel <- use L.renderChannel+ renderMethod <- use L.renderMethod - liftIO . atomically $ writeTChan renderChannel (MsgRemoveImage name)+ case renderMethod of+ Left renderer -> liftIO $ deleteImage renderer name+ Right chan -> liftIO . atomically $ writeTChan chan (MsgRemoveImage name)+ return previousStep handleExitApplication@@ -662,9 +665,17 @@ -> HandlerStep s e -> m (HandlerStep s e) handleRunInRenderThread widgetId path handler previousStep = do- renderChannel <- use L.renderChannel+ renderMethod <- use L.renderMethod - handleRunTask widgetId path (taskWrapper renderChannel) previousStep+ task <- case renderMethod of+ Left renderer -> do+ -- Force running in main thread to avoid issues with OpenGL+ result <- liftIO handler+ return (return result)+ Right chan -> do+ return $ liftIO (taskWrapper chan)++ handleRunTask widgetId path task previousStep where taskWrapper renderChannel = do msgChan <- newTChanIO@@ -829,7 +840,7 @@ restartPath = fromMaybe emptyPath overlay candidateWni = widgetFindNextFocus widget wenv widgetRoot dir start fromRootWni = widgetFindNextFocus widget wenv widgetRoot dir restartPath- focusWni = fromMaybe def (findWidgetByPath wenv widgetRoot start)+ focusWni = fromMaybe def (findChildNodeInfoByPath wenv widgetRoot start) nextFocus = candidateWni <|> fromRootWni <|> Just focusWni dropNonParentWidgetId
src/Monomer/Main/Platform.hs view
@@ -22,6 +22,7 @@ getDisplayDPI ) where +import Control.Exception (finally) import Control.Monad (void) import Control.Monad.Extra (whenJust) import Control.Monad.State@@ -38,6 +39,8 @@ import qualified SDL.Input.Mouse as Mouse import qualified SDL.Raw as Raw import qualified SDL.Raw.Error as SRE+import qualified SDL.Internal.Types as SIT+import qualified SDL.Video.Renderer as SVR import Monomer.Common import Monomer.Core.StyleTypes@@ -65,10 +68,15 @@ platform <- getPlatform initDpiAwareness- factor <- case platform of++ baseFactor <- case platform of "Windows" -> getWindowsFactor "Linux" -> getLinuxFactor _ -> return 1 -- macOS++ let factor+ | disableAutoScale = 1+ | otherwise = baseFactor let (winW, winH) = (factor * fromIntegral baseW, factor * fromIntegral baseH) window <-@@ -91,6 +99,8 @@ | platform `elem` ["Windows", "Linux"] = (scaleFactor, 1 / scaleFactor) | otherwise = (scaleFactor * contentRatio, 1 / scaleFactor) -- macOS + setWindowIcon window config+ whenJust (_apcWindowTitle config) $ \title -> SDL.windowTitle window $= title @@ -123,6 +133,7 @@ } compositingFlag = fromMaybe False (_apcDisableCompositing config) userScaleFactor = fromMaybe 1 (_apcScaleFactor config)+ disableAutoScale = _apcDisableAutoScale config == Just True (baseW, baseH) = case _apcWindowState config of Just (MainWindowNormal size) -> size _ -> defaultWindowSize@@ -135,6 +146,16 @@ Just MainWindowMaximized -> True _ -> False +setWindowIcon :: SDL.Window -> AppConfig e -> IO ()+setWindowIcon (SIT.Window winPtr) config =+ forM_ (_apcWindowIcon config) $ \iconPath -> do+ iconSurface <- SVR.loadBMP (T.unpack iconPath)+ let SVR.Surface iconSurfacePtr _ = iconSurface+ finally+ -- Note: this can use the high-level setWindowIcon once it is available (https://github.com/haskell-game/sdl2/pull/243)+ (Raw.setWindowIcon winPtr iconSurfacePtr)+ (SVR.freeSurface iconSurface)+ -- | Destroys the provided window, shutdowns the video subsystem and SDL. detroySDLWindow :: SDL.Window -> IO () detroySDLWindow window = do@@ -194,28 +215,32 @@ vdpi <- peek pvdpi return (realToFrac ddpi, realToFrac hdpi, realToFrac vdpi) --- | Returns the default resize factor for Windows+-- | Returns the default resize factor for Windows. getWindowsFactor :: IO Double-getWindowsFactor = do- (ddpi, hdpi, vdpi) <- getDisplayDPI- return (hdpi / 96)+getWindowsFactor = max 1 <$> getDisplayDPIFactor -{-|-Returns a resizing factor to handle HiDPI on Linux. Currently only tested on-Wayland (Ubuntu 21.04).--}+-- | Returns the default resize factor for Linux. getLinuxFactor :: IO Double-getLinuxFactor =+getLinuxFactor = do+ dpiFactor <- getDisplayDPIFactor+ alloca $ \pmode -> do Raw.getCurrentDisplayMode 0 pmode mode <- peek pmode+ let width = Raw.displayModeW mode- -- Applies scale in half steps (1, 1.5, 2, etc)- let baseFactor = 2 * fromIntegral width / 1920+ let detectedDPI+ | dpiFactor > 0 = dpiFactor+ | width <= 1920 = 1+ | otherwise = 2 - if width <= 1920- then return 1- else return (fromIntegral (ceiling baseFactor) / 2)+ return detectedDPI++-- | Returns DPI scaling factor using SDL_GetDisplayDPI.+getDisplayDPIFactor :: IO Double+getDisplayDPIFactor = do+ (ddpi, hdpi, vdpi) <- getDisplayDPI+ return (hdpi / 96) setDisableCompositorHint :: Bool -> IO () setDisableCompositorHint disable = void $
src/Monomer/Main/Types.hs view
@@ -47,11 +47,19 @@ -- | Messages received by the rendering thread. data RenderMsg s e- = MsgRender (WidgetEnv s e) (WidgetNode s e)+ = MsgInit (WidgetEnv s e) (WidgetNode s e)+ | MsgRender (WidgetEnv s e) (WidgetNode s e) | MsgResize Size | MsgRemoveImage Text | forall i . MsgRunInRender (TChan i) (IO i) +-- | Result from attempting to set up the secondary rendering thread.+data RenderSetupResult+ = RenderSetupSingle+ | RenderSetupMulti+ | RenderSetupMakeCurrentFailed String+ deriving (Eq, Show)+ {-| Requirements for periodic rendering by a widget. Start time is stored to calculate next frame based on the step ms. A maximum number of repetitions may@@ -59,8 +67,8 @@ -} data RenderSchedule = RenderSchedule { _rsWidgetId :: WidgetId,- _rsStart :: Int,- _rsMs :: Int,+ _rsStart :: Millisecond,+ _rsMs :: Millisecond, _rsRepeat :: Maybe Int } deriving (Eq, Show, Generic) @@ -93,8 +101,8 @@ _mcDpr :: Double, -- | Event pixel rate. _mcEpr :: Double,- -- | Event pixel rate.- _mcRenderChannel :: TChan (RenderMsg s e),+ -- | Renderer instance or communication channel with the render thread.+ _mcRenderMethod :: Either Renderer (TChan (RenderMsg s e)), -- | Input status (mouse and keyboard). _mcInputStatus :: InputStatus, -- | Cursor icons (a stack is used because of parent -> child relationship).@@ -153,6 +161,8 @@ _apcWindowResizable :: Maybe Bool, -- | Whether the main window has a border. _apcWindowBorder :: Maybe Bool,+ -- | Path to an icon file in BMP format.+ _apcWindowIcon :: Maybe Text, -- | Whether a separate render thread should be used. Defaults to True. _apcUseRenderThread :: Maybe Bool, {-|@@ -163,11 +173,15 @@ _apcMaxFps :: Maybe Int, {-| Scale factor to apply. This factor only affects the content, not the size of- the window. It is applied in addition to the OS zoom in plaforms where it is- reliably detected (i.e., system scaling may not be detected reliably on Linux)+ the window. It is applied in addition to the detected display scaling. -} _apcScaleFactor :: Maybe Double, {-|+ Whether display scaling detection should not be attempted. If set to True, the+ display scale will be set to 1. This works together with 'appScaleFactor'.+ -}+ _apcDisableAutoScale :: Maybe Bool,+ {-| Available fonts to the application. An empty list will make it impossible to render text. -}@@ -200,9 +214,11 @@ _apcWindowTitle = Nothing, _apcWindowResizable = Nothing, _apcWindowBorder = Nothing,+ _apcWindowIcon = Nothing, _apcUseRenderThread = Nothing, _apcMaxFps = Nothing, _apcScaleFactor = Nothing,+ _apcDisableAutoScale = Nothing, _apcFonts = [], _apcTheme = Nothing, _apcInitEvent = [],@@ -222,9 +238,11 @@ _apcWindowTitle = _apcWindowTitle a2 <|> _apcWindowTitle a1, _apcWindowResizable = _apcWindowResizable a2 <|> _apcWindowResizable a1, _apcWindowBorder = _apcWindowBorder a2 <|> _apcWindowBorder a1,+ _apcWindowIcon = _apcWindowIcon a2 <|> _apcWindowIcon a1, _apcUseRenderThread = _apcUseRenderThread a2 <|> _apcUseRenderThread a1, _apcMaxFps = _apcMaxFps a2 <|> _apcMaxFps a1, _apcScaleFactor = _apcScaleFactor a2 <|> _apcScaleFactor a1,+ _apcDisableAutoScale = _apcDisableAutoScale a2 <|> _apcDisableAutoScale a1, _apcFonts = _apcFonts a1 ++ _apcFonts a2, _apcTheme = _apcTheme a2 <|> _apcTheme a1, _apcInitEvent = _apcInitEvent a1 ++ _apcInitEvent a2,@@ -265,16 +283,32 @@ _apcWindowBorder = Just border } +-- | Path to an icon file in BMP format.+appWindowIcon :: Text -> AppConfig e+appWindowIcon path = def {+ _apcWindowIcon = Just path+}+ {-| Performs rendering on the main thread. On macOS and Windows this also disables continuous rendering on window resize, but in some Linux configurations it still works. -This option is useful when OpenGL driver issues prevent normal startup showing-the "Unable to make GL context current" error.+This configuration option was originally available to handle: -It can also be used for single threaded applications (without -threaded).+ - OpenGL driver issues which prevented normal startup showing the "Unable to+ make GL context current" error.+ - Single threaded applications (without -threaded) which cannot use forkOS.++This flag is no longer necessary for those cases, since the library will:++ - Attempt to fall back to rendering on the main thread if setting up a+ secondary rendering thread fails.+ - Will not attempt to set up a secondary rendering thread if the runtime does+ not support bound threads (i.e. compiled without the -threaded flag). -}+{-# DEPRECATED appRenderOnMainThread+ "Should no longer be needed. Check appRenderOnMainThread's Haddock page." #-} appRenderOnMainThread :: AppConfig e appRenderOnMainThread = def { _apcUseRenderThread = Just False@@ -291,13 +325,56 @@ } {-|-Scale factor to apply. This factor only affects the content, not the size of the-window. It is applied in addition to the OS zoom in plaforms where it is-reliably detected (i.e., system scaling may not be detected reliably on Linux).+Scale factor to apply to the viewport. This factor only affects the content, not+the size of the window. It is applied in addition to the detected display scale+factor, and can be useful if the detected value is not the desired. -} appScaleFactor :: Double -> AppConfig e appScaleFactor factor = def { _apcScaleFactor = Just factor+}++{-|+Whether display scaling detection should not be attempted. If set to True, the+display scale will be set to 1. This flag does not cause an effect on macOS.++Disabling auto scaling also affects window size on Linux and Windows in the+cases where the library would have applied scaling. This happens because window+and viewport size are the same in those operating systems. Window size can be+adjusted with 'appWindowState'.++The logic for detecting display scaling varies depending on the platform:++__macOS__++Scaling can be detected based on the window size and viewport size; the ratio+between these two give the scaling factor.++Using window and viewport size for detecting DPI only works on macOS; both+Windows and Linux return the same value for window and viewport size.++__Windows__++SDL_GetDisplayDPI returns the DPI of the screen, and dividing by 96 gives the+scaling factor. This factor is used to scale the window size and the content.++__Linux__++The situation is more complex, since SDL_GetDisplayDPI does not always return+valid information. There is not a practical DPI/scale detection solution that+works for all combinations of Linux display servers and window managers. Even+when using the most popular window managers, the scaling factor may be handled+differently by the distribution (GNOME in Ubuntu). For a reference of some of+the existing options for DPI scaling detection, check here:+https://wiki.archlinux.org/title/HiDPI.++Considering the above, when SDL_GetDisplayDPI fails, the library assumes that a+screen width larger than 1920 belongs to an HiDPI display and uses a scale+factor of 2. This factor is used to scale the window size and the content.+-}+appDisableAutoScale :: Bool -> AppConfig e+appDisableAutoScale disable = def {+ _apcDisableAutoScale = Just disable } {-|
src/Monomer/Main/Util.hs view
@@ -21,15 +21,14 @@ import Control.Monad.State import Data.Default import Data.Maybe-import Safe (headMay) import qualified Data.Sequence as Seq import qualified Data.Map as Map-import qualified Graphics.Rendering.OpenGL as GL import qualified SDL import Monomer.Core import Monomer.Event+import Monomer.Helper (headMay) import Monomer.Main.Platform import Monomer.Main.Types import Monomer.Widgets.Util.Widget@@ -52,7 +51,7 @@ _mcWindowSize = winSize, _mcDpr = dpr, _mcEpr = epr,- _mcRenderChannel = channel,+ _mcRenderMethod = Right channel, _mcInputStatus = def, _mcCursorStack = [], _mcFocusedWidgetId = def,
src/Monomer/Widgets.hs view
@@ -9,12 +9,13 @@ Widgets module, grouping and re-exporting all the existing widgets. -} module Monomer.Widgets (+ -- * Composite widget module Monomer.Widgets.Composite,-+ -- * Animation module Monomer.Widgets.Animation.Fade, module Monomer.Widgets.Animation.Slide, module Monomer.Widgets.Animation.Types,-+ -- * Containers module Monomer.Widgets.Containers.Alert, module Monomer.Widgets.Containers.Box, module Monomer.Widgets.Containers.Confirm,@@ -30,7 +31,7 @@ module Monomer.Widgets.Containers.ThemeSwitch, module Monomer.Widgets.Containers.Tooltip, module Monomer.Widgets.Containers.ZStack,-+ -- * Single widgets module Monomer.Widgets.Singles.Button, module Monomer.Widgets.Singles.Checkbox, module Monomer.Widgets.Singles.ColorPicker,
src/Monomer/Widgets/Animation/Fade.hs view
@@ -54,7 +54,7 @@ -} data FadeCfg e = FadeCfg { _fdcAutoStart :: Maybe Bool,- _fdcDuration :: Maybe Int,+ _fdcDuration :: Maybe Millisecond, _fdcOnFinished :: [e] } deriving (Eq, Show) @@ -80,7 +80,7 @@ _fdcAutoStart = Just start } -instance CmbDuration (FadeCfg e) Int where+instance CmbDuration (FadeCfg e) Millisecond where duration dur = def { _fdcDuration = Just dur }@@ -92,7 +92,7 @@ data FadeState = FadeState { _fdsRunning :: Bool,- _fdsStartTs :: Int+ _fdsStartTs :: Millisecond } deriving (Eq, Show, Generic) instance Default FadeState where@@ -141,7 +141,7 @@ autoStart = fromMaybe False (_fdcAutoStart config) duration = fromMaybe 500 (_fdcDuration config) period = 20- steps = duration `div` period+ steps = fromIntegral $ duration `div` period finishedReq node = delayedMessage node AnimationFinished duration renderReq wenv node = req where
src/Monomer/Widgets/Animation/Slide.hs view
@@ -66,7 +66,7 @@ data SlideCfg e = SlideCfg { _slcDirection :: Maybe SlideDirection, _slcAutoStart :: Maybe Bool,- _slcDuration :: Maybe Int,+ _slcDuration :: Maybe Millisecond, _slcOnFinished :: [e] } deriving (Eq, Show) @@ -94,7 +94,7 @@ _slcAutoStart = Just start } -instance CmbDuration (SlideCfg e) Int where+instance CmbDuration (SlideCfg e) Millisecond where duration dur = def { _slcDuration = Just dur }@@ -122,7 +122,7 @@ data SlideState = SlideState { _slsRunning :: Bool,- _slsStartTs :: Int+ _slsStartTs :: Millisecond } deriving (Eq, Show, Generic) instance Default SlideState where@@ -174,7 +174,7 @@ autoStart = fromMaybe False (_slcAutoStart config) duration = fromMaybe 500 (_slcDuration config) period = 20- steps = duration `div` period+ steps = fromIntegral $ duration `div` period finishedReq node = delayedMessage node AnimationFinished duration renderReq wenv node = req where
src/Monomer/Widgets/Composite.hs view
@@ -11,13 +11,15 @@ the need to implement a lower level widget. It can comunicate with its parent component by reporting events. -Requires two main functions:+Requires two functions: - UI Builder: creates the widget tree based on the provided Widget Environment and model. This widget tree is made of other widgets, in general combinations of containers and singles. - Event Handler: processes user defined events which are raised by the widgets created when building the UI.++Composite is discussed in detail in the tutorials. -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-}@@ -42,14 +44,16 @@ CompositeEvent, MergeRequired, MergeReqsHandler,- CompositeCustomModelBuilder,+ MergeEventsHandler,+ MergeModelHandler, EventHandler, UIBuilder, TaskHandler, ProducerHandler, CompMsgUpdate, compositeMergeReqs,- customModelBuilder,+ compositeMergeEvents,+ compositeMergeModel, -- * Constructors composite,@@ -93,26 +97,40 @@ type CompositeEvent e = WidgetEvent e -- | Checks if merging the composite is required.-type MergeRequired s- = s -- ^ Old composite model.- -> s -- ^ New composite model- -> Bool -- ^ True if merge is required.+type MergeRequired s e+ = WidgetEnv s e -- ^ Widget environment.+ -> s -- ^ Old composite model.+ -> s -- ^ New composite model+ -> Bool -- ^ True if merge is required. -- | Generates requests during the merge process.-type MergeReqsHandler s e+type MergeReqsHandler s e sp = WidgetEnv s e -- ^ Widget environment. -> WidgetNode s e -- ^ New widget node. -> WidgetNode s e -- ^ Old widget node.- -> s -- ^ The current model.+ -> sp -- ^ Parent model.+ -> s -- ^ Old composite model.+ -> s -- ^ New composite model. -> [WidgetRequest s e] -- ^ The list of requests. --- | Creates a custom composite model from the parent model.-type CompositeCustomModelBuilder s sp- = sp -- ^ Parent model.- -> s -- ^ Old custom composite model.- -> s -- ^ New custom composite model.- -> s -- ^ Custom composite model.+-- | Generates events during the merge process.+type MergeEventsHandler s e sp+ = WidgetEnv s e -- ^ Widget environment.+ -> WidgetNode s e -- ^ New widget node.+ -> WidgetNode s e -- ^ Old widget node.+ -> sp -- ^ Parent model.+ -> s -- ^ Old composite model.+ -> s -- ^ New composite model.+ -> [e] -- ^ The list of events. +-- | Allows updating the composite model with information from the parent model.+type MergeModelHandler s e sp+ = WidgetEnv s e -- ^ Widget environment.+ -> sp -- ^ Parent model.+ -> s -- ^ Old composite model.+ -> s -- ^ New composite model.+ -> s -- ^ Updated composite model.+ -- | Handles a composite event and returns a set of responses. type EventHandler s e sp ep = WidgetEnv s e@@ -193,51 +211,53 @@ {-| Configuration options for composite: +- 'onInit': event to raise when the widget is created. Useful for initializing+ required resources.+- 'onDispose': event to raise when the widget is disposed. Useful for freeing+ acquired resources.+- 'onResize': event to raise when the size of the widget changes.+- 'onChange': event to raise when the model changes. The value passed to the+ provided event is the previous version of the model. The current version of+ the model is always available as a parameter in the _handleEvent_ function.+- 'onChangeReq': 'WidgetRequest' to generate when the model changes.+- 'onEnabledChange': event to raise when the enabled status changes.+- 'onVisibleChange': event to raise when the visibility changes. - 'mergeRequired': indicates if merging is necessary for this widget. In case the UI build process references information outside the model, it can be used to signal that merging is required even if the model has not changed. It can also be used as a performance tweak if the changes do not require rebuilding the UI.-- 'onInit': event to raise when the widget is created. Useful for performing all- kinds of initialization.-- 'onDispose': event to raise when the widget is disposed. Used to free- resources.-- 'onResize': event to raise when the size of the widget changes.-- 'onChange': event to raise when the size of the model changes.-- 'onChangeReq': 'WidgetRequest' to generate when the size of the widget- changes.-- 'onEnabledChange': event to raise when the enabled status changes.-- 'onVisibleChange': event to raise when the visibility changes. - 'compositeMergeReqs': functions to generate WidgetRequests during the merge process. Since merge is already handled by Composite (by merging its tree), this is complementary for the cases when more control, and the previous version of the widget tree, is required. For example, it is used in 'Monomer.Widgets.Containers.Confirm' to set the focus on its Accept button- when visibility is restored (usually means it was brought to the front in a- zstack, and the visibility flag of the previous version needs to be checked).-- 'customModelBuilder': function for extracting a custom model from the current- parent model and the previous composite model. Useful when the composite needs- a more complex model than what the user is binding.+ when visibility is restored (this usually means it was brought to the front in+ a zstack, and the visibility flag of the previous version needs to be+ checked).+- 'compositeMergeModel': Allows updating the composite model with information+ from the parent model. Useful when the composite needs a more complex model+ than what the user is binding. -} data CompositeCfg s e sp ep = CompositeCfg {- _cmcModelBuilder :: Maybe (CompositeCustomModelBuilder s sp),- _cmcMergeRequired :: Maybe (MergeRequired s),- _cmcMergeReqs :: [MergeReqsHandler s e],- _cmcOnInit :: [e],- _cmcOnDispose :: [e],+ _cmcMergeRequired :: Maybe (MergeRequired s e),+ _cmcMergeReqs :: [MergeReqsHandler s e sp],+ _cmcMergeModel :: Maybe (MergeModelHandler s e sp),+ _cmcOnInitReq :: [WidgetRequest s e],+ _cmcOnDisposeReq :: [WidgetRequest s e], _cmcOnResize :: [Rect -> e],- _cmcOnChangeReq :: [s -> WidgetRequest sp ep],+ _cmcOnChangeReq :: [s -> WidgetRequest s e], _cmcOnEnabledChange :: [e], _cmcOnVisibleChange :: [e] } instance Default (CompositeCfg s e sp ep) where def = CompositeCfg {- _cmcModelBuilder = Nothing,+ _cmcMergeModel = Nothing, _cmcMergeRequired = Nothing, _cmcMergeReqs = [],- _cmcOnInit = [],- _cmcOnDispose = [],+ _cmcOnInitReq = [],+ _cmcOnDisposeReq = [], _cmcOnResize = [], _cmcOnChangeReq = [], _cmcOnEnabledChange = [],@@ -246,11 +266,11 @@ instance Semigroup (CompositeCfg s e sp ep) where (<>) c1 c2 = CompositeCfg {- _cmcModelBuilder = _cmcModelBuilder c2 <|> _cmcModelBuilder c1,+ _cmcMergeModel = _cmcMergeModel c2 <|> _cmcMergeModel c1, _cmcMergeRequired = _cmcMergeRequired c2 <|> _cmcMergeRequired c1, _cmcMergeReqs = _cmcMergeReqs c1 <> _cmcMergeReqs c2,- _cmcOnInit = _cmcOnInit c1 <> _cmcOnInit c2,- _cmcOnDispose = _cmcOnDispose c1 <> _cmcOnDispose c2,+ _cmcOnInitReq = _cmcOnInitReq c1 <> _cmcOnInitReq c2,+ _cmcOnDisposeReq = _cmcOnDisposeReq c1 <> _cmcOnDisposeReq c2, _cmcOnResize = _cmcOnResize c1 <> _cmcOnResize c2, _cmcOnChangeReq = _cmcOnChangeReq c1 <> _cmcOnChangeReq c2, _cmcOnEnabledChange = _cmcOnEnabledChange c1 <> _cmcOnEnabledChange c2,@@ -260,32 +280,42 @@ instance Monoid (CompositeCfg s e sp ep) where mempty = def -instance CmbMergeRequired (CompositeCfg s e sp ep) s where+instance CmbMergeRequired (CompositeCfg s e sp ep) (WidgetEnv s e) s where mergeRequired fn = def { _cmcMergeRequired = Just fn } -instance CmbOnInit (CompositeCfg s e sp ep) e where+instance WidgetEvent e => CmbOnInit (CompositeCfg s e sp ep) e where onInit fn = def {- _cmcOnInit = [fn]+ _cmcOnInitReq = [RaiseEvent fn] } -instance CmbOnDispose (CompositeCfg s e sp ep) e where+instance CmbOnInitReq (CompositeCfg s e sp ep) s e where+ onInitReq req = def {+ _cmcOnInitReq = [req]+ }++instance WidgetEvent e => CmbOnDispose (CompositeCfg s e sp ep) e where onDispose fn = def {- _cmcOnDispose = [fn]+ _cmcOnDisposeReq = [RaiseEvent fn] } +instance CmbOnDisposeReq (CompositeCfg s e sp ep) s e where+ onDisposeReq req = def {+ _cmcOnDisposeReq = [req]+ }+ instance CmbOnResize (CompositeCfg s e sp ep) e Rect where onResize fn = def { _cmcOnResize = [fn] } -instance WidgetEvent ep => CmbOnChange (CompositeCfg s e sp ep) s ep where+instance WidgetEvent e => CmbOnChange (CompositeCfg s e sp ep) s e where onChange fn = def { _cmcOnChangeReq = [RaiseEvent . fn] } -instance CmbOnChangeReq (CompositeCfg s e sp ep) sp ep s where+instance CmbOnChangeReq (CompositeCfg s e sp ep) s e s where onChangeReq req = def { _cmcOnChangeReq = [req] }@@ -300,35 +330,58 @@ _cmcOnVisibleChange = [fn] } --- | Generate WidgetRequests during the merge process.-compositeMergeReqs :: MergeReqsHandler s e -> CompositeCfg s e sp ep+{-|+Generate WidgetRequests during the merge process.++This function is not called during initialization; 'onInitReq' can be used.+-}+compositeMergeReqs :: MergeReqsHandler s e sp -> CompositeCfg s e sp ep compositeMergeReqs fn = def { _cmcMergeReqs = [fn] } {-|-Generates a custom model from the current parent model and the previous-composite model. Useful when the composite needs a more complex model than what-the user is binding.+Generate events during the merge process.++This function is not called during initialization; 'onInit' can be used. -}-customModelBuilder- :: CompositeCustomModelBuilder s sp- -> CompositeCfg s e sp ep-customModelBuilder fn = def {- _cmcModelBuilder = Just fn+compositeMergeEvents+ :: WidgetEvent e => MergeEventsHandler s e sp -> CompositeCfg s e sp ep+compositeMergeEvents fn = cfg where+ cfg = def {+ _cmcMergeReqs = [wrapper]+ }+ wrapper wenv node oldNode parentModel oldModel newModel+ = RaiseEvent <$> fn wenv node oldNode parentModel oldModel newModel++{-|+Allows updating the composite model with information from the parent model.+Useful when the composite needs a more complex model than what the user is+binding.++For example, a database record may be binded as the model from the parent, but+the composite needs its own boolean flags to toggle visibility on different+sections.++This function is called during both merge and init. On init, the oldModel will+be equal to the current model.+-}+compositeMergeModel :: MergeModelHandler s e sp -> CompositeCfg s e sp ep+compositeMergeModel fn = def {+ _cmcMergeModel = Just fn } data Composite s e sp ep = Composite { _cmpWidgetData :: !(WidgetData sp s), _cmpEventHandler :: !(EventHandler s e sp ep), _cmpUiBuilder :: !(UIBuilder s e),- _cmpMergeRequired :: MergeRequired s,- _cmpMergeReqs :: [MergeReqsHandler s e],- _cmpModelBuilder :: Maybe (CompositeCustomModelBuilder s sp),- _cmpOnInit :: [e],- _cmpOnDispose :: [e],+ _cmpMergeRequired :: MergeRequired s e,+ _cmpMergeReqs :: [MergeReqsHandler s e sp],+ _cmpMergeModel :: Maybe (MergeModelHandler s e sp),+ _cmpOnInitReq :: [WidgetRequest s e],+ _cmpOnDisposeReq :: [WidgetRequest s e], _cmpOnResize :: [Rect -> e],- _cmpOnChangeReq :: [s -> WidgetRequest sp ep],+ _cmpOnChangeReq :: [s -> WidgetRequest s e], _cmpOnEnabledChange :: [e], _cmpOnVisibleChange :: [e] }@@ -383,7 +436,7 @@ :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => WidgetType -- ^ The name of the composite. -> s -- ^ The model.- -> (s -> ep) -- ^ The event to report when model changes.+ -> (s -> e) -- ^ The event to report when model changes. -> UIBuilder s e -- ^ The UI builder function. -> EventHandler s e sp ep -- ^ The event handler. -> WidgetNode sp ep -- ^ The resulting widget.@@ -398,7 +451,7 @@ :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp) => WidgetType -- ^ The name of the composite. -> s -- ^ The model.- -> (s -> ep) -- ^ The event to report when model changes.+ -> (s -> e) -- ^ The event to report when model changes. -> UIBuilder s e -- ^ The UI builder function. -> EventHandler s e sp ep -- ^ The event handler. -> [CompositeCfg s e sp ep] -- ^ The config options.@@ -419,7 +472,7 @@ -> WidgetNode sp ep -- ^ The resulting widget. compositeD_ wType wData uiBuilder evtHandler configs = newNode where config = mconcat configs- mergeReq = fromMaybe (/=) (_cmcMergeRequired config)+ mergeReq = fromMaybe (const (/=)) (_cmcMergeRequired config) !widgetRoot = spacer composite = Composite { _cmpWidgetData = wData,@@ -427,9 +480,9 @@ _cmpUiBuilder = uiBuilder, _cmpMergeRequired = mergeReq, _cmpMergeReqs = _cmcMergeReqs config,- _cmpModelBuilder = _cmcModelBuilder config,- _cmpOnInit = _cmcOnInit config,- _cmpOnDispose = _cmcOnDispose config,+ _cmpMergeModel = _cmcMergeModel config,+ _cmpOnInitReq = _cmcOnInitReq config,+ _cmpOnDisposeReq = _cmcOnDisposeReq config, _cmpOnResize = _cmcOnResize config, _cmpOnChangeReq = _cmcOnChangeReq config, _cmpOnEnabledChange = _cmcOnEnabledChange config,@@ -472,11 +525,12 @@ compositeInit comp state wenv widgetComp = newResult where CompositeState{..} = state - !customModelBuilder = _cmpModelBuilder comp+ !mergeModel = _cmpMergeModel comp !parentModel = wenv ^. L.model !userModel = getUserModel comp wenv- !model = case customModelBuilder of- Just buildCustomModel -> buildCustomModel parentModel userModel userModel+ !model = case mergeModel of+ Just merge -> merge cwenv parentModel userModel userModel where+ !cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap userModel _ -> userModel -- Creates UI using provided function@@ -491,11 +545,11 @@ _cpsWidgetKeyMap = collectWidgetKeys M.empty root } - !newEvts = RaiseEvent <$> Seq.fromList (_cmpOnInit comp)- getBaseStyle wenv node = Nothing styledComp = initNodeStyle getBaseStyle wenv widgetComp- tempResult = WidgetResult root (RenderOnce <| reqs <> newEvts)++ initReqs = Seq.fromList (_cmpOnInitReq comp)+ tempResult = WidgetResult root (RenderOnce <| reqs <> initReqs) !newResult = toParentResult comp newState wenv styledComp tempResult -- | Merge@@ -513,11 +567,12 @@ validState = fromMaybe state (useState oldState) CompositeState oldModel oldRoot oldWidgetKeys = validState - !customModelBuilder = _cmpModelBuilder comp+ !mergeModel = _cmpMergeModel comp !parentModel = wenv ^. L.model !userModel = getUserModel comp wenv- !model = case customModelBuilder of- Just buildCustomModel -> buildCustomModel parentModel (fromJust oldModel) userModel+ !model = case mergeModel of+ Just merge -> merge cwenv parentModel (fromJust oldModel) userModel where+ cwenv = convertWidgetEnv wenv oldWidgetKeys userModel _ -> userModel -- Creates new UI using provided function@@ -527,7 +582,7 @@ -- Needed in case the user references something outside model when building UI -- 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+ modelChanged = _cmpMergeRequired comp cwenv (fromJust oldModel) model visibleChg = nodeVisibleChanged oldComp newComp enabledChg = nodeEnabledChanged oldComp newComp flagsChanged = visibleChg || enabledChg@@ -563,7 +618,8 @@ evts = RaiseEvent <$> Seq.fromList (visibleEvts ++ enabledEvts) mergeReqsFns = _cmpMergeReqs comp- mergeReqs = concatMap (\fn -> fn cwenv newRoot oldRoot model) mergeReqsFns+ mergeHelper f = f cwenv newRoot oldRoot parentModel (fromJust oldModel) model+ mergeReqs = concatMap mergeHelper mergeReqsFns extraReqs = seqCatMaybes (toParentReq widgetId <$> Seq.fromList mergeReqs) tmpResult = WidgetResult newRoot (RenderOnce <| tmpReqs <> extraReqs <> evts)@@ -586,10 +642,11 @@ model = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap model widget = _cpsRoot ^. L.widget- newEvts = RaiseEvent <$> Seq.fromList (_cmpOnDispose comp) WidgetResult _ reqs = widgetDispose widget cwenv _cpsRoot- tempResult = WidgetResult _cpsRoot (reqs <> newEvts)++ disposeReqs = Seq.fromList (_cmpOnDisposeReq comp)+ tempResult = WidgetResult _cpsRoot (reqs <> disposeReqs) result = toParentResult comp state wenv widgetComp tempResult compositeGetInstanceTree@@ -911,8 +968,9 @@ -> WidgetNode s e -> WidgetNode sp ep -> WidgetResult sp ep-mergeChild comp state wenv newModel widgetRoot widgetComp = newResult where+mergeChild comp state wenv newModel widgetRoot widgetComp = parentResult where CompositeState{..} = state+ oldModel = getCompositeModel state cwenv = convertWidgetEnv wenv _cpsWidgetKeyMap newModel widgetId = _cpsRoot ^. L.info . L.widgetId builtRoot = cascadeCtx wenv widgetComp (_cmpUiBuilder comp cwenv newModel)@@ -927,12 +985,14 @@ _cpsRoot = mergedResult ^. L.node, _cpsWidgetKeyMap = collectWidgetKeys M.empty (mergedResult ^. L.node) }- !result = toParentResult comp mergedState wenv widgetComp mergedResult- !newReqs = widgetDataSet (_cmpWidgetData comp) newModel- ++ fmap ($ newModel) (_cmpOnChangeReq comp)+ childReqs = fmap ($ oldModel) (_cmpOnChangeReq comp)+ parentReqs = widgetDataSet (_cmpWidgetData comp) newModel ++ [ResizeWidgets widgetId | initRequired]- !newResult = result- & L.requests <>~ Seq.fromList newReqs+ childResult = mergedResult+ & L.requests <>~ Seq.fromList childReqs+ result = toParentResult comp mergedState wenv widgetComp childResult+ parentResult = result+ & L.requests .~ Seq.fromList parentReqs <> result ^. L.requests getUserModel :: (CompositeModel s, CompositeEvent e, CompositeEvent ep, CompParentModel sp)@@ -1001,6 +1061,7 @@ convertWidgetEnv wenv widgetKeyMap model = WidgetEnv { _weOs = _weOs wenv, _weDpr = _weDpr wenv,+ _weAppStartTs = _weAppStartTs wenv, _weFontManager = _weFontManager wenv, _weFindBranchByPath = _weFindBranchByPath wenv, _weMainButton = _weMainButton wenv,
src/Monomer/Widgets/Container.hs view
@@ -25,6 +25,7 @@ -- * Configuration ContainerGetBaseStyle, ContainerGetCurrentStyle,+ ContainerCreateContainerFromModel, ContainerUpdateCWenvHandler, ContainerInitHandler, ContainerInitPostHandler,@@ -680,13 +681,13 @@ -> WidgetNode s e -> WidgetResult s e -> WidgetResult s e-mergeChildren updateCWenv !wenv !newNode !oldNode !result = newResult where- WidgetResult uNode uReqs = result+mergeChildren updateCWenv !wenv !newNode !oldNode !pResult = newResult where+ WidgetResult pNode pReqs = pResult oldChildren = oldNode ^. L.children oldIts = Seq.mapWithIndex (,) oldChildren- updatedChildren = uNode ^. L.children+ updatedChildren = pNode ^. L.children - mergeChild idx child = (idx, cascadeCtx wenv uNode child idx)+ mergeChild idx child = (idx, cascadeCtx wenv pNode child idx) newIts = Seq.mapWithIndex mergeChild updatedChildren oldKeys = buildLocalMap oldChildren newKeys = buildLocalMap (snd <$> newIts)@@ -696,8 +697,8 @@ mergedChildren = fmap _wrNode mergedResults mergedReqs = foldMap _wrRequests mergedResults removedReqs = foldMap _wrRequests removedResults- mergedNode = uNode & L.children .~ mergedChildren- newReqs = uReqs <> mergedReqs <> removedReqs+ mergedNode = pNode & L.children .~ mergedChildren+ newReqs = pReqs <> mergedReqs <> removedReqs !newResult = WidgetResult mergedNode newReqs mergeChildSeq
src/Monomer/Widgets/Containers/Alert.hs view
@@ -8,6 +8,24 @@ Simple alert dialog, displaying a close button and optional title. Usually embedded in a zstack component and displayed/hidden depending on context.++A simple text message can be displayed with 'alertMsg', providing the message+text and the event to generate when the user closes the alert:++@+alertMsg "En error occurred" AlertClosedEvent+@++Alternatively, a custom widget can be provided to display as content:++@+customAlert = alert AlertClosedEvent content where+ content = hstack [+ label "Error:",+ filler,+ label errorDescription+ ]+@ -} {-# LANGUAGE Strict #-}
src/Monomer/Widgets/Containers/Base/LabeledItem.hs view
@@ -8,6 +8,11 @@ Container for items with an associated clickable label. Mainly used with radio and checkbox.++For usage examples, see:++- "Monomer.Widgets.Singles.LabeledCheckbox"+- "Monomer.Widgets.Singles.LabeledRadio" -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-}
src/Monomer/Widgets/Containers/Box.hs view
@@ -6,19 +6,45 @@ Stability : experimental Portability : non-portable -Container for a single item.+Container for a single item, providing functionalities that may not be available+in other widgets. Useful in different layout situations, since it provides alignment options. This allows for the inner widget to keep its size while being positioned more explicitly, while the box takes up the complete space assigned by its parent (in-particular for containers which do not follow SizeReq restriccions, such as+particular for containers which do not follow SizeReq restrictions, such as Grid). +@+box_ [alignRight, alignBottom] $+ image "assets/test-image.jpg"+ \`styleBasic\` [width 100, height 100]+@+ Can be used to add padding to an inner widget with a border. This is equivalent to the margin property in CSS. +@+-- Padding is inside the border+content = label \"Message\"+ \`styleBasic\` [padding 5, border 1 black]+-- Padding in the wrapper box acts as margin+container = box content+ \`styleBasic\` [padding 5]+@+ Also useful to handle click events in complex widget structures (for example, a label with an image at its side).++@+content = vstack [+ label "All the content widget is clickable",+ spacer,+ image "assets/test-image.jpg"+ ]+clickableItem = box_ [onClick ItemClicked] content+ \`styleBasic\' [cursorHand]+@ -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}@@ -80,7 +106,7 @@ _boxExpandContent :: Maybe Bool, _boxIgnoreEmptyArea :: Maybe Bool, _boxSizeReqUpdater :: [SizeReqUpdater],- _boxMergeRequired :: Maybe (s -> s -> Bool),+ _boxMergeRequired :: Maybe (WidgetEnv s e -> s -> s -> Bool), _boxAlignH :: Maybe AlignH, _boxAlignV :: Maybe AlignV, _boxOnFocusReq :: [Path -> WidgetRequest s e],@@ -142,7 +168,7 @@ _boxSizeReqUpdater = [updater] } -instance CmbMergeRequired (BoxCfg s e) s where+instance CmbMergeRequired (BoxCfg s e) (WidgetEnv s e) s where mergeRequired fn = def { _boxMergeRequired = Just fn }@@ -320,7 +346,7 @@ mergeRequired wenv node oldNode oldState = required where newModel = wenv ^. L.model required = case (_boxMergeRequired config, _bxsModel oldState) of- (Just mergeReqFn, Just oldModel) -> mergeReqFn oldModel newModel+ (Just mergeReqFn, Just oldModel) -> mergeReqFn wenv oldModel newModel _ -> True merge wenv node oldNode oldState = resultNode newNode where
src/Monomer/Widgets/Containers/Confirm.hs view
@@ -6,9 +6,30 @@ Stability : experimental Portability : non-portable -Simple confirm dialog, displaying an accept and close buttons and optional-title. Usually embedded in a zstack component and displayed/hidden depending on+Simple confirm dialog, displaying accept and close buttons and optional title.+Usually embedded in a zstack component and displayed/hidden depending on context.++Similar to "Monomer.Widgets.Containers.Alert", but takes two events to handle+the Accept and Cancel actions.++A simple text message can be displayed with 'confirmMsg', providing the message+text and the events to generate when the user interacts with the dialog:++@+confirmMsg "Save changes?" ConfirmAcceptEvent ConfirmCancelEvent+@++Alternatively, a custom widget can be provided to display as content:++@+customConfirm = confirm ConfirmAcceptEvent ConfirmCancelEvent where+ content = hstack [+ label "Save changes?",+ filler,+ label fileName+ ]+@ -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-}@@ -17,6 +38,7 @@ module Monomer.Widgets.Containers.Confirm ( -- * Configuration ConfirmCfg,+ InnerConfirmEvt, -- * Constructors confirm, confirm_,@@ -89,28 +111,34 @@ _cfcCancel = Just t } -newtype ConfirmEvt e+{-|+Wraps the user event to be able to implement custom events and still be able to+report to its parent.++Previously used, now kept for reference.+-}+newtype InnerConfirmEvt e = ConfirmParentEvt e deriving (Eq, Show) -- | Creates a confirm dialog with the provided content. confirm :: (WidgetModel s, WidgetEvent e)- => e -- ^ The accept button event.- -> e -- ^ The cancel button event.- -> WidgetNode () (ConfirmEvt e) -- ^ The content to display in the dialog.- -> WidgetNode s e -- ^ The created dialog.+ => e -- ^ The accept button event.+ -> e -- ^ The cancel button event.+ -> WidgetNode () (InnerConfirmEvt e) -- ^ Content to display in the dialog.+ -> WidgetNode s e -- ^ The created dialog. confirm acceptEvt cancelEvt dialogBody = newNode where newNode = confirm_ acceptEvt cancelEvt def dialogBody -- | Creates an alert dialog with the provided content. Accepts config. confirm_ :: (WidgetModel s, WidgetEvent e)- => e -- ^ The accept button event.- -> e -- ^ The cancel button event.- -> [ConfirmCfg] -- ^ The config options for the dialog.- -> WidgetNode () (ConfirmEvt e) -- ^ The content to display in the dialog.- -> WidgetNode s e -- ^ The created dialog.+ => e -- ^ The accept button event.+ -> e -- ^ The cancel button event.+ -> [ConfirmCfg] -- ^ The config options for the dialog.+ -> WidgetNode () (InnerConfirmEvt e) -- ^ Content to display in the dialog.+ -> WidgetNode s e -- ^ The created dialog. confirm_ acceptEvt cancelEvt configs dialogBody = newNode where config = mconcat configs createUI = buildUI (const dialogBody) acceptEvt cancelEvt config@@ -120,19 +148,19 @@ -- | Creates an alert dialog with a text message as content. confirmMsg :: (WidgetModel s, WidgetEvent e)- => Text -- ^ The message to display in the dialog.- -> e -- ^ The accept button event.- -> e -- ^ The cancel button event.+ => Text -- ^ The message to display in the dialog.+ -> e -- ^ The accept button event.+ -> e -- ^ The cancel button event. -> WidgetNode s e -- ^ The created dialog. confirmMsg msg acceptEvt cancelEvt = confirmMsg_ msg acceptEvt cancelEvt def -- | Creates an alert dialog with a text message as content. Accepts config. confirmMsg_ :: (WidgetModel s, WidgetEvent e)- => Text -- ^ The message to display in the dialog.- -> e -- ^ The accept button event.- -> e -- ^ The cancel button event.- -> [ConfirmCfg] -- ^ The config options for the dialog.+ => Text -- ^ The message to display in the dialog.+ -> e -- ^ The accept button event.+ -> e -- ^ The cancel button event.+ -> [ConfirmCfg] -- ^ The config options for the dialog. -> WidgetNode s e -- ^ The created dialog. confirmMsg_ message acceptEvt cancelEvt configs = newNode where config = mconcat configs@@ -142,8 +170,8 @@ compCfg = [compositeMergeReqs mergeReqs] newNode = compositeD_ "confirm" (WidgetValue ()) createUI handleEvent compCfg -mergeReqs :: MergeReqsHandler s e-mergeReqs wenv newNode oldNode model = reqs where+mergeReqs :: MergeReqsHandler s e sp+mergeReqs wenv newNode oldNode parentModel oldModel model = reqs where acceptPath = SetFocus <$> widgetIdFromKey wenv "acceptBtn" isVisible node = node ^. L.info . L.visible reqs@@ -152,13 +180,13 @@ buildUI :: (WidgetModel s, WidgetEvent ep)- => (WidgetEnv s (ConfirmEvt ep) -> WidgetNode s (ConfirmEvt ep))+ => (WidgetEnv s (InnerConfirmEvt ep) -> WidgetNode s (InnerConfirmEvt ep)) -> ep -> ep -> ConfirmCfg- -> WidgetEnv s (ConfirmEvt ep)+ -> WidgetEnv s (InnerConfirmEvt ep) -> s- -> WidgetNode s (ConfirmEvt ep)+ -> WidgetNode s (InnerConfirmEvt ep) buildUI dialogBody pAcceptEvt pCancelEvt config wenv model = mainTree where acceptEvt = ConfirmParentEvt pAcceptEvt cancelEvt = ConfirmParentEvt pCancelEvt@@ -190,10 +218,10 @@ mainTree = keystroke [("Esc", cancelEvt)] confirmBox handleEvent- :: WidgetEnv s (ConfirmEvt ep)- -> WidgetNode s (ConfirmEvt ep)+ :: WidgetEnv s (InnerConfirmEvt ep)+ -> WidgetNode s (InnerConfirmEvt ep) -> s- -> ConfirmEvt ep- -> [EventResponse s (ConfirmEvt ep) sp ep]+ -> InnerConfirmEvt ep+ -> [EventResponse s (InnerConfirmEvt ep) sp ep] handleEvent wenv node model evt = case evt of ConfirmParentEvt pevt -> [Report pevt]
src/Monomer/Widgets/Containers/Draggable.hs view
@@ -8,7 +8,16 @@ Draggable container for a single item. Useful for adding drag support without having to implement a custom widget. Usually works in tandem with-'Monomer.Widgets.Containers.DropTarget'.+"Monomer.Widgets.Containers.DropTarget".++Requires a value to identify the content (used when the item is dropped) and the+content to display.++@+dragItem = draggable "item" $ label "This label is draggable"+@++See Tutorial 6 (Composite) for a usage example. -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}
src/Monomer/Widgets/Containers/DropTarget.hs view
@@ -8,10 +8,18 @@ Drop target container for a single element. Useful for adding drag support without having to implement a custom widget. Usually works in tandem with-'Monomer.Widgets.Containers.Draggable'.+"Monomer.Widgets.Containers.Draggable". Raises a user provided event when an item is dropped. The type must match with-the dragged message, otherwise it will not be raised.+the type of the dragged widget message, otherwise it will not be raised.++@+target = dropTarget ItemDropped $+ vstack itemsRows+ \`styleBasic\` [width 200, height 400]+@++See Tutorial 6 (Composite) for a usage example. -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}
src/Monomer/Widgets/Containers/Dropdown.hs view
@@ -7,9 +7,21 @@ Portability : non-portable Dropdown widget, allowing selection of a single item from a collapsable list.-Both header and list content is customizable, and so is its styling. In case-only 'Text' content is needed, 'Monomer.Widgets.Singles.TextDropdown' is easier-to use.+Both header and list content are customizable, and so is their styling.++In case only 'Text' content is needed, "Monomer.Widgets.Singles.TextDropdown" is+easier to use.++@+makeSelected username = hstack [+ label "Selected: ",+ spacer,+ label username+ ]+makeRow username = label username++customDropdown = dropdown userLens usernames makeSelected makeRow+@ -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-}
src/Monomer/Widgets/Containers/Grid.hs view
@@ -6,9 +6,23 @@ Stability : experimental Portability : non-portable -Layout container which distributes size equally along the main axis. For hgrid-it requests max width * elements as its width, and the max height as its height.-The reverse happens for vgrid.+Layout container which distributes space evenly along the main axis. For the+secondary axis children will receive as much space as available for the grid+widget itself.++In the same way as with hstack and vstack, 'hgrid' and 'vgrid' can be combined+to create more complex layouts.++The hgrid widget requests maxWidth * elements as its width, and the max height+as its height. The inverse happens for vgrid.++@+hgrid [+ label "Third 1",+ label "Third 2",+ label "Third 3"+ ]+@ -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE Strict #-}
src/Monomer/Widgets/Containers/Keystroke.hs view
@@ -11,28 +11,62 @@ implementing a widget from scratch, keyboard events are directly available. The shortcut definitions are provided as a list of tuples of 'Text', containing-the key combination and associated event. The widget handles unordered-combinations of multiple keys at the same time, but does not support ordered-sequences (pressing "a", releasing, then "b" and "c"). The available keys are:+the key combination and associated event, separated by "-". The widget handles+unordered combinations of multiple keys at the same time, but does not support+ordered sequences (pressing "a", releasing, then "b" and "c"). The available+keys are: - Mod keys: A, Alt, C, Ctrl, Cmd, O, Option, S, Shift - Action keys: Caps, Delete, Enter, Esc, Return, Space, Tab - Arrows: Up, Down, Left, Right - Function keys: F1-F12+- Separator: Dash (since '-' is used for defining keystrokes)+- Symbols: brackets, ^, *, &, etc. - Lowercase letters (uppercase keys are reserved for mod and action keys) - Numbers -These can be combined, for example:+The keys can be combined, for example: - Copy: "Ctrl-c" or "C-c" - App config: "Ctrl-Shift-p" or "C-S-p"++@+keystroke [("Esc", CancelEditing)] $ hstack [+ label "Username:",+ spacer,+ textField userLens+ ]+@++Note 1: Following the pattern explained in 'CmbIgnoreChildrenEvts', this widget+by default allows children widgets (i.e., focused widgets) that may receive the+events to respond to the pressed keys. If you want to avoid this, and only keep+the keystroke widgets's response when a combination matches, add the+'ignoreChildrenEvts' config option. To clarify: the only keypress event that+will be filtered is the one that causes a combination to match (the last one).++Note 2: Except in the specific cases mentioned here (Ctrl, Cmd, etc), the keys+must be single characters.++Note 3: Full words must be input exactly as indicated (Ctrl, Cmd, etc). Alias+only exist for the keys described here (A for Alt, C for Ctrl/Cmd, etc).++Note 4: Symbols that require pressing the Shift key (^, &, etc) are virtual keys+and share the KeyCode with the symbol associated to the same physical key. This+causes issues when detecting their pressed status, and thus it's not possible to+combine these symbols with letters, numbers or other symbols in the same+keystroke. The same happens with characters that require pressing a combination+of keys (e.g. accented characters). It is still possible to combine them with+mod keys, so using "C-^" or "C-[" should work. If you find that binding a+symbol/complex character does not work, try using the names of the physical keys+instead (e.g. "Shift-e" instead of "E"). -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StrictData #-} {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE Strict #-} module Monomer.Widgets.Containers.Keystroke ( -- * Configuration@@ -42,6 +76,8 @@ keystroke_ ) where +import Debug.Trace (traceShow)+ import Control.Applicative ((<|>)) import Control.Lens ((&), (^.), (^..), (.~), (%~), _1, at, folded) import Control.Lens.TH (abbreviatedFields, makeLensesWith)@@ -96,7 +132,9 @@ _kstKsCmd :: Bool, _kstKsAlt :: Bool, _kstKsShift :: Bool,- _kstKsKeys :: Set KeyCode+ _kstKsKeys :: Set KeyCode,+ _kstKsKeysText :: Set Text,+ _kstKsErrors :: [Text] } deriving (Eq, Show) instance Default KeyStroke where@@ -107,13 +145,20 @@ _kstKsCmd = False, _kstKsAlt = False, _kstKsShift = False,- _kstKsKeys = Set.empty+ _kstKsKeys = Set.empty,+ _kstKsKeysText = Set.empty,+ _kstKsErrors = [] } newtype KeyStrokeState e = KeyStrokeState { _kssLatest :: [(KeyStroke, e)] } deriving (Eq, Show) +data KeyEntry+ = KeyEntryCode KeyCode+ | KeyEntryText Text+ deriving (Eq, Show)+ makeLensesWith abbreviatedFields ''KeyStroke makeLensesWith abbreviatedFields ''KeyStrokeState @@ -156,37 +201,47 @@ & L.widget .~ makeKeystroke bindings config oldState handleEvent wenv node target evt = case evt of- KeyAction mod code KeyPressed -> Just result where- newWenv = wenv & L.inputStatus %~ removeMods- matches = filter (keyStrokeActive newWenv code . fst) bindings- newState = KeyStrokeState matches- newNode = node- & L.widget .~ makeKeystroke bindings config newState- evts = snd <$> matches- reqs- | ignoreChildren && not (null evts) = [IgnoreChildrenEvents]- | otherwise = []- result = resultReqsEvts newNode reqs evts- TextInput t- | ignoreChildren && ignorePrevious t -> Just result where- previousMatch t = t `elem` _kssLatest state ^.. folded . _1 . ksText- ignorePrevious t = isTextValidCode t && previousMatch t+ KeyAction mod code KeyPressed -> result where+ result = handleKeystroke (KeyEntryCode code)+ TextInput text+ | ignoreChildren && ignorePrevious text -> Just result where newState = KeyStrokeState [] newNode = node & L.widget .~ makeKeystroke bindings config newState result = resultReqs newNode [IgnoreChildrenEvents]+ TextInput text+ | not (previousMatch text) -> result where+ result = handleKeystroke (KeyEntryText text) _ -> Nothing where ignoreChildren = Just True == _kscIgnoreChildren config+ previousMatch t = t `elem` _kssLatest state ^.. folded . _1 . ksText+ ignorePrevious t = isTextValidCode t && previousMatch t -keyStrokeActive :: WidgetEnv s e -> KeyCode -> KeyStroke -> Bool-keyStrokeActive wenv code ks = currValid && allPressed && validMods where+ handleKeystroke entry = Just result where+ newWenv = wenv & L.inputStatus %~ removeMods+ matches = filter (keyStrokeActive newWenv entry . fst) bindings+ newState = KeyStrokeState matches+ newNode = node+ & L.widget .~ makeKeystroke bindings config newState+ evts = snd <$> matches+ reqs+ | ignoreChildren && not (null evts) = [IgnoreChildrenEvents]+ | otherwise = []+ result = resultReqsEvts newNode reqs evts++keyStrokeActive :: WidgetEnv s e -> KeyEntry -> KeyStroke -> Bool+keyStrokeActive wenv entry ks = currValid && allPressed && validMods where status = wenv ^. L.inputStatus keyMod = status ^. L.keyMod pressedKeys = M.filter (== KeyPressed) (status ^. L.keys) - currValid = code `elem` (ks ^. ksKeys) || code `elem` modKeys- allPressed = M.keysSet pressedKeys == ks ^. ksKeys+ (currValid, allPressed, ignoreShift) = case entry of+ KeyEntryCode code -> (valid, pressed, False) where+ valid = code `elem` (ks ^. ksKeys) || code `elem` modKeys+ pressed = M.keysSet pressedKeys == ks ^. ksKeys+ KeyEntryText txt -> (valid, True, True) where+ valid = txt `elem` (ks ^. ksKeysText) ctrlPressed = isCtrlPressed keyMod cmdPressed = isMacOS wenv && isGUIPressed keyMod@@ -194,17 +249,24 @@ validC = not (ks ^. ksC) || ks ^. ksC == (ctrlPressed || cmdPressed) validCtrl = ks ^. ksCtrl == ctrlPressed || ctrlPressed && validC validCmd = ks ^. ksCmd == cmdPressed || cmdPressed && validC- validShift = ks ^. ksShift == isShiftPressed keyMod+ validShift = ks ^. ksShift == isShiftPressed keyMod || ignoreShift validAlt = ks ^. ksAlt == isAltPressed keyMod validMods = (validC && validCtrl && validCmd) && validShift && validAlt textToStroke :: Text -> KeyStroke-textToStroke text = ks where+textToStroke text = result where parts = T.split (=='-') text ks = foldl' partToStroke def parts & ksText .~ text + errors = ks ^. ksErrors+ errorMsg = "'" <> text <> "' is not valid. Invalid parts: "++ result+ | not (T.null text) && null errors = ks+ | otherwise = traceShow (errorMsg, errors) ks+ partToStroke :: KeyStroke -> Text -> KeyStroke partToStroke ks "A" = ks & ksAlt .~ True partToStroke ks "Alt" = ks & ksAlt .~ True@@ -216,9 +278,11 @@ partToStroke ks "S" = ks & ksShift .~ True partToStroke ks "Shift" = ks & ksShift .~ True -- Main keys+partToStroke ks "Backspace" = ks & ksKeys %~ Set.insert keyBackspace partToStroke ks "Caps" = ks & ksKeys %~ Set.insert keyCapsLock partToStroke ks "Delete" = ks & ksKeys %~ Set.insert keyDelete partToStroke ks "Enter" = ks & ksKeys %~ Set.insert keyReturn+partToStroke ks "KpEnter" = ks & ksKeys %~ Set.insert keyPadEnter partToStroke ks "Esc" = ks & ksKeys %~ Set.insert keyEscape partToStroke ks "Return" = ks & ksKeys %~ Set.insert keyReturn partToStroke ks "Space" = ks & ksKeys %~ Set.insert keySpace@@ -242,9 +306,13 @@ partToStroke ks "F11" = ks & ksKeys %~ Set.insert keyF11 partToStroke ks "F12" = ks & ksKeys %~ Set.insert keyF12 -- Other keys (numbers, letters, points, etc)+partToStroke ks "Dash" = partToStroke ks "-" partToStroke ks txt- | isTextValidCode txt = ks & ksKeys %~ Set.insert (KeyCode (ord txtHead))+ | isTextValidCode txt = ks+ & ksKeys %~ Set.insert (KeyCode (ord txtHead))+ & ksKeysText %~ Set.insert txt | otherwise = ks+ & ksErrors %~ (++ [txt]) where txtHead = T.index txt 0
src/Monomer/Widgets/Containers/Scroll.hs view
@@ -11,10 +11,14 @@ of the inner node with the scroll bars. It also supports automatic focus following. -Messages:+Accepts the following messages: - 'ScrollTo': Causes the scroll to update its handles to ensure rect is visible. - 'ScrollReset': Sets both handle positions to zero.++@+vscroll (vstack longItemsList)+@ -} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-}@@ -68,12 +72,19 @@ | ScrollBoth deriving (Eq, Show) +{-|+Information provided in the 'onChange' event.++The currently visible viewport is affected by the position of the scroll bars,+their size and any other parent widget that restricts the visible viewport+(e.g., another scroll).+-} data ScrollStatus = ScrollStatus {- scrollDeltaX :: Double,- scrollDeltaY :: Double,- scrollRect :: Rect,- scrollVpSize :: Size,- scrollChildSize :: Size+ scrollDeltaX :: Double, -- ^ Displacement in the x axis.+ scrollDeltaY :: Double, -- ^ Displacement in the y axis.+ scrollRect :: Rect, -- ^ The viewport assigned to the scroll widget.+ scrollVpSize :: Size, -- ^ The currently visible viewport.+ scrollChildSize :: Size -- ^ The total size of the child widget. } deriving (Eq, Show) instance Default ScrollStatus where@@ -106,6 +117,7 @@ - 'thumbColor': the color of the thumb. - 'thumbHoverColor': the color of the thumb when mouse is on top. - 'thumbWidth': the width of the thumb.+- 'thumbMinSize': the minimum size of the thumb. - 'thumbRadius': the radius of the corners of the thumb. - 'onChange': event to raise when the viewport changes. - 'onChangeReq': 'WidgetRequest' to generate when the viewport changes.@@ -123,6 +135,7 @@ _scStyle :: Maybe (ALens' ThemeState StyleState), _scBarWidth :: Maybe Double, _scThumbWidth :: Maybe Double,+ _scThumbMinSize :: Maybe Double, _scThumbRadius :: Maybe Double, _scOnChangeReq :: [ScrollStatus -> WidgetRequest s e] }@@ -141,6 +154,7 @@ _scStyle = Nothing, _scBarWidth = Nothing, _scThumbWidth = Nothing,+ _scThumbMinSize = Nothing, _scThumbRadius = Nothing, _scOnChangeReq = [] }@@ -159,6 +173,7 @@ _scStyle = _scStyle t2 <|> _scStyle t1, _scBarWidth = _scBarWidth t2 <|> _scBarWidth t1, _scThumbWidth = _scThumbWidth t2 <|> _scThumbWidth t1,+ _scThumbMinSize = _scThumbMinSize t2 <|> _scThumbMinSize t1, _scThumbRadius = _scThumbRadius t2 <|> _scThumbRadius t1, _scOnChangeReq = _scOnChangeReq t2 <|> _scOnChangeReq t1 }@@ -202,6 +217,11 @@ _scThumbWidth = Just w } +instance CmbThumbMinSize (ScrollCfg s e) where+ thumbMinSize w = def {+ _scThumbMinSize = Just w+ }+ instance CmbThumbRadius (ScrollCfg s e) where thumbRadius r = def { _scThumbRadius = Just r@@ -300,14 +320,16 @@ _sstDragging :: Maybe ActiveBar, _sstDeltaX :: !Double, _sstDeltaY :: !Double,+ _sstThumbOffsetX :: !Double,+ _sstThumbOffsetY :: !Double, _sstVpSize :: Size, _sstChildSize :: Size, _sstScissor :: Rect } deriving (Eq, Show, Generic) data ScrollContext = ScrollContext {- hScrollRatio :: Double,- vScrollRatio :: Double,+ hThumbRatio :: Double,+ vThumbRatio :: Double, hScrollRequired :: Bool, vScrollRequired :: Bool, hMouseInScroll :: Bool,@@ -325,6 +347,8 @@ _sstDragging = Nothing, _sstDeltaX = 0, _sstDeltaY = 0,+ _sstThumbOffsetX = 0,+ _sstThumbOffsetY = 0, _sstVpSize = def, _sstChildSize = def, _sstScissor = def@@ -395,7 +419,7 @@ } widget = createContainer state container - ScrollState dragging dx dy _ _ _ = state+ (dragging, dx, dy) = (_sstDragging state, _sstDeltaX state, _sstDeltaY state) Size childWidth childHeight = _sstChildSize state Size maxVpW maxVpH = _sstVpSize state offset = Point dx dy@@ -484,7 +508,10 @@ follow = fromMaybe (theme ^. L.scrollFollowFocus) (_scFollowFocus config) overlayMatch = focusOverlay == inOverlay (node ^. L.info) + fwdFocus = Just (resultReqs node [MoveFocus Nothing FocusFwd])+ result+ | target == node ^. L.info . L.path = fwdFocus | follow && overlayMatch = focusVp >>= scrollTo wenv node | otherwise = Nothing @@ -503,9 +530,20 @@ mouseInThumb = hMouseInThumb sctx || vMouseInThumb sctx mouseInScroll = hMouseInScroll sctx || vMouseInScroll sctx + thumbOffsetX = point ^. L.x - hThumbRect sctx ^. L.x+ thumbOffsetY = point ^. L.y - vThumbRect sctx ^. L.y+ newState- | startDragH = state { _sstDragging = Just HBar }- | startDragV = state { _sstDragging = Just VBar }+ | startDragH = state {+ _sstDragging = Just HBar,+ _sstThumbOffsetX = thumbOffsetX,+ _sstThumbOffsetY = 0+ }+ | startDragV = state {+ _sstDragging = Just VBar,+ _sstThumbOffsetX = 0,+ _sstThumbOffsetY = thumbOffsetY+ } | jumpScrollH = updateScrollThumb state HBar point contentArea sctx | jumpScrollV = updateScrollThumb state VBar point contentArea sctx | mainReleased = state { _sstDragging = Nothing }@@ -627,11 +665,10 @@ ScrollContext{..} = sctx Rect cx cy _ _ = contentArea - hMid = _rW hThumbRect / 2- vMid = _rH vThumbRect / 2+ (offsetH, offsetV) = (_sstThumbOffsetX state, _sstThumbOffsetY state) - hDelta = (cx - px + hMid) / hScrollRatio- vDelta = (cy - py + vMid) / vScrollRatio+ hDelta = (cx - px + offsetH) / hThumbRatio+ vDelta = (cy - py + offsetV) / vThumbRatio newDeltaX | activeBar == HBar = scrollAxisH hDelta@@ -814,16 +851,19 @@ -> ScrollState -> Point -> ScrollContext-scrollStatus config wenv node scrollState mousePos = ScrollContext{..} where- ScrollState _ dx dy _ _ _ = scrollState- Size childWidth childHeight = _sstChildSize scrollState- Size vpWidth vpHeight = _sstVpSize scrollState+scrollStatus config wenv node state mousePos = ScrollContext{..} where+ (dragging, dx, dy) = (_sstDragging state, _sstDeltaX state, _sstDeltaY state)++ Size childWidth childHeight = _sstChildSize state+ Size vpWidth vpHeight = _sstVpSize state+ theme = currentTheme wenv node style = scrollCurrentStyle wenv node contentArea = getContentArea node style barW = fromMaybe (theme ^. L.scrollBarWidth) (_scBarWidth config) thumbW = fromMaybe (theme ^. L.scrollThumbWidth) (_scThumbWidth config)+ minSize = fromMaybe (theme ^. L.scrollThumbMinSize) (_scThumbMinSize config) caLeft = _rX contentArea caTop = _rY contentArea@@ -835,15 +875,24 @@ hRatio = caWidth / childWidth vRatio = caHeight / childHeight- hRatioR = (caWidth - barW) / childWidth- vRatioR = (caHeight - barW) / childHeight - (hScrollRatio, vScrollRatio)- | hRatio < 1 && vRatio < 1 = (hRatioR, vRatioR)- | otherwise = (hRatio, vRatio)+ ratioBarW+ | hRatio < 1 && vRatio < 1 = barW+ | otherwise = 0+ hScrollRatio = (caWidth - ratioBarW) / childWidth+ vScrollRatio = (caHeight - ratioBarW) / childHeight+ hScrollRequired = hScrollRatio < 1 vScrollRequired = vScrollRatio < 1 + hThumbSize = max minSize (hScrollRatio * vpWidth)+ vThumbSize = max minSize (vScrollRatio * vpHeight)++ hThumbArea = caWidth - ratioBarW+ vThumbArea = caHeight - ratioBarW+ hThumbRatio = (hThumbArea - hThumbSize) / (childWidth - hThumbArea)+ vThumbRatio = (vThumbArea - vThumbSize) / (childHeight - vThumbArea)+ hScrollRect = Rect { _rX = caLeft, _rY = caTop + hScrollTop,@@ -857,16 +906,16 @@ _rH = vpHeight } hThumbRect = Rect {- _rX = caLeft - hScrollRatio * dx,+ _rX = caLeft - hThumbRatio * dx, _rY = caTop + hScrollTop + (barW - thumbW) / 2,- _rW = hScrollRatio * vpWidth,+ _rW = hThumbSize, _rH = thumbW } vThumbRect = Rect { _rX = caLeft + vScrollLeft + (barW - thumbW) / 2,- _rY = caTop - vScrollRatio * dy,+ _rY = caTop - vThumbRatio * dy, _rW = thumbW,- _rH = vScrollRatio * vpHeight+ _rH = vThumbSize } hMouseInScroll = pointInRect mousePos hScrollRect
src/Monomer/Widgets/Containers/SelectList.hs view
@@ -7,13 +7,22 @@ Portability : non-portable Select list widget, allowing selection of a single item. List content (rows) is-customizable, plus its styling.+customizable, and so is its styling. This widget is used by+"Monomer.Widgets.Containers.Dropdown" when in its open state.++@+makeRow username = hstack [+ label "User: ",+ label username+ ]++customSelect = selectList userLens usernames makeRow+@ -} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE StrictData #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -83,7 +92,7 @@ _slcSelectOnBlur :: Maybe Bool, _slcItemStyle :: Maybe Style, _slcItemSelectedStyle :: Maybe Style,- _slcMergeRequired :: Maybe (Seq a -> Seq a -> Bool),+ _slcMergeRequired :: Maybe (WidgetEnv s e -> Seq a -> Seq a -> Bool), _slcOnFocusReq :: [Path -> WidgetRequest s e], _slcOnBlurReq :: [Path -> WidgetRequest s e], _slcOnChangeReq :: [a -> WidgetRequest s e],@@ -172,7 +181,7 @@ _slcItemSelectedStyle = Just style } -instance CmbMergeRequired (SelectListCfg s e a) (Seq a) where+instance CmbMergeRequired (SelectListCfg s e a) (WidgetEnv s e) (Seq a) where mergeRequired fn = def { _slcMergeRequired = Just fn }@@ -299,8 +308,8 @@ mergeChildrenReq wenv node oldNode oldState = result where oldItems = _prevItems oldState- mergeRequiredFn = fromMaybe (/=) (_slcMergeRequired config)- result = mergeRequiredFn oldItems items+ mergeRequiredFn = fromMaybe (const (/=)) (_slcMergeRequired config)+ result = mergeRequiredFn wenv oldItems items merge wenv node oldNode oldState = resultNode newNode where selected = currentValue wenv@@ -406,7 +415,7 @@ itemScrollTo wenv node idx = maybeToList (scrollToReq <$> mwid <*> vp) where vp = itemViewport node idx- mwid = findWidgetIdFromPath wenv (parentPath node)+ mwid = widgetIdFromPath wenv (parentPath node) scrollToReq wid rect = SendMessage wid (ScrollTo rect) itemViewport node idx = viewport where
src/Monomer/Widgets/Containers/Split.hs view
@@ -6,9 +6,20 @@ Stability : experimental Portability : non-portable -Splits the assigned space into two parts, vertically or horizontally, which are+Splits the assigned space into two areas, vertically or horizontally, which are assigned to its two child nodes. The space assigned depends on the style and size requirements of each child node.++@+actionPanel = vstack [+ button "Image 1" ShowImage1,+ button "Image 2" ShowImage2,+ button "Image 3" ShowImage3+ ]+contentPanel = scroll (image activeImage)++mainPanel = hsplit (actionPanel, contentPanel)+@ -} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-}
src/Monomer/Widgets/Containers/Stack.hs view
@@ -6,10 +6,35 @@ Stability : experimental Portability : non-portable -Container which stacks its children along a main axis. The layout algorithm-considers the different type of size requirements and assigns space according to-the logic defined in 'SizeReq'. If the requested fixed space is larger that the-viewport of the stack, the content will overflow.+Container that stacks its children along a main axis.++An hstack widget will assign horizontal space to its children according to their+size requests. The inverse happens with vstack and vertical space, which assigns+vertical space as requested and all the horizontal space available.++For example, a label will get enough space to be displayed completely, and it+will also get all the vertical space the hstack has. This means that if the+hstack is the top level widget in the window, the label will also be as tall as+the window.++Both can be combined to create complex layouts. Considering the situation of a+top-level hstack which created a large vertical label, we could wrap the hstack+with a vstack to only use as much horizontal and vertical space as needed. Both+stack widgets will request space from their parent, along their corresponding+axis, based on the requests of their children.++The layout algorithm considers the different type of size requirements and+assigns space according to the logic defined in 'SizeReq'. If the requested+fixed space is larger that the viewport of the stack, the content will overflow.++@+vstack_ [childSpacing] [+ label "Selected image",+ image "assets/large-image.jpg"+ \`styleBasic\` [maxHeight 400],+ button \"Complete\" CompleteAction+ ]+@ -} {-# LANGUAGE Strict #-}
src/Monomer/Widgets/Containers/ThemeSwitch.hs view
@@ -8,9 +8,23 @@ Switches to the provided theme for its child nodes. -Note: this widget ignores style settings. If you need to display borders or any-other kind of style configuration, set it on the child node or wrap the-themeSwitch widget in a "Monomer.Widgets.Containers.Box".+@+theme = case activeTheme of+ DarkTheme -> darkTheme+ LightTheme -> lightTheme++widgetTree = themeSwitch theme $ vstack [+ hstack [+ label "Select theme:",+ spacer,+ textDropdownS activeTheme [DarkTheme, LightTheme]+ ]+ ]+@++Note: this widget ignores style settings applied to itself. If you need to+display borders or any other kind of style configuration, set it on the child+node or wrap the themeSwitch widget in a "Monomer.Widgets.Containers.Box". -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE StrictData #-}
src/Monomer/Widgets/Containers/Tooltip.hs view
@@ -6,13 +6,18 @@ Stability : experimental Portability : non-portable -Displays a text message above its child node when the pointer is on top and-the delay, if any, has ellapsed.+Displays a text message above its child node when the pointer is on top and the+delay, if any, has ellapsed. -Tooltip styling is a bit unusual, since it only applies to the overlaid element.-This means, padding will not be shown for the contained child element, but only+Tooltip styling is a bit unusual, since it is applied to the overlaid element.+This means padding will not be shown for the contained child element, but only on the message when the tooltip is active. If you need padding around the child-element, you may want to use a box.+element, you can use a "Monomer.Widgets.Containers.Box" around it.++@+tooltip "Click the button" (buttom \"Accept\" AcceptAction)+ \`styleBasic\` [textSize 16, bgColor steelBlue, paddingH 5, radius 5]+@ -} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-}@@ -51,7 +56,7 @@ - 'tooltipFollow': if, after tooltip is displayed, it should follow the mouse. -} data TooltipCfg = TooltipCfg {- _ttcDelay :: Maybe Int,+ _ttcDelay :: Maybe Millisecond, _ttcFollowCursor :: Maybe Bool, _ttcMaxWidth :: Maybe Double, _ttcMaxHeight :: Maybe Double@@ -87,7 +92,7 @@ } -- | Delay before the tooltip is displayed when child widget is hovered.-tooltipDelay :: Int -> TooltipCfg+tooltipDelay :: Millisecond -> TooltipCfg tooltipDelay ms = def { _ttcDelay = Just ms }@@ -100,7 +105,7 @@ data TooltipState = TooltipState { _ttsLastPos :: Point,- _ttsLastPosTs :: Int+ _ttsLastPosTs :: Millisecond } deriving (Eq, Show, Generic) -- | Creates a tooltip for the child widget.
src/Monomer/Widgets/Containers/ZStack.hs view
@@ -7,13 +7,21 @@ Portability : non-portable Layered container, stacking children one on top of the other. Useful for-handling widgets that need to be visible in certain contexts only (dialogs), or-to overlay unrelated widgets (text on top of an image).+handling widgets that need to be visible in certain contexts only, such as+dialogs, or to overlay unrelated widgets (text on top of an image). The order of the widgets is from bottom to top. The container will request the largest combination of horizontal and vertical size requested by its child nodes.++@+zstack [+ image_ "assets/test-image.png" [fitFill],+ label "Image caption"+ \`styleBasic\` [textFont \"Bold\", textSize 20, textCenter]+ ]+@ -} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-}@@ -127,7 +135,7 @@ ZStackState oldFocusMap oldTopIdx = oldState children = node ^. L.children focusedPath = wenv ^. L.focusedPath- focusedWid = findWidgetIdFromPath wenv focusedPath+ focusedWid = widgetIdFromPath wenv focusedPath isFocusParent = isNodeParentOfPath node focusedPath topLevel = isNodeTopLevel wenv node
src/Monomer/Widgets/Singles/Base/InputField.hs view
@@ -10,8 +10,8 @@ representations of other types, such as numbers and dates. It is not meant for direct use, but to create custom widgets using it. -See "Monomer.Widgets.Singles.NumericField", "Monomer.Widgets.Singles.DateField"-and "Monomer.Widgets.Singles.TimeField".+See "Monomer.Widgets.Singles.NumericField", "Monomer.Widgets.Singles.DateField",+"Monomer.Widgets.Singles.TimeField" and "Monomer.Widgets.Singles.TextField". -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-}@@ -34,8 +34,7 @@ import Control.Applicative ((<|>)) import Control.Monad-import Control.Lens- (ALens', (&), (.~), (?~), (%~), (^.), (^?), _2, _Just, cloneLens, non)+import Control.Lens hiding ((|>)) import Data.Default import Data.Maybe import Data.Sequence (Seq(..), (|>))@@ -97,13 +96,15 @@ -- | Caret width. _ifcCaretWidth :: Maybe Double, -- | Caret blink period.- _ifcCaretMs :: Maybe Int,+ _ifcCaretMs :: Maybe Millisecond, -- | Character to display as text replacement. Useful for passwords. _ifcDisplayChar :: Maybe Char, -- | Whether input causes ResizeWidgets requests. Defaults to False. _ifcResizeOnChange :: Bool, -- | If all input should be selected when focus is received. _ifcSelectOnFocus :: Bool,+ -- | Whether the input should be read-only (with editing not allowed, but allowing selection).+ _ifcReadOnly :: Bool, -- | Conversion from text to the expected value. Failure returns Nothing. _ifcFromText :: Text -> Maybe a, -- | Conversion from a value to text. Cannot fail.@@ -184,7 +185,7 @@ -- | Current index into history. _ifsHistIdx :: Int, -- | The timestamp when focus was received (used for caret blink)- _ifsFocusStart :: Int+ _ifsFocusStart :: Millisecond } deriving (Eq, Show, Typeable, Generic) initialState :: a -> InputFieldState a@@ -207,7 +208,7 @@ defCaretW :: Double defCaretW = 2 -defCaretMs :: Int+defCaretMs :: Millisecond defCaretMs = 500 -- | Creates an instance of an input field, with customizations in config.@@ -255,6 +256,7 @@ -- Text/value conversion functions !caretW = fromMaybe defCaretW (_ifcCaretWidth config) !caretMs = fromMaybe defCaretMs (_ifcCaretMs config)+ !editable = not (_ifcReadOnly config) !fromText = _ifcFromText config !toText = _ifcToText config getModelValue !wenv = widgetDataGet (_weModel wenv) (_ifcValue config)@@ -318,10 +320,10 @@ reqs = [ RenderStop widgetId ] handleKeyPress wenv mod code- | isDelBackWordNoSel = Just $ moveCursor removeWord prevWordStartIdx Nothing- | isDelBackWord = Just $ moveCursor removeText minTpSel Nothing- | isBackspace && emptySel = Just $ moveCursor removeText (tp - 1) Nothing- | isBackspace = Just $ moveCursor removeText minTpSel Nothing+ | isDelBackWordNoSel && editable = Just $ moveCursor removeWord prevWordStartIdx Nothing+ | isDelBackWord && editable = Just $ moveCursor removeText minTpSel Nothing+ | isBackspace && emptySel && editable = Just $ moveCursor removeText (tp - 1) Nothing+ | isBackspace && editable = Just $ moveCursor removeText minTpSel Nothing | isMoveLeft = Just $ moveCursor txt (tp - 1) Nothing | isMoveRight = Just $ moveCursor txt (tp + 1) Nothing | isMoveWordL = Just $ moveCursor txt prevWordStartIdx Nothing@@ -476,7 +478,8 @@ -- Handle custom drag Move point- | isNodePressed wenv node && shiftPressed -> Just result where+ | isNodePressed wenv node && isShiftDrag -> Just result where+ isShiftDrag = shiftPressed && isJust dragHandler (_, stPoint) = fromJust $ wenv ^. L.mainBtnPress handlerRes = fromJust dragHandler state stPoint point (newText, newPos, newSel) = handlerRes@@ -499,11 +502,11 @@ KeyAction mod code KeyPressed | isKeyboardCopy wenv evt -> Just $ resultReqs node [SetClipboard (ClipboardText selectedText)]- | isKeyboardPaste wenv evt+ | isKeyboardPaste wenv evt && editable -> Just $ resultReqs node [GetClipboard widgetId]- | isKeyboardCut wenv evt -> cutTextRes wenv node- | isKeyboardUndo wenv evt -> moveHistory wenv node state config (-1)- | isKeyboardRedo wenv evt -> moveHistory wenv node state config 1+ | isKeyboardCut wenv evt && editable -> cutTextRes wenv node+ | isKeyboardUndo wenv evt && editable -> moveHistory wenv node state config (-1)+ | isKeyboardRedo wenv evt && editable -> moveHistory wenv node state config 1 | otherwise -> fmap handleKeyRes keyRes <|> cursorRes where !keyRes = handleKeyPress wenv mod code handleKeyRes (!newText, !newPos, !newSel) = result where@@ -522,8 +525,9 @@ result = Just (resultReqs node reqs) -- Text input has unicode already processed (it's not the same as KeyAction)- TextInput newText -> result where- result = insertTextRes wenv node newText+ TextInput newText+ | editable -> result where+ result = insertTextRes wenv node newText -- Paste clipboard contents Clipboard (ClipboardText newText) -> result where@@ -568,7 +572,7 @@ && shiftPressed && isJust dragHandler validCursor- | not shiftPressed = CursorIBeam+ | not shiftPressed || isNothing dragHandler = CursorIBeam | otherwise = fromMaybe CursorArrow dragCursor changeCursorReq newCursor = reqs where cursorMatch = wenv ^? L.cursor . _Just . _2 == Just newCursor@@ -706,9 +710,14 @@ tsFontColor = styleFontColor style getCaretH :: InputFieldState a -> Double-getCaretH state = ta - td * 2 where- TextMetrics ta td _ _ = _ifsTextMetrics state+getCaretH state = lineh where+ TextMetrics asc desc lineh _ = _ifsTextMetrics state +getCaretOffset :: TextMetrics -> StyleState -> Double+getCaretOffset metrics style = textOffset - desc where+ TextMetrics asc desc lineh _ = metrics+ textOffset = textOffsetY metrics style+ getCaretRect :: InputFieldCfg s e a -> InputFieldState a@@ -727,7 +736,7 @@ | pos >= length glyphs = _glpXMax (seqLast glyphs) | otherwise = _glpXMin (Seq.index glyphs pos) caretX tx = max 0 $ min (cx + cw - caretW) (tx + caretPos)- caretY = ty + textOffsetY textMetrics style+ caretY = ty + getCaretOffset textMetrics style caretRect = Rect (caretX tx) caretY caretW (getCaretH state) getSelRect :: InputFieldState a -> StyleState -> Rect@@ -737,7 +746,7 @@ glyphs = _ifsGlyphs state pos = _ifsCursorPos state sel = _ifsSelStart state- caretY = ty + textOffsetY textMetrics style+ caretY = ty + getCaretOffset textMetrics style caretH = getCaretH state glyph idx = Seq.index glyphs (min idx (length glyphs - 1)) gx idx = _glpXMin (glyph idx)@@ -755,7 +764,7 @@ textLen = getGlyphsMax (_ifsGlyphs state) glyphs | Seq.null (_ifsGlyphs state) = Seq.empty- | otherwise = _ifsGlyphs state |> GlyphPos ' ' textLen 0 0 0 0 0+ | otherwise = _ifsGlyphs state |> GlyphPos ' ' 0 textLen 0 0 0 0 0 glyphStart i g = (i, abs (_glpXMin g - localX)) pairs = Seq.mapWithIndex glyphStart glyphs cpm (_, g1) (_, g2) = compare g1 g2
src/Monomer/Widgets/Singles/Button.hs view
@@ -8,6 +8,10 @@ Button widget, with support for multiline text. At the most basic level, a button consists of a caption and an event to raise when clicked.++@+button "Increase count" AppIncrease+@ -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}@@ -19,10 +23,12 @@ -- * Configuration ButtonCfg, -- * Constructors+ mainButton,+ mainButton_,+ mainButtonD_, button, button_,- mainButton,- mainButton_+ buttonD_ ) where import Control.Applicative ((<|>))@@ -46,6 +52,9 @@ {-| Configuration options for button: +- 'ignoreParentEvts': whether to ignore all other responses to the click or+ keypress that triggered the button, and only keep this button's response.+ Useful when the button is child of a _keystroke_ widget. - 'trimSpaces': whether to remove leading/trailing spaces in the caption. - 'ellipsis': if ellipsis should be used for overflown text. - 'multiline': if text may be split in multiple lines.@@ -62,6 +71,7 @@ -} data ButtonCfg s e = ButtonCfg { _btnButtonType :: Maybe ButtonType,+ _btnIgnoreParent :: Maybe Bool, _btnIgnoreTheme :: Maybe Bool, _btnLabelCfg :: LabelCfg s e, _btnOnFocusReq :: [Path -> WidgetRequest s e],@@ -72,6 +82,7 @@ instance Default (ButtonCfg s e) where def = ButtonCfg { _btnButtonType = Nothing,+ _btnIgnoreParent = Nothing, _btnIgnoreTheme = Nothing, _btnLabelCfg = def, _btnOnFocusReq = [],@@ -82,6 +93,7 @@ instance Semigroup (ButtonCfg s e) where (<>) t1 t2 = ButtonCfg { _btnButtonType = _btnButtonType t2 <|> _btnButtonType t1,+ _btnIgnoreParent = _btnIgnoreParent t2 <|> _btnIgnoreParent t1, _btnIgnoreTheme = _btnIgnoreTheme t2 <|> _btnIgnoreTheme t1, _btnLabelCfg = _btnLabelCfg t1 <> _btnLabelCfg t2, _btnOnFocusReq = _btnOnFocusReq t1 <> _btnOnFocusReq t2,@@ -92,6 +104,11 @@ instance Monoid (ButtonCfg s e) where mempty = def +instance CmbIgnoreParentEvts (ButtonCfg s e) where+ ignoreParentEvts_ ignore = def {+ _btnIgnoreParent = Just ignore+ }+ instance CmbIgnoreTheme (ButtonCfg s e) where ignoreTheme_ ignore = def { _btnIgnoreTheme = Just ignore@@ -165,15 +182,30 @@ _btnButtonType = Just ButtonMain } --- | Creates a button with main styling. Useful for dialogs.+{-|+Creates a button with main styling. Useful to highlight an option, such as+"Accept", when multiple buttons are available.+-} mainButton :: WidgetEvent e => Text -> e -> WidgetNode s e mainButton caption handler = button_ caption handler [mainConfig] --- | Creates a button with main styling. Useful for dialogs. Accepts config.+{-|+Creates a button with main styling. Useful to highlight an option, such as+"Accept", when multiple buttons are available. Accepts config.+-} mainButton_ :: WidgetEvent e => Text -> e -> [ButtonCfg s e] -> WidgetNode s e mainButton_ caption handler configs = button_ caption handler newConfigs where newConfigs = mainConfig : configs +{-|+Creates a button with main styling. Useful to highlight an option, such as+"Accept", when multiple buttons are available. Accepts config but does not+require an event. See 'buttonD_'.+-}+mainButtonD_ :: WidgetEvent e => Text -> [ButtonCfg s e] -> WidgetNode s e+mainButtonD_ caption configs = buttonD_ caption newConfigs where+ newConfigs = mainConfig : configs+ -- | Creates a button with normal styling. button :: WidgetEvent e => Text -> e -> WidgetNode s e button caption handler = button_ caption handler def@@ -181,7 +213,20 @@ -- | Creates a button with normal styling. Accepts config. button_ :: WidgetEvent e => Text -> e -> [ButtonCfg s e] -> WidgetNode s e button_ caption handler configs = buttonNode where- config = onClick handler <> mconcat configs+ buttonNode = buttonD_ caption (onClick handler : configs)++{-|+Creates a button without forcing an event to be provided. The other constructors+use this version, adding an 'onClick' handler in configs.++Using this constructor directly can be helpful in cases where the event to be+raised belongs in a _Composite_ above in the widget tree, outside the scope of+the Composite that contains the button. This parent Composite can be reached by+sending a message ('SendMessage') to its 'WidgetId' using 'onClickReq'.+-}+buttonD_ :: WidgetEvent e => Text -> [ButtonCfg s e] -> WidgetNode s e+buttonD_ caption configs = buttonNode where+ config = mconcat configs widget = makeButton caption config !buttonNode = defaultWidgetNode "button" widget & L.info . L.focusable .~ True@@ -242,9 +287,12 @@ _ -> Nothing where mainBtn btn = btn == wenv ^. L.mainButton+ focused = isNodeFocused wenv node pointInVp p = isPointInNodeVp node p- reqs = _btnOnClickReq config+ ignoreParent = _btnIgnoreParent config == Just True++ reqs = _btnOnClickReq config ++ [IgnoreParentEvents | ignoreParent] result = resultReqs node reqs resultFocus = resultReqs node [SetFocus (node ^. L.info . L.widgetId)]
src/Monomer/Widgets/Singles/Checkbox.hs view
@@ -8,10 +8,15 @@ Checkbox widget, used for interacting with boolean values. It does not include text, which can be added with a label in the desired position (usually with-hstack). Alternatively, "Monomer.Widgets.Singles.LabeledCheckbox" provides this-functionality out of the box.+[hstack/vstack]("Monomer.Widgets.Containers.Stack")). Alternatively,+"Monomer.Widgets.Singles.LabeledCheckbox" provides this functionality out of the+box. -'Monomer.Widgets.Singles.ToggleButton' provides similar functionality but with+@+checkbox booleanLens+@++"Monomer.Widgets.Singles.ToggleButton" provides similar functionality but with the look of a regular button. -} {-# LANGUAGE BangPatterns #-}
src/Monomer/Widgets/Singles/ColorPicker.hs view
@@ -6,16 +6,21 @@ Stability : experimental Portability : non-portable -Color picker using sliders and numeric fields.+Color picker, displayed inside its parent container as a regular widget.++@+colorPicker colorLens+@ -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE Strict #-} module Monomer.Widgets.Singles.ColorPicker ( -- * Configuration ColorPickerCfg,+ ColorPickerEvt, -- * Constructors colorPicker, colorPicker_,@@ -122,11 +127,12 @@ _cpcOnChangeReq = [req] } +-- | Internal events for the 'colorPicker' widget. data ColorPickerEvt = PickerFocus Path | PickerBlur Path- | ColorChanged Int- | AlphaChanged Double+ | PickerColorChanged Int+ | PickerAlphaChanged Double deriving (Eq, Show) -- | Creates a color picker using the given lens.@@ -188,8 +194,10 @@ buildUI config wenv model = mainTree where showAlpha = fromMaybe False (_cpcShowAlpha config) colorSample = zstack [- patternImage 2 10 (rgb 255 255 255) (rgb 150 150 150),- filler `styleBasic` [bgColor model]+ patternImage 2 10 (rgb 255 255 255) (rgb 150 150 150)+ `styleBasic` [radius 4],+ filler+ `styleBasic` [bgColor model, radius 4] ] `styleBasic` [width 32] compRow lensCol evt lbl minV maxV = hstack [@@ -201,11 +209,11 @@ spacer_ [width 5], numericField_ lensCol [minValue minV, maxValue maxV, onChange evt, onFocus PickerFocus, onBlur PickerBlur]- `styleBasic` [width 40, padding 0, textRight]+ `styleBasic` [width 40, padding 0, textAscender, textRight] ] - colorRow lens lbl = compRow lens ColorChanged lbl 0 255- alphaRow lens lbl = compRow lens AlphaChanged lbl 0 1+ colorRow lens lbl = compRow lens PickerColorChanged lbl 0 255+ alphaRow lens lbl = compRow lens PickerAlphaChanged lbl 0 1 mainTree = hstack_ [sizeReqUpdater clearExtra] [ vstack [@@ -234,8 +242,8 @@ | not (isNodeParentOfPath node prev) -> reportFocus prev PickerBlur next | not (isNodeParentOfPath node next) -> reportBlur next- ColorChanged _ -> reportChange- AlphaChanged _ -> reportChange+ PickerColorChanged _ -> reportChange+ PickerAlphaChanged _ -> reportChange _ -> [] where report reqs = RequestParent <$> reqs
src/Monomer/Widgets/Singles/DateField.hs view
@@ -6,14 +6,23 @@ Stability : experimental Portability : non-portable -Input field for dates types.+Input field for dates types with support for valid ranges, different formats and+separators. -Supports the Day type of the <https://hackage.haskell.org/package/time time>-library, but other types can be supported by implementing 'DayConverter'. Maybe-is also supported.+@+dateField dateLens+@ -Supports different date formats and separators.+With configuration options: +@+dateField_ dateLens [dateFormatMMDDYYYY, dateFormatDelimiter \'-\']+@++Supports the 'Day' type of the <https://hackage.haskell.org/package/time time>+library, but other types can be supported by implementing 'DayConverter'.+'Maybe' is also supported.+ Handles mouse wheel and shift + vertical drag to increase/decrease days. -} {-# LANGUAGE ConstraintKinds #-}@@ -25,14 +34,16 @@ module Monomer.Widgets.Singles.DateField ( -- * Configuration DateFieldCfg,+ DateFieldFormat, FormattableDate, DayConverter(..),- DateTextConverter,+ DateTextConverter(..), -- * Constructors dateField, dateField_, dateFieldV, dateFieldV_,+ dateFieldD_, dateFormatDelimiter, dateFormatDDMMYYYY, dateFormatMMDDYYYY,@@ -61,13 +72,14 @@ import qualified Monomer.Lens as L import qualified Monomer.Widgets.Util.Parser as P -data DateFormat+-- | Available formats for 'dateField'.+data DateFieldFormat = FormatDDMMYYYY | FormatYYYYMMDD | FormatMMDDYYYY deriving (Eq, Show) -defaultDateFormat :: DateFormat+defaultDateFormat :: DateFieldFormat defaultDateFormat = FormatDDMMYYYY defaultDateDelim :: Char@@ -85,11 +97,14 @@ convertFromDay = id convertToDay = Just --- | Converts a 'Day' instance to and from 'Text'.+{-|+Converts a 'Day' instance to and from 'Text'. Implementing this typeclass+is not necessary for instances of 'DayConverter'.+-} class DateTextConverter a where- dateAcceptText :: DateFormat -> Char -> Maybe a -> Maybe a -> Text -> (Bool, Bool, Maybe a)- dateFromText :: DateFormat -> Char -> Text -> Maybe a- dateToText :: DateFormat -> Char -> a -> Text+ dateAcceptText :: DateFieldFormat -> Char -> Maybe a -> Maybe a -> Text -> (Bool, Bool, Maybe a)+ dateFromText :: DateFieldFormat -> Char -> Text -> Maybe a+ dateToText :: DateFieldFormat -> Char -> a -> Text dateFromDay :: Day -> a dateToDay :: a -> Maybe Day @@ -134,6 +149,7 @@ warnings in the UI, or disable buttons if needed. - 'resizeOnChange': Whether input causes 'ResizeWidgets' requests. - 'selectOnFocus': Whether all input should be selected when focus is received.+- 'readOnly': Whether to prevent the user changing the input text. - 'minValue': Minimum valid date. - 'maxValue': Maximum valid date. - 'wheelRate': The rate at which wheel movement affects the date.@@ -151,17 +167,18 @@ -} data DateFieldCfg s e a = DateFieldCfg { _dfcCaretWidth :: Maybe Double,- _dfcCaretMs :: Maybe Int,+ _dfcCaretMs :: Maybe Millisecond, _dfcValid :: Maybe (WidgetData s Bool), _dfcValidV :: [Bool -> e], _dfcDateDelim :: Maybe Char,- _dfcDateFormat :: Maybe DateFormat,+ _dfcDateFormat :: Maybe DateFieldFormat, _dfcMinValue :: Maybe a, _dfcMaxValue :: Maybe a, _dfcWheelRate :: Maybe Double, _dfcDragRate :: Maybe Double, _dfcResizeOnChange :: Maybe Bool, _dfcSelectOnFocus :: Maybe Bool,+ _dfcReadOnly :: Maybe Bool, _dfcOnFocusReq :: [Path -> WidgetRequest s e], _dfcOnBlurReq :: [Path -> WidgetRequest s e], _dfcOnChangeReq :: [a -> WidgetRequest s e]@@ -181,6 +198,7 @@ _dfcDragRate = Nothing, _dfcResizeOnChange = Nothing, _dfcSelectOnFocus = Nothing,+ _dfcReadOnly = Nothing, _dfcOnFocusReq = [], _dfcOnBlurReq = [], _dfcOnChangeReq = []@@ -200,6 +218,7 @@ _dfcDragRate = _dfcDragRate t2 <|> _dfcDragRate t1, _dfcResizeOnChange = _dfcResizeOnChange t2 <|> _dfcResizeOnChange t1, _dfcSelectOnFocus = _dfcSelectOnFocus t2 <|> _dfcSelectOnFocus t1,+ _dfcReadOnly = _dfcReadOnly t2 <|> _dfcReadOnly t1, _dfcOnFocusReq = _dfcOnFocusReq t1 <> _dfcOnFocusReq t2, _dfcOnBlurReq = _dfcOnBlurReq t1 <> _dfcOnBlurReq t2, _dfcOnChangeReq = _dfcOnChangeReq t1 <> _dfcOnChangeReq t2@@ -213,7 +232,7 @@ _dfcCaretWidth = Just w } -instance CmbCaretMs (DateFieldCfg s e a) Int where+instance CmbCaretMs (DateFieldCfg s e a) Millisecond where caretMs ms = def { _dfcCaretMs = Just ms }@@ -238,14 +257,19 @@ _dfcSelectOnFocus = Just sel } +instance CmbReadOnly (DateFieldCfg s e a) where+ readOnly_ ro = def {+ _dfcReadOnly = Just ro+ }+ instance FormattableDate a => CmbMinValue (DateFieldCfg s e a) a where- minValue len = def {- _dfcMinValue = Just len+ minValue value = def {+ _dfcMinValue = Just value } instance FormattableDate a => CmbMaxValue (DateFieldCfg s e a) a where- maxValue len = def {- _dfcMaxValue = Just len+ maxValue value = def {+ _dfcMaxValue = Just value } instance CmbWheelRate (DateFieldCfg s e a) Double where@@ -358,6 +382,7 @@ delim = fromMaybe defaultDateDelim (_dfcDateDelim config) minVal = _dfcMinValue config maxVal = _dfcMaxValue config+ readOnly = fromMaybe False (_dfcReadOnly config) initialValue | isJust minVal = fromJust minVal@@ -387,9 +412,10 @@ _ifcDisplayChar = Nothing, _ifcResizeOnChange = fromMaybe False (_dfcResizeOnChange config), _ifcSelectOnFocus = fromMaybe True (_dfcSelectOnFocus config),+ _ifcReadOnly = readOnly, _ifcStyle = Just L.dateFieldStyle,- _ifcWheelHandler = Just (handleWheel config),- _ifcDragHandler = Just (handleDrag config),+ _ifcWheelHandler = if readOnly then Nothing else Just (handleWheel config),+ _ifcDragHandler = if readOnly then Nothing else Just (handleDrag config), _ifcDragCursor = Just CursorSizeV, _ifcOnFocusReq = _dfcOnFocusReq config, _ifcOnBlurReq = _dfcOnBlurReq config,@@ -463,7 +489,7 @@ dateFromTextSimple :: (DayConverter a, FormattableDate a)- => DateFormat+ => DateFieldFormat -> Char -> Text -> Maybe a@@ -478,7 +504,7 @@ | otherwise -> fromGregorianValid (fromIntegral n1) n2 n3 newDate = tmpDate >>= dateFromDay -dateToTextSimple :: FormattableDate a => DateFormat -> Char -> a -> Text+dateToTextSimple :: FormattableDate a => DateFieldFormat -> Char -> a -> Text dateToTextSimple format delim val = result where converted = dateToDay val (year, month, day) = toGregorian (fromJust converted)@@ -495,7 +521,7 @@ | format == FormatMMDDYYYY = tmonth <> sep <> tday <> sep <> tyear | otherwise = tyear <> sep <> tmonth <> sep <> tday -acceptTextInput :: DateFormat -> Char -> Text -> Bool+acceptTextInput :: DateFieldFormat -> Char -> Text -> Bool acceptTextInput format delim text = isRight (A.parseOnly parser text) where numP = A.digit *> "" delimP = A.char delim *> ""
src/Monomer/Widgets/Singles/Dial.hs view
@@ -7,9 +7,14 @@ Portability : non-portable Dial widget, used for interacting with numeric values. It allows changing the-value by keyboard arrows, dragging the mouse or using the wheel.+value using the keyboard arrows, dragging the mouse or using the wheel. -Similar in objective to "Monomer.Widgets.Singles.Slider", but uses less space.+@+dial numericLens 0 100+@++Similar in objective to "Monomer.Widgets.Singles.Slider", but uses less visual+space in its parent container. -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-}
src/Monomer/Widgets/Singles/ExternalLink.hs view
@@ -6,8 +6,12 @@ Stability : experimental Portability : non-portable -Provides a clickable link that opens in the system's browser. It uses OS-services to open the URI, which means not only URLs can be opened.+Provides a clickable link that is opened by the host OS. Since it relies on the+OS to open the content, it is possible to open URIs other than urls.++@+externalLink "Open Wikipedia" "https://en.wikipedia.org"+@ -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}
src/Monomer/Widgets/Singles/Icon.hs view
@@ -7,6 +7,10 @@ Portability : non-portable Icon widget. Used for showing basic icons without the need of an asset.++@+icon IconPlus+@ -} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}
src/Monomer/Widgets/Singles/Image.hs view
@@ -8,6 +8,12 @@ Displays an image from local storage or a url. +@+image "https://picsum.photos/id/1059/800/600"+@++It is also possible to create images from a block of memory using 'imageMem'.+ Notes: - Depending on the type of image fit chosen and the assigned viewport, some
src/Monomer/Widgets/Singles/Label.hs view
@@ -7,6 +7,18 @@ Portability : non-portable Label widget, with support for multiline text.++Single line label:++@+label "This is a label"+@++Multi-line label:++@+label_ "This is a\nmultiline label" [multiline]+@ -} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE Strict #-}@@ -142,7 +154,7 @@ _lstTextStyle :: Maybe TextStyle, _lstTextRect :: Rect, _lstTextLines :: Seq TextLine,- _lstPrevResize :: (Int, Bool)+ _lstPrevResize :: (Millisecond, Bool) } deriving (Eq, Show, Generic) -- | Creates a label using the provided 'Text'.
src/Monomer/Widgets/Singles/LabeledCheckbox.hs view
@@ -6,8 +6,18 @@ Stability : experimental Portability : non-portable -Labeled checkbox, used for interacting with boolean values with an associated-clickable label.+Labeled checkbox, used for interacting with boolean values. In contrast to+'checkbox', it includes a clickable label.++@+labeledCheckbox "Bool value" booleanLens+@++With text in a different location:++@+labeledCheckbox_ "Checkbox with text below" booleanLens [textBottom]+@ -} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}
src/Monomer/Widgets/Singles/LabeledRadio.hs view
@@ -6,9 +6,19 @@ Stability : experimental Portability : non-portable -Radio widget, used for interacting with a fixed set of values with an associated-clickable label. Each instance of the radio will be associated with a single-value.+Labeled radio widget, used for interacting with a fixed set of values. Each+instance of labeled radio is associated with a single value. In contrast to+'radio', it includes a clickable label.++@+labeledRadio "First option" Option1 optionLens+@++With text in a different location:++@+labeledRadio_ "Radio with text below" Option1 optionLens [textBottom]+@ -} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleInstances #-}
src/Monomer/Widgets/Singles/NumericField.hs view
@@ -6,8 +6,18 @@ Stability : experimental Portability : non-portable -Input field for numeric types.+Input field for numeric types, with support for valid ranges and decimal places. +@+numericField numericLens+@++With configuration options:++@+numericField_ numericLens [minValue 0, maxValue 100, decimals 2]+@+ Supports instances of the 'FromFractional' typeclass. Several basic types are implemented, both for integer and floating point types. @@ -23,12 +33,13 @@ -- * Configuration NumericFieldCfg, FormattableNumber,- NumericTextConverter,+ NumericTextConverter(..), -- * Constructors numericField, numericField_, numericFieldV,- numericFieldV_+ numericFieldV_,+ numericFieldD_ ) where import Control.Applicative ((<|>))@@ -56,7 +67,10 @@ import qualified Monomer.Lens as L import qualified Monomer.Widgets.Util.Parser as P --- | Converts a numeric instance to and from 'Text'.+{-|+Converts a numeric instance to and from 'Text'. Implementing this typeclass+is not necessary for instances of 'FromFractional'.+-} class NumericTextConverter a where numericAcceptText :: Maybe a -> Maybe a -> Int -> Text -> (Bool, Bool, Maybe a) numericFromText :: Text -> Maybe a@@ -107,6 +121,7 @@ warnings in the UI, or disable buttons if needed. - 'resizeOnChange': Whether input causes ResizeWidgets requests. - 'selectOnFocus': Whether all input should be selected when focus is received.+- 'readOnly': Whether to prevent the user changing the input text. - 'minValue': Minimum valid number. - 'maxValue': Maximum valid number. - 'wheelRate': The rate at which wheel movement affects the number.@@ -122,7 +137,7 @@ -} data NumericFieldCfg s e a = NumericFieldCfg { _nfcCaretWidth :: Maybe Double,- _nfcCaretMs :: Maybe Int,+ _nfcCaretMs :: Maybe Millisecond, _nfcValid :: Maybe (WidgetData s Bool), _nfcValidV :: [Bool -> e], _nfcDecimals :: Maybe Int,@@ -132,6 +147,7 @@ _nfcDragRate :: Maybe Double, _nfcResizeOnChange :: Maybe Bool, _nfcSelectOnFocus :: Maybe Bool,+ _nfcReadOnly :: Maybe Bool, _nfcOnFocusReq :: [Path -> WidgetRequest s e], _nfcOnBlurReq :: [Path -> WidgetRequest s e], _nfcOnChangeReq :: [a -> WidgetRequest s e]@@ -150,6 +166,7 @@ _nfcDragRate = Nothing, _nfcResizeOnChange = Nothing, _nfcSelectOnFocus = Nothing,+ _nfcReadOnly = Nothing, _nfcOnFocusReq = [], _nfcOnBlurReq = [], _nfcOnChangeReq = []@@ -167,6 +184,7 @@ _nfcWheelRate = _nfcWheelRate t2 <|> _nfcWheelRate t1, _nfcDragRate = _nfcDragRate t2 <|> _nfcDragRate t1, _nfcResizeOnChange = _nfcResizeOnChange t2 <|> _nfcResizeOnChange t1,+ _nfcReadOnly = _nfcReadOnly t2 <|> _nfcReadOnly t1, _nfcSelectOnFocus = _nfcSelectOnFocus t2 <|> _nfcSelectOnFocus t1, _nfcOnFocusReq = _nfcOnFocusReq t1 <> _nfcOnFocusReq t2, _nfcOnBlurReq = _nfcOnBlurReq t1 <> _nfcOnBlurReq t2,@@ -181,7 +199,7 @@ _nfcCaretWidth = Just w } -instance CmbCaretMs (NumericFieldCfg s e a) Int where+instance CmbCaretMs (NumericFieldCfg s e a) Millisecond where caretMs ms = def { _nfcCaretMs = Just ms }@@ -206,14 +224,19 @@ _nfcSelectOnFocus = Just sel } +instance CmbReadOnly (NumericFieldCfg s e a) where+ readOnly_ ro = def {+ _nfcReadOnly = Just ro+ }+ instance FormattableNumber a => CmbMinValue (NumericFieldCfg s e a) a where- minValue len = def {- _nfcMinValue = Just len+ minValue value = def {+ _nfcMinValue = Just value } instance FormattableNumber a => CmbMaxValue (NumericFieldCfg s e a) a where- maxValue len = def {- _nfcMaxValue = Just len+ maxValue value = def {+ _nfcMaxValue = Just value } instance CmbWheelRate (NumericFieldCfg s e a) Double where@@ -305,14 +328,17 @@ config = mconcat configs minVal = _nfcMinValue config maxVal = _nfcMaxValue config+ readOnly = fromMaybe False (_nfcReadOnly config) initialValue | isJust minVal = fromJust minVal | isJust maxVal = fromJust maxVal | otherwise = numericFromFractional 0- decimals- | isIntegral initialValue = 0- | otherwise = max 0 $ fromMaybe 2 (_nfcDecimals config)+ decimals = case _nfcDecimals config of+ Just count -> max 0 count+ Nothing+ | isIntegral initialValue -> 0+ | otherwise -> 2 defWidth | isIntegral initialValue = 50 | otherwise = 70@@ -340,9 +366,10 @@ _ifcDisplayChar = Nothing, _ifcResizeOnChange = fromMaybe False (_nfcResizeOnChange config), _ifcSelectOnFocus = fromMaybe True (_nfcSelectOnFocus config),+ _ifcReadOnly = readOnly, _ifcStyle = Just L.numericFieldStyle,- _ifcWheelHandler = Just (handleWheel config),- _ifcDragHandler = Just (handleDrag config),+ _ifcWheelHandler = if readOnly then Nothing else Just (handleWheel config),+ _ifcDragHandler = if readOnly then Nothing else Just (handleDrag config), _ifcDragCursor = Just CursorSizeV, _ifcOnFocusReq = _nfcOnFocusReq config, _ifcOnBlurReq = _nfcOnBlurReq config,@@ -438,7 +465,6 @@ isIntegral :: Typeable a => a -> Bool isIntegral val | "Int" `isPrefixOf` name = True- | "Fixed" `isPrefixOf` name = True | "Word" `isPrefixOf` name = True | otherwise = False where
src/Monomer/Widgets/Singles/OptionButton.hs view
@@ -7,27 +7,32 @@ Portability : non-portable Option button widget, used for choosing one value from a fixed set. Each-instance of optionButton will be associated with a single value.+instance of optionButton is associated with a single value. -Its behavior is equivalent to 'Monomer.Widgets.Singles.Radio' and-'Monomer.Widgets.Singles.LabeledRadio', with a different visual representation.+@+optionButton "First option" Option1 optionLens+@ -This widget, and the associated 'ToggleButton', uses two separate styles for the-On and Off states which can be modified individually for the theme. If you use-any of the the standard style functions (styleBasic, styleHover, etc) in an-optionButton/toggleButton these changes will apply to both On and Off states,-except for the color related styles. The reason for this is that, in general,-you will want to use the same font and padding for both states, but colors will-usually differ. For changing the colors of the Off state you can use-'optionButtonOffStyle', that receives a 'Style' instance. The values set here-are higher priority than any inherited style from the theme or node text style.+Its behavior is equivalent to "Monomer.Widgets.Singles.Radio" and+"Monomer.Widgets.Singles.LabeledRadio", with a different visual representation. +This widget, and the associated "Monomer.Widgets.Singles.ToggleButton", uses two+separate styles for the On and Off states which can be modified individually for+the theme. If you use any of the the standard style functions (styleBasic,+styleHover, etc) in an optionButton/toggleButton these changes will apply to+both On and Off states, except for the color related styles. The reason is that,+in general, the font and padding will be the same for both states, but the+colors will differ. The 'optionButtonOffStyle' option, which receives a 'Style'+instance, can be used to change the colors of the Off state. The values set with+this option are higher priority than any inherited style from the theme or node+text style.+ 'Style' instances can be created this way: @ newStyle :: Style = def- `styleBasic` [textSize 20]- `styleHover` [textColor white]+ \`styleBasic\` [textSize 20]+ \`styleHover\` [textColor white] @ -} {-# LANGUAGE BangPatterns #-}@@ -88,8 +93,10 @@ - 'onFocusReq': 'WidgetRequest' to generate when focus is received. - 'onBlur': event to raise when focus is lost. - 'onBlurReq': 'WidgetRequest' to generate when focus is lost.-- 'onChange': event to raise when the value changes/is clicked.-- 'onChangeReq': 'WidgetRequest' to generate when the value changes/is clicked.+- 'onClick': event to raise when the value is clicked.+- 'onClickReq': 'WidgetRequest' to generate when the value is clicked.+- 'onChange': event to raise when the value changes.+- 'onChangeReq': 'WidgetRequest' to generate when the value changes. -} data OptionButtonCfg s e a = OptionButtonCfg { _obcIgnoreTheme :: Maybe Bool,@@ -97,6 +104,7 @@ _obcLabelCfg :: LabelCfg s e, _obcOnFocusReq :: [Path -> WidgetRequest s e], _obcOnBlurReq :: [Path -> WidgetRequest s e],+ _obcOnClickReq :: [WidgetRequest s e], _obcOnChangeReq :: [a -> WidgetRequest s e] } @@ -107,6 +115,7 @@ _obcLabelCfg = def, _obcOnFocusReq = [], _obcOnBlurReq = [],+ _obcOnClickReq = [], _obcOnChangeReq = [] } @@ -117,6 +126,7 @@ _obcLabelCfg = _obcLabelCfg t1 <> _obcLabelCfg t2, _obcOnFocusReq = _obcOnFocusReq t1 <> _obcOnFocusReq t2, _obcOnBlurReq = _obcOnBlurReq t1 <> _obcOnBlurReq t2,+ _obcOnClickReq = _obcOnClickReq t1 <> _obcOnClickReq t2, _obcOnChangeReq = _obcOnChangeReq t1 <> _obcOnChangeReq t2 } @@ -181,6 +191,16 @@ _obcOnBlurReq = [req] } +instance WidgetEvent e => CmbOnClick (OptionButtonCfg s e a) e where+ onClick req = def {+ _obcOnClickReq = [RaiseEvent req]+ }++instance CmbOnClickReq (OptionButtonCfg s e a) s e where+ onClickReq req = def {+ _obcOnClickReq = [req]+ }+ instance WidgetEvent e => CmbOnChange (OptionButtonCfg s e a) a e where onChange fn = def { _obcOnChangeReq = [RaiseEvent . fn]@@ -259,6 +279,10 @@ optionButtonNode = defaultWidgetNode wtype widget & L.info . L.focusable .~ True +{-|+Helper function for creating a button associated to a value. Used by+_optionButton_ and _toggleButton_.+-} makeOptionButton :: OptionButtonValue a => Lens' ThemeState StyleState@@ -339,7 +363,11 @@ currValue = widgetDataGet (wenv ^. L.model) field nextValue = getNextVal currValue setValueReq = widgetDataSet field nextValue- reqs = setValueReq ++ fmap ($ nextValue) (_obcOnChangeReq config)+ clickReqs = _obcOnClickReq config+ changeReqs+ | currValue /= nextValue = fmap ($ nextValue) (_obcOnChangeReq config)+ | otherwise = []+ reqs = setValueReq ++ clickReqs ++ changeReqs result = resultReqs node reqs resultFocus = resultReqs node [SetFocus (node ^. L.info . L.widgetId)]
src/Monomer/Widgets/Singles/Radio.hs view
@@ -7,12 +7,17 @@ Portability : non-portable Radio widget, used for interacting with a fixed set of values. Each instance of-the radio will be associated with a single value. It does not include text,-which should be added as a label in the desired position (usually with hstack).-Alternatively, 'Monomer.Widgets.Singles.LabeledRadio' provides this-functionality out of the box.+the radio is associated with a single value. It does not include text, which can+be added with a label in the desired position (usually with+[hstack/vstack]("Monomer.Widgets.Containers.Stack")). Alternatively,+"Monomer.Widgets.Singles.LabeledRadio" provides this functionality out of the+box. -'Monomer.Widgets.Singles.OptionButton' provides similar functionality but with+@+radio Option1 optionLens+@++"Monomer.Widgets.Singles.OptionButton" provides similar functionality but with the look of a regular button. -} {-# LANGUAGE BangPatterns #-}@@ -57,13 +62,16 @@ - 'onFocusReq': 'WidgetRequest' to generate when focus is received. - 'onBlur': event to raise when focus is lost. - 'onBlurReq': 'WidgetRequest' to generate when focus is lost.-- 'onChange': event to raise when the value changes/is clicked.-- 'onChangeReq': 'WidgetRequest' to generate when the value changes/is clicked.+- 'onClick': event to raise when the value is clicked.+- 'onClickReq': 'WidgetRequest' to generate when the value is clicked.+- 'onChange': event to raise when the value changes.+- 'onChangeReq': 'WidgetRequest' to generate when the value changes. -} data RadioCfg s e a = RadioCfg { _rdcWidth :: Maybe Double, _rdcOnFocusReq :: [Path -> WidgetRequest s e], _rdcOnBlurReq :: [Path -> WidgetRequest s e],+ _rdcOnClickReq :: [WidgetRequest s e], _rdcOnChangeReq :: [a -> WidgetRequest s e] } @@ -72,6 +80,7 @@ _rdcWidth = Nothing, _rdcOnFocusReq = [], _rdcOnBlurReq = [],+ _rdcOnClickReq = [], _rdcOnChangeReq = [] } @@ -80,6 +89,7 @@ _rdcWidth = _rdcWidth t2 <|> _rdcWidth t1, _rdcOnFocusReq = _rdcOnFocusReq t1 <> _rdcOnFocusReq t2, _rdcOnBlurReq = _rdcOnBlurReq t1 <> _rdcOnBlurReq t2,+ _rdcOnClickReq = _rdcOnClickReq t1 <> _rdcOnClickReq t2, _rdcOnChangeReq = _rdcOnChangeReq t1 <> _rdcOnChangeReq t2 } @@ -111,6 +121,16 @@ _rdcOnBlurReq = [req] } +instance WidgetEvent e => CmbOnClick (RadioCfg s e a) e where+ onClick fn = def {+ _rdcOnClickReq = [RaiseEvent fn]+ }++instance CmbOnClickReq (RadioCfg s e a) s e where+ onClickReq req = def {+ _rdcOnClickReq = [req]+ }+ instance WidgetEvent e => CmbOnChange (RadioCfg s e a) a e where onChange fn = def { _rdcOnChangeReq = [RaiseEvent . fn]@@ -208,8 +228,14 @@ rdArea = getRadioArea wenv node config path = node ^. L.info . L.path isSelectKey code = isKeyReturn code || isKeySpace code++ currValue = widgetDataGet (wenv ^. L.model) field setValueReq = widgetDataSet field option- reqs = setValueReq ++ fmap ($ option) (_rdcOnChangeReq config)+ clickReqs = _rdcOnClickReq config+ changeReqs+ | currValue /= option = fmap ($ option) (_rdcOnChangeReq config)+ | otherwise = []+ reqs = setValueReq ++ clickReqs ++ changeReqs getSizeReq wenv node = req where theme = currentTheme wenv node
src/Monomer/Widgets/Singles/SeparatorLine.hs view
@@ -10,8 +10,16 @@ to the active layout direction, creating a vertical line on a horizontal layout and viceversa. -The line has the provided width in the direction orthogonal to the layout-direction, and takes all the available space in the other direction. In case of+@+hstack [+ label "Left half",+ separatorLine,+ label "Right half"+]+@++The separator line has the provided width in the direction orthogonal to the+parent layout, and takes all the available space in the other axis. In case of wanting a shorter line, padding should be used. -} {-# LANGUAGE BangPatterns #-}
src/Monomer/Widgets/Singles/Slider.hs view
@@ -7,9 +7,13 @@ Portability : non-portable Slider widget, used for interacting with numeric values. It allows changing the-value by keyboard arrows, dragging the mouse or using the wheel.+value using the keyboard arrows, dragging the mouse or using the wheel. -Similar in objective to 'Monomer.Widgets.Singles.Dial', but more convenient in+@+hslider numericLens 0 100+@++Similar in objective to "Monomer.Widgets.Singles.Dial", but more convenient in some layouts. -} {-# LANGUAGE BangPatterns #-}
src/Monomer/Widgets/Singles/Spacer.hs view
@@ -7,10 +7,27 @@ Portability : non-portable Spacer is used for adding a fixed space between two widgets.++@+hstack [+ label \"Username\",+ spacer,+ label username+]+@+ Filler is used for taking all the unused space between two widgets. Useful for alignment purposes. -Both adapt to the current layout direction, if any.+@+hstack [+ label "Section title",+ filler,+ button \"Close\" CloseSection+]+@++Both adapt to the active layout direction. -} {-# LANGUAGE FlexibleContexts #-}
src/Monomer/Widgets/Singles/TextArea.hs view
@@ -6,7 +6,18 @@ Stability : experimental Portability : non-portable -Input field for multiline 'Text'.+Input field for multiline 'Text'. Allows setting the maximum number of+characters, lines and whether the tab key should trigger focus change.++@+textArea longTextLens+@++With configuration options:++@+textArea_ longTextLens [maxLength 1000, selectOnFocus]+@ -} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-}@@ -23,11 +34,12 @@ textArea, textArea_, textAreaV,- textAreaV_+ textAreaV_,+ textAreaD_ ) where import Control.Applicative ((<|>))-import Control.Lens ((&), (^.), (^?), (.~), (%~), (<>~), ALens', ix, view)+import Control.Lens hiding ((|>)) import Control.Monad (forM_, when) import Data.Default import Data.Foldable (toList)@@ -49,7 +61,7 @@ defCaretW :: Double defCaretW = 2 -defCaretMs :: Int+defCaretMs :: Millisecond defCaretMs = 500 {-|@@ -60,6 +72,7 @@ - 'acceptTab': whether to handle tab and convert it to spaces (cancelling change of focus), or keep default behaviour and lose focus. - 'selectOnFocus': Whether all input should be selected when focus is received.+- 'readOnly': Whether to prevent the user changing the input text. - 'onFocus': event to raise when focus is received. - 'onFocusReq': 'WidgetRequest' to generate when focus is received. - 'onBlur': event to raise when focus is lost.@@ -69,11 +82,12 @@ -} data TextAreaCfg s e = TextAreaCfg { _tacCaretWidth :: Maybe Double,- _tacCaretMs :: Maybe Int,+ _tacCaretMs :: Maybe Millisecond, _tacMaxLength :: Maybe Int, _tacMaxLines :: Maybe Int, _tacAcceptTab :: Maybe Bool, _tacSelectOnFocus :: Maybe Bool,+ _tacReadOnly :: Maybe Bool, _tacOnFocusReq :: [Path -> WidgetRequest s e], _tacOnBlurReq :: [Path -> WidgetRequest s e], _tacOnChangeReq :: [Text -> WidgetRequest s e]@@ -87,6 +101,7 @@ _tacMaxLines = Nothing, _tacAcceptTab = Nothing, _tacSelectOnFocus = Nothing,+ _tacReadOnly = Nothing, _tacOnFocusReq = [], _tacOnBlurReq = [], _tacOnChangeReq = []@@ -100,6 +115,7 @@ _tacMaxLines = _tacMaxLines t2 <|> _tacMaxLines t1, _tacAcceptTab = _tacAcceptTab t2 <|> _tacAcceptTab t1, _tacSelectOnFocus = _tacSelectOnFocus t2 <|> _tacSelectOnFocus t1,+ _tacReadOnly = _tacReadOnly t2 <|> _tacReadOnly t1, _tacOnFocusReq = _tacOnFocusReq t1 <> _tacOnFocusReq t2, _tacOnBlurReq = _tacOnBlurReq t1 <> _tacOnBlurReq t2, _tacOnChangeReq = _tacOnChangeReq t1 <> _tacOnChangeReq t2@@ -113,7 +129,7 @@ _tacCaretWidth = Just w } -instance CmbCaretMs (TextAreaCfg s e) Int where+instance CmbCaretMs (TextAreaCfg s e) Millisecond where caretMs ms = def { _tacCaretMs = Just ms }@@ -138,6 +154,11 @@ _tacSelectOnFocus = Just sel } +instance CmbReadOnly (TextAreaCfg s e) where+ readOnly_ ro = def {+ _tacReadOnly = Just ro+ }+ instance WidgetEvent e => CmbOnFocus (TextAreaCfg s e) e Path where onFocus fn = def { _tacOnFocusReq = [RaiseEvent . fn]@@ -183,7 +204,7 @@ _tasTextLines :: Seq TextLine, _tasHistory :: Seq HistoryStep, _tasHistoryIdx :: Int,- _tasFocusStart :: Int+ _tasFocusStart :: Millisecond } deriving (Eq, Show, Generic) instance Default TextAreaState where@@ -254,6 +275,7 @@ !caretMs = fromMaybe defCaretMs (_tacCaretMs config) !maxLength = _tacMaxLength config !maxLines = _tacMaxLines config+ !editable = _tacReadOnly config /= Just True getModelValue !wenv = widgetDataGet (_weModel wenv) wdata -- State !currText = _tasText state@@ -291,10 +313,10 @@ reqs = [RenderStop widgetId] handleKeyPress wenv mod code- | isDelBackWordNoSel = Just removeWordL- | isDelBackWord = Just (replaceText state selStart "")- | isBackspace && emptySel = Just removeCharL- | isBackspace = Just (replaceText state selStart "")+ | isDelBackWordNoSel && editable = Just removeWordL+ | isDelBackWord && editable = Just (replaceText state selStart "")+ | isBackspace && emptySel && editable = Just removeCharL+ | isBackspace && editable = Just (replaceText state selStart "") | isMoveLeft = Just $ moveCursor txt (tpX - 1, tpY) Nothing | isMoveRight = Just $ moveCursor txt (tpX + 1, tpY) Nothing | isMoveUp = Just $ moveCursor txt (tpX, tpY - 1) Nothing@@ -534,12 +556,12 @@ KeyAction mod code KeyPressed | isKeyboardCopy wenv evt -> Just resultCopy- | isKeyboardPaste wenv evt -> Just resultPaste- | isKeyboardCut wenv evt -> Just resultCut- | isKeyboardUndo wenv evt -> Just $ moveHistory bwdState (-1)- | isKeyboardRedo wenv evt -> Just $ moveHistory state 1- | isKeyReturn code -> Just resultReturn- | isKeyTab code && acceptTab -> Just resultTab+ | isKeyboardPaste wenv evt && editable -> Just resultPaste+ | isKeyboardCut wenv evt && editable -> Just resultCut+ | isKeyboardUndo wenv evt && editable -> Just $ moveHistory bwdState (-1)+ | isKeyboardRedo wenv evt && editable -> Just $ moveHistory state 1+ | isKeyReturn code && editable -> Just resultReturn+ | isKeyTab code && acceptTab && editable -> Just resultTab | otherwise -> fmap handleKeyRes (handleKeyPress wenv mod code) where acceptTab = fromMaybe False (_tacAcceptTab config)@@ -576,8 +598,9 @@ & L.widget .~ makeTextArea wdata config newState result = resultReqs newNode (generateReqs wenv node newState) - TextInput newText -> Just result where- result = insertText wenv node newText+ TextInput newText+ | editable -> Just result where+ result = insertText wenv node newText Clipboard (ClipboardText newText) -> Just result where result = insertText wenv node newText@@ -644,10 +667,10 @@ generateScrollReq wenv node newState = scrollReq where style = currentStyle wenv node scPath = parentPath node- scWid = findWidgetIdFromPath wenv scPath+ scWid = widgetIdFromPath wenv scPath contentArea = getContentArea node style offset = Point (contentArea ^. L.x) (contentArea ^. L.y)- caretRect = getCaretRect config newState True+ caretRect = getCaretRect config newState -- Padding/border added to show left/top borders when moving near them scrollRect = fromMaybe caretRect (addOuterBounds style caretRect) scrollMsg = ScrollTo $ moveRect offset scrollRect@@ -683,43 +706,41 @@ caretTs = ts - _tasFocusStart state caretRequired = focused && even (caretTs `div` caretMs) caretColor = styleFontColor style- caretRect = getCaretRect config state False+ caretRect = getCaretRect config state selRequired = isJust (_tasSelStart state) selColor = styleHlColor style selRects = getSelectionRects state contentArea -getCaretRect :: TextAreaCfg s e -> TextAreaState -> Bool -> Rect-getCaretRect config state addSpcV = caretRect where- caretW = fromMaybe defCaretW (_tacCaretWidth config)+getCaretRect :: TextAreaCfg s e -> TextAreaState -> Rect+getCaretRect config state = caretRect where (cursorX, cursorY) = _tasCursorPos state- TextMetrics _ _ lineh _ = _tasTextMetrics state+ Rect tx ty _ _ = lineRect+ TextMetrics asc desc lineh _ = _tasTextMetrics state textLines = _tasTextLines state - (lineRect, glyphs, spaceV) = case Seq.lookup cursorY textLines of+ (lineRect, glyphs, fspaceV) = case Seq.lookup cursorY textLines of Just tl -> (tl ^. L.rect, tl ^. L.glyphs, tl ^. L.fontSpaceV) Nothing -> (def, Seq.empty, def)-- Rect tx ty _ _ = lineRect- totalH- | addSpcV = lineh + unFontSpace spaceV- | otherwise = lineh+ spaceV = unFontSpace fspaceV caretPos | cursorX == 0 || cursorX > length glyphs = 0 | cursorX == length glyphs = _glpXMax (Seq.index glyphs (cursorX - 1)) | otherwise = _glpXMin (Seq.index glyphs cursorX)+ caretX = max 0 (tx + caretPos)- caretY- | cursorY == length textLines = fromIntegral cursorY * totalH- | otherwise = ty- caretRect = Rect caretX caretY caretW totalH+ caretY = ty - spaceV+ caretW = fromMaybe defCaretW (_tacCaretWidth config)+ caretH = lineh + spaceV + caretRect = Rect caretX caretY caretW caretH+ getSelectionRects :: TextAreaState -> Rect -> [Rect] getSelectionRects state contentArea = rects where currPos = _tasCursorPos state currSel = fromMaybe def (_tasSelStart state)- TextMetrics _ _ lineh _ = _tasTextMetrics state+ TextMetrics asc desc lineh _ = _tasTextMetrics state textLines = _tasTextLines state spaceV = getSpaceV textLines@@ -740,14 +761,16 @@ | swap currPos <= swap currSel = (currPos, currSel) | otherwise = (currSel, currPos) + totalH = lineh + spaceV updateRect rect = rect- & L.h .~ lineh + spaceV+ & L.y -~ spaceV+ & L.h .~ totalH & L.w %~ max 5 -- Empty lines show a small rect to indicate they are there. makeRect cx1 cx2 cy = Rect rx ry rw rh where rx = glyphPos cx1 cy rw = glyphPos cx2 cy - rx- ry = fromIntegral cy * (lineh + spaceV)- rh = lineh + spaceV+ ry = fromIntegral cy * totalH - spaceV+ rh = totalH rects | selY1 == selY2 = [makeRect selX1 selX2 selY1] | otherwise = begin : middle ++ end where@@ -907,7 +930,7 @@ glyphs | Seq.null lineGlyphs = Seq.empty- | otherwise = lineGlyphs |> GlyphPos ' ' textLen 0 0 0 0 0+ | otherwise = lineGlyphs |> GlyphPos ' ' 0 textLen 0 0 0 0 0 glyphStart i g = (i, abs (_glpXMin g - x)) pairs = Seq.mapWithIndex glyphStart glyphs
src/Monomer/Widgets/Singles/TextDropdown.hs view
@@ -7,8 +7,14 @@ Portability : non-portable Dropdown widget, allowing selection of a single item from a collapsable list.-Both header and list content is text based. In case a customizable version is-is needed, 'Monomer.Widgets.Containers.Dropdown' can be used.+Both header and list content are text based.++@+textDropdown textLens ["Option 1", "Option 2", "Option 3"]+@++In case a customizable version is needed, to display rich content in the header+or list items, "Monomer.Widgets.Containers.Dropdown" can be used. -} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE Strict #-}
src/Monomer/Widgets/Singles/TextField.hs view
@@ -6,7 +6,19 @@ Stability : experimental Portability : non-portable -Input field for single line 'Text'.+Input field for single line 'Text'. Allows setting the maximum number of+characters and a replacement character for password.++@+textField shortTextLens+@++With configuration options:++@+textField_ shortTextLens [maxLength 100, selectOnFocus_ False]+@+ -} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-}@@ -47,6 +59,7 @@ warnings in the UI, or disable buttons if needed. - 'resizeOnChange': Whether input causes ResizeWidgets requests. - 'selectOnFocus': Whether all input should be selected when focus is received.+- 'readOnly': Whether to prevent the user changing the input text. - 'maxLength': the maximum length of input text. - 'textFieldDisplayChar': the character that will be displayed as replacement of the real text. Useful for password fields.@@ -59,7 +72,7 @@ -} data TextFieldCfg s e = TextFieldCfg { _tfcCaretWidth :: Maybe Double,- _tfcCaretMs :: Maybe Int,+ _tfcCaretMs :: Maybe Millisecond, _tfcDisplayChar :: Maybe Char, _tfcPlaceholder :: Maybe Text, _tfcValid :: Maybe (WidgetData s Bool),@@ -67,6 +80,7 @@ _tfcMaxLength :: Maybe Int, _tfcResizeOnChange :: Maybe Bool, _tfcSelectOnFocus :: Maybe Bool,+ _tfcReadOnly :: Maybe Bool, _tfcOnFocusReq :: [Path -> WidgetRequest s e], _tfcOnBlurReq :: [Path -> WidgetRequest s e], _tfcOnChangeReq :: [Text -> WidgetRequest s e]@@ -83,6 +97,7 @@ _tfcMaxLength = Nothing, _tfcResizeOnChange = Nothing, _tfcSelectOnFocus = Nothing,+ _tfcReadOnly = Nothing, _tfcOnFocusReq = [], _tfcOnBlurReq = [], _tfcOnChangeReq = []@@ -99,6 +114,7 @@ _tfcMaxLength = _tfcMaxLength t2 <|> _tfcMaxLength t1, _tfcResizeOnChange = _tfcResizeOnChange t2 <|> _tfcResizeOnChange t1, _tfcSelectOnFocus = _tfcSelectOnFocus t2 <|> _tfcSelectOnFocus t1,+ _tfcReadOnly = _tfcReadOnly t2 <|> _tfcReadOnly t1, _tfcOnFocusReq = _tfcOnFocusReq t1 <> _tfcOnFocusReq t2, _tfcOnBlurReq = _tfcOnBlurReq t1 <> _tfcOnBlurReq t2, _tfcOnChangeReq = _tfcOnChangeReq t1 <> _tfcOnChangeReq t2@@ -112,7 +128,7 @@ _tfcCaretWidth = Just w } -instance CmbCaretMs (TextFieldCfg s e) Int where+instance CmbCaretMs (TextFieldCfg s e) Millisecond where caretMs ms = def { _tfcCaretMs = Just ms }@@ -142,6 +158,11 @@ _tfcSelectOnFocus = Just sel } +instance CmbReadOnly (TextFieldCfg s e) where+ readOnly_ ro = def {+ _tfcReadOnly = Just ro+ }+ instance CmbMaxLength (TextFieldCfg s e) where maxLength len = def { _tfcMaxLength = Just len@@ -227,6 +248,7 @@ _ifcDisplayChar = _tfcDisplayChar config, _ifcResizeOnChange = fromMaybe False (_tfcResizeOnChange config), _ifcSelectOnFocus = fromMaybe False (_tfcSelectOnFocus config),+ _ifcReadOnly = fromMaybe False (_tfcReadOnly config), _ifcStyle = Just L.textFieldStyle, _ifcWheelHandler = Nothing, _ifcDragHandler = Nothing,
src/Monomer/Widgets/Singles/TimeField.hs view
@@ -6,14 +6,22 @@ Stability : experimental Portability : non-portable -Input field for time types.+Input field for time types with support for different formats. -Supports TimeOfDay type of the <https://hackage.haskell.org/package/time time>-library, but other types can be supported by implementing 'TimeOfDayConverter'.-Maybe is also supported.+@+timeField timeLens+@ -Supports different time formats.+With configuration options: +@+timeField_ timeLens [timeFormatHHMMSS]+@++Supports 'TimeOfDay' type of the <https://hackage.haskell.org/package/time time>+library, but other types can be supported by implementing 'TimeOfDayConverter'.+'Maybe' is also supported.+ Handles mouse wheel and shift + vertical drag to increase/decrease minutes. -} {-# LANGUAGE ConstraintKinds #-}@@ -26,12 +34,15 @@ -- * Configuration TimeFieldCfg, FormattableTime,+ TimeFieldFormat, TimeOfDayConverter(..),+ TimeTextConverter(..), -- * Constructors timeField, timeField_, timeFieldV, timeFieldV_,+ timeFieldD_, timeFormatHHMM, timeFormatHHMMSS ) where@@ -58,12 +69,13 @@ import qualified Monomer.Lens as L import qualified Monomer.Widgets.Util.Parser as P -data TimeFormat+-- | Available formats for 'timeField'.+data TimeFieldFormat = FormatHHMM | FormatHHMMSS deriving (Eq, Show) -defaultTimeFormat :: TimeFormat+defaultTimeFormat :: TimeFieldFormat defaultTimeFormat = FormatHHMM defaultTimeDelim :: Char@@ -81,11 +93,14 @@ convertFromTimeOfDay = id convertToTimeOfDay = Just --- | Converts a 'TimeOfDay' instance to and from 'Text'.+{-|+Converts a 'TimeOfDay' instance to and from 'Text'. Implementing this typeclass+is not necessary for instances of 'TimeOfDayConverter'.+-} class TimeTextConverter a where- timeAcceptText :: TimeFormat -> Maybe a -> Maybe a -> Text -> (Bool, Bool, Maybe a)- timeFromText :: TimeFormat -> Text -> Maybe a- timeToText :: TimeFormat -> a -> Text+ timeAcceptText :: TimeFieldFormat -> Maybe a -> Maybe a -> Text -> (Bool, Bool, Maybe a)+ timeFromText :: TimeFieldFormat -> Text -> Maybe a+ timeToText :: TimeFieldFormat -> a -> Text timeFromTimeOfDay' :: TimeOfDay -> a timeToTimeOfDay' :: a -> Maybe TimeOfDay @@ -130,10 +145,11 @@ warnings in the UI, or disable buttons if needed. - 'resizeOnChange': Whether input causes ResizeWidgets requests. - 'selectOnFocus': Whether all input should be selected when focus is received.-- 'minValue': Minimum valid date.-- 'maxValue': Maximum valid date.-- 'wheelRate': The rate at which wheel movement affects the date.-- 'dragRate': The rate at which drag movement affects the date.+- 'readOnly': Whether to prevent the user changing the input text.+- 'minValue': Minimum valid time.+- 'maxValue': Maximum valid time.+- 'wheelRate': The rate at which wheel movement affects the time.+- 'dragRate': The rate at which drag movement affects the time. - 'onFocus': event to raise when focus is received. - 'onFocusReq': 'WidgetRequest' to generate when focus is received. - 'onBlur': event to raise when focus is lost.@@ -145,16 +161,17 @@ -} data TimeFieldCfg s e a = TimeFieldCfg { _tfcCaretWidth :: Maybe Double,- _tfcCaretMs :: Maybe Int,+ _tfcCaretMs :: Maybe Millisecond, _tfcValid :: Maybe (WidgetData s Bool), _tfcValidV :: [Bool -> e],- _tfcTimeFormat :: Maybe TimeFormat,+ _tfcTimeFormat :: Maybe TimeFieldFormat, _tfcMinValue :: Maybe a, _tfcMaxValue :: Maybe a, _tfcWheelRate :: Maybe Double, _tfcDragRate :: Maybe Double, _tfcResizeOnChange :: Maybe Bool, _tfcSelectOnFocus :: Maybe Bool,+ _tfcReadOnly :: Maybe Bool, _tfcOnFocusReq :: [Path -> WidgetRequest s e], _tfcOnBlurReq :: [Path -> WidgetRequest s e], _tfcOnChangeReq :: [a -> WidgetRequest s e]@@ -173,6 +190,7 @@ _tfcDragRate = Nothing, _tfcResizeOnChange = Nothing, _tfcSelectOnFocus = Nothing,+ _tfcReadOnly = Nothing, _tfcOnFocusReq = [], _tfcOnBlurReq = [], _tfcOnChangeReq = []@@ -191,6 +209,7 @@ _tfcDragRate = _tfcDragRate t2 <|> _tfcDragRate t1, _tfcResizeOnChange = _tfcResizeOnChange t2 <|> _tfcResizeOnChange t1, _tfcSelectOnFocus = _tfcSelectOnFocus t2 <|> _tfcSelectOnFocus t1,+ _tfcReadOnly = _tfcReadOnly t2 <|> _tfcReadOnly t1, _tfcOnFocusReq = _tfcOnFocusReq t1 <> _tfcOnFocusReq t2, _tfcOnBlurReq = _tfcOnBlurReq t1 <> _tfcOnBlurReq t2, _tfcOnChangeReq = _tfcOnChangeReq t1 <> _tfcOnChangeReq t2@@ -204,7 +223,7 @@ _tfcCaretWidth = Just w } -instance CmbCaretMs (TimeFieldCfg s e a) Int where+instance CmbCaretMs (TimeFieldCfg s e a) Millisecond where caretMs ms = def { _tfcCaretMs = Just ms }@@ -229,6 +248,11 @@ _tfcSelectOnFocus = Just sel } +instance CmbReadOnly (TimeFieldCfg s e a) where+ readOnly_ ro = def {+ _tfcReadOnly = Just ro+ }+ instance FormattableTime a => CmbMinValue (TimeFieldCfg s e a) a where minValue len = def { _tfcMinValue = Just len@@ -336,6 +360,7 @@ format = fromMaybe defaultTimeFormat (_tfcTimeFormat config) minVal = _tfcMinValue config maxVal = _tfcMaxValue config+ readOnly = fromMaybe False (_tfcReadOnly config) initialValue | isJust minVal = fromJust minVal | isJust maxVal = fromJust maxVal@@ -364,9 +389,10 @@ _ifcDisplayChar = Nothing, _ifcResizeOnChange = fromMaybe False (_tfcResizeOnChange config), _ifcSelectOnFocus = fromMaybe True (_tfcSelectOnFocus config),+ _ifcReadOnly = readOnly, _ifcStyle = Just L.timeFieldStyle,- _ifcWheelHandler = Just (handleWheel config),- _ifcDragHandler = Just (handleDrag config),+ _ifcWheelHandler = if readOnly then Nothing else Just (handleWheel config),+ _ifcDragHandler = if readOnly then Nothing else Just (handleDrag config), _ifcDragCursor = Just CursorSizeV, _ifcOnFocusReq = _tfcOnFocusReq config, _ifcOnBlurReq = _tfcOnBlurReq config,@@ -439,7 +465,7 @@ timeFromTextSimple :: (TimeOfDayConverter a, FormattableTime a)- => TimeFormat+ => TimeFieldFormat -> Text -> Maybe a timeFromTextSimple format text = newTime where@@ -454,7 +480,7 @@ | otherwise -> makeTimeOfDayValid n1 n2 (fromIntegral n3) newTime = tmpTime >>= timeFromTimeOfDay' -timeToTextSimple :: FormattableTime a => TimeFormat -> a -> Text+timeToTextSimple :: FormattableTime a => TimeFieldFormat -> a -> Text timeToTextSimple format val = result where sep = T.singleton defaultTimeDelim converted = timeToTimeOfDay' val@@ -470,7 +496,7 @@ | format == FormatHHMM = thh <> sep <> tmm | otherwise = thh <> sep <> tmm <> sep <> tss -acceptTextInput :: TimeFormat -> Text -> Bool+acceptTextInput :: TimeFieldFormat -> Text -> Bool acceptTextInput format text = isRight (A.parseOnly parser text) where numP = A.digit *> "" delimP = A.char defaultTimeDelim *> ""
src/Monomer/Widgets/Singles/ToggleButton.hs view
@@ -6,15 +6,18 @@ Stability : experimental Portability : non-portable -Toggle button widget, used for boolean properties.+Toggle button widget, used for boolean values. -Its behavior is equivalent to 'Monomer.Widgets.Singles.Checkbox' and-'Monomer.Widgets.Singles.LabeledCheckbox', with a different visual+@+toggleButton \"Toggle\" booleanLens+@++Its behavior is equivalent to "Monomer.Widgets.Singles.Checkbox" and+"Monomer.Widgets.Singles.LabeledCheckbox", with a different visual representation. -See 'Monomer.Widgets.Singles.OptionButton' for detailed notes.+See "Monomer.Widgets.Singles.OptionButton" for detailed notes. -}-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE StrictData #-}@@ -59,8 +62,10 @@ - 'onFocusReq': 'WidgetRequest' to generate when focus is received. - 'onBlur': event to raise when focus is lost. - 'onBlurReq': 'WidgetRequest' to generate when focus is lost.-- 'onChange': event to raise when the value changes/is clicked.-- 'onChangeReq': 'WidgetRequest' to generate when the value changes/is clicked.+- 'onClick': event to raise when the value is clicked.+- 'onClickReq': 'WidgetRequest' to generate when the value is clicked.+- 'onChange': event to raise when the value changes.+- 'onChangeReq': 'WidgetRequest' to generate when the value changes. -} type ToggleButtonCfg = OptionButtonCfg
src/Monomer/Widgets/Util/Widget.hs view
@@ -33,7 +33,6 @@ infoMatches, nodeMatches, handleWidgetIdChange,- findWidgetIdFromPath, delayedMessage, delayedMessage_ ) where@@ -202,21 +201,15 @@ & L.requests %~ (SetWidgetPath widgetId newPath <|) | otherwise = result --- | Returns the WidgetId associated to the given path, if any.-findWidgetIdFromPath :: WidgetEnv s e -> Path -> Maybe WidgetId-findWidgetIdFromPath wenv path = mwni ^? _Just . L.widgetId where- branch = wenv ^. L.findBranchByPath $ path- mwni = Seq.lookup (length branch - 1) branch- -- | Sends a message to the given node with a delay of n ms.-delayedMessage :: Typeable i => WidgetNode s e -> i -> Int -> WidgetRequest s e+delayedMessage :: Typeable i => WidgetNode s e -> i -> Millisecond -> WidgetRequest s e delayedMessage node msg delay = delayedMessage_ widgetId path msg delay where widgetId = node ^. L.info . L.widgetId path = node ^. L.info . L.path -- | Sends a message to the given WidgetId with a delay of n ms. delayedMessage_- :: Typeable i => WidgetId -> Path -> i -> Int -> WidgetRequest s e+ :: Typeable i => WidgetId -> Path -> i -> Millisecond -> WidgetRequest s e delayedMessage_ widgetId path msg delay = RunTask widgetId path $ do- threadDelay (delay * 1000)+ threadDelay (fromIntegral delay * 1000) return msg
test/unit/Monomer/Common/CursorIconSpec.hs view
@@ -21,7 +21,6 @@ import Data.Maybe import Data.Sequence (Seq(..)) import Data.Text (Text)-import Safe import Test.Hspec import qualified Data.Map.Strict as M@@ -31,6 +30,7 @@ import Monomer.Core.Combinators import Monomer.Core.Themes.SampleThemes import Monomer.Event+import Monomer.Helper (headMay) import Monomer.Main import Monomer.TestUtil import Monomer.TestEventUtil
+ test/unit/Monomer/Core/StyleUtilSpec.hs view
@@ -0,0 +1,78 @@+{-|+Module : Monomer.Core.StyleUtilSpec+Copyright : (c) 2018 Francisco Vallarino+License : BSD-3-Clause (see the LICENSE file)+Maintainer : fjvallarino@gmail.com+Stability : experimental+Portability : non-portable++Unit tests for StyleUtil functions.+-}+module Monomer.Core.StyleUtilSpec (spec) where++import Control.Lens+import Test.Hspec++import Monomer.Core.Combinators+import Monomer.Graphics (Color, rgb)+import Monomer.Widgets.Singles.Spacer++import qualified Monomer.Lens as L++red :: Color+red = rgb 255 0 0++spec :: Spec+spec = describe "StyleUtil" $ do+ styleStateSpec++styleStateSpec :: Spec+styleStateSpec = describe "StyleState actions" $ do+ describe "StyleState merge" $ do+ it "should merge basic styles" $ do+ checkStyleAction styleBasic L.basic mergedStyles++ it "should merge hover styles" $ do+ checkStyleAction styleHover L.hover mergedStyles++ it "should merge focus styles" $ do+ checkStyleAction styleFocus L.focus mergedStyles++ it "should merge hover focus styles" $ do+ checkStyleAction styleFocusHover L.focusHover mergedStyles++ it "should merge active styles" $ do+ checkStyleAction styleActive L.active mergedStyles++ it "should merge disabled styles" $ do+ checkStyleAction styleDisabled L.disabled mergedStyles++ describe "StyleState merge" $ do+ it "should set basic styles" $ do+ checkStyleAction styleBasicSet L.basic newStyles++ it "should set hover styles" $ do+ checkStyleAction styleHoverSet L.hover newStyles++ it "should set focus styles" $ do+ checkStyleAction styleFocusSet L.focus newStyles++ it "should set focus hover styles" $ do+ checkStyleAction styleFocusHoverSet L.focusHover newStyles++ it "should set active styles" $ do+ checkStyleAction styleActiveSet L.active newStyles++ it "should set disabled styles" $ do+ checkStyleAction styleDisabledSet L.disabled newStyles++ where+ baseStyles = [width 200, padding 10]+ newStyles = [padding 5, bgColor red]+ mergedStyles = [width 200, padding 5, bgColor red]++ checkStyleAction styleFn field targetStyles = do+ let node = styleFn spacer baseStyles+ let newNode = styleFn node newStyles++ newNode ^. L.info . L.style . field `shouldBe` Just (mconcat targetStyles)
test/unit/Monomer/TestUtil.hs view
@@ -56,27 +56,35 @@ testWindowRect :: Rect testWindowRect = Rect 0 0 testW testH -mockTextMetrics :: Font -> FontSize -> TextMetrics-mockTextMetrics font fontSize = TextMetrics {+mockTextMetrics :: Double -> Font -> FontSize -> TextMetrics+mockTextMetrics scale font fontSize = TextMetrics { _txmAsc = 15, _txmDesc = 5, _txmLineH = 20, _txmLowerX = 10 } -mockTextSize :: Maybe Double -> Font -> FontSize -> FontSpace -> Text -> Size-mockTextSize mw font (FontSize fs) spaceH text = Size width height where+mockTextSize+ :: Maybe Double -> Double -> Font -> FontSize -> FontSpace -> Text -> Size+mockTextSize mw scale font (FontSize fs) spaceH text = Size width height where w = fromMaybe fs mw + unFontSpace spaceH width = fromIntegral (T.length text) * w height = 20 mockGlyphsPos- :: Maybe Double -> Font -> FontSize -> FontSpace -> Text -> Seq GlyphPos-mockGlyphsPos mw font (FontSize fs) spaceH text = glyphs where+ :: Maybe Double+ -> Double+ -> Font+ -> FontSize+ -> FontSpace+ -> Text+ -> Seq GlyphPos+mockGlyphsPos mw scale font (FontSize fs) spaceH text = glyphs where w = fromMaybe fs mw + unFontSpace spaceH chars = Seq.fromList $ T.unpack text mkGlyph idx chr = GlyphPos { _glpGlyph = chr,+ _glpX = fromIntegral idx * w, _glpXMin = fromIntegral idx * w, _glpXMax = (fromIntegral idx + 1) * w, _glpYMin = 0,@@ -154,15 +162,19 @@ mockFontManager :: FontManager mockFontManager = FontManager {- computeTextMetrics = mockTextMetrics,- computeTextSize = mockTextSize (Just 10),- computeGlyphsPos = mockGlyphsPos (Just 10)+ computeTextMetrics = mockTextMetrics 1,+ computeTextMetrics_ = mockTextMetrics,+ computeTextSize = mockTextSize (Just 10) 1,+ computeTextSize_ = mockTextSize (Just 10),+ computeGlyphsPos = mockGlyphsPos (Just 10) 1,+ computeGlyphsPos_ = mockGlyphsPos (Just 10) } mockWenv :: s -> WidgetEnv s e mockWenv model = WidgetEnv { _weOs = "Mac OS X", _weDpr = 2,+ _weAppStartTs = 0, _weFontManager = mockFontManager, _weFindBranchByPath = const Seq.empty, _weMainButton = BtnLeft,
test/unit/Monomer/Widgets/CompositeSpec.hs view
@@ -17,9 +17,10 @@ module Monomer.Widgets.CompositeSpec (spec) where import Control.Lens (- (&), (^.), (^?), (^..), (.~), (%~), _Just, ix, folded, traverse, dropping)+ (&), (^.), (^?), (^?!), (^..), (.~), (%~), _Just, ix, folded, traverse, dropping) import Control.Lens.TH (abbreviatedFields, makeLensesWith) import Data.Default+import Data.Foldable (toList) import Data.Maybe import Data.Text (Text) import Data.Typeable (Typeable, cast)@@ -52,6 +53,10 @@ = MainBtnClicked | ChildClicked | MainResize Rect+ | OnInit+ | OnDispose+ | OnChange MainModel+ | ReportOnChange MainModel MainModel deriving (Eq, Show) data ChildEvt@@ -140,6 +145,9 @@ handleEvent :: Spec handleEvent = describe "handleEvent" $ do handleEventBasic+ handleEventOnInit+ handleEventOnDispose+ handleEventOnChange handleEventNewRoot handleEventChild handleEventResize@@ -171,6 +179,82 @@ model es = nodeHandleEventModel wenv es cmpNode reqs es = nodeHandleEventReqs wenv es cmpNode +handleEventOnInit :: Spec+handleEventOnInit = describe "handleEventOnInit" $ do+ it "should generate an init event" $ do+ evts [] `shouldBe` Seq.singleton OnInit++ 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+ OnInit{} -> [Report evt]+ _ -> []+ buildUI wenv model = vstack []+ cmpNode = composite_ "main" id buildUI handleEvent [onInit OnInit]+ evts es = nodeHandleEventEvts_ wenv WInitKeepFirst es cmpNode++handleEventOnDispose :: Spec+handleEventOnDispose = describe "handleEventOnDispose" $ do+ it "should generate an init event" $ do+ let val = case evts [] ^?! L.requests . ix 1 of+ SendMessage wid msg -> cast msg+ _ -> Nothing++ val `shouldBe` Just OnDispose++ 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+ OnInit{} -> [Report evt]+ _ -> []+ buildUI wenv model = vstack []+ cmpNode = nodeInit wenv+ $ composite_ "main" id buildUI handleEvent [onDispose OnDispose]+ evts es = widgetDispose (cmpNode ^. L.widget) wenv cmpNode++handleEventOnChange :: Spec+handleEventOnChange = describe "handleEventOnChange" $ do+ it "should not generate an event if model did not change" $ do+ evts [evtClick (Point 10 30)] `shouldBe` Seq.empty++ it "should generate an event if model changed" $ do+ let items = toList $ evts [evtClick (Point 10 10)]+ let ReportOnChange oldModel newModel = head items++ oldModel `shouldNotBe` newModel++ 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))]+ ChildClicked -> [Model model]+ OnChange oldModel -> [Report $ ReportOnChange oldModel model]+ _ -> []+ buildUI wenv model = vstack [+ button "Click main" MainBtnClicked,+ button "Click secondary" ChildClicked+ ]+ cmpNode = composite_ "main" id buildUI handleEvent [onChange OnChange]+ evts es = nodeHandleEventEvts wenv es cmpNode+ handleEventNewRoot :: Spec handleEventNewRoot = describe "handleEventNewRoot" $ do it "should generate a resize request when the widgetType of the root widget changes" $ do@@ -340,7 +424,7 @@ ] `nodeKey` "localTxt1" ] cmpNode1 = composite "main" id buildUI1 handleEvent- cmpNode2 = composite_ "main" id buildUI2 handleEvent [mergeRequired (\_ _ -> True)]+ cmpNode2 = composite_ "main" id buildUI2 handleEvent [mergeRequired (\_ _ _ -> True)] evts1 = [evtK keyTab, evtT "aacc", moveCharL, moveCharL] (wenv1, root1, _) = fst $ nodeHandleEvents wenv WInit evts1 cmpNode1 cntNodeM = nodeMerge wenv1 cmpNode2 root1@@ -473,7 +557,7 @@ cmpNode = findByHelperUI wni path = res where inode = nodeInit wenv cmpNode- res = findWidgetByPath wenv inode path+ res = findChildNodeInfoByPath wenv inode path findNextFocus :: Spec findNextFocus = describe "findNextFocus" $ do
test/unit/Monomer/Widgets/Containers/BoxSpec.hs view
@@ -68,8 +68,8 @@ btnNew = button "Click" (BtnClick 0) `nodeKey` "btnNew" btnOld = button "Click" (BtnClick 0) `nodeKey` "btnOld" box1 = box btnNew- box2 = box_ [mergeRequired (\_ _ -> True)] btnNew- box3 = box_ [mergeRequired (\_ _ -> False)] btnNew+ box2 = box_ [mergeRequired (\_ _ _ -> True)] btnNew+ box3 = box_ [mergeRequired (\_ _ _ -> False)] btnNew boxM = box btnOld mergeWith newNode oldNode = result ^?! L.node . L.children . ix 0 where oldNode2 = nodeInit wenv oldNode
test/unit/Monomer/Widgets/Containers/KeystrokeSpec.hs view
@@ -23,25 +23,30 @@ import qualified Data.Sequence as Seq -import Monomer.Core+import Monomer.Common import Monomer.Core.Combinators import Monomer.Event import Monomer.TestUtil import Monomer.TestEventUtil import Monomer.Widgets.Containers.Keystroke+import Monomer.Widgets.Singles.Button import Monomer.Widgets.Singles.Label import Monomer.Widgets.Singles.TextField import qualified Monomer.Lens as L data TestEvt- = SingleO+ = ButtonEvent+ | EnterPressed+ | SingleO | TextFieldChanged Text | CtrlA | CtrlSpace+ | CtrlDash | CtrlShiftSpace | MultiKey Int | FunctionKey Int+ | SymbolKey Text deriving (Eq, Show) newtype TestModel = TestModel {@@ -53,6 +58,7 @@ spec :: Spec spec = describe "Keystroke" $ do handleEvent+ handleNestedEvent getSizeReq handleEvent :: Spec@@ -63,6 +69,13 @@ it "should generate an event when Ctrl-Space is pressed" $ do events [evtKC keySpace] `shouldBe` Seq.fromList [CtrlSpace] + it "should generate an event when Ctrl-Dash is pressed" $ do+ let wenv = mockWenv (TestModel "")+ & L.inputStatus . L.keyMod . L.leftCtrl .~ True+ let events es = nodeHandleEventEvts wenv es kstNode++ events [evtT "-"] `shouldBe` Seq.fromList [CtrlDash]+ it "should generate an event when Ctrl-Shift-Space is pressed" $ do events [evtKCS keySpace] `shouldBe` Seq.fromList [CtrlShiftSpace] @@ -72,6 +85,14 @@ events [evtKG keyF7] `shouldBe` Seq.fromList [FunctionKey 7] events [evtKS keyF12] `shouldBe` Seq.fromList [FunctionKey 12] + it "should generate events when symbol keys are pressed" $ do+ let wenv = mockWenv (TestModel "")+ & L.inputStatus . L.keyMod . L.leftCtrl .~ True+ let events es = nodeHandleEventEvts wenv es kstNode++ events [evtT "["] `shouldBe` Seq.fromList [SymbolKey "["]+ events [evtT "^"] `shouldBe` Seq.fromList [SymbolKey "^"]+ it "should only generate events when the exact keys are pressed" $ do events [evtKC keyA, evtKC keyB] `shouldBe` Seq.fromList [] events [evtKC keyA, evtKC keyB, evtKC keyD, evtKC keyC] `shouldBe` Seq.fromList []@@ -104,13 +125,16 @@ wenv = mockWenv (TestModel "") bindings = [ ("C-Space", CtrlSpace),+ ("C-Dash", CtrlDash), ("C-S-Space", CtrlShiftSpace), ("C-a-b-c", MultiKey 1), ("C-d-e", MultiKey 2), ("F1", FunctionKey 1), ("Ctrl-F3", FunctionKey 3), ("Cmd-F7", FunctionKey 7),- ("S-F12", FunctionKey 12)+ ("S-F12", FunctionKey 12),+ ("C-[", SymbolKey "["),+ ("Shift-^", SymbolKey "^") ] kstNode = keystroke bindings (textField textValue) events es = nodeHandleEventEvts wenv es kstNode@@ -121,6 +145,35 @@ model2 es = nodeHandleEventModel wenv2 es kstModel2 events1 es = nodeHandleEventEvts wenv2 es kstModel1 events2 es = nodeHandleEventEvts wenv2 es kstModel2++handleNestedEvent :: Spec+handleNestedEvent = describe "handleNestedEvent" $ do+ it "should not generate events" $ do+ events False False [] `shouldBe` Seq.empty++ it "should generate an event when clicking the button" $ do+ events False False [evtClick (Point 100 100)] `shouldBe` Seq.fromList [ButtonEvent]+ events True False [evtClick (Point 100 100)] `shouldBe` Seq.fromList [ButtonEvent]+ events False True [evtClick (Point 100 100)] `shouldBe` Seq.fromList [ButtonEvent]+ events True True [evtClick (Point 100 100)] `shouldBe` Seq.fromList [ButtonEvent]++ it "should generate the expected events when pressing the enter key" $ do+ events False False [evtK keyEnter] `shouldBe` Seq.fromList [EnterPressed, ButtonEvent]+ events True False [evtK keyEnter] `shouldBe` Seq.fromList [EnterPressed]+ events False True [evtK keyEnter] `shouldBe` Seq.fromList [ButtonEvent]+ events True True [evtK keyEnter] `shouldBe` Seq.fromList [EnterPressed]++ where+ wenv = mockWenv (TestModel "")+ bindings = [+ ("Enter", EnterPressed)+ ]+ kstNode ignoreChild ignoreParent =+ keystroke_ bindings [ignoreChildrenEvts_ ignoreChild] $+ button_ "Test" ButtonEvent [ignoreParentEvts_ ignoreParent]+ events ignoreChild ignoreParent es =+ nodeHandleEventEvts wenv es $+ kstNode ignoreChild ignoreParent getSizeReq :: Spec getSizeReq = describe "getSizeReq" $ do
test/unit/Monomer/Widgets/Containers/ScrollSpec.hs view
@@ -51,6 +51,7 @@ handleEvent :: Spec handleEvent = describe "handleEvent" $ do handleBarClick+ handleThumbDrag handleChildrenFocus handleNestedWheel handleMessageReset@@ -61,15 +62,12 @@ it "should click the first button" $ do evts [evtClick point] `shouldBe` Seq.fromList [Button1] - it "should scroll right and click the third button" $ do+ it "should scroll right and click the second button" $ do evts [evtPress midHBar, evtClick point] `shouldBe` Seq.fromList [Button2] it "should scroll down and click the third button" $ do evts [evtPress midVBar, evtClick point] `shouldBe` Seq.fromList [Button3] - it "should scroll down and click the third button" $ do- evts [evtPress midVBar, evtClick point] `shouldBe` Seq.fromList [Button3]- it "should scroll down and right and click the fourth button" $ do evts [evtPress midHBar, evtPress midVBar, evtClick point] `shouldBe` Seq.fromList [Button4] @@ -93,6 +91,53 @@ ] scrollNode = scroll stackNode evts es = nodeHandleEventEvts wenv es scrollNode++handleThumbDrag :: Spec+handleThumbDrag = describe "handleThumbDrag" $ do+ it "should click the first button" $ do+ evts [evtClick pClick] `shouldBe` Seq.fromList [Button1]++ it "should drag the thumb right and click the second button" $ do+ evts [evtPress pStartH, evtMove pEndH, evtClick pClick] `shouldBe` Seq.fromList [Button2]++ it "should drag the thumb down and click the third button" $ do+ evts [evtPress pStartV, evtMove pEndV, evtClick pClick] `shouldBe` Seq.fromList [Button3]++ it "should drag the thumb down and right and click the fourth button" $ do+ let steps = [ evtPress pStartH, evtMove pEndH, evtRelease pEndH,+ evtPress pStartV, evtMove pEndV, evtRelease pEndV,+ evtClick pClick ]+ evts steps `shouldBe` Seq.fromList [Button4]++ it "should fail to drag the thumb right because of thumb size, causing it to click the first button" $ do+ evtsSmall [evtPress pStartH, evtMove pEndH, evtClick pClick] `shouldBe` Seq.fromList [Button1]++ it "should fail to drag the thumb down because of thumb size, causing it to click the first button" $ do+ evtsSmall [evtPress pStartV, evtMove pEndV, evtClick pClick] `shouldBe` Seq.fromList [Button1]++ where+ wenv = mockWenv ()+ & L.theme .~ darkTheme+ & L.windowSize .~ Size 640 480+ pClick = Point 320 200+ pStartH = Point 50 476+ pEndH = Point 400 476+ pStartV = Point 636 50+ pEndV = Point 636 300+ st = [width 6400, height 4800]+ 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_ [thumbMinSize 100] stackNode+ evts es = nodeHandleEventEvts wenv es scrollNode+ evtsSmall es = nodeHandleEventEvts wenv es (scroll stackNode) handleChildrenFocus :: Spec handleChildrenFocus = describe "handleChildrenFocus" $ do
test/unit/Monomer/Widgets/Singles/DateFieldSpec.hs view
@@ -72,6 +72,7 @@ handleEventDate handleEventValueDate handleEventMouseDragDate+ handleEventReadOnly handleShiftFocus getSizeReqDate testWidgetType@@ -246,6 +247,30 @@ model es = nodeHandleEventModel wenv es dateNode lastIdx es = Seq.index es (Seq.length es - 1) lastEvt es = lastIdx (evts es)++handleEventReadOnly :: Spec+handleEventReadOnly = describe "handleEventReadOnly" $ do+ it "should ignore text input" $ do+ let steps = [moveCharR, delCharL, evtT "5"]+ model steps ^. dateValue `shouldBe` initDate+ + it "should ignore drag" $ do+ let selStart = Point 5 5+ let selEnd = Point 100 2000+ let steps = [evtPress selStart, evtMove selEnd, evtRelease selEnd]+ model steps ^. dateValue `shouldBe` initDate++ it "should ignore wheel" $ do+ let steps = [WheelScroll (Point 100 10) (Point 0 (-2000)) WheelNormal]+ model steps ^. dateValue `shouldBe` initDate++ where+ initDate = fromGregorian 1999 11 21+ wenv = mockWenv (DateModel initDate True)+ & L.inputStatus . L.keyMod . L.leftShift .~ True+ dateCfg = [readOnly :: DateFieldCfg DateModel TestEvt Day]+ dateNode = dateField_ dateValue dateCfg+ model es = nodeHandleEventModel wenv es dateNode handleShiftFocus :: Spec handleShiftFocus = describe "handleShiftFocus" $ do
test/unit/Monomer/Widgets/Singles/LabelSpec.hs view
@@ -121,7 +121,7 @@ where fontMgr = mockFontManager {- computeGlyphsPos = mockGlyphsPos Nothing+ computeGlyphsPos = mockGlyphsPos Nothing 1 } wenv = mockWenvEvtUnit () & L.fontManager .~ fontMgr
test/unit/Monomer/Widgets/Singles/NumericFieldSpec.hs view
@@ -65,6 +65,7 @@ handleEventIntegral handleEventValueIntegral handleEventMouseDragIntegral+ handleEventReadOnly getSizeReqIntegral testIntegralWidgetType @@ -475,6 +476,30 @@ numericField_ fractionalValue [wheelRate 1, onFocus GotFocus] ] evts es = nodeHandleEventEvts wenv es floatNode++handleEventReadOnly :: Spec+handleEventReadOnly = describe "handleEventReadOnly" $ do+ it "should ignore text input" $ do+ let steps = [moveCharR, delCharL, evtT "5"]+ model steps ^. fractionalValue `shouldBe` initValue+ + it "should ignore drag" $ do+ let selStart = Point 5 5+ let selEnd = Point 100 2000+ let steps = [evtPress selStart, evtMove selEnd, evtRelease selEnd]+ model steps ^. fractionalValue `shouldBe` initValue++ it "should ignore wheel" $ do+ let steps = [WheelScroll (Point 100 10) (Point 0 (-2000)) WheelNormal]+ model steps ^. fractionalValue `shouldBe` initValue++ where+ initValue = Just 42.4+ wenv = mockWenv (FractionalModel initValue False)+ & L.inputStatus . L.keyMod . L.leftShift .~ True+ fieldCfg = [readOnly :: NumericFieldCfg FractionalModel TestEvt (Maybe Double)]+ fieldNode = numericField_ fractionalValue fieldCfg+ model es = nodeHandleEventModel wenv es fieldNode getSizeReqFractional :: Spec getSizeReqFractional = describe "getSizeReqFractional" $ do
test/unit/Monomer/Widgets/Singles/OptionButtonSpec.hs view
@@ -42,7 +42,8 @@ deriving (Eq, Show) data FruitEvt- = FruitSel Fruit+ = FruitClicked+ | FruitSel Fruit | GotFocus Path | LostFocus Path deriving (Eq, Show)@@ -77,11 +78,21 @@ it "should generate an event when focus is lost" $ events evtBlur orangeNode `shouldBe` Seq.singleton (LostFocus emptyPath) + it "should generate multiple click events when clicked, but a single onChange because the value did not change" $ do+ let evt = evtClick (Point 320 240)+ let events es = nodeHandleEventEvts wenv es bananaClickNode++ events [evt] `shouldBe` Seq.fromList [FruitClicked, FruitSel Banana]+ events [evt, evt] `shouldBe` Seq.fromList [FruitClicked, FruitSel Banana, FruitClicked]+ events [evt, evt, evt] `shouldBe` Seq.fromList [FruitClicked, FruitSel Banana, FruitClicked, FruitClicked]+ where wenv = mockWenv (TestModel Apple) orangeNode = optionButton_ "Orange" Orange fruit [onFocus GotFocus, onBlur LostFocus] bananaNode :: WidgetNode TestModel FruitEvt bananaNode = optionButton "Banana" Banana fruit+ bananaClickNode = optionButton_ "Banana" Banana fruit [onClick FruitClicked, onChange FruitSel]+ clickModel p node = nodeHandleEventModel wenv [evtClick p] node keyModel key node = nodeHandleEventModel wenv [KeyAction def key KeyPressed] node events evt node = nodeHandleEventEvts wenv [evt] node
test/unit/Monomer/Widgets/Singles/RadioSpec.hs view
@@ -40,7 +40,8 @@ deriving (Eq, Show) data FruitEvt- = FruitSel Fruit+ = FruitClicked+ | FruitSel Fruit | GotFocus Path | LostFocus Path deriving (Eq, Show)@@ -75,12 +76,22 @@ it "should generate an event when focus is lost" $ events evtBlur orangeNode `shouldBe` Seq.singleton (LostFocus emptyPath) + it "should generate multiple click events when clicked, but a single onChange because the value did not change" $ do+ let evt = evtClick (Point 320 240)+ let events es = nodeHandleEventEvts wenv es bananaClickNode++ events [evt] `shouldBe` Seq.fromList [FruitClicked, FruitSel Banana]+ events [evt, evt] `shouldBe` Seq.fromList [FruitClicked, FruitSel Banana, FruitClicked]+ events [evt, evt, evt] `shouldBe` Seq.fromList [FruitClicked, FruitSel Banana, FruitClicked, FruitClicked]+ where wenv = mockWenv (TestModel Apple) & L.theme .~ darkTheme orangeNode = radio_ Orange fruit [onFocus GotFocus, onBlur LostFocus] bananaNode :: WidgetNode TestModel FruitEvt bananaNode = radio Banana fruit+ bananaClickNode = radio_ Banana fruit [onClick FruitClicked, onChange FruitSel]+ clickModel p node = nodeHandleEventModel wenv [evtClick p] node keyModel key node = nodeHandleEventModel wenv [KeyAction def key KeyPressed] node events evt node = nodeHandleEventEvts wenv [evt] node
test/unit/Monomer/Widgets/Singles/TextAreaSpec.hs view
@@ -51,6 +51,7 @@ handleEventValue handleEventMouseSelect handleEventHistory+ handleEventReadOnly getSizeReq handleEvent :: Spec@@ -322,6 +323,24 @@ evts es = nodeHandleEventEvts wenv es txtNode lastIdx es = Seq.index es (Seq.length es - 1) lastEvt es = lastIdx (evts es)++handleEventReadOnly :: Spec+handleEventReadOnly = describe "handleEventReadOnly" $ do+ it "should ignore text input" $ do+ model [evtT "a"] ^. textValue `shouldBe` initText+ + it "should ignore cut" $ do+ model [selWordR, evtKG keyX] ^. textValue `shouldBe` initText+ + it "should ignore paste" $ do+ model [selWordR, evtKG keyV] ^. textValue `shouldBe` initText+ + where+ initText = "hello"+ wenv = mockWenv (TestModel initText)+ txtCfg = [readOnly :: TextAreaCfg TestModel TestEvt]+ txtNode = textArea_ textValue txtCfg+ model es = nodeHandleEventModel wenv es txtNode getSizeReq :: Spec getSizeReq = describe "getSizeReq" $ do
test/unit/Monomer/Widgets/Singles/TextFieldSpec.hs view
@@ -51,6 +51,9 @@ handleEventValue handleEventMouseSelect handleEventHistory+ handleEventMouseDrag+ handleEventWheel+ handleEventReadOnly getSizeReq handleEvent :: Spec@@ -267,6 +270,48 @@ evts es = nodeHandleEventEvts wenv es txtNode lastIdx es = Seq.index es (Seq.length es - 1) lastEvt es = lastIdx (evts es)++handleEventMouseDrag :: Spec+handleEventMouseDrag = describe "handleEventMouseDrag" $ do+ it "should ignore shift+drag events" $ do+ let selStart = Point 50 10+ let selEnd = Point 50 (-70)+ let steps = [evtPress selStart, evtMove selEnd, evtRelease selEnd]+ model steps ^. textValue `shouldBe` ""++ where+ wenv = mockWenvEvtUnit (TestModel "")+ & L.inputStatus . L.keyMod . L.leftShift .~ True+ model es = nodeHandleEventModel wenv es (textField textValue)++handleEventWheel :: Spec+handleEventWheel = describe "handleEventWheel" $ do+ it "should ignore wheel events" $ do+ let p = Point 50 10+ let steps1 = [WheelScroll p (Point 0 (-8000)) WheelNormal]+ model steps1 ^. textValue `shouldBe` ""++ where+ wenv = mockWenvEvtUnit (TestModel "")+ model es = nodeHandleEventModel wenv es (textField textValue)++handleEventReadOnly :: Spec+handleEventReadOnly = describe "handleEventReadOnly" $ do+ it "should ignore text input" $ do+ model [evtT "a"] ^. textValue `shouldBe` initText+ + it "should ignore cut" $ do+ model [selWordR, evtKG keyX] ^. textValue `shouldBe` initText+ + it "should ignore paste" $ do+ model [selWordR, evtKG keyV] ^. textValue `shouldBe` initText+ + where+ initText = "hello"+ wenv = mockWenv (TestModel initText)+ txtCfg = [readOnly :: TextFieldCfg TestModel TestEvt]+ txtNode = textField_ textValue txtCfg+ model es = nodeHandleEventModel wenv es txtNode getSizeReq :: Spec getSizeReq = describe "getSizeReq" $ do
test/unit/Monomer/Widgets/Singles/TimeFieldSpec.hs view
@@ -72,6 +72,7 @@ handleEventTime handleEventValueTime handleEventMouseDragTime+ handleEventReadOnly handleShiftFocus getSizeReqTime testWidgetType@@ -255,6 +256,30 @@ model es = nodeHandleEventModel wenv es timeNode lastIdx es = Seq.index es (Seq.length es - 1) lastEvt es = lastIdx (evts es)++handleEventReadOnly :: Spec+handleEventReadOnly = describe "handleEventReadOnly" $ do+ it "should ignore text input" $ do+ let steps = [moveCharR, delCharL, evtT "5"]+ model steps ^. timeValue `shouldBe` initTime+ + it "should ignore drag" $ do+ let selStart = Point 5 5+ let selEnd = Point 100 2000+ let steps = [evtPress selStart, evtMove selEnd, evtRelease selEnd]+ model steps ^. timeValue `shouldBe` initTime++ it "should ignore wheel" $ do+ let steps = [WheelScroll (Point 100 10) (Point 0 (-2000)) WheelNormal]+ model steps ^. timeValue `shouldBe` initTime+ + where+ initTime = TimeOfDay 20 37 00+ wenv = mockWenv (TimeModel initTime False)+ & L.inputStatus . L.keyMod . L.leftShift .~ True+ timeCfg = [readOnly :: TimeFieldCfg TimeModel TestEvt TimeOfDay]+ timeNode = timeField_ timeValue timeCfg+ model es = nodeHandleEventModel wenv es timeNode handleShiftFocus :: Spec handleShiftFocus = describe "handleShiftFocus" $ do
test/unit/Monomer/Widgets/Singles/ToggleButtonSpec.hs view
@@ -36,7 +36,8 @@ import qualified Monomer.Lens as L data TestEvt- = BoolSel Bool+ = BoolClicked+ | BoolSel Bool | GotFocus Path | LostFocus Path deriving (Eq, Show)@@ -70,9 +71,19 @@ it "should generate an event when focus is lost" $ events evtBlur `shouldBe` Seq.singleton (LostFocus emptyPath) + it "should generate multiple click and change events when clicked, since it toggles between True and False" $ do+ let evt = evtClick (Point 320 240)+ let events es = nodeHandleEventEvts wenv es chkClickNode++ events [evt] `shouldBe` Seq.fromList [BoolClicked, BoolSel True]+ events [evt, evt] `shouldBe` Seq.fromList [BoolClicked, BoolSel True, BoolClicked, BoolSel False]+ events [evt, evt, evt] `shouldBe` Seq.fromList [BoolClicked, BoolSel True, BoolClicked, BoolSel False, BoolClicked, BoolSel True]+ where wenv = mockWenv (TestModel False) chkNode = toggleButton_ "Toggle" testBool [onFocus GotFocus, onBlur LostFocus]+ chkClickNode = toggleButton_ "Toggle" testBool [onClick BoolClicked, onChange BoolSel]+ clickModel p = nodeHandleEventModel wenv [evtClick p] chkNode keyModel key = nodeHandleEventModel wenv [KeyAction def key KeyPressed] chkNode events evt = nodeHandleEventEvts wenv [evt] chkNode
test/unit/Monomer/Widgets/Util/TextSpec.hs view
@@ -152,6 +152,16 @@ clipKeep "This is a tad bit longer\nMore" ^. ix 1 . L.text `shouldBe` " a tad" clipKeep "This is a tad bit longer\nMore" ^. ix 2 . L.text `shouldBe` " bit " + it "should return text broken even in the middle of words, clipped, trimmed, if it does not fit" $ do+ breakOnChar "This is really-long\nMore" `shouldSatisfy` elementCount 3+ breakOnChar "This is really-long\nMore" ^. ix 0 . L.text `shouldBe` "This is"+ breakOnChar "This is really-long\nMore" ^. ix 1 . L.text `shouldBe` "reall"+ breakOnChar "This is really-long\nMore" ^. ix 2 . L.text `shouldBe` "y-long"+ breakOnChar "This is a tad bit longer\nMore" `shouldSatisfy` elementCount 3+ breakOnChar "This is a tad bit longer\nMore" ^. ix 0 . L.text `shouldBe` "This is"+ breakOnChar "This is a tad bit longer\nMore" ^. ix 1 . L.text `shouldBe` "a tad"+ breakOnChar "This is a tad bit longer\nMore" ^. ix 2 . L.text `shouldBe` "bit lon"+ where wenv = mockWenv () fontMgr = wenv ^. L.fontManager@@ -167,6 +177,7 @@ elpsKeep_ size text = fitTextToSize fontMgr style Ellipsis MultiLine KeepSpaces Nothing size text clipTrim_ size text = fitTextToSize fontMgr style ClipText MultiLine TrimSpaces Nothing size text clipKeep_ size text = fitTextToSize fontMgr style ClipText MultiLine KeepSpaces Nothing size text+ breakOnChar text = fitTextToSize fontMgr (textLineBreak OnCharacters) ClipText MultiLine TrimSpaces Nothing sizeC text elementCount count sq = Seq.length sq == count fitTextSpace :: Spec
test/unit/Spec.hs view
@@ -8,7 +8,10 @@ import Monomer.TestUtil (useVideoSubSystem) import qualified Monomer.Common.CursorIconSpec as CursorIconSpec+ import qualified Monomer.Core.SizeReqSpec as SizeReqSpec+import qualified Monomer.Core.StyleUtilSpec as StyleUtilSpec+ import qualified Monomer.Graphics.UtilSpec as GraphicsUtilSpec import qualified Monomer.Widgets.CompositeSpec as CompositeSpec@@ -88,6 +91,7 @@ core :: Spec core = describe "Core" $ do SizeReqSpec.spec+ StyleUtilSpec.spec graphics :: Spec graphics = describe "Graphics" $ do