mirror of https://github.com/bsnes-emu/bsnes.git
Update to v088r12 release.
byuu says: Changelog: - all hotkeys from target-ui now exist in target-ethos - controller port menus now show up when you load a system (hidden if there are no options to choose from) - tools menu auto-hides with no game open ... not much point to it then - since we aren't using RawInput's multi-KB/MS support anyway, input and hotkey mappings remove KB0:: and turn MS0:: into Mouse::, makes it a lot easier to read - added mute audio, sync video, sync audio, mask overscan - added video settings: saturation, gamma, luminance, overscan horizontal, overscan vertical - added audio settings: frequency, latency, resampler, volume - added input settings: when focus is lost [ ] pause emulator [ ] allow input - pausing and autopausing works - status messages hooked up (show a message in status bar for a few seconds, then revert to normal status text) - sub systems (SGB, BSX, ST) sorted below primary systems list - added geometry settings cache - Emulator::Interface cleanups and simplifications - save states go into (cart foldername.extension/bsnes/state-#.bsa) now. Idea is to put emulator-specific data in their own subfolders Caveats / Missing: - SGB input does not work - Sufami Turbo second slot doesn't work yet - BS-X BIOS won't show the data pack - need XML mapping information window - need cheat editor and cheat database - need state manager - need video shaders - need driver selection - need NSS DIP switch settings - need to hide controllers that have no inputs from the input mapping list So for video settings, I used to have contrast/brightness/gamma. Contrast was just a multiplier on intensity of each channel, and brightness was an addition or subtraction against each channel. They kind of overlapped and weren't that effective. The new setup has saturation, gamma and luminance. Saturation of 100% is normal. If you lower it, color information goes away. 0% = grayscale. If you raise it, color intensity increases (and clamps.) This is wonderful for GBA games, since they are oversaturated to fucking death. Of course we'll want to normalize that inside the core, so the same sat. value works on all systems, but for now it's nice. If you raise saturation above 100%, it basically acts like contrast used to. It's just that lowering it fades to grayscale rather than black. Adding doesn't really work well for brightness, it throws off the relative distance between channels and looks like shit. So now we have luminance, which takes over the old contrast <100% role, and just fades the pixels toward black. Obviously, luminance > 100% would be the same as saturation > 100%, so that isn't allowed, it caps at 100% now. Gamma's the same old function. Gamma curve on the lower-half of the color range. Effects are applied in the order they appear in the GUI: color -> saturate -> gammify -> luminate -> output.
This commit is contained in:
parent
8703d57030
commit
5d273c5265
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
static const char Name[] = "bsnes";
|
||||
static const char Version[] = "088.11";
|
||||
static const char Version[] = "088.12";
|
||||
static const char Author[] = "byuu";
|
||||
static const char License[] = "GPLv3";
|
||||
}
|
||||
|
|
|
@ -8,34 +8,31 @@ struct Interface {
|
|||
string name;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
bool overscan;
|
||||
double aspectRatio;
|
||||
unsigned frequency;
|
||||
bool resettable;
|
||||
|
||||
struct Media {
|
||||
string name;
|
||||
string filter;
|
||||
string extension;
|
||||
};
|
||||
vector<Media> media;
|
||||
} information;
|
||||
|
||||
struct Firmware {
|
||||
string displayname;
|
||||
string name;
|
||||
unsigned id;
|
||||
};
|
||||
vector<Firmware> firmware;
|
||||
|
||||
struct Media {
|
||||
string displayname;
|
||||
string path;
|
||||
string name;
|
||||
string filter;
|
||||
unsigned id;
|
||||
string name;
|
||||
string extension;
|
||||
string path;
|
||||
};
|
||||
vector<Media> firmware;
|
||||
|
||||
struct Schema : Media {
|
||||
vector<Media> slot;
|
||||
Schema(const Media &media) {
|
||||
id = media.id, name = media.name, extension = media.extension, path = media.path;
|
||||
}
|
||||
};
|
||||
vector<Schema> schema;
|
||||
|
||||
|
@ -46,19 +43,19 @@ struct Interface {
|
|||
vector<Memory> memory;
|
||||
|
||||
struct Port {
|
||||
string name;
|
||||
unsigned id;
|
||||
string name;
|
||||
struct Device {
|
||||
string name;
|
||||
unsigned id;
|
||||
string name;
|
||||
struct Input {
|
||||
string name;
|
||||
unsigned type; //0 = digital, 1 = analog
|
||||
unsigned id;
|
||||
unsigned type; //0 = digital, 1 = analog
|
||||
string name;
|
||||
unsigned guid;
|
||||
};
|
||||
vector<Input> input;
|
||||
vector<unsigned> displayinput;
|
||||
vector<unsigned> order;
|
||||
};
|
||||
vector<Device> device;
|
||||
};
|
||||
|
@ -72,7 +69,7 @@ struct Interface {
|
|||
function<void (Media)> mediaRequest;
|
||||
} callback;
|
||||
|
||||
//audio/visual bindings (provided by user interface)
|
||||
//callback bindings (provided by user interface)
|
||||
virtual uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) {
|
||||
if(callback.videoColor) return callback.videoColor(source, red, green, blue);
|
||||
return (red >> 8) << 16 | (green >> 8) << 8 | (blue >> 8) << 0;
|
||||
|
@ -102,10 +99,15 @@ struct Interface {
|
|||
virtual void unload() {}
|
||||
|
||||
//system interface
|
||||
virtual void connect(unsigned port, unsigned device) {}
|
||||
virtual void power() {}
|
||||
virtual void reset() {}
|
||||
virtual void run() {}
|
||||
|
||||
//state functions
|
||||
virtual serializer serialize() = 0;
|
||||
virtual bool unserialize(serializer&) = 0;
|
||||
|
||||
//utility functions
|
||||
virtual void updatePalette() {}
|
||||
};
|
||||
|
|
|
@ -43,6 +43,15 @@ void Interface::run() {
|
|||
system.run();
|
||||
}
|
||||
|
||||
serializer Interface::serialize() {
|
||||
system.runtosave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
bool Interface::unserialize(serializer &s) {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -53,64 +62,40 @@ Interface::Interface() {
|
|||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.frequency = 1789772;
|
||||
information.resettable = true;
|
||||
|
||||
information.media.append({"Famicom", "*.fc"});
|
||||
information.media.append({"Famicom", "fc"});
|
||||
|
||||
schema.append(Media{ID::ROM, "Famicom", "fc", "program.rom"});
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Famicom";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.fc";
|
||||
schema.id = ID::ROM;
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Port port;
|
||||
port.name = "Port 1";
|
||||
port.id = 0;
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "Controller";
|
||||
device.id = 0;
|
||||
device.input.append({"A", 0, 0});
|
||||
device.input.append({"B", 0, 1});
|
||||
device.input.append({"Select", 0, 2});
|
||||
device.input.append({"Start", 0, 3});
|
||||
device.input.append({"Up", 0, 4});
|
||||
device.input.append({"Down", 0, 5});
|
||||
device.input.append({"Left", 0, 6});
|
||||
device.input.append({"Right", 0, 7});
|
||||
device.displayinput = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
port.device.append(device);
|
||||
}
|
||||
Port port{0, "Port 1"};
|
||||
port.device.append(controller());
|
||||
this->port.append(port);
|
||||
}
|
||||
|
||||
{
|
||||
Port port;
|
||||
port.name = "Port 2";
|
||||
port.id = 1;
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "Controller";
|
||||
device.id = 0;
|
||||
device.input.append({"A", 0, 0});
|
||||
device.input.append({"B", 0, 1});
|
||||
device.input.append({"Select", 0, 2});
|
||||
device.input.append({"Start", 0, 3});
|
||||
device.input.append({"Up", 0, 4});
|
||||
device.input.append({"Down", 0, 5});
|
||||
device.input.append({"Left", 0, 6});
|
||||
device.input.append({"Right", 0, 7});
|
||||
device.displayinput = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
port.device.append(device);
|
||||
}
|
||||
Port port{1, "Port 2"};
|
||||
port.device.append(controller());
|
||||
this->port.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::controller() {
|
||||
Port::Device device{0, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.input.append({4, 0, "Up" });
|
||||
device.input.append({5, 0, "Down" });
|
||||
device.input.append({6, 0, "Left" });
|
||||
device.input.append({7, 0, "Right" });
|
||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
return device;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,9 +19,15 @@ struct Interface : Emulator::Interface {
|
|||
void reset();
|
||||
void run();
|
||||
|
||||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
||||
private:
|
||||
Port::Device controller();
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
||||
|
|
|
@ -58,6 +58,15 @@ void Interface::run() {
|
|||
system.run();
|
||||
}
|
||||
|
||||
serializer Interface::serialize() {
|
||||
system.runtosave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
bool Interface::unserialize(serializer &s) {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -68,72 +77,34 @@ Interface::Interface() {
|
|||
information.name = "Game Boy";
|
||||
information.width = 160;
|
||||
information.height = 144;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.frequency = 4194304;
|
||||
information.resettable = false;
|
||||
|
||||
information.media.append({"Game Boy", "*.gb"});
|
||||
information.media.append({"Game Boy Color", "*.gbc"});
|
||||
information.media.append({"Game Boy", "gb" });
|
||||
information.media.append({"Game Boy Color", "gbc"});
|
||||
|
||||
firmware.append({ID::GameBoyBootROM, "Game Boy", "sys", "boot.rom"});
|
||||
firmware.append({ID::SuperGameBoyBootROM, "Super Game Boy", "sfc", "boot.rom"});
|
||||
firmware.append({ID::GameBoyColorBootROM, "Game Boy Color", "sys", "boot.rom"});
|
||||
|
||||
schema.append(Media{ID::GameBoyROM, "Game Boy", "gb", "program.rom"});
|
||||
schema.append(Media{ID::GameBoyColorROM, "Game Boy Color", "gbc", "program.rom"});
|
||||
|
||||
{
|
||||
Firmware firmware;
|
||||
firmware.displayname = "Game Boy";
|
||||
firmware.name = "Game Boy.sys/boot.rom";
|
||||
firmware.id = ID::GameBoyBootROM;
|
||||
this->firmware.append(firmware);
|
||||
}
|
||||
|
||||
{
|
||||
Firmware firmware;
|
||||
firmware.displayname = "Super Game Boy";
|
||||
firmware.name = "Super Game Boy.sfc/boot.rom";
|
||||
firmware.id = ID::SuperGameBoyBootROM;
|
||||
this->firmware.append(firmware);
|
||||
}
|
||||
|
||||
{
|
||||
Firmware firmware;
|
||||
firmware.displayname = "Game Boy Color";
|
||||
firmware.name = "Game Boy Color.sys/boot.rom";
|
||||
firmware.id = ID::GameBoyColorBootROM;
|
||||
this->firmware.append(firmware);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Game Boy";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.gb";
|
||||
schema.id = ID::GameBoyROM;
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Game Boy Color";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.gbc";
|
||||
schema.id = ID::GameBoyColorROM;
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Port port;
|
||||
port.name = "Device";
|
||||
port.id = 0;
|
||||
Port port{0, "Device"};
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "Controller";
|
||||
device.id = 0;
|
||||
device.input.append({"Up", 0, 0});
|
||||
device.input.append({"Down", 0, 1});
|
||||
device.input.append({"Left", 0, 2});
|
||||
device.input.append({"Right", 0, 3});
|
||||
device.input.append({"B", 0, 4});
|
||||
device.input.append({"A", 0, 5});
|
||||
device.input.append({"Select", 0, 6});
|
||||
device.input.append({"Start", 0, 7});
|
||||
device.displayinput = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
Port::Device device{0, "Controller"};
|
||||
device.input.append({0, 0, "Up" });
|
||||
device.input.append({1, 0, "Down" });
|
||||
device.input.append({2, 0, "Left" });
|
||||
device.input.append({3, 0, "Right" });
|
||||
device.input.append({4, 0, "B" });
|
||||
device.input.append({5, 0, "A" });
|
||||
device.input.append({6, 0, "Select"});
|
||||
device.input.append({7, 0, "Start" });
|
||||
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
port.device.append(device);
|
||||
}
|
||||
this->port.append(port);
|
||||
|
|
|
@ -27,6 +27,9 @@ struct Interface : Emulator::Interface {
|
|||
void reset();
|
||||
void run();
|
||||
|
||||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
|
|
@ -62,6 +62,15 @@ void Interface::run() {
|
|||
system.run();
|
||||
}
|
||||
|
||||
serializer Interface::serialize() {
|
||||
system.runtosave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
bool Interface::unserialize(serializer &s) {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -72,48 +81,32 @@ Interface::Interface() {
|
|||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.frequency = 32768;
|
||||
information.resettable = false;
|
||||
|
||||
information.media.append({"Game Boy Advance", "*.gba"});
|
||||
information.media.append({"Game Boy Advance", "gba"});
|
||||
|
||||
firmware.append({ID::BIOS, "Game Boy Advance", "sys", "bios.rom"});
|
||||
|
||||
schema.append(Media{ID::ROM, "Game Boy Advance", "gba", "program.rom"});
|
||||
|
||||
{
|
||||
Firmware firmware;
|
||||
firmware.displayname = "Game Boy Advance";
|
||||
firmware.name = "Game Boy Advance.sys/bios.rom";
|
||||
firmware.id = ID::BIOS;
|
||||
this->firmware.append(firmware);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Game Boy Advance";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.gba";
|
||||
schema.id = ID::ROM;
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Port port;
|
||||
port.name = "Device";
|
||||
port.id = 0;
|
||||
Port port{0, "Device"};
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "Controller";
|
||||
device.id = 0;
|
||||
device.input.append({"A", 0, 0});
|
||||
device.input.append({"B", 0, 1});
|
||||
device.input.append({"Select", 0, 2});
|
||||
device.input.append({"Start", 0, 3});
|
||||
device.input.append({"Right", 0, 4});
|
||||
device.input.append({"Left", 0, 5});
|
||||
device.input.append({"Up", 0, 6});
|
||||
device.input.append({"Down", 0, 7});
|
||||
device.input.append({"R", 0, 8});
|
||||
device.input.append({"L", 0, 9});
|
||||
device.displayinput = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
|
||||
Port::Device device{0, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.input.append({4, 0, "Right" });
|
||||
device.input.append({5, 0, "Left" });
|
||||
device.input.append({6, 0, "Up" });
|
||||
device.input.append({7, 0, "Down" });
|
||||
device.input.append({8, 0, "R" });
|
||||
device.input.append({9, 0, "L" });
|
||||
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
|
||||
port.device.append(device);
|
||||
}
|
||||
this->port.append(port);
|
||||
|
|
|
@ -22,6 +22,9 @@ struct Interface : Emulator::Interface {
|
|||
void reset();
|
||||
void run();
|
||||
|
||||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
|
|
@ -110,7 +110,7 @@ void Cartridge::parse_markup_icd2(XML::Node &root) {
|
|||
if(root.exists() == false) return;
|
||||
has_gb_slot = true;
|
||||
|
||||
interface->mediaRequest({"Game Boy", "", "program.rom", "*.gb", 5});
|
||||
interface->mediaRequest({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
|
||||
|
||||
icd2.revision = max(1, numeral(root["revision"].data));
|
||||
|
||||
|
@ -372,7 +372,7 @@ void Cartridge::parse_markup_bsx(XML::Node &root) {
|
|||
has_bs_cart = root["mmio"].exists();
|
||||
has_bs_slot = true;
|
||||
|
||||
interface->mediaRequest({"BS-X Satellaview", "", "program.rom", "*.bs", 2});
|
||||
interface->mediaRequest({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
|
||||
|
||||
for(auto &node : root["slot"]) {
|
||||
if(node.name != "map") continue;
|
||||
|
@ -400,7 +400,7 @@ void Cartridge::parse_markup_sufamiturbo(XML::Node &root) {
|
|||
if(root.exists() == false) return;
|
||||
has_st_slot = true;
|
||||
|
||||
interface->mediaRequest({"Sufami Turbo", "", "program.rom", "*.st", 3});
|
||||
interface->mediaRequest({ID::SufamiTurboSlotAROM, "Sufami Turbo", "st", "program.rom"});
|
||||
|
||||
for(auto &slot : root) {
|
||||
if(slot.name != "slot") continue;
|
||||
|
|
|
@ -18,8 +18,8 @@ void Justifier::enter() {
|
|||
}
|
||||
|
||||
if(next < prev) {
|
||||
int nx1 = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::X);
|
||||
int ny1 = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Y);
|
||||
int nx1 = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::X);
|
||||
int ny1 = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Y);
|
||||
nx1 += player1.x;
|
||||
ny1 += player1.y;
|
||||
player1.x = max(-16, min(256 + 16, nx1));
|
||||
|
@ -27,8 +27,8 @@ void Justifier::enter() {
|
|||
}
|
||||
|
||||
if(next < prev && chained) {
|
||||
int nx2 = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::X);
|
||||
int ny2 = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Y);
|
||||
int nx2 = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::X);
|
||||
int ny2 = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Y);
|
||||
nx2 += player2.x;
|
||||
ny2 += player2.y;
|
||||
player2.x = max(-16, min(256 + 16, nx2));
|
||||
|
@ -44,13 +44,13 @@ uint2 Justifier::data() {
|
|||
if(counter >= 32) return 1;
|
||||
|
||||
if(counter == 0) {
|
||||
player1.trigger = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Trigger);
|
||||
player1.start = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Start);
|
||||
player1.trigger = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Trigger);
|
||||
player1.start = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Start);
|
||||
}
|
||||
|
||||
if(counter == 0 && chained) {
|
||||
player2.trigger = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Trigger);
|
||||
player2.start = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Start);
|
||||
player2.trigger = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Trigger);
|
||||
player2.start = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Start);
|
||||
}
|
||||
|
||||
switch(counter++) {
|
||||
|
@ -100,7 +100,11 @@ void Justifier::latch(bool data) {
|
|||
if(latched == 0) active = !active; //toggle between both controllers, even when unchained
|
||||
}
|
||||
|
||||
Justifier::Justifier(bool port, bool chained) : Controller(port), chained(chained) {
|
||||
Justifier::Justifier(bool port, bool chained):
|
||||
Controller(port),
|
||||
chained(chained),
|
||||
device(chained == false ? (unsigned)Input::Device::Justifier : (unsigned)Input::Device::Justifiers)
|
||||
{
|
||||
create(Controller::Enter, 21477272);
|
||||
latched = 0;
|
||||
counter = 0;
|
||||
|
|
|
@ -6,6 +6,7 @@ struct Justifier : Controller {
|
|||
|
||||
//private:
|
||||
const bool chained; //true if the second justifier is attached to the first
|
||||
const unsigned device;
|
||||
bool latched;
|
||||
unsigned counter;
|
||||
|
||||
|
|
|
@ -8,18 +8,20 @@ uint2 Multitap::data() {
|
|||
index = counter1;
|
||||
if(index >= 16) return 3;
|
||||
counter1++;
|
||||
if(index >= 12) return 0;
|
||||
port1 = 0; //controller 1
|
||||
port2 = 1; //controller 2
|
||||
} else {
|
||||
index = counter2;
|
||||
if(index >= 16) return 3;
|
||||
counter2++;
|
||||
if(index >= 12) return 0;
|
||||
port1 = 2; //controller 3
|
||||
port2 = 3; //controller 4
|
||||
}
|
||||
|
||||
bool data1 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port1 << 16 | index);
|
||||
bool data2 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port2 << 16 | index);
|
||||
bool data1 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port1 * 12 + index);
|
||||
bool data2 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port2 * 12 + index);
|
||||
return (data2 << 1) | (data1 << 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,8 @@ uint2 USART::data() {
|
|||
//Joypad
|
||||
if(iobit()) {
|
||||
if(counter >= 16) return 1;
|
||||
uint2 result = interface->inputPoll(port, (unsigned)Input::Device::Joypad, counter);
|
||||
uint2 result = 0;
|
||||
if(counter < 12) result = interface->inputPoll(port, (unsigned)Input::Device::Joypad, counter);
|
||||
if(latched == 0) counter++;
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,10 @@ void Interface::unload() {
|
|||
cartridge.unload();
|
||||
}
|
||||
|
||||
void Interface::connect(unsigned port, unsigned device) {
|
||||
input.connect(port, (Input::Device)device);
|
||||
}
|
||||
|
||||
void Interface::power() {
|
||||
system.power();
|
||||
}
|
||||
|
@ -95,6 +99,15 @@ void Interface::run() {
|
|||
system.run();
|
||||
}
|
||||
|
||||
serializer Interface::serialize() {
|
||||
system.runtosave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
bool Interface::unserialize(serializer &s) {
|
||||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -105,157 +118,159 @@ Interface::Interface() {
|
|||
information.name = "Super Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.frequency = 32040;
|
||||
information.resettable = true;
|
||||
|
||||
information.media.append({"Super Famicom", "*.sfc"});
|
||||
information.media.append({"BS-X Satellaview", "*.bs"});
|
||||
information.media.append({"Sufami Turbo", "*.st"});
|
||||
information.media.append({"Super Game Boy", "*.gb"});
|
||||
information.media.append({"Super Famicom", "sfc"});
|
||||
information.media.append({"BS-X Satellaview", "bs" });
|
||||
information.media.append({"Sufami Turbo", "st" });
|
||||
information.media.append({"Super Game Boy", "gb" });
|
||||
|
||||
firmware.append({ID::IPLROM, "Super Famicom", "sys", "spc700.rom"});
|
||||
|
||||
{
|
||||
Firmware firmware;
|
||||
firmware.displayname = "Super Famicom";
|
||||
firmware.name = "Super Famicom.sys/spc700.rom";
|
||||
firmware.id = ID::IPLROM;
|
||||
this->firmware.append(firmware);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Super Famicom";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.sfc";
|
||||
schema.id = ID::ROM;
|
||||
Schema schema(Media{ID::ROM, "Super Famicom", "sfc", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Super Game Boy";
|
||||
schema.path = "Super Game Boy.sfc/";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.sfc";
|
||||
schema.id = ID::ROM;
|
||||
{
|
||||
Media slot;
|
||||
slot.displayname = "Game Boy";
|
||||
slot.name = "program.rom";
|
||||
slot.filter = "*.gb";
|
||||
slot.id = ID::SuperGameBoyROM;
|
||||
schema.slot.append(schema);
|
||||
}
|
||||
Schema schema(Media{ID::ROM, "Super Game Boy", "sfc", "program.rom"});
|
||||
schema.slot.append({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "BS-X Satellaview";
|
||||
schema.path = "BS-X Satellaview.sfc/";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.sfc";
|
||||
schema.id = ID::ROM;
|
||||
{
|
||||
Media slot;
|
||||
slot.displayname = "BS-X Satellaview";
|
||||
slot.name = "program.rom";
|
||||
slot.filter = "*.bs";
|
||||
slot.id = ID::BsxFlashROM;
|
||||
schema.slot.append(slot);
|
||||
}
|
||||
Schema schema(Media{ID::ROM, "BS-X Satellaview", "sfc", "program.rom"});
|
||||
schema.slot.append({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema;
|
||||
schema.displayname = "Sufami Turbo";
|
||||
schema.path = "Sufami Turbo.sfc/";
|
||||
schema.name = "program.rom";
|
||||
schema.filter = "*.sfc";
|
||||
schema.id = ID::ROM;
|
||||
{
|
||||
Media slot;
|
||||
slot.displayname = "Sufami Turbo - Slot A";
|
||||
slot.name = "program.rom";
|
||||
slot.filter = "*.st";
|
||||
slot.id = ID::SufamiTurboSlotAROM;
|
||||
schema.slot.append(slot);
|
||||
}
|
||||
{
|
||||
Media slot;
|
||||
slot.displayname = "Sufami Turbo - Slot B";
|
||||
slot.name = "program.rom";
|
||||
slot.filter = "*.st";
|
||||
slot.id = ID::SufamiTurboSlotBROM;
|
||||
schema.slot.append(slot);
|
||||
}
|
||||
Schema schema(Media{ID::ROM, "Sufami Turbo", "sfc", "program.rom"});
|
||||
schema.slot.append({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "st", "program.rom"});
|
||||
schema.slot.append({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "st", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Port port;
|
||||
port.name = "Port 1";
|
||||
port.id = 0;
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "None";
|
||||
device.id = 0;
|
||||
port.device.append(device);
|
||||
}
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "Controller";
|
||||
device.id = 1;
|
||||
device.input.append({"B", 0, 0});
|
||||
device.input.append({"Y", 0, 1});
|
||||
device.input.append({"Select", 0, 2});
|
||||
device.input.append({"Start", 0, 3});
|
||||
device.input.append({"Up", 0, 4});
|
||||
device.input.append({"Down", 0, 5});
|
||||
device.input.append({"Left", 0, 6});
|
||||
device.input.append({"Right", 0, 7});
|
||||
device.input.append({"A", 0, 8});
|
||||
device.input.append({"X", 0, 9});
|
||||
device.input.append({"L", 0, 10});
|
||||
device.input.append({"R", 0, 11});
|
||||
device.displayinput = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
|
||||
port.device.append(device);
|
||||
}
|
||||
Port port{0, "Port 1"};
|
||||
port.device.append(none());
|
||||
port.device.append(controller());
|
||||
port.device.append(multitap());
|
||||
port.device.append(mouse());
|
||||
port.device.append(usart());
|
||||
this->port.append(port);
|
||||
}
|
||||
|
||||
{
|
||||
Port port;
|
||||
port.name = "Port 2";
|
||||
port.id = 1;
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "None";
|
||||
device.id = 0;
|
||||
port.device.append(device);
|
||||
}
|
||||
{
|
||||
Port::Device device;
|
||||
device.name = "Controller";
|
||||
device.id = 1;
|
||||
device.input.append({"B", 0, 0});
|
||||
device.input.append({"Y", 0, 1});
|
||||
device.input.append({"Select", 0, 2});
|
||||
device.input.append({"Start", 0, 3});
|
||||
device.input.append({"Up", 0, 4});
|
||||
device.input.append({"Down", 0, 5});
|
||||
device.input.append({"Left", 0, 6});
|
||||
device.input.append({"Right", 0, 7});
|
||||
device.input.append({"A", 0, 8});
|
||||
device.input.append({"X", 0, 9});
|
||||
device.input.append({"L", 0, 10});
|
||||
device.input.append({"R", 0, 11});
|
||||
device.displayinput = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
|
||||
port.device.append(device);
|
||||
}
|
||||
Port port{1, "Port 2"};
|
||||
port.device.append(none());
|
||||
port.device.append(controller());
|
||||
port.device.append(multitap());
|
||||
port.device.append(mouse());
|
||||
port.device.append(superScope());
|
||||
port.device.append(justifier());
|
||||
port.device.append(justifiers());
|
||||
this->port.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::none() {
|
||||
Port::Device device{0, "None"};
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::controller() {
|
||||
Port::Device device{1, "Controller"};
|
||||
device.input.append({ 0, 0, "B" });
|
||||
device.input.append({ 1, 0, "Y" });
|
||||
device.input.append({ 2, 0, "Select"});
|
||||
device.input.append({ 3, 0, "Start" });
|
||||
device.input.append({ 4, 0, "Up" });
|
||||
device.input.append({ 5, 0, "Down" });
|
||||
device.input.append({ 6, 0, "Left" });
|
||||
device.input.append({ 7, 0, "Right" });
|
||||
device.input.append({ 8, 0, "A" });
|
||||
device.input.append({ 9, 0, "X" });
|
||||
device.input.append({10, 0, "L" });
|
||||
device.input.append({11, 0, "R" });
|
||||
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::multitap() {
|
||||
Port::Device device{2, "Multitap"};
|
||||
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
|
||||
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
|
||||
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
|
||||
device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}});
|
||||
device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }});
|
||||
device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }});
|
||||
device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }});
|
||||
device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }});
|
||||
device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }});
|
||||
device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }});
|
||||
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
|
||||
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
|
||||
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
|
||||
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
|
||||
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::mouse() {
|
||||
Port::Device device{3, "Mouse"};
|
||||
device.input.append({0, 1, "X-axis"});
|
||||
device.input.append({1, 1, "Y-axis"});
|
||||
device.input.append({2, 0, "Left" });
|
||||
device.input.append({3, 0, "Right" });
|
||||
device.order = {0, 1, 2, 3};
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::superScope() {
|
||||
Port::Device device{4, "Super Scope"};
|
||||
device.input.append({0, 1, "X-axis" });
|
||||
device.input.append({1, 1, "Y-axis" });
|
||||
device.input.append({2, 0, "Trigger"});
|
||||
device.input.append({3, 0, "Cursor" });
|
||||
device.input.append({4, 0, "Turbo" });
|
||||
device.input.append({5, 0, "Pause" });
|
||||
device.order = {0, 1, 2, 3, 4, 5};
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::justifier() {
|
||||
Port::Device device{5, "Justifier"};
|
||||
device.input.append({0, 1, "X-axis" });
|
||||
device.input.append({1, 1, "Y-axis" });
|
||||
device.input.append({2, 0, "Trigger"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.order = {0, 1, 2, 3};
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::justifiers() {
|
||||
Port::Device device{6, "Justifiers"};
|
||||
device.input.append({0, 1, "Port 1 - X-axis" });
|
||||
device.input.append({1, 1, "Port 1 - Y-axis" });
|
||||
device.input.append({2, 0, "Port 1 - Trigger"});
|
||||
device.input.append({3, 0, "Port 1 - Start" });
|
||||
device.order.append(0, 1, 2, 3);
|
||||
device.input.append({4, 1, "Port 2 - X-axis" });
|
||||
device.input.append({5, 1, "Port 2 - Y-axis" });
|
||||
device.input.append({6, 0, "Port 2 - Trigger"});
|
||||
device.input.append({7, 0, "Port 2 - Start" });
|
||||
device.order.append(4, 5, 6, 7);
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::usart() {
|
||||
Port::Device device{7, "Serial USART"};
|
||||
return device;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,13 +27,27 @@ struct Interface : Emulator::Interface {
|
|||
void save(unsigned id, const stream &stream);
|
||||
void unload();
|
||||
|
||||
void connect(unsigned port, unsigned device);
|
||||
void power();
|
||||
void reset();
|
||||
void run();
|
||||
|
||||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
||||
private:
|
||||
Port::Device none();
|
||||
Port::Device controller();
|
||||
Port::Device multitap();
|
||||
Port::Device mouse();
|
||||
Port::Device superScope();
|
||||
Port::Device justifier();
|
||||
Port::Device justifiers();
|
||||
Port::Device usart();
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
||||
|
|
|
@ -7,7 +7,7 @@ include gb/Makefile
|
|||
include gba/Makefile
|
||||
name := ethos
|
||||
|
||||
ui_objects := ui-ethos ui-configuration ui-interface ui-utility ui-input ui-general ui-settings
|
||||
ui_objects := ui-ethos ui-configuration ui-interface ui-utility ui-input ui-window ui-general ui-settings
|
||||
ui_objects += phoenix ruby
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
|
@ -43,6 +43,7 @@ obj/ui-configuration.o: $(ui)/configuration/configuration.cpp $(call rwildcard,$
|
|||
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-window.o: $(ui)/window/window.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
|
||||
|
||||
|
|
|
@ -17,10 +17,9 @@ void Application::bootstrap() {
|
|||
system->callback.audioSample = {&Interface::audioSample, interface};
|
||||
system->callback.inputPoll = {&Interface::inputPoll, interface};
|
||||
system->callback.mediaRequest = {&Interface::mediaRequest, interface};
|
||||
system->updatePalette();
|
||||
|
||||
for(auto &firmware : system->firmware) {
|
||||
filestream fs{application->path(firmware.name)};
|
||||
filestream fs{application->path({firmware.name, ".", firmware.extension, "/", firmware.path})};
|
||||
system->load(firmware.id, fs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,23 @@
|
|||
Configuration *config = nullptr;
|
||||
|
||||
Configuration::Configuration() {
|
||||
append(video.synchronize = false, "Video::Synchronize");
|
||||
append(video.scaleMode = 0, "Video::ScaleMode");
|
||||
append(video.aspectCorrection = true, "Video::AspectCorrection");
|
||||
append(video.maskOverscan = false, "Video::MaskOverscan");
|
||||
append(video.maskOverscanHorizontal = 8, "Video::MaskOverscan::Horizontal");
|
||||
append(video.maskOverscanVertical = 8, "Video::MaskOverscan::Vertical");
|
||||
append(video.saturation = 100, "Video::Saturation");
|
||||
append(video.gamma = 150, "Video::Gamma");
|
||||
append(video.luminance = 100, "Video::Luminance");
|
||||
append(audio.synchronize = true, "Audio::Synchronize");
|
||||
append(audio.frequency = 48000, "Audio::Frequency");
|
||||
append(audio.latency = 60, "Audio::Latency");
|
||||
append(audio.resampler = 2, "Audio::Resampler");
|
||||
append(audio.volume = 100, "Audio::Volume");
|
||||
append(audio.mute = false, "Audio::Mute");
|
||||
append(input.focusPause = false, "Input::Focus::Pause");
|
||||
append(input.focusAllow = false, "Input::Focus::AllowInput");
|
||||
|
||||
load();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,30 @@
|
|||
struct Configuration : configuration {
|
||||
struct Video {
|
||||
bool synchronize;
|
||||
unsigned scaleMode;
|
||||
bool aspectCorrection;
|
||||
bool maskOverscan;
|
||||
unsigned maskOverscanHorizontal;
|
||||
unsigned maskOverscanVertical;
|
||||
unsigned saturation;
|
||||
unsigned gamma;
|
||||
unsigned luminance;
|
||||
} video;
|
||||
|
||||
struct Audio {
|
||||
bool synchronize;
|
||||
unsigned frequency;
|
||||
unsigned latency;
|
||||
unsigned resampler;
|
||||
unsigned volume;
|
||||
bool mute;
|
||||
} audio;
|
||||
|
||||
struct Input {
|
||||
bool focusPause;
|
||||
bool focusAllow;
|
||||
} input;
|
||||
|
||||
void load();
|
||||
void save();
|
||||
Configuration();
|
||||
|
|
|
@ -19,8 +19,11 @@ string Application::path(const string &filename) {
|
|||
|
||||
void Application::run() {
|
||||
inputManager->poll();
|
||||
utility->updateStatus();
|
||||
autopause = config->input.focusPause && presentation->focused() == false;
|
||||
|
||||
if(active == nullptr || system().loaded() == false) {
|
||||
if(active == nullptr || system().loaded() == false || pause || autopause) {
|
||||
audio.clear();
|
||||
usleep(20 * 1000);
|
||||
return;
|
||||
}
|
||||
|
@ -61,9 +64,14 @@ Application::Application(int argc, char **argv) {
|
|||
monospaceFont = "Liberation Mono, 8";
|
||||
}
|
||||
|
||||
video.driver("OpenGL");
|
||||
audio.driver("ALSA");
|
||||
input.driver("SDL");
|
||||
|
||||
config = new Configuration;
|
||||
utility = new Utility;
|
||||
inputManager = new InputManager;
|
||||
windowManager = new WindowManager;
|
||||
browser = new Browser;
|
||||
presentation = new Presentation;
|
||||
videoSettings = new VideoSettings;
|
||||
|
@ -71,31 +79,26 @@ Application::Application(int argc, char **argv) {
|
|||
inputSettings = new InputSettings;
|
||||
hotkeySettings = new HotkeySettings;
|
||||
settings = new Settings;
|
||||
|
||||
windowManager->loadGeometry();
|
||||
presentation->setVisible();
|
||||
|
||||
video.driver("OpenGL");
|
||||
video.set(Video::Handle, presentation->viewport.handle());
|
||||
video.set(Video::Synchronize, false);
|
||||
video.set(Video::Depth, 24u);
|
||||
if(!video.cap(Video::Depth) || !video.set(Video::Depth, depth = 30u)) {
|
||||
video.set(Video::Depth, depth = 24u);
|
||||
}
|
||||
video.init();
|
||||
|
||||
audio.driver("ALSA");
|
||||
audio.set(Audio::Handle, presentation->viewport.handle());
|
||||
audio.set(Audio::Synchronize, true);
|
||||
audio.set(Audio::Latency, 80u);
|
||||
audio.set(Audio::Frequency, 48000u);
|
||||
audio.init();
|
||||
|
||||
input.driver("SDL");
|
||||
input.set(Input::Handle, presentation->viewport.handle());
|
||||
input.init();
|
||||
|
||||
dspaudio.setPrecision(16);
|
||||
dspaudio.setVolume(2.0);
|
||||
dspaudio.setBalance(0.0);
|
||||
dspaudio.setResampler(DSP::ResampleEngine::Sinc);
|
||||
dspaudio.setResamplerFrequency(48000u);
|
||||
dspaudio.setFrequency(96000);
|
||||
|
||||
utility->synchronizeRuby();
|
||||
|
||||
while(quit == false) {
|
||||
OS::processEvents();
|
||||
|
@ -106,6 +109,7 @@ Application::Application(int argc, char **argv) {
|
|||
config->save();
|
||||
browser->saveConfiguration();
|
||||
inputManager->saveConfiguration();
|
||||
windowManager->saveGeometry();
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
|
|
|
@ -21,6 +21,7 @@ using namespace ruby;
|
|||
#include "interface/interface.hpp"
|
||||
#include "utility/utility.hpp"
|
||||
#include "input/input.hpp"
|
||||
#include "window/window.hpp"
|
||||
#include "general/general.hpp"
|
||||
#include "settings/settings.hpp"
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ Browser *browser = nullptr;
|
|||
Browser::Browser() {
|
||||
bootstrap();
|
||||
setGeometry({128, 128, 640, 400});
|
||||
windowManager->append(this, "Browser");
|
||||
|
||||
layout.setMargin(5);
|
||||
pathBrowse.setText("Browse ...");
|
||||
|
@ -50,7 +51,7 @@ void Browser::synchronize() {
|
|||
openButton.setEnabled(fileList.selected());
|
||||
if(fileList.selected()) {
|
||||
for(auto &folder : folderList) {
|
||||
if(folder.filter == filter) {
|
||||
if(folder.extension == extension) {
|
||||
folder.selection = fileList.selection();
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +67,7 @@ void Browser::bootstrap() {
|
|||
for(auto &media : emulator->information.media) {
|
||||
bool found = false;
|
||||
for(auto &folder : folderList) {
|
||||
if(folder.filter == filter) {
|
||||
if(folder.extension == media.extension) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ void Browser::bootstrap() {
|
|||
if(found == true) continue;
|
||||
|
||||
Folder folder;
|
||||
folder.filter = media.filter;
|
||||
folder.extension = media.extension;
|
||||
folder.path = application->basepath;
|
||||
folder.selection = 0;
|
||||
folderList.append(folder);
|
||||
|
@ -82,21 +83,21 @@ void Browser::bootstrap() {
|
|||
}
|
||||
|
||||
for(auto &folder : folderList) {
|
||||
config.append(folder.path, folder.filter);
|
||||
config.append(folder.selection, string{folder.filter, "::selection"});
|
||||
config.append(folder.path, folder.extension);
|
||||
config.append(folder.selection, string{folder.extension, "::selection"});
|
||||
}
|
||||
|
||||
config.load(application->path("paths.cfg"));
|
||||
config.save(application->path("paths.cfg"));
|
||||
}
|
||||
|
||||
string Browser::select(const string &title, const string &filter) {
|
||||
this->filter = filter;
|
||||
string Browser::select(const string &title, const string &extension) {
|
||||
this->extension = extension;
|
||||
|
||||
string path;
|
||||
unsigned selection = 0;
|
||||
for(auto &folder : folderList) {
|
||||
if(folder.filter == filter) {
|
||||
if(folder.extension == extension) {
|
||||
path = folder.path;
|
||||
selection = folder.selection;
|
||||
break;
|
||||
|
@ -105,8 +106,9 @@ string Browser::select(const string &title, const string &filter) {
|
|||
if(path.empty()) path = application->basepath;
|
||||
setPath(path, selection);
|
||||
|
||||
filterLabel.setText({"Files of type: ", filter});
|
||||
filterLabel.setText({"Files of type: *.", extension});
|
||||
|
||||
audio.clear();
|
||||
setTitle(title);
|
||||
setModal();
|
||||
setVisible();
|
||||
|
@ -123,7 +125,7 @@ string Browser::select(const string &title, const string &filter) {
|
|||
void Browser::setPath(const string &path, unsigned selection) {
|
||||
//save path for next browser selection
|
||||
for(auto &folder : folderList) {
|
||||
if(folder.filter == filter) folder.path = path;
|
||||
if(folder.extension == extension) folder.path = path;
|
||||
}
|
||||
|
||||
this->path = path;
|
||||
|
@ -135,7 +137,6 @@ void Browser::setPath(const string &path, unsigned selection) {
|
|||
lstring contents = directory::folders(path);
|
||||
|
||||
for(auto &filename : contents) {
|
||||
string filter = {this->filter, "/"};
|
||||
if(!filename.wildcard(R"(*.??/)") && !filename.wildcard(R"(*.???/)")) {
|
||||
string name = filename;
|
||||
name.rtrim<1>("/");
|
||||
|
@ -146,12 +147,11 @@ void Browser::setPath(const string &path, unsigned selection) {
|
|||
}
|
||||
|
||||
for(auto &filename : contents) {
|
||||
string filter = {this->filter, "/"};
|
||||
string suffix = {".", this->extension, "/"};
|
||||
if(filename.wildcard(R"(*.??/)") || filename.wildcard(R"(*.???/)")) {
|
||||
if(filename.wildcard(filter)) {
|
||||
if(filename.endswith(suffix)) {
|
||||
string name = filename;
|
||||
filter.ltrim<1>("*");
|
||||
name.rtrim<1>(filter);
|
||||
name.rtrim<1>(suffix);
|
||||
filenameList.append(filename);
|
||||
fileList.append(name);
|
||||
}
|
||||
|
@ -166,8 +166,8 @@ void Browser::setPath(const string &path, unsigned selection) {
|
|||
void Browser::fileListActivate() {
|
||||
unsigned selection = fileList.selection();
|
||||
string filename = filenameList[selection];
|
||||
string filter = {this->filter, "/"};
|
||||
if(filename.wildcard(filter) == false) return setPath({path, filename});
|
||||
string suffix = {this->extension, "/"};
|
||||
if(filename.endswith(suffix) == false) return setPath({path, filename});
|
||||
|
||||
setVisible(false);
|
||||
dialogActive = false;
|
||||
|
|
|
@ -9,7 +9,7 @@ struct Browser : Window {
|
|||
Label filterLabel;
|
||||
Button openButton;
|
||||
|
||||
string select(const string &title, const string &filter);
|
||||
string select(const string &title, const string &extension);
|
||||
void saveConfiguration();
|
||||
void synchronize();
|
||||
void bootstrap();
|
||||
|
@ -18,7 +18,7 @@ struct Browser : Window {
|
|||
private:
|
||||
configuration config;
|
||||
struct Folder {
|
||||
string filter;
|
||||
string extension;
|
||||
string path;
|
||||
unsigned selection;
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ private:
|
|||
bool dialogActive;
|
||||
string outputFilename;
|
||||
|
||||
string filter;
|
||||
string extension;
|
||||
string path;
|
||||
lstring filenameList;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
Presentation *presentation = nullptr;
|
||||
|
||||
void Presentation::synchronize() {
|
||||
for(auto &system : emulatorList) system->menu.setVisible(false);
|
||||
for(auto &system : emulatorList) {
|
||||
if(system->interface == application->active) {
|
||||
activeSystem = system;
|
||||
system->menu.setVisible(true);
|
||||
for(auto &emulator : emulatorList) emulator->menu.setVisible(false);
|
||||
for(auto &emulator : emulatorList) {
|
||||
if(emulator->interface == application->active) {
|
||||
active = emulator;
|
||||
emulator->menu.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,22 +15,26 @@ void Presentation::synchronize() {
|
|||
case 2: stretchVideo.setChecked(); break;
|
||||
}
|
||||
aspectCorrection.setChecked(config->video.aspectCorrection);
|
||||
resizeWindow.setVisible(application->active && config->video.scaleMode != 2);
|
||||
maskOverscan.setChecked(config->video.maskOverscan);
|
||||
synchronizeVideo.setChecked(config->video.synchronize);
|
||||
synchronizeAudio.setChecked(config->audio.synchronize);
|
||||
muteAudio.setChecked(config->audio.mute);
|
||||
toolsMenu.setVisible(application->active);
|
||||
resizeWindow.setVisible(config->video.scaleMode != 2);
|
||||
}
|
||||
|
||||
void Presentation::setSystemName(const string &name) {
|
||||
if(activeSystem) activeSystem->menu.setText(name);
|
||||
if(active) active->menu.setText(name);
|
||||
}
|
||||
|
||||
Presentation::Presentation() : activeSystem(nullptr) {
|
||||
Presentation::Presentation() : active(nullptr) {
|
||||
bootstrap();
|
||||
|
||||
setTitle({Emulator::Name, " v", Emulator::Version});
|
||||
setGeometry({1024, 600, 720, 480});
|
||||
windowManager->append(this, "Presentation");
|
||||
|
||||
setTitle({::Emulator::Name, " v", ::Emulator::Version});
|
||||
setBackgroundColor({0, 0, 0});
|
||||
setMenuFont(application->normalFont);
|
||||
setMenuVisible();
|
||||
setStatusFont(application->boldFont);
|
||||
setStatusVisible();
|
||||
|
||||
loadMenu.setText("Load");
|
||||
|
@ -40,20 +44,39 @@ Presentation::Presentation() : activeSystem(nullptr) {
|
|||
scaleVideo.setText("Scale");
|
||||
stretchVideo.setText("Stretch");
|
||||
RadioItem::group(centerVideo, scaleVideo, stretchVideo);
|
||||
aspectCorrection.setText("Correct Aspect Ratio");
|
||||
aspectCorrection.setText("Aspect Correction");
|
||||
maskOverscan.setText("Mask Overscan");
|
||||
synchronizeVideo.setText("Synchronize Video");
|
||||
synchronizeAudio.setText("Synchronize Audio");
|
||||
muteAudio.setText("Mute Audio");
|
||||
configurationSettings.setText("Configuration ...");
|
||||
toolsMenu.setText("Tools");
|
||||
saveStateMenu.setText("Save State");
|
||||
for(unsigned n = 0; n < 5; n++) saveStateItem[n].setText({"Slot ", 1 + n});
|
||||
loadStateMenu.setText("Load State");
|
||||
for(unsigned n = 0; n < 5; n++) loadStateItem[n].setText({"Slot ", 1 + n});
|
||||
resizeWindow.setText("Resize Window");
|
||||
|
||||
append(loadMenu);
|
||||
for(auto &item : loadList) loadMenu.append(*item);
|
||||
for(auto &item : loadListDirect) loadMenu.append(*item);
|
||||
if(loadListSlotted.size() > 0) {
|
||||
loadMenu.append(*new Separator);
|
||||
for(auto &item : loadListSlotted) loadMenu.append(*item);
|
||||
}
|
||||
for(auto &system : emulatorList) append(system->menu);
|
||||
append(settingsMenu);
|
||||
settingsMenu.append(videoMenu);
|
||||
videoMenu.append(centerVideo, scaleVideo, stretchVideo, *new Separator, aspectCorrection);
|
||||
videoMenu.append(centerVideo, scaleVideo, stretchVideo, *new Separator, aspectCorrection, maskOverscan);
|
||||
settingsMenu.append(*new Separator);
|
||||
settingsMenu.append(synchronizeVideo, synchronizeAudio, muteAudio);
|
||||
settingsMenu.append(*new Separator);
|
||||
settingsMenu.append(configurationSettings);
|
||||
append(toolsMenu);
|
||||
toolsMenu.append(saveStateMenu);
|
||||
for(unsigned n = 0; n < 5; n++) saveStateMenu.append(saveStateItem[n]);
|
||||
toolsMenu.append(loadStateMenu);
|
||||
for(unsigned n = 0; n < 5; n++) loadStateMenu.append(loadStateItem[n]);
|
||||
toolsMenu.append(stateMenuSeparator);
|
||||
toolsMenu.append(resizeWindow);
|
||||
|
||||
append(layout);
|
||||
|
@ -66,7 +89,13 @@ Presentation::Presentation() : activeSystem(nullptr) {
|
|||
scaleVideo.onActivate = [&] { config->video.scaleMode = 1; utility->resize(); };
|
||||
stretchVideo.onActivate = [&] { config->video.scaleMode = 2; utility->resize(); };
|
||||
aspectCorrection.onToggle = [&] { config->video.aspectCorrection = aspectCorrection.checked(); utility->resize(); };
|
||||
configurationSettings.onActivate = [&] { settings->setVisible(); };
|
||||
maskOverscan.onToggle = [&] { config->video.maskOverscan = maskOverscan.checked(); };
|
||||
synchronizeVideo.onToggle = [&] { config->video.synchronize = synchronizeVideo.checked(); utility->synchronizeRuby(); };
|
||||
synchronizeAudio.onToggle = [&] { config->audio.synchronize = synchronizeAudio.checked(); utility->synchronizeRuby(); };
|
||||
muteAudio.onToggle = [&] { config->audio.mute = muteAudio.checked(); utility->synchronizeRuby(); };
|
||||
configurationSettings.onActivate = [&] { settings->setVisible(); settings->panelList.setFocused(); };
|
||||
for(unsigned n = 0; n < 5; n++) saveStateItem[n].onActivate = [=] { utility->saveState(1 + n); };
|
||||
for(unsigned n = 0; n < 5; n++) loadStateItem[n].onActivate = [=] { utility->loadState(1 + n); };
|
||||
resizeWindow.onActivate = [&] { utility->resize(true); };
|
||||
|
||||
synchronize();
|
||||
|
@ -74,33 +103,65 @@ Presentation::Presentation() : activeSystem(nullptr) {
|
|||
|
||||
void Presentation::bootstrap() {
|
||||
for(auto &emulator : application->emulator) {
|
||||
System *system = new System;
|
||||
system->interface = emulator;
|
||||
auto iEmulator = new Emulator;
|
||||
iEmulator->interface = emulator;
|
||||
|
||||
for(auto &schema : emulator->schema) {
|
||||
Item *item = new Item;
|
||||
item->setText({schema.displayname, " ..."});
|
||||
item->setText({schema.name, " ..."});
|
||||
item->onActivate = [=, &schema] {
|
||||
utility->loadSchema(system->interface, schema);
|
||||
utility->loadSchema(iEmulator->interface, schema);
|
||||
};
|
||||
loadList.append(item);
|
||||
if(schema.slot.size() == 0) loadListDirect.append(item);
|
||||
if(schema.slot.size() >= 1) loadListSlotted.append(item);
|
||||
}
|
||||
|
||||
system->menu.setText(emulator->information.name);
|
||||
system->power.setText("Power");
|
||||
system->reset.setText("Reset");
|
||||
system->unload.setText("Unload");
|
||||
iEmulator->menu.setText(emulator->information.name);
|
||||
iEmulator->power.setText("Power");
|
||||
iEmulator->reset.setText("Reset");
|
||||
iEmulator->unload.setText("Unload");
|
||||
|
||||
system->menu.append(system->power);
|
||||
unsigned portNumber = 0;
|
||||
for(auto &port : emulator->port) {
|
||||
auto iPort = new Emulator::Port;
|
||||
iPort->menu.setText(port.name);
|
||||
iEmulator->port.append(iPort);
|
||||
|
||||
unsigned deviceNumber = 0;
|
||||
for(auto &device : port.device) {
|
||||
auto iDevice = new RadioItem;
|
||||
iDevice->setText(device.name);
|
||||
iDevice->onActivate = [=] { utility->connect(portNumber, deviceNumber); };
|
||||
iPort->group.append(*iDevice);
|
||||
iPort->device.append(iDevice);
|
||||
deviceNumber++;
|
||||
}
|
||||
|
||||
RadioItem::group(iPort->group);
|
||||
portNumber++;
|
||||
}
|
||||
|
||||
iEmulator->menu.append(iEmulator->power);
|
||||
if(emulator->information.resettable)
|
||||
system->menu.append(system->reset);
|
||||
system->menu.append(system->separator);
|
||||
system->menu.append(system->unload);
|
||||
iEmulator->menu.append(iEmulator->reset);
|
||||
iEmulator->menu.append(*new Separator);
|
||||
unsigned visiblePorts = 0;
|
||||
for(auto &iPort : iEmulator->port) {
|
||||
iEmulator->menu.append(iPort->menu);
|
||||
if(iPort->device.size() <= 1) iPort->menu.setVisible(false);
|
||||
else visiblePorts++;
|
||||
for(auto &iDevice : iPort->device) {
|
||||
iPort->menu.append(*iDevice);
|
||||
}
|
||||
}
|
||||
iEmulator->menu.append(iEmulator->controllerSeparator);
|
||||
if(visiblePorts == 0) iEmulator->controllerSeparator.setVisible(false);
|
||||
iEmulator->menu.append(iEmulator->unload);
|
||||
|
||||
system->power.onActivate = {&Utility::power, utility};
|
||||
system->reset.onActivate = {&Utility::reset, utility};
|
||||
system->unload.onActivate = {&Utility::unload, utility};
|
||||
iEmulator->power.onActivate = {&Utility::power, utility};
|
||||
iEmulator->reset.onActivate = {&Utility::reset, utility};
|
||||
iEmulator->unload.onActivate = {&Utility::unload, utility};
|
||||
|
||||
emulatorList.append(system);
|
||||
emulatorList.append(iEmulator);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,28 +2,44 @@ struct Presentation : Window {
|
|||
FixedLayout layout;
|
||||
Viewport viewport;
|
||||
|
||||
struct System {
|
||||
Emulator::Interface *interface;
|
||||
struct Emulator {
|
||||
::Emulator::Interface *interface;
|
||||
|
||||
Menu menu;
|
||||
Item power;
|
||||
Item reset;
|
||||
Separator separator;
|
||||
Item unload;
|
||||
Separator controllerSeparator;
|
||||
struct Port {
|
||||
Menu menu;
|
||||
set<RadioItem&> group;
|
||||
vector<RadioItem*> device;
|
||||
};
|
||||
vector<Port*> port;
|
||||
function<void (string)> callback;
|
||||
};
|
||||
vector<System*> emulatorList;
|
||||
vector<Emulator*> emulatorList;
|
||||
|
||||
Menu loadMenu;
|
||||
vector<Action*> loadList;
|
||||
vector<Item*> loadListDirect;
|
||||
vector<Item*> loadListSlotted;
|
||||
Menu settingsMenu;
|
||||
Menu videoMenu;
|
||||
RadioItem centerVideo;
|
||||
RadioItem scaleVideo;
|
||||
RadioItem stretchVideo;
|
||||
CheckItem aspectCorrection;
|
||||
CheckItem maskOverscan;
|
||||
CheckItem synchronizeVideo;
|
||||
CheckItem synchronizeAudio;
|
||||
CheckItem muteAudio;
|
||||
Item configurationSettings;
|
||||
Menu toolsMenu;
|
||||
Menu saveStateMenu;
|
||||
Item saveStateItem[5];
|
||||
Menu loadStateMenu;
|
||||
Item loadStateItem[5];
|
||||
Separator stateMenuSeparator;
|
||||
Item resizeWindow;
|
||||
|
||||
void synchronize();
|
||||
|
@ -32,7 +48,7 @@ struct Presentation : Window {
|
|||
Presentation();
|
||||
|
||||
private:
|
||||
System *activeSystem;
|
||||
Emulator *active;
|
||||
};
|
||||
|
||||
extern Presentation *presentation;
|
||||
|
|
|
@ -2,21 +2,37 @@ void InputManager::appendHotkeys() {
|
|||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Toggle Fullscreen Mode";
|
||||
hotkey->mapping = "KB0::Alt,KB0::Return";
|
||||
hotkey->logic = 1;
|
||||
hotkeyMap.append(hotkey);
|
||||
hotkey->mapping = "KB0::F11";
|
||||
|
||||
hotkey->press = [] {
|
||||
utility->toggleFullScreen();
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Toggle Mouse Capture";
|
||||
hotkey->mapping = "KB0::F12";
|
||||
|
||||
hotkey->press = [] {
|
||||
input.acquired() ? input.unacquire() : input.acquire();
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Pause Emulation";
|
||||
hotkey->mapping = "KB0::P";
|
||||
|
||||
hotkey->press = [] {
|
||||
application->pause = !application->pause;
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Fast Forward";
|
||||
hotkey->mapping = "KB0::Tilde";
|
||||
hotkey->logic = 1;
|
||||
hotkeyMap.append(hotkey);
|
||||
|
||||
hotkey->press = [] {
|
||||
video.set(Video::Synchronize, false);
|
||||
|
@ -24,8 +40,8 @@ void InputManager::appendHotkeys() {
|
|||
};
|
||||
|
||||
hotkey->release = [] {
|
||||
video.set(Video::Synchronize, false);
|
||||
audio.set(Audio::Synchronize, true);
|
||||
video.set(Video::Synchronize, ::config->video.synchronize);
|
||||
audio.set(Audio::Synchronize, ::config->audio.synchronize);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,8 +49,6 @@ void InputManager::appendHotkeys() {
|
|||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Power Cycle";
|
||||
hotkey->mapping = "None";
|
||||
hotkey->logic = 1;
|
||||
hotkeyMap.append(hotkey);
|
||||
|
||||
hotkey->press = [] {
|
||||
utility->power();
|
||||
|
@ -45,16 +59,70 @@ void InputManager::appendHotkeys() {
|
|||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Soft Reset";
|
||||
hotkey->mapping = "None";
|
||||
hotkey->logic = 1;
|
||||
hotkeyMap.append(hotkey);
|
||||
|
||||
hotkey->press = [] {
|
||||
utility->reset();
|
||||
};
|
||||
}
|
||||
|
||||
static unsigned activeSlot = 1;
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Save State";
|
||||
hotkey->mapping = "KB0::F5";
|
||||
|
||||
hotkey->press = [&] {
|
||||
utility->saveState(activeSlot);
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Load State";
|
||||
hotkey->mapping = "KB0::F7";
|
||||
|
||||
hotkey->press = [&] {
|
||||
utility->loadState(activeSlot);
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Decrement Slot";
|
||||
hotkey->mapping = "KB0::F6";
|
||||
|
||||
hotkey->press = [&] {
|
||||
if(--activeSlot == 0) activeSlot = 5;
|
||||
utility->showMessage({"Selected slot ", activeSlot});
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Increment Slot";
|
||||
hotkey->mapping = "KB0::F8";
|
||||
|
||||
hotkey->press = [&] {
|
||||
if(++activeSlot == 6) activeSlot = 1;
|
||||
utility->showMessage({"Selected slot ", activeSlot});
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
auto hotkey = new HotkeyInput;
|
||||
hotkey->name = "Close Emulator";
|
||||
hotkey->mapping = "None";
|
||||
|
||||
hotkey->press = [] {
|
||||
application->quit = true;
|
||||
};
|
||||
}
|
||||
|
||||
for(auto &hotkey : hotkeyMap) {
|
||||
config.append(hotkey->mapping, hotkey->name);
|
||||
string name = {"Hotkey::", hotkey->name};
|
||||
name.replace(" ", "");
|
||||
config.append(hotkey->mapping, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,13 @@ int16_t AnalogInput::poll() {
|
|||
|
||||
//
|
||||
|
||||
HotkeyInput::HotkeyInput() {
|
||||
logic = 1; //AND
|
||||
inputManager->hotkeyMap.append(this);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void InputManager::bind() {
|
||||
for(auto &input : inputMap) input->bind();
|
||||
for(auto &input : hotkeyMap) input->bind();
|
||||
|
@ -174,6 +181,7 @@ void InputManager::saveConfiguration() {
|
|||
}
|
||||
|
||||
InputManager::InputManager() {
|
||||
inputManager = this;
|
||||
bootstrap();
|
||||
}
|
||||
|
||||
|
@ -182,7 +190,7 @@ void InputManager::bootstrap() {
|
|||
for(auto &emulator : application->emulator) {
|
||||
for(auto &port : emulator->port) {
|
||||
for(auto &device : port.device) {
|
||||
for(auto &number : device.displayinput) {
|
||||
for(auto &number : device.order) {
|
||||
auto &input = device.input[number];
|
||||
|
||||
AbstractInput *abstract = nullptr;
|
||||
|
|
|
@ -32,6 +32,7 @@ struct AnalogInput : AbstractInput {
|
|||
struct HotkeyInput : DigitalInput {
|
||||
function<void ()> press;
|
||||
function<void ()> release;
|
||||
HotkeyInput();
|
||||
};
|
||||
|
||||
struct InputManager {
|
||||
|
|
|
@ -1,9 +1,42 @@
|
|||
#include "../ethos.hpp"
|
||||
Interface *interface = nullptr;
|
||||
|
||||
uint32_t Interface::videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) {
|
||||
red >>= 8, green >>= 8, blue >>= 8;
|
||||
return red << 16 | green << 8 | blue << 0;
|
||||
uint32_t Interface::videoColor(unsigned source, uint16_t r, uint16_t g, uint16_t b) {
|
||||
if(config->video.saturation != 100) {
|
||||
uint16_t grayscale = uclamp<16>((r + g + b) / 3);
|
||||
double saturation = config->video.saturation * 0.01;
|
||||
double inverse = max(0.0, 1.0 - saturation);
|
||||
r = uclamp<16>(r * saturation + grayscale * inverse);
|
||||
g = uclamp<16>(g * saturation + grayscale * inverse);
|
||||
b = uclamp<16>(b * saturation + grayscale * inverse);
|
||||
}
|
||||
|
||||
if(config->video.gamma != 100) {
|
||||
double exponent = config->video.gamma * 0.01;
|
||||
double reciprocal = 1.0 / 32767.0;
|
||||
r = r > 32767 ? r : 32767 * pow(r * reciprocal, exponent);
|
||||
g = g > 32767 ? g : 32767 * pow(g * reciprocal, exponent);
|
||||
b = b > 32767 ? b : 32767 * pow(b * reciprocal, exponent);
|
||||
}
|
||||
|
||||
if(config->video.luminance != 100) {
|
||||
double luminance = config->video.luminance * 0.01;
|
||||
r = r * luminance;
|
||||
g = g * luminance;
|
||||
b = b * luminance;
|
||||
}
|
||||
|
||||
if(application->depth == 30) {
|
||||
r >>= 6, g >>= 6, b >>= 6;
|
||||
return r << 20 | g << 10 | b << 0;
|
||||
}
|
||||
|
||||
if(application->depth == 24) {
|
||||
r >>= 8, g >>= 8, b >>= 8;
|
||||
return r << 16 | g << 8 | b << 0;
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned width, unsigned height) {
|
||||
|
@ -17,6 +50,21 @@ void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned widt
|
|||
memcpy(output + y * outputPitch, data + y * pitch, 4 * width);
|
||||
}
|
||||
|
||||
if(system().information.overscan && config->video.maskOverscan) {
|
||||
unsigned h = config->video.maskOverscanHorizontal;
|
||||
unsigned v = config->video.maskOverscanVertical;
|
||||
|
||||
if(h) for(unsigned y = 0; y < height; y++) {
|
||||
memset(output + y * outputPitch, 0, 4 * h);
|
||||
memset(output + y * outputPitch + (width - h), 0, 4 * h);
|
||||
}
|
||||
|
||||
if(v) for(unsigned y = 0; y < v; y++) {
|
||||
memset(output + y * outputPitch, 0, 4 * width);
|
||||
memset(output + (height - 1 - y) * outputPitch, 0, 4 * width);
|
||||
}
|
||||
}
|
||||
|
||||
video.unlock();
|
||||
video.refresh();
|
||||
}
|
||||
|
@ -43,16 +91,17 @@ void Interface::audioSample(int16_t lsample, int16_t rsample) {
|
|||
}
|
||||
|
||||
int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
|
||||
if(config->input.focusAllow == false && presentation->focused() == false) return 0;
|
||||
unsigned guid = system().port[port].device[device].input[input].guid;
|
||||
return inputManager->inputMap[guid]->poll();
|
||||
}
|
||||
|
||||
void Interface::mediaRequest(Emulator::Interface::Media media) {
|
||||
string pathname = browser->select({"Load ", media.displayname}, media.filter);
|
||||
string pathname = browser->select({"Load ", media.name}, media.extension);
|
||||
if(pathname.empty()) return;
|
||||
|
||||
string markup;
|
||||
markup.readfile({pathname, "manifest.xml"});
|
||||
mmapstream stream({pathname, media.name});
|
||||
mmapstream stream({pathname, media.path});
|
||||
system().load(media.id, stream, markup);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,73 @@
|
|||
AudioSettings *audioSettings = nullptr;
|
||||
|
||||
AudioSlider::AudioSlider() {
|
||||
append(name, {75, 0});
|
||||
append(value, {75, 0});
|
||||
append(slider, {~0, 0});
|
||||
}
|
||||
|
||||
AudioSettings::AudioSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Audio Settings");
|
||||
frequencyLabel.setText("Frequency:");
|
||||
frequency.append("32000hz", "44100hz", "48000hz", "96000hz");
|
||||
latencyLabel.setText("Latency:");
|
||||
latency.append("20ms", "40ms", "60ms", "80ms", "100ms");
|
||||
resamplerLabel.setText("Resampler:");
|
||||
resampler.append("Linear", "Hermite", "Sinc");
|
||||
volume.name.setText("Volume:");
|
||||
volume.slider.setLength(201);
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(controlLayout, {~0, 0}, 5);
|
||||
controlLayout.append(frequencyLabel, {0, 0}, 5);
|
||||
controlLayout.append(frequency, {~0, 0}, 5);
|
||||
controlLayout.append(latencyLabel, {0, 0}, 5);
|
||||
controlLayout.append(latency, {~0, 0}, 5);
|
||||
controlLayout.append(resamplerLabel, {0, 0}, 5);
|
||||
controlLayout.append(resampler, {~0, 0});
|
||||
append(volume, {~0, 0});
|
||||
|
||||
switch(config->audio.frequency) { default:
|
||||
case 32000: frequency.setSelection(0); break;
|
||||
case 44100: frequency.setSelection(1); break;
|
||||
case 48000: frequency.setSelection(2); break;
|
||||
case 96000: frequency.setSelection(3); break;
|
||||
}
|
||||
switch(config->audio.latency) { default:
|
||||
case 20: latency.setSelection(0); break;
|
||||
case 40: latency.setSelection(1); break;
|
||||
case 60: latency.setSelection(2); break;
|
||||
case 80: latency.setSelection(3); break;
|
||||
case 100: latency.setSelection(4); break;
|
||||
}
|
||||
resampler.setSelection(config->audio.resampler);
|
||||
volume.slider.setPosition(config->audio.volume);
|
||||
|
||||
frequency.onChange = latency.onChange = resampler.onChange = volume.slider.onChange =
|
||||
{&AudioSettings::synchronize, this};
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void AudioSettings::synchronize() {
|
||||
switch(frequency.selection()) {
|
||||
case 0: config->audio.frequency = 32000; break;
|
||||
case 1: config->audio.frequency = 44100; break;
|
||||
case 2: config->audio.frequency = 48000; break;
|
||||
case 3: config->audio.frequency = 96000; break;
|
||||
}
|
||||
switch(latency.selection()) {
|
||||
case 0: config->audio.latency = 20; break;
|
||||
case 1: config->audio.latency = 40; break;
|
||||
case 2: config->audio.latency = 60; break;
|
||||
case 3: config->audio.latency = 80; break;
|
||||
case 4: config->audio.latency = 100; break;
|
||||
}
|
||||
config->audio.resampler = resampler.selection();
|
||||
config->audio.volume = volume.slider.position();
|
||||
|
||||
volume.value.setText({config->audio.volume, "%"});
|
||||
|
||||
utility->synchronizeRuby();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
struct AudioSlider : HorizontalLayout {
|
||||
Label name;
|
||||
Label value;
|
||||
HorizontalSlider slider;
|
||||
|
||||
AudioSlider();
|
||||
};
|
||||
|
||||
struct AudioSettings : SettingsLayout {
|
||||
Label title;
|
||||
HorizontalLayout controlLayout;
|
||||
Label frequencyLabel;
|
||||
ComboBox frequency;
|
||||
Label latencyLabel;
|
||||
ComboBox latency;
|
||||
Label resamplerLabel;
|
||||
ComboBox resampler;
|
||||
AudioSlider volume;
|
||||
|
||||
void synchronize();
|
||||
AudioSettings();
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ void HotkeySettings::refresh() {
|
|||
inputList.reset();
|
||||
for(auto &hotkey : inputManager->hotkeyMap) {
|
||||
string mapping = hotkey->mapping;
|
||||
mapping.replace("KB0::", "");
|
||||
mapping.replace("MS0::", "Mouse::");
|
||||
mapping.replace(",", " and ");
|
||||
inputList.append(hotkey->name, mapping);
|
||||
}
|
||||
|
@ -44,23 +46,22 @@ void HotkeySettings::assignInput() {
|
|||
activeInput = inputManager->hotkeyMap[inputList.selection()];
|
||||
|
||||
settings->setStatusText({"Set assignment for [", activeInput->name, "] ..."});
|
||||
settings->panelList.setEnabled(false);
|
||||
inputList.setEnabled(false);
|
||||
clearButton.setEnabled(false);
|
||||
settings->layout.setEnabled(false);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
void HotkeySettings::inputEvent(unsigned scancode, int16_t value) {
|
||||
using nall::Mouse;
|
||||
|
||||
if(activeInput == nullptr) return;
|
||||
if(value != 1) return;
|
||||
if(Mouse::isAnyButton(scancode) || Mouse::isAnyAxis(scancode)) return;
|
||||
if(Joypad::isAnyAxis(scancode)) return;
|
||||
if(activeInput->bind(scancode, value) == false) return;
|
||||
|
||||
activeInput = nullptr;
|
||||
settings->setStatusText("");
|
||||
settings->panelList.setEnabled(true);
|
||||
inputList.setEnabled(true);
|
||||
clearButton.setEnabled(true);
|
||||
settings->layout.setEnabled(true);
|
||||
setEnabled(true);
|
||||
refresh();
|
||||
}
|
||||
|
|
|
@ -3,11 +3,18 @@ InputSettings *inputSettings = nullptr;
|
|||
InputSettings::InputSettings() : activeInput(nullptr) {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Input Settings");
|
||||
focusLabel.setText("When Focus is Lost:");
|
||||
focusPause.setText("Pause Emulation");
|
||||
focusAllow.setText("Allow Input");
|
||||
inputList.setHeaderText("Name", "Mapping");
|
||||
inputList.setHeaderVisible();
|
||||
clearButton.setText("Clear");
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(focusLayout, {~0, 0}, 5);
|
||||
focusLayout.append(focusLabel, {0, 0}, 5);
|
||||
focusLayout.append(focusPause, {0, 0}, 5);
|
||||
focusLayout.append(focusAllow, {0, 0});
|
||||
append(selectionLayout, {~0, 0}, 5);
|
||||
selectionLayout.append(systemList, {~0, 0}, 5);
|
||||
selectionLayout.append(portList, {~0, 0}, 5);
|
||||
|
@ -24,6 +31,12 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
|||
systemList.append(emulator->information.name);
|
||||
}
|
||||
|
||||
focusPause.setChecked(config->input.focusPause);
|
||||
focusAllow.setChecked(config->input.focusAllow);
|
||||
focusAllow.setEnabled(!config->input.focusPause);
|
||||
|
||||
focusPause.onToggle = [&] { config->input.focusPause = focusPause.checked(); focusAllow.setEnabled(!focusPause.checked()); };
|
||||
focusAllow.onToggle = [&] { config->input.focusAllow = focusAllow.checked(); };
|
||||
systemList.onChange = {&InputSettings::systemChanged, this};
|
||||
portList.onChange = {&InputSettings::portChanged, this};
|
||||
deviceList.onChange = {&InputSettings::deviceChanged, this};
|
||||
|
@ -43,11 +56,11 @@ void InputSettings::synchronize() {
|
|||
assign[1].setVisible(false);
|
||||
assign[2].setVisible(false);
|
||||
} else {
|
||||
unsigned number = activeDevice().displayinput[inputList.selection()];
|
||||
unsigned number = activeDevice().order[inputList.selection()];
|
||||
auto &input = activeDevice().input[number];
|
||||
activeInput = inputManager->inputMap[input.guid];
|
||||
auto selectedInput = inputManager->inputMap[input.guid];
|
||||
|
||||
if(dynamic_cast<DigitalInput*>(activeInput)) {
|
||||
if(dynamic_cast<DigitalInput*>(selectedInput)) {
|
||||
assign[0].setText("Mouse Left");
|
||||
assign[1].setText("Mouse Middle");
|
||||
assign[2].setText("Mouse Right");
|
||||
|
@ -56,7 +69,7 @@ void InputSettings::synchronize() {
|
|||
assign[2].setVisible(true);
|
||||
}
|
||||
|
||||
if(dynamic_cast<AnalogInput*>(activeInput)) {
|
||||
if(dynamic_cast<AnalogInput*>(selectedInput)) {
|
||||
assign[0].setText("Mouse X-axis");
|
||||
assign[1].setText("Mouse Y-axis");
|
||||
assign[0].setVisible(true);
|
||||
|
@ -98,10 +111,12 @@ void InputSettings::portChanged() {
|
|||
|
||||
void InputSettings::deviceChanged() {
|
||||
inputList.reset();
|
||||
for(unsigned number : activeDevice().displayinput) {
|
||||
for(unsigned number : activeDevice().order) {
|
||||
auto &input = activeDevice().input[number];
|
||||
auto abstract = inputManager->inputMap(input.guid);
|
||||
string mapping = abstract->mapping;
|
||||
mapping.replace("KB0::", "");
|
||||
mapping.replace("MS0::", "Mouse::");
|
||||
mapping.replace(",", " or ");
|
||||
inputList.append(input.name, mapping);
|
||||
}
|
||||
|
@ -109,31 +124,24 @@ void InputSettings::deviceChanged() {
|
|||
}
|
||||
|
||||
void InputSettings::clearInput() {
|
||||
unsigned number = activeDevice().displayinput[inputList.selection()];
|
||||
unsigned number = activeDevice().order[inputList.selection()];
|
||||
auto &input = activeDevice().input[number];
|
||||
activeInput = inputManager->inputMap[input.guid];
|
||||
inputEvent(Scancode::None, 1);
|
||||
}
|
||||
|
||||
void InputSettings::assignInput() {
|
||||
unsigned number = activeDevice().displayinput[inputList.selection()];
|
||||
unsigned number = activeDevice().order[inputList.selection()];
|
||||
auto &input = activeDevice().input[number];
|
||||
activeInput = inputManager->inputMap[input.guid];
|
||||
|
||||
settings->setStatusText({"Set assignment for [", activeDevice().name, "::", input.name, "] ..."});
|
||||
settings->panelList.setEnabled(false);
|
||||
systemList.setEnabled(false);
|
||||
portList.setEnabled(false);
|
||||
deviceList.setEnabled(false);
|
||||
inputList.setEnabled(false);
|
||||
assign[0].setEnabled(false);
|
||||
assign[1].setEnabled(false);
|
||||
assign[2].setEnabled(false);
|
||||
clearButton.setEnabled(false);
|
||||
settings->layout.setEnabled(false);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
void InputSettings::assignMouseInput(unsigned n) {
|
||||
unsigned number = activeDevice().displayinput[inputList.selection()];
|
||||
unsigned number = activeDevice().order[inputList.selection()];
|
||||
auto &input = activeDevice().input[number];
|
||||
activeInput = inputManager->inputMap[input.guid];
|
||||
|
||||
|
@ -155,14 +163,7 @@ void InputSettings::inputEvent(unsigned scancode, int16_t value, bool allowMouse
|
|||
activeInput = nullptr;
|
||||
deviceChanged();
|
||||
settings->setStatusText("");
|
||||
settings->panelList.setEnabled(true);
|
||||
systemList.setEnabled(true);
|
||||
portList.setEnabled(true);
|
||||
deviceList.setEnabled(true);
|
||||
inputList.setEnabled(true);
|
||||
assign[0].setEnabled(true);
|
||||
assign[1].setEnabled(true);
|
||||
assign[2].setEnabled(true);
|
||||
clearButton.setEnabled(true);
|
||||
settings->layout.setEnabled(true);
|
||||
setEnabled(true);
|
||||
synchronize();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
struct InputSettings : SettingsLayout {
|
||||
Label title;
|
||||
HorizontalLayout focusLayout;
|
||||
Label focusLabel;
|
||||
CheckBox focusPause;
|
||||
CheckBox focusAllow;
|
||||
HorizontalLayout selectionLayout;
|
||||
ComboBox systemList;
|
||||
ComboBox portList;
|
||||
|
|
|
@ -16,9 +16,10 @@ SettingsLayout::SettingsLayout() {
|
|||
}
|
||||
|
||||
Settings::Settings() {
|
||||
setTitle("Configuration Settings");
|
||||
setGeometry({128, 128, 640, 360});
|
||||
setStatusFont(application->boldFont);
|
||||
windowManager->append(this, "Settings");
|
||||
|
||||
setTitle("Configuration Settings");
|
||||
setStatusVisible();
|
||||
|
||||
layout.setMargin(5);
|
||||
|
|
|
@ -1,8 +1,63 @@
|
|||
VideoSettings *videoSettings = nullptr;
|
||||
|
||||
VideoSlider::VideoSlider() {
|
||||
append(name, {75, 0});
|
||||
append(value, {75, 0});
|
||||
append(slider, {~0, 0});
|
||||
}
|
||||
|
||||
VideoSettings::VideoSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Video Settings");
|
||||
colorAdjustment.setFont(application->boldFont);
|
||||
colorAdjustment.setText("Color adjustment:");
|
||||
saturation.name.setText("Saturation:");
|
||||
saturation.slider.setLength(201);
|
||||
gamma.name.setText("Gamma:");
|
||||
gamma.slider.setLength(101);
|
||||
luminance.name.setText("Luminance:");
|
||||
luminance.slider.setLength(101);
|
||||
overscanAdjustment.setFont(application->boldFont);
|
||||
overscanAdjustment.setText("Overscan mask:");
|
||||
overscanHorizontal.name.setText("Horizontal:");
|
||||
overscanHorizontal.slider.setLength(17);
|
||||
overscanVertical.name.setText("Vertical:");
|
||||
overscanVertical.slider.setLength(17);
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(colorAdjustment, {~0, 0});
|
||||
append(saturation, {~0, 0});
|
||||
append(gamma, {~0, 0});
|
||||
append(luminance, {~0, 0}, 5);
|
||||
append(overscanAdjustment, {~0, 0});
|
||||
append(overscanHorizontal, {~0, 0});
|
||||
append(overscanVertical, {~0, 0}, 5);
|
||||
|
||||
saturation.slider.setPosition(config->video.saturation);
|
||||
gamma.slider.setPosition(config->video.gamma - 100);
|
||||
luminance.slider.setPosition(config->video.luminance);
|
||||
overscanHorizontal.slider.setPosition(config->video.maskOverscanHorizontal);
|
||||
overscanVertical.slider.setPosition(config->video.maskOverscanVertical);
|
||||
|
||||
synchronize();
|
||||
|
||||
saturation.slider.onChange = gamma.slider.onChange = luminance.slider.onChange =
|
||||
overscanHorizontal.slider.onChange = overscanVertical.slider.onChange =
|
||||
{&VideoSettings::synchronize, this};
|
||||
}
|
||||
|
||||
void VideoSettings::synchronize() {
|
||||
config->video.saturation = saturation.slider.position();
|
||||
config->video.gamma = 100 + gamma.slider.position();
|
||||
config->video.luminance = luminance.slider.position();
|
||||
config->video.maskOverscanHorizontal = overscanHorizontal.slider.position();
|
||||
config->video.maskOverscanVertical = overscanVertical.slider.position();
|
||||
|
||||
saturation.value.setText({config->video.saturation, "%"});
|
||||
gamma.value.setText({config->video.gamma, "%"});
|
||||
luminance.value.setText({config->video.luminance, "%"});
|
||||
overscanHorizontal.value.setText({config->video.maskOverscanHorizontal, "px"});
|
||||
overscanVertical.value.setText({config->video.maskOverscanVertical, "px"});
|
||||
|
||||
if(application->active) system().updatePalette();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
struct VideoSlider : HorizontalLayout {
|
||||
Label name;
|
||||
Label value;
|
||||
HorizontalSlider slider;
|
||||
|
||||
VideoSlider();
|
||||
};
|
||||
|
||||
struct VideoSettings : SettingsLayout {
|
||||
Label title;
|
||||
Label colorAdjustment;
|
||||
VideoSlider saturation;
|
||||
VideoSlider gamma;
|
||||
VideoSlider luminance;
|
||||
Label overscanAdjustment;
|
||||
VideoSlider overscanHorizontal;
|
||||
VideoSlider overscanVertical;
|
||||
|
||||
void synchronize();
|
||||
VideoSettings();
|
||||
};
|
||||
|
||||
|
|
|
@ -8,9 +8,8 @@ void Utility::setInterface(Emulator::Interface *emulator) {
|
|||
}
|
||||
|
||||
void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) {
|
||||
string pathname;
|
||||
if(!schema.path.empty()) pathname = application->path(schema.path);
|
||||
if(!directory::exists(pathname)) pathname = browser->select(schema.displayname, schema.filter);
|
||||
string pathname = application->path({schema.name, ".", schema.extension, "/"});
|
||||
if(!directory::exists(pathname)) pathname = browser->select({"Load ", schema.name}, schema.extension);
|
||||
if(!directory::exists(pathname)) return;
|
||||
|
||||
loadMedia(emulator, schema, pathname);
|
||||
|
@ -23,7 +22,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi
|
|||
|
||||
string manifest;
|
||||
manifest.readfile({pathname, "manifest.xml"});
|
||||
auto memory = file::read({pathname, media.name});
|
||||
auto memory = file::read({pathname, media.path});
|
||||
system().load(media.id, vectorstream{memory}, manifest);
|
||||
|
||||
for(auto &memory : system().memory) {
|
||||
|
@ -37,7 +36,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi
|
|||
string displayname = pathname;
|
||||
displayname.rtrim<1>("/");
|
||||
presentation->setTitle(notdir(nall::basename(displayname)));
|
||||
presentation->setSystemName(media.displayname);
|
||||
presentation->setSystemName(media.name);
|
||||
resize();
|
||||
}
|
||||
|
||||
|
@ -48,6 +47,11 @@ void Utility::saveMedia() {
|
|||
}
|
||||
}
|
||||
|
||||
void Utility::connect(unsigned port, unsigned device) {
|
||||
if(application->active == nullptr) return;
|
||||
system().connect(port, device);
|
||||
}
|
||||
|
||||
void Utility::power() {
|
||||
if(application->active == nullptr) return;
|
||||
system().power();
|
||||
|
@ -68,6 +72,39 @@ void Utility::unload() {
|
|||
video.clear();
|
||||
}
|
||||
|
||||
void Utility::saveState(unsigned slot) {
|
||||
if(application->active == nullptr) return;
|
||||
serializer s = system().serialize();
|
||||
if(s.size() == 0) return;
|
||||
mkdir(string{pathname, "bsnes/"}, 0755);
|
||||
if(file::write({pathname, "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
|
||||
showMessage({"Save to slot ", slot});
|
||||
}
|
||||
|
||||
void Utility::loadState(unsigned slot) {
|
||||
if(application->active == nullptr) return;
|
||||
auto memory = file::read({pathname, "bsnes/state-", slot, ".bsa"});
|
||||
if(memory.size() == 0) return;
|
||||
serializer s(memory.data(), memory.size());
|
||||
if(system().unserialize(s) == false) return;
|
||||
showMessage({"Loaded from slot ", slot});
|
||||
}
|
||||
|
||||
void Utility::synchronizeRuby() {
|
||||
video.set(Video::Synchronize, config->video.synchronize);
|
||||
audio.set(Audio::Synchronize, config->audio.synchronize);
|
||||
audio.set(Audio::Frequency, config->audio.frequency);
|
||||
audio.set(Audio::Latency, config->audio.latency);
|
||||
|
||||
switch(config->audio.resampler) {
|
||||
case 0: dspaudio.setResampler(DSP::ResampleEngine::Linear); break;
|
||||
case 1: dspaudio.setResampler(DSP::ResampleEngine::Hermite); break;
|
||||
case 2: dspaudio.setResampler(DSP::ResampleEngine::Sinc); break;
|
||||
}
|
||||
dspaudio.setResamplerFrequency(config->audio.frequency);
|
||||
dspaudio.setVolume(config->audio.mute ? 0.0 : config->audio.volume * 0.01);
|
||||
}
|
||||
|
||||
void Utility::resize(bool resizeWindow) {
|
||||
if(application->active == nullptr) return;
|
||||
Geometry geometry = presentation->geometry();
|
||||
|
@ -132,6 +169,32 @@ void Utility::toggleFullScreen() {
|
|||
resize();
|
||||
}
|
||||
|
||||
void Utility::setStatusText(const string &text) {
|
||||
presentation->setStatusText(text);
|
||||
void Utility::updateStatus() {
|
||||
time_t currentTime = time(0);
|
||||
string text;
|
||||
if((currentTime - statusTime) <= 2) {
|
||||
text = statusMessage;
|
||||
} else if(application->active == nullptr) {
|
||||
text = "No cartridge loaded";
|
||||
} else if(application->pause || application->autopause) {
|
||||
text = "Paused";
|
||||
} else {
|
||||
text = statusText;
|
||||
}
|
||||
if(text != presentation->statusText()) {
|
||||
presentation->setStatusText(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::setStatusText(const string &text) {
|
||||
statusText = text;
|
||||
}
|
||||
|
||||
void Utility::showMessage(const string &message) {
|
||||
statusTime = time(0);
|
||||
statusMessage = message;
|
||||
}
|
||||
|
||||
Utility::Utility() {
|
||||
statusTime = 0;
|
||||
}
|
||||
|
|
|
@ -6,13 +6,28 @@ struct Utility {
|
|||
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname);
|
||||
void saveMedia();
|
||||
|
||||
void connect(unsigned port, unsigned device);
|
||||
void power();
|
||||
void reset();
|
||||
void unload();
|
||||
|
||||
void saveState(unsigned slot);
|
||||
void loadState(unsigned slot);
|
||||
|
||||
void synchronizeRuby();
|
||||
void resize(bool resizeWindow = false);
|
||||
void toggleFullScreen();
|
||||
|
||||
void updateStatus();
|
||||
void setStatusText(const string &text);
|
||||
void showMessage(const string &message);
|
||||
|
||||
Utility();
|
||||
|
||||
private:
|
||||
string statusText;
|
||||
string statusMessage;
|
||||
time_t statusTime;
|
||||
};
|
||||
|
||||
extern Utility *utility;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
#include "../ethos.hpp"
|
||||
WindowManager *windowManager = nullptr;
|
||||
|
||||
void WindowManager::append(Window *window, const string &name) {
|
||||
window->setMenuFont(application->normalFont);
|
||||
window->setWidgetFont(application->normalFont);
|
||||
window->setStatusFont(application->boldFont);
|
||||
windowList.append({window, name, window->geometry().text()});
|
||||
}
|
||||
|
||||
void WindowManager::loadGeometry() {
|
||||
for(auto &window : windowList) {
|
||||
config.append(window.geometry, window.name);
|
||||
}
|
||||
config.load(application->path("geometry.cfg"));
|
||||
config.save(application->path("geometry.cfg"));
|
||||
for(auto &window : windowList) {
|
||||
window.window->setGeometry(window.geometry);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::saveGeometry() {
|
||||
for(auto &window : windowList) {
|
||||
window.geometry = window.window->geometry().text();
|
||||
}
|
||||
config.save(application->path("geometry.cfg"));
|
||||
}
|
||||
|
||||
void WindowManager::hideAll() {
|
||||
for(auto &window : windowList) {
|
||||
window.window->setVisible(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
struct WindowManager {
|
||||
struct WindowList {
|
||||
Window *window;
|
||||
string name;
|
||||
string geometry;
|
||||
};
|
||||
vector<WindowList> windowList;
|
||||
|
||||
void append(Window *window, const string &name);
|
||||
void loadGeometry();
|
||||
void saveGeometry();
|
||||
void hideAll();
|
||||
|
||||
private:
|
||||
configuration config;
|
||||
};
|
||||
|
||||
extern WindowManager *windowManager;
|
Loading…
Reference in New Issue