Merge pull request #10247 from unknownbrackets/ui-tween

UI: Show a loading message during shader preload
This commit is contained in:
Henrik Rydgård 2017-12-06 14:24:21 +01:00 committed by GitHub
commit cf5ede493b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 464 additions and 106 deletions

View File

@ -959,6 +959,8 @@ add_library(native STATIC
ext/native/ui/ui_context.h
ext/native/ui/ui_screen.cpp
ext/native/ui/ui_screen.h
ext/native/ui/ui_tween.cpp
ext/native/ui/ui_tween.h
ext/native/ui/view.cpp
ext/native/ui/view.h
ext/native/ui/viewgroup.cpp

View File

@ -436,16 +436,16 @@ bool PSP_InitUpdate(std::string *error_string) {
bool success = coreParameter.fileToStart != "";
*error_string = coreParameter.errorString;
if (success) {
if (success && gpu == nullptr) {
success = GPU_Init(coreParameter.graphicsContext, coreParameter.thin3d);
if (!success) {
PSP_Shutdown();
*error_string = "Unable to initialize rendering engine.";
}
}
pspIsInited = success;
pspIsIniting = false;
return true;
pspIsInited = success && GPU_IsReady();
pspIsIniting = success && !pspIsInited;
return !success || pspIsInited;
}
bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) {

View File

@ -171,7 +171,8 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
if (discID.size()) {
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) + "/" + discID + ".glshadercache";
shaderManagerGL_->LoadAndPrecompile(shaderCachePath_);
// Actually precompiled by IsReady() since we're single-threaded.
shaderManagerGL_->Load(shaderCachePath_);
}
if (g_Config.bHardwareTessellation) {
@ -350,6 +351,10 @@ void GPU_GLES::CheckGPUFeatures() {
gstate_c.featureFlags = features;
}
bool GPU_GLES::IsReady() {
return shaderManagerGL_->ContinuePrecompile();
}
// Let's avoid passing nulls into snprintf().
static const char *GetGLStringAlways(GLenum name) {
const GLubyte *value = glGetString(name);

View File

@ -38,6 +38,8 @@ public:
// This gets called on startup and when we get back from settings.
void CheckGPUFeatures();
bool IsReady() override;
void PreExecuteOp(u32 op, u32 diff) override;
void ExecuteOp(u32 op, u32 diff) override;

View File

@ -955,7 +955,7 @@ struct CacheHeader {
int numLinkedPrograms;
};
void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
void ShaderManagerGLES::Load(const std::string &filename) {
File::IOFile f(filename, "rb");
u64 sz = f.GetSize();
if (!f.IsOpen()) {
@ -969,7 +969,8 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
return;
}
time_update();
double start = time_now_d();
diskCachePending_.start = time_now_d();
diskCachePending_.Clear();
// Sanity check the file contents
if (header.numFragmentShaders > 1000 || header.numVertexShaders > 1000 || header.numLinkedPrograms > 1000) {
@ -987,42 +988,19 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
return;
}
for (int i = 0; i < header.numVertexShaders; i++) {
VShaderID id;
if (!f.ReadArray(&id, 1)) {
return;
}
if (!vsCache_.Get(id)) {
if (id.Bit(VS_BIT_IS_THROUGH) && id.Bit(VS_BIT_USE_HW_TRANSFORM)) {
// Clearly corrupt, bailing.
ERROR_LOG_REPORT(G3D, "Corrupt shader cache: Both IS_THROUGH and USE_HW_TRANSFORM set.");
return;
}
diskCachePending_.vert.resize(header.numVertexShaders);
if (!f.ReadArray(&diskCachePending_.vert[0], header.numVertexShaders)) {
diskCachePending_.vert.clear();
return;
}
Shader *vs = CompileVertexShader(id);
if (vs->Failed()) {
// Give up on using the cache, just bail. We can't safely create the fallback shaders here
// without trying to deduce the vertType from the VSID.
ERROR_LOG(G3D, "Failed to compile a vertex shader loading from cache. Skipping rest of shader cache.");
delete vs;
return;
}
vsCache_.Insert(id, vs);
} else {
WARN_LOG(G3D, "Duplicate vertex shader found in GL shader cache, ignoring");
}
}
for (int i = 0; i < header.numFragmentShaders; i++) {
FShaderID id;
if (!f.ReadArray(&id, 1)) {
return;
}
if (!fsCache_.Get(id)) {
fsCache_.Insert(id, CompileFragmentShader(id));
} else {
WARN_LOG(G3D, "Duplicate fragment shader found in GL shader cache, ignoring");
}
diskCachePending_.frag.resize(header.numFragmentShaders);
if (!f.ReadArray(&diskCachePending_.frag[0], header.numFragmentShaders)) {
diskCachePending_.vert.clear();
diskCachePending_.frag.clear();
return;
}
for (int i = 0; i < header.numLinkedPrograms; i++) {
VShaderID vsid;
FShaderID fsid;
@ -1032,6 +1010,76 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
if (!f.ReadArray(&fsid, 1)) {
return;
}
diskCachePending_.link.push_back(std::make_pair(vsid, fsid));
}
// Actual compilation happens in ContinuePrecompile(), called by GPU_GLES's IsReady.
NOTICE_LOG(G3D, "Precompiling the shader cache from '%s'", filename.c_str());
diskCacheDirty_ = false;
}
bool ShaderManagerGLES::ContinuePrecompile(float sliceTime) {
auto &pending = diskCachePending_;
if (pending.Done()) {
return true;
}
double start = real_time_now();
// Let's try to keep it under sliceTime if possible.
double end = start + sliceTime;
for (size_t &i = pending.vertPos; i < pending.vert.size(); i++) {
if (real_time_now() >= end) {
// We'll finish later.
return false;
}
const VShaderID &id = pending.vert[i];
if (!vsCache_.Get(id)) {
if (id.Bit(VS_BIT_IS_THROUGH) && id.Bit(VS_BIT_USE_HW_TRANSFORM)) {
// Clearly corrupt, bailing.
ERROR_LOG_REPORT(G3D, "Corrupt shader cache: Both IS_THROUGH and USE_HW_TRANSFORM set.");
pending.Clear();
return false;
}
Shader *vs = CompileVertexShader(id);
if (vs->Failed()) {
// Give up on using the cache, just bail. We can't safely create the fallback shaders here
// without trying to deduce the vertType from the VSID.
ERROR_LOG(G3D, "Failed to compile a vertex shader loading from cache. Skipping rest of shader cache.");
delete vs;
pending.Clear();
return false;
}
vsCache_.Insert(id, vs);
} else {
WARN_LOG(G3D, "Duplicate vertex shader found in GL shader cache, ignoring");
}
}
for (size_t &i = pending.fragPos; i < pending.frag.size(); i++) {
if (real_time_now() >= end) {
// We'll finish later.
return false;
}
const FShaderID &id = pending.frag[i];
if (!fsCache_.Get(id)) {
fsCache_.Insert(id, CompileFragmentShader(id));
} else {
WARN_LOG(G3D, "Duplicate fragment shader found in GL shader cache, ignoring");
}
}
for (size_t &i = pending.linkPos; i < pending.link.size(); i++) {
if (real_time_now() >= end) {
// We'll finish later.
return false;
}
const VShaderID &vsid = pending.link[i].first;
const FShaderID &fsid = pending.link[i].second;
Shader *vs = vsCache_.Get(vsid);
Shader *fs = fsCache_.Get(fsid);
if (vs && fs) {
@ -1040,12 +1088,15 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
linkedShaderCache_.push_back(entry);
}
}
time_update();
double end = time_now_d();
NOTICE_LOG(G3D, "Compiled and linked %d programs (%d vertex, %d fragment) in %0.1f milliseconds", header.numLinkedPrograms, header.numVertexShaders, header.numFragmentShaders, 1000 * (end - start));
NOTICE_LOG(G3D, "Loaded the shader cache from '%s'", filename.c_str());
diskCacheDirty_ = false;
// Okay, finally done. Time to report status.
time_update();
double finish = time_now_d();
NOTICE_LOG(G3D, "Compiled and linked %d programs (%d vertex, %d fragment) in %0.1f milliseconds", (int)pending.link.size(), (int)pending.vert.size(), (int)pending.frag.size(), 1000 * (finish - pending.start));
pending.Clear();
return true;
}
void ShaderManagerGLES::Save(const std::string &filename) {

View File

@ -167,7 +167,8 @@ public:
std::vector<std::string> DebugGetShaderIDs(DebugShaderType type);
std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType);
void LoadAndPrecompile(const std::string &filename);
void Load(const std::string &filename);
bool ContinuePrecompile(float sliceTime = 1.0f / 60.0f);
void Save(const std::string &filename);
private:
@ -203,4 +204,27 @@ private:
VSCache vsCache_;
bool diskCacheDirty_;
struct {
std::vector<VShaderID> vert;
std::vector<FShaderID> frag;
std::vector<std::pair<VShaderID, FShaderID>> link;
size_t vertPos = 0;
size_t fragPos = 0;
size_t linkPos = 0;
double start;
void Clear() {
vert.clear();
frag.clear();
link.clear();
vertPos = 0;
fragPos = 0;
linkPos = 0;
}
bool Done() {
return vertPos >= vert.size() && fragPos >= frag.size() && linkPos >= link.size();
}
} diskCachePending_;
};

View File

@ -55,6 +55,12 @@ static void SetGPU(T *obj) {
#undef new
#endif
bool GPU_IsReady() {
if (gpu)
return gpu->IsReady();
return false;
}
bool GPU_Init(GraphicsContext *ctx, Draw::DrawContext *draw) {
#if PPSSPP_PLATFORM(UWP)
SetGPU(new GPU_D3D11(ctx, draw));

View File

@ -105,4 +105,5 @@ namespace Draw {
}
bool GPU_Init(GraphicsContext *ctx, Draw::DrawContext *thin3d);
bool GPU_IsReady();
void GPU_Shutdown();

View File

@ -70,6 +70,9 @@ public:
Draw::DrawContext *GetDrawContext() override {
return draw_;
}
bool IsReady() override {
return true;
}
void Reinitialize() override;
void BeginHostFrame() override;

View File

@ -168,6 +168,7 @@ public:
virtual Draw::DrawContext *GetDrawContext() = 0;
// Initialization
virtual bool IsReady() = 0;
virtual void InitClear() = 0;
virtual void Reinitialize() = 0;

View File

@ -28,8 +28,11 @@
#include "gfx_es2/draw_text.h"
#include "input/input_state.h"
#include "math/curves.h"
#include "ui/ui.h"
#include "ui/ui_context.h"
#include "ui/ui_tween.h"
#include "ui/view.h"
#include "i18n/i18n.h"
#include "Common/KeyMap.h"
@ -220,6 +223,9 @@ void EmuScreen::bootGame(const std::string &filename) {
I18NCategory *gr = GetI18NCategory("Graphics");
host->NotifyUserMessage(gr->T("BlockTransferRequired", "Warning: This game requires Simulate Block Transfer Mode to be set to On."), 15.0f);
}
loadingViewColor_->Divert(0xFFFFFFFF, 0.15f);
loadingViewVisible_->Divert(UI::V_VISIBLE, 0.15f);
}
void EmuScreen::bootComplete() {
@ -268,6 +274,9 @@ void EmuScreen::bootComplete() {
System_SendMessage("event", "startgame");
saveStateSlot_ = SaveState::GetCurrentSlot();
loadingViewColor_->Divert(0x00FFFFFF, 0.2f);
loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);
}
EmuScreen::~EmuScreen() {
@ -344,7 +353,7 @@ void EmuScreen::sendMessage(const char *message, const char *value) {
} else {
PSP_Shutdown();
bootPending_ = true;
bootGame(value);
gamePath_ = value;
}
} else if (!strcmp(message, "control mapping") && screenManager()->topScreen() == this) {
UpdateUIState(UISTATE_MENU);
@ -794,11 +803,15 @@ void EmuScreen::processAxis(const AxisInput &axis, int direction) {
void EmuScreen::CreateViews() {
using namespace UI;
I18NCategory *sc = GetI18NCategory("Screen");
I18NCategory *dev = GetI18NCategory("Developer");
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 Button("DevMenu"))->OnClick.Handle(this, &EmuScreen::OnDevTools);
root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools);
}
saveStatePreview_ = new AsyncImageFileView("", IS_FIXED, nullptr, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, true));
saveStatePreview_->SetFixedSize(160, 90);
@ -807,6 +820,13 @@ void EmuScreen::CreateViews() {
saveStatePreview_->SetCanBeFocused(false);
root_->Add(saveStatePreview_);
root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h)));
loadingView_ = new TextView(sc->T("Loading game..."), new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true));
root_->Add(loadingView_);
// We start invisible here, in case of recreated views.
loadingViewColor_ = loadingView_->AddTween(new TextColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut));
loadingViewVisible_ = loadingView_->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut));
}
UI::EventReturn EmuScreen::OnDevTools(UI::EventParams &params) {
@ -820,11 +840,11 @@ UI::EventReturn EmuScreen::OnDevTools(UI::EventParams &params) {
}
void EmuScreen::update() {
UIScreen::update();
if (bootPending_)
bootGame(gamePath_);
UIScreen::update();
// Simply forcibly update to the current screen size every frame. Doesn't cost much.
// If bounds is set to be smaller than the actual pixel resolution of the display, respect that.
// TODO: Should be able to use g_dpi_scale here instead. Might want to store the dpi scale in the UI context too.
@ -1026,6 +1046,7 @@ void EmuScreen::render() {
// In this case, we need to double check it here.
checkPowerDown();
thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR });
renderUI();
return;
}
@ -1072,47 +1093,10 @@ void EmuScreen::render() {
if (invalid_)
return;
const bool hasVisibleUI = !osm.IsEmpty() || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowTouchControls;
const bool hasVisibleUI = !osm.IsEmpty() || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowTouchControls || loadingView_->GetVisibility() == UI::V_VISIBLE;
const bool showDebugUI = g_Config.bShowDebugStats || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || g_Config.bShowFrameProfiler;
if (hasVisibleUI || showDebugUI || g_Config.iShowFPSCounter != 0) {
// This sets up some important states but not the viewport.
screenManager()->getUIContext()->Begin();
Viewport viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = pixel_xres;
viewport.Height = pixel_yres;
viewport.MaxDepth = 1.0;
viewport.MinDepth = 0.0;
thin3d->SetViewports(1, &viewport);
DrawBuffer *draw2d = screenManager()->getUIContext()->Draw();
if (root_) {
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_);
root_->Draw(*screenManager()->getUIContext());
}
if (g_Config.bShowDebugStats) {
DrawDebugStats(draw2d);
}
if (g_Config.bShowAudioDebug) {
DrawAudioDebugStats(draw2d);
}
if (g_Config.iShowFPSCounter) {
DrawFPS(draw2d, screenManager()->getUIContext()->GetBounds());
}
#ifdef USE_PROFILER
if (g_Config.bShowFrameProfiler) {
DrawProfile(*screenManager()->getUIContext());
}
#endif
screenManager()->getUIContext()->End();
renderUI();
}
// We have no use for backbuffer depth or stencil, so let tiled renderers discard them after tiling.
@ -1133,6 +1117,51 @@ void EmuScreen::render() {
*/
}
void EmuScreen::renderUI() {
using namespace Draw;
DrawContext *thin3d = screenManager()->getDrawContext();
// This sets up some important states but not the viewport.
screenManager()->getUIContext()->Begin();
Viewport viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = pixel_xres;
viewport.Height = pixel_yres;
viewport.MaxDepth = 1.0;
viewport.MinDepth = 0.0;
thin3d->SetViewports(1, &viewport);
DrawBuffer *draw2d = screenManager()->getUIContext()->Draw();
if (root_) {
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), root_);
root_->Draw(*screenManager()->getUIContext());
}
if (g_Config.bShowDebugStats && !invalid_) {
DrawDebugStats(draw2d);
}
if (g_Config.bShowAudioDebug && !invalid_) {
DrawAudioDebugStats(draw2d);
}
if (g_Config.iShowFPSCounter && !invalid_) {
DrawFPS(draw2d, screenManager()->getUIContext()->GetBounds());
}
#ifdef USE_PROFILER
if (g_Config.bShowFrameProfiler && !invalid_) {
DrawProfile(*screenManager()->getUIContext());
}
#endif
screenManager()->getUIContext()->End();
}
void EmuScreen::deviceLost() {
ILOG("EmuScreen::deviceLost()");
if (gpu)

View File

@ -24,6 +24,7 @@
#include "input/keycodes.h"
#include "ui/screen.h"
#include "ui/ui_screen.h"
#include "ui/ui_tween.h"
#include "Common/KeyMap.h"
struct AxisInput;
@ -56,6 +57,7 @@ protected:
private:
void bootGame(const std::string &filename);
void bootComplete();
void renderUI();
void processAxis(const AxisInput &axis, int direction);
void pspKey(int pspKeyCode, int flags);
@ -97,4 +99,8 @@ private:
double saveStatePreviewShownTime_;
AsyncImageFileView *saveStatePreview_;
int saveStateSlot_;
UI::View *loadingView_ = nullptr;
UI::TextColorTween *loadingViewColor_ = nullptr;
UI::VisibilityTween *loadingViewVisible_ = nullptr;
};

