Commit f622e305 authored by Jack's avatar Jack
Browse files

implemented basic menu functions, added icon and .desktop file for opk

parent 6a71d6f9
[Desktop Entry]
Type=Emulator
Name=retro-8
Comment=PICO-8 Emulator
Terminal=false
StartupNotify=true
Exec=retro8
Icon=icon
Categories=emulator;
data/icon.png

341 Bytes

......@@ -57,11 +57,15 @@ private:
#define LOGD(...)
#endif
#define OPTS_ENABLED true
#ifdef _WIN32
#define MOUSE_ENABLED true
#define WINDOW_SCALE 1
#define DEBUGGER true
#define TEST_MODE true
#define DESKTOP_MODE true
#define TEST_MODE false
#else
#define MOUSE_ENABLED false
#define DESKTOP_MODE false
#endif
......@@ -10,7 +10,6 @@ namespace r8 = retro8;
retro8::Machine machine;
GameView::GameView(ViewManager* manager) : manager(manager),
_paused(false), _showFPS(false), _showCartridgeName(false)
{
......@@ -118,20 +117,22 @@ SDL_Rect dest;
dest = { (480 - 384) / 2, (480 - 384) / 2, 384, 384 };
#else
if (scale == Scale::UNSCALED)
if (_scaler == Scaler::UNSCALED)
dest = { (320 - 128) / 2, (240 - 128) / 2, 128, 128 };
else if (scale == Scale::SCALED_ASPECT_2x)
else if (_scaler == Scaler::SCALED_ASPECT_2x)
dest = { (320 - 256) / 2, (240 - 256) / 2, 256, 256 };
else
dest = { 0, 0, 320, 240 };
#endif
SDL_RenderCopy(renderer, _outputTexture, nullptr, &dest);
manager->text(_path.c_str(), 10, 10);
char buffer[16];
sprintf(buffer, "%.0f/%c0", 1000.0f / manager->lastFrameTicks(), machine.code().require60fps() ? '6' : '3');
manager->text(buffer, 10, 22);
if (_showFPS)
{
char buffer[16];
sprintf(buffer, "%.0f/%c0", 1000.0f / manager->lastFrameTicks(), machine.code().require60fps() ? '6' : '3');
manager->text(buffer, 10, 10);
}
++_frameCounter;
......@@ -282,30 +283,28 @@ void GameView::handleKeyboardEvent(const SDL_Event& event)
manageKey(5, event.type == SDL_KEYDOWN);
break;
#if DESKTOP_MODE
case SDLK_p:
if (event.type == SDL_KEYDOWN)
_paused = !_paused;
#if SOUND_ENABLED
if (_paused)
machine.sound().pause();
else
machine.sound().resume();
#endif
if (_paused)
pause();
else
resume();
break;
#ifndef _WIN32
#else
case SDLK_TAB:
if (event.type == SDL_KEYDOWN)
{
if (scale == Scale::UNSCALED) scale = Scale::SCALED_ASPECT_2x;
else if (scale == Scale::SCALED_ASPECT_2x) scale = Scale::FULLSCREEN;
else scale = Scale::UNSCALED;
if (_scaler < Scaler::LAST) _scaler = Scaler(_scaler + 1);
else _scaler = Scaler::FIRST;
}
break;
#endif
case SDLK_RETURN:
manager->openMenu();
break;
case SDLK_ESCAPE:
if (event.type == SDL_KEYDOWN)
manager->exit();
......@@ -319,6 +318,24 @@ void GameView::handleMouseEvent(const SDL_Event& event)
}
void GameView::pause()
{
_paused = true;
#if SOUND_ENABLED
machine.sound().pause();
#endif
}
void GameView::resume()
{
_paused = false;
#if SOUND_ENABLED
machine.sound().resume();
#endif
}
GameView::~GameView()
{
SDL_FreeSurface(_output);
......
......@@ -14,18 +14,21 @@
namespace ui
{
enum class Scale
enum Scaler
{
UNSCALED,
UNSCALED = 0,
SCALED_ASPECT_2x,
FULLSCREEN
FULLSCREEN,
FIRST = UNSCALED,
LAST = FULLSCREEN
};
class GameView : public View
{
private:
uint32_t _frameCounter;
Scale scale = Scale::UNSCALED;
Scaler _scaler = Scaler::UNSCALED;
ViewManager* manager;
......@@ -65,6 +68,14 @@ namespace ui
void loadCartridge(const std::string& path) { _path = path; }
void pause();
void resume();
void setScaler(Scaler scaler) { _scaler = scaler; }
Scaler scaler() const { return _scaler; }
void toggleFPS(bool active) { _showFPS = active; }
bool isFPSShown() { return _showFPS; }
};
class MenuView : public View
......@@ -80,5 +91,8 @@ namespace ui
void handleMouseEvent(const SDL_Event& event) override;
void render() override;
void reset();
void updateLabels();
};
}
#include "main_view.h"
extern retro8::Machine machine;
using namespace ui;
struct MenuEntry
{
enum class Type
{
SUB_MENU,
ENTRY
};
Type type;
std::string caption;
mutable std::string caption;
mutable std::function<void(void)> lambda;
MenuEntry(const std::string& caption) : type(Type::ENTRY), caption(caption) { }
MenuEntry(const std::string& caption) : caption(caption) { }
};
static const std::vector<MenuEntry> mainMenu = {
......@@ -25,26 +20,98 @@ static const std::vector<MenuEntry> mainMenu = {
MenuEntry("exit")
};
const std::vector<MenuEntry>* menu = &mainMenu;
std::vector<MenuEntry>::const_iterator selected = menu->begin();
static const std::vector<MenuEntry> optionsMenu = {
MenuEntry("show fps"),
MenuEntry("scaler 1:1"),
MenuEntry("sound on"),
MenuEntry("music on"),
MenuEntry("back")
};
const std::vector<MenuEntry>* menu;
std::vector<MenuEntry>::const_iterator selected;
enum { RESUME = 0, HELP, OPTIONS, RESET, EXIT, SHOW_FPS = 0, SCALER, SOUND, MUSIC, BACK };
MenuView::MenuView(ViewManager* gvm) : _gvm(gvm)
{
mainMenu[4].lambda = [this]() {
static_assert(SCALER == 1, "must be 1");
mainMenu[RESUME].lambda = [this]() {
_gvm->backToGame();
};
mainMenu[OPTIONS].lambda = [this]() {
menu = &optionsMenu;
selected = menu->begin();
};
mainMenu[EXIT].lambda = [this]() {
_gvm->exit();
};
optionsMenu[SHOW_FPS].lambda = [this]() {
bool v = !_gvm->gameView()->isFPSShown();
_gvm->gameView()->toggleFPS(v);
updateLabels();
};
optionsMenu[SCALER].lambda = [this]() {
auto scaler = _gvm->gameView()->scaler();
if (scaler < Scaler::LAST)
_gvm->gameView()->setScaler(Scaler(scaler + 1));
else
_gvm->gameView()->setScaler(Scaler::FIRST);
updateLabels();
};
optionsMenu[SOUND].lambda = [this]() {
bool v = !machine.sound().isMusicEnabled();
machine.sound().toggleMusic(v);
updateLabels();
};
optionsMenu[MUSIC].lambda = [this]() {
bool v = !machine.sound().isMusicEnabled();
machine.sound().toggleMusic(v);
updateLabels();
};
optionsMenu[BACK].lambda = [this]() {
menu = &mainMenu;
selected = menu->begin();
};
reset();
}
MenuView::~MenuView() { }
void MenuView::handleKeyboardEvent(const SDL_Event& event)
{
static constexpr auto ACTION_BUTTON = SDLK_LCTRL;
static constexpr auto BACK_BUTTON = SDLK_LALT;
if (event.type == SDL_KEYDOWN)
{
switch (event.key.keysym.sym)
{
case SDLK_UP: if (selected > menu->begin()) --selected; else selected = menu->end() - 1; break;
case SDLK_DOWN: if (selected < menu->end() - 1) ++selected; else selected = menu->begin(); break;
case ACTION_BUTTON:
{
if (selected->lambda)
selected->lambda();
break;
}
case BACK_BUTTON:
{
if (menu == &mainMenu)
mainMenu[0].lambda();
else if (menu == &optionsMenu)
optionsMenu[4].lambda();
break;
}
}
}
}
......@@ -54,6 +121,31 @@ void MenuView::handleMouseEvent(const SDL_Event& event)
}
void MenuView::updateLabels()
{
optionsMenu[MUSIC].caption = std::string("music ") + (machine.sound().isMusicEnabled() ? "on" : "off");
optionsMenu[SOUND].caption = std::string("sound ") + (machine.sound().isMusicEnabled() ? "on" : "off");
optionsMenu[SHOW_FPS].caption = std::string("show fps ") + (_gvm->gameView()->isFPSShown() ? "on" : "off");
auto scaler = _gvm->gameView()->scaler();
std::string scalerLabel = "scaler ";
switch (scaler) {
case Scaler::UNSCALED: scalerLabel += "1:1"; break;
case Scaler::SCALED_ASPECT_2x: scalerLabel += "2:1"; break;
case Scaler::FULLSCREEN: scalerLabel += "fit screen"; break;
}
optionsMenu[SCALER].caption = scalerLabel;
}
void MenuView::reset()
{
menu = &mainMenu;
selected = menu->begin();
updateLabels();
}
void MenuView::render()
{
constexpr int32_t W = 320;
......@@ -72,14 +164,16 @@ void MenuView::render()
_gvm->text("retro8", W / 2, 20, { 0, 47, 255 }, TextAlign::CENTER, 4.0f);
_gvm->text("v0.1", W / 2 + _gvm->textWidth("retro8", 4.0)/2 + 3, 34, { 0, 47, 255 }, TextAlign::LEFT, 2.0f);
retro8::point_t menuBase = { W / 2, 80 };
retro8::point_t menuBase = { W / 2, 90 };
for (auto it = menu->begin(); it != menu->end(); ++it)
{
const SDL_Color color = it == selected ? SDL_Color{ 255, 255, 0 } : SDL_Color{ 255,255,255 };
SDL_Color color = it == selected ? SDL_Color{ 255, 255, 0 } : SDL_Color{ 255,255,255 };
if (it != selected && !it->lambda)
color = { 160, 160, 160 };
_gvm->text(it->caption, menuBase.x, menuBase.y, color, TextAlign::CENTER, 2.0f);
menuBase.y += 16;
}
}
\ No newline at end of file
......@@ -12,11 +12,10 @@ using namespace ui;
extern retro8::Machine machine;
ui::ViewManager::ViewManager() : SDL<ui::ViewManager, ui::ViewManager>(*this, *this), _font(nullptr)
ui::ViewManager::ViewManager() : SDL<ui::ViewManager, ui::ViewManager>(*this, *this), _font(nullptr),
_gameView(new GameView(this)), _menuView(new MenuView(this))
{
views[0] = new GameView(this);
views[1] = new MenuView(this);
view = views[1];
_view = _gameView;
}
void ui::ViewManager::deinit()
......@@ -41,18 +40,18 @@ bool ui::ViewManager::loadData()
void ui::ViewManager::handleKeyboardEvent(const SDL_Event& event, bool press)
{
view->handleKeyboardEvent(event);
_view->handleKeyboardEvent(event);
}
void ui::ViewManager::handleMouseEvent(const SDL_Event& event)
{
view->handleMouseEvent(event);
_view->handleMouseEvent(event);
}
void ui::ViewManager::render()
{
view->render();
_view->render();
}
void ui::ViewManager::text(const std::string& text, int32_t x, int32_t y)
......@@ -89,4 +88,17 @@ void ViewManager::text(const std::string& text, int32_t x, int32_t y, SDL_Color
}
SDL_SetTextureColorMod(_font, 255, 255, 255);
}
void ViewManager::openMenu()
{
_gameView->pause();
_menuView->reset();
_view = _menuView;
}
void ViewManager::backToGame()
{
_gameView->resume();
_view = _gameView;
}
\ No newline at end of file
......@@ -20,6 +20,7 @@ namespace ui
};
class GameView;
class MenuView;
class ViewManager : public SDL<ViewManager, ViewManager>
{
......@@ -30,8 +31,9 @@ namespace ui
SDL_Texture* _font;
private:
std::array<view_t*, 2> views;
view_t* view;
GameView* _gameView;
MenuView* _menuView;
view_t* _view;
public:
ViewManager();
......@@ -47,11 +49,14 @@ namespace ui
SDL_Texture* font() { return _font; }
//TODO: hacky cast to avoid header inclusion
GameView* gameView() { return (GameView*)views[0]; }
GameView* gameView() { return _gameView; }
int32_t textWidth(const std::string& text, float scale = 2.0f) const { return text.length() * scale * 4; }
void text(const std::string& text, int32_t x, int32_t y, SDL_Color color, TextAlign align, float scale = 2.0f);
void text(const std::string& text, int32_t x, int32_t y);
void openMenu();
void backToGame();
};
}
......@@ -124,9 +124,33 @@ void Machine::rect(coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color
void Machine::rectfill(coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color)
{
#if OPTS_ENABLED
/* compute directly actual bounding box and set the rect without invoking pset */
auto* clip = _memory.clipRect();
auto cx = memory().camera()->x(), cy = memory().camera()->y();
x0 -= cx;
y0 -= cy;
x1 -= cx;
y1 -= cy;
x0 = std::max(x0, coord_t(clip->x0));
x1 = std::min(x1, coord_t(clip->x1));
y0 = std::max(y0, coord_t(clip->y0));
y1 = std::min(y1, coord_t(clip->y1));
color = _memory.paletteAt(gfx::DRAW_PALETTE_INDEX)->get(color_t(color % gfx::COLOR_COUNT));
for (coord_t y = y0; y <= y1; ++y)
for (coord_t x = x0; x <= x1; ++x)
_memory.screenData(x, y)->set(x, color);
#else
for (coord_t y = y0; y <= y1; ++y)
for (coord_t x = x0; x <= x1; ++x)
pset(x, y, color);
#endif
}
void Machine::circHelper(coord_t xc, coord_t yc, coord_t x, coord_t y, color_t color)
......
......@@ -439,23 +439,27 @@ void APU::renderSounds(int16_t* dest, size_t totalSamples)
SoundState& channel = channels[i].sound ? channels[i] : mstate.channels[i];
const Music* music = &channel == &this->mstate.channels[i] ? this->mstate.music : nullptr; //TODO: crappy comparison
if (channel.sound)
/* render only if enabled */
if ((music && _musicEnabled) || (!music && _soundEnabled))
{
const size_t samplePerTick = (44100 / 128) * (channel.sound->speed + 1);
while (samples > 0 && channel.sound)
if (channel.sound)
{
/* generate the maximum amount of samples available for same note */
// TODO: optimize if next note is equal to current
size_t available = std::min(samples, samplePerTick - (channel.position % samplePerTick));
renderSound(channel, buffer, available);
const size_t samplePerTick = (44100 / 128) * (channel.sound->speed + 1);
while (samples > 0 && channel.sound)
{
/* generate the maximum amount of samples available for same note */
// TODO: optimize if next note is equal to current
size_t available = std::min(samples, samplePerTick - (channel.position % samplePerTick));
renderSound(channel, buffer, available);
samples -= available;
buffer += available;
channel.position += available;
channel.sample = channel.position / samplePerTick;
samples -= available;
buffer += available;
channel.position += available;
channel.sample = channel.position / samplePerTick;
updateChannel(channel, music);
updateChannel(channel, music);
}
}
}
}
......
......@@ -201,22 +201,25 @@ namespace retro8
SDL_AudioSpec spec;
SDL_AudioDeviceID device;
std::array<SoundState, CHANNEL_COUNT> channels;
MusicState mstate;
std::mutex queueMutex;
std::vector<Command> queue;
bool _soundEnabled, _musicEnabled;
void handleCommands();
void updateMusic();
void renderSound(const SoundState& sound, int16_t* buffer, size_t samples);
void updateChannel(SoundState& channel, const Music* music);
public:
APU(Memory& memory) : memory(memory) { }
APU(Memory& memory) : memory(memory), _soundEnabled(true), _musicEnabled(true) { }
void init();
void close();
......@@ -228,6 +231,12 @@ namespace retro8
void music(music_index_t index, int32_t fadeMs, int32_t mask);
void renderSounds(int16_t* dest, size_t samples);
bool isMusicEnabled() const { return _musicEnabled; }
bool isSoundEnabled() const { return _soundEnabled; }
void toggleSound(bool active) { _soundEnabled = active; }
void toggleMusic(bool active) { _musicEnabled = active; }
};
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment