@blockly/field-colour
Version:
A Blockly colour field.
295 lines (282 loc) • 9.99 kB
text/typescript
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Block, common as BlocklyCommon} from 'blockly/core';
import {
JavascriptGenerator,
Order as JavascriptOrder,
} from 'blockly/javascript';
import {DartGenerator, Order as DartOrder} from 'blockly/dart';
import {LuaGenerator, Order as LuaOrder} from 'blockly/lua';
import {PhpGenerator, Order as PhpOrder} from 'blockly/php';
import {PythonGenerator, Order as PythonOrder} from 'blockly/python';
import {registerFieldColour} from '../field_colour';
import {Generators} from './generatorsType';
/** The name this block is registered under. */
export const BLOCK_NAME = 'colour_blend';
// Block for blending two colours together.
const jsonDefinition = {
type: BLOCK_NAME,
message0:
'%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} ' +
'%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3',
args0: [
{
type: 'input_value',
name: 'COLOUR1',
check: 'Colour',
align: 'RIGHT',
},
{
type: 'input_value',
name: 'COLOUR2',
check: 'Colour',
align: 'RIGHT',
},
{
type: 'input_value',
name: 'RATIO',
check: 'Number',
align: 'RIGHT',
},
],
output: 'Colour',
helpUrl: '%{BKY_COLOUR_BLEND_HELPURL}',
style: 'colour_blocks',
tooltip: '%{BKY_COLOUR_BLEND_TOOLTIP}',
};
/**
* Javascript block generator function.
*
* @param block The Block instance to generate code for.
* @param generator The JavascriptGenerator calling the function.
* @returns A tuple containing the code string and precedence.
*/
export function toJavascript(
block: Block,
generator: JavascriptGenerator,
): [string, JavascriptOrder] {
// Blend two colours together.
const colour1 =
generator.valueToCode(block, 'COLOUR1', JavascriptOrder.NONE) ||
"'#000000'";
const colour2 =
generator.valueToCode(block, 'COLOUR2', JavascriptOrder.NONE) ||
"'#000000'";
const ratio =
generator.valueToCode(block, 'RATIO', JavascriptOrder.NONE) || 0.5;
const functionName = generator.provideFunction_(
'colourBlend',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) {
ratio = Math.max(Math.min(Number(ratio), 1), 0);
var r1 = parseInt(c1.substring(1, 3), 16);
var g1 = parseInt(c1.substring(3, 5), 16);
var b1 = parseInt(c1.substring(5, 7), 16);
var r2 = parseInt(c2.substring(1, 3), 16);
var g2 = parseInt(c2.substring(3, 5), 16);
var b2 = parseInt(c2.substring(5, 7), 16);
var r = Math.round(r1 * (1 - ratio) + r2 * ratio);
var g = Math.round(g1 * (1 - ratio) + g2 * ratio);
var b = Math.round(b1 * (1 - ratio) + b2 * ratio);
r = ('0' + (r || 0).toString(16)).slice(-2);
g = ('0' + (g || 0).toString(16)).slice(-2);
b = ('0' + (b || 0).toString(16)).slice(-2);
return '#' + r + g + b;
}
`,
);
const code = `${functionName}(${colour1}, ${colour2}, ${ratio})`;
return [code, JavascriptOrder.FUNCTION_CALL];
}
/**
* Dart block generator function.
*
* @param block The Block instance to generate code for.
* @param generator The DartGenerator calling the function.
* @returns A tuple containing the code string and precedence.
*/
export function toDart(
block: Block,
generator: DartGenerator,
): [string, DartOrder] {
// Blend two colours together.
const colour1 =
generator.valueToCode(block, 'COLOUR1', DartOrder.NONE) || "'#000000'";
const colour2 =
generator.valueToCode(block, 'COLOUR2', DartOrder.NONE) || "'#000000'";
const ratio = generator.valueToCode(block, 'RATIO', DartOrder.NONE) || 0.5;
// TODO(#7600): find better approach than casting to any to override
// CodeGenerator declaring .definitions protected.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(generator as any).definitions_['import_dart_math'] =
"import 'dart:math' as Math;";
const functionName = generator.provideFunction_(
'colour_blend',
`
String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String c1, String c2, num ratio) {
ratio = Math.max(Math.min(ratio, 1), 0);
int r1 = int.parse('0x\${c1.substring(1, 3)}');
int g1 = int.parse('0x\${c1.substring(3, 5)}');
int b1 = int.parse('0x\${c1.substring(5, 7)}');
int r2 = int.parse('0x\${c2.substring(1, 3)}');
int g2 = int.parse('0x\${c2.substring(3, 5)}');
int b2 = int.parse('0x\${c2.substring(5, 7)}');
num rn = (r1 * (1 - ratio) + r2 * ratio).round();
String rs = rn.toInt().toRadixString(16);
num gn = (g1 * (1 - ratio) + g2 * ratio).round();
String gs = gn.toInt().toRadixString(16);
num bn = (b1 * (1 - ratio) + b2 * ratio).round();
String bs = bn.toInt().toRadixString(16);
rs = '0$rs';
rs = rs.substring(rs.length - 2);
gs = '0$gs';
gs = gs.substring(gs.length - 2);
bs = '0$bs';
bs = bs.substring(bs.length - 2);
return '#$rs$gs$bs';
}
`,
);
const code = `${functionName}(${colour1}, ${colour2}, ${ratio})`;
return [code, DartOrder.UNARY_POSTFIX];
}
/**
* Lua generator definition.
*
* @param block The Block instance to generate code for.
* @param generator The LuaGenerator calling the function.
* @returns A tuple containing the code string and precedence.
*/
export function toLua(
block: Block,
generator: LuaGenerator,
): [string, LuaOrder] {
// Blend two colours together.
const functionName = generator.provideFunction_(
'colour_blend',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio)
local r1 = tonumber(string.sub(colour1, 2, 3), 16)
local r2 = tonumber(string.sub(colour2, 2, 3), 16)
local g1 = tonumber(string.sub(colour1, 4, 5), 16)
local g2 = tonumber(string.sub(colour2, 4, 5), 16)
local b1 = tonumber(string.sub(colour1, 6, 7), 16)
local b2 = tonumber(string.sub(colour2, 6, 7), 16)
local ratio = math.min(1, math.max(0, ratio))
local r = math.floor(r1 * (1 - ratio) + r2 * ratio + .5)
local g = math.floor(g1 * (1 - ratio) + g2 * ratio + .5)
local b = math.floor(b1 * (1 - ratio) + b2 * ratio + .5)
return string.format("#%02x%02x%02x", r, g, b)
end
`,
);
const colour1 =
generator.valueToCode(block, 'COLOUR1', LuaOrder.NONE) || "'#000000'";
const colour2 =
generator.valueToCode(block, 'COLOUR2', LuaOrder.NONE) || "'#000000'";
const ratio = generator.valueToCode(block, 'RATIO', LuaOrder.NONE) || 0;
const code = `${functionName}(${colour1}, ${colour2}, ${ratio})`;
return [code, LuaOrder.HIGH];
}
/**
* PHP generator definition.
*
* @param block The Block instance to generate code for.
* @param generator The PhpGenerator calling the function.
* @returns A tuple containing the code string and precedence.
*/
export function toPhp(
block: Block,
generator: PhpGenerator,
): [string, PhpOrder] {
// Blend two colours together.
const colour1 =
generator.valueToCode(block, 'COLOUR1', PhpOrder.NONE) || "'#000000'";
const colour2 =
generator.valueToCode(block, 'COLOUR2', PhpOrder.NONE) || "'#000000'";
const ratio = generator.valueToCode(block, 'RATIO', PhpOrder.NONE) || 0.5;
const functionName = generator.provideFunction_(
'colour_blend',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) {
$ratio = max(min($ratio, 1), 0);
$r1 = hexdec(substr($c1, 1, 2));
$g1 = hexdec(substr($c1, 3, 2));
$b1 = hexdec(substr($c1, 5, 2));
$r2 = hexdec(substr($c2, 1, 2));
$g2 = hexdec(substr($c2, 3, 2));
$b2 = hexdec(substr($c2, 5, 2));
$r = round($r1 * (1 - $ratio) + $r2 * $ratio);
$g = round($g1 * (1 - $ratio) + $g2 * $ratio);
$b = round($b1 * (1 - $ratio) + $b2 * $ratio);
$hex = '#';
$hex .= str_pad(dechex($r), 2, '0', STR_PAD_LEFT);
$hex .= str_pad(dechex($g), 2, '0', STR_PAD_LEFT);
$hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT);
return $hex;
}
`,
);
const code = `${functionName}(${colour1}, ${colour2}, ${ratio})`;
return [code, PhpOrder.FUNCTION_CALL];
}
/**
* Python generator definition.
*
* @param block The Block instance to generate code for.
* @param generator The PythonGenerator calling the function.
* @returns A tuple containing the code string and precedence.
*/
export function toPython(
block: Block,
generator: PythonGenerator,
): [string, PythonOrder] {
// Blend two colours together.
const functionName = generator.provideFunction_(
'colour_blend',
`
def ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio):
r1, r2 = int(colour1[1:3], 16), int(colour2[1:3], 16)
g1, g2 = int(colour1[3:5], 16), int(colour2[3:5], 16)
b1, b2 = int(colour1[5:7], 16), int(colour2[5:7], 16)
ratio = min(1, max(0, ratio))
r = round(r1 * (1 - ratio) + r2 * ratio)
g = round(g1 * (1 - ratio) + g2 * ratio)
b = round(b1 * (1 - ratio) + b2 * ratio)
return '#%02x%02x%02x' % (r, g, b)
`,
);
const colour1 =
generator.valueToCode(block, 'COLOUR1', PythonOrder.NONE) || "'#000000'";
const colour2 =
generator.valueToCode(block, 'COLOUR2', PythonOrder.NONE) || "'#000000'";
const ratio = generator.valueToCode(block, 'RATIO', PythonOrder.NONE) || 0;
const code = `${functionName}(${colour1}, ${colour2}, ${ratio})`;
return [code, PythonOrder.FUNCTION_CALL];
}
const definitionsDict = BlocklyCommon.createBlockDefinitionsFromJsonArray([
jsonDefinition,
]);
/** The colour_blend BlockDefinition. */
export const blockDefinition = definitionsDict[BLOCK_NAME];
/**
* Install the `colour_blend` block and all of its dependencies.
*
* @param gens The CodeGenerators to install per-block
* generators on.
*/
export function installBlock(gens: Generators = {}) {
registerFieldColour();
BlocklyCommon.defineBlocks(definitionsDict);
if (gens.javascript) gens.javascript.forBlock[BLOCK_NAME] = toJavascript;
if (gens.dart) {
gens.dart.forBlock[BLOCK_NAME] = toDart;
gens.dart.addReservedWords('Math');
}
if (gens.lua) gens.lua.forBlock[BLOCK_NAME] = toLua;
if (gens.php) gens.php.forBlock[BLOCK_NAME] = toPhp;
if (gens.python) gens.python.forBlock[BLOCK_NAME] = toPython;
}