View File

@ -376,6 +376,7 @@
<ClInclude Include="..\..\ext\native\ui\ui.h" />
<ClInclude Include="..\..\ext\native\ui\ui_context.h" />
<ClInclude Include="..\..\ext\native\ui\ui_screen.h" />
<ClInclude Include="..\..\ext\native\ui\ui_tween.h" />
<ClInclude Include="..\..\ext\native\ui\view.h" />
<ClInclude Include="..\..\ext\native\ui\viewgroup.h" />
<ClInclude Include="..\..\ext\native\util\const_map.h" />
@ -1290,6 +1291,7 @@
<ClCompile Include="..\..\ext\native\ui\ui.cpp" />
<ClCompile Include="..\..\ext\native\ui\ui_context.cpp" />
<ClCompile Include="..\..\ext\native\ui\ui_screen.cpp" />
<ClCompile Include="..\..\ext\native\ui\ui_tween.cpp" />
<ClCompile Include="..\..\ext\native\ui\view.cpp" />
<ClCompile Include="..\..\ext\native\ui\viewgroup.cpp" />
<ClCompile Include="..\..\ext\native\util\hash\hash.cpp" />

View File

@ -154,6 +154,9 @@
<ClCompile Include="..\..\ext\native\ui\ui_screen.cpp">
<Filter>ui</Filter>
</ClCompile>
<ClCompile Include="..\..\ext\native\ui\ui_tween.cpp">
<Filter>ui</Filter>
</ClCompile>
<ClCompile Include="..\..\ext\native\ui\view.cpp">
<Filter>ui</Filter>
</ClCompile>
@ -590,6 +593,9 @@
<ClInclude Include="..\..\ext\native\ui\ui_screen.h">
<Filter>ui</Filter>
</ClInclude>
<ClInclude Include="..\..\ext\native\ui\ui_tween.h">
<Filter>ui</Filter>
</ClInclude>
<ClInclude Include="..\..\ext\native\ui\view.h">
<Filter>ui</Filter>
</ClInclude>

