#include #include "Common/System/Display.h" #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" #include "Common/Math/curves.h" #include "Common/UI/UIScreen.h" #include "Common/UI/Context.h" #include "Common/UI/Screen.h" #include "Common/UI/Root.h" #include "Common/Data/Text/I18n.h" #include "Common/Render/DrawBuffer.h" #include "Common/Log.h" static const bool ClickDebug = false; UIScreen::UIScreen() : Screen() { lastVertical_ = UseVerticalLayout(); } UIScreen::~UIScreen() { delete root_; } bool UIScreen::UseVerticalLayout() const { return dp_yres > dp_xres * 1.1f; } void UIScreen::DoRecreateViews() { if (recreateViews_) { std::lock_guard guard(screenManager()->inputLock_); UI::PersistMap persisted; bool persisting = root_ != nullptr; if (persisting) { root_->PersistData(UI::PERSIST_SAVE, "root", persisted); } delete root_; root_ = nullptr; CreateViews(); UI::View *defaultView = root_ ? root_->GetDefaultFocusView() : nullptr; if (defaultView && defaultView->GetVisibility() == UI::V_VISIBLE) { defaultView->SetFocus(); } recreateViews_ = false; if (persisting && root_ != nullptr) { root_->PersistData(UI::PERSIST_RESTORE, "root", persisted); // Update layout and refocus so things scroll into view. // This is for resizing down, when focused on something now offscreen. UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_, ignoreInsets_); UI::View *focused = UI::GetFocusedView(); if (focused) { root_->SubviewFocused(focused); } } } } void UIScreen::update() { bool vertical = UseVerticalLayout(); if (vertical != lastVertical_) { RecreateViews(); lastVertical_ = vertical; } DoRecreateViews(); if (root_) { UpdateViewHierarchy(root_); } } void UIScreen::deviceLost() { if (root_) root_->DeviceLost(); } void UIScreen::deviceRestored() { if (root_) root_->DeviceRestored(screenManager()->getDrawContext()); } void UIScreen::preRender() { using namespace Draw; Draw::DrawContext *draw = screenManager()->getDrawContext(); if (!draw) { return; } draw->BeginFrame(); // Bind and clear the back buffer draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "UI"); screenManager()->getUIContext()->BeginFrame(); Draw::Viewport viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; viewport.Width = pixel_xres; viewport.Height = pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; draw->SetViewports(1, &viewport); draw->SetTargetSize(pixel_xres, pixel_yres); } void UIScreen::postRender() { Draw::DrawContext *draw = screenManager()->getDrawContext(); if (!draw) { return; } draw->EndFrame(); } void UIScreen::render() { DoRecreateViews(); if (root_) { UIContext *uiContext = screenManager()->getUIContext(); uiContext->SetScreenTag(tag()); UI::LayoutViewHierarchy(*uiContext, root_, ignoreInsets_); uiContext->PushTransform({translation_, scale_, alpha_}); uiContext->Begin(); DrawBackground(*uiContext); root_->Draw(*uiContext); uiContext->Flush(); uiContext->PopTransform(); } } TouchInput UIScreen::transformTouch(const TouchInput &touch) { TouchInput updated = touch; float x = touch.x - translation_.x; float y = touch.y - translation_.y; // Scale around the center as the origin. updated.x = (x - dp_xres * 0.5f) / scale_.x + dp_xres * 0.5f; updated.y = (y - dp_yres * 0.5f) / scale_.y + dp_yres * 0.5f; return updated; } void UIScreen::touch(const TouchInput &touch) { if (root_) { if (ClickDebug && (touch.flags & TOUCH_DOWN)) { INFO_LOG(SYSTEM, "Touch down!"); std::vector views; root_->Query(touch.x, touch.y, views); for (auto view : views) { INFO_LOG(SYSTEM, "%s", view->DescribeLog().c_str()); } } UI::TouchEvent(touch, root_); } } bool UIScreen::key(const KeyInput &key) { if (root_) { return UI::KeyEvent(key, root_); } return false; } void UIScreen::TriggerFinish(DialogResult result) { screenManager()->finishDialog(this, result); } bool UIDialogScreen::key(const KeyInput &key) { bool retval = UIScreen::key(key); if (!retval && (key.flags & KEY_DOWN) && UI::IsEscapeKey(key)) { if (finished_) { ERROR_LOG(SYSTEM, "Screen already finished"); } else { finished_ = true; TriggerFinish(DR_BACK); UI::PlayUISound(UI::UISound::BACK); } return true; } return retval; } void UIDialogScreen::sendMessage(const char *msg, const char *value) { Screen *screen = screenManager()->dialogParent(this); if (screen) { screen->sendMessage(msg, value); } } void UIScreen::axis(const AxisInput &axis) { if (root_) { UI::AxisEvent(axis, root_); } } UI::EventReturn UIScreen::OnBack(UI::EventParams &e) { TriggerFinish(DR_BACK); return UI::EVENT_DONE; } UI::EventReturn UIScreen::OnOK(UI::EventParams &e) { TriggerFinish(DR_OK); return UI::EVENT_DONE; } UI::EventReturn UIScreen::OnCancel(UI::EventParams &e) { TriggerFinish(DR_CANCEL); return UI::EVENT_DONE; } PopupScreen::PopupScreen(std::string title, std::string button1, std::string button2) : title_(title) { auto di = GetI18NCategory("Dialog"); if (!button1.empty()) button1_ = di->T(button1.c_str()); if (!button2.empty()) button2_ = di->T(button2.c_str()); alpha_ = 0.0f; // inherited } void PopupScreen::touch(const TouchInput &touch) { if (!box_ || (touch.flags & TOUCH_DOWN) == 0) { UIDialogScreen::touch(touch); } // Extra bounds to avoid closing the dialog while trying to aim for something // near the edge. if (!box_->GetBounds().Expand(100.0f, 60.0f).Contains(touch.x, touch.y)) { TriggerFinish(DR_CANCEL); } UIDialogScreen::touch(touch); } bool PopupScreen::key(const KeyInput &key) { if (key.flags & KEY_DOWN) { if ((key.keyCode == NKCODE_ENTER || key.keyCode == NKCODE_NUMPAD_ENTER) && defaultButton_) { UI::EventParams e{}; defaultButton_->OnClick.Trigger(e); return true; } } return UIDialogScreen::key(key); } void PopupScreen::update() { UIDialogScreen::update(); if (defaultButton_) defaultButton_->SetEnabled(CanComplete(DR_OK)); float animatePos = 1.0f; ++frames_; if (finishFrame_ >= 0) { float leadOut = bezierEaseInOut((frames_ - finishFrame_) * (1.0f / (float)FRAMES_LEAD_OUT)); animatePos = 1.0f - leadOut; if (frames_ >= finishFrame_ + FRAMES_LEAD_OUT) { // Actual finish happens here. screenManager()->finishDialog(this, finishResult_); } } else if (frames_ < FRAMES_LEAD_IN) { float leadIn = bezierEaseInOut(frames_ * (1.0f / (float)FRAMES_LEAD_IN)); animatePos = leadIn; } if (animatePos < 1.0f) { alpha_ = animatePos; scale_.x = 0.9f + animatePos * 0.1f; scale_.y = 0.9f + animatePos * 0.1f; if (hasPopupOrigin_) { float xoff = popupOrigin_.x - dp_xres / 2; float yoff = popupOrigin_.y - dp_yres / 2; // Pull toward the origin a bit. translation_.x = xoff * (1.0f - animatePos) * 0.2f; translation_.y = yoff * (1.0f - animatePos) * 0.2f; } else { translation_.y = -dp_yres * (1.0f - animatePos) * 0.2f; } } else { alpha_ = 1.0f; scale_.x = 1.0f; scale_.y = 1.0f; translation_.x = 0.0f; translation_.y = 0.0f; } } void PopupScreen::SetPopupOrigin(const UI::View *view) { hasPopupOrigin_ = true; popupOrigin_ = view->GetBounds().Center(); } void PopupScreen::SetPopupOffset(float y) { offsetY_ = y; } void PopupScreen::TriggerFinish(DialogResult result) { if (CanComplete(result)) { finishFrame_ = frames_; finishResult_ = result; OnCompleted(result); } } void PopupScreen::CreateViews() { using namespace UI; UIContext &dc = *screenManager()->getUIContext(); AnchorLayout *anchor = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); anchor->Overflow(false); root_ = anchor; float yres = screenManager()->getUIContext()->GetBounds().h; box_ = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(PopupWidth(), FillVertical() ? yres - 30 : WRAP_CONTENT, dc.GetBounds().centerX(), dc.GetBounds().centerY() + offsetY_, NONE, NONE, true)); root_->Add(box_); box_->SetBG(dc.theme->popupStyle.background); box_->SetHasDropShadow(hasDropShadow_); // Since we scale a bit, make the dropshadow bleed past the edges. box_->SetDropShadowExpand(std::max(dp_xres, dp_yres)); box_->SetSpacing(0.0f); View *title = new PopupHeader(title_); if (HasTitleBar()) { box_->Add(title); } CreatePopupContents(box_); root_->SetDefaultFocusView(box_); if (ShowButtons() && !button1_.empty()) { // And the two buttons at the bottom. LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(200, WRAP_CONTENT)); buttonRow->SetSpacing(0); Margins buttonMargins(5, 5); // Adjust button order to the platform default. #if defined(_WIN32) defaultButton_ = buttonRow->Add(new Button(button1_, new LinearLayoutParams(1.0f, buttonMargins))); defaultButton_->OnClick.Handle(this, &UIScreen::OnOK); if (!button2_.empty()) buttonRow->Add(new Button(button2_, new LinearLayoutParams(1.0f, buttonMargins)))->OnClick.Handle(this, &UIScreen::OnCancel); #else if (!button2_.empty()) buttonRow->Add(new Button(button2_, new LinearLayoutParams(1.0f)))->OnClick.Handle(this, &UIScreen::OnCancel); defaultButton_ = buttonRow->Add(new Button(button1_, new LinearLayoutParams(1.0f))); defaultButton_->OnClick.Handle(this, &UIScreen::OnOK); #endif box_->Add(buttonRow); } }