~ppsspp/ppsspp/ppsspp_1.3.0

« back to all changes in this revision

Viewing changes to ext/native/ui/view.cpp

  • Committer: Sérgio Benjamim
  • Date: 2017-01-02 00:12:05 UTC
  • Revision ID: sergio_br2@yahoo.com.br-20170102001205-cxbta9za203nmjwm
1.3.0 source (from ppsspp_1.3.0-r160.p5.l1762.a165.t83~56~ubuntu16.04.1.tar.xz).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#include <queue>
 
2
#include <algorithm>
 
3
 
 
4
#include "base/mutex.h"
 
5
#include "base/stringutil.h"
 
6
#include "base/timeutil.h"
 
7
#include "input/input_state.h"
 
8
#include "input/keycodes.h"
 
9
#include "gfx_es2/draw_buffer.h"
 
10
#include "gfx/texture_atlas.h"
 
11
#include "util/text/utf8.h"
 
12
#include "ui/ui.h"
 
13
#include "ui/view.h"
 
14
#include "ui/ui_context.h"
 
15
#include "thin3d/thin3d.h"
 
16
#include "base/NativeApp.h"
 
17
 
 
18
namespace UI {
 
19
 
 
20
static View *focusedView;
 
21
static bool focusMovementEnabled;
 
22
bool focusForced;
 
23
static recursive_mutex mutex_;
 
24
 
 
25
const float ITEM_HEIGHT = 64.f;
 
26
const float MIN_TEXT_SCALE = 0.8f;
 
27
const float MAX_ITEM_SIZE = 65535.0f;
 
28
 
 
29
struct DispatchQueueItem {
 
30
        Event *e;
 
31
        EventParams params;
 
32
};
 
33
 
 
34
std::deque<DispatchQueueItem> g_dispatchQueue;
 
35
 
 
36
 
 
37
void EventTriggered(Event *e, EventParams params) {
 
38
        lock_guard guard(mutex_);
 
39
 
 
40
        DispatchQueueItem item;
 
41
        item.e = e;
 
42
        item.params = params;
 
43
        g_dispatchQueue.push_front(item);
 
44
}
 
45
 
 
46
void DispatchEvents() {
 
47
        lock_guard guard(mutex_);
 
48
 
 
49
        while (!g_dispatchQueue.empty()) {
 
50
                DispatchQueueItem item = g_dispatchQueue.back();
 
51
                g_dispatchQueue.pop_back();
 
52
                if (item.e) {
 
53
                        item.e->Dispatch(item.params);
 
54
                }
 
55
        }
 
56
}
 
57
 
 
58
void RemoveQueuedEvents(View *v) {
 
59
        for (size_t i = 0; i < g_dispatchQueue.size(); i++) {
 
60
                if (g_dispatchQueue[i].params.v == v)
 
61
                        g_dispatchQueue.erase(g_dispatchQueue.begin() + i);
 
62
        }
 
63
}
 
64
 
 
65
View *GetFocusedView() {
 
66
        return focusedView;
 
67
}
 
68
 
 
69
void SetFocusedView(View *view, bool force) {
 
70
        if (focusedView) {
 
71
                focusedView->FocusChanged(FF_LOSTFOCUS);
 
72
        }
 
73
        focusedView = view;
 
74
        if (focusedView) {
 
75
                focusedView->FocusChanged(FF_GOTFOCUS);
 
76
                if (force) {
 
77
                        focusForced = true;
 
78
                }
 
79
        }
 
80
}
 
81
 
 
82
void EnableFocusMovement(bool enable) {
 
83
        focusMovementEnabled = enable;
 
84
        if (!enable) {
 
85
                if (focusedView) {
 
86
                        focusedView->FocusChanged(FF_LOSTFOCUS); 
 
87
                }
 
88
                focusedView = 0;
 
89
        }
 
90
}
 
91
 
 
92
bool IsFocusMovementEnabled() {
 
93
        return focusMovementEnabled;
 
94
}
 
95
 
 
96
void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured) {
 
97
        *measured = sz;
 
98
        if (sz == WRAP_CONTENT) {
 
99
                if (spec.type == UNSPECIFIED)
 
100
                        *measured = contentWidth;
 
101
                else if (spec.type == AT_MOST)
 
102
                        *measured = contentWidth < spec.size ? contentWidth : spec.size;
 
103
                else if (spec.type == EXACTLY)
 
104
                        *measured = spec.size;
 
105
        } else if (sz == FILL_PARENT) {
 
106
                // UNSPECIFIED may have a minimum size of the parent.  Let's use it to fill.
 
107
                if (spec.type == UNSPECIFIED)
 
108
                        *measured = std::max(spec.size, contentWidth);
 
109
                else
 
110
                        *measured = spec.size;
 
111
        } else if (spec.type == EXACTLY || (spec.type == AT_MOST && *measured > spec.size)) {
 
112
                *measured = spec.size;
 
113
        }
 
114
}
 
115
 
 
116
void ApplyBoundBySpec(float &bound, MeasureSpec spec) {
 
117
        switch (spec.type) {
 
118
        case AT_MOST:
 
119
                bound = bound < spec.size ? bound : spec.size;
 
120
                break;
 
121
        case EXACTLY:
 
122
                bound = spec.size;
 
123
                break;
 
124
        case UNSPECIFIED:
 
125
                break;
 
126
        }
 
127
}
 
128
 
 
129
void ApplyBoundsBySpec(Bounds &bounds, MeasureSpec horiz, MeasureSpec vert) {
 
130
        ApplyBoundBySpec(bounds.w, horiz);
 
131
        ApplyBoundBySpec(bounds.h, vert);
 
132
}
 
133
 
 
134
void Event::Add(std::function<EventReturn(EventParams&)> func) {
 
135
        HandlerRegistration reg;
 
136
        reg.func = func;
 
137
        handlers_.push_back(reg);
 
138
}
 
139
 
 
140
// Call this from input thread or whatever, it doesn't matter
 
141
void Event::Trigger(EventParams &e) {
 
142
        EventTriggered(this, e);
 
143
}
 
144
 
 
145
// Call this from UI thread
 
146
EventReturn Event::Dispatch(EventParams &e) {
 
147
        for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) {
 
148
                if ((iter->func)(e) == UI::EVENT_DONE) {
 
149
                        // Event is handled, stop looping immediately. This event might even have gotten deleted.
 
150
                        return UI::EVENT_DONE;
 
151
                }
 
152
        }
 
153
        return UI::EVENT_SKIPPED;
 
154
}
 
155
 
 
156
View::~View() {
 
157
        if (HasFocus())
 
158
                SetFocusedView(0);
 
159
        RemoveQueuedEvents(this);
 
160
}
 
161
 
 
162
void View::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
 
163
        float contentW = 0.0f, contentH = 0.0f;
 
164
        GetContentDimensionsBySpec(dc, horiz, vert, contentW, contentH);
 
165
        MeasureBySpec(layoutParams_->width, contentW, horiz, &measuredWidth_);
 
166
        MeasureBySpec(layoutParams_->height, contentH, vert, &measuredHeight_);
 
167
}
 
168
 
 
169
// Default values
 
170
 
 
171
void View::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
172
        w = 10.0f;
 
173
        h = 10.0f;
 
174
}
 
175
 
 
176
void View::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const {
 
177
        GetContentDimensions(dc, w, h);
 
178
}
 
179
 
 
180
void View::Query(float x, float y, std::vector<View *> &list) {
 
181
        if (bounds_.Contains(x, y)) {
 
182
                list.push_back(this);
 
183
        }
 
184
}
 
185
 
 
186
std::string View::Describe() const {
 
187
        return StringFromFormat("%0.1f,%0.1f %0.1fx%0.1f", bounds_.x, bounds_.y, bounds_.w, bounds_.h);
 
188
}
 
189
 
 
190
 
 
191
void View::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) {
 
192
        // Remember if this view was a focused view.
 
193
        std::string tag = Tag();
 
194
        if (tag.empty()) {
 
195
                tag = anonId;
 
196
        }
 
197
 
 
198
        const std::string focusedKey = "ViewFocused::" + tag;
 
199
        switch (status) {
 
200
        case UI::PERSIST_SAVE:
 
201
                if (HasFocus()) {
 
202
                        storage[focusedKey].resize(1);
 
203
                }
 
204
                break;
 
205
        case UI::PERSIST_RESTORE:
 
206
                if (storage.find(focusedKey) != storage.end()) {
 
207
                        SetFocus();
 
208
                }
 
209
                break;
 
210
        }
 
211
}
 
212
 
 
213
Point View::GetFocusPosition(FocusDirection dir) {
 
214
        // The +2/-2 is some extra fudge factor to cover for views sitting right next to each other.
 
215
        // Distance zero yields strange results otherwise.
 
216
        switch (dir) {
 
217
        case FOCUS_LEFT: return Point(bounds_.x + 2, bounds_.centerY());
 
218
        case FOCUS_RIGHT: return Point(bounds_.x2() - 2, bounds_.centerY());
 
219
        case FOCUS_UP: return Point(bounds_.centerX(), bounds_.y + 2);
 
220
        case FOCUS_DOWN: return Point(bounds_.centerX(), bounds_.y2() - 2);
 
221
 
 
222
        default:
 
223
                return bounds_.Center();
 
224
        }
 
225
}
 
226
 
 
227
bool View::SetFocus() {
 
228
        if (IsFocusMovementEnabled()) {
 
229
                if (CanBeFocused()) {
 
230
                        SetFocusedView(this);
 
231
                        return true;
 
232
                }
 
233
        }
 
234
        return false;
 
235
}
 
236
 
 
237
void Clickable::Click() {
 
238
        UI::EventParams e;
 
239
        e.v = this;
 
240
        OnClick.Trigger(e);
 
241
};
 
242
 
 
243
void Clickable::FocusChanged(int focusFlags) {
 
244
        if (focusFlags & FF_LOSTFOCUS) {
 
245
                down_ = false;
 
246
                dragging_ = false;
 
247
        }
 
248
}
 
249
 
 
250
void Clickable::Touch(const TouchInput &input) {
 
251
        if (!IsEnabled()) {
 
252
                dragging_ = false;
 
253
                down_ = false;
 
254
                return;
 
255
        }
 
256
 
 
257
        if (input.flags & TOUCH_DOWN) {
 
258
                if (bounds_.Contains(input.x, input.y)) {
 
259
                        if (IsFocusMovementEnabled())
 
260
                                SetFocusedView(this);
 
261
                        dragging_ = true;
 
262
                        down_ = true;
 
263
                } else {
 
264
                        down_ = false;
 
265
                        dragging_ = false;
 
266
                }
 
267
        } else if (input.flags & TOUCH_MOVE) {
 
268
                if (dragging_)
 
269
                        down_ = bounds_.Contains(input.x, input.y);
 
270
        }
 
271
        if (input.flags & TOUCH_UP) {
 
272
                if ((input.flags & TOUCH_CANCEL) == 0 && dragging_ && bounds_.Contains(input.x, input.y)) {
 
273
                        Click();
 
274
                }
 
275
                down_ = false;
 
276
                downCountDown_ = 0;
 
277
                dragging_ = false;
 
278
        }
 
279
}
 
280
 
 
281
static bool MatchesKeyDef(const std::vector<KeyDef> &defs, const KeyInput &key) {
 
282
        // In addition to the actual search, we need to do another search where we replace the device ID with "ANY".
 
283
        return
 
284
                std::find(defs.begin(), defs.end(), KeyDef(key.deviceId, key.keyCode)) != defs.end() ||
 
285
                std::find(defs.begin(), defs.end(), KeyDef(DEVICE_ID_ANY, key.keyCode)) != defs.end();
 
286
}
 
287
 
 
288
// TODO: O/X confirm preference for xperia play?
 
289
 
 
290
bool IsDPadKey(const KeyInput &key) {
 
291
        if (dpadKeys.empty()) {
 
292
                return key.keyCode >= NKCODE_DPAD_UP && key.keyCode <= NKCODE_DPAD_RIGHT;
 
293
        } else {
 
294
                return MatchesKeyDef(dpadKeys, key);
 
295
        }
 
296
}
 
297
 
 
298
bool IsAcceptKey(const KeyInput &key) {
 
299
        if (confirmKeys.empty()) {
 
300
                if (key.deviceId == DEVICE_ID_KEYBOARD) {
 
301
                        return key.keyCode == NKCODE_SPACE || key.keyCode == NKCODE_ENTER || key.keyCode == NKCODE_Z;
 
302
                } else {
 
303
                        return key.keyCode == NKCODE_BUTTON_A || key.keyCode == NKCODE_BUTTON_CROSS || key.keyCode == NKCODE_BUTTON_1;
 
304
                }
 
305
        } else {
 
306
                return MatchesKeyDef(confirmKeys, key);
 
307
        }
 
308
}
 
309
 
 
310
bool IsEscapeKey(const KeyInput &key) {
 
311
        if (cancelKeys.empty()) {
 
312
                if (key.deviceId == DEVICE_ID_KEYBOARD) {
 
313
                        return key.keyCode == NKCODE_ESCAPE || key.keyCode == NKCODE_BACK;
 
314
                } else {
 
315
                        return key.keyCode == NKCODE_BUTTON_CIRCLE || key.keyCode == NKCODE_BUTTON_B || key.keyCode == NKCODE_BUTTON_2;
 
316
                }
 
317
        } else {
 
318
                return MatchesKeyDef(cancelKeys, key);
 
319
        }
 
320
}
 
321
 
 
322
bool IsTabLeftKey(const KeyInput &key) {
 
323
        if (tabLeftKeys.empty()) {
 
324
                return key.keyCode == NKCODE_BUTTON_L1;
 
325
        } else {
 
326
                return MatchesKeyDef(tabLeftKeys, key);
 
327
        }
 
328
}
 
329
 
 
330
bool IsTabRightKey(const KeyInput &key) {
 
331
        if (tabRightKeys.empty()) {
 
332
                return key.keyCode == NKCODE_BUTTON_R1;
 
333
        } else {
 
334
                return MatchesKeyDef(tabRightKeys, key);
 
335
        }
 
336
}
 
337
 
 
338
bool Clickable::Key(const KeyInput &key) {
 
339
        if (!HasFocus() && key.deviceId != DEVICE_ID_MOUSE) {
 
340
                down_ = false;
 
341
                return false;
 
342
        }
 
343
        // TODO: Replace most of Update with this.
 
344
        bool ret = false;
 
345
        if (key.flags & KEY_DOWN) {
 
346
                if (IsAcceptKey(key)) {
 
347
                        down_ = true;
 
348
                        ret = true;
 
349
                }
 
350
        }
 
351
        if (key.flags & KEY_UP) {
 
352
                if (IsAcceptKey(key)) {
 
353
                        if (down_) {
 
354
                                Click();
 
355
                                down_ = false;
 
356
                                ret = true;
 
357
                        }
 
358
                } else if (IsEscapeKey(key)) {
 
359
                        down_ = false;
 
360
                }
 
361
        }
 
362
        return ret;
 
363
}
 
364
 
 
365
void StickyChoice::Touch(const TouchInput &input) {
 
366
        dragging_ = false;
 
367
        if (!IsEnabled()) {
 
368
                down_ = false;
 
369
                return;
 
370
        }
 
371
 
 
372
        if (input.flags & TOUCH_DOWN) {
 
373
                if (bounds_.Contains(input.x, input.y)) {
 
374
                        if (IsFocusMovementEnabled())
 
375
                                SetFocusedView(this);
 
376
                        down_ = true;
 
377
                        Click();
 
378
                }
 
379
        }
 
380
}
 
381
 
 
382
bool StickyChoice::Key(const KeyInput &key) {
 
383
        if (!HasFocus()) {
 
384
                return false;
 
385
        }
 
386
 
 
387
        // TODO: Replace most of Update with this.
 
388
        if (key.flags & KEY_DOWN) {
 
389
                if (IsAcceptKey(key)) {
 
390
                        down_ = true;
 
391
                        Click();
 
392
                        return true;
 
393
                }
 
394
        }
 
395
        return false;
 
396
}
 
397
 
 
398
void StickyChoice::FocusChanged(int focusFlags) {
 
399
        // Override Clickable's FocusChanged to do nothing.
 
400
}
 
401
 
 
402
Item::Item(LayoutParams *layoutParams) : InertView(layoutParams) {
 
403
        if (!layoutParams) {
 
404
                layoutParams_->width = FILL_PARENT;
 
405
                layoutParams_->height = ITEM_HEIGHT;
 
406
        }
 
407
}
 
408
 
 
409
void Item::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
410
        w = 0.0f;
 
411
        h = 0.0f;
 
412
}
 
413
 
 
414
void ClickableItem::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
415
        w = 0.0f;
 
416
        h = ITEM_HEIGHT;
 
417
}
 
418
 
 
419
ClickableItem::ClickableItem(LayoutParams *layoutParams) : Clickable(layoutParams) {
 
420
        if (!layoutParams) {
 
421
                if (layoutParams_->width == WRAP_CONTENT)
 
422
                        layoutParams_->width = FILL_PARENT;
 
423
        }
 
424
}
 
425
 
 
426
void ClickableItem::Draw(UIContext &dc) {
 
427
        Style style =   dc.theme->itemStyle;
 
428
 
 
429
        if (HasFocus()) {
 
430
                style = dc.theme->itemFocusedStyle;
 
431
        }
 
432
        if (down_) {
 
433
                style = dc.theme->itemDownStyle;
 
434
        }
 
435
 
 
436
        dc.FillRect(style.background, bounds_);
 
437
}
 
438
 
 
439
void Choice::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const {
 
440
        if (atlasImage_ != -1) {
 
441
                const AtlasImage &img = dc.Draw()->GetAtlas()->images[atlasImage_];
 
442
                w = img.w;
 
443
                h = img.h;
 
444
        } else {
 
445
                const int paddingX = 12;
 
446
                float availWidth = horiz.size - paddingX * 2 - textPadding_.horiz();
 
447
                if (availWidth < 0.0f) {
 
448
                        // Let it have as much space as it needs.
 
449
                        availWidth = MAX_ITEM_SIZE;
 
450
                }
 
451
                float scale = CalculateTextScale(dc, availWidth);
 
452
                Bounds availBounds(0, 0, availWidth, vert.size);
 
453
                dc.MeasureTextRect(dc.theme->uiFont, scale, scale, text_.c_str(), (int)text_.size(), availBounds, &w, &h, FLAG_WRAP_TEXT);
 
454
        }
 
455
        w += 24;
 
456
        h += 16;
 
457
        h = std::max(h, ITEM_HEIGHT);
 
458
}
 
459
 
 
460
float Choice::CalculateTextScale(const UIContext &dc, float availWidth) const {
 
461
        float actualWidth, actualHeight;
 
462
        Bounds availBounds(0, 0, availWidth, bounds_.h);
 
463
        dc.MeasureTextRect(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), (int)text_.size(), availBounds, &actualWidth, &actualHeight);
 
464
        if (actualWidth > availWidth) {
 
465
                return std::max(MIN_TEXT_SCALE, availWidth / actualWidth);
 
466
        }
 
467
        return 1.0f;
 
468
}
 
469
 
 
470
void Choice::HighlightChanged(bool highlighted){
 
471
        highlighted_ = highlighted;
 
472
}
 
