mirror of
https://github.com/google/blockly.git
synced 2025-12-15 13:50:08 +01:00
Field dropdown with an SVG arrow (#3442)
* Add SVG arrow as an option on the dropdown field
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 + ' ' :
|
||||
@@ -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
|
||||
@@ -242,6 +270,8 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
|
||||
/** @type {!Element} */ (this.selectedMenuItem_.getElement()),
|
||||
/** @type {!Element} */ (this.menu_.getElement()));
|
||||
}
|
||||
|
||||
this.applyColour();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -296,6 +326,7 @@ Blockly.FieldDropdown.prototype.dropdownDispose_ = function() {
|
||||
}
|
||||
this.menu_ = null;
|
||||
this.selectedMenuItem_ = null;
|
||||
this.applyColour();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -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()) {
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -549,27 +599,55 @@ 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the `getText_` developer hook to override the field's text
|
||||
* representation. Get the selected option text. If the selected option is an
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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.
|
||||
|
||||
1
media/dropdown-arrow.svg
Normal file
1
media/dropdown-arrow.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="12.71" height="8.79" viewBox="0 0 12.71 8.79"><title>dropdown-arrow</title><g opacity="0.1"><path d="M12.71,2.44A2.41,2.41,0,0,1,12,4.16L8.08,8.08a2.45,2.45,0,0,1-3.45,0L0.72,4.16A2.42,2.42,0,0,1,0,2.44,2.48,2.48,0,0,1,.71.71C1,0.47,1.43,0,6.36,0S11.75,0.46,12,.71A2.44,2.44,0,0,1,12.71,2.44Z" fill="#231f20"/></g><path d="M6.36,7.79a1.43,1.43,0,0,1-1-.42L1.42,3.45a1.44,1.44,0,0,1,0-2c0.56-.56,9.31-0.56,9.87,0a1.44,1.44,0,0,1,0,2L7.37,7.37A1.43,1.43,0,0,1,6.36,7.79Z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 569 B |
Reference in New Issue
Block a user