mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-09-04 05:03:10 +02:00
1116 lines
26 KiB
C++
1116 lines
26 KiB
C++
/*
|
|
** window.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
|
|
**
|
|
** mkxp is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** mkxp is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "window.h"
|
|
|
|
#include "viewport.h"
|
|
#include "sharedstate.h"
|
|
#include "bitmap.h"
|
|
#include "etc.h"
|
|
#include "etc-internal.h"
|
|
#include "tilequad.h"
|
|
|
|
#include "gl-util.h"
|
|
#include "quad.h"
|
|
#include "quadarray.h"
|
|
#include "texpool.h"
|
|
#include "glstate.h"
|
|
|
|
#include "sigslot/signal.hpp"
|
|
|
|
#ifdef MKXPZ_RETRO
|
|
# include "sandbox-serial-util.h"
|
|
#endif // MKXPZ_RETRO
|
|
|
|
#define GUARD_V(value, expression) do { expression; if (exception.is_error()) return value; } while (0)
|
|
#define GUARD(expression) GUARD_V(, expression)
|
|
|
|
template<typename T>
|
|
struct Sides
|
|
{
|
|
T l, r, t, b;
|
|
};
|
|
|
|
template<typename T>
|
|
struct Corners
|
|
{
|
|
T tl, tr, bl, br;
|
|
};
|
|
|
|
static const IntRect backgroundSrc(0, 0, 128, 128);
|
|
|
|
static const IntRect cursorSrc(128, 64, 32, 32);
|
|
|
|
static const IntRect pauseAniSrc[] =
|
|
{
|
|
IntRect(160, 64, 16, 16),
|
|
IntRect(176, 64, 16, 16),
|
|
IntRect(160, 80, 16, 16),
|
|
IntRect(176, 80, 16, 16)
|
|
};
|
|
|
|
static const Sides<IntRect> bordersSrc =
|
|
{
|
|
IntRect(128, 16, 16, 32),
|
|
IntRect(176, 16, 16, 32),
|
|
IntRect(144, 0, 32, 16),
|
|
IntRect(144, 48, 32, 16)
|
|
};
|
|
|
|
static const Corners<IntRect> cornersSrc =
|
|
{
|
|
IntRect(128, 0, 16, 16),
|
|
IntRect(176, 0, 16, 16),
|
|
IntRect(128, 48, 16, 16),
|
|
IntRect(176, 48, 16, 16)
|
|
};
|
|
|
|
static const Sides<IntRect> scrollArrowSrc =
|
|
{
|
|
IntRect(144, 24, 8, 16),
|
|
IntRect(168, 24, 8, 16),
|
|
IntRect(152, 16, 16, 8),
|
|
IntRect(152, 40, 16, 8)
|
|
};
|
|
|
|
/* Cycling */
|
|
static const uint8_t cursorAniAlpha[] =
|
|
{
|
|
/* Fade out */
|
|
0xFF, 0xF7, 0xEF, 0xE7, 0xDF, 0xD7, 0xCF, 0xC7,
|
|
0xBF, 0xB7, 0xAF, 0xA7, 0x9F, 0x97, 0x8F, 0x87,
|
|
/* Fade in */
|
|
0x7F, 0x87, 0x8F, 0x97, 0x9F, 0xA7, 0xAF, 0xB7,
|
|
0xBF, 0xC7, 0xCF, 0xD7, 0xDF, 0xE7, 0xEF, 0xF7
|
|
};
|
|
|
|
static elementsN(cursorAniAlpha);
|
|
|
|
/* Cycling */
|
|
static const uint8_t pauseAniQuad[] =
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 1, 1, 1, 1, 1, 1,
|
|
2, 2, 2, 2, 2, 2, 2, 2,
|
|
3, 3, 3, 3, 3, 3, 3, 3
|
|
};
|
|
|
|
static elementsN(pauseAniQuad);
|
|
|
|
/* No cycle */
|
|
static const uint8_t pauseAniAlpha[] =
|
|
{
|
|
0x00, 0x20, 0x40, 0x60,
|
|
0x80, 0xA0, 0xC0, 0xE0,
|
|
0xFF
|
|
};
|
|
|
|
static elementsN(pauseAniAlpha);
|
|
|
|
/* Points to an array of quads which it doesn't own.
|
|
* Useful for setting alpha of quads stored inside
|
|
* bigger arrays */
|
|
struct QuadChunk
|
|
{
|
|
Vertex *vert;
|
|
int count; /* In quads */
|
|
|
|
QuadChunk()
|
|
: vert(0), count(0)
|
|
{}
|
|
|
|
void setAlpha(float value)
|
|
{
|
|
for (int i = 0; i < count*4; ++i)
|
|
vert[i].color.w = value;
|
|
}
|
|
};
|
|
|
|
/* Vocabulary:
|
|
*
|
|
* Base: 'Base' layer of window; includes background and borders.
|
|
* Drawn at z+0.
|
|
*
|
|
* Controls: 'Controls' layer of window; includes scroll arrows,
|
|
* pause animation, cursor rectangle and contents bitmap.
|
|
* Drawn at z+2.
|
|
*
|
|
* Scroll arrows: Arrows that appear automatically when a part of
|
|
* the contents bitmap is not visible in either upper, lower, left
|
|
* or right direction.
|
|
*
|
|
* Pause: Animation that displays an animating icon in the bottom
|
|
* center of the window, usually indicating user input is awaited,
|
|
* such as when text is displayed.
|
|
*
|
|
* Cursor: Blinking rectangle that usually displays a selection to
|
|
* the user.
|
|
*
|
|
* Contents: User settable bitmap that is drawn inside the window,
|
|
* clipped to a 16 pixel smaller rectangle. Position is adjusted
|
|
* with OX/OY.
|
|
*
|
|
* BaseTex: If the window has an opacity <255, we have to prerender
|
|
* the base to a texture and draw that. Otherwise, we can draw the
|
|
* quad array directly to the screen.
|
|
*/
|
|
|
|
struct WindowPrivate
|
|
{
|
|
Bitmap *windowskin;
|
|
|
|
Bitmap *contents;
|
|
|
|
sigslot::connection windowskinDispCon;
|
|
sigslot::connection contentsDispCon;
|
|
|
|
bool bgStretch;
|
|
Rect *cursorRect;
|
|
bool active;
|
|
bool pause;
|
|
|
|
sigslot::connection cursorRectCon;
|
|
#ifdef MKXPZ_RETRO
|
|
uint64_t deserSavedContentsId;
|
|
Rect deserSavedCursorRect;
|
|
#endif // MKXPZ_RETRO
|
|
|
|
Vec2i sceneOffset;
|
|
|
|
Vec2i position;
|
|
Vec2i size;
|
|
Vec2i contentsOffset;
|
|
|
|
NormValue opacity;
|
|
NormValue backOpacity;
|
|
NormValue contentsOpacity;
|
|
|
|
bool baseVertDirty;
|
|
bool opacityDirty;
|
|
bool baseTexDirty;
|
|
|
|
ColorQuadArray baseQuadArray;
|
|
|
|
/* Used when opacity < 255 */
|
|
TEXFBO baseTex;
|
|
bool useBaseTex;
|
|
|
|
QuadChunk backgroundVert;
|
|
|
|
Quad baseTexQuad;
|
|
|
|
struct WindowControls : public ViewportElement
|
|
{
|
|
WindowPrivate *p;
|
|
|
|
WindowControls(WindowPrivate *p,
|
|
Viewport *viewport = 0)
|
|
: ViewportElement(nullptr, viewport),
|
|
p(p)
|
|
{
|
|
// Ignore errors
|
|
Exception e;
|
|
setZ(e, 2);
|
|
}
|
|
|
|
void draw(Exception &exception)
|
|
{
|
|
p->drawControls();
|
|
}
|
|
|
|
void release()
|
|
{
|
|
unlink();
|
|
}
|
|
|
|
ABOUT_TO_ACCESS_NOOP
|
|
};
|
|
|
|
WindowControls controlsElement;
|
|
|
|
ColorQuadArray controlsQuadArray;
|
|
int controlsQuadCount;
|
|
|
|
Quad contentsQuad;
|
|
|
|
QuadChunk pauseAniVert;
|
|
QuadChunk cursorVert;
|
|
|
|
uint8_t cursorAniAlphaIdx;
|
|
uint8_t pauseAniAlphaIdx;
|
|
uint8_t pauseAniQuadIdx;
|
|
|
|
bool controlsVertDirty;
|
|
|
|
EtcTemps tmp;
|
|
|
|
sigslot::connection prepareCon;
|
|
|
|
WindowPrivate(Viewport *viewport = 0)
|
|
: windowskin(0),
|
|
contents(0),
|
|
bgStretch(true),
|
|
cursorRect(&tmp.rect),
|
|
active(true),
|
|
pause(false),
|
|
opacity(255),
|
|
backOpacity(255),
|
|
contentsOpacity(255),
|
|
baseVertDirty(true),
|
|
opacityDirty(true),
|
|
baseTexDirty(true),
|
|
controlsElement(this, viewport),
|
|
cursorAniAlphaIdx(0),
|
|
pauseAniAlphaIdx(0),
|
|
pauseAniQuadIdx(0),
|
|
controlsVertDirty(true)
|
|
{
|
|
refreshCursorRectCon();
|
|
|
|
controlsQuadArray.resize(14);
|
|
cursorVert.count = 9;
|
|
pauseAniVert.count = 1;
|
|
|
|
prepareCon = shState->prepareDraw.connect
|
|
(&WindowPrivate::prepare, this);
|
|
}
|
|
|
|
~WindowPrivate()
|
|
{
|
|
shState->texPool().release(baseTex);
|
|
cursorRectCon.disconnect();
|
|
prepareCon.disconnect();
|
|
|
|
windowskinDisposal();
|
|
contentsDisposal();
|
|
}
|
|
|
|
void windowskinDisposal()
|
|
{
|
|
windowskin = 0;
|
|
windowskinDispCon.disconnect();
|
|
}
|
|
|
|
void contentsDisposal()
|
|
{
|
|
contents = 0;
|
|
contentsDispCon.disconnect();
|
|
}
|
|
|
|
void markControlVertDirty()
|
|
{
|
|
controlsVertDirty = true;
|
|
}
|
|
|
|
void refreshCursorRectCon()
|
|
{
|
|
cursorRectCon.disconnect();
|
|
cursorRectCon = cursorRect->valueChanged.connect
|
|
(&WindowPrivate::markControlVertDirty, this);
|
|
}
|
|
|
|
void buildBaseVert()
|
|
{
|
|
int w = size.x;
|
|
int h = size.y;
|
|
|
|
IntRect bgRect(2, 2, w - 4, h - 4);
|
|
|
|
Sides<IntRect> borderRects;
|
|
borderRects.l = IntRect(0, 8, 16, h-16);
|
|
borderRects.r = IntRect(w-16, 8, 16, h-16);
|
|
borderRects.t = IntRect(8, 0, w-16, 16 );
|
|
borderRects.b = IntRect(8, h-16, w-16, 16 );
|
|
|
|
Corners<IntRect> cornerRects;
|
|
cornerRects.tl = IntRect(0, 0, 16, 16);
|
|
cornerRects.tr = IntRect(w-16, 0, 16, 16);
|
|
cornerRects.bl = IntRect(0, h-16, 16, 16);
|
|
cornerRects.br = IntRect(w-16, h-16, 16, 16);
|
|
|
|
/* Required quad count */
|
|
int count = 0;
|
|
|
|
/* Background */
|
|
if (bgStretch)
|
|
backgroundVert.count = 1;
|
|
else
|
|
backgroundVert.count =
|
|
TileQuads::twoDimCount(128, 128, bgRect.w, bgRect.h);
|
|
|
|
count += backgroundVert.count;
|
|
|
|
/* Borders (sides) */
|
|
count += TileQuads::oneDimCount(32, w-16) * 2;
|
|
count += TileQuads::oneDimCount(32, h-16) * 2;
|
|
|
|
/* Corners */
|
|
count += 4;
|
|
|
|
/* Our vertex array */
|
|
baseQuadArray.resize(count);
|
|
Vertex *vert = baseQuadArray.vertices.data();
|
|
|
|
int i = 0;
|
|
backgroundVert.vert = &vert[i];
|
|
|
|
/* Background */
|
|
if (bgStretch)
|
|
{
|
|
Quad::setTexRect(&vert[i*4], backgroundSrc);
|
|
Quad::setPosRect(&vert[i*4], bgRect);
|
|
i += 1;
|
|
}
|
|
else
|
|
{
|
|
i += TileQuads::build(backgroundSrc, bgRect, &vert[i*4]);
|
|
}
|
|
|
|
/* Borders */
|
|
i += TileQuads::buildH(bordersSrc.t, w-16, 8, 0, &vert[i*4]);
|
|
i += TileQuads::buildH(bordersSrc.b, w-16, 8, h-16, &vert[i*4]);
|
|
i += TileQuads::buildV(bordersSrc.l, h-16, 0, 8, &vert[i*4]);
|
|
i += TileQuads::buildV(bordersSrc.r, h-16, w-16, 8, &vert[i*4]);
|
|
|
|
/* Corners */
|
|
i += Quad::setTexPosRect(&vert[i*4], cornersSrc.tl, cornerRects.tl);
|
|
i += Quad::setTexPosRect(&vert[i*4], cornersSrc.tr, cornerRects.tr);
|
|
i += Quad::setTexPosRect(&vert[i*4], cornersSrc.bl, cornerRects.bl);
|
|
i += Quad::setTexPosRect(&vert[i*4], cornersSrc.br, cornerRects.br);
|
|
|
|
for (int j = 0; j < count*4; ++j)
|
|
vert[j].color = Vec4(1, 1, 1, 1);
|
|
|
|
|
|
FloatRect texRect = FloatRect(0, 0, size.x, size.y);
|
|
baseTexQuad.setTexPosRect(texRect, texRect);
|
|
|
|
opacityDirty = true;
|
|
baseTexDirty = true;
|
|
}
|
|
|
|
void updateBaseAlpha()
|
|
{
|
|
/* This is always applied unconditionally */
|
|
backgroundVert.setAlpha(backOpacity.norm);
|
|
|
|
baseTexQuad.setColor(Vec4(1, 1, 1, opacity.norm));
|
|
|
|
baseTexDirty = true;
|
|
}
|
|
|
|
void ensureBaseTexReady(Exception &exception)
|
|
{
|
|
/* Make sure texture is big enough */
|
|
int newW = baseTex.width;
|
|
int newH = baseTex.height;
|
|
bool resizeNeeded = false;
|
|
|
|
if (size.x > baseTex.width)
|
|
{
|
|
newW = findNextPow2(size.x);
|
|
resizeNeeded = true;
|
|
}
|
|
if (size.y > baseTex.height)
|
|
{
|
|
newH = findNextPow2(size.y);
|
|
resizeNeeded = true;
|
|
}
|
|
|
|
if (!resizeNeeded)
|
|
return;
|
|
|
|
shState->texPool().release(baseTex);
|
|
GUARD(baseTex = shState->texPool().request(exception, newW, newH));
|
|
|
|
baseTexDirty = true;
|
|
}
|
|
|
|
void redrawBaseTex()
|
|
{
|
|
/* Discard old buffer */
|
|
TEX::bind(baseTex.tex);
|
|
TEX::allocEmpty(baseTex.width, baseTex.height);
|
|
TEX::unbind();
|
|
|
|
FBO::bind(baseTex.fbo);
|
|
glState.viewport.pushSet(IntRect(0, 0, baseTex.width, baseTex.height));
|
|
glState.clearColor.pushSet(Vec4());
|
|
|
|
SimpleAlphaShader &shader = shState->shaders().simpleAlpha;
|
|
shader.bind();
|
|
shader.applyViewportProj();
|
|
shader.setTranslation(Vec2i());
|
|
|
|
/* Clear texture */
|
|
FBO::clear();
|
|
|
|
/* Repaint base */
|
|
windowskin->bindTex(shader);
|
|
TEX::setSmooth(true);
|
|
|
|
/* We need to blit the background without blending,
|
|
* because we want to retain its correct alpha value.
|
|
* Otherwise it would be mutliplied by the backgrounds 0 alpha */
|
|
glState.blend.pushSet(false);
|
|
|
|
baseQuadArray.draw(0, backgroundVert.count);
|
|
|
|
/* Now draw the rest (ie. the frame) with blending */
|
|
glState.blend.pop();
|
|
glState.blendMode.pushSet(BlendNormal);
|
|
|
|
baseQuadArray.draw(backgroundVert.count, baseQuadArray.count()-backgroundVert.count);
|
|
|
|
glState.clearColor.pop();
|
|
glState.blendMode.pop();
|
|
glState.viewport.pop();
|
|
TEX::setSmooth(false);
|
|
}
|
|
|
|
void buildControlsVert()
|
|
{
|
|
int i = 0;
|
|
Vertex *vert = controlsQuadArray.vertices.data();
|
|
|
|
/* Cursor */
|
|
if (!cursorRect->isEmpty())
|
|
{
|
|
/* Effective cursor rect has 16 xy offset to window */
|
|
IntRect effectRect(cursorRect->x+16, cursorRect->y+16,
|
|
cursorRect->width, cursorRect->height);
|
|
cursorVert.vert = &vert[i*4];
|
|
TileQuads::buildFrameSource(cursorSrc, cursorVert.vert);
|
|
i += TileQuads::buildFrame(effectRect, cursorVert.vert);
|
|
}
|
|
|
|
/* Scroll arrow position: Top Bottom X, Left Right Y */
|
|
const Vec2i scroll = (size - Vec2i(16)) / 2;
|
|
|
|
Sides<IntRect> scrollArrows;
|
|
|
|
scrollArrows.l = IntRect(4, scroll.y, 8, 16);
|
|
scrollArrows.r = IntRect(size.x - 12, scroll.y, 8, 16);
|
|
scrollArrows.t = IntRect(scroll.x, 4, 16, 8);
|
|
scrollArrows.b = IntRect(scroll.x, size.y - 12, 16, 8);
|
|
|
|
if (contents)
|
|
{
|
|
if (contentsOffset.x > 0)
|
|
i += Quad::setTexPosRect(&vert[i*4], scrollArrowSrc.l, scrollArrows.l);
|
|
|
|
if (contentsOffset.y > 0)
|
|
i += Quad::setTexPosRect(&vert[i*4], scrollArrowSrc.t, scrollArrows.t);
|
|
|
|
if ((size.x - 32) < (contents->width() - contentsOffset.x))
|
|
i += Quad::setTexPosRect(&vert[i*4], scrollArrowSrc.r, scrollArrows.r);
|
|
|
|
if ((size.y - 32) < (contents->height() - contentsOffset.y))
|
|
i += Quad::setTexPosRect(&vert[i*4], scrollArrowSrc.b, scrollArrows.b);
|
|
}
|
|
|
|
/* Pause animation */
|
|
if (pause)
|
|
{
|
|
pauseAniVert.vert = &vert[i*4];
|
|
i += Quad::setTexPosRect(&vert[i*4], pauseAniSrc[pauseAniQuad[pauseAniQuadIdx]],
|
|
FloatRect((size.x - 16) / 2, size.y - 16, 16, 16));
|
|
}
|
|
|
|
controlsQuadArray.commit();
|
|
controlsQuadCount = i;
|
|
}
|
|
|
|
void prepare()
|
|
{
|
|
if (size.x <= 0 || size.y <= 0)
|
|
return;
|
|
|
|
bool updateBaseQuadArray = false;
|
|
|
|
if (baseVertDirty)
|
|
{
|
|
buildBaseVert();
|
|
baseVertDirty = false;
|
|
updateBaseQuadArray = true;
|
|
}
|
|
|
|
if (opacityDirty)
|
|
{
|
|
updateBaseAlpha();
|
|
opacityDirty = false;
|
|
updateBaseQuadArray = true;
|
|
}
|
|
|
|
if (updateBaseQuadArray)
|
|
baseQuadArray.commit();
|
|
|
|
/* If opacity has effect, we must prerender to a texture
|
|
* and then draw this texture instead of the quad array */
|
|
useBaseTex = opacity < 255;
|
|
|
|
if (useBaseTex)
|
|
{
|
|
Exception e;
|
|
ensureBaseTexReady(e);
|
|
if (e.is_error())
|
|
return;
|
|
|
|
if (baseTexDirty)
|
|
{
|
|
redrawBaseTex();
|
|
baseTexDirty = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawBase()
|
|
{
|
|
if (nullOrDisposed(windowskin))
|
|
return;
|
|
|
|
if (size == Vec2i(0, 0))
|
|
return;
|
|
|
|
SimpleAlphaShader &shader = shState->shaders().simpleAlpha;
|
|
shader.bind();
|
|
shader.applyViewportProj();
|
|
shader.setTranslation(position + sceneOffset);
|
|
|
|
if (useBaseTex)
|
|
{
|
|
shader.setTexSize(Vec2i(baseTex.width, baseTex.height));
|
|
|
|
TEX::bind(baseTex.tex);
|
|
baseTexQuad.draw();
|
|
}
|
|
else
|
|
{
|
|
windowskin->bindTex(shader);
|
|
TEX::setSmooth(true);
|
|
|
|
baseQuadArray.draw();
|
|
|
|
TEX::setSmooth(false);
|
|
}
|
|
}
|
|
|
|
void drawControls()
|
|
{
|
|
if (nullOrDisposed(windowskin) && nullOrDisposed(contents))
|
|
return;
|
|
|
|
if (size == Vec2i(0, 0))
|
|
return;
|
|
|
|
if (controlsVertDirty)
|
|
{
|
|
buildControlsVert();
|
|
updateControls();
|
|
controlsVertDirty = false;
|
|
}
|
|
|
|
/* Effective on screen coordinates */
|
|
const Vec2i efPos = position + sceneOffset;
|
|
|
|
const IntRect windowRect(efPos, size);
|
|
const IntRect contentsRect(efPos + Vec2i(16), size - Vec2i(32));
|
|
|
|
glState.scissorTest.pushSet(true);
|
|
glState.scissorBox.push();
|
|
glState.scissorBox.setIntersect(windowRect);
|
|
|
|
SimpleAlphaShader &shader = shState->shaders().simpleAlpha;
|
|
shader.bind();
|
|
shader.applyViewportProj();
|
|
|
|
if (!nullOrDisposed(windowskin))
|
|
{
|
|
shader.setTranslation(efPos);
|
|
|
|
/* Draw arrows / cursors */
|
|
windowskin->bindTex(shader);
|
|
TEX::setSmooth(true);
|
|
|
|
controlsQuadArray.draw(0, controlsQuadCount);
|
|
|
|
TEX::setSmooth(false);
|
|
}
|
|
|
|
if (!nullOrDisposed(contents))
|
|
{
|
|
/* Draw contents bitmap */
|
|
glState.scissorBox.setIntersect(contentsRect);
|
|
|
|
shader.setTranslation(efPos + (Vec2i(16) - contentsOffset));
|
|
|
|
contents->bindTex(shader);
|
|
contentsQuad.draw();
|
|
}
|
|
|
|
glState.scissorBox.pop();
|
|
glState.scissorTest.pop();
|
|
}
|
|
|
|
void updateControls()
|
|
{
|
|
bool updateArray = false;
|
|
|
|
if (active && cursorVert.vert)
|
|
{
|
|
float alpha = cursorAniAlpha[cursorAniAlphaIdx] / 255.0f;
|
|
|
|
cursorVert.setAlpha(alpha);
|
|
|
|
updateArray = true;
|
|
}
|
|
|
|
if (pause && pauseAniVert.vert)
|
|
{
|
|
float alpha = pauseAniAlpha[pauseAniAlphaIdx] / 255.0f;
|
|
FloatRect frameRect = pauseAniSrc[pauseAniQuad[pauseAniQuadIdx]];
|
|
|
|
pauseAniVert.setAlpha(alpha);
|
|
Quad::setTexRect(pauseAniVert.vert, frameRect);
|
|
|
|
updateArray = true;
|
|
}
|
|
|
|
if (updateArray)
|
|
controlsQuadArray.commit();
|
|
}
|
|
|
|
void stepAnimations()
|
|
{
|
|
if (++cursorAniAlphaIdx == cursorAniAlphaN)
|
|
cursorAniAlphaIdx = 0;
|
|
|
|
if (pauseAniAlphaIdx < pauseAniAlphaN-1)
|
|
++pauseAniAlphaIdx;
|
|
|
|
if (++pauseAniQuadIdx == pauseAniQuadN)
|
|
pauseAniQuadIdx = 0;
|
|
}
|
|
};
|
|
|
|
static void disposePtr(void *ptr)
|
|
{
|
|
((Window *)ptr)->dispose();
|
|
}
|
|
|
|
Window::Window(Viewport *viewport)
|
|
: ViewportElement(disposePtr, viewport)
|
|
{
|
|
p = new WindowPrivate(viewport);
|
|
onGeometryChange(scene->getGeometry());
|
|
}
|
|
|
|
Window::~Window()
|
|
{
|
|
dispose();
|
|
}
|
|
|
|
void Window::update(Exception &exception)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
p->updateControls();
|
|
p->stepAnimations();
|
|
}
|
|
|
|
DEF_ATTR_SIMPLE(Window, X, int, p->position.x)
|
|
DEF_ATTR_SIMPLE(Window, Y, int, p->position.y)
|
|
DEF_ATTR_SIMPLE(Window, CursorRect, Rect&, *p->cursorRect)
|
|
|
|
DEF_ATTR_RD_SIMPLE(Window, Windowskin, Bitmap*, p->windowskin)
|
|
DEF_ATTR_RD_SIMPLE(Window, Contents, Bitmap*, p->contents)
|
|
DEF_ATTR_RD_SIMPLE(Window, Stretch, bool, p->bgStretch)
|
|
DEF_ATTR_RD_SIMPLE(Window, Active, bool, p->active)
|
|
DEF_ATTR_RD_SIMPLE(Window, Pause, bool, p->pause)
|
|
DEF_ATTR_RD_SIMPLE(Window, Width, int, p->size.x)
|
|
DEF_ATTR_RD_SIMPLE(Window, Height, int, p->size.y)
|
|
DEF_ATTR_RD_SIMPLE(Window, OX, int, p->contentsOffset.x)
|
|
DEF_ATTR_RD_SIMPLE(Window, OY, int, p->contentsOffset.y)
|
|
DEF_ATTR_RD_SIMPLE(Window, Opacity, int, p->opacity)
|
|
DEF_ATTR_RD_SIMPLE(Window, BackOpacity, int, p->backOpacity)
|
|
DEF_ATTR_RD_SIMPLE(Window, ContentsOpacity, int, p->contentsOpacity)
|
|
|
|
void Window::setWindowskin(Exception &exception, Bitmap *value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
p->windowskin = value;
|
|
|
|
p->windowskinDispCon.disconnect();
|
|
|
|
if (nullOrDisposed(value))
|
|
{
|
|
p->windowskin = 0;
|
|
return;
|
|
}
|
|
|
|
GUARD(value->ensureNonMega(exception));
|
|
|
|
p->windowskinDispCon = value->wasDisposed.connect(&WindowPrivate::windowskinDisposal, p);
|
|
}
|
|
|
|
void Window::setContents(Exception &exception, Bitmap *value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->contents == value)
|
|
return;
|
|
|
|
p->contents = value;
|
|
p->controlsVertDirty = true;
|
|
|
|
p->contentsDispCon.disconnect();
|
|
|
|
if (nullOrDisposed(value))
|
|
{
|
|
p->contents = 0;
|
|
return;
|
|
}
|
|
|
|
p->contentsDispCon = value->wasDisposed.connect(&WindowPrivate::contentsDisposal, p);
|
|
|
|
GUARD(value->ensureNonMega(exception));
|
|
|
|
p->contentsQuad.setTexPosRect(value->rect(), value->rect());
|
|
}
|
|
|
|
void Window::setStretch(Exception &exception, bool value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (value == p->bgStretch)
|
|
return;
|
|
|
|
p->bgStretch = value;
|
|
p->baseVertDirty = true;
|
|
}
|
|
|
|
void Window::setActive(Exception &exception, bool value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->active == value)
|
|
return;
|
|
|
|
p->active = value;
|
|
p->cursorAniAlphaIdx = 0;
|
|
}
|
|
|
|
void Window::setPause(Exception &exception, bool value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->pause == value)
|
|
return;
|
|
|
|
p->pause = value;
|
|
p->pauseAniAlphaIdx = 0;
|
|
p->pauseAniQuadIdx = 0;
|
|
p->controlsVertDirty = true;
|
|
}
|
|
|
|
void Window::setWidth(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->size.x == value)
|
|
return;
|
|
|
|
p->size.x = value;
|
|
p->baseVertDirty = true;
|
|
}
|
|
|
|
void Window::setHeight(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->size.y == value)
|
|
return;
|
|
|
|
p->size.y = value;
|
|
p->baseVertDirty = true;
|
|
}
|
|
|
|
void Window::setOX(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->contentsOffset.x == value)
|
|
return;
|
|
|
|
p->contentsOffset.x = value;
|
|
p->controlsVertDirty = true;
|
|
}
|
|
|
|
void Window::setOY(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->contentsOffset.y == value)
|
|
return;
|
|
|
|
p->contentsOffset.y = value;
|
|
p->controlsVertDirty = true;
|
|
}
|
|
|
|
void Window::setOpacity(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->opacity == value)
|
|
return;
|
|
|
|
p->opacity = value;
|
|
p->opacityDirty = true;
|
|
}
|
|
|
|
void Window::setBackOpacity(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->backOpacity == value)
|
|
return;
|
|
|
|
p->backOpacity = value;
|
|
p->opacityDirty = true;
|
|
}
|
|
|
|
void Window::setContentsOpacity(Exception &exception, int value)
|
|
{
|
|
GUARD(guardDisposed(exception));
|
|
|
|
if (p->contentsOpacity == value)
|
|
return;
|
|
|
|
p->contentsOpacity = value;
|
|
p->contentsQuad.setColor(Vec4(1, 1, 1, p->contentsOpacity.norm));
|
|
}
|
|
|
|
void Window::initDynAttribs()
|
|
{
|
|
p->cursorRect = new Rect;
|
|
|
|
p->refreshCursorRectCon();
|
|
}
|
|
|
|
void Window::draw(Exception &exception)
|
|
{
|
|
p->drawBase();
|
|
}
|
|
|
|
void Window::onGeometryChange(const Scene::Geometry &geo)
|
|
{
|
|
p->sceneOffset = geo.offset();
|
|
}
|
|
|
|
void Window::setZ(Exception &exception, int value)
|
|
{
|
|
GUARD(ViewportElement::setZ(exception, value));
|
|
|
|
GUARD(p->controlsElement.setZ(exception, value + 2));
|
|
}
|
|
|
|
void Window::setVisible(Exception &exception, bool value)
|
|
{
|
|
GUARD(ViewportElement::setVisible(exception, value));
|
|
|
|
GUARD(p->controlsElement.setVisible(exception, value));
|
|
}
|
|
|
|
void Window::onViewportChange()
|
|
{
|
|
p->controlsElement.setScene(*this->scene);
|
|
}
|
|
|
|
void Window::releaseResources()
|
|
{
|
|
p->controlsElement.release();
|
|
|
|
unlink();
|
|
|
|
delete p;
|
|
}
|
|
|
|
#ifdef MKXPZ_RETRO
|
|
bool Window::sandbox_serialize(void *&data, mkxp_sandbox::wasm_size_t &max_size) const
|
|
{
|
|
if (!mkxp_sandbox::sandbox_serialize(p->bgStretch, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->active, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->pause, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->sceneOffset, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->position, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->size, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->contentsOffset, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->opacity, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->backOpacity, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->contentsOpacity, data, max_size)) return false;
|
|
|
|
if (!p->controlsElement.sandbox_serialize_viewport_element(data, max_size)) return false;
|
|
|
|
if (!mkxp_sandbox::sandbox_serialize(p->cursorAniAlphaIdx, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->pauseAniAlphaIdx, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->pauseAniQuadIdx, data, max_size)) return false;
|
|
|
|
if (!sandbox_serialize_viewport_element(data, max_size)) return false;
|
|
|
|
if (!mkxp_sandbox::sandbox_serialize(p->windowskin, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->contents, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_serialize(p->cursorRect == &p->tmp.rect ? nullptr : p->cursorRect, data, max_size)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Window::sandbox_deserialize(const void *&data, mkxp_sandbox::wasm_size_t &max_size)
|
|
{
|
|
{
|
|
bool value = p->bgStretch;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->bgStretch, data, max_size)) return false;
|
|
if (p->bgStretch != value) {
|
|
p->baseVertDirty = true;
|
|
}
|
|
}
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->active, data, max_size)) return false;
|
|
{
|
|
bool value = p->pause;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->pause, data, max_size)) return false;
|
|
if (p->pause != value) {
|
|
p->controlsVertDirty = true;
|
|
}
|
|
}
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->sceneOffset, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->position, data, max_size)) return false;
|
|
{
|
|
Vec2i value = p->size;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->size, data, max_size)) return false;
|
|
if (p->size != value) {
|
|
p->baseVertDirty = true;
|
|
}
|
|
}
|
|
{
|
|
Vec2i value = p->contentsOffset;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->contentsOffset, data, max_size)) return false;
|
|
if (p->contentsOffset != value) {
|
|
p->controlsVertDirty = true;
|
|
}
|
|
}
|
|
{
|
|
bool value = p->opacity;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->opacity, data, max_size)) return false;
|
|
if (p->opacity != value) {
|
|
p->opacityDirty = true;
|
|
}
|
|
}
|
|
{
|
|
bool value = p->backOpacity;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->backOpacity, data, max_size)) return false;
|
|
if (p->backOpacity != value) {
|
|
p->opacityDirty = true;
|
|
}
|
|
}
|
|
{
|
|
NormValue value = p->contentsOpacity;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->contentsOpacity, data, max_size)) return false;
|
|
if (value != p->contentsOpacity) {
|
|
p->contentsQuad.setColor(Vec4(1, 1, 1, p->contentsOpacity.norm));
|
|
}
|
|
}
|
|
|
|
if (!p->controlsElement.sandbox_deserialize_viewport_element(data, max_size)) return false;
|
|
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->cursorAniAlphaIdx, data, max_size)) return false;
|
|
p->cursorAniAlphaIdx %= cursorAniAlphaN;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->pauseAniAlphaIdx, data, max_size)) return false;
|
|
p->pauseAniAlphaIdx = std::min(p->pauseAniAlphaIdx, (uint8_t)(pauseAniAlphaN - 1));
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->pauseAniQuadIdx, data, max_size)) return false;
|
|
p->pauseAniQuadIdx %= pauseAniQuadN;
|
|
|
|
if (!sandbox_deserialize_viewport_element(data, max_size)) return false;
|
|
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->windowskin, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->contents, data, max_size)) return false;
|
|
if (!mkxp_sandbox::sandbox_deserialize(p->cursorRect, data, max_size)) return false;
|
|
if (p->cursorRect == nullptr) {
|
|
p->cursorRect = &p->tmp.rect;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Window::sandbox_deserialize_begin()
|
|
{
|
|
sandbox_deserialize_begin_viewport_element();
|
|
|
|
if (isDisposed()) return;
|
|
|
|
p->windowskinDispCon.disconnect();
|
|
|
|
p->contentsDispCon.disconnect();
|
|
|
|
p->deserSavedContentsId = p->contents == nullptr ? 0 : p->contents->id;
|
|
|
|
p->cursorRectCon.disconnect();
|
|
if (p->cursorRect != nullptr) {
|
|
p->deserSavedCursorRect = *p->cursorRect;
|
|
} else {
|
|
p->deserSavedCursorRect.set(0, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
void Window::sandbox_deserialize_end()
|
|
{
|
|
if (isDisposed()) return;
|
|
sandbox_deserialize_end_viewport_element();
|
|
|
|
if (isDisposed()) return;
|
|
if (p->windowskin != nullptr) {
|
|
p->windowskinDispCon = p->windowskin->wasDisposed.connect(&WindowPrivate::windowskinDisposal, p);
|
|
if (p->windowskin->isDisposed()) {
|
|
p->windowskinDisposal();
|
|
}
|
|
}
|
|
|
|
if (isDisposed()) return;
|
|
if (p->contents != nullptr) {
|
|
p->contentsDispCon = p->contents->wasDisposed.connect(&WindowPrivate::contentsDisposal, p);
|
|
if (p->contents->isDisposed()) {
|
|
p->contentsDisposal();
|
|
}
|
|
}
|
|
|
|
if (isDisposed()) return;
|
|
if (p->contents != nullptr && (p->contents->deserSizeChanged || p->contents->id != p->deserSavedContentsId)) {
|
|
p->contentsQuad.setTexPosRect(p->contents->rect(), p->contents->rect());
|
|
}
|
|
|
|
if (isDisposed()) return;
|
|
if (p->cursorRect != nullptr) {
|
|
p->cursorRectCon = p->cursorRect->valueChanged.connect(&WindowPrivate::markControlVertDirty, p);
|
|
if (*p->cursorRect != p->deserSavedCursorRect) {
|
|
p->markControlVertDirty();
|
|
}
|
|
}
|
|
}
|
|
#endif // MKXPZ_RETRO
|