From ce93188a4ace3c9eab73ff536533f181abbe5e5c Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Sun, 20 Feb 2022 21:08:57 +0100 Subject: [PATCH] Add units support (POINT, PIXEL, SPACE) (#2578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add units support (POINT, PIXEL, SPACE) for polybar - 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) * Fix unit test compilation * clang-format * Better names The old names didn't really capture the purpose of the structs and function. space_type -> spacing_type space_size -> spacing_val size_type -> extent_type geometry -> extent_val geometry_format_values -> percentage_with_offset * Remove parse_size_with_unit No longer needed. The convert function in config.cpp already does all the work for us and always setting the type to pixel was wrong. In addition, line-size should not be of type spacing_val but extent_val. * Cleanup I tried to address most of my comments on the old PR * Fix renderer width calculation We can't just blindly add the x difference to the width because for example the width should increase if x < width and the increase keeps x < width. Similarly, we can't just add the offset to the width. * Rename geom_format_to_pixels to percentage_with_offset_to_pixel * Cleanup * Apply suggested changes from Patrick on GitHub Co-authored-by: Patrick Ziegler * Update src/components/bar.cpp Co-authored-by: Patrick Ziegler * Update src/components/config.cpp Co-authored-by: Patrick Ziegler * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler * config: Use stod for parsing percentage * Use stof instead of strtof * units: Fix test edge cases * Remove unnecessary clang-format toggle * Use percentage_with_offset for margin-{top,bottom} * Support negative extent values * Rename unit to units and create a cpp file * Move percentage_with_offset_to_pixel unit test to units * Add unit tests for units_utils * Clarify when and how negative spacing/extent is allowed Negative spacing is never allowed and produces a config error. Extents allow negative values in theory, but only a few use-cases accept it. Only the extent value used for the `%{O}` tag and the offset value in percentage_with_offset can be negative. Everything else is capped below at 0. The final pixel value of percentage_with_offset also caps below at 0. * Fix parsing errors not being caught in config * Print a proper error message for uncaught exceptions * Cleanup module::get_output All changes preserve the existing semantics * Stop using remove_trailing_space in module::get_output Instead, we first check if the current tag is built, and only if it is, the spacing is prepended. * Remove unused imports * Restore old behavior If there are two tags and the second one isn't built (module::build returns false), the space in between them is removed. For example in the mpd module: format-online = foo If mpd is not running, the mpd module will return false when trying to build the `` tag. If we don't remove the space between `` and ``, we end up with two spaces between `` and `foo`. This change is to match the old behavior where at least one trailing space character was removed from the builder. * Add changelog entry * Remove unused setting * Use percentage with offset for tray-offset Co-authored-by: Jérôme BOULMIER Co-authored-by: Joe Groocock --- .clang-format | 1 + CHANGELOG.md | 5 +- include/components/bar.hpp | 24 ---- include/components/builder.hpp | 15 ++- include/components/config.hpp | 4 +- include/components/controller.hpp | 3 +- include/components/renderer.hpp | 15 ++- include/components/renderer_interface.hpp | 2 +- include/components/types.hpp | 61 ++++++++- include/drawtypes/label.hpp | 24 ++-- include/modules/cpu.hpp | 3 +- include/modules/fs.hpp | 2 +- include/modules/meta/base.hpp | 12 +- include/modules/meta/base.inl | 117 +++++++++++------ include/tags/parser.hpp | 2 +- include/tags/types.hpp | 30 ++--- include/utils/color.hpp | 2 + include/utils/units.hpp | 31 +++++ src/CMakeLists.txt | 1 + src/components/bar.cpp | 82 +++++++++--- src/components/builder.cpp | 96 ++++++++------ src/components/config.cpp | 35 ++++++ src/components/controller.cpp | 8 +- src/components/renderer.cpp | 82 ++++++------ src/drawtypes/label.cpp | 43 ++++--- src/main.cpp | 2 +- src/modules/bspwm.cpp | 2 +- src/modules/cpu.cpp | 17 ++- src/modules/fs.cpp | 2 +- src/modules/menu.cpp | 8 +- src/modules/meta/base.cpp | 23 ++-- src/modules/xkeyboard.cpp | 2 +- src/modules/xworkspaces.cpp | 2 +- src/tags/parser.cpp | 18 +-- src/utils/color.cpp | 8 ++ src/utils/units.cpp | 137 ++++++++++++++++++++ src/x11/tray_manager.cpp | 42 +++---- tests/CMakeLists.txt | 2 +- tests/unit_tests/components/bar.cpp | 48 ------- tests/unit_tests/tags/dispatch.cpp | 12 +- tests/unit_tests/tags/parser.cpp | 47 ++++++- tests/unit_tests/utils/units.cpp | 146 ++++++++++++++++++++++ 42 files changed, 860 insertions(+), 358 deletions(-) create mode 100644 include/utils/units.hpp create mode 100644 src/utils/units.cpp delete mode 100644 tests/unit_tests/components/bar.cpp create mode 100644 tests/unit_tests/utils/units.cpp diff --git a/.clang-format b/.clang-format index 3caddce4..4d561f89 100644 --- a/.clang-format +++ b/.clang-format @@ -10,4 +10,5 @@ AllowShortIfStatementsOnASingleLine: false BreakConstructorInitializersBeforeComma: true DerivePointerAlignment: false PointerAlignment: Left +SpacesBeforeTrailingComments: 1 --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b789c10..1e28cdc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DEBUG_SHADED` cmake variable and its associated functionality. ### Added +- Support `px` and `pt` units everyhwere where before only a number of spaces + or pixels could be specified. + ([`#2578`](https://github.com/polybar/polybar/pull/2578)) - Right and middle click events for alsa module. ([`#2566`](https://github.com/polybar/polybar/issues/2566)) - `internal/network`: New token `%mac%` shows MAC address of selected interface @@ -111,7 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `internal/memory`: `format-warn`, `label-warn`, `warn-percentage = 90` - `radius` now affects the bar border as well ([`#1566`](https://github.com/polybar/polybar/issues/1566)) -- Per-corner corner radius with `radius-{bottom,top}-{left,right}` +- Per-corner radius with `radius-{bottom,top}-{left,right}` ([`#2294`](https://github.com/polybar/polybar/issues/2294)) - `internal/network`: `speed-unit = B/s` can be used to customize how network speeds are displayed. diff --git a/include/components/bar.hpp b/include/components/bar.hpp index 83ded49b..43a444dc 100644 --- a/include/components/bar.hpp +++ b/include/components/bar.hpp @@ -1,8 +1,6 @@ #pragma once -#include #include -#include #include "common.hpp" #include "components/eventloop.hpp" @@ -31,28 +29,6 @@ namespace tags { } // }}} -/** - * Allows a new format for pixel sizes (like width in the bar section) - * - * The new format is X%:Z, where X is in [0, 100], and Z is any real value - * describing a pixel offset. The actual value is calculated by X% * max + Z - */ -inline double geom_format_to_pixels(std::string str, double max) { - size_t i; - if ((i = str.find(':')) != std::string::npos) { - std::string a = str.substr(0, i - 1); - std::string b = str.substr(i + 1); - return std::max( - 0, math_util::percentage_to_value(strtod(a.c_str(), nullptr), max) + strtod(b.c_str(), nullptr)); - } else { - if (str.find('%') != std::string::npos) { - return math_util::percentage_to_value(strtod(str.c_str(), nullptr), max); - } else { - return strtod(str.c_str(), nullptr); - } - } -} - class bar : public xpp::event::sink, public signal_receiver(move(section), move(key), move(string_value), move(result)); } catch (const key_error& err) { return default_value; - } catch (const value_error& err) { + } catch (const std::exception& err) { m_log.err("Invalid value for \"%s.%s\", using default value (reason: %s)", section, key, err.what()); return default_value; } @@ -196,7 +196,7 @@ class config { } } catch (const key_error& err) { break; - } catch (const value_error& err) { + } catch (const std::exception& err) { m_log.err("Invalid value in list \"%s.%s\", using list as-is (reason: %s)", section, key, err.what()); return default_value; } diff --git a/include/components/controller.hpp b/include/components/controller.hpp index ef2d28e9..7c64ae10 100644 --- a/include/components/controller.hpp +++ b/include/components/controller.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -29,7 +28,7 @@ class logger; class signal_emitter; namespace modules { struct module_interface; -} // namespace modules +} // namespace modules using module_t = shared_ptr; using modulemap_t = std::map>; // }}} diff --git a/include/components/renderer.hpp b/include/components/renderer.hpp index a6fd0b7a..5dfc998a 100644 --- a/include/components/renderer.hpp +++ b/include/components/renderer.hpp @@ -28,8 +28,18 @@ using std::map; struct alignment_block { cairo_pattern_t* pattern; + /** + * The x-position where the next thing will be rendered. + */ double x; double y; + /** + * The total width of this block. + * + * This is always >= x, but may be larger because a negative offset may + * decrease x, but the width doesn't change. + */ + double width; }; class renderer : public renderer_interface, @@ -48,7 +58,7 @@ class renderer : public renderer_interface, void end(); void flush(); - void render_offset(const tags::context& ctxt, int pixels) override; + void render_offset(const tags::context& ctxt, const extent_val offset) override; void render_text(const tags::context& ctxt, const string&&) override; void change_alignment(const tags::context& ctxt) override; @@ -62,12 +72,15 @@ class renderer : public renderer_interface, void fill_overline(rgba color, double x, double w); void fill_underline(rgba color, double x, double w); void fill_borders(); + void draw_offset(rgba color, double x, double w); double block_x(alignment a) const; double block_y(alignment a) const; double block_w(alignment a) const; double block_h(alignment a) const; + void increase_x(double dx); + void flush(alignment a); void highlight_clickable_areas(); diff --git a/include/components/renderer_interface.hpp b/include/components/renderer_interface.hpp index 84189851..77c718ff 100644 --- a/include/components/renderer_interface.hpp +++ b/include/components/renderer_interface.hpp @@ -10,7 +10,7 @@ class renderer_interface { public: renderer_interface(const tags::action_context& action_ctxt) : m_action_ctxt(action_ctxt){}; - virtual void render_offset(const tags::context& ctxt, int pixels) = 0; + virtual void render_offset(const tags::context& ctxt, const extent_val offset) = 0; virtual void render_text(const tags::context& ctxt, const string&& str) = 0; virtual void change_alignment(const tags::context& ctxt) = 0; diff --git a/include/components/types.hpp b/include/components/types.hpp index 01c9d72a..69fe0ace 100644 --- a/include/components/types.hpp +++ b/include/components/types.hpp @@ -84,9 +84,52 @@ struct size { unsigned int h{1U}; }; +enum class spacing_type { SPACE, POINT, PIXEL }; + +enum class extent_type { POINT, PIXEL }; + +struct spacing_val { + spacing_type type{spacing_type::SPACE}; + /** + * Numerical spacing value. Is truncated to an integer for pixels and spaces. + * Must be non-negative. + */ + float value{0}; + + /** + * Any non-positive number is interpreted as no spacing. + */ + operator bool() const { + return value > 0; + } +}; + +static constexpr spacing_val ZERO_SPACE = {spacing_type::SPACE, 0}; + +/* + * Defines the signed length of something as either a number of pixels or points. + * + * Used for widths, heights, and offsets + */ +struct extent_val { + extent_type type{extent_type::PIXEL}; + float value{0}; + + operator bool() const { + return value != 0; + } +}; + +static constexpr extent_val ZERO_PX_EXTENT = {extent_type::PIXEL, 0}; + struct side_values { - unsigned int left{0U}; - unsigned int right{0U}; + spacing_val left{ZERO_SPACE}; + spacing_val right{ZERO_SPACE}; +}; + +struct percentage_with_offset { + double percentage{0}; + extent_val offset{ZERO_PX_EXTENT}; }; struct edge_values { @@ -134,11 +177,14 @@ struct bar_settings { struct size size { 1U, 1U }; + + double dpi_x{0.}; + double dpi_y{0.}; + position pos{0, 0}; position offset{0, 0}; - side_values padding{0U, 0U}; - side_values margin{0U, 0U}; - side_values module_margin{0U, 0U}; + side_values padding{ZERO_SPACE, ZERO_SPACE}; + side_values module_margin{ZERO_SPACE, ZERO_SPACE}; edge_values strut{0U, 0U, 0U, 0U}; rgba background{0xFF000000}; @@ -151,7 +197,10 @@ struct bar_settings { std::unordered_map borders{}; struct radius radius {}; - int spacing{0}; + /** + * TODO deprecated + */ + spacing_val spacing{ZERO_SPACE}; label_t separator{}; string wmname{}; diff --git a/include/drawtypes/label.hpp b/include/drawtypes/label.hpp index b20b6bca..3e178fdf 100644 --- a/include/drawtypes/label.hpp +++ b/include/drawtypes/label.hpp @@ -25,8 +25,8 @@ namespace drawtypes { rgba m_underline{}; rgba m_overline{}; int m_font{0}; - side_values m_padding{0U, 0U}; - side_values m_margin{0U, 0U}; + side_values m_padding{ZERO_SPACE, ZERO_SPACE}; + side_values m_margin{ZERO_SPACE, ZERO_SPACE}; size_t m_minlen{0}; /* @@ -40,15 +40,15 @@ namespace drawtypes { alignment m_alignment{alignment::LEFT}; bool m_ellipsis{true}; - explicit label(string text, int font) : m_font(font), m_text(text), m_tokenized(m_text) {} + explicit label(string text, int font) : m_font(font), m_text(move(text)), m_tokenized(m_text) {} explicit label(string text, rgba foreground = rgba{}, rgba background = rgba{}, rgba underline = rgba{}, - rgba overline = rgba{}, int font = 0, struct side_values padding = {0U, 0U}, - struct side_values margin = {0U, 0U}, int minlen = 0, size_t maxlen = 0_z, + rgba overline = rgba{}, int font = 0, side_values padding = {ZERO_SPACE, ZERO_SPACE}, + side_values margin = {ZERO_SPACE, ZERO_SPACE}, int minlen = 0, size_t maxlen = 0_z, alignment label_alignment = alignment::LEFT, bool ellipsis = true, vector&& tokens = {}) - : m_foreground(foreground) - , m_background(background) - , m_underline(underline) - , m_overline(overline) + : m_foreground(move(foreground)) + , m_background(move(background)) + , m_underline(move(underline)) + , m_overline(move(overline)) , m_font(font) , m_padding(padding) , m_margin(margin) @@ -56,14 +56,14 @@ namespace drawtypes { , m_maxlen(maxlen) , m_alignment(label_alignment) , m_ellipsis(ellipsis) - , m_text(text) + , m_text(move(text)) , m_tokenized(m_text) , m_tokens(forward>(tokens)) { assert(!m_ellipsis || (m_maxlen == 0 || m_maxlen >= 3)); } string get() const; - operator bool(); + explicit operator bool(); label_t clone(); void clear(); void reset_tokens(); @@ -81,6 +81,6 @@ namespace drawtypes { label_t load_label(const config& conf, const string& section, string name, bool required = true, string def = ""s); label_t load_optional_label(const config& conf, string section, string name, string def = ""s); -} // namespace drawtypes +} // namespace drawtypes POLYBAR_NS_END diff --git a/include/modules/cpu.hpp b/include/modules/cpu.hpp index 26d7e165..c8cbc75e 100644 --- a/include/modules/cpu.hpp +++ b/include/modules/cpu.hpp @@ -40,13 +40,12 @@ namespace modules { static constexpr auto TAG_RAMP_LOAD_PER_CORE = ""; static constexpr auto FORMAT_WARN = "format-warn"; - label_t m_label; label_t m_labelwarn; progressbar_t m_barload; ramp_t m_rampload; ramp_t m_rampload_core; - int m_ramp_padding; + spacing_val m_ramp_padding{spacing_type::SPACE, 1U}; vector m_cputimes; vector m_cputimes_prev; diff --git a/include/modules/fs.hpp b/include/modules/fs.hpp index b43d4ea4..2de51340 100644 --- a/include/modules/fs.hpp +++ b/include/modules/fs.hpp @@ -68,7 +68,7 @@ namespace modules { vector m_mounts; bool m_fixed{false}; bool m_remove_unmounted{false}; - int m_spacing{2}; + spacing_val m_spacing{spacing_type::SPACE, 2U}; int m_perc_used_warn{90}; // used while formatting output diff --git a/include/modules/meta/base.hpp b/include/modules/meta/base.hpp index 8264a96b..62e00df9 100644 --- a/include/modules/meta/base.hpp +++ b/include/modules/meta/base.hpp @@ -36,7 +36,7 @@ namespace drawtypes { using animation_t = shared_ptr; class iconset; using iconset_t = shared_ptr; -} // namespace drawtypes +} // namespace drawtypes class builder; class config; @@ -67,10 +67,10 @@ namespace modules { rgba ol{}; size_t ulsize{0}; size_t olsize{0}; - size_t spacing{0}; - size_t padding{0}; - size_t margin{0}; - int offset{0}; + spacing_val spacing{ZERO_SPACE}; + spacing_val padding{ZERO_SPACE}; + spacing_val margin{ZERO_SPACE}; + extent_val offset{ZERO_PX_EXTENT}; int font{0}; string decorate(builder* builder, string output); @@ -225,6 +225,6 @@ namespace modules { }; // }}} -} // namespace modules +} // namespace modules POLYBAR_NS_END diff --git a/include/modules/meta/base.inl b/include/modules/meta/base.inl index 0d3bd82c..1f4378bc 100644 --- a/include/modules/meta/base.inl +++ b/include/modules/meta/base.inl @@ -197,48 +197,89 @@ namespace modules { std::lock_guard guard(m_buildlock); auto format_name = CONST_MOD(Impl).get_format(); auto format = m_formatter->get(format_name); - bool no_tag_built{true}; - bool fake_no_tag_built{false}; - bool tag_built{false}; - auto mingap = std::max(1_z, format->spacing); - size_t start, end; - string value{format->value}; - while ((start = value.find('<')) != string::npos && (end = value.find('>', start)) != string::npos) { - if (start > 0) { - if (no_tag_built) { - // If no module tag has been built we do not want to add - // whitespace defined between the format tags, but we do still - // want to output other non-tag content - auto trimmed = string_util::ltrim(value.substr(0, start), ' '); - if (!trimmed.empty()) { - fake_no_tag_built = false; - m_builder->node(move(trimmed)); - } - } else { - m_builder->node(value.substr(0, start)); + + /* + * Builder for building individual tags isolated, so that we can + */ + builder tag_builder(m_bar); + + // Whether any tags have been processed yet + bool has_tags = false; + + // Cursor pointing into 'value' + size_t cursor = 0; + const string& value{format->value}; + + /* + * Search for all tags in the format definition. A tag is enclosed in '<' and '>'. + * Each tag is given to the module to produce some output for it. All other text is added as-is. + */ + while (cursor < value.size()) { + // Check if there are any tags left + + // Start index of next tag + size_t start = value.find('<', cursor); + + if (start == string::npos) { + break; + } + + // End index (inclusive) of next tag + size_t end = value.find('>', start + 1); + + if (end == string::npos) { + break; + } + + // Potential regular text that appears before the tag. + string non_tag; + + // There is some non-tag text + if (start > cursor) { + /* + * Produce anything between the previous and current tag as regular text. + */ + non_tag = value.substr(cursor, start - cursor); + if (!has_tags) { + /* + * If no module tag has been built we do not want to add + * whitespace defined between the format tags, but we do still + * want to output other non-tag content + */ + non_tag = string_util::ltrim(move(non_tag), ' '); } - value.erase(0, start); - end -= start; - start = 0; } - string tag{value.substr(start, end + 1)}; - if (tag.empty()) { - continue; - } else if (tag[0] == '<' && tag[tag.size() - 1] == '>') { - if (!no_tag_built) - m_builder->space(format->spacing); - else if (fake_no_tag_built) - no_tag_built = false; - if (!(tag_built = CONST_MOD(Impl).build(m_builder.get(), tag)) && !no_tag_built) - m_builder->remove_trailing_space(mingap); - if (tag_built) - no_tag_built = false; + + string tag = value.substr(start, end - start + 1); + bool tag_built = CONST_MOD(Impl).build(&tag_builder, tag); + string tag_content = tag_builder.flush(); + + /* + * Remove exactly one space between two tags if the second tag was not built. + */ + if (!tag_built && has_tags && !format->spacing) { + if (!non_tag.empty() && non_tag.back() == ' ') { + non_tag.erase(non_tag.size() - 1); + } } - value.erase(0, tag.size()); + + m_builder->node(non_tag); + + if (tag_built) { + if (has_tags) { + // format-spacing is added between all tags + m_builder->spacing(format->spacing); + } + + m_builder->append(tag_content); + has_tags = true; + } + + cursor = end + 1; } - if (!value.empty()) { - m_builder->append(value); + if (cursor < value.size()) { + m_builder->append(value.substr(cursor)); } return format->decorate(&*m_builder, m_builder->flush()); @@ -267,6 +308,6 @@ namespace modules { } // }}} -} // namespace modules +} // namespace modules POLYBAR_NS_END diff --git a/include/tags/parser.hpp b/include/tags/parser.hpp index e2d5229d..ab86394c 100644 --- a/include/tags/parser.hpp +++ b/include/tags/parser.hpp @@ -126,7 +126,7 @@ namespace tags { color_value parse_color(); int parse_fontindex(); - int parse_offset(); + extent_val parse_offset(); controltag parse_control(); std::pair parse_action(); mousebtn parse_action_btn(); diff --git a/include/tags/types.hpp b/include/tags/types.hpp index 151ab90b..7ac2ee09 100644 --- a/include/tags/types.hpp +++ b/include/tags/types.hpp @@ -13,18 +13,18 @@ namespace tags { enum class attr_activation { NONE, ON, OFF, TOGGLE }; enum class syntaxtag { - A, // mouse action - B, // background color - F, // foreground color - T, // font index - O, // pixel offset - R, // flip colors - o, // overline color - u, // underline color - P, // Polybar control tag - l, // Left alignment - r, // Right alignment - c, // Center alignment + A, // mouse action + B, // background color + F, // foreground color + T, // font index + O, // pixel offset + R, // flip colors + o, // overline color + u, // underline color + P, // Polybar control tag + l, // Left alignment + r, // Right alignment + c, // Center alignment }; /** @@ -35,7 +35,7 @@ namespace tags { */ enum class controltag { NONE = 0, - R, // Reset all open tags (B, F, T, o, u). Used at module edges + R, // Reset all open tags (B, F, T, o, u). Used at module edges }; enum class color_type { RESET = 0, COLOR }; @@ -89,7 +89,7 @@ namespace tags { /** * For for 'O' tags */ - int offset; + extent_val offset; /** * For for 'P' tags */ @@ -113,6 +113,6 @@ namespace tags { using format_string = vector; -} // namespace tags +} // namespace tags POLYBAR_NS_END diff --git a/include/utils/color.hpp b/include/utils/color.hpp index c27e679e..3961876f 100644 --- a/include/utils/color.hpp +++ b/include/utils/color.hpp @@ -19,7 +19,9 @@ class rgba { operator string() const; operator uint32_t() const; + operator bool() const; bool operator==(const rgba& other) const; + bool operator!=(const rgba& other) const; uint32_t value() const; type get_type() const; diff --git a/include/utils/units.hpp b/include/utils/units.hpp new file mode 100644 index 00000000..9066ed42 --- /dev/null +++ b/include/utils/units.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +#include "components/types.hpp" +#include "utils/string.hpp" + +POLYBAR_NS + +namespace units_utils { + int point_to_pixel(double point, double dpi); + + int extent_to_pixel(const extent_val size, double dpi); + unsigned extent_to_pixel_nonnegative(const extent_val size, double dpi); + + extent_type parse_extent_unit(const string& str); + extent_val parse_extent(const string& str); + + string extent_to_string(extent_val extent); + + unsigned percentage_with_offset_to_pixel(percentage_with_offset g_format, double max, double dpi); + + spacing_type parse_spacing_unit(const string& str); + spacing_val parse_spacing(const string& str); + +} // namespace units_utils + +POLYBAR_NS_END diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1be92e3e..1037f661 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -121,6 +121,7 @@ set(POLY_SOURCES ${src_dir}/utils/process.cpp ${src_dir}/utils/socket.cpp ${src_dir}/utils/string.cpp + ${src_dir}/utils/units.cpp ${src_dir}/x11/atoms.cpp ${src_dir}/x11/background_manager.cpp diff --git a/src/components/bar.cpp b/src/components/bar.cpp index 6a8b8f15..1fe4f904 100644 --- a/src/components/bar.cpp +++ b/src/components/bar.cpp @@ -14,6 +14,7 @@ #include "utils/color.hpp" #include "utils/math.hpp" #include "utils/string.hpp" +#include "utils/units.hpp" #include "x11/atoms.hpp" #include "x11/connection.hpp" #include "x11/ewmh.hpp" @@ -157,7 +158,39 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.wmname = m_conf.get(bs, "wm-name", "polybar-" + bs.substr(4) + "_" + m_opts.monitor->name); m_opts.wmname = string_util::replace(m_opts.wmname, " ", "-"); + // Configure DPI + { + double dpi_x = 96, dpi_y = 96; + if (m_conf.has(m_conf.section(), "dpi")) { + dpi_x = dpi_y = m_conf.get("dpi"); + } else { + if (m_conf.has(m_conf.section(), "dpi-x")) { + dpi_x = m_conf.get("dpi-x"); + } + if (m_conf.has(m_conf.section(), "dpi-y")) { + dpi_y = m_conf.get("dpi-y"); + } + } + + // dpi to be computed + if (dpi_x <= 0 || dpi_y <= 0) { + auto screen = m_connection.screen(); + if (dpi_x <= 0) { + dpi_x = screen->width_in_pixels * 25.4 / screen->width_in_millimeters; + } + if (dpi_y <= 0) { + dpi_y = screen->height_in_pixels * 25.4 / screen->height_in_millimeters; + } + } + + m_opts.dpi_x = dpi_x; + m_opts.dpi_y = dpi_y; + + m_log.info("Configured DPI = %gx%g", dpi_x, dpi_y); + } + // Load configuration values + m_opts.origin = m_conf.get(bs, "bottom", false) ? edge::BOTTOM : edge::TOP; m_opts.spacing = m_conf.get(bs, "spacing", m_opts.spacing); m_opts.separator = drawtypes::load_optional_label(m_conf, bs, "separator", ""); @@ -171,11 +204,11 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.radius.bottom_left = m_conf.get(bs, "radius-bottom-left", bottom); m_opts.radius.bottom_right = m_conf.get(bs, "radius-bottom-right", bottom); - auto padding = m_conf.get(bs, "padding", 0U); + auto padding = m_conf.get(bs, "padding", ZERO_SPACE); m_opts.padding.left = m_conf.get(bs, "padding-left", padding); m_opts.padding.right = m_conf.get(bs, "padding-right", padding); - auto margin = m_conf.get(bs, "module-margin", 0U); + auto margin = m_conf.get(bs, "module-margin", ZERO_SPACE); m_opts.module_margin.left = m_conf.get(bs, "module-margin-left", margin); m_opts.module_margin.right = m_conf.get(bs, "module-margin-right", margin); @@ -186,8 +219,10 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const } // Load values used to adjust the struts atom - m_opts.strut.top = m_conf.get("global/wm", "margin-top", 0); - m_opts.strut.bottom = m_conf.get("global/wm", "margin-bottom", 0); + auto margin_top = m_conf.get("global/wm", "margin-top", percentage_with_offset{}); + auto margin_bottom = m_conf.get("global/wm", "margin-bottom", percentage_with_offset{}); + m_opts.strut.top = units_utils::percentage_with_offset_to_pixel(margin_top, m_opts.monitor->h, m_opts.dpi_y); + m_opts.strut.bottom = units_utils::percentage_with_offset_to_pixel(margin_bottom, m_opts.monitor->h, m_opts.dpi_y); // Load commands used for fallback click handlers vector actions; @@ -240,44 +275,51 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const // Load over-/underline auto line_color = m_conf.get(bs, "line-color", rgba{0xFFFF0000}); - auto line_size = m_conf.get(bs, "line-size", 0); + auto line_size = m_conf.get(bs, "line-size", ZERO_PX_EXTENT); - m_opts.overline.size = m_conf.get(bs, "overline-size", line_size); + auto overline_size = m_conf.get(bs, "overline-size", line_size); + auto underline_size = m_conf.get(bs, "underline-size", line_size); + + m_opts.overline.size = units_utils::extent_to_pixel_nonnegative(overline_size, m_opts.dpi_y); m_opts.overline.color = parse_or_throw_color("overline-color", line_color); - m_opts.underline.size = m_conf.get(bs, "underline-size", line_size); + m_opts.underline.size = units_utils::extent_to_pixel_nonnegative(underline_size, m_opts.dpi_y); m_opts.underline.color = parse_or_throw_color("underline-color", line_color); // Load border settings auto border_color = m_conf.get(bs, "border-color", rgba{0x00000000}); - auto border_size = m_conf.get(bs, "border-size", ""s); + auto border_size = m_conf.get(bs, "border-size", percentage_with_offset{}); auto border_top = m_conf.deprecated(bs, "border-top", "border-top-size", border_size); auto border_bottom = m_conf.deprecated(bs, "border-bottom", "border-bottom-size", border_size); auto border_left = m_conf.deprecated(bs, "border-left", "border-left-size", border_size); auto border_right = m_conf.deprecated(bs, "border-right", "border-right-size", border_size); m_opts.borders.emplace(edge::TOP, border_settings{}); - m_opts.borders[edge::TOP].size = geom_format_to_pixels(border_top, m_opts.monitor->h); + m_opts.borders[edge::TOP].size = + units_utils::percentage_with_offset_to_pixel(border_top, m_opts.monitor->h, m_opts.dpi_y); m_opts.borders[edge::TOP].color = parse_or_throw_color("border-top-color", border_color); m_opts.borders.emplace(edge::BOTTOM, border_settings{}); - m_opts.borders[edge::BOTTOM].size = geom_format_to_pixels(border_bottom, m_opts.monitor->h); + m_opts.borders[edge::BOTTOM].size = + units_utils::percentage_with_offset_to_pixel(border_bottom, m_opts.monitor->h, m_opts.dpi_y); m_opts.borders[edge::BOTTOM].color = parse_or_throw_color("border-bottom-color", border_color); m_opts.borders.emplace(edge::LEFT, border_settings{}); - m_opts.borders[edge::LEFT].size = geom_format_to_pixels(border_left, m_opts.monitor->w); + m_opts.borders[edge::LEFT].size = + units_utils::percentage_with_offset_to_pixel(border_left, m_opts.monitor->w, m_opts.dpi_x); m_opts.borders[edge::LEFT].color = parse_or_throw_color("border-left-color", border_color); m_opts.borders.emplace(edge::RIGHT, border_settings{}); - m_opts.borders[edge::RIGHT].size = geom_format_to_pixels(border_right, m_opts.monitor->w); + m_opts.borders[edge::RIGHT].size = + units_utils::percentage_with_offset_to_pixel(border_right, m_opts.monitor->w, m_opts.dpi_x); m_opts.borders[edge::RIGHT].color = parse_or_throw_color("border-right-color", border_color); // Load geometry values - auto w = m_conf.get(m_conf.section(), "width", "100%"s); - auto h = m_conf.get(m_conf.section(), "height", "24"s); - auto offsetx = m_conf.get(m_conf.section(), "offset-x", ""s); - auto offsety = m_conf.get(m_conf.section(), "offset-y", ""s); + auto w = m_conf.get(m_conf.section(), "width", percentage_with_offset{100.}); + auto h = m_conf.get(m_conf.section(), "height", percentage_with_offset{0., {extent_type::PIXEL, 24}}); + auto offsetx = m_conf.get(m_conf.section(), "offset-x", percentage_with_offset{}); + auto offsety = m_conf.get(m_conf.section(), "offset-y", percentage_with_offset{}); - m_opts.size.w = geom_format_to_pixels(w, m_opts.monitor->w); - m_opts.size.h = geom_format_to_pixels(h, m_opts.monitor->h); - m_opts.offset.x = geom_format_to_pixels(offsetx, m_opts.monitor->w); - m_opts.offset.y = geom_format_to_pixels(offsety, m_opts.monitor->h); + m_opts.size.w = units_utils::percentage_with_offset_to_pixel(w, m_opts.monitor->w, m_opts.dpi_x); + m_opts.size.h = units_utils::percentage_with_offset_to_pixel(h, m_opts.monitor->h, m_opts.dpi_y); + m_opts.offset.x = units_utils::percentage_with_offset_to_pixel(offsetx, m_opts.monitor->w, m_opts.dpi_x); + m_opts.offset.y = units_utils::percentage_with_offset_to_pixel(offsety, m_opts.monitor->h, m_opts.dpi_y); // Apply offsets m_opts.pos.x = m_opts.offset.x + m_opts.monitor->x; diff --git a/src/components/builder.cpp b/src/components/builder.cpp index 954a15ba..ff313255 100644 --- a/src/components/builder.cpp +++ b/src/components/builder.cpp @@ -7,6 +7,8 @@ #include "utils/color.hpp" #include "utils/string.hpp" #include "utils/time.hpp" +#include "utils/units.hpp" + POLYBAR_NS using namespace tags; @@ -86,9 +88,9 @@ string builder::flush() { /** * Insert raw text string */ -void builder::append(string text) { +void builder::append(const string& text) { m_output.reserve(text.size()); - m_output += move(text); + m_output += text; } /** @@ -96,12 +98,12 @@ void builder::append(string text) { * * This will also parse raw syntax tags */ -void builder::node(string str) { +void builder::node(const string& str) { if (str.empty()) { return; } - append(move(str)); + append(str); } /** @@ -109,9 +111,9 @@ void builder::node(string str) { * * \see builder::node */ -void builder::node(string str, int font_index) { +void builder::node(const string& str, int font_index) { font(font_index); - node(move(str)); + node(str); font_close(); } @@ -125,8 +127,8 @@ void builder::node(const label_t& label) { auto text = label->get(); - if (label->m_margin.left > 0) { - space(label->m_margin.left); + if (label->m_margin.left) { + spacing(label->m_margin.left); } if (label->m_overline.has_color()) { @@ -143,14 +145,14 @@ void builder::node(const label_t& label) { color(label->m_foreground); } - if (label->m_padding.left > 0) { - space(label->m_padding.left); + if (label->m_padding.left) { + spacing(label->m_padding.left); } node(text, label->m_font); - if (label->m_padding.right > 0) { - space(label->m_padding.right); + if (label->m_padding.right) { + spacing(label->m_padding.right); } if (label->m_background.has_color()) { @@ -167,8 +169,8 @@ void builder::node(const label_t& label) { overline_close(); } - if (label->m_margin.right > 0) { - space(label->m_margin.right); + if (label->m_margin.right) { + spacing(label->m_margin.right); } } @@ -200,42 +202,29 @@ void builder::node_repeat(const label_t& label, size_t n) { } /** - * Insert tag that will offset the contents by given pixels + * Insert tag that will offset the contents by the given extent */ -void builder::offset(int pixels) { - if (pixels == 0) { +void builder::offset(extent_val extent) { + if (extent) { return; } - tag_open(syntaxtag::O, to_string(pixels)); + tag_open(syntaxtag::O, units_utils::extent_to_string(extent)); } /** - * Insert spaces + * Insert spacing */ -void builder::space(size_t width) { - if (width) { - m_output.append(width, ' '); - } else { - space(); +void builder::spacing(spacing_val size) { + if (!size && m_bar.spacing) { + // TODO remove once the deprecated spacing key in the bar section is removed + // The spacing in the bar section acts as a fallback for all spacing value + size = m_bar.spacing; } -} -void builder::space() { - m_output.append(m_bar.spacing, ' '); -} -/** - * Remove trailing space - */ -void builder::remove_trailing_space(size_t len) { - if (len == 0_z || len > m_output.size()) { - return; - } else if (m_output.substr(m_output.size() - len) == string(len, ' ')) { - m_output.erase(m_output.size() - len); + if (size) { + m_output += get_spacing_format_string(size); } } -void builder::remove_trailing_space() { - remove_trailing_space(m_bar.spacing); -} /** * Insert tag to alter the current font index @@ -574,4 +563,33 @@ void builder::tag_close(attribute attr) { } } +string builder::get_spacing_format_string(const spacing_val& space) { + float value = space.value; + if (value == 0) { + return ""; + } + + string out; + if (space.type == spacing_type::SPACE) { + out += string(value, ' '); + } else { + out += "%{O"; + + switch (space.type) { + case spacing_type::POINT: + out += to_string(value) + "pt"; + break; + case spacing_type::PIXEL: + out += to_string(static_cast(value)) + "px"; + break; + default: + break; + } + + out += '}'; + } + + return out; +} + POLYBAR_NS_END diff --git a/src/components/config.cpp b/src/components/config.cpp index d72819f1..de30204f 100644 --- a/src/components/config.cpp +++ b/src/components/config.cpp @@ -1,13 +1,16 @@ #include "components/config.hpp" #include +#include #include #include "cairo/utils.hpp" +#include "components/types.hpp" #include "utils/color.hpp" #include "utils/env.hpp" #include "utils/factory.hpp" #include "utils/string.hpp" +#include "utils/units.hpp" POLYBAR_NS @@ -217,6 +220,38 @@ unsigned long long config::convert(string&& value) const { return v < ULLONG_MAX ? v : 0ULL; } +template <> +spacing_val config::convert(string&& value) const { + return units_utils::parse_spacing(value); +} + +template <> +extent_val config::convert(std::string&& value) const { + return units_utils::parse_extent(value); +} + +/** + * Allows a new format for pixel sizes (like width in the bar section) + * + * The new format is X%:Z, where X is in [0, 100], and Z is any real value + * describing a pixel offset. The actual value is calculated by X% * max + Z + */ +template <> +percentage_with_offset config::convert(string&& value) const { + size_t i = value.find(':'); + + if (i == std::string::npos) { + if (value.find('%') != std::string::npos) { + return {std::stod(value), {}}; + } else { + return {0., convert(move(value))}; + } + } else { + std::string percentage = value.substr(0, i - 1); + return {std::stod(percentage), convert(value.substr(i + 1))}; + } +} + template <> chrono::seconds config::convert(string&& value) const { return chrono::seconds{convert(forward(value))}; diff --git a/src/components/controller.cpp b/src/components/controller.cpp index fe536fc2..a73eff2e 100644 --- a/src/components/controller.cpp +++ b/src/components/controller.cpp @@ -465,10 +465,10 @@ void controller::process_inputdata(string&& cmd) { bool controller::process_update(bool force) { const bar_settings& bar{m_bar->settings()}; string contents; - string padding_left(bar.padding.left, ' '); - string padding_right(bar.padding.right, ' '); - string margin_left(bar.module_margin.left, ' '); - string margin_right(bar.module_margin.right, ' '); + string padding_left = builder::get_spacing_format_string(bar.padding.left); + string padding_right = builder::get_spacing_format_string(bar.padding.right); + string margin_left = builder::get_spacing_format_string(bar.module_margin.left); + string margin_right = builder::get_spacing_format_string(bar.module_margin.right); builder build{bar}; build.node(bar.separator); diff --git a/src/components/renderer.cpp b/src/components/renderer.cpp index 94436619..4da65ec9 100644 --- a/src/components/renderer.cpp +++ b/src/components/renderer.cpp @@ -8,6 +8,7 @@ #include "events/signal_emitter.hpp" #include "events/signal_receiver.hpp" #include "utils/math.hpp" +#include "utils/units.hpp" #include "x11/atoms.hpp" #include "x11/background_manager.hpp" #include "x11/connection.hpp" @@ -109,9 +110,9 @@ renderer::renderer(connection& conn, signal_emitter& sig, const config& conf, co m_log.trace("renderer: Allocate alignment blocks"); { - m_blocks.emplace(alignment::LEFT, alignment_block{nullptr, 0.0, 0.0}); - m_blocks.emplace(alignment::CENTER, alignment_block{nullptr, 0.0, 0.0}); - m_blocks.emplace(alignment::RIGHT, alignment_block{nullptr, 0.0, 0.0}); + m_blocks.emplace(alignment::LEFT, alignment_block{nullptr, 0.0, 0.0, 0.}); + m_blocks.emplace(alignment::CENTER, alignment_block{nullptr, 0.0, 0.0, 0.}); + m_blocks.emplace(alignment::RIGHT, alignment_block{nullptr, 0.0, 0.0, 0.}); } m_log.trace("renderer: Allocate cairo components"); @@ -122,31 +123,6 @@ renderer::renderer(connection& conn, signal_emitter& sig, const config& conf, co m_log.trace("renderer: Load fonts"); { - double dpi_x = 96, dpi_y = 96; - if (m_conf.has(m_conf.section(), "dpi")) { - dpi_x = dpi_y = m_conf.get("dpi"); - } else { - if (m_conf.has(m_conf.section(), "dpi-x")) { - dpi_x = m_conf.get("dpi-x"); - } - if (m_conf.has(m_conf.section(), "dpi-y")) { - dpi_y = m_conf.get("dpi-y"); - } - } - - // dpi to be computed - if (dpi_x <= 0 || dpi_y <= 0) { - auto screen = m_connection.screen(); - if (dpi_x <= 0) { - dpi_x = screen->width_in_pixels * 25.4 / screen->width_in_millimeters; - } - if (dpi_y <= 0) { - dpi_y = screen->height_in_pixels * 25.4 / screen->height_in_millimeters; - } - } - - m_log.info("Configured DPI = %gx%g", dpi_x, dpi_y); - auto fonts = m_conf.get_list(m_conf.section(), "font", {}); if (fonts.empty()) { m_log.warn("No fonts specified, using fallback font \"fixed\""); @@ -161,7 +137,7 @@ renderer::renderer(connection& conn, signal_emitter& sig, const config& conf, co offset = std::strtol(pattern.substr(pos + 1).c_str(), nullptr, 10); pattern.erase(pos); } - auto font = cairo::make_font(*m_context, string{pattern}, offset, dpi_x, dpi_y); + auto font = cairo::make_font(*m_context, string{pattern}, offset, m_bar.dpi_x, m_bar.dpi_y); m_log.notice("Loaded font \"%s\" (name=%s, offset=%i, file=%s)", pattern, font->name(), offset, font->file()); *m_context << move(font); } @@ -287,7 +263,7 @@ void renderer::end() { // the bar will be filled by the wallpaper creating illusion of transparency. if (m_pseudo_transparency) { cairo_pattern_t* barcontents{}; - m_context->pop(&barcontents); // corresponding push is in renderer::begin + m_context->pop(&barcontents); // corresponding push is in renderer::begin auto root_bg = m_background->get_surface(); if (root_bg != nullptr) { @@ -491,7 +467,7 @@ double renderer::block_y(alignment) const { * Get block width for given alignment */ double renderer::block_w(alignment a) const { - return m_blocks.at(a).x; + return m_blocks.at(a).width; } /** @@ -501,6 +477,14 @@ double renderer::block_h(alignment) const { return m_rect.height; } +void renderer::increase_x(double dx) { + m_blocks[m_align].x += dx; + /* + * The width only increases when x becomes larger than the old width. + */ + m_blocks[m_align].width = std::max(m_blocks[m_align].width, m_blocks[m_align].x); +} + /** * Fill background color */ @@ -695,11 +679,17 @@ void renderer::render_text(const tags::context& ctxt, const string&& contents) { origin.x = m_rect.x + m_blocks[m_align].x; origin.y = m_rect.y + m_rect.height / 2.0; + double x_old = m_blocks[m_align].x; + /* + * This variable is increased by the text renderer + */ + double x_new = x_old; + cairo::textblock block{}; block.align = m_align; block.contents = contents; block.font = ctxt.get_font(); - block.x_advance = &m_blocks[m_align].x; + block.x_advance = &x_new; block.y_advance = &m_blocks[m_align].y; block.bg_rect = cairo::rect{0.0, 0.0, 0.0, 0.0}; @@ -724,7 +714,9 @@ void renderer::render_text(const tags::context& ctxt, const string&& contents) { *m_context << block; m_context->restore(); - double dx = m_rect.x + m_blocks[m_align].x - origin.x; + double dx = x_new - x_old; + increase_x(dx); + if (dx > 0.0) { if (ctxt.has_underline()) { fill_underline(ctxt.get_ul(), origin.x, dx); @@ -736,9 +728,26 @@ void renderer::render_text(const tags::context& ctxt, const string&& contents) { } } -void renderer::render_offset(const tags::context&, int pixels) { - m_log.trace_x("renderer: offset_pixel(%f)", pixels); - m_blocks[m_align].x += pixels; +void renderer::draw_offset(rgba color, double x, double w) { + if (w > 0 && color != m_bar.background) { + m_log.trace_x("renderer: offset(x=%f, w=%f)", x, w); + m_context->save(); + *m_context << m_comp_bg; + *m_context << color; + *m_context << cairo::rect{ + m_rect.x + x, static_cast(m_rect.y), w, static_cast(m_rect.y + m_rect.height)}; + m_context->fill(); + m_context->restore(); + } +} + +void renderer::render_offset(const tags::context& ctxt, const extent_val offset) { + m_log.trace_x("renderer: offset_pixel(%f)", offset); + + int offset_width = units_utils::extent_to_pixel(offset, m_bar.dpi_x); + rgba bg = ctxt.get_bg(); + draw_offset(bg, m_blocks[m_align].x, offset_width); + increase_x(offset_width); } void renderer::change_alignment(const tags::context& ctxt) { @@ -754,6 +763,7 @@ void renderer::change_alignment(const tags::context& ctxt) { m_align = align; m_blocks[m_align].x = 0.0; m_blocks[m_align].y = 0.0; + m_blocks[m_align].width = 0.; m_context->push(); m_log.trace_x("renderer: push(%i)", static_cast(m_align)); diff --git a/src/drawtypes/label.cpp b/src/drawtypes/label.cpp index 6dbdd680..e4317d9b 100644 --- a/src/drawtypes/label.cpp +++ b/src/drawtypes/label.cpp @@ -113,16 +113,16 @@ namespace drawtypes { if (label->m_font != 0) { m_font = label->m_font; } - if (label->m_padding.left != 0U) { + if (label->m_padding.left) { m_padding.left = label->m_padding.left; } - if (label->m_padding.right != 0U) { + if (label->m_padding.right) { m_padding.right = label->m_padding.right; } - if (label->m_margin.left != 0U) { + if (label->m_margin.left) { m_margin.left = label->m_margin.left; } - if (label->m_margin.right != 0U) { + if (label->m_margin.right) { m_margin.right = label->m_margin.right; } if (label->m_maxlen != 0_z) { @@ -147,16 +147,16 @@ namespace drawtypes { if (m_font == 0 && label->m_font != 0) { m_font = label->m_font; } - if (m_padding.left == 0U && label->m_padding.left != 0U) { + if (!m_padding.left && label->m_padding.left) { m_padding.left = label->m_padding.left; } - if (m_padding.right == 0U && label->m_padding.right != 0U) { + if (!m_padding.right && label->m_padding.right) { m_padding.right = label->m_padding.right; } - if (m_margin.left == 0U && label->m_margin.left != 0U) { + if (!m_margin.left && label->m_margin.left) { m_margin.left = label->m_margin.left; } - if (m_margin.right == 0U && label->m_margin.right != 0U) { + if (!m_margin.right && label->m_margin.right) { m_margin.right = label->m_margin.right; } if (m_maxlen == 0_z && label->m_maxlen != 0_z) { @@ -182,14 +182,23 @@ namespace drawtypes { if (required) { text = conf.get(section, name); } else { - text = conf.get(section, name, move(def)); + text = conf.get(section, name, def); } - const auto get_left_right = [&](string key) { - auto value = conf.get(section, key, 0U); - auto left = conf.get(section, key + "-left", value); - auto right = conf.get(section, key + "-right", value); - return side_values{static_cast(left), static_cast(right)}; + const auto get_left_right = [&](string&& key) { + const auto parse_or_throw = [&](const string& key, spacing_val default_value) { + try { + return conf.get(section, key, default_value); + } catch (const std::exception& err) { + throw application_error( + sstream() << "Failed to set " << section << "." << key << " (reason: " << err.what() << ")"); + } + }; + + auto value = parse_or_throw(key, ZERO_SPACE); + auto left = parse_or_throw(key + "-left", value); + auto right = parse_or_throw(key + "-right", value); + return side_values{left, right}; }; padding = get_left_right(name + "-padding"); @@ -271,10 +280,12 @@ namespace drawtypes { } bool ellipsis = conf.get(section, name + "-ellipsis", true); + // clang-format off if (ellipsis && maxlen > 0 && maxlen < 3) { throw application_error(sstream() << "Label " << section << "." << name << " has maxlen " << maxlen << ", which is smaller than length of ellipsis (3)"); } + // clang-format on // clang-format off return std::make_shared