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 &params) {
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 &params) {
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 &params) {
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 &params) {
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 &params) {
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 &params) {
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 &params) {
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 &params) {
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