#include "components/renderer.hpp" #include #include "cairo/context.hpp" #include "components/config.hpp" #include "events/signal.hpp" #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" #include "x11/winspec.hpp" POLYBAR_NS static constexpr double BLOCK_GAP{20.0}; /** * Create instance */ renderer::make_type renderer::make(const bar_settings& bar, tags::action_context& action_ctxt, const config& conf) { // clang-format off return std::make_unique( connection::make(), signal_emitter::make(), conf, logger::make(), forward(bar), background_manager::make(), action_ctxt); // clang-format on } /** * Construct renderer instance */ renderer::renderer(connection& conn, signal_emitter& sig, const config& conf, const logger& logger, const bar_settings& bar, background_manager& background, tags::action_context& action_ctxt) : renderer_interface(action_ctxt) , m_connection(conn) , m_sig(sig) , m_conf(conf) , m_log(logger) , m_bar(forward(bar)) , m_rect(m_bar.inner_area()) { m_sig.attach(this); m_log.trace("renderer: Get TrueColor visual"); if ((m_visual = m_connection.visual_type(XCB_VISUAL_CLASS_TRUE_COLOR, 32)) != nullptr) { m_depth = 32; } else if ((m_visual = m_connection.visual_type(XCB_VISUAL_CLASS_TRUE_COLOR, 24)) != nullptr) { m_depth = 24; } else { throw application_error("Could not find a 24 or 32-bit TrueColor visual"); } m_log.info("renderer: Using %d-bit TrueColor visual: 0x%x", m_depth, m_visual->visual_id); m_log.trace("renderer: Allocate colormap"); m_colormap = m_connection.generate_id(); m_connection.create_colormap(XCB_COLORMAP_ALLOC_NONE, m_colormap, m_connection.root(), m_visual->visual_id); m_log.trace("renderer: Allocate output window"); // clang-format off m_window = winspec(m_connection) << cw_size(m_bar.size) << cw_pos(m_bar.pos) << cw_depth(m_depth) << cw_visual(m_visual->visual_id) << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT) << cw_params_back_pixel(0) << cw_params_border_pixel(0) << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED) << cw_params_colormap(m_colormap) << cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE |XCB_EVENT_MASK_EXPOSURE |XCB_EVENT_MASK_BUTTON_PRESS) << cw_params_override_redirect(m_bar.override_redirect) << cw_flush(true); // clang-format on 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); uint32_t configure_mask = 0; std::array configure_values{}; xcb_params_cw_t configure_params{}; XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, back_pixmap, m_pixmap); connection::pack_values(configure_mask, &configure_params, configure_values); m_connection.change_window_attributes_checked(m_window, configure_mask, configure_values.data()); } m_log.trace("renderer: Allocate graphic contexts"); { uint32_t mask{0}; std::array value_list{}; xcb_params_gc_t params{}; XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, m_bar.foreground); XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0); connection::pack_values(mask, ¶ms, value_list); m_gcontext = m_connection.generate_id(); m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list.data()); } m_log.trace("renderer: Allocate alignment blocks"); { 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"); { m_surface = make_unique(m_connection, m_pixmap, m_visual, m_bar.size.w, m_bar.size.h); m_context = make_unique(*m_surface, m_log); } m_log.trace("renderer: Load fonts"); { auto fonts = m_conf.get_list(m_conf.section(), "font", {}); if (fonts.empty()) { m_log.warn("No fonts specified, using fallback font \"fixed\""); fonts.emplace_back("fixed"); } for (const auto& f : fonts) { int offset{0}; string pattern{f}; size_t pos = pattern.rfind(';'); if (pos != string::npos) { offset = std::strtol(pattern.substr(pos + 1).c_str(), nullptr, 10); pattern.erase(pos); } 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); } } m_pseudo_transparency = m_conf.get("settings", "pseudo-transparency", m_pseudo_transparency); if (m_pseudo_transparency) { m_log.trace("Activate root background manager"); m_background = background.observe(m_bar.outer_area(false), m_window); } m_comp_bg = m_conf.get("settings", "compositing-background", m_comp_bg); m_comp_fg = m_conf.get("settings", "compositing-foreground", m_comp_fg); m_comp_ol = m_conf.get("settings", "compositing-overline", m_comp_ol); m_comp_ul = m_conf.get("settings", "compositing-underline", m_comp_ul); m_comp_border = m_conf.get("settings", "compositing-border", m_comp_border); m_fixedcenter = m_conf.get(m_conf.section(), "fixed-center", true); } /** * Deconstruct instance */ renderer::~renderer() { m_sig.detach(this); } /** * Get bar window */ xcb_window_t renderer::window() const { return m_window; } /** * Get the bar window visual */ xcb_visualtype_t* renderer::visual() const { return m_visual; } /** * Get the bar window depth */ int renderer::depth() const { return m_depth; } /** * Begin render routine */ void renderer::begin(xcb_rectangle_t rect) { m_log.trace_x("renderer: begin (geom=%ix%i+%i+%i)", rect.width, rect.height, rect.x, rect.y); // Reset state m_rect = rect; m_align = alignment::NONE; // Clear canvas m_context->save(); m_context->clear(); // when pseudo-transparency is requested, render the bar into a new layer // that will later be composited against the desktop background if (m_pseudo_transparency) { m_context->push(); } // Create corner mask if (m_bar.radius && m_cornermask == nullptr) { m_context->save(); m_context->push(); // clang-format off *m_context << cairo::rounded_corners{ static_cast(m_rect.x), static_cast(m_rect.y), static_cast(m_rect.width), static_cast(m_rect.height), m_bar.radius}; // clang-format on *m_context << rgba{0xffffffff}; m_context->fill(); m_context->pop(&m_cornermask); m_context->restore(); } fill_borders(); // clang-format off m_context->clip(cairo::rect{ static_cast(m_rect.x), static_cast(m_rect.y), static_cast(m_rect.width), static_cast(m_rect.height)}); // clang-format on } /** * End render routine */ void renderer::end() { m_log.trace_x("renderer: end"); if (m_align != alignment::NONE) { m_log.trace_x("renderer: pop(%i)", static_cast(m_align)); m_context->pop(&m_blocks[m_align].pattern); // Capture the concatenated block contents // so that it can be masked with the corner pattern m_context->push(); // Draw the background on the new layer to make up for // the areas not covered by the alignment blocks fill_background(); for (auto&& b : m_blocks) { flush(b.first); } cairo_pattern_t* blockcontents{}; m_context->pop(&blockcontents); if (m_cornermask != nullptr) { *m_context << blockcontents; m_context->mask(m_cornermask); } else { *m_context << blockcontents; m_context->paint(); } m_context->destroy(&blockcontents); } else { fill_background(); } // For pseudo-transparency, capture the contents of the rendered bar and // composite it against the desktop wallpaper. This way transparent parts of // 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 auto root_bg = m_background->get_surface(); if (root_bg != nullptr) { m_log.trace_x("renderer: root background"); *m_context << *root_bg; m_context->paint(); *m_context << CAIRO_OPERATOR_OVER; } *m_context << barcontents; m_context->paint(); m_context->destroy(&barcontents); } m_context->restore(); m_surface->flush(); flush(); m_sig.emit(signals::ui::changed{}); } /** * Flush contents of given alignment block */ void renderer::flush(alignment a) { if (m_blocks[a].pattern == nullptr) { return; } m_context->save(); double x = static_cast(block_x(a) + 0.5); double y = static_cast(block_y(a) + 0.5); double w = static_cast(block_w(a) + 0.5); double h = static_cast(block_h(a) + 0.5); double xw = x + w; bool fits{xw <= m_rect.width}; m_log.trace("renderer: flush(%i geom=%gx%g+%g+%g, falloff=%i)", static_cast(a), w, h, x, y, !fits); // Set block shape *m_context << cairo::abspos{0.0, 0.0}; *m_context << cairo::rect{m_rect.x + x, m_rect.y + y, w, h}; // Restrict drawing to the block rectangle m_context->clip(true); // Clear the area covered by the block m_context->clear(); *m_context << cairo::translate{x, 0.0}; *m_context << m_blocks[a].pattern; m_context->paint(); *m_context << cairo::abspos{0.0, 0.0}; m_context->destroy(&m_blocks[a].pattern); m_context->restore(); if (!fits) { // Paint falloff gradient at the end of the visible block // to indicate that the content expands past the canvas /* * How many pixels are hidden */ double overflow = xw - m_rect.width; double visible_width = w - overflow; /* * Width of the falloff gradient. Depends on how much of the block is hidden */ double fsize = std::max(5.0, std::min(std::abs(overflow), 30.0)); m_log.trace("renderer: Drawing falloff (pos=%g, size=%g, overflow=%g)", visible_width - fsize, fsize, overflow); m_context->save(); *m_context << cairo::translate{(double)m_rect.x, (double)m_rect.y}; *m_context << cairo::abspos{0.0, 0.0}; *m_context << cairo::rect{x + visible_width - fsize, y, fsize, h}; m_context->clip(true); *m_context << cairo::linear_gradient{ x + visible_width - fsize, y, x + visible_width, y, {rgba{0x00000000}, rgba{0xFF000000}}}; m_context->paint(0.25); m_context->restore(); } } /** * Flush pixmap contents onto the target window */ void renderer::flush() { m_log.trace_x("renderer: flush"); highlight_clickable_areas(); m_surface->flush(); // Clear entire window so that the new pixmap is shown m_connection.clear_area(0, m_window, 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.notice("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 * * The position is relative to m_rect.x (the left side of the bar w/o borders and tray) */ double renderer::block_x(alignment a) const { switch (a) { case alignment::CENTER: { // The leftmost x position this block can start at double min_pos = block_w(alignment::LEFT); if (min_pos != 0) { min_pos += BLOCK_GAP; } double right_width = block_w(alignment::RIGHT); /* * The rightmost x position this block can end at * * We can't use block_x(alignment::RIGHT) because that would lead to infinite recursion */ double max_pos = m_rect.width - right_width; if (right_width != 0) { max_pos -= BLOCK_GAP; } /* * x position of the center of this block * * With fixed-center this will be the center of the bar unless it is pushed to the left by a large right block * Without fixed-center this will be the middle between the end of the left and the start of the right block. */ double base_pos{0.0}; if (m_fixedcenter) { /* * This is in the middle of the *bar*. Not just the middle of m_rect because this way we need to account for the * tray. * * The resulting position is relative to the very left of the bar (including border and tray), so we need to * compensate for that by subtracting m_rect.x */ base_pos = m_bar.size.w / 2.0 - m_rect.x; /* * The center block can be moved to the left if the right block is too large */ base_pos = std::min(base_pos, max_pos - block_w(a) / 2.0); } else { base_pos = (min_pos + max_pos) / 2.0; } /* * The left block always has priority (even with fixed-center = true) */ return std::max(base_pos - block_w(a) / 2.0, min_pos); } case alignment::RIGHT: { /* * The block immediately to the left of this block * * Generally the center block unless it is empty. */ alignment left_barrier = alignment::CENTER; if (block_w(alignment::CENTER) == 0) { left_barrier = alignment::LEFT; } // The minimum x position this block can start at double min_pos = block_x(left_barrier) + block_w(left_barrier); if (block_w(left_barrier) != 0) { min_pos += BLOCK_GAP; } return std::max(m_rect.width - block_w(a), min_pos); } 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).width; } /** * Get block height for given alignment */ 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 */ void renderer::fill_background() { m_context->save(); *m_context << m_comp_bg; if (!m_bar.background_steps.empty()) { m_log.trace_x("renderer: gradient background (steps=%lu)", m_bar.background_steps.size()); *m_context << cairo::linear_gradient{0.0, 0.0 + m_rect.y, 0.0, 0.0 + m_rect.height, m_bar.background_steps}; } else { m_log.trace_x("renderer: solid background #%08x", m_bar.background); *m_context << m_bar.background; } m_context->paint(); m_context->restore(); } /** * Fill overline color */ void renderer::fill_overline(rgba color, double x, double w) { if (m_bar.overline.size) { m_log.trace_x("renderer: overline(x=%f, w=%f)", x, w); m_context->save(); *m_context << m_comp_ol; *m_context << color; *m_context << cairo::rect{x, static_cast(m_rect.y), w, static_cast(m_bar.overline.size)}; m_context->fill(); m_context->restore(); } } /** * Fill underline color */ void renderer::fill_underline(rgba color, double x, double w) { if (m_bar.underline.size) { m_log.trace_x("renderer: underline(x=%f, w=%f)", x, w); m_context->save(); *m_context << m_comp_ul; *m_context << color; *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(); m_context->restore(); } } /** * Fill border colors */ void renderer::fill_borders() { m_context->save(); *m_context << m_comp_border; // Draw round border corners if (m_bar.radius.top_left) { cairo::circle_segment borderTL; borderTL.radius = m_bar.borders.at(edge::LEFT).size + m_bar.radius.top_left; borderTL.x = m_bar.borders.at(edge::LEFT).size + m_bar.radius.top_left; borderTL.y = m_bar.borders.at(edge::TOP).size + m_bar.radius.top_left; borderTL.angle_from = 180; borderTL.angle_to = 270; borderTL.w = m_bar.borders.at(edge::LEFT).size; (*m_context << borderTL << m_bar.borders.at(edge::LEFT).color).fill(); } if (m_bar.radius.bottom_left) { cairo::circle_segment borderBL; borderBL.radius = m_bar.borders.at(edge::LEFT).size + m_bar.radius.bottom_left; borderBL.x = m_bar.borders.at(edge::LEFT).size + m_bar.radius.bottom_left; borderBL.y = m_bar.size.h - (m_bar.borders.at(edge::BOTTOM).size + m_bar.radius.bottom_left); borderBL.angle_from = 90; borderBL.angle_to = 180; borderBL.w = m_bar.borders.at(edge::LEFT).size; (*m_context << borderBL << m_bar.borders.at(edge::LEFT).color).fill(); } if (m_bar.radius.top_right) { cairo::circle_segment borderTR; borderTR.radius = m_bar.borders.at(edge::RIGHT).size + m_bar.radius.top_right; borderTR.x = m_bar.size.w - (m_bar.borders.at(edge::RIGHT).size + m_bar.radius.top_right); borderTR.y = m_bar.borders.at(edge::TOP).size + m_bar.radius.top_right; borderTR.angle_from = -90; borderTR.angle_to = 0; borderTR.w = m_bar.borders.at(edge::RIGHT).size; (*m_context << borderTR << m_bar.borders.at(edge::RIGHT).color).fill(); } if (m_bar.radius.bottom_right) { cairo::circle_segment borderBR; borderBR.radius = m_bar.borders.at(edge::RIGHT).size + m_bar.radius.bottom_right; borderBR.x = m_bar.size.w - (m_bar.borders.at(edge::RIGHT).size + m_bar.radius.bottom_right); borderBR.y = m_bar.size.h - (m_bar.borders.at(edge::BOTTOM).size + m_bar.radius.bottom_right); borderBR.angle_from = 0; borderBR.angle_to = 90; borderBR.w = m_bar.borders.at(edge::RIGHT).size; (*m_context << borderBR << m_bar.borders.at(edge::RIGHT).color).fill(); } // Draw straight horizontal / vertical borders if (m_bar.borders.at(edge::TOP).size) { cairo::rect top{0.0, 0.0, 0.0, 0.0}; top.x += m_bar.borders.at(edge::LEFT).size; top.w += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size; top.h += m_bar.borders.at(edge::TOP).size; if (m_bar.radius.top_left) { top.x += m_bar.radius.top_left; top.w -= m_bar.radius.top_left; } if (m_bar.radius.top_right) { top.w -= m_bar.radius.top_right; } m_log.trace_x("renderer: border T(%.0f, #%08x)", top.h, m_bar.borders.at(edge::TOP).color); (*m_context << top << m_bar.borders.at(edge::TOP).color).fill(); } if (m_bar.borders.at(edge::BOTTOM).size) { cairo::rect bottom{0.0, 0.0, 0.0, 0.0}; bottom.x += m_bar.borders.at(edge::LEFT).size; bottom.y += m_bar.size.h - m_bar.borders.at(edge::BOTTOM).size; bottom.w += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size; bottom.h += m_bar.borders.at(edge::BOTTOM).size; if (m_bar.radius.bottom_left) { bottom.x += m_bar.radius.bottom_left; bottom.w -= m_bar.radius.bottom_left; } if (m_bar.radius.bottom_right) { bottom.w -= m_bar.radius.bottom_right; } m_log.trace_x("renderer: border B(%.0f, #%08x)", bottom.h, m_bar.borders.at(edge::BOTTOM).color); (*m_context << bottom << m_bar.borders.at(edge::BOTTOM).color).fill(); } if (m_bar.borders.at(edge::LEFT).size) { cairo::rect left{0.0, 0.0, 0.0, 0.0}; left.w += m_bar.borders.at(edge::LEFT).size; left.h += m_bar.size.h; if (m_bar.radius.top_left) { left.y += m_bar.radius.top_left + m_bar.borders.at(edge::TOP).size; left.h -= m_bar.radius.top_left + m_bar.borders.at(edge::TOP).size; } if (m_bar.radius.bottom_left) { left.h -= m_bar.radius.bottom_left + m_bar.borders.at(edge::BOTTOM).size; } m_log.trace_x("renderer: border L(%.0f, #%08x)", left.w, m_bar.borders.at(edge::LEFT).color); (*m_context << left << m_bar.borders.at(edge::LEFT).color).fill(); } if (m_bar.borders.at(edge::RIGHT).size) { cairo::rect right{0.0, 0.0, 0.0, 0.0}; right.x += m_bar.size.w - m_bar.borders.at(edge::RIGHT).size; right.w += m_bar.borders.at(edge::RIGHT).size; right.h += m_bar.size.h; if (m_bar.radius.top_right) { right.y += m_bar.radius.top_right + m_bar.borders.at(edge::TOP).size; right.h -= m_bar.radius.top_right + m_bar.borders.at(edge::TOP).size; } if (m_bar.radius.bottom_right) { right.h -= m_bar.radius.bottom_right + m_bar.borders.at(edge::BOTTOM).size; } m_log.trace_x("renderer: border R(%.0f, #%08x)", right.w, m_bar.borders.at(edge::RIGHT).color); (*m_context << right << m_bar.borders.at(edge::RIGHT).color).fill(); } m_context->restore(); } /** * Draw text contents */ void renderer::render_text(const tags::context& ctxt, const string&& contents) { assert(ctxt.get_alignment() != alignment::NONE && ctxt.get_alignment() == m_align); m_log.trace_x("renderer: text(%s)", contents.c_str()); cairo::abspos origin{}; 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 = &x_new; block.y_advance = &m_blocks[m_align].y; block.bg_rect = cairo::rect{0.0, 0.0, 0.0, 0.0}; rgba bg = ctxt.get_bg(); // Only draw text background if the color differs from // the background color of the bar itself // Note: this means that if the user explicitly set text // background color equal to background-0 it will be ignored if (bg != m_bar.background) { block.bg = bg; block.bg_operator = m_comp_bg; block.bg_rect.x = m_rect.x; block.bg_rect.y = m_rect.y; block.bg_rect.h = m_rect.height; } m_context->save(); *m_context << origin; *m_context << m_comp_fg; *m_context << ctxt.get_fg(); *m_context << block; m_context->restore(); 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); } if (ctxt.has_overline()) { fill_overline(ctxt.get_ol(), origin.x, dx); } } } void renderer::draw_offset(const tags::context& ctxt, rgba color, double x, double w) { if (w <= 0) { return; } if (color != m_bar.background) { m_log.trace_x("renderer: offset(x=%f, w=%f)", x, w); m_context->save(); *m_context << m_comp_bg; *m_context << color; *m_context << cairo::rect{ m_rect.x + x, static_cast(m_rect.y), w, static_cast(m_rect.y + m_rect.height)}; m_context->fill(); m_context->restore(); } if (ctxt.has_underline()) { fill_underline(ctxt.get_ul(), x, w); } if (ctxt.has_overline()) { fill_overline(ctxt.get_ol(), x, w); } } void renderer::render_offset(const tags::context& ctxt, const extent_val offset) { assert(ctxt.get_alignment() != alignment::NONE && ctxt.get_alignment() == m_align); 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(ctxt, bg, m_blocks[m_align].x, offset_width); increase_x(offset_width); } void renderer::change_alignment(const tags::context& ctxt) { auto align = ctxt.get_alignment(); assert(align != alignment::NONE); if (align != m_align) { m_log.trace_x("renderer: change_alignment(%i)", static_cast(align)); if (m_align != alignment::NONE) { m_log.trace_x("renderer: pop(%i)", static_cast(m_align)); m_context->pop(&m_blocks[m_align].pattern); } m_align = align; m_blocks[m_align].x = 0.0; m_blocks[m_align].y = 0.0; m_blocks[m_align].width = 0.; m_context->push(); m_log.trace_x("renderer: push(%i)", static_cast(m_align)); fill_background(); } } double renderer::get_x(const tags::context& ctxt) const { assert(ctxt.get_alignment() != alignment::NONE && ctxt.get_alignment() == m_align); return m_blocks.at(ctxt.get_alignment()).x; } double renderer::get_alignment_start(const alignment align) const { return block_x(align) + m_rect.x; } /** * Colorize the bounding box of created action blocks */ void renderer::highlight_clickable_areas() { #ifdef DEBUG_HINTS map hint_num{}; for (auto&& action : m_action_ctxt.get_blocks()) { int n = hint_num.find(action.align)->second++; 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_DIFFERENCE << (n % 2 ? rgba{0xFF00FF00} : rgba{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; } void renderer::apply_tray_position(const tags::context& context) { auto [alignment, pos] = context.get_relative_tray_position(); if (alignment != alignment::NONE) { int absolute_x = static_cast(block_x(alignment) + pos); m_sig.emit(signals::ui_tray::tray_pos_change{absolute_x}); } } POLYBAR_NS_END