Skip to content

Commit

Permalink
First attempt at save state UI with screenshot thumbnails in the paus…
Browse files Browse the repository at this point in the history
…e screen
  • Loading branch information
hrydgard committed Dec 31, 2014
1 parent 240ebcb commit 6879f05
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 45 deletions.
2 changes: 2 additions & 0 deletions UI/GameInfoCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ class GameInfoCache {
void Save();
void Load();

PrioritizedWorkQueue *WorkQueue() { return gameInfoWQ_; }

private:
void SetupTexture(GameInfo *info, std::string &textureData, Thin3DContext *thin3d, Thin3DTexture *&tex, double &loadTime);

Expand Down
171 changes: 131 additions & 40 deletions UI/PauseScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "i18n/i18n.h"
#include "ui/view.h"
#include "ui/viewgroup.h"
#include "thin3d/thin3d.h"

#include "Core/Reporting.h"
#include "Core/SaveState.h"
Expand All @@ -37,16 +38,132 @@
#include "UI/MainScreen.h"
#include "UI/GameInfoCache.h"

#include "gfx_es2/draw_buffer.h"
#include "ui/ui_context.h"

// TextureView takes a texture that is assumed to be alive during the lifetime
// of the view. TODO: Actually make async using the task.
class AsyncImageFileView : public UI::InertView {
public:
AsyncImageFileView(const std::string &filename, UI::ImageSizeMode sizeMode, PrioritizedWorkQueue *wq, UI::LayoutParams *layoutParams = 0)
: UI::InertView(layoutParams), filename_(filename), color_(0xFFFFFFFF), sizeMode_(sizeMode), texture_(NULL), textureFailed_(false) {}
~AsyncImageFileView() {
delete texture_;
}

void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
void Draw(UIContext &dc) override;

void SetTexture(Thin3DTexture *texture) { texture_ = texture; }
void SetColor(uint32_t color) { color_ = color; }

private:
std::string filename_;
uint32_t color_;
UI::ImageSizeMode sizeMode_;

Thin3DTexture *texture_;
bool textureFailed_;
};

void AsyncImageFileView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
// TODO: involve sizemode
if (texture_) {
w = (float)texture_->Width();
h = (float)texture_->Height();
} else {
w = 16;
h = 16;
}
}

void AsyncImageFileView::Draw(UIContext &dc) {
if (!texture_ && !textureFailed_) {
texture_ = dc.GetThin3DContext()->CreateTextureFromFile(filename_.c_str(), DETECT);
if (!texture_)
textureFailed_ = true;
}

// TODO: involve sizemode
if (texture_) {
dc.Flush();
dc.GetThin3DContext()->SetTexture(0, texture_);
dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
dc.Flush();
dc.RebindTexture();
} else {
// draw a dark gray rectangle to represent the texture.
dc.FillRect(UI::Drawable(0x50202020), GetBounds());
}
}

class SaveSlotView : public UI::LinearLayout {
public:
SaveSlotView(int slot, UI::LayoutParams *layoutParams = nullptr) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), slot_(slot) {
std::string filename = SaveState::GenerateSaveSlotFilename(slot, "jpg");
PrioritizedWorkQueue *wq = g_gameInfoCache.WorkQueue();
Add(new UI::TextView(StringFromFormat("%i", slot_ + 1), 0, false, new UI::LinearLayoutParams(0.0, UI::G_CENTER)));
Add(new AsyncImageFileView(filename, UI::IS_DEFAULT, wq, new UI::LayoutParams(80 * 2, 45 * 2)));

I18NCategory *i = GetI18NCategory("Pause");

saveStateButton_ = Add(new UI::Button(i->T("Save State"), new UI::LinearLayoutParams(0.0, UI::G_VCENTER)));
saveStateButton_->OnClick.Handle(this, &SaveSlotView::OnSaveState);

if (SaveState::HasSaveInSlot(slot)) {
loadStateButton_ = Add(new UI::Button(i->T("Load State"), new UI::LinearLayoutParams(0.0, UI::G_VCENTER)));
loadStateButton_->OnClick.Handle(this, &SaveSlotView::OnLoadState);
}
}

