#include "Grid.hpp" #include #include #include #include #include const std::string black {"#000000"}; const std::string white {"#FFFFFF"}; const std::string sand {"#B69061"}; const std::string pastel_green {"#B1D1AF"}; struct Rect { double cx {0}; double cy {0}; double width {0}; Color color {black}; }; struct Situation { std::vector rects; Color bg {pastel_green}; }; enum class State {wait, fade}; class Grid : public ScreensaverPlugin { public: Grid() = default; ~Grid() = default; void setup(cairo_t* context, const cairo_rectangle_t& rect) override; void configure() override; int fps() const override; void update() override; void render() override; private: size_t _h; size_t _v; Situation _old; Situation _new; Situation _cur; std::vector _colors; int _frames {0}; State _s {State::fade}; double _d {75}; // grid point distance int _wait {7}; // seconds int _fade {3}; // seconds double _border {75}; double _min_size {2}; double _max_size {50}; bool _change_bg {true}; void animate(); }; ScreensaverPlugin* create_instance() { return new Grid; } void Grid::setup(cairo_t* context, const cairo_rectangle_t& rect) { ScreensaverPlugin::setup(context, rect); if (_colors.size() == 0) { _colors.emplace_back(black); _colors.emplace_back(white); _colors.emplace_back(sand); } _old.rects.clear(); _old.bg = black; _new.rects.clear(); _cur.rects.clear(); cairo_rectangle_t inset {rect}; inset.width -= 2 * _border; inset.height -= 2 * _border; _h = static_cast(round(inset.width / _d)); _v = static_cast(round(inset.height / _d)); double h_margin = (inset.width - (_h - 1) * _d) / 2; double v_margin = (inset.height - (_v - 1) * _d) / 2; for (size_t x = 0; x < _h; ++x) { for (size_t y = 0; y < _v; ++y) { Rect r; r.cx = _border + h_margin + x * _d; r.cy = _border + v_margin + y * _d; r.width = 0; r.color = black; _old.rects.push_back(r); _cur.rects.push_back(r); r.width = random_between(_min_size, _max_size); r.color = sc::random::choice(_colors); _new.rects.push_back(r); } } } void Grid::configure() { _min_size = _j["min-size"]; _max_size = _j["max-size"]; _d = _j["dist"]; _wait = _j["wait"]; _fade = _j["fade"]; _border = _j["border"]; _new.bg = Color {_j["bg"]}; _change_bg = _j["changebg"]; _colors.clear(); for (const auto& hex : _j["colors"]) { _colors.emplace_back(hex); } setup(_c, _r); } int Grid::fps() const { return 30; } void Grid::update() { // adjust state for next render switch (_s) { case State::wait: if (++_frames > _wait * fps()) { _frames = 0; _s = State::fade; } break; case State::fade: if (++_frames > _fade * fps()) { _frames = 0; _old = _new; for (Rect& r : _new.rects) { r.width = random_between(_min_size, _max_size); r.color = sc::random::choice(_colors); } HSB newbg {random_between(0, 360), 0.16, 0.82}; _new.bg = newbg; _s = State::wait; } else { animate(); } break; } } void Grid::render() { // render one frame based on current state if (_s == State::fade) { RGB bg {RGB(_cur.bg)}; cairo_set_source_rgb(_c, bg.r, bg.g, bg.b); cairo_rectangle(_c, _r.x, _r.y, _r.width, _r.height); cairo_fill(_c); for (const Rect& r : _cur.rects) { RGB col {RGB(r.color)}; cairo_set_source_rgb(_c, col.r, col.g, col.b); cairo_rectangle(_c, r.cx - r.width / 2, r.cy - r.width / 2, r.width, r.width); cairo_fill(_c); } } } void Grid::animate() { double f {static_cast(_frames) / (_fade * fps())}; if (_change_bg) { RGB bg1 {RGB(_old.bg)}; RGB bg2 {RGB(_new.bg)}; RGB bg3 {bg1.r + f * (bg2.r - bg1.r), bg1.g + f * (bg2.g - bg1.g), bg1.b + f * (bg2.b - bg1.b)}; _cur.bg = bg3; } for (size_t i = 0; i < _old.rects.size(); ++i) { Rect& r1 = _old.rects[i]; Rect& r2 = _new.rects[i]; Rect& r3 = _cur.rects[i]; RGB rgb1 {RGB(r1.color)}; RGB rgb2 {RGB(r2.color)}; RGB rgb3 {rgb1.r + f * (rgb2.r - rgb1.r), rgb1.g + f * (rgb2.g - rgb1.g), rgb1.b + f * (rgb2.b - rgb1.b)}; r3.color = rgb3; r3.width = r1.width + f * (r2.width - r1.width); } }