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