brick icon indicating copy to clipboard operation
brick copied to clipboard

Transparency/tinting/opacity for layers

Open sschuldenzucker opened this issue 4 months ago • 10 comments

This is probably not implemented yet but maybe I'm missing something:

I'm using layers to draw overlay windows ("popups") to ask the user for input. It would be cool if this could "gray out" / tint the layer behind it to make it more obvious to the user that they cannot interact with that layer while the overlay is active.

This can of course be emulated by having a variant (say .inactive) for each attr that uses a different color when that attr is unavailable. But ideally, there would be a global option that mixes each and every color on the inactive layer with gray to achieve this globally.

sschuldenzucker avatar Oct 16 '25 12:10 sschuldenzucker

Interestingly enough, I've wanted to do this exact same thing before! Here's how I do it: when the pop-up window is being displayed, I wrap the lower layer (the UI that will be "grayed out") with forceAttr $ attrName "invalid". That means that the whole UI will be drawn with that attribute, but since it's an invalid attribute name that doesn't correspond to an entry in the attribute map, it will default to the foreground and background color of the terminal. If you wanted a specific attribute, you could add an entry and use its name, but the key here is to use forceAttr. So something like:

draw :: MyState -> [Widget Name]
draw st =
    [ popUpWindow st
    , forceAttr (attrName "invalid") $ mainUI st
    ]

jtdaugherty avatar Oct 16 '25 14:10 jtdaugherty

Let me know if that gets you what you were after and we can close this or discuss further.

jtdaugherty avatar Oct 16 '25 15:10 jtdaugherty

Oooh that's a nice hack! Yes, should get me a decent 80/20. Or one could force a configured global "inactive" attr (which does exist) in the same way, right?

This is not perfect ofc because the colors are now all the same: if I have (say) a red and a green element, they don't go to faded-red and faded-green, but they both go to (say) gray.

Maybe I can actually programmatically patch the attr map on the inactive layer (replacing all colors by a manipulated version) to get the effect I want?

sschuldenzucker avatar Oct 17 '25 21:10 sschuldenzucker

Or one could force a configured global "inactive" attr (which does exist) in the same way, right?

Yes, that's right.

This is not perfect ofc because the colors are now all the same: if I have (say) a red and a green element, they don't go to faded-red and faded-green, but they both go to (say) gray.

Yes, and I'm afraid that I'm not up for implementing full-blown compositing in vty. :) But if someone wanted to, I'd be happy to consider patches!

Maybe I can actually programmatically patch the attr map on the inactive layer (replacing all colors by a manipulated version) to get the effect I want?

Yes: this is exactly why appAttrMap is a function, not a constant.

jtdaugherty avatar Oct 17 '25 21:10 jtdaugherty

Maybe I can actually programmatically patch the attr map on the inactive layer (replacing all colors by a manipulated version) to get the effect I want?

Although having this effect apply only to one layer will be tricky if not impossible.

jtdaugherty avatar Oct 17 '25 21:10 jtdaugherty

It occurs to me that if your attribute map entries use RGB colors, you could definitely programmatically subdue those. So as long as your underlying UI uses those entries and your pop-up uses a separate set of map entries that aren't subdued, that technique would work nicely.

jtdaugherty avatar Oct 17 '25 21:10 jtdaugherty

Although having this effect apply only to one layer will be tricky if not impossible.

I was mistaken about this! I forgot that there is a function updateAttrMap that you can use on a Widget to completely customize the entire attribute map, which would allow you to make whole-map transformations on a per-layer basis:

https://hackage-content.haskell.org/package/brick-2.10/docs/Brick-Widgets-Core.html#v:updateAttrMap

jtdaugherty avatar Oct 23 '25 19:10 jtdaugherty

@sschuldenzucker Let me know if you need more assistance; if not, I'll close this.

jtdaugherty avatar Oct 26 '25 00:10 jtdaugherty

@jtdaugherty didn't get to actually trying this out this out fully yet but I think it should be fine. Ok to close. Thank you!

Btw, I played around with this a little bit though and one issue I noticed is that the AttrMap constructors are not exported, which means I can't iterate over attributes/values in updateAttrMap, for example. I hacked something together with unsafeCoerce and it's working fine but obviously this is not the nicest way of doing things. Maybe these constructors should be exported or there should be some helper functions to let you look inside the map (with some not-so-pretty default if it's Force).

sschuldenzucker avatar Oct 30 '25 10:10 sschuldenzucker

Depending on what you are doing, it's probably already possible with the existing API. While one could think of this as a transformation on the existing attribute map, it might be more straightforward to think of it instead as a map replacement. In that case, you'd do updateAttrMap (const newMap), with the idea being that newMap is already something you can build: you built your original attribute map, and you could keep its "dimmed" version nearby, called newMap here, and then override the map entirely this way rather than transforming the map in the rendering context.

If that doesn't get you what you want, I'd be happy to consider adding some kind of transformation function to the API.

jtdaugherty avatar Oct 30 '25 15:10 jtdaugherty