mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-08-01 04:00:24 +02:00
Update to v094r14 release.
byuu says: Man, over five weeks have passed without so much as touching the codebase ... time is advancing so fast it's positively frightening. Oh well, little by little, and we'll get there eventually. Changelog: - added save state slots (1-5 in the menu) - added hotkeys settings dialog + mapping system - added fullscreen toggle (with a cute aspect correction trick) About three hours of work here. Short-term: - add input port changing support - add other input types (mouse-based, etc) - add cheat codes - add timing configuration (video/audio sync) Long-term: - add slotted cart loader (SGB, BSX, ST) - add DIP switch selection window (NSS) - add cheat code database - add state manager - add overscan masking Not planned: - video color adjustments (will allow emulated color vs raw color; but no more sliders) - pixel shaders - ananke integration (will need to make a command-line version to get my games in) - fancy audio adjustment controls (resampler, latency, volume) - input focus settings - relocating game library (not hard, just don't feel like it) - localization support (not enough users) - window geometry memory - anything else not in higan v094
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const char Name[] = "higan";
|
static const char Name[] = "higan";
|
||||||
static const char Version[] = "094.13";
|
static const char Version[] = "094.14";
|
||||||
static const char Author[] = "byuu";
|
static const char Author[] = "byuu";
|
||||||
static const char License[] = "GPLv3";
|
static const char License[] = "GPLv3";
|
||||||
static const char Website[] = "http://byuu.org/";
|
static const char Website[] = "http://byuu.org/";
|
||||||
|
@@ -139,9 +139,8 @@ template<> struct stringify<vector<uint8_t>> {
|
|||||||
auto data() const -> const char* { return _text.data(); }
|
auto data() const -> const char* { return _text.data(); }
|
||||||
auto size() const -> unsigned { return _text.size(); }
|
auto size() const -> unsigned { return _text.size(); }
|
||||||
stringify(vector<uint8_t> source) {
|
stringify(vector<uint8_t> source) {
|
||||||
_text.resize(source.size() + 1);
|
_text.resize(source.size());
|
||||||
memory::copy(_text.data(), source.data(), source.size());
|
memory::copy(_text.data(), source.data(), source.size());
|
||||||
_text[_text.size()] = 0;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,9 +149,8 @@ template<> struct stringify<const vector<uint8_t>&> {
|
|||||||
auto data() const -> const char* { return _text.data(); }
|
auto data() const -> const char* { return _text.data(); }
|
||||||
auto size() const -> unsigned { return _text.size(); }
|
auto size() const -> unsigned { return _text.size(); }
|
||||||
stringify(const vector<uint8_t>& source) {
|
stringify(const vector<uint8_t>& source) {
|
||||||
_text.resize(source.size() + 1);
|
_text.resize(source.size());
|
||||||
memory::copy(_text.data(), source.data(), source.size());
|
memory::copy(_text.data(), source.data(), source.size());
|
||||||
_text[_text.size()] = 0;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -15,8 +15,8 @@ auto activepath() -> string {
|
|||||||
auto realpath(rstring name) -> string {
|
auto realpath(rstring name) -> string {
|
||||||
string result;
|
string result;
|
||||||
char path[PATH_MAX] = "";
|
char path[PATH_MAX] = "";
|
||||||
if(::realpath(name, path)) result = string{path}.pathname();
|
if(::realpath(name, path)) result = string{path}.transform("\\", "/").pathname();
|
||||||
if(result.empty()) result = activepath();
|
if(result.empty()) return activepath();
|
||||||
result.transform("\\", "/");
|
result.transform("\\", "/");
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
|
@@ -5,6 +5,9 @@ auto config() -> ConfigurationManager& { return *configurationManager; }
|
|||||||
ConfigurationManager::ConfigurationManager() {
|
ConfigurationManager::ConfigurationManager() {
|
||||||
configurationManager = this;
|
configurationManager = this;
|
||||||
|
|
||||||
|
userInterface.append(userInterface.showStatusBar, "ShowStatusBar");
|
||||||
|
append(userInterface, "UserInterface");
|
||||||
|
|
||||||
video.append(video.driver, "Driver");
|
video.append(video.driver, "Driver");
|
||||||
video.append(video.synchronize, "Synchronize");
|
video.append(video.synchronize, "Synchronize");
|
||||||
video.append(video.scale, "Scale");
|
video.append(video.scale, "Scale");
|
||||||
|
@@ -2,6 +2,10 @@ struct ConfigurationManager : Configuration::Document {
|
|||||||
ConfigurationManager();
|
ConfigurationManager();
|
||||||
auto quit() -> void;
|
auto quit() -> void;
|
||||||
|
|
||||||
|
struct UserInterface : Configuration::Node {
|
||||||
|
bool showStatusBar = true;
|
||||||
|
} userInterface;
|
||||||
|
|
||||||
struct Video : Configuration::Node {
|
struct Video : Configuration::Node {
|
||||||
string driver;
|
string driver;
|
||||||
bool synchronize = false;
|
bool synchronize = false;
|
||||||
|
24
target-tomoko/input/hotkeys.cpp
Normal file
24
target-tomoko/input/hotkeys.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
auto InputManager::appendHotkeys() -> void {
|
||||||
|
{
|
||||||
|
auto hotkey = new InputHotkey;
|
||||||
|
hotkey->name = "Toggle Fullscreen";
|
||||||
|
hotkey->action = [] {
|
||||||
|
presentation->toggleFullScreen();
|
||||||
|
};
|
||||||
|
hotkeys.append(hotkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration::Node nodeHotkeys;
|
||||||
|
for(auto& hotkey : hotkeys) {
|
||||||
|
nodeHotkeys.append(hotkey->assignment, string{hotkey->name}.replace(" ", ""));
|
||||||
|
}
|
||||||
|
config.append(nodeHotkeys, "Hotkeys");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InputManager::pollHotkeys() -> void {
|
||||||
|
for(auto& hotkey : hotkeys) {
|
||||||
|
int16 state = hotkey->poll();
|
||||||
|
if(hotkey->state == 0 && state == 1 && hotkey->action) hotkey->action();
|
||||||
|
hotkey->state = state;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
#include "../tomoko.hpp"
|
#include "../tomoko.hpp"
|
||||||
|
#include "hotkeys.cpp"
|
||||||
InputManager* inputManager = nullptr;
|
InputManager* inputManager = nullptr;
|
||||||
|
|
||||||
auto InputMapping::bind() -> void {
|
auto InputMapping::bind() -> void {
|
||||||
@@ -85,6 +86,7 @@ InputManager::InputManager() {
|
|||||||
config.append(nodeEmulator, string{inputEmulator.name}.replace(" ", ""));
|
config.append(nodeEmulator, string{inputEmulator.name}.replace(" ", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendHotkeys();
|
||||||
config.load({configpath(), "tomoko/input.bml"});
|
config.load({configpath(), "tomoko/input.bml"});
|
||||||
config.save({configpath(), "tomoko/input.bml"});
|
config.save({configpath(), "tomoko/input.bml"});
|
||||||
poll(); //will call bind();
|
poll(); //will call bind();
|
||||||
@@ -100,6 +102,10 @@ auto InputManager::bind() -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(auto& hotkey : hotkeys) {
|
||||||
|
hotkey->bind();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputManager::poll() -> void {
|
auto InputManager::poll() -> void {
|
||||||
@@ -115,14 +121,19 @@ auto InputManager::poll() -> void {
|
|||||||
this->devices = devices;
|
this->devices = devices;
|
||||||
bind();
|
bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(presentation && presentation->focused()) pollHotkeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputManager::onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void {
|
auto InputManager::onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void {
|
||||||
if(settingsManager->focused()) {
|
if(settingsManager->focused()) {
|
||||||
settingsManager->input.inputEvent(device, group, input, oldValue, newValue);
|
settingsManager->input.inputEvent(device, group, input, oldValue, newValue);
|
||||||
|
settingsManager->hotkeys.inputEvent(device, group, input, oldValue, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputManager::quit() -> void {
|
auto InputManager::quit() -> void {
|
||||||
config.save({configpath(), "tomoko/input.bml"});
|
config.save({configpath(), "tomoko/input.bml"});
|
||||||
|
emulators.reset();
|
||||||
|
hotkeys.reset();
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,12 @@ struct InputMapping {
|
|||||||
unsigned input = 0;
|
unsigned input = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct InputHotkey : InputMapping {
|
||||||
|
function<void ()> action;
|
||||||
|
|
||||||
|
int16 state = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct InputDevice {
|
struct InputDevice {
|
||||||
string name;
|
string name;
|
||||||
vector<InputMapping*> mappings; //pointers used so that addresses do not change when arrays are resized
|
vector<InputMapping*> mappings; //pointers used so that addresses do not change when arrays are resized
|
||||||
@@ -34,8 +40,13 @@ struct InputManager {
|
|||||||
auto onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void;
|
auto onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void;
|
||||||
auto quit() -> void;
|
auto quit() -> void;
|
||||||
|
|
||||||
|
//hotkeys.cpp
|
||||||
|
auto appendHotkeys() -> void;
|
||||||
|
auto pollHotkeys() -> void;
|
||||||
|
|
||||||
vector<HID::Device*> devices;
|
vector<HID::Device*> devices;
|
||||||
vector<InputEmulator> emulators;
|
vector<InputEmulator> emulators;
|
||||||
|
vector<InputHotkey*> hotkeys;
|
||||||
Configuration::Document config;
|
Configuration::Document config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -67,6 +67,11 @@ Presentation::Presentation() {
|
|||||||
config().audio.mute = muteAudio.checked();
|
config().audio.mute = muteAudio.checked();
|
||||||
program->dsp.setVolume(config().audio.mute ? 0.0 : 1.0);
|
program->dsp.setVolume(config().audio.mute ? 0.0 : 1.0);
|
||||||
});
|
});
|
||||||
|
showStatusBar.setText("Show Status Bar").setChecked(config().userInterface.showStatusBar).onToggle([&] {
|
||||||
|
config().userInterface.showStatusBar = showStatusBar.checked();
|
||||||
|
statusBar.setVisible(config().userInterface.showStatusBar);
|
||||||
|
if(visible()) resizeViewport();
|
||||||
|
});
|
||||||
showConfiguration.setText("Configuration ...").onActivate([&] {
|
showConfiguration.setText("Configuration ...").onActivate([&] {
|
||||||
settingsManager->setVisible();
|
settingsManager->setVisible();
|
||||||
settingsManager->setFocused();
|
settingsManager->setFocused();
|
||||||
@@ -74,26 +79,28 @@ Presentation::Presentation() {
|
|||||||
|
|
||||||
toolsMenu.setText("Tools").setVisible(false);
|
toolsMenu.setText("Tools").setVisible(false);
|
||||||
saveStateMenu.setText("Save State");
|
saveStateMenu.setText("Save State");
|
||||||
saveSlot1.setText("Slot 1").onActivate([&] {});
|
saveSlot1.setText("Slot 1").onActivate([&] { program->saveState(1); });
|
||||||
saveSlot2.setText("Slot 2").onActivate([&] {});
|
saveSlot2.setText("Slot 2").onActivate([&] { program->saveState(2); });
|
||||||
saveSlot3.setText("Slot 3").onActivate([&] {});
|
saveSlot3.setText("Slot 3").onActivate([&] { program->saveState(3); });
|
||||||
saveSlot4.setText("Slot 4").onActivate([&] {});
|
saveSlot4.setText("Slot 4").onActivate([&] { program->saveState(4); });
|
||||||
saveSlot5.setText("Slot 5").onActivate([&] {});
|
saveSlot5.setText("Slot 5").onActivate([&] { program->saveState(5); });
|
||||||
loadStateMenu.setText("Load State");
|
loadStateMenu.setText("Load State");
|
||||||
loadSlot1.setText("Slot 1").onActivate([&] {});
|
loadSlot1.setText("Slot 1").onActivate([&] { program->loadState(1); });
|
||||||
loadSlot2.setText("Slot 2").onActivate([&] {});
|
loadSlot2.setText("Slot 2").onActivate([&] { program->loadState(2); });
|
||||||
loadSlot3.setText("Slot 3").onActivate([&] {});
|
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
||||||
loadSlot4.setText("Slot 4").onActivate([&] {});
|
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
||||||
loadSlot5.setText("Slot 5").onActivate([&] {});
|
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
||||||
stateManager.setText("State Manager").onActivate([&] {});
|
stateManager.setText("State Manager").onActivate([&] {});
|
||||||
cheatEditor.setText("Cheat Editor").onActivate([&] {});
|
cheatEditor.setText("Cheat Editor").onActivate([&] {});
|
||||||
|
|
||||||
statusBar.setFont(Font::sans(8, "Bold"));
|
statusBar.setFont(Font::sans(8, "Bold"));
|
||||||
|
statusBar.setVisible(config().userInterface.showStatusBar);
|
||||||
|
|
||||||
onClose([&] { program->quit(); });
|
onClose([&] { program->quit(); });
|
||||||
|
|
||||||
setTitle({"tomoko v", Emulator::Version});
|
setTitle({"tomoko v", Emulator::Version});
|
||||||
setResizable(false);
|
setResizable(false);
|
||||||
|
setBackgroundColor({16, 16, 16});
|
||||||
resizeViewport();
|
resizeViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,18 +113,62 @@ auto Presentation::resizeViewport() -> void {
|
|||||||
height = program->emulator().information.height;
|
height = program->emulator().information.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config().video.scale == "Small" ) width *= 1, height *= 1;
|
if(fullScreen() == false) {
|
||||||
if(config().video.scale == "Normal") width *= 2, height *= 2;
|
bool arc = config().video.aspectCorrection
|
||||||
if(config().video.scale == "Large" ) width *= 4, height *= 4;
|
&& program->activeEmulator
|
||||||
if(config().video.aspectCorrection) {
|
&& program->emulator().information.aspectRatio != 1.0;
|
||||||
if(!program->activeEmulator || program->emulator().information.aspectRatio != 1.0) width = width * 5 / 4;
|
|
||||||
|
if(config().video.scale == "Small" ) width *= 1, height *= 1;
|
||||||
|
if(config().video.scale == "Normal") width *= 2, height *= 2;
|
||||||
|
if(config().video.scale == "Large" ) width *= 4, height *= 4;
|
||||||
|
if(arc) width = width * 8 / 7;
|
||||||
|
|
||||||
|
setSize({width, height});
|
||||||
|
viewport.setGeometry({0, 0, width, height});
|
||||||
|
setPlacement(0.5, 0.5);
|
||||||
|
} else {
|
||||||
|
auto desktop = Desktop::size();
|
||||||
|
|
||||||
|
//aspect ratio correction is always enabled in fullscreen mode
|
||||||
|
//note that below algorithm yields 7:6 ratio on 2560x(1440,1600) monitors
|
||||||
|
//this is extremely close to the optimum 8:7 ratio
|
||||||
|
//it is used so that linear interpolation isn't required
|
||||||
|
//todo: we should handle other resolutions nicely as well
|
||||||
|
unsigned multiplier = desktop.height() / height;
|
||||||
|
width *= 1 + multiplier;
|
||||||
|
height *= multiplier;
|
||||||
|
|
||||||
|
signed x = (desktop.width() - width) / 2;
|
||||||
|
signed y = (desktop.height() - height) / 2;
|
||||||
|
|
||||||
|
if(x < 0) x = 0;
|
||||||
|
if(y < 0) y = 0;
|
||||||
|
if(width > desktop.width()) width = desktop.width();
|
||||||
|
if(height > desktop.height()) height = desktop.height();
|
||||||
|
|
||||||
|
viewport.setGeometry({x, y, width, height});
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize({width, height});
|
|
||||||
setPlacement(0.5, 0.5);
|
|
||||||
if(!program->activeEmulator) drawSplashScreen();
|
if(!program->activeEmulator) drawSplashScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Presentation::toggleFullScreen() -> void {
|
||||||
|
if(fullScreen() == false) {
|
||||||
|
menuBar.setVisible(false);
|
||||||
|
statusBar.setVisible(false);
|
||||||
|
setResizable(true);
|
||||||
|
setFullScreen(true);
|
||||||
|
} else {
|
||||||
|
setFullScreen(false);
|
||||||
|
setResizable(false);
|
||||||
|
menuBar.setVisible(true);
|
||||||
|
statusBar.setVisible(config().userInterface.showStatusBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::processEvents();
|
||||||
|
resizeViewport();
|
||||||
|
}
|
||||||
|
|
||||||
auto Presentation::drawSplashScreen() -> void {
|
auto Presentation::drawSplashScreen() -> void {
|
||||||
uint32* output;
|
uint32* output;
|
||||||
unsigned length;
|
unsigned length;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
struct Presentation : Window {
|
struct Presentation : Window {
|
||||||
Presentation();
|
Presentation();
|
||||||
auto resizeViewport() -> void;
|
auto resizeViewport() -> void;
|
||||||
|
auto toggleFullScreen() -> void;
|
||||||
auto drawSplashScreen() -> void;
|
auto drawSplashScreen() -> void;
|
||||||
|
|
||||||
MenuBar menuBar{this};
|
MenuBar menuBar{this};
|
||||||
@@ -28,6 +29,7 @@ struct Presentation : Window {
|
|||||||
MenuCheckItem synchronizeVideo{&settingsMenu};
|
MenuCheckItem synchronizeVideo{&settingsMenu};
|
||||||
MenuCheckItem synchronizeAudio{&settingsMenu};
|
MenuCheckItem synchronizeAudio{&settingsMenu};
|
||||||
MenuCheckItem muteAudio{&settingsMenu};
|
MenuCheckItem muteAudio{&settingsMenu};
|
||||||
|
MenuCheckItem showStatusBar{&settingsMenu};
|
||||||
MenuSeparator settingsMenuSeparator2{&settingsMenu};
|
MenuSeparator settingsMenuSeparator2{&settingsMenu};
|
||||||
MenuItem showConfiguration{&settingsMenu};
|
MenuItem showConfiguration{&settingsMenu};
|
||||||
Menu toolsMenu{&menuBar};
|
Menu toolsMenu{&menuBar};
|
||||||
@@ -47,8 +49,8 @@ struct Presentation : Window {
|
|||||||
MenuItem stateManager{&toolsMenu};
|
MenuItem stateManager{&toolsMenu};
|
||||||
MenuItem cheatEditor{&toolsMenu};
|
MenuItem cheatEditor{&toolsMenu};
|
||||||
|
|
||||||
VerticalLayout layout{this};
|
FixedLayout layout{this};
|
||||||
Viewport viewport{&layout, Size{~0, ~0}};
|
Viewport viewport{&layout, Geometry{0, 0, 1, 1}};
|
||||||
|
|
||||||
StatusBar statusBar{this};
|
StatusBar statusBar{this};
|
||||||
};
|
};
|
||||||
|
@@ -51,7 +51,7 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned p
|
|||||||
time(¤t);
|
time(¤t);
|
||||||
if(current != previous) {
|
if(current != previous) {
|
||||||
previous = current;
|
previous = current;
|
||||||
presentation->statusBar.setText({"FPS: ", frameCounter});
|
statusText = {"FPS: ", frameCounter};
|
||||||
frameCounter = 0;
|
frameCounter = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
|
|||||||
|
|
||||||
mediaPaths(0) = {userpath(), "Emulation/System/", media.name, ".sys/"};
|
mediaPaths(0) = {userpath(), "Emulation/System/", media.name, ".sys/"};
|
||||||
mediaPaths(media.id) = location;
|
mediaPaths(media.id) = location;
|
||||||
|
folderPaths.append(location);
|
||||||
|
|
||||||
setEmulator(&_emulator);
|
setEmulator(&_emulator);
|
||||||
updateVideoPalette();
|
updateVideoPalette();
|
||||||
@@ -35,6 +36,8 @@ auto Program::unloadMedia() -> void {
|
|||||||
emulator().unload();
|
emulator().unload();
|
||||||
|
|
||||||
setEmulator(nullptr);
|
setEmulator(nullptr);
|
||||||
|
mediaPaths.reset();
|
||||||
|
folderPaths.reset();
|
||||||
|
|
||||||
presentation->setTitle({"tomoko v", Emulator::Version});
|
presentation->setTitle({"tomoko v", Emulator::Version});
|
||||||
presentation->systemMenu.setVisible(false);
|
presentation->systemMenu.setVisible(false);
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
#include <gba/interface/interface.hpp>
|
#include <gba/interface/interface.hpp>
|
||||||
#include "interface.cpp"
|
#include "interface.cpp"
|
||||||
#include "media.cpp"
|
#include "media.cpp"
|
||||||
|
#include "state.cpp"
|
||||||
#include "utility.cpp"
|
#include "utility.cpp"
|
||||||
Program* program = nullptr;
|
Program* program = nullptr;
|
||||||
|
|
||||||
@@ -56,14 +57,15 @@ Program::Program() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Program::emulator() -> Emulator::Interface& {
|
auto Program::emulator() -> Emulator::Interface& {
|
||||||
if(activeEmulator == nullptr) throw;
|
if(!activeEmulator) throw;
|
||||||
return *activeEmulator;
|
return *activeEmulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Program::main() -> void {
|
auto Program::main() -> void {
|
||||||
|
updateStatusText();
|
||||||
inputManager->poll();
|
inputManager->poll();
|
||||||
|
|
||||||
if(activeEmulator == nullptr || emulator().loaded() == false) {
|
if(!activeEmulator || emulator().loaded() == false) {
|
||||||
audio.clear();
|
audio.clear();
|
||||||
usleep(20 * 1000);
|
usleep(20 * 1000);
|
||||||
return;
|
return;
|
||||||
|
@@ -24,7 +24,13 @@ struct Program : Emulator::Interface::Bind {
|
|||||||
auto loadMedia(Emulator::Interface& interface, Emulator::Interface::Media& media, const string& location) -> void;
|
auto loadMedia(Emulator::Interface& interface, Emulator::Interface::Media& media, const string& location) -> void;
|
||||||
auto unloadMedia() -> void;
|
auto unloadMedia() -> void;
|
||||||
|
|
||||||
|
//state.cpp
|
||||||
|
auto loadState(unsigned slot) -> bool;
|
||||||
|
auto saveState(unsigned slot) -> bool;
|
||||||
|
|
||||||
//utility.cpp
|
//utility.cpp
|
||||||
|
auto showMessage(const string& text) -> void;
|
||||||
|
auto updateStatusText() -> void;
|
||||||
auto updateVideoFilter() -> void;
|
auto updateVideoFilter() -> void;
|
||||||
auto updateVideoPalette() -> void;
|
auto updateVideoPalette() -> void;
|
||||||
|
|
||||||
@@ -32,7 +38,13 @@ struct Program : Emulator::Interface::Bind {
|
|||||||
|
|
||||||
vector<Emulator::Interface*> emulators;
|
vector<Emulator::Interface*> emulators;
|
||||||
Emulator::Interface* activeEmulator = nullptr;
|
Emulator::Interface* activeEmulator = nullptr;
|
||||||
|
|
||||||
vector<string> mediaPaths;
|
vector<string> mediaPaths;
|
||||||
|
vector<string> folderPaths;
|
||||||
|
|
||||||
|
string statusText;
|
||||||
|
string statusMessage;
|
||||||
|
time_t statusTime = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Program* program;
|
extern Program* program;
|
||||||
|
19
target-tomoko/program/state.cpp
Normal file
19
target-tomoko/program/state.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
auto Program::loadState(unsigned slot) -> bool {
|
||||||
|
if(!activeEmulator) return false;
|
||||||
|
auto memory = file::read({folderPaths[0], "higan/state-", slot, ".bst"});
|
||||||
|
if(memory.size() == 0) return showMessage({"Slot ", slot, " does not exist"}), false;
|
||||||
|
serializer s(memory.data(), memory.size());
|
||||||
|
if(emulator().unserialize(s) == false) return showMessage({"Slot ", slot, " state incompatible"}), false;
|
||||||
|
return showMessage({"Loaded from slot ", slot}), true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Program::saveState(unsigned slot) -> bool {
|
||||||
|
if(!activeEmulator) return false;
|
||||||
|
serializer s = emulator().serialize();
|
||||||
|
if(s.size() == 0) return showMessage({"Failed to save state to slot ", slot}), false;
|
||||||
|
directory::create({folderPaths[0], "higan/"});
|
||||||
|
if(file::write({folderPaths[0], "higan/state-", slot, ".bst"}, s.data(), s.size()) == false) {
|
||||||
|
return showMessage({"Unable to write to slot ", slot}), false;
|
||||||
|
}
|
||||||
|
return showMessage({"Saved to slot ", slot}), true;
|
||||||
|
}
|
@@ -1,3 +1,27 @@
|
|||||||
|
auto Program::showMessage(const string& text) -> void {
|
||||||
|
statusTime = time(0);
|
||||||
|
statusMessage = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Program::updateStatusText() -> void {
|
||||||
|
time_t currentTime = time(0);
|
||||||
|
|
||||||
|
string text;
|
||||||
|
if((currentTime - statusTime) <= 2) {
|
||||||
|
text = statusMessage;
|
||||||
|
} else if(!activeEmulator || emulator().loaded() == false) {
|
||||||
|
text = "No cartridge loaded";
|
||||||
|
} else if(0) {
|
||||||
|
text = "Paused";
|
||||||
|
} else {
|
||||||
|
text = statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(text != presentation->statusBar.text()) {
|
||||||
|
presentation->statusBar.setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Program::updateVideoFilter() -> void {
|
auto Program::updateVideoFilter() -> void {
|
||||||
if(config().video.filter == "None") video.set(Video::Filter, Video::FilterNearest);
|
if(config().video.filter == "None") video.set(Video::Filter, Video::FilterNearest);
|
||||||
if(config().video.filter == "Blur") video.set(Video::Filter, Video::FilterLinear);
|
if(config().video.filter == "Blur") video.set(Video::Filter, Video::FilterLinear);
|
||||||
|
62
target-tomoko/settings/hotkeys.cpp
Normal file
62
target-tomoko/settings/hotkeys.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
HotkeySettings::HotkeySettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||||
|
setIcon(Icon::Device::Keyboard);
|
||||||
|
setText("Hotkeys");
|
||||||
|
|
||||||
|
layout.setMargin(5);
|
||||||
|
mappingList.setHeaderVisible();
|
||||||
|
mappingList.onActivate([&] { assignMapping(); });
|
||||||
|
mappingList.onChange([&] {
|
||||||
|
eraseButton.setEnabled((bool)mappingList.selected());
|
||||||
|
});
|
||||||
|
eraseButton.setText("Erase").onActivate([&] {
|
||||||
|
if(auto item = mappingList.selected()) {
|
||||||
|
inputManager->hotkeys[item->offset()]->unbind();
|
||||||
|
refreshMappings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reloadMappings();
|
||||||
|
refreshMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HotkeySettings::reloadMappings() -> void {
|
||||||
|
mappingList.reset();
|
||||||
|
mappingList.append(ListViewColumn().setText("Name"));
|
||||||
|
mappingList.append(ListViewColumn().setText("Mapping").setWidth(~0));
|
||||||
|
mappingList.append(ListViewColumn().setText("Device"));
|
||||||
|
for(auto& hotkey : inputManager->hotkeys) {
|
||||||
|
mappingList.append(ListViewItem().setText(0, hotkey->name));
|
||||||
|
}
|
||||||
|
mappingList.resizeColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HotkeySettings::refreshMappings() -> void {
|
||||||
|
unsigned position = 0;
|
||||||
|
for(auto& hotkey : inputManager->hotkeys) {
|
||||||
|
auto path = hotkey->assignment.split("/");
|
||||||
|
string assignment = path.takeLast();
|
||||||
|
string device = path(0);
|
||||||
|
mappingList.item(position++)->setText(1, assignment).setText(2, device);
|
||||||
|
}
|
||||||
|
mappingList.resizeColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HotkeySettings::assignMapping() -> void {
|
||||||
|
inputManager->poll(); //clear any pending events first
|
||||||
|
|
||||||
|
if(auto item = mappingList.selected()) {
|
||||||
|
activeMapping = inputManager->hotkeys[item->offset()];
|
||||||
|
settingsManager->statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HotkeySettings::inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void {
|
||||||
|
if(!activeMapping) return;
|
||||||
|
if(!device.isKeyboard() || oldValue != 0 || newValue != 1) return;
|
||||||
|
|
||||||
|
if(activeMapping->bind(device, group, input, oldValue, newValue)) {
|
||||||
|
activeMapping = nullptr;
|
||||||
|
settingsManager->statusBar.setText("");
|
||||||
|
refreshMappings();
|
||||||
|
}
|
||||||
|
}
|
@@ -9,10 +9,11 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
|
|||||||
emulatorList.onChange([&] { reloadPorts(); });
|
emulatorList.onChange([&] { reloadPorts(); });
|
||||||
portList.onChange([&] { reloadDevices(); });
|
portList.onChange([&] { reloadDevices(); });
|
||||||
deviceList.onChange([&] { reloadMappings(); });
|
deviceList.onChange([&] { reloadMappings(); });
|
||||||
mappingList.onActivate([&] { assignMapping(); }).onChange([&] {
|
mappingList.setHeaderVisible();
|
||||||
|
mappingList.onActivate([&] { assignMapping(); });
|
||||||
|
mappingList.onChange([&] {
|
||||||
eraseButton.setEnabled((bool)mappingList.selected());
|
eraseButton.setEnabled((bool)mappingList.selected());
|
||||||
});
|
});
|
||||||
mappingList.setHeaderVisible();
|
|
||||||
resetButton.setText("Reset").onActivate([&] {
|
resetButton.setText("Reset").onActivate([&] {
|
||||||
if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsManager).question() == 0) {
|
if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsManager).question() == 0) {
|
||||||
for(auto& mapping : activeDevice().mappings) mapping->unbind();
|
for(auto& mapping : activeDevice().mappings) mapping->unbind();
|
||||||
@@ -25,6 +26,7 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
|
|||||||
refreshMappings();
|
refreshMappings();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reloadPorts();
|
reloadPorts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#include "../tomoko.hpp"
|
#include "../tomoko.hpp"
|
||||||
#include "input.cpp"
|
#include "input.cpp"
|
||||||
|
#include "hotkeys.cpp"
|
||||||
#include "advanced.cpp"
|
#include "advanced.cpp"
|
||||||
SettingsManager* settingsManager = nullptr;
|
SettingsManager* settingsManager = nullptr;
|
||||||
|
|
||||||
@@ -14,4 +15,5 @@ SettingsManager::SettingsManager() {
|
|||||||
setPlacement(0.0, 1.0);
|
setPlacement(0.0, 1.0);
|
||||||
|
|
||||||
input.mappingList.resizeColumns();
|
input.mappingList.resizeColumns();
|
||||||
|
hotkeys.mappingList.resizeColumns();
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,22 @@ struct InputSettings : TabFrameItem {
|
|||||||
Button eraseButton{&controlLayout, Size{80, 0}};
|
Button eraseButton{&controlLayout, Size{80, 0}};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HotkeySettings : TabFrameItem {
|
||||||
|
HotkeySettings(TabFrame*);
|
||||||
|
auto reloadMappings() -> void;
|
||||||
|
auto refreshMappings() -> void;
|
||||||
|
auto assignMapping() -> void;
|
||||||
|
auto inputEvent(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void;
|
||||||
|
|
||||||
|
InputMapping* activeMapping = nullptr;
|
||||||
|
|
||||||
|
VerticalLayout layout{this};
|
||||||
|
ListView mappingList{&layout, Size{~0, ~0}};
|
||||||
|
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||||
|
Widget spacer{&controlLayout, Size{~0, 0}};
|
||||||
|
Button eraseButton{&controlLayout, Size{80, 0}};
|
||||||
|
};
|
||||||
|
|
||||||
struct AdvancedSettings : TabFrameItem {
|
struct AdvancedSettings : TabFrameItem {
|
||||||
AdvancedSettings(TabFrame*);
|
AdvancedSettings(TabFrame*);
|
||||||
|
|
||||||
@@ -44,6 +60,7 @@ struct SettingsManager : Window {
|
|||||||
VerticalLayout layout{this};
|
VerticalLayout layout{this};
|
||||||
TabFrame panelLayout{&layout, Size{~0, ~0}};
|
TabFrame panelLayout{&layout, Size{~0, ~0}};
|
||||||
InputSettings input{&panelLayout};
|
InputSettings input{&panelLayout};
|
||||||
|
HotkeySettings hotkeys{&panelLayout};
|
||||||
AdvancedSettings advanced{&panelLayout};
|
AdvancedSettings advanced{&panelLayout};
|
||||||
|
|
||||||
StatusBar statusBar{this};
|
StatusBar statusBar{this};
|
||||||
|
Reference in New Issue
Block a user