Skip to content

Commit

Permalink
Field dropdown with an SVG arrow (google#3442)
Browse files Browse the repository at this point in the history
* Add SVG arrow as an option on the dropdown field
  • Loading branch information
samelhusseini authored Nov 14, 2019
1 parent 564af76 commit c868a86
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 25 deletions.
128 changes: 103 additions & 25 deletions core/field_dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,18 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator, opt_config) {
this.imageElement_ = null;

/**
* SVG arrow element.
* Tspan based arrow element.
* @type {SVGTSpanElement}
* @private
*/
this.arrow_ = null;

/**
* SVG based arrow element.
* @type {SVGElement}
* @private
*/
this.svgArrow_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldDropdown, Blockly.Field);

Expand Down Expand Up @@ -198,14 +205,22 @@ Blockly.FieldDropdown.prototype.initView = function() {
Blockly.FieldDropdown.superClass_.initView.call(this);

this.imageElement_ = /** @type {!SVGImageElement} */
(Blockly.utils.dom.createSvgElement('image',
{
'y': Blockly.FieldDropdown.IMAGE_Y_OFFSET
}, this.fieldGroup_));
(Blockly.utils.dom.createSvgElement('image', {}, this.fieldGroup_));

if (this.constants_.FIELD_DROPDOWN_SVG_ARROW) {
this.createSVGArrow_();
} else {
this.createTextArrow_();
}
};

/**
* Create a tspan based arrow.
* @protected
*/
Blockly.FieldDropdown.prototype.createTextArrow_ = function() {
this.arrow_ = /** @type {!SVGTSpanElement} */
(Blockly.utils.dom.createSvgElement('tspan',
{}, this.textElement_));
(Blockly.utils.dom.createSvgElement('tspan', {}, this.textElement_));
this.arrow_.appendChild(document.createTextNode(
this.sourceBlock_.RTL ?
Blockly.FieldDropdown.ARROW_CHAR + ' ' :
Expand All @@ -217,6 +232,19 @@ Blockly.FieldDropdown.prototype.initView = function() {
}
};

/**
* Create an SVG based arrow.
* @protected
*/
Blockly.FieldDropdown.prototype.createSVGArrow_ = function() {
this.svgArrow_ = Blockly.utils.dom.createSvgElement('image', {
'height': this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
'width': this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px'
}, this.fieldGroup_);
this.svgArrow_.setAttributeNS(Blockly.utils.dom.XLINK_NS, 'xlink:href',
this.constants_.FIELD_DROPDOWN_SVG_ARROW_DATAURI);
};

/**
* Create a dropdown menu under the text.
* @private
Expand All @@ -242,6 +270,8 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
/** @type {!Element} */ (this.selectedMenuItem_.getElement()),
/** @type {!Element} */ (this.menu_.getElement()));
}

this.applyColour();
};

/**
Expand Down Expand Up @@ -296,6 +326,7 @@ Blockly.FieldDropdown.prototype.dropdownDispose_ = function() {
}
this.menu_ = null;
this.selectedMenuItem_ = null;
this.applyColour();
};

/**
Expand Down Expand Up @@ -469,6 +500,17 @@ Blockly.FieldDropdown.prototype.doValueUpdate_ = function(newValue) {
* @package
*/
Blockly.FieldDropdown.prototype.applyColour = function() {
if (this.borderRect_) {
this.borderRect_.setAttribute('stroke',
this.sourceBlock_.style.colourTertiary);
if (this.menu_) {
this.borderRect_.setAttribute('fill',
this.sourceBlock_.style.colourTertiary);
} else {
this.borderRect_.setAttribute('fill',
this.sourceBlock_.style.colourPrimary);
}
}
// Update arrow's colour.
if (this.sourceBlock_ && this.arrow_) {
if (this.sourceBlock_.isShadow()) {
Expand Down Expand Up @@ -514,18 +556,26 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
this.imageElement_.setAttribute('height', imageJson.height);
this.imageElement_.setAttribute('width', imageJson.width);

var arrowWidth = Blockly.utils.dom.getFastTextWidth(
/** @type {!SVGTSpanElement} */ (this.arrow_),
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY);

var imageHeight = Number(imageJson.height);
var imageWidth = Number(imageJson.width);

// Height and width include the border rect.
this.size_.height = imageHeight + Blockly.FieldDropdown.IMAGE_Y_PADDING;
this.size_.height = Math.max(
this.constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT,
imageHeight + Blockly.FieldDropdown.IMAGE_Y_PADDING);
var halfHeight = this.size_.height / 2;
var xPadding = this.constants_.FIELD_BORDER_RECT_X_PADDING;
var arrowWidth = 0;
if (this.svgArrow_) {
arrowWidth = this.positionSVGArrow_(imageWidth + xPadding, halfHeight -
this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
} else {
arrowWidth = Blockly.utils.dom.getFastTextWidth(
/** @type {!SVGTSpanElement} */ (this.arrow_),
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY);
}
this.size_.width = imageWidth + arrowWidth + xPadding * 2;

if (this.sourceBlock_.RTL) {
Expand All @@ -534,12 +584,12 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
this.imageElement_.setAttribute('x', imageX);
this.textElement_.setAttribute('x', arrowX);
} else {
var arrowX =
imageWidth + arrowWidth + xPadding + 1;
var arrowX = imageWidth + arrowWidth + xPadding + 1;
this.textElement_.setAttribute('text-anchor', 'end');
this.textElement_.setAttribute('x', arrowX);
this.imageElement_.setAttribute('x', xPadding);
}
this.imageElement_.setAttribute('y', halfHeight - imageHeight / 2);
};

/**
Expand All @@ -549,25 +599,53 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
Blockly.FieldDropdown.prototype.renderSelectedText_ = function() {
// Retrieves the selected option to display through getText_.
this.textContent_.nodeValue = this.getDisplayText_();
Blockly.utils.dom.addClass(/** @type {!Element} */ (this.textElement_),
'blocklyDropdownText');
this.textElement_.setAttribute('text-anchor', 'start');
this.textElement_.setAttribute('x',
this.constants_.FIELD_BORDER_RECT_X_PADDING);

// Height and width include the border rect.
this.size_.height = Math.max(
this.constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT,
this.constants_.FIELD_TEXT_HEIGHT +
this.constants_.FIELD_BORDER_RECT_Y_PADDING * 2);
this.size_.width = Blockly.utils.dom.getFastTextWidth(this.textElement_,
this.constants_.FIELD_TEXT_HEIGHT);
var halfHeight = this.size_.height / 2;
var textWidth = Blockly.utils.dom.getFastTextWidth(this.textElement_,
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY) +
this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.constants_.FIELD_TEXT_FONTFAMILY);
var xPadding = this.constants_.FIELD_BORDER_RECT_X_PADDING;
var arrowWidth = 0;
if (this.svgArrow_) {
arrowWidth = this.positionSVGArrow_(textWidth + xPadding, halfHeight -
this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
}
this.size_.width = textWidth + arrowWidth + xPadding * 2;

this.textElement_.setAttribute('y', this.size_.height / 2);
this.textElement_.setAttribute('x', this.sourceBlock_.RTL ?
this.size_.width - textWidth - xPadding : xPadding);
this.textElement_.setAttribute('y', halfHeight);
if (!this.constants_.FIELD_TEXT_BASELINE_CENTER) {
this.textElement_.setAttribute('dy',
this.constants_.FIELD_TEXT_BASELINE_Y - this.size_.height / 2);
this.constants_.FIELD_TEXT_BASELINE_Y - halfHeight);
}
};

