From 8b9461e63e8aaad965c0e0eaf0c2ea4fb070e87c Mon Sep 17 00:00:00 2001 From: Michael Carlberg Date: Tue, 24 Jan 2017 06:59:58 +0100 Subject: [PATCH] wip(refactor): Cairo drawing --- include/cairo/context.hpp | 107 +++++---- include/cairo/font.hpp | 76 ++----- include/cairo/surface.hpp | 38 +++- include/cairo/types.hpp | 26 ++- include/cairo/utils.hpp | 64 +++++- include/components/controller.hpp | 12 +- include/components/renderer.hpp | 54 +++-- include/components/types.hpp | 12 +- include/events/signal.hpp | 3 + include/events/signal_fwd.hpp | 1 + src/components/bar.cpp | 8 +- src/components/builder.cpp | 4 +- src/components/controller.cpp | 41 +++- src/components/renderer.cpp | 351 +++++++++++++++++++----------- src/main.cpp | 2 +- 15 files changed, 499 insertions(+), 300 deletions(-) diff --git a/include/cairo/context.hpp b/include/cairo/context.hpp index 156fc58f..02fe7a8a 100644 --- a/include/cairo/context.hpp +++ b/include/cairo/context.hpp @@ -19,6 +19,9 @@ POLYBAR_NS namespace cairo { + /** + * @brief Cairo context + */ class context { public: explicit context(const surface& surface, const logger& log) : m_c(cairo_create(surface)), m_log(log) { @@ -99,9 +102,18 @@ namespace cairo { return *this; } + context& operator<<(const displace& d) { + cairo_set_operator(m_c, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(m_c, cairo_get_target(m_c), d.x - d.w, d.y); + cairo_rectangle(m_c, d.x - d.w, d.y, d.x + d.dx, d.y + d.dy); + cairo_fill(m_c); + cairo_surface_flush(cairo_get_target(m_c)); + return *this; + } + context& operator<<(const linear_gradient& l) { if (l.steps.size() >= 2) { - auto pattern = cairo_pattern_create_linear(l.x0, l.y0, l.x1, l.y1); + auto pattern = cairo_pattern_create_linear(l.x1, l.y1, l.x2, l.y2); *this << pattern; auto stops = l.steps.size(); auto step = 1.0 / (stops - 1); @@ -149,7 +161,7 @@ namespace cairo { }); string utf8 = string(t.contents); - unicode_charlist chars; + utils::unicode_charlist chars; utils::utf8_to_ucs4((const unsigned char*)utf8.c_str(), chars); while (!chars.empty()) { @@ -167,55 +179,32 @@ namespace cairo { end++; } - // encapsulate the vert. centering - save(); + // Use the font + f->use(); + + // Get subset extents + cairo_text_extents_t extents; + f->textwidth(subset, &extents); + + save(true); { - *this << abspos{x, y}; - - f->use(); - - cairo_text_extents_t extents; - f->textwidth(utf8, &extents); - double dx{0.0}, dy{0.0}; - - if (t.align == alignment::CENTER) { - dx -= extents.x_advance / 2.0; - - save(); - { - cairo_rectangle(m_c, x, y, extents.x_advance / 2.0, extents.height); - auto pattern = cairo_pattern_create_rgba(1.0, 0.0, 0.0, 0.0); - cairo_set_source(m_c, pattern); - cairo_translate(m_c, dx, 0.0); - cairo_set_operator(m_c, CAIRO_OPERATOR_DEST); - cairo_fill(m_c); - cairo_pattern_destroy(pattern); - } - restore(); - } else if (t.align == alignment::RIGHT) { - dx -= extents.x_advance; - - save(); - { - cairo_rectangle(m_c, x, y, extents.x_advance, extents.height); - auto pattern = cairo_pattern_create_rgba(0.0, 0.0, 0.0, 0.0); - cairo_set_source(m_c, pattern); - cairo_set_operator(m_c, CAIRO_OPERATOR_DEST); - cairo_translate(m_c, dx, 0.0); - cairo_fill(m_c); - cairo_pattern_destroy(pattern); - } - restore(); - } - - auto fontextents = f->extents(); - f->render(subset, x + dx, - y + dy - (extents.height / 2.0 + extents.y_bearing + fontextents.descent) + f->offset()); - position(&x, nullptr); - x += dx; - y += dy; + *this << t.bg; + cairo_set_operator(m_c, static_cast(t.bg_operator)); + cairo_rectangle(m_c, t.bg_rect.x, t.bg_rect.y, t.bg_rect.w + extents.x_advance, t.bg_rect.h); + cairo_fill(m_c); } - restore(); + restore(true); + + // Render subset + auto fontextents = f->extents(); + f->render(subset, x, y - (extents.height / 2.0 + extents.y_bearing + fontextents.descent) + f->offset()); + + // Get updated position + position(&x, nullptr); + + // Increase position + *t.x_advance += extents.x_advance; + *t.y_advance += extents.y_advance; chars.erase(chars.begin(), end); break; @@ -227,7 +216,7 @@ namespace cairo { continue; } - char unicode[5]{'\0'}; + char unicode[6]{'\0'}; utils::ucs4_to_utf8(unicode, chars.begin()->codepoint); m_log.warn("Dropping unmatched character %s (U+%04x)", unicode, chars.begin()->codepoint); utf8.erase(chars.begin()->offset, chars.begin()->length); @@ -237,7 +226,7 @@ namespace cairo { chars.erase(chars.begin(), ++chars.begin()); } - *this << abspos{x, y}; + // *this << abspos{x, y}; return *this; } @@ -247,7 +236,7 @@ namespace cairo { return *this; } - context& save(bool save_point = true) { + context& save(bool save_point = false) { if (save_point) { m_points.emplace_front(make_pair(0.0, 0.0)); position(&m_points.front().first, &m_points.front().second); @@ -256,9 +245,9 @@ namespace cairo { return *this; } - context& restore(bool restore_point = true) { + context& restore(bool restore_point = false) { cairo_restore(m_c); - if (restore_point) { + if (restore_point && !m_points.empty()) { *this << abspos{m_points.front().first, m_points.front().first}; m_points.pop_front(); } @@ -284,9 +273,19 @@ namespace cairo { return *this; } + context& clear() { + cairo_save(m_c); + cairo_set_operator(m_c, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(m_c, 0.0, 0.0, 0.0, 0.0); + cairo_paint(m_c); + cairo_restore(m_c); + return *this; + } + context& clip(const rect& r) { *this << r; cairo_clip(m_c); + cairo_new_path(m_c); return *this; } diff --git a/include/cairo/font.hpp b/include/cairo/font.hpp index 4d49d40f..499eafe7 100644 --- a/include/cairo/font.hpp +++ b/include/cairo/font.hpp @@ -1,10 +1,9 @@ #pragma once #include -#include -#include -#include +#include "cairo/types.hpp" +#include "cairo/utils.hpp" #include "common.hpp" #include "errors.hpp" #include "settings.hpp" @@ -15,47 +14,10 @@ POLYBAR_NS namespace cairo { - namespace details { - /** - * @brief Global pointer to the Freetype library handler - */ - static FT_Library g_ftlib; - - /** - * @brief RAII wrapper used to access the underlying - * FT_Face of a scaled font face - */ - class ft_face_lock { - public: - explicit ft_face_lock(cairo_scaled_font_t* font) : m_font(font) { - m_face = cairo_ft_scaled_font_lock_face(m_font); - } - ~ft_face_lock() { - cairo_ft_scaled_font_unlock_face(m_font); - } - - operator FT_Face() const { - return m_face; - } - - private: - cairo_scaled_font_t* m_font; - FT_Face m_face; - }; - } - /** - * @brief Unicode character containing converted codepoint - * and details on where its position in the source string + * @brief Global pointer to the Freetype library handler */ - struct unicode_character { - explicit unicode_character() : codepoint(0), offset(0), length(0) {} - - unsigned long codepoint; - int offset; - int length; - }; - using unicode_charlist = std::list; + static FT_Library g_ftlib; /** * @brief Abstract font face @@ -76,8 +38,8 @@ namespace cairo { cairo_set_font_face(m_cairo, cairo_font_face_reference(m_font_face)); } - virtual size_t match(unicode_charlist& charlist) = 0; - virtual size_t render(const string& text, double x = 0.0, double y = 0.0, bool reverse = false) = 0; + virtual size_t match(utils::unicode_charlist& charlist) = 0; + virtual size_t render(const string& text, double x = 0.0, double y = 0.0) = 0; virtual void textwidth(const string& text, cairo_text_extents_t* extents) = 0; protected: @@ -109,7 +71,7 @@ namespace cairo { throw application_error(sstream() << "cairo_scaled_font_create(): " << cairo_status_to_string(status)); } - auto lock = make_unique(m_scaled); + auto lock = make_unique(m_scaled); auto face = static_cast(*lock); if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) == FT_Err_Ok) { @@ -166,8 +128,8 @@ namespace cairo { cairo_set_scaled_font(m_cairo, m_scaled); } - size_t match(unicode_charlist& charlist) override { - auto lock = make_unique(m_scaled); + size_t match(utils::unicode_charlist& charlist) override { + auto lock = make_unique(m_scaled); auto face = static_cast(*lock); size_t available_chars = 0; for (auto&& c : charlist) { @@ -181,16 +143,12 @@ namespace cairo { return available_chars; } - size_t render(const string& text, double x = 0.0, double y = 0.0, bool reverse = false) override { + size_t render(const string& text, double x = 0.0, double y = 0.0) override { cairo_glyph_t* glyphs{nullptr}; cairo_text_cluster_t* clusters{nullptr}; cairo_text_cluster_flags_t cf{}; int nglyphs = 0, nclusters = 0; - if (reverse) { - cf = CAIRO_TEXT_CLUSTER_FLAG_BACKWARD; - } - string utf8 = string(text); auto status = cairo_scaled_font_text_to_glyphs( m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf); @@ -222,12 +180,16 @@ namespace cairo { } if (bytes) { + // auto lock = make_unique(cairo_surface_get_device(cairo_get_target(m_cairo))); + // if (lock.get()) { + // cairo_glyph_path(m_cairo, glyphs, nglyphs); + // } + cairo_text_extents_t extents{}; cairo_scaled_font_glyph_extents(m_scaled, glyphs, nglyphs, &extents); cairo_show_text_glyphs(m_cairo, utf8.c_str(), utf8.size(), glyphs, nglyphs, clusters, nclusters, cf); - cairo_rel_move_to(m_cairo, extents.x_advance, 0.0); - // cairo_glyph_path(m_cairo, glyphs, nglyphs); - cairo_fill_preserve(m_cairo); + cairo_fill(m_cairo); + cairo_move_to(m_cairo, x + extents.x_advance, 0.0); } cairo_glyph_free(glyphs); @@ -276,12 +238,12 @@ namespace cairo { static bool fc_init{false}; if (!fc_init && !(fc_init = FcInit())) { throw application_error("Could not load fontconfig"); - } else if (FT_Init_FreeType(&details::g_ftlib) != FT_Err_Ok) { + } else if (FT_Init_FreeType(&g_ftlib) != FT_Err_Ok) { throw application_error("Could not load FreeType"); } static auto fc_cleanup = scope_util::make_exit_handler([] { - FT_Done_FreeType(details::g_ftlib); + FT_Done_FreeType(g_ftlib); FcFini(); }); diff --git a/include/cairo/surface.hpp b/include/cairo/surface.hpp index 51ffa13a..0ec7f4ee 100644 --- a/include/cairo/surface.hpp +++ b/include/cairo/surface.hpp @@ -4,11 +4,16 @@ #include "cairo/types.hpp" #include "common.hpp" +#include "errors.hpp" #include "utils/color.hpp" +#include "utils/string.hpp" POLYBAR_NS namespace cairo { + /** + * @brief Base surface + */ class surface { public: explicit surface(cairo_surface_t* s) : m_s(s) {} @@ -20,30 +25,51 @@ namespace cairo { return m_s; } - surface& flush() { + void flush() { cairo_surface_flush(m_s); - return *this; } - surface& dirty() { + void show(bool clear = true) { + if (clear) { + cairo_surface_show_page(m_s); + } else { + cairo_surface_copy_page(m_s); + } + } + + void dirty() { cairo_surface_mark_dirty(m_s); - return *this; } - surface& dirty(const rect& r) { + void dirty(const rect& r) { cairo_surface_mark_dirty_rectangle(m_s, r.x, r.y, r.w, r.h); - return *this; + } + + void write_png(const string& dst) { + auto status = cairo_surface_write_to_png(m_s, dst.c_str()); + if (status != CAIRO_STATUS_SUCCESS) { + throw application_error(sstream() << "cairo_surface_write_to_png(): " << cairo_status_to_string(status)); + } } protected: cairo_surface_t* m_s; }; + /** + * @brief Surface for xcb + */ class xcb_surface : public surface { public: explicit xcb_surface(xcb_connection_t* c, xcb_pixmap_t p, xcb_visualtype_t* v, int w, int h) : surface(cairo_xcb_surface_create(c, p, v, w, h)) {} + ~xcb_surface() override {} + + void set_drawable(xcb_drawable_t d, int w, int h) { + cairo_surface_flush(m_s); + cairo_xcb_surface_set_drawable(m_s, d, w, h); + } }; } diff --git a/include/cairo/types.hpp b/include/cairo/types.hpp index d5dccbd1..39d92381 100644 --- a/include/cairo/types.hpp +++ b/include/cairo/types.hpp @@ -7,6 +7,10 @@ POLYBAR_NS enum class alignment; namespace cairo { + struct point { + double x; + double y; + }; struct abspos { double x; double y; @@ -16,14 +20,12 @@ namespace cairo { double x; double y; }; - struct rect { double x; double y; double w; double h; }; - struct line { double x1; double y1; @@ -31,15 +33,21 @@ namespace cairo { double y2; double w; }; - + struct displace { + double x; + double y; + double w; + double h; + double dx; + double dy; + }; struct linear_gradient { - double x0; - double y0; double x1; double y1; + double x2; + double y2; vector steps; }; - struct rounded_corners { double x; double y; @@ -47,11 +55,15 @@ namespace cairo { double h; double radius; }; - struct textblock { alignment align; string contents; int fontindex; + unsigned int bg; + int bg_operator; + rect bg_rect; + double *x_advance; + double *y_advance; }; } diff --git a/include/cairo/utils.hpp b/include/cairo/utils.hpp index 6a2fbf38..33b74892 100644 --- a/include/cairo/utils.hpp +++ b/include/cairo/utils.hpp @@ -1,9 +1,9 @@ #pragma once #include -#include +#include +#include -#include "cairo/font.hpp" #include "common.hpp" #include "utils/string.hpp" @@ -11,6 +11,66 @@ POLYBAR_NS namespace cairo { namespace utils { + /** + * @brief RAII wrapper used acquire cairo_device_t + */ + class device_lock { + public: + explicit device_lock(cairo_device_t* device) { + auto status = cairo_device_acquire(device); + if (status == CAIRO_STATUS_SUCCESS) { + m_device = device; + } + } + ~device_lock() { + cairo_device_release(m_device); + } + operator bool() const { + return m_device != nullptr; + } + operator cairo_device_t*() const { + return m_device; + } + + private: + cairo_device_t* m_device{nullptr}; + }; + + /** + * @brief RAII wrapper used to access the underlying + * FT_Face of a scaled font face + */ + class ft_face_lock { + public: + explicit ft_face_lock(cairo_scaled_font_t* font) : m_font(font) { + m_face = cairo_ft_scaled_font_lock_face(m_font); + } + ~ft_face_lock() { + cairo_ft_scaled_font_unlock_face(m_font); + } + + operator FT_Face() const { + return m_face; + } + + private: + cairo_scaled_font_t* m_font; + FT_Face m_face; + }; + + /** + * @brief Unicode character containing converted codepoint + * and details on where its position in the source string + */ + struct unicode_character { + explicit unicode_character() : codepoint(0), offset(0), length(0) {} + + unsigned long codepoint; + int offset; + int length; + }; + using unicode_charlist = std::list; + /** * @see */ diff --git a/include/components/controller.hpp b/include/components/controller.hpp index e3b7ddea..1a616fd9 100644 --- a/include/components/controller.hpp +++ b/include/components/controller.hpp @@ -45,7 +45,7 @@ class controller : public signal_receiver&&); ~controller(); - bool run(bool writeback = false); + bool run(bool writeback, string snapshot_dst); bool enqueue(event&& evt); bool enqueue(string&& input_data); @@ -84,6 +84,11 @@ class controller : public signal_receiver m_process_events{false}; + /** + * @brief Destination path of generated snapshot + */ + string m_snapshot_dst; + /** * @brief Controls weather the output gets printed to stdout */ @@ -133,6 +138,11 @@ class controller : public signal_receiver m_threads; }; POLYBAR_NS_END diff --git a/include/components/renderer.hpp b/include/components/renderer.hpp index 8b97e0d1..7ddfbee0 100644 --- a/include/components/renderer.hpp +++ b/include/components/renderer.hpp @@ -21,8 +21,14 @@ class logger; using std::map; +struct alignment_block { + xcb_pixmap_t pixmap; + double x; + double y; +}; + class renderer - : public signal_receiver m_actions; + xcb_rectangle_t m_rect{0, 0, 0U, 0U}; + reserve_area m_cleararea{}; // bool m_autosize{false}; unique_ptr m_context; - unique_ptr m_surface; + unique_ptr m_surface; + map m_blocks; - cairo_operator_t m_compositing_background; - cairo_operator_t m_compositing_foreground; - cairo_operator_t m_compositing_overline; - cairo_operator_t m_compositing_underline; - cairo_operator_t m_compositing_borders; + cairo_operator_t m_comp_bg; + cairo_operator_t m_comp_fg; + cairo_operator_t m_comp_ol; + cairo_operator_t m_comp_ul; + cairo_operator_t m_comp_border; - alignment m_alignment{alignment::NONE}; - std::bitset<2> m_attributes; + alignment m_align; + std::bitset<3> m_attr; + int m_font; + unsigned int m_bg; + unsigned int m_fg; + unsigned int m_ol; + unsigned int m_ul; + vector m_actions; - int m_fontindex; - - unsigned int m_color_background; - unsigned int m_color_foreground; - unsigned int m_color_overline; - unsigned int m_color_underline; - - double m_x{0.0}; - double m_y{0.0}; + bool m_fixedcenter; + string m_snapshot_dst; }; POLYBAR_NS_END diff --git a/include/components/types.hpp b/include/components/types.hpp index dc55c1e2..e25f1869 100644 --- a/include/components/types.hpp +++ b/include/components/types.hpp @@ -39,17 +39,7 @@ enum class syntaxtag { u, // underline color }; -enum class mousebtn { - NONE = 0, - LEFT, - MIDDLE, - RIGHT, - SCROLL_UP, - SCROLL_DOWN, - DOUBLE_LEFT, - DOUBLE_MIDDLE, - DOUBLE_RIGHT -}; +enum class mousebtn { NONE = 0, LEFT, MIDDLE, RIGHT, SCROLL_UP, SCROLL_DOWN, DOUBLE_LEFT, DOUBLE_MIDDLE, DOUBLE_RIGHT }; enum class strut { LEFT = 0, diff --git a/include/events/signal.hpp b/include/events/signal.hpp index 6e8cd316..d0c6d0c7 100644 --- a/include/events/signal.hpp +++ b/include/events/signal.hpp @@ -111,6 +111,9 @@ namespace signals { struct unshade_window : public detail::base_signal { using base_type::base_type; }; + struct request_snapshot : public detail::value_signal { + using base_type::base_type; + }; } namespace ui_tray { diff --git a/include/events/signal_fwd.hpp b/include/events/signal_fwd.hpp index bae459f2..9d7a65cb 100644 --- a/include/events/signal_fwd.hpp +++ b/include/events/signal_fwd.hpp @@ -35,6 +35,7 @@ namespace signals { struct dim_window; struct shade_window; struct unshade_window; + struct request_snapshot; } namespace ui_tray { struct mapped_clients; diff --git a/src/components/bar.cpp b/src/components/bar.cpp index ec2eb2fa..2e5a3bc6 100644 --- a/src/components/bar.cpp +++ b/src/components/bar.cpp @@ -208,7 +208,7 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.foreground = parse_or_throw("foreground", color_util::hex(m_opts.foreground)); // Load over-/underline - auto line_color = m_conf.get(bs, "line-color", "#f00"s); + auto line_color = m_conf.get(bs, "line-color", "#ffff0000"s); auto line_size = m_conf.get(bs, "line-size", 0); m_opts.overline.size = m_conf.get(bs, "overline-size", line_size); @@ -217,7 +217,7 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.underline.color = parse_or_throw("underline-color", line_color); // Load border settings - auto border_color = m_conf.get(bs, "border-color", ""s); + auto border_color = m_conf.get(bs, "border-color", "#00000000"s); auto border_size = m_conf.get(bs, "border-size", 0); m_opts.borders.emplace(edge::TOP, border_settings{}); @@ -494,11 +494,13 @@ void bar::handle(const evt::destroy_notify& evt) { * _NET_WM_WINDOW_OPACITY atom value */ void bar::handle(const evt::enter_notify&) { +#if 0 #ifdef DEBUG_SHADED if (m_opts.origin == edge::TOP) { m_taskqueue->defer_unique("window-hover", 25ms, [&](size_t) { m_sig.emit(signals::ui::unshade_window{}); }); return; } +#endif #endif if (m_opts.dimmed) { @@ -518,11 +520,13 @@ void bar::handle(const evt::enter_notify&) { * _NET_WM_WINDOW_OPACITY atom value */ void bar::handle(const evt::leave_notify&) { +#if 0 #ifdef DEBUG_SHADED if (m_opts.origin == edge::TOP) { m_taskqueue->defer_unique("window-hover", 25ms, [&](size_t) { m_sig.emit(signals::ui::shade_window{}); }); return; } +#endif #endif if (!m_opts.dimmed) { diff --git a/src/components/builder.cpp b/src/components/builder.cpp index 91dd9ba4..42878fa7 100644 --- a/src/components/builder.cpp +++ b/src/components/builder.cpp @@ -47,10 +47,10 @@ string builder::flush() { if (m_tags[syntaxtag::u]) { underline_color_close(); } - if ((m_attributes >> static_cast(attribute::UNDERLINE)) & 1U) { + if ((m_attributes >> static_cast(attribute::UNDERLINE)) & 1) { underline_close(); } - if ((m_attributes >> static_cast(attribute::OVERLINE)) & 1U) { + if ((m_attributes >> static_cast(attribute::OVERLINE)) & 1) { overline_close(); } diff --git a/src/components/controller.cpp b/src/components/controller.cpp index 1342f7b9..64289ede 100644 --- a/src/components/controller.cpp +++ b/src/components/controller.cpp @@ -23,6 +23,8 @@ #include "x11/tray_manager.hpp" #include "x11/types.hpp" +#include + POLYBAR_NS array g_eventpipe{{-1, -1}}; @@ -37,6 +39,10 @@ void interrupt_handler(int signum) { } } +void handler(int signum) { + printf("atestin %i\n", signum); +} + /** * Build controller instance */ @@ -141,15 +147,25 @@ controller::~controller() { m_log.info("Deconstruction of %s took %lu ms.", module_name, cleanup_ms); } } + + m_log.trace("controller: Joining threads"); + for (auto&& t : m_threads) { + if (t.joinable()) { + t.join(); + } + } } /** * Run the main loop */ -bool controller::run(bool writeback) { +bool controller::run(bool writeback, string snapshot_dst) { m_log.info("Starting application"); assert(!m_connection.connection_has_error()); + m_writeback = writeback; + m_snapshot_dst = move(snapshot_dst); + m_sig.attach(this); size_t started_modules{0}; @@ -434,6 +450,7 @@ bool controller::process_update(bool force) { bool is_left = false; bool is_center = false; bool is_right = false; + bool is_first = true; if (block.first == alignment::LEFT) { is_left = true; @@ -448,7 +465,14 @@ bool controller::process_update(bool force) { continue; } - string module_contents{module->contents()}; + string module_contents; + + try { + module_contents = module->contents(); + } catch (const exception& err) { + m_log.err("Failed to get contents for \"%s\" (err: %s)", module->name(), err.what()); + } + if (module_contents.empty()) { continue; } @@ -461,12 +485,14 @@ bool controller::process_update(bool force) { block_contents += separator; } - if (!block_contents.empty() && !margin_left.empty() && !(is_left && module == block.second.front())) { + if (!block_contents.empty() && !margin_left.empty() && !(is_left && is_first)) { block_contents += margin_left; } block_contents.reserve(module_contents.size()); block_contents += module_contents; + + is_first = false; } if (block_contents.empty()) { @@ -558,6 +584,15 @@ bool controller::on(const signals::eventqueue::check_state&) { bool controller::on(const signals::ui::ready&) { m_process_events = true; enqueue(make_update_evt(true)); + + if (!m_snapshot_dst.empty()) { + m_threads.emplace_back(thread([&]{ + this_thread::sleep_for(3s); + m_sig.emit(signals::ui::request_snapshot{move(m_snapshot_dst)}); + enqueue(make_update_evt(true)); + })); + } + // let the event bubble return false; } diff --git a/src/components/renderer.cpp b/src/components/renderer.cpp index d7845d48..ba425e84 100644 --- a/src/components/renderer.cpp +++ b/src/components/renderer.cpp @@ -92,13 +92,13 @@ renderer::renderer( // clang-format on } - m_log.trace("renderer: Allocate window pixmap"); + m_log.trace("renderer: Allocate window pixmaps"); { m_pixmap = m_connection.generate_id(); m_connection.create_pixmap(m_depth, m_pixmap, m_window, m_bar.size.w, m_bar.size.h); } - m_log.trace("renderer: Allocate graphic context"); + m_log.trace("renderer: Allocate graphic contexts"); { unsigned int mask{0}; unsigned int value_list[32]{0}; @@ -110,10 +110,23 @@ renderer::renderer( m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list); } + m_log.trace("renderer: Allocate alignment blocks"); + { + const auto create_block = [&](alignment a) { + auto pid = m_connection.generate_id(); + m_connection.create_pixmap_checked(m_depth, pid, m_pixmap, m_bar.size.w, m_bar.size.h); + m_blocks.emplace(a, alignment_block{pid, 0.0, 0.0}); + }; + + create_block(alignment::LEFT); + create_block(alignment::CENTER); + create_block(alignment::RIGHT); + } + m_log.trace("renderer: Allocate cairo components"); { m_surface = make_unique(m_connection, m_pixmap, m_visual, m_bar.size.w, m_bar.size.h); - m_context = make_unique(*m_surface.get(), m_log); + m_context = make_unique(*m_surface, m_log); } m_log.trace("renderer: Load fonts"); @@ -138,16 +151,13 @@ renderer::renderer( } } - m_compositing_background = - cairo::utils::str2operator(m_conf.get("settings", "compositing-background", ""s), CAIRO_OPERATOR_SOURCE); - m_compositing_foreground = - cairo::utils::str2operator(m_conf.get("settings", "compositing-foreground", ""s), CAIRO_OPERATOR_OVER); - m_compositing_overline = - cairo::utils::str2operator(m_conf.get("settings", "compositing-overline", ""s), CAIRO_OPERATOR_OVER); - m_compositing_underline = - cairo::utils::str2operator(m_conf.get("settings", "compositing-underline", ""s), CAIRO_OPERATOR_OVER); - m_compositing_borders = - cairo::utils::str2operator(m_conf.get("settings", "compositing-border", ""s), CAIRO_OPERATOR_SOURCE); + m_comp_bg = cairo::utils::str2operator(m_conf.get("settings", "compositing-background", ""s), CAIRO_OPERATOR_SOURCE); + m_comp_fg = cairo::utils::str2operator(m_conf.get("settings", "compositing-foreground", ""s), CAIRO_OPERATOR_OVER); + m_comp_ol = cairo::utils::str2operator(m_conf.get("settings", "compositing-overline", ""s), CAIRO_OPERATOR_OVER); + m_comp_ul = cairo::utils::str2operator(m_conf.get("settings", "compositing-underline", ""s), CAIRO_OPERATOR_OVER); + m_comp_border = cairo::utils::str2operator(m_conf.get("settings", "compositing-border", ""s), CAIRO_OPERATOR_SOURCE); + + m_fixedcenter = m_conf.get(m_conf.section(), "fixed-center", true); } /** @@ -179,27 +189,22 @@ void renderer::begin() { // Reset state m_actions.clear(); - m_attributes.reset(); - m_alignment = alignment::NONE; + m_attr.reset(); + m_align = alignment::NONE; m_rect = m_bar.inner_area(); - m_x = 0.0; // Reset colors - m_color_background = 0; - m_color_foreground = m_bar.foreground; - m_color_underline = m_bar.underline.color; - m_color_overline = m_bar.overline.color; + m_bg = 0; + m_fg = m_bar.foreground; + m_ul = m_bar.underline.color; + m_ol = m_bar.overline.color; + + m_context->save(); // Clear canvas - m_context->save(); - *m_context << CAIRO_OPERATOR_SOURCE; - *m_context << rgba{0.0, 0.0, 0.0, 0.0}; - m_context->paint(); - m_context->restore(); + m_surface->set_drawable(m_pixmap, m_bar.size.w, m_bar.size.h); + m_context->clear(); - m_context->save(); - - fill_background(); fill_borders(); // clang-format off @@ -209,6 +214,8 @@ void renderer::begin() { static_cast(m_rect.width), static_cast(m_rect.height)}); // clang-format on + + fill_background(); } /** @@ -217,10 +224,30 @@ void renderer::begin() { void renderer::end() { m_log.trace_x("renderer: end"); - highlight_clickable_areas(); + for (auto&& b : m_blocks) { + if (block_x(b.first) + block_w(b.first) > (m_rect.width)) { + double x = m_rect.x + block_x(b.first) + block_w(b.first); + if ((x -= x - m_rect.x - m_rect.width + block_x(b.first)) > 0.0) { + // clang-format off + rgba bg1{m_bar.background}; bg1.a = 0.0; + rgba bg2{m_bar.background}; bg2.a = 1.0; + // clang-format on + + m_surface->set_drawable(b.second.pixmap, m_bar.size.w, m_bar.size.h); + m_context->save(); + *m_context << cairo::linear_gradient{x - 40.0, 0.0, x, 0.0, {bg1, bg2}}; + m_context->paint(); + m_context->restore(); + } + } + } + + for (auto&& a : m_actions) { + a.start_x += block_x(a.align) + m_rect.x; + a.end_x += block_x(a.align) + m_rect.x; + } m_context->restore(); - m_surface->flush(); flush(); } @@ -231,6 +258,31 @@ void renderer::end() { void renderer::flush() { m_log.trace_x("renderer: flush"); + m_surface->set_drawable(m_pixmap, m_bar.size.w, m_bar.size.h); + m_surface->flush(); + + for (auto&& b : m_blocks) { + double x = m_rect.x + block_x(b.first); + double y = m_rect.y + block_y(b.first); + double w = m_rect.x + m_rect.width - x; + double h = m_rect.y + m_rect.height - y; + + if (w > 0.0) { + m_log.trace_x("renderer: copy alignment block (x=%.0f y=%.0f w=%.0f h=%.0f)", x, y, w, h); + m_connection.copy_area(b.second.pixmap, m_pixmap, m_gcontext, m_rect.x, m_rect.y, x, y, w, h); + m_connection.flush(); + } else { + m_log.trace_x("renderer: ignoring empty alignment block"); + continue; + } + } + + m_surface->dirty(); + m_surface->flush(); + + highlight_clickable_areas(); + +#if 0 #ifdef DEBUG_SHADED if (m_bar.shaded && m_bar.origin == edge::TOP) { m_log.trace_x( @@ -246,11 +298,54 @@ void renderer::flush() { m_connection.flush(); return; } +#endif #endif - m_log.trace_x("renderer: copy pixmap (geom=%dx%d+%d+%d)", m_rect.width, m_rect.height, m_rect.x, m_rect.y); m_connection.copy_area(m_pixmap, m_window, m_gcontext, 0, 0, 0, 0, m_bar.size.w, m_bar.size.h); m_connection.flush(); + + if (!m_snapshot_dst.empty()) { + try { + m_surface->write_png(m_snapshot_dst); + m_log.info("Successfully wrote %s", m_snapshot_dst); + } catch (const exception& err) { + m_log.err("Failed to write snapshot (err: %s)", err.what()); + } + m_snapshot_dst.clear(); + } +} + +/** + * Get x position of block for given alignment + */ +double renderer::block_x(alignment a) const { + switch (a) { + case alignment::CENTER: + if (!m_fixedcenter || m_rect.width / 2.0 + block_w(a) / 2.0 > m_rect.width - block_w(alignment::RIGHT)) { + return std::max((m_rect.width - block_w(alignment::RIGHT) + block_w(alignment::LEFT)) / 2.0 - block_w(a) / 2.0, + block_w(alignment::LEFT)); + } else { + return std::max(m_rect.width / 2.0 - block_w(a) / 2.0, block_w(alignment::LEFT)); + } + case alignment::RIGHT: + return std::max(m_rect.width - block_w(a), block_x(alignment::CENTER) + block_w(alignment::CENTER)); + default: + return 0.0; + } +} + +/** + * Get y position of block for given alignment + */ +double renderer::block_y(alignment) const { + return 0.0; +} + +/** + * Get block width for given alignment + */ +double renderer::block_w(alignment a) const { + return m_blocks.at(a).x; } /** @@ -293,7 +388,7 @@ void renderer::reserve_space(edge side, unsigned int w) { */ void renderer::fill_background() { m_context->save(); - *m_context << m_compositing_background; + *m_context << m_comp_bg; if (m_bar.radius != 0.0) { // clang-format off @@ -326,11 +421,11 @@ void renderer::fill_background() { * Fill overline color */ void renderer::fill_overline(double x, double w) { - if (m_bar.overline.size && m_attributes.test(static_cast(attribute::OVERLINE))) { + if (m_bar.overline.size && m_attr.test(static_cast(attribute::OVERLINE))) { m_log.trace_x("renderer: overline(x=%f, w=%f)", x, w); m_context->save(); - *m_context << m_compositing_overline; - *m_context << m_color_overline; + *m_context << m_comp_ol; + *m_context << m_ol; *m_context << cairo::rect{x, static_cast(m_rect.y), w, static_cast(m_bar.overline.size)}; m_context->fill(); m_context->restore(); @@ -341,11 +436,11 @@ void renderer::fill_overline(double x, double w) { * Fill underline color */ void renderer::fill_underline(double x, double w) { - if (m_bar.underline.size && m_attributes.test(static_cast(attribute::UNDERLINE))) { + if (m_bar.underline.size && m_attr.test(static_cast(attribute::UNDERLINE))) { m_log.trace_x("renderer: underline(x=%f, w=%f)", x, w); m_context->save(); - *m_context << m_compositing_underline; - *m_context << m_color_underline; + *m_context << m_comp_ul; + *m_context << m_ul; *m_context << cairo::rect{x, static_cast(m_rect.y + m_rect.height - m_bar.underline.size), w, static_cast(m_bar.underline.size)}; m_context->fill(); @@ -358,7 +453,7 @@ void renderer::fill_underline(double x, double w) { */ void renderer::fill_borders() { m_context->save(); - *m_context << m_compositing_borders; + *m_context << m_comp_border; cairo::rect top{0.0, 0.0, 0.0, 0.0}; top.x += m_bar.borders.at(edge::LEFT).size; @@ -397,54 +492,44 @@ void renderer::fill_borders() { void renderer::draw_text(const string& contents) { m_log.trace_x("renderer: text(%s)", contents.c_str()); - cairo::abspos origin{static_cast(m_rect.x), static_cast(m_rect.y)}; - origin.y += m_rect.height / 2.0; + cairo::abspos origin{}; + origin.x = m_rect.x + m_blocks[m_align].x; + origin.y = m_rect.y + m_rect.height / 2.0; - if (m_alignment == alignment::CENTER) { - origin.x += m_rect.width / 2.0; - // adjust_clickable_areas(extents.width / 2.0); - } else if (m_alignment == alignment::RIGHT) { - origin.x += m_rect.width; - // adjust_clickable_areas(extents.width); - } else { - origin.x += m_x; + cairo::textblock block{}; + block.align = m_align; + block.contents = contents; + block.fontindex = m_font; + block.bg = 0; + block.bg_rect = cairo::rect{0.0, 0.0, 0.0, 0.0}; + block.bg_operator = 0; + block.x_advance = &m_blocks[m_align].x; + block.y_advance = &m_blocks[m_align].y; + + if (m_bg && m_bg != m_bar.background) { + block.bg = m_bg; + block.bg_operator = m_comp_bg; + block.bg_rect.x = m_rect.x + m_blocks[m_align].x; + block.bg_rect.y = m_rect.y; + block.bg_rect.h = m_rect.height; } - // if (m_color_background && m_color_background != m_bar.background) { - // m_context->save(); - // *m_context << m_color_background; - // *m_context << m_compositing_background; - // *m_context << cairo::rect{origin.x, origin.y, extents.width, static_cast(m_rect.height)}; - // m_context->fill(); - // m_context->restore(); - // } - m_context->save(); *m_context << origin; - *m_context << m_compositing_foreground; - *m_context << m_color_foreground; - *m_context << cairo::textblock{m_alignment, contents, m_fontindex}; - m_context->position(&m_x, &m_y); + *m_context << m_comp_fg; + *m_context << m_fg; + *m_context << block; m_context->restore(); - fill_underline(origin.x, m_x - origin.x); - fill_overline(origin.x, m_x - origin.x); -} - -/** - * Move clickable areas position by given delta - */ -void renderer::adjust_clickable_areas(double delta) { - for (auto&& action : m_actions) { - if (!action.active && action.align == m_alignment) { - action.start_x -= delta; - action.end_x -= delta; - } + double dx = m_rect.x + m_blocks[m_align].x - origin.x; + if (dx > 0.0) { + fill_underline(origin.x, dx); + fill_overline(origin.x, dx); } } /** - * Draw boxes at the location of each created action block + * Colorize the bounding box of created action blocks */ void renderer::highlight_clickable_areas() { #ifdef DEBUG_HINTS @@ -452,128 +537,132 @@ void renderer::highlight_clickable_areas() { for (auto&& action : m_actions) { if (!action.active) { int n = hint_num.find(action.align)->second++; - double x = action.start_x + n * DEBUG_HINTS_OFFSET_X; - double y = m_bar.pos.y + m_rect.y + n * DEBUG_HINTS_OFFSET_Y; + double x = action.start_x; + double y = m_rect.y; double w = action.width(); double h = m_rect.height; m_context->save(); - *m_context << CAIRO_OPERATOR_OVERLAY << (n % 2 ? 0x55FF0000 : 0x5500FF00); + *m_context << CAIRO_OPERATOR_DIFFERENCE << (n % 2 ? 0xFF00FF00 : 0xFFFF0000); *m_context << cairo::rect{x, y, w, h}; m_context->fill(); m_context->restore(); } } + m_surface->flush(); #endif } +bool renderer::on(const signals::ui::request_snapshot& evt) { + m_snapshot_dst = evt.cast(); + return true; +} + bool renderer::on(const signals::parser::change_background& evt) { const unsigned int color{evt.cast()}; - if (color != m_color_background) { - m_color_background = color; + if (color != m_bg) { + m_log.trace_x("renderer: change_background(#%08x)", color); + m_bg = color; } return true; } bool renderer::on(const signals::parser::change_foreground& evt) { const unsigned int color{evt.cast()}; - if (color != m_color_foreground) { - m_color_foreground = color; + if (color != m_fg) { + m_log.trace_x("renderer: change_foreground(#%08x)", color); + m_fg = color; } return true; } bool renderer::on(const signals::parser::change_underline& evt) { const unsigned int color{evt.cast()}; - if (color != m_color_underline) { - m_color_underline = color; + if (color != m_ul) { + m_log.trace_x("renderer: change_underline(#%08x)", color); + m_ul = color; } return true; } bool renderer::on(const signals::parser::change_overline& evt) { const unsigned int color{evt.cast()}; - if (color != m_color_overline) { - m_color_overline = color; + if (color != m_ol) { + m_log.trace_x("renderer: change_overline(#%08x)", color); + m_ol = color; } return true; } bool renderer::on(const signals::parser::change_font& evt) { - m_fontindex = evt.cast(); + const int font{evt.cast()}; + if (font != m_font) { + m_log.trace_x("renderer: change_font(%i)", font); + m_font = font; + } return true; } bool renderer::on(const signals::parser::change_alignment& evt) { auto align = static_cast(evt.cast()); - if (align != m_alignment) { - m_alignment = align; - m_x = 0.0; + if (align != m_align) { + m_log.trace_x("renderer: change_alignment(%i)", static_cast(align)); + m_align = align; + m_blocks[m_align].x = 0.0; + m_blocks[m_align].y = 0.0; + m_surface->set_drawable(m_blocks.at(m_align).pixmap, m_bar.size.w, m_bar.size.h); + m_context->clear(); + fill_background(); } return true; } bool renderer::on(const signals::parser::offset_pixel& evt) { - (void)evt; - // m_x += evt.cast(); + m_log.trace_x("renderer: offset_pixel(%f)", evt.cast()); + m_blocks[m_align].x += evt.cast(); return true; } bool renderer::on(const signals::parser::attribute_set& evt) { - m_attributes.set(static_cast(evt.cast()), true); + m_log.trace_x("renderer: attribute_set(%i)", static_cast(evt.cast())); + m_attr.set(static_cast(evt.cast()), true); return true; } bool renderer::on(const signals::parser::attribute_unset& evt) { - m_attributes.set(static_cast(evt.cast()), false); + m_log.trace_x("renderer: attribute_unset(%i)", static_cast(evt.cast())); + m_attr.set(static_cast(evt.cast()), false); return true; } bool renderer::on(const signals::parser::attribute_toggle& evt) { - m_attributes.flip(static_cast(evt.cast())); + m_log.trace_x("renderer: attribute_toggle(%i)", static_cast(evt.cast())); + m_attr.flip(static_cast(evt.cast())); return true; } bool renderer::on(const signals::parser::action_begin& evt) { - (void)evt; - // auto a = evt.cast(); - // action_block action{}; - // action.button = a.button == mousebtn::NONE ? mousebtn::LEFT : a.button; - // action.align = m_alignment; - // action.start_x = m_x; - // action.command = string_util::replace_all(a.command, ":", "\\:"); - // action.active = true; - // m_actions.emplace_back(action); + auto a = evt.cast(); + m_log.trace_x("renderer: action_begin(btn=%i, command=%s)", static_cast(a.button), a.command); + action_block action{}; + action.button = a.button == mousebtn::NONE ? mousebtn::LEFT : a.button; + action.align = m_align; + action.start_x = m_blocks.at(m_align).x; + action.command = string_util::replace_all(a.command, ":", "\\:"); + action.active = true; + m_actions.emplace_back(action); return true; } bool renderer::on(const signals::parser::action_end& evt) { - (void)evt; - // auto btn = evt.cast(); - // int clickable_width = 0; - // for (auto action = m_actions.rbegin(); action != m_actions.rend(); action++) { - // if (action->active && action->align == m_alignment && action->button == btn) { - // switch (action->align) { - // case alignment::NONE: - // break; - // case alignment::LEFT: - // action->end_x = m_x; - // break; - // case alignment::CENTER: - // clickable_width = m_x - action->start_x; - // action->start_x = m_rect.width / 2 - clickable_width / 2 + action->start_x / 2; - // action->end_x = action->start_x + clickable_width; - // break; - // case alignment::RIGHT: - // action->start_x = m_rect.width - m_x + action->start_x; - // action->end_x = m_rect.width; - // break; - // } - // action->start_x += m_rect.x; - // action->end_x += m_rect.x; - // action->active = false; - // } - // } + auto btn = evt.cast(); + m_log.trace_x("renderer: action_end(btn=%i)", static_cast(btn)); + for (auto action = m_actions.rbegin(); action != m_actions.rend(); action++) { + if (action->active && action->align == m_align && action->button == btn) { + action->end_x = m_blocks.at(action->align).x; + action->active = false; + } + } return true; } diff --git a/src/main.cpp b/src/main.cpp index a38ddeab..2f73c249 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -146,7 +146,7 @@ int main(int argc, char** argv) { auto ctrl = controller::make(move(ipc), move(config_watch)); - if (!ctrl->run(cli->has("stdout"))) { + if (!ctrl->run(cli->has("stdout"), cli->get("png"))) { reload = true; } } catch (const exception& err) {