UI: show color controls based on segment light capabilities (#2567)

* Dynamic hiding of unused color controls in UI

(e.g. a PWM white bus with no auto white mode will not display the color wheel or palette list)

* Store segment capabilities

Don't use palettes if no RGB supported
Make it safe to update busses using `/json/cfg`
This commit is contained in:
Christian Schwinne 2022-03-05 01:05:26 +01:00 committed by GitHub
parent 85b1c309d1
commit 9c864c9759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 2351 additions and 2301 deletions

View File

@ -247,7 +247,7 @@ class WS2812FX {
// segment parameters
public:
typedef struct Segment { // 30 (32 in memory) bytes
typedef struct Segment { // 31 (32 in memory) bytes
uint16_t start;
uint16_t stop; //segment invalid if stop == 0
uint16_t offset;
@ -260,6 +260,7 @@ class WS2812FX {
uint8_t opacity;
uint32_t colors[NUM_COLORS];
uint8_t cct; //0==1900K, 255==10091K
uint8_t _capabilities;
char *name;
bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed
if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false;
@ -335,7 +336,8 @@ class WS2812FX {
return vLength;
}
uint8_t differs(Segment& b);
uint8_t getLightCapabilities();
inline uint8_t getLightCapabilities() {return _capabilities;}
void refreshLightCapabilities();
} segment;
// segment runtime parameters
@ -887,14 +889,15 @@ class WS2812FX {
uint32_t _colors_t[3];
uint8_t _bri_t;
bool _no_rgb = false;
uint8_t _segment_index = 0;
uint8_t _segment_index_palette_last = 99;
uint8_t _mainSegment;
segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 24 bytes per element
// start, stop, offset, speed, intensity, palette, mode, options, grouping, spacing, opacity (unused), color[]
{0, 7, 0, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}}
// start, stop, offset, speed, intensity, palette, mode, options, grouping, spacing, opacity (unused), color[], capabilities
{0, 7, 0, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}, 0}
};
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element
friend class Segment_runtime;

View File

@ -152,7 +152,14 @@ void WS2812FX::service() {
_colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]);
}
if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB);
for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]);
_no_rgb = !(SEGMENT.getLightCapabilities() & 0x01);
for (uint8_t c = 0; c < NUM_COLORS; c++) {
// if segment is not RGB capable, treat RGB channels of main segment colors as if 0
// this prevents Dual mode with white value 0 from setting White channel from inaccessible RGB values
// If not RGB capable, also treat palette as if default (0), as palettes set white channel to 0
if (_no_rgb) _colors_t[c] = _colors_t[c] & 0xFF000000;
_colors_t[c] = gamma32(_colors_t[c]);
}
handle_palette();
delay = (this->*_mode[SEGMENT.mode])(); //effect function
if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++;
@ -561,8 +568,10 @@ uint8_t WS2812FX::Segment::differs(Segment& b) {
return d;
}
uint8_t WS2812FX::Segment::getLightCapabilities() {
if (!isActive()) return 0;
void WS2812FX::Segment::refreshLightCapabilities() {
if (!isActive()) {
_capabilities = 0; return;
}
uint8_t capabilities = 0;
uint8_t awm = Bus::getAutoWhiteMode();
bool whiteSlider = (awm == RGBW_MODE_DUAL || awm == RGBW_MODE_MANUAL_ONLY);
@ -588,7 +597,7 @@ uint8_t WS2812FX::Segment::getLightCapabilities() {
}
if (correctWB && type != TYPE_ANALOG_1CH) capabilities |= 0x04; //white balance correction (uses CCT slider)
}
return capabilities;
_capabilities = capabilities;
}
//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw.
@ -627,7 +636,8 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
Segment& seg = _segments[n];
//return if neither bounds nor grouping have changed
if (seg.start == i1 && seg.stop == i2
bool boundsUnchanged = (seg.start == i1 && seg.stop == i2);
if (boundsUnchanged
&& (!grouping || (seg.grouping == grouping && seg.spacing == spacing))
&& (offset == UINT16_MAX || offset == seg.offset)) return;
@ -652,6 +662,7 @@ void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping,
}
if (offset < UINT16_MAX) seg.offset = offset;
_segment_runtimes[n].markForReset();
if (!boundsUnchanged) seg.refreshLightCapabilities();
}
void WS2812FX::restartRuntime() {
@ -741,6 +752,8 @@ void WS2812FX::fixInvalidSegments() {
{
if (_segments[i].start >= _length) setSegment(i, 0, 0);
if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length);
// this is always called as the last step after finalizeInit(), update covered bus types
getSegment(i).refreshLightCapabilities();
}
}
@ -1116,22 +1129,17 @@ void WS2812FX::handle_palette(void)
*/
uint32_t IRAM_ATTR WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
{
if (SEGMENT.palette == 0 && mcol < 3) {
if ((SEGMENT.palette == 0 && mcol < 3) || _no_rgb) {
uint32_t color = SEGCOLOR(mcol);
if (pbri != 255) {
CRGB crgb_color = col_to_crgb(color);
crgb_color.nscale8_video(pbri);
return crgb_to_col(crgb_color);
} else {
return color;
}
if (pbri == 255) return color;
return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri));
}
uint8_t paletteIndex = i;
if (mapping && SEGLEN > 1) paletteIndex = (i*255)/(SEGLEN -1);
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col;
fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
fastled_col = ColorFromPalette(currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
return crgb_to_col(fastled_col);
}

