brick
                                
                                 brick copied to clipboard
                                
                                    brick copied to clipboard
                            
                            
                            
                        Colored border
Hello, I'd like to get a colored border on some of my widgets (only the border, not the content). I tried:
w = withDefAttr highlighted $ border $ str "XXX"
highlighted = attrName "highlighted"
   
theMap :: AttrMap
theMap = attrMap V.defAttr [(highlighted <> borderAttr, fg VA.green)] 
But this didn't work. Thanks!
The border widget already colors itself with borderAttr, all you need to do to color borders is to add an entry for borderAttr to your AttrMap.
You can see an example of that in programs/BorderDemo.hs.
Actually I would like to color the border for only one widget with border (I have several). So I need to use updateAttrMap on that Widget right? I'm wondering why my approach in my first post wouldn't work? Maybe I misunderstood the AttrMap concept... Thanks again for your help, it's very appreciated!
Actually I would like to color the border for only one widget with border (I have several). So I need to use updateAttrMap on that Widget right?
I think what you'll need to do is use overrideAttr from Brick.Widgets.Core for this. Here's the idea:
- Create your own attribute name, e.g. customBorderAttr, that you want to use for your colored borders.
- Add an entry for customBorderAttrto your attribute map.
- When rendering a widget whose border you want to color with customBorderAttr, useoverrideAttrto remap it toborderAttrso that the border-drawing code uses your attribute. For example,overrideAttr borderAttr customBorderAttr $ border $ ...
You could use updateAttrMap, too, but I wouldn't recommend that just because it's going to require you to do more work to figure out what the attribute map should be in that spot. I'd suggest overrideAttr instead.
I'm wondering why my approach in my first post wouldn't work? Maybe I misunderstood the AttrMap concept...
Your first approach doesn't work because you only set the default attribute (i.e. you used withDefAttr). The attribute map has a default attribute that is used if no other attribute is requested, and with which all attribute lookups are merged. You can think of the default attribute in the map as the foreground/background colors and style that you want your application to use by default when no customizations have been applied. But border internally requests that it be styled with Brick.Widgets.Border.borderAttr, so Brick looks up borderAttr in your attribute map. If it finds an entry, it merges that with the default attribute in your attribute map. If it doesn't find an entry, it just uses the default attribute in your attribute map. Either way, any aspects of borderAttr take precedence over the default attribute, giving you the behavior you probably expect.
For example, if you set borderAttr to fg cyan and then set the default attribute of your attribute map to bg blue, then the result of rendering with borderAttr (in that particular attribute map) would be cyan text on a blue background.
Also, since I saw this show up in your example here as well as in the other ticket, I wanted to point out that things like this,
foo :: AttrName
foo = bar <> baz
construct a hierarchical attribute name, meaning in this case that foo is a specific case of bar. You can think of <> in this case as behaving like / in a Unix filesystem path. In that metaphor, / is the default attribute, /bar is bar merged with the default attribute (preference for whatever bar has set), and /bar/baz (i.e. named foo) is baz merged with bar merged with the default attribute (preference for baz, then bar, then the default).
And I'm happy to answer any questions, review code, offer suggestions, etc. Thanks for opening these tickets!
I was just looking at BorderDemo.hs and saw some code there that made me think that updateAttrMap wouldn't be all that bad, if you find it to be a better fit for your purposes. Here's the code there that illustrates how to use it:
borderMappings :: [(A.AttrName, V.Attr)]
borderMappings =
    [ (B.borderAttr,         V.yellow `on` V.black)
    , (titleAttr,            fg V.cyan)
    ]
colorDemo :: Widget ()
colorDemo =
    updateAttrMap (A.applyAttrMappings borderMappings) $
    B.borderWithLabel (withAttr titleAttr $ str "title") $
    hLimit 20 $
    vLimit 5 $
    C.center $
    str "colors!"
(Still, if this is something you want to do in a lot of spots, overrideAttr wrapped up in your own function might be the cleanest way to go.)
Thanks, it works well with overrideAttr!
I was wondering if such a function would work too:
addAttr :: AttrName -> Widget n -> Widget n
addAttr an p =
    Widget (hSize p) (vSize p) $
      withReaderT (ctxAttrNameL %~ (an:)) (render p)
In practice adding an attribute name to the list in ctxAttrName.
In that case, the ctxAttrName of my borders would become ["highlight", "border"], so I could address them directly in my AttrMap:
w = addAttr highlighted $ border $ str "XXX"
highlighted = attrName "highlighted"
   
theMap :: AttrMap
theMap = attrMap V.defAttr [(highlighted <> borderAttr, fg VA.green)] 
Would that work as well?
I was wondering if such a function would work too:
That can't be done because ctxAttrNameL is not a list; only one attribute is set at a time in the context.
theMap = attrMap V.defAttr [(highlighted <> borderAttr, fg VA.green)]
Just to emphasize the point I made earlier: this does not combine two attributes. It makes a new attribute name that inherits from highlighted.
By the way, the application is taking good shape: https://github.com/cdupont/timetravel It's been fun to work with Brick. I'm very open to suggestions!
@cdupont Is there anything else I can do to help on this ticket?
No it's good, working well now!