Add units support (POINT, PIXEL, SPACE) (#2578)
* 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<spacing_val> 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 <p.ziegler96@gmail.com> * Update src/components/bar.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/config.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * 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 = <toggle> <label-song> foo If mpd is not running, the mpd module will return false when trying to build the `<label-song>` tag. If we don't remove the space between `<toggle>` and `<label-song>`, we end up with two spaces between `<toggle>` 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 <jerome.boulmier@outlook.fr> Co-authored-by: Joe Groocock <github@frebib.net>
This commit is contained in:
parent
ab915fb724
commit
ce93188a4a
@ -10,4 +10,5 @@ AllowShortIfStatementsOnASingleLine: false
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Left
|
||||
SpacesBeforeTrailingComments: 1
|
||||
---
|
||||
|
@ -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.
|
||||
|
@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
|
||||
#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<double>(
|
||||
0, math_util::percentage_to_value<double>(strtod(a.c_str(), nullptr), max) + strtod(b.c_str(), nullptr));
|
||||
} else {
|
||||
if (str.find('%') != std::string::npos) {
|
||||
return math_util::percentage_to_value<double>(strtod(str.c_str(), nullptr), max);
|
||||
} else {
|
||||
return strtod(str.c_str(), nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify, evt::enter_notify,
|
||||
evt::leave_notify, evt::motion_notify, evt::destroy_notify, evt::client_message, evt::configure_notify>,
|
||||
public signal_receiver<SIGN_PRIORITY_BAR, signals::ui::dim_window
|
||||
|
@ -21,17 +21,14 @@ class builder {
|
||||
|
||||
void reset();
|
||||
string flush();
|
||||
void append(string text);
|
||||
void node(string str);
|
||||
void node(string str, int font_index);
|
||||
void append(const string& text);
|
||||
void node(const string& str);
|
||||
void node(const string& str, int font_index);
|
||||
void node(const label_t& label);
|
||||
void node_repeat(const string& str, size_t n);
|
||||
void node_repeat(const label_t& label, size_t n);
|
||||
void offset(int pixels);
|
||||
void space(size_t width);
|
||||
void space();
|
||||
void remove_trailing_space(size_t len);
|
||||
void remove_trailing_space();
|
||||
void offset(extent_val pixels = ZERO_PX_EXTENT);
|
||||
void spacing(spacing_val size);
|
||||
void font(int index);
|
||||
void font_close();
|
||||
void background(rgba color);
|
||||
@ -55,6 +52,8 @@ class builder {
|
||||
void action(mousebtn btn, const modules::module_interface& module, string action, string data, const label_t& label);
|
||||
void action_close();
|
||||
|
||||
static string get_spacing_format_string(const spacing_val& space);
|
||||
|
||||
protected:
|
||||
void tag_open(tags::syntaxtag tag, const string& value);
|
||||
void tag_open(tags::attribute attr);
|
||||
|
@ -107,7 +107,7 @@ class config {
|
||||
return dereference<T>(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;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
@ -29,7 +28,7 @@ class logger;
|
||||
class signal_emitter;
|
||||
namespace modules {
|
||||
struct module_interface;
|
||||
} // namespace modules
|
||||
} // namespace modules
|
||||
using module_t = shared_ptr<modules::module_interface>;
|
||||
using modulemap_t = std::map<alignment, vector<module_t>>;
|
||||
// }}}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<edge, border_settings, enum_hash> borders{};
|
||||
|
||||
struct radius radius {};
|
||||
int spacing{0};
|
||||
/**
|
||||
* TODO deprecated
|
||||
*/
|
||||
spacing_val spacing{ZERO_SPACE};
|
||||
label_t separator{};
|
||||
|
||||
string wmname{};
|
||||
|
@ -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<token>&& 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<vector<token>>(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
|
||||
|
@ -40,13 +40,12 @@ namespace modules {
|
||||
static constexpr auto TAG_RAMP_LOAD_PER_CORE = "<ramp-coreload>";
|
||||
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<cpu_time_t> m_cputimes;
|
||||
vector<cpu_time_t> m_cputimes_prev;
|
||||
|
@ -68,7 +68,7 @@ namespace modules {
|
||||
vector<fs_mount_t> 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
|
||||
|
@ -36,7 +36,7 @@ namespace drawtypes {
|
||||
using animation_t = shared_ptr<animation>;
|
||||
class iconset;
|
||||
using iconset_t = shared_ptr<iconset>;
|
||||
} // 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
|
||||
|
@ -197,48 +197,89 @@ namespace modules {
|
||||
std::lock_guard<std::mutex> 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
|
||||
|
@ -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<action_value, string> parse_action();
|
||||
mousebtn parse_action_btn();
|
||||
|
@ -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<element>;
|
||||
|
||||
} // namespace tags
|
||||
} // namespace tags
|
||||
|
||||
POLYBAR_NS_END
|
||||
|
@ -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;
|
||||
|
31
include/utils/units.hpp
Normal file
31
include/utils/units.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#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
|
@ -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
|
||||
|
@ -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<double>("dpi");
|
||||
} else {
|
||||
if (m_conf.has(m_conf.section(), "dpi-x")) {
|
||||
dpi_x = m_conf.get<double>("dpi-x");
|
||||
}
|
||||
if (m_conf.has(m_conf.section(), "dpi-y")) {
|
||||
dpi_y = m_conf.get<double>("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<unsigned int>(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<unsigned int>(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<action> 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;
|
||||
|
@ -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<int>(value)) + "px";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
out += '}';
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
POLYBAR_NS_END
|
||||
|
@ -1,13 +1,16 @@
|
||||
#include "components/config.hpp"
|
||||
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
|
||||
#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<extent_val>(move(value))};
|
||||
}
|
||||
} else {
|
||||
std::string percentage = value.substr(0, i - 1);
|
||||
return {std::stod(percentage), convert<extent_val>(value.substr(i + 1))};
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
chrono::seconds config::convert(string&& value) const {
|
||||
return chrono::seconds{convert<chrono::seconds::rep>(forward<string>(value))};
|
||||
|
@ -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);
|
||||
|
@ -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<double>("dpi");
|
||||
} else {
|
||||
if (m_conf.has(m_conf.section(), "dpi-x")) {
|
||||
dpi_x = m_conf.get<double>("dpi-x");
|
||||
}
|
||||
if (m_conf.has(m_conf.section(), "dpi-y")) {
|
||||
dpi_y = m_conf.get<double>("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<string>(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<double>(m_rect.y), w, static_cast<double>(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<int>(m_align));
|
||||
|
||||
|
@ -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<unsigned short int>(left), static_cast<unsigned short int>(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<label>(text,
|
||||
@ -297,9 +308,9 @@ namespace drawtypes {
|
||||
* Create a label by loading optional values from the configuration
|
||||
*/
|
||||
label_t load_optional_label(const config& conf, string section, string name, string def) {
|
||||
return load_label(conf, move(section), move(name), false, move(def));
|
||||
return load_label(conf, section, move(name), false, move(def));
|
||||
}
|
||||
|
||||
} // namespace drawtypes
|
||||
} // namespace drawtypes
|
||||
|
||||
POLYBAR_NS_END
|
||||
|
@ -162,7 +162,7 @@ int main(int argc, char** argv) {
|
||||
reload = true;
|
||||
}
|
||||
} catch (const exception& err) {
|
||||
logger.err(err.what());
|
||||
logger.err("Uncaught exception, shutting down: %s", err.what());
|
||||
exit_code = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ namespace modules {
|
||||
string output;
|
||||
for (m_index = 0U; m_index < m_monitors.size(); m_index++) {
|
||||
if (m_index > 0) {
|
||||
m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||
m_builder->spacing(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||
}
|
||||
output += this->event_module::get_output();
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
#include "modules/cpu.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <istream>
|
||||
|
||||
#include "modules/cpu.hpp"
|
||||
|
||||
#include "drawtypes/label.hpp"
|
||||
#include "drawtypes/progressbar.hpp"
|
||||
#include "drawtypes/ramp.hpp"
|
||||
#include "utils/math.hpp"
|
||||
|
||||
#include "modules/meta/base.inl"
|
||||
#include "utils/math.hpp"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
@ -18,7 +17,7 @@ namespace modules {
|
||||
cpu_module::cpu_module(const bar_settings& bar, string name_) : timer_module<cpu_module>(bar, move(name_)) {
|
||||
set_interval(1s);
|
||||
m_totalwarn = m_conf.get(name(), "warn-percentage", m_totalwarn);
|
||||
m_ramp_padding = m_conf.get<decltype(m_ramp_padding)>(name(), "ramp-coreload-spacing", 1);
|
||||
m_ramp_padding = m_conf.get(name(), "ramp-coreload-spacing", m_ramp_padding);
|
||||
|
||||
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE});
|
||||
m_formatter->add_optional(FORMAT_WARN, {TAG_LABEL_WARN, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE});
|
||||
@ -73,7 +72,8 @@ namespace modules {
|
||||
const auto replace_tokens = [&](label_t& label) {
|
||||
label->reset_tokens();
|
||||
label->replace_token("%percentage%", to_string(static_cast<int>(m_total + 0.5)));
|
||||
label->replace_token("%percentage-sum%", to_string(static_cast<int>(m_total * static_cast<float>(cores_n) + 0.5)));
|
||||
label->replace_token(
|
||||
"%percentage-sum%", to_string(static_cast<int>(m_total * static_cast<float>(cores_n) + 0.5)));
|
||||
label->replace_token("%percentage-cores%", string_util::join(percentage_cores, "% ") + "%");
|
||||
|
||||
for (size_t i = 0; i < percentage_cores.size(); i++) {
|
||||
@ -99,7 +99,6 @@ namespace modules {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool cpu_module::build(builder* builder, const string& tag) const {
|
||||
if (tag == TAG_LABEL) {
|
||||
builder->node(m_label);
|
||||
@ -113,7 +112,7 @@ namespace modules {
|
||||
auto i = 0;
|
||||
for (auto&& load : m_load) {
|
||||
if (i++ > 0) {
|
||||
builder->space(m_ramp_padding);
|
||||
builder->spacing(m_ramp_padding);
|
||||
}
|
||||
builder->node(m_rampload_core->get_by_percentage_with_borders(load, 0.0f, m_totalwarn));
|
||||
}
|
||||
@ -179,6 +178,6 @@ namespace modules {
|
||||
|
||||
return math_util::cap<float>(percentage, 0, 100);
|
||||
}
|
||||
}
|
||||
} // namespace modules
|
||||
|
||||
POLYBAR_NS_END
|
||||
|
@ -143,7 +143,7 @@ namespace modules {
|
||||
* are not empty and there is already other content in the module.
|
||||
*/
|
||||
if (!output.empty() && !mount_output.empty()) {
|
||||
m_builder->space(m_spacing);
|
||||
m_builder->spacing(m_spacing);
|
||||
output += m_builder->flush();
|
||||
}
|
||||
output += mount_output;
|
||||
|
@ -76,7 +76,7 @@ namespace modules {
|
||||
// Insert separator after menu-toggle and before menu-items for expand-right=true
|
||||
if (m_expand_right && *m_labelseparator) {
|
||||
builder->node(m_labelseparator);
|
||||
builder->space(spacing);
|
||||
builder->spacing(spacing);
|
||||
}
|
||||
auto&& items = m_levels[m_level]->items;
|
||||
for (size_t i = 0; i < items.size(); i++) {
|
||||
@ -84,14 +84,14 @@ namespace modules {
|
||||
builder->action(
|
||||
mousebtn::LEFT, *this, string(EVENT_EXEC), to_string(m_level) + "-" + to_string(i), item->label);
|
||||
if (item != m_levels[m_level]->items.back()) {
|
||||
builder->space(spacing);
|
||||
builder->spacing(spacing);
|
||||
if (*m_labelseparator) {
|
||||
builder->node(m_labelseparator);
|
||||
builder->space(spacing);
|
||||
builder->spacing(spacing);
|
||||
}
|
||||
// Insert separator after last menu-item and before menu-toggle for expand-right=false
|
||||
} else if (!m_expand_right && *m_labelseparator) {
|
||||
builder->space(spacing);
|
||||
builder->spacing(spacing);
|
||||
builder->node(m_labelseparator);
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ namespace modules {
|
||||
builder->flush();
|
||||
return "";
|
||||
}
|
||||
if (offset != 0) {
|
||||
if (offset) {
|
||||
builder->offset(offset);
|
||||
}
|
||||
if (margin > 0) {
|
||||
builder->space(margin);
|
||||
if (margin) {
|
||||
builder->spacing(margin);
|
||||
}
|
||||
if (bg.has_color()) {
|
||||
builder->background(bg);
|
||||
@ -36,8 +36,8 @@ namespace modules {
|
||||
if (font > 0) {
|
||||
builder->font(font);
|
||||
}
|
||||
if (padding > 0) {
|
||||
builder->space(padding);
|
||||
if (padding) {
|
||||
builder->spacing(padding);
|
||||
}
|
||||
|
||||
builder->node(prefix);
|
||||
@ -58,8 +58,8 @@ namespace modules {
|
||||
builder->append(move(output));
|
||||
builder->node(suffix);
|
||||
|
||||
if (padding > 0) {
|
||||
builder->space(padding);
|
||||
if (padding) {
|
||||
builder->spacing(padding);
|
||||
}
|
||||
if (font > 0) {
|
||||
builder->font_close();
|
||||
@ -76,8 +76,8 @@ namespace modules {
|
||||
if (bg.has_color()) {
|
||||
builder->background_close();
|
||||
}
|
||||
if (margin > 0) {
|
||||
builder->space(margin);
|
||||
if (margin) {
|
||||
builder->spacing(margin);
|
||||
}
|
||||
|
||||
return builder->flush();
|
||||
@ -87,8 +87,9 @@ namespace modules {
|
||||
// module_formatter {{{
|
||||
|
||||
void module_formatter::add_value(string&& name, string&& value, vector<string>&& tags, vector<string>&& whitelist) {
|
||||
const auto formatdef = [&](
|
||||
const string& param, const auto& fallback) { return m_conf.get("settings", "format-" + param, fallback); };
|
||||
const auto formatdef = [&](const string& param, const auto& fallback) {
|
||||
return m_conf.get("settings", "format-" + param, fallback);
|
||||
};
|
||||
|
||||
auto format = make_unique<module_format>();
|
||||
format->value = move(value);
|
||||
|
@ -187,7 +187,7 @@ namespace modules {
|
||||
for (auto&& indicator : m_indicators) {
|
||||
if (*indicator.second) {
|
||||
if (n++) {
|
||||
builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||
builder->spacing(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||
}
|
||||
builder->node(indicator.second);
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ namespace modules {
|
||||
string output;
|
||||
for (m_index = 0; m_index < m_viewports.size(); m_index++) {
|
||||
if (m_index > 0) {
|
||||
m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||
m_builder->spacing(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||
}
|
||||
output += module::get_output();
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
|
||||
#include "utils/units.hpp"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
namespace tags {
|
||||
@ -340,26 +342,18 @@ namespace tags {
|
||||
}
|
||||
}
|
||||
|
||||
int parser::parse_offset() {
|
||||
extent_val parser::parse_offset() {
|
||||
string s = get_tag_value();
|
||||
|
||||
if (s.empty()) {
|
||||
return 0;
|
||||
return ZERO_PX_EXTENT;
|
||||
}
|
||||
|
||||
try {
|
||||
size_t ptr;
|
||||
int ret = std::stoi(s, &ptr, 10);
|
||||
|
||||
if (ptr != s.size()) {
|
||||
throw offset_error(s, "Offset contains non-number characters");
|
||||
}
|
||||
|
||||
return ret;
|
||||
return units_utils::parse_extent(string{s});
|
||||
} catch (const std::exception& err) {
|
||||
throw offset_error(s, err.what());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
controltag parser::parse_control() {
|
||||
@ -508,6 +502,6 @@ namespace tags {
|
||||
|
||||
return s;
|
||||
}
|
||||
} // namespace tags
|
||||
} // namespace tags
|
||||
|
||||
POLYBAR_NS_END
|
||||
|
@ -98,10 +98,18 @@ bool rgba::operator==(const rgba& other) const {
|
||||
}
|
||||
}
|
||||
|
||||
bool rgba::operator!=(const rgba& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
rgba::operator uint32_t() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
rgba::operator bool() const {
|
||||
return has_color();
|
||||
}
|
||||
|
||||
uint32_t rgba::value() const {
|
||||
return this->m_value;
|
||||
}
|
||||
|
137
src/utils/units.cpp
Normal file
137
src/utils/units.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include "utils/units.hpp"
|
||||
|
||||
#include "common.hpp"
|
||||
#include "components/types.hpp"
|
||||
#include "errors.hpp"
|
||||
#include "utils/math.hpp"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
namespace units_utils {
|
||||
|
||||
/**
|
||||
* Converts points to pixels under the given DPI (PPI).
|
||||
*
|
||||
* 1 pt = 1/72in, so point / 72 * DPI = #pixels
|
||||
*/
|
||||
int point_to_pixel(double point, double dpi) {
|
||||
return dpi * point / 72.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an extent value to a pixel value according to the given DPI (if needed).
|
||||
*/
|
||||
int extent_to_pixel(const extent_val size, double dpi) {
|
||||
if (size.type == extent_type::PIXEL) {
|
||||
return size.value;
|
||||
}
|
||||
|
||||
return point_to_pixel(size.value, dpi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as extent_to_pixel but is capped below at 0 pixels.
|
||||
*/
|
||||
unsigned extent_to_pixel_nonnegative(const extent_val size, double dpi) {
|
||||
return std::max(0, extent_to_pixel(size, dpi));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a percentage with offset into pixels
|
||||
*/
|
||||
unsigned int percentage_with_offset_to_pixel(percentage_with_offset g_format, double max, double dpi) {
|
||||
int offset_pixel = extent_to_pixel(g_format.offset, dpi);
|
||||
|
||||
return static_cast<unsigned int>(
|
||||
std::max<double>(0, math_util::percentage_to_value<double, double>(g_format.percentage, max) + offset_pixel));
|
||||
}
|
||||
|
||||
extent_type parse_extent_unit(const string& str) {
|
||||
if (!str.empty()) {
|
||||
if (str == "px") {
|
||||
return extent_type::PIXEL;
|
||||
} else if (str == "pt") {
|
||||
return extent_type::POINT;
|
||||
} else {
|
||||
throw std::runtime_error("Unrecognized unit '" + str + "'");
|
||||
}
|
||||
} else {
|
||||
return extent_type::PIXEL;
|
||||
}
|
||||
}
|
||||
|
||||
extent_val parse_extent(const string& str) {
|
||||
size_t pos;
|
||||
auto size_value = std::stof(str, &pos);
|
||||
|
||||
string unit = string_util::trim(str.substr(pos));
|
||||
extent_type type = parse_extent_unit(unit);
|
||||
|
||||
// Pixel values should be integers
|
||||
if (type == extent_type::PIXEL) {
|
||||
size_value = std::trunc(size_value);
|
||||
}
|
||||
|
||||
return {type, size_value};
|
||||
}
|
||||
|
||||
string extent_to_string(extent_val extent) {
|
||||
std::stringstream ss;
|
||||
|
||||
switch (extent.type) {
|
||||
case extent_type::POINT:
|
||||
ss << extent.value << "pt";
|
||||
break;
|
||||
case extent_type::PIXEL:
|
||||
ss << static_cast<int>(extent.value) << "px";
|
||||
break;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
spacing_type parse_spacing_unit(const string& str) {
|
||||
if (!str.empty()) {
|
||||
if (str == "px") {
|
||||
return spacing_type::PIXEL;
|
||||
} else if (str == "pt") {
|
||||
return spacing_type::POINT;
|
||||
} else {
|
||||
throw std::runtime_error("Unrecognized unit '" + str + "'");
|
||||
}
|
||||
} else {
|
||||
return spacing_type::SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
spacing_val parse_spacing(const string& str) {
|
||||
size_t pos;
|
||||
auto size_value = std::stof(str, &pos);
|
||||
|
||||
if (size_value < 0) {
|
||||
throw runtime_error(sstream() << "value '" << str << "' must not be negative");
|
||||
}
|
||||
|
||||
spacing_type type;
|
||||
|
||||
string unit = string_util::trim(str.substr(pos));
|
||||
if (!unit.empty()) {
|
||||
if (unit == "px") {
|
||||
type = spacing_type::PIXEL;
|
||||
size_value = std::trunc(size_value);
|
||||
} else if (unit == "pt") {
|
||||
type = spacing_type::POINT;
|
||||
} else {
|
||||
throw runtime_error("Unrecognized unit '" + unit + "'");
|
||||
}
|
||||
} else {
|
||||
type = spacing_type::SPACE;
|
||||
size_value = std::trunc(size_value);
|
||||
}
|
||||
|
||||
return {type, size_value};
|
||||
}
|
||||
|
||||
} // namespace units_utils
|
||||
|
||||
POLYBAR_NS_END
|
@ -14,6 +14,7 @@
|
||||
#include "utils/math.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/process.hpp"
|
||||
#include "utils/units.hpp"
|
||||
#include "x11/background_manager.hpp"
|
||||
#include "x11/ewmh.hpp"
|
||||
#include "x11/icccm.hpp"
|
||||
@ -146,30 +147,23 @@ void tray_manager::setup(const bar_settings& bar_opts) {
|
||||
m_opts.spacing += conf.get<unsigned int>(bs, "tray-padding", 0);
|
||||
|
||||
// Add user-defiend offset
|
||||
auto offset_x_def = conf.get(bs, "tray-offset-x", ""s);
|
||||
auto offset_y_def = conf.get(bs, "tray-offset-y", ""s);
|
||||
auto offset_x = conf.get(bs, "tray-offset-x", percentage_with_offset{});
|
||||
auto offset_y = conf.get(bs, "tray-offset-y", percentage_with_offset{});
|
||||
|
||||
auto offset_x = strtol(offset_x_def.c_str(), nullptr, 10);
|
||||
auto offset_y = strtol(offset_y_def.c_str(), nullptr, 10);
|
||||
int max_x;
|
||||
int max_y;
|
||||
|
||||
if (offset_x != 0 && offset_x_def.find('%') != string::npos) {
|
||||
if (m_opts.detached) {
|
||||
offset_x = math_util::signed_percentage_to_value<int>(offset_x, bar_opts.monitor->w);
|
||||
} else {
|
||||
offset_x = math_util::signed_percentage_to_value<int>(offset_x, inner_area.width);
|
||||
}
|
||||
if (m_opts.detached) {
|
||||
max_x = bar_opts.monitor->w;
|
||||
max_y = bar_opts.monitor->h;
|
||||
} else {
|
||||
max_x = inner_area.width;
|
||||
max_y = inner_area.height;
|
||||
}
|
||||
|
||||
if (offset_y != 0 && offset_y_def.find('%') != string::npos) {
|
||||
if (m_opts.detached) {
|
||||
offset_y = math_util::signed_percentage_to_value<int>(offset_y, bar_opts.monitor->h);
|
||||
} else {
|
||||
offset_y = math_util::signed_percentage_to_value<int>(offset_y, inner_area.height);
|
||||
}
|
||||
}
|
||||
|
||||
m_opts.orig_x += offset_x;
|
||||
m_opts.orig_y += offset_y;
|
||||
m_opts.orig_x += units_utils::percentage_with_offset_to_pixel(offset_x, max_x, bar_opts.dpi_x);
|
||||
m_opts.orig_y += units_utils::percentage_with_offset_to_pixel(offset_y, max_y, bar_opts.dpi_y);
|
||||
;
|
||||
m_opts.rel_x = m_opts.orig_x - bar_opts.pos.x;
|
||||
m_opts.rel_y = m_opts.orig_y - bar_opts.pos.y;
|
||||
|
||||
@ -662,10 +656,10 @@ void tray_manager::set_tray_colors() {
|
||||
const uint16_t b16 = (b << 8) | b;
|
||||
|
||||
const uint32_t colors[12] = {
|
||||
r16, g16, b16, // normal
|
||||
r16, g16, b16, // error
|
||||
r16, g16, b16, // warning
|
||||
r16, g16, b16, // success
|
||||
r16, g16, b16, // normal
|
||||
r16, g16, b16, // error
|
||||
r16, g16, b16, // warning
|
||||
r16, g16, b16, // success
|
||||
};
|
||||
|
||||
m_connection.change_property(
|
||||
|
@ -57,8 +57,8 @@ add_unit_test(utils/scope)
|
||||
add_unit_test(utils/string)
|
||||
add_unit_test(utils/file)
|
||||
add_unit_test(utils/process)
|
||||
add_unit_test(utils/units)
|
||||
add_unit_test(components/command_line)
|
||||
add_unit_test(components/bar)
|
||||
add_unit_test(components/config_parser)
|
||||
add_unit_test(drawtypes/label)
|
||||
add_unit_test(drawtypes/ramp)
|
||||
|
@ -1,48 +0,0 @@
|
||||
#include "common/test.hpp"
|
||||
#include "components/bar.hpp"
|
||||
|
||||
using namespace polybar;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* \brief Class for parameterized tests on geom_format_to_pixels
|
||||
*
|
||||
* The first element in the tuple is the expected return value, the second
|
||||
* value is the format string. The max value is always 1000
|
||||
*/
|
||||
class GeomFormatToPixelsTest :
|
||||
public ::testing::Test,
|
||||
public ::testing::WithParamInterface<pair<double, string>> {};
|
||||
|
||||
vector<pair<double, string>> to_pixels_no_offset_list = {
|
||||
{1000, "100%"},
|
||||
{0, "0%"},
|
||||
{1000, "150%"},
|
||||
{100, "10%"},
|
||||
{0, "0"},
|
||||
{1234, "1234"},
|
||||
{1.234, "1.234"},
|
||||
};
|
||||
|
||||
vector<pair<double, string>> to_pixels_with_offset_list = {
|
||||
{1000, "100%:-0"},
|
||||
{1000, "100%:+0"},
|
||||
{1010, "100%:+10"},
|
||||
{990, "100%:-10"},
|
||||
{10, "0%:+10"},
|
||||
{1000, "99%:+10"},
|
||||
{0, "1%:-100"},
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(NoOffset, GeomFormatToPixelsTest,
|
||||
::testing::ValuesIn(to_pixels_no_offset_list));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(WithOffset, GeomFormatToPixelsTest,
|
||||
::testing::ValuesIn(to_pixels_with_offset_list));
|
||||
|
||||
TEST_P(GeomFormatToPixelsTest, correctness) {
|
||||
double exp = GetParam().first;
|
||||
std::string str = GetParam().second;
|
||||
EXPECT_DOUBLE_EQ(exp, geom_format_to_pixels(str, 1000));
|
||||
}
|
@ -15,11 +15,17 @@ using ::testing::Property;
|
||||
using ::testing::Return;
|
||||
using ::testing::Truly;
|
||||
|
||||
namespace polybar {
|
||||
inline bool operator==(const extent_val& a, const extent_val& b) {
|
||||
return a.type == b.type && a.value == b.value;
|
||||
}
|
||||
} // namespace polybar
|
||||
|
||||
class MockRenderer : public renderer_interface {
|
||||
public:
|
||||
MockRenderer(action_context& action_ctxt) : renderer_interface(action_ctxt){};
|
||||
|
||||
MOCK_METHOD(void, render_offset, (const context& ctxt, int pixels), (override));
|
||||
MOCK_METHOD(void, render_offset, (const context& ctxt, const extent_val offset), (override));
|
||||
MOCK_METHOD(void, render_text, (const context& ctxt, const string&& str), (override));
|
||||
MOCK_METHOD(void, change_alignment, (const context& ctxt), (override));
|
||||
MOCK_METHOD(double, get_x, (const context& ctxt), (const, override));
|
||||
@ -53,7 +59,7 @@ class DispatchTest : public ::testing::Test {
|
||||
TEST_F(DispatchTest, ignoreFormatting) {
|
||||
{
|
||||
InSequence seq;
|
||||
EXPECT_CALL(r, render_offset(_, 10)).Times(1);
|
||||
EXPECT_CALL(r, render_offset(_, extent_val{extent_type::PIXEL, 10})).Times(1);
|
||||
EXPECT_CALL(r, render_text(_, string{"abc"})).Times(1);
|
||||
EXPECT_CALL(r, render_text(_, string{"foo"})).Times(1);
|
||||
}
|
||||
@ -74,7 +80,7 @@ TEST_F(DispatchTest, formatting) {
|
||||
|
||||
{
|
||||
InSequence seq;
|
||||
EXPECT_CALL(r, render_offset(_, 10)).Times(1);
|
||||
EXPECT_CALL(r, render_offset(_, extent_val{extent_type::PIXEL, 10})).Times(1);
|
||||
EXPECT_CALL(r, render_text(match_fg(c1), string{"abc"})).Times(1);
|
||||
EXPECT_CALL(r, render_text(match_fg(bar_fg), string{"foo"})).Times(1);
|
||||
EXPECT_CALL(r, change_alignment(match_left_align)).Times(1);
|
||||
|
@ -75,10 +75,18 @@ class TestableTagParser : public parser {
|
||||
EXPECT_EQ(exp, current.tag_data.font);
|
||||
}
|
||||
|
||||
void expect_offset(int exp) {
|
||||
void expect_offset_pixel(int exp) {
|
||||
set_current();
|
||||
assert_format(syntaxtag::O);
|
||||
EXPECT_EQ(exp, current.tag_data.offset);
|
||||
EXPECT_EQ(extent_type::PIXEL, current.tag_data.offset.type);
|
||||
EXPECT_EQ(exp, current.tag_data.offset.value);
|
||||
}
|
||||
|
||||
void expect_offset_points(float exp) {
|
||||
set_current();
|
||||
assert_format(syntaxtag::O);
|
||||
EXPECT_EQ(extent_type::POINT, current.tag_data.offset.type);
|
||||
EXPECT_EQ(exp, current.tag_data.offset.value);
|
||||
}
|
||||
|
||||
void expect_ctrl(controltag exp) {
|
||||
@ -308,19 +316,43 @@ TEST_F(TagParserTest, font) {
|
||||
|
||||
TEST_F(TagParserTest, offset) {
|
||||
p.setup_parser_test("%{O}");
|
||||
p.expect_offset(0);
|
||||
p.expect_offset_pixel(0);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O0}");
|
||||
p.expect_offset(0);
|
||||
p.expect_offset_pixel(0);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O-112}");
|
||||
p.expect_offset(-112);
|
||||
p.expect_offset_pixel(-112);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O123}");
|
||||
p.expect_offset(123);
|
||||
p.expect_offset_pixel(123);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O0pt}");
|
||||
p.expect_offset_points(0);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O-112pt}");
|
||||
p.expect_offset_points(-112);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O123pt}");
|
||||
p.expect_offset_points(123);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O1.5pt}");
|
||||
p.expect_offset_points(1.5);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O1.1px}");
|
||||
p.expect_offset_pixel(1);
|
||||
p.expect_done();
|
||||
|
||||
p.setup_parser_test("%{O1.1}");
|
||||
p.expect_offset_pixel(1);
|
||||
p.expect_done();
|
||||
}
|
||||
|
||||
@ -408,6 +440,9 @@ vector<exception_test> parse_error_test = {
|
||||
{"%{P}", exc::CTRL},
|
||||
{"%{PA}", exc::CTRL},
|
||||
{"%{Oabc}", exc::OFFSET},
|
||||
{"%{O123foo}", exc::OFFSET},
|
||||
{"%{O0ptx}", exc::OFFSET},
|
||||
{"%{O0a}", exc::OFFSET},
|
||||
{"%{A2:cmd:cmd:}", exc::TAG_END},
|
||||
{"%{A9}", exc::BTN},
|
||||
{"%{rQ}", exc::TAG_END},
|
||||
|
146
tests/unit_tests/utils/units.cpp
Normal file
146
tests/unit_tests/utils/units.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
#include "utils/units.hpp"
|
||||
|
||||
#include "common/test.hpp"
|
||||
#include "utils/units.hpp"
|
||||
|
||||
using namespace polybar;
|
||||
using namespace units_utils;
|
||||
|
||||
|
||||
namespace polybar {
|
||||
bool operator==(const extent_val lhs, const extent_val rhs) {
|
||||
return lhs.type == rhs.type && lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
bool operator==(const spacing_val lhs, const spacing_val rhs) {
|
||||
return lhs.type == rhs.type && lhs.value == rhs.value;
|
||||
}
|
||||
} // namespace polybar
|
||||
|
||||
/**
|
||||
* \brief Class for parameterized tests on geom_format_to_pixels
|
||||
*
|
||||
* The first element in the tuple is the expected return value, the second
|
||||
* value represents the format string. The max value is always 1000 and dpi is always 96
|
||||
*/
|
||||
class GeomFormatToPixelsTest : public ::testing::Test,
|
||||
public ::testing::WithParamInterface<pair<unsigned, percentage_with_offset>> {};
|
||||
|
||||
vector<pair<unsigned, percentage_with_offset>> to_pixels_no_offset_list = {
|
||||
{1000, percentage_with_offset{100.}},
|
||||
{0, percentage_with_offset{0.}},
|
||||
{1000, percentage_with_offset{150.}},
|
||||
{100, percentage_with_offset{10.}},
|
||||
{0, percentage_with_offset{0., ZERO_PX_EXTENT}},
|
||||
{1234, percentage_with_offset{0., extent_val{extent_type::PIXEL, 1234}}},
|
||||
{1, percentage_with_offset{0., extent_val{extent_type::PIXEL, 1}}},
|
||||
};
|
||||
|
||||
vector<pair<unsigned, percentage_with_offset>> to_pixels_with_offset_list = {
|
||||
{1000, percentage_with_offset{100., ZERO_PX_EXTENT}},
|
||||
{1010, percentage_with_offset{100., extent_val{extent_type::PIXEL, 10}}},
|
||||
{990, percentage_with_offset{100., extent_val{extent_type::PIXEL, -10}}},
|
||||
{10, percentage_with_offset{0., extent_val{extent_type::PIXEL, 10}}},
|
||||
{1000, percentage_with_offset{99., extent_val{extent_type::PIXEL, 10}}},
|
||||
{0, percentage_with_offset{1., extent_val{extent_type::PIXEL, -100}}},
|
||||
};
|
||||
|
||||
vector<pair<unsigned, percentage_with_offset>> to_pixels_with_units_list = {
|
||||
{1013, percentage_with_offset{100., extent_val{extent_type::POINT, 10}}},
|
||||
{987, percentage_with_offset{100., extent_val{extent_type::POINT, -10}}},
|
||||
{1003, percentage_with_offset{99., extent_val{extent_type::POINT, 10}}},
|
||||
{13, percentage_with_offset{0., extent_val{extent_type::POINT, 10}}},
|
||||
{0, percentage_with_offset{0, extent_val{extent_type::POINT, -10}}},
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(NoOffset, GeomFormatToPixelsTest, ::testing::ValuesIn(to_pixels_no_offset_list));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(WithOffset, GeomFormatToPixelsTest, ::testing::ValuesIn(to_pixels_with_offset_list));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(WithUnits, GeomFormatToPixelsTest, ::testing::ValuesIn(to_pixels_with_units_list));
|
||||
|
||||
static constexpr int MAX_WIDTH = 1000;
|
||||
static constexpr int DPI = 96;
|
||||
|
||||
TEST_P(GeomFormatToPixelsTest, correctness) {
|
||||
unsigned exp = GetParam().first;
|
||||
percentage_with_offset geometry = GetParam().second;
|
||||
EXPECT_DOUBLE_EQ(exp, percentage_with_offset_to_pixel(geometry, MAX_WIDTH, DPI));
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, point_to_pixel) {
|
||||
EXPECT_EQ(72, point_to_pixel(72, 72));
|
||||
EXPECT_EQ(96, point_to_pixel(72, 96));
|
||||
EXPECT_EQ(48, point_to_pixel(36, 96));
|
||||
EXPECT_EQ(-48, point_to_pixel(-36, 96));
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, extent_to_pixel) {
|
||||
EXPECT_EQ(100, extent_to_pixel_nonnegative({extent_type::PIXEL, 100}, 0));
|
||||
EXPECT_EQ(48, extent_to_pixel_nonnegative({extent_type::POINT, 36}, 96));
|
||||
|
||||
EXPECT_EQ(0, extent_to_pixel_nonnegative({extent_type::PIXEL, -100}, 0));
|
||||
EXPECT_EQ(0, extent_to_pixel_nonnegative({extent_type::POINT, -36}, 96));
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, percentage_with_offset_to_pixel) {
|
||||
EXPECT_EQ(1100, percentage_with_offset_to_pixel({100, {extent_type::PIXEL, 100}}, 1000, 0));
|
||||
EXPECT_EQ(1048, percentage_with_offset_to_pixel({100, {extent_type::POINT, 36}}, 1000, 96));
|
||||
|
||||
EXPECT_EQ(900, percentage_with_offset_to_pixel({100, {extent_type::PIXEL, -100}}, 1000, 0));
|
||||
EXPECT_EQ(952, percentage_with_offset_to_pixel({100, {extent_type::POINT, -36}}, 1000, 96));
|
||||
|
||||
EXPECT_EQ(0, percentage_with_offset_to_pixel({0, {extent_type::PIXEL, -100}}, 1000, 0));
|
||||
EXPECT_EQ(100, percentage_with_offset_to_pixel({0, {extent_type::PIXEL, 100}}, 1000, 0));
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, parse_extent_unit) {
|
||||
EXPECT_EQ(extent_type::PIXEL, parse_extent_unit("px"));
|
||||
EXPECT_EQ(extent_type::POINT, parse_extent_unit("pt"));
|
||||
|
||||
EXPECT_EQ(extent_type::PIXEL, parse_extent_unit(""));
|
||||
|
||||
EXPECT_THROW(parse_extent_unit("foo"), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, parse_extent) {
|
||||
EXPECT_EQ((extent_val{extent_type::PIXEL, 100}), parse_extent("100px"));
|
||||
EXPECT_EQ((extent_val{extent_type::POINT, 36}), parse_extent("36pt"));
|
||||
|
||||
EXPECT_EQ((extent_val{extent_type::PIXEL, -100}), parse_extent("-100px"));
|
||||
EXPECT_EQ((extent_val{extent_type::POINT, -36}), parse_extent("-36pt"));
|
||||
|
||||
EXPECT_EQ((extent_val{extent_type::PIXEL, 100}), parse_extent("100"));
|
||||
EXPECT_EQ((extent_val{extent_type::PIXEL, -100}), parse_extent("-100"));
|
||||
|
||||
EXPECT_THROW(parse_extent("100foo"), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, extent_to_string) {
|
||||
EXPECT_EQ("100px", extent_to_string({extent_type::PIXEL, 100}));
|
||||
EXPECT_EQ("36pt", extent_to_string({extent_type::POINT, 36}));
|
||||
|
||||
EXPECT_EQ("-100px", extent_to_string({extent_type::PIXEL, -100}));
|
||||
EXPECT_EQ("-36pt", extent_to_string({extent_type::POINT, -36}));
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, parse_spacing_unit) {
|
||||
EXPECT_EQ(spacing_type::PIXEL, parse_spacing_unit("px"));
|
||||
EXPECT_EQ(spacing_type::POINT, parse_spacing_unit("pt"));
|
||||
|
||||
EXPECT_EQ(spacing_type::SPACE, parse_spacing_unit(""));
|
||||
|
||||
EXPECT_THROW(parse_spacing_unit("foo"), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(UnitsUtils, parse_spacing) {
|
||||
EXPECT_EQ((spacing_val{spacing_type::PIXEL, 100}), parse_spacing("100px"));
|
||||
EXPECT_EQ((spacing_val{spacing_type::POINT, 36}), parse_spacing("36pt"));
|
||||
|
||||
EXPECT_EQ((spacing_val{spacing_type::SPACE, 100}), parse_spacing("100"));
|
||||
|
||||
EXPECT_THROW(parse_spacing("-100px"), std::runtime_error);
|
||||
EXPECT_THROW(parse_spacing("-36pt"), std::runtime_error);
|
||||
EXPECT_THROW(parse_spacing("-100"), std::runtime_error);
|
||||
EXPECT_THROW(parse_spacing("100foo"), std::runtime_error);
|
||||
}
|
Loading…
Reference in New Issue
Block a user