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 |