mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Break out ScrollView from ViewGroup.h, and PopupScreens from UIScreen.h
This commit is contained in:
parent
5c79b930e9
commit
c27689910e
21 changed files with 1658 additions and 1596 deletions
|
@ -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
|
||||
|
|
|
@ -554,8 +554,10 @@
|
|||
<ClInclude Include="TimeUtil.h" />
|
||||
<ClInclude Include="UI\AsyncImageFileView.h" />
|
||||
<ClInclude Include="UI\Context.h" />
|
||||
<ClInclude Include="UI\PopupScreens.h" />
|
||||
<ClInclude Include="UI\Root.h" />
|
||||
<ClInclude Include="UI\Screen.h" />
|
||||
<ClInclude Include="UI\ScrollView.h" />
|
||||
<ClInclude Include="UI\Tween.h" />
|
||||
<ClInclude Include="UI\UI.h" />
|
||||
<ClInclude Include="UI\UIScreen.h" />
|
||||
|
@ -996,8 +998,10 @@
|
|||
<ClCompile Include="TimeUtil.cpp" />
|
||||
<ClCompile Include="UI\AsyncImageFileView.cpp" />
|
||||
<ClCompile Include="UI\Context.cpp" />
|
||||
<ClCompile Include="UI\PopupScreens.cpp" />
|
||||
<ClCompile Include="UI\Root.cpp" />
|
||||
<ClCompile Include="UI\Screen.cpp" />
|
||||
<ClCompile Include="UI\ScrollView.cpp" />
|
||||
<ClCompile Include="UI\Tween.cpp" />
|
||||
<ClCompile Include="UI\UI.cpp" />
|
||||
<ClCompile Include="UI\UIScreen.cpp" />
|
||||
|
|
|
@ -458,6 +458,12 @@
|
|||
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="UI\ScrollView.h">
|
||||
<Filter>UI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="UI\PopupScreens.h">
|
||||
<Filter>UI</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
@ -866,6 +872,12 @@
|
|||
<ClCompile Include="GPU\Vulkan\VulkanFramebuffer.cpp">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UI\ScrollView.cpp">
|
||||
<Filter>UI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UI\PopupScreens.cpp">
|
||||
<Filter>UI</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Crypto">
|
||||
|
|
584
Common/UI/PopupScreens.cpp
Normal file
584
Common/UI/PopupScreens.cpp
Normal file
|
@ -0,0 +1,584 @@
|
|||
#include <sstream>
|
||||
|
||||
#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<std::string> 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<std::string> 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
|
388
Common/UI/PopupScreens.h
Normal file
388
Common/UI/PopupScreens.h
Normal file
|
@ -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<std::string> &items, int selected, std::function<void(int)> callback, bool showButtons = false)
|
||||
: PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), callback_(callback), showButtons_(showButtons) {
|
||||
}
|
||||
ListPopupScreen(std::string title, const std::vector<std::string> &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<int> 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<void(int)> callback_;
|
||||
bool showButtons_ = false;
|
||||
std::set<int> hidden_;
|
||||
};
|
||||
|
||||
class MessagePopupScreen : public PopupScreen {
|
||||
public:
|
||||
MessagePopupScreen(std::string title, std::string message, std::string button1, std::string button2, std::function<void(bool)> 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<void(bool)> 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<bool> 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<int> 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<std::string> 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
|
|
@ -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"
|
||||
|
|
502
Common/UI/ScrollView.cpp
Normal file
502
Common/UI/ScrollView.cpp
Normal file
|
@ -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<LinearLayoutParams>();
|
||||
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<LinearLayoutParams>();
|
||||
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<ViewGroup *>(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<int> 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<EventReturn(EventParams &)> 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<EventReturn(EventParams &)> callback) {
|
||||
Choice *choice = (Choice *)view;
|
||||
choice->OnClick.Add(callback);
|
||||
return EVENT_DONE;
|
||||
}
|
||||
|
||||
}
|
133
Common/UI/ScrollView.h
Normal file
133
Common/UI/ScrollView.h
Normal file
|
@ -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<EventReturn(EventParams &)> 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<EventReturn(EventParams &)> 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<std::string> &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<EventReturn(EventParams &)> 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<std::string> 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<int> hidden = std::set<int>(), 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<int> hidden_;
|
||||
};
|
||||
|
||||
} // namespace UI
|
|
@ -1,7 +1,3 @@
|
|||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#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<std::string> 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<std::string> 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
|
||||
|
|
|
@ -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<std::string> &items, int selected, std::function<void(int)> callback, bool showButtons = false)
|
||||
: PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), callback_(callback), showButtons_(showButtons) {
|
||||
}
|
||||
ListPopupScreen(std::string title, const std::vector<std::string> &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<int> 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<void(int)> callback_;
|
||||
bool showButtons_ = false;
|
||||
std::set<int> hidden_;
|
||||
};
|
||||
|
||||
class MessagePopupScreen : public PopupScreen {
|
||||
public:
|
||||
MessagePopupScreen(std::string title, std::string message, std::string button1, std::string button2, std::function<void(bool)> 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<void(bool)> 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<bool> 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<int> 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<std::string> 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
|
||||
|
|
|
@ -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<LinearLayoutParams>();
|
||||
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<LinearLayoutParams>();
|
||||
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<ViewGroup *>(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<int> 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<EventReturn(EventParams&)> 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<EventReturn(EventParams&)> callback) {
|
||||
Choice *choice = (Choice *)view;
|
||||
choice->OnClick.Add(callback);
|
||||
return EVENT_DONE;
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
|
|
|
@ -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<AnchorTranslateTween *> 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<EventReturn(EventParams&)> 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<EventReturn(EventParams&)> 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<std::string> &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<EventReturn(EventParams&)> 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<std::string> 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<int> hidden = std::set<int>(), 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<int> hidden_;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ private:
|
|||
bool toBottom_ = false;
|
||||
};
|
||||
|
||||
class LogLevelScreen : public ListPopupScreen {
|
||||
class LogLevelScreen : public UI::ListPopupScreen {
|
||||
public:
|
||||
LogLevelScreen(const std::string &title);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ private:
|
|||
std::deque<bool> settingsVisible_; // vector<bool> 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) { }
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <vector>
|
||||
|
||||
#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<void(bool)> callback_;
|
||||
};
|
||||
|
||||
class NewLanguageScreen : public ListPopupScreen {
|
||||
class NewLanguageScreen : public UI::ListPopupScreen {
|
||||
public:
|
||||
NewLanguageScreen(const std::string &title);
|
||||
|
||||
|
@ -112,7 +113,7 @@ private:
|
|||
std::vector<File::FileInfo> langs_;
|
||||
};
|
||||
|
||||
class TextureShaderScreen : public ListPopupScreen {
|
||||
class TextureShaderScreen : public UI::ListPopupScreen {
|
||||
public:
|
||||
TextureShaderScreen(const std::string &title);
|
||||
|
||||
|
|
|
@ -498,8 +498,10 @@
|
|||
<ClInclude Include="..\..\Common\TimeUtil.h" />
|
||||
<ClInclude Include="..\..\Common\UI\AsyncImageFileView.h" />
|
||||
<ClInclude Include="..\..\Common\UI\Context.h" />
|
||||
<ClInclude Include="..\..\Common\UI\PopupScreens.h" />
|
||||
<ClInclude Include="..\..\Common\UI\Root.h" />
|
||||
<ClInclude Include="..\..\Common\UI\Screen.h" />
|
||||
<ClInclude Include="..\..\Common\UI\ScrollView.h" />
|
||||
<ClInclude Include="..\..\Common\UI\Tween.h" />
|
||||
<ClInclude Include="..\..\Common\UI\UI.h" />
|
||||
<ClInclude Include="..\..\Common\UI\UIScreen.h" />
|
||||
|
@ -618,8 +620,10 @@
|
|||
<ClCompile Include="..\..\Common\TimeUtil.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\AsyncImageFileView.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\Context.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\PopupScreens.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\Root.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\Screen.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\ScrollView.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\Tween.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\UI.cpp" />
|
||||
<ClCompile Include="..\..\Common\UI\UIScreen.cpp" />
|
||||
|
|
|
@ -411,6 +411,12 @@
|
|||
<ClCompile Include="..\..\Common\Render\ManagedTexture.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\Common\UI\PopupScreens.cpp">
|
||||
<Filter>UI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\Common\UI\ScrollView.cpp">
|
||||
<Filter>UI</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="targetver.h" />
|
||||
|
@ -760,6 +766,12 @@
|
|||
<ClInclude Include="..\..\Common\Render\ManagedTexture.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\Common\UI\PopupScreens.h">
|
||||
<Filter>UI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\Common\UI\ScrollView.h">
|
||||
<Filter>UI</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Common\Math\fast\fast_matrix_neon.S">
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 \
|
||||
|
|
Loading…
Add table
Reference in a new issue