* added CPU and SA1 overclocking support
* added fast forward speed limiting
* added option to mute during fast forwarding and rewinding
* lowered volume when not muting during FF/rewind
* reformatted settings/tools windows from tabs to lists
* moved focus settings to input settings panel
* redesigned input and hotkey settings panels to be easier to use
* fixed offscreen placement issue with path settings panel
* added hotkey combinational logic option (AND / OR mode setting)
* added search support to file browser dialog
* fixed --fullscreen command-line option
This commit is contained in:
byuu 2019-07-31 06:57:31 +09:00
parent 7e88bdde09
commit f65b7a8528
59 changed files with 1019 additions and 642 deletions

View File

@ -10,45 +10,45 @@ Audio::~Audio() {
} }
auto Audio::reset(Interface* interface) -> void { auto Audio::reset(Interface* interface) -> void {
this->interface = interface; _interface = interface;
streams.reset(); _streams.reset();
channels = 0; _channels = 0;
} }
auto Audio::setFrequency(double frequency) -> void { auto Audio::setFrequency(double frequency) -> void {
this->frequency = frequency; _frequency = frequency;
for(auto& stream : streams) { for(auto& stream : _streams) {
stream->setFrequency(stream->inputFrequency, frequency); stream->setFrequency(stream->inputFrequency, frequency);
} }
} }
auto Audio::setVolume(double volume) -> void { auto Audio::setVolume(double volume) -> void {
this->volume = volume; _volume = volume;
} }
auto Audio::setBalance(double balance) -> void { auto Audio::setBalance(double balance) -> void {
this->balance = balance; _balance = balance;
} }
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> { auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
this->channels = max(this->channels, channels); _channels = max(_channels, channels);
shared_pointer<Stream> stream = new Stream; shared_pointer<Stream> stream = new Stream;
stream->reset(channels, frequency, this->frequency); stream->reset(channels, frequency, _frequency);
streams.append(stream); _streams.append(stream);
return stream; return stream;
} }
auto Audio::process() -> void { auto Audio::process() -> void {
while(streams) { while(_streams) {
for(auto& stream : streams) { for(auto& stream : _streams) {
if(!stream->pending()) return; if(!stream->pending()) return;
} }
double samples[channels]; double samples[_channels];
for(auto& sample : samples) sample = 0.0; for(auto& sample : samples) sample = 0.0;
for(auto& stream : streams) { for(auto& stream : _streams) {
double buffer[channels]; double buffer[_channels];
uint length = stream->read(buffer), offset = 0; uint length = stream->read(buffer), offset = 0;
for(auto& sample : samples) { for(auto& sample : samples) {
@ -57,16 +57,16 @@ auto Audio::process() -> void {
} }
} }
for(auto c : range(channels)) { for(auto c : range(_channels)) {
samples[c] = max(-1.0, min(+1.0, samples[c] * volume)); samples[c] = max(-1.0, min(+1.0, samples[c] * _volume));
} }
if(channels == 2) { if(_channels == 2) {
if(balance < 0.0) samples[1] *= 1.0 + balance; if(_balance < 0.0) samples[1] *= 1.0 + _balance;
if(balance > 0.0) samples[0] *= 1.0 - balance; if(_balance > 0.0) samples[0] *= 1.0 - _balance;
} }
platform->audioFrame(samples, channels); platform->audioFrame(samples, _channels);
} }
} }

View File

@ -17,6 +17,11 @@ struct Audio {
~Audio(); ~Audio();
auto reset(Interface* interface) -> void; auto reset(Interface* interface) -> void;
inline auto channels() const -> uint { return _channels; }
inline auto frequency() const -> double { return _frequency; }
inline auto volume() const -> double { return _volume; }
inline auto balance() const -> double { return _balance; }
auto setFrequency(double frequency) -> void; auto setFrequency(double frequency) -> void;
auto setVolume(double volume) -> void; auto setVolume(double volume) -> void;
auto setBalance(double balance) -> void; auto setBalance(double balance) -> void;
@ -26,14 +31,14 @@ struct Audio {
private: private:
auto process() -> void; auto process() -> void;
Interface* interface = nullptr; Interface* _interface = nullptr;
vector<shared_pointer<Stream>> streams; vector<shared_pointer<Stream>> _streams;
uint channels = 0; uint _channels = 0;
double frequency = 48000.0; double _frequency = 48000.0;
double volume = 1.0; double _volume = 1.0;
double balance = 0.0; double _balance = 0.0;
friend class Stream; friend class Stream;
}; };

View File

@ -31,7 +31,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "bsnes"; static const string Name = "bsnes";
static const string Version = "108"; static const string Version = "108.1";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "https://byuu.org"; static const string Website = "https://byuu.org";

View File

@ -127,8 +127,10 @@ auto SA1::unload() -> void {
} }
auto SA1::power() -> void { auto SA1::power() -> void {
double overclock = max(1.0, min(4.0, configuration.hacks.sa1.overclock / 100.0));
WDC65816::power(); WDC65816::power();
create(SA1::Enter, system.cpuFrequency()); create(SA1::Enter, system.cpuFrequency() * overclock);
bwram.dma = false; bwram.dma = false;
for(uint address : range(iram.size())) { for(uint address : range(iram.size())) {

View File

@ -37,8 +37,10 @@ auto SuperFX::unload() -> void {
} }
auto SuperFX::power() -> void { auto SuperFX::power() -> void {
double overclock = max(1.0, min(8.0, configuration.hacks.superfx.overclock / 100.0));
GSU::power(); GSU::power();
create(SuperFX::Enter, Frequency); create(SuperFX::Enter, Frequency * overclock);
romMask = rom.size() - 1; romMask = rom.size() - 1;
ramMask = ram.size() - 1; ramMask = ram.size() - 1;

View File

@ -58,8 +58,8 @@ auto CPU::power(bool reset) -> void {
PPUcounter::reset(); PPUcounter::reset();
PPUcounter::scanline = {&CPU::scanline, this}; PPUcounter::scanline = {&CPU::scanline, this};
function<auto (uint, uint8) -> uint8> reader; function<uint8 (uint, uint8)> reader;
function<auto (uint, uint8) -> void> writer; function<void (uint, uint8)> writer;
reader = {&CPU::readRAM, this}; reader = {&CPU::readRAM, this};
writer = {&CPU::writeRAM, this}; writer = {&CPU::writeRAM, this};

View File

@ -68,6 +68,11 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
uint8 wram[128 * 1024]; uint8 wram[128 * 1024];
vector<Thread*> coprocessors; vector<Thread*> coprocessors;
struct Overclocking {
uint counter = 0;
uint target = 0;
} overclocking;
private: private:
uint version = 2; //allowed: 1, 2 uint version = 2; //allowed: 1, 2

View File

@ -27,6 +27,12 @@ auto CPU::stepOnce() -> void {
template<uint Clocks, bool Synchronize> template<uint Clocks, bool Synchronize>
auto CPU::step() -> void { auto CPU::step() -> void {
static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12); static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12);
if(overclocking.target) {
overclocking.counter += Clocks;
if(overclocking.counter < overclocking.target) return;
}
if constexpr(Clocks >= 2) stepOnce(); if constexpr(Clocks >= 2) stepOnce();
if constexpr(Clocks >= 4) stepOnce(); if constexpr(Clocks >= 4) stepOnce();
if constexpr(Clocks >= 6) stepOnce(); if constexpr(Clocks >= 6) stepOnce();
@ -88,6 +94,17 @@ auto CPU::scanline() -> void {
status.hdmaPosition = 1104; status.hdmaPosition = 1104;
status.hdmaTriggered = false; status.hdmaTriggered = false;
} }
//overclocking
if(vcounter() == (Region::NTSC() ? 261 : 311)) {
overclocking.counter = 0;
overclocking.target = 0;
double overclock = configuration.hacks.cpu.overclock / 100.0;
if(overclock > 1.0) {
int clocks = (Region::NTSC() ? 262 : 312) * 1364;
overclocking.target = clocks * overclock - clocks;
}
}
} }
auto CPU::aluEdge() -> void { auto CPU::aluEdge() -> void {

View File

@ -16,9 +16,9 @@ void DSP::main() {
clock += 2 * 32; clock += 2 * 32;
} }
signed count = spc_dsp.sample_count(); int count = spc_dsp.sample_count();
if(count > 0) { if(count > 0) {
for(unsigned n = 0; n < count; n += 2) { for(uint n = 0; n < count; n += 2) {
stream->sample(samplebuffer[n + 0] / 32768.0f, samplebuffer[n + 1] / 32768.0f); stream->sample(samplebuffer[n + 0] / 32768.0f, samplebuffer[n + 1] / 32768.0f);
} }
spc_dsp.set_output(samplebuffer, 8192); spc_dsp.set_output(samplebuffer, 8192);

View File

@ -2,7 +2,7 @@
struct DSP { struct DSP {
shared_pointer<Emulator::Stream> stream; shared_pointer<Emulator::Stream> stream;
uint8 apuram[64 * 1024] = {}; uint8_t apuram[64 * 1024] = {};
void main(); void main();
uint8 read(uint8 addr); uint8 read(uint8 addr);

View File

@ -16,6 +16,7 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
bind(boolean, "Video/BlurEmulation", video.blurEmulation); bind(boolean, "Video/BlurEmulation", video.blurEmulation);
bind(boolean, "Video/ColorEmulation", video.colorEmulation); bind(boolean, "Video/ColorEmulation", video.colorEmulation);
bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock);
bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast); bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast);
bind(boolean, "Hacks/PPU/NoSpriteLimit", hacks.ppu.noSpriteLimit); bind(boolean, "Hacks/PPU/NoSpriteLimit", hacks.ppu.noSpriteLimit);
bind(natural, "Hacks/PPU/Mode7/Scale", hacks.ppu.mode7.scale); bind(natural, "Hacks/PPU/Mode7/Scale", hacks.ppu.mode7.scale);
@ -26,6 +27,8 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
bind(boolean, "Hacks/DSP/Cubic", hacks.dsp.cubic); bind(boolean, "Hacks/DSP/Cubic", hacks.dsp.cubic);
bind(boolean, "Hacks/Coprocessors/HLE", hacks.coprocessors.hle); bind(boolean, "Hacks/Coprocessors/HLE", hacks.coprocessors.hle);
bind(boolean, "Hacks/Coprocessors/DelayedSync", hacks.coprocessors.delayedSync); bind(boolean, "Hacks/Coprocessors/DelayedSync", hacks.coprocessors.delayedSync);
bind(natural, "Hacks/SA1/Overclock", hacks.sa1.overclock);
bind(natural, "Hacks/SuperFX/Overclock", hacks.superfx.overclock);
#undef bind #undef bind
} }

View File

@ -25,6 +25,9 @@ struct Configuration {
} video; } video;
struct Hacks { struct Hacks {
struct CPU {
uint overclock = 100;
} cpu;
struct PPU { struct PPU {
bool fast = true; bool fast = true;
bool noSpriteLimit = false; bool noSpriteLimit = false;
@ -43,6 +46,12 @@ struct Configuration {
bool delayedSync = true; bool delayedSync = true;
bool hle = true; bool hle = true;
} coprocessors; } coprocessors;
struct SA1 {
uint overclock = 100;
} sa1;
struct SuperFX {
uint overclock = 100;
} superfx;
} hacks; } hacks;
private: private:

View File

@ -90,15 +90,11 @@ auto PPU::main() -> void {
} }
auto PPU::load() -> bool { auto PPU::load() -> bool {
if(system.fastPPU()) {
return ppufast.load();
}
ppu1.version = max(1, min(1, configuration.system.ppu1.version)); ppu1.version = max(1, min(1, configuration.system.ppu1.version));
ppu2.version = max(1, min(3, configuration.system.ppu2.version)); ppu2.version = max(1, min(3, configuration.system.ppu2.version));
vram.mask = configuration.system.ppu1.vram.size / sizeof(uint16) - 1; vram.mask = configuration.system.ppu1.vram.size / sizeof(uint16) - 1;
if(vram.mask != 0xffff) vram.mask = 0x7fff; if(vram.mask != 0xffff) vram.mask = 0x7fff;
return true; return true && ppufast.load();
} }
auto PPU::power(bool reset) -> void { auto PPU::power(bool reset) -> void {

View File

@ -32,7 +32,6 @@ auto System::runToSave() -> void {
auto System::load(Emulator::Interface* interface) -> bool { auto System::load(Emulator::Interface* interface) -> bool {
information = {}; information = {};
hacks.fastPPU = configuration.hacks.ppu.fast;
bus.reset(); bus.reset();
if(!cpu.load()) return false; if(!cpu.load()) return false;
@ -92,6 +91,8 @@ auto System::unload() -> void {
} }
auto System::power(bool reset) -> void { auto System::power(bool reset) -> void {
hacks.fastPPU = configuration.hacks.ppu.fast;
Emulator::audio.reset(interface); Emulator::audio.reset(interface);
random.entropy(Random::Entropy::Low); random.entropy(Random::Entropy::Low);

View File

@ -9,8 +9,11 @@ auto locate(string name) -> string {
string location = {Path::program(), name}; string location = {Path::program(), name};
if(inode::exists(location)) return location; if(inode::exists(location)) return location;
directory::create({Path::userData(), "bsnes/"}); location = {Path::userData(), "bsnes/", name};
return {Path::userData(), "bsnes/", name}; if(inode::exists(location)) return location;
directory::create({Path::userSettings(), "bsnes/"});
return {Path::userSettings(), "bsnes/", name};
} }
#include <nall/main.hpp> #include <nall/main.hpp>
@ -19,7 +22,7 @@ auto nall::main(Arguments arguments) -> void {
for(auto argument : arguments) { for(auto argument : arguments) {
if(argument == "--fullscreen") { if(argument == "--fullscreen") {
presentation.startFullScreen = true; program.startFullScreen = true;
} else if(argument.beginsWith("--locale=")) { } else if(argument.beginsWith("--locale=")) {
Application::locale().scan(locate("Locale/")); Application::locale().scan(locate("Locale/"));
Application::locale().select(argument.trimLeft("--locale=", 1L)); Application::locale().select(argument.trimLeft("--locale=", 1L));

View File

@ -18,6 +18,7 @@ extern unique_pointer<Emulator::Interface> emulator;
#include <nall/decode/zip.hpp> #include <nall/decode/zip.hpp>
#include <nall/encode/rle.hpp> #include <nall/encode/rle.hpp>
#include <nall/encode/zip.hpp> #include <nall/encode/zip.hpp>
#include <nall/hash/crc16.hpp>
#include "program/program.hpp" #include "program/program.hpp"
#include "input/input.hpp" #include "input/input.hpp"

View File

@ -1,5 +1,15 @@
auto InputHotkey::logic() const -> Logic {
return inputManager.hotkeyLogic;
}
//
auto InputManager::bindHotkeys() -> void { auto InputManager::bindHotkeys() -> void {
static int stateSlot = 1; static int stateSlot = 1;
static double frequency = 48000.0;
static double volume = 0.0;
static bool fastForwarding = false;
static bool rewinding = false;
hotkeys.append(InputHotkey("Toggle Fullscreen Mode").onPress([] { hotkeys.append(InputHotkey("Toggle Fullscreen Mode").onPress([] {
presentation.toggleFullscreenMode(); presentation.toggleFullscreenMode();
@ -14,15 +24,24 @@ auto InputManager::bindHotkeys() -> void {
})); }));
hotkeys.append(InputHotkey("Rewind").onPress([&] { hotkeys.append(InputHotkey("Rewind").onPress([&] {
if(!emulator->loaded()) return; if(!emulator->loaded() || fastForwarding) return;
rewinding = true;
if(program.rewind.frequency == 0) { if(program.rewind.frequency == 0) {
program.showMessage("Please enable rewind support in Settings->Emulator first"); program.showMessage("Please enable rewind support in Settings->Emulator first");
} else { } else {
program.rewindMode(Program::Rewind::Mode::Rewinding); program.rewindMode(Program::Rewind::Mode::Rewinding);
} }
volume = Emulator::audio.volume();
if(settings.rewind.mute) {
Emulator::audio.setVolume(0.0);
} else {
Emulator::audio.setVolume(volume * 0.65);
}
}).onRelease([&] { }).onRelease([&] {
rewinding = false;
if(!emulator->loaded()) return; if(!emulator->loaded()) return;
program.rewindMode(Program::Rewind::Mode::Playing); program.rewindMode(Program::Rewind::Mode::Playing);
Emulator::audio.setVolume(volume);
})); }));
hotkeys.append(InputHotkey("Save State").onPress([&] { hotkeys.append(InputHotkey("Save State").onPress([&] {
@ -56,15 +75,33 @@ auto InputManager::bindHotkeys() -> void {
})); }));
hotkeys.append(InputHotkey("Fast Forward").onPress([] { hotkeys.append(InputHotkey("Fast Forward").onPress([] {
emulator->setFrameSkip(emulator->configuration("Hacks/PPU/Fast") == "true" && settings.video.fastForwardFrameSkip ? 9 : 0); if(!emulator->loaded() || rewinding) return;
fastForwarding = true;
emulator->setFrameSkip(emulator->configuration("Hacks/PPU/Fast") == "true" ? settings.fastForward.frameSkip : 0);
video.setBlocking(false); video.setBlocking(false);
audio.setBlocking(false); audio.setBlocking(settings.fastForward.limiter != 0);
audio.setDynamic(false); audio.setDynamic(false);
frequency = Emulator::audio.frequency();
volume = Emulator::audio.volume();
if(settings.fastForward.limiter) {
Emulator::audio.setFrequency(frequency / settings.fastForward.limiter);
}
if(settings.fastForward.mute) {
Emulator::audio.setVolume(0.0);
} else if(settings.fastForward.limiter) {
Emulator::audio.setVolume(volume * 0.65);
}
}).onRelease([] { }).onRelease([] {
fastForwarding = false;
if(!emulator->loaded()) return;
emulator->setFrameSkip(0); emulator->setFrameSkip(0);
video.setBlocking(settings.video.blocking); video.setBlocking(settings.video.blocking);
audio.setBlocking(settings.audio.blocking); audio.setBlocking(settings.audio.blocking);
audio.setDynamic(settings.audio.dynamic); audio.setDynamic(settings.audio.dynamic);
if(settings.fastForward.limiter) {
Emulator::audio.setFrequency(frequency);
}
Emulator::audio.setVolume(volume);
})); }));
hotkeys.append(InputHotkey("Pause Emulation").onPress([] { hotkeys.append(InputHotkey("Pause Emulation").onPress([] {
@ -85,7 +122,8 @@ auto InputManager::bindHotkeys() -> void {
for(auto& hotkey : hotkeys) { for(auto& hotkey : hotkeys) {
hotkey.path = string{"Hotkey/", hotkey.name}.replace(" ", ""); hotkey.path = string{"Hotkey/", hotkey.name}.replace(" ", "");
hotkey.assignment = settings(hotkey.path).text(); auto assignments = settings(hotkey.path).text().split(";");
for(uint index : range(BindingLimit)) hotkey.assignments[index] = assignments(index);
hotkey.bind(); hotkey.bind();
} }
} }

View File

@ -3,10 +3,13 @@
InputManager inputManager; InputManager inputManager;
auto InputMapping::bind() -> void { auto InputMapping::bind() -> void {
mappings.reset(); for(auto& binding : bindings) binding = {};
for(auto& item : assignment.split(logic() == Logic::AND ? "&" : "|")) { for(uint index : range(BindingLimit)) {
auto token = item.split("/"); auto& assignment = assignments[index];
auto& binding = bindings[index];
auto token = assignment.split("/");
if(token.size() < 3) continue; //skip invalid mappings if(token.size() < 3) continue; //skip invalid mappings
uint64 id = token[0].natural(); uint64 id = token[0].natural();
@ -14,41 +17,35 @@ auto InputMapping::bind() -> void {
uint input = token[2].natural(); uint input = token[2].natural();
string qualifier = token(3, "None"); string qualifier = token(3, "None");
Mapping mapping;
for(auto& device : inputManager.devices) { for(auto& device : inputManager.devices) {
if(id != device->id()) continue; if(id != device->id()) continue;
mapping.device = device; binding.device = device;
mapping.group = group; binding.group = group;
mapping.input = input; binding.input = input;
mapping.qualifier = Qualifier::None; binding.qualifier = Qualifier::None;
if(qualifier == "Lo") mapping.qualifier = Qualifier::Lo; if(qualifier == "Lo") binding.qualifier = Qualifier::Lo;
if(qualifier == "Hi") mapping.qualifier = Qualifier::Hi; if(qualifier == "Hi") binding.qualifier = Qualifier::Hi;
if(qualifier == "Rumble") mapping.qualifier = Qualifier::Rumble; if(qualifier == "Rumble") binding.qualifier = Qualifier::Rumble;
break; break;
} }
if(!mapping.device) continue;
mappings.append(mapping);
} }
settings[path].setValue(assignment); string text;
for(auto& assignment : assignments) text.append(assignment, ";");
text.trimRight(";");
settings[path].setValue(text);
} }
auto InputMapping::bind(string mapping) -> void { auto InputMapping::bind(string mapping, uint binding) -> void {
if(assignment.split(logic() == Logic::AND ? "&" : "|").find(mapping)) return; //ignore if already in mappings list if(binding >= BindingLimit) return;
if(!assignment || assignment == "None") { //create new mapping assignments[binding] = mapping;
assignment = mapping;
} else { //add additional mapping
assignment.append(logic() == Logic::AND ? "&" : "|");
assignment.append(mapping);
}
bind(); bind();
} }
auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool { auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, uint binding) -> bool {
if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) { if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) {
return unbind(), true; return unbind(binding), true;
} }
string encoding = {"0x", hex(device->id()), "/", group, "/", input}; string encoding = {"0x", hex(device->id()), "/", group, "/", input};
@ -58,7 +55,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|| (device->isMouse() && group == HID::Mouse::GroupID::Button) || (device->isMouse() && group == HID::Mouse::GroupID::Button)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Button)) { || (device->isJoypad() && group == HID::Joypad::GroupID::Button)) {
if(newValue) { if(newValue) {
return bind(encoding), true; return bind(encoding, binding), true;
} }
} }
@ -66,11 +63,11 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat) || (device->isJoypad() && group == HID::Joypad::GroupID::Hat)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) { || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) {
if(newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi if(newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi
return bind({encoding, "/Lo"}), true; return bind({encoding, "/Lo"}, binding), true;
} }
if(newValue > +16384) { if(newValue > +16384) {
return bind({encoding, "/Hi"}), true; return bind({encoding, "/Hi"}, binding), true;
} }
} }
} }
@ -80,7 +77,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|| (device->isJoypad() && group == HID::Joypad::GroupID::Axis) || (device->isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) { || (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) {
if(newValue < -16384 || newValue > +16384) { if(newValue < -16384 || newValue > +16384) {
return bind(encoding), true; return bind(encoding, binding), true;
} }
} }
} }
@ -88,7 +85,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
if(isRumble()) { if(isRumble()) {
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) { if(device->isJoypad() && group == HID::Joypad::GroupID::Button) {
if(newValue) { if(newValue) {
return bind({encoding, "/Rumble"}), true; return bind({encoding, "/Rumble"}, binding), true;
} }
} }
} }
@ -96,9 +93,13 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
return false; return false;
} }
auto InputMapping::unbind() -> void { auto InputMapping::unbind(uint binding) -> void {
mappings.reset(); bindings[binding] = {};
settings[path].setValue(assignment = "None"); assignments[binding] = {};
string text;
for(auto& assignment : assignments) text.append(assignment, ";");
text.trimRight(";");
settings[path].setValue(text);
} }
auto InputMapping::poll() -> int16 { auto InputMapping::poll() -> int16 {
@ -108,13 +109,17 @@ auto InputMapping::poll() -> int16 {
if(result) return inputManager.turboCounter >= inputManager.turboFrequency; if(result) return inputManager.turboCounter >= inputManager.turboFrequency;
} }
uint validBindings = 0;
int16 result = 0; int16 result = 0;
for(auto& mapping : mappings) { for(auto& binding : bindings) {
auto& device = mapping.device; if(!binding.device) continue; //unbound
auto& group = mapping.group; validBindings++;
auto& input = mapping.input;
auto& qualifier = mapping.qualifier; auto& device = binding.device;
auto& group = binding.group;
auto& input = binding.input;
auto& qualifier = binding.qualifier;
auto value = device->group(group).input(input).value(); auto value = device->group(group).input(input).value();
if(isDigital()) { if(isDigital()) {
@ -144,38 +149,43 @@ auto InputMapping::poll() -> int16 {
} }
} }
if(isDigital() && logic() == Logic::AND) return 1; if(isDigital() && logic() == Logic::AND && validBindings > 0) return 1;
return result; return result;
} }
auto InputMapping::rumble(bool enable) -> void { auto InputMapping::rumble(bool enable) -> void {
for(auto& mapping : mappings) { for(auto& binding : bindings) {
input.rumble(mapping.device->id(), enable); if(!binding.device) continue;
input.rumble(binding.device->id(), enable);
} }
} }
auto InputMapping::displayName() -> string { //
if(!mappings) return "None";
string path; auto InputMapping::Binding::icon() -> image {
for(auto& mapping : mappings) { if(device && device->isKeyboard()) return Icon::Device::Keyboard;
path.append(mapping.device->name()); if(device && device->isMouse()) return Icon::Device::Mouse;
if(mapping.device->name() != "Keyboard" && mapping.device->name() != "Mouse") { if(device && device->isJoypad()) return Icon::Device::Joypad;
//show device IDs to distinguish between multiple joypads return {};
path.append("(", hex(mapping.device->id()), ")");
}
if(mapping.device->name() != "Keyboard" && mapping.device->name() != "Mouse") {
//keyboards only have one group; no need to append group name
path.append(".", mapping.device->group(mapping.group).name());
}
path.append(".", mapping.device->group(mapping.group).input(mapping.input).name());
if(mapping.qualifier == Qualifier::Lo) path.append(".Lo");
if(mapping.qualifier == Qualifier::Hi) path.append(".Hi");
if(mapping.qualifier == Qualifier::Rumble) path.append(".Rumble");
path.append(logic() == Logic::AND ? " and " : " or ");
} }
return path.trimRight(logic() == Logic::AND ? " and " : " or ", 1L); auto InputMapping::Binding::name() -> string {
if(device && device->isKeyboard()) {
return device->group(group).input(input).name();
}
if(device && device->isMouse()) {
return device->group(group).input(input).name();
}
if(device && device->isJoypad()) {
string name{Hash::CRC16(string{device->id()}).digest().upcase()};
name.append(" ", device->group(group).name());
name.append(" ", device->group(group).input(input).name());
if(qualifier == Qualifier::Lo) name.append(" Lo");
if(qualifier == Qualifier::Hi) name.append(" Hi");
if(qualifier == Qualifier::Rumble) name.append(" Rumble");
return name;
}
return {};
} }
// //
@ -212,7 +222,8 @@ auto InputManager::initialize() -> void {
inputMapping.name = input.name; inputMapping.name = input.name;
inputMapping.type = input.type; inputMapping.type = input.type;
inputMapping.path = string{information.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping.name}.replace(" ", ""); inputMapping.path = string{information.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping.name}.replace(" ", "");
inputMapping.assignment = settings(inputMapping.path).text(); auto assignments = settings(inputMapping.path).text().split(";");
for(uint index : range(BindingLimit)) inputMapping.assignments[index] = assignments(index);
inputDevice.mappings.append(inputMapping); inputDevice.mappings.append(inputMapping);
} }
for(uint inputID : range(inputs.size())) { for(uint inputID : range(inputs.size())) {
@ -226,7 +237,8 @@ auto InputManager::initialize() -> void {
inputMapping.name = string{"Turbo ", input.name}; inputMapping.name = string{"Turbo ", input.name};
inputMapping.type = input.type; inputMapping.type = input.type;
inputMapping.path = string{information.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping.name}.replace(" ", ""); inputMapping.path = string{information.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping.name}.replace(" ", "");
inputMapping.assignment = settings(inputMapping.path).text(); auto assignments = settings(inputMapping.path).text().split(";");
for(uint index : range(BindingLimit)) inputMapping.assignments[index] = assignments(index);
inputDevice.mappings.append(inputMapping); inputDevice.mappings.append(inputMapping);
inputDevice.mappings[inputID].turboID = turboID; inputDevice.mappings[inputID].turboID = turboID;
} }

View File

@ -1,11 +1,12 @@
enum : uint { BindingLimit = 4 };
struct InputMapping { struct InputMapping {
auto bind() -> void; auto bind() -> void;
auto bind(string mapping) -> void; auto bind(string mapping, uint binding) -> void;
auto bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool; auto bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, uint binding) -> bool;
auto unbind() -> void; auto unbind(uint binding) -> void;
auto poll() -> int16; auto poll() -> int16;
auto rumble(bool enable) -> void; auto rumble(bool enable) -> void;
auto displayName() -> string;
using Type = Emulator::Interface::Input::Type; using Type = Emulator::Interface::Input::Type;
auto isDigital() const -> bool { auto isDigital() const -> bool {
@ -26,19 +27,22 @@ struct InputMapping {
string path; //configuration file key path string path; //configuration file key path
string name; //input name (human readable) string name; //input name (human readable)
uint type = 0; uint type = 0;
string assignment = "None"; string assignments[BindingLimit];
enum class Logic : uint { AND, OR }; enum class Logic : uint { AND, OR };
enum class Qualifier : uint { None, Lo, Hi, Rumble }; enum class Qualifier : uint { None, Lo, Hi, Rumble };
virtual auto logic() const -> Logic { return Logic::OR; } virtual auto logic() const -> Logic { return Logic::OR; }
struct Mapping { struct Binding {
auto icon() -> image;
auto name() -> string;
shared_pointer<HID::Device> device; shared_pointer<HID::Device> device;
uint group = 0; uint group = 0;
uint input = 0; uint input = 0;
Qualifier qualifier = Qualifier::None; Qualifier qualifier = Qualifier::None;
}; };
vector<Mapping> mappings; Binding bindings[BindingLimit];
uint3 turboCounter = 0; uint3 turboCounter = 0;
}; };
@ -47,10 +51,10 @@ struct InputHotkey : InputMapping {
InputHotkey(string name) { this->name = name; } InputHotkey(string name) { this->name = name; }
auto& onPress(function<void ()> press) { return this->press = press, *this; } auto& onPress(function<void ()> press) { return this->press = press, *this; }
auto& onRelease(function<void ()> release) { return this->release = release, *this; } auto& onRelease(function<void ()> release) { return this->release = release, *this; }
//auto logic() const -> Logic override { return Logic::AND; } auto logic() const -> Logic override;
function<auto() -> void> press; function<void ()> press;
function<auto() -> void> release; function<void ()> release;
int16 state = 0; int16 state = 0;
}; };
@ -67,6 +71,8 @@ struct InputPort {
}; };
struct InputManager { struct InputManager {
InputMapping::Logic hotkeyLogic = InputMapping::Logic::OR;
auto initialize() -> void; auto initialize() -> void;
auto bind() -> void; auto bind() -> void;
auto poll() -> void; auto poll() -> void;

View File

@ -102,8 +102,9 @@ auto Presentation::create() -> void {
inputSettings.setIcon(Icon::Device::Joypad).setText("Input ...").onActivate([&] { settingsWindow.show(2); }); inputSettings.setIcon(Icon::Device::Joypad).setText("Input ...").onActivate([&] { settingsWindow.show(2); });
hotkeySettings.setIcon(Icon::Device::Keyboard).setText("Hotkeys ...").onActivate([&] { settingsWindow.show(3); }); hotkeySettings.setIcon(Icon::Device::Keyboard).setText("Hotkeys ...").onActivate([&] { settingsWindow.show(3); });
pathSettings.setIcon(Icon::Emblem::Folder).setText("Paths ...").onActivate([&] { settingsWindow.show(4); }); pathSettings.setIcon(Icon::Emblem::Folder).setText("Paths ...").onActivate([&] { settingsWindow.show(4); });
emulatorSettings.setIcon(Icon::Action::Settings).setText("Emulator ...").onActivate([&] { settingsWindow.show(5); }); speedSettings.setIcon(Icon::Device::Clock).setText("Speed ...").onActivate([&] { settingsWindow.show(5); });
driverSettings.setIcon(Icon::Place::Settings).setText("Drivers ...").onActivate([&] { settingsWindow.show(6); }); emulatorSettings.setIcon(Icon::Action::Settings).setText("Emulator ...").onActivate([&] { settingsWindow.show(6); });
driverSettings.setIcon(Icon::Place::Settings).setText("Drivers ...").onActivate([&] { settingsWindow.show(7); });
toolsMenu.setText(tr("Tools")).setVisible(false); toolsMenu.setText(tr("Tools")).setVisible(false);
saveState.setIcon(Icon::Action::Save).setText("Save State"); saveState.setIcon(Icon::Action::Save).setText("Save State");
@ -163,7 +164,7 @@ auto Presentation::create() -> void {
captureScreenshot.setIcon(Icon::Emblem::Image).setText("Capture Screenshot").onActivate([&] { captureScreenshot.setIcon(Icon::Emblem::Image).setText("Capture Screenshot").onActivate([&] {
program.captureScreenshot(); program.captureScreenshot();
}); });
cheatFinder.setIcon(Icon::Edit::Find).setText("Cheat Finder ...").onActivate([&] { toolsWindow.show(0); }); cheatFinder.setIcon(Icon::Action::Search).setText("Cheat Finder ...").onActivate([&] { toolsWindow.show(0); });
cheatEditor.setIcon(Icon::Edit::Replace).setText("Cheat Editor ...").onActivate([&] { toolsWindow.show(1); }); cheatEditor.setIcon(Icon::Edit::Replace).setText("Cheat Editor ...").onActivate([&] { toolsWindow.show(1); });
stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow.show(2); }); stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow.show(2); });
manifestViewer.setIcon(Icon::Emblem::Text).setText("Manifest Viewer ...").onActivate([&] { toolsWindow.show(3); }); manifestViewer.setIcon(Icon::Emblem::Text).setText("Manifest Viewer ...").onActivate([&] { toolsWindow.show(3); });
@ -248,10 +249,6 @@ auto Presentation::create() -> void {
resizeWindow(); resizeWindow();
setAlignment(Alignment::Center); setAlignment(Alignment::Center);
//start in fullscreen mode if requested ...
//perform the exclusive mode change later on inside Program::create(), after the video driver has been initialized
if(startFullScreen) setFullScreen();
#if defined(PLATFORM_MACOS) #if defined(PLATFORM_MACOS)
Application::Cocoa::onAbout([&] { about.doActivate(); }); Application::Cocoa::onAbout([&] { about.doActivate(); });
Application::Cocoa::onActivate([&] { setFocused(); }); Application::Cocoa::onActivate([&] { setFocused(); });

View File

@ -19,8 +19,6 @@ struct Presentation : Window {
auto addRecentGame(string location) -> void; auto addRecentGame(string location) -> void;
auto updateShaders() -> void; auto updateShaders() -> void;
bool startFullScreen = false;
MenuBar menuBar{this}; MenuBar menuBar{this};
Menu systemMenu{&menuBar}; Menu systemMenu{&menuBar};
MenuItem loadGame{&systemMenu}; MenuItem loadGame{&systemMenu};
@ -88,6 +86,7 @@ struct Presentation : Window {
MenuItem inputSettings{&settingsMenu}; MenuItem inputSettings{&settingsMenu};
MenuItem hotkeySettings{&settingsMenu}; MenuItem hotkeySettings{&settingsMenu};
MenuItem pathSettings{&settingsMenu}; MenuItem pathSettings{&settingsMenu};
MenuItem speedSettings{&settingsMenu};
MenuItem emulatorSettings{&settingsMenu}; MenuItem emulatorSettings{&settingsMenu};
MenuItem driverSettings{&settingsMenu}; MenuItem driverSettings{&settingsMenu};
Menu toolsMenu{&menuBar}; Menu toolsMenu{&menuBar};

View File

@ -1,10 +1,18 @@
auto Program::load() -> void { auto Program::load() -> void {
unload(); unload();
if(auto configuration = string::read(locate("configuration.bml"))) { emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock);
emulator->configure(configuration); emulator->configure("Hacks/PPU/Fast", settings.emulator.hack.ppu.fast);
emulatorSettings.updateConfiguration(); emulator->configure("Hacks/PPU/NoSpriteLimit", settings.emulator.hack.ppu.noSpriteLimit);
} emulator->configure("Hacks/PPU/Mode7/Scale", settings.emulator.hack.ppu.mode7.scale);
emulator->configure("Hacks/PPU/Mode7/Perspective", settings.emulator.hack.ppu.mode7.perspective);
emulator->configure("Hacks/PPU/Mode7/Supersample", settings.emulator.hack.ppu.mode7.supersample);
emulator->configure("Hacks/PPU/Mode7/Mosaic", settings.emulator.hack.ppu.mode7.mosaic);
emulator->configure("Hacks/DSP/Fast", settings.emulator.hack.dsp.fast);
emulator->configure("Hacks/DSP/Cubic", settings.emulator.hack.dsp.cubic);
emulator->configure("Hacks/Coprocessor/DelayedSync", settings.emulator.hack.coprocessors.delayedSync);
emulator->configure("Hacks/Coprocessor/HLE", settings.emulator.hack.coprocessors.hle);
emulator->configure("Hacks/SuperFX/Overclock", settings.emulator.hack.superfx.overclock);
if(!emulator->load()) return; if(!emulator->load()) return;
gameQueue = {}; gameQueue = {};
@ -123,7 +131,6 @@ auto Program::loadSuperFamicom(string location) -> bool {
superFamicom.title = heuristics.title(); superFamicom.title = heuristics.title();
superFamicom.manifest = manifest ? manifest : heuristics.manifest(); superFamicom.manifest = manifest ? manifest : heuristics.manifest();
hackPatchMemory(rom); hackPatchMemory(rom);
hackOverclockSuperFX();
superFamicom.document = BML::unserialize(superFamicom.manifest); superFamicom.document = BML::unserialize(superFamicom.manifest);
superFamicom.location = location; superFamicom.location = location;
@ -308,9 +315,6 @@ auto Program::unload() -> void {
if(emulatorSettings.autoSaveStateOnUnload.checked()) { if(emulatorSettings.autoSaveStateOnUnload.checked()) {
saveUndoState(); saveUndoState();
} }
if(auto configuration = emulator->configuration()) {
file::write(locate("configuration.bml"), configuration);
}
emulator->unload(); emulator->unload();
showMessage("Game unloaded"); showMessage("Game unloaded");
superFamicom = {}; superFamicom = {};

View File

@ -34,32 +34,3 @@ auto Program::hackPatchMemory(vector<uint8_t>& data) -> void {
if(data[0x4e9a] == 0x10) data[0x4e9a] = 0x80; if(data[0x4e9a] == 0x10) data[0x4e9a] = 0x80;
} }
} }
auto Program::hackOverclockSuperFX() -> void {
//todo: implement a better way of detecting SuperFX games
//todo: apply multiplier changes on reset, not just on game load?
double multiplier = emulatorSettings.superFXValue.text().natural() / 100.0;
if(multiplier == 1.0) return;
auto title = superFamicom.title;
if(title == "NIDAN MORITASHOGI2") return; //ST018 uses same clock speed as SuperFX
auto document = BML::unserialize(superFamicom.manifest);
//GSU-1, GSU-2 have a 21440000hz oscillator
if(auto oscillator = document["game/board/oscillator"]) {
if(oscillator["frequency"].text() == "21440000") {
oscillator["frequency"].setValue(uint(21440000 * multiplier));
superFamicom.manifest = BML::serialize(document);
}
return;
}
//MARIO CHIP 1 uses CPU oscillator; force it to use its own crystal to overclock it
bool marioChip1 = false;
if(title == "STAR FOX" || title == "STAR WING") marioChip1 = true;
if(marioChip1) {
document("game/board/oscillator/frequency").setValue(uint(21440000 * multiplier));
superFamicom.manifest = BML::serialize(document);
}
}

View File

@ -262,7 +262,7 @@ auto Program::audioFrame(const float* samples, uint channels) -> void {
auto Program::inputPoll(uint port, uint device, uint input) -> int16 { auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
int16 value = 0; int16 value = 0;
if(focused() || emulatorSettings.allowInput().checked()) { if(focused() || inputSettings.allowInput().checked()) {
inputManager.poll(); inputManager.poll();
if(auto mapping = inputManager.mapping(port, device, input)) { if(auto mapping = inputManager.mapping(port, device, input)) {
value = mapping->poll(); value = mapping->poll();
@ -282,7 +282,7 @@ auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
} }
auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void { auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void {
if(focused() || emulatorSettings.allowInput().checked() || !enable) { if(focused() || inputSettings.allowInput().checked() || !enable) {
if(auto mapping = inputManager.mapping(port, device, input)) { if(auto mapping = inputManager.mapping(port, device, input)) {
return mapping->rumble(enable); return mapping->rumble(enable);
} }

View File

@ -30,6 +30,7 @@ auto Program::create() -> void {
inputSettings.create(); inputSettings.create();
hotkeySettings.create(); hotkeySettings.create();
pathSettings.create(); pathSettings.create();
speedSettings.create();
emulatorSettings.create(); emulatorSettings.create();
driverSettings.create(); driverSettings.create();
@ -65,9 +66,7 @@ auto Program::create() -> void {
driverSettings.inputDriverChanged(); driverSettings.inputDriverChanged();
if(gameQueue) load(); if(gameQueue) load();
if(presentation.startFullScreen && emulator->loaded()) { if(startFullScreen && emulator->loaded()) {
//remove the earlier fullscreen mode state, so that toggleFullscreenMode will enter fullscreen exclusive mode
presentation.setFullScreen(false);
presentation.toggleFullscreenMode(); presentation.toggleFullscreenMode();
} }
Application::onMain({&Program::main, this}); Application::onMain({&Program::main, this});

View File

@ -125,7 +125,6 @@ struct Program : Lock, Emulator::Platform {
//hacks.cpp //hacks.cpp
auto hackCompatibility() -> void; auto hackCompatibility() -> void;
auto hackPatchMemory(vector<uint8_t>& data) -> void; auto hackPatchMemory(vector<uint8_t>& data) -> void;
auto hackOverclockSuperFX() -> void;
//filter.cpp //filter.cpp
auto filterSelect(uint& width, uint& height, uint scale) -> Filter::Render; auto filterSelect(uint& width, uint& height, uint scale) -> Filter::Render;
@ -186,6 +185,8 @@ public:
uint64 statusTime; uint64 statusTime;
string statusMessage; string statusMessage;
string statusFrameRate; string statusFrameRate;
bool startFullScreen = false;
}; };
extern Program program; extern Program program;

View File

@ -6,8 +6,8 @@ auto Program::rewindMode(Rewind::Mode mode) -> void {
auto Program::rewindReset() -> void { auto Program::rewindReset() -> void {
rewindMode(Rewind::Mode::Playing); rewindMode(Rewind::Mode::Playing);
rewind.history.reset(); rewind.history.reset();
rewind.frequency = settings.emulator.rewind.frequency; rewind.frequency = settings.rewind.frequency;
rewind.length = settings.emulator.rewind.length; rewind.length = settings.rewind.length;
} }
auto Program::rewindRun() -> void { auto Program::rewindRun() -> void {

View File

@ -21,7 +21,7 @@ auto Program::updateStatus() -> void {
frameRate = tr("Unloaded"); frameRate = tr("Unloaded");
} else if(presentation.pauseEmulation.checked()) { } else if(presentation.pauseEmulation.checked()) {
frameRate = tr("Paused"); frameRate = tr("Paused");
} else if(!focused() && emulatorSettings.pauseEmulation.checked()) { } else if(!focused() && inputSettings.pauseEmulation.checked()) {
frameRate = tr("Paused"); frameRate = tr("Paused");
} else { } else {
frameRate = statusFrameRate; frameRate = statusFrameRate;
@ -66,7 +66,7 @@ auto Program::inactive() -> bool {
if(locked()) return true; if(locked()) return true;
if(!emulator->loaded()) return true; if(!emulator->loaded()) return true;
if(presentation.pauseEmulation.checked()) return true; if(presentation.pauseEmulation.checked()) return true;
if(!focused() && emulatorSettings.pauseEmulation.checked()) return true; if(!focused() && inputSettings.pauseEmulation.checked()) return true;
return false; return false;
} }

View File

@ -1,8 +1,6 @@
auto AudioSettings::create() -> void { auto AudioSettings::create() -> void {
setIcon(Icon::Device::Speaker); setCollapsible();
setText("Audio"); setVisible(false);
layout.setPadding(5_sx);
effectsLabel.setFont(Font().setBold()).setText("Effects"); effectsLabel.setFont(Font().setBold()).setText("Effects");
effectsLayout.setSize({3, 3}); effectsLayout.setSize({3, 3});

View File

@ -1,8 +1,6 @@
auto DriverSettings::create() -> void { auto DriverSettings::create() -> void {
setIcon(Icon::Place::Settings); setCollapsible();
setText("Drivers"); setVisible(false);
layout.setPadding(5_sx);
videoLabel.setText("Video").setFont(Font().setBold()); videoLabel.setText("Video").setFont(Font().setBold());
videoLayout.setSize({2, 2}); videoLayout.setSize({2, 2});
@ -113,7 +111,7 @@ auto DriverSettings::videoDriverChanged() -> void {
videoFormatChanged(); videoFormatChanged();
videoBlockingToggle.setChecked(video.blocking()).setEnabled(video.hasBlocking()); videoBlockingToggle.setChecked(video.blocking()).setEnabled(video.hasBlocking());
videoFlushToggle.setChecked(video.flush()).setEnabled(video.hasFlush()); videoFlushToggle.setChecked(video.flush()).setEnabled(video.hasFlush());
layout.setGeometry(layout.geometry()); setGeometry(geometry());
} }
auto DriverSettings::videoDriverChange() -> void { auto DriverSettings::videoDriverChange() -> void {
@ -143,7 +141,7 @@ auto DriverSettings::videoFormatChanged() -> void {
if(format == video.format()) item.setSelected(); if(format == video.format()) item.setSelected();
} }
//videoFormatOption.setEnabled(video.hasFormat()); //videoFormatOption.setEnabled(video.hasFormat());
layout.setGeometry(layout.geometry()); setGeometry(geometry());
videoFormatChange(); videoFormatChange();
} }
@ -170,7 +168,7 @@ auto DriverSettings::audioDriverChanged() -> void {
audioExclusiveToggle.setChecked(audio.exclusive()).setEnabled(audio.hasExclusive()); audioExclusiveToggle.setChecked(audio.exclusive()).setEnabled(audio.hasExclusive());
audioBlockingToggle.setChecked(audio.blocking()).setEnabled(audio.hasBlocking()); audioBlockingToggle.setChecked(audio.blocking()).setEnabled(audio.hasBlocking());
audioDynamicToggle.setChecked(audio.dynamic()).setEnabled(audio.hasDynamic()); audioDynamicToggle.setChecked(audio.dynamic()).setEnabled(audio.hasDynamic());
layout.setGeometry(layout.geometry()); setGeometry(geometry());
} }
auto DriverSettings::audioDriverChange() -> void { auto DriverSettings::audioDriverChange() -> void {
@ -200,7 +198,7 @@ auto DriverSettings::audioDeviceChanged() -> void {
if(device == audio.device()) item.setSelected(); if(device == audio.device()) item.setSelected();
} }
//audioDeviceOption.setEnabled(audio->hasDevice()); //audioDeviceOption.setEnabled(audio->hasDevice());
layout.setGeometry(layout.geometry()); setGeometry(geometry());
} }
auto DriverSettings::audioDeviceChange() -> void { auto DriverSettings::audioDeviceChange() -> void {
@ -219,7 +217,7 @@ auto DriverSettings::audioFrequencyChanged() -> void {
if(frequency == audio.frequency()) item.setSelected(); if(frequency == audio.frequency()) item.setSelected();
} }
//audioFrequencyOption.setEnabled(audio->hasFrequency()); //audioFrequencyOption.setEnabled(audio->hasFrequency());
layout.setGeometry(layout.geometry()); setGeometry(geometry());
} }
auto DriverSettings::audioFrequencyChange() -> void { auto DriverSettings::audioFrequencyChange() -> void {
@ -236,7 +234,7 @@ auto DriverSettings::audioLatencyChanged() -> void {
if(latency == audio.latency()) item.setSelected(); if(latency == audio.latency()) item.setSelected();
} }
//audioLatencyOption.setEnabled(audio->hasLatency()); //audioLatencyOption.setEnabled(audio->hasLatency());
layout.setGeometry(layout.geometry()); setGeometry(geometry());
} }
auto DriverSettings::audioLatencyChange() -> void { auto DriverSettings::audioLatencyChange() -> void {
@ -256,7 +254,7 @@ auto DriverSettings::inputDriverChanged() -> void {
} }
inputDriverActive.setText({"Active driver: ", input.driver()}); inputDriverActive.setText({"Active driver: ", input.driver()});
inputDriverOption.doChange(); inputDriverOption.doChange();
layout.setGeometry(layout.geometry()); setGeometry(geometry());
} }
auto DriverSettings::inputDriverChange() -> void { auto DriverSettings::inputDriverChange() -> void {

View File

@ -1,23 +1,8 @@
auto EmulatorSettings::create() -> void { auto EmulatorSettings::create() -> void {
setIcon(Icon::Action::Settings); setCollapsible();
setText("Emulator"); setVisible(false);
layout.setPadding(5_sx);
optionsLabel.setText("Options").setFont(Font().setBold()); optionsLabel.setText("Options").setFont(Font().setBold());
inputFocusLabel.setText("When focus is lost:");
pauseEmulation.setText("Pause emulation").onActivate([&] {
settings.input.defocus = "Pause";
});
blockInput.setText("Block input").onActivate([&] {
settings.input.defocus = "Block";
});
allowInput.setText("Allow input").onActivate([&] {
settings.input.defocus = "Allow";
});
if(settings.input.defocus == "Pause") pauseEmulation.setChecked();
if(settings.input.defocus == "Block") blockInput.setChecked();
if(settings.input.defocus == "Allow") allowInput.setChecked();
warnOnUnverifiedGames.setText("Warn when loading games that have not been verified").setChecked(settings.emulator.warnOnUnverifiedGames).onToggle([&] { warnOnUnverifiedGames.setText("Warn when loading games that have not been verified").setChecked(settings.emulator.warnOnUnverifiedGames).onToggle([&] {
settings.emulator.warnOnUnverifiedGames = warnOnUnverifiedGames.checked(); settings.emulator.warnOnUnverifiedGames = warnOnUnverifiedGames.checked();
}); });
@ -35,42 +20,6 @@ auto EmulatorSettings::create() -> void {
autoLoadStateOnLoad.setText("Auto-resume on load").setChecked(settings.emulator.autoLoadStateOnLoad).onToggle([&] { autoLoadStateOnLoad.setText("Auto-resume on load").setChecked(settings.emulator.autoLoadStateOnLoad).onToggle([&] {
settings.emulator.autoLoadStateOnLoad = autoLoadStateOnLoad.checked(); settings.emulator.autoLoadStateOnLoad = autoLoadStateOnLoad.checked();
}); });
rewindFrequencyLabel.setText("Rewind Frequency:");
rewindFrequencyOption.append(ComboButtonItem().setText("Disabled"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 10 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 20 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 30 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 40 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 50 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 60 frames"));
if(settings.emulator.rewind.frequency == 0) rewindFrequencyOption.item(0).setSelected();
if(settings.emulator.rewind.frequency == 10) rewindFrequencyOption.item(1).setSelected();
if(settings.emulator.rewind.frequency == 20) rewindFrequencyOption.item(2).setSelected();
if(settings.emulator.rewind.frequency == 30) rewindFrequencyOption.item(3).setSelected();
if(settings.emulator.rewind.frequency == 40) rewindFrequencyOption.item(4).setSelected();
if(settings.emulator.rewind.frequency == 50) rewindFrequencyOption.item(5).setSelected();
if(settings.emulator.rewind.frequency == 60) rewindFrequencyOption.item(6).setSelected();
rewindFrequencyOption.onChange([&] {
settings.emulator.rewind.frequency = rewindFrequencyOption.selected().offset() * 10;
program.rewindReset();
});
rewindLengthLabel.setText("Rewind Length:");
rewindLengthOption.append(ComboButtonItem().setText( "10 states"));
rewindLengthOption.append(ComboButtonItem().setText( "20 states"));
rewindLengthOption.append(ComboButtonItem().setText( "40 states"));
rewindLengthOption.append(ComboButtonItem().setText( "80 states"));
rewindLengthOption.append(ComboButtonItem().setText("160 states"));
rewindLengthOption.append(ComboButtonItem().setText("320 states"));
if(settings.emulator.rewind.length == 10) rewindLengthOption.item(0).setSelected();
if(settings.emulator.rewind.length == 20) rewindLengthOption.item(1).setSelected();
if(settings.emulator.rewind.length == 40) rewindLengthOption.item(2).setSelected();
if(settings.emulator.rewind.length == 80) rewindLengthOption.item(3).setSelected();
if(settings.emulator.rewind.length == 160) rewindLengthOption.item(4).setSelected();
if(settings.emulator.rewind.length == 320) rewindLengthOption.item(5).setSelected();
rewindLengthOption.onChange([&] {
settings.emulator.rewind.length = 10 << rewindLengthOption.selected().offset();
program.rewindReset();
});
optionsSpacer.setColor({192, 192, 192}); optionsSpacer.setColor({192, 192, 192});
ppuLabel.setText("PPU (video)").setFont(Font().setBold()); ppuLabel.setText("PPU (video)").setFont(Font().setBold());
@ -131,24 +80,5 @@ auto EmulatorSettings::create() -> void {
coprocessorsHLEOption.setText("Prefer HLE").setChecked(settings.emulator.hack.coprocessors.hle).onToggle([&] { coprocessorsHLEOption.setText("Prefer HLE").setChecked(settings.emulator.hack.coprocessors.hle).onToggle([&] {
settings.emulator.hack.coprocessors.hle = coprocessorsHLEOption.checked(); settings.emulator.hack.coprocessors.hle = coprocessorsHLEOption.checked();
}); });
superFXLabel.setText("SuperFX clock speed:");
superFXValue.setAlignment(0.5);
superFXClock.setLength(71).setPosition((settings.emulator.hack.fastSuperFX - 100) / 10).onChange([&] {
settings.emulator.hack.fastSuperFX = superFXClock.position() * 10 + 100;
superFXValue.setText({settings.emulator.hack.fastSuperFX, "%"});
}).doChange();
hacksNote.setText("Note: some hack setting changes do not take effect until after reloading games."); hacksNote.setText("Note: some hack setting changes do not take effect until after reloading games.");
} }
auto EmulatorSettings::updateConfiguration() -> void {
emulator->configure("Hacks/PPU/Fast", fastPPU.checked());
emulator->configure("Hacks/PPU/NoSpriteLimit", noSpriteLimit.checked());
emulator->configure("Hacks/PPU/Mode7/Scale", mode7Scale.selected().property("multiplier").natural());
emulator->configure("Hacks/PPU/Mode7/Perspective", mode7Perspective.checked());
emulator->configure("Hacks/PPU/Mode7/Supersample", mode7Supersample.checked());
emulator->configure("Hacks/PPU/Mode7/Mosaic", mode7Mosaic.checked());
emulator->configure("Hacks/DSP/Fast", fastDSP.checked());
emulator->configure("Hacks/DSP/Cubic", cubicInterpolation.checked());
emulator->configure("Hacks/Coprocessor/DelayedSync", coprocessorsDelayedSyncOption.checked());
emulator->configure("Hacks/Coprocessor/HLE", coprocessorsHLEOption.checked());
}

View File

@ -1,13 +1,10 @@
auto HotkeySettings::create() -> void { auto HotkeySettings::create() -> void {
setIcon(Icon::Device::Keyboard); setCollapsible();
setText("Hotkeys"); setVisible(false);
layout.setPadding(5_sx);
mappingList.setBatchable(); mappingList.setBatchable();
mappingList.setHeadered(); mappingList.setHeadered();
mappingList.onActivate([&] { mappingList.onActivate([&](auto cell) { assignMapping(cell); });
if(assignButton.enabled()) assignButton.doActivate();
});
mappingList.onChange([&] { mappingList.onChange([&] {
auto batched = mappingList.batched(); auto batched = mappingList.batched();
assignButton.setEnabled(batched.size() == 1); assignButton.setEnabled(batched.size() == 1);
@ -16,12 +13,33 @@ auto HotkeySettings::create() -> void {
mappingList.onSize([&] { mappingList.onSize([&] {
mappingList.resizeColumns(); mappingList.resizeColumns();
}); });
logicLabel.setText("Combinational logic:").setToolTip(
"Determines whether all or any mappings must be pressed to activate hotkeys.\n"
"Use 'and' logic if you want keyboard combinations such as Ctrl+F to trigger a hotkey.\n"
"Use 'or' logic if you want both a keyboard and joypad to trigger the same hotkey."
);
logicAND.setText("And").setToolTip("Every mapping must be pressed to activate a given hotkey.").onActivate([&] {
settings.input.hotkey.logic = "and";
inputManager.hotkeyLogic = InputMapping::Logic::AND;
});
logicOR.setText("Or").setToolTip("Any mapping can be pressed to activate a given hotkey.").onActivate([&] {
settings.input.hotkey.logic = "or";
inputManager.hotkeyLogic = InputMapping::Logic::OR;
});
if(settings.input.hotkey.logic == "and") logicAND.setChecked().doActivate();
if(settings.input.hotkey.logic == "or") logicOR.setChecked().doActivate();
inputSink.setFocusable();
assignButton.setText("Assign").onActivate([&] { assignButton.setText("Assign").onActivate([&] {
assignMapping(); clearButton.doActivate();
assignMapping(mappingList.selected().cell(0));
}); });
clearButton.setText("Clear").onActivate([&] { clearButton.setText("Clear").onActivate([&] {
for(auto item : mappingList.batched()) { cancelMapping();
inputManager.hotkeys[item.offset()].unbind(); for(auto mapping : mappingList.batched()) {
auto& hotkey = inputManager.hotkeys[mapping.offset()];
for(uint index : range(BindingLimit)) {
hotkey.unbind(index);
}
} }
refreshMappings(); refreshMappings();
}); });
@ -30,49 +48,60 @@ auto HotkeySettings::create() -> void {
auto HotkeySettings::reloadMappings() -> void { auto HotkeySettings::reloadMappings() -> void {
mappingList.reset(); mappingList.reset();
mappingList.append(TableViewColumn().setText("Name")); mappingList.append(TableViewColumn().setText("Name"));
mappingList.append(TableViewColumn().setText("Mapping").setExpandable()); for(uint index : range(BindingLimit)) {
mappingList.append(TableViewColumn().setText({"Mapping #", 1 + index}).setExpandable());
}
for(auto& hotkey : inputManager.hotkeys) { for(auto& hotkey : inputManager.hotkeys) {
mappingList.append(TableViewItem() TableViewItem item{&mappingList};
.append(TableViewCell().setText(hotkey.name).setFont(Font().setBold())) item.append(TableViewCell().setText(hotkey.name).setFont(Font().setBold()));
.append(TableViewCell()) for(uint index : range(BindingLimit)) item.append(TableViewCell());
);
} }
refreshMappings(); refreshMappings();
mappingList.doChange(); mappingList.doChange();
} }
auto HotkeySettings::refreshMappings() -> void { auto HotkeySettings::refreshMappings() -> void {
uint index = 0; uint item = 0;
for(auto& hotkey : inputManager.hotkeys) { for(auto& hotkey : inputManager.hotkeys) {
mappingList.item(index++).cell(1).setText(hotkey.displayName()); for(uint index : range(BindingLimit)) {
auto cell = mappingList.item(item).cell(1 + index);
auto& binding = hotkey.bindings[index];
cell.setIcon(binding.icon());
cell.setText(binding.name());
} }
item++;
}
Application::processEvents();
mappingList.resizeColumns(); mappingList.resizeColumns();
} }
auto HotkeySettings::assignMapping() -> void { auto HotkeySettings::assignMapping(TableViewCell cell) -> void {
inputManager.poll(); //clear any pending events first inputManager.poll(); //clear any pending events first
if(auto item = mappingList.selected()) { for(auto mapping : mappingList.batched()) {
activeMapping = inputManager.hotkeys[item.offset()]; activeMapping = inputManager.hotkeys[mapping.offset()];
settingsWindow.layout.setEnabled(false); activeBinding = max(0, (int)cell.offset() - 1);
settingsWindow.statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."}); mappingList.item(mapping.offset()).cell(1 + activeBinding).setIcon(Icon::Go::Right).setText("(assign ...)");
settingsWindow.statusBar.setText({"Press a key or button for mapping# ", 1 + activeBinding, " [", activeMapping->name, "] ..."});
settingsWindow.setDismissable(false); settingsWindow.setDismissable(false);
inputSink.setFocused();
return; //map only one input at a time
} }
} }
auto HotkeySettings::cancelMapping() -> void { auto HotkeySettings::cancelMapping() -> void {
activeMapping.reset(); activeMapping.reset();
settingsWindow.statusBar.setText(); settingsWindow.statusBar.setText();
settingsWindow.layout.setEnabled();
settingsWindow.doSize(); settingsWindow.doSize();
settingsWindow.setDismissable(true); settingsWindow.setDismissable(true);
mappingList.setFocused();
} }
auto HotkeySettings::inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void { auto HotkeySettings::inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void {
if(!activeMapping) return; if(!activeMapping) return;
if(device->isMouse()) return; if(device->isMouse()) return;
if(activeMapping->bind(device, group, input, oldValue, newValue)) { if(activeMapping->bind(device, group, input, oldValue, newValue, activeBinding)) {
activeMapping.reset(); activeMapping.reset();
settingsWindow.statusBar.setText("Mapping assigned."); settingsWindow.statusBar.setText("Mapping assigned.");
refreshMappings(); refreshMappings();

View File

@ -1,8 +1,22 @@
auto InputSettings::create() -> void { auto InputSettings::create() -> void {
setIcon(Icon::Device::Joypad); setCollapsible();
setText("Input"); setVisible(false);
inputFocusLabel.setText("When focus is lost:");
pauseEmulation.setText("Pause emulation").onActivate([&] {
settings.input.defocus = "Pause";
});
blockInput.setText("Block input").onActivate([&] {
settings.input.defocus = "Block";
});
allowInput.setText("Allow input").onActivate([&] {
settings.input.defocus = "Allow";
});
if(settings.input.defocus == "Pause") pauseEmulation.setChecked();
if(settings.input.defocus == "Block") blockInput.setChecked();
if(settings.input.defocus == "Allow") allowInput.setChecked();
separator.setColor({192, 192, 192});
layout.setPadding(5_sx);
portLabel.setText("Port:"); portLabel.setText("Port:");
portList.onChange([&] { reloadDevices(); }); portList.onChange([&] { reloadDevices(); });
deviceLabel.setText("Device:"); deviceLabel.setText("Device:");
@ -23,18 +37,23 @@ auto InputSettings::create() -> void {
}); });
mappingList.setBatchable(); mappingList.setBatchable();
mappingList.setHeadered(); mappingList.setHeadered();
mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); }); mappingList.onActivate([&](auto cell) { assignMapping(cell); });
mappingList.onChange([&] { updateControls(); }); mappingList.onChange([&] { updateControls(); });
mappingList.onSize([&] { mappingList.resizeColumns(); }); mappingList.onSize([&] { mappingList.resizeColumns(); });
inputSink.setFocusable();
assignMouse1.onActivate([&] { assignMouseInput(0); }); assignMouse1.onActivate([&] { assignMouseInput(0); });
assignMouse2.onActivate([&] { assignMouseInput(1); }); assignMouse2.onActivate([&] { assignMouseInput(1); });
assignMouse3.onActivate([&] { assignMouseInput(2); }); assignMouse3.onActivate([&] { assignMouseInput(2); });
assignButton.setText("Assign").onActivate([&] { assignButton.setText("Assign").onActivate([&] {
assignMapping(); clearButton.doActivate();
assignMapping(mappingList.selected().cell(0));
}); });
clearButton.setText("Clear").onActivate([&] { clearButton.setText("Clear").onActivate([&] {
cancelMapping();
for(auto mapping : mappingList.batched()) { for(auto mapping : mappingList.batched()) {
activeDevice().mappings[mapping.offset()].unbind(); for(uint index : range(BindingLimit)) {
activeDevice().mappings[mapping.offset()].unbind(index);
}
} }
refreshMappings(); refreshMappings();
}); });
@ -48,7 +67,7 @@ auto InputSettings::updateControls() -> void {
assignMouse2.setVisible(false); assignMouse2.setVisible(false);
assignMouse3.setVisible(false); assignMouse3.setVisible(false);
if(batched.size() == 1) { if(activeMapping) {
auto& input = activeDevice().mappings[batched.left().offset()]; auto& input = activeDevice().mappings[batched.left().offset()];
if(input.isDigital()) { if(input.isDigital()) {
assignMouse1.setVisible().setText("Mouse Left"); assignMouse1.setVisible().setText("Mouse Left");
@ -59,6 +78,8 @@ auto InputSettings::updateControls() -> void {
assignMouse2.setVisible().setText("Mouse Y-axis"); assignMouse2.setVisible().setText("Mouse Y-axis");
} }
} }
controlLayout.resize();
} }
auto InputSettings::activePort() -> InputPort& { auto InputSettings::activePort() -> InputPort& {
@ -94,48 +115,60 @@ auto InputSettings::reloadDevices() -> void {
auto InputSettings::reloadMappings() -> void { auto InputSettings::reloadMappings() -> void {
mappingList.reset(); mappingList.reset();
mappingList.append(TableViewColumn().setText("Name")); mappingList.append(TableViewColumn().setText("Name"));
mappingList.append(TableViewColumn().setText("Mapping").setExpandable()); for(uint n : range(BindingLimit)) {
mappingList.append(TableViewColumn().setText({"Mapping #", 1 + n}).setExpandable());
}
for(auto& mapping : activeDevice().mappings) { for(auto& mapping : activeDevice().mappings) {
mappingList.append(TableViewItem() TableViewItem item{&mappingList};
.append(TableViewCell().setText(mapping.name).setFont(Font().setBold())) item.append(TableViewCell().setText(mapping.name).setFont(Font().setBold()));
.append(TableViewCell()) for(uint n : range(BindingLimit)) item.append(TableViewCell());
);
} }
refreshMappings(); refreshMappings();
updateControls(); updateControls();
} }
auto InputSettings::refreshMappings() -> void { auto InputSettings::refreshMappings() -> void {
uint index = 0; uint item = 0;
for(auto& mapping : activeDevice().mappings) { for(auto& mapping : activeDevice().mappings) {
mappingList.item(index++).cell(1).setText(mapping.displayName()); for(uint index : range(BindingLimit)) {
auto cell = mappingList.item(item).cell(1 + index);
auto& binding = mapping.bindings[index];
cell.setIcon(binding.icon());
cell.setText(binding.name());
}
item++;
} }
Application::processEvents(); Application::processEvents();
mappingList.resizeColumns(); mappingList.resizeColumns();
} }
auto InputSettings::assignMapping() -> void { auto InputSettings::assignMapping(TableViewCell cell) -> void {
inputManager.poll(); //clear any pending events first inputManager.poll(); //clear any pending events first
for(auto mapping : mappingList.batched()) { for(auto mapping : mappingList.batched()) {
activeMapping = activeDevice().mappings[mapping.offset()]; activeMapping = activeDevice().mappings[mapping.offset()];
settingsWindow.layout.setEnabled(false); activeBinding = max(0, (int)cell.offset() - 1);
settingsWindow.statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."}); mappingList.item(mapping.offset()).cell(1 + activeBinding).setIcon(Icon::Go::Right).setText("(assign ...)");
settingsWindow.statusBar.setText({"Press a key or button for mapping #", 1 + activeBinding, " [", activeMapping->name, "] ..."});
settingsWindow.setDismissable(false); settingsWindow.setDismissable(false);
updateControls();
Application::processEvents();
inputSink.setFocused();
return; //map only one input at a time
} }
} }
auto InputSettings::cancelMapping() -> void { auto InputSettings::cancelMapping() -> void {
activeMapping.reset(); activeMapping.reset();
settingsWindow.statusBar.setText(); settingsWindow.statusBar.setText();
settingsWindow.layout.setEnabled();
settingsWindow.doSize(); settingsWindow.doSize();
settingsWindow.setDismissable(true); settingsWindow.setDismissable(true);
mappingList.setFocused();
updateControls();
} }
auto InputSettings::assignMouseInput(uint id) -> void { auto InputSettings::assignMouseInput(uint id) -> void {
if(auto mapping = mappingList.selected()) { if(!activeMapping) return;
activeMapping = activeDevice().mappings[mapping.offset()];
if(auto mouse = inputManager.findMouse()) { if(auto mouse = inputManager.findMouse()) {
if(activeMapping->isDigital()) { if(activeMapping->isDigital()) {
return inputEvent(mouse, HID::Mouse::GroupID::Button, id, 0, 1, true); return inputEvent(mouse, HID::Mouse::GroupID::Button, id, 0, 1, true);
@ -144,13 +177,12 @@ auto InputSettings::assignMouseInput(uint id) -> void {
} }
} }
} }
}
auto InputSettings::inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput) -> void { auto InputSettings::inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput) -> void {
if(!activeMapping) return; if(!activeMapping) return;
if(device->isMouse() && !allowMouseInput) return; if(device->isMouse() && !allowMouseInput) return;
if(activeMapping->bind(device, group, input, oldValue, newValue)) { if(activeMapping->bind(device, group, input, oldValue, newValue, activeBinding)) {
activeMapping.reset(); activeMapping.reset();
settingsWindow.statusBar.setText("Mapping assigned."); settingsWindow.statusBar.setText("Mapping assigned.");
refreshMappings(); refreshMappings();

View File

@ -1,15 +1,14 @@
auto PathSettings::create() -> void { auto PathSettings::create() -> void {
setIcon(Icon::Emblem::Folder); setCollapsible();
setText("Paths"); setVisible(false);
layout.setPadding(5_sx);
layout.setSize({4, 6}); layout.setSize({4, 6});
layout.column(0).setAlignment(1.0); layout.column(0).setAlignment(1.0);
gamesLabel.setText("Games:"); gamesLabel.setText("Games:");
gamesPath.setEditable(false); gamesPath.setEditable(false);
gamesAssign.setText("Assign ...").onActivate([&] { gamesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().selectFolder()) {
settings.path.games = location; settings.path.games = location;
refreshPaths(); refreshPaths();
} }
@ -22,7 +21,7 @@ auto PathSettings::create() -> void {
patchesLabel.setText("Patches:"); patchesLabel.setText("Patches:");
patchesPath.setEditable(false); patchesPath.setEditable(false);
patchesAssign.setText("Assign ...").onActivate([&] { patchesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().selectFolder()) {
settings.path.patches = location; settings.path.patches = location;
refreshPaths(); refreshPaths();
} }
@ -35,7 +34,7 @@ auto PathSettings::create() -> void {
savesLabel.setText("Saves:"); savesLabel.setText("Saves:");
savesPath.setEditable(false); savesPath.setEditable(false);
savesAssign.setText("Assign ...").onActivate([&] { savesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().selectFolder()) {
settings.path.saves = location; settings.path.saves = location;
refreshPaths(); refreshPaths();
} }
@ -48,7 +47,7 @@ auto PathSettings::create() -> void {
cheatsLabel.setText("Cheats:"); cheatsLabel.setText("Cheats:");
cheatsPath.setEditable(false); cheatsPath.setEditable(false);
cheatsAssign.setText("Assign ...").onActivate([&] { cheatsAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().selectFolder()) {
settings.path.cheats = location; settings.path.cheats = location;
refreshPaths(); refreshPaths();
} }
@ -61,7 +60,7 @@ auto PathSettings::create() -> void {
statesLabel.setText("States:"); statesLabel.setText("States:");
statesPath.setEditable(false); statesPath.setEditable(false);
statesAssign.setText("Assign ...").onActivate([&] { statesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().selectFolder()) {
settings.path.states = location; settings.path.states = location;
refreshPaths(); refreshPaths();
} }
@ -74,7 +73,7 @@ auto PathSettings::create() -> void {
screenshotsLabel.setText("Screenshots:"); screenshotsLabel.setText("Screenshots:");
screenshotsPath.setEditable(false); screenshotsPath.setEditable(false);
screenshotsAssign.setText("Assign ...").onActivate([&] { screenshotsAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().selectFolder()) {
settings.path.screenshots = location; settings.path.screenshots = location;
refreshPaths(); refreshPaths();
} }

View File

@ -4,6 +4,7 @@
#include "input.cpp" #include "input.cpp"
#include "hotkeys.cpp" #include "hotkeys.cpp"
#include "paths.cpp" #include "paths.cpp"
#include "speed.cpp"
#include "emulator.cpp" #include "emulator.cpp"
#include "drivers.cpp" #include "drivers.cpp"
Settings settings; Settings settings;
@ -12,6 +13,7 @@ AudioSettings audioSettings;
InputSettings inputSettings; InputSettings inputSettings;
HotkeySettings hotkeySettings; HotkeySettings hotkeySettings;
PathSettings pathSettings; PathSettings pathSettings;
SpeedSettings speedSettings;
EmulatorSettings emulatorSettings; EmulatorSettings emulatorSettings;
DriverSettings driverSettings; DriverSettings driverSettings;
namespace Instances { Instance<SettingsWindow> settingsWindow; } namespace Instances { Instance<SettingsWindow> settingsWindow; }
@ -31,9 +33,9 @@ auto Settings::save() -> void {
auto Settings::process(bool load) -> void { auto Settings::process(bool load) -> void {
if(load) { if(load) {
//initialize non-static default settings //initialize non-static default settings
video.driver = ruby::Video::safestDriver(); video.driver = ruby::Video::optimalDriver();
audio.driver = ruby::Audio::safestDriver(); audio.driver = ruby::Audio::optimalDriver();
input.driver = ruby::Input::safestDriver(); input.driver = ruby::Input::optimalDriver();
} }
#define bind(type, path, name) \ #define bind(type, path, name) \
@ -52,7 +54,6 @@ auto Settings::process(bool load) -> void {
bind(natural, "Video/Luminance", video.luminance); bind(natural, "Video/Luminance", video.luminance);
bind(natural, "Video/Saturation", video.saturation); bind(natural, "Video/Saturation", video.saturation);
bind(natural, "Video/Gamma", video.gamma); bind(natural, "Video/Gamma", video.gamma);
bind(boolean, "Video/FastForwardFrameSkip", video.fastForwardFrameSkip);
bind(boolean, "Video/Snow", video.snow); bind(boolean, "Video/Snow", video.snow);
bind(text, "Video/Output", video.output); bind(text, "Video/Output", video.output);
@ -79,6 +80,7 @@ auto Settings::process(bool load) -> void {
bind(natural, "Input/Frequency", input.frequency); bind(natural, "Input/Frequency", input.frequency);
bind(text, "Input/Defocus", input.defocus); bind(text, "Input/Defocus", input.defocus);
bind(natural, "Input/Turbo/Frequency", input.turbo.frequency); bind(natural, "Input/Turbo/Frequency", input.turbo.frequency);
bind(text, "Input/Hotkey/Logic", input.hotkey.logic);
bind(text, "Path/Games", path.games); bind(text, "Path/Games", path.games);
bind(text, "Path/Patches", path.patches); bind(text, "Path/Patches", path.patches);
@ -93,13 +95,20 @@ auto Settings::process(bool load) -> void {
bind(text, "Path/Recent/SufamiTurboA", path.recent.sufamiTurboA); bind(text, "Path/Recent/SufamiTurboA", path.recent.sufamiTurboA);
bind(text, "Path/Recent/SufamiTurboB", path.recent.sufamiTurboB); bind(text, "Path/Recent/SufamiTurboB", path.recent.sufamiTurboB);
bind(natural, "FastForward/FrameSkip", fastForward.frameSkip);
bind(natural, "FastForward/Limiter", fastForward.limiter);
bind(boolean, "FastForward/Mute", fastForward.mute);
bind(natural, "Rewind/Frequency", rewind.frequency);
bind(natural, "Rewind/Length", rewind.length);
bind(boolean, "Rewind/Mute", rewind.mute);
bind(boolean, "Emulator/WarnOnUnverifiedGames", emulator.warnOnUnverifiedGames); bind(boolean, "Emulator/WarnOnUnverifiedGames", emulator.warnOnUnverifiedGames);
bind(boolean, "Emulator/AutoSaveMemory/Enable", emulator.autoSaveMemory.enable); bind(boolean, "Emulator/AutoSaveMemory/Enable", emulator.autoSaveMemory.enable);
bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval); bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval);
bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload); bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload);
bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad); bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad);
bind(natural, "Emulator/Rewind/Frequency", emulator.rewind.frequency); bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock);
bind(natural, "Emulator/Rewind/Length", emulator.rewind.length);
bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast); bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast);
bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit); bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit);
bind(natural, "Emulator/Hack/PPU/Mode7/Scale", emulator.hack.ppu.mode7.scale); bind(natural, "Emulator/Hack/PPU/Mode7/Scale", emulator.hack.ppu.mode7.scale);
@ -110,7 +119,8 @@ auto Settings::process(bool load) -> void {
bind(boolean, "Emulator/Hack/DSP/Cubic", emulator.hack.dsp.cubic); bind(boolean, "Emulator/Hack/DSP/Cubic", emulator.hack.dsp.cubic);
bind(boolean, "Emulator/Hack/Coprocessors/DelayedSync", emulator.hack.coprocessors.delayedSync); bind(boolean, "Emulator/Hack/Coprocessors/DelayedSync", emulator.hack.coprocessors.delayedSync);
bind(boolean, "Emulator/Hack/Coprocessors/HLE", emulator.hack.coprocessors.hle); bind(boolean, "Emulator/Hack/Coprocessors/HLE", emulator.hack.coprocessors.hle);
bind(natural, "Emulator/Hack/FastSuperFX", emulator.hack.fastSuperFX); bind(natural, "Emulator/Hack/SA1/Overclock", emulator.hack.sa1.overclock);
bind(natural, "Emulator/Hack/SuperFX/Overclock", emulator.hack.superfx.overclock);
bind(boolean, "Emulator/Cheats/Enable", emulator.cheats.enable); bind(boolean, "Emulator/Cheats/Enable", emulator.cheats.enable);
bind(boolean, "General/StatusBar", general.statusBar); bind(boolean, "General/StatusBar", general.statusBar);
@ -121,19 +131,61 @@ auto Settings::process(bool load) -> void {
#undef bind #undef bind
} }
struct SettingsHome : VerticalLayout {
SettingsHome() {
setCollapsible();
setVisible(false);
image icon{Resource::Icon};
auto data = icon.data();
for(uint y : range(icon.height())) {
for(uint x : range(icon.width())) {
auto pixel = icon.read(data);
auto a = pixel >> 24 & 255;
auto r = pixel >> 16 & 255;
auto g = pixel >> 8 & 255;
auto b = pixel >> 0 & 255;
a = a * 0.25;
icon.write(data, a << 24 | r << 16 | g << 8 | b << 0);
data += icon.stride();
}
}
canvas.setIcon(icon);
}
public:
Canvas canvas{this, Size{~0, ~0}};
} settingsHome;
auto SettingsWindow::create() -> void { auto SettingsWindow::create() -> void {
layout.setPadding(5_sx); layout.setPadding(5_sx);
panel.append(videoSettings); panelList.append(ListViewItem().setText("Video").setIcon(Icon::Device::Display));
panel.append(audioSettings); panelList.append(ListViewItem().setText("Audio").setIcon(Icon::Device::Speaker));
panel.append(inputSettings); panelList.append(ListViewItem().setText("Input").setIcon(Icon::Device::Joypad));
panel.append(hotkeySettings); panelList.append(ListViewItem().setText("Hotkeys").setIcon(Icon::Device::Keyboard));
panel.append(pathSettings); panelList.append(ListViewItem().setText("Paths").setIcon(Icon::Emblem::Folder));
panel.append(emulatorSettings); panelList.append(ListViewItem().setText("Speed").setIcon(Icon::Device::Clock));
panel.append(driverSettings); panelList.append(ListViewItem().setText("Emulator").setIcon(Icon::Action::Settings));
panelList.append(ListViewItem().setText("Drivers").setIcon(Icon::Place::Settings));
panelList.onChange([&] {
if(auto item = panelList.selected()) {
show(item.offset());
} else {
show(-1);
}
});
panelContainer.append(settingsHome, Size{~0, ~0});
panelContainer.append(videoSettings, Size{~0, ~0});
panelContainer.append(audioSettings, Size{~0, ~0});
panelContainer.append(inputSettings, Size{~0, ~0});
panelContainer.append(hotkeySettings, Size{~0, ~0});
panelContainer.append(pathSettings, Size{~0, ~0});
panelContainer.append(speedSettings, Size{~0, ~0});
panelContainer.append(emulatorSettings, Size{~0, ~0});
panelContainer.append(driverSettings, Size{~0, ~0});
statusBar.setFont(Font().setBold()); statusBar.setFont(Font().setBold());
setTitle("Settings"); setTitle("Settings");
setSize({600_sx, 400_sx}); setSize({680_sx, 400_sx});
setAlignment({0.0, 1.0}); setAlignment({0.0, 1.0});
setDismissable(); setDismissable();
@ -153,8 +205,27 @@ auto SettingsWindow::setVisible(bool visible) -> SettingsWindow& {
return Window::setVisible(visible), *this; return Window::setVisible(visible), *this;
} }
auto SettingsWindow::show(uint index) -> void { auto SettingsWindow::show(int index) -> void {
panel.item(index).setSelected(); settingsHome.setVisible(false);
videoSettings.setVisible(false);
audioSettings.setVisible(false);
inputSettings.setVisible(false);
hotkeySettings.setVisible(false);
pathSettings.setVisible(false);
speedSettings.setVisible(false);
emulatorSettings.setVisible(false);
driverSettings.setVisible(false);
panelList.item(index).setSelected();
if(index ==-1) settingsHome.setVisible(true);
if(index == 0) videoSettings.setVisible(true);
if(index == 1) audioSettings.setVisible(true);
if(index == 2) inputSettings.setVisible(true);
if(index == 3) hotkeySettings.setVisible(true);
if(index == 4) pathSettings.setVisible(true);
if(index == 5) speedSettings.setVisible(true);
if(index == 6) emulatorSettings.setVisible(true);
if(index == 7) driverSettings.setVisible(true);
panelContainer.resize();
setVisible(); setVisible();
setFocused(); setFocused();
} }

View File

@ -17,7 +17,6 @@ struct Settings : Markup::Node {
uint luminance = 100; uint luminance = 100;
uint saturation = 100; uint saturation = 100;
uint gamma = 150; uint gamma = 150;
bool fastForwardFrameSkip = true;
bool snow = false; bool snow = false;
string output = "Scale"; string output = "Scale";
@ -50,6 +49,9 @@ struct Settings : Markup::Node {
struct Turbo { struct Turbo {
uint frequency = 4; uint frequency = 4;
} turbo; } turbo;
struct Hotkey {
string logic = "or";
} hotkey;
} input; } input;
struct Path { struct Path {
@ -68,6 +70,18 @@ struct Settings : Markup::Node {
} recent; } recent;
} path; } path;
struct FastForward {
uint frameSkip = 9;
uint limiter = 0;
bool mute = false;
} fastForward;
struct Rewind {
uint frequency = 0;
uint length = 80;
bool mute = false;
} rewind;
struct Emulator { struct Emulator {
bool warnOnUnverifiedGames = false; bool warnOnUnverifiedGames = false;
struct AutoSaveMemory { struct AutoSaveMemory {
@ -76,11 +90,10 @@ struct Settings : Markup::Node {
} autoSaveMemory; } autoSaveMemory;
bool autoSaveStateOnUnload = false; bool autoSaveStateOnUnload = false;
bool autoLoadStateOnLoad = false; bool autoLoadStateOnLoad = false;
struct Rewind {
uint frequency = 0;
uint length = 80;
} rewind;
struct Hack { struct Hack {
struct CPU {
uint overclock = 100;
} cpu;
struct PPU { struct PPU {
bool fast = true; bool fast = true;
bool noSpriteLimit = false; bool noSpriteLimit = false;
@ -99,7 +112,12 @@ struct Settings : Markup::Node {
bool delayedSync = true; bool delayedSync = true;
bool hle = true; bool hle = true;
} coprocessors; } coprocessors;
uint fastSuperFX = 100; struct SA1 {
uint overclock = 100;
} sa1;
struct SuperFX {
uint overclock = 100;
} superfx;
} hack; } hack;
struct Cheats { struct Cheats {
bool enable = true; bool enable = true;
@ -114,13 +132,12 @@ struct Settings : Markup::Node {
} general; } general;
}; };
struct VideoSettings : TabFrameItem { struct VideoSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
private: private:
VerticalLayout layout{this}; Label colorAdjustmentLabel{this, Size{~0, 0}, 2};
Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2}; TableLayout colorLayout{this, Size{~0, 0}};
TableLayout colorLayout{&layout, Size{~0, 0}};
Label luminanceLabel{&colorLayout, Size{0, 0}}; Label luminanceLabel{&colorLayout, Size{0, 0}};
Label luminanceValue{&colorLayout, Size{50_sx, 0}}; Label luminanceValue{&colorLayout, Size{50_sx, 0}};
HorizontalSlider luminanceSlider{&colorLayout, Size{~0, 0}}; HorizontalSlider luminanceSlider{&colorLayout, Size{~0, 0}};
@ -133,17 +150,15 @@ private:
Label gammaValue{&colorLayout, Size{50_sx, 0}}; Label gammaValue{&colorLayout, Size{50_sx, 0}};
HorizontalSlider gammaSlider{&colorLayout, Size{~0, 0}}; HorizontalSlider gammaSlider{&colorLayout, Size{~0, 0}};
// //
CheckLabel fastForwardFrameSkip{&layout, Size{~0, 0}}; CheckLabel snowOption{this, Size{~0, 0}};
CheckLabel snowOption{&layout, Size{~0, 0}};
}; };
struct AudioSettings : TabFrameItem { struct AudioSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
private: private:
VerticalLayout layout{this}; Label effectsLabel{this, Size{~0, 0}, 2};
Label effectsLabel{&layout, Size{~0, 0}, 2}; TableLayout effectsLayout{this, Size{~0, 0}};
TableLayout effectsLayout{&layout, Size{~0, 0}};
Label skewLabel{&effectsLayout, Size{0, 0}}; Label skewLabel{&effectsLayout, Size{0, 0}};
Label skewValue{&effectsLayout, Size{50_sx, 0}}; Label skewValue{&effectsLayout, Size{50_sx, 0}};
HorizontalSlider skewSlider{&effectsLayout, Size{~0, 0}}; HorizontalSlider skewSlider{&effectsLayout, Size{~0, 0}};
@ -157,7 +172,7 @@ private:
HorizontalSlider balanceSlider{&effectsLayout, Size{~0, 0}}; HorizontalSlider balanceSlider{&effectsLayout, Size{~0, 0}};
}; };
struct InputSettings : TabFrameItem { struct InputSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
auto updateControls() -> void; auto updateControls() -> void;
auto activePort() -> InputPort&; auto activePort() -> InputPort&;
@ -166,61 +181,72 @@ struct InputSettings : TabFrameItem {
auto reloadDevices() -> void; auto reloadDevices() -> void;
auto reloadMappings() -> void; auto reloadMappings() -> void;
auto refreshMappings() -> void; auto refreshMappings() -> void;
auto assignMapping() -> void; auto assignMapping(TableViewCell cell) -> void;
auto cancelMapping() -> void; auto cancelMapping() -> void;
auto assignMouseInput(uint id) -> void; auto assignMouseInput(uint id) -> void;
auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void; auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void;
maybe<InputMapping&> activeMapping; maybe<InputMapping&> activeMapping;
uint activeBinding = 0;
private: public:
Timer timer; Timer timer;
VerticalLayout layout{this}; HorizontalLayout inputFocusLayout{this, Size{~0, 0}};
HorizontalLayout selectionLayout{&layout, Size{~0, 0}}; Label inputFocusLabel{&inputFocusLayout, Size{0, 0}};
RadioLabel pauseEmulation{&inputFocusLayout, Size{0, 0}};
RadioLabel blockInput{&inputFocusLayout, Size{0, 0}};
RadioLabel allowInput{&inputFocusLayout, Size{0, 0}};
Group inputFocusGroup{&pauseEmulation, &blockInput, &allowInput};
Canvas separator{this, Size{~0, 1}};
HorizontalLayout selectionLayout{this, Size{~0, 0}};
Label portLabel{&selectionLayout, Size{0, 0}}; Label portLabel{&selectionLayout, Size{0, 0}};
ComboButton portList{&selectionLayout, Size{~0, 0}}; ComboButton portList{&selectionLayout, Size{~0, 0}};
Label deviceLabel{&selectionLayout, Size{0, 0}}; Label deviceLabel{&selectionLayout, Size{0, 0}};
ComboButton deviceList{&selectionLayout, Size{~0, 0}}; ComboButton deviceList{&selectionLayout, Size{~0, 0}};
Label turboLabel{&selectionLayout, Size{0, 0}}; Label turboLabel{&selectionLayout, Size{0, 0}};
ComboButton turboList{&selectionLayout, Size{0, 0}}; ComboButton turboList{&selectionLayout, Size{0, 0}};
TableView mappingList{&layout, Size{~0, ~0}}; TableView mappingList{this, Size{~0, ~0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}}; HorizontalLayout controlLayout{this, Size{~0, 0}};
Button assignMouse1{&controlLayout, Size{100_sx, 0}}; Button assignMouse1{&controlLayout, Size{100_sx, 0}};
Button assignMouse2{&controlLayout, Size{100_sx, 0}}; Button assignMouse2{&controlLayout, Size{100_sx, 0}};
Button assignMouse3{&controlLayout, Size{100_sx, 0}}; Button assignMouse3{&controlLayout, Size{100_sx, 0}};
Widget controlSpacer{&controlLayout, Size{~0, 0}}; Canvas inputSink{&controlLayout, Size{~0, ~0}};
Button assignButton{&controlLayout, Size{80_sx, 0}}; Button assignButton{&controlLayout, Size{80_sx, 0}};
Button clearButton{&controlLayout, Size{80_sx, 0}}; Button clearButton{&controlLayout, Size{80_sx, 0}};
}; };
struct HotkeySettings : TabFrameItem { struct HotkeySettings : VerticalLayout {
auto create() -> void; auto create() -> void;
auto reloadMappings() -> void; auto reloadMappings() -> void;
auto refreshMappings() -> void; auto refreshMappings() -> void;
auto assignMapping() -> void; auto assignMapping(TableViewCell cell) -> void;
auto cancelMapping() -> void; auto cancelMapping() -> void;
auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void; auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void;
maybe<InputMapping&> activeMapping; maybe<InputMapping&> activeMapping;
uint activeBinding = 0;
private: private:
Timer timer; Timer timer;
VerticalLayout layout{this}; TableView mappingList{this, Size{~0, ~0}};
TableView mappingList{&layout, Size{~0, ~0}}; HorizontalLayout controlLayout{this, Size{~0, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Label logicLabel{&controlLayout, Size{0, 0}};
Widget controlSpacer{&controlLayout, Size{~0, 0}}; RadioLabel logicAND{&controlLayout, Size{0, 0}};
RadioLabel logicOR{&controlLayout, Size{0, 0}};
Group logicGroup{&logicAND, &logicOR};
Canvas inputSink{&controlLayout, Size{~0, ~0}};
Button assignButton{&controlLayout, Size{80_sx, 0}}; Button assignButton{&controlLayout, Size{80_sx, 0}};
Button clearButton{&controlLayout, Size{80_sx, 0}}; Button clearButton{&controlLayout, Size{80_sx, 0}};
}; };
struct PathSettings : TabFrameItem { struct PathSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
auto refreshPaths() -> void; auto refreshPaths() -> void;
public: public:
TableLayout layout{this}; TableLayout layout{this, Size{~0, ~0}};
Label gamesLabel{&layout, Size{0, 0}}; Label gamesLabel{&layout, Size{0, 0}};
LineEdit gamesPath{&layout, Size{~0, 0}}; LineEdit gamesPath{&layout, Size{~0, 0}};
Button gamesAssign{&layout, Size{80_sx, 0}}; Button gamesAssign{&layout, Size{80_sx, 0}};
@ -252,57 +278,73 @@ public:
Button screenshotsReset{&layout, Size{80_sx, 0}}; Button screenshotsReset{&layout, Size{80_sx, 0}};
}; };
struct EmulatorSettings : TabFrameItem { struct SpeedSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
auto updateConfiguration() -> void;
public: public:
VerticalLayout layout{this}; Label overclockingLabel{this, Size{~0, 0}, 2};
Label optionsLabel{&layout, Size{~0, 0}, 2}; TableLayout overclockingLayout{this, Size{~0, 0}};
HorizontalLayout inputFocusLayout{&layout, Size{~0, 0}}; Label cpuLabel{&overclockingLayout, Size{0, 0}};
Label inputFocusLabel{&inputFocusLayout, Size{0, 0}}; Label cpuValue{&overclockingLayout, Size{50_sx, 0}};
RadioLabel pauseEmulation{&inputFocusLayout, Size{0, 0}}; HorizontalSlider cpuClock{&overclockingLayout, Size{~0, 0}};
RadioLabel blockInput{&inputFocusLayout, Size{0, 0}}; //
RadioLabel allowInput{&inputFocusLayout, Size{0, 0}}; Label sa1Label{&overclockingLayout, Size{0, 0}};
Group inputFocusGroup{&pauseEmulation, &blockInput, &allowInput}; Label sa1Value{&overclockingLayout, Size{50_sx, 0}};
CheckLabel warnOnUnverifiedGames{&layout, Size{~0, 0}}; HorizontalSlider sa1Clock{&overclockingLayout, Size{~0, 0}};
CheckLabel autoSaveMemory{&layout, Size{~0, 0}}; //
HorizontalLayout autoStateLayout{&layout, Size{~0, 0}}; Label sfxLabel{&overclockingLayout, Size{0, 0}};
CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}}; Label sfxValue{&overclockingLayout, Size{50_sx, 0}};
CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}}; HorizontalSlider sfxClock{&overclockingLayout, Size{~0, 0}};
HorizontalLayout rewindLayout{&layout, Size{~0, 0}}; Label fastForwardLabel{this, Size{~0, 0}, 2};
HorizontalLayout fastForwardLayout{this, Size{~0, 0}};
Label frameSkipLabel{&fastForwardLayout, Size{0, 0}};
ComboButton frameSkipAmount{&fastForwardLayout, Size{0, 0}};
Label limiterLabel{&fastForwardLayout, Size{0, 0}};
ComboButton limiterAmount{&fastForwardLayout, Size{0, 0}};
CheckLabel fastForwardMute{this, Size{0, 0}};
Label rewindLabel{this, Size{~0, 0}, 2};
HorizontalLayout rewindLayout{this, Size{~0, 0}};
Label rewindFrequencyLabel{&rewindLayout, Size{0, 0}}; Label rewindFrequencyLabel{&rewindLayout, Size{0, 0}};
ComboButton rewindFrequencyOption{&rewindLayout, Size{0, 0}}; ComboButton rewindFrequencyOption{&rewindLayout, Size{0, 0}};
Label rewindLengthLabel{&rewindLayout, Size{0, 0}}; Label rewindLengthLabel{&rewindLayout, Size{0, 0}};
ComboButton rewindLengthOption{&rewindLayout, Size{0, 0}}; ComboButton rewindLengthOption{&rewindLayout, Size{0, 0}};
Canvas optionsSpacer{&layout, Size{~0, 1}}; CheckLabel rewindMute{this, Size{0, 0}};
Label ppuLabel{&layout, Size{~0, 0}, 2}; };
HorizontalLayout ppuLayout{&layout, Size{~0, 0}};
struct EmulatorSettings : VerticalLayout {
auto create() -> void;
public:
Label optionsLabel{this, Size{~0, 0}, 2};
CheckLabel warnOnUnverifiedGames{this, Size{~0, 0}};
CheckLabel autoSaveMemory{this, Size{~0, 0}};
HorizontalLayout autoStateLayout{this, Size{~0, 0}};
CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}};
CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}};
Canvas optionsSpacer{this, Size{~0, 1}};
Label ppuLabel{this, Size{~0, 0}, 2};
HorizontalLayout ppuLayout{this, Size{~0, 0}};
CheckLabel fastPPU{&ppuLayout, Size{0, 0}}; CheckLabel fastPPU{&ppuLayout, Size{0, 0}};
CheckLabel noSpriteLimit{&ppuLayout, Size{0, 0}}; CheckLabel noSpriteLimit{&ppuLayout, Size{0, 0}};
Label mode7Label{&layout, Size{~0, 0}, 2}; Label mode7Label{this, Size{~0, 0}, 2};
HorizontalLayout mode7Layout{&layout, Size{~0, 0}}; HorizontalLayout mode7Layout{this, Size{~0, 0}};
Label mode7ScaleLabel{&mode7Layout, Size{0, 0}}; Label mode7ScaleLabel{&mode7Layout, Size{0, 0}};
ComboButton mode7Scale{&mode7Layout, Size{0, 0}}; ComboButton mode7Scale{&mode7Layout, Size{0, 0}};
CheckLabel mode7Perspective{&mode7Layout, Size{0, 0}}; CheckLabel mode7Perspective{&mode7Layout, Size{0, 0}};
CheckLabel mode7Supersample{&mode7Layout, Size{0, 0}}; CheckLabel mode7Supersample{&mode7Layout, Size{0, 0}};
CheckLabel mode7Mosaic{&mode7Layout, Size{0, 0}}; CheckLabel mode7Mosaic{&mode7Layout, Size{0, 0}};
Label dspLabel{&layout, Size{~0, 0}, 2}; Label dspLabel{this, Size{~0, 0}, 2};
HorizontalLayout dspLayout{&layout, Size{~0, 0}}; HorizontalLayout dspLayout{this, Size{~0, 0}};
CheckLabel fastDSP{&dspLayout, Size{0, 0}}; CheckLabel fastDSP{&dspLayout, Size{0, 0}};
CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}}; CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}};
Label coprocessorLabel{&layout, Size{~0, 0}, 2}; Label coprocessorLabel{this, Size{~0, 0}, 2};
HorizontalLayout coprocessorsLayout{&layout, Size{~0, 0}}; HorizontalLayout coprocessorsLayout{this, Size{~0, 0}};
CheckLabel coprocessorsDelayedSyncOption{&coprocessorsLayout, Size{0, 0}}; CheckLabel coprocessorsDelayedSyncOption{&coprocessorsLayout, Size{0, 0}};
CheckLabel coprocessorsHLEOption{&coprocessorsLayout, Size{0, 0}}; CheckLabel coprocessorsHLEOption{&coprocessorsLayout, Size{0, 0}};
HorizontalLayout superFXLayout{&layout, Size{~0, 0}}; Label hacksNote{this, Size{~0, 0}};
Label superFXLabel{&superFXLayout, Size{0, 0}};
Label superFXValue{&superFXLayout, Size{50_sx, 0}};
HorizontalSlider superFXClock{&superFXLayout, Size{~0, 0}};
Label hacksNote{&layout, Size{~0, 0}};
}; };
struct DriverSettings : TabFrameItem { struct DriverSettings : VerticalLayout {
auto create() -> void; auto create() -> void;
auto videoDriverChanged() -> void; auto videoDriverChanged() -> void;
auto videoDriverChange() -> void; auto videoDriverChange() -> void;
@ -320,9 +362,8 @@ struct DriverSettings : TabFrameItem {
auto inputDriverChange() -> void; auto inputDriverChange() -> void;
public: public:
VerticalLayout layout{this}; Label videoLabel{this, Size{~0, 0}, 2};
Label videoLabel{&layout, Size{~0, 0}, 2}; TableLayout videoLayout{this, Size{~0, 0}};
TableLayout videoLayout{&layout, Size{~0, 0}};
Label videoDriverLabel{&videoLayout, Size{0, 0}}; Label videoDriverLabel{&videoLayout, Size{0, 0}};
HorizontalLayout videoDriverLayout{&videoLayout, Size{~0, 0}}; HorizontalLayout videoDriverLayout{&videoLayout, Size{~0, 0}};
ComboButton videoDriverOption{&videoDriverLayout, Size{0, 0}}; ComboButton videoDriverOption{&videoDriverLayout, Size{0, 0}};
@ -331,12 +372,12 @@ public:
Label videoFormatLabel{&videoLayout, Size{0, 0}}; Label videoFormatLabel{&videoLayout, Size{0, 0}};
HorizontalLayout videoPropertyLayout{&videoLayout, Size{~0, 0}}; HorizontalLayout videoPropertyLayout{&videoLayout, Size{~0, 0}};
ComboButton videoFormatOption{&videoPropertyLayout, Size{0, 0}}; ComboButton videoFormatOption{&videoPropertyLayout, Size{0, 0}};
HorizontalLayout videoToggleLayout{&layout, Size{~0, 0}}; HorizontalLayout videoToggleLayout{this, Size{~0, 0}};
CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}};
CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}};
Canvas videoSpacer{&layout, Size{~0, 1}}; Canvas videoSpacer{this, Size{~0, 1}};
Label audioLabel{&layout, Size{~0, 0}, 2}; Label audioLabel{this, Size{~0, 0}, 2};
TableLayout audioLayout{&layout, Size{~0, 0}}; TableLayout audioLayout{this, Size{~0, 0}};
Label audioDriverLabel{&audioLayout, Size{0, 0}}; Label audioDriverLabel{&audioLayout, Size{0, 0}};
HorizontalLayout audioDriverLayout{&audioLayout, Size{~0, 0}}; HorizontalLayout audioDriverLayout{&audioLayout, Size{~0, 0}};
ComboButton audioDriverOption{&audioDriverLayout, Size{0, 0}}; ComboButton audioDriverOption{&audioDriverLayout, Size{0, 0}};
@ -349,13 +390,13 @@ public:
ComboButton audioFrequencyOption{&audioPropertyLayout, Size{0, 0}}; ComboButton audioFrequencyOption{&audioPropertyLayout, Size{0, 0}};
Label audioLatencyLabel{&audioPropertyLayout, Size{0, 0}}; Label audioLatencyLabel{&audioPropertyLayout, Size{0, 0}};
ComboButton audioLatencyOption{&audioPropertyLayout, Size{0, 0}}; ComboButton audioLatencyOption{&audioPropertyLayout, Size{0, 0}};
HorizontalLayout audioToggleLayout{&layout, Size{~0, 0}}; HorizontalLayout audioToggleLayout{this, Size{~0, 0}};
CheckLabel audioExclusiveToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioExclusiveToggle{&audioToggleLayout, Size{0, 0}};
CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}};
CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}};
Canvas audioSpacer{&layout, Size{~0, 1}}; Canvas audioSpacer{this, Size{~0, 1}};
Label inputLabel{&layout, Size{~0, 0}, 2}; Label inputLabel{this, Size{~0, 0}, 2};
TableLayout inputLayout{&layout, Size{~0, 0}}; TableLayout inputLayout{this, Size{~0, 0}};
Label inputDriverLabel{&inputLayout, Size{0, 0}}; Label inputDriverLabel{&inputLayout, Size{0, 0}};
HorizontalLayout inputDriverLayout{&inputLayout, Size{~0, 0}}; HorizontalLayout inputDriverLayout{&inputLayout, Size{~0, 0}};
ComboButton inputDriverOption{&inputDriverLayout, Size{0, 0}}; ComboButton inputDriverOption{&inputDriverLayout, Size{0, 0}};
@ -363,14 +404,16 @@ public:
Label inputDriverActive{&inputDriverLayout, Size{0, 0}}; Label inputDriverActive{&inputDriverLayout, Size{0, 0}};
}; };
struct SettingsWindow : Window { struct SettingsWindow : Window, Lock {
auto create() -> void; auto create() -> void;
auto setVisible(bool visible = true) -> SettingsWindow&; auto setVisible(bool visible = true) -> SettingsWindow&;
auto show(uint index) -> void; auto show(int index) -> void;
public: public:
VerticalLayout layout{this}; VerticalLayout layout{this};
TabFrame panel{&layout, Size{~0, ~0}}; HorizontalLayout panelLayout{&layout, Size{~0, ~0}};
ListView panelList{&panelLayout, Size{120_sx, ~0}};
VerticalLayout panelContainer{&panelLayout, Size{~0, ~0}};
StatusBar statusBar{this}; StatusBar statusBar{this};
}; };
@ -380,6 +423,7 @@ extern AudioSettings audioSettings;
extern InputSettings inputSettings; extern InputSettings inputSettings;
extern HotkeySettings hotkeySettings; extern HotkeySettings hotkeySettings;
extern PathSettings pathSettings; extern PathSettings pathSettings;
extern SpeedSettings speedSettings;
extern EmulatorSettings emulatorSettings; extern EmulatorSettings emulatorSettings;
extern DriverSettings driverSettings; extern DriverSettings driverSettings;
namespace Instances { extern Instance<SettingsWindow> settingsWindow; } namespace Instances { extern Instance<SettingsWindow> settingsWindow; }

View File

@ -0,0 +1,133 @@
auto SpeedSettings::create() -> void {
setCollapsible();
setVisible(false);
overclockingLabel.setText("Overclocking").setFont(Font().setBold());
overclockingLayout.setSize({3, 3});
overclockingLayout.column(0).setAlignment(1.0);
overclockingLayout.column(1).setAlignment(0.5);
cpuLabel.setText("CPU:");
cpuClock.setLength(301).setPosition((settings.emulator.hack.cpu.overclock - 100)).onChange([&] {
settings.emulator.hack.cpu.overclock = cpuClock.position() + 100;
emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock);
cpuValue.setText({settings.emulator.hack.cpu.overclock, "%"});
}).doChange();
sa1Label.setText("SA-1:");
sa1Clock.setLength(301).setPosition((settings.emulator.hack.sa1.overclock - 100)).onChange([&] {
settings.emulator.hack.sa1.overclock = sa1Clock.position() + 100;
emulator->configure("Hacks/SA1/Overclock", settings.emulator.hack.sa1.overclock);
sa1Value.setText({settings.emulator.hack.sa1.overclock, "%"});
}).doChange();
sfxLabel.setText("SuperFX:");
sfxClock.setLength(141).setPosition((settings.emulator.hack.superfx.overclock - 100) / 5).onChange([&] {
settings.emulator.hack.superfx.overclock = sfxClock.position() * 5 + 100;
emulator->configure("Hacks/SuperFX/Overclock", settings.emulator.hack.superfx.overclock);
sfxValue.setText({settings.emulator.hack.superfx.overclock, "%"});
}).doChange();
fastForwardLabel.setText("Fast Forward").setFont(Font().setBold());
frameSkipLabel.setText("Frame skip:").setToolTip({
"Set how many frames to skip while fast forwarding.\n"
"Frame skipping allows a higher maximum fast forwarding frame rate."
});
frameSkipAmount.append(ComboButtonItem().setText("None"));
frameSkipAmount.append(ComboButtonItem().setText("1 frame"));
frameSkipAmount.append(ComboButtonItem().setText("2 frames"));
frameSkipAmount.append(ComboButtonItem().setText("3 frames"));
frameSkipAmount.append(ComboButtonItem().setText("4 frames"));
frameSkipAmount.append(ComboButtonItem().setText("5 frames"));
frameSkipAmount.append(ComboButtonItem().setText("6 frames"));
frameSkipAmount.append(ComboButtonItem().setText("7 frames"));
frameSkipAmount.append(ComboButtonItem().setText("8 frames"));
frameSkipAmount.append(ComboButtonItem().setText("9 frames"));
frameSkipAmount.item(settings.fastForward.frameSkip).setSelected();
frameSkipAmount.onChange([&] {
settings.fastForward.frameSkip = frameSkipAmount.selected().offset();
});
limiterLabel.setText("Limiter:").setToolTip({
"Set the maximum speed when fast forwarding."
});
limiterAmount.append(ComboButtonItem().setText("None"));
limiterAmount.append(ComboButtonItem().setText("200%"));
limiterAmount.append(ComboButtonItem().setText("300%"));
limiterAmount.append(ComboButtonItem().setText("400%"));
limiterAmount.append(ComboButtonItem().setText("500%"));
limiterAmount.append(ComboButtonItem().setText("600%"));
limiterAmount.append(ComboButtonItem().setText("700%"));
limiterAmount.append(ComboButtonItem().setText("800%"));
if(settings.fastForward.limiter == 0) limiterAmount.item(0).setSelected();
if(settings.fastForward.limiter == 2) limiterAmount.item(1).setSelected();
if(settings.fastForward.limiter == 3) limiterAmount.item(2).setSelected();
if(settings.fastForward.limiter == 4) limiterAmount.item(3).setSelected();
if(settings.fastForward.limiter == 5) limiterAmount.item(4).setSelected();
if(settings.fastForward.limiter == 6) limiterAmount.item(5).setSelected();
if(settings.fastForward.limiter == 7) limiterAmount.item(6).setSelected();
if(settings.fastForward.limiter == 8) limiterAmount.item(7).setSelected();
limiterAmount.onChange([&] {
auto index = limiterAmount.selected().offset();
if(index == 0) settings.fastForward.limiter = 0;
if(index == 1) settings.fastForward.limiter = 2;
if(index == 2) settings.fastForward.limiter = 3;
if(index == 3) settings.fastForward.limiter = 4;
if(index == 4) settings.fastForward.limiter = 5;
if(index == 5) settings.fastForward.limiter = 6;
if(index == 6) settings.fastForward.limiter = 7;
if(index == 7) settings.fastForward.limiter = 8;
});
fastForwardMute.setText("Mute while fast forwarding").setChecked(settings.fastForward.mute).onToggle([&] {
settings.fastForward.mute = fastForwardMute.checked();
});
rewindLabel.setText("Rewind").setFont(Font().setBold());
rewindFrequencyLabel.setText("Frequency:");
rewindFrequencyOption.append(ComboButtonItem().setText("Disabled"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 10 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 20 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 30 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 40 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 50 frames"));
rewindFrequencyOption.append(ComboButtonItem().setText("Every 60 frames"));
if(settings.rewind.frequency == 0) rewindFrequencyOption.item(0).setSelected();
if(settings.rewind.frequency == 10) rewindFrequencyOption.item(1).setSelected();
if(settings.rewind.frequency == 20) rewindFrequencyOption.item(2).setSelected();
if(settings.rewind.frequency == 30) rewindFrequencyOption.item(3).setSelected();
if(settings.rewind.frequency == 40) rewindFrequencyOption.item(4).setSelected();
if(settings.rewind.frequency == 50) rewindFrequencyOption.item(5).setSelected();
if(settings.rewind.frequency == 60) rewindFrequencyOption.item(6).setSelected();
rewindFrequencyOption.onChange([&] {
settings.rewind.frequency = rewindFrequencyOption.selected().offset() * 10;
program.rewindReset();
});
rewindLengthLabel.setText("Length:");
rewindLengthOption.append(ComboButtonItem().setText( "10 states"));
rewindLengthOption.append(ComboButtonItem().setText( "20 states"));
rewindLengthOption.append(ComboButtonItem().setText( "40 states"));
rewindLengthOption.append(ComboButtonItem().setText( "80 states"));
rewindLengthOption.append(ComboButtonItem().setText("160 states"));
rewindLengthOption.append(ComboButtonItem().setText("320 states"));
if(settings.rewind.length == 10) rewindLengthOption.item(0).setSelected();
if(settings.rewind.length == 20) rewindLengthOption.item(1).setSelected();
if(settings.rewind.length == 40) rewindLengthOption.item(2).setSelected();
if(settings.rewind.length == 80) rewindLengthOption.item(3).setSelected();
if(settings.rewind.length == 160) rewindLengthOption.item(4).setSelected();
if(settings.rewind.length == 320) rewindLengthOption.item(5).setSelected();
rewindLengthOption.onChange([&] {
settings.rewind.length = 10 << rewindLengthOption.selected().offset();
program.rewindReset();
});
rewindMute.setText("Mute while rewinding").setChecked(settings.rewind.mute).onToggle([&] {
settings.rewind.mute = rewindMute.checked();
});
}

View File

@ -1,8 +1,6 @@
auto VideoSettings::create() -> void { auto VideoSettings::create() -> void {
setIcon(Icon::Device::Display); setCollapsible();
setText("Video"); setVisible(false);
layout.setPadding(5_sx);
colorAdjustmentLabel.setFont(Font().setBold()).setText("Color Adjustment"); colorAdjustmentLabel.setFont(Font().setBold()).setText("Color Adjustment");
colorLayout.setSize({3, 3}); colorLayout.setSize({3, 3});
@ -32,13 +30,6 @@ auto VideoSettings::create() -> void {
program.updateVideoPalette(); program.updateVideoPalette();
}).doChange(); }).doChange();
fastForwardFrameSkip.setText("Skip frames while fast forwarding").setChecked(settings.video.fastForwardFrameSkip).setToolTip({
"When using the fast forward hotkey, this option will enable a frame skip of 9.\n"
"Frame skipping while fast forwarding allows a higher maximum frame skipping frame rate."
}).onToggle([&] {
settings.video.fastForwardFrameSkip = fastForwardFrameSkip.checked();
});
snowOption.setText("Draw snow effect when idle").setChecked(settings.video.snow).onToggle([&] { snowOption.setText("Draw snow effect when idle").setChecked(settings.video.snow).onToggle([&] {
settings.video.snow = snowOption.checked(); settings.video.snow = snowOption.checked();
presentation.updateProgramIcon(); presentation.updateProgramIcon();

View File

@ -110,14 +110,13 @@ auto CheatWindow::doAccept() -> void {
// //
auto CheatEditor::create() -> void { auto CheatEditor::create() -> void {
setIcon(Icon::Edit::Replace); setCollapsible();
setText("Cheat Editor"); setVisible(false);
layout.setPadding(5_sx);
cheatList.setBatchable(); cheatList.setBatchable();
cheatList.setHeadered(); cheatList.setHeadered();
cheatList.setSortable(); cheatList.setSortable();
cheatList.onActivate([&] { cheatList.onActivate([&](auto cell) {
//kind of a hack: toggling a cheat code twice quickly (onToggle) will call onActivate. //kind of a hack: toggling a cheat code twice quickly (onToggle) will call onActivate.
//do not trigger the CheatWindow unless it's been at least two seconds since a cheat code was last toggled on or off. //do not trigger the CheatWindow unless it's been at least two seconds since a cheat code was last toggled on or off.
if(chrono::timestamp() - activateTimeout < 2) return; if(chrono::timestamp() - activateTimeout < 2) return;

View File

@ -1,8 +1,7 @@
auto CheatFinder::create() -> void { auto CheatFinder::create() -> void {
setIcon(Icon::Edit::Find); setCollapsible();
setText("Cheat Finder"); setVisible(false);
layout.setPadding(5_sx);
searchList.setHeadered(); searchList.setHeadered();
searchValue.onActivate([&] { eventScan(); }); searchValue.onActivate([&] { eventScan(); });
searchLabel.setText("Value:"); searchLabel.setText("Value:");

View File

@ -1,8 +1,7 @@
auto ManifestViewer::create() -> void { auto ManifestViewer::create() -> void {
setIcon(Icon::Emblem::Text); setCollapsible();
setText("Manifest Viewer"); setVisible(false);
layout.setPadding(5_sx);
manifestLabel.setText("Manifest:"); manifestLabel.setText("Manifest:");
manifestOption.onChange([&] { selectManifest(); }); manifestOption.onChange([&] { selectManifest(); });
manifestSpacer.setColor({192, 192, 192}); manifestSpacer.setColor({192, 192, 192});

View File

@ -47,15 +47,14 @@ auto StateWindow::doAccept() -> void {
} }
auto StateManager::create() -> void { auto StateManager::create() -> void {
setIcon(Icon::Application::FileManager); setCollapsible();
setText("State Manager"); setVisible(false);
layout.setPadding(5_sx);
stateLayout.setAlignment(0.0); stateLayout.setAlignment(0.0);
stateList.setBatchable(); stateList.setBatchable();
stateList.setHeadered(); stateList.setHeadered();
stateList.setSortable(); stateList.setSortable();
stateList.onActivate([&] { loadButton.doActivate(); }); stateList.onActivate([&](auto cell) { loadButton.doActivate(); });
stateList.onChange([&] { updateSelection(); }); stateList.onChange([&] { updateSelection(); });
stateList.onSort([&](TableViewColumn column) { stateList.onSort([&](TableViewColumn column) {
column.setSorting(column.sorting() == Sort::Ascending ? Sort::Descending : Sort::Ascending); column.setSorting(column.sorting() == Sort::Ascending ? Sort::Descending : Sort::Ascending);
@ -68,8 +67,9 @@ auto StateManager::create() -> void {
categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "Managed/")); categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "Managed/"));
categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "Quick/")); categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "Quick/"));
categoryOption.onChange([&] { loadStates(); }); categoryOption.onChange([&] { loadStates(); });
statePreviewSeparator.setColor({192, 192, 192}); statePreviewSeparator1.setColor({192, 192, 192});
statePreviewLabel.setFont(Font().setBold()).setText("Preview"); statePreviewLabel.setFont(Font().setBold()).setText("Preview");
statePreviewSeparator2.setColor({192, 192, 192});
loadButton.setText("Load").onActivate([&] { loadButton.setText("Load").onActivate([&] {
if(auto item = stateList.selected()) program.loadState(item.property("name")); if(auto item = stateList.selected()) program.loadState(item.property("name"));
}); });

View File

@ -4,11 +4,11 @@
#include "state-manager.cpp" #include "state-manager.cpp"
#include "manifest-viewer.cpp" #include "manifest-viewer.cpp"
namespace Instances { Instance<CheatDatabase> cheatDatabase; } namespace Instances { Instance<CheatDatabase> cheatDatabase; }
CheatFinder cheatFinder;
CheatDatabase& cheatDatabase = Instances::cheatDatabase(); CheatDatabase& cheatDatabase = Instances::cheatDatabase();
namespace Instances { Instance<CheatWindow> cheatWindow; } namespace Instances { Instance<CheatWindow> cheatWindow; }
CheatWindow& cheatWindow = Instances::cheatWindow(); CheatWindow& cheatWindow = Instances::cheatWindow();
CheatEditor cheatEditor; CheatEditor cheatEditor;
CheatFinder cheatFinder;
namespace Instances { Instance<StateWindow> stateWindow; } namespace Instances { Instance<StateWindow> stateWindow; }
StateWindow& stateWindow = Instances::stateWindow(); StateWindow& stateWindow = Instances::stateWindow();
StateManager stateManager; StateManager stateManager;
@ -16,20 +16,52 @@ ManifestViewer manifestViewer;
namespace Instances { Instance<ToolsWindow> toolsWindow; } namespace Instances { Instance<ToolsWindow> toolsWindow; }
ToolsWindow& toolsWindow = Instances::toolsWindow(); ToolsWindow& toolsWindow = Instances::toolsWindow();
struct ToolsHome : VerticalLayout {
ToolsHome() {
setCollapsible();
setVisible(false);
image icon{Resource::Icon};
auto data = icon.data();
for(uint y : range(icon.height())) {
for(uint x : range(icon.width())) {
auto pixel = icon.read(data);
auto a = pixel >> 24 & 255;
auto r = pixel >> 16 & 255;
auto g = pixel >> 8 & 255;
auto b = pixel >> 0 & 255;
a = a * 0.25;
icon.write(data, a << 24 | r << 16 | g << 8 | b << 0);
data += icon.stride();
}
}
canvas.setIcon(icon);
}
public:
Canvas canvas{this, Size{~0, ~0}};
} toolsHome;
auto ToolsWindow::create() -> void { auto ToolsWindow::create() -> void {
layout.setPadding(5_sx); layout.setPadding(5_sx);
panel.append(cheatFinder); panelList.append(ListViewItem().setText("Cheat Finder").setIcon(Icon::Action::Search));
panel.append(cheatEditor); panelList.append(ListViewItem().setText("Cheat Editor").setIcon(Icon::Edit::Replace));
panel.append(stateManager); panelList.append(ListViewItem().setText("State Manager").setIcon(Icon::Application::FileManager));
panel.append(manifestViewer); panelList.append(ListViewItem().setText("Manifest Viewer").setIcon(Icon::Emblem::Text));
panel.onChange([&] { panelList.onChange([&] {
uint offset = panel.selected().offset(); if(auto item = panelList.selected()) {
if(offset != 1) cheatDatabase.setVisible(false), cheatWindow.setVisible(false); show(item.offset());
if(offset != 2) stateWindow.setVisible(false); } else {
show(-1);
}
}); });
panelContainer.append(toolsHome, Size{~0, ~0});
panelContainer.append(cheatFinder, Size{~0, ~0});
panelContainer.append(cheatEditor, Size{~0, ~0});
panelContainer.append(stateManager, Size{~0, ~0});
panelContainer.append(manifestViewer, Size{~0, ~0});
setTitle("Tools"); setTitle("Tools");
setSize({720_sx, 480_sx}); setSize({720_sx, 400_sx});
setAlignment({1.0, 1.0}); setAlignment({1.0, 1.0});
setDismissable(); setDismissable();
@ -50,8 +82,21 @@ auto ToolsWindow::setVisible(bool visible) -> ToolsWindow& {
return *this; return *this;
} }
auto ToolsWindow::show(uint index) -> void { auto ToolsWindow::show(int index) -> void {
panel.item(index).setSelected(); toolsHome.setVisible(false);
cheatFinder.setVisible(false);
cheatEditor.setVisible(false);
stateManager.setVisible(false);
manifestViewer.setVisible(false);
panelList.item(index).setSelected();
if(index ==-1) toolsHome.setVisible(true);
if(index == 0) cheatFinder.setVisible(true);
if(index == 1) cheatEditor.setVisible(true);
if(index == 2) stateManager.setVisible(true);
if(index == 3) manifestViewer.setVisible(true);
if(index != 1) cheatDatabase.setVisible(false), cheatWindow.setVisible(false);
if(index != 2) stateWindow.setVisible(false);
panelContainer.resize();
setVisible(); setVisible();
setFocused(); setFocused();
} }

View File

@ -6,7 +6,7 @@ struct CheatCandidate {
uint32_t span; uint32_t span;
}; };
struct CheatFinder : TabFrameItem { struct CheatFinder : VerticalLayout {
auto create() -> void; auto create() -> void;
auto restart() -> void; auto restart() -> void;
auto refresh() -> void; auto refresh() -> void;
@ -18,9 +18,8 @@ struct CheatFinder : TabFrameItem {
public: public:
vector<CheatCandidate> candidates; vector<CheatCandidate> candidates;
VerticalLayout layout{this}; TableView searchList{this, Size{~0, ~0}};
TableView searchList{&layout, Size{~0, ~0}}; HorizontalLayout controlLayout{this, Size{~0, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Label searchLabel{&controlLayout, Size{0, 0}}; Label searchLabel{&controlLayout, Size{0, 0}};
LineEdit searchValue{&controlLayout, Size{~0, 0}}; LineEdit searchValue{&controlLayout, Size{~0, 0}};
ComboButton searchSize{&controlLayout, Size{0, 0}}; ComboButton searchSize{&controlLayout, Size{0, 0}};
@ -79,7 +78,7 @@ public:
Button cancelButton{&controlLayout, Size{80_sx, 0}}; Button cancelButton{&controlLayout, Size{80_sx, 0}};
}; };
struct CheatEditor : TabFrameItem { struct CheatEditor : VerticalLayout {
auto create() -> void; auto create() -> void;
auto refresh() -> void; auto refresh() -> void;
auto addCheat(Cheat cheat) -> void; auto addCheat(Cheat cheat) -> void;
@ -93,9 +92,8 @@ public:
vector<Cheat> cheats; vector<Cheat> cheats;
uint64_t activateTimeout = 0; uint64_t activateTimeout = 0;
VerticalLayout layout{this}; TableView cheatList{this, Size{~0, ~0}};
TableView cheatList{&layout, Size{~0, ~0}}; HorizontalLayout controlLayout{this, Size{~0, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button findCheatsButton{&controlLayout, Size{120_sx, 0}}; Button findCheatsButton{&controlLayout, Size{120_sx, 0}};
Widget spacer{&controlLayout, Size{~0, 0}}; Widget spacer{&controlLayout, Size{~0, 0}};
CheckLabel enableCheats{&controlLayout, Size{0, 0}}; CheckLabel enableCheats{&controlLayout, Size{0, 0}};
@ -121,7 +119,7 @@ public:
Button cancelButton{&controlLayout, Size{80_sx, 0}}; Button cancelButton{&controlLayout, Size{80_sx, 0}};
}; };
struct StateManager : TabFrameItem, Lock { struct StateManager : VerticalLayout, Lock {
auto create() -> void; auto create() -> void;
auto type() const -> string; auto type() const -> string;
auto loadStates() -> void; auto loadStates() -> void;
@ -139,17 +137,18 @@ public:
DateDescending, DateDescending,
} sortBy = SortBy::NameAscending; } sortBy = SortBy::NameAscending;
VerticalLayout layout{this}; HorizontalLayout stateLayout{this, Size{~0, ~0}};
HorizontalLayout stateLayout{&layout, Size{~0, ~0}};
TableView stateList{&stateLayout, Size{~0, ~0}}; TableView stateList{&stateLayout, Size{~0, ~0}};
VerticalLayout previewLayout{&stateLayout, Size{0, ~0}}; VerticalLayout previewLayout{&stateLayout, Size{0, ~0}};
HorizontalLayout categoryLayout{&previewLayout, Size{~0, 0}}; HorizontalLayout categoryLayout{&previewLayout, Size{~0, 0}};
Label categoryLabel{&categoryLayout, Size{0, 0}}; Label categoryLabel{&categoryLayout, Size{0, 0}};
ComboButton categoryOption{&categoryLayout, Size{~0, 0}}; ComboButton categoryOption{&categoryLayout, Size{~0, 0}};
Canvas statePreviewSeparator{&previewLayout, Size{~0, 1}}; Canvas statePreviewSeparator1{&previewLayout, Size{~0, 1}};
Label statePreviewLabel{&previewLayout, Size{~0, 0}}; Label statePreviewLabel{&previewLayout, Size{~0, 0}};
Canvas statePreview{&previewLayout, Size{256, 224}}; Canvas statePreview{&previewLayout, Size{256, 224}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Widget stateSpacer{&previewLayout, Size{~0, ~0}};
Canvas statePreviewSeparator2{&previewLayout, Size{~0, 1}};
HorizontalLayout controlLayout{this, Size{~0, 0}};
Button loadButton{&controlLayout, Size{80_sx, 0}}; Button loadButton{&controlLayout, Size{80_sx, 0}};
Button saveButton{&controlLayout, Size{80_sx, 0}}; Button saveButton{&controlLayout, Size{80_sx, 0}};
Widget spacer{&controlLayout, Size{~0, 0}}; Widget spacer{&controlLayout, Size{~0, 0}};
@ -158,35 +157,36 @@ public:
Button removeButton{&controlLayout, Size{80_sx, 0}}; Button removeButton{&controlLayout, Size{80_sx, 0}};
}; };
struct ManifestViewer : TabFrameItem { struct ManifestViewer : VerticalLayout {
auto create() -> void; auto create() -> void;
auto loadManifest() -> void; auto loadManifest() -> void;
auto selectManifest() -> void; auto selectManifest() -> void;
public: public:
VerticalLayout layout{this}; HorizontalLayout manifestLayout{this, Size{~0, 0}};
HorizontalLayout manifestLayout{&layout, Size{~0, 0}};
Label manifestLabel{&manifestLayout, Size{0, 0}}; Label manifestLabel{&manifestLayout, Size{0, 0}};
ComboButton manifestOption{&manifestLayout, Size{~0, 0}}; ComboButton manifestOption{&manifestLayout, Size{~0, 0}};
Canvas manifestSpacer{&layout, Size{~0, 1}}; Canvas manifestSpacer{this, Size{~0, 1}};
HorizontalLayout informationLayout{&layout, Size{~0, 0}}; HorizontalLayout informationLayout{this, Size{~0, 0}};
Canvas typeIcon{&informationLayout, Size{16, 16}}; Canvas typeIcon{&informationLayout, Size{16, 16}};
Label nameLabel{&informationLayout, Size{~0, 0}}; Label nameLabel{&informationLayout, Size{~0, 0}};
#if 0 && defined(Hiro_SourceEdit) #if 0 && defined(Hiro_SourceEdit)
SourceEdit manifestView{&layout, Size{~0, ~0}}; SourceEdit manifestView{this, Size{~0, ~0}};
#else #else
TextEdit manifestView{&layout, Size{~0, ~0}}; TextEdit manifestView{this, Size{~0, ~0}};
#endif #endif
}; };
struct ToolsWindow : Window { struct ToolsWindow : Window {
auto create() -> void; auto create() -> void;
auto setVisible(bool visible = true) -> ToolsWindow&; auto setVisible(bool visible = true) -> ToolsWindow&;
auto show(uint index) -> void; auto show(int index) -> void;
public: public:
VerticalLayout layout{this}; VerticalLayout layout{this};
TabFrame panel{&layout, Size{~0, ~0}}; HorizontalLayout panelLayout{&layout, Size{~0, ~0}};
ListView panelList{&panelLayout, Size{160_sx, ~0}};
VerticalLayout panelContainer{&panelLayout, Size{~0, ~0}};
}; };
namespace Instances { extern Instance<CheatDatabase> cheatDatabase; } namespace Instances { extern Instance<CheatDatabase> cheatDatabase; }

View File

@ -37,24 +37,24 @@ ifneq ($(filter $(platform),linux bsd),)
ifeq ($(hiro),gtk2) ifeq ($(hiro),gtk2)
hiro.flags = $(flags.cpp) -DHIRO_GTK=2 $(shell pkg-config --cflags gtk+-2.0 gtksourceview-2.0) hiro.flags = $(flags.cpp) -DHIRO_GTK=2 $(shell pkg-config --cflags gtk+-2.0 gtksourceview-2.0)
hiro.options = -lX11 $(shell pkg-config --libs gtk+-2.0 gtksourceview-2.0) hiro.options = -L/usr/local/lib -lX11 $(shell pkg-config --libs gtk+-2.0 gtksourceview-2.0)
endif endif
ifeq ($(hiro),gtk3) ifeq ($(hiro),gtk3)
hiro.flags = $(flags.cpp) -DHIRO_GTK=3 $(shell pkg-config --cflags gtk+-3.0 gtksourceview-3.0) -Wno-deprecated-declarations hiro.flags = $(flags.cpp) -DHIRO_GTK=3 $(shell pkg-config --cflags gtk+-3.0 gtksourceview-3.0) -Wno-deprecated-declarations
hiro.options = -lX11 $(shell pkg-config --libs gtk+-3.0 gtksourceview-3.0) hiro.options = -L/usr/local/lib -lX11 $(shell pkg-config --libs gtk+-3.0 gtksourceview-3.0)
endif endif
ifeq ($(hiro),qt4) ifeq ($(hiro),qt4)
moc = /usr/local/lib/qt4/bin/moc moc = /usr/local/lib/qt4/bin/moc
hiro.flags = $(flags.cpp) -DHIRO_QT=4 $(shell pkg-config --cflags QtCore QtGui) hiro.flags = $(flags.cpp) -DHIRO_QT=4 $(shell pkg-config --cflags QtCore QtGui)
hiro.options = -lX11 $(shell pkg-config --libs QtCore QtGui) hiro.options = -L/usr/local/lib -lX11 $(shell pkg-config --libs QtCore QtGui)
endif endif
ifeq ($(hiro),qt5) ifeq ($(hiro),qt5)
moc = /usr/local/lib/qt5/bin/moc moc = /usr/local/lib/qt5/bin/moc
hiro.flags = $(flags.cpp) -DHIRO_QT=5 -fPIC $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets) hiro.flags = $(flags.cpp) -DHIRO_QT=5 -fPIC $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets)
hiro.options = -lX11 $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets) hiro.options = -L/usr/local/lib -lX11 $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets)
endif endif
endif endif

View File

@ -111,7 +111,7 @@
} }
-(IBAction) activate:(id)sender { -(IBAction) activate:(id)sender {
tableView->doActivate(); tableView->doActivate({});
} }
-(IBAction) doubleAction:(id)sender { -(IBAction) doubleAction:(id)sender {

View File

@ -700,7 +700,7 @@ struct TableView : sTableView {
auto column(uint position) const { return self().column(position); } auto column(uint position) const { return self().column(position); }
auto columnCount() const { return self().columnCount(); } auto columnCount() const { return self().columnCount(); }
auto columns() const { return self().columns(); } auto columns() const { return self().columns(); }
auto doActivate() const { return self().doActivate(); } auto doActivate(sTableViewCell cell) const { return self().doActivate(cell); }
auto doChange() const { return self().doChange(); } auto doChange() const { return self().doChange(); }
auto doContext() const { return self().doContext(); } auto doContext() const { return self().doContext(); }
auto doEdit(sTableViewCell cell) const { return self().doEdit(cell); } auto doEdit(sTableViewCell cell) const { return self().doEdit(cell); }
@ -711,7 +711,7 @@ struct TableView : sTableView {
auto item(unsigned position) const { return self().item(position); } auto item(unsigned position) const { return self().item(position); }
auto itemCount() const { return self().itemCount(); } auto itemCount() const { return self().itemCount(); }
auto items() const { return self().items(); } auto items() const { return self().items(); }
auto onActivate(const function<void ()>& callback = {}) { return self().onActivate(callback), *this; } auto onActivate(const function<void (TableViewCell)>& callback = {}) { return self().onActivate(callback), *this; }
auto onChange(const function<void ()>& callback = {}) { return self().onChange(callback), *this; } auto onChange(const function<void ()>& callback = {}) { return self().onChange(callback), *this; }
auto onContext(const function<void ()>& callback = {}) { return self().onContext(callback), *this; } auto onContext(const function<void ()>& callback = {}) { return self().onContext(callback), *this; }
auto onEdit(const function<void (TableViewCell)>& callback = {}) { return self().onEdit(callback), *this; } auto onEdit(const function<void (TableViewCell)>& callback = {}) { return self().onEdit(callback), *this; }

View File

@ -65,8 +65,8 @@ auto mTableView::columns() const -> vector<TableViewColumn> {
return columns; return columns;
} }
auto mTableView::doActivate() const -> void { auto mTableView::doActivate(sTableViewCell cell) const -> void {
if(state.onActivate) return state.onActivate(); if(state.onActivate) return state.onActivate(cell);
} }
auto mTableView::doChange() const -> void { auto mTableView::doChange() const -> void {
@ -112,7 +112,7 @@ auto mTableView::items() const -> vector<TableViewItem> {
return items; return items;
} }
auto mTableView::onActivate(const function<void ()>& callback) -> type& { auto mTableView::onActivate(const function<void (TableViewCell)>& callback) -> type& {
state.onActivate = callback; state.onActivate = callback;
return *this; return *this;
} }

View File

@ -13,7 +13,7 @@ struct mTableView : mWidget {
auto column(uint position) const -> TableViewColumn; auto column(uint position) const -> TableViewColumn;
auto columnCount() const -> uint; auto columnCount() const -> uint;
auto columns() const -> vector<TableViewColumn>; auto columns() const -> vector<TableViewColumn>;
auto doActivate() const -> void; auto doActivate(sTableViewCell cell) const -> void;
auto doChange() const -> void; auto doChange() const -> void;
auto doContext() const -> void; auto doContext() const -> void;
auto doEdit(sTableViewCell cell) const -> void; auto doEdit(sTableViewCell cell) const -> void;
@ -24,7 +24,7 @@ struct mTableView : mWidget {
auto item(uint position) const -> TableViewItem; auto item(uint position) const -> TableViewItem;
auto itemCount() const -> uint; auto itemCount() const -> uint;
auto items() const -> vector<TableViewItem>; auto items() const -> vector<TableViewItem>;
auto onActivate(const function<void ()>& callback = {}) -> type&; auto onActivate(const function<void (TableViewCell)>& callback = {}) -> type&;
auto onChange(const function<void ()>& callback = {}) -> type&; auto onChange(const function<void ()>& callback = {}) -> type&;
auto onContext(const function<void ()>& callback = {}) -> type&; auto onContext(const function<void ()>& callback = {}) -> type&;
auto onEdit(const function<void (TableViewCell)>& callback = {}) -> type&; auto onEdit(const function<void (TableViewCell)>& callback = {}) -> type&;
@ -59,7 +59,7 @@ struct mTableView : mWidget {
Color foregroundColor; Color foregroundColor;
bool headered = false; bool headered = false;
vector<sTableViewItem> items; vector<sTableViewItem> items;
function<void ()> onActivate; function<void (TableViewCell)> onActivate;
function<void ()> onChange; function<void ()> onChange;
function<void ()> onContext; function<void ()> onContext;
function<void (TableViewCell)> onEdit; function<void (TableViewCell)> onEdit;

View File

@ -11,7 +11,7 @@ struct BrowserDialogWindow {
auto isFolder(const string& name) -> bool; auto isFolder(const string& name) -> bool;
auto isMatch(const string& name) -> bool; auto isMatch(const string& name) -> bool;
auto run() -> BrowserDialog::Response; auto run() -> BrowserDialog::Response;
auto setPath(string path) -> void; auto setPath(string path, const string& contains = "") -> void;
private: private:
Window window; Window window;
@ -24,7 +24,8 @@ private:
Button pathUp{&pathLayout, Size{0, 0}, 0}; Button pathUp{&pathLayout, Size{0, 0}, 0};
ListView view{&layout, Size{~0, ~0}, 5_sx}; ListView view{&layout, Size{~0, ~0}, 5_sx};
HorizontalLayout controlLayout{&layout, Size{~0, 0}}; HorizontalLayout controlLayout{&layout, Size{~0, 0}};
ComboButton filterList{&controlLayout, Size{0, 0}, 5_sx}; ComboButton filterList{&controlLayout, Size{0, 0}, 0};
Button searchButton{&controlLayout, Size{0, 0}, 0};
LineEdit fileName{&controlLayout, Size{~0, 0}, 5_sx}; LineEdit fileName{&controlLayout, Size{~0, 0}, 5_sx};
ComboButton optionList{&controlLayout, Size{0, 0}, 5_sx}; ComboButton optionList{&controlLayout, Size{0, 0}, 5_sx};
Button acceptButton{&controlLayout, Size{80_sx, 0}, 5_sx}; Button acceptButton{&controlLayout, Size{80_sx, 0}, 5_sx};
@ -212,12 +213,17 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
optionList.append(ComboButtonItem().setText(option)); optionList.append(ComboButtonItem().setText(option));
} }
optionList.doChange(); //updates response.option to point to the default (first) option optionList.doChange(); //updates response.option to point to the default (first) option
fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); }).onChange([&] { image iconSearch{Icon::Action::Search};
iconSearch.scale(16_sx, 16_sy);
searchButton.setIcon(iconSearch).setBordered(false).onActivate([&] { setPath(state.path, fileName.text()); });
fileName.onActivate([&] {
if(state.action == "saveFile") return accept();
setPath(state.path, fileName.text());
}).onChange([&] {
auto name = fileName.text(); auto name = fileName.text();
acceptButton.setEnabled(name && !isFolder(name)); if(state.action == "saveFile") acceptButton.setEnabled(name && !isFolder(name));
fileName.setBackgroundColor(acceptButton.enabled() ? Color{} : Color{255, 224, 224});
}); });
acceptButton.onActivate([&] { accept(); }); acceptButton.setEnabled(false).onActivate([&] { accept(); });
if(state.action.beginsWith("open")) acceptButton.setText(tr("Open")); if(state.action.beginsWith("open")) acceptButton.setText(tr("Open"));
if(state.action.beginsWith("save")) acceptButton.setText(tr("Save")); if(state.action.beginsWith("save")) acceptButton.setText(tr("Save"));
if(state.action.beginsWith("select")) acceptButton.setText(tr("Select")); if(state.action.beginsWith("select")) acceptButton.setText(tr("Select"));
@ -326,7 +332,7 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
window.setAlignment(state.relativeTo, state.alignment); window.setAlignment(state.relativeTo, state.alignment);
window.setDismissable(); window.setDismissable();
window.setVisible(); window.setVisible();
view.setFocused(); fileName.setFocused();
Application::processEvents(); Application::processEvents();
view->resizeColumns(); view->resizeColumns();
window.setModal(); window.setModal();
@ -335,7 +341,7 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
return response; return response;
} }
auto BrowserDialogWindow::setPath(string path) -> void { auto BrowserDialogWindow::setPath(string path, const string& contains) -> void {
path.transform("\\", "/"); path.transform("\\", "/");
if((path || Path::root() == "/") && !path.endsWith("/")) path.append("/"); if((path || Path::root() == "/") && !path.endsWith("/")) path.append("/");
pathName.setText(state.path = path); pathName.setText(state.path = path);
@ -365,13 +371,14 @@ auto BrowserDialogWindow::setPath(string path) -> void {
if(state.action == "openFolder") continue; if(state.action == "openFolder") continue;
} }
if(!isMatch(content)) continue; if(!isMatch(content)) continue;
if(contains && !content.ifind(contains)) continue;
if(!showHiddenOption.checked() && file::hidden({state.path, content})) continue; if(!showHiddenOption.checked() && file::hidden({state.path, content})) continue;
view.append(ListViewItem().setText(content).setIcon(isFolder ? (image)Icon::Action::Open : (image)Icon::Emblem::File)); view.append(ListViewItem().setText(content).setIcon(isFolder ? (image)Icon::Action::Open : (image)Icon::Emblem::File));
} }
Application::processEvents(); Application::processEvents();
view->resizeColumns(); //todo: on Windows, adding items may add vertical scrollbar; this hack corrects column width view->resizeColumns(); //todo: on Windows, adding items may add vertical scrollbar; this hack corrects column width
view.setFocused().doChange(); view.doChange();
} }
// //

View File

@ -1,7 +1,7 @@
#if defined(Hiro_ListView) #if defined(Hiro_ListView)
mListView::mListView() { mListView::mListView() {
mTableView::onActivate([&] { doActivate(); }); mTableView::onActivate([&](auto) { doActivate(); });
mTableView::onChange([&] { doChange(); }); mTableView::onChange([&] { doChange(); });
mTableView::onContext([&] { doContext(); }); mTableView::onContext([&] { doContext(); });
mTableView::onToggle([&](TableViewCell cell) { mTableView::onToggle([&](TableViewCell cell) {

View File

@ -2,7 +2,7 @@
namespace hiro { namespace hiro {
static auto TableView_activate(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, pTableView* p) -> void { return p->_doActivate(); } static auto TableView_activate(GtkTreeView*, GtkTreePath* gtkRow, GtkTreeViewColumn* gtkColumn, pTableView* p) -> void { return p->_doActivate(gtkRow, gtkColumn); }
static auto TableView_buttonEvent(GtkTreeView* treeView, GdkEventButton* event, pTableView* p) -> signed { return p->_doEvent(event); } static auto TableView_buttonEvent(GtkTreeView* treeView, GdkEventButton* event, pTableView* p) -> signed { return p->_doEvent(event); }
static auto TableView_change(GtkTreeSelection*, pTableView* p) -> void { return p->_doChange(); } static auto TableView_change(GtkTreeSelection*, pTableView* p) -> void { return p->_doChange(); }
static auto TableView_edit(GtkCellRendererText* renderer, const char* path, const char* text, pTableView* p) -> void { return p->_doEdit(renderer, path, text); } static auto TableView_edit(GtkCellRendererText* renderer, const char* path, const char* text, pTableView* p) -> void { return p->_doEdit(renderer, path, text); }
@ -238,8 +238,26 @@ auto pTableView::_createModel() -> void {
gtk_tree_view_set_model(gtkTreeView, gtkTreeModel); gtk_tree_view_set_model(gtkTreeView, gtkTreeModel);
} }
auto pTableView::_doActivate() -> void { auto pTableView::_doActivate(GtkTreePath* gtkRow, GtkTreeViewColumn* gtkColumn) -> void {
if(!locked()) self().doActivate(); if(locked()) return;
if(gtkRow && gtkColumn) {
auto path = gtk_tree_path_to_string(gtkRow);
auto item = self().item(toNatural(path));
auto cell = item.cell(0);
for(auto& column : state().columns) {
if(auto self = column->self()) {
if(self->gtkColumn == gtkColumn) {
cell = item.cell(column->offset());
break;
}
}
}
g_free(path);
self().doActivate(cell);
} else {
self().doActivate({});
}
} }
auto pTableView::_doChange() -> void { auto pTableView::_doChange() -> void {

View File

@ -25,7 +25,7 @@ struct pTableView : pWidget {
auto _cellWidth(uint row, uint column) -> uint; auto _cellWidth(uint row, uint column) -> uint;
auto _columnWidth(uint column) -> uint; auto _columnWidth(uint column) -> uint;
auto _createModel() -> void; auto _createModel() -> void;
auto _doActivate() -> void; auto _doActivate(GtkTreePath* = nullptr, GtkTreeViewColumn* = nullptr) -> void;
auto _doChange() -> void; auto _doChange() -> void;
auto _doContext() -> void; auto _doContext() -> void;
auto _doDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeIter* iter) -> void; auto _doDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeIter* iter) -> void;

View File

@ -252,7 +252,7 @@ public:
auto showEvent(QShowEvent*) -> void override; auto showEvent(QShowEvent*) -> void override;
pTableView& p; pTableView& p;
public slots: public slots:
void onActivate(); void onActivate(QTreeWidgetItem* item, int column);
void onChange(); void onChange();
void onContext(); void onContext();
void onSort(int column); void onSort(int column);

View File

@ -1328,20 +1328,19 @@ static const uint qt_meta_data_hiro__QtTableView[] = {
0, // signalCount 0, // signalCount
// slots: signature, parameters, type, tag, flags // slots: signature, parameters, type, tag, flags
18, 31, 31, 31, 0x0a, 18, 51, 63, 63, 0x0a,
32, 31, 31, 31, 0x0a, 64, 63, 63, 63, 0x0a,
43, 31, 31, 31, 0x0a, 75, 63, 63, 63, 0x0a,
55, 67, 31, 31, 0x0a, 87, 99, 63, 63, 0x0a,
74, 105, 31, 31, 0x0a, 106, 51, 63, 63, 0x0a,
0 // eod 0 // eod
}; };
static const char qt_meta_stringdata_hiro__QtTableView[] = { static const char qt_meta_stringdata_hiro__QtTableView[] = {
"hiro::QtTableView\0onActivate()\0\0" "hiro::QtTableView\0onActivate(QTreeWidgetItem*,int)\0"
"onChange()\0onContext()\0onSort(int)\0" "item,column\0\0onChange()\0onContext()\0"
"column\0onToggle(QTreeWidgetItem*,int)\0" "onSort(int)\0column\0onToggle(QTreeWidgetItem*,int)\0"
"item,column\0"
}; };
void hiro::QtTableView::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) void hiro::QtTableView::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
@ -1350,7 +1349,7 @@ void hiro::QtTableView::qt_static_metacall(QObject *_o, QMetaObject::Call _c, in
Q_ASSERT(staticMetaObject.cast(_o)); Q_ASSERT(staticMetaObject.cast(_o));
QtTableView *_t = static_cast<QtTableView *>(_o); QtTableView *_t = static_cast<QtTableView *>(_o);
switch (_id) { switch (_id) {
case 0: _t->onActivate(); break; case 0: _t->onActivate((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2]))); break;
case 1: _t->onChange(); break; case 1: _t->onChange(); break;
case 2: _t->onContext(); break; case 2: _t->onContext(); break;
case 3: _t->onSort((*reinterpret_cast< int(*)>(_a[1]))); break; case 3: _t->onSort((*reinterpret_cast< int(*)>(_a[1]))); break;

View File

@ -17,7 +17,7 @@ auto pTableView::construct() -> void {
qtTableViewDelegate = new QtTableViewDelegate(*this); qtTableViewDelegate = new QtTableViewDelegate(*this);
qtTableView->setItemDelegate(qtTableViewDelegate); qtTableView->setItemDelegate(qtTableViewDelegate);
qtTableView->connect(qtTableView, SIGNAL(itemActivated(QTreeWidgetItem*, int)), SLOT(onActivate())); qtTableView->connect(qtTableView, SIGNAL(itemActivated(QTreeWidgetItem*, int)), SLOT(onActivate(QTreeWidgetItem*, int)));
qtTableView->connect(qtTableView, SIGNAL(itemSelectionChanged()), SLOT(onChange())); qtTableView->connect(qtTableView, SIGNAL(itemSelectionChanged()), SLOT(onChange()));
qtTableView->connect(qtTableView, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(onContext())); qtTableView->connect(qtTableView, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(onContext()));
qtTableView->connect(qtTableView->header(), SIGNAL(sectionClicked(int)), SLOT(onSort(int))); qtTableView->connect(qtTableView->header(), SIGNAL(sectionClicked(int)), SLOT(onSort(int)));
@ -186,8 +186,20 @@ auto pTableView::_widthOfCell(unsigned _row, unsigned _column) -> unsigned {
return width; return width;
} }
auto QtTableView::onActivate() -> void { auto QtTableView::onActivate(QTreeWidgetItem* qtItem, int column) -> void {
if(!p.locked()) p.self().doActivate(); if(p.locked()) return;
for(auto& item : p.state().items) {
if(auto self = item->self()) {
if(qtItem == self->qtItem) {
if(auto cell = item->cell(column)) {
return p.self().doActivate(cell);
}
}
}
}
p.self().doActivate({});
} }
auto QtTableView::onChange() -> void { auto QtTableView::onChange() -> void {

View File

@ -453,7 +453,7 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
} }
case AppMessage::TableView_onActivate: { case AppMessage::TableView_onActivate: {
if(auto tableView = (mTableView*)lparam) tableView->doActivate(); if(auto tableView = (mTableView*)lparam) tableView->doActivate({});
break; break;
} }

View File

@ -8,8 +8,11 @@ auto locate(string name) -> string {
string location = {Path::program(), name}; string location = {Path::program(), name};
if(inode::exists(location)) return location; if(inode::exists(location)) return location;
directory::create({Path::userData(), "bsnes/"}); location = {Path::userData(), "bsnes/"};
return {Path::userData(), "bsnes/", name}; if(inode::exists(location)) return location;
directory::create({Path::userSettings(), "bsnes/"});
return {Path::userSettings(), "bsnes/", name};
} }
#include "settings.cpp" #include "settings.cpp"

View File

@ -74,7 +74,7 @@ struct VideoXShm : VideoDriver {
auto output(uint width = 0, uint height = 0) -> void override { auto output(uint width = 0, uint height = 0) -> void override {
uint windowWidth, windowHeight; uint windowWidth, windowHeight;
size(windowWidth, windowHeight); size(windowWidth, windowHeight);
if(!_image) return; if(!_image || !_inputBuffer || !_outputBuffer) return;
if(!width) width = _outputWidth; if(!width) width = _outputWidth;
if(!height) height = _outputHeight; if(!height) height = _outputHeight;