473
 
 
474
void Choice::Draw(UIContext &dc) {
 
475
        if (!IsSticky()) {
 
476
                ClickableItem::Draw(dc);
 
477
        } else {
 
478
                Style style =   dc.theme->itemStyle;
 
479
                if (highlighted_) {
 
480
                        style = dc.theme->itemHighlightedStyle;
 
481
                }
 
482
                if (down_) {
 
483
                        style = dc.theme->itemDownStyle;
 
484
                }
 
485
                if (HasFocus()) {
 
486
                        style = dc.theme->itemFocusedStyle;
 
487
                }
 
488
                dc.FillRect(style.background, bounds_);
 
489
        }
 
490
 
 
491
        Style style = dc.theme->itemStyle;
 
492
        if (!IsEnabled()) {
 
493
                style = dc.theme->itemDisabledStyle;
 
494
        }
 
495
 
 
496
        if (atlasImage_ != -1) {
 
497
                dc.Draw()->DrawImage(atlasImage_, bounds_.centerX(), bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER);
 
498
        } else {
 
499
                dc.SetFontStyle(dc.theme->uiFont);
 
500
 
 
501
                const int paddingX = 12;
 
502
                const float availWidth = bounds_.w - paddingX * 2 - textPadding_.horiz();
 
503
                float scale = CalculateTextScale(dc, availWidth);
 
504
 
 
505
                dc.SetFontScale(scale, scale);
 
506
                if (centered_) {
 
507
                        dc.DrawTextRect(text_.c_str(), bounds_, style.fgColor, ALIGN_CENTER | FLAG_WRAP_TEXT);
 
508
                } else {
 
509
                        if (iconImage_ != -1) {
 
510
                                dc.Draw()->DrawImage(iconImage_, bounds_.x2() - 32 - paddingX, bounds_.centerY(), 0.5f, style.fgColor, ALIGN_CENTER);
 
511
                        }
 
512
 
 
513
                        Bounds textBounds(bounds_.x + paddingX + textPadding_.left, bounds_.y, availWidth, bounds_.h);
 
514
                        dc.DrawTextRect(text_.c_str(), textBounds, style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT);
 
515
                }
 
516
                dc.SetFontScale(1.0f, 1.0f);
 
517
        }
 
518
 
 
519
        if (selected_) {
 
520
                dc.Draw()->DrawImage(dc.theme->checkOn, bounds_.x2() - 40, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER);
 
521
        }
 
522
}
 
523
 
 
524
void InfoItem::Draw(UIContext &dc) {
 
525
        Item::Draw(dc);
 
526
        if (HasFocus()) {
 
527
                UI::Style style = dc.theme->itemFocusedStyle;
 
528
                style.background.color &= 0x7fffffff;
 
529
                dc.FillRect(style.background, bounds_);
 
530
        }
 
531
        int paddingX = 12;
 
532
 
 
533
        dc.SetFontStyle(dc.theme->uiFont);
 
534
        dc.DrawText(text_.c_str(), bounds_.x + paddingX, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER);
 
535
        dc.DrawText(rightText_.c_str(), bounds_.x2() - paddingX, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER | ALIGN_RIGHT);
 
536
//      dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y + 2, dc.theme->itemDownStyle.bgColor);
 
537
}
 
538
 
 
539
ItemHeader::ItemHeader(const std::string &text, LayoutParams *layoutParams)
 
540
        : Item(layoutParams), text_(text) {
 
541
                layoutParams_->width = FILL_PARENT;
 
542
                layoutParams_->height = 40;
 
543
}
 
544
 
 
545
void ItemHeader::Draw(UIContext &dc) {
 
546
        dc.SetFontStyle(dc.theme->uiFontSmall);
 
547
        dc.DrawText(text_.c_str(), bounds_.x + 4, bounds_.centerY(), 0xFFFFFFFF, ALIGN_LEFT | ALIGN_VCENTER);
 
548
        dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), 0xFFFFFFFF);
 
549
}
 
550
 
 
551
void PopupHeader::Draw(UIContext &dc) {
 
552
        const float paddingHorizontal = 12;
 
553
        const float availableWidth = bounds_.w - paddingHorizontal * 2;
 
554
 
 
555
        float tw, th;
 
556
        dc.SetFontStyle(dc.theme->uiFont);
 
557
        dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, text_.c_str(), &tw, &th, 0);
 
558
 
 
559
        float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
 
560
 
 
561
        float tx = paddingHorizontal;
 
562
        if (availableWidth < tw) {
 
563
                float overageRatio = 1.5f * availableWidth * 1.0f / tw;
 
564
                tx -= (1.0f + sin(time_now_d() * overageRatio)) * sineWidth;
 
565
                Bounds tb = bounds_;
 
566
                tb.x = bounds_.x + paddingHorizontal;
 
567
                tb.w = bounds_.w - paddingHorizontal * 2;
 
568
                dc.PushScissor(tb);
 
569
        }
 
570
 
 
571
        dc.DrawText(text_.c_str(), bounds_.x + tx, bounds_.centerY(), dc.theme->popupTitle.fgColor, ALIGN_LEFT | ALIGN_VCENTER);
 
572
        dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->popupTitle.fgColor);
 
573
 
 
574
        if (availableWidth < tw) {
 
575
                dc.PopScissor();
 
576
        }
 
577
}
 
578
 
 
579
void CheckBox::Toggle(){
 
580
        if (toggle_)
 
581
                *toggle_ = !(*toggle_);
 
582
};
 
583
 
 
584
EventReturn CheckBox::OnClicked(EventParams &e) {
 
585
        Toggle();
 
586
        return EVENT_CONTINUE;  // It's safe to keep processing events.
 
587
}
 
588
 
 
589
void CheckBox::Draw(UIContext &dc) {
 
590
        Style style = dc.theme->itemStyle;
 
591
        if (!IsEnabled())
 
592
                style = dc.theme->itemDisabledStyle;
 
593
        dc.SetFontStyle(dc.theme->uiFont);
 
594
 
 
595
        ClickableItem::Draw(dc);
 
596
 
 
597
        int image = *toggle_ ? dc.theme->checkOn : dc.theme->checkOff;
 
598
        float imageW, imageH;
 
599
        dc.Draw()->MeasureImage(image, &imageW, &imageH);
 
600
 
 
601
        const int paddingX = 12;
 
602
        // Padding right of the checkbox image too.
 
603
        const float availWidth = bounds_.w - paddingX * 2 - imageW - paddingX;
 
604
        float scale = CalculateTextScale(dc, availWidth);
 
605
 
 
606
        dc.SetFontScale(scale, scale);
 
607
        Bounds textBounds(bounds_.x + paddingX, bounds_.y, availWidth, bounds_.h);
 
608
        dc.DrawTextRect(text_.c_str(), textBounds, style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT);
 
609
        dc.Draw()->DrawImage(image, bounds_.x2() - paddingX, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
 
610
        dc.SetFontScale(1.0f, 1.0f);
 
611
}
 
612
 
 
613
float CheckBox::CalculateTextScale(const UIContext &dc, float availWidth) const {
 
614
        float actualWidth, actualHeight;
 
615
        Bounds availBounds(0, 0, availWidth, bounds_.h);
 
616
        dc.MeasureTextRect(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), (int)text_.size(), availBounds, &actualWidth, &actualHeight, ALIGN_VCENTER);
 
617
        if (actualWidth > availWidth) {
 
618
                return std::max(MIN_TEXT_SCALE, availWidth / actualWidth);
 
619
        }
 
620
        return 1.0f;
 
621
}
 
622
 
 
623
void CheckBox::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
624
        int image = *toggle_ ? dc.theme->checkOn : dc.theme->checkOff;
 
625
        float imageW, imageH;
 
626
        dc.Draw()->MeasureImage(image, &imageW, &imageH);
 
627
 
 
628
        const int paddingX = 12;
 
629
        // Padding right of the checkbox image too.
 
630
        float availWidth = bounds_.w - paddingX * 2 - imageW - paddingX;
 