void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
w = 500; h = 90;
}

void Draw(UIContext &dc) {
if (g_Config.iCurrentStateSlot == slot_) {
dc.FillRect(UI::Drawable(0x40FFFFFF), GetBounds());
}
UI::LinearLayout::Draw(dc);
}

UI::Event OnStateLoaded;
UI::Event OnStateSaved;

private:
UI::EventReturn OnSaveState(UI::EventParams &e);
UI::EventReturn OnLoadState(UI::EventParams &e);

UI::Button *saveStateButton_;
UI::Button *loadStateButton_;

Thin3DTexture *texture_;
int slot_;
};


UI::EventReturn SaveSlotView::OnLoadState(UI::EventParams &e) {
g_Config.iCurrentStateSlot = slot_;
SaveState::LoadSlot(slot_, SaveState::Callback(), 0);
UI::EventParams e2;
OnStateLoaded.Trigger(e2);
return UI::EVENT_DONE;
}

UI::EventReturn SaveSlotView::OnSaveState(UI::EventParams &e) {
g_Config.iCurrentStateSlot = slot_;
SaveState::SaveSlot(slot_, SaveState::Callback(), 0);
UI::EventParams e2;
OnStateSaved.Trigger(e2);
return UI::EVENT_DONE;
}

void GamePauseScreen::update(InputState &input) {
UpdateUIState(UISTATE_PAUSEMENU);
UIScreen::update(input);
}

GamePauseScreen::~GamePauseScreen() {
if (saveSlots_ != NULL) {
g_Config.iCurrentStateSlot = saveSlots_->GetSelection();
g_Config.Save();
}
__DisplaySetWasPaused();
}

Expand All @@ -60,33 +177,20 @@ void GamePauseScreen::CreateViews() {

root_ = new LinearLayout(ORIENT_HORIZONTAL);

ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(510, FILL_PARENT, actionMenuMargins));
root_->Add(leftColumn);

root_->Add(new Spacer(new LinearLayoutParams(1.0)));

ViewGroup *leftColumnItems = new LinearLayout(ORIENT_VERTICAL);
ViewGroup *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
leftColumn->Add(leftColumnItems);

saveSlots_ = leftColumnItems->Add(new ChoiceStrip(ORIENT_HORIZONTAL, new LinearLayoutParams(300, WRAP_CONTENT)));
for (int i = 0; i < NUM_SAVESLOTS; i++) {
std::stringstream saveSlotText;
saveSlotText << " " << i + 1 << " ";
saveSlots_->AddChoice(saveSlotText.str());
if (SaveState::HasSaveInSlot(i)) {
saveSlots_->HighlightChoice(i);
}
SaveSlotView *slot = leftColumnItems->Add(new SaveSlotView(i, new LayoutParams(FILL_PARENT, WRAP_CONTENT)));
slot->OnStateLoaded.Handle(this, &GamePauseScreen::OnState);
slot->OnStateSaved.Handle(this, &GamePauseScreen::OnState);
}

saveSlots_->SetSelection(g_Config.iCurrentStateSlot);
saveSlots_->OnChoice.Handle(this, &GamePauseScreen::OnStateSelected);

saveStateButton_ = leftColumnItems->Add(new Choice(i->T("Save State")));
saveStateButton_->OnClick.Handle(this, &GamePauseScreen::OnSaveState);

loadStateButton_ = leftColumnItems->Add(new Choice(i->T("Load State")));
loadStateButton_->OnClick.Handle(this, &GamePauseScreen::OnLoadState);

if (g_Config.iRewindFlipFrequency > 0) {
UI::Choice *rewindButton = leftColumnItems->Add(new Choice(i->T("Rewind")));
rewindButton->SetEnabled(SaveState::CanRewind());
Expand Down Expand Up @@ -127,10 +231,6 @@ void GamePauseScreen::CreateViews() {
}
rightColumnItems->Add(new Spacer(25.0));
rightColumnItems->Add(new Choice(i->T("Exit to menu")))->OnClick.Handle(this, &GamePauseScreen::OnExitToMenu);

UI::EventParams e;
e.a = g_Config.iCurrentStateSlot;
saveSlots_->OnChoice.Trigger(e);
}

