mirror of https://github.com/bsnes-emu/bsnes.git
v108.1
* added CPU and SA1 overclocking support * added fast forward speed limiting * added option to mute during fast forwarding and rewinding * lowered volume when not muting during FF/rewind * reformatted settings/tools windows from tabs to lists * moved focus settings to input settings panel * redesigned input and hotkey settings panels to be easier to use * fixed offscreen placement issue with path settings panel * added hotkey combinational logic option (AND / OR mode setting) * added search support to file browser dialog * fixed --fullscreen command-line option
This commit is contained in:
parent
7e88bdde09
commit
f65b7a8528
|
@ -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<Stream> {
|
||||
this->channels = max(this->channels, channels);
|
||||
_channels = max(_channels, channels);
|
||||
shared_pointer<Stream> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<shared_pointer<Stream>> streams;
|
||||
Interface* _interface = nullptr;
|
||||
vector<shared_pointer<Stream>> _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;
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -58,8 +58,8 @@ auto CPU::power(bool reset) -> void {
|
|||
PPUcounter::reset();
|
||||
PPUcounter::scanline = {&CPU::scanline, this};
|
||||
|
||||
function<auto (uint, uint8) -> uint8> reader;
|
||||
function<auto (uint, uint8) -> void> writer;
|
||||
function<uint8 (uint, uint8)> reader;
|
||||
function<void (uint, uint8)> writer;
|
||||
|
||||
reader = {&CPU::readRAM, this};
|
||||
writer = {&CPU::writeRAM, this};
|
||||
|
|
|
@ -68,6 +68,11 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
|
|||
uint8 wram[128 * 1024];
|
||||
vector<Thread*> coprocessors;
|
||||
|
||||
struct Overclocking {
|
||||
uint counter = 0;
|
||||
uint target = 0;
|
||||
} overclocking;
|
||||
|
||||
private:
|
||||
uint version = 2; //allowed: 1, 2
|
||||
|
||||
|
|
|
@ -27,6 +27,12 @@ auto CPU::stepOnce() -> void {
|
|||
template<uint Clocks, bool Synchronize>
|
||||
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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
struct DSP {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
uint8 apuram[64 * 1024] = {};
|
||||
uint8_t apuram[64 * 1024] = {};
|
||||
|
||||
void main();
|
||||
uint8 read(uint8 addr);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <nall/main.hpp>
|
||||
|
@ -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));
|
||||
|
|
|
@ -18,6 +18,7 @@ extern unique_pointer<Emulator::Interface> emulator;
|
|||
#include <nall/decode/zip.hpp>
|
||||
#include <nall/encode/rle.hpp>
|
||||
#include <nall/encode/zip.hpp>
|
||||
#include <nall/hash/crc16.hpp>
|
||||
|
||||
#include "program/program.hpp"
|
||||
#include "input/input.hpp"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool {
|
||||
auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, uint binding) -> bool {
|
||||
if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) {
|
||||
return unbind(), true;
|
||||
return unbind(binding), true;
|
||||
}
|
||||
|
||||
string encoding = {"0x", hex(device->id()), "/", group, "/", input};
|
||||
|
@ -58,7 +55,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|
|||
|| (device->isMouse() && group == HID::Mouse::GroupID::Button)
|
||||
|| (device->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<HID::Device> 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<HID::Device> 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<HID::Device> 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<HID::Device> 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;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
enum : uint { BindingLimit = 4 };
|
||||
|
||||
struct InputMapping {
|
||||
auto bind() -> void;
|
||||
auto bind(string mapping) -> void;
|
||||
auto bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool;
|
||||
auto unbind() -> void;
|
||||
auto bind(string mapping, uint binding) -> void;
|
||||
auto bind(shared_pointer<HID::Device> 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<HID::Device> device;
|
||||
uint group = 0;
|
||||
uint input = 0;
|
||||
Qualifier qualifier = Qualifier::None;
|
||||
};
|
||||
vector<Mapping> mappings;
|
||||
Binding bindings[BindingLimit];
|
||||
|
||||
uint3 turboCounter = 0;
|
||||
};
|
||||
|
||||
struct InputHotkey : InputMapping {
|
||||
InputHotkey(string name) { this->name = name; }
|
||||
auto& onPress(function<void()> press) { return this->press = press, *this; }
|
||||
auto& onRelease(function<void()> release) { return this->release = release, *this; }
|
||||
//auto logic() const -> Logic override { return Logic::AND; }
|
||||
auto& onPress(function<void ()> press) { return this->press = press, *this; }
|
||||
auto& onRelease(function<void ()> release) { return this->release = release, *this; }
|
||||
auto logic() const -> Logic override;
|
||||
|
||||
function<auto() -> void> press;
|
||||
function<auto() -> void> release;
|
||||
function<void ()> press;
|
||||
function<void ()> 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;
|
||||
|
|
|
@ -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(); });
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -34,32 +34,3 @@ auto Program::hackPatchMemory(vector<uint8_t>& 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -125,7 +125,6 @@ struct Program : Lock, Emulator::Platform {
|
|||
//hacks.cpp
|
||||
auto hackCompatibility() -> void;
|
||||
auto hackPatchMemory(vector<uint8_t>& 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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<HID::Device> 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();
|
||||
|
|
|
@ -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<HID::Device> 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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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> 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();
|
||||
}
|
||||
|
|
|
@ -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<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void;
|
||||
|
||||
maybe<InputMapping&> 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<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void;
|
||||
|
||||
maybe<InputMapping&> 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> settingsWindow; }
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:");
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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"));
|
||||
});
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
#include "state-manager.cpp"
|
||||
#include "manifest-viewer.cpp"
|
||||
namespace Instances { Instance<CheatDatabase> cheatDatabase; }
|
||||
CheatFinder cheatFinder;
|
||||
CheatDatabase& cheatDatabase = Instances::cheatDatabase();
|
||||
namespace Instances { Instance<CheatWindow> cheatWindow; }
|
||||
CheatWindow& cheatWindow = Instances::cheatWindow();
|
||||
CheatEditor cheatEditor;
|
||||
CheatFinder cheatFinder;
|
||||
namespace Instances { Instance<StateWindow> stateWindow; }
|
||||
StateWindow& stateWindow = Instances::stateWindow();
|
||||
StateManager stateManager;
|
||||
|
@ -16,20 +16,52 @@ ManifestViewer manifestViewer;
|
|||
namespace Instances { Instance<ToolsWindow> 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();
|
||||
}
|
||||
|
|
|
@ -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<CheatCandidate> 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<Cheat> 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> cheatDatabase; }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
}
|
||||
|
||||
-(IBAction) activate:(id)sender {
|
||||
tableView->doActivate();
|
||||
tableView->doActivate({});
|
||||
}
|
||||
|
||||
-(IBAction) doubleAction:(id)sender {
|
||||
|
|
|
@ -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<void ()>& callback = {}) { return self().onActivate(callback), *this; }
|
||||
auto onActivate(const function<void (TableViewCell)>& callback = {}) { return self().onActivate(callback), *this; }
|
||||
auto onChange(const function<void ()>& callback = {}) { return self().onChange(callback), *this; }
|
||||
auto onContext(const function<void ()>& callback = {}) { return self().onContext(callback), *this; }
|
||||
auto onEdit(const function<void (TableViewCell)>& callback = {}) { return self().onEdit(callback), *this; }
|
||||
|
|
|
@ -65,8 +65,8 @@ auto mTableView::columns() const -> vector<TableViewColumn> {
|
|||
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<TableViewItem> {
|
|||
return items;
|
||||
}
|
||||
|
||||
auto mTableView::onActivate(const function<void ()>& callback) -> type& {
|
||||
auto mTableView::onActivate(const function<void (TableViewCell)>& callback) -> type& {
|
||||
state.onActivate = callback;
|
||||
return *this;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ struct mTableView : mWidget {
|
|||
auto column(uint position) const -> TableViewColumn;
|
||||
auto columnCount() const -> uint;
|
||||
auto columns() const -> vector<TableViewColumn>;
|
||||
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<TableViewItem>;
|
||||
auto onActivate(const function<void ()>& callback = {}) -> type&;
|
||||
auto onActivate(const function<void (TableViewCell)>& callback = {}) -> type&;
|
||||
auto onChange(const function<void ()>& callback = {}) -> type&;
|
||||
auto onContext(const function<void ()>& callback = {}) -> type&;
|
||||
auto onEdit(const function<void (TableViewCell)>& callback = {}) -> type&;
|
||||
|
@ -59,7 +59,7 @@ struct mTableView : mWidget {
|
|||
Color foregroundColor;
|
||||
bool headered = false;
|
||||
vector<sTableViewItem> items;
|
||||
function<void ()> onActivate;
|
||||
function<void (TableViewCell)> onActivate;
|
||||
function<void ()> onChange;
|
||||
function<void ()> onContext;
|
||||
function<void (TableViewCell)> onEdit;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<QtTableView *>(_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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue