Further cleanups to the colour picker code.
This commit is contained in:
parent
0b816b2bd4
commit
51694538ff
7 changed files with 221 additions and 154 deletions
|
@ -157,11 +157,13 @@
|
|||
}
|
||||
.colpick-presets-option {
|
||||
display: block;
|
||||
border: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.colpick-presets-option:focus {
|
||||
box-shadow: 0 0 0 1px #fff, 0 0 0 2px #000, inset 0 0 0 1px #000;
|
||||
|
@ -175,6 +177,8 @@
|
|||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
}
|
||||
.colpick-grid-option {
|
||||
display: block;
|
||||
border: 0;
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
z-index: 1;
|
||||
|
|
69
src/mami.js/colour.js
Normal file
69
src/mami.js/colour.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
const MamiColour = (() => {
|
||||
const readThres = 168,
|
||||
lumiRed = .299,
|
||||
lumiGreen = .587,
|
||||
lumiBlue = .114;
|
||||
|
||||
const pub = {};
|
||||
|
||||
const extractRGB = raw => [
|
||||
(raw >> 16) & 0xFF,
|
||||
(raw >> 8) & 0xFF,
|
||||
raw & 0xFF,
|
||||
];
|
||||
|
||||
const luminance = raw => {
|
||||
const rgb = extractRGB(raw);
|
||||
return rgb[0] * lumiRed
|
||||
+ rgb[1] * lumiGreen
|
||||
+ rgb[2] * lumiBlue;
|
||||
};
|
||||
|
||||
const weightedNumber = (num1, num2, weight) => {
|
||||
weight = Math.min(1, Math.max(0, weight));
|
||||
return Math.round((num1 * weight) + (num2 * (1 - weight)));
|
||||
};
|
||||
|
||||
const weighted = (raw1, raw2, weight) => {
|
||||
const rgb1 = extractRGB(raw1),
|
||||
rgb2 = extractRGB(raw2);
|
||||
return (weightedNumber(rgb1[0], rgb2[0], weight) << 16)
|
||||
| (weightedNumber(rgb1[1], rgb2[1], weight) << 8)
|
||||
| weightedNumber(rgb1[2], rgb2[2], weight);
|
||||
};
|
||||
|
||||
pub.extractRGB = extractRGB;
|
||||
pub.weightedNumber = weightedNumber;
|
||||
pub.luminance = luminance;
|
||||
pub.weighted = weighted;
|
||||
|
||||
pub.text = raw => luminance(raw) > readThres ? 0 : 0xFFFFFF;
|
||||
|
||||
pub.shaded = (raw, offset) => {
|
||||
if(offset == 0)
|
||||
return raw;
|
||||
|
||||
let dir = 0xFFFFFF;
|
||||
if(offset < 0) {
|
||||
dir = 0;
|
||||
offset *= -1;
|
||||
}
|
||||
|
||||
return weighted(dir, raw, offset);
|
||||
};
|
||||
|
||||
pub.hexByte = raw => {
|
||||
let str = raw.toString(16).substring(0, 2);
|
||||
return str.length < 2
|
||||
? `0${str}` : str;
|
||||
};
|
||||
|
||||
pub.hex = raw => {
|
||||
let str = raw.toString(16).substring(0, 6);
|
||||
if(str.length < 6)
|
||||
str = '000000'.substring(str.length) + str;
|
||||
return `#${str}`;
|
||||
};
|
||||
|
||||
return pub;
|
||||
})();
|
|
@ -1,3 +1,8 @@
|
|||
#include colour.js
|
||||
#include colpick/tgrid.jsx
|
||||
#include colpick/tpresets.jsx
|
||||
#include colpick/tsliders.jsx
|
||||
|
||||
const MamiColourPicker = function(callback, options, colour, onClose) {
|
||||
if(typeof callback !== 'function')
|
||||
return;
|
||||
|
@ -6,59 +11,6 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
|
|||
if(typeof colour !== 'number')
|
||||
colour = parseInt(colour || 0);
|
||||
|
||||
const readThres = 168,
|
||||
lumiRed = .299,
|
||||
lumiGreen = .587,
|
||||
lumiBlue = .114;
|
||||
|
||||
const hexFormat = raw => {
|
||||
let str = raw.toString(16).substring(0, 6);
|
||||
if(str.length < 6)
|
||||
str = '000000'.substring(str.length) + str;
|
||||
return `#${str}`;
|
||||
};
|
||||
|
||||
const extractRGB = raw => [
|
||||
(raw >> 16) & 0xFF,
|
||||
(raw >> 8) & 0xFF,
|
||||
raw & 0xFF,
|
||||
];
|
||||
|
||||
const calcLumi = raw => {
|
||||
const rgb = extractRGB(raw);
|
||||
return rgb[0] * lumiRed
|
||||
+ rgb[1] * lumiGreen
|
||||
+ rgb[2] * lumiBlue;
|
||||
};
|
||||
|
||||
const textColour = raw => calcLumi(raw) > readThres ? 0 : 0xFFFFFF;
|
||||
|
||||
const weightNum = (n1, n2, w) => {
|
||||
w = Math.min(1, Math.max(0, w));
|
||||
return Math.round((n1 * w) + (n2 * (1 - w)));
|
||||
};
|
||||
|
||||
const weightColour = (c1, c2, w) => {
|
||||
c1 = extractRGB(c1);
|
||||
c2 = extractRGB(c2);
|
||||
return (weightNum(c1[0], c2[0], w) << 16)
|
||||
| (weightNum(c1[1], c2[1], w) << 8)
|
||||
| weightNum(c1[2], c2[2], w);
|
||||
};
|
||||
|
||||
const shadeColour = (raw, offset) => {
|
||||
if(offset == 0)
|
||||
return raw;
|
||||
|
||||
let dir = 0xFFFFFF;
|
||||
if(offset < 0) {
|
||||
dir = 0;
|
||||
offset *= -1;
|
||||
}
|
||||
|
||||
return weightColour(dir, raw, offset);
|
||||
};
|
||||
|
||||
const verifyOption = (name, type, def) => {
|
||||
if(typeof options[name] !== type)
|
||||
options[name] = def;
|
||||
|
@ -76,34 +28,27 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
|
|||
|
||||
const onColourChange = [];
|
||||
const runOnColourChange = () => {
|
||||
const text = textColour(colour);
|
||||
const text = MamiColour.text(colour);
|
||||
for(const handler of onColourChange)
|
||||
handler(colour, text);
|
||||
};
|
||||
|
||||
const setColour = raw => {
|
||||
colour = parseInt(raw || 0) & 0xFFFFFF;
|
||||
const setColour = (raw, mask) => {
|
||||
raw = raw === undefined ? 0 : parseInt(raw);
|
||||
mask = mask === undefined ? ~0 : parseInt(mask);
|
||||
|
||||
colour = (colour & ~mask) | (raw & mask);
|
||||
runOnColourChange();
|
||||
};
|
||||
|
||||
const runCallback = apply => {
|
||||
const result = Object.defineProperties({}, {
|
||||
apply: { value: apply },
|
||||
colour: { value: colour },
|
||||
hex: { get() { return hexFormat(colour); } },
|
||||
});
|
||||
|
||||
callback(result);
|
||||
};
|
||||
|
||||
const apply = () => {
|
||||
runCallback(true);
|
||||
callback(colour);
|
||||
if(options.autoClose)
|
||||
close();
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
runCallback(false);
|
||||
callback(null);
|
||||
if(options.autoClose)
|
||||
close();
|
||||
};
|
||||
|
@ -118,8 +63,8 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
|
|||
|
||||
const container = <div class="colpick" style={{ zIndex: '9001' }}/>;
|
||||
onColourChange.push((colour, text) => {
|
||||
container.style.setProperty('--colpick-colour', hexFormat(colour));
|
||||
container.style.setProperty('--colpick-text', hexFormat(text));
|
||||
container.style.setProperty('--colpick-colour', MamiColour.hex(colour));
|
||||
container.style.setProperty('--colpick-text', MamiColour.hex(text));
|
||||
});
|
||||
|
||||
const form = <form class="colpick-form" onsubmit={ev => {
|
||||
|
@ -155,8 +100,11 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
|
|||
}
|
||||
};
|
||||
|
||||
const createTab = (id, name, construct) => {
|
||||
const tabContainer = construct();
|
||||
const createTab = (id, name, tabInfo) => {
|
||||
tabInfo.onUpdate(setColour);
|
||||
onColourChange.push(value => tabInfo.updateColour(value));
|
||||
|
||||
const tabContainer = tabInfo.getElement();
|
||||
tabContainer.className = `colpick-tab-container colpick-tab-${id}-container colpick-tab-container-inactive`;
|
||||
tabbedContainer.appendChild(tabContainer);
|
||||
|
||||
|
@ -173,88 +121,13 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
|
|||
};
|
||||
|
||||
if(options.showPresetsTab)
|
||||
createTab('presets', 'Presets', () => {
|
||||
const presets = options.presets;
|
||||
const cont = <div/>;
|
||||
|
||||
for(const preset of presets) {
|
||||
const option = <a href="javascript:void(0);" class="colpick-presets-option" style={{ background: hexFormat(preset.c) }} title={preset.n} onclick={() => setColour(preset.c)}/>;
|
||||
onColourChange.push(value => option.classList.toggle('colpick-presets-option-active', value === preset.c));
|
||||
cont.appendChild(option);
|
||||
}
|
||||
|
||||
return cont;
|
||||
});
|
||||
createTab('presets', 'Presets', new MamiColourPickerPresetsTab(options.presets));
|
||||
|
||||
if(options.showGridTab)
|
||||
createTab('grid', 'Grid', () => {
|
||||
const greys = [0xFFFFFF, 0xEBEBEB, 0xD6D6D6, 0xC2C2C2, 0xADADAD, 0x999999, 0x858585, 0x707070, 0x5C5C5C, 0x474747, 0x333333, 0];
|
||||
const colours = [0x00A1D8, 0x0061FE, 0x4D22B2, 0x982ABC, 0xB92D5D, 0xFF4015, 0xFF6A00, 0xFFAB01, 0xFDC700, 0xFEFB41, 0xD9EC37, 0x76BB40];
|
||||
const shades = [-.675, -.499, -.345, -.134, 0, .134, .345, .499, .675];
|
||||
|
||||
const cont = <div/>;
|
||||
|
||||
for(const grey of greys) {
|
||||
const option = <a href="javascript:void(0);" class="colpick-grid-option" style={{ background: hexFormat(grey) }} onclick={() => setColour(grey)}/>;
|
||||
onColourChange.push(value => option.classList.toggle('colpick-grid-option-active', value === grey));
|
||||
cont.appendChild(option);
|
||||
}
|
||||
|
||||
for(const shade of shades)
|
||||
for(const colour of colours) {
|
||||
const shaded = shadeColour(colour, shade);
|
||||
const option = <a href="javascript:void(0);" class="colpick-grid-option" style={{ background: hexFormat(shaded) }} onclick={() => setColour(shaded)}/>;
|
||||
onColourChange.push(value => option.classList.toggle('colpick-grid-option-active', value === shaded));
|
||||
cont.appendChild(option);
|
||||
}
|
||||
|
||||
return cont;
|
||||
});
|
||||
createTab('grid', 'Grid', new MamiColourPickerGridTab());
|
||||
|
||||
if(options.showSlidersTab)
|
||||
createTab('sliders', 'Sliders', () => {
|
||||
const cont = <div/>;
|
||||
|
||||
const addSlider = (id, name, update, apply) => {
|
||||
let sGradient, sValueSlider, sValueInput;
|
||||
|
||||
const sCont = <div class={['colpick-slider', `colpick-slider-${id}`]}>
|
||||
<div class="colpick-slider-name">{name}</div>
|
||||
<div class="colpick-slider-value">
|
||||
<div class="colpick-slider-value-slider-container">
|
||||
{sGradient = <div class="colpick-slider-gradient" onmousedown={ev => { if(ev.button === 0) setColour(apply(colour, Math.floor(255 * (Math.max(0, Math.min(200, (ev.layerX - 5))) / 200)))) }}/>}
|
||||
{sValueSlider = <input class="colpick-slider-value-slider" type="range" min="0" max="255" oninput={() => setColour(apply(colour, sValueSlider.value))}/>}
|
||||
</div>
|
||||
<div class="colpick-slider-value-input-container">
|
||||
{sValueInput = <input class="colpick-slider-value-input" type="number" min="0" max="255" oninput={() => setColour(apply(colour, sValueInput.value))}/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
cont.appendChild(sCont);
|
||||
|
||||
onColourChange.push(colour => {
|
||||
sCont.style.setProperty('--colpick-slider-colour', hexFormat(apply(0, update(colour))));
|
||||
sValueSlider.value = update(colour);
|
||||
sValueInput.value = update(colour);
|
||||
sGradient.style.background = `linear-gradient(to right, ${hexFormat(apply(colour, 0))}, ${hexFormat(apply(colour, 0xFF))})`;
|
||||
});
|
||||
};
|
||||
|
||||
addSlider('red', 'Red',
|
||||
value => (value >> 16) & 0xFF,
|
||||
(colour, value) => (colour & 0xFFFF) | (Math.min(255, Math.max(0, value)) << 16)
|
||||
);
|
||||
addSlider('green', 'Green',
|
||||
value => (value >> 8) & 0xFF,
|
||||
(colour, value) => (colour & 0xFF00FF) | (Math.min(255, Math.max(0, value)) << 8)
|
||||
);
|
||||
addSlider('blue', 'Blue',
|
||||
value => value & 0xFF,
|
||||
(colour, value) => (colour & 0xFFFF00) | Math.min(255, Math.max(0, value))
|
||||
);
|
||||
|
||||
return cont;
|
||||
});
|
||||
createTab('sliders', 'Sliders', new MamiColourPickerSlidersTab());
|
||||
|
||||
if(activeTab) {
|
||||
height += 261;
|
||||
|
@ -285,7 +158,7 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
|
|||
};
|
||||
|
||||
if(options.showHexValue)
|
||||
addValue('hex', 'Hex', 'text', value => hexFormat(value), value => {
|
||||
addValue('hex', 'Hex', 'text', value => MamiColour.hex(value), value => {
|
||||
while(value.substring(0, 1) === '#')
|
||||
value = value.substring(1);
|
||||
value = value.substring(0, 6);
|
||||
|
|
38
src/mami.js/colpick/tgrid.jsx
Normal file
38
src/mami.js/colpick/tgrid.jsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include colour.js
|
||||
|
||||
const MamiColourPickerGridTab = function() {
|
||||
let onUpdate = () => {};
|
||||
|
||||
const greys = [0xFFFFFF, 0xEBEBEB, 0xD6D6D6, 0xC2C2C2, 0xADADAD, 0x999999, 0x858585, 0x707070, 0x5C5C5C, 0x474747, 0x333333, 0];
|
||||
const colours = [0x00A1D8, 0x0061FE, 0x4D22B2, 0x982ABC, 0xB92D5D, 0xFF4015, 0xFF6A00, 0xFFAB01, 0xFDC700, 0xFEFB41, 0xD9EC37, 0x76BB40];
|
||||
const shades = [-.675, -.499, -.345, -.134, 0, .134, .345, .499, .675];
|
||||
|
||||
const html = <div/>;
|
||||
|
||||
for(const grey of greys)
|
||||
html.appendChild(<button class={['colpick-grid-option', `colpick-grid-option-${grey}`]}
|
||||
style={{ background: MamiColour.hex(grey) }}
|
||||
type="button" onclick={() => onUpdate(grey)}/>);
|
||||
|
||||
for(const shade of shades)
|
||||
for(const colour of colours) {
|
||||
const shaded = MamiColour.shaded(colour, shade);
|
||||
html.appendChild(<button class={['colpick-grid-option', `colpick-grid-option-${shaded}`]}
|
||||
style={{ background: MamiColour.hex(shaded) }}
|
||||
type="button" onclick={() => onUpdate(shaded)}/>);
|
||||
}
|
||||
|
||||
return {
|
||||
getElement: () => html,
|
||||
onUpdate: handler => onUpdate = handler,
|
||||
updateColour: colour => {
|
||||
let elem = html.querySelector('.colpick-grid-option-active');
|
||||
if(elem !== null)
|
||||
elem.classList.remove('colpick-grid-option-active');
|
||||
|
||||
elem = html.querySelector(`.colpick-grid-option-${colour}`);
|
||||
if(elem !== null)
|
||||
elem.classList.add('colpick-grid-option-active');
|
||||
},
|
||||
};
|
||||
};
|
26
src/mami.js/colpick/tpresets.jsx
Normal file
26
src/mami.js/colpick/tpresets.jsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include colour.js
|
||||
|
||||
const MamiColourPickerPresetsTab = function(presets) {
|
||||
let onUpdate = () => {};
|
||||
|
||||
const html = <div/>;
|
||||
|
||||
for(const preset of presets)
|
||||
html.appendChild(<button class={['colpick-presets-option', `colpick-presets-option-${preset.c}`]}
|
||||
style={{ background: MamiColour.hex(preset.c) }} title={preset.n}
|
||||
type="button" onclick={() => onUpdate(preset.c)}/>);
|
||||
|
||||
return {
|
||||
getElement: () => html,
|
||||
onUpdate: handler => onUpdate = handler,
|
||||
updateColour: colour => {
|
||||
let elem = html.querySelector('.colpick-presets-option-active');
|
||||
if(elem !== null)
|
||||
elem.classList.remove('colpick-presets-option-active');
|
||||
|
||||
elem = html.querySelector(`.colpick-presets-option-${colour}`);
|
||||
if(elem !== null)
|
||||
elem.classList.add('colpick-presets-option-active');
|
||||
},
|
||||
};
|
||||
};
|
56
src/mami.js/colpick/tsliders.jsx
Normal file
56
src/mami.js/colpick/tsliders.jsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include colour.js
|
||||
|
||||
const MamiColourPickerSlider = function(name, title, mask, shift) {
|
||||
let onUpdate = () => {};
|
||||
let gradient, slider, input;
|
||||
|
||||
const html = <div class={['colpick-slider', `colpick-slider-${name}`]}>
|
||||
<div class="colpick-slider-name">{title}</div>
|
||||
<div class="colpick-slider-value">
|
||||
<div class="colpick-slider-value-slider-container">
|
||||
{gradient = <div class="colpick-slider-gradient" onmousedown={ev => { if(ev.button === 0) onUpdate(Math.floor(255 * (Math.max(0, Math.min(200, (ev.layerX - 5))) / 200)) << shift, mask); }}/>}
|
||||
{slider = <input class="colpick-slider-value-slider" type="range" min="0" max="255" oninput={() => onUpdate(slider.value << shift, mask)}/>}
|
||||
</div>
|
||||
<div class="colpick-slider-value-input-container">
|
||||
{input = <input class="colpick-slider-value-input" type="number" min="0" max="255" oninput={() => onUpdate(input.value << shift, mask)}/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return {
|
||||
getElement: () => html,
|
||||
onUpdate: handler => onUpdate = handler,
|
||||
updateColour: colour => {
|
||||
const masked = colour & mask,
|
||||
shifted = masked >> shift,
|
||||
inverted = colour & ~mask;
|
||||
|
||||
html.style.setProperty('--colpick-slider-colour', MamiColour.hex(masked));
|
||||
slider.value = shifted;
|
||||
input.value = shifted;
|
||||
gradient.style.background = `linear-gradient(90deg, ${MamiColour.hex(inverted)}, ${MamiColour.hex(inverted | (0xFF << shift))})`;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const MamiColourPickerSlidersTab = function() {
|
||||
const sliders = [
|
||||
new MamiColourPickerSlider('red', 'Red', 0xFF0000, 16),
|
||||
new MamiColourPickerSlider('green', 'Green', 0xFF00, 8),
|
||||
new MamiColourPickerSlider('blue', 'Blue', 0xFF, 0),
|
||||
];
|
||||
|
||||
const html = <div>{...sliders}</div>;
|
||||
|
||||
return {
|
||||
getElement: () => html,
|
||||
onUpdate: handler => {
|
||||
for(const slider of sliders)
|
||||
slider.onUpdate(handler);
|
||||
},
|
||||
updateColour: colour => {
|
||||
for(const slider of sliders)
|
||||
slider.updateColour(colour);
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
#include colour.js
|
||||
#include common.js
|
||||
#include utility.js
|
||||
#include colpick/picker.jsx
|
||||
|
@ -23,9 +24,9 @@ Umi.UI.Markup = (function() {
|
|||
elem.dataset.umiPickerVisible = 'yes';
|
||||
|
||||
const picker = new MamiColourPicker(
|
||||
function(result) {
|
||||
if(result.apply)
|
||||
insertRaw(`[color=${result.hex}]`, '[/color]');
|
||||
function(colour) {
|
||||
if(colour !== null)
|
||||
insertRaw(`[color=${MamiColour.hex(colour)}]`, '[/color]');
|
||||
}, { presets: futami.get('colours') }, null, function() {
|
||||
elem.dataset.umiPickerVisible = 'no';
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue