brick 0.32.1 → 0.33
raw patch · 7 files changed
+103/−73 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Brick.Widgets.Border: blCornerAttr :: AttrName
- Brick.Widgets.Border: brCornerAttr :: AttrName
- Brick.Widgets.Border: hBorderAttr :: AttrName
- Brick.Widgets.Border: hBorderLabelAttr :: AttrName
- Brick.Widgets.Border: tlCornerAttr :: AttrName
- Brick.Widgets.Border: trCornerAttr :: AttrName
- Brick.Widgets.Border: vBorderAttr :: AttrName
+ Brick.Forms: [formFieldExternallyValid] :: FormField a b e n -> Bool
+ Brick.Forms: setFieldValid :: (Eq n) => Bool -> n -> Form s e n -> Form s e n
- Brick.Forms: FormField :: n -> (b -> Maybe a) -> (Bool -> b -> Widget n) -> (BrickEvent n e -> b -> EventM n b) -> FormField a b e n
+ Brick.Forms: FormField :: n -> (b -> Maybe a) -> Bool -> (Bool -> b -> Widget n) -> (BrickEvent n e -> b -> EventM n b) -> FormField a b e n
- Brick.Types: cursorLocationL :: forall n_asre. Lens' (CursorLocation n_asre) Location
+ Brick.Types: cursorLocationL :: forall n_asrl. Lens' (CursorLocation n_asrl) Location
- Brick.Types: cursorLocationNameL :: forall n_asre n_asUP. Lens (CursorLocation n_asre) (CursorLocation n_asUP) (Maybe n_asre) (Maybe n_asUP)
+ Brick.Types: cursorLocationNameL :: forall n_asrl n_asUW. Lens (CursorLocation n_asrl) (CursorLocation n_asUW) (Maybe n_asrl) (Maybe n_asUW)
- Brick.Types: cursorsL :: forall n_asrd. Lens' (Result n_asrd) [CursorLocation n_asrd]
+ Brick.Types: cursorsL :: forall n_asrk. Lens' (Result n_asrk) [CursorLocation n_asrk]
- Brick.Types: extentsL :: forall n_asrd. Lens' (Result n_asrd) [Extent n_asrd]
+ Brick.Types: extentsL :: forall n_asrk. Lens' (Result n_asrk) [Extent n_asrk]
- Brick.Types: imageL :: forall n_asrd. Lens' (Result n_asrd) Image
+ Brick.Types: imageL :: forall n_asrk. Lens' (Result n_asrk) Image
- Brick.Types: visibilityRequestsL :: forall n_asrd. Lens' (Result n_asrd) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_asrk. Lens' (Result n_asrk) [VisibilityRequest]
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_aQFe a_aQFU. Lens (Dialog a_aQFe) (Dialog a_aQFU) [(String, a_aQFe)] [(String, a_aQFU)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_aQfs a_aQg8. Lens (Dialog a_aQfs) (Dialog a_aQg8) [(String, a_aQfs)] [(String, a_aQg8)]
- Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_aQFe. Lens' (Dialog a_aQFe) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_aQfs. Lens' (Dialog a_aQfs) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: forall a_aQFe. Lens' (Dialog a_aQFe) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_aQfs. Lens' (Dialog a_aQfs) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: forall a_aQFe. Lens' (Dialog a_aQFe) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_aQfs. Lens' (Dialog a_aQfs) Int
- Brick.Widgets.Edit: editContentsL :: forall t_aMhT n_aMhU t_aMiB. Lens (Editor t_aMhT n_aMhU) (Editor t_aMiB n_aMhU) (TextZipper t_aMhT) (TextZipper t_aMiB)
+ Brick.Widgets.Edit: editContentsL :: forall t_aMi2 n_aMi3 t_aMiK. Lens (Editor t_aMi2 n_aMi3) (Editor t_aMiK n_aMi3) (TextZipper t_aMi2) (TextZipper t_aMiK)
- Brick.Widgets.List: listElementsL :: forall n_aYEK e_aYEL e_aYPq. Lens (List n_aYEK e_aYEL) (List n_aYEK e_aYPq) (Vector e_aYEL) (Vector e_aYPq)
+ Brick.Widgets.List: listElementsL :: forall n_aYrF e_aYrG e_aYCl. Lens (List n_aYrF e_aYrG) (List n_aYrF e_aYCl) (Vector e_aYrG) (Vector e_aYCl)
- Brick.Widgets.List: listItemHeightL :: forall n_aYEK e_aYEL. Lens' (List n_aYEK e_aYEL) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_aYrF e_aYrG. Lens' (List n_aYrF e_aYrG) Int
- Brick.Widgets.List: listNameL :: forall n_aYEK e_aYEL n_aYPr. Lens (List n_aYEK e_aYEL) (List n_aYPr e_aYEL) n_aYEK n_aYPr
+ Brick.Widgets.List: listNameL :: forall n_aYrF e_aYrG n_aYCm. Lens (List n_aYrF e_aYrG) (List n_aYCm e_aYrG) n_aYrF n_aYCm
- Brick.Widgets.List: listSelectedL :: forall n_aYEK e_aYEL. Lens' (List n_aYEK e_aYEL) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_aYrF e_aYrG. Lens' (List n_aYrF e_aYrG) (Maybe Int)
Files
- CHANGELOG.md +10/−0
- brick.cabal +1/−1
- docs/guide.rst +10/−0
- programs/BorderDemo.hs +7/−9
- programs/FormDemo.hs +11/−2
- src/Brick/Forms.hs +58/−13
- src/Brick/Widgets/Border.hs +6/−48
CHANGELOG.md view
@@ -2,6 +2,16 @@ Brick changelog --------------- +0.33+----++API changes:+ * Forms: added support for external validation of form fields using+ `setFieldValid`. See the Haddock, User Guide, and FormDemo.hs for+ details.+ * Borders: removed all attribute names except `borderAttr` to simplify+ border attribute assignment.+ 0.32.1 ------
brick.cabal view
@@ -1,5 +1,5 @@ name: brick-version: 0.32.1+version: 0.33 synopsis: A declarative terminal user interface library description: Write terminal applications painlessly with 'brick'! You write an
docs/guide.rst view
@@ -1418,6 +1418,16 @@ list of any fields which had invalid values can be retrieved with the ``Brick.Forms.invalidFields`` function. +While each form field type provides a validator function to validate+its current user input value, that function is pure. As a result it's+not suitable for doing validation that requires I/O such as searching+a database or making network requests. If your application requires+that kind of validation, you can use the ``Brick.Forms.setFieldValid``+function to set the validation state of any form field as you see+fit. The validation state set by that function will be considered by+``allFieldsValid`` and ``invalidFields``. See ``FormDemo.hs`` for an+example of this API.+ Note that if mouse events are enabled in your application (see `Mouse Support`_), all built-in form fields will respond to mouse interaction. Radio buttons and check boxes change selection on mouse clicks and
programs/BorderDemo.hs view
@@ -10,7 +10,7 @@ import qualified Graphics.Vty as V import qualified Brick.Main as M-import Brick.Util (fg, bg, on)+import Brick.Util (fg, on) import qualified Brick.AttrMap as A import Brick.Types ( Widget@@ -18,6 +18,7 @@ import Brick.Widgets.Core ( (<=>) , (<+>)+ , withAttr , vLimit , hLimit , hBox@@ -66,22 +67,19 @@ C.vCenter $ txt $ " " <> styleName <> " style " +titleAttr :: A.AttrName+titleAttr = "title"+ borderMappings :: [(A.AttrName, V.Attr)] borderMappings = [ (B.borderAttr, V.yellow `on` V.black)- , (B.vBorderAttr, V.green `on` V.red)- , (B.hBorderAttr, V.white `on` V.green)- , (B.hBorderLabelAttr, fg V.blue)- , (B.tlCornerAttr, bg V.red)- , (B.trCornerAttr, bg V.blue)- , (B.blCornerAttr, bg V.yellow)- , (B.brCornerAttr, bg V.green)+ , (titleAttr, fg V.cyan) ] colorDemo :: Widget () colorDemo = updateAttrMap (A.applyAttrMappings borderMappings) $- B.borderWithLabel (str "title") $+ B.borderWithLabel (withAttr titleAttr $ str "title") $ hLimit 20 $ vLimit 5 $ C.center $
programs/FormDemo.hs view
@@ -3,6 +3,7 @@ module Main where import qualified Data.Text as T+import Lens.Micro ((^.)) import Lens.Micro.TH import Data.Monoid ((<>)) @@ -13,6 +14,7 @@ , newForm , formState , formFocus+ , setFieldValid , renderForm , handleFormEvent , invalidFields@@ -114,7 +116,13 @@ -- Enter quits only when we aren't in the multi-line editor. VtyEvent (V.EvKey V.KEnter []) | focusGetCurrent (formFocus s) /= Just AddressField -> halt s- _ -> continue =<< handleFormEvent ev s+ _ -> do+ s' <- handleFormEvent ev s++ -- Example of external validation:+ -- Require age field to contain a value that is at least 18.+ continue $ setFieldValid ((formState s')^.age >= 18) AgeField s'+ , appChooseCursor = focusRingCursor formFocus , appStartEvent = return , appAttrMap = const theMap@@ -134,7 +142,8 @@ , _ridesBike = False , _password = "" }- f = mkForm initialUserInfo+ f = setFieldValid False AgeField $+ mkForm initialUserInfo f' <- customMain buildVty Nothing app f
src/Brick/Forms.hs view
@@ -48,6 +48,7 @@ , (@@=) , allFieldsValid , invalidFields+ , setFieldValid -- * Simple form field constructors , editTextField@@ -99,8 +100,18 @@ -- ^ A validation function converting this field's state -- into a value of your choosing. @Nothing@ indicates a -- validation failure. For example, this might validate- -- an 'Editor' state value by parsing its text contents- -- as an integer and return 'Maybe' 'Int'.+ -- an 'Editor' state value by parsing its text contents s+ -- aan integer and return 'Maybe' 'Int'. This is for pure+ -- avalue validation; if additional validation is required+ -- a(e.g. via 'IO'), use this field's state value in an+ -- aexternal validation routine and use 'setFieldValid' to+ -- afeed the result back into the form.+ , formFieldExternallyValid :: Bool+ -- ^ Whether the field is valid according to an external+ -- validation source. Defaults to always being 'True' and+ -- can be set with 'setFieldValid'. The value of this+ -- field also affects the behavior of 'allFieldsValid' and+ -- 'getInvalidFields'. , formFieldRender :: Bool -> b -> Widget n -- ^ A function to render this form field. Parameters are -- whether the field is currently focused, followed by the@@ -235,7 +246,7 @@ handleEvent _ s = return s in FormFieldState { formFieldState = initVal- , formFields = [ FormField name Just+ , formFields = [ FormField name Just True (renderCheckbox label name) handleEvent ]@@ -284,6 +295,7 @@ mkOptionField (val, name, label) = FormField name Just+ True (renderRadio val name label) (handleEvent val) @@ -350,6 +362,7 @@ in FormFieldState { formFieldState = initVal , formFields = [ FormField n (val . getEditContents)+ True (\b e -> wrapEditor $ renderEditor renderText b e) handleEvent ]@@ -451,10 +464,36 @@ invalidFields :: Form s e n -> [n] invalidFields f = concat $ getInvalidFields <$> formFieldStates f +-- | Manually indicate that a field has invalid contents. This can be+-- useful in situations where validation beyond the form element's+-- validator needs to be performed and the result of that validation+-- needs to be fed back into the form state.+setFieldValid :: (Eq n)+ => Bool+ -- ^ Whether the field is considered valid.+ -> n+ -- ^ The name of the form field to set as (in)valid.+ -> Form s e n+ -- ^ The form to modify.+ -> Form s e n+setFieldValid v n form =+ let go1 [] = []+ go1 (s:ss) =+ let s' = case s of+ FormFieldState st l fs rh ->+ let go2 [] = []+ go2 (f@(FormField fn val _ r h):ff)+ | n == fn = FormField fn val v r h : ff+ | otherwise = f : go2 ff+ in FormFieldState st l (go2 fs) rh+ in s' : go1 ss++ in form { formFieldStates = go1 (formFieldStates form) }+ getInvalidFields :: FormFieldState s e n -> [n] getInvalidFields (FormFieldState st _ fs _) =- let gather (FormField n validate _ _) =- if (isNothing $ validate st) then [n] else []+ let gather (FormField n validate extValid _ _) =+ if (not extValid || (isNothing $ validate st)) then [n] else [] in concat $ gather <$> fs -- | Render a form.@@ -462,7 +501,10 @@ -- For each form field, each input for the field is rendered using the -- implementation provided by its 'FormField'. The inputs are then -- vertically concatenated with 'vBox' and then augmented using the form--- field's rendering augmentation function (see '@@=').+-- field's rendering augmentation function (see '@@='). Fields with+-- invalid inputs (either due to built-in validator failure or due to+-- external validation failure via 'setFieldValid') will be displayed+-- using the 'invalidFormInputAttr' attribute. -- -- The augmented field renderings are then placed in a 'vBox' and -- returned.@@ -476,8 +518,8 @@ -> Widget n renderFormFieldState fr (FormFieldState st _ fields helper) = let renderFields [] = []- renderFields ((FormField n validate renderField _):fs) =- let maybeInvalid = if isJust $ validate st+ renderFields ((FormField n validate extValid renderField _):fs) =+ let maybeInvalid = if (isJust $ validate st) && extValid then id else forceAttr invalidFormInputAttr foc = Just n == focusGetCurrent fr@@ -503,10 +545,13 @@ -- * All other events are forwarded to the currently focused form field. -- -- In all cases where an event is forwarded to a form field, validation--- of the field's input state is performed after the event has been--- handled. If the form field's input state succeeds validation, its--- value is immediately stored in the form state using the form field's--- state lens.+-- of the field's input state is performed immediately after the+-- event has been handled. If the form field's input state succeeds+-- validation using the field's validator function, its value is+-- immediately stored in the form state using the form field's state+-- lens. The external validation flag is ignored during this step to+-- ensure that external validators have a chance to get the intermediate+-- validated value. handleFormEvent :: (Eq n) => BrickEvent n e -> Form s e n -> EventM n (Form s e n) handleFormEvent (VtyEvent (EvKey (KChar '\t') [])) f = return $ f { formFocus = focusNext $ formFocus f }@@ -584,7 +629,7 @@ let findField [] = return Nothing findField (field:rest) = case field of- FormField n' validate _ handleFunc | n == n' -> do+ FormField n' validate _ _ handleFunc | n == n' -> do nextSt <- handleFunc ev st -- If the new state validates, go ahead and update -- the form state with it.
src/Brick/Widgets/Border.hs view
@@ -18,20 +18,11 @@ -- * Drawing single border elements , borderElem - -- * Border attribute names+ -- * Attribute names , borderAttr- , vBorderAttr- , hBorderAttr- , hBorderLabelAttr- , tlCornerAttr- , trCornerAttr- , blCornerAttr- , brCornerAttr ) where -import Data.Monoid ((<>))- #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif@@ -49,34 +40,6 @@ borderAttr :: AttrName borderAttr = "border" --- | The vertical border attribute name.-vBorderAttr :: AttrName-vBorderAttr = borderAttr <> "vertical"---- | The horizontal border attribute name.-hBorderAttr :: AttrName-hBorderAttr = borderAttr <> "horizontal"---- | The attribute used for horizontal border labels.-hBorderLabelAttr :: AttrName-hBorderLabelAttr = hBorderAttr <> "label"---- | The attribute used for border box top-left corners.-tlCornerAttr :: AttrName-tlCornerAttr = borderAttr <> "corner" <> "tl"---- | The attribute used for border box top-right corners.-trCornerAttr :: AttrName-trCornerAttr = borderAttr <> "corner" <> "tr"---- | The attribute used for border box bottom-left corners.-blCornerAttr :: AttrName-blCornerAttr = borderAttr <> "corner" <> "bl"---- | The attribute used for border box bottom-right corners.-brCornerAttr :: AttrName-brCornerAttr = borderAttr <> "corner" <> "br"- -- | Draw the specified border element using the active border style -- using 'borderAttr'. borderElem :: (BorderStyle -> Char) -> Widget n@@ -108,19 +71,14 @@ border_ :: Maybe (Widget n) -> Widget n -> Widget n border_ label wrapped = Widget (hSize wrapped) (vSize wrapped) $ do- bs <- ctxBorderStyle <$> getContext c <- getContext middleResult <- render $ hLimit (c^.availWidthL - 2) $ vLimit (c^.availHeightL - 2) $ wrapped - let top = (withAttr tlCornerAttr $ str [bsCornerTL bs])- <+> hBorder_ label <+>- (withAttr trCornerAttr $ str [bsCornerTR bs])- bottom = (withAttr blCornerAttr $ str [bsCornerBL bs])- <+> hBorder <+>- (withAttr brCornerAttr $ str [bsCornerBR bs])+ let top = (borderElem bsCornerTL) <+> hBorder_ label <+> (borderElem bsCornerTR)+ bottom = (borderElem bsCornerBL) <+> hBorder <+> (borderElem bsCornerBR) middle = vBorder <+> (Widget Fixed Fixed $ return middleResult) <+> vBorder total = top <=> middle <=> bottom @@ -143,12 +101,12 @@ hBorder_ label = Widget Greedy Fixed $ do bs <- ctxBorderStyle <$> getContext- let msg = maybe (str [bsHorizontal bs]) (withAttr hBorderLabelAttr) label- render $ vLimit 1 $ withAttr hBorderAttr $ hCenterWith (Just $ bsHorizontal bs) msg+ let msg = maybe (str [bsHorizontal bs]) id label+ render $ vLimit 1 $ withAttr borderAttr $ hCenterWith (Just $ bsHorizontal bs) msg -- | A vertical border. Fills all vertical space. vBorder :: Widget n vBorder = Widget Fixed Greedy $ do bs <- ctxBorderStyle <$> getContext- render $ hLimit 1 $ withAttr vBorderAttr $ fill (bsVertical bs)+ render $ hLimit 1 $ withAttr borderAttr $ fill (bsVertical bs)