View File

@ -91,6 +91,7 @@ LOCAL_SRC_FILES :=\
ui/viewgroup.cpp \
ui/ui.cpp \
ui/ui_screen.cpp \
ui/ui_tween.cpp \
ui/ui_context.cpp \
ui/screen.cpp \
util/text/utf8.cpp \

View File

@ -1,5 +1,14 @@
#include "base/colorutil.h"
template <typename T>
static T clamp(T f, T low, T high) {
if (f < low)
return low;
if (f > high)
return high;
return f;
}
uint32_t whiteAlpha(float alpha) {
if (alpha < 0.0f) alpha = 0.0f;
if (alpha > 1.0f) alpha = 1.0f;
@ -20,6 +29,20 @@ uint32_t colorAlpha(uint32_t rgb, float alpha) {
return ((int)(alpha*255)<<24) | (rgb & 0xFFFFFF);
}
uint32_t colorBlend(uint32_t rgb1, uint32_t rgb2, float alpha) {
float invAlpha = (1.0f - alpha);
int r = (int)(((rgb1 >> 0) & 0xFF) * alpha + ((rgb2 >> 0) & 0xFF) * invAlpha);
int g = (int)(((rgb1 >> 8) & 0xFF) * alpha + ((rgb2 >> 8) & 0xFF) * invAlpha);
int b = (int)(((rgb1 >> 16) & 0xFF) * alpha + ((rgb2 >> 16) & 0xFF) * invAlpha);
int a = (int)(((rgb1 >> 24) & 0xFF) * alpha + ((rgb2 >> 24) & 0xFF) * invAlpha);
uint32_t c = clamp(a, 0, 255) << 24;
c |= clamp(b, 0, 255) << 16;
c |= clamp(g, 0, 255) << 8;
c |= clamp(r, 0, 255);
return c;
}
uint32_t alphaMul(uint32_t color, float alphaMul) {
uint32_t rgb = color & 0xFFFFFF;
int32_t alpha = color >> 24;
@ -38,17 +61,7 @@ uint32_t rgba(float r, float g, float b, float alpha) {
}
uint32_t rgba_clamp(float r, float g, float b, float a) {
if (r > 1.0f) r = 1.0f;
if (g > 1.0f) g = 1.0f;
if (b > 1.0f) b = 1.0f;
if (a > 1.0f) a = 1.0f;
if (r < 0.0f) r = 0.0f;
if (g < 0.0f) g = 0.0f;
if (b < 0.0f) b = 0.0f;
if (a < 0.0f) a = 0.0f;
return rgba(r,g,b,a);
return rgba(clamp(r, 0.0f, 1.0f), clamp(g, 0.0f, 1.0f), clamp(b, 0.0f, 1.0f), clamp(a, 0.0f, 1.0f));
}
/* hsv2rgb.c

View File

@ -285,6 +285,7 @@
<ClInclude Include="ui\ui.h" />
<ClInclude Include="ui\ui_context.h" />
<ClInclude Include="ui\ui_screen.h" />
<ClInclude Include="ui\ui_tween.h" />
<ClInclude Include="ui\view.h" />
<ClInclude Include="ui\viewgroup.h" />
<ClInclude Include="util\random\rng.h" />
@ -752,6 +753,7 @@
<ClCompile Include="ui\ui.cpp" />
<ClCompile Include="ui\ui_context.cpp" />
<ClCompile Include="ui\ui_screen.cpp" />
<ClCompile Include="ui\ui_tween.cpp" />
<ClCompile Include="ui\view.cpp" />
<ClCompile Include="ui\viewgroup.cpp" />
<ClCompile Include="util\hash\hash.cpp" />

View File

@ -329,6 +329,9 @@
<ClInclude Include="thin3d\DataFormat.h">
<Filter>thin3d</Filter>
</ClInclude>
<ClInclude Include="ui\ui_tween.h">
<Filter>ui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gfx\gl_debug_log.cpp">
@ -796,6 +799,9 @@
<ClCompile Include="thin3d\VulkanRenderManager.cpp">
<Filter>thin3d</Filter>
</ClCompile>
<ClCompile Include="ui\ui_tween.cpp">
<Filter>ui</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="gfx">

View File

@ -0,0 +1,38 @@
#include "base/colorutil.h"
#include "ui/ui_tween.h"
#include "ui/view.h"
namespace UI {
void Tween::Apply(View *view) {
if (time_now() >= start_ + duration_)
finishApplied_ = true;
float pos = Position();
DoApply(view, pos);
}
uint32_t ColorTween::Current(float pos) {
return colorBlend(to_, from_, pos);
}
void TextColorTween::DoApply(View *view, float pos) {
// TODO: No validation without RTTI?
TextView *tv = (TextView *)view;
tv->SetTextColor(Current(pos));
}
void VisibilityTween::DoApply(View *view, float pos) {
view->SetVisibility(Current(pos));
}
Visibility VisibilityTween::Current(float p) {
// Prefer V_VISIBLE over V_GONE/V_INVISIBLE.
if (from_ == V_VISIBLE && p < 1.0f)
return from_;
if (to_ == V_VISIBLE && p > 0.0f)
return to_;
return p >= 1.0f ? to_ : from_;
}
} // namespace

139
ext/native/ui/ui_tween.h Normal file
View File

@ -0,0 +1,139 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include "base/timeutil.h"
#include "ui/view.h"
namespace UI {
// This is the class to use in Update().
class Tween {
public:
Tween(float duration, float (*curve)(float)) : duration_(duration), curve_(curve) {
start_ = time_now();
}
virtual ~Tween() {
}
// Actually apply the tween to a view.
void Apply(View *view);
bool Finished() {
return finishApplied_ && time_now() >= start_ + duration_;
}
protected:
float DurationOffset() {
return time_now() - start_;
}
float Position() {
return curve_(std::min(1.0f, DurationOffset() / duration_));
}
virtual void DoApply(View *view, float pos) = 0;
float start_;
float duration_;
bool finishApplied_ = false;
float (*curve_)(float);
};
// This is the class all tweens inherit from. Shouldn't be used directly, see below.
template <typename Value>
class TweenBase: public Tween {
public:
TweenBase(Value from, Value to, float duration, float (*curve)(float) = [](float f) { return f; })
: Tween(duration, curve), from_(from), to_(to) {
}
// Use this to change the destination value.
// Useful when a state flips while the tween is half-way through.
void Divert(const Value &newTo, float newDuration = -1.0f) {
const Value newFrom = Current(Position());
// Are we already part way through another transition?
if (time_now() < start_ + duration_) {
if (newTo == to_) {
// Already on course. Don't change.
} else if (newTo == from_) {
// Reversing, adjust start_ to be smooth from the current value.
float newOffset = duration_ - DurationOffset();
if (newDuration >= 0.0f && duration_ > 0.0f) {
newOffset *= newDuration / duration_;
}
start_ = time_now() - newOffset;
} else {
// Otherwise, start over.
start_ = time_now();
}
} else {
// Already finished, so restart.
start_ = time_now();
finishApplied_ = false;
}
from_ = newFrom;
to_ = newTo;
if (newDuration >= 0.0f) {
duration_ = newDuration;
}
}
// Stop animating the value.
void Stop() {
Reset(Current(Position()));
}
// Use when the value is explicitly reset. Implicitly stops the tween.
void Reset(const Value &newFrom) {
from_ = newFrom;
to_ = newFrom;
}
const Value &FromValue() const {
return from_;
}
const Value &ToValue() const {
return to_;
}
Value CurrentValue() {
return Current(Position());
}
protected:
virtual Value Current(float pos) = 0;
Value from_;
Value to_;
};
// Generic - subclass this for specific color handling.
class ColorTween : public TweenBase<uint32_t> {
public:
using TweenBase::TweenBase;
protected:
uint32_t Current(float pos) override;
};
class TextColorTween : public ColorTween {
public:
using ColorTween::ColorTween;
protected:
void DoApply(View *view, float pos) override;
};
class VisibilityTween : public TweenBase<Visibility> {
public:
using TweenBase::TweenBase;
protected:
void DoApply(View *view, float pos) override;
Visibility Current(float pos) override;
};
} // namespace

View File

@ -12,6 +12,7 @@
#include "ui/ui.h"
#include "ui/view.h"
#include "ui/ui_context.h"
#include "ui/ui_tween.h"
#include "thin3d/thin3d.h"
#include "base/NativeApp.h"
@ -160,6 +161,18 @@ View::~View() {
if (HasFocus())
SetFocusedView(0);
RemoveQueuedEvents(this);
// Could use unique_ptr, but then we have to include tween everywhere.
for (auto &tween : tweens_)
delete tween;
tweens_.clear();
}
void View::Update() {
for (Tween *tween : tweens_) {
if (!tween->Finished())
tween->Apply(this);
}
}
void View::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) {
@ -1119,6 +1132,7 @@ void Slider::Draw(UIContext &dc) {
}
void Slider::Update() {
View::Update();
if (repeat_ >= 0) {
repeat_++;
}
@ -1228,6 +1242,7 @@ void SliderFloat::Draw(UIContext &dc) {
}
void SliderFloat::Update() {
View::Update();
if (repeat_ >= 0) {
repeat_++;
}

View File

@ -224,9 +224,6 @@ struct MeasureSpec {
float size;
};
class View;
// Should cover all bases.
struct EventParams {
View *v;
@ -349,6 +346,8 @@ private:
View *GetFocusedView();
class Tween;
class View {
public:
View(LayoutParams *layoutParams = 0) : layoutParams_(layoutParams), visibility_(V_VISIBLE), measuredWidth_(0), measuredHeight_(0), enabledPtr_(0), enabled_(true), enabledMeansDisabled_(false) {
@ -363,7 +362,7 @@ public:
virtual bool Key(const KeyInput &input) { return false; }
virtual void Touch(const TouchInput &input) {}
virtual void Axis(const AxisInput &input) {}
virtual void Update() {}
virtual void Update();
// If this view covers these coordinates, it should add itself and its children to the list.
virtual void Query(float x, float y, std::vector<View *> &list);
@ -424,6 +423,12 @@ public:
Point GetFocusPosition(FocusDirection dir);
template <class T>
T *AddTween(T *t) {
tweens_.push_back(t);
return t;
}
protected:
// Inputs to layout
std::unique_ptr<LayoutParams> layoutParams_;
@ -438,6 +443,8 @@ protected:
// Outputs of layout. X/Y are absolute screen coordinates, hierarchy is "gone" here.
Bounds bounds_;
std::vector<Tween *> tweens_;
private:
bool *enabledPtr_;
bool enabled_;
@ -455,7 +462,6 @@ public:
bool Key(const KeyInput &input) override { return false; }
void Touch(const TouchInput &input) override {}
bool CanBeFocused() const override { return false; }
void Update() override {}
};