631
        if (availWidth < 0.0f) {
 
632
                // Let it have as much space as it needs.
 
633
                availWidth = MAX_ITEM_SIZE;
 
634
        }
 
635
        float scale = CalculateTextScale(dc, availWidth);
 
636
 
 
637
        float actualWidth, actualHeight;
 
638
        Bounds availBounds(0, 0, availWidth, bounds_.h);
 
639
        dc.MeasureTextRect(dc.theme->uiFont, scale, scale, text_.c_str(), (int)text_.size(), availBounds, &actualWidth, &actualHeight, ALIGN_VCENTER | FLAG_WRAP_TEXT);
 
640
 
 
641
        w = bounds_.w;
 
642
        h = std::max(actualHeight, ITEM_HEIGHT);
 
643
}
 
644
 
 
645
void Button::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
646
        if (imageID_ != -1) {
 
647
                const AtlasImage *img = &dc.Draw()->GetAtlas()->images[imageID_];
 
648
                w = img->w;
 
649
                h = img->h;
 
650
        } else {
 
651
                dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), &w, &h);
 
652
        }
 
653
        // Add some internal padding to not look totally ugly
 
654
        w += 16;
 
655
        h += 8;
 
656
}
 
657
 
 
658
void Button::Draw(UIContext &dc) {
 
659
        Style style = dc.theme->buttonStyle;
 
660
 
 
661
        if (HasFocus()) style = dc.theme->buttonFocusedStyle;
 
662
        if (down_) style = dc.theme->buttonDownStyle;
 
663
        if (!IsEnabled()) style = dc.theme->buttonDisabledStyle;
 
664
 
 
665
        // dc.Draw()->DrawImage4Grid(style.image, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), style.bgColor);
 
666
        dc.FillRect(style.background, bounds_);
 
667
        float tw, th;
 
668
        dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), &tw, &th);
 
669
        if (tw > bounds_.w || imageID_ != -1) {
 
670
                dc.PushScissor(bounds_);
 
671
        }
 
672
        dc.SetFontStyle(dc.theme->uiFont);
 
673
        if (imageID_ != -1 && text_.empty()) {
 
674
                dc.Draw()->DrawImage(imageID_, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
 
675
        } else if (!text_.empty()) {
 
676
                dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), style.fgColor, ALIGN_CENTER);
 
677
                if (imageID_ != -1) {
 
678
                        const AtlasImage &img = dc.Draw()->GetAtlas()->images[imageID_];
 
679
                        dc.Draw()->DrawImage(imageID_, bounds_.centerX() - tw / 2 - 5 - img.w/2, bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
 
680
                }
 
681
        }
 
682
        if (tw > bounds_.w || imageID_ != -1) {
 
683
                dc.PopScissor();
 
684
        }
 
685
}
 
686
 
 
687
void ImageView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
688
        const AtlasImage &img = dc.Draw()->GetAtlas()->images[atlasImage_];
 
689
        // TODO: involve sizemode
 
690
        w = img.w;
 
691
        h = img.h;
 
692
}
 
693
 
 
694
void ImageView::Draw(UIContext &dc) {
 
695
        const AtlasImage &img = dc.Draw()->GetAtlas()->images[atlasImage_];
 
696
        // TODO: involve sizemode
 
697
        float scale = bounds_.w / img.w;
 
698
        dc.Draw()->DrawImage(atlasImage_, bounds_.x, bounds_.y, scale, 0xFFFFFFFF, ALIGN_TOPLEFT);
 
699
}
 
700
 
 
701
void Thin3DTextureView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
702
        // TODO: involve sizemode
 
703
        if (texture_) {
 
704
                w = (float)texture_->Width();
 
705
                h = (float)texture_->Height();
 
706
        } else {
 
707
                w = 16;
 
708
                h = 16;
 
709
        }
 
710
}
 
711
 
 
712
void Thin3DTextureView::Draw(UIContext &dc) {
 
713
        // TODO: involve sizemode
 
714
        if (texture_) {
 
715
                dc.Flush();
 
716
                dc.GetThin3DContext()->SetTexture(0, texture_);
 
717
                dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
 
718
                dc.Flush();
 
719
                dc.RebindTexture();
 
720
        }
 
721
}
 
722
 
 
723
void TextView::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const {
 
724
        Bounds bounds(0, 0, layoutParams_->width, layoutParams_->height);
 
725
        if (bounds.w < 0) {
 
726
                // If there's no size, let's grow as big as we want.
 
727
                bounds.w = horiz.size == 0 ? MAX_ITEM_SIZE : horiz.size;
 
728
        }
 
729
        if (bounds.h < 0) {
 
730
                bounds.h = vert.size == 0 ? MAX_ITEM_SIZE : vert.size;
 
731
        }
 
732
        ApplyBoundsBySpec(bounds, horiz, vert);
 
733
        dc.MeasureTextRect(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), (int)text_.length(), bounds, &w, &h, textAlign_);
 
734
}
 
735
 
 
736
void TextView::Draw(UIContext &dc) {
 
737
        bool clip = false;
 
738
        if (measuredWidth_ > bounds_.w || measuredHeight_ > bounds_.h)
 
739
                clip = true;
 
740
        if (bounds_.w < 0 || bounds_.h < 0 || !clip_) {
 
741
                // We have a layout but, but try not to screw up rendering.
 
742
                // TODO: Fix properly.
 
743
                clip = false;
 
744
        }
 
745
        if (clip) {
 
746
                dc.Flush();
 
747
                dc.PushScissor(bounds_);
 
748
        }
 
749
        // In case it's been made focusable.
 
750
        if (HasFocus()) {
 
751
                UI::Style style = dc.theme->itemFocusedStyle;
 
752
                style.background.color &= 0x7fffffff;
 
753
                dc.FillRect(style.background, bounds_);
 
754
        }
 
755
        dc.SetFontStyle(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont);
 
756
        if (shadow_) {
 
757
                uint32_t shadowColor = 0x80000000;
 
758
                dc.DrawTextRect(text_.c_str(), bounds_, shadowColor, textAlign_);
 
759
        }
 
760
        dc.DrawTextRect(text_.c_str(), bounds_, textColor_, textAlign_);
 
761
        if (clip) {
 
762
                dc.PopScissor();
 
763
        }
 
764
}
 
765
 
 
766
TextEdit::TextEdit(const std::string &text, const std::string &placeholderText, LayoutParams *layoutParams)
 
767
  : View(layoutParams), text_(text), undo_(text), placeholderText_(placeholderText), maxLen_(255), ctrlDown_(false) {
 
768
        caret_ = (int)text_.size();
 
769
}
 
