Add really primitive save preview on "Next Slot". Improve pause screen layout a little.

This commit is contained in:
Henrik Rydgard 2015-02-01 18:04:06 +01:00
parent 6420f5f476
commit 001eda39e0
12 changed files with 142 additions and 55 deletions

View File

@ -18,6 +18,7 @@
#include <vector>
#include "base/timeutil.h"
#include "base/NativeApp.h"
#include "i18n/i18n.h"
#include "Common/StdMutex.h"
@ -308,6 +309,11 @@ namespace SaveState
}
}
int GetCurrentSlot()
{
return g_Config.iCurrentStateSlot;
}
void NextSlot()
{
I18NCategory *sy = GetI18NCategory("System");
@ -315,6 +321,7 @@ namespace SaveState
char msg[128];
snprintf(msg, sizeof(msg), "%s: %d", sy->T("Savestate Slot"), g_Config.iCurrentStateSlot + 1);
osm.Show(msg);
NativeMessageReceived("slotchanged", "");
}
void LoadSlot(int slot, Callback callback, void *cbUserData)

View File

@ -38,6 +38,9 @@ namespace SaveState
// Checks whether there's an existing save in the specified slot.
bool HasSaveInSlot(int slot);
bool HasScreenshotInSlot(int slot);
int GetCurrentSlot();
// Returns -1 if there's no newest slot.
int GetNewestSlot();

View File

