diff --git a/modules/Grid/Grid.cpp b/modules/Grid/Grid.cpp new file mode 100644 index 0000000..68b9b48 --- /dev/null +++ b/modules/Grid/Grid.cpp @@ -0,0 +1,166 @@ +#include "Grid.hpp" +#include +#include +#include +#include + +// rectangles have width between 2 and 50 pixels +// rectangles have color black, white, or sand +// background is pastel green +// rectangles are placed on a grid where they're centered on grid points +// grid points have 75 pixels in between +// screensize therefore determines grid, which should be centered +// after some time, a new random setup is chosen, and we animate to the new situation + +const RGB black {0, 0, 0}; +const RGB white {1, 1, 1}; +const RGB sand {182.0 / 256.0, 144.0 / 256.0, 97.0 / 256.0}; +const RGB pastel_green {177.0 / 256.0, 209.0 / 256.0, 175.0 / 256.0}; + +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}; + + 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 = 50; + r.color = black; + _old.rects.push_back(r); + _cur.rects.push_back(r); + r.width = random_between(2, 50); + r.color = sc::random::choice(_colors); + _new.rects.push_back(r); + } + } +} + +void Grid::configure() { + // do something with _j["key"] + +} + +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(2, 50); + r.color = sc::random::choice(_colors); + } + _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())}; + 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); + } +} diff --git a/modules/Grid/Grid.hpp b/modules/Grid/Grid.hpp new file mode 100644 index 0000000..97e0426 --- /dev/null +++ b/modules/Grid/Grid.hpp @@ -0,0 +1,18 @@ +// +// Grid.hpp +// Grid +// +// Created by Bob Polis at 2022-09-21 +// Copyright (c) 2022 SwiftCoder. All rights reserved. +// + +#ifndef _Grid_H_ +#define _Grid_H_ + +class ScreensaverPlugin; + +extern "C" { + ScreensaverPlugin* create_instance(); +} + +#endif // _Grid_H_ diff --git a/modules/Grid/Makefile b/modules/Grid/Makefile new file mode 100644 index 0000000..9d10008 --- /dev/null +++ b/modules/Grid/Makefile @@ -0,0 +1,62 @@ +LIBNAME := $(shell basename $$(pwd)) +MAJOR := 1 +MINOR := 0.0 + +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Darwin) + LINKERNAME := $(LIBNAME).dylib + SONAME := $(LIBNAME).$(MAJOR).dylib + REALNAME := $(LINKERNAME) +else + LINKERNAME := $(LIBNAME).so + SONAME := $(LINKERNAME).$(MAJOR) + REALNAME := $(SONAME).$(MINOR) +endif + +PREFIX ?= ../.. +LIBDIR ?= $(PREFIX)/plugins + +SRCS := $(wildcard *.cpp) +OBJS := $(subst .cpp,.o,$(SRCS)) +DEPS := $(subst .cpp,.d,$(SRCS)) +HDRS := $(filter-out $(LIBNAME).hpp,$(wildcard *.hpp)) + +CXX ?= g++ + +CXXFLAGS += -Wshadow -Wall -Wpedantic -Wextra -g -std=c++17 -fPIC +ifeq ($(DEBUG),1) + CXXFLAGS += -D DEBUG -O0 +else + CXXFLAGS += -D NDEBUG -O3 +endif + +LDLIBS := -lcairo -lscscreensaver -lscnumerics + +RM := /bin/rm -f +INSTALL := /usr/bin/install -c + +.PHONY: all clean install + +all: $(REALNAME) + +$(REALNAME): $(OBJS) $(DEPS) +ifeq ($(UNAME_S),Darwin) + $(CXX) -dynamiclib -o $(REALNAME) -current_version $(MAJOR) -compatibility_version $(MINOR) $(LDFLAGS) $(LDLIBS) $(OBJS) +else + $(CXX) -g -shared -Wl,-soname,$(SONAME) -o $(REALNAME) $(LDFLAGS) $(LDLIBS) $(OBJS) +endif + +%.o: %.cpp %.d Makefile + $(CXX) $(CXXFLAGS) -MMD -MP -MT $@ -MF $*.d -c $< + +-include *.d + +%.d: ; + +clean: + $(RM) $(OBJS) $(DEPS) $(REALNAME) + +install: $(REALNAME) + $(INSTALL) -d $(LIBDIR) + $(INSTALL) -m 644 $(REALNAME) $(LIBDIR)/$(LIBNAME).saver