| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 /** | |
| 5 * @unrestricted | |
| 6 */ | |
| 7 UI.CSSShadowEditor = class extends UI.VBox { | |
| 8 constructor() { | |
| 9 super(true); | |
| 10 this.registerRequiredCSS('ui/cssShadowEditor.css'); | |
| 11 this.contentElement.tabIndex = 0; | |
| 12 this.setDefaultFocusedElement(this.contentElement); | |
| 13 | |
| 14 this._typeField = this.contentElement.createChild('div', 'shadow-editor-fiel
d'); | |
| 15 this._typeField.createChild('label', 'shadow-editor-label').textContent = Co
mmon.UIString('Type'); | |
| 16 this._outsetButton = this._typeField.createChild('button', 'shadow-editor-bu
tton-left'); | |
| 17 this._outsetButton.textContent = Common.UIString('Outset'); | |
| 18 this._outsetButton.addEventListener('click', this._onButtonClick.bind(this),
false); | |
| 19 this._insetButton = this._typeField.createChild('button', 'shadow-editor-but
ton-right'); | |
| 20 this._insetButton.textContent = Common.UIString('Inset'); | |
| 21 this._insetButton.addEventListener('click', this._onButtonClick.bind(this),
false); | |
| 22 | |
| 23 var xField = this.contentElement.createChild('div', 'shadow-editor-field'); | |
| 24 this._xInput = this._createTextInput(xField, Common.UIString('X offset')); | |
| 25 var yField = this.contentElement.createChild('div', 'shadow-editor-field'); | |
| 26 this._yInput = this._createTextInput(yField, Common.UIString('Y offset')); | |
| 27 this._xySlider = xField.createChild('canvas', 'shadow-editor-2D-slider'); | |
| 28 this._xySlider.width = UI.CSSShadowEditor.canvasSize; | |
| 29 this._xySlider.height = UI.CSSShadowEditor.canvasSize; | |
| 30 this._xySlider.tabIndex = -1; | |
| 31 this._halfCanvasSize = UI.CSSShadowEditor.canvasSize / 2; | |
| 32 this._innerCanvasSize = this._halfCanvasSize - UI.CSSShadowEditor.sliderThum
bRadius; | |
| 33 UI.installDragHandle(this._xySlider, this._dragStart.bind(this), this._dragM
ove.bind(this), null, 'default'); | |
| 34 this._xySlider.addEventListener('keydown', this._onCanvasArrowKey.bind(this)
, false); | |
| 35 this._xySlider.addEventListener('blur', this._onCanvasBlur.bind(this), false
); | |
| 36 | |
| 37 var blurField = this.contentElement.createChild('div', 'shadow-editor-blur-f
ield'); | |
| 38 this._blurInput = this._createTextInput(blurField, Common.UIString('Blur')); | |
| 39 this._blurSlider = this._createSlider(blurField); | |
| 40 | |
| 41 this._spreadField = this.contentElement.createChild('div', 'shadow-editor-fi
eld'); | |
| 42 this._spreadInput = this._createTextInput(this._spreadField, Common.UIString
('Spread')); | |
| 43 this._spreadSlider = this._createSlider(this._spreadField); | |
| 44 } | |
| 45 | |
| 46 /** | |
| 47 * @param {!Element} field | |
| 48 * @param {string} propertyName | |
| 49 * @return {!Element} | |
| 50 */ | |
| 51 _createTextInput(field, propertyName) { | |
| 52 var label = field.createChild('label', 'shadow-editor-label'); | |
| 53 label.textContent = propertyName; | |
| 54 label.setAttribute('for', propertyName); | |
| 55 var textInput = field.createChild('input', 'shadow-editor-text-input'); | |
| 56 textInput.type = 'text'; | |
| 57 textInput.id = propertyName; | |
| 58 textInput.addEventListener('keydown', this._handleValueModification.bind(thi
s), false); | |
| 59 textInput.addEventListener('mousewheel', this._handleValueModification.bind(
this), false); | |
| 60 textInput.addEventListener('input', this._onTextInput.bind(this), false); | |
| 61 textInput.addEventListener('blur', this._onTextBlur.bind(this), false); | |
| 62 return textInput; | |
| 63 } | |
| 64 | |
| 65 /** | |
| 66 * @param {!Element} field | |
| 67 * @return {!Element} | |
| 68 */ | |
| 69 _createSlider(field) { | |
| 70 var slider = UI.createSliderLabel(0, UI.CSSShadowEditor.maxRange, -1); | |
| 71 slider.addEventListener('input', this._onSliderInput.bind(this), false); | |
| 72 field.appendChild(slider); | |
| 73 return slider; | |
| 74 } | |
| 75 | |
| 76 /** | |
| 77 * @override | |
| 78 */ | |
| 79 wasShown() { | |
| 80 this._updateUI(); | |
| 81 } | |
| 82 | |
| 83 /** | |
| 84 * @param {!Common.CSSShadowModel} model | |
| 85 */ | |
| 86 setModel(model) { | |
| 87 this._model = model; | |
| 88 this._typeField.hidden = !model.isBoxShadow(); | |
| 89 this._spreadField.hidden = !model.isBoxShadow(); | |
| 90 this._updateUI(); | |
| 91 } | |
| 92 | |
| 93 _updateUI() { | |
| 94 this._updateButtons(); | |
| 95 this._xInput.value = this._model.offsetX().asCSSText(); | |
| 96 this._yInput.value = this._model.offsetY().asCSSText(); | |
| 97 this._blurInput.value = this._model.blurRadius().asCSSText(); | |
| 98 this._spreadInput.value = this._model.spreadRadius().asCSSText(); | |
| 99 this._blurSlider.value = this._model.blurRadius().amount; | |
| 100 this._spreadSlider.value = this._model.spreadRadius().amount; | |
| 101 this._updateCanvas(false); | |
| 102 } | |
| 103 | |
| 104 _updateButtons() { | |
| 105 this._insetButton.classList.toggle('enabled', this._model.inset()); | |
| 106 this._outsetButton.classList.toggle('enabled', !this._model.inset()); | |
| 107 } | |
| 108 | |
| 109 /** | |
| 110 * @param {boolean} drawFocus | |
| 111 */ | |
| 112 _updateCanvas(drawFocus) { | |
| 113 var context = this._xySlider.getContext('2d'); | |
| 114 context.clearRect(0, 0, this._xySlider.width, this._xySlider.height); | |
| 115 | |
| 116 // Draw dashed axes. | |
| 117 context.save(); | |
| 118 context.setLineDash([1, 1]); | |
| 119 context.strokeStyle = 'rgba(210, 210, 210, 0.8)'; | |
| 120 context.beginPath(); | |
| 121 context.moveTo(this._halfCanvasSize, 0); | |
| 122 context.lineTo(this._halfCanvasSize, UI.CSSShadowEditor.canvasSize); | |
| 123 context.moveTo(0, this._halfCanvasSize); | |
| 124 context.lineTo(UI.CSSShadowEditor.canvasSize, this._halfCanvasSize); | |
| 125 context.stroke(); | |
| 126 context.restore(); | |
| 127 | |
| 128 var thumbPoint = this._sliderThumbPosition(); | |
| 129 // Draw 2D slider line. | |
| 130 context.save(); | |
| 131 context.translate(this._halfCanvasSize, this._halfCanvasSize); | |
| 132 context.lineWidth = 2; | |
| 133 context.strokeStyle = 'rgba(130, 130, 130, 0.75)'; | |
| 134 context.beginPath(); | |
| 135 context.moveTo(0, 0); | |
| 136 context.lineTo(thumbPoint.x, thumbPoint.y); | |
| 137 context.stroke(); | |
| 138 // Draw 2D slider thumb. | |
| 139 if (drawFocus) { | |
| 140 context.beginPath(); | |
| 141 context.fillStyle = 'rgba(66, 133, 244, 0.4)'; | |
| 142 context.arc(thumbPoint.x, thumbPoint.y, UI.CSSShadowEditor.sliderThumbRadi
us + 2, 0, 2 * Math.PI); | |
| 143 context.fill(); | |
| 144 } | |
| 145 context.beginPath(); | |
| 146 context.fillStyle = '#4285F4'; | |
| 147 context.arc(thumbPoint.x, thumbPoint.y, UI.CSSShadowEditor.sliderThumbRadius
, 0, 2 * Math.PI); | |
| 148 context.fill(); | |
| 149 context.restore(); | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * @param {!Event} event | |
| 154 */ | |
| 155 _onButtonClick(event) { | |
| 156 var insetClicked = (event.currentTarget === this._insetButton); | |
| 157 if (insetClicked && this._model.inset() || !insetClicked && !this._model.ins
et()) | |
| 158 return; | |
| 159 this._model.setInset(insetClicked); | |
| 160 this._updateButtons(); | |
| 161 this.dispatchEventToListeners(UI.CSSShadowEditor.Events.ShadowChanged, this.
_model); | |
| 162 } | |
| 163 | |
| 164 /** | |
| 165 * @param {!Event} event | |
| 166 */ | |
| 167 _handleValueModification(event) { | |
| 168 var modifiedValue = UI.createReplacementString(event.currentTarget.value, ev
ent, customNumberHandler); | |
| 169 if (!modifiedValue) | |
| 170 return; | |
| 171 var length = Common.CSSLength.parse(modifiedValue); | |
| 172 if (!length) | |
| 173 return; | |
| 174 if (event.currentTarget === this._blurInput && length.amount < 0) | |
| 175 length.amount = 0; | |
| 176 event.currentTarget.value = length.asCSSText(); | |
| 177 event.currentTarget.selectionStart = 0; | |
| 178 event.currentTarget.selectionEnd = event.currentTarget.value.length; | |
| 179 this._onTextInput(event); | |
| 180 event.consume(true); | |
| 181 | |
| 182 /** | |
| 183 * @param {string} prefix | |
| 184 * @param {number} number | |
| 185 * @param {string} suffix | |
| 186 * @return {string} | |
| 187 */ | |
| 188 function customNumberHandler(prefix, number, suffix) { | |
| 189 if (!suffix.length) | |
| 190 suffix = UI.CSSShadowEditor.defaultUnit; | |
| 191 return prefix + number + suffix; | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 /** | |
| 196 * @param {!Event} event | |
| 197 */ | |
| 198 _onTextInput(event) { | |
| 199 this._changedElement = event.currentTarget; | |
| 200 this._changedElement.classList.remove('invalid'); | |
| 201 var length = Common.CSSLength.parse(event.currentTarget.value); | |
| 202 if (!length || event.currentTarget === this._blurInput && length.amount < 0) | |
| 203 return; | |
| 204 if (event.currentTarget === this._xInput) { | |
| 205 this._model.setOffsetX(length); | |
| 206 this._updateCanvas(false); | |
| 207 } else if (event.currentTarget === this._yInput) { | |
| 208 this._model.setOffsetY(length); | |
| 209 this._updateCanvas(false); | |
| 210 } else if (event.currentTarget === this._blurInput) { | |
| 211 this._model.setBlurRadius(length); | |
| 212 this._blurSlider.value = length.amount; | |
| 213 } else if (event.currentTarget === this._spreadInput) { | |
| 214 this._model.setSpreadRadius(length); | |
| 215 this._spreadSlider.value = length.amount; | |
| 216 } | |
| 217 this.dispatchEventToListeners(UI.CSSShadowEditor.Events.ShadowChanged, this.
_model); | |
| 218 } | |
| 219 | |
| 220 _onTextBlur() { | |
| 221 if (!this._changedElement) | |
| 222 return; | |
| 223 var length = !this._changedElement.value.trim() ? Common.CSSLength.zero() : | |
| 224 Common.CSSLength.parse(thi
s._changedElement.value); | |
| 225 if (!length) | |
| 226 length = Common.CSSLength.parse(this._changedElement.value + UI.CSSShadowE
ditor.defaultUnit); | |
| 227 if (!length) { | |
| 228 this._changedElement.classList.add('invalid'); | |
| 229 this._changedElement = null; | |
| 230 return; | |
| 231 } | |
| 232 if (this._changedElement === this._xInput) { | |
| 233 this._model.setOffsetX(length); | |
| 234 this._xInput.value = length.asCSSText(); | |
| 235 this._updateCanvas(false); | |
| 236 } else if (this._changedElement === this._yInput) { | |
| 237 this._model.setOffsetY(length); | |
| 238 this._yInput.value = length.asCSSText(); | |
| 239 this._updateCanvas(false); | |
| 240 } else if (this._changedElement === this._blurInput) { | |
| 241 if (length.amount < 0) | |
| 242 length = Common.CSSLength.zero(); | |
| 243 this._model.setBlurRadius(length); | |
| 244 this._blurInput.value = length.asCSSText(); | |
| 245 this._blurSlider.value = length.amount; | |
| 246 } else if (this._changedElement === this._spreadInput) { | |
| 247 this._model.setSpreadRadius(length); | |
| 248 this._spreadInput.value = length.asCSSText(); | |
| 249 this._spreadSlider.value = length.amount; | |
| 250 } | |
| 251 this._changedElement = null; | |
| 252 this.dispatchEventToListeners(UI.CSSShadowEditor.Events.ShadowChanged, this.
_model); | |
| 253 } | |
| 254 | |
| 255 /** | |
| 256 * @param {!Event} event | |
| 257 */ | |
| 258 _onSliderInput(event) { | |
| 259 if (event.currentTarget === this._blurSlider) { | |
| 260 this._model.setBlurRadius(new Common.CSSLength( | |
| 261 this._blurSlider.value, this._model.blurRadius().unit || UI.CSSShadowE
ditor.defaultUnit)); | |
| 262 this._blurInput.value = this._model.blurRadius().asCSSText(); | |
| 263 this._blurInput.classList.remove('invalid'); | |
| 264 } else if (event.currentTarget === this._spreadSlider) { | |
| 265 this._model.setSpreadRadius(new Common.CSSLength( | |
| 266 this._spreadSlider.value, this._model.spreadRadius().unit || UI.CSSSha
dowEditor.defaultUnit)); | |
| 267 this._spreadInput.value = this._model.spreadRadius().asCSSText(); | |
| 268 this._spreadInput.classList.remove('invalid'); | |
| 269 } | |
| 270 this.dispatchEventToListeners(UI.CSSShadowEditor.Events.ShadowChanged, this.
_model); | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * @param {!MouseEvent} event | |
| 275 * @return {boolean} | |
| 276 */ | |
| 277 _dragStart(event) { | |
| 278 this._xySlider.focus(); | |
| 279 this._updateCanvas(true); | |
| 280 this._canvasOrigin = new Common.Geometry.Point( | |
| 281 this._xySlider.totalOffsetLeft() + this._halfCanvasSize, | |
| 282 this._xySlider.totalOffsetTop() + this._halfCanvasSize); | |
| 283 var clickedPoint = new Common.Geometry.Point(event.x - this._canvasOrigin.x,
event.y - this._canvasOrigin.y); | |
| 284 var thumbPoint = this._sliderThumbPosition(); | |
| 285 if (clickedPoint.distanceTo(thumbPoint) >= UI.CSSShadowEditor.sliderThumbRad
ius) | |
| 286 this._dragMove(event); | |
| 287 return true; | |
| 288 } | |
| 289 | |
| 290 /** | |
| 291 * @param {!MouseEvent} event | |
| 292 */ | |
| 293 _dragMove(event) { | |
| 294 var point = new Common.Geometry.Point(event.x - this._canvasOrigin.x, event.
y - this._canvasOrigin.y); | |
| 295 if (event.shiftKey) | |
| 296 point = this._snapToClosestDirection(point); | |
| 297 var constrainedPoint = this._constrainPoint(point, this._innerCanvasSize); | |
| 298 var newX = Math.round((constrainedPoint.x / this._innerCanvasSize) * UI.CSSS
hadowEditor.maxRange); | |
| 299 var newY = Math.round((constrainedPoint.y / this._innerCanvasSize) * UI.CSSS
hadowEditor.maxRange); | |
| 300 | |
| 301 if (event.shiftKey) { | |
| 302 this._model.setOffsetX(new Common.CSSLength(newX, this._model.offsetX().un
it || UI.CSSShadowEditor.defaultUnit)); | |
| 303 this._model.setOffsetY(new Common.CSSLength(newY, this._model.offsetY().un
it || UI.CSSShadowEditor.defaultUnit)); | |
| 304 } else { | |
| 305 if (!event.altKey) { | |
| 306 this._model.setOffsetX( | |
| 307 new Common.CSSLength(newX, this._model.offsetX().unit || UI.CSSShado
wEditor.defaultUnit)); | |
| 308 } | |
| 309 if (!UI.KeyboardShortcut.eventHasCtrlOrMeta(event)) { | |
| 310 this._model.setOffsetY( | |
| 311 new Common.CSSLength(newY, this._model.offsetY().unit || UI.CSSShado
wEditor.defaultUnit)); | |
| 312 } | |
| 313 } | |
| 314 this._xInput.value = this._model.offsetX().asCSSText(); | |
| 315 this._yInput.value = this._model.offsetY().asCSSText(); | |
| 316 this._xInput.classList.remove('invalid'); | |
| 317 this._yInput.classList.remove('invalid'); | |
| 318 this._updateCanvas(true); | |
| 319 this.dispatchEventToListeners(UI.CSSShadowEditor.Events.ShadowChanged, this.
_model); | |
| 320 } | |
| 321 | |
| 322 _onCanvasBlur() { | |
| 323 this._updateCanvas(false); | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * @param {!Event} event | |
| 328 */ | |
| 329 _onCanvasArrowKey(event) { | |
| 330 var shiftX = 0; | |
| 331 var shiftY = 0; | |
| 332 if (event.key === 'ArrowRight') | |
| 333 shiftX = 1; | |
| 334 else if (event.key === 'ArrowLeft') | |
| 335 shiftX = -1; | |
| 336 else if (event.key === 'ArrowUp') | |
| 337 shiftY = -1; | |
| 338 else if (event.key === 'ArrowDown') | |
| 339 shiftY = 1; | |
| 340 | |
| 341 if (!shiftX && !shiftY) | |
| 342 return; | |
| 343 event.consume(true); | |
| 344 | |
| 345 if (shiftX) { | |
| 346 var offsetX = this._model.offsetX(); | |
| 347 var newAmount = | |
| 348 Number.constrain(offsetX.amount + shiftX, -UI.CSSShadowEditor.maxRange
, UI.CSSShadowEditor.maxRange); | |
| 349 if (newAmount === offsetX.amount) | |
| 350 return; | |
| 351 this._model.setOffsetX(new Common.CSSLength(newAmount, offsetX.unit || UI.
CSSShadowEditor.defaultUnit)); | |
| 352 this._xInput.value = this._model.offsetX().asCSSText(); | |
| 353 this._xInput.classList.remove('invalid'); | |
| 354 } | |
| 355 if (shiftY) { | |
| 356 var offsetY = this._model.offsetY(); | |
| 357 var newAmount = | |
| 358 Number.constrain(offsetY.amount + shiftY, -UI.CSSShadowEditor.maxRange
, UI.CSSShadowEditor.maxRange); | |
| 359 if (newAmount === offsetY.amount) | |
| 360 return; | |
| 361 this._model.setOffsetY(new Common.CSSLength(newAmount, offsetY.unit || UI.
CSSShadowEditor.defaultUnit)); | |
| 362 this._yInput.value = this._model.offsetY().asCSSText(); | |
| 363 this._yInput.classList.remove('invalid'); | |
| 364 } | |
| 365 this._updateCanvas(true); | |
| 366 this.dispatchEventToListeners(UI.CSSShadowEditor.Events.ShadowChanged, this.
_model); | |
| 367 } | |
| 368 | |
| 369 /** | |
| 370 * @param {!Common.Geometry.Point} point | |
| 371 * @param {number} max | |
| 372 * @return {!Common.Geometry.Point} | |
| 373 */ | |
| 374 _constrainPoint(point, max) { | |
| 375 if (Math.abs(point.x) <= max && Math.abs(point.y) <= max) | |
| 376 return new Common.Geometry.Point(point.x, point.y); | |
| 377 return point.scale(max / Math.max(Math.abs(point.x), Math.abs(point.y))); | |
| 378 } | |
| 379 | |
| 380 /** | |
| 381 * @param {!Common.Geometry.Point} point | |
| 382 * @return {!Common.Geometry.Point} | |
| 383 */ | |
| 384 _snapToClosestDirection(point) { | |
| 385 var minDistance = Number.MAX_VALUE; | |
| 386 var closestPoint = point; | |
| 387 | |
| 388 var directions = [ | |
| 389 new Common.Geometry.Point(0, -1), // North | |
| 390 new Common.Geometry.Point(1, -1), // Northeast | |
| 391 new Common.Geometry.Point(1, 0), // East | |
| 392 new Common.Geometry.Point(1, 1) // Southeast | |
| 393 ]; | |
| 394 | |
| 395 for (var direction of directions) { | |
| 396 var projection = point.projectOn(direction); | |
| 397 var distance = point.distanceTo(projection); | |
| 398 if (distance < minDistance) { | |
| 399 minDistance = distance; | |
| 400 closestPoint = projection; | |
| 401 } | |
| 402 } | |
| 403 | |
| 404 return closestPoint; | |
| 405 } | |
| 406 | |
| 407 /** | |
| 408 * @return {!Common.Geometry.Point} | |
| 409 */ | |
| 410 _sliderThumbPosition() { | |
| 411 var x = (this._model.offsetX().amount / UI.CSSShadowEditor.maxRange) * this.
_innerCanvasSize; | |
| 412 var y = (this._model.offsetY().amount / UI.CSSShadowEditor.maxRange) * this.
_innerCanvasSize; | |
| 413 return this._constrainPoint(new Common.Geometry.Point(x, y), this._innerCanv
asSize); | |
| 414 } | |
| 415 }; | |
| 416 | |
| 417 /** @enum {symbol} */ | |
| 418 UI.CSSShadowEditor.Events = { | |
| 419 ShadowChanged: Symbol('ShadowChanged') | |
| 420 }; | |
| 421 | |
| 422 /** @type {number} */ | |
| 423 UI.CSSShadowEditor.maxRange = 20; | |
| 424 /** @type {string} */ | |
| 425 UI.CSSShadowEditor.defaultUnit = 'px'; | |
| 426 /** @type {number} */ | |
| 427 UI.CSSShadowEditor.sliderThumbRadius = 6; | |
| 428 /** @type {number} */ | |
| 429 UI.CSSShadowEditor.canvasSize = 88; | |
| OLD | NEW |