OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SampleCode.h" | 8 #include "SampleCode.h" |
9 #include "SkCanvas.h" | 9 #include "SkCanvas.h" |
10 #include "SkLightingShader.h" | 10 #include "SkLightingShader.h" |
11 #include "SkNormalSource.h" | 11 #include "SkNormalSource.h" |
12 #include "sk_tool_utils.h" | 12 #include "sk_tool_utils.h" |
13 | 13 |
14 class ParentControl; | |
15 | |
16 // Abstract base class for all components that a control panel must have | |
17 class Control : public SkRefCnt { | |
18 public: | |
robertphillips
2016/08/19 17:50:20
one line ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
19 Control(SkString name) | |
20 : fName(name), fParent(nullptr), fRelativeY(0) {} | |
21 | |
22 // Use this to propagate a click's position down to a control. Gets modulate d by the component's | |
23 // relative position | |
robertphillips
2016/08/19 17:50:19
const & ?
dvonbeck
2016/08/19 18:25:16
Done.
| |
24 bool click(SkPoint clickPos) { | |
25 SkPoint relativeClickPos = SkPoint::Make(clickPos.fX, clickPos.fY - fRel ativeY); | |
26 return this->onClick(relativeClickPos); | |
27 } | |
28 | |
29 // Use this to draw the control and its appropriate children. Gets modulated by the component's | |
30 // relative position. | |
31 void drawContent(SkCanvas *canvas) { | |
32 canvas->save(); | |
33 canvas->translate(0, fRelativeY); | |
34 this->onDrawContent(canvas); | |
35 canvas->restore(); | |
36 } | |
37 | |
robertphillips
2016/08/19 17:50:19
argumend ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
38 // Returns true when click position argumend lands over a control region in this control. Click | |
39 // position gets modulated by the component's relative position. | |
robertphillips
2016/08/19 17:50:19
const& ?
dvonbeck
2016/08/19 18:25:18
Done.
| |
40 bool isInCtrlRegion(SkPoint click) { | |
41 SkPoint relativeClickPos = SkPoint::Make(click.fX, click.fY - fRelativeY ); | |
42 return this->onIsInCtrlRegion(relativeClickPos); | |
43 } | |
44 | |
45 // Returns height of content drawn | |
46 virtual SkScalar height() const = 0; | |
47 | |
48 // Sets the parent of this component. May only be used once. Height must rem ain constant after | |
49 // parent is set. | |
50 void setParent(ParentControl *parent, SkScalar relativeY) { | |
51 SkASSERT(parent); | |
52 SkASSERT(!fParent); // No chidren transfer since relativeY would get inv alid for younger kid | |
53 | |
54 fParent = parent; | |
55 fRelativeY = relativeY; | |
56 this->onSetParent(); | |
57 } | |
58 | |
59 // Overriden by sub-classes that need to recompute fields after parent is se t. Called after | |
60 // setting fParent. | |
61 virtual void onSetParent() {} | |
62 | |
63 // Overriden by sub-classes that need to know when a click is released. | |
64 virtual void onClickRelease() {} | |
65 | |
66 protected: | |
67 | |
68 // Draws a label for the component, using its name and a passed value. Does NOT modulate by | |
69 // relative height, expects CTM to have been adjusted in advance. | |
robertphillips
2016/08/19 17:50:19
& on string ?
dvonbeck
2016/08/19 18:25:18
Done.
| |
70 void drawLabel(SkCanvas *canvas, const SkString valueStr) const { | |
71 // TODO Cache this | |
72 sk_sp<SkTypeface> fLabelTypeface = | |
73 sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyl e()); | |
74 | |
75 SkString label; | |
76 label.append(fName); | |
77 label.append(": "); | |
78 label.append(valueStr); | |
79 | |
80 SkPaint labelPaint; | |
81 labelPaint.setTypeface(fLabelTypeface); | |
82 labelPaint.setAntiAlias(true); | |
83 labelPaint.setColor(0xFFFFFFFF); | |
84 labelPaint.setTextSize(12.0f); | |
85 | |
86 canvas->drawText(label.c_str(), label.size(), 0, kLabelHeight - 6.0f, la belPaint); | |
87 } | |
88 | |
89 SkString fName; | |
90 ParentControl* fParent; | |
91 | |
92 static constexpr SkScalar kLabelHeight = 20.0f; | |
93 | |
94 private: | |
95 // Overriden by sub-class to draw component. Do not call directly, drawConte nt() modulates by | |
96 // relative position. | |
97 virtual void onDrawContent(SkCanvas *canvas) = 0; | |
98 | |
99 // Overriden by sub-class to handle clicks. Do not call directly, click() mo dulates by relative | |
100 // position. Return true if holding mouse capture | |
101 virtual bool onClick(SkPoint clickPos) { return false; }; | |
egdaniel
2016/08/19 17:56:35
const &
dvonbeck
2016/08/19 18:25:18
Done.
| |
102 | |
103 // Overriden by sub-classes with controls. Should return true if clickPos la nds inside a control | |
104 // region, to enable mouse caputre. | |
robertphillips
2016/08/19 17:50:19
const SkPoint& ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
105 virtual bool onIsInCtrlRegion(SkPoint clickPos) const { return false; }; | |
106 | |
robertphillips
2016/08/19 17:50:19
Why not a relativeX too ? An SkPoint ?
| |
107 // The y-coordinate of the control relative to it's parent | |
108 SkScalar fRelativeY; | |
109 }; | |
110 | |
111 class ParentControl : public Control { // Interface for all controls that have c hildren | |
112 public: | |
robertphillips
2016/08/19 17:50:20
const SkString& ?
use INHERITED ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
113 ParentControl(SkString name) : Control(name) {} | |
114 | |
115 // Adds a child | |
116 virtual void add(sk_sp<Control> control) = 0; | |
117 | |
118 // Returns the control's width. Used to propagate width down to components t hat don't specify it | |
119 virtual SkScalar width() const = 0; | |
robertphillips
2016/08/19 17:50:19
private:
INHERITED
?
dvonbeck
2016/08/19 18:25:17
Done.
| |
120 }; | |
121 | |
122 class ControlPanel : public ParentControl { | |
123 public: | |
124 | |
125 ControlPanel(SkScalar width) | |
robertphillips
2016/08/19 17:50:19
tabbed too far over
dvonbeck
2016/08/19 18:25:18
Done.
| |
126 : ParentControl(SkString("ControlPanel")) | |
127 , fWidth(width) | |
128 , fHeight(0.0f) | |
129 , fSelectedControl(-1) {} | |
130 | |
131 // Width unspecified, expectation is inheritance from parent | |
132 ControlPanel() : ControlPanel(-1.0f) {} | |
133 | |
134 // Use this for introducing clicks on a ControlPanel from outside of the fra mework. It | |
135 // propagates click release or position down the chain. Returns false when c lick capture is | |
136 // being released. | |
137 bool inClick(SkView::Click *inClick) { | |
138 if (SkView::Click::State::kUp_State == inClick->fState) { | |
139 this->onClickRelease(); | |
140 return false; | |
141 } | |
142 return this->click(inClick->fCurr); | |
143 } | |
144 | |
145 // Add children | |
146 void add(sk_sp<Control> control) override { | |
147 SkASSERT(!fParent); // Validity of parent's relativeY and fHeight depend s on immutability | |
148 fControls.push_back(control); | |
149 control->setParent(this, fHeight); | |
150 fHeight += control->height(); | |
151 } | |
152 | |
153 SkScalar width() const override { | |
154 return fParent ? fParent->width() : fWidth; // Width inherited from pare nt if there is one | |
155 } | |
156 | |
157 SkScalar height() const override { | |
158 return fHeight; | |
159 } | |
160 | |
robertphillips
2016/08/19 17:50:20
overlength
dvonbeck
2016/08/19 18:25:17
Done.
| |
161 void onClickRelease() override { // Propagate click release to selected cont rol, deselect control | |
robertphillips
2016/08/19 17:50:19
add newlines and brackets
dvonbeck
2016/08/19 18:25:17
Done.
| |
162 if (fSelectedControl >= 0) fControls[fSelectedControl]->onClickRelease() ; | |
163 fSelectedControl = -1; | |
164 } | |
165 | |
166 // Propagate onSetParent() down to children, some might need fParent->width( ) refresh | |
167 void onSetParent() override { | |
168 for (int i = 0; i < fControls.count(); i++) { | |
169 fControls[i]->onSetParent(); | |
170 } | |
171 } | |
172 | |
173 // Holds a vertical shelf of controls. Can't be hierarchy root if not given a width value. | |
174 static sk_sp<ParentControl> Make() { | |
175 return sk_sp<ParentControl>(new ControlPanel()); | |
176 } | |
177 | |
178 // Holds a vertical shelf of controls. Only control that can be hooked from outside the | |
179 // framework. | |
180 static sk_sp<ParentControl> Make(SkScalar width) { | |
181 return sk_sp<ParentControl>(new ControlPanel(width)); | |
182 } | |
183 | |
184 protected: | |
185 // Returns true if control panel has mouse captured, false when it is ready to release | |
186 // capture | |
robertphillips
2016/08/19 17:50:19
const & ?
dvonbeck
2016/08/19 18:25:16
Done.
| |
187 bool onClick(SkPoint click) override { | |
188 | |
189 if (fSelectedControl == -1) { // If no child control selected, check eve ry child | |
190 for (int i = 0; i < fControls.count(); i++) { | |
191 if (fControls[i]->isInCtrlRegion(click)) { | |
192 fSelectedControl = i; | |
193 break; | |
194 } | |
195 } | |
196 } | |
197 | |
198 if (fSelectedControl >= 0) { // If child control selected, propagate cli ck | |
199 bool keepSelection = fControls[fSelectedControl]->click(click); | |
200 if (!keepSelection) { | |
201 fSelectedControl = -1; | |
202 } | |
203 return keepSelection; | |
204 } | |
205 | |
206 return false; | |
207 } | |
208 | |
209 // Draw all children | |
210 void onDrawContent(SkCanvas* canvas) override { | |
211 canvas->save(); | |
212 for (int i = 0; i < fControls.count(); i++) { | |
213 fControls[i]->drawContent(canvas); | |
214 } | |
215 canvas->restore(); | |
216 } | |
217 | |
218 // Check all children's control regions | |
219 bool onIsInCtrlRegion(SkPoint clickPos) const override { | |
egdaniel
2016/08/19 17:56:35
const &
dvonbeck
2016/08/19 18:25:17
Done.
| |
220 for (int i = 0; i < fControls.count(); i++) { | |
robertphillips
2016/08/19 17:50:20
\ns and brackets
dvonbeck
2016/08/19 18:25:16
Done.
| |
221 if (fControls[i]->isInCtrlRegion(clickPos)) return true; | |
222 } | |
223 | |
224 return false; | |
225 } | |
226 | |
227 private: | |
228 SkScalar fWidth; | |
229 SkScalar fHeight; | |
230 | |
231 SkTArray<sk_sp<Control>> fControls; | |
232 int fSelectedControl; | |
233 }; | |
234 | |
235 class DiscreteSliderControl : public Control { | |
236 public: | |
robertphillips
2016/08/19 17:50:19
ovveride ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
237 SkScalar height() const { //ovverride} | |
238 return 2.0f * kLabelHeight; | |
239 } | |
240 | |
241 // Set width-dependant variables when new parent is set | |
242 void onSetParent() override { | |
robertphillips
2016/08/19 17:50:19
one line ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
243 fCtrlRegion = SkRect::MakeXYWH(0.0f, kLabelHeight, | |
244 fParent->width(), kSliderHeight); | |
245 fSliderRange = fParent->width() - kSliderWidth; | |
246 } | |
247 | |
248 /* Make a slider for an integer value. Snaps to discrete positions. | |
249 * | |
250 * @params name The name of the control, displayed in the label | |
251 * @params output Pointer to the integer that will be set by the slider | |
252 * @params min Min value for output. | |
253 * @params max Max value for output. | |
254 */ | |
255 static sk_sp<Control> Make(SkString name, int* output, int min, int max) { | |
256 return sk_sp<Control>(new DiscreteSliderControl(name, output, min, max)) ; | |
257 } | |
258 | |
259 protected: | |
260 void onDrawContent(SkCanvas* canvas) override { | |
261 SkASSERT(fParent); | |
262 int numChoices = fMax - fMin + 1; | |
263 fSlider.offsetTo(fSliderRange * ( (*fOutput)/SkIntToScalar(numChoices) | |
264 + 1.0f/(2.0f * numChoices) ), | |
265 fSlider.fTop); | |
266 | |
267 SkString valueStr; | |
268 valueStr.appendScalar(*fOutput); | |
269 this->drawLabel(canvas, valueStr); | |
270 | |
271 SkPaint sliderPaint; | |
272 sliderPaint.setColor(0xFFF3F3F3); | |
273 canvas->drawRect(fSlider, sliderPaint); | |
274 | |
275 SkPaint ctrlRegionPaint; | |
276 ctrlRegionPaint.setColor(0xFFFFFFFF); | |
277 ctrlRegionPaint.setStyle(SkPaint::kStroke_Style); | |
278 ctrlRegionPaint.setStrokeWidth(2.0f); | |
279 canvas->drawRect(fCtrlRegion, ctrlRegionPaint); | |
280 } | |
281 | |
282 bool onClick(SkPoint clickPos) override { | |
egdaniel
2016/08/19 17:56:35
const &
dvonbeck
2016/08/19 18:25:17
Done.
| |
283 SkASSERT(fParent); | |
284 SkScalar x = SkScalarPin(clickPos.fX, 0.0f, fSliderRange); | |
285 int numChoices = fMax - fMin + 1; | |
286 *fOutput = SkTMin(SkScalarFloorToInt(numChoices * x / fSliderRange) + fM in, fMax); | |
287 | |
288 return true; | |
289 } | |
290 | |
robertphillips
2016/08/19 17:50:19
const & ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
291 bool onIsInCtrlRegion(SkPoint clickPos) const override { | |
292 SkASSERT(fParent); | |
293 return fCtrlRegion.contains(SkRect::MakeXYWH(clickPos.fX, clickPos.fY, 1 , 1)); | |
294 } | |
295 | |
296 private: | |
297 DiscreteSliderControl(SkString name, int* output, int min, int max) | |
robertphillips
2016/08/19 17:50:20
use INHERITED here ?
dvonbeck
2016/08/19 18:25:18
Done.
| |
298 : Control(name) | |
299 , fOutput(output) | |
300 , fMin(min) | |
301 , fMax(max) { | |
302 fSlider = SkRect::MakeXYWH(0, kLabelHeight, kSliderWidth, kSliderHeight) ; | |
303 } | |
304 | |
305 int* fOutput; | |
306 int fMin; | |
307 int fMax; | |
308 SkRect fSlider; // The rectangle that slides | |
309 // The region in which the rectangle slides. Also the region in which mouse is caputred | |
310 SkRect fCtrlRegion; | |
311 SkScalar fSliderRange; // The width in pixels over which the slider can slid e | |
312 | |
313 static constexpr SkScalar kSliderHeight = 20.0f; | |
314 static constexpr SkScalar kSliderWidth = 10.0f; | |
robertphillips
2016/08/19 17:50:19
private:
INHERITED
?
dvonbeck
2016/08/19 18:25:17
Done.
| |
315 }; | |
316 | |
317 class ControlSwitcher : public ParentControl { | |
318 public: | |
319 // Add children | |
320 void add(sk_sp<Control> control) override { | |
321 SkASSERT(!fParent); // Validity of parent's relativeY and fHeight depend s on immutability | |
322 fControls.push_back(control); | |
323 control->setParent(this, kSelectorHeight); | |
324 fHeight = SkMaxScalar(fHeight, control->height()); // Setting height to max child height. | |
325 } | |
326 | |
327 SkScalar width() const override { return fParent ? (fParent->width()) : 0; } | |
328 | |
329 SkScalar height() const override { | |
330 return fHeight; | |
331 } | |
332 | |
333 // Propagate onClickRelease to control that currently captures mouse | |
334 void onClickRelease() override { | |
335 if (fCtrlOnClick) { | |
336 fCtrlOnClick->onClickRelease(); | |
337 } | |
338 fCtrlOnClick = nullptr; | |
339 } | |
340 | |
341 void onSetParent() override { | |
342 for (int i = 0; i < fControls.count(); i++) { | |
343 fControls[i]->onSetParent(); // Propagate to children | |
344 } | |
345 | |
346 // Finalize control selector | |
347 // TODO can be moved to constructor if list-initialized | |
348 if (!finalizedChildren) { | |
349 fControlSelector = DiscreteSliderControl::Make( | |
350 SkString(fName), &fSelectedControl, 0, fControls.count()-1); | |
351 fControlSelector->setParent(this, 0.0f); | |
352 fHeight += kSelectorHeight; | |
353 | |
354 SkASSERT(fControlSelector->height() <= kSelectorHeight); | |
355 } | |
356 } | |
357 | |
358 /* A set of a selector and a list of controls. Displays the control from the list of controls | |
359 * with the index set by the aforementioned selector. | |
360 * | |
361 * @param name The name of the switcher. Will be displayed in the selector's label. | |
362 */ | |
robertphillips
2016/08/19 17:50:19
const & ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
363 static sk_sp<ParentControl> Make(SkString name) { | |
364 return sk_sp<ParentControl>(new ControlSwitcher(name)); | |
365 } | |
366 | |
367 protected: | |
368 // Draw selector and currently selected control | |
369 void onDrawContent(SkCanvas* canvas) override { | |
370 fControlSelector->drawContent(canvas); | |
371 fControls[fSelectedControl]->drawContent(canvas); | |
372 } | |
373 | |
374 // Returns true if control panel has mouse captured, false when it is ready to release | |
375 // capture | |
robertphillips
2016/08/19 17:50:19
const & ?
dvonbeck
2016/08/19 18:25:17
Done.
| |
376 bool onClick(SkPoint click) override { | |
377 if (!fCtrlOnClick) { | |
378 if (fControlSelector->isInCtrlRegion(click)) { | |
379 fCtrlOnClick = fControlSelector.get(); | |
380 } else if (fControls[fSelectedControl]->isInCtrlRegion(click)) { | |
381 fCtrlOnClick = fControls[fSelectedControl].get(); | |
382 } | |
383 } | |
384 if (fCtrlOnClick) { | |
385 return fCtrlOnClick->click(click); | |
386 } | |
387 | |
388 return false; | |
389 } | |
390 | |
391 // Is in control region of selector or currently selected control | |
392 bool onIsInCtrlRegion(SkPoint clickPos) const override { | |
393 if (fControlSelector->isInCtrlRegion(clickPos)) return true; | |
robertphillips
2016/08/19 17:50:19
\ns and brackets
dvonbeck
2016/08/19 18:25:18
Done.
| |
394 if (fControls[fSelectedControl]->isInCtrlRegion(clickPos)) return true; | |
395 | |
396 return false; | |
397 } | |
398 | |
399 private: | |
robertphillips
2016/08/19 17:50:19
one line ?
const SkString& ?
use INHERITED here ?
dvonbeck
2016/08/19 18:25:17
Doesn't fit in one line, rest are done
| |
400 ControlSwitcher(SkString name) | |
401 : ParentControl(name) | |
402 , fHeight(0.0) | |
403 , fSelectedControl(0) | |
404 , fCtrlOnClick(nullptr){} | |
405 | |
406 bool finalizedChildren = false; | |
407 | |
408 sk_sp<Control> fControlSelector; | |
409 SkScalar fHeight; | |
410 SkTArray<sk_sp<Control>> fControls; | |
411 int fSelectedControl; | |
412 | |
413 Control* fCtrlOnClick; | |
414 | |
415 static constexpr SkScalar kSelectorHeight = 40.0f; | |
416 }; | |
417 | |
418 class ContinuousSliderControl : public Control { | |
419 public: | |
420 SkScalar height() const override { | |
421 return 2.0f * kLabelHeight; | |
422 } | |
423 | |
424 void onSetParent() { | |
425 fSlider = SkRect::MakeXYWH(0, kLabelHeight, kSliderWidth, kSliderHeight) ; | |
426 fCtrlRegion = SkRect::MakeXYWH(0.0f, kLabelHeight, | |
427 fParent->width(), kSliderHeight); | |
428 fSliderRange = fParent->width() - kSliderWidth; | |
429 } | |
430 | |
431 /* Make a slider for an SkScalar. | |
432 * | |
433 * @params name The name of the control, displayed in the label | |
434 * @params output Pointer to the SkScalar that will be set by the slider | |
435 * @params min Min value for output | |
436 * @params max Max value for output | |
437 */ | |
438 static sk_sp<Control> Make(SkString name, SkScalar* output, SkScalar min, Sk Scalar max) { | |
439 return sk_sp<Control>(new ContinuousSliderControl(name, output, min, max) ); | |
440 } | |
441 | |
442 protected: | |
443 void onDrawContent(SkCanvas* canvas) override { | |
444 SkASSERT(fParent); | |
445 SkScalar x = fSliderRange * (*fOutput - fMin) / (fMax - fMin); | |
446 fSlider.offsetTo(SkScalarPin(x, 0.0f, fSliderRange), fSlider.fTop); | |
447 | |
448 SkString valueStr; | |
449 valueStr.appendScalar(*fOutput); | |
450 this->drawLabel(canvas, valueStr); | |
451 | |
452 SkPaint sliderPaint; | |
453 sliderPaint.setColor(0xFFF3F3F3); | |
454 canvas->drawRect(fSlider, sliderPaint); | |
455 | |
456 SkPaint ctrlRegionPaint; | |
457 ctrlRegionPaint.setColor(0xFFFFFFFF); | |
458 ctrlRegionPaint.setStyle(SkPaint::kStroke_Style); | |
459 ctrlRegionPaint.setStrokeWidth(2.0f); | |
460 canvas->drawRect(fCtrlRegion, ctrlRegionPaint); | |
461 } | |
462 | |
463 bool onClick(SkPoint clickPos) override { | |
464 SkASSERT(fParent); | |
465 SkScalar x = SkScalarPin(clickPos.fX, 0.0f, fSliderRange); | |
466 *fOutput = (x/fSliderRange) * (fMax - fMin) + fMin; | |
467 return true; | |
468 } | |
469 | |
470 bool onIsInCtrlRegion(SkPoint clickPos) const override { | |
471 SkASSERT(fParent); | |
472 return fCtrlRegion.contains(SkRect::MakeXYWH(clickPos.fX, clickPos.fY, 1 , 1)); | |
473 } | |
474 | |
475 private: | |
476 ContinuousSliderControl(SkString name, SkScalar* output, SkScalar min, SkSca lar max) | |
477 : Control(name) | |
478 , fOutput(output) | |
479 , fMin(min) | |
480 , fMax(max) {} | |
481 | |
482 SkScalar* fOutput; | |
483 SkScalar fMin; | |
484 SkScalar fMax; | |
485 SkRect fSlider; | |
486 SkRect fCtrlRegion; | |
487 SkScalar fSliderRange; | |
488 | |
489 static constexpr SkScalar kSliderHeight = 20.0f; | |
490 static constexpr SkScalar kSliderWidth = 10.0f; | |
491 }; | |
492 | |
493 class RadialDirectionControl : public Control { | |
494 public: | |
495 SkScalar height() const override { | |
496 return kLabelHeight + 2.0f * kRegionRadius; | |
497 } | |
498 | |
499 /* Make a direction selector. | |
500 * | |
501 * @params name The name of the control, displayed in the label | |
502 * @params output Pointer to the SkVector that will be set by the slider | |
503 */ | |
504 static sk_sp<Control> Make(SkString name, SkVector* output) { | |
505 return sk_sp<Control>(new RadialDirectionControl(name, output)); | |
506 } | |
507 | |
508 protected: | |
509 void onDrawContent(SkCanvas* canvas) override { | |
510 SkASSERT(fParent); | |
511 | |
512 SkString valueStr; | |
513 valueStr.appendf("%.2f, %.2f", fOutput->fX, fOutput->fY); | |
514 this->drawLabel(canvas, valueStr); | |
515 | |
516 SkPoint lineEnd = SkPoint::Make(fCtrlRegion.centerX(), fCtrlRegion.cente rY()) | |
517 + (*fOutput * (kRegionRadius - kCapRadius)); | |
518 SkPaint linePaint; | |
519 linePaint.setColor(0xFFF3F3F3); | |
520 linePaint.setStrokeWidth(kStrokeWidth); | |
521 linePaint.setAntiAlias(true); | |
522 linePaint.setStrokeCap(SkPaint::kRound_Cap); | |
523 canvas->drawLine(fCtrlRegion.centerX(), fCtrlRegion.centerY(), | |
524 lineEnd.fX, lineEnd.fY, linePaint); | |
525 | |
526 SkPaint ctrlRegionPaint; | |
527 ctrlRegionPaint.setColor(0xFFFFFFFF); | |
528 ctrlRegionPaint.setStyle(SkPaint::kStroke_Style); | |
529 ctrlRegionPaint.setStrokeWidth(2.0f); | |
530 ctrlRegionPaint.setAntiAlias(true); | |
531 canvas->drawCircle(fCtrlRegion.centerX(), fCtrlRegion.centerY(), kRegion Radius, | |
532 ctrlRegionPaint); | |
533 } | |
534 | |
535 bool onClick(SkPoint clickPos) override { | |
536 SkASSERT(fParent); | |
537 fOutput->fX = clickPos.fX - fCtrlRegion.centerX(); | |
538 fOutput->fY = clickPos.fY - fCtrlRegion.centerY(); | |
539 fOutput->normalize(); | |
540 | |
541 return true; | |
542 } | |
543 | |
544 bool onIsInCtrlRegion(SkPoint clickPos) const override { | |
545 SkASSERT(fParent); | |
546 return fCtrlRegion.contains(SkRect::MakeXYWH(clickPos.fX, clickPos.fY, | |
547 1, 1)); | |
548 } | |
549 | |
550 private: | |
551 RadialDirectionControl(SkString name, SkVector* output) | |
552 : Control(name) | |
553 , fOutput(output) { | |
554 fCtrlRegion = SkRect::MakeXYWH(0.0f, kLabelHeight, kRegionRadius * 2.0f, kRegionRadius * 2.0f); | |
egdaniel
2016/08/19 17:56:35
100 chars.
dvonbeck
2016/08/19 18:25:17
Done.
| |
555 } | |
556 | |
557 SkVector* fOutput; | |
558 SkRect fCtrlRegion; | |
559 | |
560 static constexpr SkScalar kRegionRadius = 50.0f; | |
561 static constexpr SkScalar kStrokeWidth = 6.0f; | |
562 static constexpr SkScalar kCapRadius = kStrokeWidth / 2.0f; | |
563 }; | |
564 | |
565 class ColorDisplay: public Control { | |
566 public: | |
567 SkScalar height() const override { | |
568 return kHeight; | |
569 } | |
570 | |
571 void onSetParent() override { | |
572 fDisplayRect = SkRect::MakeXYWH(0.0f, kPadding, fParent->width(), kHeigh t - kPadding); | |
573 } | |
574 | |
575 /* Make a display that shows an SkColor3f. | |
576 * | |
577 * @params output Pointer to the SkColor3f that will be displayed | |
578 */ | |
579 static sk_sp<Control> Make(SkColor3f* input) { | |
580 return sk_sp<Control>(new ColorDisplay(SkString("ColorDisplay"), input)) ; | |
581 } | |
582 | |
583 protected: | |
584 void onDrawContent(SkCanvas* canvas) override { | |
585 SkASSERT(fParent); | |
586 | |
587 SkPaint displayPaint; | |
588 displayPaint.setColor(SkColor4f::FromColor3f(*fInput, 1.0f).toSkColor()) ; | |
589 canvas->drawRect(fDisplayRect, displayPaint); | |
590 } | |
591 | |
592 private: | |
593 ColorDisplay(SkString name, SkColor3f* input) | |
594 : Control(name) | |
595 , fInput(input) {} | |
596 | |
597 SkColor3f* fInput; | |
598 SkRect fDisplayRect; | |
599 | |
600 static constexpr SkScalar kHeight = 24.0f; | |
601 static constexpr SkScalar kPadding = 4.0f; | |
602 }; | |
14 | 603 |
15 class BevelView : public SampleView { | 604 class BevelView : public SampleView { |
16 public: | 605 public: |
17 BevelView() | 606 BevelView() |
18 : fShapeBounds(SkRect::MakeWH(kShapeBoundsSize, kShapeBoundsSize)) | 607 : fShapeBounds(SkRect::MakeWH(kShapeBoundsSize, kShapeBoundsSize)) |
19 , fRedLight(SkLights::Light::MakeDirectional(SkColor3f::Make(0.6f, 0.45f , 0.3f), | 608 , fControlPanel(kCtrlRange) { |
20 SkVector3::Make(0.0f, -5.0f , 1.0f))) | |
21 , fBlueLight(SkLights::Light::MakeDirectional(SkColor3f::Make(0.3f, 0.45 f, 0.6f), | |
22 SkVector3::Make(0.0f, 5.0f , 1.0f))) { | |
23 this->setBGColor(0xFF666868); // Slightly colorized gray for contrast | 609 this->setBGColor(0xFF666868); // Slightly colorized gray for contrast |
24 | 610 |
25 // Lights | |
26 SkLights::Builder builder; | |
27 builder.add(fRedLight); | |
28 builder.add(fBlueLight); | |
29 builder.add(SkLights::Light::MakeAmbient(SkColor3f::Make(0.4f, 0.4f, 0.4 f))); | |
30 fLights = builder.finish(); | |
31 | |
32 // Controls | 611 // Controls |
33 | 612 fBevelWidth = 25.0f; |
34 SkScalar currY = kSliderHeight; | 613 fBevelHeight = 25.0f; |
35 | 614 fBevelType = SkNormalSource::BevelType::kLinear; |
36 const SkScalar kWidthCtrlInitialPos = 0.2f; | 615 |
37 fCtrlRangeRects[0] = SkRect::MakeXYWH(0.0f, currY, | 616 int currLight = 0; |
38 kCtrlRange + kSliderWidth, | 617 fLightDefs[currLight++] = |
39 kSliderHeight); | 618 {SkVector::Make(0.0f, 1.0f), 1.0f, SkColor3f::Make(0.6f, 0.45f, 0.3f)}; |
40 fWidthCtrlRect = SkRect::MakeXYWH(kWidthCtrlInitialPos * kCtrlRange, cur rY, | 619 fLightDefs[currLight++] = |
41 kSliderWidth, kSliderHeight); | 620 {SkVector::Make(0.0f, -1.0f), 1.0f, SkColor3f::Make(0.3f, 0.45f, 0.6f)}; |
42 fBevelWidth = kBevelWidthMax * kWidthCtrlInitialPos; | 621 fLightDefs[currLight++] = |
43 currY += 2 * kSliderHeight; | 622 {SkVector::Make(1.0f, 0.0f), 1.0f, SkColor3f::Make(0.0f, 0.0f, 0 .0f)}; |
44 | 623 // Making sure we initialized all lights |
45 const SkScalar kHeightCtrlInitialPos = 0.75f; | 624 SkASSERT(currLight == kNumLights); |
46 fCtrlRangeRects[1] = SkRect::MakeXYWH(0.0f, currY, | 625 |
47 kCtrlRange + kSliderWidth, | 626 fControlPanel.add(ContinuousSliderControl::Make(SkString("BevelWidth"), &fBevelWidth, |
48 kSliderHeight); | 627 1.0f, kShapeBoundsSize)) ; |
49 fHeightCtrlRect = SkRect::MakeXYWH(kHeightCtrlInitialPos * kCtrlRange, c urrY, | 628 fControlPanel.add(ContinuousSliderControl::Make(SkString("BevelHeight"), &fBevelHeight, |
50 kSliderWidth, kSliderHeight); | 629 -50.0f, 50.0f)); |
robertphillips
2016/08/19 17:50:19
That's and exciting cast there. I propose you actu
dvonbeck
2016/08/19 18:25:16
Done.
| |
51 // Mapping from (0, 1) to (-1, 1) | 630 fControlPanel.add(DiscreteSliderControl::Make(SkString("BevelType"), (in t*)&fBevelType, |
52 fBevelHeight = kBevelHeightMax * (kHeightCtrlInitialPos * 2.0f - 1.0f); | 631 0, 2)); |
53 currY += 2 * kSliderHeight; | 632 sk_sp<ParentControl> lightCtrlSelector = ControlSwitcher::Make(SkString( "SelectedLight")); |
54 | 633 for (int i = 0; i < kNumLights; i++) { |
55 const SkScalar kTypeCtrlInitialPos = 1.0f / (2.0f * kBevelTypeCount); | 634 SkString name("Light"); |
56 fCtrlRangeRects[2] = SkRect::MakeXYWH(0.0f, currY, | 635 name.appendS32(i); |
57 kCtrlRange + kSliderWidth, | 636 sk_sp<ParentControl> currLightPanel = ControlPanel::Make(); |
58 kSliderHeight); | 637 SkString dirName(name); |
59 fTypeCtrlRect = SkRect::MakeXYWH(kTypeCtrlInitialPos * kCtrlRange, currY , | 638 dirName.append("Dir"); |
60 kSliderWidth, kSliderHeight); | 639 currLightPanel->add(RadialDirectionControl::Make(dirName, &(fLightDe fs[i].fDirXY))); |
61 fBevelType = (SkNormalSource::BevelType) SkScalarFloorToInt(kTypeCtrlIni tialPos); | 640 SkString heightName(name); |
62 currY += 2 * kSliderHeight; | 641 heightName.append("Height"); |
63 | 642 currLightPanel->add(ContinuousSliderControl::Make(heightName, &(fLig htDefs[i].fDirZ), |
64 fSelectedCtrlRect = nullptr; | 643 0.0f, 2.0f)); |
644 SkString redName(name); | |
645 redName.append("Red"); | |
646 currLightPanel->add(ContinuousSliderControl::Make(redName, &(fLightD efs[i].fColor.fX), | |
647 0.0f, 1.0f)); | |
648 SkString greenName(name); | |
649 greenName.append("Green"); | |
650 currLightPanel->add(ContinuousSliderControl::Make(greenName, &(fLigh tDefs[i].fColor.fY), | |
651 0.0f, 1.0f)); | |
652 SkString blueName(name); | |
653 blueName.append("Blue"); | |
654 currLightPanel->add(ContinuousSliderControl::Make(blueName, &(fLight Defs[i].fColor.fZ), | |
655 0.0f, 1.0f)); | |
656 currLightPanel->add(ColorDisplay::Make(&(fLightDefs[i].fColor))); | |
657 lightCtrlSelector->add(currLightPanel); | |
658 } | |
659 fControlPanel.add(lightCtrlSelector); | |
660 | |
661 fControlPanelSelected = false; | |
65 fDirtyNormalSource = true; | 662 fDirtyNormalSource = true; |
66 | 663 |
67 fLabelTypeface = sk_tool_utils::create_portable_typeface("sans-serif", S kFontStyle()); | 664 fLabelTypeface = sk_tool_utils::create_portable_typeface("sans-serif", S kFontStyle()); |
68 } | 665 } |
69 | 666 |
70 protected: | 667 protected: |
71 bool onQuery(SkEvent *evt) override { | 668 bool onQuery(SkEvent *evt) override { |
72 if (SampleCode::TitleQ(*evt)) { | 669 if (SampleCode::TitleQ(*evt)) { |
73 SampleCode::TitleR(evt, "Bevel"); | 670 SampleCode::TitleR(evt, "Bevel"); |
74 return true; | 671 return true; |
(...skipping 30 matching lines...) Expand all Loading... | |
105 default: | 702 default: |
106 SkDEBUGFAIL("Invalid shape enum for drawShape"); | 703 SkDEBUGFAIL("Invalid shape enum for drawShape"); |
107 } | 704 } |
108 | 705 |
109 canvas->restore(); | 706 canvas->restore(); |
110 } | 707 } |
111 | 708 |
112 void onDrawContent(SkCanvas *canvas) override { | 709 void onDrawContent(SkCanvas *canvas) override { |
113 | 710 |
114 canvas->save(); | 711 canvas->save(); |
115 canvas->resetMatrix(); // Force static controls and labels | 712 canvas->resetMatrix(); // Force static control panel position |
713 fControlPanel.drawContent(canvas); | |
714 canvas->restore(); | |
116 | 715 |
117 // Draw controls | 716 SkLights::Builder builder; |
118 | 717 for (int i = 0; i < kNumLights; i++) { |
119 SkPaint ctrlRectPaint; | 718 builder.add(SkLights::Light::MakeDirectional(fLightDefs[i].fColor, |
120 ctrlRectPaint.setColor(0xFFF3F3F3); | 719 SkPoint3::Make(fLightDe fs[i].fDirXY.fX, |
121 canvas->drawRect(fWidthCtrlRect, ctrlRectPaint); | 720 fLightDe fs[i].fDirXY.fY, |
122 canvas->drawRect(fHeightCtrlRect, ctrlRectPaint); | 721 fLightDe fs[i].fDirZ))); |
123 canvas->drawRect(fTypeCtrlRect, ctrlRectPaint); | |
124 | |
125 SkPaint ctrlRectRangePaint; | |
126 ctrlRectRangePaint.setColor(0xFFFFFFFF); | |
127 ctrlRectRangePaint.setStyle(SkPaint::kStroke_Style); | |
128 ctrlRectRangePaint.setStrokeWidth(2.0f); | |
129 | |
130 for (size_t i = 0; i < kNumControls; i++) { | |
131 canvas->drawRect(fCtrlRangeRects[i], ctrlRectRangePaint); | |
132 } | 722 } |
133 | 723 builder.add(SkLights::Light::MakeAmbient(SkColor3f::Make(0.4f, 0.4f, 0.4 f))); |
134 // Draw labels | 724 fLights = builder.finish(); |
egdaniel
2016/08/19 17:56:35
Does this correctly destroy the old lights in fLig
dvonbeck
2016/08/19 18:25:17
Yes, SkLights is backed by SkTArray of lights. Des
| |
135 constexpr SkScalar kTextSize = 12.0f; | |
136 SkString widthLabel, heightLabel, typeLabel; | |
137 SkPaint labelPaint; | |
138 labelPaint.setTypeface(fLabelTypeface); | |
139 labelPaint.setAntiAlias(true); | |
140 labelPaint.setColor(0xFFFFFFFF); | |
141 labelPaint.setTextSize(kTextSize); | |
142 | |
143 widthLabel.appendf("BevelWidth: %f", fBevelWidth); | |
144 heightLabel.appendf("BevelHeight: %f", fBevelHeight); | |
145 typeLabel.append("BevelType: "); | |
146 | |
147 switch (fBevelType) { | |
148 case SkNormalSource::BevelType::kLinear: | |
149 typeLabel.append("Linear"); | |
150 break; | |
151 case SkNormalSource::BevelType::kRoundedIn: | |
152 typeLabel.append("RoundedIn"); | |
153 break; | |
154 case SkNormalSource::BevelType::kRoundedOut: | |
155 typeLabel.append("RoundedOut"); | |
156 break; | |
157 } | |
158 | |
159 canvas->drawText(widthLabel.c_str(), widthLabel.size(), 0, | |
160 fWidthCtrlRect.fTop - kTextSize/2.0f, labelPaint); | |
161 canvas->drawText(heightLabel.c_str(), heightLabel.size(), 0, | |
162 fHeightCtrlRect.fTop - kTextSize/2.0f, labelPaint); | |
163 canvas->drawText(typeLabel.c_str(), typeLabel.size(), 0, | |
164 fTypeCtrlRect.fTop - kTextSize/2.0f, labelPaint); | |
165 | |
166 canvas->restore(); // Return to modified matrix when drawing shapes | |
167 | 725 |
168 // Draw shapes | 726 // Draw shapes |
169 SkScalar xPos = kCtrlRange + 25.0f; | 727 SkScalar xPos = kCtrlRange + 25.0f; |
170 SkScalar yPos = fShapeBounds.height(); | 728 SkScalar yPos = fShapeBounds.height(); |
171 for (Shape shape : { kCircle_Shape, kRect_Shape }) { | 729 for (Shape shape : { kCircle_Shape, kRect_Shape }) { |
172 canvas->save(); | 730 canvas->save(); |
173 canvas->translate(xPos, yPos); | 731 canvas->translate(xPos, yPos); |
174 this->drawShape(shape, canvas); | 732 this->drawShape(shape, canvas); |
175 canvas->restore(); | 733 canvas->restore(); |
176 | 734 |
177 xPos += 1.2f * fShapeBounds.width(); | 735 xPos += 1.2f * fShapeBounds.width(); |
178 } | 736 } |
179 } | 737 } |
180 | 738 |
181 SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) ove rride { | 739 SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) ove rride { |
182 return new SkView::Click(this); | 740 return new SkView::Click(this); |
183 } | 741 } |
184 | 742 |
185 bool onClick(Click *click) override { | 743 bool onClick(Click *click) override { |
186 SkScalar x = click->fCurr.fX; | 744 // Control panel mouse handling |
187 SkScalar y = click->fCurr.fY; | 745 fControlPanelSelected = fControlPanel.inClick(click); |
188 | 746 |
189 SkScalar dx = x - click->fPrev.fX; | 747 if (fControlPanelSelected) { // Control modification |
190 SkScalar dy = y - click->fPrev.fY; | |
191 | |
192 // Control deselection | |
193 if (Click::State::kUp_State == click->fState) { | |
194 fSelectedCtrlRect = nullptr; | |
195 return true; | |
196 } | |
197 | |
198 // Control selection | |
199 if (nullptr == fSelectedCtrlRect && Click::State::kDown_State == click-> fState) { | |
200 if (fWidthCtrlRect.contains(SkRect::MakeXYWH(x, y, 1, 1))) { | |
201 fSelectedCtrlRect = &fWidthCtrlRect; | |
202 } else if (fHeightCtrlRect.contains(SkRect::MakeXYWH(x, y, 1, 1))) { | |
203 fSelectedCtrlRect = &fHeightCtrlRect; | |
204 } else if (fTypeCtrlRect.contains(SkRect::MakeXYWH(x, y, 1, 1))) { | |
205 fSelectedCtrlRect = &fTypeCtrlRect; | |
206 } | |
207 } | |
208 | |
209 if (nullptr != fSelectedCtrlRect) { // Control modification | |
210 fSelectedCtrlRect->offsetTo(SkScalarPin(x, 0.0f, kCtrlRange), fSelec tedCtrlRect->fTop); | |
211 | |
212 fBevelHeight = (fHeightCtrlRect.fLeft / kCtrlRange) * kBevelHeightMa x * 2.0f | |
213 - kBevelHeightMax; | |
214 fBevelWidth = (fWidthCtrlRect.fLeft / kCtrlRange) * kBevelWidthMax; | |
215 fBevelType = (SkNormalSource::BevelType)SkTMin( | |
216 SkScalarFloorToInt(kBevelTypeCount * fTypeCtrlRect.fLeft / k CtrlRange), | |
217 kBevelTypeCount - 1); | |
218 | |
219 // Snap type controls to 3 positions | |
220 fTypeCtrlRect.offsetTo(kCtrlRange * ( ((int)fBevelType)/SkIntToScala r(kBevelTypeCount) | |
221 + 1.0f/(2.0f * kBevelTypeCount ) ), | |
222 fTypeCtrlRect.fTop); | |
223 | |
224 // Ensuring width is non-zero | |
225 fBevelWidth = SkMaxScalar(1.0f, fBevelWidth); | |
226 | |
227 fDirtyNormalSource = true; | 748 fDirtyNormalSource = true; |
228 | 749 |
229 this->inval(nullptr); | 750 this->inval(nullptr); |
230 return true; | 751 return true; |
231 } else { // Moving light | |
232 if (dx != 0 || dy != 0) { | |
233 float recipX = 1.0f / kAppWidth; | |
234 float recipY = 1.0f / kAppHeight; | |
235 | |
236 if (0 == click->fModifierKeys) { // No modifier | |
237 fBlueLight = SkLights::Light::MakeDirectional(fBlueLight.col or(), | |
238 SkVector3::Make((kAppWidth/2.0f - x) * recipX * -3.0 f, | |
239 (kAppHeight/2.0f - y) * recipY * -3. 0f, | |
240 1.0f)); | |
241 } else if (1 == click->fModifierKeys) { // Shift key | |
242 fRedLight = SkLights::Light::MakeDirectional(fRedLight.color (), | |
243 SkVector3::Make((kAppWidth/2.0f - x) * recipX * -3.0 f, | |
244 (kAppHeight/2.0f - y) * recipY * -3. 0f, | |
245 1.0f)); | |
246 } | |
247 | |
248 SkLights::Builder builder; | |
249 builder.add(fRedLight); | |
250 builder.add(fBlueLight); | |
251 builder.add(SkLights::Light::MakeAmbient( | |
252 SkColor3f::Make(0.4f, 0.4f, 0.4f))); | |
253 fLights = builder.finish(); | |
254 | |
255 this->inval(nullptr); | |
256 } | |
257 return true; | |
258 } | 752 } |
259 | 753 |
754 // TODO move shapes | |
755 this->inval(nullptr); | |
260 return true; | 756 return true; |
261 } | 757 } |
262 | 758 |
263 private: | 759 private: |
264 static constexpr int kNumTestRects = 3; | 760 static constexpr int kNumTestRects = 3; |
265 | 761 |
266 static constexpr SkScalar kAppWidth = 400.0f; | |
267 static constexpr SkScalar kAppHeight = 400.0f; | |
268 static constexpr SkScalar kShapeBoundsSize = 120.0f; | 762 static constexpr SkScalar kShapeBoundsSize = 120.0f; |
269 | 763 |
270 static constexpr SkScalar kCtrlRange = 150.0f; | 764 static constexpr SkScalar kCtrlRange = 150.0f; |
271 static constexpr SkScalar kBevelWidthMax = kShapeBoundsSize; | |
272 static constexpr SkScalar kBevelHeightMax = 50.0f; | |
273 static constexpr int kBevelTypeCount = 3; | |
274 | 765 |
275 static constexpr SkScalar kSliderHeight = 20.0f; | 766 static constexpr int kNumLights = 3; |
276 static constexpr SkScalar kSliderWidth = 10.0f; | |
277 | 767 |
278 const SkRect fShapeBounds; | 768 const SkRect fShapeBounds; |
279 | 769 |
280 static constexpr int kNumControls = 3; | |
281 SkRect fCtrlRangeRects[kNumControls]; | |
282 SkRect* fSelectedCtrlRect; | |
283 SkRect fWidthCtrlRect; | |
284 SkRect fHeightCtrlRect; | |
285 SkRect fTypeCtrlRect; | |
286 | |
287 SkScalar fBevelWidth; | 770 SkScalar fBevelWidth; |
288 SkScalar fBevelHeight; | 771 SkScalar fBevelHeight; |
289 SkNormalSource::BevelType fBevelType; | 772 SkNormalSource::BevelType fBevelType; |
290 sk_sp<SkNormalSource> fNormalSource; | 773 sk_sp<SkNormalSource> fNormalSource; |
291 bool fDirtyNormalSource; | 774 bool fDirtyNormalSource; |
292 | 775 |
293 sk_sp<SkLights> fLights; | 776 sk_sp<SkLights> fLights; |
294 SkLights::Light fRedLight; | 777 |
295 SkLights::Light fBlueLight; | 778 struct LightDef { |
779 SkVector fDirXY; | |
780 SkScalar fDirZ; | |
781 SkColor3f fColor; | |
782 | |
783 LightDef() {} | |
784 LightDef(SkVector dirXY, SkScalar dirZ, SkColor3f color) | |
785 : fDirXY(dirXY) | |
786 , fDirZ(dirZ) | |
787 , fColor(color) {} | |
788 }; | |
789 LightDef fLightDefs[kNumLights]; | |
790 | |
791 ControlPanel fControlPanel; | |
792 bool fControlPanelSelected; | |
296 | 793 |
297 sk_sp<SkTypeface> fLabelTypeface; | 794 sk_sp<SkTypeface> fLabelTypeface; |
298 | 795 |
299 typedef SampleView INHERITED; | 796 typedef SampleView INHERITED; |
300 }; | 797 }; |
301 | 798 |
302 ////////////////////////////////////////////////////////////////////////////// | 799 ////////////////////////////////////////////////////////////////////////////// |
303 | 800 |
304 static SkView* MyFactory() { return new BevelView; } | 801 static SkView* MyFactory() { return new BevelView; } |
305 static SkViewRegister reg(MyFactory); | 802 static SkViewRegister reg(MyFactory); |
306 | 803 |
OLD | NEW |