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"
14
#include "ui/ui_context.h"
15
#include "thin3d/thin3d.h"
16
#include "base/NativeApp.h"
20
static View *focusedView;
21
static bool focusMovementEnabled;
23
static recursive_mutex mutex_;
25
const float ITEM_HEIGHT = 64.f;
26
const float MIN_TEXT_SCALE = 0.8f;
27
const float MAX_ITEM_SIZE = 65535.0f;
29
struct DispatchQueueItem {
34
std::deque<DispatchQueueItem> g_dispatchQueue;
37
void EventTriggered(Event *e, EventParams params) {
38
lock_guard guard(mutex_);
40
DispatchQueueItem item;
43
g_dispatchQueue.push_front(item);
46
void DispatchEvents() {
47
lock_guard guard(mutex_);
49
while (!g_dispatchQueue.empty()) {
50
DispatchQueueItem item = g_dispatchQueue.back();
51
g_dispatchQueue.pop_back();
53
item.e->Dispatch(item.params);
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);
65
View *GetFocusedView() {
69
void SetFocusedView(View *view, bool force) {
71
focusedView->FocusChanged(FF_LOSTFOCUS);
75
focusedView->FocusChanged(FF_GOTFOCUS);
82
void EnableFocusMovement(bool enable) {
83
focusMovementEnabled = enable;
86
focusedView->FocusChanged(FF_LOSTFOCUS);
92
bool IsFocusMovementEnabled() {
93
return focusMovementEnabled;
96
void MeasureBySpec(Size sz, float contentWidth, MeasureSpec spec, float *measured) {
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);
110
*measured = spec.size;
111
} else if (spec.type == EXACTLY || (spec.type == AT_MOST && *measured > spec.size)) {
112
*measured = spec.size;
116
void ApplyBoundBySpec(float &bound, MeasureSpec spec) {
119
bound = bound < spec.size ? bound : spec.size;
129
void ApplyBoundsBySpec(Bounds &bounds, MeasureSpec horiz, MeasureSpec vert) {
130
ApplyBoundBySpec(bounds.w, horiz);
131
ApplyBoundBySpec(bounds.h, vert);
134
void Event::Add(std::function<EventReturn(EventParams&)> func) {
135
HandlerRegistration reg;
137
handlers_.push_back(reg);
140
// Call this from input thread or whatever, it doesn't matter
141
void Event::Trigger(EventParams &e) {
142
EventTriggered(this, e);
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;
153
return UI::EVENT_SKIPPED;
159
RemoveQueuedEvents(this);
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_);
171
void View::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
176
void View::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const {
177
GetContentDimensions(dc, w, h);
180
void View::Query(float x, float y, std::vector<View *> &list) {
181
if (bounds_.Contains(x, y)) {
182
list.push_back(this);
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);
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();
198
const std::string focusedKey = "ViewFocused::" + tag;
200
case UI::PERSIST_SAVE:
202
storage[focusedKey].resize(1);
205
case UI::PERSIST_RESTORE:
206
if (storage.find(focusedKey) != storage.end()) {
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.
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);
223
return bounds_.Center();
227
bool View::SetFocus() {
228
if (IsFocusMovementEnabled()) {
229
if (CanBeFocused()) {
230
SetFocusedView(this);
237
void Clickable::Click() {
243
void Clickable::FocusChanged(int focusFlags) {
244
if (focusFlags & FF_LOSTFOCUS) {
250
void Clickable::Touch(const TouchInput &input) {
257
if (input.flags & TOUCH_DOWN) {
258
if (bounds_.Contains(input.x, input.y)) {
259
if (IsFocusMovementEnabled())
260
SetFocusedView(this);
267
} else if (input.flags & TOUCH_MOVE) {
269
down_ = bounds_.Contains(input.x, input.y);
271
if (input.flags & TOUCH_UP) {
272
if ((input.flags & TOUCH_CANCEL) == 0 && dragging_ && bounds_.Contains(input.x, input.y)) {
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".
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();
288
// TODO: O/X confirm preference for xperia play?
290
bool IsDPadKey(const KeyInput &key) {
291
if (dpadKeys.empty()) {
292
return key.keyCode >= NKCODE_DPAD_UP && key.keyCode <= NKCODE_DPAD_RIGHT;
294
return MatchesKeyDef(dpadKeys, key);
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;
303
return key.keyCode == NKCODE_BUTTON_A || key.keyCode == NKCODE_BUTTON_CROSS || key.keyCode == NKCODE_BUTTON_1;
306
return MatchesKeyDef(confirmKeys, key);
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;
315
return key.keyCode == NKCODE_BUTTON_CIRCLE || key.keyCode == NKCODE_BUTTON_B || key.keyCode == NKCODE_BUTTON_2;
318
return MatchesKeyDef(cancelKeys, key);
322
bool IsTabLeftKey(const KeyInput &key) {
323
if (tabLeftKeys.empty()) {
324
return key.keyCode == NKCODE_BUTTON_L1;
326
return MatchesKeyDef(tabLeftKeys, key);
330
bool IsTabRightKey(const KeyInput &key) {
331
if (tabRightKeys.empty()) {
332
return key.keyCode == NKCODE_BUTTON_R1;
334
return MatchesKeyDef(tabRightKeys, key);
338
bool Clickable::Key(const KeyInput &key) {
339
if (!HasFocus() && key.deviceId != DEVICE_ID_MOUSE) {
343
// TODO: Replace most of Update with this.
345
if (key.flags & KEY_DOWN) {
346
if (IsAcceptKey(key)) {
351
if (key.flags & KEY_UP) {
352
if (IsAcceptKey(key)) {
358
} else if (IsEscapeKey(key)) {
365
void StickyChoice::Touch(const TouchInput &input) {
372
if (input.flags & TOUCH_DOWN) {
373
if (bounds_.Contains(input.x, input.y)) {
374
if (IsFocusMovementEnabled())
375
SetFocusedView(this);
382
bool StickyChoice::Key(const KeyInput &key) {
387
// TODO: Replace most of Update with this.
388
if (key.flags & KEY_DOWN) {
389
if (IsAcceptKey(key)) {
398
void StickyChoice::FocusChanged(int focusFlags) {
399
// Override Clickable's FocusChanged to do nothing.
402
Item::Item(LayoutParams *layoutParams) : InertView(layoutParams) {
404
layoutParams_->width = FILL_PARENT;
405
layoutParams_->height = ITEM_HEIGHT;
409
void Item::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
414
void ClickableItem::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
419
ClickableItem::ClickableItem(LayoutParams *layoutParams) : Clickable(layoutParams) {
421
if (layoutParams_->width == WRAP_CONTENT)
422
layoutParams_->width = FILL_PARENT;
426
void ClickableItem::Draw(UIContext &dc) {
427
Style style = dc.theme->itemStyle;
430
style = dc.theme->itemFocusedStyle;
433
style = dc.theme->itemDownStyle;
436
dc.FillRect(style.background, bounds_);
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_];
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;
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);
457
h = std::max(h, ITEM_HEIGHT);
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);
470
void Choice::HighlightChanged(bool highlighted){
471
highlighted_ = highlighted;
474
void Choice::Draw(UIContext &dc) {
476
ClickableItem::Draw(dc);
478
Style style = dc.theme->itemStyle;
480
style = dc.theme->itemHighlightedStyle;
483
style = dc.theme->itemDownStyle;
486
style = dc.theme->itemFocusedStyle;
488
dc.FillRect(style.background, bounds_);
491
Style style = dc.theme->itemStyle;
493
style = dc.theme->itemDisabledStyle;
496
if (atlasImage_ != -1) {
497
dc.Draw()->DrawImage(atlasImage_, bounds_.centerX(), bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER);
499
dc.SetFontStyle(dc.theme->uiFont);
501
const int paddingX = 12;
502
const float availWidth = bounds_.w - paddingX * 2 - textPadding_.horiz();
503
float scale = CalculateTextScale(dc, availWidth);
505
dc.SetFontScale(scale, scale);
507
dc.DrawTextRect(text_.c_str(), bounds_, style.fgColor, ALIGN_CENTER | FLAG_WRAP_TEXT);
509
if (iconImage_ != -1) {
510
dc.Draw()->DrawImage(iconImage_, bounds_.x2() - 32 - paddingX, bounds_.centerY(), 0.5f, style.fgColor, ALIGN_CENTER);
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);
516
dc.SetFontScale(1.0f, 1.0f);
520
dc.Draw()->DrawImage(dc.theme->checkOn, bounds_.x2() - 40, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER);
524
void InfoItem::Draw(UIContext &dc) {
527
UI::Style style = dc.theme->itemFocusedStyle;
528
style.background.color &= 0x7fffffff;
529
dc.FillRect(style.background, bounds_);
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);
539
ItemHeader::ItemHeader(const std::string &text, LayoutParams *layoutParams)
540
: Item(layoutParams), text_(text) {
541
layoutParams_->width = FILL_PARENT;
542
layoutParams_->height = 40;
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);
551
void PopupHeader::Draw(UIContext &dc) {
552
const float paddingHorizontal = 12;
553
const float availableWidth = bounds_.w - paddingHorizontal * 2;
556
dc.SetFontStyle(dc.theme->uiFont);
557
dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, text_.c_str(), &tw, &th, 0);
559
float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
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;
566
tb.x = bounds_.x + paddingHorizontal;
567
tb.w = bounds_.w - paddingHorizontal * 2;
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);
574
if (availableWidth < tw) {
579
void CheckBox::Toggle(){
581
*toggle_ = !(*toggle_);
584
EventReturn CheckBox::OnClicked(EventParams &e) {
586
return EVENT_CONTINUE; // It's safe to keep processing events.
589
void CheckBox::Draw(UIContext &dc) {
590
Style style = dc.theme->itemStyle;
592
style = dc.theme->itemDisabledStyle;
593
dc.SetFontStyle(dc.theme->uiFont);
595
ClickableItem::Draw(dc);
597
int image = *toggle_ ? dc.theme->checkOn : dc.theme->checkOff;
598
float imageW, imageH;
599
dc.Draw()->MeasureImage(image, &imageW, &imageH);
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);
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);
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);
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);
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;
635
float scale = CalculateTextScale(dc, availWidth);
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);
642
h = std::max(actualHeight, ITEM_HEIGHT);
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_];
651
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, text_.c_str(), &w, &h);
653
// Add some internal padding to not look totally ugly
658
void Button::Draw(UIContext &dc) {
659
Style style = dc.theme->buttonStyle;
661
if (HasFocus()) style = dc.theme->buttonFocusedStyle;
662
if (down_) style = dc.theme->buttonDownStyle;
663
if (!IsEnabled()) style = dc.theme->buttonDisabledStyle;
665
// dc.Draw()->DrawImage4Grid(style.image, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), style.bgColor);
666
dc.FillRect(style.background, bounds_);
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_);
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);
682
if (tw > bounds_.w || imageID_ != -1) {
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
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);
701
void Thin3DTextureView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
702
// TODO: involve sizemode
704
w = (float)texture_->Width();
705
h = (float)texture_->Height();
712
void Thin3DTextureView::Draw(UIContext &dc) {
713
// TODO: involve sizemode
716
dc.GetThin3DContext()->SetTexture(0, texture_);
717
dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
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);
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;
730
bounds.h = vert.size == 0 ? MAX_ITEM_SIZE : vert.size;
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_);
736
void TextView::Draw(UIContext &dc) {
738
if (measuredWidth_ > bounds_.w || measuredHeight_ > bounds_.h)
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.
747
dc.PushScissor(bounds_);
749
// In case it's been made focusable.
751
UI::Style style = dc.theme->itemFocusedStyle;
752
style.background.color &= 0x7fffffff;
753
dc.FillRect(style.background, bounds_);
755
dc.SetFontStyle(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont);
757
uint32_t shadowColor = 0x80000000;
758
dc.DrawTextRect(text_.c_str(), bounds_, shadowColor, textAlign_);
760
dc.DrawTextRect(text_.c_str(), bounds_, textColor_, textAlign_);
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();
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_);
776
float textX = bounds_.x;
779
Bounds textBounds = bounds_;
780
textBounds.x = textX;
783
if (placeholderText_.size()) {
784
dc.DrawTextRect(placeholderText_.c_str(), bounds_, 0x50FFFFFF, ALIGN_CENTER);
787
dc.DrawTextRect(text_.c_str(), textBounds, 0xFFFFFFFF, ALIGN_VCENTER | ALIGN_LEFT);
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);
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;
800
dc.FillRect(UI::Drawable(0xFFFFFFFF), Bounds(caretX - 1, bounds_.y + 2, 3, bounds_.h - 4));
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);
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);
817
pos = text.find('\n');
818
if (pos != std::string::npos) {
819
return text.substr(0, pos);
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);
832
bool TextEdit::Key(const KeyInput &input) {
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:
843
case NKCODE_DPAD_LEFT: // ASCII left arrow
844
u8_dec(text_.c_str(), &caret_);
846
case NKCODE_DPAD_RIGHT: // ASCII right arrow
847
u8_inc(text_.c_str(), &caret_);
849
case NKCODE_MOVE_HOME:
853
case NKCODE_MOVE_END:
854
case NKCODE_PAGE_DOWN:
855
caret_ = (int)text_.size();
857
case NKCODE_FORWARD_DEL:
858
if (caret_ < (int)text_.size()) {
859
int endCaret = caret_;
860
u8_inc(text_.c_str(), &endCaret);
862
text_.erase(text_.begin() + caret_, text_.begin() + endCaret);
868
int begCaret = caret_;
869
u8_dec(text_.c_str(), &begCaret);
871
text_.erase(text_.begin() + begCaret, text_.begin() + caret_);
889
switch (input.keyCode) {
891
// Just copy the entire text contents, until we get selection support.
892
System_SendMessage("setclipboardtext", text_.c_str());
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
904
size_t maxPaste = maxLen_ - text_.size();
905
if (clipText.size() > maxPaste) {
907
while ((size_t)end < maxPaste) {
908
u8_inc(clipText.c_str(), &end);
911
u8_dec(clipText.c_str(), &end);
913
clipText = clipText.substr(0, end);
915
InsertAtCaret(clipText.c_str());
929
if (caret_ > (int)text_.size()) {
930
caret_ = (int)text_.size();
934
if (input.flags & KEY_UP) {
935
switch (input.keyCode) {
936
case NKCODE_CTRL_LEFT:
937
case NKCODE_CTRL_RIGHT:
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)
949
buf[u8_wc_toutf8(buf, unichar)] = '\0';
950
if (strlen(buf) + text_.size() < maxLen_) {
961
OnTextChange.Trigger(e);
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]);
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);
978
void ProgressBar::Draw(UIContext &dc) {
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);
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;
992
if (input.flags & TOUCH_MOVE) {
993
if (bounds_.Contains(input.x, input.y))
994
down_ |= 1 << input.id;
996
down_ &= ~(1 << input.id);
999
if (input.flags & TOUCH_UP) {
1000
down_ &= ~(1 << input.id);
1006
*bitField_ &= ~bit_;
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);
1015
void TriggerButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
1016
const AtlasImage &image = dc.Draw()->GetAtlas()->images[imageBackground_];
1021
bool Slider::Key(const KeyInput &input) {
1022
if (HasFocus() && (input.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
1023
if (ApplyKey(input.keyCode)) {
1026
repeatCode_ = input.keyCode;
1030
} else if ((input.flags & KEY_UP) && input.keyCode == repeatCode_) {
1038
bool Slider::ApplyKey(int keyCode) {
1040
case NKCODE_DPAD_LEFT:
1042
case NKCODE_NUMPAD_SUBTRACT:
1045
case NKCODE_DPAD_RIGHT:
1047
case NKCODE_NUMPAD_ADD:
1050
case NKCODE_PAGE_UP:
1051
*value_ -= step_ * 10;
1053
case NKCODE_PAGE_DOWN:
1054
*value_ += step_ * 10;
1056
case NKCODE_MOVE_HOME:
1057
*value_ = minValue_;
1059
case NKCODE_MOVE_END:
1060
*value_ = maxValue_;
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);
1072
float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
1073
*value_ = floorf(relativeX * (maxValue_ - minValue_) + minValue_ + 0.5f);
1077
params.a = (uint32_t)(*value_);
1078
params.f = (float)(*value_);
1079
OnChange.Trigger(params);
1082
// Cancel any key repeat.
1086
void Slider::Clamp() {
1087
if (*value_ < minValue_) *value_ = minValue_;
1088
else if (*value_ > maxValue_) *value_ = maxValue_;
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_);
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);
1104
sprintf(temp, "%i%%", *value_);
1106
sprintf(temp, "%i", *value_);
1107
dc.SetFontStyle(dc.theme->uiFont);
1108
dc.DrawText(temp, bounds_.x2() - 22, bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER);
1111
void Slider::Update(const InputState &input_state) {
1116
if (repeat_ >= 47) {
1117
ApplyKey(repeatCode_);
1118
if ((maxValue_ - minValue_) / step_ >= 300) {
1119
ApplyKey(repeatCode_);
1122
} else if (repeat_ >= 12 && (repeat_ & 1) == 1) {
1123
ApplyKey(repeatCode_);
1128
void Slider::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
1134
bool SliderFloat::Key(const KeyInput &input) {
1135
if (HasFocus() && (input.flags & (KEY_DOWN | KEY_IS_REPEAT)) == KEY_DOWN) {
1136
if (ApplyKey(input.keyCode)) {
1139
repeatCode_ = input.keyCode;
1143
} else if ((input.flags & KEY_UP) && input.keyCode == repeatCode_) {
1151
bool SliderFloat::ApplyKey(int keyCode) {
1153
case NKCODE_DPAD_LEFT:
1155
case NKCODE_NUMPAD_SUBTRACT:
1156
*value_ -= (maxValue_ - minValue_) / 50.0f;
1158
case NKCODE_DPAD_RIGHT:
1160
case NKCODE_NUMPAD_ADD:
1161
*value_ += (maxValue_ - minValue_) / 50.0f;
1163
case NKCODE_PAGE_UP:
1164
*value_ -= (maxValue_ - minValue_) / 5.0f;
1166
case NKCODE_PAGE_DOWN:
1167
*value_ += (maxValue_ - minValue_) / 5.0f;
1169
case NKCODE_MOVE_HOME:
1170
*value_ = minValue_;
1172
case NKCODE_MOVE_END:
1173
*value_ = maxValue_;
1181
void SliderFloat::Touch(const TouchInput &input) {
1182
Clickable::Touch(input);
1184
float relativeX = (input.x - (bounds_.x + paddingLeft_)) / (bounds_.w - paddingLeft_ - paddingRight_);
1185
*value_ = (relativeX * (maxValue_ - minValue_) + minValue_);
1189
params.a = (uint32_t)(*value_);
1190
params.f = (float)(*value_);
1191
OnChange.Trigger(params);
1194
// Cancel any key repeat.
1198
void SliderFloat::Clamp() {
1199
if (*value_ < minValue_)
1200
*value_ = minValue_;
1201
else if (*value_ > maxValue_)
1202
*value_ = maxValue_;
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;
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);
1215
sprintf(temp, "%0.2f", *value_);
1216
dc.SetFontStyle(dc.theme->uiFont);
1217
dc.DrawText(temp, bounds_.x2() - 22, bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER);
1220
void SliderFloat::Update(const InputState &input_state) {
1225
if (repeat_ >= 47) {
1226
ApplyKey(repeatCode_);
1228
} else if (repeat_ >= 12 && (repeat_ & 1) == 1) {
1229
ApplyKey(repeatCode_);
1234
void SliderFloat::GetContentDimensions(const UIContext &dc, float &w, float &h) const {