mirror of https://github.com/bsnes-emu/bsnes.git
Update to v102r21 release.
byuu says: Changelog: - GBA: fixed WININ2 reads, BG3PB writes [Jonas Quinn] - R65816: added support for yielding/resuming from WAI/STP¹ - SFC: removed status.dmaCounter functionality (also fixes possible TAS desync issue) - tomoko: added support for combinatorial inputs [hex\_usr\]² - nall: fixed missing return value from Arithmetic::operator-- [Hendricks266] Now would be the time to start looking for major regressions with the new GBA PPU renderer, I suppose ... ¹: this doesn't matter for the master thread (SNES CPU), but is important for slave threads (SNES SA1). If you try to save a state and the SA1 is inside of a WAI instruction, it will get stuck there forever. This was causing attempts to create a save state in Super Bomberman - Panic Bomber W to deadlock the emulator and crash it. This is now finally fixed. Note that I still need to implement similar functionality into the Mega Drive 68K and Z80 cores. They still have the possibility of deadlocking. The SNES implementation was more a dry-run test for this new functionality. This possible crashing bug in the Mega Drive core is the major blocking bug for a new official release. ²: many, many thanks to hex\_usr for coming up with a really nice design. I mostly implemented it the exact same way, but with a few tiny differences that don't really matter (display " and ", " or " instead of " & ", " | " in the input settings windows; append → bind; assignmentName changed to displayName.) The actual functionality is identical to the old higan v094 and earlier builds. Emulated digital inputs let you combine multiple possible keys to trigger the buttons. This is OR logic, so you can map to eg keyboard.up OR gamepad.up for instance. Emulated analog inputs always sum together. Emulated rumble outputs will cause all mapped devices to rumble, which is probably not at all useful but whatever. Hotkeys use AND logic, so you have to press every key mapped to trigger them. Useful for eg Ctrl+F to trigger fullscreen. Obviously, there are cases where OR logic would be nice for hotkeys, too. Eg if you want both F11 and your gamepad's guide button to trigger the fullscreen toggle. Unfortunately, this isn't supported, and likely won't ever be in tomoko. Something I might consider is a throw switch in the configuration file to swap between AND or OR logic for hotkeys, but I'm not going to allow construction of mappings like "(Keyboard.Ctrl and Keyboard.F) or Gamepad.Guide", as that's just too complicated to code, and too complicated to make a nice GUI to set up the mappings for.
This commit is contained in:
parent
3bcf3c24c9
commit
a4629e1f64
|
@ -12,7 +12,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "102.20";
|
||||
static const string Version = "102.21";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -68,7 +68,7 @@ auto PPU::readIO(uint32 addr) -> uint8 {
|
|||
case 0x0400'004a: return window3.io.active[BG0] << 0 | window3.io.active[BG1] << 1 | window3.io.active[BG2] << 2 | window3.io.active[BG3] << 3 | window3.io.active[OBJ] << 4 | window3.io.active[SFX] << 5;
|
||||
|
||||
//WININ2
|
||||
case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 3 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5;
|
||||
case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 2 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5;
|
||||
|
||||
//MOSAIC (write-only)
|
||||
|
||||
|
@ -266,7 +266,7 @@ auto PPU::writeIO(uint32 addr, uint8 data) -> void {
|
|||
|
||||
//BG3PB
|
||||
case 0x0400'0032: bg3.io.pb.byte(0) = data; return;
|
||||
case 0x0400'0033: bg3.io.pb.byte(0) = data; return;
|
||||
case 0x0400'0033: bg3.io.pb.byte(1) = data; return;
|
||||
|
||||
//BG3PC
|
||||
case 0x0400'0034: bg3.io.pc.byte(0) = data; return;
|
||||
|
|
|
@ -53,15 +53,16 @@ L r.pc.h = readLong(vector + 1);
|
|||
r.pc.b = 0x00;
|
||||
}
|
||||
|
||||
auto R65816::op_stp() {
|
||||
while(r.wai = true) {
|
||||
auto R65816::op_stp() -> void {
|
||||
r.stp = true;
|
||||
while(r.stp && !synchronizing()) {
|
||||
L idle();
|
||||
}
|
||||
}
|
||||
|
||||
auto R65816::op_wai() {
|
||||
auto R65816::op_wai() -> void {
|
||||
r.wai = true;
|
||||
while(r.wai) {
|
||||
while(r.wai && !synchronizing()) {
|
||||
L idle();
|
||||
}
|
||||
idle();
|
||||
|
|
|
@ -19,6 +19,7 @@ struct R65816 {
|
|||
virtual auto lastCycle() -> void = 0;
|
||||
virtual auto interruptPending() const -> bool = 0;
|
||||
virtual auto interrupt() -> void;
|
||||
virtual auto synchronizing() const -> bool = 0;
|
||||
|
||||
virtual auto readDisassembler(uint24 addr) -> uint8 { return 0; }
|
||||
|
||||
|
@ -174,8 +175,8 @@ struct R65816 {
|
|||
auto op_move_b(int adjust);
|
||||
auto op_move_w(int adjust);
|
||||
auto op_interrupt(uint16);
|
||||
auto op_stp();
|
||||
auto op_wai();
|
||||
auto op_stp() -> void;
|
||||
auto op_wai() -> void;
|
||||
auto op_xce();
|
||||
auto op_set_flag(uint bit);
|
||||
auto op_clear_flag(uint bit);
|
||||
|
|
|
@ -75,6 +75,7 @@ struct Registers {
|
|||
|
||||
bool irq = false; //IRQ pin (0 = low, 1 = trigger)
|
||||
bool wai = false; //raised during wai, cleared after interrupt triggered
|
||||
bool stp = false; //raised during stp, never cleared
|
||||
uint8 mdr = 0; //memory data register
|
||||
uint16 vector = 0; //interrupt vector address
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ auto R65816::serialize(serializer& s) -> void {
|
|||
s.integer(r.e);
|
||||
s.integer(r.irq);
|
||||
s.integer(r.wai);
|
||||
s.integer(r.stp);
|
||||
s.integer(r.mdr);
|
||||
s.integer(r.vector);
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@ auto SA1::Enter() -> void {
|
|||
}
|
||||
|
||||
auto SA1::main() -> void {
|
||||
if(r.wai) return op_wai();
|
||||
if(r.stp) return op_stp();
|
||||
|
||||
if(mmio.sa1_rdyb || mmio.sa1_resb) {
|
||||
//SA-1 co-processor is asleep
|
||||
tick();
|
||||
|
@ -75,6 +78,10 @@ auto SA1::interruptPending() const -> bool {
|
|||
return status.interruptPending;
|
||||
}
|
||||
|
||||
auto SA1::synchronizing() const -> bool {
|
||||
return scheduler.synchronizing();
|
||||
}
|
||||
|
||||
auto SA1::tick() -> void {
|
||||
step(2);
|
||||
if(++status.counter == 0) synchronize(cpu);
|
||||
|
@ -142,6 +149,7 @@ auto SA1::power() -> void {
|
|||
r.e = 1;
|
||||
r.mdr = 0x00;
|
||||
r.wai = false;
|
||||
r.stp = false;
|
||||
r.vector = 0x0000;
|
||||
|
||||
status.counter = 0;
|
||||
|
|
|
@ -8,6 +8,7 @@ struct SA1 : Processor::R65816, Thread {
|
|||
alwaysinline auto triggerIRQ() -> void;
|
||||
alwaysinline auto lastCycle() -> void override;
|
||||
alwaysinline auto interruptPending() const -> bool override;
|
||||
auto synchronizing() const -> bool override;
|
||||
|
||||
auto init() -> void;
|
||||
auto load() -> void;
|
||||
|
|
|
@ -13,6 +13,7 @@ CPU cpu;
|
|||
auto CPU::interruptPending() const -> bool { return status.interruptPending; }
|
||||
auto CPU::pio() const -> uint8 { return io.pio; }
|
||||
auto CPU::joylatch() const -> bool { return io.joypadStrobeLatch; }
|
||||
auto CPU::synchronizing() const -> bool { return scheduler.synchronizing(); }
|
||||
|
||||
CPU::CPU() {
|
||||
PPUcounter::scanline = {&CPU::scanline, this};
|
||||
|
@ -23,6 +24,9 @@ auto CPU::Enter() -> void {
|
|||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(r.wai) return op_wai();
|
||||
if(r.stp) return op_stp();
|
||||
|
||||
if(status.interruptPending) {
|
||||
status.interruptPending = false;
|
||||
if(status.nmiPending) {
|
||||
|
@ -93,6 +97,7 @@ auto CPU::power() -> void {
|
|||
r.e = 1;
|
||||
r.mdr = 0x00;
|
||||
r.wai = false;
|
||||
r.stp = false;
|
||||
r.vector = 0xfffc; //reset vector address
|
||||
|
||||
//DMA
|
||||
|
@ -209,7 +214,6 @@ auto CPU::power() -> void {
|
|||
status.interruptPending = true;
|
||||
|
||||
status.dmaActive = false;
|
||||
status.dmaCounter = 0;
|
||||
status.dmaClocks = 0;
|
||||
status.dmaPending = false;
|
||||
status.hdmaPending = false;
|
||||
|
|
|
@ -2,6 +2,7 @@ struct CPU : Processor::R65816, Thread, PPUcounter {
|
|||
auto interruptPending() const -> bool override;
|
||||
auto pio() const -> uint8;
|
||||
auto joylatch() const -> bool;
|
||||
auto synchronizing() const -> bool override;
|
||||
|
||||
CPU();
|
||||
|
||||
|
@ -122,7 +123,6 @@ private:
|
|||
|
||||
//DMA
|
||||
bool dmaActive;
|
||||
uint dmaCounter;
|
||||
uint dmaClocks;
|
||||
bool dmaPending;
|
||||
bool hdmaPending;
|
||||
|
|
|
@ -40,7 +40,6 @@ auto CPU::serialize(serializer& s) -> void {
|
|||
s.integer(status.resetPending);
|
||||
|
||||
s.integer(status.dmaActive);
|
||||
s.integer(status.dmaCounter);
|
||||
s.integer(status.dmaClocks);
|
||||
s.integer(status.dmaPending);
|
||||
s.integer(status.hdmaPending);
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
auto CPU::dmaCounter() const -> uint {
|
||||
return clockCounter & 7;
|
||||
//return (status.dmaCounter + hcounter()) & 7;
|
||||
}
|
||||
|
||||
auto CPU::joypadCounter() const -> uint {
|
||||
return clockCounter & 255;
|
||||
}
|
||||
auto CPU::dmaCounter() const -> uint { return clockCounter & 7; }
|
||||
auto CPU::joypadCounter() const -> uint { return clockCounter & 255; }
|
||||
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
status.irqLock = false;
|
||||
uint ticks = clocks >> 1;
|
||||
while(ticks--) {
|
||||
clockCounter += 2;
|
||||
tick();
|
||||
if(hcounter() & 2) pollInterrupts();
|
||||
clockCounter += 2;
|
||||
if(joypadCounter() == 0) joypadEdge();
|
||||
}
|
||||
|
||||
|
@ -37,7 +31,6 @@ auto CPU::step(uint clocks) -> void {
|
|||
|
||||
//called by ppu.tick() when Hcounter=0
|
||||
auto CPU::scanline() -> void {
|
||||
status.dmaCounter = (status.dmaCounter + status.lineClocks) & 7;
|
||||
status.lineClocks = lineclocks();
|
||||
|
||||
//forcefully sync S-CPU to other processors, in case chips are not communicating
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//keeps track of previous counter positions in history table
|
||||
auto PPUcounter::tick() -> void {
|
||||
status.hcounter += 2; //increment by smallest unit of time
|
||||
if(status.hcounter >= 1360 && status.hcounter == lineclocks()) {
|
||||
if(status.hcounter == lineclocks()) {
|
||||
status.hcounter = 0;
|
||||
vcounterTick();
|
||||
}
|
||||
|
@ -27,12 +27,12 @@ auto PPUcounter::tick(uint clocks) -> void {
|
|||
auto PPUcounter::vcounterTick() -> void {
|
||||
if(++status.vcounter == 128) status.interlace = ppu.interlace();
|
||||
|
||||
if((system.region() == System::Region::NTSC && status.interlace == false && status.vcounter == 262)
|
||||
|| (system.region() == System::Region::NTSC && status.interlace == true && status.vcounter == 263)
|
||||
|| (system.region() == System::Region::NTSC && status.interlace == true && status.vcounter == 262 && status.field == 1)
|
||||
|| (system.region() == System::Region::PAL && status.interlace == false && status.vcounter == 312)
|
||||
|| (system.region() == System::Region::PAL && status.interlace == true && status.vcounter == 313)
|
||||
|| (system.region() == System::Region::PAL && status.interlace == true && status.vcounter == 312 && status.field == 1)
|
||||
if((system.region() == System::Region::NTSC && status.interlace == 0 && status.vcounter == 262)
|
||||
|| (system.region() == System::Region::NTSC && status.interlace == 1 && status.vcounter == 263)
|
||||
|| (system.region() == System::Region::NTSC && status.interlace == 1 && status.vcounter == 262 && status.field == 1)
|
||||
|| (system.region() == System::Region::PAL && status.interlace == 0 && status.vcounter == 312)
|
||||
|| (system.region() == System::Region::PAL && status.interlace == 1 && status.vcounter == 313)
|
||||
|| (system.region() == System::Region::PAL && status.interlace == 1 && status.vcounter == 312 && status.field == 1)
|
||||
) {
|
||||
status.vcounter = 0;
|
||||
status.field = !status.field;
|
||||
|
@ -58,7 +58,7 @@ auto PPUcounter::hcounter(uint offset) const -> uint16 { return history.hcounter
|
|||
//dot 327 range = {1310, 1312, 1314}
|
||||
|
||||
auto PPUcounter::hdot() const -> uint16 {
|
||||
if(system.region() == System::Region::NTSC && status.interlace == false && vcounter() == 240 && field() == 1) {
|
||||
if(system.region() == System::Region::NTSC && status.interlace == 0 && vcounter() == 240 && field() == 1) {
|
||||
return (hcounter() >> 2);
|
||||
} else {
|
||||
return (hcounter() - ((hcounter() > 1292) << 1) - ((hcounter() > 1310) << 1)) >> 2;
|
||||
|
@ -66,12 +66,12 @@ auto PPUcounter::hdot() const -> uint16 {
|
|||
}
|
||||
|
||||
auto PPUcounter::lineclocks() const -> uint16 {
|
||||
if(system.region() == System::Region::NTSC && status.interlace == false && vcounter() == 240 && field() == 1) return 1360;
|
||||
if(system.region() == System::Region::NTSC && status.interlace == 0 && vcounter() == 240 && field() == 1) return 1360;
|
||||
return 1364;
|
||||
}
|
||||
|
||||
auto PPUcounter::reset() -> void {
|
||||
status.interlace = false;
|
||||
status.interlace = 0;
|
||||
status.field = 0;
|
||||
status.vcounter = 0;
|
||||
status.hcounter = 0;
|
||||
|
|
|
@ -2,30 +2,56 @@
|
|||
#include "hotkeys.cpp"
|
||||
unique_pointer<InputManager> inputManager;
|
||||
|
||||
//build mappings list from assignment string
|
||||
auto InputMapping::bind() -> void {
|
||||
auto token = assignment.split("/");
|
||||
if(token.size() < 3) return unbind();
|
||||
uint64 id = token[0].natural();
|
||||
uint group = token[1].natural();
|
||||
uint input = token[2].natural();
|
||||
string qualifier = token(3, "None");
|
||||
mappings.reset();
|
||||
|
||||
for(auto& device : inputManager->devices) {
|
||||
if(id != device->id()) continue;
|
||||
auto list = assignment.split(logic() == Logic::AND ? "&" : "|");
|
||||
for(auto& item : list) {
|
||||
auto token = item.split("/");
|
||||
if(token.size() < 3) continue; //skip invalid mappings
|
||||
|
||||
this->device = device;
|
||||
this->group = group;
|
||||
this->input = input;
|
||||
this->qualifier = Qualifier::None;
|
||||
if(qualifier == "Lo") this->qualifier = Qualifier::Lo;
|
||||
if(qualifier == "Hi") this->qualifier = Qualifier::Hi;
|
||||
if(qualifier == "Rumble") this->qualifier = Qualifier::Rumble;
|
||||
break;
|
||||
uint64 id = token[0].natural();
|
||||
uint group = token[1].natural();
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mapping.device) continue;
|
||||
mappings.append(mapping);
|
||||
}
|
||||
|
||||
settings[path].setValue(assignment);
|
||||
}
|
||||
|
||||
//append new mapping to mappings list
|
||||
auto InputMapping::bind(string mapping) -> void {
|
||||
auto list = assignment.split(logic() == Logic::AND ? "&" : "|");
|
||||
if(list.find(mapping)) return; //already in the mappings list
|
||||
if(!assignment || assignment == "None") {
|
||||
//create new mapping
|
||||
assignment = mapping;
|
||||
} else {
|
||||
//add additional mapping
|
||||
assignment.append(logic() == Logic::AND ? "&" : "|");
|
||||
assignment.append(mapping);
|
||||
}
|
||||
bind();
|
||||
}
|
||||
|
||||
auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool {
|
||||
if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) {
|
||||
return unbind(), true;
|
||||
|
@ -38,8 +64,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) {
|
||||
this->assignment = encoding;
|
||||
return bind(), true;
|
||||
return bind(encoding), true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,13 +72,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
|
||||
this->assignment = {encoding, "/Lo"};
|
||||
return bind(), true;
|
||||
return bind({encoding, "/Lo"}), true;
|
||||
}
|
||||
|
||||
if(newValue > +16384) {
|
||||
this->assignment = {encoding, "/Hi"};
|
||||
return bind(), true;
|
||||
return bind({encoding, "/Hi"}), true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +86,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) {
|
||||
this->assignment = encoding;
|
||||
return bind(), true;
|
||||
return bind(encoding), true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,8 +94,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) {
|
||||
this->assignment = {encoding, "/Rumble"};
|
||||
return bind(), true;
|
||||
return bind({encoding, "/Rumble"}), true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,62 +103,86 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|
|||
}
|
||||
|
||||
auto InputMapping::poll() -> int16 {
|
||||
if(!device) return 0;
|
||||
auto value = device->group(group).input(input).value();
|
||||
if(!mappings) return 0;
|
||||
|
||||
if(isDigital()) {
|
||||
if(device->isKeyboard() && group == HID::Keyboard::GroupID::Button) return value != 0;
|
||||
if(device->isMouse() && group == HID::Mouse::GroupID::Button) return value != 0;
|
||||
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) return value != 0;
|
||||
if((device->isJoypad() && group == HID::Joypad::GroupID::Axis)
|
||||
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)
|
||||
|| (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) {
|
||||
if(qualifier == Qualifier::Lo) return value < -16384;
|
||||
if(qualifier == Qualifier::Hi) return value > +16384;
|
||||
bool result = logic() == Logic::AND ? 1 : 0;
|
||||
|
||||
for(auto& mapping : mappings) {
|
||||
auto value = mapping.device->group(mapping.group).input(mapping.input).value();
|
||||
bool output = logic() == Logic::AND ? 0 : 1;
|
||||
|
||||
if(mapping.device->isKeyboard() && mapping.group == HID::Keyboard::GroupID::Button) output = value != 0;
|
||||
if(mapping.device->isMouse() && mapping.group == HID::Mouse::GroupID::Button) output = value != 0;
|
||||
if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Button) output = value != 0;
|
||||
if((mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Axis)
|
||||
|| (mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Hat)
|
||||
|| (mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Trigger)) {
|
||||
if(mapping.qualifier == Qualifier::Lo) output = value < -16384;
|
||||
if(mapping.qualifier == Qualifier::Hi) output = value > +16384;
|
||||
}
|
||||
|
||||
if(logic() == Logic::AND) result &= output;
|
||||
if(logic() == Logic::OR ) result |= output;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if(isAnalog()) {
|
||||
if(device->isMouse() && group == HID::Mouse::GroupID::Axis) return value;
|
||||
if(device->isJoypad() && group == HID::Joypad::GroupID::Axis) return value >> 8;
|
||||
if(device->isJoypad() && group == HID::Joypad::GroupID::Hat) return value < 0 ? -1 : value > 0 ? +1 : 0;
|
||||
int16 result = 0;
|
||||
|
||||
for(auto& mapping : mappings) {
|
||||
auto value = mapping.device->group(mapping.group).input(mapping.input).value();
|
||||
|
||||
//logic does not apply to analog inputs ... always combinatorial
|
||||
if(mapping.device->isMouse() && mapping.group == HID::Mouse::GroupID::Axis) result += value;
|
||||
if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Axis) result += value >> 8;
|
||||
if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Hat) result += value < 0 ? -1 : value > 0 ? +1 : 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto InputMapping::rumble(bool enable) -> void {
|
||||
if(!device) return;
|
||||
::input->rumble(device->id(), enable);
|
||||
if(!mappings) return;
|
||||
for(auto& mapping : mappings) {
|
||||
::input->rumble(mapping.device->id(), enable);
|
||||
}
|
||||
}
|
||||
|
||||
auto InputMapping::unbind() -> void {
|
||||
mappings.reset();
|
||||
assignment = "None";
|
||||
device = nullptr;
|
||||
group = 0;
|
||||
input = 0;
|
||||
qualifier = Qualifier::None;
|
||||
settings[path].setValue(assignment);
|
||||
}
|
||||
|
||||
auto InputMapping::assignmentName() -> string {
|
||||
if(!device) return "None";
|
||||
string path;
|
||||
path.append(device->name());
|
||||
if(device->name() != "Keyboard") {
|
||||
//keyboards only have one group; no need to append group name
|
||||
path.append(".", device->group(group).name());
|
||||
}
|
||||
path.append(".", device->group(group).input(input).name());
|
||||
if(qualifier == Qualifier::Lo) path.append(".Lo");
|
||||
if(qualifier == Qualifier::Hi) path.append(".Hi");
|
||||
if(qualifier == Qualifier::Rumble) path.append(".Rumble");
|
||||
return path;
|
||||
}
|
||||
//create a human-readable string from mappings list for display in the user interface
|
||||
auto InputMapping::displayName() -> string {
|
||||
if(!mappings) return "None";
|
||||
|
||||
auto InputMapping::deviceName() -> string {
|
||||
if(!device) return "";
|
||||
return hex(device->id());
|
||||
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") {
|
||||
//keyboards only have one group; no need to append group name
|
||||
path.append(".", mapping.device->group(mapping.group).name());
|
||||
}
|
||||
path.append(".", mapping.device->group(mapping.group).input(mapping.input).name());
|
||||
if(mapping.qualifier == Qualifier::Lo) path.append(".Lo");
|
||||
if(mapping.qualifier == Qualifier::Hi) path.append(".Hi");
|
||||
if(mapping.qualifier == Qualifier::Rumble) path.append(".Rumble");
|
||||
path.append(logic() == Logic::AND ? " and " : " or ");
|
||||
}
|
||||
|
||||
return path.trimRight(logic() == Logic::AND ? " and " : " or ", 1L);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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 poll() -> int16;
|
||||
auto rumble(bool enable) -> void;
|
||||
|
@ -9,20 +10,28 @@ struct InputMapping {
|
|||
auto isAnalog() const -> bool { return type == 1; }
|
||||
auto isRumble() const -> bool { return type == 2; }
|
||||
|
||||
auto assignmentName() -> string;
|
||||
auto deviceName() -> string;
|
||||
auto displayName() -> string;
|
||||
|
||||
string path; //configuration file key path
|
||||
string name; //input name (human readable)
|
||||
uint type = 0;
|
||||
string assignment = "None";
|
||||
shared_pointer<HID::Device> device;
|
||||
uint group = 0;
|
||||
uint input = 0;
|
||||
enum class Qualifier : uint { None, Lo, Hi, Rumble } qualifier = Qualifier::None;
|
||||
|
||||
enum class Logic : uint { AND, OR };
|
||||
enum class Qualifier : uint { None, Lo, Hi, Rumble };
|
||||
virtual auto logic() const -> Logic { return Logic::OR; }
|
||||
struct Mapping {
|
||||
shared_pointer<HID::Device> device;
|
||||
uint group = 0;
|
||||
uint input = 0;
|
||||
Qualifier qualifier = Qualifier::None;
|
||||
};
|
||||
vector<Mapping> mappings;
|
||||
};
|
||||
|
||||
struct InputHotkey : InputMapping {
|
||||
auto logic() const -> Logic override { return Logic::AND; }
|
||||
|
||||
function<auto () -> void> press;
|
||||
function<auto () -> void> release;
|
||||
|
||||
|
|
|
@ -29,13 +29,11 @@ auto HotkeySettings::reloadMappings() -> void {
|
|||
mappingList.append(TableViewHeader().setVisible()
|
||||
.append(TableViewColumn().setText("Name"))
|
||||
.append(TableViewColumn().setText("Mapping").setExpandable())
|
||||
.append(TableViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0}))
|
||||
);
|
||||
for(auto& hotkey : inputManager->hotkeys) {
|
||||
mappingList.append(TableViewItem()
|
||||
.append(TableViewCell().setText(hotkey->name))
|
||||
.append(TableViewCell())
|
||||
.append(TableViewCell())
|
||||
);
|
||||
}
|
||||
mappingList.resizeColumns();
|
||||
|
@ -44,8 +42,7 @@ auto HotkeySettings::reloadMappings() -> void {
|
|||
auto HotkeySettings::refreshMappings() -> void {
|
||||
uint position = 0;
|
||||
for(auto& hotkey : inputManager->hotkeys) {
|
||||
mappingList.item(position).cell(1).setText(hotkey->assignmentName());
|
||||
mappingList.item(position).cell(2).setText(hotkey->deviceName());
|
||||
mappingList.item(position).cell(1).setText(hotkey->displayName());
|
||||
position++;
|
||||
}
|
||||
mappingList.resizeColumns();
|
||||
|
|
|
@ -95,13 +95,11 @@ auto InputSettings::reloadMappings() -> void {
|
|||
mappingList.append(TableViewHeader().setVisible()
|
||||
.append(TableViewColumn().setText("Name"))
|
||||
.append(TableViewColumn().setText("Mapping").setExpandable())
|
||||
.append(TableViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0}))
|
||||
);
|
||||
for(auto& mapping : activeDevice().mappings) {
|
||||
mappingList.append(TableViewItem()
|
||||
.append(TableViewCell().setText(mapping.name))
|
||||
.append(TableViewCell())
|
||||
.append(TableViewCell())
|
||||
);
|
||||
}
|
||||
refreshMappings();
|
||||
|
@ -110,8 +108,7 @@ auto InputSettings::reloadMappings() -> void {
|
|||
auto InputSettings::refreshMappings() -> void {
|
||||
uint position = 0;
|
||||
for(auto& mapping : activeDevice().mappings) {
|
||||
mappingList.item(position).cell(1).setText(mapping.assignmentName());
|
||||
mappingList.item(position).cell(2).setText(mapping.deviceName());
|
||||
mappingList.item(position).cell(1).setText(mapping.displayName());
|
||||
position++;
|
||||
}
|
||||
mappingList.resizeColumns();
|
||||
|
|
|
@ -24,7 +24,7 @@ struct Pair {
|
|||
auto operator!() const -> bool { return !(hi || lo); }
|
||||
|
||||
auto operator++() -> Pair& { lo++; hi += lo == 0; return *this; }
|
||||
auto operator--() -> Pair& { hi -= lo == 0; lo--; }
|
||||
auto operator--() -> Pair& { hi -= lo == 0; lo--; return *this; }
|
||||
|
||||
auto operator++(int) -> Pair { Pair r = *this; lo++; hi += lo == 0; return r; }
|
||||
auto operator--(int) -> Pair { Pair r = *this; hi -= lo == 0; lo--; return r; }
|
||||
|
|
Loading…
Reference in New Issue