tobi-wan-kenobi / bumblebee-status

bumblebee-status is a modular, theme-able status line generator for the i3 window manager.
https://bumblebee-status.readthedocs.io/en/main/
MIT License
1.2k stars 228 forks source link

[development] Generalize value-dependent decorations #571

Closed tobi-wan-kenobi closed 4 years ago

tobi-wan-kenobi commented 4 years ago

Looking at the "hbar" and "vbar" that @somospocos implemented, I was suddenly struck by an idea: These functionalities are conceptually very similar to the existing notion of having a battery icon that depends on the charge level.

In reality, what all those things do in a concrete fashion is the general concept of having a decoration (before/after the full text) that depends on the value. Doing this in a generic fashion would reduce overhead and open up similar implementations - on a per-theme basis!

For example:

"pulseaudio": {
    "decorations": [
        { "name": "volumebar", "data": "hbar" },
    ],
    "layout": [ "prefix", "content", "volumebar", "suffix" ]
}

In the icons file:

"functions": [
    { "name": "hbar", "data": [
        { "range": [ 0, 9 ] , "data": "_" },
        { "range": [ 10, 19 ], "data": "=" },
    ]
]

And so on.

The module themeself then would need to expose the actual value somehow, maybe simply via the "status" callback.

@somospocos: Any thoughts on this?

ghost commented 4 years ago

In reality, what all those things do in a concrete fashion is the general concept of having a decoration (before/after the full text) that depends on the value.

This is correct, at least for the hbar/vbar. As you remember, those eyecandy goodies were born first. Their purpose was to visualize percentages like cpu core load, sound volume level. When we say percentage, we mean there is a fixed maximum value that we know and consider it equal to 100%, and we do the math for smaller values. From this perspective your hypothetical snippet from the icons file shall work: as I understand, it will just template the existing code logic - it will partition the [0, 100] interval into smaller parts and attach visual labels to each subset.

However, before finally settling on a design plan, I suggest extending the topic to include the braille charts too, just to estimate the chance if it's possible to make it fit.

The braille charts were designed for a slightly different, a bit more complex scenario. Technically, the graph is drawn for a limited timeframe - x seconds. But there is a difference here. That maximum value that serves as gauge for 100% doesn't stay constant - it changes over time - it is computed as the maximum of all values that need to be plotted. Also, each braille char depicts two values rather than one.

Since I have implemented hbar+vbar and braille visuals separately and for slightly different use cases, I suffer from the "perspective bias", as in, I don't see (at least for now) an obvious way to generalize both into the same pattern. But hopefully the outcome of this issue will be a clearer view on the topic if it's possible to generalize these two scenarios into one or if it will be easier to go with different templates for them.

tobi-wan-kenobi commented 4 years ago

Good point reg. Braille - I guess one core question will be whether this is really a "more of the same" use case or whether Braille is sufficiently different to warrant a separate implementation.

TBH, I do not fully understand what the Braille bar does, why do 2 input values get mapped to a single output character, and what are those inputs?

But from an abstract perspective, I think in the suggestion above, I'd model the Braille as something of a two-stage: First, the inputs would be "translated", mapped into a result string (this could be done using he outline above). Then, the result would be scrolled using the existing "scrollable" decorator.

If you could explain what you're doing with the Braille graph, that'd help me understand whether my thinking goes into the right direction or not.

Thanks a lot in advance!

ghost commented 4 years ago

TBH, I do not fully understand what the Braille bar does, why do 2 input values get mapped to a single output character, and what are those inputs?

Technically, it is possible to have graphs using same bars that hbar/vbar use, but using braille chars is a hack that allows fitting a twice longer timeframe into the same area of bumblebee-status.

If you run a font browser program, gucharmap for example, pick a font and navigate to the "Braille Patterns" section, you can see that the template for Braille char is a 2x4 matrix:

xx
xx
xx
xx

Technically, that is two 4-pixel vertical bars in one char:

x   x
x   x
x   x
x   x

Starting from a template that has no pixels (empty), I additionally need the following:

       x
     x x
   x x x
 x x x x

Obviously, I don't need vertical bars with "gaps" - empty spaces between pixels, like, for example:

x

x
x

Then I just do combinations of all these possible vertical bars and pick the unicode chars that correspond to those combinations.

Have a look here https://github.com/tobi-wan-kenobi/bumblebee-status/blob/bb2c92732ee7cf834d937025a03c87d7ee5bc343/bumblebee/output.py#L34 Two-item tuples represent how many dots the first vertical bar has and how many dots the second one has - and they map to the corresponding Braiile char. For example (1, 4) maps to:

 x
 x
 x
xx

This is why the timeframe is requred to be even, so the list of values splits nicely into 2-item pieces (sure the value can be padded if it's not even).

The next step is to build the graph from a list of values. For example, a list of 10 items (so 10 seconds, 10 iterations of bumblebee-status) - would require just 5 chars on screen. Initially, all list values are 0. Maximum value is found and is used as 100%, the rest of values are scaled to it and the corresponding Braille chars for value pairs are picked.

This is the static situation. Each iteration the list is treated as FIFO and values in it get shifted to left - the oldest is gone, the newest is appended at the end and the finding maximum/scaling everything to it process just gets repeated.

Then, the result would be scrolled using the existing "scrollable" decorator.

The scrollable approach is used to draw a fixed length string into an area of smaller size. It goes back and forth once it hits the start/end of the string. The Braille graph technically displays a piece of an infinite list, but that piece moves in one direction only.

tobi-wan-kenobi commented 4 years ago

Ah, wait - so the input is Braille already, and the chart "just" displays it?

I still don't really get why you can skip empty vertical bars :thinking:

Do you have a concrete example of what you are using this for in bumblebee? I mean, in the codebase, it's not actually used anywhere (yet).

ghost commented 4 years ago

Ah, wait - so the input is Braille already, and the chart "just" displays it?

The input for a Braille graph is a list of numbers.

The "animation" effect is achieved in the code that is actually using it (updating the list by pushing "oldest" values out and adding "new" ones).

I still don't really get why you can skip empty vertical bars

I did not understand this question, please explain.

Do you have a concrete example of what you are using this for in bumblebee? I mean, in the codebase, it's not actually used anywhere (yet).

It is used in the traffic module, see the traffic.graphlen parameter in the docstring. This is the code that collects up/down values for the list https://github.com/tobi-wan-kenobi/bumblebee-status/blob/bb2c92732ee7cf834d937025a03c87d7ee5bc343/bumblebee/modules/traffic.py#L153 . And here is the inlined call to output.bgraph() that gets prepended to the numeric value https://github.com/tobi-wan-kenobi/bumblebee-status/blob/bb2c92732ee7cf834d937025a03c87d7ee5bc343/bumblebee/modules/traffic.py#L155

ghost commented 4 years ago

PS: if the traffic specific code gets in your way, perhaps it will be simpler to understand Braille graph from here, where I originally wrote a simple demo https://github.com/somospocos/pytxtbar/blob/14e6911c88b4de4f2f4d0c5eaa8e4a2755714e85/pytxtbar.py#L226

I'm sure I linked to it when I announced this feature. Just run that pytxtbar.py script and watch. The numbers that are randomly generated there are the up/down values in bytes in the traffic module.

tobi-wan-kenobi commented 4 years ago

I see, I was grepping for "braille", so I missed the traffic module, interesting.

I still don't really get why you can skip empty vertical bars

I did not understand this question, please explain.

Never mind, I figured it out. You mean that the dots always stack from bottom to top, there cannot be an empty space in between the dots of a single column.

I feel stupid, but I still don't fully get why there's 2 values per bar - do I understand it correctly, that this is done to double the timeframe by encoding two values in a single character?

PS: if the traffic specific code gets in your way, perhaps it will be simpler to understand Braille graph from here, where I originally wrote a simple demo https://github.com/somospocos/pytxtbar/blob/14e6911c88b4de4f2f4d0c5eaa8e4a2755714e85/pytxtbar.py#L226

I did have a look at your code, I just didn't understand it :P

But now, I think I see where this is going. You are right, this is probably not going to fit nicely into what I had in mind, but I'm tempted to say "too much of a specific case" for now :)

Anyhow, to be honest, I've somewhat tabled the value-dependent decorations for now, as I get stuck every time I sit down in front of the keyboard. I am not sure how to start yet, so it's probably too soon to actually do something.

Your input is much appreciated and will surely influence the final design a lot.

Thanks!

ghost commented 4 years ago

I still don't fully get why there's 2 values per bar - do I understand it correctly, that this is done to double the timeframe by encoding two values in a single character?

Technically, this is true, I just prefer to approach it from the opposite perspective, as in "for the same timeframe, encoding two values in a single character allows using twice less space in the bar (so there's more free space left for other modules)". I even recorded this situation, which I find entertaining, here https://github.com/somospocos/bumblebee-status-contrib#if-one-bar-is-not-enough

tobi-wan-kenobi commented 4 years ago

Got it, sounds meaningful, thanks!

Reg. your contrib: I read that - many thanks! Two small remarks:

  1. You probably know it, but you can have even more than 2 bars - it's 2 per screen. At work, for example, I have a grand total of 5 bars distributed over 3 screens :)
  2. "Ricing without much coding": Using shell & spacer, you don't have to ignore the mouse events, as you can configurationally bind mouse events using something like spacer.right-click=<cmd>

Cheers!

ghost commented 4 years ago

You probably know it, but you can have even more than 2 bars - it's 2 per screen. At work, for example, I have a grand total of 5 bars distributed over 3 screens :)

I wrote this before implementing the dzen2 bridge, which allows spawning an unlimited amount of bars and is not i3-specific :)

you can configurationally bind mouse events using something like spacer.right-click=

I have never did this before, I shall do some testing. I believe I saw something similar in i3blocks

tobi-wan-kenobi commented 4 years ago

Closing, as further analysis makes me thing that this is overly complicated and doesn't bring a lot of benefit in the end.

Also, adding pango support anyhow adds a lot of styling flexibility.