1 #include <algorithm>
2 #include <map>
3 #include <sstream>
4
5 #include "Common/System/Display.h"
6 #include "Common/Input/InputState.h"
7 #include "Common/Input/KeyCodes.h"
8 #include "Common/Math/curves.h"
9 #include "Common/UI/UIScreen.h"
10 #include "Common/UI/Context.h"
11 #include "Common/UI/Screen.h"
12 #include "Common/UI/Root.h"
13 #include "Common/Data/Text/I18n.h"
14 #include "Common/Render/DrawBuffer.h"
15
16 #include "Common/Log.h"
17 #include "Common/StringUtils.h"
18
19 static const bool ClickDebug = false;
20
UIScreen()21 UIScreen::UIScreen()
22 : Screen() {
23 }
24
~UIScreen()25 UIScreen::~UIScreen() {
26 delete root_;
27 }
28
DoRecreateViews()29 void UIScreen::DoRecreateViews() {
30 if (recreateViews_) {
31 std::lock_guard<std::recursive_mutex> guard(screenManager()->inputLock_);
32
33 UI::PersistMap persisted;
34 bool persisting = root_ != nullptr;
35 if (persisting) {
36 root_->PersistData(UI::PERSIST_SAVE, "root", persisted);
37 }
38
39 delete root_;
40 root_ = nullptr;
41 CreateViews();
42 UI::View *defaultView = root_ ? root_->GetDefaultFocusView() : nullptr;
43 if (defaultView && defaultView->GetVisibility() == UI::V_VISIBLE) {
44 defaultView->SetFocus();
45 }
46 recreateViews_ = false;
47
48 if (persisting && root_ != nullptr) {
49 root_->PersistData(UI::PERSIST_RESTORE, "root", persisted);
50
51 // Update layout and refocus so things scroll into view.
52 // This is for resizing down, when focused on something now offscreen.
53 UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_, ignoreInsets_);
54 UI::View *focused = UI::GetFocusedView();
55 if (focused) {
56 root_->SubviewFocused(focused);
57 }
58 }
59 }
60 }
61
update()62 void UIScreen::update() {
63 DoRecreateViews();
64
65 if (root_) {
66 UpdateViewHierarchy(root_);
67 }
68 }
69
deviceLost()70 void UIScreen::deviceLost() {
71 if (root_)
72 root_->DeviceLost();
73 }
74
deviceRestored()75 void UIScreen::deviceRestored() {
76 if (root_)
77 root_->DeviceRestored(screenManager()->getDrawContext());
78 }
79
preRender()80 void UIScreen::preRender() {
81 using namespace Draw;
82 Draw::DrawContext *draw = screenManager()->getDrawContext();
83 if (!draw) {
84 return;
85 }
86 draw->BeginFrame();
87 // Bind and clear the back buffer
88 draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "UI");
89 screenManager()->getUIContext()->BeginFrame();
90
91 Draw::Viewport viewport;
92 viewport.TopLeftX = 0;
93 viewport.TopLeftY = 0;
94 viewport.Width = pixel_xres;
95 viewport.Height = pixel_yres;
96 viewport.MaxDepth = 1.0;
97 viewport.MinDepth = 0.0;
98 draw->SetViewports(1, &viewport);
99 draw->SetTargetSize(pixel_xres, pixel_yres);
100 }
101
postRender()102 void UIScreen::postRender() {
103 Draw::DrawContext *draw = screenManager()->getDrawContext();
104 if (!draw) {
105 return;
106 }
107 draw->EndFrame();
108 }
109
render()110 void UIScreen::render() {
111 DoRecreateViews();
112
113 if (root_) {
114 UIContext *uiContext = screenManager()->getUIContext();
115 UI::LayoutViewHierarchy(*uiContext, root_, ignoreInsets_);
116
117 uiContext->PushTransform({translation_, scale_, alpha_});
118
119 uiContext->Begin();
120 DrawBackground(*uiContext);
121 root_->Draw(*uiContext);
122 uiContext->Flush();
123
124 uiContext->PopTransform();
125 }
126 }
127
transformTouch(const TouchInput & touch)128 TouchInput UIScreen::transformTouch(const TouchInput &touch) {
129 TouchInput updated = touch;
130
131 float x = touch.x - translation_.x;
132 float y = touch.y - translation_.y;
133 // Scale around the center as the origin.
134 updated.x = (x - dp_xres * 0.5f) / scale_.x + dp_xres * 0.5f;
135 updated.y = (y - dp_yres * 0.5f) / scale_.y + dp_yres * 0.5f;
136
137 return updated;
138 }
139
touch(const TouchInput & touch)140 bool UIScreen::touch(const TouchInput &touch) {
141 if (root_) {
142 if (ClickDebug && (touch.flags & TOUCH_DOWN)) {
143 INFO_LOG(SYSTEM, "Touch down!");
144 std::vector<UI::View *> views;
145 root_->Query(touch.x, touch.y, views);
146 for (auto view : views) {
147 INFO_LOG(SYSTEM, "%s", view->DescribeLog().c_str());
148 }
149 }
150
151 UI::TouchEvent(touch, root_);
152 return true;
153 }
154 return false;
155 }
156
key(const KeyInput & key)157 bool UIScreen::key(const KeyInput &key) {
158 if (root_) {
159 return UI::KeyEvent(key, root_);
160 }
161 return false;
162 }
163
TriggerFinish(DialogResult result)164 void UIScreen::TriggerFinish(DialogResult result) {
165 screenManager()->finishDialog(this, result);
166 }
167
key(const KeyInput & key)168 bool UIDialogScreen::key(const KeyInput &key) {
169 bool retval = UIScreen::key(key);
170 if (!retval && (key.flags & KEY_DOWN) && UI::IsEscapeKey(key)) {
171 if (finished_) {
172 ERROR_LOG(SYSTEM, "Screen already finished");
173 } else {
174 finished_ = true;
175 TriggerFinish(DR_BACK);
176 UI::PlayUISound(UI::UISound::BACK);
177 }
178 return true;
179 }
180 return retval;
181 }
182
sendMessage(const char * msg,const char * value)183 void UIDialogScreen::sendMessage(const char *msg, const char *value) {
184 Screen *screen = screenManager()->dialogParent(this);
185 if (screen) {
186 screen->sendMessage(msg, value);
187 }
188 }
189
axis(const AxisInput & axis)190 bool UIScreen::axis(const AxisInput &axis) {
191 if (root_) {
192 UI::AxisEvent(axis, root_);
193 return true;
194 }
195 return false;
196 }
197
OnBack(UI::EventParams & e)198 UI::EventReturn UIScreen::OnBack(UI::EventParams &e) {
199 TriggerFinish(DR_BACK);
200 return UI::EVENT_DONE;
201 }
202
OnOK(UI::EventParams & e)203 UI::EventReturn UIScreen::OnOK(UI::EventParams &e) {
204 TriggerFinish(DR_OK);
205 return UI::EVENT_DONE;
206 }
207
OnCancel(UI::EventParams & e)208 UI::EventReturn UIScreen::OnCancel(UI::EventParams &e) {
209 TriggerFinish(DR_CANCEL);
210 return UI::EVENT_DONE;
211 }
212
PopupScreen(std::string title,std::string button1,std::string button2)213 PopupScreen::PopupScreen(std::string title, std::string button1, std::string button2)
214 : box_(0), defaultButton_(nullptr), title_(title) {
215 auto di = GetI18NCategory("Dialog");
216 if (!button1.empty())
217 button1_ = di->T(button1.c_str());
218 if (!button2.empty())
219 button2_ = di->T(button2.c_str());
220
221 alpha_ = 0.0f;
222 }
223
touch(const TouchInput & touch)224 bool PopupScreen::touch(const TouchInput &touch) {
225 if (!box_ || (touch.flags & TOUCH_DOWN) == 0) {
226 return UIDialogScreen::touch(touch);
227 }
228
229 if (!box_->GetBounds().Contains(touch.x, touch.y)) {
230 TriggerFinish(DR_BACK);
231 }
232
233 return UIDialogScreen::touch(touch);
234 }
235
key(const KeyInput & key)236 bool PopupScreen::key(const KeyInput &key) {
237 if (key.flags & KEY_DOWN) {
238 if (key.keyCode == NKCODE_ENTER && defaultButton_) {
239 UI::EventParams e{};
240 defaultButton_->OnClick.Trigger(e);
241 return true;
242 }
243 }
244
245 return UIDialogScreen::key(key);
246 }
247
update()248 void PopupScreen::update() {
249 UIDialogScreen::update();
250
251 if (defaultButton_)
252 defaultButton_->SetEnabled(CanComplete(DR_OK));
253
254 float animatePos = 1.0f;
255
256 ++frames_;
257 if (finishFrame_ >= 0) {
258 float leadOut = bezierEaseInOut((frames_ - finishFrame_) * (1.0f / (float)FRAMES_LEAD_OUT));
259 animatePos = 1.0f - leadOut;
260
261 if (frames_ >= finishFrame_ + FRAMES_LEAD_OUT) {
262 // Actual finish happens here.
263 screenManager()->finishDialog(this, finishResult_);
264 }
265 } else if (frames_ < FRAMES_LEAD_IN) {
266 float leadIn = bezierEaseInOut(frames_ * (1.0f / (float)FRAMES_LEAD_IN));
267 animatePos = leadIn;
268 }
269
270 if (animatePos < 1.0f) {
271 alpha_ = animatePos;
272 scale_.x = 0.9f + animatePos * 0.1f;
273 scale_.y = 0.9f + animatePos * 0.1f;
274
275 if (hasPopupOrigin_) {
276 float xoff = popupOrigin_.x - dp_xres / 2;
277 float yoff = popupOrigin_.y - dp_yres / 2;
278
279 // Pull toward the origin a bit.
280 translation_.x = xoff * (1.0f - animatePos) * 0.2f;
281 translation_.y = yoff * (1.0f - animatePos) * 0.2f;
282 } else {
283 translation_.y = -dp_yres * (1.0f - animatePos) * 0.2f;
284 }
285 } else {
286 alpha_ = 1.0f;
287 scale_.x = 1.0f;
288 scale_.y = 1.0f;
289 translation_.x = 0.0f;
290 translation_.y = 0.0f;
291 }
292 }
293
SetPopupOrigin(const UI::View * view)294 void PopupScreen::SetPopupOrigin(const UI::View *view) {
295 hasPopupOrigin_ = true;
296 popupOrigin_ = view->GetBounds().Center();
297 }
298
SetPopupOffset(float y)299 void PopupScreen::SetPopupOffset(float y) {
300 offsetY_ = y;
301 }
302
TriggerFinish(DialogResult result)303 void PopupScreen::TriggerFinish(DialogResult result) {
304 if (CanComplete(result)) {
305 finishFrame_ = frames_;
306 finishResult_ = result;
307
308 OnCompleted(result);
309 }
310 }
311
resized()312 void PopupScreen::resized() {
313 RecreateViews();
314 }
315
CreateViews()316 void PopupScreen::CreateViews() {
317 using namespace UI;
318 UIContext &dc = *screenManager()->getUIContext();
319
320 AnchorLayout *anchor = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
321 anchor->Overflow(false);
322 root_ = anchor;
323
324 float yres = screenManager()->getUIContext()->GetBounds().h;
325
326 box_ = new LinearLayout(ORIENT_VERTICAL,
327 new AnchorLayoutParams(PopupWidth(), FillVertical() ? yres - 30 : WRAP_CONTENT, dc.GetBounds().centerX(), dc.GetBounds().centerY() + offsetY_, NONE, NONE, true));
328
329 root_->Add(box_);
330 box_->SetBG(dc.theme->popupStyle.background);
331 box_->SetHasDropShadow(true);
332 // Since we scale a bit, make the dropshadow bleed past the edges.
333 box_->SetDropShadowExpand(std::max(dp_xres, dp_yres));
334
335 View *title = new PopupHeader(title_);
336 box_->Add(title);
337
338 CreatePopupContents(box_);
339 root_->SetDefaultFocusView(box_);
340
341 if (ShowButtons() && !button1_.empty()) {
342 // And the two buttons at the bottom.
343 LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(200, WRAP_CONTENT));
344 buttonRow->SetSpacing(0);
345 Margins buttonMargins(5, 5);
346
347 // Adjust button order to the platform default.
348 #if defined(_WIN32)
349 defaultButton_ = buttonRow->Add(new Button(button1_, new LinearLayoutParams(1.0f, buttonMargins)));
350 defaultButton_->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK);
351 if (!button2_.empty())
352 buttonRow->Add(new Button(button2_, new LinearLayoutParams(1.0f, buttonMargins)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnCancel);
353 #else
354 if (!button2_.empty())
355 buttonRow->Add(new Button(button2_, new LinearLayoutParams(1.0f)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnCancel);
356 defaultButton_ = buttonRow->Add(new Button(button1_, new LinearLayoutParams(1.0f)));
357 defaultButton_->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK);
358 #endif
359
360 box_->Add(buttonRow);
361 }
362 }
363
CreatePopupContents(UI::ViewGroup * parent)364 void MessagePopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
365 using namespace UI;
366 UIContext &dc = *screenManager()->getUIContext();
367
368 std::vector<std::string> messageLines;
369 SplitString(message_, '\n', messageLines);
370 for (const auto& lineOfText : messageLines)
371 parent->Add(new UI::TextView(lineOfText, ALIGN_LEFT | ALIGN_VCENTER, false))->SetTextColor(dc.theme->popupStyle.fgColor);
372 }
373
OnCompleted(DialogResult result)374 void MessagePopupScreen::OnCompleted(DialogResult result) {
375 if (result == DR_OK) {
376 if (callback_)
377 callback_(true);
378 } else {
379 if (callback_)
380 callback_(false);
381 }
382 }
383
CreatePopupContents(UI::ViewGroup * parent)384 void ListPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
385 using namespace UI;
386
387 listView_ = parent->Add(new ListView(&adaptor_, hidden_)); //, new LinearLayoutParams(1.0)));
388 listView_->SetMaxHeight(screenManager()->getUIContext()->GetBounds().h - 140);
389 listView_->OnChoice.Handle(this, &ListPopupScreen::OnListChoice);
390 }
391
OnListChoice(UI::EventParams & e)392 UI::EventReturn ListPopupScreen::OnListChoice(UI::EventParams &e) {
393 adaptor_.SetSelected(e.a);
394 if (callback_)
395 callback_(adaptor_.GetSelected());
396 TriggerFinish(DR_OK);
397 OnChoice.Dispatch(e);
398 return UI::EVENT_DONE;
399 }
400
401 namespace UI {
402
ChopTitle(const std::string & title)403 std::string ChopTitle(const std::string &title) {
404 size_t pos = title.find('\n');
405 if (pos != title.npos) {
406 return title.substr(0, pos);
407 }
408 return title;
409 }
410
HandleClick(UI::EventParams & e)411 UI::EventReturn PopupMultiChoice::HandleClick(UI::EventParams &e) {
412 restoreFocus_ = HasFocus();
413
414 auto category = category_ ? GetI18NCategory(category_) : nullptr;
415
416 std::vector<std::string> choices;
417 for (int i = 0; i < numChoices_; i++) {
418 choices.push_back(category ? category->T(choices_[i]) : choices_[i]);
419 }
420
421 ListPopupScreen *popupScreen = new ListPopupScreen(ChopTitle(text_), choices, *value_ - minVal_,
422 std::bind(&PopupMultiChoice::ChoiceCallback, this, std::placeholders::_1));
423 popupScreen->SetHiddenChoices(hidden_);
424 if (e.v)
425 popupScreen->SetPopupOrigin(e.v);
426 screenManager_->push(popupScreen);
427 return UI::EVENT_DONE;
428 }
429
Update()430 void PopupMultiChoice::Update() {
431 UpdateText();
432 }
433
UpdateText()434 void PopupMultiChoice::UpdateText() {
435 if (!choices_)
436 return;
437 auto category = GetI18NCategory(category_);
438 // Clamp the value to be safe.
439 if (*value_ < minVal_ || *value_ > minVal_ + numChoices_ - 1) {
440 valueText_ = "(invalid choice)"; // Shouldn't happen. Should be no need to translate this.
441 } else {
442 valueText_ = category ? category->T(choices_[*value_ - minVal_]) : choices_[*value_ - minVal_];
443 }
444 }
445
ChoiceCallback(int num)446 void PopupMultiChoice::ChoiceCallback(int num) {
447 if (num != -1) {
448 *value_ = num + minVal_;
449 UpdateText();
450
451 UI::EventParams e{};
452 e.v = this;
453 e.a = num;
454 OnChoice.Trigger(e);
455
456 if (restoreFocus_) {
457 SetFocusedView(this);
458 }
459 PostChoiceCallback(num);
460 }
461 }
462
ValueText() const463 std::string PopupMultiChoice::ValueText() const {
464 return valueText_;
465 }
466
PopupSliderChoice(int * value,int minValue,int maxValue,const std::string & text,ScreenManager * screenManager,const std::string & units,LayoutParams * layoutParams)467 PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams)
468 : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1), units_(units), screenManager_(screenManager) {
469 fmt_ = "%i";
470 OnClick.Handle(this, &PopupSliderChoice::HandleClick);
471 }
472
PopupSliderChoice(int * value,int minValue,int maxValue,const std::string & text,int step,ScreenManager * screenManager,const std::string & units,LayoutParams * layoutParams)473 PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, int step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams)
474 : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) {
475 fmt_ = "%i";
476 OnClick.Handle(this, &PopupSliderChoice::HandleClick);
477 }
478
PopupSliderChoiceFloat(float * value,float minValue,float maxValue,const std::string & text,ScreenManager * screenManager,const std::string & units,LayoutParams * layoutParams)479 PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams)
480 : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1.0f), units_(units), screenManager_(screenManager) {
481 fmt_ = "%2.2f";
482 OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick);
483 }
484
PopupSliderChoiceFloat(float * value,float minValue,float maxValue,const std::string & text,float step,ScreenManager * screenManager,const std::string & units,LayoutParams * layoutParams)485 PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, float step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams)
486 : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) {
487 fmt_ = "%2.2f";
488 OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick);
489 }
490
HandleClick(EventParams & e)491 EventReturn PopupSliderChoice::HandleClick(EventParams &e) {
492 restoreFocus_ = HasFocus();
493
494 SliderPopupScreen *popupScreen = new SliderPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_);
495 if (!negativeLabel_.empty())
496 popupScreen->SetNegativeDisable(negativeLabel_);
497 popupScreen->OnChange.Handle(this, &PopupSliderChoice::HandleChange);
498 if (e.v)
499 popupScreen->SetPopupOrigin(e.v);
500 screenManager_->push(popupScreen);
501 return EVENT_DONE;
502 }
503
HandleChange(EventParams & e)504 EventReturn PopupSliderChoice::HandleChange(EventParams &e) {
505 e.v = this;
506 OnChange.Trigger(e);
507
508 if (restoreFocus_) {
509 SetFocusedView(this);
510 }
511 return EVENT_DONE;
512 }
513
ValueText() const514 std::string PopupSliderChoice::ValueText() const {
515 // Always good to have space for Unicode.
516 char temp[256];
517 if (zeroLabel_.size() && *value_ == 0) {
518 strcpy(temp, zeroLabel_.c_str());
519 } else if (negativeLabel_.size() && *value_ < 0) {
520 strcpy(temp, negativeLabel_.c_str());
521 } else {
522 sprintf(temp, fmt_, *value_);
523 }
524
525 return temp;
526 }
527
HandleClick(EventParams & e)528 EventReturn PopupSliderChoiceFloat::HandleClick(EventParams &e) {
529 restoreFocus_ = HasFocus();
530
531 SliderFloatPopupScreen *popupScreen = new SliderFloatPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_);
532 popupScreen->OnChange.Handle(this, &PopupSliderChoiceFloat::HandleChange);
533 if (e.v)
534 popupScreen->SetPopupOrigin(e.v);
535 screenManager_->push(popupScreen);
536 return EVENT_DONE;
537 }
538
HandleChange(EventParams & e)539 EventReturn PopupSliderChoiceFloat::HandleChange(EventParams &e) {
540 e.v = this;
541 OnChange.Trigger(e);
542
543 if (restoreFocus_) {
544 SetFocusedView(this);
545 }
546 return EVENT_DONE;
547 }
548
ValueText() const549 std::string PopupSliderChoiceFloat::ValueText() const {
550 char temp[256];
551 if (zeroLabel_.size() && *value_ == 0.0f) {
552 strcpy(temp, zeroLabel_.c_str());
553 } else {
554 sprintf(temp, fmt_, *value_);
555 }
556
557 return temp;
558 }
559
OnDecrease(EventParams & params)560 EventReturn SliderPopupScreen::OnDecrease(EventParams ¶ms) {
561 if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) {
562 sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f);
563 }
564 sliderValue_ -= step_;
565 slider_->Clamp();
566 changing_ = true;
567 char temp[64];
568 sprintf(temp, "%d", sliderValue_);
569 edit_->SetText(temp);
570 changing_ = false;
571 disabled_ = false;
572 return EVENT_DONE;
573 }
574
OnIncrease(EventParams & params)575 EventReturn SliderPopupScreen::OnIncrease(EventParams ¶ms) {
576 if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) {
577 sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f);
578 }
579 sliderValue_ += step_;
580 slider_->Clamp();
581 changing_ = true;
582 char temp[64];
583 sprintf(temp, "%d", sliderValue_);
584 edit_->SetText(temp);
585 changing_ = false;
586 disabled_ = false;
587 return EVENT_DONE;
588 }
589
OnSliderChange(EventParams & params)590 EventReturn SliderPopupScreen::OnSliderChange(EventParams ¶ms) {
591 changing_ = true;
592 char temp[64];
593 sprintf(temp, "%d", sliderValue_);
594 edit_->SetText(temp);
595 changing_ = false;
596 disabled_ = false;
597 return EVENT_DONE;
598 }
599
OnTextChange(EventParams & params)600 EventReturn SliderPopupScreen::OnTextChange(EventParams ¶ms) {
601 if (!changing_) {
602 sliderValue_ = atoi(edit_->GetText().c_str());
603 disabled_ = false;
604 slider_->Clamp();
605 }
606 return EVENT_DONE;
607 }
608
CreatePopupContents(UI::ViewGroup * parent)609 void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
610 using namespace UI;
611 UIContext &dc = *screenManager()->getUIContext();
612
613 sliderValue_ = *value_;
614 if (disabled_ && sliderValue_ < 0)
615 sliderValue_ = 0;
616 LinearLayout *vert = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::Margins(10, 10))));
617 slider_ = new Slider(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 10)));
618 slider_->OnChange.Handle(this, &SliderPopupScreen::OnSliderChange);
619 vert->Add(slider_);
620
621 LinearLayout *lin = vert->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(UI::Margins(10, 10))));
622 lin->Add(new Button(" - "))->OnClick.Handle(this, &SliderPopupScreen::OnDecrease);
623 lin->Add(new Button(" + "))->OnClick.Handle(this, &SliderPopupScreen::OnIncrease);
624
625 char temp[64];
626 sprintf(temp, "%d", sliderValue_);
627 edit_ = new TextEdit(temp, Title(), "", new LinearLayoutParams(10.0f));
628 edit_->SetMaxLen(16);
629 edit_->SetTextColor(dc.theme->popupStyle.fgColor);
630 edit_->SetTextAlign(FLAG_DYNAMIC_ASCII);
631 edit_->OnTextChange.Handle(this, &SliderPopupScreen::OnTextChange);
632 changing_ = false;
633 lin->Add(edit_);
634
635 if (!units_.empty())
636 lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->popupStyle.fgColor);
637
638 if (!negativeLabel_.empty())
639 vert->Add(new CheckBox(&disabled_, negativeLabel_));
640
641 if (IsFocusMovementEnabled())
642 UI::SetFocusedView(slider_);
643 }
644
CreatePopupContents(UI::ViewGroup * parent)645 void SliderFloatPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
646 using namespace UI;
647 UIContext &dc = *screenManager()->getUIContext();
648
649 sliderValue_ = *value_;
650 LinearLayout *vert = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::Margins(10, 10))));
651 slider_ = new SliderFloat(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 10)));
652 slider_->OnChange.Handle(this, &SliderFloatPopupScreen::OnSliderChange);
653 vert->Add(slider_);
654
655 LinearLayout *lin = vert->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(UI::Margins(10, 10))));
656 lin->Add(new Button(" - "))->OnClick.Handle(this, &SliderFloatPopupScreen::OnDecrease);
657 lin->Add(new Button(" + "))->OnClick.Handle(this, &SliderFloatPopupScreen::OnIncrease);
658
659 char temp[64];
660 sprintf(temp, "%0.3f", sliderValue_);
661 edit_ = new TextEdit(temp, Title(), "", new LinearLayoutParams(10.0f));
662 edit_->SetMaxLen(16);
663 edit_->SetTextColor(dc.theme->popupStyle.fgColor);
664 edit_->SetTextAlign(FLAG_DYNAMIC_ASCII);
665 edit_->OnTextChange.Handle(this, &SliderFloatPopupScreen::OnTextChange);
666 changing_ = false;
667 lin->Add(edit_);
668 if (!units_.empty())
669 lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->popupStyle.fgColor);
670
671 // slider_ = parent->Add(new SliderFloat(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 5))));
672 if (IsFocusMovementEnabled())
673 UI::SetFocusedView(slider_);
674 }
675
OnDecrease(EventParams & params)676 EventReturn SliderFloatPopupScreen::OnDecrease(EventParams ¶ms) {
677 if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) {
678 sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f);
679 }
680 sliderValue_ -= step_;
681 slider_->Clamp();
682 changing_ = true;
683 char temp[64];
684 sprintf(temp, "%0.3f", sliderValue_);
685 edit_->SetText(temp);
686 changing_ = false;
687 return EVENT_DONE;
688 }
689
OnIncrease(EventParams & params)690 EventReturn SliderFloatPopupScreen::OnIncrease(EventParams ¶ms) {
691 if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) {
692 sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f);
693 }
694 sliderValue_ += step_;
695 slider_->Clamp();
696 changing_ = true;
697 char temp[64];
698 sprintf(temp, "%0.3f", sliderValue_);
699 edit_->SetText(temp);
700 changing_ = false;
701 return EVENT_DONE;
702 }
703
OnSliderChange(EventParams & params)704 EventReturn SliderFloatPopupScreen::OnSliderChange(EventParams ¶ms) {
705 changing_ = true;
706 char temp[64];
707 sprintf(temp, "%0.3f", sliderValue_);
708 edit_->SetText(temp);
709 changing_ = false;
710 return EVENT_DONE;
711 }
712
OnTextChange(EventParams & params)713 EventReturn SliderFloatPopupScreen::OnTextChange(EventParams ¶ms) {
714 if (!changing_) {
715 sliderValue_ = atof(edit_->GetText().c_str());
716 slider_->Clamp();
717 }
718 return EVENT_DONE;
719 }
720
OnCompleted(DialogResult result)721 void SliderPopupScreen::OnCompleted(DialogResult result) {
722 if (result == DR_OK) {
723 *value_ = disabled_ ? -1 : sliderValue_;
724 EventParams e{};
725 e.v = nullptr;
726 e.a = *value_;
727 OnChange.Trigger(e);
728 }
729 }
730
OnCompleted(DialogResult result)731 void SliderFloatPopupScreen::OnCompleted(DialogResult result) {
732 if (result == DR_OK) {
733 *value_ = sliderValue_;
734 EventParams e{};
735 e.v = nullptr;
736 e.a = (int)*value_;
737 e.f = *value_;
738 OnChange.Trigger(e);
739 }
740 }
741
PopupTextInputChoice(std::string * value,const std::string & title,const std::string & placeholder,int maxLen,ScreenManager * screenManager,LayoutParams * layoutParams)742 PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string &title, const std::string &placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams)
743 : AbstractChoiceWithValueDisplay(title, layoutParams), screenManager_(screenManager), value_(value), placeHolder_(placeholder), maxLen_(maxLen) {
744 OnClick.Handle(this, &PopupTextInputChoice::HandleClick);
745 }
746
HandleClick(EventParams & e)747 EventReturn PopupTextInputChoice::HandleClick(EventParams &e) {
748 restoreFocus_ = HasFocus();
749
750 TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, ChopTitle(text_), maxLen_);
751 popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange);
752 if (e.v)
753 popupScreen->SetPopupOrigin(e.v);
754 screenManager_->push(popupScreen);
755 return EVENT_DONE;
756 }
757
ValueText() const758 std::string PopupTextInputChoice::ValueText() const {
759 return *value_;
760 }
761
HandleChange(EventParams & e)762 EventReturn PopupTextInputChoice::HandleChange(EventParams &e) {
763 e.v = this;
764 OnChange.Trigger(e);
765
766 if (restoreFocus_) {
767 SetFocusedView(this);
768 }
769 return EVENT_DONE;
770 }
771
CreatePopupContents(UI::ViewGroup * parent)772 void TextEditPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
773 using namespace UI;
774 UIContext &dc = *screenManager()->getUIContext();
775
776 textEditValue_ = *value_;
777 LinearLayout *lin = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams((UI::Size)300, WRAP_CONTENT)));
778 edit_ = new TextEdit(textEditValue_, Title(), placeholder_, new LinearLayoutParams(1.0f));
779 edit_->SetMaxLen(maxLen_);
780 edit_->SetTextColor(dc.theme->popupStyle.fgColor);
781 lin->Add(edit_);
782
783 if (IsFocusMovementEnabled())
784 UI::SetFocusedView(edit_);
785 }
786
OnCompleted(DialogResult result)787 void TextEditPopupScreen::OnCompleted(DialogResult result) {
788 if (result == DR_OK) {
789 *value_ = StripSpaces(edit_->GetText());
790 EventParams e{};
791 e.v = edit_;
792 OnChange.Trigger(e);
793 }
794 }
795
GetContentDimensionsBySpec(const UIContext & dc,MeasureSpec horiz,MeasureSpec vert,float & w,float & h) const796 void AbstractChoiceWithValueDisplay::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const {
797 const std::string valueText = ValueText();
798 int paddingX = 12;
799 // Assume we want at least 20% of the size for the label, at a minimum.
800 float availWidth = (horiz.size - paddingX * 2) * 0.8f;
801 if (availWidth < 0) {
802 availWidth = 65535.0f;
803 }
804 float scale = CalculateValueScale(dc, valueText, availWidth);
805 Bounds availBounds(0, 0, availWidth, vert.size);
806
807 float valueW, valueH;
808 dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &valueW, &valueH, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
809 valueW += paddingX;
810
811 // Give the choice itself less space to grow in, so it shrinks if needed.
812 MeasureSpec horizLabel = horiz;
813 horizLabel.size -= valueW;
814 Choice::GetContentDimensionsBySpec(dc, horiz, vert, w, h);
815
816 w += valueW;
817 h = std::max(h, valueH);
818 }
819
Draw(UIContext & dc)820 void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
821 Style style = dc.theme->itemStyle;
822 if (!IsEnabled()) {
823 style = dc.theme->itemDisabledStyle;
824 }
825 int paddingX = 12;
826 dc.SetFontStyle(dc.theme->uiFont);
827
828 const std::string valueText = ValueText();
829 // Assume we want at least 20% of the size for the label, at a minimum.
830 float availWidth = (bounds_.w - paddingX * 2) * 0.8f;
831 float scale = CalculateValueScale(dc, valueText, availWidth);
832
833 float w, h;
834 Bounds availBounds(0, 0, availWidth, bounds_.h);
835 dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &w, &h, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
836 textPadding_.right = w + paddingX;
837
838 Choice::Draw(dc);
839 dc.SetFontScale(scale, scale);
840 Bounds valueBounds(bounds_.x2() - textPadding_.right, bounds_.y, w, bounds_.h);
841 dc.DrawTextRect(valueText.c_str(), valueBounds, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
842 dc.SetFontScale(1.0f, 1.0f);
843 }
844
CalculateValueScale(const UIContext & dc,const std::string & valueText,float availWidth) const845 float AbstractChoiceWithValueDisplay::CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const {
846 float actualWidth, actualHeight;
847 Bounds availBounds(0, 0, availWidth, bounds_.h);
848 dc.MeasureTextRect(dc.theme->uiFont, 1.0f, 1.0f, valueText.c_str(), (int)valueText.size(), availBounds, &actualWidth, &actualHeight);
849 if (actualWidth > availWidth) {
850 return std::max(0.8f, availWidth / actualWidth);
851 }
852 return 1.0f;
853 }
854
ValueText() const855 std::string ChoiceWithValueDisplay::ValueText() const {
856 auto category = GetI18NCategory(category_);
857 std::ostringstream valueText;
858 if (translateCallback_ && sValue_) {
859 valueText << translateCallback_(sValue_->c_str());
860 } else if (sValue_ != nullptr) {
861 if (category)
862 valueText << category->T(*sValue_);
863 else
864 valueText << *sValue_;
865 } else if (iValue_ != nullptr) {
866 valueText << *iValue_;
867 }
868
869 return valueText.str();
870 }
871
872 } // namespace UI
873