blob: 69d755bd8b9b9588bc06100a13e2a767db68b459 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00008#include "SkWidget.h"
9#include "SkCanvas.h"
10#include "SkKey.h"
11#include "SkParsePaint.h"
12#include "SkSystemEventTypes.h"
13#include "SkTextBox.h"
14
15#if 0
16
17#ifdef SK_DEBUG
18 static void assert_no_attr(const SkDOM& dom, const SkDOM::Node* node, const char attr[])
19 {
20 const char* value = dom.findAttr(node, attr);
21 if (value)
22 SkDebugf("unknown attribute %s=\"%s\"\n", attr, value);
23 }
24#else
25 #define assert_no_attr(dom, node, attr)
26#endif
27
28#include "SkAnimator.h"
29#include "SkTime.h"
30
31///////////////////////////////////////////////////////////////////////////////
32
33enum SkinType {
34 kPushButton_SkinType,
35 kStaticText_SkinType,
36
37 kSkinTypeCount
38};
39
40struct SkinSuite {
41 SkinSuite();
42 ~SkinSuite()
43 {
44 for (int i = 0; i < kSkinTypeCount; i++)
45 delete fAnimators[i];
46 }
47
48 SkAnimator* get(SkinType);
49
50private:
51 SkAnimator* fAnimators[kSkinTypeCount];
52};
53
54SkinSuite::SkinSuite()
55{
56 static const char kSkinPath[] = "skins/";
57
58 static const char* gSkinNames[] = {
59 "pushbutton_skin.xml",
60 "statictext_skin.xml"
61 };
62
63 for (unsigned i = 0; i < SK_ARRAY_COUNT(gSkinNames); i++)
64 {
65 size_t len = strlen(gSkinNames[i]);
66 SkString path(sizeof(kSkinPath) - 1 + len);
67
68 memcpy(path.writable_str(), kSkinPath, sizeof(kSkinPath) - 1);
69 memcpy(path.writable_str() + sizeof(kSkinPath) - 1, gSkinNames[i], len);
70
71 fAnimators[i] = new SkAnimator;
72 if (!fAnimators[i]->decodeURI(path.c_str()))
73 {
74 delete fAnimators[i];
deanm@chromium.org1599a432009-06-04 15:37:11 +000075 fAnimators[i] = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 }
77 }
78}
79
80SkAnimator* SkinSuite::get(SkinType st)
81{
82 SkASSERT((unsigned)st < kSkinTypeCount);
83 return fAnimators[st];
84}
85
86static SkinSuite* gSkinSuite;
87
88static SkAnimator* get_skin_animator(SkinType st)
89{
90#if 0
deanm@chromium.org1599a432009-06-04 15:37:11 +000091 if (gSkinSuite == NULL)
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 gSkinSuite = new SkinSuite;
93 return gSkinSuite->get(st);
94#else
deanm@chromium.org1599a432009-06-04 15:37:11 +000095 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +000096#endif
97}
98
99///////////////////////////////////////////////////////////////////////////////
100
101void SkWidget::Init()
102{
103}
104
105void SkWidget::Term()
106{
107 delete gSkinSuite;
108}
109
110void SkWidget::onEnabledChange()
111{
deanm@chromium.org1599a432009-06-04 15:37:11 +0000112 this->inval(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113}
114
115void SkWidget::postWidgetEvent()
116{
117 if (!fEvent.isType("") && this->hasListeners())
118 {
119 this->prepareWidgetEvent(&fEvent);
120 this->postToListeners(fEvent);
121 }
122}
123
124void SkWidget::prepareWidgetEvent(SkEvent*)
125{
126 // override in subclass to add any additional fields before posting
127}
128
129void SkWidget::onInflate(const SkDOM& dom, const SkDOM::Node* node)
130{
131 this->INHERITED::onInflate(dom, node);
132
deanm@chromium.org1599a432009-06-04 15:37:11 +0000133 if ((node = dom.getFirstChild(node, "event")) != NULL)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 fEvent.inflate(dom, node);
135}
136
137///////////////////////////////////////////////////////////////////////////////
138
139size_t SkHasLabelWidget::getLabel(SkString* str) const
140{
141 if (str)
142 *str = fLabel;
143 return fLabel.size();
144}
145
146size_t SkHasLabelWidget::getLabel(char buffer[]) const
147{
148 if (buffer)
149 memcpy(buffer, fLabel.c_str(), fLabel.size());
150 return fLabel.size();
151}
152
153void SkHasLabelWidget::setLabel(const SkString& str)
154{
155 this->setLabel(str.c_str(), str.size());
156}
157
158void SkHasLabelWidget::setLabel(const char label[])
159{
160 this->setLabel(label, strlen(label));
161}
162
163void SkHasLabelWidget::setLabel(const char label[], size_t len)
164{
165 if (!fLabel.equals(label, len))
166 {
167 fLabel.set(label, len);
168 this->onLabelChange();
169 }
170}
171
172void SkHasLabelWidget::onLabelChange()
173{
174 // override in subclass
175}
176
177void SkHasLabelWidget::onInflate(const SkDOM& dom, const SkDOM::Node* node)
178{
179 this->INHERITED::onInflate(dom, node);
180
181 const char* text = dom.findAttr(node, "label");
182 if (text)
183 this->setLabel(text);
184}
185
186/////////////////////////////////////////////////////////////////////////////////////
187
188void SkButtonWidget::setButtonState(State state)
189{
190 if (fState != state)
191 {
192 fState = state;
193 this->onButtonStateChange();
194 }
195}
196
197void SkButtonWidget::onButtonStateChange()
198{
deanm@chromium.org1599a432009-06-04 15:37:11 +0000199 this->inval(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200}
201
202void SkButtonWidget::onInflate(const SkDOM& dom, const SkDOM::Node* node)
203{
204 this->INHERITED::onInflate(dom, node);
205
206 int index;
207 if ((index = dom.findList(node, "buttonState", "off,on,unknown")) >= 0)
208 this->setButtonState((State)index);
209}
210
211/////////////////////////////////////////////////////////////////////////////////////
212
213bool SkPushButtonWidget::onEvent(const SkEvent& evt)
214{
215 if (evt.isType(SK_EventType_Key) && evt.getFast32() == kOK_SkKey)
216 {
217 this->postWidgetEvent();
218 return true;
219 }
220 return this->INHERITED::onEvent(evt);
221}
222
223static const char* computeAnimatorState(int enabled, int focused, SkButtonWidget::State state)
224{
225 if (!enabled)
226 return "disabled";
227 if (state == SkButtonWidget::kOn_State)
228 {
229 SkASSERT(focused);
230 return "enabled-pressed";
231 }
232 if (focused)
233 return "enabled-focused";
234 return "enabled";
235}
236
237#include "SkBlurMaskFilter.h"
238#include "SkEmbossMaskFilter.h"
239
240static void create_emboss(SkPaint* paint, SkScalar radius, bool focus, bool pressed)
241{
242 SkEmbossMaskFilter::Light light;
243
244 light.fDirection[0] = SK_Scalar1/2;
245 light.fDirection[1] = SK_Scalar1/2;
246 light.fDirection[2] = SK_Scalar1/3;
247 light.fAmbient = 0x48;
248 light.fSpecular = 0x80;
249
250 if (pressed)
251 {
252 light.fDirection[0] = -light.fDirection[0];
253 light.fDirection[1] = -light.fDirection[1];
254 }
255 if (focus)
256 light.fDirection[2] += SK_Scalar1/4;
257
258 paint->setMaskFilter(new SkEmbossMaskFilter(light, radius))->unref();
259}
260
261void SkPushButtonWidget::onDraw(SkCanvas* canvas)
262{
263 this->INHERITED::onDraw(canvas);
264
265 SkString label;
266 this->getLabel(&label);
267
268 SkAnimator* anim = get_skin_animator(kPushButton_SkinType);
269
270 if (anim)
271 {
272 SkEvent evt("user");
273
274 evt.setString("id", "prime");
275 evt.setScalar("prime-width", this->width());
276 evt.setScalar("prime-height", this->height());
277 evt.setString("prime-text", label);
278 evt.setString("prime-state", computeAnimatorState(this->isEnabled(), this->hasFocus(), this->getButtonState()));
279
280 (void)anim->doUserEvent(evt);
281 SkPaint paint;
282 anim->draw(canvas, &paint, SkTime::GetMSecs());
283 }
284 else
285 {
286 SkRect r;
287 SkPaint p;
288
289 r.set(0, 0, this->width(), this->height());
290 p.setAntiAliasOn(true);
291 p.setColor(SK_ColorBLUE);
292 create_emboss(&p, SkIntToScalar(12)/5, this->hasFocus(), this->getButtonState() == kOn_State);
293 canvas->drawRoundRect(r, SkScalarHalf(this->height()), SkScalarHalf(this->height()), p);
deanm@chromium.org1599a432009-06-04 15:37:11 +0000294 p.setMaskFilter(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295
296 p.setTextAlign(SkPaint::kCenter_Align);
297
298 SkTextBox box;
299 box.setMode(SkTextBox::kOneLine_Mode);
300 box.setSpacingAlign(SkTextBox::kCenter_SpacingAlign);
301 box.setBox(0, 0, this->width(), this->height());
302
303// if (this->getButtonState() == kOn_State)
304// p.setColor(SK_ColorRED);
305// else
306 p.setColor(SK_ColorWHITE);
307
308 box.draw(canvas, label.c_str(), label.size(), p);
309 }
310}
311
312SkView::Click* SkPushButtonWidget::onFindClickHandler(SkScalar x, SkScalar y)
313{
314 this->acceptFocus();
315 return new Click(this);
316}
317
318bool SkPushButtonWidget::onClick(Click* click)
319{
320 SkRect r;
321 State state = kOff_State;
322
323 this->getLocalBounds(&r);
324 if (r.contains(click->fCurr))
325 {
326 if (click->fState == Click::kUp_State)
327 this->postWidgetEvent();
328 else
329 state = kOn_State;
330 }
331 this->setButtonState(state);
332 return true;
333}
334
335//////////////////////////////////////////////////////////////////////////////////////////
336
337SkStaticTextView::SkStaticTextView(U32 flags) : SkView(flags)
338{
339 fMargin.set(0, 0);
340 fMode = kFixedSize_Mode;
341 fSpacingAlign = SkTextBox::kStart_SpacingAlign;
342}
343
344SkStaticTextView::~SkStaticTextView()
345{
346}
347
348void SkStaticTextView::computeSize()
349{
350 if (fMode == kAutoWidth_Mode)
351 {
deanm@chromium.org1599a432009-06-04 15:37:11 +0000352 SkScalar width = fPaint.measureText(fText.c_str(), fText.size(), NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353 this->setWidth(width + fMargin.fX * 2);
354 }
355 else if (fMode == kAutoHeight_Mode)
356 {
357 SkScalar width = this->width() - fMargin.fX * 2;
358 int lines = width > 0 ? SkTextLineBreaker::CountLines(fText.c_str(), fText.size(), fPaint, width) : 0;
359
360 SkScalar before, after;
deanm@chromium.org1599a432009-06-04 15:37:11 +0000361 (void)fPaint.measureText(0, NULL, &before, &after);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362
363 this->setHeight(lines * (after - before) + fMargin.fY * 2);
364 }
365}
366
367void SkStaticTextView::setMode(Mode mode)
368{
369 SkASSERT((unsigned)mode < kModeCount);
370
371 if (fMode != mode)
372 {
373 fMode = SkToU8(mode);
374 this->computeSize();
375 }
376}
377
378void SkStaticTextView::setSpacingAlign(SkTextBox::SpacingAlign align)
379{
380 fSpacingAlign = SkToU8(align);
deanm@chromium.org1599a432009-06-04 15:37:11 +0000381 this->inval(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382}
383
384void SkStaticTextView::getMargin(SkPoint* margin) const
385{
386 if (margin)
387 *margin = fMargin;
388}
389
390void SkStaticTextView::setMargin(SkScalar dx, SkScalar dy)
391{
392 if (fMargin.fX != dx || fMargin.fY != dy)
393 {
394 fMargin.set(dx, dy);
395 this->computeSize();
deanm@chromium.org1599a432009-06-04 15:37:11 +0000396 this->inval(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397 }
398}
399
400size_t SkStaticTextView::getText(SkString* text) const
401{
402 if (text)
403 *text = fText;
404 return fText.size();
405}
406
407size_t SkStaticTextView::getText(char text[]) const
408{
409 if (text)
410 memcpy(text, fText.c_str(), fText.size());
411 return fText.size();
412}
413
414void SkStaticTextView::setText(const SkString& text)
415{
416 this->setText(text.c_str(), text.size());
417}
418
419void SkStaticTextView::setText(const char text[])
420{
421 this->setText(text, strlen(text));
422}
423
424void SkStaticTextView::setText(const char text[], size_t len)
425{
426 if (!fText.equals(text, len))
427 {
428 fText.set(text, len);
429 this->computeSize();
deanm@chromium.org1599a432009-06-04 15:37:11 +0000430 this->inval(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431 }
432}
433
434void SkStaticTextView::getPaint(SkPaint* paint) const
435{
436 if (paint)
437 *paint = fPaint;
438}
439
440void SkStaticTextView::setPaint(const SkPaint& paint)
441{
442 if (fPaint != paint)
443 {
444 fPaint = paint;
445 this->computeSize();
deanm@chromium.org1599a432009-06-04 15:37:11 +0000446 this->inval(NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447 }
448}
449
450void SkStaticTextView::onDraw(SkCanvas* canvas)
451{
452 this->INHERITED::onDraw(canvas);
453
454 if (fText.isEmpty())
455 return;
456
457 SkTextBox box;
458
459 box.setMode(fMode == kAutoWidth_Mode ? SkTextBox::kOneLine_Mode : SkTextBox::kLineBreak_Mode);
460 box.setSpacingAlign(this->getSpacingAlign());
461 box.setBox(fMargin.fX, fMargin.fY, this->width() - fMargin.fX, this->height() - fMargin.fY);
462 box.draw(canvas, fText.c_str(), fText.size(), fPaint);
463}
464
465void SkStaticTextView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
466{
467 this->INHERITED::onInflate(dom, node);
468
469 int index;
470 if ((index = dom.findList(node, "mode", "fixed,auto-width,auto-height")) >= 0)
471 this->setMode((Mode)index);
472 else
473 assert_no_attr(dom, node, "mode");
474
475 if ((index = dom.findList(node, "spacing-align", "start,center,end")) >= 0)
476 this->setSpacingAlign((SkTextBox::SpacingAlign)index);
477 else
478 assert_no_attr(dom, node, "mode");
479
480 SkScalar s[2];
481 if (dom.findScalars(node, "margin", s, 2))
482 this->setMargin(s[0], s[1]);
483 else
484 assert_no_attr(dom, node, "margin");
485
486 const char* text = dom.findAttr(node, "text");
487 if (text)
488 this->setText(text);
489
deanm@chromium.org1599a432009-06-04 15:37:11 +0000490 if ((node = dom.getFirstChild(node, "paint")) != NULL)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000491 SkPaint_Inflate(&fPaint, dom, node);
492}
493
494/////////////////////////////////////////////////////////////////////////////////////////////////////
495
496#include "SkImageDecoder.h"
497
498SkBitmapView::SkBitmapView(U32 flags) : SkView(flags)
499{
500}
501
502SkBitmapView::~SkBitmapView()
503{
504}
505
506bool SkBitmapView::getBitmap(SkBitmap* bitmap) const
507{
508 if (bitmap)
509 *bitmap = fBitmap;
510 return fBitmap.getConfig() != SkBitmap::kNo_Config;
511}
512
513void SkBitmapView::setBitmap(const SkBitmap* bitmap, bool viewOwnsPixels)
514{
515 if (bitmap)
516 {
517 fBitmap = *bitmap;
518 fBitmap.setOwnsPixels(viewOwnsPixels);
519 }
520}
521
522bool SkBitmapView::loadBitmapFromFile(const char path[])
523{
524 SkBitmap bitmap;
525
526 if (SkImageDecoder::DecodeFile(path, &bitmap))
527 {
528 this->setBitmap(&bitmap, true);
529 bitmap.setOwnsPixels(false);
530 return true;
531 }
532 return false;
533}
534
535void SkBitmapView::onDraw(SkCanvas* canvas)
536{
537 if (fBitmap.getConfig() != SkBitmap::kNo_Config &&
538 fBitmap.width() && fBitmap.height())
539 {
540 SkAutoCanvasRestore restore(canvas, true);
541 SkPaint p;
542
543 p.setFilterType(SkPaint::kBilinear_FilterType);
544 canvas->scale( this->width() / fBitmap.width(),
545 this->height() / fBitmap.height(),
546 0, 0);
547 canvas->drawBitmap(fBitmap, 0, 0, p);
548 }
549}
550
551void SkBitmapView::onInflate(const SkDOM& dom, const SkDOM::Node* node)
552{
553 this->INHERITED::onInflate(dom, node);
554
555 const char* src = dom.findAttr(node, "src");
556 if (src)
557 (void)this->loadBitmapFromFile(src);
558}
559
560#endif
561