Public Access
Add HA dropdowns, off image, flip fixes and refresh effects
Home Assistant: remove the Clock switch (its entity is deleted on connect), add an IP sensor and Mode / Off image dropdowns whose options follow the saved bitmaps. Remove the built-in "Nintendo border" bitmap. Off image: a bitmap (or blank) left on the dots when the display switches off. Nano: pause after each flip pulse and repeat pulses for changed dots, both stored in EEPROM (G/N commands). Refresh effects (E command): instant, sparkle, wipe, rain, from the middle out, diagonal, spiral, flip all then reveal, push and random, optionally only for small changes. Effects run in steps from loop() so serial input keeps flowing. Web UI: flip settings, refresh effect and test patterns under Settings; the System tab shows why the ESP32 last restarted. Settings records from versions 1 and 2 are migrated. Firmware 2.3.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4a5f6a8b5d
commit
1d3b084de1
42 files changed
+1631
-230
No files matched your search
+235
-41
@@ -75,38 +75,17 @@ void BROSE9323::display(bool force)
|
||||
#else
|
||||
if (_direct_mode)
|
||||
return;
|
||||
if (_force_next)
|
||||
bool all = force || _force_next;
|
||||
_force_next = false;
|
||||
_drawing = false;
|
||||
// An explicit full redraw is always instant (the periodic refresh)
|
||||
if (_effect == EFFECT_INSTANT || force)
|
||||
{
|
||||
force = true;
|
||||
_force_next = false;
|
||||
_animating = false;
|
||||
_drawAll(all);
|
||||
}
|
||||
for (uint8_t x = 0; x < width(); x++)
|
||||
{
|
||||
_selectPanel(x / _panel_width);
|
||||
|
||||
_selectColumn(x % _panel_width);
|
||||
|
||||
for (uint8_t y = 0; y < height(); y++)
|
||||
{
|
||||
bool b = _new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
||||
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
||||
if (!force && (bool)(_old_buffer[y * _buffer_width + x / 8] & (1 << (x & 7))) == b)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
_selectRow(y);
|
||||
|
||||
_setData(_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)));
|
||||
|
||||
_strobe();
|
||||
}
|
||||
}
|
||||
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
||||
// Store currently displayed content in old buffer
|
||||
memcpy(_old_buffer, _new_buffer, _buffer_size);
|
||||
#endif
|
||||
else
|
||||
_startEffect(all);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -136,6 +115,8 @@ void BROSE9323::drawPixel(int16_t x, int16_t y, uint16_t color)
|
||||
#else
|
||||
if (y >= height() || y < 0 || x >= width() || x < 0)
|
||||
return;
|
||||
if (!_direct_mode)
|
||||
_drawing = true;
|
||||
if (color)
|
||||
{
|
||||
if (_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)))
|
||||
@@ -158,18 +139,8 @@ void BROSE9323::drawPixel(int16_t x, int16_t y, uint16_t color)
|
||||
|
||||
_selectColumn(x % _panel_width);
|
||||
|
||||
_selectRow(y);
|
||||
|
||||
_setData(_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)));
|
||||
|
||||
_strobe();
|
||||
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
||||
// The dot is flipped now, so the next display() does not have to flip it again
|
||||
if (color)
|
||||
_old_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
||||
else
|
||||
_old_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
||||
#endif
|
||||
_flipDot(x, y, _pulses);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -181,6 +152,8 @@ void BROSE9323::fillScreen(uint16_t color)
|
||||
stream->print(color ? "F1\n" : "F0\n");
|
||||
#else
|
||||
memset(_new_buffer, color ? 0xFF : 0x00, _buffer_size);
|
||||
if (!_direct_mode)
|
||||
_drawing = true;
|
||||
if (_direct_mode)
|
||||
{
|
||||
_setData(color);
|
||||
@@ -219,6 +192,45 @@ void BROSE9323::setTiming(uint16_t t)
|
||||
#endif
|
||||
}
|
||||
|
||||
void BROSE9323::setGap(uint16_t t)
|
||||
{
|
||||
#ifdef ESPSERIAL
|
||||
stream->write('G');
|
||||
stream->print(t);
|
||||
stream->write('\n');
|
||||
#else
|
||||
_flip_gap = t;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BROSE9323::setPulses(uint8_t n)
|
||||
{
|
||||
#ifdef ESPSERIAL
|
||||
stream->write('N');
|
||||
stream->print(n);
|
||||
stream->write('\n');
|
||||
#else
|
||||
_pulses = n ? n : 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BROSE9323::setEffect(uint8_t effect, uint16_t time_ms, uint8_t flags)
|
||||
{
|
||||
#ifdef ESPSERIAL
|
||||
stream->write('E');
|
||||
stream->print(effect);
|
||||
stream->write(',');
|
||||
stream->print(time_ms);
|
||||
stream->write(',');
|
||||
stream->print(flags);
|
||||
stream->write('\n');
|
||||
#else
|
||||
_effect = effect < EFFECT_COUNT ? effect : EFFECT_INSTANT;
|
||||
_effect_time = time_ms;
|
||||
_effect_flags = flags;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BROSE9323::setDirect(bool d)
|
||||
{
|
||||
_direct_mode = d;
|
||||
@@ -297,6 +309,188 @@ void BROSE9323::_strobe(void)
|
||||
digitalWrite(ENABLE, 0);
|
||||
delayMicroseconds(_flip_time);
|
||||
digitalWrite(ENABLE, 1);
|
||||
if (_flip_gap)
|
||||
delayMicroseconds(_flip_gap);
|
||||
}
|
||||
|
||||
// What the panel shows now (without an old buffer: unknown, so the opposite of the
|
||||
// wanted state, which makes every dot count as changed)
|
||||
bool BROSE9323::_shown(uint8_t x, uint8_t y) const
|
||||
{
|
||||
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
||||
return _old_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
||||
#else
|
||||
return !_wanted(x, y);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BROSE9323::_changed(uint8_t x, uint8_t y) const
|
||||
{
|
||||
return _shown(x, y) != _wanted(x, y);
|
||||
}
|
||||
|
||||
bool BROSE9323::_wanted(uint8_t x, uint8_t y) const
|
||||
{
|
||||
return _new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
||||
}
|
||||
|
||||
// Flip one dot and remember that it is shown
|
||||
void BROSE9323::_flipTo(uint8_t x, uint8_t y, bool on, uint8_t pulses)
|
||||
{
|
||||
_selectPanel(x / _panel_width);
|
||||
_selectColumn(x % _panel_width);
|
||||
_selectRow(y);
|
||||
_setData(on);
|
||||
for (uint8_t i = 0; i < pulses; i++)
|
||||
_strobe();
|
||||
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
||||
if (on)
|
||||
_old_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
||||
else
|
||||
_old_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Flip one dot to its state in the new buffer
|
||||
void BROSE9323::_flipDot(uint8_t x, uint8_t y, uint8_t pulses)
|
||||
{
|
||||
_flipTo(x, y, _wanted(x, y), pulses);
|
||||
}
|
||||
|
||||
// Column by column, as fast as possible. all: also the dots that look unchanged (one
|
||||
// pulse each, they are only repeated in case they didn't flip before).
|
||||
void BROSE9323::_drawAll(bool all)
|
||||
{
|
||||
for (uint8_t x = 0; x < width(); x++)
|
||||
for (uint8_t y = 0; y < height(); y++)
|
||||
{
|
||||
bool changed = _changed(x, y);
|
||||
if (changed || all)
|
||||
_flipDot(x, y, changed ? _pulses : 1);
|
||||
}
|
||||
}
|
||||
|
||||
void BROSE9323::_startEffect(bool all)
|
||||
{
|
||||
uint16_t total = (uint16_t)width() * height();
|
||||
uint16_t changed = 0;
|
||||
for (uint8_t y = 0; y < height(); y++)
|
||||
for (uint8_t x = 0; x < width(); x++)
|
||||
changed += _changed(x, y);
|
||||
if (changed == 0 && !all)
|
||||
{
|
||||
_animating = false;
|
||||
return;
|
||||
}
|
||||
// E.g. only the clock's minutes get the effect, a new page appears at once
|
||||
if ((_effect_flags & EFFECT_FLAG_SMALL_ONLY) && (all || changed > total / 10))
|
||||
{
|
||||
_animating = false;
|
||||
_drawAll(all);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t effect = _effect;
|
||||
if (effect == EFFECT_RANDOM)
|
||||
effect = 1 + micros() % (EFFECT_RANDOM - 1);
|
||||
// Pushing needs to know what is shown; after boot that is unknown
|
||||
if (all && effect == EFFECT_PUSH)
|
||||
effect = EFFECT_WIPE;
|
||||
_running_effect = effect;
|
||||
_animate_all = all;
|
||||
_phase = 0;
|
||||
|
||||
uint32_t steps;
|
||||
if (effect == EFFECT_PUSH)
|
||||
{
|
||||
// One step per column shift; the dots of a shift are flipped as fast as possible
|
||||
_push_shift = 1;
|
||||
_push_pos = 0;
|
||||
steps = width();
|
||||
}
|
||||
else
|
||||
{
|
||||
_sequence.begin(effect == EFFECT_FLAP ? EFFECT_WIPE : effect, width(), height(), (uint16_t)micros());
|
||||
// Sparkle: only the changed dots take time, so a small change sparkles as long as a big one
|
||||
steps = (effect == EFFECT_SPARKLE && !all) ? changed : total;
|
||||
if (effect == EFFECT_FLAP)
|
||||
steps *= 2;
|
||||
}
|
||||
_step_time = (uint32_t)_effect_time * 1000 / steps;
|
||||
_next_step = micros();
|
||||
_animating = true;
|
||||
}
|
||||
|
||||
// One dot of the current push step. Shift s (1..width) shows old columns s.. on the left
|
||||
// and new columns 0..s-1 on the right: each column takes over its right neighbour (not
|
||||
// changed yet in this step, as the columns go left to right), the last one a new column.
|
||||
// False when the last shift is done.
|
||||
bool BROSE9323::_pushDot(void)
|
||||
{
|
||||
uint8_t w = width(), h = height();
|
||||
uint8_t x = _push_pos / h, y = _push_pos % h;
|
||||
bool target = x + 1 < w ? _shown(x + 1, y) : _wanted(_push_shift - 1, y);
|
||||
if (_shown(x, y) != target)
|
||||
_flipTo(x, y, target, _push_shift == w ? _pulses : 1);
|
||||
if (++_push_pos < (uint16_t)w * h)
|
||||
return true;
|
||||
_push_pos = 0;
|
||||
_next_step += _step_time;
|
||||
return ++_push_shift <= w;
|
||||
}
|
||||
|
||||
bool BROSE9323::busy(void) const
|
||||
{
|
||||
return _animating && !_drawing;
|
||||
}
|
||||
|
||||
bool BROSE9323::update(void)
|
||||
{
|
||||
// Wait for the D that ends the frame being drawn, it starts the effect again
|
||||
if (!_animating || _drawing)
|
||||
return busy();
|
||||
|
||||
// At most about 1 ms per call, so loop() keeps reading the serial port
|
||||
uint32_t start = micros();
|
||||
while ((int32_t)(micros() - _next_step) >= 0 && micros() - start < 1000)
|
||||
{
|
||||
if (_running_effect == EFFECT_PUSH)
|
||||
{
|
||||
if (!_pushDot())
|
||||
{
|
||||
_animating = false;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t x, y;
|
||||
if (!_sequence.next(x, y))
|
||||
{
|
||||
if (_running_effect == EFFECT_FLAP && _phase == 0)
|
||||
{
|
||||
// Everything is yellow now: reveal the new image
|
||||
_phase = 1;
|
||||
_sequence.begin(EFFECT_WIPE, width(), height(), 0);
|
||||
continue;
|
||||
}
|
||||
_animating = false;
|
||||
break;
|
||||
}
|
||||
// Flap: first every dot to yellow (one pulse), then the new image
|
||||
bool yellow_phase = _running_effect == EFFECT_FLAP && _phase == 0;
|
||||
bool target = yellow_phase ? true : _wanted(x, y);
|
||||
bool changed = _shown(x, y) != target;
|
||||
if (!changed && !_animate_all)
|
||||
{
|
||||
if (_running_effect == EFFECT_SPARKLE)
|
||||
continue; // unchanged dots take no time
|
||||
}
|
||||
else
|
||||
_flipTo(x, y, target, changed && !yellow_phase ? _pulses : 1);
|
||||
_next_step += _step_time;
|
||||
}
|
||||
return busy();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
|
||||
#include "dot_sequence.h"
|
||||
|
||||
//#define FLIPDOT_PLCC_ADAPTER
|
||||
// ESPSERIAL is set per target via build_flags in platformio.ini (-D ESPSERIAL for the ESP32)
|
||||
|
||||
@@ -10,6 +12,11 @@ class BROSE9323 : public Adafruit_GFX
|
||||
{
|
||||
private:
|
||||
uint16_t _flip_time;
|
||||
uint16_t _flip_gap = 0; // pause after each pulse in us, lets the coil supply recover
|
||||
uint8_t _pulses = 1; // pulses for each dot that changed
|
||||
uint8_t _effect = EFFECT_INSTANT;
|
||||
uint16_t _effect_time = 1500; // ms, how long an effect takes
|
||||
uint8_t _effect_flags = 0; // EFFECT_FLAG_...
|
||||
const uint8_t _panel_width;
|
||||
const uint8_t _buffer_width;
|
||||
const uint16_t _buffer_size;
|
||||
@@ -27,6 +34,18 @@ private:
|
||||
// After boot the physical dot state is unknown, so the first display() redraws everything
|
||||
bool _force_next = true;
|
||||
|
||||
// A running effect (display() with an effect other than EFFECT_INSTANT)
|
||||
DotSequence _sequence;
|
||||
bool _animating = false;
|
||||
uint8_t _running_effect = EFFECT_INSTANT; // _effect, or the one chosen for EFFECT_RANDOM
|
||||
uint8_t _phase = 0; // EFFECT_FLAP: 0 = to yellow, 1 = the new image
|
||||
uint8_t _push_shift = 0; // EFFECT_PUSH: column shift 1..width
|
||||
uint16_t _push_pos = 0; // EFFECT_PUSH: dot within the shift
|
||||
bool _animate_all = false; // flip every dot, not only the changed ones
|
||||
bool _drawing = false; // the buffer changed since the last display(): pause
|
||||
uint32_t _step_time = 0; // us per step
|
||||
uint32_t _next_step = 0; // micros() of the next step
|
||||
|
||||
#ifndef FLIPDOT_PLCC_ADAPTER
|
||||
const uint8_t ENABLE = 7,
|
||||
ADDR_0 = 3,
|
||||
@@ -71,6 +90,14 @@ private:
|
||||
void _selectRow(uint8_t);
|
||||
void _setData(bool);
|
||||
void _strobe(void);
|
||||
bool _shown(uint8_t x, uint8_t y) const;
|
||||
bool _changed(uint8_t x, uint8_t y) const;
|
||||
bool _wanted(uint8_t x, uint8_t y) const;
|
||||
void _flipTo(uint8_t x, uint8_t y, bool on, uint8_t pulses);
|
||||
void _flipDot(uint8_t x, uint8_t y, uint8_t pulses);
|
||||
bool _pushDot(void);
|
||||
void _drawAll(bool all);
|
||||
void _startEffect(bool all);
|
||||
#endif
|
||||
public:
|
||||
BROSE9323(uint8_t, uint8_t, uint8_t, uint16_t ft = 280);
|
||||
@@ -83,6 +110,21 @@ public:
|
||||
void fillScreen(uint16_t);
|
||||
void setDirect(bool);
|
||||
void setTiming(uint16_t);
|
||||
// Pause after each flip pulse in us
|
||||
void setGap(uint16_t);
|
||||
// Pulses for each changed dot (a second pulse catches dots that didn't flip). Full
|
||||
// redraws use one pulse per dot.
|
||||
void setPulses(uint8_t);
|
||||
// How display() shows a new frame (RefreshEffect), how long it takes in ms (pushing
|
||||
// can take longer: it flips many dots per step) and EFFECT_FLAG_... flags.
|
||||
// Effects run in steps from update(), so call that often (every loop).
|
||||
void setEffect(uint8_t effect, uint16_t time_ms, uint8_t flags = 0);
|
||||
#ifndef ESPSERIAL
|
||||
// Runs the next steps of an effect. Returns busy().
|
||||
bool update(void);
|
||||
// An effect is running (paused while a new frame is being drawn into the buffer)
|
||||
bool busy(void) const;
|
||||
#endif
|
||||
#ifndef ESPSERIAL
|
||||
void printBuffer(void);
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,13 @@ The flags are set per environment in `platformio.ini` through `build_flags`. Don
|
||||
|
||||
- In direct mode (`setDirect(true)`) every pixel is flipped right away, without a call to
|
||||
`display()`.
|
||||
- `setGap(us)` adds a pause after each flip pulse, `setPulses(n)` pulses each changed
|
||||
dot `n` times. Full redraws (`display(true)`, the first `display()` after boot) pulse
|
||||
the dots that look unchanged only once.
|
||||
- `setEffect(effect, ms)`: how `display()` shows a frame (`RefreshEffect` in
|
||||
`dot_sequence.h`). Anything but `EFFECT_INSTANT` runs in steps from `update()`, which
|
||||
must be called often; `busy()` says whether an effect is running. `display(true)` is
|
||||
always instant. On the ESP32 these calls become the `G`, `N` and `E` commands.
|
||||
- `print()` maps German umlauts (ÄäÖöÜüß) to the code page 437 glyphs used by the GFX
|
||||
font.
|
||||
- On an ATmega168 the old buffer is left out to save RAM. Every `display()` call then
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// The order in which display() flips the dots, and how it is spread over time.
|
||||
// The numbers are part of the serial protocol (E command) and the ESP32 settings.
|
||||
enum RefreshEffect : uint8_t
|
||||
{
|
||||
EFFECT_INSTANT = 0, // column by column, left to right, as fast as possible
|
||||
EFFECT_SPARKLE = 1, // the changed dots one at a time, in a scrambled order
|
||||
EFFECT_WIPE = 2, // column by column, left to right
|
||||
EFFECT_RAIN = 3, // row by row, top to bottom
|
||||
EFFECT_CENTER = 4, // column by column, from the middle outwards
|
||||
EFFECT_DIAGONAL = 5, // from the top left to the bottom right corner
|
||||
EFFECT_SPIRAL = 6, // from the outside in, clockwise
|
||||
// The ones below are run by BROSE9323 itself (DotSequence uses column order for them)
|
||||
EFFECT_FLAP = 7, // every dot to yellow (wipe), then the new image (wipe)
|
||||
EFFECT_PUSH = 8, // the new image slides in from the right, pushing the old one out
|
||||
EFFECT_RANDOM = 9, // one of the effects 1-8, chosen for each frame
|
||||
EFFECT_COUNT = 10,
|
||||
};
|
||||
|
||||
// Flags for BROSE9323::setEffect()
|
||||
const uint8_t EFFECT_FLAG_SMALL_ONLY = 1; // big changes (> 1/10 of the dots) are instant
|
||||
|
||||
// Visits every dot of a w x h display exactly once, in the order of an effect. Needs no
|
||||
// buffer: the sparkle order comes from a maximal length LFSR (all states except 0, out
|
||||
// of range values skipped), scrambled with a multiplication modulo the dot count.
|
||||
class DotSequence
|
||||
{
|
||||
public:
|
||||
void begin(uint8_t effect, uint8_t w, uint8_t h, uint16_t seed)
|
||||
{
|
||||
_effect = effect < EFFECT_COUNT ? effect : EFFECT_INSTANT;
|
||||
_w = w;
|
||||
_h = h;
|
||||
_total = (uint16_t)w * h;
|
||||
_pos = 0;
|
||||
_diagonal = 0;
|
||||
_left = 0;
|
||||
_top = 0;
|
||||
_right = w - 1;
|
||||
_bottom = h - 1;
|
||||
_x = 0;
|
||||
_y = 0;
|
||||
_direction = 0;
|
||||
if (_effect != EFFECT_SPARKLE || _total == 0)
|
||||
return;
|
||||
|
||||
uint8_t bits = 2;
|
||||
while (bits < 16 && ((1UL << bits) - 1) < _total)
|
||||
bits++;
|
||||
// Galois LFSR feedback masks with a maximal period, for 2..16 bits
|
||||
static const uint16_t masks[15] = {0x3, 0x6, 0xC, 0x14, 0x30, 0x60, 0xB8, 0x110,
|
||||
0x240, 0x500, 0xE08, 0x1C80, 0x3802, 0x6000, 0xD008};
|
||||
_mask = masks[bits - 2];
|
||||
_period = (uint16_t)((1UL << bits) - 1);
|
||||
_lfsr = seed % _period + 1; // any state except 0
|
||||
|
||||
// A multiplier without common factors with the total keeps the mapping 1:1
|
||||
_mult = 997 % _total;
|
||||
while (_mult == 0 || gcd(_mult, _total) != 1)
|
||||
_mult++;
|
||||
}
|
||||
|
||||
// The next dot; false when every dot was visited
|
||||
bool next(uint8_t &x, uint8_t &y)
|
||||
{
|
||||
if (_effect == EFFECT_SPARKLE)
|
||||
{
|
||||
while (_pos < _period)
|
||||
{
|
||||
_pos++;
|
||||
uint16_t index = _lfsr - 1;
|
||||
_lfsr = (_lfsr & 1) ? (_lfsr >> 1) ^ _mask : _lfsr >> 1;
|
||||
if (index < _total)
|
||||
{
|
||||
uint16_t p = (uint32_t)index * _mult % _total;
|
||||
x = p % _w;
|
||||
y = p / _w;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_pos >= _total)
|
||||
return false;
|
||||
|
||||
if (_effect == EFFECT_DIAGONAL)
|
||||
{
|
||||
// Diagonal d holds the dots with x + y = d, top to bottom
|
||||
while (true)
|
||||
{
|
||||
if (_y > (int16_t)_diagonal || _y >= _h)
|
||||
{
|
||||
_diagonal++;
|
||||
_y = 0;
|
||||
continue;
|
||||
}
|
||||
uint16_t dx = _diagonal - _y;
|
||||
uint8_t dy = _y++;
|
||||
if (dx < _w)
|
||||
{
|
||||
x = dx;
|
||||
y = dy;
|
||||
_pos++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_effect == EFFECT_SPIRAL)
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
_pos++;
|
||||
// Walk along the edge of the remaining rectangle, then make it smaller
|
||||
switch (_direction)
|
||||
{
|
||||
case 0: // right along the top
|
||||
if (_x < _right)
|
||||
_x++;
|
||||
else
|
||||
{
|
||||
_top++;
|
||||
_direction = 1;
|
||||
_y++;
|
||||
}
|
||||
break;
|
||||
case 1: // down the right side
|
||||
if (_y < _bottom)
|
||||
_y++;
|
||||
else
|
||||
{
|
||||
_right--;
|
||||
_direction = 2;
|
||||
_x--;
|
||||
}
|
||||
break;
|
||||
case 2: // left along the bottom
|
||||
if (_x > _left)
|
||||
_x--;
|
||||
else
|
||||
{
|
||||
_bottom--;
|
||||
_direction = 3;
|
||||
_y--;
|
||||
}
|
||||
break;
|
||||
default: // up the left side
|
||||
if (_y > _top)
|
||||
_y--;
|
||||
else
|
||||
{
|
||||
_left++;
|
||||
_direction = 0;
|
||||
_x++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t p = _pos++;
|
||||
if (_effect == EFFECT_RAIN)
|
||||
{
|
||||
x = p % _w;
|
||||
y = p / _w;
|
||||
return true;
|
||||
}
|
||||
uint8_t column = p / _h;
|
||||
y = p % _h;
|
||||
if (_effect == EFFECT_CENTER)
|
||||
{
|
||||
uint8_t middle = _w / 2;
|
||||
x = (column & 1) ? middle - (column + 1) / 2 : middle + column / 2;
|
||||
}
|
||||
else
|
||||
x = column;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Number of next() calls that return true
|
||||
uint16_t total() const { return _total; }
|
||||
|
||||
private:
|
||||
static uint16_t gcd(uint16_t a, uint16_t b)
|
||||
{
|
||||
while (b)
|
||||
{
|
||||
uint16_t t = a % b;
|
||||
a = b;
|
||||
b = t;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
uint8_t _effect = EFFECT_INSTANT;
|
||||
uint8_t _w = 0, _h = 0;
|
||||
uint16_t _total = 0, _pos = 0;
|
||||
uint16_t _lfsr = 1, _mask = 0, _period = 0, _mult = 1;
|
||||
uint16_t _diagonal = 0; // diagonal
|
||||
int16_t _left = 0, _top = 0, _right = 0, _bottom = 0; // spiral
|
||||
int16_t _x = 0, _y = 0; // spiral; _y also diagonal
|
||||
uint8_t _direction = 0; // spiral
|
||||
};
|
||||
Reference in new issue
Block a user