770
 
 
771
void TextEdit::Draw(UIContext &dc) {
 
772
        dc.PushScissor(bounds_);
 
773
        dc.SetFontStyle(dc.theme->uiFont);
 
774
        dc.FillRect(HasFocus() ? UI::Drawable(0x80000000) : UI::Drawable(0x30000000), bounds_);
 
775
 
 
776
        float textX = bounds_.x;
 
777
        float w, h;
 
778
 
 
779
        Bounds textBounds = bounds_;
 
780
        textBounds.x = textX;
 
781
 
 
782
        if (text_.empty()) {
 
783
                if (placeholderText_.size()) {
 
784
                        dc.DrawTextRect(placeholderText_.c_str(), bounds_, 0x50FFFFFF, ALIGN_CENTER);
 
785
                }
 
786
        } else {
 
787
                dc.DrawTextRect(text_.c_str(), textBounds, 0xFFFFFFFF, ALIGN_VCENTER | ALIGN_LEFT);
 
788
        }
 
789
 
 
790
        if (HasFocus()) {
 
791
                // Hack to find the caret position. Might want to find a better way...
 
792
                dc.MeasureTextCount(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), caret_, &w, &h, ALIGN_VCENTER | ALIGN_LEFT);
 
793
                float caretX = w;
 
794
                caretX += textX;
 
795
 
 
796
                if (caretX > bounds_.w) {
 
797
                        // Scroll text to the left if the caret won't fit. Not ideal but looks better than not scrolling it.
 
798
                        textX -= caretX - bounds_.w;
 
799
                }
 
800
                dc.FillRect(UI::Drawable(0xFFFFFFFF), Bounds(caretX - 1, bounds_.y + 2, 3, bounds_.h - 4));
 
801
        }
 
802
        dc.PopScissor();
 
803
}
 
804
 
 
805
void TextEdit::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
806
        dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.size() ? text_.c_str() : "Wj", &w, &h);
 
807
        w += 2;
 
808
        h += 2;
 
809
}
 
810
 
 
811
// Handles both windows and unix line endings.
 
812
static std::string FirstLine(const std::string &text) {
 
813
        size_t pos = text.find("\r\n");
 
814
        if (pos != std::string::npos) {
 
815
                return text.substr(0, pos);
 
816
        }
 
817
        pos = text.find('\n');
 
818
        if (pos != std::string::npos) {
 
819
                return text.substr(0, pos);
 
820
        }
 
821
        return text;
 
822
}
 
823
 
 
824
void TextEdit::Touch(const TouchInput &touch) {
 
825
        if (touch.flags & TOUCH_DOWN) {
 
826
                if (bounds_.Contains(touch.x, touch.y)) {
 
827
                        SetFocusedView(this, true);
 
828
                }
 
829
        }
 
830
}
 
831
 
 
832
bool TextEdit::Key(const KeyInput &input) {
 
833
        if (!HasFocus())
 
834
                return false;
 
835
        bool textChanged = false;
 
836
        // Process navigation keys. These aren't chars.
 
837
        if (input.flags & KEY_DOWN) {
 
838
                switch (input.keyCode) {
 
839
                case NKCODE_CTRL_LEFT:
 
840
                case NKCODE_CTRL_RIGHT:
 
841
                        ctrlDown_ = true;
 
842
                        break;
 
843
                case NKCODE_DPAD_LEFT:  // ASCII left arrow
 
844
                        u8_dec(text_.c_str(), &caret_);
 
845
                        break;
 
846
                case NKCODE_DPAD_RIGHT: // ASCII right arrow
 
847
                        u8_inc(text_.c_str(), &caret_);
 
848
                        break;
 
849
                case NKCODE_MOVE_HOME:
 
850
                case NKCODE_PAGE_UP:
 
851
                        caret_ = 0;
 
852
                        break;
 
853
                case NKCODE_MOVE_END:
 
854
                case NKCODE_PAGE_DOWN:
 
855
                        caret_ = (int)text_.size();
 
856
                        break;
 
857
                case NKCODE_FORWARD_DEL:
 
858
                        if (caret_ < (int)text_.size()) {
 
859
                                int endCaret = caret_;
 
860
                                u8_inc(text_.c_str(), &endCaret);
 
861
                                undo_ = text_;
 
862
                                text_.erase(text_.begin() + caret_, text_.begin() + endCaret);
 
863
                                textChanged = true;
 
864
                        }
 
865
                        break;
 
866
                case NKCODE_DEL:
 
867
                        if (caret_ > 0) {
 
868
                                int begCaret = caret_;
 
869
                                u8_dec(text_.c_str(), &begCaret);
 
870
                                undo_ = text_;
 
871
                                text_.erase(text_.begin() + begCaret, text_.begin() + caret_);
 
872
                                caret_--;
 
873
                                textChanged = true;
 
874
                        }
 
875
                        break;
 
876
                case NKCODE_ENTER:
 
877
                        {
 
878
                                EventParams e;
 
879
                                e.s = text_;
 
880
                                OnEnter.Trigger(e);
 
881
                                break;
 
882
                        }
 
883
                case NKCODE_BACK:
 
884
                case NKCODE_ESCAPE:
 
885
                        return false;
 
886
                }
 
887
 
 
888
                if (ctrlDown_) {
 
889
                        switch (input.keyCode) {
 
890
                        case NKCODE_C:
 
891
                                // Just copy the entire text contents, until we get selection support.
 
892
                                System_SendMessage("setclipboardtext", text_.c_str());
 
893
                                break;
 
894
                        case NKCODE_V:
 
895
                                {
 
896
                                        std::string clipText = System_GetProperty(SYSPROP_CLIPBOARD_TEXT);
 
897
                                        clipText = FirstLine(clipText);
 
898
                                        if (clipText.size()) {
 
899
                                                // Until we get selection, replace the whole text
 
900
                                                undo_ = text_;
 
901
                                                text_.clear();
 
902
                                                caret_ = 0;
 
903
 
 
904
                                                size_t maxPaste = maxLen_ - text_.size();
 
905
                                                if (clipText.size() > maxPaste) {
 
906
                                                        int end = 0;
 
907
                                                        while ((size_t)end < maxPaste) {
 
908
                                                                u8_inc(clipText.c_str(), &end);
 
909
                                                        }
 
910
                                                        if (end > 0) {
 
911
                                                                u8_dec(clipText.c_str(), &end);
 
912
                                                        }
 
913
                                                        clipText = clipText.substr(0, end);
 
914
                                                }
 
915
                                                InsertAtCaret(clipText.c_str());
 
916
                                                textChanged = true;
 
917
                                        }
 
918
                                }
 
919
                                break;
 
920
                        case NKCODE_Z:
 
921
                                text_ = undo_;
 
922
                                break;
 
923
                        }
 
924
                }
 
925
 
 
926
                if (caret_ < 0) {
 
927
                        caret_ = 0;
 
928
                }
 
929
                if (caret_ > (int)text_.size()) {
 
930
                        caret_ = (int)text_.size();
 
931
                }
 
932
        }
 
933
 
 
934
        if (input.flags & KEY_UP) {
 
935
                switch (input.keyCode) {
 
936
                case NKCODE_CTRL_LEFT:
 
937
                case NKCODE_CTRL_RIGHT:
 
938
                        ctrlDown_ = false;
 
939
                        break;
 
940
                }
 
941
        }
 
942
 
 
943
        // Process chars.
 
944
        if (input.flags & KEY_CHAR) {
 
945
                int unichar = input.keyCode;
 
946
                if (unichar >= 0x20 && !ctrlDown_) {  // Ignore control characters.
 
947
                        // Insert it! (todo: do it with a string insert)
 
948
                        char buf[8];
 
949
                        buf[u8_wc_toutf8(buf, unichar)] = '\0';
 
950
                        if (strlen(buf) + text_.size() < maxLen_) {
 
951
                                undo_ = text_;
 
952
                                InsertAtCaret(buf);
 
953
                                textChanged = true;
 
954
                        }
 
955
                }
 
956
        }
 
957
 
 
958
        if (textChanged) {
 
959
                UI::EventParams e;
 
960
                e.v = this;
 
961
                OnTextChange.Trigger(e);
 
962
        }
 
963
        return true;
 
964
}
 
