mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
b4ba95242f
commit
89d578bc7f
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
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 License[] = "GPLv3";
|
||||
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 size() const -> unsigned { return _text.size(); }
|
||||
stringify(vector<uint8_t> source) {
|
||||
_text.resize(source.size() + 1);
|
||||
_text.resize(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 size() const -> unsigned { return _text.size(); }
|
||||
stringify(const vector<uint8_t>& source) {
|
||||
_text.resize(source.size() + 1);
|
||||
_text.resize(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 {
|
||||
string result;
|
||||
char path[PATH_MAX] = "";
|
||||
if(::realpath(name, path)) result = string{path}.pathname();
|
||||
if(result.empty()) result = activepath();
|
||||
if(::realpath(name, path)) result = string{path}.transform("\\", "/").pathname();
|
||||
if(result.empty()) return activepath();
|
||||
result.transform("\\", "/");
|
||||
if(result.endsWith("/") == false) result.append("/");
|
||||
return result;
|
||||
|
|
|
@ -5,6 +5,9 @@ auto config() -> ConfigurationManager& { return *configurationManager; }
|
|||
ConfigurationManager::ConfigurationManager() {
|
||||
configurationManager = this;
|
||||
|
||||
userInterface.append(userInterface.showStatusBar, "ShowStatusBar");
|
||||
append(userInterface, "UserInterface");
|
||||
|
||||
video.append(video.driver, "Driver");
|
||||
video.append(video.synchronize, "Synchronize");
|
||||
video.append(video.scale, "Scale");
|
||||
|
|
|
@ -2,6 +2,10 @@ struct ConfigurationManager : Configuration::Document {
|
|||
ConfigurationManager();
|
||||
auto quit() -> void;
|
||||
|
||||
struct UserInterface : Configuration::Node {
|
||||
bool showStatusBar = true;
|
||||
} userInterface;
|
||||
|
||||
struct Video : Configuration::Node {
|
||||
string driver;
|
||||
bool synchronize = false;
|
||||
|
|
|
@ -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 "hotkeys.cpp"
|
||||
InputManager* inputManager = nullptr;
|
||||
|
||||
auto InputMapping::bind() -> void {
|
||||
|
@ -85,6 +86,7 @@ InputManager::InputManager() {
|
|||
config.append(nodeEmulator, string{inputEmulator.name}.replace(" ", ""));
|
||||
}
|
||||
|
||||
appendHotkeys();
|
||||
config.load({configpath(), "tomoko/input.bml"});
|
||||
config.save({configpath(), "tomoko/input.bml"});
|
||||
poll(); //will call bind();
|
||||
|
@ -100,6 +102,10 @@ auto InputManager::bind() -> void {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& hotkey : hotkeys) {
|
||||
hotkey->bind();
|
||||
}
|
||||
}
|
||||
|
||||
auto InputManager::poll() -> void {
|
||||
|
@ -115,14 +121,19 @@ auto InputManager::poll() -> void {
|
|||
this->devices = devices;
|
||||
bind();
|
||||
}
|
||||
|
||||
if(presentation && presentation->focused()) pollHotkeys();
|
||||
}
|
||||
|
||||
auto InputManager::onChange(HID::Device& device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void {
|
||||
if(settingsManager->focused()) {
|
||||
settingsManager->input.inputEvent(device, group, input, oldValue, newValue);
|
||||
settingsManager->hotkeys.inputEvent(device, group, input, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
auto InputManager::quit() -> void {
|
||||
config.save({configpath(), "tomoko/input.bml"});
|
||||
emulators.reset();
|
||||
hotkeys.reset();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,12 @@ struct InputMapping {
|
|||
unsigned input = 0;
|
||||
};
|
||||
|
||||
struct InputHotkey : InputMapping {
|
||||
function<void ()> action;
|
||||
|
||||
int16 state = 0;
|
||||
};
|
||||
|
||||
struct InputDevice {
|
||||
string name;
|
||||
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 quit() -> void;
|
||||
|
||||
//hotkeys.cpp
|
||||
auto appendHotkeys() -> void;
|
||||
auto pollHotkeys() -> void;
|
||||
|
||||
vector<HID::Device*> devices;
|
||||
vector<InputEmulator> emulators;
|
||||
vector<InputHotkey*> hotkeys;
|
||||
Configuration::Document config;
|
||||
};
|
||||
|
||||
|
|
|
@ -67,6 +67,11 @@ Presentation::Presentation() {
|
|||
config().audio.mute = muteAudio.checked();
|
||||
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([&] {
|
||||
settingsManager->setVisible();
|
||||
settingsManager->setFocused();
|
||||
|
@ -74,26 +79,28 @@ Presentation::Presentation() {
|
|||
|
||||
toolsMenu.setText("Tools").setVisible(false);
|
||||
saveStateMenu.setText("Save State");
|
||||
saveSlot1.setText("Slot 1").onActivate([&] {});
|
||||
saveSlot2.setText("Slot 2").onActivate([&] {});
|
||||
saveSlot3.setText("Slot 3").onActivate([&] {});
|
||||
saveSlot4.setText("Slot 4").onActivate([&] {});
|
||||
saveSlot5.setText("Slot 5").onActivate([&] {});
|
||||
saveSlot1.setText("Slot 1").onActivate([&] { program->saveState(1); });
|
||||
saveSlot2.setText("Slot 2").onActivate([&] { program->saveState(2); });
|
||||
saveSlot3.setText("Slot 3").onActivate([&] { program->saveState(3); });
|
||||
saveSlot4.setText("Slot 4").onActivate([&] { program->saveState(4); });
|
||||
saveSlot5.setText("Slot 5").onActivate([&] { program->saveState(5); });
|
||||
loadStateMenu.setText("Load State");
|
||||
loadSlot1.setText("Slot 1").onActivate([&] {});
|
||||
loadSlot2.setText("Slot 2").onActivate([&] {});
|
||||
loadSlot3.setText("Slot 3").onActivate([&] {});
|
||||
loadSlot4.setText("Slot 4").onActivate([&] {});
|
||||
loadSlot5.setText("Slot 5").onActivate([&] {});
|
||||
loadSlot1.setText("Slot 1").onActivate([&] { program->loadState(1); });
|
||||
loadSlot2.setText("Slot 2").onActivate([&] { program->loadState(2); });
|
||||
loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
||||
loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
||||
loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
||||
stateManager.setText("State Manager").onActivate([&] {});
|
||||
cheatEditor.setText("Cheat Editor").onActivate([&] {});
|
||||
|
||||
statusBar.setFont(Font::sans(8, "Bold"));
|
||||
statusBar.setVisible(config().userInterface.showStatusBar);
|
||||
|
||||
onClose([&] { program->quit(); });
|
||||
|
||||
setTitle({"tomoko v", Emulator::Version});
|
||||
setResizable(false);
|
||||
setBackgroundColor({16, 16, 16});
|
||||
resizeViewport();
|
||||
}
|
||||
|
||||
|
@ -106,18 +113,62 @@ auto Presentation::resizeViewport() -> void {
|
|||
height = program->emulator().information.height;
|
||||
}
|
||||
|
||||
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(config().video.aspectCorrection) {
|
||||
if(!program->activeEmulator || program->emulator().information.aspectRatio != 1.0) width = width * 5 / 4;
|
||||
if(fullScreen() == false) {
|
||||
bool arc = config().video.aspectCorrection
|
||||
&& program->activeEmulator
|
||||
&& program->emulator().information.aspectRatio != 1.0;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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 {
|
||||
uint32* output;
|
||||
unsigned length;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
struct Presentation : Window {
|
||||
Presentation();
|
||||
auto resizeViewport() -> void;
|
||||
auto toggleFullScreen() -> void;
|
||||
auto drawSplashScreen() -> void;
|
||||
|
||||
MenuBar menuBar{this};
|
||||
|
@ -28,6 +29,7 @@ struct Presentation : Window {
|
|||
MenuCheckItem synchronizeVideo{&settingsMenu};
|
||||
MenuCheckItem synchronizeAudio{&settingsMenu};
|
||||
MenuCheckItem muteAudio{&settingsMenu};
|
||||
MenuCheckItem showStatusBar{&settingsMenu};
|
||||
MenuSeparator settingsMenuSeparator2{&settingsMenu};
|
||||
MenuItem showConfiguration{&settingsMenu};
|
||||
Menu toolsMenu{&menuBar};
|
||||
|
@ -47,8 +49,8 @@ struct Presentation : Window {
|
|||
MenuItem stateManager{&toolsMenu};
|
||||
MenuItem cheatEditor{&toolsMenu};
|
||||
|
||||
VerticalLayout layout{this};
|
||||
Viewport viewport{&layout, Size{~0, ~0}};
|
||||
FixedLayout layout{this};
|
||||
Viewport viewport{&layout, Geometry{0, 0, 1, 1}};
|
||||
|
||||
StatusBar statusBar{this};
|
||||
};
|
||||
|
|
|
@ -51,7 +51,7 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned p
|
|||
time(¤t);
|
||||
if(current != previous) {
|
||||
previous = current;
|
||||
presentation->statusBar.setText({"FPS: ", frameCounter});
|
||||
statusText = {"FPS: ", frameCounter};
|
||||
frameCounter = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med
|
|||
|
||||
mediaPaths(0) = {userpath(), "Emulation/System/", media.name, ".sys/"};
|
||||
mediaPaths(media.id) = location;
|
||||
folderPaths.append(location);
|
||||
|
||||
setEmulator(&_emulator);
|
||||
updateVideoPalette();
|
||||
|
@ -35,6 +36,8 @@ auto Program::unloadMedia() -> void {
|
|||
emulator().unload();
|
||||
|
||||
setEmulator(nullptr);
|
||||
mediaPaths.reset();
|
||||
folderPaths.reset();
|
||||
|
||||
presentation->setTitle({"tomoko v", Emulator::Version});
|
||||
presentation->systemMenu.setVisible(false);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <gba/interface/interface.hpp>
|
||||
#include "interface.cpp"
|
||||
#include "media.cpp"
|
||||
#include "state.cpp"
|
||||
#include "utility.cpp"
|
||||
Program* program = nullptr;
|
||||
|
||||
|
@ -56,14 +57,15 @@ Program::Program() {
|
|||
}
|
||||
|
||||
auto Program::emulator() -> Emulator::Interface& {
|
||||
if(activeEmulator == nullptr) throw;
|
||||
if(!activeEmulator) throw;
|
||||
return *activeEmulator;
|
||||
}
|
||||
|
||||
auto Program::main() -> void {
|
||||
updateStatusText();
|
||||
inputManager->poll();
|
||||
|
||||
if(activeEmulator == nullptr || emulator().loaded() == false) {
|
||||
if(!activeEmulator || emulator().loaded() == false) {
|
||||
audio.clear();
|
||||
usleep(20 * 1000);
|
||||
return;
|
||||
|
|
|
@ -24,7 +24,13 @@ struct Program : Emulator::Interface::Bind {
|
|||
auto loadMedia(Emulator::Interface& interface, Emulator::Interface::Media& media, const string& location) -> void;
|
||||
auto unloadMedia() -> void;
|
||||
|
||||
//state.cpp
|
||||
auto loadState(unsigned slot) -> bool;
|
||||
auto saveState(unsigned slot) -> bool;
|
||||
|
||||
//utility.cpp
|
||||
auto showMessage(const string& text) -> void;
|
||||
auto updateStatusText() -> void;
|
||||
auto updateVideoFilter() -> void;
|
||||
auto updateVideoPalette() -> void;
|
||||
|
||||
|
@ -32,7 +38,13 @@ struct Program : Emulator::Interface::Bind {
|
|||
|
||||
vector<Emulator::Interface*> emulators;
|
||||
Emulator::Interface* activeEmulator = nullptr;
|
||||
|
||||
vector<string> mediaPaths;
|
||||
vector<string> folderPaths;
|
||||
|
||||
string statusText;
|
||||
string statusMessage;
|
||||
time_t statusTime = 0;
|
||||
};
|
||||
|
||||
extern Program* program;
|
||||
|
|
|
@ -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 {
|
||||
if(config().video.filter == "None") video.set(Video::Filter, Video::FilterNearest);
|
||||
if(config().video.filter == "Blur") video.set(Video::Filter, Video::FilterLinear);
|
||||
|
|
|
@ -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(); });
|
||||
portList.onChange([&] { reloadDevices(); });
|
||||
deviceList.onChange([&] { reloadMappings(); });
|
||||
mappingList.onActivate([&] { assignMapping(); }).onChange([&] {
|
||||
mappingList.setHeaderVisible();
|
||||
mappingList.onActivate([&] { assignMapping(); });
|
||||
mappingList.onChange([&] {
|
||||
eraseButton.setEnabled((bool)mappingList.selected());
|
||||
});
|
||||
mappingList.setHeaderVisible();
|
||||
resetButton.setText("Reset").onActivate([&] {
|
||||
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();
|
||||
|
@ -25,6 +26,7 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
|
|||
refreshMappings();
|
||||
}
|
||||
});
|
||||
|
||||
reloadPorts();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "../tomoko.hpp"
|
||||
#include "input.cpp"
|
||||
#include "hotkeys.cpp"
|
||||
#include "advanced.cpp"
|
||||
SettingsManager* settingsManager = nullptr;
|
||||
|
||||
|
@ -14,4 +15,5 @@ SettingsManager::SettingsManager() {
|
|||
setPlacement(0.0, 1.0);
|
||||
|
||||
input.mappingList.resizeColumns();
|
||||
hotkeys.mappingList.resizeColumns();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,22 @@ struct InputSettings : TabFrameItem {
|
|||
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 {
|
||||
AdvancedSettings(TabFrame*);
|
||||
|
||||
|
@ -44,6 +60,7 @@ struct SettingsManager : Window {
|
|||
VerticalLayout layout{this};
|
||||
TabFrame panelLayout{&layout, Size{~0, ~0}};
|
||||
InputSettings input{&panelLayout};
|
||||
HotkeySettings hotkeys{&panelLayout};
|
||||
AdvancedSettings advanced{&panelLayout};
|
||||
|
||||
StatusBar statusBar{this};
|
||||
|
|
Loading…
Reference in New Issue