#include #include "base/display.h" #include "input/input_state.h" #include "input/keycodes.h" #include "ui/ui_screen.h" #include "ui/ui_context.h" #include "ui/screen.h" #include "i18n/i18n.h" #include "gfx_es2/draw_buffer.h" static const bool ClickDebug = false; UIScreen::UIScreen() : Screen(), root_(0), recreateViews_(true), hatDown_(0) { } UIScreen::~UIScreen() { delete root_; } void UIScreen::DoRecreateViews() { if (recreateViews_) { UI::PersistMap persisted; bool persisting = root_ != nullptr; if (persisting) { root_->PersistData(UI::PERSIST_SAVE, "root", persisted); } delete root_; root_ = nullptr; CreateViews(); if (root_ && root_->GetDefaultFocusView()) { root_->GetDefaultFocusView()->SetFocus(); } recreateViews_ = false; if (persisting && root_ != nullptr) { root_->PersistData(UI::PERSIST_RESTORE, "root", persisted); // Update layout and refocus so things scroll into view. // This is for resizing down, when focused on something now offscreen. UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_); UI::View *focused = UI::GetFocusedView(); if (focused) { root_->SubviewFocused(focused); } } } } void UIScreen::update(InputState &input) { DoRecreateViews(); if (root_) { UpdateViewHierarchy(input, root_); } } void UIScreen::preRender() { Thin3DContext *thin3d = screenManager()->getThin3DContext(); if (!thin3d) { return; } thin3d->Begin(true, 0xFF000000, 0.0f, 0); T3DViewport viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; viewport.Width = pixel_xres; viewport.Height = pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; thin3d->SetViewports(1, &viewport); thin3d->SetTargetSize(pixel_xres, pixel_yres); } void UIScreen::postRender() { Thin3DContext *thin3d = screenManager()->getThin3DContext(); if (!thin3d) { return; } thin3d->End(); } void UIScreen::render() { DoRecreateViews(); if (root_) { UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_); screenManager()->getUIContext()->Begin(); DrawBackground(*screenManager()->getUIContext()); root_->Draw(*screenManager()->getUIContext()); screenManager()->getUIContext()->End(); screenManager()->getUIContext()->Flush(); } } bool UIScreen::touch(const TouchInput &touch) { if (root_) { if (ClickDebug && (touch.flags & TOUCH_DOWN)) { ILOG("Touch down!"); std::vector views; root_->Query(touch.x, touch.y, views); for (auto view : views) { ILOG("%s", view->Describe().c_str()); } } UI::TouchEvent(touch, root_); return true; } return false; } bool UIScreen::key(const KeyInput &key) { if (root_) { return UI::KeyEvent(key, root_); } return false; } bool UIDialogScreen::key(const KeyInput &key) { bool retval = UIScreen::key(key); if (!retval && (key.flags & KEY_DOWN) && UI::IsEscapeKey(key)) { if (finished_) { ELOG("Screen already finished"); } else { finished_ = true; screenManager()->finishDialog(this, DR_BACK); } return true; } return retval; } bool UIScreen::axis(const AxisInput &axis) { // Simple translation of hat to keys for Shield and other modern pads. // TODO: Use some variant of keymap? int flags = 0; if (axis.axisId == JOYSTICK_AXIS_HAT_X) { if (axis.value < -0.7f) flags |= PAD_BUTTON_LEFT; if (axis.value > 0.7f) flags |= PAD_BUTTON_RIGHT; } if (axis.axisId == JOYSTICK_AXIS_HAT_Y) { if (axis.value < -0.7f) flags |= PAD_BUTTON_UP; if (axis.value > 0.7f) flags |= PAD_BUTTON_DOWN; } // Yeah yeah, this should be table driven.. int pressed = flags & ~hatDown_; int released = ~flags & hatDown_; if (pressed & PAD_BUTTON_LEFT) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_LEFT, KEY_DOWN)); if (pressed & PAD_BUTTON_RIGHT) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_RIGHT, KEY_DOWN)); if (pressed & PAD_BUTTON_UP) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_UP, KEY_DOWN)); if (pressed & PAD_BUTTON_DOWN) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_DOWN, KEY_DOWN)); if (released & PAD_BUTTON_LEFT) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_LEFT, KEY_UP)); if (released & PAD_BUTTON_RIGHT) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_RIGHT, KEY_UP)); if (released & PAD_BUTTON_UP) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_UP, KEY_UP)); if (released & PAD_BUTTON_DOWN) key(KeyInput(DEVICE_ID_KEYBOARD, NKCODE_DPAD_DOWN, KEY_UP)); hatDown_ = flags; if (root_) { UI::AxisEvent(axis, root_); return true; } return (pressed & (PAD_BUTTON_LEFT | PAD_BUTTON_RIGHT | PAD_BUTTON_UP | PAD_BUTTON_DOWN)) != 0; } UI::EventReturn UIScreen::OnBack(UI::EventParams &e) { screenManager()->finishDialog(this, DR_BACK); return UI::EVENT_DONE; } UI::EventReturn UIScreen::OnOK(UI::EventParams &e) { screenManager()->finishDialog(this, DR_OK); return UI::EVENT_DONE; } UI::EventReturn UIScreen::OnCancel(UI::EventParams &e) { screenManager()->finishDialog(this, DR_CANCEL); return UI::EVENT_DONE; } PopupScreen::PopupScreen(std::string title, std::string button1, std::string button2) : box_(0), title_(title), defaultButton_(NULL) { I18NCategory *di = GetI18NCategory("Dialog"); if (!button1.empty()) button1_ = di->T(button1.c_str()); if (!button2.empty()) button2_ = di->T(button2.c_str()); } bool PopupScreen::touch(const TouchInput &touch) { if (!box_ || (touch.flags & TOUCH_DOWN) == 0 || touch.id != 0) { return UIDialogScreen::touch(touch); } if (!box_->GetBounds().Contains(touch.x, touch.y)) screenManager()->finishDialog(this, DR_BACK); return UIDialogScreen::touch(touch); } bool PopupScreen::key(const KeyInput &key) { if (key.flags & KEY_DOWN) { if (key.keyCode == NKCODE_ENTER && defaultButton_) { UI::EventParams e; defaultButton_->OnClick.Trigger(e); return true; } } return UIDialogScreen::key(key); } void PopupScreen::CreateViews() { using namespace UI; UIContext &dc = *screenManager()->getUIContext(); AnchorLayout *anchor = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); anchor->Overflow(false); root_ = anchor; float yres = screenManager()->getUIContext()->GetBounds().h; box_ = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(550, FillVertical() ? yres - 30 : WRAP_CONTENT, dc.GetBounds().centerX(), dc.GetBounds().centerY(), NONE, NONE, true)); root_->Add(box_); box_->SetBG(UI::Drawable(0xFF303030)); box_->SetHasDropShadow(true); View *title = new PopupHeader(title_); box_->Add(title); CreatePopupContents(box_); root_->SetDefaultFocusView(box_); if (ShowButtons() && !button1_.empty()) { // And the two buttons at the bottom. LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(200, WRAP_CONTENT)); buttonRow->SetSpacing(0); Margins buttonMargins(5, 5); // Adjust button order to the platform default. #if defined(_WIN32) defaultButton_ = buttonRow->Add(new Button(button1_, new LinearLayoutParams(1.0f, buttonMargins))); defaultButton_->OnClick.Handle(this, &PopupScreen::OnOK); if (!button2_.empty()) buttonRow->Add(new Button(button2_, new LinearLayoutParams(1.0f, buttonMargins)))->OnClick.Handle(this, &PopupScreen::OnCancel); #else if (!button2_.empty()) buttonRow->Add(new Button(button2_, new LinearLayoutParams(1.0f)))->OnClick.Handle(this, &PopupScreen::OnCancel); defaultButton_ = buttonRow->Add(new Button(button1_, new LinearLayoutParams(1.0f))); defaultButton_->OnClick.Handle(this, &PopupScreen::OnOK); #endif box_->Add(buttonRow); } } void MessagePopupScreen::CreatePopupContents(UI::ViewGroup *parent) { std::vector messageLines; SplitString(message_, '\n', messageLines); for (const auto& lineOfText : messageLines) parent->Add(new UI::TextView(lineOfText, ALIGN_LEFT | ALIGN_VCENTER, false)); } void MessagePopupScreen::OnCompleted(DialogResult result) { if (result == DR_OK) { if (callback_) callback_(true); } else { if (callback_) callback_(false); } } UI::EventReturn PopupScreen::OnOK(UI::EventParams &e) { OnCompleted(DR_OK); screenManager()->finishDialog(this, DR_OK); return UI::EVENT_DONE; } UI::EventReturn PopupScreen::OnCancel(UI::EventParams &e) { OnCompleted(DR_CANCEL); screenManager()->finishDialog(this, DR_CANCEL); return UI::EVENT_DONE; } void ListPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; listView_ = parent->Add(new ListView(&adaptor_, hidden_)); //, new LinearLayoutParams(1.0))); listView_->SetMaxHeight(screenManager()->getUIContext()->GetBounds().h - 140); listView_->OnChoice.Handle(this, &ListPopupScreen::OnListChoice); } UI::EventReturn ListPopupScreen::OnListChoice(UI::EventParams &e) { adaptor_.SetSelected(e.a); if (callback_) callback_(adaptor_.GetSelected()); screenManager()->finishDialog(this, DR_OK); OnCompleted(DR_OK); OnChoice.Dispatch(e); return UI::EVENT_DONE; } namespace UI { UI::EventReturn PopupMultiChoice::HandleClick(UI::EventParams &e) { restoreFocus_ = HasFocus(); I18NCategory *category = category_ ? GetI18NCategory(category_) : nullptr; std::vector choices; for (int i = 0; i < numChoices_; i++) { choices.push_back(category ? category->T(choices_[i]) : choices_[i]); } ListPopupScreen *popupScreen = new ListPopupScreen(text_, choices, *value_ - minVal_, std::bind(&PopupMultiChoice::ChoiceCallback, this, placeholder::_1)); popupScreen->SetHiddenChoices(hidden_); screenManager_->push(popupScreen); return UI::EVENT_DONE; } void PopupMultiChoice::Update(const InputState &input_state) { UpdateText(); } void PopupMultiChoice::UpdateText() { I18NCategory *category = GetI18NCategory(category_); // Clamp the value to be safe. if (*value_ < minVal_ || *value_ > minVal_ + numChoices_ - 1) { valueText_ = "(invalid choice)"; // Shouldn't happen. Should be no need to translate this. } else { valueText_ = category ? category->T(choices_[*value_ - minVal_]) : choices_[*value_ - minVal_]; } } void PopupMultiChoice::ChoiceCallback(int num) { if (num != -1) { *value_ = num + minVal_; UpdateText(); UI::EventParams e; e.v = this; e.a = num; OnChoice.Trigger(e); if (restoreFocus_) { SetFocusedView(this); } } } void PopupMultiChoice::Draw(UIContext &dc) { Style style = dc.theme->itemStyle; if (!IsEnabled()) { style = dc.theme->itemDisabledStyle; } Choice::Draw(dc); int paddingX = 12; dc.SetFontStyle(dc.theme->uiFont); dc.DrawText(valueText_.c_str(), bounds_.x2() - paddingX, bounds_.centerY(), style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); } PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1), screenManager_(screenManager), units_(units) { fmt_ = "%i"; OnClick.Handle(this, &PopupSliderChoice::HandleClick); } PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, int step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), screenManager_(screenManager), units_(units) { fmt_ = "%i"; OnClick.Handle(this, &PopupSliderChoice::HandleClick); } PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1.0f), screenManager_(screenManager), units_(units) { fmt_ = "%2.2f"; OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick); } PopupSliderChoiceFloat::PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, float step, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) : Choice(text, "", false, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), screenManager_(screenManager), units_(units) { fmt_ = "%2.2f"; OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick); } EventReturn PopupSliderChoice::HandleClick(EventParams &e) { restoreFocus_ = HasFocus(); SliderPopupScreen *popupScreen = new SliderPopupScreen(value_, minValue_, maxValue_, text_, step_, units_); popupScreen->OnChange.Handle(this, &PopupSliderChoice::HandleChange); screenManager_->push(popupScreen); return EVENT_DONE; } EventReturn PopupSliderChoice::HandleChange(EventParams &e) { e.v = this; OnChange.Trigger(e); if (restoreFocus_) { SetFocusedView(this); } return EVENT_DONE; } void PopupSliderChoice::Draw(UIContext &dc) { Style style = dc.theme->itemStyle; if (!IsEnabled()) { style = dc.theme->itemDisabledStyle; } Choice::Draw(dc); char temp[32]; if (zeroLabel_.size() && *value_ == 0) { strcpy(temp, zeroLabel_.c_str()); } else { sprintf(temp, fmt_, *value_); } dc.SetFontStyle(dc.theme->uiFont); dc.DrawText(temp, bounds_.x2() - 12, bounds_.centerY(), style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); } EventReturn PopupSliderChoiceFloat::HandleClick(EventParams &e) { restoreFocus_ = HasFocus(); SliderFloatPopupScreen *popupScreen = new SliderFloatPopupScreen(value_, minValue_, maxValue_, text_, step_, units_); popupScreen->OnChange.Handle(this, &PopupSliderChoiceFloat::HandleChange); screenManager_->push(popupScreen); return EVENT_DONE; } EventReturn PopupSliderChoiceFloat::HandleChange(EventParams &e) { e.v = this; OnChange.Trigger(e); if (restoreFocus_) { SetFocusedView(this); } return EVENT_DONE; } void PopupSliderChoiceFloat::Draw(UIContext &dc) { Style style = dc.theme->itemStyle; if (!IsEnabled()) { style = dc.theme->itemDisabledStyle; } Choice::Draw(dc); char temp[32]; if (zeroLabel_.size() && *value_ == 0.0f) { strcpy(temp, zeroLabel_.c_str()); } else { sprintf(temp, fmt_, *value_); } dc.SetFontStyle(dc.theme->uiFont); dc.DrawText(temp, bounds_.x2() - 12, bounds_.centerY(), style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); } EventReturn SliderPopupScreen::OnDecrease(EventParams ¶ms) { if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) { sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f); } sliderValue_ -= step_; slider_->Clamp(); changing_ = true; char temp[64]; sprintf(temp, "%d", sliderValue_); edit_->SetText(temp); changing_ = false; return EVENT_DONE; } EventReturn SliderPopupScreen::OnIncrease(EventParams ¶ms) { if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) { sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f); } sliderValue_ += step_; slider_->Clamp(); changing_ = true; char temp[64]; sprintf(temp, "%d", sliderValue_); edit_->SetText(temp); changing_ = false; return EVENT_DONE; } EventReturn SliderPopupScreen::OnSliderChange(EventParams ¶ms) { changing_ = true; char temp[64]; sprintf(temp, "%d", sliderValue_); edit_->SetText(temp); changing_ = false; return EVENT_DONE; } EventReturn SliderPopupScreen::OnTextChange(EventParams ¶ms) { if (!changing_) { sliderValue_ = atoi(edit_->GetText().c_str()); slider_->Clamp(); } return EVENT_DONE; } void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; sliderValue_ = *value_; LinearLayout *vert = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::Margins(10, 10)))); slider_ = new Slider(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 10))); slider_->OnChange.Handle(this, &SliderPopupScreen::OnSliderChange); vert->Add(slider_); LinearLayout *lin = vert->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(UI::Margins(10, 10)))); lin->Add(new Button(" - "))->OnClick.Handle(this, &SliderPopupScreen::OnDecrease); lin->Add(new Button(" + "))->OnClick.Handle(this, &SliderPopupScreen::OnIncrease); char temp[64]; sprintf(temp, "%d", sliderValue_); edit_ = new TextEdit(temp, "", new LinearLayoutParams(10.0f)); edit_->SetMaxLen(16); edit_->OnTextChange.Handle(this, &SliderPopupScreen::OnTextChange); changing_ = false; lin->Add(edit_); if (!units_.empty()) lin->Add(new TextView(units_, new LinearLayoutParams(10.0f))); if (IsFocusMovementEnabled()) UI::SetFocusedView(slider_); } void SliderFloatPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; sliderValue_ = *value_; LinearLayout *vert = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::Margins(10, 10)))); slider_ = new SliderFloat(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 10))); slider_->OnChange.Handle(this, &SliderFloatPopupScreen::OnSliderChange); vert->Add(slider_); LinearLayout *lin = vert->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(UI::Margins(10, 10)))); lin->Add(new Button(" - "))->OnClick.Handle(this, &SliderFloatPopupScreen::OnDecrease); lin->Add(new Button(" + "))->OnClick.Handle(this, &SliderFloatPopupScreen::OnIncrease); char temp[64]; sprintf(temp, "%0.3f", sliderValue_); edit_ = new TextEdit(temp, "", new LinearLayoutParams(10.0f)); edit_->SetMaxLen(16); edit_->OnTextChange.Handle(this, &SliderFloatPopupScreen::OnTextChange); changing_ = false; lin->Add(edit_); if (!units_.empty()) lin->Add(new TextView(units_, new LinearLayoutParams(10.0f))); // slider_ = parent->Add(new SliderFloat(&sliderValue_, minValue_, maxValue_, new LinearLayoutParams(UI::Margins(10, 5)))); if (IsFocusMovementEnabled()) UI::SetFocusedView(slider_); } EventReturn SliderFloatPopupScreen::OnDecrease(EventParams ¶ms) { if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) { sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f); } sliderValue_ -= step_; slider_->Clamp(); changing_ = true; char temp[64]; sprintf(temp, "%0.3f", sliderValue_); edit_->SetText(temp); changing_ = false; return EVENT_DONE; } EventReturn SliderFloatPopupScreen::OnIncrease(EventParams ¶ms) { if (sliderValue_ > minValue_ && sliderValue_ < maxValue_) { sliderValue_ = step_ * floor((sliderValue_ / step_) + 0.5f); } sliderValue_ += step_; slider_->Clamp(); changing_ = true; char temp[64]; sprintf(temp, "%0.3f", sliderValue_); edit_->SetText(temp); changing_ = false; return EVENT_DONE; } EventReturn SliderFloatPopupScreen::OnSliderChange(EventParams ¶ms) { changing_ = true; char temp[64]; sprintf(temp, "%0.3f", sliderValue_); edit_->SetText(temp); changing_ = false; return EVENT_DONE; } EventReturn SliderFloatPopupScreen::OnTextChange(EventParams ¶ms) { if (!changing_) { sliderValue_ = atof(edit_->GetText().c_str()); slider_->Clamp(); } return EVENT_DONE; } void SliderPopupScreen::OnCompleted(DialogResult result) { if (result == DR_OK) { *value_ = sliderValue_; EventParams e; e.v = 0; e.a = *value_; OnChange.Trigger(e); } } void SliderFloatPopupScreen::OnCompleted(DialogResult result) { if (result == DR_OK) { *value_ = sliderValue_; EventParams e; e.v = 0; e.a = (int)*value_; e.f = *value_; OnChange.Trigger(e); } } PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string &title, const std::string &placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams) : Choice(title, "", false, layoutParams), value_(value), placeHolder_(placeholder), screenManager_(screenManager), maxLen_(maxLen) { OnClick.Handle(this, &PopupTextInputChoice::HandleClick); } EventReturn PopupTextInputChoice::HandleClick(EventParams &e) { restoreFocus_ = HasFocus(); TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, text_, maxLen_); popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange); screenManager_->push(popupScreen); return EVENT_DONE; } void PopupTextInputChoice::Draw(UIContext &dc) { Style style = dc.theme->itemStyle; if (!IsEnabled()) { style = dc.theme->itemDisabledStyle; } Choice::Draw(dc); dc.SetFontStyle(dc.theme->uiFont); dc.DrawText(value_->c_str(), bounds_.x2() - 12, bounds_.centerY(), style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); } EventReturn PopupTextInputChoice::HandleChange(EventParams &e) { e.v = this; OnChange.Trigger(e); if (restoreFocus_) { SetFocusedView(this); } return EVENT_DONE; } void TextEditPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; textEditValue_ = *value_; LinearLayout *lin = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams((UI::Size)300, WRAP_CONTENT))); edit_ = new TextEdit(textEditValue_, placeholder_, new LinearLayoutParams(1.0f)); edit_->SetMaxLen(maxLen_); lin->Add(edit_); if (IsFocusMovementEnabled()) UI::SetFocusedView(edit_); } void TextEditPopupScreen::OnCompleted(DialogResult result) { if (result == DR_OK) { *value_ = edit_->GetText(); EventParams e; e.v = edit_; OnChange.Trigger(e); } } void ChoiceWithValueDisplay::Draw(UIContext &dc) { Style style = dc.theme->itemStyle; std::ostringstream valueText; Choice::Draw(dc); dc.SetFontStyle(dc.theme->uiFont); I18NCategory *category = GetI18NCategory(category_); if (sValue_ != nullptr) { if (category) valueText << category->T(*sValue_); else valueText << *sValue_; } else if (iValue_ != nullptr) { valueText << *iValue_; } dc.DrawText(valueText.str().c_str(), bounds_.x2() - 12, bounds_.centerY(), style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); } } // namespace UI