From f4d85ac575e4a69ed463fc022659035651fc19b6 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 24 Apr 2024 15:40:17 -0700 Subject: [PATCH] fix: modernize and reenable the colour slider field --- blocks_common/colour.js | 11 +- core/css.js | 34 ++ core/field_colour_slider.js | 655 ++++++++++++++++++------------------ src/constants.js | 3 + src/index.js | 2 + 5 files changed, 375 insertions(+), 330 deletions(-) diff --git a/blocks_common/colour.js b/blocks_common/colour.js index 30be7523ac..c947329ce3 100644 --- a/blocks_common/colour.js +++ b/blocks_common/colour.js @@ -22,13 +22,8 @@ * @fileoverview Colour blocks for Blockly. * @author fraser@google.com (Neil Fraser) */ -'use strict'; - -goog.provide('Blockly.Blocks.colour'); - -goog.require('Blockly.Blocks'); - -goog.require('Blockly.constants'); +import * as Blockly from 'blockly'; +import * as Constants from '../src/constants.js'; /** * Pick a random colour. @@ -54,7 +49,7 @@ Blockly.Blocks['colour_picker'] = { "colour": randomColour() } ], - "outputShape": Blockly.OUTPUT_SHAPE_ROUND, + "outputShape": Constants.OUTPUT_SHAPE_ROUND, "output": "Colour" }); } diff --git a/core/css.js b/core/css.js index f7c15db668..6eb7d6d266 100644 --- a/core/css.js +++ b/core/css.js @@ -881,6 +881,10 @@ const styles = ` cursor: pointer; } + .scratchColourPicker { + width: min-content; + } + .scratchColourPickerLabel { font-family: "Helvetica Neue", Helvetica, sans-serif; font-size: 0.65rem; @@ -896,6 +900,36 @@ const styles = ` margin-left: 10px; } + .scratchColourSlider { + appearance: none; + margin: 8px; + height: 22px; + width: 150px; + position: relative; + outline: none; + border-radius: 11px; + margin-bottom: 20px; + } + + /* Combining this and the -moz equivalent below with a comma break the webkit version */ + .scratchColourSlider::-webkit-slider-thumb { + appearance: none; + background-color: #fff; + height: 26px; + width: 26px; + border-radius: 100%; + box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.15); + } + + .scratchColourSlider::-moz-range-thumb { + appearance: none; + background-color: #fff; + height: 26px; + width: 26px; + border-radius: 100%; + box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.15); + } + .scratchMatrixButtonDiv { width: 50%; text-align: center; diff --git a/core/field_colour_slider.js b/core/field_colour_slider.js index 331de6975a..23d0cc628a 100644 --- a/core/field_colour_slider.js +++ b/core/field_colour_slider.js @@ -22,17 +22,7 @@ * @fileoverview Colour input field. * @author fraser@google.com (Neil Fraser) */ -'use strict'; - -goog.provide('Blockly.FieldColourSlider'); - -goog.require('Blockly.Field'); -goog.require('Blockly.DropDownDiv'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.style'); -goog.require('goog.color'); -goog.require('goog.ui.Slider'); +import * as Blockly from 'blockly/core'; /** * Class for a slider-based colour input field. @@ -45,343 +35,364 @@ goog.require('goog.ui.Slider'); * @extends {Blockly.Field} * @constructor */ -Blockly.FieldColourSlider = function(colour, opt_validator) { - Blockly.FieldColourSlider.superClass_.constructor.call(this, colour, opt_validator); - this.addArgType('colour'); - - // Flag to track whether or not the slider callbacks should execute - this.sliderCallbacksEnabled_ = false; -}; -goog.inherits(Blockly.FieldColourSlider, Blockly.Field); +export class FieldColourSlider extends Blockly.FieldColour { + /** + * Function to be called if eyedropper can be activated. + * If defined, an eyedropper button will be added to the color picker. + * The button calls this function with a callback to update the field value. + * BEWARE: This is not a stable API, so it is being marked as private. It may change. + * @private + */ + static activateEyedropper_ = null; + + constructor(colour, opt_validator) { + super(colour, opt_validator); + + // Flag to track whether or not the slider callbacks should execute + this.sliderCallbacksEnabled_ = false; -/** - * Construct a FieldColourSlider from a JSON arg object. - * @param {!Object} options A JSON object with options (colour). - * @returns {!Blockly.FieldColourSlider} The new field instance. - * @package - * @nocollapse - */ -Blockly.FieldColourSlider.fromJson = function(options) { - return new Blockly.FieldColourSlider(options['colour']); -}; + /** + * Path to the eyedropper svg icon. + */ + this.EYEDROPPER_PATH = 'eyedropper.svg'; + this.SERIALIZABLE = true; + this.EDITABLE = true; + } -/** - * Function to be called if eyedropper can be activated. - * If defined, an eyedropper button will be added to the color picker. - * The button calls this function with a callback to update the field value. - * BEWARE: This is not a stable API, so it is being marked as private. It may change. - * @private - */ -Blockly.FieldColourSlider.activateEyedropper_ = null; + /** + * Construct a FieldColourSlider from a JSON arg object. + * @param {!Object} options A JSON object with options (colour). + * @returns {!Blockly.FieldColourSlider} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + return new FieldColourSlider(options['colour']); + } -/** - * Path to the eyedropper svg icon. - */ -Blockly.FieldColourSlider.EYEDROPPER_PATH = 'eyedropper.svg'; + /** + * Install this field on a block. + * @param {!Blockly.Block} block The block containing this field. + */ + init(block) { + if (this.fieldGroup_) { + // Colour slider has already been initialized once. + return; + } + super.init(block); + this.setValue(this.getValue()); + } -/** - * Install this field on a block. - * @param {!Blockly.Block} block The block containing this field. - */ -Blockly.FieldColourSlider.prototype.init = function(block) { - if (this.fieldGroup_) { - // Colour slider has already been initialized once. - return; + /** + * Return the current colour. + * @return {string} Current colour in '#rrggbb' format. + */ + getValue() { + return this.colour_; } - Blockly.FieldColourSlider.superClass_.init.call(this, block); - this.setValue(this.getValue()); -}; -/** - * Return the current colour. - * @return {string} Current colour in '#rrggbb' format. - */ -Blockly.FieldColourSlider.prototype.getValue = function() { - return this.colour_; -}; + /** + * Set the colour. + * @param {string} colour The new colour in '#rrggbb' format. + */ + setValue(colour) { + if (this.sourceBlock_ && Blockly.Events.isEnabled() && + this.colour_ != colour) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, this.colour_, colour)); + } + this.colour_ = colour; + if (this.sourceBlock_) { + // Set the colours to this value. + // The renderer expects to be able to use the secondary colour as the fill for a shadow. + this.sourceBlock_.setColour(colour); + } + this.updateSliderHandles_(); + this.updateDom_(); + } -/** - * Set the colour. - * @param {string} colour The new colour in '#rrggbb' format. - */ -Blockly.FieldColourSlider.prototype.setValue = function(colour) { - if (this.sourceBlock_ && Blockly.Events.isEnabled() && - this.colour_ != colour) { - Blockly.Events.fire(new Blockly.Events.BlockChange( - this.sourceBlock_, 'field', this.name, this.colour_, colour)); + /** + * Create the hue, saturation or value CSS gradient for the slide backgrounds. + * @param {string} channel – Either "hue", "saturation" or "value". + * @return {string} Array colour hex colour stops for the given channel + * @private + */ + createColourStops_(channel) { + var stops = []; + for(var n = 0; n <= 360; n += 20) { + switch (channel) { + case 'hue': + stops.push(Blockly.utils.colour.hsvToHex(n, this.saturation_, this.brightness_)); + break; + case 'saturation': + stops.push(Blockly.utils.colour.hsvToHex(this.hue_, n / 360, this.brightness_)); + break; + case 'brightness': + stops.push(Blockly.utils.colour.hsvToHex(this.hue_, this.saturation_, 255 * n / 360)); + break; + default: + throw new Error("Unknown channel for colour sliders: " + channel); + } + } + return stops; } - this.colour_ = colour; - if (this.sourceBlock_) { - // Set the colours to this value. - // The renderer expects to be able to use the secondary colour as the fill for a shadow. - this.sourceBlock_.setColour(colour, colour, this.sourceBlock_.getColourTertiary(), - this.sourceBlock_.getColourQuaternary()); + + /** + * Set the gradient CSS properties for the given node and channel + * @param {Node} node - The DOM node the gradient will be set on. + * @param {string} channel – Either "hue", "saturation" or "value". + * @private + */ + setGradient_(node, channel) { + var gradient = this.createColourStops_(channel).join(','); + node.style['background'] = `linear-gradient(to right, ${gradient})`; } - this.updateSliderHandles_(); - this.updateDom_(); -}; -/** - * Create the hue, saturation or value CSS gradient for the slide backgrounds. - * @param {string} channel – Either "hue", "saturation" or "value". - * @return {string} Array colour hex colour stops for the given channel - * @private - */ -Blockly.FieldColourSlider.prototype.createColourStops_ = function(channel) { - var stops = []; - for(var n = 0; n <= 360; n += 20) { - switch (channel) { - case 'hue': - stops.push(goog.color.hsvToHex(n, this.saturation_, this.brightness_)); - break; - case 'saturation': - stops.push(goog.color.hsvToHex(this.hue_, n / 360, this.brightness_)); - break; - case 'brightness': - stops.push(goog.color.hsvToHex(this.hue_, this.saturation_, 255 * n / 360)); - break; - default: - throw new Error("Unknown channel for colour sliders: " + channel); + /** + * Update the readouts and slider backgrounds after value has changed. + * @private + */ + updateDom_() { + + if (this.hueSlider_) { + // Update the slider backgrounds + this.setGradient_(this.hueSlider_, 'hue'); + this.setGradient_(this.saturationSlider_, 'saturation'); + this.setGradient_(this.brightnessSlider_, 'brightness'); + + // Update the readouts + this.hueReadout_.textContent = Math.floor(100 * this.hue_ / 360).toFixed(0); + this.saturationReadout_.textContent = Math.floor(100 * this.saturation_).toFixed(0); + this.brightnessReadout_.textContent = Math.floor(100 * this.brightness_ / 255).toFixed(0); } } - return stops; -}; -/** - * Set the gradient CSS properties for the given node and channel - * @param {Node} node - The DOM node the gradient will be set on. - * @param {string} channel – Either "hue", "saturation" or "value". - * @private - */ -Blockly.FieldColourSlider.prototype.setGradient_ = function(node, channel) { - var gradient = this.createColourStops_(channel).join(','); - goog.style.setStyle(node, 'background', - '-moz-linear-gradient(left, ' + gradient + ')'); - goog.style.setStyle(node, 'background', - '-webkit-linear-gradient(left, ' + gradient + ')'); - goog.style.setStyle(node, 'background', - '-o-linear-gradient(left, ' + gradient + ')'); - goog.style.setStyle(node, 'background', - '-ms-linear-gradient(left, ' + gradient + ')'); - goog.style.setStyle(node, 'background', - 'linear-gradient(left, ' + gradient + ')'); -}; + /** + * Update the slider handle positions from the current field value. + * @private + */ + updateSliderHandles_() { + if (this.hueSlider_) { + // Don't let the following calls to setValue for each of the sliders + // trigger the slider callbacks (which then call setValue on this field again + // unnecessarily) + this.sliderCallbacksEnabled_ = false; + this.hueSlider_.value = this.hue_; + this.saturationSlider_.value = this.saturation_; + this.brightnessSlider_.value = this.brightness_; + this.sliderCallbacksEnabled_ = true; + } + } -/** - * Update the readouts and slider backgrounds after value has changed. - * @private - */ -Blockly.FieldColourSlider.prototype.updateDom_ = function() { - if (this.hueSlider_) { - // Update the slider backgrounds - this.setGradient_(this.hueSlider_.getElement(), 'hue'); - this.setGradient_(this.saturationSlider_.getElement(), 'saturation'); - this.setGradient_(this.brightnessSlider_.getElement(), 'brightness'); - - // Update the readouts - this.hueReadout_.textContent = Math.floor(100 * this.hue_ / 360).toFixed(0); - this.saturationReadout_.textContent = Math.floor(100 * this.saturation_).toFixed(0); - this.brightnessReadout_.textContent = Math.floor(100 * this.brightness_ / 255).toFixed(0); + /** + * Get the text from this field. Used when the block is collapsed. + * @return {string} Current text. + */ + getText() { + var colour = this.colour_; + // Try to use #rgb format if possible, rather than #rrggbb. + var m = colour.match(/^#(.)\1(.)\2(.)\3$/); + if (m) { + colour = '#' + m[1] + m[2] + m[3]; + } + return colour; } -}; -/** - * Update the slider handle positions from the current field value. - * @private - */ -Blockly.FieldColourSlider.prototype.updateSliderHandles_ = function() { - if (this.hueSlider_) { - // Don't let the following calls to setValue for each of the sliders - // trigger the slider callbacks (which then call setValue on this field again - // unnecessarily) - this.sliderCallbacksEnabled_ = false; - this.hueSlider_.setValue(this.hue_); - this.saturationSlider_.setValue(this.saturation_); - this.brightnessSlider_.setValue(this.brightness_); - this.sliderCallbacksEnabled_ = true; + /** + * Create label and readout DOM elements, returning the readout + * @param {string} labelText - Text for the label + * @return {Array} The container node and the readout node. + * @private + */ + createLabelDom_(labelText) { + var labelContainer = document.createElement('div'); + labelContainer.setAttribute('class', 'scratchColourPickerLabel'); + var readout = document.createElement('span'); + readout.setAttribute('class', 'scratchColourPickerReadout'); + var label = document.createElement('span'); + label.setAttribute('class', 'scratchColourPickerLabelText'); + label.textContent = labelText; + labelContainer.appendChild(label); + labelContainer.appendChild(readout); + return [labelContainer, readout]; } -}; -/** - * Get the text from this field. Used when the block is collapsed. - * @return {string} Current text. - */ -Blockly.FieldColourSlider.prototype.getText = function() { - var colour = this.colour_; - // Try to use #rgb format if possible, rather than #rrggbb. - var m = colour.match(/^#(.)\1(.)\2(.)\3$/); - if (m) { - colour = '#' + m[1] + m[2] + m[3]; + /** + * Factory for creating the different slider callbacks + * @param {string} channel - One of "hue", "saturation" or "brightness" + * @return {function} the callback for slider update + * @private + */ + sliderCallbackFactory_(channel) { + var thisField = this; + return function(event) { + if (!thisField.sliderCallbacksEnabled_) return; + var channelValue = event.target.value; + switch (channel) { + case 'hue': + thisField.hue_ = channelValue; + break; + case 'saturation': + thisField.saturation_ = channelValue; + break; + case 'brightness': + thisField.brightness_ = channelValue; + break; + } + var colour = Blockly.utils.colour.hsvToHex(thisField.hue_, thisField.saturation_, thisField.brightness_); + if (colour !== null) { + thisField.setValue(colour, true); + } + }; } - return colour; -}; -/** - * Create label and readout DOM elements, returning the readout - * @param {string} labelText - Text for the label - * @return {Array} The container node and the readout node. - * @private - */ -Blockly.FieldColourSlider.prototype.createLabelDom_ = function(labelText) { - var labelContainer = document.createElement('div'); - labelContainer.setAttribute('class', 'scratchColourPickerLabel'); - var readout = document.createElement('span'); - readout.setAttribute('class', 'scratchColourPickerReadout'); - var label = document.createElement('span'); - label.setAttribute('class', 'scratchColourPickerLabelText'); - label.textContent = labelText; - labelContainer.appendChild(label); - labelContainer.appendChild(readout); - return [labelContainer, readout]; -}; + /** + * Activate the eyedropper, passing in a callback for setting the field value. + * @private + */ + activateEyedropperInternal_() { + var thisField = this; + FieldColourSlider.activateEyedropper_(function(value) { + // Update the internal hue/saturation/brightness values so sliders update. + const components = Blockly.utils.colour.hexToRgb(value); + const hsv = thisField.rgbToHsv(components[0], components[1], components[2]); + thisField.hue_ = hsv[0]; + thisField.saturation_ = hsv[1]; + thisField.brightness_ = hsv[2]; + thisField.setValue(value); + }); + } -/** - * Factory for creating the different slider callbacks - * @param {string} channel - One of "hue", "saturation" or "brightness" - * @return {function} the callback for slider update - * @private - */ -Blockly.FieldColourSlider.prototype.sliderCallbackFactory_ = function(channel) { - var thisField = this; - return function(event) { - if (!thisField.sliderCallbacksEnabled_) return; - var channelValue = event.target.getValue(); - switch (channel) { - case 'hue': - thisField.hue_ = channelValue; - break; - case 'saturation': - thisField.saturation_ = channelValue; - break; - case 'brightness': - thisField.brightness_ = channelValue; - break; - } - var colour = goog.color.hsvToHex(thisField.hue_, thisField.saturation_, thisField.brightness_); - if (thisField.sourceBlock_) { - // Call any validation function, and allow it to override. - colour = thisField.callValidator(colour); + /** + * Create hue, saturation and brightness sliders under the colour field. + * @private + */ + showEditor_() { + Blockly.DropDownDiv.hideWithoutAnimation(); + Blockly.DropDownDiv.clearContent(); + var div = Blockly.DropDownDiv.getContentDiv(); + div.className = 'scratchColourPicker'; + + // Init color component values that are used while the editor is open + // in order to keep the slider values stable. + const components = Blockly.utils.colour.hexToRgb(this.getValue()); + var hsv = this.rgbToHsv(components[0], components[1], components[2]); + this.hue_ = hsv[0]; + this.saturation_ = hsv[1]; + this.brightness_ = hsv[2]; + + var hueElements = this.createLabelDom_(Blockly.Msg.COLOUR_HUE_LABEL); + div.appendChild(hueElements[0]); + this.hueReadout_ = hueElements[1]; + this.hueSlider_ = document.createElement('input'); + this.hueSlider_.type = 'range'; + this.hueSlider_.min = 0; + this.hueSlider_.max = 360; + this.hueSlider_.className = 'scratchColourSlider'; + div.appendChild(this.hueSlider_); + + var saturationElements = + this.createLabelDom_(Blockly.Msg.COLOUR_SATURATION_LABEL); + div.appendChild(saturationElements[0]); + this.saturationReadout_ = saturationElements[1]; + this.saturationSlider_ = document.createElement('input'); + this.saturationSlider_.type = 'range'; + this.saturationSlider_.step = 0.001; + this.saturationSlider_.min = 0; + this.saturationSlider_.max = 1.0; + this.saturationSlider_.className = 'scratchColourSlider'; + div.appendChild(this.saturationSlider_); + + var brightnessElements = + this.createLabelDom_(Blockly.Msg.COLOUR_BRIGHTNESS_LABEL); + div.appendChild(brightnessElements[0]); + this.brightnessReadout_ = brightnessElements[1]; + this.brightnessSlider_ = document.createElement('input'); + this.brightnessSlider_.type = 'range'; + this.brightnessSlider_.min = 0; + this.brightnessSlider_.max = 255; + this.brightnessSlider_.className = 'scratchColourSlider'; + div.appendChild(this.brightnessSlider_); + + if (FieldColourSlider.activateEyedropper_) { + var button = document.createElement('button'); + button.setAttribute('class', 'scratchEyedropper'); + var image = document.createElement('img'); + image.src = Blockly.getMainWorkspace().options.pathToMedia + this.EYEDROPPER_PATH; + button.appendChild(image); + div.appendChild(button); + this.eyedropperEventData_ = + Blockly.browserEvents.conditionalBind(button, 'click', this, + this.activateEyedropperInternal_); } - if (colour !== null) { - thisField.setValue(colour, true); - } - }; -}; -/** - * Activate the eyedropper, passing in a callback for setting the field value. - * @private - */ -Blockly.FieldColourSlider.prototype.activateEyedropperInternal_ = function() { - var thisField = this; - Blockly.FieldColourSlider.activateEyedropper_(function(value) { - // Update the internal hue/saturation/brightness values so sliders update. - var hsv = goog.color.hexToHsv(value); - thisField.hue_ = hsv[0]; - thisField.saturation_ = hsv[1]; - thisField.brightness_ = hsv[2]; - thisField.setValue(value); - }); -}; + Blockly.DropDownDiv.setColour('#ffffff', '#dddddd'); + Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_); -/** - * Create hue, saturation and brightness sliders under the colour field. - * @private - */ -Blockly.FieldColourSlider.prototype.showEditor_ = function() { - Blockly.DropDownDiv.hideWithoutAnimation(); - Blockly.DropDownDiv.clearContent(); - var div = Blockly.DropDownDiv.getContentDiv(); - - // Init color component values that are used while the editor is open - // in order to keep the slider values stable. - var hsv = goog.color.hexToHsv(this.getValue()); - this.hue_ = hsv[0]; - this.saturation_ = hsv[1]; - this.brightness_ = hsv[2]; - - var hueElements = this.createLabelDom_(Blockly.Msg.COLOUR_HUE_LABEL); - div.appendChild(hueElements[0]); - this.hueReadout_ = hueElements[1]; - this.hueSlider_ = new goog.ui.Slider(); - this.hueSlider_.setUnitIncrement(5); - this.hueSlider_.setMinimum(0); - this.hueSlider_.setMaximum(360); - this.hueSlider_.setMoveToPointEnabled(true); - this.hueSlider_.render(div); - - var saturationElements = - this.createLabelDom_(Blockly.Msg.COLOUR_SATURATION_LABEL); - div.appendChild(saturationElements[0]); - this.saturationReadout_ = saturationElements[1]; - this.saturationSlider_ = new goog.ui.Slider(); - this.saturationSlider_.setMoveToPointEnabled(true); - this.saturationSlider_.setUnitIncrement(0.01); - this.saturationSlider_.setStep(0.001); - this.saturationSlider_.setMinimum(0); - this.saturationSlider_.setMaximum(1.0); - this.saturationSlider_.render(div); - - var brightnessElements = - this.createLabelDom_(Blockly.Msg.COLOUR_BRIGHTNESS_LABEL); - div.appendChild(brightnessElements[0]); - this.brightnessReadout_ = brightnessElements[1]; - this.brightnessSlider_ = new goog.ui.Slider(); - this.brightnessSlider_.setUnitIncrement(2); - this.brightnessSlider_.setMinimum(0); - this.brightnessSlider_.setMaximum(255); - this.brightnessSlider_.setMoveToPointEnabled(true); - this.brightnessSlider_.render(div); - - if (Blockly.FieldColourSlider.activateEyedropper_) { - var button = document.createElement('button'); - button.setAttribute('class', 'scratchEyedropper'); - var image = document.createElement('img'); - image.src = Blockly.mainWorkspace.options.pathToMedia + Blockly.FieldColourSlider.EYEDROPPER_PATH; - button.appendChild(image); - div.appendChild(button); - Blockly.FieldColourSlider.eyedropperEventData_ = - Blockly.bindEventWithChecks_(button, 'click', this, - this.activateEyedropperInternal_); - } + // Set value updates the slider positions + // Do this before attaching callbacks to avoid extra events from initial set + this.setValue(this.getValue()); - Blockly.DropDownDiv.setColour('#ffffff', '#dddddd'); - Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory()); - Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_); - - // Set value updates the slider positions - // Do this before attaching callbacks to avoid extra events from initial set - this.setValue(this.getValue()); - - // Enable callbacks for the sliders - this.sliderCallbacksEnabled_ = true; - - Blockly.FieldColourSlider.hueChangeEventKey_ = goog.events.listen(this.hueSlider_, - goog.ui.Component.EventType.CHANGE, - this.sliderCallbackFactory_('hue')); - Blockly.FieldColourSlider.saturationChangeEventKey_ = goog.events.listen(this.saturationSlider_, - goog.ui.Component.EventType.CHANGE, - this.sliderCallbackFactory_('saturation')); - Blockly.FieldColourSlider.brightnessChangeEventKey_ = goog.events.listen(this.brightnessSlider_, - goog.ui.Component.EventType.CHANGE, - this.sliderCallbackFactory_('brightness')); -}; - -Blockly.FieldColourSlider.prototype.dispose = function() { - if (Blockly.FieldColourSlider.hueChangeEventKey_) { - goog.events.unlistenByKey(Blockly.FieldColourSlider.hueChangeEventKey_); - } - if (Blockly.FieldColourSlider.saturationChangeEventKey_) { - goog.events.unlistenByKey(Blockly.FieldColourSlider.saturationChangeEventKey_); + // Enable callbacks for the sliders + this.sliderCallbacksEnabled_ = true; + + this.hueChangeEventKey_ = Blockly.browserEvents.bind( + this.hueSlider_, 'input', this, this.sliderCallbackFactory_('hue')); + this.saturationChangeEventKey_ = Blockly.browserEvents.bind( + this.saturationSlider_, 'input', this, this.sliderCallbackFactory_('saturation')); + this.brightnessChangeEventKey_ = Blockly.browserEvents.bind( + this.brightnessSlider_, 'input', this, this.sliderCallbackFactory_('brightness')); } - if (Blockly.FieldColourSlider.brightnessChangeEventKey_) { - goog.events.unlistenByKey(Blockly.FieldColourSlider.brightnessChangeEventKey_); + + dispose() { + if (this.hueChangeEventKey_) { + Blockly.browserEvents.unbind(this.hueChangeEventKey_); + } + if (this.saturationChangeEventKey_) { + Blockly.browserEvents.unbind(this.saturationChangeEventKey_); + } + if (this.brightnessChangeEventKey_) { + Blockly.browserEvents.unbind(this.brightnessChangeEventKey_); + } + if (this.eyedropperEventData_) { + Blockly.browserEvents.unbind(this.eyedropperEventData_); + } + Blockly.Events.setGroup(false); + super.dispose(); } - if (Blockly.FieldColourSlider.eyedropperEventData_) { - Blockly.unbindEvent_(Blockly.FieldColourSlider.eyedropperEventData_); + + // From Closure + rgbToHsv(red, green, blue) { + const max = Math.max(Math.max(red, green), blue); + const min = Math.min(Math.min(red, green), blue); + let hue; + let saturation; + const value = max; + if (min == max) { + hue = 0; + saturation = 0; + } else { + const delta = (max - min); + saturation = delta / max; + + if (red == max) { + hue = (green - blue) / delta; + } else if (green == max) { + hue = 2 + ((blue - red) / delta); + } else { + hue = 4 + ((red - green) / delta); + } + hue *= 60; + if (hue < 0) { + hue += 360; + } + if (hue > 360) { + hue -= 360; + } + } + + return [hue, saturation, value]; } - Blockly.Events.setGroup(false); - Blockly.FieldColourSlider.superClass_.dispose.call(this); -}; +} -Blockly.Field.register('field_colour_slider', Blockly.FieldColourSlider); +Blockly.fieldRegistry.register('field_colour_slider', FieldColourSlider); diff --git a/src/constants.js b/src/constants.js index b1bf5d3da4..39cc18d098 100644 --- a/src/constants.js +++ b/src/constants.js @@ -37,3 +37,6 @@ export {PROCEDURES_PROTOTYPE_BLOCK_TYPE}; */ const PROCEDURES_CALL_BLOCK_TYPE = 'procedures_call'; export {PROCEDURES_CALL_BLOCK_TYPE}; + +const OUTPUT_SHAPE_ROUND = 2; +export {OUTPUT_SHAPE_ROUND}; diff --git a/src/index.js b/src/index.js index b945430684..500a560126 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ */ import * as Blockly from 'blockly/core'; +import '../blocks_common/colour.js'; import '../blocks_common/math.js'; import '../blocks_common/text.js'; import '../blocks_vertical/vertical_extensions.js'; @@ -31,6 +32,7 @@ export * from 'blockly'; export * from './categories.js'; export * from './procedures.js'; export * from '../core/colours.js'; +export * from '../core/field_colour_slider.js'; export * from '../msg/scratch_msgs.js'; export {scratchBlocksUtils};