summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--meson.build3
-rw-r--r--src/animation.hh17
-rw-r--r--src/animator.cc262
-rw-r--r--src/animator.hh60
-rw-r--r--src/main.cc104
5 files changed, 436 insertions, 10 deletions
diff --git a/meson.build b/meson.build
index c61b6f7..82ff072 100644
--- a/meson.build
+++ b/meson.build
@@ -43,6 +43,7 @@ configure_file(output: 'config.h',
executable('monmon',
sources: ['src/main.cc', 'src/x.cc', 'src/args.cc',
'src/poll_looper.cc', 'src/looper.cc', 'src/clock.cc',
- 'src/io.cc', 'src/monitor.cc', 'src/fake_monitor.cc'],
+ 'src/io.cc', 'src/monitor.cc', 'src/fake_monitor.cc',
+ 'src/animator.cc'],
dependencies: [dep_thread, dep_pango, dep_cairo, dep_icecc,
xcb_deps])
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 <vector>
+
+#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<Looper> 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<Animation> 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> animation_;
+ AnimationObserver* observer_;
+ Observers<AnimationObserver> others_;
+ double started_;
+ bool alive_;
+
+ Entry(std::shared_ptr<Animation> 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<Entry>::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> looper_;
+ uint32_t timer_;
+ Observers<Observer> observers_;
+ std::vector<Entry> delayed_animations_;
+ std::vector<Entry> animations_;
+ bool callback_;
+ double target_;
+ double frame_;
+ unsigned good_;
+ unsigned bad_;
+ unsigned frame_count_;
+};
+
+} // namespace
+
+// static
+Animator* Animator::create(std::shared_ptr<Looper> 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 <memory>
+
+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<Looper> const& looper);
+
+ virtual void start(std::shared_ptr<Animation> 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 <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
+#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<PollLooper> 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<MachineAnimation> 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<double>(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<double>(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_path_t, CairoPathDelete>(
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<PollLooper> looper_;
std::unique_ptr<Monitor> monitor_;
+ std::unique_ptr<Animator> animator_;
bool connected_;
io::pipe pipe_;
std::shared_ptr<x::Atoms> atoms_;