#include "common.hh" #include #include #include #include "animation.hh" #include "args.hh" #include "blissful_monitor.hh" #include "cairo.hh" #include "fake_monitor.hh" #include "icecc.hh" #include "monitor.hh" #include "monmon.hh" #include "pango.hh" namespace { class IceccMonMon : public MonMon, virtual Monitor::Observer { public: IceccMonMon(std::shared_ptr const& looper, unsigned columns) : MonMon(looper), connected_(false), max_jobs_(0), jobs_(0), requests_(0), force_columns_(columns), layout_w_(0), layout_h_(0), pad_x_(0.0), pad_y_(0.0), margin_x_(0.0), margin_y_(0.0), columns_(0), box_width_(0.0), box_height_c_(0.0), all_dirty_(true) { } bool connect(Args const* args) override { std::unique_ptr monitor; #if FAKE_MONITOR monitor = FakeMonitor::create(looper_); #else monitor = Monitor::create(looper_); #endif monitor_ = BlissfulMonitor::create(looper_, std::move(monitor)); monitor_->add_observer(this); monitor_->connect(args->arg("network", ""), args->arg("scheduler", "")); return true; } protected: std::string title() const override { return "MonMon"; } void mark_all_dirty() override { all_dirty_ = true; } void width_changed() override { job_pattern_.reset(); layout_w_ = 0; layout_h_ = 0; all_dirty_ = true; } #if FAKE_MONITOR void do_toggle_fakes() override { monitor_->toggle_fakes(); } #endif private: class MachineAnimation; struct Machine : virtual Animator::AnimationObserver { uint32_t id; Monitor::Machine data; unsigned jobs; unsigned requests; double x; std::shared_ptr animation; pango::unique_layout name_layout; int name_layout_width; // pango units; -1 means not yet set bool name_dirty; bool dirty; // needs redraw this frame explicit Machine(uint32_t id) : id(id), jobs(0), requests(0), x(0.0), name_layout_width(-1), name_dirty(true), dirty(true) { } Machine(Machine const&) = delete; Machine& operator=(Machine const&) = delete; ~Machine() override { assert(!animation); } bool operator<(Machine const& machine) const { if (data.name < machine.data.name) return true; if (data.name > machine.data.name) return false; return id < machine.id; } void ticked(Animator*, Animation*, double value) override { x = value; dirty = true; } void stopped(Animator*, Animation*) override { x = static_cast(jobs) / data.max_jobs; animation.reset(); } }; class MachineAnimation : public Animation { public: explicit MachineAnimation(Machine* machine) : machine_(machine), last_(0.0) { } bool tick(double duration, double* value) override { auto target = static_cast(machine_->jobs) / machine_->data.max_jobs; if (duration == 0.0) { last_ = duration; *value = machine_->x; return machine_->x != target; } else { double diff = target - machine_->x; double max = (duration - last_) * .5; last_ = duration; if (fabs(diff) > max) { diff = diff < 0.0 ? -max : max; *value = machine_->x + diff; return true; } else { *value = target; return false; } } } private: Machine* machine_; double last_; }; void state(Monitor*, Monitor::State state) override { switch (state) { case Monitor::SEARCHING: connected_ = false; stop_all_animations(); machines_.clear(); by_id_.clear(); max_jobs_ = 0; jobs_ = 0; requests_ = 0; all_dirty_ = true; if (!wnd_) return; request_draw(); break; case Monitor::CONNECTED: connected_ = true; all_dirty_ = true; request_draw(); break; } } void stop_all_animations() override { if (!animator_) return; for (auto& machine : machines_) { if (machine->animation) animator_->stop(machine->animation.get()); } } void internal_quit() override { monitor_->disconnect(); } void update(Machine* machine) { auto const old_name = machine->data.name; auto const old_max_jobs = machine->data.max_jobs; machine->data = monitor_->machine(machine->id); if (machine->data.name != old_name) machine->name_dirty = true; if (old_max_jobs < machine->data.max_jobs) { max_jobs_ += machine->data.max_jobs - old_max_jobs; if (machine->jobs) animate(machine); } else if (old_max_jobs > machine->data.max_jobs) { max_jobs_ -= old_max_jobs - machine->data.max_jobs; if (machine->jobs) animate(machine); } } void draw_content(cairo_t* cairo, PangoLayout* /*layout*/, uint16_t w, uint16_t h, std::vector& dirty) override { if (w != layout_w_ || h != layout_h_) { pad_x_ = std::min(9.0, w / 20.0); pad_y_ = pad_x_; margin_x_ = std::min(7.5, w / 20.0); margin_y_ = margin_x_; columns_ = force_columns_ ? force_columns_ : std::max(1, w / h); box_width_ = (w - pad_x_) / columns_ - pad_x_; box_height_c_ = box_height_ + margin_y_ * 2; job_pattern_.reset(); layout_w_ = w; layout_h_ = h; } if (!job_pattern_) { job_pattern_.reset(cairo_pattern_create_linear(0.0, 0.0, box_width_, 0.0)); cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.000000, 0.000000, 0.000000, 0.000000); cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.594324, 0.729412, 0.000000, 0.000000); cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.809683, 1.000000, 0.545098, 0.196078); cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.899833, 0.972549, 0.937255, 0.074510); cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 1.000000, 0.976471, 0.968627, 0.831373); } bool const full = all_dirty_; if (full) { fill_background(cairo, {0, 0, w, h}); all_dirty_ = false; for (auto const& machine : machines_) machine->dirty = true; } int const text_width_pu = static_cast((box_width_ - margin_x_ * 2) * PANGO_SCALE); unsigned col = 0; auto y = pad_y_; for (auto const& machine : machines_) { auto const x = pad_x_ + col * (box_width_ + pad_x_); auto const bx = static_cast(x); auto const by = static_cast(std::max(0.0, y - 1.0)); auto const bw = static_cast(box_width_ + 1); auto const bh = static_cast(box_height_c_ + 2); if (machine->dirty) { machine->dirty = false; if (!machine->name_layout) { machine->name_layout = make_layout(); machine->name_layout_width = -1; machine->name_dirty = true; } if (machine->name_layout_width != text_width_pu) { pango_layout_set_width(machine->name_layout.get(), text_width_pu); machine->name_layout_width = text_width_pu; machine->name_dirty = true; } if (machine->name_dirty) { pango_layout_set_text(machine->name_layout.get(), machine->data.name.data(), machine->data.name.size()); machine->name_dirty = false; } auto* ml = machine->name_layout.get(); if (!full) { fill_background(cairo, {bx, by, bw, bh}); dirty.push_back({bx, by, bw, bh}); } rounded_path(cairo, x, y, box_width_, box_height_c_); cairo_set_source_rgba(cairo, 0.1, 0.1, 0.1, 0.7); if (machine->x > 0.0) { cairo_fill_preserve(cairo); auto old = cairo::unique_path(cairo_copy_path(cairo)); cairo_new_path(cairo); cairo_rectangle(cairo, x, y, machine->x * box_width_, box_height_c_); cairo_clip(cairo); cairo_append_path(cairo, old.get()); cairo_matrix_t matrix; cairo_matrix_init_translate(&matrix, -x, 0); cairo_pattern_set_matrix(job_pattern_.get(), &matrix); cairo_set_source(cairo, job_pattern_.get()); cairo_fill(cairo); cairo_reset_clip(cairo); } else { cairo_fill(cairo); } if (machine->requests > 0) { auto radius = box_height_c_ / 10.0; cairo_set_line_width(cairo, 1.5); cairo_new_path(cairo); cairo_move_to(cairo, x + box_width_ - radius, y); cairo_rel_line_to(cairo, -(machine->requests * (box_width_ - radius * 2)) / requests_, 0); cairo_set_source_rgb(cairo, 0, 0.6, 0); cairo_stroke(cairo); } cairo_move_to(cairo, x + margin_x_, y + margin_y_); pango_cairo_layout_path(cairo, ml); cairo_set_source_rgb(cairo, 0.0, 0.0, 0.0); cairo_set_line_cap(cairo, CAIRO_LINE_CAP_ROUND); cairo_set_line_width(cairo, 2.0); cairo_stroke(cairo); cairo_set_source_rgb(cairo, 1.0, 1.0, 1.0); cairo_move_to(cairo, x + margin_x_, y + margin_y_); pango_cairo_show_layout(cairo, ml); } if (++col == columns_) { col = 0; y += box_height_c_ + pad_y_ * 2; if (y >= h) break; } } if (full) dirty.push_back({0, 0, w, h}); } void added_machine(Monitor*, uint32_t id) override { auto machine = std::make_unique(id); update(machine.get()); auto it = machines_.emplace( std::lower_bound(machines_.begin(), machines_.end(), machine, compare_machine), std::move(machine)); by_id_[id] = it->get(); all_dirty_ = true; request_draw(); } static bool compare_machine(std::unique_ptr const& m1, std::unique_ptr const& m2) { return *m1 < *m2; } void updated_machine(Monitor*, uint32_t id) override { auto bit = by_id_.find(id); if (bit == by_id_.end()) { assert(false); return; } auto* machine = bit->second; auto const old_name = machine->data.name; update(machine); if (machine->data.name != old_name) { // TODO: Perhaps remove and insert instead? std::sort(machines_.begin(), machines_.end(), compare_machine); all_dirty_ = true; } else { machine->dirty = true; } request_draw(); } void removed_machine(Monitor*, uint32_t id) override { auto bit = by_id_.find(id); if (bit == by_id_.end()) { assert(false); return; } auto* machine = bit->second; max_jobs_ -= machine->data.max_jobs; requests_ -= machine->requests; jobs_ -= machine->jobs; if (animator_ && machine->animation) animator_->stop(machine->animation.get()); by_id_.erase(bit); auto it = std::find_if(machines_.begin(), machines_.end(), [id](std::unique_ptr const& m) { return m->id == id; }); machines_.erase(it); all_dirty_ = true; request_draw(); } void mark_request_machines_dirty() { for (auto const& machine : machines_) { if (machine->requests > 0) machine->dirty = true; } } void added_job(Monitor*, uint32_t source, uint32_t target) override { if (source == target) { auto it = by_id_.find(source); if (it != by_id_.end()) { ++it->second->jobs; it->second->dirty = true; animate(it->second); } } else { auto src = by_id_.find(source); if (src != by_id_.end()) { ++src->second->requests; src->second->dirty = true; } auto tgt = by_id_.find(target); if (tgt != by_id_.end()) { ++tgt->second->jobs; tgt->second->dirty = true; animate(tgt->second); } ++requests_; mark_request_machines_dirty(); } ++jobs_; request_draw(); } void removed_job(Monitor*, uint32_t source, uint32_t target) override { if (source == target) { auto it = by_id_.find(source); if (it != by_id_.end()) { --it->second->jobs; it->second->dirty = true; animate(it->second); } } else { auto src = by_id_.find(source); if (src != by_id_.end()) { --src->second->requests; src->second->dirty = true; } auto tgt = by_id_.find(target); if (tgt != by_id_.end()) { --tgt->second->jobs; tgt->second->dirty = true; animate(tgt->second); } --requests_; mark_request_machines_dirty(); } --jobs_; request_draw(); } void animate(Machine* machine) { if (!animator_) return; if (machine->animation) return; machine->animation = std::make_unique(machine); animator_->start(machine->animation, machine); } std::unique_ptr monitor_; bool connected_; std::vector> machines_; std::unordered_map by_id_; unsigned max_jobs_; unsigned jobs_; unsigned requests_; cairo::unique_pattern job_pattern_; unsigned force_columns_; // Cached layout geometry (valid when layout_w_ / layout_h_ != 0) uint16_t layout_w_; uint16_t layout_h_; double pad_x_; double pad_y_; double margin_x_; double margin_y_; unsigned columns_; double box_width_; double box_height_c_; bool all_dirty_; }; } // namespace std::unique_ptr create_icecc_monmon( std::shared_ptr const& looper, unsigned columns) { return std::make_unique(looper, columns); }