965
 
 
966
void TextEdit::InsertAtCaret(const char *text) {
 
967
        size_t len = strlen(text);
 
968
        for (size_t i = 0; i < len; i++) {
 
969
                text_.insert(text_.begin() + caret_, text[i]);
 
970
                caret_++;
 
971
        }
 
972
}
 
973
 
 
974
void ProgressBar::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
975
        dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, "  100%  ", &w, &h);
 
976
}
 
977
 
 
978
void ProgressBar::Draw(UIContext &dc) {
 
979
        char temp[32];
 
980
        sprintf(temp, "%i%%", (int)(progress_ * 100.0f));
 
981
        dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x + bounds_.w * progress_, bounds_.y2(), 0xc0c0c0c0);
 
982
        dc.SetFontStyle(dc.theme->uiFont);
 
983
        dc.DrawTextRect(temp, bounds_, 0xFFFFFFFF, ALIGN_CENTER);
 
984
}
 
985
 
 
986
void TriggerButton::Touch(const TouchInput &input) {
 
987
        if (input.flags & TOUCH_DOWN) {
 
988
                if (bounds_.Contains(input.x, input.y)) {
 
989
                        down_ |= 1 << input.id;
 
990
                }
 
991
        }
 
992
        if (input.flags & TOUCH_MOVE) {
 
993
                if (bounds_.Contains(input.x, input.y))
 
994
                        down_ |= 1 << input.id;
 
995
                else
 
996
                        down_ &= ~(1 << input.id);
 
997
        }
 
998
 
 
999
        if (input.flags & TOUCH_UP) {
 
1000
                down_ &= ~(1 << input.id);
 
1001
        }
 
1002
 
 
1003
        if (down_ != 0) {
 
1004
                *bitField_ |= bit_;
 
1005
        } else {
 
1006
                *bitField_ &= ~bit_;
 
1007
        }
 
1008
}
 