@ -16,9 +16,11 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include "android/app-android.h"
#include "base/display.h"
#include "base/logging.h"
#include "base/timeutil.h"
#include "gfx_es2/glsl_program.h"
#include "gfx_es2/gl_state.h"
@ -63,7 +65,7 @@
#include "UI/InstallZipScreen.h"
EmuScreen::EmuScreen(const std::string &filename)
: bootPending_(true), gamePath_(filename), invalid_(true), quit_(false), pauseTrigger_(false) {
: bootPending_(true), gamePath_(filename), invalid_(true), quit_(false), pauseTrigger_(false), saveStatePreviewShownTime_(0.0), saveStatePreview_(nullptr) {
memset(axisState_, 0, sizeof(axisState_));
}
@ -242,6 +244,22 @@ void EmuScreen::sendMessage(const char *message, const char *value) {
} else {
gstate_c.skipDrawReason &= ~SKIPDRAW_WINDOW_MINIMIZED;
}
} else if (!strcmp(message, "slotchanged")) {
if (saveStatePreview_) {
int curSlot = SaveState::GetCurrentSlot();
std::string fn;
if (SaveState::HasSaveInSlot(curSlot)) {
fn = SaveState::GenerateSaveSlotFilename(curSlot, "jpg");
}
saveStatePreview_->SetFilename(fn);
if (!fn.empty()) {
saveStatePreview_->SetVisibility(UI::V_VISIBLE);
saveStatePreviewShownTime_ = time_now_d();
} else {
saveStatePreview_->SetVisibility(UI::V_GONE);
}
}
}
}
@ -561,13 +579,19 @@ void EmuScreen::processAxis(const AxisInput &axis, int direction) {
}
void EmuScreen::CreateViews() {
using namespace UI;
const Bounds &bounds = screenManager()->getUIContext()->GetBounds();
InitPadLayout(bounds.w, bounds.h);
root_ = CreatePadLayout(bounds.w, bounds.h, &pauseTrigger_);
if (g_Config.bShowDeveloperMenu) {
root_->Add(new UI::Button("DevMenu"))->OnClick.Handle(this, &EmuScreen::OnDevTools);
root_->Add(new Button("DevMenu"))->OnClick.Handle(this, &EmuScreen::OnDevTools);
}
root_->Add(new OnScreenMessagesView(new UI::AnchorLayoutParams((UI::Size)bounds.w, (UI::Size)bounds.h)));
saveStatePreview_ = new AsyncImageFileView("", IS_FIXED, nullptr, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, true));
saveStatePreview_->SetFixedSize(160, 90);
saveStatePreview_->SetColor(0x90FFFFFF);
saveStatePreview_->SetVisibility(V_GONE);
root_->Add(saveStatePreview_);
root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h)));
}
UI::EventReturn EmuScreen::OnDevTools(UI::EventParams &params) {
@ -668,6 +692,10 @@ void EmuScreen::update(InputState &input) {
pauseTrigger_ = false;
screenManager()->push(new GamePauseScreen(gamePath_));
}
if (time_now_d() - saveStatePreviewShownTime_ > 2 && saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {
saveStatePreview_->SetVisibility(UI::V_GONE);
}
}
void EmuScreen::checkPowerDown() {

View File

@ -28,6 +28,8 @@
struct AxisInput;
class AsyncImageFileView;
class EmuScreen : public UIScreen {
public:
EmuScreen(const std::string &filename);
@ -82,4 +84,7 @@ private:
// De-noise mapped axis updates
int axisState_[JOYSTICK_AXIS_MAX];
double saveStatePreviewShownTime_;
AsyncImageFileView *saveStatePreview_;
};

View File

@ -343,8 +343,8 @@ void GameSettingsScreen::CreateViews() {
#ifdef _WIN32
if (IsVistaOrHigher()) {
static const char *backend[] = { "Auto", "DirectSound (compatible)", "WASAPI (fast)" };
PopupMultiChoice *audioBackend = audioSettings->Add(new PopupMultiChoice(&g_Config.iAudioBackend, a->T("Audio backend", "Audio backend (change requires restart)"), backend, 0, ARRAY_SIZE(backend), a, screenManager()));
static const char *backend[] = { "Auto", "DSound (compatible)", "WASAPI (fast)" };
PopupMultiChoice *audioBackend = audioSettings->Add(new PopupMultiChoice(&g_Config.iAudioBackend, a->T("Audio backend", "Audio backend (restart req.)"), backend, 0, ARRAY_SIZE(backend), a, screenManager()));
audioBackend->SetEnabledPtr(&g_Config.bEnableSound);
}
#endif
@ -354,7 +354,7 @@ void GameSettingsScreen::CreateViews() {
lowAudio->SetEnabledPtr(&g_Config.bEnableSound);
if (System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE) == 44100) {
CheckBox *resampling = audioSettings->Add(new CheckBox(&g_Config.bAudioResampler, a->T("Audio sync", "Audio sync (using resampling)")));
CheckBox *resampling = audioSettings->Add(new CheckBox(&g_Config.bAudioResampler, a->T("Audio sync", "Audio sync (resampling)")));
resampling->SetEnabledPtr(&g_Config.bEnableSound);
}

View File

@ -728,15 +728,6 @@ void NativeRender() {
TakeScreenshot();
}
if (resized) {
resized = false;
if (g_Config.iGPUBackend == GPU_BACKEND_DIRECT3D9) {
#ifdef _WIN32
D3D9_Resize(0);
#endif
}
}
thin3d->SetScissorEnabled(false);
if (g_Config.iGPUBackend == GPU_BACKEND_OPENGL) {
glstate.depthWrite.set(GL_TRUE);
@ -747,6 +738,15 @@ void NativeRender() {
DX9::dxstate.colorMask.set(true, true, true, true);
#endif
}
if (resized) {
resized = false;
if (g_Config.iGPUBackend == GPU_BACKEND_DIRECT3D9) {
#ifdef _WIN32
D3D9_Resize(0);
#endif
}
}
}
void HandleGlobalMessage(const std::string &msg, const std::string &value) {

View File

@ -42,39 +42,16 @@
#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::Clickable {
public:
AsyncImageFileView(const std::string &filename, UI::ImageSizeMode sizeMode, PrioritizedWorkQueue *wq, UI::LayoutParams *layoutParams = 0)
: UI::Clickable(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; }
const std::string &GetFilename() const { return filename_; }
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_) {
float texw = (float)texture_->Width();
float texh = (float)texture_->Height();
switch (sizeMode_) {
case UI::IS_FIXED:
w = fixedSizeW_;
h = fixedSizeH_;
break;
case UI::IS_DEFAULT:
default:
w = texw;
@ -87,8 +64,19 @@ void AsyncImageFileView::GetContentDimensions(const UIContext &dc, float &w, flo
}
}
void AsyncImageFileView::SetFilename(std::string filename) {
if (filename_ != filename) {
textureFailed_ = false;
filename_ = filename;
if (texture_) {
texture_->Release();
texture_ = nullptr;
}
}
}
void AsyncImageFileView::Draw(UIContext &dc) {
if (!texture_ && !textureFailed_) {
if (!texture_ && !textureFailed_ && !filename_.empty()) {
texture_ = dc.GetThin3DContext()->CreateTextureFromFile(filename_.c_str(), DETECT);
if (!texture_)
textureFailed_ = true;
@ -99,18 +87,29 @@ void AsyncImageFileView::Draw(UIContext &dc) {
dc.Flush();
dc.GetThin3DContext()->SetTexture(0, texture_);
dc.Draw()->Rect(bounds_.x, bounds_.y, bounds_.w, bounds_.h, color_);
if (!text_.empty()) {
dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER | FLAG_DYNAMIC_ASCII);
}
dc.Flush();
dc.RebindTexture();
} else {
// draw a dark gray rectangle to represent the texture.
dc.FillRect(UI::Drawable(0x50202020), GetBounds());
if (!filename_.empty()) {
// draw a black rectangle to represent the missing screenshot.
dc.FillRect(UI::Drawable(0xFF000000), GetBounds());
} else {
// draw a dark gray rectangle to represent no save state.
dc.FillRect(UI::Drawable(0x50202020), GetBounds());
}
if (!text_.empty()) {
dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER | FLAG_DYNAMIC_ASCII);
}
}
}
class ScreenshotViewScreen : public PopupScreen {
public:
ScreenshotViewScreen(std::string filename, std::string title, int slot, I18NCategory *i18n)
: PopupScreen(title, i18n->T("Load State"), i18n->T("Back")), filename_(filename), slot_(slot) {}
: PopupScreen(title, i18n->T("Load State"), "Back"), filename_(filename), slot_(slot) {} // PopupScreen will translate Back on its own
int GetSlot() const {
return slot_;
@ -137,22 +136,31 @@ private:
class SaveSlotView : public UI::LinearLayout {
public:
SaveSlotView(int slot, UI::LayoutParams *layoutParams = nullptr) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), slot_(slot) {
using namespace UI;
screenshotFilename_ = SaveState::GenerateSaveSlotFilename(slot, "jpg");
PrioritizedWorkQueue *wq = g_gameInfoCache.WorkQueue();
Add(new UI::Spacer(10));
Add(new UI::TextView(StringFromFormat("%i", slot_ + 1), 0, false, new UI::LinearLayoutParams(0.0, UI::G_CENTER)));
AsyncImageFileView *fv = Add(new AsyncImageFileView(screenshotFilename_, UI::IS_DEFAULT, wq, new UI::LayoutParams(80 * 2, 45 * 2)));
AsyncImageFileView *fv = Add(new AsyncImageFileView(screenshotFilename_, IS_DEFAULT, wq, new UI::LayoutParams(82 * 2, 47 * 2)));
fv->SetOverlayText(StringFromFormat("%i", slot_ + 1));
I18NCategory *i = GetI18NCategory("Pause");
saveStateButton_ = Add(new UI::Button(i->T("Save State"), new UI::LinearLayoutParams(0.0, UI::G_VCENTER)));
LinearLayout *buttons = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
buttons->SetSpacing(2.0);
Add(buttons);
saveStateButton_ = buttons->Add(new Button(i->T("Save State"), new LinearLayoutParams(0.0, 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_ = buttons->Add(new Button(i->T("Load State"), new LinearLayoutParams(0.0, G_VCENTER)));
loadStateButton_->OnClick.Handle(this, &SaveSlotView::OnLoadState);
fv->OnClick.Handle(this, &SaveSlotView::OnScreenshotClick);
} else {
fv->SetFilename("");
}
}
@ -162,7 +170,7 @@ public:
void Draw(UIContext &dc) {
if (g_Config.iCurrentStateSlot == slot_) {
dc.FillRect(UI::Drawable(0x40FFFFFF), GetBounds());
dc.FillRect(UI::Drawable(0x70FFFFFF), GetBounds().Expand(3));
}
UI::LinearLayout::Draw(dc);
}
@ -245,11 +253,9 @@ void GamePauseScreen::CreateViews() {
root_ = new LinearLayout(ORIENT_HORIZONTAL);
ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(520, FILL_PARENT, actionMenuMargins));
ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0, actionMenuMargins));
root_->Add(leftColumn);
root_->Add(new Spacer(new LinearLayoutParams(1.0)));
ViewGroup *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
leftColumn->Add(leftColumnItems);

View File

@ -59,3 +59,38 @@ private:
// hack
bool finishNextFrame_;
};
class PrioritizedWorkQueue;
// 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::Clickable {
public:
AsyncImageFileView(const std::string &filename, UI::ImageSizeMode sizeMode, PrioritizedWorkQueue *wq, UI::LayoutParams *layoutParams = 0)
: UI::Clickable(layoutParams), filename_(filename), color_(0xFFFFFFFF), sizeMode_(sizeMode), texture_(NULL), textureFailed_(false), fixedSizeW_(0.0f), fixedSizeH_(0.0f) {}
~AsyncImageFileView() {
delete texture_;
}
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
void Draw(UIContext &dc) override;
void SetFilename(std::string filename);
void SetTexture(Thin3DTexture *texture) { texture_ = texture; }
void SetColor(uint32_t color) { color_ = color; }
void SetOverlayText(std::string text) { text_ = text; }
void SetFixedSize(float fixW, float fixH) { fixedSizeW_ = fixW; fixedSizeH_ = fixH; }
const std::string &GetFilename() const { return filename_; }
private:
std::string filename_;
std::string text_;
uint32_t color_;
UI::ImageSizeMode sizeMode_;
Thin3DTexture *texture_;
bool textureFailed_;
float fixedSizeW_;
float fixedSizeH_;
};

View File

@ -210,6 +210,7 @@ void D3D9_Resize(HWND window) {
HRESULT hr = device->Reset(&pp);
if (FAILED(hr)) {
ERROR_LOG_REPORT(G3D, "Unable to reset device: %s", DXGetErrorStringA(hr));
PanicAlert("Unable to reset D3D9 device: %s", DXGetErrorStringA(hr));
}
DX9::fbo_init(d3d);
}

View File

@ -72,6 +72,7 @@ void GL_SwapBuffers() { }
void NativeUpdate(InputState &input_state) { }
void NativeRender() { }
void NativeResized() { }
void NativeMessageReceived(const char *message, const char *value) {}
std::string System_GetProperty(SystemProperty prop) { return ""; }
int System_GetPropertyInt(SystemProperty prop) { return -1; }

2
native

@ -1 +1 @@
Subproject commit a72858d15e3678051787738076912b11e33982b9
Subproject commit 5becb9a4be8f6e18b102de4eafe902284e9faded

View File

@ -46,6 +46,7 @@
std::string System_GetProperty(SystemProperty prop) { return ""; }
int System_GetPropertyInt(SystemProperty prop) { return -1; }
void NativeMessageReceived(const char *message, const char *value) {}
#define M_PI_2 1.57079632679489661923