View File

@ -81,6 +81,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode());
strip.fixInvalidSegments(); // refreshes segment light capabilities (in case auto white mode changed)
CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
@ -91,7 +92,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (fromFS || !ins.isNull()) {
uint8_t s = 0; // bus iterator
busses.removeAll();
if (fromFS) busses.removeAll(); // can't safely manipulate busses directly in network callback
uint32_t mem = 0;
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES) break;
@ -113,11 +114,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
s++;
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
if (fromFS) {
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
} else {
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
doInitBusses = true;
}
}
// finalization done in beginStrip()
}

View File

@ -533,7 +533,8 @@ input[type=range]:active + .sliderbubble {
display: inline;
transform: translateX(-50%);
}
#wwrap, #wbal {
/* hide color controls until enabled in updateUI() */
#pwrap, #wwrap, #wbal, #rgbwrap, #palwrap {
display: none;
}
@ -566,10 +567,6 @@ input[type=range]:active + .sliderbubble {
width: 260px;
}
#rgbwrap {
display: none;
}
.btn {
padding: 8px;
margin: 10px;
@ -623,6 +620,7 @@ input[type=range]:active + .sliderbubble {
/* Quick color select buttons wrapper div */
#qcs-w {
margin-top: 10px;
display: none;
}
/* Quick color select buttons */

View File

@ -44,13 +44,15 @@
<div class ="container">
<div id="Colors" class="tabcontent">
<div id="picker" class="noslide"></div>
<div id="vwrap">
<div class="sliderwrap il" id="vwrap">
<input id="sliderV" class="noslide" oninput="fromV()" onchange="setColor(0)" max="100" min="0" type="range" value="128" step="any" />
<div class="sliderdisplay"></div>
</div><br>
</div>
<div id="pwrap">
<div id="picker" class="noslide"></div>
<div id="vwrap">
<div class="sliderwrap il" id="vwrap">
<input id="sliderV" class="noslide" oninput="fromV()" onchange="setColor(0)" max="100" min="0" type="range" value="128" step="any" />
<div class="sliderdisplay"></div>
</div><br>
</div>
</div>
<div id="kwrap">
<div class="sliderwrap il">
<input id="sliderK" class="noslide" oninput="fromK()" onchange="setColor(0)" max="10091" min="1900" type="range" value="6550" />
@ -108,32 +110,26 @@
<input id="hexc" type="text" class="noslide" onkeydown="hexEnter()" autocomplete="off" maxlength="8" />
<button id="hexcnf" class="xxs btn" onclick="fromHex();"><i class="icons no-margin">&#xe390;</i></button>
</div>
<p class="labels">
<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i>
Color palette
</p>
<div class="il">
<div id="pallist" class="list">
<div class="lstI" data-id="0">
<label class="check schkl">
&nbsp;
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent">
<span class="lstIname">
Default
</span>
<div id="palwrap">
<p class="labels">
<i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i>
Color palette
</p>
<div class="il">
<div id="pallist" class="list">
<div class="lstI" data-id="0">
<label class="check schkl">
&nbsp;
<input type="radio" value="${palettes[i].id}" name="palette" onChange="setPalette()">
<span class="radiomark schk"></span>
</label>
<div class="lstIcontent">
<span class="lstIname">
Default
</span>
</div>
</div>
</div>
<div class="lstI">
<div class="lstIcontent">
<span class="lstIname">
Loading...
</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,8 @@
//page js
var loc = false, locip;
var noNewSegs = false;
var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true, isRgbw = false;
var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true;
var hasWhite = false, hasRGB = false, hasCCT = false;
var whites = [0,0,0];
var colors = [[0,0,0],[0,0,0],[0,0,0]];
var expanded = [false];
@ -60,6 +61,10 @@ function sCol(na, col) {
d.documentElement.style.setProperty(na, col);
}
function isRgbBlack(a, s) {
return (a[s][0] == 0 && a[s][1] == 0 && a[s][2] == 0);
}
// returns RGB color from a given slot s 0-2 from color array a
function rgbStr(a, s) {
return "rgb(" + a[s][0] + "," + a[s][1] + "," + a[s][2] + ")";
@ -72,10 +77,20 @@ function rgbBri(a, s) {
}
// sets background of color slot selectors
function setCSL(a, s) {
function setCSL(s) {
var cd = d.getElementsByClassName('cl')[s];
cd.style.backgroundColor = rgbStr(a, s);
cd.style.color = rgbBri(a, s) > 127 ? "#000":"#fff";
var w = whites[s];
if (hasRGB && !isRgbBlack(colors, s)) {
cd.style.background = rgbStr(colors, s);
cd.style.color = rgbBri(colors, s) > 127 ? "#000":"#fff";
if (hasWhite && w > 0) {
cd.style.background = `linear-gradient(180deg, ${rgbStr(colors, s)} 30%, ${rgbStr([[w,w,w]], 0)})`;
}
} else {
if (!hasWhite) w = 0;
cd.style.background = rgbStr([[w,w,w]], 0);
cd.style.color = w > 127 ? "#000":"#fff";
}
}
function applyCfg()
@ -83,13 +98,7 @@ function applyCfg()
cTheme(cfg.theme.base === "light");
var bg = cfg.theme.color.bg;
if (bg) sCol('--c-1', bg);
var ccfg = cfg.comp.colors;
d.getElementById('hexw').style.display = ccfg.hex ? "block":"none";
d.getElementById('picker').style.display = ccfg.picker ? "block":"none";
d.getElementById('vwrap').style.display = ccfg.picker ? "block":"none";
d.getElementById('kwrap').style.display = ccfg.picker ? "block":"none";
d.getElementById('rgbwrap').style.display = ccfg.rgb ? "block":"none";
d.getElementById('qcs-w').style.display = ccfg.quick ? "block":"none";
if (lastinfo.leds) updateUI(); // update component visibility
var l = cfg.comp.labels;
var e = d.querySelectorAll('.tab-label');
for (var i of e)
@ -918,22 +927,19 @@ function updateLen(s)
function updatePA()
{
var ps = d.getElementsByClassName("pres"); //reset all preset buttons
for (let i = 0; i < ps.length; i++) {
//ps[i].style.backgroundColor = "var(--c-2)";
ps[i].classList.remove("selected");
for (var i of ps) {
i.classList.remove("selected");
}
ps = d.getElementsByClassName("psts"); //reset all quick selectors
for (let i = 0; i < ps.length; i++) {
ps[i].style.backgroundColor = "var(--c-2)";
for (var i of ps) {
i.classList.remove("selected");
}
if (currentPreset > 0) {
var acv = d.getElementById(`p${currentPreset}o`);
if (acv && !expanded[currentPreset+100])
//acv.style.background = "var(--c-6)"; //highlight current preset
acv.classList.add("selected");
acv = d.getElementById(`p${currentPreset}qlb`);
if (acv)
//acv.style.background = "var(--c-6)"; //highlight quick selector
acv.classList.add("selected");
}
}
@ -947,9 +953,15 @@ function updateUI()
updateTrail(d.getElementById('sliderBri'));
updateTrail(d.getElementById('sliderSpeed'));
updateTrail(d.getElementById('sliderIntensity'));
d.getElementById('wwrap').style.display = (isRgbw) ? "block":"none";
d.getElementById('wbal').style.display = (lastinfo.leds.cct) ? "block":"none";
d.getElementById('kwrap').style.display = (lastinfo.leds.cct) ? "none":"block";
d.getElementById('wwrap').style.display = (hasWhite) ? "block":"none";
d.getElementById('wbal').style.display = (hasCCT) ? "block":"none";
var ccfg = cfg.comp.colors;
d.getElementById('hexw').style.display = ccfg.hex ? "block":"none";
d.getElementById('pwrap').style.display = (hasRGB && ccfg.picker) ? "block":"none";
d.getElementById('kwrap').style.display = (hasRGB && !hasCCT && ccfg.picker) ? "block":"none";
d.getElementById('rgbwrap').style.display = (hasRGB && ccfg.rgb) ? "block":"none";
d.getElementById('qcs-w').style.display = (hasRGB && ccfg.quick) ? "block":"none";
d.getElementById('palwrap').style.display = hasRGB ? "block":"none";
updatePA();
updatePSliders();
@ -1031,13 +1043,32 @@ function readState(s,command=false) {
tr = s.transition;
d.getElementById('tt').value = tr/10;
var selc=0; var ind=0;
populateSegments(s);
var selc=0;
var sellvl=0; // 0: selc is invalid, 1: selc is mainseg, 2: selc is first selected
hasRGB = hasWhite = hasCCT = false;
for (let i = 0; i < (s.seg||[]).length; i++)
{
if(s.seg[i].sel) {selc = ind; break;} ind++;
if (sellvl == 0 && s.seg[i].id == s.mainseg) {
selc = i;
sellvl = 1;
}
if (s.seg[i].sel) {
if (sellvl < 2) selc = i; // get first selected segment
sellvl = 2;
var lc = lastinfo.leds.seglc[s.seg[i].id];
hasRGB |= lc & 0x01;
hasWhite |= lc & 0x02;
hasCCT |= lc & 0x04;
}
}
var i=s.seg[selc];
if (sellvl == 1) {
var lc = lastinfo.leds.seglc[i.id];
hasRGB = lc & 0x01;
hasWhite = lc & 0x02;
hasCCT = lc & 0x04;
}
if (!i) {
showToast('No Segments!', true);
updateUI();
@ -1047,8 +1078,8 @@ function readState(s,command=false) {
colors = i.col;
for (let e = 2; e >= 0; e--)
{
setCSL(colors, e);
if (isRgbw) whites[e] = parseInt(i.col[e][3]);
if (i.col[e].length > 3) whites[e] = parseInt(i.col[e][3]);
setCSL(e);
selectSlot(csel);
}
if (i.cct != null && i.cct>=0) d.getElementById("sliderA").value = i.cct;
@ -1199,7 +1230,6 @@ function requestJson(command, rinfo = true) {
name = "(L) " + name;
}
d.title = name;
isRgbw = info.leds.wv;
ledCount = info.leds.count;
syncTglRecv = info.str;
maxSeg = info.leds.maxseg;
@ -1889,13 +1919,9 @@ function setColor(sr) {
if (sr != 2) whites[csel] = parseInt(d.getElementById('sliderW').value);
var col = cpick.color.rgb;
colors[csel] = [col.r, col.g, col.b, whites[csel]];
setCSL(colors, csel);
var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}};
if (csel == 1) {
obj = {"seg": {"col": [[],[col.r, col.g, col.b, whites[csel]],[]]}};
} else if (csel == 2) {
obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}};
}
setCSL(csel);
var obj = {"seg": {"col": [[],[],[]]}};
obj.seg.col[csel] = colors[csel];
requestJson(obj);
}

File diff suppressed because it is too large Load Diff