1009
 
 
1010
void TriggerButton::Draw(UIContext &dc) {
 
1011
        dc.Draw()->DrawImage(imageBackground_, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
 
1012
        dc.Draw()->DrawImage(imageForeground_, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
 
1013
}
 
1014
 
 
1015
void TriggerButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
1016
        const AtlasImage &image = dc.Draw()->GetAtlas()->images[imageBackground_];
 
1017
        w = image.w;
 
1018
        h = image.h;
 
1019
}
 
1020
 
 
1021
bool Slider::Key(const KeyInput &input) {
 
1022
        if (HasFocus() && (input.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
 
1023
                if (ApplyKey(input.keyCode)) {
 
1024
                        Clamp();
 
1025
                        repeat_ = 0;
 
1026
                        repeatCode_ = input.keyCode;
 
1027
                        return true;
 
1028
                }
 
1029
                return false;
 
1030
        } else if ((input.flags & KEY_UP) && input.keyCode == repeatCode_) {
 
1031
                repeat_ = -1;
 
1032
                return false;
 
1033
        } else {
 
1034
                return false;
 
1035
        }
 
1036
}
 
1037
 
 
1038
bool Slider::ApplyKey(int keyCode) {
 
1039
        switch (keyCode) {
 
1040
        case NKCODE_DPAD_LEFT:
 
1041
        case NKCODE_MINUS:
 
1042
        case NKCODE_NUMPAD_SUBTRACT:
 
1043
                *value_ -= step_;
 
1044
                break;
 
1045
        case NKCODE_DPAD_RIGHT:
 
1046
        case NKCODE_PLUS:
 
1047
        case NKCODE_NUMPAD_ADD:
 
1048
                *value_ += step_;
 
1049
                break;
 
1050
        case NKCODE_PAGE_UP:
 
1051
                *value_ -= step_ * 10;
 
1052
                break;
 
1053
        case NKCODE_PAGE_DOWN:
 
1054
                *value_ += step_ * 10;
 
1055
                break;
 
1056
        case NKCODE_MOVE_HOME:
 
1057
                *value_ = minValue_;
 
1058
                break;
 
1059
        case NKCODE_MOVE_END:
 
1060
                *value_ = maxValue_;
 
1061
                break;
 
1062
        default:
 
1063
                return false;
 
1064
        }
 
1065
        return true;
 
1066
}
 
1067
 
 
1068
void Slider::Touch(const TouchInput &input) {
 
1069
        // Calling it afterwards, so dragging_ hasn't been set false yet when checking it above.
 
1070
        Clickable::Touch(input);
 
1071
        if (dragging_) {
 
1072
                float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
 
1073
                *value_ = floorf(relativeX * (maxValue_ - minValue_) + minValue_ + 0.5f);
 
1074
                Clamp();
 
1075
                EventParams params;
 
1076
                params.v = this;
 
1077
                params.a = (uint32_t)(*value_);
 
1078
                params.f = (float)(*value_);
 
1079
                OnChange.Trigger(params);
 
1080
        }
 
1081
 
 
1082
        // Cancel any key repeat.
 
1083
        repeat_ = -1;
 
1084
}
 
1085
 
 
1086
void Slider::Clamp() {
 
1087
        if (*value_ < minValue_) *value_ = minValue_;
 
1088
        else if (*value_ > maxValue_) *value_ = maxValue_;
 
1089
 
 
1090
        // Clamp the value to be a multiple of the nearest step (e.g. if step == 5, value == 293, it'll round down to 290).
 
1091
        *value_ = *value_ - fmodf(*value_, step_);
 
1092
}
 
1093
 
 
1094
void Slider::Draw(UIContext &dc) {
 
1095
        bool focus = HasFocus();
 
1096
        uint32_t linecolor = dc.theme->popupTitle.fgColor;
 
1097
        uint32_t knobcolor = (down_ || focus) ? dc.theme->popupTitle.fgColor : 0xFFFFFFFF;
 
1098
        float knobX = ((float)(*value_) - minValue_) / (maxValue_ - minValue_) * (bounds_.w - paddingLeft_ - paddingRight_) + (bounds_.x + paddingLeft_);
 
1099
        dc.FillRect(Drawable(linecolor), Bounds(bounds_.x + paddingLeft_, bounds_.centerY() - 2, knobX - (bounds_.x + paddingLeft_), 4));
 
1100
        dc.FillRect(Drawable(0xFF808080), Bounds(knobX, bounds_.centerY() - 2, (bounds_.x + bounds_.w - paddingRight_ - knobX), 4));
 
1101
        dc.Draw()->DrawImage(dc.theme->sliderKnob, knobX, bounds_.centerY(), 1.0f, knobcolor, ALIGN_CENTER);
 
1102
        char temp[64];
 
1103
        if (showPercent_)
 
1104
                sprintf(temp, "%i%%", *value_);
 
1105
        else
 
1106
                sprintf(temp, "%i", *value_);
 
1107
        dc.SetFontStyle(dc.theme->uiFont);
 
1108
        dc.DrawText(temp, bounds_.x2() - 22, bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER);
 
1109
}
 
1110
 
 
1111
void Slider::Update(const InputState &input_state) {
 
1112
        if (repeat_ >= 0) {
 
1113
                repeat_++;
 
1114
        }
 
1115
 
 
1116
        if (repeat_ >= 47) {
 
1117
                ApplyKey(repeatCode_);
 
1118
                if ((maxValue_ - minValue_) / step_ >= 300) {
 
1119
                        ApplyKey(repeatCode_);
 
1120
                }
 
1121
                Clamp();
 
1122
        } else if (repeat_ >= 12 && (repeat_ & 1) == 1) {
 
1123
                ApplyKey(repeatCode_);
 
1124
                Clamp();
 
1125
        }
 
1126
}
 
1127
 
 
1128
void Slider::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
1129
        // TODO
 
1130
        w = 100;
 
1131
        h = 50;
 
1132
}
 
1133
 
 
1134
bool SliderFloat::Key(const KeyInput &input) {
 
1135
        if (HasFocus() && (input.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
 
1136
                if (ApplyKey(input.keyCode)) {
 
1137
                        Clamp();
 
1138
                        repeat_ = 0;
 
1139
                        repeatCode_ = input.keyCode;
 
1140
                        return true;
 
1141
                }
 
1142
                return false;
 
1143
        } else if ((input.flags & KEY_UP) && input.keyCode == repeatCode_) {
 
1144
                repeat_ = -1;
 
1145
                return false;
 
1146
        } else {
 
1147
                return false;
 
1148
        }
 
1149
}
 
1150
 
 
1151
bool SliderFloat::ApplyKey(int keyCode) {
 
1152
        switch (keyCode) {
 
1153
        case NKCODE_DPAD_LEFT:
 
1154
        case NKCODE_MINUS:
 
1155
        case NKCODE_NUMPAD_SUBTRACT:
 
1156
                *value_ -= (maxValue_ - minValue_) / 50.0f;
 
1157
                break;
 
1158
        case NKCODE_DPAD_RIGHT:
 
1159
        case NKCODE_PLUS:
 
1160
        case NKCODE_NUMPAD_ADD:
 
1161
                *value_ += (maxValue_ - minValue_) / 50.0f;
 
1162
                break;
 
1163
        case NKCODE_PAGE_UP:
 
1164
                *value_ -= (maxValue_ - minValue_) / 5.0f;
 
1165
                break;
 
1166
        case NKCODE_PAGE_DOWN:
 
1167
                *value_ += (maxValue_ - minValue_) / 5.0f;
 
1168
                break;
 
1169
        case NKCODE_MOVE_HOME:
 
1170
                *value_ = minValue_;
 
1171
                break;
 
1172
        case NKCODE_MOVE_END:
 
1173
                *value_ = maxValue_;
 
1174
                break;
 
1175
        default:
 
1176
                return false;
 
1177
        }
 
1178
        return true;
 
1179
}
 
1180
 
 
1181
void SliderFloat::Touch(const TouchInput &input) {
 
1182
        Clickable::Touch(input);
 
1183
        if (dragging_) {
 
1184
                float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
 
1185
                *value_ = (relativeX * (maxValue_ - minValue_) + minValue_);
 
1186
                Clamp();
 
1187
                EventParams params;
 
1188
                params.v = this;
 
1189
                params.a = (uint32_t)(*value_);
 
1190
                params.f = (float)(*value_);
 
1191
                OnChange.Trigger(params);
 
1192
        }
 
1193
 
 
1194
        // Cancel any key repeat.
 
1195
        repeat_ = -1;
 
1196
}
 
1197
 
 
1198
void SliderFloat::Clamp() {
 
1199
        if (*value_ < minValue_)
 
1200
                *value_ = minValue_;
 
1201
        else if (*value_ > maxValue_)
 
1202
                *value_ = maxValue_;
 
1203
}
 
1204
 
 
1205
void SliderFloat::Draw(UIContext &dc) {
 
1206
        bool focus = HasFocus();
 
1207
        uint32_t linecolor = dc.theme->popupTitle.fgColor;
 
1208
        uint32_t knobcolor = (down_ || focus) ? dc.theme->popupTitle.fgColor : 0xFFFFFFFF;
 
1209
 
 
1210
        float knobX = (*value_ - minValue_) / (maxValue_ - minValue_) * (bounds_.w - paddingLeft_ - paddingRight_) + (bounds_.x + paddingLeft_);
 
1211
        dc.FillRect(Drawable(linecolor), Bounds(bounds_.x + paddingLeft_, bounds_.centerY() - 2, knobX - (bounds_.x + paddingLeft_), 4));
 
1212
        dc.FillRect(Drawable(0xFF808080), Bounds(knobX, bounds_.centerY() - 2, (bounds_.x + bounds_.w - paddingRight_ - knobX), 4));
 
1213
        dc.Draw()->DrawImage(dc.theme->sliderKnob, knobX, bounds_.centerY(), 1.0f, knobcolor, ALIGN_CENTER);
 
1214
        char temp[64];
 
1215
        sprintf(temp, "%0.2f", *value_);
 
1216
        dc.SetFontStyle(dc.theme->uiFont);
 
1217
        dc.DrawText(temp, bounds_.x2() - 22, bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER);
 
1218
}
 
1219
 
 
1220
void SliderFloat::Update(const InputState &input_state) {
 
1221
        if (repeat_ >= 0) {
 
1222
                repeat_++;
 
1223
        }
 
1224
 
 
1225
        if (repeat_ >= 47) {
 
1226
                ApplyKey(repeatCode_);
 
1227
                Clamp();
 
1228
        } else if (repeat_ >= 12 && (repeat_ & 1) == 1) {
 
1229
                ApplyKey(repeatCode_);
 
1230
                Clamp();
 
1231
        }
 
1232
}
 
1233
 
 
1234
void SliderFloat::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
 
1235
        // TODO
 
1236
        w = 100;
 
1237
        h = 50;
 
1238
}
 
1239
 
 
1240
}  // namespace