UI::EventReturn GamePauseScreen::OnGameSettings(UI::EventParams &e) {
Expand All @@ -151,6 +251,11 @@ void GamePauseScreen::onFinish(DialogResult result) {
Reporting::UpdateConfig();
}

UI::EventReturn GamePauseScreen::OnState(UI::EventParams &e) {
screenManager()->finishDialog(this, DR_CANCEL);
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnExitToMenu(UI::EventParams &e) {
screenManager()->finishDialog(this, DR_OK);
return UI::EVENT_DONE;
Expand All @@ -161,20 +266,6 @@ UI::EventReturn GamePauseScreen::OnReportFeedback(UI::EventParams &e) {
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnLoadState(UI::EventParams &e) {
SaveState::LoadSlot(saveSlots_->GetSelection(), SaveState::Callback(), 0);

screenManager()->finishDialog(this, DR_CANCEL);
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnSaveState(UI::EventParams &e) {
SaveState::SaveSlot(saveSlots_->GetSelection(), SaveState::Callback(), 0);

screenManager()->finishDialog(this, DR_CANCEL);
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnRewind(UI::EventParams &e) {
SaveState::Rewind(SaveState::Callback(), 0);

Expand Down
6 changes: 2 additions & 4 deletions UI/PauseScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

class GamePauseScreen : public UIDialogScreenWithGameBackground {
public:
GamePauseScreen(const std::string &filename) : UIDialogScreenWithGameBackground(filename), saveSlots_(NULL) {}
GamePauseScreen(const std::string &filename) : UIDialogScreenWithGameBackground(filename) {}
virtual ~GamePauseScreen();

virtual void onFinish(DialogResult result);
Expand All @@ -41,8 +41,6 @@ class GamePauseScreen : public UIDialogScreenWithGameBackground {
UI::EventReturn OnExitToMenu(UI::EventParams &e);
UI::EventReturn OnReportFeedback(UI::EventParams &e);

UI::EventReturn OnSaveState(UI::EventParams &e);
UI::EventReturn OnLoadState(UI::EventParams &e);
UI::EventReturn OnRewind(UI::EventParams &e);

UI::EventReturn OnStateSelected(UI::EventParams &e);
Expand All @@ -52,8 +50,8 @@ class GamePauseScreen : public UIDialogScreenWithGameBackground {
UI::EventReturn OnDeleteConfig(UI::EventParams &e);

UI::EventReturn OnSwitchUMD(UI::EventParams &e);
UI::EventReturn OnState(UI::EventParams &e);

UI::ChoiceStrip *saveSlots_;
UI::Choice *saveStateButton_;
UI::Choice *loadStateButton_;
};
2 changes: 1 addition & 1 deletion native

5 comments on commit 6879f05

@hrydgard
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example screenshot:

savestate

@chinhodado
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks nice! Although I think the numbers are too close to the left edge of the window. Some paddings would be nice.

@unknownbrackets
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few thoughts, although I have nothing strong:

  1. To reduce clutter, we could soften the slots > 1 that have nothing in them yet. The initial experience (no slots yet) puts a lot of buttons in your face.
  2. We could overlay the numbers on the screenshots with a shadow / outline. I think this would look nice and not block out much (if kept to the side), and look nicer than at the edge. Otherwise, padding is needed on the left.
  3. Also, the scrollbar is less than ideal (at least as seen at 1x.) Below are rough screenshots. Probably the buttons are too small...

Horizontal:
savestates-thoughts-1

Initial view (not great, maybe compact image height if there are no savestates yet?):
savestates-thoughts-2

-[Unknown]

@unknownbrackets
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also try to overlay the screenshot with an age or put it below / near the buttons, though that would increase clutter... e.g. "1 day", "15 seconds", "4 hours" etc.

-[Unknown]

@i30817
Copy link

@i30817 i30817 commented on 6879f05 Jan 1, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A tooltip or dedicated area on selection would be enough for that,

Please sign in to comment.