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:
Tim Allen 2012-05-04 22:47:41 +10:00
parent 8703d57030
commit 5d273c5265
41 changed files with 964 additions and 431 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
Port port{0, "Device"};
{
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::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);

View File

@ -27,6 +27,9 @@ struct Interface : Emulator::Interface {
void reset();
void run();
serializer serialize();
bool unserialize(serializer&);
void updatePalette();
Interface();

View File

@ -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);
}
Port port{0, "Device"};
{
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::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);

View File

@ -22,6 +22,9 @@ struct Interface : Emulator::Interface {
void reset();
void run();
serializer serialize();
bool unserialize(serializer&);
void updatePalette();
Interface();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
system->power.onActivate = {&Utility::power, utility};
system->reset.onActivate = {&Utility::reset, utility};
system->unload.onActivate = {&Utility::unload, utility};
emulatorList.append(system);
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);
iEmulator->power.onActivate = {&Utility::power, utility};
iEmulator->reset.onActivate = {&Utility::reset, utility};
iEmulator->unload.onActivate = {&Utility::unload, utility};
emulatorList.append(iEmulator);
}
}

View File

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

View File

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

View File

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

View File

@ -32,6 +32,7 @@ struct AnalogInput : AbstractInput {
struct HotkeyInput : DigitalInput {
function<void ()> press;
function<void ()> release;
HotkeyInput();
};
struct InputManager {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,9 @@
struct InputSettings : SettingsLayout {
Label title;
HorizontalLayout focusLayout;
Label focusLabel;
CheckBox focusPause;
CheckBox focusAllow;
HorizontalLayout selectionLayout;
ComboBox systemList;
ComboBox portList;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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