/**
* Position a drop-down arrow at the appropriate location at render-time.
* @param {number} x X position the arrow is being rendered at, in px.
* @param {number} y Y position the arrow is being rendered at, in px.
* @return {number} Amount of space the arrow is taking up, in px.
* @private
*/
Blockly.FieldDropdown.prototype.positionSVGArrow_ = function(x, y) {
if (!this.svgArrow_) {
return 0;
}
var padding = this.constants_.FIELD_BORDER_RECT_X_PADDING;
var svgArrowSize = this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE;
var arrowX = this.sourceBlock_.RTL ? padding : x + padding;
this.svgArrow_.setAttribute('transform',
'translate(' + arrowX + ',' + y + ')');
return svgArrowSize + padding;
};

/**
Expand Down
6 changes: 6 additions & 0 deletions core/renderers/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ Blockly.blockRendering.ConstantProvider = function() {
*/
this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT;

/**
* Whether or not a dropdown field uses a text or SVG arrow.
* @type {boolean}
*/
this.FIELD_DROPDOWN_SVG_ARROW = false;

/**
* A colour field's default width.
* @type {number}
Expand Down
30 changes: 30 additions & 0 deletions core/renderers/zelos/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,36 @@ Blockly.zelos.ConstantProvider = function() {
*/
this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = 8 * this.GRID_UNIT;

/**
* @override
*/
this.FIELD_DROPDOWN_SVG_ARROW = true;

/**
* A dropdown field's SVG arrow size.
* @type {number}
* @const
*/
this.FIELD_DROPDOWN_SVG_ARROW_SIZE = 12;

/**
* A dropdown field's SVG arrow datauri.
* @type {string}
* @const
*/
this.FIELD_DROPDOWN_SVG_ARROW_DATAURI =
'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllci' +
'AxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMi43MSIgaG' +
'VpZ2h0PSI4Ljc5IiB2aWV3Qm94PSIwIDAgMTIuNzEgOC43OSI+PHRpdGxlPmRyb3Bkb3duLW' +
'Fycm93PC90aXRsZT48ZyBvcGFjaXR5PSIwLjEiPjxwYXRoIGQ9Ik0xMi43MSwyLjQ0QTIuND' +
'EsMi40MSwwLDAsMSwxMiw0LjE2TDguMDgsOC4wOGEyLjQ1LDIuNDUsMCwwLDEtMy40NSwwTD' +
'AuNzIsNC4xNkEyLjQyLDIuNDIsMCwwLDEsMCwyLjQ0LDIuNDgsMi40OCwwLDAsMSwuNzEuNz' +
'FDMSwwLjQ3LDEuNDMsMCw2LjM2LDBTMTEuNzUsMC40NiwxMiwuNzFBMi40NCwyLjQ0LDAsMC' +
'wxLDEyLjcxLDIuNDRaIiBmaWxsPSIjMjMxZjIwIi8+PC9nPjxwYXRoIGQ9Ik02LjM2LDcuNz' +
'lhMS40MywxLjQzLDAsMCwxLTEtLjQyTDEuNDIsMy40NWExLjQ0LDEuNDQsMCwwLDEsMC0yYz' +
'AuNTYtLjU2LDkuMzEtMC41Niw5Ljg3LDBhMS40NCwxLjQ0LDAsMCwxLDAsMkw3LjM3LDcuMz' +
'dBMS40MywxLjQzLDAsMCwxLDYuMzYsNy43OVoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=';

/**
* The ID of the highlight glow filter, or the empty string if no filter is
* set.
Expand Down
1 change: 1 addition & 0 deletions media/dropdown-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c868a86

Please sign in to comment.