From eff2e4cc49bfadd9716f3ad65854b3b0ca309b74 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 26 Sep 2017 23:44:35 +0200 Subject: Animate job count changes --- src/animation.hh | 17 ++++ src/animator.cc | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/animator.hh | 60 +++++++++++++ src/main.cc | 104 ++++++++++++++++++++-- 4 files changed, 434 insertions(+), 9 deletions(-) create mode 100644 src/animation.hh create mode 100644 src/animator.cc create mode 100644 src/animator.hh (limited to 'src') diff --git a/src/animation.hh b/src/animation.hh new file mode 100644 index 0000000..fb77142 --- /dev/null +++ b/src/animation.hh @@ -0,0 +1,17 @@ +#ifndef ANIMATION_HH +#define ANIMATION_HH + +class Animation { +public: + virtual ~Animation() {} + + // Return true if animation is still active, value is always set + virtual bool tick(double duration, double* value) = 0; + +protected: + Animation() {} + Animation(Animation const&) = delete; + Animation& operator=(Animation const&) = delete; +}; + +#endif // ANIMATION_HH diff --git a/src/animator.cc b/src/animator.cc new file mode 100644 index 0000000..7ed412c --- /dev/null +++ b/src/animator.cc @@ -0,0 +1,262 @@ +#include "common.hh" + +#include + +#include "animator.hh" +#include "animation.hh" +#include "clock.hh" +#include "looper.hh" +#include "observers.hh" + +namespace { + +double const FRAME = 1.0 / 60.0; +unsigned const NEED_BAD = 2; +unsigned const NEED_GOOD = 12; + +class AnimatorImpl : public Animator { +public: + explicit AnimatorImpl(std::shared_ptr const& looper) + : looper_(looper), timer_(0), callback_(false), target_(-1.0), + frame_(FRAME), good_(0), bad_(0), frame_count_(1) { + } + + ~AnimatorImpl() override { + if (timer_) { + looper_->cancel(timer_); + } + } + + void start(std::shared_ptr const& animation, + AnimationObserver* observer) override { + delayed_animations_.emplace_back(animation, observer); + + if (!timer_) { + timer_ = looper_->schedule(0.0, std::bind(&AnimatorImpl::tick, this, + std::placeholders::_1, + std::placeholders::_2)); + } + } + + bool observe(Animation* animation, AnimationObserver* observer) override { + for (auto& entry : animations_) { + if (entry.match(animation)) { + add_observer(entry, observer); + return true; + } + } + for (auto& entry : delayed_animations_) { + if (entry.match(animation)) { + add_observer(entry, observer); + return true; + } + } + return false; + } + + bool stop(Animation* animation) override { + for (auto it = delayed_animations_.begin(); + it != delayed_animations_.end(); ++it) { + if (it->match(animation)) { + notify_stopped(*it); + delayed_animations_.erase(it); + return true; + } + } + for (auto it = animations_.begin(); it != animations_.end(); ++it) { + if (it->match(animation)) { + notify_stopped(*it); + if (!callback_) animations_.erase(it); + return true; + } + } + return false; + } + + bool active(Animation* animation) const override { + for (auto& entry : animations_) { + if (entry.match(animation)) return true; + } + for (auto& entry : delayed_animations_) { + if (entry.match(animation)) return true; + } + return false; + } + + bool active() const override { + return !animations_.empty() || !delayed_animations_.empty(); + } + + void add_observer(Observer* observer) override { + observers_.add(observer); + } + + void remove_observer(Observer* observer) override { + observers_.remove(observer); + } + +private: + struct Entry { + std::shared_ptr animation_; + AnimationObserver* observer_; + Observers others_; + double started_; + bool alive_; + + Entry(std::shared_ptr const& animation, + AnimationObserver* observer) + : animation_(animation), observer_(observer), started_(0.0), + alive_(true) { + } + + bool match(Animation* animation) const { + return alive_ && animation_.get() == animation; + } + }; + + void add_observer(Entry& entry, AnimationObserver* observer) { + if (!observer) { + assert(false); + return; + } + if (entry.observer_) { + entry.others_.add(observer); + } else { + entry.observer_ = observer; + } + } + + void tick_animations(std::vector::iterator start, double now) { + assert(callback_); + double value; + auto it = start; + while (it != animations_.end()) { + if (it->alive_) { + auto stopped = !it->animation_->tick(now - it->started_, &value); + notify_ticked(*it, value); + if (it->alive_) { + if (!stopped) { + ++it; + continue; + } + notify_stopped(*it); + } + } + it = animations_.erase(it); + } + } + + void tick(Looper*, uint32_t timer) { + auto now = clk::steady(); + assert(timer_ == timer); + + callback_ = true; + tick_animations(animations_.begin(), now); + auto const base = animations_.size(); + for (auto& entry : delayed_animations_) { + animations_.push_back(entry); + animations_.back().started_ = now; + } + delayed_animations_.clear(); + tick_animations(animations_.begin() + base, now); + callback_ = false; + + auto observer = observers_.first(); + while (observer) { + observer->tick(this); + observer = observers_.next(); + } + + if (animations_.empty()) { + target_ = -1.0; + timer_ = 0; + return; + } + + if (target_ < 0.0) { + target_ = now + frame_; + } + auto t = clk::steady(); + auto diff = target_ - t; + if (diff < 0.0) { + ++bad_; + // If we miss N frames in a row, double the frame length + if (bad_ >= NEED_BAD) { + frame_count_ *= 2; + frame_ = frame_count_ * FRAME; + bad_ = 0; + } + // "Fix" this delay with FRAME steps and not frame_ to give a bit + // of a basic stepping between + do { + diff += FRAME; + } while (diff < 0.0); + good_ = 0; + } else { + bad_ = 0; + // If we have an increased frame length, check if we have enough + // timer over now to go back down + if (frame_count_ > 1) { + auto d = frame_ - diff; + if (d <= frame_ / 2.0) { + ++good_; + // If N frames in a row has been good reduce the frame length + if (good_ >= NEED_GOOD) { + frame_count_ /= 2; + frame_ = frame_count_ * FRAME; + diff -= FRAME; + good_ = 0; + } + } else { + good_ = 0; + } + } + } + target_ += frame_; + timer_ = looper_->schedule(diff, std::bind(&AnimatorImpl::tick, this, + std::placeholders::_1, + std::placeholders::_2)); + } + + void notify_stopped(Entry& entry) { + entry.alive_ = false; + if (!entry.observer_) return; + + entry.observer_->stopped(this, entry.animation_.get()); + auto observer = entry.others_.first(); + while (observer) { + observer->stopped(this, entry.animation_.get()); + observer = entry.others_.next(); + } + } + + void notify_ticked(Entry& entry, double value) { + if (!entry.observer_) return; + + entry.observer_->ticked(this, entry.animation_.get(), value); + auto observer = entry.others_.first(); + while (observer) { + observer->ticked(this, entry.animation_.get(), value); + observer = entry.others_.next(); + } + } + + std::shared_ptr looper_; + uint32_t timer_; + Observers observers_; + std::vector delayed_animations_; + std::vector animations_; + bool callback_; + double target_; + double frame_; + unsigned good_; + unsigned bad_; + unsigned frame_count_; +}; + +} // namespace + +// static +Animator* Animator::create(std::shared_ptr const& looper) { + return new AnimatorImpl(looper); +} diff --git a/src/animator.hh b/src/animator.hh new file mode 100644 index 0000000..c8072ac --- /dev/null +++ b/src/animator.hh @@ -0,0 +1,60 @@ +#ifndef ANIMATOR_HH +#define ANIMATOR_HH + +#include + +class Animation; +class Looper; + +class Animator { +public: + class Observer { + public: + virtual ~Observer() {} + + // Called after all active animations callbacks has been called. + // Not called if there are no active animations. + virtual void tick(Animator* animator) = 0; + + protected: + Observer() {} + }; + + class AnimationObserver { + public: + virtual ~AnimationObserver() {} + + // Called after the animation has been updated with the new value + virtual void ticked(Animator* animator, Animation* animation, + double value) = 0; + // Called after the animation stopped, either because stop() was called + // or because animation->tick() return false. + // If animation->tick() returned false ticked method above is always called + // first + virtual void stopped(Animator* animator, Animation* animation) = 0; + + protected: + AnimationObserver() {} + }; + + virtual ~Animator() {} + + static Animator* create(std::shared_ptr const& looper); + + virtual void start(std::shared_ptr const& animation, + AnimationObserver* observer) = 0; + virtual bool observe(Animation* animation, AnimationObserver* observer) = 0; + virtual bool stop(Animation* animation) = 0; + virtual bool active(Animation* animation) const = 0; + virtual bool active() const = 0; + + virtual void add_observer(Observer* observer) = 0; + virtual void remove_observer(Observer* observer) = 0; + +protected: + Animator() {} + Animator(Animator const&) = delete; + Animator& operator=(Animator const&) = delete; +}; + +#endif // ANIMATOR_HH diff --git a/src/main.cc b/src/main.cc index c5d47fd..d3017d9 100644 --- a/src/main.cc +++ b/src/main.cc @@ -12,6 +12,8 @@ #include #include +#include "animation.hh" +#include "animator.hh" #include "args.hh" #include "fake_monitor.hh" #include "io.hh" @@ -65,10 +67,11 @@ struct MwmHints { uint32_t status; }; -class MonMon : virtual Monitor::Observer { +class MonMon : virtual Monitor::Observer, virtual Animator::Observer { public: explicit MonMon(std::shared_ptr const& looper) - : looper_(looper), connected_(false), depth_(0), black_pixel_(0), + : looper_(looper), animator_(Animator::create(looper)), + connected_(false), depth_(0), black_pixel_(0), screen_(nullptr), max_jobs_(0), jobs_(0), requests_(0), x_(0), y_(0), w_(0), h_(0), rootpmap_(XCB_ATOM_NONE), desktop_window_(XCB_NONE), @@ -77,9 +80,11 @@ public: looper_->add(pipe_.read(), Looper::EV_READ, std::bind(&MonMon::pipe, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + animator_->add_observer(this); } ~MonMon() { + stop_all_animations(); unset_desktop_window(); close_pipe(); wnd_.reset(); @@ -179,6 +184,7 @@ public: depth_ = format.depth; black_pixel_ = screen->black_pixel; update_desktop_window(); + internal_draw(); xcb_map_window(wnd_.conn(), wnd_.get()); xcb_flush(wnd_.conn()); @@ -339,14 +345,22 @@ public: } private: - struct Machine { + class MachineAnimation; + + struct Machine : virtual Animator::AnimationObserver { uint32_t id; Monitor::Machine data; unsigned jobs; unsigned requests; + double x; + std::shared_ptr animation; explicit Machine(uint32_t id) - : id(id), jobs(0), requests(0) { + : id(id), jobs(0), requests(0), x(0.0) { + } + + ~Machine() override { + assert(!animation); } bool operator<(Machine const& machine) const { @@ -354,6 +368,48 @@ private: if (data.name > machine.data.name) return false; return id < machine.id; } + + void ticked(Animator*, Animation*, double value) override { + x = value; + } + + 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 unset_desktop_window() { @@ -453,6 +509,8 @@ private: } } + stop_all_animations(); + animator_.reset(); monitor_->disconnect(); looper_->exit_when_empty(); close_pipe(); @@ -462,6 +520,7 @@ private: switch (state) { case Monitor::SEARCHING: connected_ = false; + stop_all_animations(); machines_.clear(); max_jobs_ = 0; jobs_ = 0; @@ -476,19 +535,29 @@ private: } } + void stop_all_animations() { + if (!animator_) return; + for (auto& machine : machines_) { + if (machine.animation) animator_->stop(machine.animation.get()); + } + } + void update(Machine& machine) { auto old = machine.data.max_jobs; machine.data = monitor_->machine(machine.id); if (old < machine.data.max_jobs) { max_jobs_ += machine.data.max_jobs - old; + if (machine.jobs) animate(machine); } else if (old > machine.data.max_jobs) { max_jobs_ -= old - machine.data.max_jobs; + if (machine.jobs) animate(machine); } } void draw() { - internal_draw(); - xcb_flush(wnd_.conn()); + // Animator will draw soon anyway, so let it + if (animator_ && animator_->active()) return; + tick(nullptr); } static void rounded_path(cairo_t* cr, double x, double y, @@ -543,14 +612,13 @@ private: rounded_path(cairo_.get(), pad_x, y, box_width, box_height); cairo_set_source_rgba(cairo_.get(), 0.1, 0.1, 0.1, 0.7); - if (machine.jobs > 0) { + if (machine.x > 0.0) { cairo_fill_preserve(cairo_.get()); auto old = std::unique_ptr( cairo_copy_path(cairo_.get())); cairo_new_path(cairo_.get()); cairo_rectangle(cairo_.get(), pad_x, y, - (machine.jobs * box_width) / machine.data.max_jobs, - box_height); + machine.x * box_width, box_height); cairo_clip(cairo_.get()); cairo_append_path(cairo_.get(), old.get()); cairo_set_source(cairo_.get(), job_pattern_.get()); @@ -619,6 +687,7 @@ private: max_jobs_ -= it->data.max_jobs; requests_ -= it->requests; jobs_ -= it->jobs; + if (animator_) animator_->stop(it->animation.get()); machines_.erase(it); draw(); return; @@ -632,6 +701,7 @@ private: for (auto& machine : machines_) { if (machine.id == source) { ++machine.jobs; + animate(machine); } } } else { @@ -641,6 +711,7 @@ private: } if (machine.id == target) { ++machine.jobs; + animate(machine); } } ++requests_; @@ -654,6 +725,7 @@ private: for (auto& machine : machines_) { if (machine.id == source) { --machine.jobs; + animate(machine); } } } else { @@ -663,6 +735,7 @@ private: } if (machine.id == target) { --machine.jobs; + animate(machine); } } --requests_; @@ -671,8 +744,21 @@ private: draw(); } + void tick(Animator*) override { + internal_draw(); + xcb_flush(wnd_.conn()); + } + + void animate(Machine& machine) { + if (!animator_) return; + if (machine.animation) return; + machine.animation.reset(new MachineAnimation(&machine)); + animator_->start(machine.animation, &machine); + } + std::shared_ptr looper_; std::unique_ptr monitor_; + std::unique_ptr animator_; bool connected_; io::pipe pipe_; std::shared_ptr atoms_; -- cgit v1.3