quickerNES/extern/hqn/hqn_gui_controller.cpp

340 lines
9.7 KiB
C++

#include "hqn_gui_controller.h"
#include "hqn_util.h"
#include <algorithm>
#include <cstring>
#include <SDL.h>
#define DEFAULT_WIDTH 256
#define DEFAULT_HEIGHT 240
namespace hqn
{
const char *DEFAULT_WINDOW_TITLE = "HeadlessQuickNES";
const SDL_Rect NES_BLIT_RECT = { 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT };
// Determine the letterboxing required to display something on screen.
// The original code is basically magic.
// aspectW/H and windowW/H are the aspect ratio and window size, they are
// inputs. The view* parameters are the outputs and correspond to the
// calculated area in the window where the view should be.
void determineLetterbox(double aspectW, double aspectH, double windowW,
double windowH, int &viewX, int &viewY, int &viewW, int &viewH)
{
double scale = std::min(windowW / aspectW, windowH / aspectH);
viewW = (int)(aspectW * scale);
viewH = (int)(aspectH * scale);
viewX = (int)(windowW - viewW) / 2;
viewY = (int)(windowH - viewH) / 2;
}
/*
-- Returns: scale, scissorX, scissorY, scissorW, scissorH, offsetX, offsetY
function xl.calculateViewport(sizes, winW, winH, scaleInterval, screenFlex )
local gameW,gameH = sizes.w, sizes.h
local screenW,screenH = winW + screenFlex, winH + screenFlex
local scale = math.min(screenW / gameW, screenH / gameH)
scale = math.floor(scale * scaleInterval) / scaleInterval
local scissorW, scissorH = gameW * scale, gameH * scale
local scissorX, scissorY = (winW - scissorW) / 2, (winH - scissorH) / 2
local offsetX, offsetY = scissorX / scale, scissorY / scale
return scale, scissorX, scissorY, scissorW, scissorH, offsetX, offsetY
end
*/
GUIController::GUIController(HQNState &state)
:m_state(state)
{
m_tex = nullptr;
m_texOverlay = nullptr;
m_renderer = nullptr;
m_window = nullptr;
m_overlay = nullptr;
m_closeOp = CLOSE_QUIT;
m_isFullscreen = false;
}
GUIController::~GUIController()
{
if (m_tex)
SDL_DestroyTexture(m_tex);
if (m_texOverlay)
SDL_DestroyTexture(m_texOverlay);
if (m_renderer)
SDL_DestroyRenderer(m_renderer);
if (m_window)
SDL_DestroyWindow(m_window);
m_state.setListener(nullptr);
}
GUIController *GUIController::create(HQNState &state)
{
GUIController *self = new GUIController(state);
if (!self->init())
{
delete self;
return nullptr;
}
else
{
return self;
}
}
bool GUIController::init()
{
// create the window
if (!(m_window = SDL_CreateWindow(DEFAULT_WINDOW_TITLE,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DEFAULT_WIDTH,
DEFAULT_HEIGHT, 0)))
return false;
if (!(m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED)))
return false;
if (!(m_tex = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, 256, 256)))
return false;
// Set the clear color now rather than later
SDL_SetRenderDrawColor(m_renderer, 0, 0, 0, 255);
// Default the scale to 1
if (!setScale(1))
return false;
return true;
}
bool GUIController::setScale(int scale)
{
if (scale < 1 || scale > 5)
return false;
if (m_isFullscreen)
setFullscreen(false, false); // reset to windowed and don't bother changing the overlay
int winW = DEFAULT_WIDTH * scale;
int winH = DEFAULT_HEIGHT * scale;
// Change the window size
SDL_SetWindowSize(m_window, winW, winH);
if (!onResize(winW, winH, true))
return false;
// update the overlay destination
m_overlayDest = { 0, 0, winW, winH };
m_nesDest = { 0, 0, winW, winH };
// Update internal scale variable
m_scale = scale;
return true;
}
int GUIController::getScale() const
{ return m_scale; }
void GUIController::setFullscreen(bool full, bool adjustOverlay)
{
m_isFullscreen = full;
SDL_SetWindowFullscreen(m_window, full ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
int w, h;
SDL_GetWindowSize(m_window, &w, &h);
onResize((size_t)w, (size_t)h, adjustOverlay);
}
bool GUIController::isFullscreen() const
{
return m_isFullscreen;
}
bool GUIController::onResize(size_t w, size_t h, bool adjustOverlay)
{
// first calculate letterboxing
int viewX, viewY, viewW, viewH;
determineLetterbox(DEFAULT_WIDTH, DEFAULT_HEIGHT, w, h,
viewX, viewY, viewW, viewH);
// make sure images are put in the right places
m_overlayDest = { viewX, viewY, viewW, viewH };
m_nesDest = { viewX, viewY, viewW, viewH };
if (adjustOverlay && !resizeOverlay(viewW, viewH))
return false;
return true;
}
bool GUIController::resizeOverlay(size_t w, size_t h)
{
// destroy the overlay and corresponding texture
if (m_overlay)
{
// first check if the overlay is already the correct size
if (m_overlay->getWidth() == (int)w
&& m_overlay->getHeight() == (int)h)
return true;
else
delete m_overlay;
}
if (m_texOverlay)
SDL_DestroyTexture(m_texOverlay);
// Now re-create them
m_overlay = new Surface(w, h);
if (!(m_texOverlay = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, w, h)))
return false;
SDL_SetTextureBlendMode(m_texOverlay, SDL_BLENDMODE_BLEND);
return true;
}
void GUIController::update(bool readNES)
{
void *nesPixels = nullptr;
int pitch = 0;
if (SDL_LockTexture(m_tex, nullptr, &nesPixels, &pitch) < 0) return;
SDL_UnlockTexture(m_tex);
// render to screen
SDL_RenderClear(m_renderer);
SDL_RenderCopy(m_renderer, m_tex, &NES_BLIT_RECT, &m_nesDest);
SDL_RenderPresent(m_renderer);
// Process any outstanding events
processEvents();
}
void GUIController::update_blit(const int32_t* blit, SDL_Surface* base, SDL_Surface* button_a, SDL_Surface* button_b, SDL_Surface* button_select, SDL_Surface* button_start, SDL_Surface* button_up, SDL_Surface* button_down, SDL_Surface* button_left, SDL_Surface* button_right)
{
void *nesPixels = nullptr;
int pitch = 0;
if (SDL_LockTexture(m_tex, nullptr, &nesPixels, &pitch) < 0) return;
memcpy(nesPixels, blit, sizeof(int32_t) * BLIT_SIZE);
SDL_UnlockTexture(m_tex);
const SDL_Rect OVERLAY_BLIT_RECT_SRC = { 0, 0, 169, 53 };
const SDL_Rect OVERLAY_BLIT_RECT_DST = { 343, 425, 169, 53 };
// render to screen
SDL_RenderClear(m_renderer);
SDL_RenderCopy(m_renderer, m_tex, &NES_BLIT_RECT, &m_nesDest);
if (base != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, base);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_a != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_a);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_b != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_b);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_select != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_select);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_start != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_start);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_up != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_up);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_down != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_down);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_left != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_left);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
if (button_right != NULL)
{
auto overlayTex = SDL_CreateTextureFromSurface(m_renderer, button_right);
SDL_RenderCopy(m_renderer, overlayTex, &OVERLAY_BLIT_RECT_SRC, &OVERLAY_BLIT_RECT_DST);
SDL_DestroyTexture(overlayTex);
}
SDL_RenderPresent(m_renderer);
// Process any outstanding events
processEvents();
}
void GUIController::onAdvanceFrame(HQNState *state)
{
update(true);
}
void GUIController::setTitle(const char *title)
{
SDL_SetWindowTitle(m_window, title);
}
void GUIController::setCloseOperation(CloseOperation closeop)
{
m_closeOp = closeop;
}
GUIController::CloseOperation GUIController::getCloseOperation() const
{
return m_closeOp;
}
void GUIController::processEvents()
{
bool quit = false;
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
quit = true;
break;
}
}
if (quit)
{
if (m_closeOp & CLOSE_QUIT)
{
exit(0);
}
if (m_closeOp & CLOSE_DELETE)
{
m_state.setListener(nullptr);
delete this;
}
}
}
void GUIController::onLoadROM(HQNState *state, const char *filename) {} // unimportant
void GUIController::onLoadState(HQNState *state) {} // also unimportant
}