From c27689910ed6e6d168c79f0e4bf6409fb23b9b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Jan 2023 10:36:00 +0100 Subject: [PATCH] Break out ScrollView from ViewGroup.h, and PopupScreens from UIScreen.h --- CMakeLists.txt | 4 + Common/Common.vcxproj | 4 + Common/Common.vcxproj.filters | 12 + Common/UI/PopupScreens.cpp | 584 ++++++++++++++++++++++++ Common/UI/PopupScreens.h | 388 ++++++++++++++++ Common/UI/Screen.cpp | 1 + Common/UI/ScrollView.cpp | 502 ++++++++++++++++++++ Common/UI/ScrollView.h | 133 ++++++ Common/UI/UIScreen.cpp | 582 ----------------------- Common/UI/UIScreen.h | 387 ---------------- Common/UI/ViewGroup.cpp | 496 +------------------- Common/UI/ViewGroup.h | 127 +----- UI/ChatScreen.cpp | 1 + UI/ControlMappingScreen.cpp | 4 +- UI/DevScreens.h | 2 +- UI/DisplayLayoutScreen.h | 2 +- UI/MiscScreens.h | 5 +- UWP/CommonUWP/CommonUWP.vcxproj | 4 + UWP/CommonUWP/CommonUWP.vcxproj.filters | 12 + android/jni/Android.mk | 2 + libretro/Makefile.common | 2 + 21 files changed, 1658 insertions(+), 1596 deletions(-) create mode 100644 Common/UI/PopupScreens.cpp create mode 100644 Common/UI/PopupScreens.h create mode 100644 Common/UI/ScrollView.cpp create mode 100644 Common/UI/ScrollView.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 26862ebc01..19d8e8ca45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -735,6 +735,10 @@ add_library(Common STATIC Common/UI/View.h Common/UI/ViewGroup.cpp Common/UI/ViewGroup.h + Common/UI/ScrollView.cpp + Common/UI/ScrollView.h + Common/UI/PopupScreens.cpp + Common/UI/PopupScreens.h Common/BitScan.h Common/BitSet.h Common/Buffer.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index d51b49d585..cf3502c526 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -554,8 +554,10 @@ + + @@ -996,8 +998,10 @@ + + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 18cbbd9985..6dfa552f1b 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -458,6 +458,12 @@ GPU\Vulkan + + UI + + + UI + @@ -866,6 +872,12 @@ GPU\Vulkan + + UI + + + UI + diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp new file mode 100644 index 0000000000..bcdc16606a --- /dev/null +++ b/Common/UI/PopupScreens.cpp @@ -0,0 +1,584 @@ +#include + +#include "Common/UI/PopupScreens.h" +#include "Common/UI/ViewGroup.h" +#include "Common/UI/Context.h" +#include "Common/UI/Root.h" +#include "Common/StringUtils.h" +#include "Common/Data/Text/I18n.h" + +namespace UI { + +void MessagePopupScreen::CreatePopupContents(UI::ViewGroup *parent) { + using namespace UI; + UIContext &dc = *screenManager()->getUIContext(); + + std::vector messageLines; + SplitString(message_, '\n', messageLines); + for (const auto &lineOfText : messageLines) + parent->Add(new UI::TextView(lineOfText, ALIGN_LEFT | ALIGN_VCENTER, false))->SetTextColor(dc.theme->popupStyle.fgColor); +} + +void MessagePopupScreen::OnCompleted(DialogResult result) { + if (result == DR_OK) { + if (callback_) + callback_(true); + } else { + if (callback_) + callback_(false); + } +} + +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()); + TriggerFinish(DR_OK); + OnChoice.Dispatch(e); + return UI::EVENT_DONE; +} + +PopupContextMenuScreen::PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView) + : PopupScreen("", "", ""), items_(items), itemCount_(itemCount), category_(category), sourceView_(sourceView) +{ + enabled_.resize(itemCount, true); + SetPopupOrigin(sourceView); +} + +void PopupContextMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { + for (size_t i = 0; i < itemCount_; i++) { + if (items_[i].imageID) { + Choice *choice = new Choice(category_->T(items_[i].text), ImageID(items_[i].imageID)); + parent->Add(choice); + if (enabled_[i]) { + choice->OnClick.Add([=](EventParams &p) { + TriggerFinish(DR_OK); + p.a = (uint32_t)i; + OnChoice.Dispatch(p); + return EVENT_DONE; + }); + } else { + choice->SetEnabled(false); + } + } + } + + // Hacky: Override the position to look like a popup menu. + AnchorLayoutParams *ap = (AnchorLayoutParams *)parent->GetLayoutParams(); + ap->center = false; + ap->left = sourceView_->GetBounds().x; + ap->top = sourceView_->GetBounds().y2(); +} + +std::string ChopTitle(const std::string &title) { + size_t pos = title.find('\n'); + if (pos != title.npos) { + return title.substr(0, pos); + } + return title; +} + +UI::EventReturn PopupMultiChoice::HandleClick(UI::EventParams &e) { + restoreFocus_ = HasFocus(); + + auto 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(ChopTitle(text_), choices, *value_ - minVal_, + std::bind(&PopupMultiChoice::ChoiceCallback, this, std::placeholders::_1)); + popupScreen->SetHiddenChoices(hidden_); + if (e.v) + popupScreen->SetPopupOrigin(e.v); + screenManager_->push(popupScreen); + return UI::EVENT_DONE; +} + +void PopupMultiChoice::Update() { + UpdateText(); +} + +void PopupMultiChoice::UpdateText() { + if (!choices_) + return; + auto 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); + } + PostChoiceCallback(num); + } +} + +std::string PopupMultiChoice::ValueText() const { + return valueText_; +} + +PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) + : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1), units_(units), screenManager_(screenManager) { + 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) + : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) { + 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) + : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1.0f), units_(units), screenManager_(screenManager) { + 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) + : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) { + fmt_ = "%2.2f"; + OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick); +} + +EventReturn PopupSliderChoice::HandleClick(EventParams &e) { + restoreFocus_ = HasFocus(); + + SliderPopupScreen *popupScreen = new SliderPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_); + if (!negativeLabel_.empty()) + popupScreen->SetNegativeDisable(negativeLabel_); + popupScreen->OnChange.Handle(this, &PopupSliderChoice::HandleChange); + if (e.v) + popupScreen->SetPopupOrigin(e.v); + screenManager_->push(popupScreen); + return EVENT_DONE; +} + +EventReturn PopupSliderChoice::HandleChange(EventParams &e) { + e.v = this; + OnChange.Trigger(e); + + if (restoreFocus_) { + SetFocusedView(this); + } + return EVENT_DONE; +} + +std::string PopupSliderChoice::ValueText() const { + // Always good to have space for Unicode. + char temp[256]; + if (zeroLabel_.size() && *value_ == 0) { + strcpy(temp, zeroLabel_.c_str()); + } else if (negativeLabel_.size() && *value_ < 0) { + strcpy(temp, negativeLabel_.c_str()); + } else { + sprintf(temp, fmt_, *value_); + } + + return temp; +} + +EventReturn PopupSliderChoiceFloat::HandleClick(EventParams &e) { + restoreFocus_ = HasFocus(); + + SliderFloatPopupScreen *popupScreen = new SliderFloatPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_, liveUpdate_); + popupScreen->OnChange.Handle(this, &PopupSliderChoiceFloat::HandleChange); + popupScreen->SetHasDropShadow(hasDropShadow_); + if (e.v) + popupScreen->SetPopupOrigin(e.v); + screenManager_->push(popupScreen); + return EVENT_DONE; +} + +EventReturn PopupSliderChoiceFloat::HandleChange(EventParams &e) { + e.v = this; + OnChange.Trigger(e); + + if (restoreFocus_) { + SetFocusedView(this); + } + return EVENT_DONE; +} + +std::string PopupSliderChoiceFloat::ValueText() const { + char temp[256]; + if (zeroLabel_.size() && *value_ == 0.0f) { + strcpy(temp, zeroLabel_.c_str()); + } else { + sprintf(temp, fmt_, *value_); + } + + return temp; +} + +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; + disabled_ = 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; + disabled_ = false; + return EVENT_DONE; +} + +EventReturn SliderPopupScreen::OnSliderChange(EventParams ¶ms) { + changing_ = true; + char temp[64]; + sprintf(temp, "%d", sliderValue_); + edit_->SetText(temp); + changing_ = false; + disabled_ = false; + return EVENT_DONE; +} + +EventReturn SliderPopupScreen::OnTextChange(EventParams ¶ms) { + if (!changing_) { + sliderValue_ = atoi(edit_->GetText().c_str()); + disabled_ = false; + slider_->Clamp(); + } + return EVENT_DONE; +} + +void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { + using namespace UI; + UIContext &dc = *screenManager()->getUIContext(); + + sliderValue_ = *value_; + if (disabled_ && sliderValue_ < 0) + sliderValue_ = 0; + 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, Title(), "", new LinearLayoutParams(10.0f)); + edit_->SetMaxLen(16); + edit_->SetTextColor(dc.theme->itemStyle.fgColor); + edit_->SetTextAlign(FLAG_DYNAMIC_ASCII); + edit_->OnTextChange.Handle(this, &SliderPopupScreen::OnTextChange); + changing_ = false; + lin->Add(edit_); + + if (!units_.empty()) + lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->itemStyle.fgColor); + + if (!negativeLabel_.empty()) + vert->Add(new CheckBox(&disabled_, negativeLabel_)); + + if (IsFocusMovementEnabled()) + UI::SetFocusedView(slider_); +} + +void SliderFloatPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { + using namespace UI; + UIContext &dc = *screenManager()->getUIContext(); + + 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, Title(), "", new LinearLayoutParams(10.0f)); + edit_->SetMaxLen(16); + edit_->SetTextColor(dc.theme->itemStyle.fgColor); + edit_->SetTextAlign(FLAG_DYNAMIC_ASCII); + edit_->OnTextChange.Handle(this, &SliderFloatPopupScreen::OnTextChange); + changing_ = false; + lin->Add(edit_); + if (!units_.empty()) + lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->itemStyle.fgColor); + + // 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; + if (liveUpdate_) { + *value_ = sliderValue_; + } + 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; + if (liveUpdate_) { + *value_ = sliderValue_; + } + return EVENT_DONE; +} + +EventReturn SliderFloatPopupScreen::OnSliderChange(EventParams ¶ms) { + changing_ = true; + char temp[64]; + sprintf(temp, "%0.3f", sliderValue_); + edit_->SetText(temp); + changing_ = false; + if (liveUpdate_) { + *value_ = sliderValue_; + } + return EVENT_DONE; +} + +EventReturn SliderFloatPopupScreen::OnTextChange(EventParams ¶ms) { + if (!changing_) { + sliderValue_ = atof(edit_->GetText().c_str()); + slider_->Clamp(); + if (liveUpdate_) { + *value_ = sliderValue_; + } + } + return EVENT_DONE; +} + +void SliderPopupScreen::OnCompleted(DialogResult result) { + if (result == DR_OK) { + *value_ = disabled_ ? -1 : sliderValue_; + EventParams e{}; + e.v = nullptr; + e.a = *value_; + OnChange.Trigger(e); + } +} + +void SliderFloatPopupScreen::OnCompleted(DialogResult result) { + if (result == DR_OK) { + *value_ = sliderValue_; + EventParams e{}; + e.v = nullptr; + e.a = (int)*value_; + e.f = *value_; + OnChange.Trigger(e); + } else { + *value_ = originalValue_; + } +} + +PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string &title, const std::string &placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams) + : AbstractChoiceWithValueDisplay(title, layoutParams), screenManager_(screenManager), value_(value), placeHolder_(placeholder), maxLen_(maxLen) { + OnClick.Handle(this, &PopupTextInputChoice::HandleClick); +} + +EventReturn PopupTextInputChoice::HandleClick(EventParams &e) { + restoreFocus_ = HasFocus(); + + TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, ChopTitle(text_), maxLen_); + popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange); + if (e.v) + popupScreen->SetPopupOrigin(e.v); + screenManager_->push(popupScreen); + return EVENT_DONE; +} + +std::string PopupTextInputChoice::ValueText() const { + return *value_; +} + +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; + UIContext &dc = *screenManager()->getUIContext(); + + textEditValue_ = *value_; + LinearLayout *lin = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams((UI::Size)300, WRAP_CONTENT))); + edit_ = new TextEdit(textEditValue_, Title(), placeholder_, new LinearLayoutParams(1.0f)); + edit_->SetMaxLen(maxLen_); + edit_->SetTextColor(dc.theme->popupStyle.fgColor); + lin->Add(edit_); + + UI::SetFocusedView(edit_); +} + +void TextEditPopupScreen::OnCompleted(DialogResult result) { + if (result == DR_OK) { + *value_ = StripSpaces(edit_->GetText()); + EventParams e{}; + e.v = edit_; + OnChange.Trigger(e); + } +} + +void AbstractChoiceWithValueDisplay::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const { + const std::string valueText = ValueText(); + int paddingX = 12; + // Assume we want at least 20% of the size for the label, at a minimum. + float availWidth = (horiz.size - paddingX * 2) * (text_.empty() ? 1.0f : 0.8f); + if (availWidth < 0) { + availWidth = 65535.0f; + } + float scale = CalculateValueScale(dc, valueText, availWidth); + Bounds availBounds(0, 0, availWidth, vert.size); + + float valueW, valueH; + dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &valueW, &valueH, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); + valueW += paddingX; + + // Give the choice itself less space to grow in, so it shrinks if needed. + // MeasureSpec horizLabel = horiz; + // horizLabel.size -= valueW; + Choice::GetContentDimensionsBySpec(dc, horiz, vert, w, h); + + w += valueW; + // Fill out anyway if there's space. + if (horiz.type == AT_MOST && w < horiz.size) { + w = horiz.size; + } + h = std::max(h, valueH); +} + +void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) { + Style style = dc.theme->itemStyle; + if (!IsEnabled()) { + style = dc.theme->itemDisabledStyle; + } + if (HasFocus()) { + style = dc.theme->itemFocusedStyle; + } + if (down_) { + style = dc.theme->itemDownStyle; + } + int paddingX = 12; + dc.SetFontStyle(dc.theme->uiFont); + + const std::string valueText = ValueText(); + + // If there is a label, assume we want at least 20% of the size for it, at a minimum. + + if (!text_.empty()) { + float availWidth = (bounds_.w - paddingX * 2) * 0.8f; + float scale = CalculateValueScale(dc, valueText, availWidth); + + float w, h; + Bounds availBounds(0, 0, availWidth, bounds_.h); + dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &w, &h, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); + textPadding_.right = w + paddingX; + + Choice::Draw(dc); + dc.SetFontScale(scale, scale); + Bounds valueBounds(bounds_.x2() - textPadding_.right, bounds_.y, w, bounds_.h); + dc.DrawTextRect(valueText.c_str(), valueBounds, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); + dc.SetFontScale(1.0f, 1.0f); + } else { + Choice::Draw(dc); + float scale = CalculateValueScale(dc, valueText, bounds_.w); + dc.SetFontScale(scale, scale); + dc.DrawTextRect(valueText.c_str(), bounds_.Expand(-paddingX, 0.0f), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER | FLAG_WRAP_TEXT); + dc.SetFontScale(1.0f, 1.0f); + } +} + +float AbstractChoiceWithValueDisplay::CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const { + float actualWidth, actualHeight; + Bounds availBounds(0, 0, availWidth, bounds_.h); + dc.MeasureTextRect(dc.theme->uiFont, 1.0f, 1.0f, valueText.c_str(), (int)valueText.size(), availBounds, &actualWidth, &actualHeight); + if (actualWidth > availWidth) { + return std::max(0.8f, availWidth / actualWidth); + } + return 1.0f; +} + +std::string ChoiceWithValueDisplay::ValueText() const { + auto category = GetI18NCategory(category_); + std::ostringstream valueText; + if (translateCallback_ && sValue_) { + valueText << translateCallback_(sValue_->c_str()); + } else if (sValue_ != nullptr) { + if (category) + valueText << category->T(*sValue_); + else + valueText << *sValue_; + } else if (iValue_ != nullptr) { + valueText << *iValue_; + } + + return valueText.str(); +} + +} // namespace diff --git a/Common/UI/PopupScreens.h b/Common/UI/PopupScreens.h new file mode 100644 index 0000000000..403b3955bf --- /dev/null +++ b/Common/UI/PopupScreens.h @@ -0,0 +1,388 @@ +#pragma once + +#include "Common/UI/UIScreen.h" +#include "Common/UI/UI.h" +#include "Common/UI/View.h" +#include "Common/UI/ScrollView.h" + +namespace UI { + +class ListPopupScreen : public PopupScreen { +public: + ListPopupScreen(std::string title) : PopupScreen(title) {} + ListPopupScreen(std::string title, const std::vector &items, int selected, std::function callback, bool showButtons = false) + : PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), callback_(callback), showButtons_(showButtons) { + } + ListPopupScreen(std::string title, const std::vector &items, int selected, bool showButtons = false) + : PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), showButtons_(showButtons) { + } + + int GetChoice() const { + return listView_->GetSelected(); + } + std::string GetChoiceString() const { + return adaptor_.GetTitle(listView_->GetSelected()); + } + void SetHiddenChoices(std::set hidden) { + hidden_ = hidden; + } + const char *tag() const override { return "listpopup"; } + + UI::Event OnChoice; + +protected: + bool FillVertical() const override { return false; } + bool ShowButtons() const override { return showButtons_; } + void CreatePopupContents(UI::ViewGroup *parent) override; + UI::StringVectorListAdaptor adaptor_; + UI::ListView *listView_ = nullptr; + +private: + UI::EventReturn OnListChoice(UI::EventParams &e); + + std::function callback_; + bool showButtons_ = false; + std::set hidden_; +}; + +class MessagePopupScreen : public PopupScreen { +public: + MessagePopupScreen(std::string title, std::string message, std::string button1, std::string button2, std::function callback) + : PopupScreen(title, button1, button2), message_(message), callback_(callback) {} + UI::Event OnChoice; + +protected: + bool FillVertical() const override { return false; } + bool ShowButtons() const override { return true; } + void CreatePopupContents(UI::ViewGroup *parent) override; + +private: + void OnCompleted(DialogResult result) override; + std::string message_; + std::function callback_; +}; + +class SliderPopupScreen : public PopupScreen { +public: + SliderPopupScreen(int *value, int minValue, int maxValue, const std::string &title, int step = 1, const std::string &units = "") + : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step) {} + void CreatePopupContents(ViewGroup *parent) override; + + void SetNegativeDisable(const std::string &str) { + negativeLabel_ = str; + disabled_ = *value_ < 0; + } + + const char *tag() const override { return "SliderPopup"; } + + Event OnChange; + +private: + EventReturn OnDecrease(EventParams ¶ms); + EventReturn OnIncrease(EventParams ¶ms); + EventReturn OnTextChange(EventParams ¶ms); + EventReturn OnSliderChange(EventParams ¶ms); + void OnCompleted(DialogResult result) override; + Slider *slider_ = nullptr; + UI::TextEdit *edit_ = nullptr; + std::string units_; + std::string negativeLabel_; + int *value_; + int sliderValue_ = 0; + int minValue_; + int maxValue_; + int step_; + bool changing_ = false; + bool disabled_ = false; +}; + +class SliderFloatPopupScreen : public PopupScreen { +public: + SliderFloatPopupScreen(float *value, float minValue, float maxValue, const std::string &title, float step = 1.0f, const std::string &units = "", bool liveUpdate = false) + : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), originalValue_(*value), minValue_(minValue), maxValue_(maxValue), step_(step), changing_(false), liveUpdate_(liveUpdate) {} + void CreatePopupContents(UI::ViewGroup *parent) override; + + const char *tag() const override { return "SliderFloatPopup"; } + + Event OnChange; + +private: + EventReturn OnIncrease(EventParams ¶ms); + EventReturn OnDecrease(EventParams ¶ms); + EventReturn OnTextChange(EventParams ¶ms); + EventReturn OnSliderChange(EventParams ¶ms); + void OnCompleted(DialogResult result) override; + UI::SliderFloat *slider_ = nullptr; + UI::TextEdit *edit_ = nullptr; + std::string units_ = nullptr; + float sliderValue_; + float originalValue_; + float *value_; + float minValue_; + float maxValue_; + float step_; + bool changing_; + bool liveUpdate_; +}; + +class TextEditPopupScreen : public PopupScreen { +public: + TextEditPopupScreen(std::string *value, const std::string &placeholder, const std::string &title, int maxLen) + : PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {} + void CreatePopupContents(ViewGroup *parent) override; + + const char *tag() const override { return "TextEditPopup"; } + + Event OnChange; + +private: + void OnCompleted(DialogResult result) override; + TextEdit *edit_ = nullptr; + std::string *value_; + std::string textEditValue_; + std::string placeholder_; + int maxLen_; +}; + +struct ContextMenuItem { + const char *text; + const char *imageID; +}; + +// Once a selection has been made, +class PopupContextMenuScreen : public PopupScreen { +public: + PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView); + void CreatePopupContents(ViewGroup *parent) override; + + const char *tag() const override { return "ContextMenuPopup"; } + + void SetEnabled(size_t index, bool enabled) { + enabled_[index] = enabled; + } + + UI::Event OnChoice; + +protected: + bool HasTitleBar() const override { return false; } + +private: + const ContextMenuItem *items_; + size_t itemCount_; + I18NCategory *category_; + UI::View *sourceView_; + std::vector enabled_; +}; + +class AbstractChoiceWithValueDisplay : public UI::Choice { +public: + AbstractChoiceWithValueDisplay(const std::string &text, LayoutParams *layoutParams = nullptr) + : Choice(text, layoutParams) { + } + + void Draw(UIContext &dc) override; + void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; + +protected: + virtual std::string ValueText() const = 0; + + float CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const; +}; + +// Reads and writes value to determine the current selection. +class PopupMultiChoice : public AbstractChoiceWithValueDisplay { +public: + PopupMultiChoice(int *value, const std::string &text, const char **choices, int minVal, int numChoices, + const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr) + : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), choices_(choices), minVal_(minVal), numChoices_(numChoices), + category_(category), screenManager_(screenManager) { + if (*value >= numChoices + minVal) + *value = numChoices + minVal - 1; + if (*value < minVal) + *value = minVal; + OnClick.Handle(this, &PopupMultiChoice::HandleClick); + UpdateText(); + } + + void Update() override; + + void HideChoice(int c) { + hidden_.insert(c); + } + + UI::Event OnChoice; + +protected: + std::string ValueText() const override; + + int *value_; + const char **choices_; + int minVal_; + int numChoices_; + void UpdateText(); + +private: + UI::EventReturn HandleClick(UI::EventParams &e); + + void ChoiceCallback(int num); + virtual void PostChoiceCallback(int num) {} + + const char *category_; + ScreenManager *screenManager_; + std::string valueText_; + bool restoreFocus_ = false; + std::set hidden_; +}; + +// Allows passing in a dynamic vector of strings. Saves the string. +class PopupMultiChoiceDynamic : public PopupMultiChoice { +public: + PopupMultiChoiceDynamic(std::string *value, const std::string &text, std::vector choices, + const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr) + : UI::PopupMultiChoice(&valueInt_, text, nullptr, 0, (int)choices.size(), category, screenManager, layoutParams), + valueStr_(value) { + choices_ = new const char *[numChoices_]; + valueInt_ = 0; + for (int i = 0; i < numChoices_; i++) { + choices_[i] = new char[choices[i].size() + 1]; + memcpy((char *)choices_[i], choices[i].c_str(), choices[i].size() + 1); + if (*value == choices_[i]) + valueInt_ = i; + } + value_ = &valueInt_; + UpdateText(); + } + ~PopupMultiChoiceDynamic() { + for (int i = 0; i < numChoices_; i++) { + delete[] choices_[i]; + } + delete[] choices_; + } + +protected: + void PostChoiceCallback(int num) override { + *valueStr_ = choices_[num]; + } + +private: + int valueInt_; + std::string *valueStr_; +}; + +class PopupSliderChoice : public AbstractChoiceWithValueDisplay { +public: + PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); + PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, int step, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); + + void SetFormat(const char *fmt) { + fmt_ = fmt; + } + void SetZeroLabel(const std::string &str) { + zeroLabel_ = str; + } + void SetNegativeDisable(const std::string &str) { + negativeLabel_ = str; + } + + Event OnChange; + +protected: + std::string ValueText() const override; + +private: + EventReturn HandleClick(EventParams &e); + EventReturn HandleChange(EventParams &e); + + int *value_; + int minValue_; + int maxValue_; + int step_; + const char *fmt_; + std::string zeroLabel_; + std::string negativeLabel_; + std::string units_; + ScreenManager *screenManager_; + bool restoreFocus_ = false; +}; + +class PopupSliderChoiceFloat : public AbstractChoiceWithValueDisplay { +public: + PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); + PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, float step, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); + + void SetFormat(const char *fmt) { + fmt_ = fmt; + } + void SetZeroLabel(const std::string &str) { + zeroLabel_ = str; + } + void SetLiveUpdate(bool update) { + liveUpdate_ = update; + } + void SetHasDropShadow(bool has) { + hasDropShadow_ = has; + } + + Event OnChange; + +protected: + std::string ValueText() const override; + +private: + EventReturn HandleClick(EventParams &e); + EventReturn HandleChange(EventParams &e); + float *value_; + float minValue_; + float maxValue_; + float step_; + const char *fmt_; + std::string zeroLabel_; + std::string units_; + ScreenManager *screenManager_; + bool restoreFocus_ = false; + bool liveUpdate_ = false; + bool hasDropShadow_ = true; +}; + +class PopupTextInputChoice : public AbstractChoiceWithValueDisplay { +public: + PopupTextInputChoice(std::string *value, const std::string &title, const std::string &placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams = 0); + + Event OnChange; + +protected: + std::string ValueText() const override; + +private: + EventReturn HandleClick(EventParams &e); + EventReturn HandleChange(EventParams &e); + ScreenManager *screenManager_; + std::string *value_; + std::string placeHolder_; + std::string defaultText_; + int maxLen_; + bool restoreFocus_; +}; + +class ChoiceWithValueDisplay : public AbstractChoiceWithValueDisplay { +public: + ChoiceWithValueDisplay(int *value, const std::string &text, LayoutParams *layoutParams = 0) + : AbstractChoiceWithValueDisplay(text, layoutParams), iValue_(value) {} + + ChoiceWithValueDisplay(std::string *value, const std::string &text, const char *category, LayoutParams *layoutParams = 0) + : AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value), category_(category) {} + + ChoiceWithValueDisplay(std::string *value, const std::string &text, std::string(*translateCallback)(const char *value), LayoutParams *layoutParams = 0) + : AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value), translateCallback_(translateCallback) { + } + +private: + std::string ValueText() const override; + + std::string *sValue_ = nullptr; + int *iValue_ = nullptr; + const char *category_ = nullptr; + std::string(*translateCallback_)(const char *value) = nullptr; +}; + +} // namespace UI diff --git a/Common/UI/Screen.cpp b/Common/UI/Screen.cpp index 866959ebe7..d06a26cd13 100644 --- a/Common/UI/Screen.cpp +++ b/Common/UI/Screen.cpp @@ -2,6 +2,7 @@ #include "Common/Input/InputState.h" #include "Common/UI/Root.h" #include "Common/UI/Screen.h" +#include "Common/UI/ScrollView.h" #include "Common/UI/UI.h" #include "Common/UI/View.h" #include "Common/UI/ViewGroup.h" diff --git a/Common/UI/ScrollView.cpp b/Common/UI/ScrollView.cpp new file mode 100644 index 0000000000..0158839e82 --- /dev/null +++ b/Common/UI/ScrollView.cpp @@ -0,0 +1,502 @@ +#include "Common/UI/Context.h" +#include "Common/UI/ScrollView.h" +#include "Common/Data/Text/I18n.h" + +namespace UI { + +float ScrollView::lastScrollPosX = 0; +float ScrollView::lastScrollPosY = 0; + +ScrollView::~ScrollView() { + lastScrollPosX = 0; + lastScrollPosY = 0; +} + +void ScrollView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) { + // Respect margins + Margins margins; + if (views_.size()) { + const LinearLayoutParams *linLayoutParams = views_[0]->GetLayoutParams()->As(); + if (linLayoutParams) { + margins = linLayoutParams->margins; + } + } + + // The scroll view itself simply obeys its parent - but also tries to fit the child if possible. + MeasureBySpec(layoutParams_->width, horiz.size, horiz, &measuredWidth_); + MeasureBySpec(layoutParams_->height, vert.size, vert, &measuredHeight_); + + if (views_.size()) { + if (orientation_ == ORIENT_HORIZONTAL) { + MeasureSpec v = MeasureSpec(AT_MOST, measuredHeight_ - margins.vert()); + if (measuredHeight_ == 0.0f && (vert.type == UNSPECIFIED || layoutParams_->height == WRAP_CONTENT)) { + v.type = UNSPECIFIED; + } + views_[0]->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), v); + MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_); + if (layoutParams_->width == WRAP_CONTENT) + MeasureBySpec(layoutParams_->width, views_[0]->GetMeasuredWidth(), horiz, &measuredWidth_); + } else { + MeasureSpec h = MeasureSpec(AT_MOST, measuredWidth_ - margins.horiz()); + if (measuredWidth_ == 0.0f && (horiz.type == UNSPECIFIED || layoutParams_->width == WRAP_CONTENT)) { + h.type = UNSPECIFIED; + } + views_[0]->Measure(dc, h, MeasureSpec(UNSPECIFIED, measuredHeight_)); + MeasureBySpec(layoutParams_->width, views_[0]->GetMeasuredWidth(), horiz, &measuredWidth_); + if (layoutParams_->height == WRAP_CONTENT) + MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_); + } + if (orientation_ == ORIENT_VERTICAL && vert.type != EXACTLY) { + float bestHeight = std::max(views_[0]->GetMeasuredHeight(), views_[0]->GetBounds().h); + if (vert.type == AT_MOST) + bestHeight = std::min(bestHeight, vert.size); + + if (measuredHeight_ < bestHeight && layoutParams_->height < 0.0f) { + measuredHeight_ = bestHeight; + } + } + } +} + +void ScrollView::Layout() { + if (!views_.size()) + return; + Bounds scrolled; + + // Respect margins + Margins margins; + const LinearLayoutParams *linLayoutParams = views_[0]->GetLayoutParams()->As(); + if (linLayoutParams) { + margins = linLayoutParams->margins; + } + + scrolled.w = views_[0]->GetMeasuredWidth() - margins.horiz(); + scrolled.h = views_[0]->GetMeasuredHeight() - margins.vert(); + + layoutScrollPos_ = ClampedScrollPos(scrollPos_); + + switch (orientation_) { + case ORIENT_HORIZONTAL: + if (scrolled.w != lastViewSize_) { + if (rememberPos_) + scrollPos_ = *rememberPos_; + lastViewSize_ = scrolled.w; + } + scrolled.x = bounds_.x - layoutScrollPos_; + scrolled.y = bounds_.y + margins.top; + break; + case ORIENT_VERTICAL: + if (scrolled.h != lastViewSize_) { + if (rememberPos_) + scrollPos_ = *rememberPos_; + lastViewSize_ = scrolled.h; + } + scrolled.x = bounds_.x + margins.left; + scrolled.y = bounds_.y - layoutScrollPos_; + break; + } + + views_[0]->SetBounds(scrolled); + views_[0]->Layout(); +} + +bool ScrollView::Key(const KeyInput &input) { + if (visibility_ != V_VISIBLE) + return ViewGroup::Key(input); + + float scrollSpeed = 250; + switch (input.deviceId) { + case DEVICE_ID_XR_CONTROLLER_LEFT: + case DEVICE_ID_XR_CONTROLLER_RIGHT: + scrollSpeed = 50; + break; + } + + if (input.flags & KEY_DOWN) { + if ((input.keyCode == NKCODE_EXT_MOUSEWHEEL_UP || input.keyCode == NKCODE_EXT_MOUSEWHEEL_DOWN) && + (input.flags & KEY_HASWHEELDELTA)) { + scrollSpeed = (float)(short)(input.flags >> 16) * 1.25f; // Fudge factor + } + + switch (input.keyCode) { + case NKCODE_EXT_MOUSEWHEEL_UP: + ScrollRelative(-scrollSpeed); + break; + case NKCODE_EXT_MOUSEWHEEL_DOWN: + ScrollRelative(scrollSpeed); + break; + } + } + return ViewGroup::Key(input); +} + +const float friction = 0.92f; +const float stop_threshold = 0.1f; + +bool ScrollView::Touch(const TouchInput &input) { + if ((input.flags & TOUCH_DOWN) && scrollTouchId_ == -1) { + scrollStart_ = scrollPos_; + inertia_ = 0.0f; + scrollTouchId_ = input.id; + } + + Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL; + + if ((input.flags & TOUCH_UP) && input.id == scrollTouchId_) { + float info[4]; + if (gesture_.GetGestureInfo(gesture, input.id, info)) { + inertia_ = info[1]; + } + scrollTouchId_ = -1; + } + + TouchInput input2; + if (CanScroll()) { + input2 = gesture_.Update(input, bounds_); + float info[4]; + if (input.id == scrollTouchId_ && gesture_.GetGestureInfo(gesture, input.id, info) && !(input.flags & TOUCH_DOWN)) { + float pos = scrollStart_ - info[0]; + scrollPos_ = pos; + scrollTarget_ = pos; + scrollToTarget_ = false; + } + } else { + input2 = input; + scrollTarget_ = scrollPos_; + scrollToTarget_ = false; + } + + if (!(input.flags & TOUCH_DOWN) || bounds_.Contains(input.x, input.y)) { + return ViewGroup::Touch(input2); + } else { + return false; + } +} + +void ScrollView::Draw(UIContext &dc) { + if (!views_.size()) { + ViewGroup::Draw(dc); + return; + } + + dc.PushScissor(bounds_); + dc.FillRect(bg_, bounds_); + + // For debugging layout issues, this can be useful. + // dc.FillRect(Drawable(0x60FF00FF), bounds_); + views_[0]->Draw(dc); + dc.PopScissor(); + + float childHeight = views_[0]->GetBounds().h; + float scrollMax = std::max(0.0f, childHeight - bounds_.h); + + float ratio = bounds_.h / std::max(0.01f, views_[0]->GetBounds().h); + + float bobWidth = 5; + if (ratio < 1.0f && scrollMax > 0.0f) { + float bobHeight = ratio * bounds_.h; + float bobOffset = (ClampedScrollPos(scrollPos_) / scrollMax) * (bounds_.h - bobHeight); + + Bounds bob(bounds_.x2() - bobWidth, bounds_.y + bobOffset, bobWidth, bobHeight); + dc.FillRect(Drawable(0x80FFFFFF), bob); + } +} + +bool ScrollView::SubviewFocused(View *view) { + if (!ViewGroup::SubviewFocused(view)) + return false; + + const Bounds &vBounds = view->GetBounds(); + + // Scroll so that the focused view is visible, and a bit more so that headers etc gets visible too, in most cases. + const float overscroll = std::min(view->GetBounds().h / 1.5f, GetBounds().h / 4.0f); + + float pos = ClampedScrollPos(scrollPos_); + float visibleSize = orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w; + float visibleEnd = scrollPos_ + visibleSize; + + float viewStart = 0.0f, viewEnd = 0.0f; + switch (orientation_) { + case ORIENT_HORIZONTAL: + viewStart = layoutScrollPos_ + vBounds.x - bounds_.x; + viewEnd = layoutScrollPos_ + vBounds.x2() - bounds_.x; + break; + case ORIENT_VERTICAL: + viewStart = layoutScrollPos_ + vBounds.y - bounds_.y; + viewEnd = layoutScrollPos_ + vBounds.y2() - bounds_.y; + break; + } + + if (viewEnd > visibleEnd) { + ScrollTo(viewEnd - visibleSize + overscroll); + } else if (viewStart < pos) { + ScrollTo(viewStart - overscroll); + } + + return true; +} + +NeighborResult ScrollView::FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) { + if (ContainsSubview(view) && views_[0]->IsViewGroup()) { + ViewGroup *vg = static_cast(views_[0]); + int found = -1; + for (int i = 0, n = vg->GetNumSubviews(); i < n; ++i) { + View *child = vg->GetViewByIndex(i); + if (child == view || child->ContainsSubview(view)) { + found = i; + break; + } + } + + // Okay, the previously focused view is inside this. + if (found != -1) { + float mult = 0.0f; + switch (direction) { + case FOCUS_PREV_PAGE: + mult = -1.0f; + break; + case FOCUS_NEXT_PAGE: + mult = 1.0f; + break; + default: + break; + } + + // Okay, now where is our ideal target? + Point targetPos = view->GetBounds().Center(); + if (orientation_ == ORIENT_VERTICAL) + targetPos.y += mult * bounds_.h; + else + targetPos.x += mult * bounds_.x; + + // Okay, which subview is closest to that? + best = vg->FindScrollNeighbor(view, targetPos, direction, best); + // Avoid reselecting the same view. + if (best.view == view) + best.view = nullptr; + return best; + } + } + + return ViewGroup::FindScrollNeighbor(view, target, direction, best); +} + +void ScrollView::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) { + ViewGroup::PersistData(status, anonId, storage); + + std::string tag = Tag(); + if (tag.empty()) { + tag = anonId; + } + + PersistBuffer &buffer = storage["ScrollView::" + tag]; + switch (status) { + case PERSIST_SAVE: + { + buffer.resize(1); + float pos = scrollToTarget_ ? scrollTarget_ : scrollPos_; + // Hmm, ugly... better buffer? + buffer[0] = *(int *)&pos; + } + break; + + case PERSIST_RESTORE: + if (buffer.size() == 1) { + float pos = *(float *)&buffer[0]; + scrollPos_ = pos; + scrollTarget_ = pos; + scrollToTarget_ = false; + } + break; + } +} + +void ScrollView::SetVisibility(Visibility visibility) { + ViewGroup::SetVisibility(visibility); + + if (visibility == V_GONE && !rememberPos_) { + // Since this is no longer shown, forget the scroll position. + // For example, this happens when switching tabs. + ScrollTo(0.0f); + } +} + +void ScrollView::ScrollTo(float newScrollPos) { + scrollTarget_ = newScrollPos; + scrollToTarget_ = true; +} + +void ScrollView::ScrollRelative(float distance) { + scrollTarget_ = scrollPos_ + distance; + scrollToTarget_ = true; +} + +float ScrollView::ClampedScrollPos(float pos) { + if (!views_.size() || bounds_.h == 0.0f) { + return 0.0f; + } + + float childSize = orientation_ == ORIENT_VERTICAL ? views_[0]->GetBounds().h : views_[0]->GetBounds().w; + float containerSize = (orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w); + float scrollMax = std::max(0.0f, childSize - containerSize); + + Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL; + + if (scrollTouchId_ >= 0 && gesture_.IsGestureActive(gesture, scrollTouchId_) && bounds_.h > 0.0f) { + float maxPull = bounds_.h * 0.1f; + if (pos < 0.0f) { + float dist = std::min(-pos * (1.0f / bounds_.h), 1.0f); + pull_ = -(sqrt(dist) * maxPull); + } else if (pos > scrollMax) { + float dist = std::min((pos - scrollMax) * (1.0f / bounds_.h), 1.0f); + pull_ = sqrt(dist) * maxPull; + } else { + pull_ = 0.0f; + } + } + + if (pos < 0.0f && pos < pull_) { + pos = pull_; + } + if (pos > scrollMax && pos > scrollMax + pull_) { + pos = scrollMax + pull_; + } + if (childSize < containerSize &&alignOpposite_) { + pos = -(containerSize - childSize); + } + return pos; +} + +void ScrollView::ScrollToBottom() { + float childHeight = views_[0]->GetBounds().h; + float scrollMax = std::max(0.0f, childHeight - bounds_.h); + scrollPos_ = scrollMax; + scrollTarget_ = scrollMax; +} + +bool ScrollView::CanScroll() const { + if (!views_.size()) + return false; + switch (orientation_) { + case ORIENT_VERTICAL: + return views_[0]->GetBounds().h > bounds_.h; + case ORIENT_HORIZONTAL: + return views_[0]->GetBounds().w > bounds_.w; + default: + return false; + } +} + +void ScrollView::GetLastScrollPosition(float &x, float &y) { + x = lastScrollPosX; + y = lastScrollPosY; +} + +void ScrollView::Update() { + if (visibility_ != V_VISIBLE) { + inertia_ = 0.0f; + } + ViewGroup::Update(); + float oldPos = scrollPos_; + + Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL; + gesture_.UpdateFrame(); + if (scrollToTarget_) { + float target = ClampedScrollPos(scrollTarget_); + + inertia_ = 0.0f; + if (fabsf(target - scrollPos_) < 0.5f) { + scrollPos_ = target; + scrollToTarget_ = false; + } else { + scrollPos_ += (target - scrollPos_) * 0.3f; + } + } else if (inertia_ != 0.0f && !gesture_.IsGestureActive(gesture, scrollTouchId_)) { + scrollPos_ -= inertia_; + inertia_ *= friction; + if (fabsf(inertia_) < stop_threshold) + inertia_ = 0.0f; + } + + if (!gesture_.IsGestureActive(gesture, scrollTouchId_)) { + scrollPos_ = ClampedScrollPos(scrollPos_); + + pull_ *= friction; + if (fabsf(pull_) < 0.01f) { + pull_ = 0.0f; + } + } + + if (oldPos != scrollPos_) + orientation_ == ORIENT_HORIZONTAL ? lastScrollPosX = scrollPos_ : lastScrollPosY = scrollPos_; + + // We load some lists asynchronously, so don't update the position until it's loaded. + if (rememberPos_ && ClampedScrollPos(scrollPos_) != ClampedScrollPos(*rememberPos_)) { + *rememberPos_ = scrollPos_; + } +} + +ListView::ListView(ListAdaptor *a, std::set hidden, LayoutParams *layoutParams) + : ScrollView(ORIENT_VERTICAL, layoutParams), adaptor_(a), maxHeight_(0), hidden_(hidden) { + + linLayout_ = new LinearLayout(ORIENT_VERTICAL); + linLayout_->SetSpacing(0.0f); + Add(linLayout_); + CreateAllItems(); +} + +void ListView::CreateAllItems() { + linLayout_->Clear(); + // Let's not be clever yet, we'll just create them all up front and add them all in. + for (int i = 0; i < adaptor_->GetNumItems(); i++) { + if (hidden_.find(i) == hidden_.end()) { + View *v = linLayout_->Add(adaptor_->CreateItemView(i)); + adaptor_->AddEventCallback(v, std::bind(&ListView::OnItemCallback, this, i, std::placeholders::_1)); + } + } +} + +void ListView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) { + ScrollView::Measure(dc, horiz, vert); + if (maxHeight_ > 0 && measuredHeight_ > maxHeight_) { + measuredHeight_ = maxHeight_; + } +} + +std::string ListView::DescribeText() const { + auto u = GetI18NCategory("UI Elements"); + return DescribeListOrdered(u->T("List:")); +} + +EventReturn ListView::OnItemCallback(int num, EventParams &e) { + EventParams ev{}; + ev.v = nullptr; + ev.a = num; + adaptor_->SetSelected(num); + OnChoice.Trigger(ev); + CreateAllItems(); + return EVENT_DONE; +} + +View *ChoiceListAdaptor::CreateItemView(int index) { + return new Choice(items_[index]); +} + +bool ChoiceListAdaptor::AddEventCallback(View *view, std::function callback) { + Choice *choice = (Choice *)view; + choice->OnClick.Add(callback); + return EVENT_DONE; +} + + +View *StringVectorListAdaptor::CreateItemView(int index) { + return new Choice(items_[index], "", index == selected_); +} + +bool StringVectorListAdaptor::AddEventCallback(View *view, std::function callback) { + Choice *choice = (Choice *)view; + choice->OnClick.Add(callback); + return EVENT_DONE; +} + +} diff --git a/Common/UI/ScrollView.h b/Common/UI/ScrollView.h new file mode 100644 index 0000000000..d9092d3d34 --- /dev/null +++ b/Common/UI/ScrollView.h @@ -0,0 +1,133 @@ +#pragma once + +#include "Common/UI/View.h" +#include "Common/UI/ViewGroup.h" + +namespace UI { + +// A scrollview usually contains just a single child - a linear layout or similar. +class ScrollView : public ViewGroup { +public: + ScrollView(Orientation orientation, LayoutParams *layoutParams = 0) + : ViewGroup(layoutParams), orientation_(orientation) {} + ~ScrollView(); + + void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override; + void Layout() override; + + bool Key(const KeyInput &input) override; + bool Touch(const TouchInput &input) override; + void Draw(UIContext &dc) override; + std::string DescribeLog() const override { return "ScrollView: " + View::DescribeLog(); } + + void ScrollTo(float newScrollPos); + void ScrollToBottom(); + void ScrollRelative(float distance); + bool CanScroll() const; + void Update() override; + + void RememberPosition(float *pos) { + rememberPos_ = pos; + ScrollTo(*pos); + } + + // Get the last moved scroll view position + static void GetLastScrollPosition(float &x, float &y); + + // Override so that we can scroll to the active one after moving the focus. + bool SubviewFocused(View *view) override; + void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override; + void SetVisibility(Visibility visibility) override; + + // If the view is smaller than the scroll view, sets whether to align to the bottom/right instead of the left. + void SetAlignOpposite(bool alignOpposite) { + alignOpposite_ = alignOpposite; + } + + NeighborResult FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) override; + +private: + float ClampedScrollPos(float pos); + + GestureDetector gesture_; + Orientation orientation_; + float scrollPos_ = 0.0f; + float scrollStart_ = 0.0f; + float scrollTarget_ = 0.0f; + int scrollTouchId_ = -1; + bool scrollToTarget_ = false; + float layoutScrollPos_ = 0.0f; + float inertia_ = 0.0f; + float pull_ = 0.0f; + float lastViewSize_ = 0.0f; + float *rememberPos_ = nullptr; + bool alignOpposite_ = false; + + static float lastScrollPosX; + static float lastScrollPosY; +}; + +// Yes, this feels a bit Java-ish... +class ListAdaptor { +public: + virtual ~ListAdaptor() {} + virtual View *CreateItemView(int index) = 0; + virtual int GetNumItems() = 0; + virtual bool AddEventCallback(View *view, std::function callback) { return false; } + virtual std::string GetTitle(int index) const { return ""; } + virtual void SetSelected(int sel) { } + virtual int GetSelected() { return -1; } +}; + +class ChoiceListAdaptor : public ListAdaptor { +public: + ChoiceListAdaptor(const char *items[], int numItems) : items_(items), numItems_(numItems) {} + View *CreateItemView(int index) override; + int GetNumItems() override { return numItems_; } + bool AddEventCallback(View *view, std::function callback) override; + +private: + const char **items_; + int numItems_; +}; + +// The "selected" item is what was previously selected (optional). This items will be drawn differently. +class StringVectorListAdaptor : public ListAdaptor { +public: + StringVectorListAdaptor() : selected_(-1) {} + StringVectorListAdaptor(const std::vector &items, int selected = -1) : items_(items), selected_(selected) {} + View *CreateItemView(int index) override; + int GetNumItems() override { return (int)items_.size(); } + bool AddEventCallback(View *view, std::function callback) override; + void SetSelected(int sel) override { selected_ = sel; } + std::string GetTitle(int index) const override { return items_[index]; } + int GetSelected() override { return selected_; } + +private: + std::vector items_; + int selected_; +}; + +// A list view is a scroll view with autogenerated items. +// In the future, it might be smart and load/unload items as they go, but currently not. +class ListView : public ScrollView { +public: + ListView(ListAdaptor *a, std::set hidden = std::set(), LayoutParams *layoutParams = 0); + + int GetSelected() { return adaptor_->GetSelected(); } + void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override; + virtual void SetMaxHeight(float mh) { maxHeight_ = mh; } + Event OnChoice; + std::string DescribeLog() const override { return "ListView: " + View::DescribeLog(); } + std::string DescribeText() const override; + +private: + void CreateAllItems(); + EventReturn OnItemCallback(int num, EventParams &e); + ListAdaptor *adaptor_; + LinearLayout *linLayout_; + float maxHeight_; + std::set hidden_; +}; + +} // namespace UI diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index ec586cdfe6..f8bf09602c 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -1,7 +1,3 @@ -#include -#include -#include - #include "Common/System/Display.h" #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" @@ -14,7 +10,6 @@ #include "Common/Render/DrawBuffer.h" #include "Common/Log.h" -#include "Common/StringUtils.h" static const bool ClickDebug = false; @@ -369,580 +364,3 @@ void PopupScreen::CreateViews() { box_->Add(buttonRow); } } - -void MessagePopupScreen::CreatePopupContents(UI::ViewGroup *parent) { - using namespace UI; - UIContext &dc = *screenManager()->getUIContext(); - - std::vector messageLines; - SplitString(message_, '\n', messageLines); - for (const auto& lineOfText : messageLines) - parent->Add(new UI::TextView(lineOfText, ALIGN_LEFT | ALIGN_VCENTER, false))->SetTextColor(dc.theme->popupStyle.fgColor); -} - -void MessagePopupScreen::OnCompleted(DialogResult result) { - if (result == DR_OK) { - if (callback_) - callback_(true); - } else { - if (callback_) - callback_(false); - } -} - -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()); - TriggerFinish(DR_OK); - OnChoice.Dispatch(e); - return UI::EVENT_DONE; -} - -namespace UI { - -PopupContextMenuScreen::PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView) - : PopupScreen("", "", ""), items_(items), itemCount_(itemCount), category_(category), sourceView_(sourceView) -{ - enabled_.resize(itemCount, true); - SetPopupOrigin(sourceView); -} - -void PopupContextMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { - for (size_t i = 0; i < itemCount_; i++) { - if (items_[i].imageID) { - Choice *choice = new Choice(category_->T(items_[i].text), ImageID(items_[i].imageID)); - parent->Add(choice); - if (enabled_[i]) { - choice->OnClick.Add([=](EventParams &p) { - TriggerFinish(DR_OK); - p.a = (uint32_t)i; - OnChoice.Dispatch(p); - return EVENT_DONE; - }); - } - else { - choice->SetEnabled(false); - } - } - } - - // Hacky: Override the position to look like a popup menu. - AnchorLayoutParams *ap = (AnchorLayoutParams *)parent->GetLayoutParams(); - ap->center = false; - ap->left = sourceView_->GetBounds().x; - ap->top = sourceView_->GetBounds().y2(); -} - -std::string ChopTitle(const std::string &title) { - size_t pos = title.find('\n'); - if (pos != title.npos) { - return title.substr(0, pos); - } - return title; -} - -UI::EventReturn PopupMultiChoice::HandleClick(UI::EventParams &e) { - restoreFocus_ = HasFocus(); - - auto 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(ChopTitle(text_), choices, *value_ - minVal_, - std::bind(&PopupMultiChoice::ChoiceCallback, this, std::placeholders::_1)); - popupScreen->SetHiddenChoices(hidden_); - if (e.v) - popupScreen->SetPopupOrigin(e.v); - screenManager_->push(popupScreen); - return UI::EVENT_DONE; -} - -void PopupMultiChoice::Update() { - UpdateText(); -} - -void PopupMultiChoice::UpdateText() { - if (!choices_) - return; - auto 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); - } - PostChoiceCallback(num); - } -} - -std::string PopupMultiChoice::ValueText() const { - return valueText_; -} - -PopupSliderChoice::PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units, LayoutParams *layoutParams) - : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1), units_(units), screenManager_(screenManager) { - 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) - : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) { - 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) - : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(1.0f), units_(units), screenManager_(screenManager) { - 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) - : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step), units_(units), screenManager_(screenManager) { - fmt_ = "%2.2f"; - OnClick.Handle(this, &PopupSliderChoiceFloat::HandleClick); -} - -EventReturn PopupSliderChoice::HandleClick(EventParams &e) { - restoreFocus_ = HasFocus(); - - SliderPopupScreen *popupScreen = new SliderPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_); - if (!negativeLabel_.empty()) - popupScreen->SetNegativeDisable(negativeLabel_); - popupScreen->OnChange.Handle(this, &PopupSliderChoice::HandleChange); - if (e.v) - popupScreen->SetPopupOrigin(e.v); - screenManager_->push(popupScreen); - return EVENT_DONE; -} - -EventReturn PopupSliderChoice::HandleChange(EventParams &e) { - e.v = this; - OnChange.Trigger(e); - - if (restoreFocus_) { - SetFocusedView(this); - } - return EVENT_DONE; -} - -std::string PopupSliderChoice::ValueText() const { - // Always good to have space for Unicode. - char temp[256]; - if (zeroLabel_.size() && *value_ == 0) { - strcpy(temp, zeroLabel_.c_str()); - } else if (negativeLabel_.size() && *value_ < 0) { - strcpy(temp, negativeLabel_.c_str()); - } else { - sprintf(temp, fmt_, *value_); - } - - return temp; -} - -EventReturn PopupSliderChoiceFloat::HandleClick(EventParams &e) { - restoreFocus_ = HasFocus(); - - SliderFloatPopupScreen *popupScreen = new SliderFloatPopupScreen(value_, minValue_, maxValue_, ChopTitle(text_), step_, units_, liveUpdate_); - popupScreen->OnChange.Handle(this, &PopupSliderChoiceFloat::HandleChange); - popupScreen->SetHasDropShadow(hasDropShadow_); - if (e.v) - popupScreen->SetPopupOrigin(e.v); - screenManager_->push(popupScreen); - return EVENT_DONE; -} - -EventReturn PopupSliderChoiceFloat::HandleChange(EventParams &e) { - e.v = this; - OnChange.Trigger(e); - - if (restoreFocus_) { - SetFocusedView(this); - } - return EVENT_DONE; -} - -std::string PopupSliderChoiceFloat::ValueText() const { - char temp[256]; - if (zeroLabel_.size() && *value_ == 0.0f) { - strcpy(temp, zeroLabel_.c_str()); - } else { - sprintf(temp, fmt_, *value_); - } - - return temp; -} - -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; - disabled_ = 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; - disabled_ = false; - return EVENT_DONE; -} - -EventReturn SliderPopupScreen::OnSliderChange(EventParams ¶ms) { - changing_ = true; - char temp[64]; - sprintf(temp, "%d", sliderValue_); - edit_->SetText(temp); - changing_ = false; - disabled_ = false; - return EVENT_DONE; -} - -EventReturn SliderPopupScreen::OnTextChange(EventParams ¶ms) { - if (!changing_) { - sliderValue_ = atoi(edit_->GetText().c_str()); - disabled_ = false; - slider_->Clamp(); - } - return EVENT_DONE; -} - -void SliderPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { - using namespace UI; - UIContext &dc = *screenManager()->getUIContext(); - - sliderValue_ = *value_; - if (disabled_ && sliderValue_ < 0) - sliderValue_ = 0; - 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, Title(), "", new LinearLayoutParams(10.0f)); - edit_->SetMaxLen(16); - edit_->SetTextColor(dc.theme->itemStyle.fgColor); - edit_->SetTextAlign(FLAG_DYNAMIC_ASCII); - edit_->OnTextChange.Handle(this, &SliderPopupScreen::OnTextChange); - changing_ = false; - lin->Add(edit_); - - if (!units_.empty()) - lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->itemStyle.fgColor); - - if (!negativeLabel_.empty()) - vert->Add(new CheckBox(&disabled_, negativeLabel_)); - - if (IsFocusMovementEnabled()) - UI::SetFocusedView(slider_); -} - -void SliderFloatPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { - using namespace UI; - UIContext &dc = *screenManager()->getUIContext(); - - 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, Title(), "", new LinearLayoutParams(10.0f)); - edit_->SetMaxLen(16); - edit_->SetTextColor(dc.theme->itemStyle.fgColor); - edit_->SetTextAlign(FLAG_DYNAMIC_ASCII); - edit_->OnTextChange.Handle(this, &SliderFloatPopupScreen::OnTextChange); - changing_ = false; - lin->Add(edit_); - if (!units_.empty()) - lin->Add(new TextView(units_, new LinearLayoutParams(10.0f)))->SetTextColor(dc.theme->itemStyle.fgColor); - - // 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; - if (liveUpdate_) { - *value_ = sliderValue_; - } - 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; - if (liveUpdate_) { - *value_ = sliderValue_; - } - return EVENT_DONE; -} - -EventReturn SliderFloatPopupScreen::OnSliderChange(EventParams ¶ms) { - changing_ = true; - char temp[64]; - sprintf(temp, "%0.3f", sliderValue_); - edit_->SetText(temp); - changing_ = false; - if (liveUpdate_) { - *value_ = sliderValue_; - } - return EVENT_DONE; -} - -EventReturn SliderFloatPopupScreen::OnTextChange(EventParams ¶ms) { - if (!changing_) { - sliderValue_ = atof(edit_->GetText().c_str()); - slider_->Clamp(); - if (liveUpdate_) { - *value_ = sliderValue_; - } - } - return EVENT_DONE; -} - -void SliderPopupScreen::OnCompleted(DialogResult result) { - if (result == DR_OK) { - *value_ = disabled_ ? -1 : sliderValue_; - EventParams e{}; - e.v = nullptr; - e.a = *value_; - OnChange.Trigger(e); - } -} - -void SliderFloatPopupScreen::OnCompleted(DialogResult result) { - if (result == DR_OK) { - *value_ = sliderValue_; - EventParams e{}; - e.v = nullptr; - e.a = (int)*value_; - e.f = *value_; - OnChange.Trigger(e); - } else { - *value_ = originalValue_; - } -} - -PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string &title, const std::string &placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams) -: AbstractChoiceWithValueDisplay(title, layoutParams), screenManager_(screenManager), value_(value), placeHolder_(placeholder), maxLen_(maxLen) { - OnClick.Handle(this, &PopupTextInputChoice::HandleClick); -} - -EventReturn PopupTextInputChoice::HandleClick(EventParams &e) { - restoreFocus_ = HasFocus(); - - TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, ChopTitle(text_), maxLen_); - popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange); - if (e.v) - popupScreen->SetPopupOrigin(e.v); - screenManager_->push(popupScreen); - return EVENT_DONE; -} - -std::string PopupTextInputChoice::ValueText() const { - return *value_; -} - -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; - UIContext &dc = *screenManager()->getUIContext(); - - textEditValue_ = *value_; - LinearLayout *lin = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams((UI::Size)300, WRAP_CONTENT))); - edit_ = new TextEdit(textEditValue_, Title(), placeholder_, new LinearLayoutParams(1.0f)); - edit_->SetMaxLen(maxLen_); - edit_->SetTextColor(dc.theme->popupStyle.fgColor); - lin->Add(edit_); - - UI::SetFocusedView(edit_); -} - -void TextEditPopupScreen::OnCompleted(DialogResult result) { - if (result == DR_OK) { - *value_ = StripSpaces(edit_->GetText()); - EventParams e{}; - e.v = edit_; - OnChange.Trigger(e); - } -} - -void AbstractChoiceWithValueDisplay::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const { - const std::string valueText = ValueText(); - int paddingX = 12; - // Assume we want at least 20% of the size for the label, at a minimum. - float availWidth = (horiz.size - paddingX * 2) * (text_.empty() ? 1.0f : 0.8f); - if (availWidth < 0) { - availWidth = 65535.0f; - } - float scale = CalculateValueScale(dc, valueText, availWidth); - Bounds availBounds(0, 0, availWidth, vert.size); - - float valueW, valueH; - dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &valueW, &valueH, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); - valueW += paddingX; - - // Give the choice itself less space to grow in, so it shrinks if needed. - // MeasureSpec horizLabel = horiz; - // horizLabel.size -= valueW; - Choice::GetContentDimensionsBySpec(dc, horiz, vert, w, h); - - w += valueW; - // Fill out anyway if there's space. - if (horiz.type == AT_MOST && w < horiz.size) { - w = horiz.size; - } - h = std::max(h, valueH); -} - -void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) { - Style style = dc.theme->itemStyle; - if (!IsEnabled()) { - style = dc.theme->itemDisabledStyle; - } - if (HasFocus()) { - style = dc.theme->itemFocusedStyle; - } - if (down_) { - style = dc.theme->itemDownStyle; - } - int paddingX = 12; - dc.SetFontStyle(dc.theme->uiFont); - - const std::string valueText = ValueText(); - - // If there is a label, assume we want at least 20% of the size for it, at a minimum. - - if (!text_.empty()) { - float availWidth = (bounds_.w - paddingX * 2) * 0.8f; - float scale = CalculateValueScale(dc, valueText, availWidth); - - float w, h; - Bounds availBounds(0, 0, availWidth, bounds_.h); - dc.MeasureTextRect(dc.theme->uiFont, scale, scale, valueText.c_str(), (int)valueText.size(), availBounds, &w, &h, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); - textPadding_.right = w + paddingX; - - Choice::Draw(dc); - dc.SetFontScale(scale, scale); - Bounds valueBounds(bounds_.x2() - textPadding_.right, bounds_.y, w, bounds_.h); - dc.DrawTextRect(valueText.c_str(), valueBounds, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); - dc.SetFontScale(1.0f, 1.0f); - } else { - Choice::Draw(dc); - float scale = CalculateValueScale(dc, valueText, bounds_.w); - dc.SetFontScale(scale, scale); - dc.DrawTextRect(valueText.c_str(), bounds_.Expand(-paddingX, 0.0f), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER | FLAG_WRAP_TEXT); - dc.SetFontScale(1.0f, 1.0f); - } -} - -float AbstractChoiceWithValueDisplay::CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const { - float actualWidth, actualHeight; - Bounds availBounds(0, 0, availWidth, bounds_.h); - dc.MeasureTextRect(dc.theme->uiFont, 1.0f, 1.0f, valueText.c_str(), (int)valueText.size(), availBounds, &actualWidth, &actualHeight); - if (actualWidth > availWidth) { - return std::max(0.8f, availWidth / actualWidth); - } - return 1.0f; -} - -std::string ChoiceWithValueDisplay::ValueText() const { - auto category = GetI18NCategory(category_); - std::ostringstream valueText; - if (translateCallback_ && sValue_) { - valueText << translateCallback_(sValue_->c_str()); - } else if (sValue_ != nullptr) { - if (category) - valueText << category->T(*sValue_); - else - valueText << *sValue_; - } else if (iValue_ != nullptr) { - valueText << *iValue_; - } - - return valueText.str(); -} - -} // namespace UI diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index d91bb0371c..a868485e65 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -68,7 +68,6 @@ private: bool finished_; }; - class PopupScreen : public UIDialogScreen { public: PopupScreen(std::string title, std::string button1 = "", std::string button2 = ""); @@ -118,389 +117,3 @@ private: bool hasDropShadow_ = true; }; - -class ListPopupScreen : public PopupScreen { -public: - ListPopupScreen(std::string title) : PopupScreen(title) {} - ListPopupScreen(std::string title, const std::vector &items, int selected, std::function callback, bool showButtons = false) - : PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), callback_(callback), showButtons_(showButtons) { - } - ListPopupScreen(std::string title, const std::vector &items, int selected, bool showButtons = false) - : PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), showButtons_(showButtons) { - } - - int GetChoice() const { - return listView_->GetSelected(); - } - std::string GetChoiceString() const { - return adaptor_.GetTitle(listView_->GetSelected()); - } - void SetHiddenChoices(std::set hidden) { - hidden_ = hidden; - } - const char *tag() const override { return "listpopup"; } - - UI::Event OnChoice; - -protected: - bool FillVertical() const override { return false; } - bool ShowButtons() const override { return showButtons_; } - void CreatePopupContents(UI::ViewGroup *parent) override; - UI::StringVectorListAdaptor adaptor_; - UI::ListView *listView_ = nullptr; - -private: - UI::EventReturn OnListChoice(UI::EventParams &e); - - std::function callback_; - bool showButtons_ = false; - std::set hidden_; -}; - -class MessagePopupScreen : public PopupScreen { -public: - MessagePopupScreen(std::string title, std::string message, std::string button1, std::string button2, std::function callback) - : PopupScreen(title, button1, button2), message_(message), callback_(callback) {} - UI::Event OnChoice; - -protected: - bool FillVertical() const override { return false; } - bool ShowButtons() const override { return true; } - void CreatePopupContents(UI::ViewGroup *parent) override; - -private: - void OnCompleted(DialogResult result) override; - std::string message_; - std::function callback_; -}; - -// TODO: Need a way to translate OK and Cancel - -namespace UI { - -class SliderPopupScreen : public PopupScreen { -public: - SliderPopupScreen(int *value, int minValue, int maxValue, const std::string &title, int step = 1, const std::string &units = "") - : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), minValue_(minValue), maxValue_(maxValue), step_(step) {} - void CreatePopupContents(ViewGroup *parent) override; - - void SetNegativeDisable(const std::string &str) { - negativeLabel_ = str; - disabled_ = *value_ < 0; - } - - const char *tag() const override { return "SliderPopup"; } - - Event OnChange; - -private: - EventReturn OnDecrease(EventParams ¶ms); - EventReturn OnIncrease(EventParams ¶ms); - EventReturn OnTextChange(EventParams ¶ms); - EventReturn OnSliderChange(EventParams ¶ms); - void OnCompleted(DialogResult result) override; - Slider *slider_ = nullptr; - UI::TextEdit *edit_ = nullptr; - std::string units_; - std::string negativeLabel_; - int *value_; - int sliderValue_ = 0; - int minValue_; - int maxValue_; - int step_; - bool changing_ = false; - bool disabled_ = false; -}; - -class SliderFloatPopupScreen : public PopupScreen { -public: - SliderFloatPopupScreen(float *value, float minValue, float maxValue, const std::string &title, float step = 1.0f, const std::string &units = "", bool liveUpdate = false) - : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), originalValue_(*value), minValue_(minValue), maxValue_(maxValue), step_(step), changing_(false), liveUpdate_(liveUpdate) {} - void CreatePopupContents(UI::ViewGroup *parent) override; - - const char *tag() const override { return "SliderFloatPopup"; } - - Event OnChange; - -private: - EventReturn OnIncrease(EventParams ¶ms); - EventReturn OnDecrease(EventParams ¶ms); - EventReturn OnTextChange(EventParams ¶ms); - EventReturn OnSliderChange(EventParams ¶ms); - void OnCompleted(DialogResult result) override; - UI::SliderFloat *slider_; - UI::TextEdit *edit_; - std::string units_; - float sliderValue_; - float originalValue_; - float *value_; - float minValue_; - float maxValue_; - float step_; - bool changing_; - bool liveUpdate_; -}; - -class TextEditPopupScreen : public PopupScreen { -public: - TextEditPopupScreen(std::string *value, const std::string &placeholder, const std::string &title, int maxLen) - : PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {} - void CreatePopupContents(ViewGroup *parent) override; - - const char *tag() const override { return "TextEditPopup"; } - - Event OnChange; - -private: - void OnCompleted(DialogResult result) override; - TextEdit *edit_; - std::string *value_; - std::string textEditValue_; - std::string placeholder_; - int maxLen_; -}; - -// TODO: Break out a lot of popup stuff from UIScreen.h. - -struct ContextMenuItem { - const char *text; - const char *imageID; -}; - -// Once a selection has been made, -class PopupContextMenuScreen : public PopupScreen { -public: - PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCategory *category, UI::View *sourceView); - void CreatePopupContents(ViewGroup *parent) override; - - const char *tag() const override { return "ContextMenuPopup"; } - - void SetEnabled(size_t index, bool enabled) { - enabled_[index] = enabled; - } - - UI::Event OnChoice; - -protected: - bool HasTitleBar() const override { return false; } - -private: - const ContextMenuItem *items_; - size_t itemCount_; - I18NCategory *category_; - UI::View *sourceView_; - std::vector enabled_; -}; - -class AbstractChoiceWithValueDisplay : public UI::Choice { -public: - AbstractChoiceWithValueDisplay(const std::string &text, LayoutParams *layoutParams = nullptr) - : Choice(text, layoutParams) { - } - - void Draw(UIContext &dc) override; - void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; - -protected: - virtual std::string ValueText() const = 0; - - float CalculateValueScale(const UIContext &dc, const std::string &valueText, float availWidth) const; -}; - -// Reads and writes value to determine the current selection. -class PopupMultiChoice : public AbstractChoiceWithValueDisplay { -public: - PopupMultiChoice(int *value, const std::string &text, const char **choices, int minVal, int numChoices, - const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr) - : AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), choices_(choices), minVal_(minVal), numChoices_(numChoices), - category_(category), screenManager_(screenManager) { - if (*value >= numChoices + minVal) - *value = numChoices + minVal - 1; - if (*value < minVal) - *value = minVal; - OnClick.Handle(this, &PopupMultiChoice::HandleClick); - UpdateText(); - } - - void Update() override; - - void HideChoice(int c) { - hidden_.insert(c); - } - - UI::Event OnChoice; - -protected: - std::string ValueText() const override; - - int *value_; - const char **choices_; - int minVal_; - int numChoices_; - void UpdateText(); - -private: - UI::EventReturn HandleClick(UI::EventParams &e); - - void ChoiceCallback(int num); - virtual void PostChoiceCallback(int num) {} - - const char *category_; - ScreenManager *screenManager_; - std::string valueText_; - bool restoreFocus_ = false; - std::set hidden_; -}; - -// Allows passing in a dynamic vector of strings. Saves the string. -class PopupMultiChoiceDynamic : public PopupMultiChoice { -public: - PopupMultiChoiceDynamic(std::string *value, const std::string &text, std::vector choices, - const char *category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr) - : UI::PopupMultiChoice(&valueInt_, text, nullptr, 0, (int)choices.size(), category, screenManager, layoutParams), - valueStr_(value) { - choices_ = new const char *[numChoices_]; - valueInt_ = 0; - for (int i = 0; i < numChoices_; i++) { - choices_[i] = new char[choices[i].size() + 1]; - memcpy((char *)choices_[i], choices[i].c_str(), choices[i].size() + 1); - if (*value == choices_[i]) - valueInt_ = i; - } - value_ = &valueInt_; - UpdateText(); - } - ~PopupMultiChoiceDynamic() { - for (int i = 0; i < numChoices_; i++) { - delete[] choices_[i]; - } - delete[] choices_; - } - -protected: - void PostChoiceCallback(int num) override { - *valueStr_ = choices_[num]; - } - -private: - int valueInt_; - std::string *valueStr_; -}; - -class PopupSliderChoice : public AbstractChoiceWithValueDisplay { -public: - PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); - PopupSliderChoice(int *value, int minValue, int maxValue, const std::string &text, int step, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); - - void SetFormat(const char *fmt) { - fmt_ = fmt; - } - void SetZeroLabel(const std::string &str) { - zeroLabel_ = str; - } - void SetNegativeDisable(const std::string &str) { - negativeLabel_ = str; - } - - Event OnChange; - -protected: - std::string ValueText() const override; - -private: - EventReturn HandleClick(EventParams &e); - EventReturn HandleChange(EventParams &e); - - int *value_; - int minValue_; - int maxValue_; - int step_; - const char *fmt_; - std::string zeroLabel_; - std::string negativeLabel_; - std::string units_; - ScreenManager *screenManager_; - bool restoreFocus_ = false; -}; - -class PopupSliderChoiceFloat : public AbstractChoiceWithValueDisplay { -public: - PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); - PopupSliderChoiceFloat(float *value, float minValue, float maxValue, const std::string &text, float step, ScreenManager *screenManager, const std::string &units = "", LayoutParams *layoutParams = 0); - - void SetFormat(const char *fmt) { - fmt_ = fmt; - } - void SetZeroLabel(const std::string &str) { - zeroLabel_ = str; - } - void SetLiveUpdate(bool update) { - liveUpdate_ = update; - } - void SetHasDropShadow(bool has) { - hasDropShadow_ = has; - } - - Event OnChange; - -protected: - std::string ValueText() const override; - -private: - EventReturn HandleClick(EventParams &e); - EventReturn HandleChange(EventParams &e); - float *value_; - float minValue_; - float maxValue_; - float step_; - const char *fmt_; - std::string zeroLabel_; - std::string units_; - ScreenManager *screenManager_; - bool restoreFocus_ = false; - bool liveUpdate_ = false; - bool hasDropShadow_ = true; -}; - -class PopupTextInputChoice: public AbstractChoiceWithValueDisplay { -public: - PopupTextInputChoice(std::string *value, const std::string &title, const std::string &placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams = 0); - - Event OnChange; - -protected: - std::string ValueText() const override; - -private: - EventReturn HandleClick(EventParams &e); - EventReturn HandleChange(EventParams &e); - ScreenManager *screenManager_; - std::string *value_; - std::string placeHolder_; - std::string defaultText_; - int maxLen_; - bool restoreFocus_; -}; - -class ChoiceWithValueDisplay : public AbstractChoiceWithValueDisplay { -public: - ChoiceWithValueDisplay(int *value, const std::string &text, LayoutParams *layoutParams = 0) - : AbstractChoiceWithValueDisplay(text, layoutParams), iValue_(value) {} - - ChoiceWithValueDisplay(std::string *value, const std::string &text, const char *category, LayoutParams *layoutParams = 0) - : AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value), category_(category) {} - - ChoiceWithValueDisplay(std::string *value, const std::string &text, std::string (*translateCallback)(const char *value), LayoutParams *layoutParams = 0) - : AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value), translateCallback_(translateCallback) { - } - -private: - std::string ValueText() const override; - - std::string *sValue_ = nullptr; - int *iValue_ = nullptr; - const char *category_ = nullptr; - std::string (*translateCallback_)(const char *value) = nullptr; -}; - -} // namespace UI diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index 3288eedd1f..1142b44fd2 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -10,6 +10,7 @@ #include "Common/Input/KeyCodes.h" #include "Common/Math/curves.h" #include "Common/UI/Context.h" +#include "Common/UI/ScrollView.h" #include "Common/UI/Tween.h" #include "Common/UI/Root.h" #include "Common/UI/View.h" @@ -765,438 +766,6 @@ void FrameLayout::Layout() { } } -void ScrollView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) { - // Respect margins - Margins margins; - if (views_.size()) { - const LinearLayoutParams *linLayoutParams = views_[0]->GetLayoutParams()->As(); - if (linLayoutParams) { - margins = linLayoutParams->margins; - } - } - - // The scroll view itself simply obeys its parent - but also tries to fit the child if possible. - MeasureBySpec(layoutParams_->width, horiz.size, horiz, &measuredWidth_); - MeasureBySpec(layoutParams_->height, vert.size, vert, &measuredHeight_); - - if (views_.size()) { - if (orientation_ == ORIENT_HORIZONTAL) { - MeasureSpec v = MeasureSpec(AT_MOST, measuredHeight_ - margins.vert()); - if (measuredHeight_ == 0.0f && (vert.type == UNSPECIFIED || layoutParams_->height == WRAP_CONTENT)) { - v.type = UNSPECIFIED; - } - views_[0]->Measure(dc, MeasureSpec(UNSPECIFIED, measuredWidth_), v); - MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_); - if (layoutParams_->width == WRAP_CONTENT) - MeasureBySpec(layoutParams_->width, views_[0]->GetMeasuredWidth(), horiz, &measuredWidth_); - } else { - MeasureSpec h = MeasureSpec(AT_MOST, measuredWidth_ - margins.horiz()); - if (measuredWidth_ == 0.0f && (horiz.type == UNSPECIFIED || layoutParams_->width == WRAP_CONTENT)) { - h.type = UNSPECIFIED; - } - views_[0]->Measure(dc, h, MeasureSpec(UNSPECIFIED, measuredHeight_)); - MeasureBySpec(layoutParams_->width, views_[0]->GetMeasuredWidth(), horiz, &measuredWidth_); - if (layoutParams_->height == WRAP_CONTENT) - MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_); - } - if (orientation_ == ORIENT_VERTICAL && vert.type != EXACTLY) { - float bestHeight = std::max(views_[0]->GetMeasuredHeight(), views_[0]->GetBounds().h); - if (vert.type == AT_MOST) - bestHeight = std::min(bestHeight, vert.size); - - if (measuredHeight_ < bestHeight && layoutParams_->height < 0.0f) { - measuredHeight_ = bestHeight; - } - } - } -} - -void ScrollView::Layout() { - if (!views_.size()) - return; - Bounds scrolled; - - // Respect margins - Margins margins; - const LinearLayoutParams *linLayoutParams = views_[0]->GetLayoutParams()->As(); - if (linLayoutParams) { - margins = linLayoutParams->margins; - } - - scrolled.w = views_[0]->GetMeasuredWidth() - margins.horiz(); - scrolled.h = views_[0]->GetMeasuredHeight() - margins.vert(); - - layoutScrollPos_ = ClampedScrollPos(scrollPos_); - - switch (orientation_) { - case ORIENT_HORIZONTAL: - if (scrolled.w != lastViewSize_) { - if (rememberPos_) - scrollPos_ = *rememberPos_; - lastViewSize_ = scrolled.w; - } - scrolled.x = bounds_.x - layoutScrollPos_; - scrolled.y = bounds_.y + margins.top; - break; - case ORIENT_VERTICAL: - if (scrolled.h != lastViewSize_) { - if (rememberPos_) - scrollPos_ = *rememberPos_; - lastViewSize_ = scrolled.h; - } - scrolled.x = bounds_.x + margins.left; - scrolled.y = bounds_.y - layoutScrollPos_; - break; - } - - views_[0]->SetBounds(scrolled); - views_[0]->Layout(); -} - -bool ScrollView::Key(const KeyInput &input) { - if (visibility_ != V_VISIBLE) - return ViewGroup::Key(input); - - float scrollSpeed = 250; - switch (input.deviceId) { - case DEVICE_ID_XR_CONTROLLER_LEFT: - case DEVICE_ID_XR_CONTROLLER_RIGHT: - scrollSpeed = 50; - break; - } - - if (input.flags & KEY_DOWN) { - if ((input.keyCode == NKCODE_EXT_MOUSEWHEEL_UP || input.keyCode == NKCODE_EXT_MOUSEWHEEL_DOWN) && - (input.flags & KEY_HASWHEELDELTA)) { - scrollSpeed = (float)(short)(input.flags >> 16) * 1.25f; // Fudge factor - } - - switch (input.keyCode) { - case NKCODE_EXT_MOUSEWHEEL_UP: - ScrollRelative(-scrollSpeed); - break; - case NKCODE_EXT_MOUSEWHEEL_DOWN: - ScrollRelative(scrollSpeed); - break; - } - } - return ViewGroup::Key(input); -} - -const float friction = 0.92f; -const float stop_threshold = 0.1f; - -bool ScrollView::Touch(const TouchInput &input) { - if ((input.flags & TOUCH_DOWN) && scrollTouchId_ == -1) { - scrollStart_ = scrollPos_; - inertia_ = 0.0f; - scrollTouchId_ = input.id; - } - - Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL; - - if ((input.flags & TOUCH_UP) && input.id == scrollTouchId_) { - float info[4]; - if (gesture_.GetGestureInfo(gesture, input.id, info)) { - inertia_ = info[1]; - } - scrollTouchId_ = -1; - } - - TouchInput input2; - if (CanScroll()) { - input2 = gesture_.Update(input, bounds_); - float info[4]; - if (input.id == scrollTouchId_ && gesture_.GetGestureInfo(gesture, input.id, info) && !(input.flags & TOUCH_DOWN)) { - float pos = scrollStart_ - info[0]; - scrollPos_ = pos; - scrollTarget_ = pos; - scrollToTarget_ = false; - } - } else { - input2 = input; - scrollTarget_ = scrollPos_; - scrollToTarget_ = false; - } - - if (!(input.flags & TOUCH_DOWN) || bounds_.Contains(input.x, input.y)) { - return ViewGroup::Touch(input2); - } else { - return false; - } -} - -void ScrollView::Draw(UIContext &dc) { - if (!views_.size()) { - ViewGroup::Draw(dc); - return; - } - - dc.PushScissor(bounds_); - dc.FillRect(bg_, bounds_); - - // For debugging layout issues, this can be useful. - // dc.FillRect(Drawable(0x60FF00FF), bounds_); - views_[0]->Draw(dc); - dc.PopScissor(); - - float childHeight = views_[0]->GetBounds().h; - float scrollMax = std::max(0.0f, childHeight - bounds_.h); - - float ratio = bounds_.h / std::max(0.01f, views_[0]->GetBounds().h); - - float bobWidth = 5; - if (ratio < 1.0f && scrollMax > 0.0f) { - float bobHeight = ratio * bounds_.h; - float bobOffset = (ClampedScrollPos(scrollPos_) / scrollMax) * (bounds_.h - bobHeight); - - Bounds bob(bounds_.x2() - bobWidth, bounds_.y + bobOffset, bobWidth, bobHeight); - dc.FillRect(Drawable(0x80FFFFFF), bob); - } -} - -bool ScrollView::SubviewFocused(View *view) { - if (!ViewGroup::SubviewFocused(view)) - return false; - - const Bounds &vBounds = view->GetBounds(); - - // Scroll so that the focused view is visible, and a bit more so that headers etc gets visible too, in most cases. - const float overscroll = std::min(view->GetBounds().h / 1.5f, GetBounds().h / 4.0f); - - float pos = ClampedScrollPos(scrollPos_); - float visibleSize = orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w; - float visibleEnd = scrollPos_ + visibleSize; - - float viewStart = 0.0f, viewEnd = 0.0f; - switch (orientation_) { - case ORIENT_HORIZONTAL: - viewStart = layoutScrollPos_ + vBounds.x - bounds_.x; - viewEnd = layoutScrollPos_ + vBounds.x2() - bounds_.x; - break; - case ORIENT_VERTICAL: - viewStart = layoutScrollPos_ + vBounds.y - bounds_.y; - viewEnd = layoutScrollPos_ + vBounds.y2() - bounds_.y; - break; - } - - if (viewEnd > visibleEnd) { - ScrollTo(viewEnd - visibleSize + overscroll); - } else if (viewStart < pos) { - ScrollTo(viewStart - overscroll); - } - - return true; -} - -NeighborResult ScrollView::FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) { - if (ContainsSubview(view) && views_[0]->IsViewGroup()) { - ViewGroup *vg = static_cast(views_[0]); - int found = -1; - for (int i = 0, n = vg->GetNumSubviews(); i < n; ++i) { - View *child = vg->GetViewByIndex(i); - if (child == view || child->ContainsSubview(view)) { - found = i; - break; - } - } - - // Okay, the previously focused view is inside this. - if (found != -1) { - float mult = 0.0f; - switch (direction) { - case FOCUS_PREV_PAGE: - mult = -1.0f; - break; - case FOCUS_NEXT_PAGE: - mult = 1.0f; - break; - default: - break; - } - - // Okay, now where is our ideal target? - Point targetPos = view->GetBounds().Center(); - if (orientation_ == ORIENT_VERTICAL) - targetPos.y += mult * bounds_.h; - else - targetPos.x += mult * bounds_.x; - - // Okay, which subview is closest to that? - best = vg->FindScrollNeighbor(view, targetPos, direction, best); - // Avoid reselecting the same view. - if (best.view == view) - best.view = nullptr; - return best; - } - } - - return ViewGroup::FindScrollNeighbor(view, target, direction, best); -} - -void ScrollView::PersistData(PersistStatus status, std::string anonId, PersistMap &storage) { - ViewGroup::PersistData(status, anonId, storage); - - std::string tag = Tag(); - if (tag.empty()) { - tag = anonId; - } - - PersistBuffer &buffer = storage["ScrollView::" + tag]; - switch (status) { - case PERSIST_SAVE: - { - buffer.resize(1); - float pos = scrollToTarget_ ? scrollTarget_ : scrollPos_; - // Hmm, ugly... better buffer? - buffer[0] = *(int *)&pos; - } - break; - - case PERSIST_RESTORE: - if (buffer.size() == 1) { - float pos = *(float *)&buffer[0]; - scrollPos_ = pos; - scrollTarget_ = pos; - scrollToTarget_ = false; - } - break; - } -} - -void ScrollView::SetVisibility(Visibility visibility) { - ViewGroup::SetVisibility(visibility); - - if (visibility == V_GONE && !rememberPos_) { - // Since this is no longer shown, forget the scroll position. - // For example, this happens when switching tabs. - ScrollTo(0.0f); - } -} - -void ScrollView::ScrollTo(float newScrollPos) { - scrollTarget_ = newScrollPos; - scrollToTarget_ = true; -} - -void ScrollView::ScrollRelative(float distance) { - scrollTarget_ = scrollPos_ + distance; - scrollToTarget_ = true; -} - -float ScrollView::ClampedScrollPos(float pos) { - if (!views_.size() || bounds_.h == 0.0f) { - return 0.0f; - } - - float childSize = orientation_ == ORIENT_VERTICAL ? views_[0]->GetBounds().h : views_[0]->GetBounds().w; - float containerSize = (orientation_ == ORIENT_VERTICAL ? bounds_.h : bounds_.w); - float scrollMax = std::max(0.0f, childSize - containerSize); - - Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL; - - if (scrollTouchId_ >= 0 && gesture_.IsGestureActive(gesture, scrollTouchId_) && bounds_.h > 0.0f) { - float maxPull = bounds_.h * 0.1f; - if (pos < 0.0f) { - float dist = std::min(-pos * (1.0f / bounds_.h), 1.0f); - pull_ = -(sqrt(dist) * maxPull); - } else if (pos > scrollMax) { - float dist = std::min((pos - scrollMax) * (1.0f / bounds_.h), 1.0f); - pull_ = sqrt(dist) * maxPull; - } else { - pull_ = 0.0f; - } - } - - if (pos < 0.0f && pos < pull_) { - pos = pull_; - } - if (pos > scrollMax && pos > scrollMax + pull_) { - pos = scrollMax + pull_; - } - if (childSize < containerSize && alignOpposite_) { - pos = -(containerSize - childSize); - } - return pos; -} - -void ScrollView::ScrollToBottom() { - float childHeight = views_[0]->GetBounds().h; - float scrollMax = std::max(0.0f, childHeight - bounds_.h); - scrollPos_ = scrollMax; - scrollTarget_ = scrollMax; -} - -bool ScrollView::CanScroll() const { - if (!views_.size()) - return false; - switch (orientation_) { - case ORIENT_VERTICAL: - return views_[0]->GetBounds().h > bounds_.h; - case ORIENT_HORIZONTAL: - return views_[0]->GetBounds().w > bounds_.w; - default: - return false; - } -} - -float ScrollView::lastScrollPosX = 0; -float ScrollView::lastScrollPosY = 0; - -ScrollView::~ScrollView() { - lastScrollPosX = 0; - lastScrollPosY = 0; -} - -void ScrollView::GetLastScrollPosition(float &x, float &y) { - x = lastScrollPosX; - y = lastScrollPosY; -} - -void ScrollView::Update() { - if (visibility_ != V_VISIBLE) { - inertia_ = 0.0f; - } - ViewGroup::Update(); - float oldPos = scrollPos_; - - Gesture gesture = orientation_ == ORIENT_VERTICAL ? GESTURE_DRAG_VERTICAL : GESTURE_DRAG_HORIZONTAL; - gesture_.UpdateFrame(); - if (scrollToTarget_) { - float target = ClampedScrollPos(scrollTarget_); - - inertia_ = 0.0f; - if (fabsf(target - scrollPos_) < 0.5f) { - scrollPos_ = target; - scrollToTarget_ = false; - } else { - scrollPos_ += (target - scrollPos_) * 0.3f; - } - } else if (inertia_ != 0.0f && !gesture_.IsGestureActive(gesture, scrollTouchId_)) { - scrollPos_ -= inertia_; - inertia_ *= friction; - if (fabsf(inertia_) < stop_threshold) - inertia_ = 0.0f; - } - - if (!gesture_.IsGestureActive(gesture, scrollTouchId_)) { - scrollPos_ = ClampedScrollPos(scrollPos_); - - pull_ *= friction; - if (fabsf(pull_) < 0.01f) { - pull_ = 0.0f; - } - } - - if (oldPos != scrollPos_) - orientation_ == ORIENT_HORIZONTAL ? lastScrollPosX = scrollPos_ : lastScrollPosY = scrollPos_; - - // We load some lists asynchronously, so don't update the position until it's loaded. - if (rememberPos_ && ClampedScrollPos(scrollPos_) != ClampedScrollPos(*rememberPos_)) { - *rememberPos_ = scrollPos_; - } -} - void AnchorLayout::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) { MeasureBySpec(layoutParams_->width, 0.0f, horiz, &measuredWidth_); MeasureBySpec(layoutParams_->height, 0.0f, vert, &measuredHeight_); @@ -1609,67 +1178,4 @@ StickyChoice *ChoiceStrip::Choice(int index) { return nullptr; } -ListView::ListView(ListAdaptor *a, std::set hidden, LayoutParams *layoutParams) - : ScrollView(ORIENT_VERTICAL, layoutParams), adaptor_(a), maxHeight_(0), hidden_(hidden) { - - linLayout_ = new LinearLayout(ORIENT_VERTICAL); - linLayout_->SetSpacing(0.0f); - Add(linLayout_); - CreateAllItems(); -} - -void ListView::CreateAllItems() { - linLayout_->Clear(); - // Let's not be clever yet, we'll just create them all up front and add them all in. - for (int i = 0; i < adaptor_->GetNumItems(); i++) { - if (hidden_.find(i) == hidden_.end()) { - View *v = linLayout_->Add(adaptor_->CreateItemView(i)); - adaptor_->AddEventCallback(v, std::bind(&ListView::OnItemCallback, this, i, std::placeholders::_1)); - } - } -} - -void ListView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) { - ScrollView::Measure(dc, horiz, vert); - if (maxHeight_ > 0 && measuredHeight_ > maxHeight_) { - measuredHeight_ = maxHeight_; - } -} - -std::string ListView::DescribeText() const { - auto u = GetI18NCategory("UI Elements"); - return DescribeListOrdered(u->T("List:")); -} - -EventReturn ListView::OnItemCallback(int num, EventParams &e) { - EventParams ev{}; - ev.v = nullptr; - ev.a = num; - adaptor_->SetSelected(num); - OnChoice.Trigger(ev); - CreateAllItems(); - return EVENT_DONE; -} - -View *ChoiceListAdaptor::CreateItemView(int index) { - return new Choice(items_[index]); -} - -bool ChoiceListAdaptor::AddEventCallback(View *view, std::function callback) { - Choice *choice = (Choice *)view; - choice->OnClick.Add(callback); - return EVENT_DONE; -} - - -View *StringVectorListAdaptor::CreateItemView(int index) { - return new Choice(items_[index], "", index == selected_); -} - -bool StringVectorListAdaptor::AddEventCallback(View *view, std::function callback) { - Choice *choice = (Choice *)view; - choice->OnClick.Add(callback); - return EVENT_DONE; -} - } // namespace UI diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h index 32ddb8eb56..b3a4ba074a 100644 --- a/Common/UI/ViewGroup.h +++ b/Common/UI/ViewGroup.h @@ -12,6 +12,7 @@ namespace UI { class AnchorTranslateTween; +class ScrollView; struct NeighborResult { NeighborResult() : view(0), score(0) {} @@ -260,68 +261,6 @@ public: std::string DescribeText() const override; }; -// A scrollview usually contains just a single child - a linear layout or similar. -class ScrollView : public ViewGroup { -public: - ScrollView(Orientation orientation, LayoutParams *layoutParams = 0) - : ViewGroup(layoutParams), orientation_(orientation) {} - ~ScrollView(); - - void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override; - void Layout() override; - - bool Key(const KeyInput &input) override; - bool Touch(const TouchInput &input) override; - void Draw(UIContext &dc) override; - std::string DescribeLog() const override { return "ScrollView: " + View::DescribeLog(); } - - void ScrollTo(float newScrollPos); - void ScrollToBottom(); - void ScrollRelative(float distance); - bool CanScroll() const; - void Update() override; - - void RememberPosition(float *pos) { - rememberPos_ = pos; - ScrollTo(*pos); - } - - // Get the last moved scroll view position - static void GetLastScrollPosition(float &x, float &y); - - // Override so that we can scroll to the active one after moving the focus. - bool SubviewFocused(View *view) override; - void PersistData(PersistStatus status, std::string anonId, PersistMap &storage) override; - void SetVisibility(Visibility visibility) override; - - // If the view is smaller than the scroll view, sets whether to align to the bottom/right instead of the left. - void SetAlignOpposite(bool alignOpposite) { - alignOpposite_ = alignOpposite; - } - - NeighborResult FindScrollNeighbor(View *view, const Point &target, FocusDirection direction, NeighborResult best) override; - -private: - float ClampedScrollPos(float pos); - - GestureDetector gesture_; - Orientation orientation_; - float scrollPos_ = 0.0f; - float scrollStart_ = 0.0f; - float scrollTarget_ = 0.0f; - int scrollTouchId_ = -1; - bool scrollToTarget_ = false; - float layoutScrollPos_ = 0.0f; - float inertia_ = 0.0f; - float pull_ = 0.0f; - float lastViewSize_ = 0.0f; - float *rememberPos_ = nullptr; - bool alignOpposite_ = false; - - static float lastScrollPosX; - static float lastScrollPosY; -}; - class ChoiceStrip : public LinearLayout { public: ChoiceStrip(Orientation orientation, LayoutParams *layoutParams = 0); @@ -387,68 +326,4 @@ private: std::vector tabTweens_; }; -// Yes, this feels a bit Java-ish... -class ListAdaptor { -public: - virtual ~ListAdaptor() {} - virtual View *CreateItemView(int index) = 0; - virtual int GetNumItems() = 0; - virtual bool AddEventCallback(View *view, std::function callback) { return false; } - virtual std::string GetTitle(int index) const { return ""; } - virtual void SetSelected(int sel) { } - virtual int GetSelected() { return -1; } -}; - -class ChoiceListAdaptor : public ListAdaptor { -public: - ChoiceListAdaptor(const char *items[], int numItems) : items_(items), numItems_(numItems) {} - View *CreateItemView(int index) override; - int GetNumItems() override { return numItems_; } - bool AddEventCallback(View *view, std::function callback) override; - -private: - const char **items_; - int numItems_; -}; - - -// The "selected" item is what was previously selected (optional). This items will be drawn differently. -class StringVectorListAdaptor : public ListAdaptor { -public: - StringVectorListAdaptor() : selected_(-1) {} - StringVectorListAdaptor(const std::vector &items, int selected = -1) : items_(items), selected_(selected) {} - View *CreateItemView(int index) override; - int GetNumItems() override { return (int)items_.size(); } - bool AddEventCallback(View *view, std::function callback) override; - void SetSelected(int sel) override { selected_ = sel; } - std::string GetTitle(int index) const override { return items_[index]; } - int GetSelected() override { return selected_; } - -private: - std::vector items_; - int selected_; -}; - -// A list view is a scroll view with autogenerated items. -// In the future, it might be smart and load/unload items as they go, but currently not. -class ListView : public ScrollView { -public: - ListView(ListAdaptor *a, std::set hidden = std::set(), LayoutParams *layoutParams = 0); - - int GetSelected() { return adaptor_->GetSelected(); } - void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override; - virtual void SetMaxHeight(float mh) { maxHeight_ = mh; } - Event OnChoice; - std::string DescribeLog() const override { return "ListView: " + View::DescribeLog(); } - std::string DescribeText() const override; - -private: - void CreateAllItems(); - EventReturn OnItemCallback(int num, EventParams &e); - ListAdaptor *adaptor_; - LinearLayout *linLayout_; - float maxHeight_; - std::set hidden_; -}; - } // namespace UI diff --git a/UI/ChatScreen.cpp b/UI/ChatScreen.cpp index 3a12597d11..277185b3e5 100644 --- a/UI/ChatScreen.cpp +++ b/UI/ChatScreen.cpp @@ -5,6 +5,7 @@ #include "Common/UI/Context.h" #include "Common/UI/View.h" #include "Common/UI/ViewGroup.h" +#include "Common/UI/ScrollView.h" #include "Common/UI/UI.h" #include "Common/Data/Text/I18n.h" diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 680a21a48c..725353631c 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -301,7 +301,7 @@ UI::EventReturn ControlMappingScreen::OnAutoConfigure(UI::EventParams ¶ms) { items.push_back(*s); } auto km = GetI18NCategory("KeyMapping"); - ListPopupScreen *autoConfList = new ListPopupScreen(km->T("Autoconfigure for device"), items, -1); + UI::ListPopupScreen *autoConfList = new UI::ListPopupScreen(km->T("Autoconfigure for device"), items, -1); if (params.v) autoConfList->SetPopupOrigin(params.v); screenManager()->push(autoConfList); @@ -316,7 +316,7 @@ UI::EventReturn ControlMappingScreen::OnVisualizeMapping(UI::EventParams ¶ms void ControlMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) { if (result == DR_OK && std::string(dialog->tag()) == "listpopup") { - ListPopupScreen *popup = (ListPopupScreen *)dialog; + UI::ListPopupScreen *popup = (UI::ListPopupScreen *)dialog; KeyMap::AutoConfForPad(popup->GetChoiceString()); } } diff --git a/UI/DevScreens.h b/UI/DevScreens.h index cfc332860f..0a871b56fc 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -96,7 +96,7 @@ private: bool toBottom_ = false; }; -class LogLevelScreen : public ListPopupScreen { +class LogLevelScreen : public UI::ListPopupScreen { public: LogLevelScreen(const std::string &title); diff --git a/UI/DisplayLayoutScreen.h b/UI/DisplayLayoutScreen.h index a494b9d288..769e975924 100644 --- a/UI/DisplayLayoutScreen.h +++ b/UI/DisplayLayoutScreen.h @@ -52,7 +52,7 @@ private: std::deque settingsVisible_; // vector is an insane bitpacked specialization! }; -class PostProcScreen : public ListPopupScreen { +class PostProcScreen : public UI::ListPopupScreen { public: PostProcScreen(const std::string &title, int id, bool showStereoShaders) : ListPopupScreen(title), id_(id), showStereoShaders_(showStereoShaders) { } diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index dc8f187f66..55b7484946 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -23,6 +23,7 @@ #include #include "Common/UI/UIScreen.h" +#include "Common/UI/PopupScreens.h" #include "Common/File/DirListing.h" #include "Common/File/Path.h" @@ -100,7 +101,7 @@ private: std::function callback_; }; -class NewLanguageScreen : public ListPopupScreen { +class NewLanguageScreen : public UI::ListPopupScreen { public: NewLanguageScreen(const std::string &title); @@ -112,7 +113,7 @@ private: std::vector langs_; }; -class TextureShaderScreen : public ListPopupScreen { +class TextureShaderScreen : public UI::ListPopupScreen { public: TextureShaderScreen(const std::string &title); diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 930306c7f7..7836bc30ce 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -498,8 +498,10 @@ + + @@ -618,8 +620,10 @@ + + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 954e21c236..f8e8679432 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -411,6 +411,12 @@ Render + + UI + + + UI + @@ -760,6 +766,12 @@ Render + + UI + + + UI + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2ff923f755..b42ee5eff7 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -203,6 +203,8 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/UI/Tween.cpp \ $(SRC)/Common/UI/View.cpp \ $(SRC)/Common/UI/ViewGroup.cpp \ + $(SRC)/Common/UI/ScrollView.cpp \ + $(SRC)/Common/UI/PopupScreens.cpp \ $(SRC)/Common/Serialize/Serializer.cpp \ $(SRC)/Common/ArmCPUDetect.cpp \ $(SRC)/Common/CPUDetect.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index cded3e62c6..2d8a9c2ed3 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -299,6 +299,8 @@ SOURCES_CXX += \ $(COMMONDIR)/UI/Tween.cpp \ $(COMMONDIR)/UI/View.cpp \ $(COMMONDIR)/UI/ViewGroup.cpp \ + $(COMMONDIR)/UI/ScrollView.cpp \ + $(COMMONDIR)/UI/PopupScreens.cpp \ $(COMMONDIR)/System/Display.cpp \ $(COMMONDIR)/ArmCPUDetect.cpp \ $(COMMONDIR)/CPUDetect.cpp \