diff --git a/bsnes/emulator/audio/audio.cpp b/bsnes/emulator/audio/audio.cpp index 71dc6530..cd30ee8d 100644 --- a/bsnes/emulator/audio/audio.cpp +++ b/bsnes/emulator/audio/audio.cpp @@ -10,45 +10,45 @@ Audio::~Audio() { } auto Audio::reset(Interface* interface) -> void { - this->interface = interface; - streams.reset(); - channels = 0; + _interface = interface; + _streams.reset(); + _channels = 0; } auto Audio::setFrequency(double frequency) -> void { - this->frequency = frequency; - for(auto& stream : streams) { + _frequency = frequency; + for(auto& stream : _streams) { stream->setFrequency(stream->inputFrequency, frequency); } } auto Audio::setVolume(double volume) -> void { - this->volume = volume; + _volume = volume; } auto Audio::setBalance(double balance) -> void { - this->balance = balance; + _balance = balance; } auto Audio::createStream(uint channels, double frequency) -> shared_pointer { - this->channels = max(this->channels, channels); + _channels = max(_channels, channels); shared_pointer stream = new Stream; - stream->reset(channels, frequency, this->frequency); - streams.append(stream); + stream->reset(channels, frequency, _frequency); + _streams.append(stream); return stream; } auto Audio::process() -> void { - while(streams) { - for(auto& stream : streams) { + while(_streams) { + for(auto& stream : _streams) { if(!stream->pending()) return; } - double samples[channels]; + double samples[_channels]; for(auto& sample : samples) sample = 0.0; - for(auto& stream : streams) { - double buffer[channels]; + for(auto& stream : _streams) { + double buffer[_channels]; uint length = stream->read(buffer), offset = 0; for(auto& sample : samples) { @@ -57,16 +57,16 @@ auto Audio::process() -> void { } } - for(auto c : range(channels)) { - samples[c] = max(-1.0, min(+1.0, samples[c] * volume)); + for(auto c : range(_channels)) { + samples[c] = max(-1.0, min(+1.0, samples[c] * _volume)); } - if(channels == 2) { - if(balance < 0.0) samples[1] *= 1.0 + balance; - if(balance > 0.0) samples[0] *= 1.0 - balance; + if(_channels == 2) { + if(_balance < 0.0) samples[1] *= 1.0 + _balance; + if(_balance > 0.0) samples[0] *= 1.0 - _balance; } - platform->audioFrame(samples, channels); + platform->audioFrame(samples, _channels); } } diff --git a/bsnes/emulator/audio/audio.hpp b/bsnes/emulator/audio/audio.hpp index 91786576..258f73a0 100644 --- a/bsnes/emulator/audio/audio.hpp +++ b/bsnes/emulator/audio/audio.hpp @@ -17,6 +17,11 @@ struct Audio { ~Audio(); 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 setVolume(double volume) -> void; auto setBalance(double balance) -> void; @@ -26,14 +31,14 @@ struct Audio { private: auto process() -> void; - Interface* interface = nullptr; - vector> streams; + Interface* _interface = nullptr; + vector> _streams; - uint channels = 0; - double frequency = 48000.0; + uint _channels = 0; + double _frequency = 48000.0; - double volume = 1.0; - double balance = 0.0; + double _volume = 1.0; + double _balance = 0.0; friend class Stream; }; diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index b4de4059..ffea014d 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -31,7 +31,7 @@ using namespace nall; namespace Emulator { static const string Name = "bsnes"; - static const string Version = "108"; + static const string Version = "108.1"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org"; diff --git a/bsnes/sfc/coprocessor/sa1/sa1.cpp b/bsnes/sfc/coprocessor/sa1/sa1.cpp index cfa1e5db..fc46c8d9 100644 --- a/bsnes/sfc/coprocessor/sa1/sa1.cpp +++ b/bsnes/sfc/coprocessor/sa1/sa1.cpp @@ -127,8 +127,10 @@ auto SA1::unload() -> void { } auto SA1::power() -> void { + double overclock = max(1.0, min(4.0, configuration.hacks.sa1.overclock / 100.0)); + WDC65816::power(); - create(SA1::Enter, system.cpuFrequency()); + create(SA1::Enter, system.cpuFrequency() * overclock); bwram.dma = false; for(uint address : range(iram.size())) { diff --git a/bsnes/sfc/coprocessor/superfx/superfx.cpp b/bsnes/sfc/coprocessor/superfx/superfx.cpp index 87690eeb..41f1f55b 100644 --- a/bsnes/sfc/coprocessor/superfx/superfx.cpp +++ b/bsnes/sfc/coprocessor/superfx/superfx.cpp @@ -37,8 +37,10 @@ auto SuperFX::unload() -> void { } auto SuperFX::power() -> void { + double overclock = max(1.0, min(8.0, configuration.hacks.superfx.overclock / 100.0)); + GSU::power(); - create(SuperFX::Enter, Frequency); + create(SuperFX::Enter, Frequency * overclock); romMask = rom.size() - 1; ramMask = ram.size() - 1; diff --git a/bsnes/sfc/cpu/cpu.cpp b/bsnes/sfc/cpu/cpu.cpp index ce450f89..70d8e8f3 100644 --- a/bsnes/sfc/cpu/cpu.cpp +++ b/bsnes/sfc/cpu/cpu.cpp @@ -58,8 +58,8 @@ auto CPU::power(bool reset) -> void { PPUcounter::reset(); PPUcounter::scanline = {&CPU::scanline, this}; - function uint8> reader; - function void> writer; + function reader; + function writer; reader = {&CPU::readRAM, this}; writer = {&CPU::writeRAM, this}; diff --git a/bsnes/sfc/cpu/cpu.hpp b/bsnes/sfc/cpu/cpu.hpp index 57cb62a5..a304fe1b 100644 --- a/bsnes/sfc/cpu/cpu.hpp +++ b/bsnes/sfc/cpu/cpu.hpp @@ -68,6 +68,11 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { uint8 wram[128 * 1024]; vector coprocessors; + struct Overclocking { + uint counter = 0; + uint target = 0; + } overclocking; + private: uint version = 2; //allowed: 1, 2 diff --git a/bsnes/sfc/cpu/timing.cpp b/bsnes/sfc/cpu/timing.cpp index b6763fb2..13852b1d 100644 --- a/bsnes/sfc/cpu/timing.cpp +++ b/bsnes/sfc/cpu/timing.cpp @@ -27,6 +27,12 @@ auto CPU::stepOnce() -> void { template auto CPU::step() -> void { 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 >= 4) stepOnce(); if constexpr(Clocks >= 6) stepOnce(); @@ -88,6 +94,17 @@ auto CPU::scanline() -> void { status.hdmaPosition = 1104; 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 { diff --git a/bsnes/sfc/dsp/dsp.cpp b/bsnes/sfc/dsp/dsp.cpp index 330fb172..349bb117 100644 --- a/bsnes/sfc/dsp/dsp.cpp +++ b/bsnes/sfc/dsp/dsp.cpp @@ -16,9 +16,9 @@ void DSP::main() { clock += 2 * 32; } - signed count = spc_dsp.sample_count(); + int count = spc_dsp.sample_count(); 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); } spc_dsp.set_output(samplebuffer, 8192); diff --git a/bsnes/sfc/dsp/dsp.hpp b/bsnes/sfc/dsp/dsp.hpp index 308a5d16..91937347 100644 --- a/bsnes/sfc/dsp/dsp.hpp +++ b/bsnes/sfc/dsp/dsp.hpp @@ -2,7 +2,7 @@ struct DSP { shared_pointer stream; - uint8 apuram[64 * 1024] = {}; + uint8_t apuram[64 * 1024] = {}; void main(); uint8 read(uint8 addr); diff --git a/bsnes/sfc/interface/configuration.cpp b/bsnes/sfc/interface/configuration.cpp index 6968b1ab..a4141fc1 100644 --- a/bsnes/sfc/interface/configuration.cpp +++ b/bsnes/sfc/interface/configuration.cpp @@ -16,6 +16,7 @@ auto Configuration::process(Markup::Node document, bool load) -> void { bind(boolean, "Video/BlurEmulation", video.blurEmulation); 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/NoSpriteLimit", hacks.ppu.noSpriteLimit); 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/Coprocessors/HLE", hacks.coprocessors.hle); 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 } diff --git a/bsnes/sfc/interface/configuration.hpp b/bsnes/sfc/interface/configuration.hpp index 89c06143..813be3ed 100644 --- a/bsnes/sfc/interface/configuration.hpp +++ b/bsnes/sfc/interface/configuration.hpp @@ -25,6 +25,9 @@ struct Configuration { } video; struct Hacks { + struct CPU { + uint overclock = 100; + } cpu; struct PPU { bool fast = true; bool noSpriteLimit = false; @@ -43,6 +46,12 @@ struct Configuration { bool delayedSync = true; bool hle = true; } coprocessors; + struct SA1 { + uint overclock = 100; + } sa1; + struct SuperFX { + uint overclock = 100; + } superfx; } hacks; private: diff --git a/bsnes/sfc/ppu/ppu.cpp b/bsnes/sfc/ppu/ppu.cpp index cc47b081..67b958ca 100644 --- a/bsnes/sfc/ppu/ppu.cpp +++ b/bsnes/sfc/ppu/ppu.cpp @@ -90,15 +90,11 @@ auto PPU::main() -> void { } auto PPU::load() -> bool { - if(system.fastPPU()) { - return ppufast.load(); - } - ppu1.version = max(1, min(1, configuration.system.ppu1.version)); ppu2.version = max(1, min(3, configuration.system.ppu2.version)); vram.mask = configuration.system.ppu1.vram.size / sizeof(uint16) - 1; if(vram.mask != 0xffff) vram.mask = 0x7fff; - return true; + return true && ppufast.load(); } auto PPU::power(bool reset) -> void { diff --git a/bsnes/sfc/system/system.cpp b/bsnes/sfc/system/system.cpp index a9c39f8b..456e42ec 100644 --- a/bsnes/sfc/system/system.cpp +++ b/bsnes/sfc/system/system.cpp @@ -32,7 +32,6 @@ auto System::runToSave() -> void { auto System::load(Emulator::Interface* interface) -> bool { information = {}; - hacks.fastPPU = configuration.hacks.ppu.fast; bus.reset(); if(!cpu.load()) return false; @@ -92,6 +91,8 @@ auto System::unload() -> void { } auto System::power(bool reset) -> void { + hacks.fastPPU = configuration.hacks.ppu.fast; + Emulator::audio.reset(interface); random.entropy(Random::Entropy::Low); diff --git a/bsnes/target-bsnes/bsnes.cpp b/bsnes/target-bsnes/bsnes.cpp index bd32ef35..08ddd13d 100644 --- a/bsnes/target-bsnes/bsnes.cpp +++ b/bsnes/target-bsnes/bsnes.cpp @@ -9,8 +9,11 @@ auto locate(string name) -> string { string location = {Path::program(), name}; if(inode::exists(location)) return location; - directory::create({Path::userData(), "bsnes/"}); - return {Path::userData(), "bsnes/", name}; + location = {Path::userData(), "bsnes/", name}; + if(inode::exists(location)) return location; + + directory::create({Path::userSettings(), "bsnes/"}); + return {Path::userSettings(), "bsnes/", name}; } #include @@ -19,7 +22,7 @@ auto nall::main(Arguments arguments) -> void { for(auto argument : arguments) { if(argument == "--fullscreen") { - presentation.startFullScreen = true; + program.startFullScreen = true; } else if(argument.beginsWith("--locale=")) { Application::locale().scan(locate("Locale/")); Application::locale().select(argument.trimLeft("--locale=", 1L)); diff --git a/bsnes/target-bsnes/bsnes.hpp b/bsnes/target-bsnes/bsnes.hpp index db60b679..9cbccb1e 100644 --- a/bsnes/target-bsnes/bsnes.hpp +++ b/bsnes/target-bsnes/bsnes.hpp @@ -18,6 +18,7 @@ extern unique_pointer emulator; #include #include #include +#include #include "program/program.hpp" #include "input/input.hpp" diff --git a/bsnes/target-bsnes/input/hotkeys.cpp b/bsnes/target-bsnes/input/hotkeys.cpp index e564b583..9febe88c 100644 --- a/bsnes/target-bsnes/input/hotkeys.cpp +++ b/bsnes/target-bsnes/input/hotkeys.cpp @@ -1,5 +1,15 @@ +auto InputHotkey::logic() const -> Logic { + return inputManager.hotkeyLogic; +} + +// + auto InputManager::bindHotkeys() -> void { 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([] { presentation.toggleFullscreenMode(); @@ -14,15 +24,24 @@ auto InputManager::bindHotkeys() -> void { })); hotkeys.append(InputHotkey("Rewind").onPress([&] { - if(!emulator->loaded()) return; + if(!emulator->loaded() || fastForwarding) return; + rewinding = true; if(program.rewind.frequency == 0) { program.showMessage("Please enable rewind support in Settings->Emulator first"); } else { 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([&] { + rewinding = false; if(!emulator->loaded()) return; program.rewindMode(Program::Rewind::Mode::Playing); + Emulator::audio.setVolume(volume); })); hotkeys.append(InputHotkey("Save State").onPress([&] { @@ -56,15 +75,33 @@ auto InputManager::bindHotkeys() -> void { })); 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); - audio.setBlocking(false); + audio.setBlocking(settings.fastForward.limiter != 0); 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([] { + fastForwarding = false; + if(!emulator->loaded()) return; emulator->setFrameSkip(0); video.setBlocking(settings.video.blocking); audio.setBlocking(settings.audio.blocking); audio.setDynamic(settings.audio.dynamic); + if(settings.fastForward.limiter) { + Emulator::audio.setFrequency(frequency); + } + Emulator::audio.setVolume(volume); })); hotkeys.append(InputHotkey("Pause Emulation").onPress([] { @@ -85,7 +122,8 @@ auto InputManager::bindHotkeys() -> void { for(auto& hotkey : hotkeys) { 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(); } } diff --git a/bsnes/target-bsnes/input/input.cpp b/bsnes/target-bsnes/input/input.cpp index b40134ea..1fa9e78c 100644 --- a/bsnes/target-bsnes/input/input.cpp +++ b/bsnes/target-bsnes/input/input.cpp @@ -3,10 +3,13 @@ InputManager inputManager; auto InputMapping::bind() -> void { - mappings.reset(); + for(auto& binding : bindings) binding = {}; - for(auto& item : assignment.split(logic() == Logic::AND ? "&" : "|")) { - auto token = item.split("/"); + for(uint index : range(BindingLimit)) { + auto& assignment = assignments[index]; + auto& binding = bindings[index]; + + auto token = assignment.split("/"); if(token.size() < 3) continue; //skip invalid mappings uint64 id = token[0].natural(); @@ -14,41 +17,35 @@ auto InputMapping::bind() -> void { uint input = token[2].natural(); string qualifier = token(3, "None"); - Mapping mapping; for(auto& device : inputManager.devices) { if(id != device->id()) continue; - mapping.device = device; - mapping.group = group; - mapping.input = input; - mapping.qualifier = Qualifier::None; - if(qualifier == "Lo") mapping.qualifier = Qualifier::Lo; - if(qualifier == "Hi") mapping.qualifier = Qualifier::Hi; - if(qualifier == "Rumble") mapping.qualifier = Qualifier::Rumble; + binding.device = device; + binding.group = group; + binding.input = input; + binding.qualifier = Qualifier::None; + if(qualifier == "Lo") binding.qualifier = Qualifier::Lo; + if(qualifier == "Hi") binding.qualifier = Qualifier::Hi; + if(qualifier == "Rumble") binding.qualifier = Qualifier::Rumble; 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 { - if(assignment.split(logic() == Logic::AND ? "&" : "|").find(mapping)) return; //ignore if already in mappings list - if(!assignment || assignment == "None") { //create new mapping - assignment = mapping; - } else { //add additional mapping - assignment.append(logic() == Logic::AND ? "&" : "|"); - assignment.append(mapping); - } +auto InputMapping::bind(string mapping, uint binding) -> void { + if(binding >= BindingLimit) return; + assignments[binding] = mapping; bind(); } -auto InputMapping::bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> bool { +auto InputMapping::bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue, uint binding) -> bool { 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}; @@ -58,7 +55,7 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp || (device->isMouse() && group == HID::Mouse::GroupID::Button) || (device->isJoypad() && group == HID::Joypad::GroupID::Button)) { if(newValue) { - return bind(encoding), true; + return bind(encoding, binding), true; } } @@ -66,11 +63,11 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp || (device->isJoypad() && group == HID::Joypad::GroupID::Hat) || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) { 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) { - return bind({encoding, "/Hi"}), true; + return bind({encoding, "/Hi"}, binding), true; } } } @@ -80,7 +77,7 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp || (device->isJoypad() && group == HID::Joypad::GroupID::Axis) || (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) { if(newValue < -16384 || newValue > +16384) { - return bind(encoding), true; + return bind(encoding, binding), true; } } } @@ -88,7 +85,7 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp if(isRumble()) { if(device->isJoypad() && group == HID::Joypad::GroupID::Button) { if(newValue) { - return bind({encoding, "/Rumble"}), true; + return bind({encoding, "/Rumble"}, binding), true; } } } @@ -96,9 +93,13 @@ auto InputMapping::bind(shared_pointer device, uint group, uint inp return false; } -auto InputMapping::unbind() -> void { - mappings.reset(); - settings[path].setValue(assignment = "None"); +auto InputMapping::unbind(uint binding) -> void { + bindings[binding] = {}; + assignments[binding] = {}; + string text; + for(auto& assignment : assignments) text.append(assignment, ";"); + text.trimRight(";"); + settings[path].setValue(text); } auto InputMapping::poll() -> int16 { @@ -108,13 +109,17 @@ auto InputMapping::poll() -> int16 { if(result) return inputManager.turboCounter >= inputManager.turboFrequency; } + uint validBindings = 0; int16 result = 0; - for(auto& mapping : mappings) { - auto& device = mapping.device; - auto& group = mapping.group; - auto& input = mapping.input; - auto& qualifier = mapping.qualifier; + for(auto& binding : bindings) { + if(!binding.device) continue; //unbound + validBindings++; + + auto& device = binding.device; + auto& group = binding.group; + auto& input = binding.input; + auto& qualifier = binding.qualifier; auto value = device->group(group).input(input).value(); 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; } auto InputMapping::rumble(bool enable) -> void { - for(auto& mapping : mappings) { - input.rumble(mapping.device->id(), enable); + for(auto& binding : bindings) { + if(!binding.device) continue; + input.rumble(binding.device->id(), enable); } } -auto InputMapping::displayName() -> string { - if(!mappings) return "None"; +// - string path; - for(auto& mapping : mappings) { - path.append(mapping.device->name()); - if(mapping.device->name() != "Keyboard" && mapping.device->name() != "Mouse") { - //show device IDs to distinguish between multiple joypads - 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 "); +auto InputMapping::Binding::icon() -> image { + if(device && device->isKeyboard()) return Icon::Device::Keyboard; + if(device && device->isMouse()) return Icon::Device::Mouse; + if(device && device->isJoypad()) return Icon::Device::Joypad; + return {}; +} + +auto InputMapping::Binding::name() -> string { + if(device && device->isKeyboard()) { + return device->group(group).input(input).name(); } - - return path.trimRight(logic() == Logic::AND ? " and " : " or ", 1L); + 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.type = input.type; 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); } for(uint inputID : range(inputs.size())) { @@ -226,7 +237,8 @@ auto InputManager::initialize() -> void { inputMapping.name = string{"Turbo ", input.name}; inputMapping.type = input.type; 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[inputID].turboID = turboID; } diff --git a/bsnes/target-bsnes/input/input.hpp b/bsnes/target-bsnes/input/input.hpp index 3271031d..342c0810 100644 --- a/bsnes/target-bsnes/input/input.hpp +++ b/bsnes/target-bsnes/input/input.hpp @@ -1,11 +1,12 @@ +enum : uint { BindingLimit = 4 }; + struct InputMapping { auto bind() -> void; - auto bind(string mapping) -> void; - auto bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> bool; - auto unbind() -> void; + auto bind(string mapping, uint binding) -> void; + auto bind(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue, uint binding) -> bool; + auto unbind(uint binding) -> void; auto poll() -> int16; auto rumble(bool enable) -> void; - auto displayName() -> string; using Type = Emulator::Interface::Input::Type; auto isDigital() const -> bool { @@ -26,31 +27,34 @@ struct InputMapping { string path; //configuration file key path string name; //input name (human readable) uint type = 0; - string assignment = "None"; + string assignments[BindingLimit]; enum class Logic : uint { AND, OR }; enum class Qualifier : uint { None, Lo, Hi, Rumble }; virtual auto logic() const -> Logic { return Logic::OR; } - struct Mapping { + struct Binding { + auto icon() -> image; + auto name() -> string; + shared_pointer device; uint group = 0; uint input = 0; Qualifier qualifier = Qualifier::None; }; - vector mappings; + Binding bindings[BindingLimit]; uint3 turboCounter = 0; }; struct InputHotkey : InputMapping { InputHotkey(string name) { this->name = name; } - auto& onPress(function press) { return this->press = press, *this; } - auto& onRelease(function release) { return this->release = release, *this; } -//auto logic() const -> Logic override { return Logic::AND; } + auto& onPress(function press) { return this->press = press, *this; } + auto& onRelease(function release) { return this->release = release, *this; } + auto logic() const -> Logic override; - function void> press; - function void> release; + function press; + function release; int16 state = 0; }; @@ -67,6 +71,8 @@ struct InputPort { }; struct InputManager { + InputMapping::Logic hotkeyLogic = InputMapping::Logic::OR; + auto initialize() -> void; auto bind() -> void; auto poll() -> void; diff --git a/bsnes/target-bsnes/presentation/presentation.cpp b/bsnes/target-bsnes/presentation/presentation.cpp index efe2f4df..3be8b9a2 100644 --- a/bsnes/target-bsnes/presentation/presentation.cpp +++ b/bsnes/target-bsnes/presentation/presentation.cpp @@ -102,8 +102,9 @@ auto Presentation::create() -> void { inputSettings.setIcon(Icon::Device::Joypad).setText("Input ...").onActivate([&] { settingsWindow.show(2); }); hotkeySettings.setIcon(Icon::Device::Keyboard).setText("Hotkeys ...").onActivate([&] { settingsWindow.show(3); }); pathSettings.setIcon(Icon::Emblem::Folder).setText("Paths ...").onActivate([&] { settingsWindow.show(4); }); - emulatorSettings.setIcon(Icon::Action::Settings).setText("Emulator ...").onActivate([&] { settingsWindow.show(5); }); - driverSettings.setIcon(Icon::Place::Settings).setText("Drivers ...").onActivate([&] { settingsWindow.show(6); }); + speedSettings.setIcon(Icon::Device::Clock).setText("Speed ...").onActivate([&] { settingsWindow.show(5); }); + 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); 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([&] { 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); }); stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow.show(2); }); manifestViewer.setIcon(Icon::Emblem::Text).setText("Manifest Viewer ...").onActivate([&] { toolsWindow.show(3); }); @@ -248,10 +249,6 @@ auto Presentation::create() -> void { resizeWindow(); 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) Application::Cocoa::onAbout([&] { about.doActivate(); }); Application::Cocoa::onActivate([&] { setFocused(); }); diff --git a/bsnes/target-bsnes/presentation/presentation.hpp b/bsnes/target-bsnes/presentation/presentation.hpp index 52025e68..ca041232 100644 --- a/bsnes/target-bsnes/presentation/presentation.hpp +++ b/bsnes/target-bsnes/presentation/presentation.hpp @@ -19,8 +19,6 @@ struct Presentation : Window { auto addRecentGame(string location) -> void; auto updateShaders() -> void; - bool startFullScreen = false; - MenuBar menuBar{this}; Menu systemMenu{&menuBar}; MenuItem loadGame{&systemMenu}; @@ -88,6 +86,7 @@ struct Presentation : Window { MenuItem inputSettings{&settingsMenu}; MenuItem hotkeySettings{&settingsMenu}; MenuItem pathSettings{&settingsMenu}; + MenuItem speedSettings{&settingsMenu}; MenuItem emulatorSettings{&settingsMenu}; MenuItem driverSettings{&settingsMenu}; Menu toolsMenu{&menuBar}; diff --git a/bsnes/target-bsnes/program/game.cpp b/bsnes/target-bsnes/program/game.cpp index a3d86b54..e11709ed 100644 --- a/bsnes/target-bsnes/program/game.cpp +++ b/bsnes/target-bsnes/program/game.cpp @@ -1,10 +1,18 @@ auto Program::load() -> void { unload(); - if(auto configuration = string::read(locate("configuration.bml"))) { - emulator->configure(configuration); - emulatorSettings.updateConfiguration(); - } + emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock); + emulator->configure("Hacks/PPU/Fast", settings.emulator.hack.ppu.fast); + 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; gameQueue = {}; @@ -123,7 +131,6 @@ auto Program::loadSuperFamicom(string location) -> bool { superFamicom.title = heuristics.title(); superFamicom.manifest = manifest ? manifest : heuristics.manifest(); hackPatchMemory(rom); - hackOverclockSuperFX(); superFamicom.document = BML::unserialize(superFamicom.manifest); superFamicom.location = location; @@ -308,9 +315,6 @@ auto Program::unload() -> void { if(emulatorSettings.autoSaveStateOnUnload.checked()) { saveUndoState(); } - if(auto configuration = emulator->configuration()) { - file::write(locate("configuration.bml"), configuration); - } emulator->unload(); showMessage("Game unloaded"); superFamicom = {}; diff --git a/bsnes/target-bsnes/program/hacks.cpp b/bsnes/target-bsnes/program/hacks.cpp index 3e12b249..1c83d352 100644 --- a/bsnes/target-bsnes/program/hacks.cpp +++ b/bsnes/target-bsnes/program/hacks.cpp @@ -34,32 +34,3 @@ auto Program::hackPatchMemory(vector& data) -> void { 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); - } -} diff --git a/bsnes/target-bsnes/program/platform.cpp b/bsnes/target-bsnes/program/platform.cpp index c4503f87..d1e803b4 100644 --- a/bsnes/target-bsnes/program/platform.cpp +++ b/bsnes/target-bsnes/program/platform.cpp @@ -262,7 +262,7 @@ auto Program::audioFrame(const float* samples, uint channels) -> void { auto Program::inputPoll(uint port, uint device, uint input) -> int16 { int16 value = 0; - if(focused() || emulatorSettings.allowInput().checked()) { + if(focused() || inputSettings.allowInput().checked()) { inputManager.poll(); if(auto mapping = inputManager.mapping(port, device, input)) { 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 { - if(focused() || emulatorSettings.allowInput().checked() || !enable) { + if(focused() || inputSettings.allowInput().checked() || !enable) { if(auto mapping = inputManager.mapping(port, device, input)) { return mapping->rumble(enable); } diff --git a/bsnes/target-bsnes/program/program.cpp b/bsnes/target-bsnes/program/program.cpp index c7e3f126..636aa1a4 100644 --- a/bsnes/target-bsnes/program/program.cpp +++ b/bsnes/target-bsnes/program/program.cpp @@ -30,6 +30,7 @@ auto Program::create() -> void { inputSettings.create(); hotkeySettings.create(); pathSettings.create(); + speedSettings.create(); emulatorSettings.create(); driverSettings.create(); @@ -65,9 +66,7 @@ auto Program::create() -> void { driverSettings.inputDriverChanged(); if(gameQueue) load(); - if(presentation.startFullScreen && emulator->loaded()) { - //remove the earlier fullscreen mode state, so that toggleFullscreenMode will enter fullscreen exclusive mode - presentation.setFullScreen(false); + if(startFullScreen && emulator->loaded()) { presentation.toggleFullscreenMode(); } Application::onMain({&Program::main, this}); diff --git a/bsnes/target-bsnes/program/program.hpp b/bsnes/target-bsnes/program/program.hpp index 9f594f9f..7fcc8299 100644 --- a/bsnes/target-bsnes/program/program.hpp +++ b/bsnes/target-bsnes/program/program.hpp @@ -125,7 +125,6 @@ struct Program : Lock, Emulator::Platform { //hacks.cpp auto hackCompatibility() -> void; auto hackPatchMemory(vector& data) -> void; - auto hackOverclockSuperFX() -> void; //filter.cpp auto filterSelect(uint& width, uint& height, uint scale) -> Filter::Render; @@ -186,6 +185,8 @@ public: uint64 statusTime; string statusMessage; string statusFrameRate; + + bool startFullScreen = false; }; extern Program program; diff --git a/bsnes/target-bsnes/program/rewind.cpp b/bsnes/target-bsnes/program/rewind.cpp index a1e55eaa..36dd6902 100644 --- a/bsnes/target-bsnes/program/rewind.cpp +++ b/bsnes/target-bsnes/program/rewind.cpp @@ -6,8 +6,8 @@ auto Program::rewindMode(Rewind::Mode mode) -> void { auto Program::rewindReset() -> void { rewindMode(Rewind::Mode::Playing); rewind.history.reset(); - rewind.frequency = settings.emulator.rewind.frequency; - rewind.length = settings.emulator.rewind.length; + rewind.frequency = settings.rewind.frequency; + rewind.length = settings.rewind.length; } auto Program::rewindRun() -> void { diff --git a/bsnes/target-bsnes/program/utility.cpp b/bsnes/target-bsnes/program/utility.cpp index 6ec587bf..d8b645e1 100644 --- a/bsnes/target-bsnes/program/utility.cpp +++ b/bsnes/target-bsnes/program/utility.cpp @@ -21,7 +21,7 @@ auto Program::updateStatus() -> void { frameRate = tr("Unloaded"); } else if(presentation.pauseEmulation.checked()) { frameRate = tr("Paused"); - } else if(!focused() && emulatorSettings.pauseEmulation.checked()) { + } else if(!focused() && inputSettings.pauseEmulation.checked()) { frameRate = tr("Paused"); } else { frameRate = statusFrameRate; @@ -66,7 +66,7 @@ auto Program::inactive() -> bool { if(locked()) return true; if(!emulator->loaded()) return true; if(presentation.pauseEmulation.checked()) return true; - if(!focused() && emulatorSettings.pauseEmulation.checked()) return true; + if(!focused() && inputSettings.pauseEmulation.checked()) return true; return false; } diff --git a/bsnes/target-bsnes/settings/audio.cpp b/bsnes/target-bsnes/settings/audio.cpp index 932ca09c..18364a9f 100644 --- a/bsnes/target-bsnes/settings/audio.cpp +++ b/bsnes/target-bsnes/settings/audio.cpp @@ -1,8 +1,6 @@ auto AudioSettings::create() -> void { - setIcon(Icon::Device::Speaker); - setText("Audio"); - - layout.setPadding(5_sx); + setCollapsible(); + setVisible(false); effectsLabel.setFont(Font().setBold()).setText("Effects"); effectsLayout.setSize({3, 3}); diff --git a/bsnes/target-bsnes/settings/drivers.cpp b/bsnes/target-bsnes/settings/drivers.cpp index 2b11e51e..1f278621 100644 --- a/bsnes/target-bsnes/settings/drivers.cpp +++ b/bsnes/target-bsnes/settings/drivers.cpp @@ -1,8 +1,6 @@ auto DriverSettings::create() -> void { - setIcon(Icon::Place::Settings); - setText("Drivers"); - - layout.setPadding(5_sx); + setCollapsible(); + setVisible(false); videoLabel.setText("Video").setFont(Font().setBold()); videoLayout.setSize({2, 2}); @@ -113,7 +111,7 @@ auto DriverSettings::videoDriverChanged() -> void { videoFormatChanged(); videoBlockingToggle.setChecked(video.blocking()).setEnabled(video.hasBlocking()); videoFlushToggle.setChecked(video.flush()).setEnabled(video.hasFlush()); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); } auto DriverSettings::videoDriverChange() -> void { @@ -143,7 +141,7 @@ auto DriverSettings::videoFormatChanged() -> void { if(format == video.format()) item.setSelected(); } //videoFormatOption.setEnabled(video.hasFormat()); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); videoFormatChange(); } @@ -170,7 +168,7 @@ auto DriverSettings::audioDriverChanged() -> void { audioExclusiveToggle.setChecked(audio.exclusive()).setEnabled(audio.hasExclusive()); audioBlockingToggle.setChecked(audio.blocking()).setEnabled(audio.hasBlocking()); audioDynamicToggle.setChecked(audio.dynamic()).setEnabled(audio.hasDynamic()); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); } auto DriverSettings::audioDriverChange() -> void { @@ -200,7 +198,7 @@ auto DriverSettings::audioDeviceChanged() -> void { if(device == audio.device()) item.setSelected(); } //audioDeviceOption.setEnabled(audio->hasDevice()); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); } auto DriverSettings::audioDeviceChange() -> void { @@ -219,7 +217,7 @@ auto DriverSettings::audioFrequencyChanged() -> void { if(frequency == audio.frequency()) item.setSelected(); } //audioFrequencyOption.setEnabled(audio->hasFrequency()); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); } auto DriverSettings::audioFrequencyChange() -> void { @@ -236,7 +234,7 @@ auto DriverSettings::audioLatencyChanged() -> void { if(latency == audio.latency()) item.setSelected(); } //audioLatencyOption.setEnabled(audio->hasLatency()); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); } auto DriverSettings::audioLatencyChange() -> void { @@ -256,7 +254,7 @@ auto DriverSettings::inputDriverChanged() -> void { } inputDriverActive.setText({"Active driver: ", input.driver()}); inputDriverOption.doChange(); - layout.setGeometry(layout.geometry()); + setGeometry(geometry()); } auto DriverSettings::inputDriverChange() -> void { diff --git a/bsnes/target-bsnes/settings/emulator.cpp b/bsnes/target-bsnes/settings/emulator.cpp index bd435fb2..9fc44a01 100644 --- a/bsnes/target-bsnes/settings/emulator.cpp +++ b/bsnes/target-bsnes/settings/emulator.cpp @@ -1,23 +1,8 @@ auto EmulatorSettings::create() -> void { - setIcon(Icon::Action::Settings); - setText("Emulator"); - - layout.setPadding(5_sx); + setCollapsible(); + setVisible(false); 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([&] { settings.emulator.warnOnUnverifiedGames = warnOnUnverifiedGames.checked(); }); @@ -35,42 +20,6 @@ auto EmulatorSettings::create() -> void { autoLoadStateOnLoad.setText("Auto-resume on load").setChecked(settings.emulator.autoLoadStateOnLoad).onToggle([&] { 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}); 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([&] { 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."); } - -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()); -} diff --git a/bsnes/target-bsnes/settings/hotkeys.cpp b/bsnes/target-bsnes/settings/hotkeys.cpp index daabc4d7..5b763be0 100644 --- a/bsnes/target-bsnes/settings/hotkeys.cpp +++ b/bsnes/target-bsnes/settings/hotkeys.cpp @@ -1,13 +1,10 @@ auto HotkeySettings::create() -> void { - setIcon(Icon::Device::Keyboard); - setText("Hotkeys"); + setCollapsible(); + setVisible(false); - layout.setPadding(5_sx); mappingList.setBatchable(); mappingList.setHeadered(); - mappingList.onActivate([&] { - if(assignButton.enabled()) assignButton.doActivate(); - }); + mappingList.onActivate([&](auto cell) { assignMapping(cell); }); mappingList.onChange([&] { auto batched = mappingList.batched(); assignButton.setEnabled(batched.size() == 1); @@ -16,12 +13,33 @@ auto HotkeySettings::create() -> void { mappingList.onSize([&] { 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([&] { - assignMapping(); + clearButton.doActivate(); + assignMapping(mappingList.selected().cell(0)); }); clearButton.setText("Clear").onActivate([&] { - for(auto item : mappingList.batched()) { - inputManager.hotkeys[item.offset()].unbind(); + cancelMapping(); + for(auto mapping : mappingList.batched()) { + auto& hotkey = inputManager.hotkeys[mapping.offset()]; + for(uint index : range(BindingLimit)) { + hotkey.unbind(index); + } } refreshMappings(); }); @@ -30,49 +48,60 @@ auto HotkeySettings::create() -> void { auto HotkeySettings::reloadMappings() -> void { mappingList.reset(); 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) { - mappingList.append(TableViewItem() - .append(TableViewCell().setText(hotkey.name).setFont(Font().setBold())) - .append(TableViewCell()) - ); + TableViewItem item{&mappingList}; + item.append(TableViewCell().setText(hotkey.name).setFont(Font().setBold())); + for(uint index : range(BindingLimit)) item.append(TableViewCell()); } refreshMappings(); mappingList.doChange(); } auto HotkeySettings::refreshMappings() -> void { - uint index = 0; + uint item = 0; 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(); } -auto HotkeySettings::assignMapping() -> void { +auto HotkeySettings::assignMapping(TableViewCell cell) -> void { inputManager.poll(); //clear any pending events first - if(auto item = mappingList.selected()) { - activeMapping = inputManager.hotkeys[item.offset()]; - settingsWindow.layout.setEnabled(false); - settingsWindow.statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."}); + for(auto mapping : mappingList.batched()) { + activeMapping = inputManager.hotkeys[mapping.offset()]; + activeBinding = max(0, (int)cell.offset() - 1); + 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); + inputSink.setFocused(); + return; //map only one input at a time } } auto HotkeySettings::cancelMapping() -> void { activeMapping.reset(); settingsWindow.statusBar.setText(); - settingsWindow.layout.setEnabled(); settingsWindow.doSize(); settingsWindow.setDismissable(true); + mappingList.setFocused(); } auto HotkeySettings::inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> void { if(!activeMapping) return; if(device->isMouse()) return; - if(activeMapping->bind(device, group, input, oldValue, newValue)) { + if(activeMapping->bind(device, group, input, oldValue, newValue, activeBinding)) { activeMapping.reset(); settingsWindow.statusBar.setText("Mapping assigned."); refreshMappings(); diff --git a/bsnes/target-bsnes/settings/input.cpp b/bsnes/target-bsnes/settings/input.cpp index 2611c509..211940b6 100644 --- a/bsnes/target-bsnes/settings/input.cpp +++ b/bsnes/target-bsnes/settings/input.cpp @@ -1,8 +1,22 @@ auto InputSettings::create() -> void { - setIcon(Icon::Device::Joypad); - setText("Input"); + setCollapsible(); + 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:"); portList.onChange([&] { reloadDevices(); }); deviceLabel.setText("Device:"); @@ -23,18 +37,23 @@ auto InputSettings::create() -> void { }); mappingList.setBatchable(); mappingList.setHeadered(); - mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); }); + mappingList.onActivate([&](auto cell) { assignMapping(cell); }); mappingList.onChange([&] { updateControls(); }); mappingList.onSize([&] { mappingList.resizeColumns(); }); + inputSink.setFocusable(); assignMouse1.onActivate([&] { assignMouseInput(0); }); assignMouse2.onActivate([&] { assignMouseInput(1); }); assignMouse3.onActivate([&] { assignMouseInput(2); }); assignButton.setText("Assign").onActivate([&] { - assignMapping(); + clearButton.doActivate(); + assignMapping(mappingList.selected().cell(0)); }); clearButton.setText("Clear").onActivate([&] { + cancelMapping(); for(auto mapping : mappingList.batched()) { - activeDevice().mappings[mapping.offset()].unbind(); + for(uint index : range(BindingLimit)) { + activeDevice().mappings[mapping.offset()].unbind(index); + } } refreshMappings(); }); @@ -48,7 +67,7 @@ auto InputSettings::updateControls() -> void { assignMouse2.setVisible(false); assignMouse3.setVisible(false); - if(batched.size() == 1) { + if(activeMapping) { auto& input = activeDevice().mappings[batched.left().offset()]; if(input.isDigital()) { assignMouse1.setVisible().setText("Mouse Left"); @@ -59,6 +78,8 @@ auto InputSettings::updateControls() -> void { assignMouse2.setVisible().setText("Mouse Y-axis"); } } + + controlLayout.resize(); } auto InputSettings::activePort() -> InputPort& { @@ -94,54 +115,65 @@ auto InputSettings::reloadDevices() -> void { auto InputSettings::reloadMappings() -> void { mappingList.reset(); 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) { - mappingList.append(TableViewItem() - .append(TableViewCell().setText(mapping.name).setFont(Font().setBold())) - .append(TableViewCell()) - ); + TableViewItem item{&mappingList}; + item.append(TableViewCell().setText(mapping.name).setFont(Font().setBold())); + for(uint n : range(BindingLimit)) item.append(TableViewCell()); } refreshMappings(); updateControls(); } auto InputSettings::refreshMappings() -> void { - uint index = 0; + uint item = 0; 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(); mappingList.resizeColumns(); } -auto InputSettings::assignMapping() -> void { +auto InputSettings::assignMapping(TableViewCell cell) -> void { inputManager.poll(); //clear any pending events first for(auto mapping : mappingList.batched()) { activeMapping = activeDevice().mappings[mapping.offset()]; - settingsWindow.layout.setEnabled(false); - settingsWindow.statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."}); + activeBinding = max(0, (int)cell.offset() - 1); + 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); + updateControls(); + Application::processEvents(); + inputSink.setFocused(); + return; //map only one input at a time } } auto InputSettings::cancelMapping() -> void { activeMapping.reset(); settingsWindow.statusBar.setText(); - settingsWindow.layout.setEnabled(); settingsWindow.doSize(); settingsWindow.setDismissable(true); + mappingList.setFocused(); + updateControls(); } auto InputSettings::assignMouseInput(uint id) -> void { - if(auto mapping = mappingList.selected()) { - activeMapping = activeDevice().mappings[mapping.offset()]; - if(auto mouse = inputManager.findMouse()) { - if(activeMapping->isDigital()) { - return inputEvent(mouse, HID::Mouse::GroupID::Button, id, 0, 1, true); - } else if(activeMapping->isAnalog()) { - return inputEvent(mouse, HID::Mouse::GroupID::Axis, id, 0, +32767, true); - } + if(!activeMapping) return; + if(auto mouse = inputManager.findMouse()) { + if(activeMapping->isDigital()) { + return inputEvent(mouse, HID::Mouse::GroupID::Button, id, 0, 1, true); + } else if(activeMapping->isAnalog()) { + return inputEvent(mouse, HID::Mouse::GroupID::Axis, id, 0, +32767, true); } } } @@ -150,7 +182,7 @@ auto InputSettings::inputEvent(shared_pointer device, uint group, u if(!activeMapping) 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(); settingsWindow.statusBar.setText("Mapping assigned."); refreshMappings(); diff --git a/bsnes/target-bsnes/settings/paths.cpp b/bsnes/target-bsnes/settings/paths.cpp index 661389e3..c42a1918 100644 --- a/bsnes/target-bsnes/settings/paths.cpp +++ b/bsnes/target-bsnes/settings/paths.cpp @@ -1,15 +1,14 @@ auto PathSettings::create() -> void { - setIcon(Icon::Emblem::Folder); - setText("Paths"); + setCollapsible(); + setVisible(false); - layout.setPadding(5_sx); layout.setSize({4, 6}); layout.column(0).setAlignment(1.0); gamesLabel.setText("Games:"); gamesPath.setEditable(false); gamesAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { + if(auto location = BrowserDialog().selectFolder()) { settings.path.games = location; refreshPaths(); } @@ -22,7 +21,7 @@ auto PathSettings::create() -> void { patchesLabel.setText("Patches:"); patchesPath.setEditable(false); patchesAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { + if(auto location = BrowserDialog().selectFolder()) { settings.path.patches = location; refreshPaths(); } @@ -35,7 +34,7 @@ auto PathSettings::create() -> void { savesLabel.setText("Saves:"); savesPath.setEditable(false); savesAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { + if(auto location = BrowserDialog().selectFolder()) { settings.path.saves = location; refreshPaths(); } @@ -48,7 +47,7 @@ auto PathSettings::create() -> void { cheatsLabel.setText("Cheats:"); cheatsPath.setEditable(false); cheatsAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { + if(auto location = BrowserDialog().selectFolder()) { settings.path.cheats = location; refreshPaths(); } @@ -61,7 +60,7 @@ auto PathSettings::create() -> void { statesLabel.setText("States:"); statesPath.setEditable(false); statesAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { + if(auto location = BrowserDialog().selectFolder()) { settings.path.states = location; refreshPaths(); } @@ -74,7 +73,7 @@ auto PathSettings::create() -> void { screenshotsLabel.setText("Screenshots:"); screenshotsPath.setEditable(false); screenshotsAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setAlignment(*settingsWindow).selectFolder()) { + if(auto location = BrowserDialog().selectFolder()) { settings.path.screenshots = location; refreshPaths(); } diff --git a/bsnes/target-bsnes/settings/settings.cpp b/bsnes/target-bsnes/settings/settings.cpp index 160c4382..9204888b 100644 --- a/bsnes/target-bsnes/settings/settings.cpp +++ b/bsnes/target-bsnes/settings/settings.cpp @@ -4,6 +4,7 @@ #include "input.cpp" #include "hotkeys.cpp" #include "paths.cpp" +#include "speed.cpp" #include "emulator.cpp" #include "drivers.cpp" Settings settings; @@ -12,6 +13,7 @@ AudioSettings audioSettings; InputSettings inputSettings; HotkeySettings hotkeySettings; PathSettings pathSettings; +SpeedSettings speedSettings; EmulatorSettings emulatorSettings; DriverSettings driverSettings; namespace Instances { Instance settingsWindow; } @@ -31,9 +33,9 @@ auto Settings::save() -> void { auto Settings::process(bool load) -> void { if(load) { //initialize non-static default settings - video.driver = ruby::Video::safestDriver(); - audio.driver = ruby::Audio::safestDriver(); - input.driver = ruby::Input::safestDriver(); + video.driver = ruby::Video::optimalDriver(); + audio.driver = ruby::Audio::optimalDriver(); + input.driver = ruby::Input::optimalDriver(); } #define bind(type, path, name) \ @@ -49,11 +51,10 @@ auto Settings::process(bool load) -> void { bind(text, "Video/Format", video.format); bind(text, "Video/Shader", video.shader); - bind(natural, "Video/Luminance", video.luminance); - bind(natural, "Video/Saturation", video.saturation); - bind(natural, "Video/Gamma", video.gamma); - bind(boolean, "Video/FastForwardFrameSkip", video.fastForwardFrameSkip); - bind(boolean, "Video/Snow", video.snow); + bind(natural, "Video/Luminance", video.luminance); + bind(natural, "Video/Saturation", video.saturation); + bind(natural, "Video/Gamma", video.gamma); + bind(boolean, "Video/Snow", video.snow); bind(text, "Video/Output", video.output); bind(natural, "Video/Multiplier", video.multiplier); @@ -79,6 +80,7 @@ auto Settings::process(bool load) -> void { bind(natural, "Input/Frequency", input.frequency); bind(text, "Input/Defocus", input.defocus); 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/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/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/AutoSaveMemory/Enable", emulator.autoSaveMemory.enable); bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval); bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload); bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad); - bind(natural, "Emulator/Rewind/Frequency", emulator.rewind.frequency); - bind(natural, "Emulator/Rewind/Length", emulator.rewind.length); + bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock); bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast); bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit); 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/Coprocessors/DelayedSync", emulator.hack.coprocessors.delayedSync); 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, "General/StatusBar", general.statusBar); @@ -121,19 +131,61 @@ auto Settings::process(bool load) -> void { #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 { layout.setPadding(5_sx); - panel.append(videoSettings); - panel.append(audioSettings); - panel.append(inputSettings); - panel.append(hotkeySettings); - panel.append(pathSettings); - panel.append(emulatorSettings); - panel.append(driverSettings); + panelList.append(ListViewItem().setText("Video").setIcon(Icon::Device::Display)); + panelList.append(ListViewItem().setText("Audio").setIcon(Icon::Device::Speaker)); + panelList.append(ListViewItem().setText("Input").setIcon(Icon::Device::Joypad)); + panelList.append(ListViewItem().setText("Hotkeys").setIcon(Icon::Device::Keyboard)); + panelList.append(ListViewItem().setText("Paths").setIcon(Icon::Emblem::Folder)); + panelList.append(ListViewItem().setText("Speed").setIcon(Icon::Device::Clock)); + 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()); setTitle("Settings"); - setSize({600_sx, 400_sx}); + setSize({680_sx, 400_sx}); setAlignment({0.0, 1.0}); setDismissable(); @@ -153,8 +205,27 @@ auto SettingsWindow::setVisible(bool visible) -> SettingsWindow& { return Window::setVisible(visible), *this; } -auto SettingsWindow::show(uint index) -> void { - panel.item(index).setSelected(); +auto SettingsWindow::show(int index) -> void { + 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(); setFocused(); } diff --git a/bsnes/target-bsnes/settings/settings.hpp b/bsnes/target-bsnes/settings/settings.hpp index 03b74267..0e0570da 100644 --- a/bsnes/target-bsnes/settings/settings.hpp +++ b/bsnes/target-bsnes/settings/settings.hpp @@ -17,7 +17,6 @@ struct Settings : Markup::Node { uint luminance = 100; uint saturation = 100; uint gamma = 150; - bool fastForwardFrameSkip = true; bool snow = false; string output = "Scale"; @@ -50,6 +49,9 @@ struct Settings : Markup::Node { struct Turbo { uint frequency = 4; } turbo; + struct Hotkey { + string logic = "or"; + } hotkey; } input; struct Path { @@ -68,6 +70,18 @@ struct Settings : Markup::Node { } recent; } 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 { bool warnOnUnverifiedGames = false; struct AutoSaveMemory { @@ -76,11 +90,10 @@ struct Settings : Markup::Node { } autoSaveMemory; bool autoSaveStateOnUnload = false; bool autoLoadStateOnLoad = false; - struct Rewind { - uint frequency = 0; - uint length = 80; - } rewind; struct Hack { + struct CPU { + uint overclock = 100; + } cpu; struct PPU { bool fast = true; bool noSpriteLimit = false; @@ -99,7 +112,12 @@ struct Settings : Markup::Node { bool delayedSync = true; bool hle = true; } coprocessors; - uint fastSuperFX = 100; + struct SA1 { + uint overclock = 100; + } sa1; + struct SuperFX { + uint overclock = 100; + } superfx; } hack; struct Cheats { bool enable = true; @@ -114,50 +132,47 @@ struct Settings : Markup::Node { } general; }; -struct VideoSettings : TabFrameItem { +struct VideoSettings : VerticalLayout { auto create() -> void; private: - VerticalLayout layout{this}; - Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2}; - TableLayout colorLayout{&layout, Size{~0, 0}}; - Label luminanceLabel{&colorLayout, Size{0, 0}}; - Label luminanceValue{&colorLayout, Size{50_sx, 0}}; - HorizontalSlider luminanceSlider{&colorLayout, Size{~0, 0}}; - // - Label saturationLabel{&colorLayout, Size{0, 0}}; - Label saturationValue{&colorLayout, Size{50_sx, 0}}; - HorizontalSlider saturationSlider{&colorLayout, Size{~0, 0}}; - // - Label gammaLabel{&colorLayout, Size{0, 0}}; - Label gammaValue{&colorLayout, Size{50_sx, 0}}; - HorizontalSlider gammaSlider{&colorLayout, Size{~0, 0}}; - // - CheckLabel fastForwardFrameSkip{&layout, Size{~0, 0}}; - CheckLabel snowOption{&layout, Size{~0, 0}}; + Label colorAdjustmentLabel{this, Size{~0, 0}, 2}; + TableLayout colorLayout{this, Size{~0, 0}}; + Label luminanceLabel{&colorLayout, Size{0, 0}}; + Label luminanceValue{&colorLayout, Size{50_sx, 0}}; + HorizontalSlider luminanceSlider{&colorLayout, Size{~0, 0}}; + // + Label saturationLabel{&colorLayout, Size{0, 0}}; + Label saturationValue{&colorLayout, Size{50_sx, 0}}; + HorizontalSlider saturationSlider{&colorLayout, Size{~0, 0}}; + // + Label gammaLabel{&colorLayout, Size{0, 0}}; + Label gammaValue{&colorLayout, Size{50_sx, 0}}; + HorizontalSlider gammaSlider{&colorLayout, Size{~0, 0}}; + // + CheckLabel snowOption{this, Size{~0, 0}}; }; -struct AudioSettings : TabFrameItem { +struct AudioSettings : VerticalLayout { auto create() -> void; private: - VerticalLayout layout{this}; - Label effectsLabel{&layout, Size{~0, 0}, 2}; - TableLayout effectsLayout{&layout, Size{~0, 0}}; - Label skewLabel{&effectsLayout, Size{0, 0}}; - Label skewValue{&effectsLayout, Size{50_sx, 0}}; - HorizontalSlider skewSlider{&effectsLayout, Size{~0, 0}}; - // - Label volumeLabel{&effectsLayout, Size{0, 0}}; - Label volumeValue{&effectsLayout, Size{50_sx, 0}}; - HorizontalSlider volumeSlider{&effectsLayout, Size{~0, 0}}; - // - Label balanceLabel{&effectsLayout, Size{0, 0}}; - Label balanceValue{&effectsLayout, Size{50_sx, 0}}; - HorizontalSlider balanceSlider{&effectsLayout, Size{~0, 0}}; + Label effectsLabel{this, Size{~0, 0}, 2}; + TableLayout effectsLayout{this, Size{~0, 0}}; + Label skewLabel{&effectsLayout, Size{0, 0}}; + Label skewValue{&effectsLayout, Size{50_sx, 0}}; + HorizontalSlider skewSlider{&effectsLayout, Size{~0, 0}}; + // + Label volumeLabel{&effectsLayout, Size{0, 0}}; + Label volumeValue{&effectsLayout, Size{50_sx, 0}}; + HorizontalSlider volumeSlider{&effectsLayout, Size{~0, 0}}; + // + Label balanceLabel{&effectsLayout, Size{0, 0}}; + Label balanceValue{&effectsLayout, Size{50_sx, 0}}; + HorizontalSlider balanceSlider{&effectsLayout, Size{~0, 0}}; }; -struct InputSettings : TabFrameItem { +struct InputSettings : VerticalLayout { auto create() -> void; auto updateControls() -> void; auto activePort() -> InputPort&; @@ -166,61 +181,72 @@ struct InputSettings : TabFrameItem { auto reloadDevices() -> void; auto reloadMappings() -> void; auto refreshMappings() -> void; - auto assignMapping() -> void; + auto assignMapping(TableViewCell cell) -> void; auto cancelMapping() -> void; auto assignMouseInput(uint id) -> void; auto inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void; maybe activeMapping; + uint activeBinding = 0; -private: +public: Timer timer; - VerticalLayout layout{this}; - HorizontalLayout selectionLayout{&layout, Size{~0, 0}}; - Label portLabel{&selectionLayout, Size{0, 0}}; - ComboButton portList{&selectionLayout, Size{~0, 0}}; - Label deviceLabel{&selectionLayout, Size{0, 0}}; - ComboButton deviceList{&selectionLayout, Size{~0, 0}}; - Label turboLabel{&selectionLayout, Size{0, 0}}; - ComboButton turboList{&selectionLayout, Size{0, 0}}; - TableView mappingList{&layout, Size{~0, ~0}}; - HorizontalLayout controlLayout{&layout, Size{~0, 0}}; - Button assignMouse1{&controlLayout, Size{100_sx, 0}}; - Button assignMouse2{&controlLayout, Size{100_sx, 0}}; - Button assignMouse3{&controlLayout, Size{100_sx, 0}}; - Widget controlSpacer{&controlLayout, Size{~0, 0}}; - Button assignButton{&controlLayout, Size{80_sx, 0}}; - Button clearButton{&controlLayout, Size{80_sx, 0}}; + HorizontalLayout inputFocusLayout{this, 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}}; + ComboButton portList{&selectionLayout, Size{~0, 0}}; + Label deviceLabel{&selectionLayout, Size{0, 0}}; + ComboButton deviceList{&selectionLayout, Size{~0, 0}}; + Label turboLabel{&selectionLayout, Size{0, 0}}; + ComboButton turboList{&selectionLayout, Size{0, 0}}; + TableView mappingList{this, Size{~0, ~0}}; + HorizontalLayout controlLayout{this, Size{~0, 0}}; + Button assignMouse1{&controlLayout, Size{100_sx, 0}}; + Button assignMouse2{&controlLayout, Size{100_sx, 0}}; + Button assignMouse3{&controlLayout, Size{100_sx, 0}}; + Canvas inputSink{&controlLayout, Size{~0, ~0}}; + Button assignButton{&controlLayout, Size{80_sx, 0}}; + Button clearButton{&controlLayout, Size{80_sx, 0}}; }; -struct HotkeySettings : TabFrameItem { +struct HotkeySettings : VerticalLayout { auto create() -> void; auto reloadMappings() -> void; auto refreshMappings() -> void; - auto assignMapping() -> void; + auto assignMapping(TableViewCell cell) -> void; auto cancelMapping() -> void; auto inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> void; maybe activeMapping; + uint activeBinding = 0; private: Timer timer; - VerticalLayout layout{this}; - TableView mappingList{&layout, Size{~0, ~0}}; - HorizontalLayout controlLayout{&layout, Size{~0, 0}}; - Widget controlSpacer{&controlLayout, Size{~0, 0}}; - Button assignButton{&controlLayout, Size{80_sx, 0}}; - Button clearButton{&controlLayout, Size{80_sx, 0}}; + TableView mappingList{this, Size{~0, ~0}}; + HorizontalLayout controlLayout{this, Size{~0, 0}}; + Label logicLabel{&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 clearButton{&controlLayout, Size{80_sx, 0}}; }; -struct PathSettings : TabFrameItem { +struct PathSettings : VerticalLayout { auto create() -> void; auto refreshPaths() -> void; public: - TableLayout layout{this}; + TableLayout layout{this, Size{~0, ~0}}; Label gamesLabel{&layout, Size{0, 0}}; LineEdit gamesPath{&layout, Size{~0, 0}}; Button gamesAssign{&layout, Size{80_sx, 0}}; @@ -252,57 +278,73 @@ public: Button screenshotsReset{&layout, Size{80_sx, 0}}; }; -struct EmulatorSettings : TabFrameItem { +struct SpeedSettings : VerticalLayout { auto create() -> void; - auto updateConfiguration() -> void; public: - VerticalLayout layout{this}; - Label optionsLabel{&layout, Size{~0, 0}, 2}; - HorizontalLayout inputFocusLayout{&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}; - CheckLabel warnOnUnverifiedGames{&layout, Size{~0, 0}}; - CheckLabel autoSaveMemory{&layout, Size{~0, 0}}; - HorizontalLayout autoStateLayout{&layout, Size{~0, 0}}; - CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}}; - CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}}; - HorizontalLayout rewindLayout{&layout, Size{~0, 0}}; - Label rewindFrequencyLabel{&rewindLayout, Size{0, 0}}; - ComboButton rewindFrequencyOption{&rewindLayout, Size{0, 0}}; - Label rewindLengthLabel{&rewindLayout, Size{0, 0}}; - ComboButton rewindLengthOption{&rewindLayout, Size{0, 0}}; - Canvas optionsSpacer{&layout, Size{~0, 1}}; - Label ppuLabel{&layout, Size{~0, 0}, 2}; - HorizontalLayout ppuLayout{&layout, Size{~0, 0}}; - CheckLabel fastPPU{&ppuLayout, Size{0, 0}}; - CheckLabel noSpriteLimit{&ppuLayout, Size{0, 0}}; - Label mode7Label{&layout, Size{~0, 0}, 2}; - HorizontalLayout mode7Layout{&layout, Size{~0, 0}}; - Label mode7ScaleLabel{&mode7Layout, Size{0, 0}}; - ComboButton mode7Scale{&mode7Layout, Size{0, 0}}; - CheckLabel mode7Perspective{&mode7Layout, Size{0, 0}}; - CheckLabel mode7Supersample{&mode7Layout, Size{0, 0}}; - CheckLabel mode7Mosaic{&mode7Layout, Size{0, 0}}; - Label dspLabel{&layout, Size{~0, 0}, 2}; - HorizontalLayout dspLayout{&layout, Size{~0, 0}}; - CheckLabel fastDSP{&dspLayout, Size{0, 0}}; - CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}}; - Label coprocessorLabel{&layout, Size{~0, 0}, 2}; - HorizontalLayout coprocessorsLayout{&layout, Size{~0, 0}}; - CheckLabel coprocessorsDelayedSyncOption{&coprocessorsLayout, Size{0, 0}}; - CheckLabel coprocessorsHLEOption{&coprocessorsLayout, Size{0, 0}}; - HorizontalLayout superFXLayout{&layout, 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}}; + Label overclockingLabel{this, Size{~0, 0}, 2}; + TableLayout overclockingLayout{this, Size{~0, 0}}; + Label cpuLabel{&overclockingLayout, Size{0, 0}}; + Label cpuValue{&overclockingLayout, Size{50_sx, 0}}; + HorizontalSlider cpuClock{&overclockingLayout, Size{~0, 0}}; + // + Label sa1Label{&overclockingLayout, Size{0, 0}}; + Label sa1Value{&overclockingLayout, Size{50_sx, 0}}; + HorizontalSlider sa1Clock{&overclockingLayout, Size{~0, 0}}; + // + Label sfxLabel{&overclockingLayout, Size{0, 0}}; + Label sfxValue{&overclockingLayout, Size{50_sx, 0}}; + HorizontalSlider sfxClock{&overclockingLayout, 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}}; + ComboButton rewindFrequencyOption{&rewindLayout, Size{0, 0}}; + Label rewindLengthLabel{&rewindLayout, Size{0, 0}}; + ComboButton rewindLengthOption{&rewindLayout, Size{0, 0}}; + CheckLabel rewindMute{this, Size{0, 0}}; }; -struct DriverSettings : TabFrameItem { +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 noSpriteLimit{&ppuLayout, Size{0, 0}}; + Label mode7Label{this, Size{~0, 0}, 2}; + HorizontalLayout mode7Layout{this, Size{~0, 0}}; + Label mode7ScaleLabel{&mode7Layout, Size{0, 0}}; + ComboButton mode7Scale{&mode7Layout, Size{0, 0}}; + CheckLabel mode7Perspective{&mode7Layout, Size{0, 0}}; + CheckLabel mode7Supersample{&mode7Layout, Size{0, 0}}; + CheckLabel mode7Mosaic{&mode7Layout, Size{0, 0}}; + Label dspLabel{this, Size{~0, 0}, 2}; + HorizontalLayout dspLayout{this, Size{~0, 0}}; + CheckLabel fastDSP{&dspLayout, Size{0, 0}}; + CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}}; + Label coprocessorLabel{this, Size{~0, 0}, 2}; + HorizontalLayout coprocessorsLayout{this, Size{~0, 0}}; + CheckLabel coprocessorsDelayedSyncOption{&coprocessorsLayout, Size{0, 0}}; + CheckLabel coprocessorsHLEOption{&coprocessorsLayout, Size{0, 0}}; + Label hacksNote{this, Size{~0, 0}}; +}; + +struct DriverSettings : VerticalLayout { auto create() -> void; auto videoDriverChanged() -> void; auto videoDriverChange() -> void; @@ -320,57 +362,58 @@ struct DriverSettings : TabFrameItem { auto inputDriverChange() -> void; public: - VerticalLayout layout{this}; - Label videoLabel{&layout, Size{~0, 0}, 2}; - TableLayout videoLayout{&layout, Size{~0, 0}}; - Label videoDriverLabel{&videoLayout, Size{0, 0}}; - HorizontalLayout videoDriverLayout{&videoLayout, Size{~0, 0}}; - ComboButton videoDriverOption{&videoDriverLayout, Size{0, 0}}; - Button videoDriverUpdate{&videoDriverLayout, Size{0, 0}}; - Label videoDriverActive{&videoDriverLayout, Size{0, 0}}; - Label videoFormatLabel{&videoLayout, Size{0, 0}}; - HorizontalLayout videoPropertyLayout{&videoLayout, Size{~0, 0}}; - ComboButton videoFormatOption{&videoPropertyLayout, Size{0, 0}}; - HorizontalLayout videoToggleLayout{&layout, Size{~0, 0}}; - CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}}; - CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}}; - Canvas videoSpacer{&layout, Size{~0, 1}}; - Label audioLabel{&layout, Size{~0, 0}, 2}; - TableLayout audioLayout{&layout, Size{~0, 0}}; - Label audioDriverLabel{&audioLayout, Size{0, 0}}; - HorizontalLayout audioDriverLayout{&audioLayout, Size{~0, 0}}; - ComboButton audioDriverOption{&audioDriverLayout, Size{0, 0}}; - Button audioDriverUpdate{&audioDriverLayout, Size{0, 0}}; - Label audioDriverActive{&audioDriverLayout, Size{0, 0}}; - Label audioDeviceLabel{&audioLayout, Size{0, 0}}; - HorizontalLayout audioPropertyLayout{&audioLayout, Size{~0, 0}}; - ComboButton audioDeviceOption{&audioPropertyLayout, Size{0, 0}}; - Label audioFrequencyLabel{&audioPropertyLayout, Size{0, 0}}; - ComboButton audioFrequencyOption{&audioPropertyLayout, Size{0, 0}}; - Label audioLatencyLabel{&audioPropertyLayout, Size{0, 0}}; - ComboButton audioLatencyOption{&audioPropertyLayout, Size{0, 0}}; - HorizontalLayout audioToggleLayout{&layout, Size{~0, 0}}; - CheckLabel audioExclusiveToggle{&audioToggleLayout, Size{0, 0}}; - CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}}; - CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}}; - Canvas audioSpacer{&layout, Size{~0, 1}}; - Label inputLabel{&layout, Size{~0, 0}, 2}; - TableLayout inputLayout{&layout, Size{~0, 0}}; - Label inputDriverLabel{&inputLayout, Size{0, 0}}; - HorizontalLayout inputDriverLayout{&inputLayout, Size{~0, 0}}; - ComboButton inputDriverOption{&inputDriverLayout, Size{0, 0}}; - Button inputDriverUpdate{&inputDriverLayout, Size{0, 0}}; - Label inputDriverActive{&inputDriverLayout, Size{0, 0}}; + Label videoLabel{this, Size{~0, 0}, 2}; + TableLayout videoLayout{this, Size{~0, 0}}; + Label videoDriverLabel{&videoLayout, Size{0, 0}}; + HorizontalLayout videoDriverLayout{&videoLayout, Size{~0, 0}}; + ComboButton videoDriverOption{&videoDriverLayout, Size{0, 0}}; + Button videoDriverUpdate{&videoDriverLayout, Size{0, 0}}; + Label videoDriverActive{&videoDriverLayout, Size{0, 0}}; + Label videoFormatLabel{&videoLayout, Size{0, 0}}; + HorizontalLayout videoPropertyLayout{&videoLayout, Size{~0, 0}}; + ComboButton videoFormatOption{&videoPropertyLayout, Size{0, 0}}; + HorizontalLayout videoToggleLayout{this, Size{~0, 0}}; + CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}}; + CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}}; + Canvas videoSpacer{this, Size{~0, 1}}; + Label audioLabel{this, Size{~0, 0}, 2}; + TableLayout audioLayout{this, Size{~0, 0}}; + Label audioDriverLabel{&audioLayout, Size{0, 0}}; + HorizontalLayout audioDriverLayout{&audioLayout, Size{~0, 0}}; + ComboButton audioDriverOption{&audioDriverLayout, Size{0, 0}}; + Button audioDriverUpdate{&audioDriverLayout, Size{0, 0}}; + Label audioDriverActive{&audioDriverLayout, Size{0, 0}}; + Label audioDeviceLabel{&audioLayout, Size{0, 0}}; + HorizontalLayout audioPropertyLayout{&audioLayout, Size{~0, 0}}; + ComboButton audioDeviceOption{&audioPropertyLayout, Size{0, 0}}; + Label audioFrequencyLabel{&audioPropertyLayout, Size{0, 0}}; + ComboButton audioFrequencyOption{&audioPropertyLayout, Size{0, 0}}; + Label audioLatencyLabel{&audioPropertyLayout, Size{0, 0}}; + ComboButton audioLatencyOption{&audioPropertyLayout, Size{0, 0}}; + HorizontalLayout audioToggleLayout{this, Size{~0, 0}}; + CheckLabel audioExclusiveToggle{&audioToggleLayout, Size{0, 0}}; + CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}}; + CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}}; + Canvas audioSpacer{this, Size{~0, 1}}; + Label inputLabel{this, Size{~0, 0}, 2}; + TableLayout inputLayout{this, Size{~0, 0}}; + Label inputDriverLabel{&inputLayout, Size{0, 0}}; + HorizontalLayout inputDriverLayout{&inputLayout, Size{~0, 0}}; + ComboButton inputDriverOption{&inputDriverLayout, Size{0, 0}}; + Button inputDriverUpdate{&inputDriverLayout, Size{0, 0}}; + Label inputDriverActive{&inputDriverLayout, Size{0, 0}}; }; -struct SettingsWindow : Window { +struct SettingsWindow : Window, Lock { auto create() -> void; auto setVisible(bool visible = true) -> SettingsWindow&; - auto show(uint index) -> void; + auto show(int index) -> void; public: 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}; }; @@ -380,6 +423,7 @@ extern AudioSettings audioSettings; extern InputSettings inputSettings; extern HotkeySettings hotkeySettings; extern PathSettings pathSettings; +extern SpeedSettings speedSettings; extern EmulatorSettings emulatorSettings; extern DriverSettings driverSettings; namespace Instances { extern Instance settingsWindow; } diff --git a/bsnes/target-bsnes/settings/speed.cpp b/bsnes/target-bsnes/settings/speed.cpp new file mode 100644 index 00000000..c1f1183c --- /dev/null +++ b/bsnes/target-bsnes/settings/speed.cpp @@ -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(); + }); +} diff --git a/bsnes/target-bsnes/settings/video.cpp b/bsnes/target-bsnes/settings/video.cpp index 86190c6f..4a8f0a4b 100644 --- a/bsnes/target-bsnes/settings/video.cpp +++ b/bsnes/target-bsnes/settings/video.cpp @@ -1,8 +1,6 @@ auto VideoSettings::create() -> void { - setIcon(Icon::Device::Display); - setText("Video"); - - layout.setPadding(5_sx); + setCollapsible(); + setVisible(false); colorAdjustmentLabel.setFont(Font().setBold()).setText("Color Adjustment"); colorLayout.setSize({3, 3}); @@ -32,13 +30,6 @@ auto VideoSettings::create() -> void { program.updateVideoPalette(); }).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([&] { settings.video.snow = snowOption.checked(); presentation.updateProgramIcon(); diff --git a/bsnes/target-bsnes/tools/cheat-editor.cpp b/bsnes/target-bsnes/tools/cheat-editor.cpp index f6065708..d593f5ec 100644 --- a/bsnes/target-bsnes/tools/cheat-editor.cpp +++ b/bsnes/target-bsnes/tools/cheat-editor.cpp @@ -110,14 +110,13 @@ auto CheatWindow::doAccept() -> void { // auto CheatEditor::create() -> void { - setIcon(Icon::Edit::Replace); - setText("Cheat Editor"); + setCollapsible(); + setVisible(false); - layout.setPadding(5_sx); cheatList.setBatchable(); cheatList.setHeadered(); cheatList.setSortable(); - cheatList.onActivate([&] { + cheatList.onActivate([&](auto cell) { //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. if(chrono::timestamp() - activateTimeout < 2) return; diff --git a/bsnes/target-bsnes/tools/cheat-finder.cpp b/bsnes/target-bsnes/tools/cheat-finder.cpp index 5fd1ba67..b6f56842 100644 --- a/bsnes/target-bsnes/tools/cheat-finder.cpp +++ b/bsnes/target-bsnes/tools/cheat-finder.cpp @@ -1,8 +1,7 @@ auto CheatFinder::create() -> void { - setIcon(Icon::Edit::Find); - setText("Cheat Finder"); + setCollapsible(); + setVisible(false); - layout.setPadding(5_sx); searchList.setHeadered(); searchValue.onActivate([&] { eventScan(); }); searchLabel.setText("Value:"); diff --git a/bsnes/target-bsnes/tools/manifest-viewer.cpp b/bsnes/target-bsnes/tools/manifest-viewer.cpp index 7bea962d..c5cf3613 100644 --- a/bsnes/target-bsnes/tools/manifest-viewer.cpp +++ b/bsnes/target-bsnes/tools/manifest-viewer.cpp @@ -1,8 +1,7 @@ auto ManifestViewer::create() -> void { - setIcon(Icon::Emblem::Text); - setText("Manifest Viewer"); + setCollapsible(); + setVisible(false); - layout.setPadding(5_sx); manifestLabel.setText("Manifest:"); manifestOption.onChange([&] { selectManifest(); }); manifestSpacer.setColor({192, 192, 192}); diff --git a/bsnes/target-bsnes/tools/state-manager.cpp b/bsnes/target-bsnes/tools/state-manager.cpp index 115a9ebc..ab72067f 100644 --- a/bsnes/target-bsnes/tools/state-manager.cpp +++ b/bsnes/target-bsnes/tools/state-manager.cpp @@ -47,15 +47,14 @@ auto StateWindow::doAccept() -> void { } auto StateManager::create() -> void { - setIcon(Icon::Application::FileManager); - setText("State Manager"); + setCollapsible(); + setVisible(false); - layout.setPadding(5_sx); stateLayout.setAlignment(0.0); stateList.setBatchable(); stateList.setHeadered(); stateList.setSortable(); - stateList.onActivate([&] { loadButton.doActivate(); }); + stateList.onActivate([&](auto cell) { loadButton.doActivate(); }); stateList.onChange([&] { updateSelection(); }); stateList.onSort([&](TableViewColumn column) { 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("Quick States").setProperty("type", "Quick/")); categoryOption.onChange([&] { loadStates(); }); - statePreviewSeparator.setColor({192, 192, 192}); + statePreviewSeparator1.setColor({192, 192, 192}); statePreviewLabel.setFont(Font().setBold()).setText("Preview"); + statePreviewSeparator2.setColor({192, 192, 192}); loadButton.setText("Load").onActivate([&] { if(auto item = stateList.selected()) program.loadState(item.property("name")); }); diff --git a/bsnes/target-bsnes/tools/tools.cpp b/bsnes/target-bsnes/tools/tools.cpp index 2e2a6868..c654a81f 100644 --- a/bsnes/target-bsnes/tools/tools.cpp +++ b/bsnes/target-bsnes/tools/tools.cpp @@ -4,11 +4,11 @@ #include "state-manager.cpp" #include "manifest-viewer.cpp" namespace Instances { Instance cheatDatabase; } +CheatFinder cheatFinder; CheatDatabase& cheatDatabase = Instances::cheatDatabase(); namespace Instances { Instance cheatWindow; } CheatWindow& cheatWindow = Instances::cheatWindow(); CheatEditor cheatEditor; -CheatFinder cheatFinder; namespace Instances { Instance stateWindow; } StateWindow& stateWindow = Instances::stateWindow(); StateManager stateManager; @@ -16,20 +16,52 @@ ManifestViewer manifestViewer; namespace Instances { Instance 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 { layout.setPadding(5_sx); - panel.append(cheatFinder); - panel.append(cheatEditor); - panel.append(stateManager); - panel.append(manifestViewer); - panel.onChange([&] { - uint offset = panel.selected().offset(); - if(offset != 1) cheatDatabase.setVisible(false), cheatWindow.setVisible(false); - if(offset != 2) stateWindow.setVisible(false); + panelList.append(ListViewItem().setText("Cheat Finder").setIcon(Icon::Action::Search)); + panelList.append(ListViewItem().setText("Cheat Editor").setIcon(Icon::Edit::Replace)); + panelList.append(ListViewItem().setText("State Manager").setIcon(Icon::Application::FileManager)); + panelList.append(ListViewItem().setText("Manifest Viewer").setIcon(Icon::Emblem::Text)); + panelList.onChange([&] { + if(auto item = panelList.selected()) { + show(item.offset()); + } 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"); - setSize({720_sx, 480_sx}); + setSize({720_sx, 400_sx}); setAlignment({1.0, 1.0}); setDismissable(); @@ -50,8 +82,21 @@ auto ToolsWindow::setVisible(bool visible) -> ToolsWindow& { return *this; } -auto ToolsWindow::show(uint index) -> void { - panel.item(index).setSelected(); +auto ToolsWindow::show(int index) -> void { + 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(); setFocused(); } diff --git a/bsnes/target-bsnes/tools/tools.hpp b/bsnes/target-bsnes/tools/tools.hpp index 5dd9af6c..0e0edb39 100644 --- a/bsnes/target-bsnes/tools/tools.hpp +++ b/bsnes/target-bsnes/tools/tools.hpp @@ -6,7 +6,7 @@ struct CheatCandidate { uint32_t span; }; -struct CheatFinder : TabFrameItem { +struct CheatFinder : VerticalLayout { auto create() -> void; auto restart() -> void; auto refresh() -> void; @@ -18,16 +18,15 @@ struct CheatFinder : TabFrameItem { public: vector candidates; - VerticalLayout layout{this}; - TableView searchList{&layout, Size{~0, ~0}}; - HorizontalLayout controlLayout{&layout, Size{~0, 0}}; - Label searchLabel{&controlLayout, Size{0, 0}}; - LineEdit searchValue{&controlLayout, Size{~0, 0}}; - ComboButton searchSize{&controlLayout, Size{0, 0}}; - ComboButton searchMode{&controlLayout, Size{0, 0}}; - ComboButton searchSpan{&controlLayout, Size{0, 0}}; - Button searchScan{&controlLayout, Size{80, 0}}; - Button searchClear{&controlLayout, Size{80, 0}}; + TableView searchList{this, Size{~0, ~0}}; + HorizontalLayout controlLayout{this, Size{~0, 0}}; + Label searchLabel{&controlLayout, Size{0, 0}}; + LineEdit searchValue{&controlLayout, Size{~0, 0}}; + ComboButton searchSize{&controlLayout, Size{0, 0}}; + ComboButton searchMode{&controlLayout, Size{0, 0}}; + ComboButton searchSpan{&controlLayout, Size{0, 0}}; + Button searchScan{&controlLayout, Size{80, 0}}; + Button searchClear{&controlLayout, Size{80, 0}}; }; struct Cheat { @@ -79,7 +78,7 @@ public: Button cancelButton{&controlLayout, Size{80_sx, 0}}; }; -struct CheatEditor : TabFrameItem { +struct CheatEditor : VerticalLayout { auto create() -> void; auto refresh() -> void; auto addCheat(Cheat cheat) -> void; @@ -93,15 +92,14 @@ public: vector cheats; uint64_t activateTimeout = 0; - VerticalLayout layout{this}; - TableView cheatList{&layout, Size{~0, ~0}}; - HorizontalLayout controlLayout{&layout, Size{~0, 0}}; - Button findCheatsButton{&controlLayout, Size{120_sx, 0}}; - Widget spacer{&controlLayout, Size{~0, 0}}; - CheckLabel enableCheats{&controlLayout, Size{0, 0}}; - Button addButton{&controlLayout, Size{80_sx, 0}}; - Button editButton{&controlLayout, Size{80_sx, 0}}; - Button removeButton{&controlLayout, Size{80_sx, 0}}; + TableView cheatList{this, Size{~0, ~0}}; + HorizontalLayout controlLayout{this, Size{~0, 0}}; + Button findCheatsButton{&controlLayout, Size{120_sx, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + CheckLabel enableCheats{&controlLayout, Size{0, 0}}; + Button addButton{&controlLayout, Size{80_sx, 0}}; + Button editButton{&controlLayout, Size{80_sx, 0}}; + Button removeButton{&controlLayout, Size{80_sx, 0}}; }; struct StateWindow : Window { @@ -121,7 +119,7 @@ public: Button cancelButton{&controlLayout, Size{80_sx, 0}}; }; -struct StateManager : TabFrameItem, Lock { +struct StateManager : VerticalLayout, Lock { auto create() -> void; auto type() const -> string; auto loadStates() -> void; @@ -139,54 +137,56 @@ public: DateDescending, } sortBy = SortBy::NameAscending; - VerticalLayout layout{this}; - HorizontalLayout stateLayout{&layout, Size{~0, ~0}}; - TableView stateList{&stateLayout, Size{~0, ~0}}; - VerticalLayout previewLayout{&stateLayout, Size{0, ~0}}; - HorizontalLayout categoryLayout{&previewLayout, Size{~0, 0}}; - Label categoryLabel{&categoryLayout, Size{0, 0}}; - ComboButton categoryOption{&categoryLayout, Size{~0, 0}}; - Canvas statePreviewSeparator{&previewLayout, Size{~0, 1}}; - Label statePreviewLabel{&previewLayout, Size{~0, 0}}; - Canvas statePreview{&previewLayout, Size{256, 224}}; - HorizontalLayout controlLayout{&layout, Size{~0, 0}}; - Button loadButton{&controlLayout, Size{80_sx, 0}}; - Button saveButton{&controlLayout, Size{80_sx, 0}}; - Widget spacer{&controlLayout, Size{~0, 0}}; - Button addButton{&controlLayout, Size{80_sx, 0}}; - Button editButton{&controlLayout, Size{80_sx, 0}}; - Button removeButton{&controlLayout, Size{80_sx, 0}}; + HorizontalLayout stateLayout{this, Size{~0, ~0}}; + TableView stateList{&stateLayout, Size{~0, ~0}}; + VerticalLayout previewLayout{&stateLayout, Size{0, ~0}}; + HorizontalLayout categoryLayout{&previewLayout, Size{~0, 0}}; + Label categoryLabel{&categoryLayout, Size{0, 0}}; + ComboButton categoryOption{&categoryLayout, Size{~0, 0}}; + Canvas statePreviewSeparator1{&previewLayout, Size{~0, 1}}; + Label statePreviewLabel{&previewLayout, Size{~0, 0}}; + Canvas statePreview{&previewLayout, Size{256, 224}}; + 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 saveButton{&controlLayout, Size{80_sx, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button addButton{&controlLayout, Size{80_sx, 0}}; + Button editButton{&controlLayout, Size{80_sx, 0}}; + Button removeButton{&controlLayout, Size{80_sx, 0}}; }; -struct ManifestViewer : TabFrameItem { +struct ManifestViewer : VerticalLayout { auto create() -> void; auto loadManifest() -> void; auto selectManifest() -> void; public: - VerticalLayout layout{this}; - HorizontalLayout manifestLayout{&layout, Size{~0, 0}}; - Label manifestLabel{&manifestLayout, Size{0, 0}}; - ComboButton manifestOption{&manifestLayout, Size{~0, 0}}; - Canvas manifestSpacer{&layout, Size{~0, 1}}; - HorizontalLayout informationLayout{&layout, Size{~0, 0}}; - Canvas typeIcon{&informationLayout, Size{16, 16}}; - Label nameLabel{&informationLayout, Size{~0, 0}}; - #if 0 && defined(Hiro_SourceEdit) - SourceEdit manifestView{&layout, Size{~0, ~0}}; - #else - TextEdit manifestView{&layout, Size{~0, ~0}}; - #endif + HorizontalLayout manifestLayout{this, Size{~0, 0}}; + Label manifestLabel{&manifestLayout, Size{0, 0}}; + ComboButton manifestOption{&manifestLayout, Size{~0, 0}}; + Canvas manifestSpacer{this, Size{~0, 1}}; + HorizontalLayout informationLayout{this, Size{~0, 0}}; + Canvas typeIcon{&informationLayout, Size{16, 16}}; + Label nameLabel{&informationLayout, Size{~0, 0}}; + #if 0 && defined(Hiro_SourceEdit) + SourceEdit manifestView{this, Size{~0, ~0}}; + #else + TextEdit manifestView{this, Size{~0, ~0}}; + #endif }; struct ToolsWindow : Window { auto create() -> void; auto setVisible(bool visible = true) -> ToolsWindow&; - auto show(uint index) -> void; + auto show(int index) -> void; public: 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; } diff --git a/hiro/GNUmakefile b/hiro/GNUmakefile index fd9c8c37..51671b96 100755 --- a/hiro/GNUmakefile +++ b/hiro/GNUmakefile @@ -37,24 +37,24 @@ ifneq ($(filter $(platform),linux bsd),) ifeq ($(hiro),gtk2) 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 ifeq ($(hiro),gtk3) 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 ifeq ($(hiro),qt4) moc = /usr/local/lib/qt4/bin/moc 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 ifeq ($(hiro),qt5) moc = /usr/local/lib/qt5/bin/moc 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 diff --git a/hiro/cocoa/widget/table-view.cpp b/hiro/cocoa/widget/table-view.cpp index a0dc4a76..e41bd33b 100755 --- a/hiro/cocoa/widget/table-view.cpp +++ b/hiro/cocoa/widget/table-view.cpp @@ -111,7 +111,7 @@ } -(IBAction) activate:(id)sender { - tableView->doActivate(); + tableView->doActivate({}); } -(IBAction) doubleAction:(id)sender { diff --git a/hiro/core/shared.hpp b/hiro/core/shared.hpp index c264d7a3..ed35228e 100644 --- a/hiro/core/shared.hpp +++ b/hiro/core/shared.hpp @@ -700,7 +700,7 @@ struct TableView : sTableView { auto column(uint position) const { return self().column(position); } auto columnCount() const { return self().columnCount(); } 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 doContext() const { return self().doContext(); } 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 itemCount() const { return self().itemCount(); } auto items() const { return self().items(); } - auto onActivate(const function& callback = {}) { return self().onActivate(callback), *this; } + auto onActivate(const function& callback = {}) { return self().onActivate(callback), *this; } auto onChange(const function& callback = {}) { return self().onChange(callback), *this; } auto onContext(const function& callback = {}) { return self().onContext(callback), *this; } auto onEdit(const function& callback = {}) { return self().onEdit(callback), *this; } diff --git a/hiro/core/widget/table-view.cpp b/hiro/core/widget/table-view.cpp index 5a72b8d4..5ec96bb7 100755 --- a/hiro/core/widget/table-view.cpp +++ b/hiro/core/widget/table-view.cpp @@ -65,8 +65,8 @@ auto mTableView::columns() const -> vector { return columns; } -auto mTableView::doActivate() const -> void { - if(state.onActivate) return state.onActivate(); +auto mTableView::doActivate(sTableViewCell cell) const -> void { + if(state.onActivate) return state.onActivate(cell); } auto mTableView::doChange() const -> void { @@ -112,7 +112,7 @@ auto mTableView::items() const -> vector { return items; } -auto mTableView::onActivate(const function& callback) -> type& { +auto mTableView::onActivate(const function& callback) -> type& { state.onActivate = callback; return *this; } diff --git a/hiro/core/widget/table-view.hpp b/hiro/core/widget/table-view.hpp index 9bce8964..5ce03981 100644 --- a/hiro/core/widget/table-view.hpp +++ b/hiro/core/widget/table-view.hpp @@ -13,7 +13,7 @@ struct mTableView : mWidget { auto column(uint position) const -> TableViewColumn; auto columnCount() const -> uint; auto columns() const -> vector; - auto doActivate() const -> void; + auto doActivate(sTableViewCell cell) const -> void; auto doChange() const -> void; auto doContext() const -> void; auto doEdit(sTableViewCell cell) const -> void; @@ -24,7 +24,7 @@ struct mTableView : mWidget { auto item(uint position) const -> TableViewItem; auto itemCount() const -> uint; auto items() const -> vector; - auto onActivate(const function& callback = {}) -> type&; + auto onActivate(const function& callback = {}) -> type&; auto onChange(const function& callback = {}) -> type&; auto onContext(const function& callback = {}) -> type&; auto onEdit(const function& callback = {}) -> type&; @@ -59,7 +59,7 @@ struct mTableView : mWidget { Color foregroundColor; bool headered = false; vector items; - function onActivate; + function onActivate; function onChange; function onContext; function onEdit; diff --git a/hiro/extension/browser-dialog.cpp b/hiro/extension/browser-dialog.cpp index d71fa47a..a2ef91dd 100755 --- a/hiro/extension/browser-dialog.cpp +++ b/hiro/extension/browser-dialog.cpp @@ -11,7 +11,7 @@ struct BrowserDialogWindow { auto isFolder(const string& name) -> bool; auto isMatch(const string& name) -> bool; auto run() -> BrowserDialog::Response; - auto setPath(string path) -> void; + auto setPath(string path, const string& contains = "") -> void; private: Window window; @@ -24,7 +24,8 @@ private: Button pathUp{&pathLayout, Size{0, 0}, 0}; ListView view{&layout, Size{~0, ~0}, 5_sx}; 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}; ComboButton optionList{&controlLayout, Size{0, 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.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(); - acceptButton.setEnabled(name && !isFolder(name)); - fileName.setBackgroundColor(acceptButton.enabled() ? Color{} : Color{255, 224, 224}); + if(state.action == "saveFile") acceptButton.setEnabled(name && !isFolder(name)); }); - acceptButton.onActivate([&] { accept(); }); + acceptButton.setEnabled(false).onActivate([&] { accept(); }); if(state.action.beginsWith("open")) acceptButton.setText(tr("Open")); if(state.action.beginsWith("save")) acceptButton.setText(tr("Save")); 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.setDismissable(); window.setVisible(); - view.setFocused(); + fileName.setFocused(); Application::processEvents(); view->resizeColumns(); window.setModal(); @@ -335,7 +341,7 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response { return response; } -auto BrowserDialogWindow::setPath(string path) -> void { +auto BrowserDialogWindow::setPath(string path, const string& contains) -> void { path.transform("\\", "/"); if((path || Path::root() == "/") && !path.endsWith("/")) path.append("/"); pathName.setText(state.path = path); @@ -365,13 +371,14 @@ auto BrowserDialogWindow::setPath(string path) -> void { if(state.action == "openFolder") continue; } if(!isMatch(content)) continue; + if(contains && !content.ifind(contains)) 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)); } Application::processEvents(); view->resizeColumns(); //todo: on Windows, adding items may add vertical scrollbar; this hack corrects column width - view.setFocused().doChange(); + view.doChange(); } // diff --git a/hiro/extension/list-view.cpp b/hiro/extension/list-view.cpp index a04654cc..812703d7 100644 --- a/hiro/extension/list-view.cpp +++ b/hiro/extension/list-view.cpp @@ -1,7 +1,7 @@ #if defined(Hiro_ListView) mListView::mListView() { - mTableView::onActivate([&] { doActivate(); }); + mTableView::onActivate([&](auto) { doActivate(); }); mTableView::onChange([&] { doChange(); }); mTableView::onContext([&] { doContext(); }); mTableView::onToggle([&](TableViewCell cell) { diff --git a/hiro/gtk/widget/table-view.cpp b/hiro/gtk/widget/table-view.cpp index 4ccffa75..750038e3 100755 --- a/hiro/gtk/widget/table-view.cpp +++ b/hiro/gtk/widget/table-view.cpp @@ -2,7 +2,7 @@ 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_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); } @@ -238,8 +238,26 @@ auto pTableView::_createModel() -> void { gtk_tree_view_set_model(gtkTreeView, gtkTreeModel); } -auto pTableView::_doActivate() -> void { - if(!locked()) self().doActivate(); +auto pTableView::_doActivate(GtkTreePath* gtkRow, GtkTreeViewColumn* gtkColumn) -> void { + 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 { diff --git a/hiro/gtk/widget/table-view.hpp b/hiro/gtk/widget/table-view.hpp index c04a8062..a403826a 100755 --- a/hiro/gtk/widget/table-view.hpp +++ b/hiro/gtk/widget/table-view.hpp @@ -25,7 +25,7 @@ struct pTableView : pWidget { auto _cellWidth(uint row, uint column) -> uint; auto _columnWidth(uint column) -> uint; auto _createModel() -> void; - auto _doActivate() -> void; + auto _doActivate(GtkTreePath* = nullptr, GtkTreeViewColumn* = nullptr) -> void; auto _doChange() -> void; auto _doContext() -> void; auto _doDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeIter* iter) -> void; diff --git a/hiro/qt/qt.hpp b/hiro/qt/qt.hpp index 119a0623..53982627 100644 --- a/hiro/qt/qt.hpp +++ b/hiro/qt/qt.hpp @@ -252,7 +252,7 @@ public: auto showEvent(QShowEvent*) -> void override; pTableView& p; public slots: - void onActivate(); + void onActivate(QTreeWidgetItem* item, int column); void onChange(); void onContext(); void onSort(int column); diff --git a/hiro/qt/qt.moc b/hiro/qt/qt.moc index 58136bab..9c5dd1ee 100644 --- a/hiro/qt/qt.moc +++ b/hiro/qt/qt.moc @@ -1328,20 +1328,19 @@ static const uint qt_meta_data_hiro__QtTableView[] = { 0, // signalCount // slots: signature, parameters, type, tag, flags - 18, 31, 31, 31, 0x0a, - 32, 31, 31, 31, 0x0a, - 43, 31, 31, 31, 0x0a, - 55, 67, 31, 31, 0x0a, - 74, 105, 31, 31, 0x0a, + 18, 51, 63, 63, 0x0a, + 64, 63, 63, 63, 0x0a, + 75, 63, 63, 63, 0x0a, + 87, 99, 63, 63, 0x0a, + 106, 51, 63, 63, 0x0a, 0 // eod }; static const char qt_meta_stringdata_hiro__QtTableView[] = { - "hiro::QtTableView\0onActivate()\0\0" - "onChange()\0onContext()\0onSort(int)\0" - "column\0onToggle(QTreeWidgetItem*,int)\0" - "item,column\0" + "hiro::QtTableView\0onActivate(QTreeWidgetItem*,int)\0" + "item,column\0\0onChange()\0onContext()\0" + "onSort(int)\0column\0onToggle(QTreeWidgetItem*,int)\0" }; 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)); QtTableView *_t = static_cast(_o); 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 2: _t->onContext(); break; case 3: _t->onSort((*reinterpret_cast< int(*)>(_a[1]))); break; diff --git a/hiro/qt/widget/table-view.cpp b/hiro/qt/widget/table-view.cpp index 8512a4b3..195e2e8a 100755 --- a/hiro/qt/widget/table-view.cpp +++ b/hiro/qt/widget/table-view.cpp @@ -17,7 +17,7 @@ auto pTableView::construct() -> void { qtTableViewDelegate = new QtTableViewDelegate(*this); 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(customContextMenuRequested(const QPoint&)), SLOT(onContext())); qtTableView->connect(qtTableView->header(), SIGNAL(sectionClicked(int)), SLOT(onSort(int))); @@ -186,8 +186,20 @@ auto pTableView::_widthOfCell(unsigned _row, unsigned _column) -> unsigned { return width; } -auto QtTableView::onActivate() -> void { - if(!p.locked()) p.self().doActivate(); +auto QtTableView::onActivate(QTreeWidgetItem* qtItem, int column) -> void { + 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 { diff --git a/hiro/windows/utility.cpp b/hiro/windows/utility.cpp index 04bd5c9e..951d8bb4 100755 --- a/hiro/windows/utility.cpp +++ b/hiro/windows/utility.cpp @@ -453,7 +453,7 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms } case AppMessage::TableView_onActivate: { - if(auto tableView = (mTableView*)lparam) tableView->doActivate(); + if(auto tableView = (mTableView*)lparam) tableView->doActivate({}); break; } diff --git a/icarus/icarus.cpp b/icarus/icarus.cpp index fe059834..b7548bfc 100644 --- a/icarus/icarus.cpp +++ b/icarus/icarus.cpp @@ -8,8 +8,11 @@ auto locate(string name) -> string { string location = {Path::program(), name}; if(inode::exists(location)) return location; - directory::create({Path::userData(), "bsnes/"}); - return {Path::userData(), "bsnes/", name}; + location = {Path::userData(), "bsnes/"}; + if(inode::exists(location)) return location; + + directory::create({Path::userSettings(), "bsnes/"}); + return {Path::userSettings(), "bsnes/", name}; } #include "settings.cpp" diff --git a/ruby/video/xshm.cpp b/ruby/video/xshm.cpp index a19607b7..e7cae19a 100755 --- a/ruby/video/xshm.cpp +++ b/ruby/video/xshm.cpp @@ -74,7 +74,7 @@ struct VideoXShm : VideoDriver { auto output(uint width = 0, uint height = 0) -> void override { uint windowWidth, windowHeight; size(windowWidth, windowHeight); - if(!_image) return; + if(!_image || !_inputBuffer || !_outputBuffer) return; if(!width) width = _outputWidth; if(!height) height = _outputHeight;