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

View File

@ -10,45 +10,45 @@ Audio::~Audio() {
}
auto Audio::reset(Interface* interface) -> void {
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);
}
}

View File

@ -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;
};

View File

@ -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";

View File

@ -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())) {

View File

@ -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;

View File

@ -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};

View File

@ -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

View File

@ -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 {

View File

@ -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);

View File

@ -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);

View File

@ -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
}

View File

@ -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:

View File

@ -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 {

View File

@ -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);

View File

@ -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));

View File

@ -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"

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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(); });

View File

@ -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};

View File

@ -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 = {};

View File

@ -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);
}
}

View File

@ -262,7 +262,7 @@ auto Program::audioFrame(const float* samples, uint channels) -> void {
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
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);
}

View File

@ -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});

View File

@ -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;

View File

@ -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 {

View File

@ -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;
}

View File

@ -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});

View File

@ -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 {

View File

@ -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());
}

View File

@ -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();

View File

@ -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();

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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; }

View File

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

View File

@ -1,8 +1,6 @@
auto VideoSettings::create() -> void {
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();

View File

@ -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;

View File

@ -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:");

View File

@ -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});

View File

@ -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"));
});

View File

@ -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();
}

View File

@ -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; }

View File

@ -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

View File

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

View File

@ -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; }

View File

@ -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;
}

View File

@ -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;

View File

@ -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();
}
//

View File

@ -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) {

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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 {

View File

@ -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;
}

View File

@ -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"

View File

@ -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;