Skip to content

Conversation

@Lomadriel
Copy link
Member

@Lomadriel Lomadriel commented Mar 4, 2019

Implement feature requested in #1651 and #951.
This pr add supports for px (pixel) and pt (point) for polybar.

I followed the advices given by @patrick96 in #1651, so I add a size_with_unit structure to store the value and the unit. Unlike the size definition, the value is signed to handle negative offset.

To handle the width and height problem, I added a geometry_format_values structure which contains a percentage and the offset (a size_with_unit structure).

builder::space now uses the %{O} formatting tag but this is handled by size_with_unit_to_string helper in the new header "utils/unit.hpp" to avoid duplicate code (this function is also used in controller.cpp since I can't use builder::space here).

DPI initialization is now in the constructor of the bar. However this initialization seems strange, the DPI are only computed if dpi < 0 is explicitly specified in the configuration (see below). If the DPI isn't specified in the configuration, the default value is 96. Is it the correct behavior?

https://github.com/Lomadriel/polybar/blob/cc4e09edc41cfbd5f2f4bf7235470aa1c21bc59a/src/components/bar.cpp#L155-L178

The rest of the PR is just adapting the code to use these structures. I tried to find all keys that could uses units but I am not sure if I added units support for all the key that could benefit of this feature.

EDIT: Fixes #1700

TEAMEDIT:
Closes #1651
Closes #951
Fixes #1265

@Lomadriel
Copy link
Member Author

I fixed the unit tests. Should I add a test for the function template <> geometry_format_values config::convert(string&& value) const? (which does the string -> geometry_format_values conversion that isn't checked anymore)

@Lomadriel
Copy link
Member Author

I just remove some bugs (negative margin, padding...).
Normally this should also fix #1265.

Copy link
Member

@patrick96 patrick96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I jotted down a bunch of thoughts I had about this. (almost) nothing here is set in stone, I'd appreciate your thoughts as well.

Thanks a lot for putting your time into this, I'm really excited to get this functionality inside polybar.


DPI initialization is now in the constructor of the bar. However this initialization seems strange, the DPI are only computed if dpi < 0 is explicitly specified in the configuration (see below). If the DPI isn't specified in the configuration, the default value is 96. Is it the correct behavior?

Moving it into bar.cpp is fine. I don't know what the original intention was. But since that part of the code is over 2 years old, I'd say it's the expected behavior now ;)


One thing that concerned me when writing up the instructions in #1651 was that SPACE doesn't make sense in the offset format.

I think we need to separate how we specify sizes and spacing.

Spacing can be in spaces, pixel, or points while size can be percentages, pixel, points or those combined in the offset format.

I first thought we can keep this together because it has lots of things in common (POINT and PIXEL), but I don't think this is worth it.


The more I think about it, the less I am a fan of converting points to pixels only when the value in pixels is needed and passing around point values. I know this is actually what I suggested we do originally because the builder would be the only place where this conversion happens. But this would make things quite inflexible because if we ever extend our code we would need to do the conversion in all places that use those pixel values.

Ideally we would get back and pass around only structs like this for spacing:

enum size_type { SPACE, PIXEL };

struct size {
  enum size_type type;
  ssize_t value;
}

and everything else is handled by the config. I said before that I am not a fan of the config knowing about the dpi but it seems to me this is the only way to get a clean interface for this.

I am planning on separating the config into two parts anyway: One part just accesses the config data structures and returns a string for a given key, the other part takes that string and converts it into whatever type is needed. The current way of using templates for everything in the config seems a bit messy to me. Since we will have such a separation anyway and there the part that converts things may actually know about dpi and other things from the outside, we could store dpi_x in the config as a temporary fix (and mark it as such). We only store dpi_x because spacing will always be in the x-direction.

We don't have the same problem with sizes and the geometry format because there we need to call a special function to get pixel values anyway and so we can just pass the dpi there.


The last thing is about what type the values should have. You made it very generic by using templates.

As far as I can tell, the following are the strictest types we can have for the different units:

  • spacing:
    • POINT: float >= 0
    • PIXEL: size_t
    • SPACE: size_t
  • size:
    • POINT: float
    • PIXEL: ssize_t

If we actually want to use these types, we'd have to use union inside the structs. This may be too cumbersome though, because we would have to have multiple if-else blocks whenever we use it.

For spacing, if we implement it as I described just above, the config will anyway just return either PIXEL or SPACE, so we can use size_t there.

For size, we could also use float for everything and when converting to pixels, we just cast to size_t.

EDIT: Since we use the %{O tag, the POINT and PIXEL values for spacing can also be negative. So using float everywhere is fine.

@Lomadriel
Copy link
Member Author

DPI initialization is now in the constructor of the bar. However this initialization seems strange, the DPI are only computed if dpi < 0 is explicitly specified in the configuration (see below). If the DPI isn't specified in the configuration, the default value is 96. Is it the correct behavior?

Moving it into bar.cpp is fine. I don't know what the original intention was. But since that part of the code is over 2 years old, I'd say it's the expected behavior now ;)

If that's the expected behavior, maybe the documentation/wiki should be modified to state that any value <= 0 cause the DPI to be auto-detected. Moreover, this detection is bugged, since if I change the resolution of my screen (like when I duplicate my laptop screen and the projector is in 4/3), I have to relaunch polybar to update this value. (Maybe an x event should cause the value to be updated)

I think we need to separate how we specify sizes and spacing.

Spacing can be in spaces, pixel, or points while size can be percentages, pixel, points or those combined in the offset format.

I first thought we can keep this together because it has lots of things in common (POINT and PIXEL), but I don't think this is worth it.

I agree, I tried to do my best but this is really confusing.

The more I think about it, the less I am a fan of converting points to pixels only when the value in pixels is needed and passing around point values. I know this is actually what I suggested we do originally because the builder would be the only place where this conversion happens. But this would make things quite inflexible because if we ever extend our code we would need to do the conversion in all places that use those pixel values.

The following paragraph supposes that the DPI might become dynamic otherwise its content should be ignored.

For that point, I think this might be a problem if the DPI changes during the execution.
Since the "point" is here to have a DPI sensitive unit, it is quite strange to convert point sizes into pixel sizes when charging the configuration. If the DPI changes, we would need to update every sizes and it is quite expensive.
Maybe introducing a sort of settings singleton that will hold some important values (like the DPI) could solve the problem even if I don't like introducing singleton.
It will allow to have a space_size struct like that:

struct space_size {
  space_type type;
  // If type == space_type::pixel converts the value in pixel if necessary and returns it.
  // Otherwise returns the size in number of spaces.
  // No need to use float in the return type since pixel or space doesn't need them.
  ssize_t get_value() const {
    if (type == space_type::point)     
      return convert_to_pixel(settings::get_instance().get_dpi_x(), value);
    else
      return value;
  }
private:
  // to be sure that nobody uses it directly
  float value; // could be an union if you want since the get_value is the only place
                    // were the union will be used.
};

The geometry_size struct could be defined in a similar way with an enum type like that:

enum class geometry_size_type { PERCENTAGE, POINT, PIXEL }
enum size_type { SPACE, PIXEL };

Do you want me to use a plain C enum or may I use an enum class? (I prefer using enum class to avoid global namespace pollution)

What do you think of this?

@patrick96
Copy link
Member

Everything within the bar except for the modules themselves is quite static. Polybar cannot dynamically change its size or position when changes to the monitor occur. All of this is handled through screenchange-reload which just reloads polybar whenever the screen changes.

It is conceivable that we want to make things more dynamic in the future. But even then, I would not convert to pixels in the builder but rather extend the %{O} tag to also work with points and then let the renderer handle it. Either way seems fine to me, thinking about it, doing this in the renderer may even be a little cleaner. This is up to you how you want to do it, I just don't want it to happen somewhere in the middle where we potentially have to do the conversion in multiple places.

Adding get_value might look very clean in the code that uses it, but it introduces a singleton and couples the concept of units for spacing purposes to the bar class. I want to avoid that.

The geometry_size struct could be defined in a similar way with an enum type like that:

enum class geometry_size_type { PERCENTAGE, POINT, PIXEL }

Wouldn't geometry_size_type only have point and pixel? As far as I know, percentages cannot be used as offsets.

Do you want me to use a plain C enum or may I use an enum class? (I prefer using enum class to avoid global namespace pollution)

You can use enum class. I just used enum because I'm more familiar with C ;)

@Lomadriel
Copy link
Member Author

Lomadriel commented Mar 18, 2019

Everything within the bar except for the modules themselves is quite static. Polybar cannot dynamically change its size or position when changes to the monitor occur. All of this is handled through screenchange-reload which just reloads polybar whenever the screen changes.

I didn't know it worked that way, but that's seems logical with the fact that some of my modules are reset when I change my resolution.

It is conceivable that we want to make things more dynamic in the future. But even then, I would not convert to pixels in the builder but rather extend the %{O} tag to also work with points and then let the renderer handle it. Either way seems fine to me, thinking about it, doing this in the renderer may even be a little cleaner. This is up to you how you want to do it, I just don't want it to happen somewhere in the middle where we potentially have to do the conversion in multiple places.

Maybe I can tried to do that to see if it is more clean. (Extends the %{O} tag)

The geometry_size struct could be defined in a similar way with an enum type like that:

enum class geometry_size_type { PERCENTAGE, POINT, PIXEL }

Wouldn't geometry_size_type only have point and pixel? As far as I know, percentages cannot be used as offsets.

Yeah my bad, it is just point and pixel.

@Lomadriel
Copy link
Member Author

By the way, do you have an idea for naming the enums and structs associated with that:

  • spacing:

    • POINT: float >= 0
    • PIXEL: ssize_t
    • SPACE: ssize_t
  • size:

    • POINT: float
    • PIXEL: size_t

Each time I found a name it is either bad or already taken (like size)

@patrick96
Copy link
Member

patrick96 commented Mar 20, 2019

Since we probably put these inside a namespace, naming is not such a big issue, but I think the following names are reasonable. Feel free to improve them.

enum class size_type { POINT, PIXEL }
enum class space_type { POINT, PIXEL, SPACE }

struct spacing {
  space_type type;
  ...
}

struct geometry {
  float percentage;
  size_type offset_type;
  ...
}

@Lomadriel
Copy link
Member Author

Lomadriel commented Mar 25, 2019

I just finished some modifications to take your review in account. Just as expected, there are two structures, one for spacing and the other for size.
The %{O} tag now supports units (%{O100px} and %{O100pt}).
There is also a commit that fixes the problem mentioned in #1700.

Copy link
Member

@patrick96 patrick96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally got around to reviewing this. Thanks for your patience ;)

Did some rough testing and things seem to work fine with actually emitting and parsing the offsets. Will do some more thorough testing once the other issues are resolved.

You also need to resolve the merge conflict that was likely caused by us removing icon_t in #1553.


The issue in #1700 is still not totally fixed. Offsets do produce spaces that have the proper background color, but the string I used in #1700 still does not look the same as with lemonbar:

%{O100}Filler%{B#ff0000 O100}First%{O200}Second%{O-100}

With this PR:

bg_after_fix

With lemonbar:

bg_lemonbar

You see, Second is not displayed with this PR:

@patrick96
Copy link
Member

It would probably be useful if we also allow the use of these units in format-padding, format-offset etc. Those are set in modules/meta/base.cpp

@Lomadriel
Copy link
Member Author

Lomadriel commented Apr 17, 2019

The issue in #1700 is still not totally fixed. Offsets do produce spaces that have the proper background color, but the string I used in #1700 still does not look the same as with lemonbar:

%{O100}Filler%{B#ff0000 O100}First%{O200}Second%{O-100}

With this PR:

bg_after_fix

With lemonbar:

bg_lemonbar

You see, Second is not displayed with this PR:

Fixed by fd7322b

@Lomadriel Lomadriel force-pushed the feat/units branch 2 times, most recently from b2ec470 to fd7322b Compare April 17, 2019 23:43
@Lomadriel
Copy link
Member Author

It would probably be useful if we also allow the use of these units in format-padding, format-offset etc. Those are set in modules/meta/base.cpp

If I am not wrong format-padding already support units.
ab19b3b adds unit support for format-offset

@Lomadriel
Copy link
Member Author

I just added a commit that fixes #1814.
I can't make a separate PR since I modified the renderer :/
Sorry

@patrick96
Copy link
Member

I think you should make a separate PR and resolve the merge conflict once one of the two is merged.

@Lomadriel
Copy link
Member Author

Lomadriel commented Jun 18, 2019

I think you should make a separate PR and resolve the merge conflict once one of the two is merged.

Commit removed. See my new PR #1817.

- add a size_with_unit struct
- add a geometry_format_values struct
- move dpi initialisation from renderer.cpp to bar.cpp
- add a string to size_with_unit converter
- add point support (with pt)
- add pixel support (with px)
Copy link
Member

@patrick96 patrick96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems it's taking me longer and longer to find time for these reviews. Hopefully the next time you won't have to wait so long.


I'm very happy with this 😃. I don't see any immediate defects and it does make the whole spacing and size handling more uniform. I mainly commented on small stuff and the naming of these new concepts.

The only thing we need to think about whether or not to allow negative values for spacing. Theoretically we can allow negative values for px and pt spacing but not for actual spaces. In this PR, as far as I can see, all negative values are disallowed (in order to fix #1265).
I think we should allow negative values for points and pixels and only throw an error for negative spaces.

After this we probably just need one last review and some more testing and then we can merge this.


Please also run clang-format on all the files you touched, I think you missed some:

clang-format -i $(git log --name-only --pretty=oneline --full-index 1caed747fd2991d67fee122e3fff1ba01b9dcdaf^..41381ee7038a18678ad88117971f53bbc6a3be47 | \grep -vE '^[0-9a-f]{40} ' | sort | uniq)

@frebib
Copy link
Contributor

frebib commented Feb 25, 2020

Can this be rebased against master please

@frebib
Copy link
Contributor

frebib commented Apr 7, 2020

I squashed and rebased this against master (I tried to rebase without squashing but kept screwing up the history, so figured I didn't need to keep it anyway just to build and run it)
frebib@7385b89

I rely on this fork to have polybar even remotely usable when changing DPI. Please consider reviving it @Lomadriel

@fabianschuiki
Copy link

Any way to help get this PR merged? E.g. implement requested code changes? I'd really love to see these units to improve HiDPI support 👍

@patrick96 patrick96 added this to the 3.6.0 milestone Nov 27, 2020
@frebib
Copy link
Contributor

frebib commented Jan 24, 2021

Until now I've been rebasing this PR onto master, but fd55652 was a big change that will not be a trivial change to rebase onto. I may or may not attempt to fix this up again, depending on how hard it seems to be

@patrick96
Copy link
Member

@frebib If you do attempt to resolve the conflicts and want to help get this merged, a PR would be very much appreciated :)

And of course, ask if you need help.

I think most conflicts should be quite straighforward (just tedious to get the changes from both sides in), but in the renderer and parser, it might be more difficult because there we actually changed the places where the offset handling happens.

@frebib
Copy link
Contributor

frebib commented Feb 22, 2021

@patrick96 I can certainly give it a go, but I'm not at all familiar with C++ or much of the syntax/language features.
If I were to rebase this, it'd be squashed into a single commit- hopefully that's ok.
There were a few unaddressed comments from what I remember about the implementation which I should probably address at the same time.
In use I also noticed that there are still some glaring issues with unit support particularly in the tray area. I managed to hack in support for most of it, but the padding between the icons is still not correctly scaled. Hopefully that shouldn't be too tricky to solve.

@patrick96
Copy link
Member

If I were to rebase this, it'd be squashed into a single commit- hopefully that's ok.

Yeah, that's fine. This would have happened with this PR anyway, I think.

I can certainly give it a go, but I'm not at all familiar with C++ or much of the syntax/language features.

In that case, this might get tricky. Let me know, if you need any help.

In use I also noticed that there are still some glaring issues with unit support particularly in the tray area. I managed to hack in support for most of it, but the padding between the icons is still not correctly scaled. Hopefully that shouldn't be too tricky to solve.

Yeah, I don't think this PR touches the tray at all. Feel free to leave out units in the tray if that's easier. First priority should be to get the overall framework working.

@frebib
Copy link
Contributor

frebib commented Feb 28, 2021

I gave it a go. Seems to compile & run the same as the old builds did for me.
See #2394

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

4 participants