Skip to content

Commit 11c03ac

Browse files
authored
add a new color picker block (#11292)
* beginnings of new color picker block * more color picker work * better support for keyboard controls * fix reading initial value * allow extensions to contribute color picker block * lint
1 parent f76f5fc commit 11c03ac

21 files changed

Lines changed: 1871 additions & 22 deletions

ThirdPartyNotice

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,7 @@ General Public License.
882882
864. @fortawesome/fontawesome-free 5.15.4 (https://www.npmjs.com/package/@fortawesome/fontawesome-free/v/5.15.4)
883883
865. @blockly/plugin-workspace-search 4.0.10 (https://www.npmjs.com/package/@blockly/plugin-workspace-search/v/4.0.10)
884884
866. @blockly/keyboard-navigation 1.0.0-beta.0 (https://www.npmjs.com/package/@blockly/keyboard-navigation/v/1.0.0-beta.0)
885+
867. Qix-/color-convert 5c106a633b5cd2de554d9c287ad31f9eeca7a271 (https://github.com/Qix-/color-convert)
885886

886887

887888

@@ -28361,14 +28362,14 @@ The copyright in this software is being made available under the BSD License, in
2836128362

2836228363
**Copyright (c) 2015, Dash Industry Forum.
2836328364
**All rights reserved.**
28364-
28365+
2836528366
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
2836628367
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2836728368
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
2836828369
* Neither the name of the Dash Industry Forum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
2836928370

2837028371
**THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.**
28371-
28372+
2837228373
=========================================
2837328374
END OF dashjs 4.4.0 NOTICES AND INFORMATION
2837428375

@@ -28955,3 +28956,30 @@ END OF @blockly/plugin-workspace-search 4.0.10 NOTICES AND INFORMATION
2895528956
limitations under the License.
2895628957
=========================================
2895728958
END OF @blockly/keyboard-navigation 1.0.0-beta.0 NOTICES AND INFORMATION
28959+
28960+
28961+
%% Qix-/color-convert 5c106a633b5cd2de554d9c287ad31f9eeca7a271 NOTICES AND INFORMATION BEGIN HERE
28962+
=========================================
28963+
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>.
28964+
Copyright (c) 2016-2021 Josh Junon <josh@junon.me>.
28965+
28966+
Permission is hereby granted, free of charge, to any person obtaining
28967+
a copy of this software and associated documentation files (the
28968+
"Software"), to deal in the Software without restriction, including
28969+
without limitation the rights to use, copy, modify, merge, publish,
28970+
distribute, sublicense, and/or sell copies of the Software, and to
28971+
permit persons to whom the Software is furnished to do so, subject to
28972+
the following conditions:
28973+
28974+
The above copyright notice and this permission notice shall be
28975+
included in all copies or substantial portions of the Software.
28976+
28977+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28978+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28979+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28980+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28981+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28982+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28983+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28984+
=========================================
28985+
END OF Qix-/color-convert 5c106a633b5cd2de554d9c287ad31f9eeca7a271 NOTICES AND INFORMATION

libs/pxt-common/pxt-helpers.ts

Lines changed: 170 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ function parseInt(text: string, radix?: number): number {
6565
switch (text.charAt(start)) {
6666
case "-":
6767
sign = -1;
68-
// fallthrough
68+
// fallthrough
6969
case "+":
7070
++start;
7171
}
7272

7373
if ((!radix || radix == 16)
74-
&& "0" === text[start]
75-
&& ("x" === text[start + 1] || "X" === text[start + 1])) {
74+
&& "0" === text[start]
75+
&& ("x" === text[start + 1] || "X" === text[start + 1])) {
7676
radix = 16;
7777
start += 2;
7878
} else if (!radix) {
@@ -753,3 +753,170 @@ namespace __internal {
753753
return ms;
754754
}
755755
}
756+
757+
758+
// Converted to TS from https://github.com/Qix-/color-convert/blob/5c106a633b5cd2de554d9c287ad31f9eeca7a271/conversions.js
759+
// See ThirdPartyNotice in microsoft/pxt for license information
760+
namespace colorHelpers {
761+
/**
762+
* Converts a red, green, and blue color value into a single color number.
763+
*
764+
*
765+
* @param red The red component of the color, between 0 and 255
766+
* @param green The green component of the color, between 0 and 255
767+
* @param blue The blue component of the color, between 0 and 255
768+
* @returns The combined color as a single number
769+
*/
770+
export function rgb(red: number, green: number, blue: number): number {
771+
return (Math.clamp(0, 255, red) << 16) | (Math.clamp(0, 255, green) << 8) | Math.clamp(0, 255, blue);
772+
}
773+
774+
/**
775+
* Converts a hue, saturation, and value (HSV) color into a single color number.
776+
*
777+
*
778+
* @param hue The hue component of the color, between 0 and 360
779+
* @param saturation The saturation component of the color, between 0 and 100
780+
* @param value The value component of the color, between 0 and 100
781+
* @returns The combined color as a single number
782+
*/
783+
export function hsv(hue: number, saturation: number, value: number): number {
784+
hue = Math.clamp(0, 360, hue) / 60;
785+
saturation = Math.clamp(0, 100, saturation) / 100;
786+
value = Math.clamp(0, 100, value) / 100;
787+
788+
const hi = Math.floor(hue) % 6;
789+
790+
const f = hue - Math.floor(hue);
791+
const p = 255 * value * (1 - saturation);
792+
const q = 255 * value * (1 - (saturation * f));
793+
const t = 255 * value * (1 - (saturation * (1 - f)));
794+
value *= 255;
795+
796+
switch (hi) {
797+
case 0:
798+
return rgb(value, t, p);
799+
case 1:
800+
return rgb(q, value, p);
801+
case 2:
802+
return rgb(p, value, t);
803+
case 3:
804+
return rgb(p, q, value);
805+
case 4:
806+
return rgb(t, p, value);
807+
case 5:
808+
return rgb(value, p, q);
809+
}
810+
811+
// never
812+
return 0;
813+
}
814+
815+
/**
816+
* Converts a hue, saturation, and lightness (HSL) color into a single color number.
817+
*
818+
*
819+
* @param hue The hue component of the color, between 0 and 360
820+
* @param saturation The saturation component of the color, between 0 and 100
821+
* @param lightness The lightness component of the color, between 0 and 100
822+
* @returns The combined color as a single number
823+
*/
824+
export function hsl(hue: number, saturation: number, lightness: number): number {
825+
hue = Math.clamp(0, 360, hue) / 360;
826+
saturation = Math.clamp(0, 100, saturation) / 100;
827+
lightness = Math.clamp(0, 100, lightness) / 100;
828+
829+
let t3: number;
830+
let value: number;
831+
832+
if (saturation === 0) {
833+
value = lightness * 255;
834+
return rgb(value, value, value);
835+
}
836+
837+
const t2 = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
838+
839+
const t1 = 2 * lightness - t2;
840+
841+
const channels: number[] = [0, 0, 0];
842+
for (let i = 0; i < 3; i++) {
843+
t3 = hue + 1 / 3 * -(i - 1);
844+
if (t3 < 0) {
845+
t3++;
846+
}
847+
848+
if (t3 > 1) {
849+
t3--;
850+
}
851+
852+
if (6 * t3 < 1) {
853+
value = t1 + (t2 - t1) * 6 * t3;
854+
}
855+
else if (2 * t3 < 1) {
856+
value = t2;
857+
}
858+
else if (3 * t3 < 2) {
859+
value = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
860+
}
861+
else {
862+
value = t1;
863+
}
864+
865+
channels[i] = value * 255;
866+
}
867+
868+
return rgb(channels[0], channels[1], channels[2]);
869+
}
870+
871+
/**
872+
* Converts a hexadecimal color string into a single color number. The hexadecimal string can be in the short
873+
* 3-digit form ("#f0a") or the full 6-digit form ("#ff00aa").
874+
*
875+
*
876+
* @param hex A hexadecimal color string, optionally starting with "#" and either in 3-digit or 6-digit format
877+
* @returns The combined color as a single number
878+
*/
879+
export function hex(hex: string): number {
880+
if (hex.charAt(0) === "#") {
881+
hex = hex.slice(1);
882+
}
883+
884+
if (hex.length === 3) {
885+
hex = hex.split("").map(c => c + c).join("");
886+
}
887+
888+
if (hex.length !== 6) {
889+
return 0;
890+
}
891+
892+
const num = parseInt(hex, 16);
893+
if (isNaN(num)) {
894+
return 0;
895+
}
896+
897+
return num;
898+
}
899+
900+
/**
901+
* Converts a CMYK color into a single color number.
902+
*
903+
*
904+
* @param cyan The cyan component of the color, between 0 and 100
905+
* @param magenta The magenta component of the color, between 0 and 100
906+
* @param yellow The yellow component of the color, between 0 and 100
907+
* @param black The black component of the color, between 0 and 100
908+
* @returns The combined color as a single number
909+
*/
910+
export function cmyk(cyan: number, magenta: number, yellow: number, black: number): number {
911+
cyan = Math.clamp(0, 100, cyan) / 100;
912+
magenta = Math.clamp(0, 100, magenta) / 100;
913+
yellow = Math.clamp(0, 100, yellow) / 100;
914+
black = Math.clamp(0, 100, black) / 100;
915+
916+
const r = 1 - Math.min(1, cyan * (1 - black) + black);
917+
const g = 1 - Math.min(1, magenta * (1 - black) + black);
918+
const b = 1 - Math.min(1, yellow * (1 - black) + black);
919+
920+
return rgb(r * 255, g * 255, b * 255);
921+
}
922+
}

localtypings/pxtarget.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,7 @@ declare namespace ts.pxtc {
10271027
alias?: string; // another symbol alias for this member
10281028
pyAlias?: string; // optional python version of the alias
10291029
blockAliasFor?: string; // qname of the function this block is an alias for
1030+
builtinBlockId?: string; // if present, this block is to be replaced with a builtin block in the toolbox
10301031
}
10311032

10321033
interface ParamSnippet {

pxtblocks/compiler/compiler.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { FieldTilemap, FieldTextInput } from "../fields";
1212
import { CommonFunctionBlock } from "../plugins/functions/commonFunctionMixin";
1313
import { getContainingFunction } from "../plugins/duplicateOnDrag";
1414
import { FUNCTION_DEFINITION_BLOCK_TYPE } from "../plugins/functions/constants";
15+
import { COLOR_NUMBER_BLOCK_TYPE, COLOR_PICKER_BLOCK_TYPE, COLOR_STRING_BLOCK_TYPE } from "../plugins/colorpicker";
1516

1617

1718
interface Rect {
@@ -436,8 +437,8 @@ export function compileExpression(e: Environment, b: Blockly.Block, comments: st
436437
case "math_number":
437438
case "math_integer":
438439
case "math_whole_number":
439-
expr = compileNumber(e, b, comments); break;
440440
case "math_number_minmax":
441+
case COLOR_NUMBER_BLOCK_TYPE:
441442
expr = compileNumber(e, b, comments); break;
442443
case "math_op2":
443444
expr = compileMathOp2(e, b, comments); break;
@@ -457,6 +458,7 @@ export function compileExpression(e: Environment, b: Blockly.Block, comments: st
457458
case "variables_get_reporter":
458459
expr = compileVariableGet(e, b); break;
459460
case "text":
461+
case COLOR_STRING_BLOCK_TYPE:
460462
expr = compileText(e, b, comments); break;
461463
case "text_join":
462464
expr = compileTextJoin(e, b, comments); break;
@@ -480,6 +482,9 @@ export function compileExpression(e: Environment, b: Blockly.Block, comments: st
480482
break;
481483
case "function_call_output":
482484
expr = compileFunctionCall(e, b, comments, false); break;
485+
case COLOR_PICKER_BLOCK_TYPE:
486+
expr = compileColorPicker(e, b, comments);
487+
break;
483488
default:
484489
let call = e.stdCallTable[b.type];
485490
if (call) {
@@ -1338,13 +1343,13 @@ function compileWorkspaceComment(c: Blockly.comments.RenderedWorkspaceComment):
13381343
}
13391344

13401345
function isLiteral(e: Environment, b: Blockly.Block) {
1341-
return isNumericLiteral(e, b) || b.type === "logic_boolean" || b.type === "text";
1346+
return isNumericLiteral(e, b) || b.type === "logic_boolean" || b.type === "text" || b.type === COLOR_STRING_BLOCK_TYPE;
13421347
}
13431348

13441349
function isNumericLiteral(e: Environment, b: Blockly.Block): boolean {
13451350
if (!b) return false;
13461351

1347-
if (b.type === "math_number" || b.type === "math_integer" || b.type === "math_number_minmax" || b.type === "math_whole_number") {
1352+
if (b.type === "math_number" || b.type === "math_integer" || b.type === "math_number_minmax" || b.type === "math_whole_number" || b.type === COLOR_NUMBER_BLOCK_TYPE) {
13481353
return true;
13491354
}
13501355

@@ -1388,6 +1393,35 @@ function compileNumber(e: Environment, b: Blockly.Block, comments: string[]): px
13881393
return pxt.blocks.H.mkNumberLiteral(extractNumber(b));
13891394
}
13901395

1396+
function compileColorPicker(e: Environment, b: Blockly.Block, comments: string[]): pxt.blocks.JsNode {
1397+
const format = b.getFieldValue("FORMAT");
1398+
1399+
if (format === "hex") {
1400+
return pxt.blocks.H.namespaceCall(
1401+
"colorHelpers",
1402+
"hex",
1403+
[compileExpression(e, getInputTargetBlock(e, b, "HEX_INPUT"), comments)],
1404+
false
1405+
);
1406+
}
1407+
else {
1408+
const inputs: pxt.blocks.JsNode[] = [];
1409+
for (let i = 0; i < 4; i++) {
1410+
const input = b.getInput("INPUT" + i);
1411+
if (input) {
1412+
inputs.push(compileExpression(e, getInputTargetBlock(e, b, input.name), comments));
1413+
}
1414+
}
1415+
return pxt.blocks.H.namespaceCall(
1416+
"colorHelpers",
1417+
format,
1418+
inputs,
1419+
false
1420+
);
1421+
}
1422+
}
1423+
1424+
13911425
function throwBlockError(msg: string, block: Blockly.Block) {
13921426
let e = new Error(msg);
13931427
(e as any).block = block;

pxtblocks/compiler/typeChecker.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CommonFunctionBlock } from "../plugins/functions/commonFunctionMixin";
77
import { PXT_WARNING_ID } from "./compiler";
88
import { DRAGGABLE_PARAM_INPUT_PREFIX } from "../loader";
99
import { getContainingFunction } from "../plugins/duplicateOnDrag";
10+
import { COLOR_PICKER_BLOCK_TYPE } from "..";
1011

1112
interface DeclaredVariable {
1213
name: string;
@@ -149,6 +150,22 @@ export function infer(allBlocks: Blockly.Block[], e: Environment, w: Blockly.Wor
149150
break;
150151
case pxtc.PAUSE_UNTIL_TYPE:
151152
unionParam(e, b, "PREDICATE", pBoolean);
153+
break;
154+
case COLOR_PICKER_BLOCK_TYPE:
155+
const format = b.getFieldValue("FORMAT");
156+
157+
if (format === "hex") {
158+
unionParam(e, b, "HEX_INPUT", ground("string"));
159+
}
160+
else {
161+
for (let i = 0; i < 4; i++) {
162+
const input = b.getInput("INPUT" + i);
163+
if (input) {
164+
unionParam(e, b, input.name, ground("number"));
165+
}
166+
}
167+
}
168+
152169
break;
153170
default:
154171
if (b.type in e.stdCallTable) {

pxtblocks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from "./plugins/renderer";
77
export * from "./plugins/flyout";
88
export * from "./plugins/newVariableField";
99
export * from "./plugins/comments";
10+
export * from "./plugins/colorpicker";
1011
export * from "./compiler/compiler";
1112
export * from "./compiler/environment";
1213
export * from "./loader";

0 commit comments

Comments
 (0)