added launcher image zooming

This commit is contained in:
thrust26 2023-08-31 17:59:51 +02:00
parent 667ea467fc
commit fefec189b0
2 changed files with 274 additions and 54 deletions

View File

@ -26,6 +26,8 @@
#include "Props.hxx"
#include "PropsSet.hxx"
#include "TimerManager.hxx"
#include "FBSurfaceSDL2.hxx"
#include "RomImageWidget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -37,6 +39,9 @@ RomImageWidget::RomImageWidget(GuiObject* boss, const GUI::Font& font,
_bgcolor = kDlgColor;
_bgcolorlo = kBGColorLo;
myImageHeight = _h - labelHeight(font);
myZoomRect = Common::Rect(_w * 7 / 16, myImageHeight * 7 / 16,
_w * 9 / 16, myImageHeight * 9 / 16);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -90,18 +95,16 @@ void RomImageWidget::reloadProperties(const FSNode& node)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RomImageWidget::parseProperties(const FSNode& node, bool full)
{
FrameBuffer& fb = instance().frameBuffer();
const uInt64 startTime = TimerManager::getTicks() / 1000;
if(myNavSurface == nullptr)
{
// Create navigation surface
myNavSurface = instance().frameBuffer().allocateSurface(
_w, myImageHeight);
const uInt32 scale = fb.hidpiScaleFactor();
const uInt32 scale = instance().frameBuffer().hidpiScaleFactor();
myNavSurface->setDstRect(
Common::Rect(_x * scale, _y * scale,
(_x + _w) * scale, (_y + myImageHeight) * scale));
myNavSurface = fb.allocateSurface(_w, myImageHeight);
myNavSurface->setDstSize(_w * scale, myImageHeight * scale);
FBSurface::Attributes& attr = myNavSurface->attributes();
@ -115,14 +118,21 @@ void RomImageWidget::parseProperties(const FSNode& node, bool full)
// only draw certain parts of it
if(mySurface == nullptr)
{
mySurface = instance().frameBuffer().allocateSurface(
_w, myImageHeight, ScalingInterpolation::blur);
mySurface = fb.allocateSurface(_w, myImageHeight, ScalingInterpolation::blur);
mySurface->applyAttributes();
myFrameSurface = fb.allocateSurface(1, 1, ScalingInterpolation::sharp);
myFrameSurface->applyAttributes();
myFrameSurface->setVisible(true);
dialog().addRenderCallback([this]() {
if(mySurfaceIsValid)
{
if(myIsZoomed)
myFrameSurface->render();
mySurface->render();
if(isHighlighted())
}
if(isHighlighted() && !myIsZoomed)
myNavSurface->render();
});
}
@ -178,7 +188,10 @@ void RomImageWidget::parseProperties(const FSNode& node, bool full)
setDirty();
#endif
if(mySurface)
{
mySurface->setVisible(mySurfaceIsValid);
myFrameSurface->setVisible(mySurfaceIsValid);
}
// Update maximum load time
myMaxLoadTime = std::min(
@ -268,18 +281,14 @@ bool RomImageWidget::loadImage(const string& fileName)
if(mySurfaceIsValid)
{
// Scale surface to available image area
const Common::Rect& src = mySurface->srcRect();
const float scale = std::min(
static_cast<float>(_w) / src.w(),
static_cast<float>(myImageHeight) / src.h()) *
instance().frameBuffer().hidpiScaleFactor();
mySurface->setDstSize(static_cast<uInt32>(src.w() * scale), static_cast<uInt32>(src.h() * scale));
mySrcRect = mySurface->srcRect();
zoomSurface(false, true);
}
if(mySurface)
mySurface->setVisible(mySurfaceIsValid);
myZoomTimer = 0;
setDirty();
return mySurfaceIsValid;
}
@ -341,19 +350,154 @@ bool RomImageWidget::loadJpg(const string& fileName)
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RomImageWidget::zoomSurface(bool zoomed, bool force)
{
if(zoomed != myIsZoomed || force)
{
const uInt32 scaleDpi = instance().frameBuffer().hidpiScaleFactor();
myIsZoomed = zoomed;
if(!zoomed)
{
// Scale surface to available image area
const float scale = std::min(
static_cast<float>(_w) / mySrcRect.w(),
static_cast<float>(myImageHeight) / mySrcRect.h()) * scaleDpi;
const uInt32 w = mySrcRect.w() * scale;
const uInt32 h = mySrcRect.h() * scale;
mySurface->setDstSize(w, h);
}
else
{
// display zoomed image centered over mouse position, considering launcher borders
myZoomPos = myMousePos;
const Int32 b = 3 * scaleDpi;
const Common::Size maxSize = instance().frameBuffer().fullScreen()
? instance().frameBuffer().screenSize()
: dialog().surface().dstRect().size();
const Int32 lw = maxSize.w - b * 2;
const Int32 lh = maxSize.h - b * 2;
const Int32 iw = mySrcRect.w() * scaleDpi;
const Int32 ih = mySrcRect.h() * scaleDpi;
const float zoom = std::min(1.F,
std::min(static_cast<float>(lw) / iw,
static_cast<float>(lh) / ih));
const Int32 w = iw * zoom;
const Int32 h = ih * zoom;
mySurface->setDstSize(w, h);
myFrameSurface->resize(w + b * 2, h + b * 2);
myFrameSurface->setDstSize(w + b * 2, h + b * 2);
myFrameSurface->frameRect(0, 0, myFrameSurface->width(), myFrameSurface->height(), kColor);
myZoomTimer = DELAY_TIME * REQUEST_SPEED;
}
posSurfaces();
setDirty();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RomImageWidget::posSurfaces()
{
// Make sure when positioning the image surface that we take
// the dialog surface position into account
const Common::Rect& s_dst = dialog().surface().dstRect();
const Int32 scaleDpi = instance().frameBuffer().hidpiScaleFactor();
const Int32 w = mySurface->dstRect().w();
const Int32 h = mySurface->dstRect().h();
if(!myIsZoomed)
{
// Position image and navigation surface
const uInt32 x = s_dst.x() + _x * scaleDpi;
const uInt32 y = s_dst.y() + _y * scaleDpi;
mySurface->setDstPos(x + ((_w * scaleDpi - w) >> 1),
y + ((myImageHeight * scaleDpi - h) >> 1));
myNavSurface->setDstPos(x, y);
}
else
{
// Display zoomed image centered over mouse position, considering launcher borders
// Note: Using Int32 to avoid more casting
const Int32 zx = myZoomPos.x;
const Int32 zy = myZoomPos.y;
const Int32 b = 3 * scaleDpi;
const bool fs = instance().frameBuffer().fullScreen();
const Common::Size maxSize = fs
? instance().frameBuffer().screenSize()
: dialog().surface().dstRect().size();
const Common::Point minPos = fs
? Common::Point(0, 0)
: s_dst.point();
const Int32 lw = maxSize.w - b * 2;
const Int32 lh = maxSize.h - b * 2;
// Position at right top
const Int32 x = std::min(
static_cast<Int32>(s_dst.x()) + (_x + zx) * scaleDpi - w / 2 + b,
lw - w + b);
const Int32 y = std::min(
lh - h + b,
std::max(static_cast<Int32>(s_dst.y()) + (zy + _y) * scaleDpi - h / 2 + b, b));
mySurface->setDstPos(x, y);
myFrameSurface->setDstPos(x - b, y - b);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RomImageWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
{
if(isEnabled() && x >= 0 && x < _w && y >= 0 && y < myImageHeight)
changeImage(x < _w / 2 ? -1 : 1);
if(myMouseArea == Area::LEFT)
changeImage(-1);
else if(myMouseArea == Area::RIGHT)
changeImage(1);
else
zoomSurface(true);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RomImageWidget::handleMouseMoved(int x, int y)
{
if((x < _w / 2) != myMouseLeft)
const Area oldArea = myMouseArea;
myMousePos = Common::Point(x, y);
if(myZoomRect.contains(x, y))
myMouseArea = Area::ZOOM;
else if(x < _w >> 1)
myMouseArea = Area::LEFT;
else
myMouseArea = Area::RIGHT;
if(myMouseArea != oldArea)
setDirty();
myMouseLeft = x < _w / 2;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RomImageWidget::tick()
{
if(myMouseArea == Area::ZOOM)
{
myZoomTimer += REQUEST_SPEED;
if(myZoomTimer >= DELAY_TIME * REQUEST_SPEED)
zoomSurface(true);
}
else
{
zoomSurface(false);
if(myZoomTimer)
--myZoomTimer;
}
Widget::tick();
}
#endif
@ -376,16 +520,8 @@ void RomImageWidget::drawWidget(bool hilite)
if(mySurfaceIsValid)
{
const Common::Rect& dst = mySurface->dstRect();
const uInt32 scale = instance().frameBuffer().hidpiScaleFactor();
const uInt32 x = _x * scale + ((_w * scale - dst.w()) >> 1);
const uInt32 y = _y * scale + ((myImageHeight * scale - dst.h()) >> 1);
s.fillRect(_x, _y, _w, myImageHeight, 0);
// Make sure when positioning the snapshot surface that we take
// the dialog surface position into account
const Common::Rect& s_dst = s.dstRect();
mySurface->setDstPos(x + s_dst.x(), y + s_dst.y());
posSurfaces();
}
else if(!mySurfaceErrorMsg.empty())
{
@ -393,6 +529,7 @@ void RomImageWidget::drawWidget(bool hilite)
const uInt32 y = _y + ((myImageHeight - _font.getLineHeight()) >> 1);
s.drawString(_font, mySurfaceErrorMsg, x, y, _w - 10, _textcolor);
}
// Draw the image label and counter
ostringstream buf;
buf << myImageIdx + 1 << "/" << myImageList.size();
@ -405,32 +542,75 @@ void RomImageWidget::drawWidget(bool hilite)
if(!myImageList.empty())
s.drawString(_font, buf.str(), _x + _w - wText, yText, wText, _textcolor);
// Draw the navigation arrows
// Draw the navigation icons
myNavSurface->invalidate();
if(isHighlighted() &&
((myMouseLeft && myImageIdx) || (!myMouseLeft && myImageIdx + 1 < myImageList.size())))
if(isHighlighted())
{
const int w = _w / 64;
const int w2 = 1; // w / 2;
const int ax = myMouseLeft ? _w / 12 - w / 2 : _w - _w / 12 - w / 2;
const int ay = myImageHeight >> 1;
const int dx = (_w / 32) * (myMouseLeft ? 1 : -1);
const int dy = myImageHeight / 16;
// Draw arrows
for(int dir = 0; dir < 2; ++dir)
{
// Is direction arrow shown?
if((!dir && myImageIdx) || (dir && myImageIdx + 1 < myImageList.size()))
{
const bool highlight =
(!dir && myMouseArea == Area::LEFT) ||
(dir && myMouseArea == Area::RIGHT);
const int w = _w / 64;
const int w2 = 1; // w / 2;
const int ax = !dir ? _w / 12 - w / 2 : _w - _w / 12 - w / 2;
const int ay = myImageHeight >> 1;
const int dx = (_w / 32) * (!dir ? 1 : -1);
const int dy = myImageHeight / 16;
for(int i = 0; i < w; ++i)
// Draw dark arrow frame
for(int i = 0; i < w; ++i)
{
myNavSurface->line(ax + dx + i + w2, ay - dy, ax + i + w2, ay, kBGColor);
myNavSurface->line(ax + dx + i + w2, ay + dy, ax + i + w2, ay, kBGColor);
myNavSurface->line(ax + dx + i, ay - dy + w2, ax + i, ay + w2, kBGColor);
myNavSurface->line(ax + dx + i, ay + dy + w2, ax + i, ay + w2, kBGColor);
myNavSurface->line(ax + dx + i + w2, ay - dy + w2, ax + i + w2, ay + w2, kBGColor);
myNavSurface->line(ax + dx + i + w2, ay + dy + w2, ax + i + w2, ay + w2, kBGColor);
}
// Draw bright arrow
for(int i = 0; i < w; ++i)
{
myNavSurface->line(ax + dx + i, ay - dy, ax + i, ay, highlight ? kColorInfo : kColor);
myNavSurface->line(ax + dx + i, ay + dy, ax + i, ay, highlight ? kColorInfo : kColor);
}
}
} // arrows
if(myImageList.size())
{
myNavSurface->line(ax + dx + i + w2, ay - dy, ax + i + w2, ay, kBGColor);
myNavSurface->line(ax + dx + i + w2, ay + dy, ax + i + w2, ay, kBGColor);
myNavSurface->line(ax + dx + i, ay - dy + w2, ax + i, ay + w2, kBGColor);
myNavSurface->line(ax + dx + i, ay + dy + w2, ax + i, ay + w2, kBGColor);
myNavSurface->line(ax + dx + i + w2, ay - dy + w2, ax + i + w2, ay + w2, kBGColor);
myNavSurface->line(ax + dx + i + w2, ay + dy + w2, ax + i + w2, ay + w2, kBGColor);
}
for(int i = 0; i < w; ++i)
{
myNavSurface->line(ax + dx + i, ay - dy, ax + i, ay, kColorInfo);
myNavSurface->line(ax + dx + i, ay + dy, ax + i, ay, kColorInfo);
}
// Draw zoom icon
const int dx = myZoomRect.w() / 2;
const int dy = myZoomRect.h() / 2;
const int w = dx * 2 / 3;
const int h = dy * 2 / 3;
for(int b = 1; b >= 0; --b) // First shadow, then bright
{
const ColorId color = b ? kBGColor : myMouseArea == Area::ZOOM ? kColorInfo : kColor;
const int ax = _w / 2 + b;
const int ay = myImageHeight / 2 + b;
for(int i = 0; i < myImageHeight / 80; ++i)
{
// Top left
myNavSurface->line(ax - dx, ay - dy + i, ax - dx + w, ay - dy + i, color);
myNavSurface->line(ax - dx + i, ay - dy, ax - dx + i, ay - dy + h, color);
// Top right
myNavSurface->line(ax + dx, ay - dy + i, ax + dx - w, ay - dy + i, color);
myNavSurface->line(ax + dx - i, ay - dy, ax + dx - i, ay - dy + h, color);
// Bottom left
myNavSurface->line(ax - dx, ay + dy - i, ax - dx + w, ay + dy - i, color);
myNavSurface->line(ax - dx + i, ay + dy, ax - dx + i, ay + dy - h, color);
// Bottom right
myNavSurface->line(ax + dx, ay + dy - i, ax + dx - w, ay + dy - i, color);
myNavSurface->line(ax + dx - i, ay + dy, ax + dx - i, ay + dy - h, color);
}
}
} // zoom icon
}
clearDirty();
}

View File

@ -44,11 +44,12 @@ class RomImageWidget : public Widget
uInt64 pendingLoadTime() { return myMaxLoadTime * timeFactor; }
protected:
void drawWidget(bool hilite) override;
#ifdef IMAGE_SUPPORT
void handleMouseUp(int x, int y, MouseButton b, int clickCount) override;
void handleMouseMoved(int x, int y) override;
void tick() override;
#endif
void drawWidget(bool hilite) override;
private:
void parseProperties(const FSNode& node, bool full = true);
@ -59,13 +60,31 @@ class RomImageWidget : public Widget
bool loadImage(const string& fileName);
bool loadPng(const string& fileName);
bool loadJpg(const string& fileName);
void zoomSurface(bool zoomed, bool force = false);
#endif
void posSurfaces();
private:
// Pending load time safety factor
static constexpr double timeFactor = 1.2;
private:
// Navigation areas
enum class Area {
NONE,
LEFT,
RIGHT,
ZOOM
};
// Zoom delay [frames]
static constexpr uInt32 DELAY_TIME = 45;
// The zoom is faster requested than released, so that repeated zooms are
// shown faster. This constant defines how much faster.
static constexpr uInt32 REQUEST_SPEED = 2;
// Surface pointer holding the image
shared_ptr<FBSurface> mySurface;
@ -75,6 +94,30 @@ class RomImageWidget : public Widget
// Whether the surface should be redrawn by drawWidget()
bool mySurfaceIsValid{false};
// Surface pointer holding the frame around the zoomed image
shared_ptr<FBSurface> myFrameSurface;
// Rectangle holdering the original surface size
Common::Rect mySrcRect;
// Zoom icon rectangle
Common::Rect myZoomRect;
// Surface zoom status
bool myIsZoomed{false};
// Zoom delay timer
uInt32 myZoomTimer{0};
// Initial zoom position
Common::Point myZoomPos;
// Last mouse position, used for zooming
Common::Point myMousePos;
// Current navigation area of the mouse
Area myMouseArea{Area::NONE};
// The properties for the currently selected ROM
Properties myProperties;
@ -93,9 +136,6 @@ class RomImageWidget : public Widget
// Index of currently displayed image
size_t myImageIdx{0};
// Current x-position of the mouse
bool myMouseLeft{true};
// Label for the loaded image
string myLabel;