Update to v106r57 release.

byuu says:

I've added tool tips to hiro for Windows, GTK, and Qt. I'm unsure how to
add them for Cocoa. I wasted am embarrassing ~14 hours implementing tool
tips from scratch on Windows, because the `TOOLTIPS_CLASS` widget just
absolutely refused to show up, no matter what I tried. As such, they're
not quite 100% native, but I would really appreciate any patch
submissions to help improve my implementation.

I added tool tips to all of the confusing settings in bsnes. And of
course, for those of you who don't like them, there's a configuration
file setting to turn them off globally.

I also improved Mega Drive handling of the Game Genie a bit, and
restructured the way the Settings class works in bsnes.

Starting now, I'm feature-freezing bsnes and higan. From this point
forward:

  - polishing up and fixing bugs caused by the ruby/hiro changes
  - adding DRC to XAudio2, and maybe exclusive mode to WGL
  - correcting FEoEZ (English) to load and work again out of the box

Once that's done, a final beta of bsnes will go out, I'll fix any
reported bugs that I'm able to, and then v107 should be ready. This time
with higan being functional, but marked as v107 beta. v108 will restore
higan to production status again, alongside bsnes.
This commit is contained in:
Tim Allen 2018-08-08 18:46:58 +10:00
parent 3b4e8b6d75
commit 93a6a1ce7e
121 changed files with 2014 additions and 1310 deletions

View File

@ -13,7 +13,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "106.56"; static const string Version = "106.57";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "https://byuu.org/"; static const string Website = "https://byuu.org/";

View File

@ -70,19 +70,18 @@ auto Cartridge::load() -> bool {
write = {&Cartridge::writeBanked, this}; write = {&Cartridge::writeBanked, this};
} }
if(patch) { if(information.document["game/board/slot(type=MegaDrive)"]) {
slot = new Cartridge{depth + 1}; slot = new Cartridge{depth + 1};
if(!slot->load()) slot.reset(); if(!slot->load()) slot.reset();
if(patch) {
read = {&Cartridge::readLockOn, this}; read = {&Cartridge::readLockOn, this};
write = {&Cartridge::writeLockOn, this}; write = {&Cartridge::writeLockOn, this};
} } else {
if(rom.data[0x120>>1]==0x4761 || rom.data[0x120>>1]==0x6147) {
slot = new Cartridge{depth + 1};
if(!slot->load()) slot.reset();
read = {&Cartridge::readGameGenie, this}; read = {&Cartridge::readGameGenie, this};
write = {&Cartridge::writeGameGenie, this}; write = {&Cartridge::writeGameGenie, this};
} }
}
//easter egg: power draw increases with each successively stacked cartridge //easter egg: power draw increases with each successively stacked cartridge
//simulate increasing address/data line errors as stacking increases //simulate increasing address/data line errors as stacking increases

View File

@ -1,92 +1,58 @@
Configuration configuration; Configuration configuration;
auto Configuration::process(Markup::Node document, bool load) -> void {
#define bind(type, path, name) \
if(load) { \
if(auto node = document[path]) name = node.type(); \
} else { \
document(path).setValue(name); \
} \
bind(natural, "System/CPU/Version", system.cpu.version);
bind(natural, "System/PPU1/Version", system.ppu1.version);
bind(natural, "System/PPU1/VRAM/Size", system.ppu1.vram.size);
bind(natural, "System/PPU2/Version", system.ppu2.version);
bind(boolean, "Video/BlurEmulation", video.blurEmulation);
bind(boolean, "Video/ColorEmulation", video.colorEmulation);
bind(boolean, "Hacks/FastPPU/Enable", hacks.ppuFast.enable);
bind(boolean, "Hacks/FastPPU/NoSpriteLimit", hacks.ppuFast.noSpriteLimit);
bind(boolean, "Hacks/FastPPU/HiresMode7", hacks.ppuFast.hiresMode7);
bind(boolean, "Hacks/FastDSP/Enable", hacks.dspFast.enable);
#undef bind
}
auto Configuration::read() -> string { auto Configuration::read() -> string {
return { Markup::Node document;
"system\n" process(document, false);
" cpu version=", system.cpu.version, "\n" return BML::serialize(document, " ");
" ppu1 version=", system.ppu1.version, "\n"
" vram size=0x", hex(system.ppu1.vram.size), "\n"
" ppu2 version=", system.ppu2.version, "\n"
"\n"
"video\n"
" blurEmulation: ", video.blurEmulation, "\n"
" colorEmulation: ", video.colorEmulation, "\n"
"\n"
"hacks\n"
" ppuFast\n"
" enable: ", hacks.ppuFast.enable, "\n"
" noSpriteLimit: ", hacks.ppuFast.noSpriteLimit, "\n"
" hiresMode7: ", hacks.ppuFast.hiresMode7, "\n"
" dspFast\n"
" enable: ", hacks.dspFast.enable, "\n"
};
} }
auto Configuration::read(string name) -> string { auto Configuration::read(string name) -> string {
#define bind(id) { \ auto document = BML::unserialize(read());
string key = {string{#id}.transform(".", "/")}; \ return document[name].text();
if(name == key) return name; \
}
bind(system.cpu.version);
bind(system.ppu1.version);
bind(system.ppu1.vram.size);
bind(system.ppu2.version);
bind(video.blurEmulation);
bind(video.colorEmulation);
bind(hacks.ppuFast.enable);
bind(hacks.ppuFast.noSpriteLimit);
bind(hacks.ppuFast.hiresMode7);
bind(hacks.dspFast.enable);
#undef bind
return {};
} }
auto Configuration::write(string configuration) -> bool { auto Configuration::write(string configuration) -> bool {
*this = {}; *this = {};
auto document = BML::unserialize(configuration); if(auto document = BML::unserialize(configuration)) {
if(!document) return false; return process(document, true), true;
#define bind(type, id) { \
string key = {string{#id}.transform(".", "/")}; \
if(auto node = document[key]) id = node.type(); \
} }
bind(natural, system.cpu.version);
bind(natural, system.ppu1.version);
bind(natural, system.ppu1.vram.size);
bind(natural, system.ppu2.version);
bind(boolean, video.blurEmulation); return false;
bind(boolean, video.colorEmulation);
bind(boolean, hacks.ppuFast.enable);
bind(boolean, hacks.ppuFast.noSpriteLimit);
bind(boolean, hacks.ppuFast.hiresMode7);
bind(boolean, hacks.dspFast.enable);
#undef bind
return true;
} }
auto Configuration::write(string name, string value) -> bool { auto Configuration::write(string name, string value) -> bool {
#define bind(type, id) { \ if(SuperFamicom::system.loaded() && name.beginsWith("System/")) return false;
string key = {string{#id}.transform(".", "/")}; \
if(name == key) return id = Markup::Node().setValue(value).type(), true; \ auto document = BML::unserialize(read());
if(auto node = document[name]) {
node.setValue(value);
return process(document, true), true;
} }
bind(boolean, video.blurEmulation);
bind(boolean, video.colorEmulation);
bind(boolean, hacks.ppuFast.enable);
bind(boolean, hacks.ppuFast.noSpriteLimit);
bind(boolean, hacks.ppuFast.hiresMode7);
bind(boolean, hacks.dspFast.enable);
if(SuperFamicom::system.loaded()) return false;
bind(natural, system.cpu.version);
bind(natural, system.ppu1.version);
bind(natural, system.ppu1.vram.size);
bind(natural, system.ppu2.version);
#undef bind
return false; return false;
} }

View File

@ -34,6 +34,9 @@ struct Configuration {
bool enable = false; bool enable = false;
} dspFast; } dspFast;
} hacks; } hacks;
private:
auto process(Markup::Node document, bool load) -> void;
}; };
extern Configuration configuration; extern Configuration configuration;

View File

@ -24,13 +24,15 @@ auto hiro::initialize() -> void {
#include <nall/main.hpp> #include <nall/main.hpp>
auto nall::main(vector<string> arguments) -> void { auto nall::main(vector<string> arguments) -> void {
Application::setScreenSaver(settings.general.screenSaver);
Application::setToolTips(settings.general.toolTips);
string locale; // = "日本語"; string locale; // = "日本語";
for(auto argument : arguments) { for(auto argument : arguments) {
if(argument.beginsWith("--locale=")) { if(argument.beginsWith("--locale=")) {
locale = argument.trimLeft("--locale=", 1L); locale = argument.trimLeft("--locale=", 1L);
} }
} }
Application::setScreenSaver(!settings["UserInterface/SuppressScreenSaver"].boolean());
Application::locale().scan(locate("locales/")); Application::locale().scan(locate("locales/"));
Application::locale().select(locale); Application::locale().select(locale);
emulator = new SuperFamicom::Interface; emulator = new SuperFamicom::Interface;

View File

@ -47,8 +47,8 @@ auto InputManager::bindHotkeys() -> void {
video.setBlocking(false); video.setBlocking(false);
audio.setBlocking(false); audio.setBlocking(false);
}).onRelease([] { }).onRelease([] {
video.setBlocking(settings["Video/Blocking"].boolean()); video.setBlocking(settings.video.blocking);
audio.setBlocking(settings["Audio/Blocking"].boolean()); audio.setBlocking(settings.audio.blocking);
})); }));
hotkeys.append(InputHotkey("Pause Emulation").onPress([] { hotkeys.append(InputHotkey("Pause Emulation").onPress([] {

View File

@ -188,10 +188,10 @@ auto InputManager::initialize() -> void {
input.onChange({&InputManager::onChange, this}); input.onChange({&InputManager::onChange, this});
lastPoll = chrono::millisecond(); lastPoll = chrono::millisecond();
frequency = max(1u, settings["Input/Frequency"].natural()); frequency = max(1u, settings.input.frequency);
turboCounter = 0; turboCounter = 0;
turboFrequency = max(1, settings["Input/Turbo/Frequency"].natural()); turboFrequency = max(1, settings.input.turbo.frequency);
auto information = emulator->information(); auto information = emulator->information();
auto ports = emulator->ports(); auto ports = emulator->ports();

View File

@ -14,7 +14,7 @@ auto AboutWindow::create() -> void {
versionLabel.setText({tr("Version"), ":"}).setAlignment(1.0); versionLabel.setText({tr("Version"), ":"}).setAlignment(1.0);
versionValue.setText(Emulator::Version); versionValue.setText(Emulator::Version);
authorLabel.setText({tr("Author"), ":"}).setAlignment(1.0); authorLabel.setText({tr("Author"), ":"}).setAlignment(1.0);
authorValue.setText(Emulator::Author); authorValue.setText(Emulator::Author).setToolTip("ビュウ");
licenseLabel.setText({tr("License"), ":"}).setAlignment(1.0); licenseLabel.setText({tr("License"), ":"}).setAlignment(1.0);
licenseValue.setText(Emulator::License); licenseValue.setText(Emulator::License);
websiteLabel.setText({tr("Website"), ":"}).setAlignment(1.0); websiteLabel.setText({tr("Website"), ":"}).setAlignment(1.0);

View File

@ -27,39 +27,39 @@ auto Presentation::create() -> void {
updateSizeMenu(); updateSizeMenu();
outputMenu.setIcon(Icon::Emblem::Image).setText("Output"); outputMenu.setIcon(Icon::Emblem::Image).setText("Output");
centerViewport.setText("Center").onActivate([&] { centerViewport.setText("Center").onActivate([&] {
settings["View/Output"].setValue("Center"); settings.video.output = "Center";
resizeViewport(); resizeViewport();
}); });
scaleViewport.setText("Scale").onActivate([&] { scaleViewport.setText("Scale").onActivate([&] {
settings["View/Output"].setValue("Scale"); settings.video.output = "Scale";
resizeViewport(); resizeViewport();
}); });
stretchViewport.setText("Stretch").onActivate([&] { stretchViewport.setText("Stretch").onActivate([&] {
settings["View/Output"].setValue("Stretch"); settings.video.output = "Stretch";
resizeViewport(); resizeViewport();
}); });
if(settings["View/Output"].text() == "Center") centerViewport.setChecked(); if(settings.video.output == "Center") centerViewport.setChecked();
if(settings["View/Output"].text() == "Scale") scaleViewport.setChecked(); if(settings.video.output == "Scale") scaleViewport.setChecked();
if(settings["View/Output"].text() == "Stretch") stretchViewport.setChecked(); if(settings.video.output == "Stretch") stretchViewport.setChecked();
aspectCorrection.setText("Aspect Correction").setChecked(settings["View/AspectCorrection"].boolean()).onToggle([&] { aspectCorrection.setText("Aspect Correction").setChecked(settings.video.aspectCorrection).onToggle([&] {
settings["View/AspectCorrection"].setValue(aspectCorrection.checked()); settings.video.aspectCorrection = aspectCorrection.checked();
resizeWindow(); resizeWindow();
}); });
overscanCropping.setText("Overscan Cropping").setChecked(settings["View/OverscanCropping"].boolean()).onToggle([&] { showOverscanArea.setText("Show Overscan Area").setChecked(settings.video.overscan).onToggle([&] {
settings["View/OverscanCropping"].setValue(overscanCropping.checked()); settings.video.overscan = showOverscanArea.checked();
resizeWindow(); resizeWindow();
}); });
blurEmulation.setText("Blur Emulation").setChecked(settings["View/BlurEmulation"].boolean()).onToggle([&] { blurEmulation.setText("Blur Emulation").setChecked(settings.video.blur).onToggle([&] {
settings["View/BlurEmulation"].setValue(blurEmulation.checked()); settings.video.blur = blurEmulation.checked();
emulator->configure("video/blurEmulation", blurEmulation.checked()); emulator->configure("Video/BlurEmulation", blurEmulation.checked());
}).doToggle(); }).doToggle();
shaderMenu.setIcon(Icon::Emblem::Image).setText("Shader"); shaderMenu.setIcon(Icon::Emblem::Image).setText("Shader");
muteAudio.setText("Mute Audio").setChecked(settings["Audio/Mute"].boolean()).onToggle([&] { muteAudio.setText("Mute Audio").setChecked(settings.audio.mute).onToggle([&] {
settings["Audio/Mute"].setValue(muteAudio.checked()); settings.audio.mute = muteAudio.checked();
program.updateAudioEffects(); program.updateAudioEffects();
}); });
showStatusBar.setText("Show Status Bar").setChecked(settings["UserInterface/ShowStatusBar"].boolean()).onToggle([&] { showStatusBar.setText("Show Status Bar").setChecked(settings.general.statusBar).onToggle([&] {
settings["UserInterface/ShowStatusBar"].setValue(showStatusBar.checked()); settings.general.statusBar = showStatusBar.checked();
if(!showStatusBar.checked()) { if(!showStatusBar.checked()) {
layout.remove(statusLayout); layout.remove(statusLayout);
} else { } else {
@ -112,9 +112,7 @@ auto Presentation::create() -> void {
program.removeState("Quick/Redo"); program.removeState("Quick/Redo");
} }
})); }));
speedMenu.setIcon(Icon::Device::Clock).setText("Speed").setEnabled( speedMenu.setIcon(Icon::Device::Clock).setText("Speed").setEnabled(!settings.video.blocking && settings.audio.blocking);
!settings["Video/Blocking"].boolean() && settings["Audio/Blocking"].boolean()
);
speedSlowest.setText("50% (Slowest)").setProperty("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); }); speedSlowest.setText("50% (Slowest)").setProperty("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); });
speedSlow.setText("75% (Slow)").setProperty("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); }); speedSlow.setText("75% (Slow)").setProperty("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
speedNormal.setText("100% (Normal)").setProperty("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); }); speedNormal.setText("100% (Normal)").setProperty("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
@ -153,9 +151,7 @@ auto Presentation::create() -> void {
icon.alphaBlend(0x000000); icon.alphaBlend(0x000000);
iconCanvas.setIcon(icon); iconCanvas.setIcon(icon);
if(!settings["UserInterface/ShowStatusBar"].boolean()) { if(!settings.general.statusBar) layout.remove(statusLayout);
layout.remove(statusLayout);
}
auto font = Font().setBold(); auto font = Font().setBold();
auto back = Color{ 32, 32, 32}; auto back = Color{ 32, 32, 32};
@ -240,21 +236,21 @@ auto Presentation::clearViewport() -> void {
} }
auto Presentation::resizeViewport() -> void { auto Presentation::resizeViewport() -> void {
uint windowWidth = viewportLayout.geometry().width(); uint layoutWidth = viewportLayout.geometry().width();
uint windowHeight = viewportLayout.geometry().height(); uint layoutHeight = viewportLayout.geometry().height();
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0); uint width = 256 * (settings.video.aspectCorrection ? 8.0 / 7.0 : 1.0);
uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0); uint height = (settings.video.overscan ? 240.0 : 224.0);
uint viewportWidth, viewportHeight; uint viewportWidth, viewportHeight;
if(visible() && !fullScreen()) { if(visible() && !fullScreen()) {
uint widthMultiplier = windowWidth / width; uint widthMultiplier = layoutWidth / width;
uint heightMultiplier = windowHeight / height; uint heightMultiplier = layoutHeight / height;
uint multiplier = max(1, min(widthMultiplier, heightMultiplier)); uint multiplier = max(1, min(widthMultiplier, heightMultiplier));
settings["View/Multiplier"].setValue(multiplier); settings.video.multiplier = multiplier;
for(auto item : sizeGroup.objects<MenuRadioItem>()) { for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(auto property = item->property("multiplier")) { if(auto property = item.property("multiplier")) {
if(property.natural() == multiplier) item->setChecked(); if(property.natural() == multiplier) item.setChecked();
} }
} }
} }
@ -262,26 +258,26 @@ auto Presentation::resizeViewport() -> void {
if(!emulator->loaded()) return clearViewport(); if(!emulator->loaded()) return clearViewport();
if(!video) return; if(!video) return;
if(settings["View/Output"].text() == "Center") { if(settings.video.output == "Center") {
uint widthMultiplier = windowWidth / width; uint widthMultiplier = layoutWidth / width;
uint heightMultiplier = windowHeight / height; uint heightMultiplier = layoutHeight / height;
uint multiplier = min(widthMultiplier, heightMultiplier); uint multiplier = min(widthMultiplier, heightMultiplier);
viewportWidth = width * multiplier; viewportWidth = width * multiplier;
viewportHeight = height * multiplier; viewportHeight = height * multiplier;
} else if(settings["View/Output"].text() == "Scale") { } else if(settings.video.output == "Scale") {
double widthMultiplier = (double)windowWidth / width; double widthMultiplier = (double)layoutWidth / width;
double heightMultiplier = (double)windowHeight / height; double heightMultiplier = (double)layoutHeight / height;
double multiplier = min(widthMultiplier, heightMultiplier); double multiplier = min(widthMultiplier, heightMultiplier);
viewportWidth = width * multiplier; viewportWidth = width * multiplier;
viewportHeight = height * multiplier; viewportHeight = height * multiplier;
} else if(settings["View/Output"].text() == "Stretch" || 1) { } else if(settings.video.output == "Stretch" || 1) {
viewportWidth = windowWidth; viewportWidth = layoutWidth;
viewportHeight = windowHeight; viewportHeight = layoutHeight;
} }
//center viewport within viewportLayout by use of viewportLayout padding //center viewport within viewportLayout by use of viewportLayout padding
uint paddingWidth = windowWidth - viewportWidth; uint paddingWidth = layoutWidth - viewportWidth;
uint paddingHeight = windowHeight - viewportHeight; uint paddingHeight = layoutHeight - viewportHeight;
viewportLayout.setPadding({ viewportLayout.setPadding({
paddingWidth / 2, paddingHeight / 2, paddingWidth / 2, paddingHeight / 2,
paddingWidth - paddingWidth / 2, paddingHeight - paddingHeight / 2 paddingWidth - paddingWidth / 2, paddingHeight - paddingHeight / 2
@ -294,26 +290,22 @@ auto Presentation::resizeWindow() -> void {
if(fullScreen()) return; if(fullScreen()) return;
if(maximized()) setMaximized(false); if(maximized()) setMaximized(false);
uint width = 256 * (settings["View/AspectCorrection"].boolean() ? 8.0 / 7.0 : 1.0); uint width = 256 * (settings.video.aspectCorrection ? 8.0 / 7.0 : 1.0);
uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0); uint height = (settings.video.overscan ? 240.0 : 224.0);
uint statusHeight = settings["UserInterface/ShowStatusBar"].boolean() ? StatusHeight : 0; uint multiplier = max(1, settings.video.multiplier);
uint statusHeight = settings.general.statusBar ? StatusHeight : 0;
uint multiplier = settings["View/Multiplier"].natural(); setMinimumSize({width, height + statusHeight});
if(!multiplier) multiplier = 2;
setMinimumSize({width, height + StatusHeight});
setSize({width * multiplier, height * multiplier + statusHeight}); setSize({width * multiplier, height * multiplier + statusHeight});
resizeViewport(); resizeViewport();
} }
auto Presentation::toggleFullscreenMode() -> void { auto Presentation::toggleFullscreenMode() -> void {
if(!fullScreen()) { if(!fullScreen()) {
if(settings["UserInterface/ShowStatusBar"].boolean()) { if(settings.general.statusBar) layout.remove(statusLayout);
layout.remove(statusLayout);
}
menuBar.setVisible(false); menuBar.setVisible(false);
setFullScreen(true); setFullScreen(true);
video.setExclusive(settings["Video/Exclusive"].boolean()); video.setExclusive(settings.video.exclusive);
if(video.exclusive()) setVisible(false); if(video.exclusive()) setVisible(false);
if(!input.acquired()) input.acquire(); if(!input.acquired()) input.acquire();
resizeViewport(); resizeViewport();
@ -323,9 +315,7 @@ auto Presentation::toggleFullscreenMode() -> void {
video.setExclusive(false); video.setExclusive(false);
setFullScreen(false); setFullScreen(false);
menuBar.setVisible(true); menuBar.setVisible(true);
if(settings["UserInterface/ShowStatusBar"].boolean()) { if(settings.general.statusBar) layout.append(statusLayout, Size{~0, StatusHeight});
layout.append(statusLayout, Size{~0, StatusHeight});
}
resizeWindow(); resizeWindow();
setCentered(); setCentered();
} }
@ -406,13 +396,13 @@ auto Presentation::updateSizeMenu() -> void {
item.setProperty("multiplier", multiplier); item.setProperty("multiplier", multiplier);
item.setText({multiplier, "x (", 240 * multiplier, "p)"}); item.setText({multiplier, "x (", 240 * multiplier, "p)"});
item.onActivate([=] { item.onActivate([=] {
settings["View/Multiplier"].setValue(multiplier); settings.video.multiplier = multiplier;
resizeWindow(); resizeWindow();
}); });
sizeGroup.append(item); sizeGroup.append(item);
} }
for(auto item : sizeGroup.objects<MenuRadioItem>()) { for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(settings["View/Multiplier"].natural() == item.property("multiplier").natural()) { if(settings.video.multiplier == item.property("multiplier").natural()) {
item.setChecked(); item.setChecked();
} }
} }
@ -433,9 +423,9 @@ auto Presentation::updateStateMenus() -> void {
if(auto item = action.cast<MenuItem>()) { if(auto item = action.cast<MenuItem>()) {
if(auto name = item.property("name")) { if(auto name = item.property("name")) {
if(auto offset = states.find([&](auto& state) { return state.name == name; })) { if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
item.setText({item.property("title"), " [", chrono::local::datetime(states[*offset].date), "]"}); item.setText({item.property("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
} else { } else {
item.setText({item.property("title"), " [Empty]"}); item.setText({item.property("title"), " (empty)"});
} }
} }
} }
@ -446,10 +436,10 @@ auto Presentation::updateStateMenus() -> void {
if(auto name = item.property("name")) { if(auto name = item.property("name")) {
if(auto offset = states.find([&](auto& state) { return state.name == name; })) { if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
item.setEnabled(true); item.setEnabled(true);
item.setText({item.property("title"), " [", chrono::local::datetime(states[*offset].date), "]"}); item.setText({item.property("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
} else { } else {
item.setEnabled(false); item.setEnabled(false);
item.setText({item.property("title"), " [Empty]"}); item.setText({item.property("title"), " (empty)"});
} }
} }
} }
@ -461,7 +451,7 @@ auto Presentation::updateRecentGames() -> void {
//remove missing games from list //remove missing games from list
for(uint index = 0; index < RecentGames;) { for(uint index = 0; index < RecentGames;) {
auto games = settings[string{"Game/Recent/", 1 + index}].text(); auto games = settings[{"Game/Recent/", 1 + index}].text();
bool missing = false; bool missing = false;
if(games) { if(games) {
for(auto& game : games.split("|")) { for(auto& game : games.split("|")) {
@ -472,8 +462,8 @@ auto Presentation::updateRecentGames() -> void {
//will read one past the end of Games/Recent[RecentGames] by design: //will read one past the end of Games/Recent[RecentGames] by design:
//this will always return an empty string to clear the last item in the list //this will always return an empty string to clear the last item in the list
for(uint offset = index; offset < RecentGames; offset++) { for(uint offset = index; offset < RecentGames; offset++) {
settings[string{"Game/Recent/", 1 + offset}].setValue( settings[{"Game/Recent/", 1 + offset}].setValue(
settings[string{"Game/Recent/", 2 + offset}].text() settings[{"Game/Recent/", 2 + offset}].text()
); );
} }
} else { } else {
@ -484,7 +474,7 @@ auto Presentation::updateRecentGames() -> void {
//update list //update list
for(auto index : range(RecentGames)) { for(auto index : range(RecentGames)) {
MenuItem item; MenuItem item;
if(auto game = settings[string{"Game/Recent/", 1 + index}].text()) { if(auto game = settings[{"Game/Recent/", 1 + index}].text()) {
string displayName; string displayName;
auto games = game.split("|"); auto games = game.split("|");
for(auto& part : games) { for(auto& part : games) {
@ -498,7 +488,7 @@ auto Presentation::updateRecentGames() -> void {
program.load(); program.load();
}); });
} else { } else {
item.setText({"[", tr("Empty"), "]"}); item.setText({"(", tr("empty"), ")"});
item.setEnabled(false); item.setEnabled(false);
} }
loadRecentGame.append(item); loadRecentGame.append(item);
@ -535,36 +525,36 @@ auto Presentation::updateShaders() -> void {
MenuRadioItem none{&shaderMenu}; MenuRadioItem none{&shaderMenu};
none.setText("None").onActivate([&] { none.setText("None").onActivate([&] {
settings["Video/Shader"].setValue("None"); settings.video.shader = "None";
program.updateVideoShader(); program.updateVideoShader();
}); });
shaders.append(none); shaders.append(none);
MenuRadioItem blur{&shaderMenu}; MenuRadioItem blur{&shaderMenu};
blur.setText("Blur").onActivate([&] { blur.setText("Blur").onActivate([&] {
settings["Video/Shader"].setValue("Blur"); settings.video.shader = "Blur";
program.updateVideoShader(); program.updateVideoShader();
}); });
shaders.append(blur); shaders.append(blur);
auto location = locate("shaders/"); auto location = locate("shaders/");
if(settings["Video/Driver"].text() == "OpenGL") { if(settings.video.driver == "OpenGL") {
for(auto shader : directory::folders(location, "*.shader")) { for(auto shader : directory::folders(location, "*.shader")) {
if(shaders.objectCount() == 2) shaderMenu.append(MenuSeparator()); if(shaders.objectCount() == 2) shaderMenu.append(MenuSeparator());
MenuRadioItem item{&shaderMenu}; MenuRadioItem item{&shaderMenu};
item.setText(string{shader}.trimRight(".shader/", 1L)).onActivate([=] { item.setText(string{shader}.trimRight(".shader/", 1L)).onActivate([=] {
settings["Video/Shader"].setValue({location, shader}); settings.video.shader = {location, shader};
program.updateVideoShader(); program.updateVideoShader();
}); });
shaders.append(item); shaders.append(item);
} }
} }
if(settings["Video/Shader"].text() == "None") none.setChecked(); if(settings.video.shader == "None") none.setChecked();
if(settings["Video/Shader"].text() == "Blur") blur.setChecked(); if(settings.video.shader == "Blur") blur.setChecked();
for(auto item : shaders.objects<MenuRadioItem>()) { for(auto item : shaders.objects<MenuRadioItem>()) {
if(settings["Video/Shader"].text() == string{location, item.text(), ".shader/"}) { if(settings.video.shader == string{location, item.text(), ".shader/"}) {
item.setChecked(); item.setChecked();
} }
} }

View File

@ -62,7 +62,7 @@ struct Presentation : Window {
Group outputGroup{&centerViewport, &scaleViewport, &stretchViewport}; Group outputGroup{&centerViewport, &scaleViewport, &stretchViewport};
MenuSeparator outputSeparator{&outputMenu}; MenuSeparator outputSeparator{&outputMenu};
MenuCheckItem aspectCorrection{&outputMenu}; MenuCheckItem aspectCorrection{&outputMenu};
MenuCheckItem overscanCropping{&outputMenu}; MenuCheckItem showOverscanArea{&outputMenu};
MenuCheckItem blurEmulation{&outputMenu}; MenuCheckItem blurEmulation{&outputMenu};
Menu shaderMenu{&settingsMenu}; Menu shaderMenu{&settingsMenu};
MenuSeparator settingsSeparatorA{&settingsMenu}; MenuSeparator settingsSeparatorA{&settingsMenu};

View File

@ -1,12 +1,12 @@
auto Program::updateAudioDriver(Window parent) -> void { auto Program::updateAudioDriver(Window parent) -> void {
auto changed = (bool)audio; auto changed = (bool)audio;
audio.create(settings["Audio/Driver"].text()); audio.create(settings.audio.driver);
audio.setContext(presentation.viewport.handle()); audio.setContext(presentation.viewport.handle());
audio.setChannels(2); audio.setChannels(2);
if(changed) { if(changed) {
settings["Audio/Device"].setValue(audio.device()); settings.audio.device = audio.device();
settings["Audio/Frequency"].setValue(audio.frequency()); settings.audio.frequency = audio.frequency();
settings["Audio/Latency"].setValue(audio.latency()); settings.audio.latency = audio.latency();
} }
updateAudioExclusive(); updateAudioExclusive();
updateAudioDevice(); updateAudioDevice();
@ -15,63 +15,65 @@ auto Program::updateAudioDriver(Window parent) -> void {
if(!audio.ready()) { if(!audio.ready()) {
MessageDialog({ MessageDialog({
"Error: failed to initialize [", settings["Audio/Driver"].text(), "] audio driver." "Error: failed to initialize [", settings.audio.driver, "] audio driver."
}).setParent(parent).error(); }).setParent(parent).error();
settings["Audio/Driver"].setValue("None"); settings.audio.driver = "None";
return updateAudioDriver(parent); return updateAudioDriver(parent);
} }
} }
auto Program::updateAudioExclusive() -> void { auto Program::updateAudioExclusive() -> void {
audio.setExclusive(settings["Audio/Exclusive"].boolean()); audio.setExclusive(settings.audio.exclusive);
updateAudioFrequency(); updateAudioFrequency();
updateAudioLatency(); updateAudioLatency();
} }
auto Program::updateAudioDevice() -> void { auto Program::updateAudioDevice() -> void {
audio.clear(); audio.clear();
if(!audio.hasDevice(settings["Audio/Device"].text())) { if(!audio.hasDevice(settings.audio.device)) {
settings["Audio/Device"].setValue(audio.device()); settings.audio.device = audio.device();
} }
audio.setDevice(settings["Audio/Device"].text()); audio.setDevice(settings.audio.device);
updateAudioFrequency(); updateAudioFrequency();
updateAudioLatency(); updateAudioLatency();
} }
auto Program::updateAudioBlocking() -> void { auto Program::updateAudioBlocking() -> void {
audio.clear(); audio.clear();
audio.setBlocking(settings["Audio/Blocking"].boolean()); audio.setBlocking(settings.audio.blocking);
} }
auto Program::updateAudioDynamic() -> void { auto Program::updateAudioDynamic() -> void {
audio.setDynamic(settings["Audio/Dynamic"].boolean()); audio.setDynamic(settings.audio.dynamic);
} }
auto Program::updateAudioFrequency() -> void { auto Program::updateAudioFrequency() -> void {
audio.clear(); audio.clear();
if(!audio.hasFrequency(settings["Audio/Frequency"].real())) { if(!audio.hasFrequency(settings.audio.frequency)) {
settings["Audio/Frequency"].setValue(audio.frequency()); settings.audio.frequency = audio.frequency();
} }
audio.setFrequency(settings["Audio/Frequency"].real()); audio.setFrequency(settings.audio.frequency);
double frequency = settings["Audio/Frequency"].real() + settings["Audio/Skew"].integer(); double frequency = settings.audio.frequency + settings.audio.skew;
if(!settings.video.blocking && settings.audio.blocking) {
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) { for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
if(item.checked()) frequency *= item.property("multiplier").real(); if(item.checked()) frequency *= item.property("multiplier").real();
} }
}
Emulator::audio.setFrequency(frequency); Emulator::audio.setFrequency(frequency);
} }
auto Program::updateAudioLatency() -> void { auto Program::updateAudioLatency() -> void {
audio.clear(); audio.clear();
if(!audio.hasLatency(settings["Audio/Latency"].natural())) { if(!audio.hasLatency(settings.audio.latency)) {
settings["Audio/Latency"].setValue(audio.latency()); settings.audio.latency = audio.latency();
} }
audio.setLatency(settings["Audio/Latency"].natural()); audio.setLatency(settings.audio.latency);
} }
auto Program::updateAudioEffects() -> void { auto Program::updateAudioEffects() -> void {
double volume = settings["Audio/Mute"].boolean() ? 0.0 : settings["Audio/Volume"].natural() * 0.01; double volume = settings.audio.mute ? 0.0 : settings.audio.volume * 0.01;
Emulator::audio.setVolume(volume); Emulator::audio.setVolume(volume);
double balance = max(-1.0, min(+1.0, (settings["Audio/Balance"].integer() - 50) / 50.0)); double balance = max(-1.0, min(+1.0, (settings.audio.balance - 50) / 50.0));
Emulator::audio.setBalance(balance); Emulator::audio.setBalance(balance);
} }

View File

@ -1,6 +1,6 @@
auto Program::updateInputDriver(Window parent) -> void { auto Program::updateInputDriver(Window parent) -> void {
auto changed = (bool)input; auto changed = (bool)input;
input.create(settings["Input/Driver"].text()); input.create(settings.input.driver);
input.setContext(presentation.viewport.handle()); input.setContext(presentation.viewport.handle());
if(changed) { if(changed) {
} }
@ -11,9 +11,9 @@ auto Program::updateInputDriver(Window parent) -> void {
if(!input.ready()) { if(!input.ready()) {
MessageDialog({ MessageDialog({
"Error: failed to initialize [", settings["Input/Driver"].text(), "] input driver." "Error: failed to initialize [", settings.input.driver, "] input driver."
}).setParent(parent).error(); }).setParent(parent).error();
settings["Input/Driver"].setValue("None"); settings.input.driver = "None";
return updateInputDriver(parent); return updateInputDriver(parent);
} }
} }

View File

@ -5,39 +5,27 @@ auto Program::path(string type, string location, string extension) -> string {
auto suffix = extension; auto suffix = extension;
if(type == "Games") { if(type == "Games") {
if(auto path = settings["Path/Games"].text()) { if(auto path = settings.path.games) pathname = path;
pathname = path;
}
} }
if(type == "Patches") { if(type == "Patches") {
if(auto path = settings["Path/Patches"].text()) { if(auto path = settings.path.patches) pathname = path;
pathname = path;
}
} }
if(type == "Saves") { if(type == "Saves") {
if(auto path = settings["Path/Saves"].text()) { if(auto path = settings.path.saves) pathname = path;
pathname = path;
}
} }
if(type == "Cheats") { if(type == "Cheats") {
if(auto path = settings["Path/Cheats"].text()) { if(auto path = settings.path.cheats) pathname = path;
pathname = path;
}
} }
if(type == "States") { if(type == "States") {
if(auto path = settings["Path/States"].text()) { if(auto path = settings.path.states) pathname = path;
pathname = path;
}
} }
if(type == "Screenshots") { if(type == "Screenshots") {
if(auto path = settings["Path/Screenshots"].text()) { if(auto path = settings.path.screenshots) pathname = path;
pathname = path;
}
} }
return {pathname, prefix, suffix}; return {pathname, prefix, suffix};

View File

@ -109,12 +109,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
superFamicom.location = gameQueue.takeLeft(); superFamicom.location = gameQueue.takeLeft();
} else { } else {
dialog.setTitle("Load Super Famicom"); dialog.setTitle("Load Super Famicom");
dialog.setPath(path("Games", settings["Path/Recent/SuperFamicom"].text())); dialog.setPath(path("Games", settings.path.recent.superFamicom));
dialog.setFilters({string{"Super Famicom Games|*.sfc:*.smc:*.zip"}}); dialog.setFilters({string{"Super Famicom Games|*.sfc:*.smc:*.zip"}});
superFamicom.location = dialog.openObject(); superFamicom.location = dialog.openObject();
} }
if(inode::exists(superFamicom.location)) { if(inode::exists(superFamicom.location)) {
settings["Path/Recent/SuperFamicom"].setValue(Location::dir(superFamicom.location)); settings.path.recent.superFamicom = Location::dir(superFamicom.location);
if(loadSuperFamicom(superFamicom.location)) { if(loadSuperFamicom(superFamicom.location)) {
return {id, dialog.option()}; return {id, dialog.option()};
} }
@ -126,12 +126,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
gameBoy.location = gameQueue.takeLeft(); gameBoy.location = gameQueue.takeLeft();
} else { } else {
dialog.setTitle("Load Game Boy"); dialog.setTitle("Load Game Boy");
dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text())); dialog.setPath(path("Games", settings.path.recent.gameBoy));
dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}}); dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}});
gameBoy.location = dialog.openObject(); gameBoy.location = dialog.openObject();
} }
if(inode::exists(gameBoy.location)) { if(inode::exists(gameBoy.location)) {
settings["Path/Recent/GameBoy"].setValue(Location::dir(gameBoy.location)); settings.path.recent.gameBoy = Location::dir(gameBoy.location);
if(loadGameBoy(gameBoy.location)) { if(loadGameBoy(gameBoy.location)) {
return {id, dialog.option()}; return {id, dialog.option()};
} }
@ -143,12 +143,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
bsMemory.location = gameQueue.takeLeft(); bsMemory.location = gameQueue.takeLeft();
} else { } else {
dialog.setTitle("Load BS Memory"); dialog.setTitle("Load BS Memory");
dialog.setPath(path("Games", settings["Path/Recent/BSMemory"].text())); dialog.setPath(path("Games", settings.path.recent.bsMemory));
dialog.setFilters({string{"BS Memory Games|*.bs:*.zip"}}); dialog.setFilters({string{"BS Memory Games|*.bs:*.zip"}});
bsMemory.location = dialog.openObject(); bsMemory.location = dialog.openObject();
} }
if(inode::exists(bsMemory.location)) { if(inode::exists(bsMemory.location)) {
settings["Path/Recent/BSMemory"].setValue(Location::dir(bsMemory.location)); settings.path.recent.bsMemory = Location::dir(bsMemory.location);
if(loadBSMemory(bsMemory.location)) { if(loadBSMemory(bsMemory.location)) {
return {id, dialog.option()}; return {id, dialog.option()};
} }
@ -160,12 +160,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
sufamiTurboA.location = gameQueue.takeLeft(); sufamiTurboA.location = gameQueue.takeLeft();
} else { } else {
dialog.setTitle("Load Sufami Turbo - Slot A"); dialog.setTitle("Load Sufami Turbo - Slot A");
dialog.setPath(path("Games", settings["Path/Recent/SufamiTurboA"].text())); dialog.setPath(path("Games", settings.path.recent.sufamiTurboA));
dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}}); dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}});
sufamiTurboA.location = dialog.openObject(); sufamiTurboA.location = dialog.openObject();
} }
if(inode::exists(sufamiTurboA.location)) { if(inode::exists(sufamiTurboA.location)) {
settings["Path/Recent/SufamiTurboA"].setValue(Location::dir(sufamiTurboA.location)); settings.path.recent.sufamiTurboA = Location::dir(sufamiTurboA.location);
if(loadSufamiTurboA(sufamiTurboA.location)) { if(loadSufamiTurboA(sufamiTurboA.location)) {
return {id, dialog.option()}; return {id, dialog.option()};
} }
@ -177,12 +177,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
sufamiTurboB.location = gameQueue.takeLeft(); sufamiTurboB.location = gameQueue.takeLeft();
} else { } else {
dialog.setTitle("Load Sufami Turbo - Slot B"); dialog.setTitle("Load Sufami Turbo - Slot B");
dialog.setPath(path("Games", settings["Path/Recent/SufamiTurboB"].text())); dialog.setPath(path("Games", settings.path.recent.sufamiTurboB));
dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}}); dialog.setFilters({string{"Sufami Turbo Games|*.st:*.zip"}});
sufamiTurboB.location = dialog.openObject(); sufamiTurboB.location = dialog.openObject();
} }
if(inode::exists(sufamiTurboB.location)) { if(inode::exists(sufamiTurboB.location)) {
settings["Path/Recent/SufamiTurboB"].setValue(Location::dir(sufamiTurboB.location)); settings.path.recent.sufamiTurboB = Location::dir(sufamiTurboB.location);
if(loadSufamiTurboB(sufamiTurboB.location)) { if(loadSufamiTurboB(sufamiTurboB.location)) {
return {id, dialog.option()}; return {id, dialog.option()};
} }
@ -205,7 +205,7 @@ auto Program::videoRefresh(uint display, const uint32* data, uint pitch, uint wi
screenshot.height = height; screenshot.height = height;
pitch >>= 2; pitch >>= 2;
if(presentation.overscanCropping.checked()) { if(!presentation.showOverscanArea.checked()) {
if(height == 240) data += 8 * pitch, height -= 16; if(height == 240) data += 8 * pitch, height -= 16;
if(height == 480) data += 16 * pitch, height -= 32; if(height == 480) data += 16 * pitch, height -= 32;
} }

View File

@ -37,22 +37,22 @@ auto Program::create(vector<string> arguments) -> void {
stateManager.create(); stateManager.create();
manifestViewer.create(); manifestViewer.create();
if(settings["Crashed"].boolean()) { if(settings.general.crashed) {
MessageDialog( MessageDialog(
"Driver crash detected. Hardware drivers have been disabled.\n" "Driver crash detected. Hardware drivers have been disabled.\n"
"Please reconfigure drivers in the advanced settings panel." "Please reconfigure drivers in the advanced settings panel."
).setParent(*presentation).information(); ).setParent(*presentation).information();
settings["Video/Driver"].setValue("None"); settings.video.driver = "None";
settings["Audio/Driver"].setValue("None"); settings.audio.driver = "None";
settings["Input/Driver"].setValue("None"); settings.input.driver = "None";
} }
settings["Crashed"].setValue(true); settings.general.crashed = true;
settings.save(); settings.save();
updateVideoDriver(presentation); updateVideoDriver(presentation);
updateAudioDriver(presentation); updateAudioDriver(presentation);
updateInputDriver(presentation); updateInputDriver(presentation);
settings["Crashed"].setValue(false); settings.general.crashed = false;
settings.save(); settings.save();
driverSettings.videoDriverChanged(); driverSettings.videoDriverChanged();
@ -89,7 +89,7 @@ auto Program::main() -> void {
emulator->run(); emulator->run();
if(emulatorSettings.autoSaveMemory.checked()) { if(emulatorSettings.autoSaveMemory.checked()) {
auto currentTime = chrono::timestamp(); auto currentTime = chrono::timestamp();
if(currentTime - autoSaveTime >= settings["Emulator/AutoSaveMemory/Interval"].natural()) { if(currentTime - autoSaveTime >= settings.emulator.autoSaveMemory.interval) {
autoSaveTime = currentTime; autoSaveTime = currentTime;
emulator->save(); emulator->save();
} }

View File

@ -46,7 +46,7 @@ auto Program::captureScreenshot() -> bool {
auto width = capture.width(); auto width = capture.width();
auto height = capture.height(); auto height = capture.height();
if(presentation.overscanCropping.checked()) { if(!presentation.showOverscanArea.checked()) {
if(height == 240) data += 8 * pitch, height -= 16; if(height == 240) data += 8 * pitch, height -= 16;
if(height == 480) data += 16 * pitch, height -= 32; if(height == 480) data += 16 * pitch, height -= 32;
} }

View File

@ -1,9 +1,9 @@
auto Program::updateVideoDriver(Window parent) -> void { auto Program::updateVideoDriver(Window parent) -> void {
auto changed = (bool)video; auto changed = (bool)video;
video.create(settings["Video/Driver"].text()); video.create(settings.video.driver);
video.setContext(presentation.viewport.handle()); video.setContext(presentation.viewport.handle());
if(changed) { if(changed) {
settings["Video/Format"].setValue(video.format()); settings.video.format = video.format();
} }
updateVideoExclusive(); updateVideoExclusive();
updateVideoBlocking(); updateVideoBlocking();
@ -22,9 +22,9 @@ auto Program::updateVideoDriver(Window parent) -> void {
if(!video.ready()) { if(!video.ready()) {
MessageDialog({ MessageDialog({
"Error: failed to initialize [", settings["Video/Driver"].text(), "] video driver." "Error: failed to initialize [", settings.video.driver, "] video driver."
}).setParent(parent).error(); }).setParent(parent).error();
settings["Video/Driver"].setValue("None"); settings.video.driver = "None";
return updateVideoDriver(parent); return updateVideoDriver(parent);
} }
@ -37,40 +37,37 @@ auto Program::updateVideoExclusive() -> void {
} }
auto Program::updateVideoBlocking() -> void { auto Program::updateVideoBlocking() -> void {
video.setBlocking(settings["Video/Blocking"].boolean()); video.setBlocking(settings.video.blocking);
} }
auto Program::updateVideoFlush() -> void { auto Program::updateVideoFlush() -> void {
video.setFlush(settings["Video/Flush"].boolean()); video.setFlush(settings.video.flush);
} }
auto Program::updateVideoFormat() -> void { auto Program::updateVideoFormat() -> void {
if(!video.hasFormat(settings["Video/Format"].text())) { if(!video.hasFormat(settings.video.format)) {
settings["Video/Format"].setValue(video.format()); settings.video.format = video.format();
} }
video.setFormat(settings["Video/Format"].text()); video.setFormat(settings.video.format);
} }
auto Program::updateVideoShader() -> void { auto Program::updateVideoShader() -> void {
if(settings["Video/Driver"].text() == "OpenGL" if(settings.video.driver == "OpenGL"
&& settings["Video/Shader"].text() != "None" && settings.video.shader != "None"
&& settings["Video/Shader"].text() != "Blur" && settings.video.shader != "Blur"
) { ) {
video.setSmooth(false); video.setSmooth(false);
video.setShader(settings["Video/Shader"].text()); video.setShader(settings.video.shader);
} else { } else {
video.setSmooth(settings["Video/Shader"].text() == "Blur"); video.setSmooth(settings.video.shader == "Blur");
video.setShader(""); video.setShader("");
} }
} }
auto Program::updateVideoPalette() -> void { auto Program::updateVideoPalette() -> void {
emulator->configure("video/colorEmulation", false); emulator->configure("Video/ColorEmulation", false);
double luminance = settings["Video/Luminance"].natural() / 100.0; Emulator::video.setLuminance(settings.video.luminance / 100.0);
double saturation = settings["Video/Saturation"].natural() / 100.0; Emulator::video.setSaturation(settings.video.saturation / 100.0);
double gamma = settings["Video/Gamma"].natural() / 100.0; Emulator::video.setGamma(settings.video.gamma / 100.0);
Emulator::video.setLuminance(luminance);
Emulator::video.setSaturation(saturation);
Emulator::video.setGamma(gamma);
Emulator::video.setPalette(); Emulator::video.setPalette();
} }

View File

@ -45,7 +45,7 @@ namespace: Presentation
input: Load Recent Game input: Load Recent Game
value: 最新ゲームを読み込み value: 最新ゲームを読み込み
map map
input: Empty input: empty
value: なし value: なし
map map
input: Clear List input: Clear List

View File

@ -7,27 +7,43 @@ auto AudioSettings::create() -> void {
effectsLabel.setFont(Font().setBold()).setText("Effects"); effectsLabel.setFont(Font().setBold()).setText("Effects");
effectsLayout.setSize({3, 3}); effectsLayout.setSize({3, 3});
effectsLayout.column(0).setAlignment(1.0); effectsLayout.column(0).setAlignment(1.0);
skewLabel.setText("Skew:"); skewLabel.setText("Skew:").setToolTip(
skewValue.setAlignment(0.5); "Adjusts the audio frequency by the skew amount (in hz.)\n\n"
skewSlider.setLength(10001).setPosition(settings["Audio/Skew"].integer() + 5000).onChange([&] { "This is essentially static rate control:\n"
"First, enable both video and audio sync.\n"
"Then, raise or lower this value to try to reduce errors.\n"
"One direction will help video, but hurt audio.\n"
"The other direction will do the reverse.\n"
"The idea is to find the best middle ground.\n\n"
"You should leave this at 0 when using dynamic rate control."
);
skewValue.setAlignment(0.5).setToolTip(skewLabel.toolTip());
skewSlider.setLength(10001).setPosition(settings.audio.skew + 5000).onChange([&] {
string value = {skewSlider.position() > 5000 ? "+" : "", (int)skewSlider.position() - 5000}; string value = {skewSlider.position() > 5000 ? "+" : "", (int)skewSlider.position() - 5000};
settings["Audio/Skew"].setValue(value); settings.audio.skew = value.integer();
skewValue.setText(value); skewValue.setText(value);
program.updateAudioFrequency(); program.updateAudioFrequency();
}).doChange(); }).doChange();
volumeLabel.setText("Volume:"); volumeLabel.setText("Volume:").setToolTip(
volumeValue.setAlignment(0.5); "Adjusts the audio output volume.\n\n"
volumeSlider.setLength(201).setPosition(settings["Audio/Volume"].natural()).onChange([&] { "You should not use values above 100%, if possible!\n"
"If you do, audio clipping distortion can occur."
);
volumeValue.setAlignment(0.5).setToolTip(volumeLabel.toolTip());
volumeSlider.setLength(201).setPosition(settings.audio.volume).onChange([&] {
string value = {volumeSlider.position(), "%"}; string value = {volumeSlider.position(), "%"};
settings["Audio/Volume"].setValue(value); settings.audio.volume = value.natural();
volumeValue.setText(value); volumeValue.setText(value);
program.updateAudioEffects(); program.updateAudioEffects();
}).doChange(); }).doChange();
balanceLabel.setText("Balance:"); balanceLabel.setText("Balance:").setToolTip(
balanceValue.setAlignment(0.5); "Pans audio to the left (lower values) or right (higher values.)\n\n"
balanceSlider.setLength(101).setPosition(settings["Audio/Balance"].natural()).onChange([&] { "50% (centered) is the recommended setting."
);
balanceValue.setAlignment(0.5).setToolTip(balanceLabel.toolTip());
balanceSlider.setLength(101).setPosition(settings.audio.balance).onChange([&] {
string value = {balanceSlider.position(), "%"}; string value = {balanceSlider.position(), "%"};
settings["Audio/Balance"].setValue(value); settings.audio.balance = value.natural();
balanceValue.setText(value); balanceValue.setText(value);
program.updateAudioEffects(); program.updateAudioEffects();
}).doChange(); }).doChange();

View File

@ -13,17 +13,33 @@ auto DriverSettings::create() -> void {
videoDriverUpdate.setText("Change").onActivate([&] { videoDriverChange(); }); videoDriverUpdate.setText("Change").onActivate([&] { videoDriverChange(); });
videoFormatLabel.setText("Format:"); videoFormatLabel.setText("Format:");
videoFormatOption.onChange([&] { videoFormatChange(); }); videoFormatOption.onChange([&] { videoFormatChange(); });
videoExclusiveToggle.setText("Exclusive fullscreen").onToggle([&] { videoExclusiveToggle.setText("Exclusive").setToolTip(
settings["Video/Exclusive"].setValue(videoExclusiveToggle.checked()); "(Direct3D only)\n\n"
"Acquires exclusive access to the display in fullscreen mode.\n"
"Eliminates compositing issues such as video stuttering."
).onToggle([&] {
settings.video.exclusive = videoExclusiveToggle.checked();
program.updateVideoExclusive(); program.updateVideoExclusive();
}); });
videoBlockingToggle.setText("Synchronize").onToggle([&] { videoBlockingToggle.setText("Synchronize").setToolTip(
settings["Video/Blocking"].setValue(videoBlockingToggle.checked()); "Waits for the video card to be ready before rendering frames.\n"
"Eliminates dropped or duplicated frames; but can distort audio.\n\n"
"With this option, it's recommended to disable audio sync,\n"
"and enable dynamic rate control. Or alternatively, adjust the\n"
"audio skew option to reduce buffer under/overflows."
).onToggle([&] {
settings.video.blocking = videoBlockingToggle.checked();
program.updateVideoBlocking(); program.updateVideoBlocking();
presentation.speedMenu.setEnabled(!videoBlockingToggle.checked() && audioBlockingToggle.checked()); presentation.speedMenu.setEnabled(!videoBlockingToggle.checked() && audioBlockingToggle.checked());
}); });
videoFlushToggle.setText("GPU sync").onToggle([&] { videoFlushToggle.setText("GPU sync").setToolTip({
settings["Video/Flush"].setValue(videoFlushToggle.checked()); "(OpenGL only)\n\n"
"Causes the GPU to wait until frames are fully rendered.\n"
"In the best case, this can remove up to one frame of input lag.\n"
"However, it incurs a roughly 20% performance penalty.\n\n"
"You should disable this option unless you find it necessary."
}).onToggle([&] {
settings.video.flush = videoFlushToggle.checked();
program.updateVideoFlush(); program.updateVideoFlush();
}); });
videoSpacer.setColor({192, 192, 192}); videoSpacer.setColor({192, 192, 192});
@ -41,17 +57,34 @@ auto DriverSettings::create() -> void {
audioFrequencyOption.onChange([&] { audioFrequencyChange(); }); audioFrequencyOption.onChange([&] { audioFrequencyChange(); });
audioLatencyLabel.setText("Latency:"); audioLatencyLabel.setText("Latency:");
audioLatencyOption.onChange([&] { audioLatencyChange(); }); audioLatencyOption.onChange([&] { audioLatencyChange(); });
audioExclusiveToggle.setText("Exclusive").onToggle([&] { audioExclusiveToggle.setText("Exclusive").setToolTip(
settings["Audio/Exclusive"].setValue(audioExclusiveToggle.checked()); "(ASIO, WASAPI only)\n\n"
"Acquires exclusive control of the sound card device.\n"
"This can significantly reduce audio latency.\n"
"However, it will block sounds from all other applications."
).onToggle([&] {
settings.audio.exclusive = audioExclusiveToggle.checked();
program.updateAudioExclusive(); program.updateAudioExclusive();
}); });
audioBlockingToggle.setText("Synchronize").onToggle([&] { audioBlockingToggle.setText("Synchronize").setToolTip(
settings["Audio/Blocking"].setValue(audioBlockingToggle.checked()); "Waits for the audio card to be ready before outputting samples.\n"
"Eliminates audio distortio; but can distort video.\n\n"
"With this option, it's recommended to disable video sync.\n"
"For best results, use this with an adaptive sync monitor."
).onToggle([&] {
settings.audio.blocking = audioBlockingToggle.checked();
program.updateAudioBlocking(); program.updateAudioBlocking();
presentation.speedMenu.setEnabled(!videoBlockingToggle.checked() && audioBlockingToggle.checked()); presentation.speedMenu.setEnabled(!videoBlockingToggle.checked() && audioBlockingToggle.checked());
}); });
audioDynamicToggle.setText("Dynamic rate").onToggle([&] { audioDynamicToggle.setText("Dynamic rate").setToolTip(
settings["Audio/Dynamic"].setValue(audioDynamicToggle.checked()); "(OSS only)\n\n"
"Dynamically adjusts the audio frequency by tiny amounts.\n"
"Use this with video sync enabled, and audio sync disabled.\n\n"
"This can produce perfectly smooth video and clean audio,\n"
"but only if your monitor refresh rate is set correctly:\n"
"60hz for NTSC games, and 50hz for PAL games."
).onToggle([&] {
settings.audio.dynamic = audioDynamicToggle.checked();
program.updateAudioDynamic(); program.updateAudioDynamic();
}); });
audioSpacer.setColor({192, 192, 192}); audioSpacer.setColor({192, 192, 192});
@ -90,7 +123,7 @@ auto DriverSettings::videoDriverChanged() -> void {
auto DriverSettings::videoDriverChange() -> void { auto DriverSettings::videoDriverChange() -> void {
auto item = videoDriverOption.selected(); auto item = videoDriverOption.selected();
settings["Video/Driver"].setValue(item.text()); settings.video.driver = item.text();
if(!emulator->loaded() || item.text() == "None" || MessageDialog( if(!emulator->loaded() || item.text() == "None" || MessageDialog(
"Warning: incompatible drivers may cause bsnes to crash.\n" "Warning: incompatible drivers may cause bsnes to crash.\n"
"It is highly recommended you unload your game first to be safe.\n" "It is highly recommended you unload your game first to be safe.\n"
@ -98,10 +131,10 @@ auto DriverSettings::videoDriverChange() -> void {
).setParent(*settingsWindow).question() == "Yes") { ).setParent(*settingsWindow).question() == "Yes") {
program.save(); program.save();
program.saveUndoState(); program.saveUndoState();
settings["Crashed"].setValue(true); settings.general.crashed = true;
settings.save(); settings.save();
program.updateVideoDriver(settingsWindow); program.updateVideoDriver(settingsWindow);
settings["Crashed"].setValue(false); settings.general.crashed = false;
settings.save(); settings.save();
videoDriverChanged(); videoDriverChanged();
} }
@ -120,7 +153,7 @@ auto DriverSettings::videoFormatChanged() -> void {
auto DriverSettings::videoFormatChange() -> void { auto DriverSettings::videoFormatChange() -> void {
auto item = videoFormatOption.selected(); auto item = videoFormatOption.selected();
settings["Video/Format"].setValue(item.text()); settings.video.format = item.text();
video.setFormat(item.text()); video.setFormat(item.text());
} }
@ -146,7 +179,7 @@ auto DriverSettings::audioDriverChanged() -> void {
auto DriverSettings::audioDriverChange() -> void { auto DriverSettings::audioDriverChange() -> void {
auto item = audioDriverOption.selected(); auto item = audioDriverOption.selected();
settings["Audio/Driver"].setValue(item.text()); settings.audio.driver = item.text();
if(!emulator->loaded() || item.text() == "None" || MessageDialog( if(!emulator->loaded() || item.text() == "None" || MessageDialog(
"Warning: incompatible drivers may cause bsnes to crash.\n" "Warning: incompatible drivers may cause bsnes to crash.\n"
"It is highly recommended you unload your game first to be safe.\n" "It is highly recommended you unload your game first to be safe.\n"
@ -154,10 +187,10 @@ auto DriverSettings::audioDriverChange() -> void {
).setParent(*settingsWindow).question() == "Yes") { ).setParent(*settingsWindow).question() == "Yes") {
program.save(); program.save();
program.saveUndoState(); program.saveUndoState();
settings["Crashed"].setValue(true); settings.general.crashed = true;
settings.save(); settings.save();
program.updateAudioDriver(settingsWindow); program.updateAudioDriver(settingsWindow);
settings["Crashed"].setValue(false); settings.general.crashed = false;
settings.save(); settings.save();
audioDriverChanged(); audioDriverChanged();
} }
@ -176,7 +209,7 @@ auto DriverSettings::audioDeviceChanged() -> void {
auto DriverSettings::audioDeviceChange() -> void { auto DriverSettings::audioDeviceChange() -> void {
auto item = audioDeviceOption.selected(); auto item = audioDeviceOption.selected();
settings["Audio/Device"].setValue(item.text()); settings.audio.device = item.text();
program.updateAudioDevice(); program.updateAudioDevice();
audioFrequencyChanged(); audioFrequencyChanged();
audioLatencyChanged(); audioLatencyChanged();
@ -186,7 +219,7 @@ auto DriverSettings::audioFrequencyChanged() -> void {
audioFrequencyOption.reset(); audioFrequencyOption.reset();
for(auto& frequency : audio.hasFrequencies()) { for(auto& frequency : audio.hasFrequencies()) {
ComboButtonItem item{&audioFrequencyOption}; ComboButtonItem item{&audioFrequencyOption};
item.setText({(uint)frequency, "hz"}); item.setText({frequency, "hz"});
if(frequency == audio.frequency()) item.setSelected(); if(frequency == audio.frequency()) item.setSelected();
} }
//audioFrequencyOption.setEnabled(audio->hasFrequency()); //audioFrequencyOption.setEnabled(audio->hasFrequency());
@ -195,7 +228,7 @@ auto DriverSettings::audioFrequencyChanged() -> void {
auto DriverSettings::audioFrequencyChange() -> void { auto DriverSettings::audioFrequencyChange() -> void {
auto item = audioFrequencyOption.selected(); auto item = audioFrequencyOption.selected();
settings["Audio/Frequency"].setValue(item.text()); settings.audio.frequency = item.text().natural();
program.updateAudioFrequency(); program.updateAudioFrequency();
} }
@ -212,7 +245,7 @@ auto DriverSettings::audioLatencyChanged() -> void {
auto DriverSettings::audioLatencyChange() -> void { auto DriverSettings::audioLatencyChange() -> void {
auto item = audioLatencyOption.selected(); auto item = audioLatencyOption.selected();
settings["Audio/Latency"].setValue(item.text()); settings.audio.latency = item.text().natural();
program.updateAudioLatency(); program.updateAudioLatency();
} }
@ -232,7 +265,7 @@ auto DriverSettings::inputDriverChanged() -> void {
auto DriverSettings::inputDriverChange() -> void { auto DriverSettings::inputDriverChange() -> void {
auto item = inputDriverOption.selected(); auto item = inputDriverOption.selected();
settings["Input/Driver"].setValue(item.text()); settings.input.driver = item.text();
if(!emulator->loaded() || item.text() == "None" || MessageDialog( if(!emulator->loaded() || item.text() == "None" || MessageDialog(
"Warning: incompatible drivers may cause bsnes to crash.\n" "Warning: incompatible drivers may cause bsnes to crash.\n"
"It is highly recommended you unload your game first to be safe.\n" "It is highly recommended you unload your game first to be safe.\n"
@ -240,10 +273,10 @@ auto DriverSettings::inputDriverChange() -> void {
).setParent(*settingsWindow).question() == "Yes") { ).setParent(*settingsWindow).question() == "Yes") {
program.save(); program.save();
program.saveUndoState(); program.saveUndoState();
settings["Crashed"].setValue(true); settings.general.crashed = true;
settings.save(); settings.save();
program.updateInputDriver(settingsWindow); program.updateInputDriver(settingsWindow);
settings["Crashed"].setValue(false); settings.general.crashed = false;
settings.save(); settings.save();
inputDriverChanged(); inputDriverChanged();
} }

View File

@ -7,43 +7,39 @@ auto EmulatorSettings::create() -> void {
optionsLabel.setText("Options").setFont(Font().setBold()); optionsLabel.setText("Options").setFont(Font().setBold());
inputFocusLabel.setText("When focus is lost:"); inputFocusLabel.setText("When focus is lost:");
pauseEmulation.setText("Pause emulation").onActivate([&] { pauseEmulation.setText("Pause emulation").onActivate([&] {
settings["Input/Defocus"].setValue("Pause"); settings.input.defocus = "Pause";
}); });
blockInput.setText("Block input").onActivate([&] { blockInput.setText("Block input").onActivate([&] {
settings["Input/Defocus"].setValue("Block"); settings.input.defocus = "Block";
}); });
allowInput.setText("Allow input").onActivate([&] { allowInput.setText("Allow input").onActivate([&] {
settings["Input/Defocus"].setValue("Allow"); settings.input.defocus = "Allow";
}); });
if(settings["Input/Defocus"].text() == "Pause") pauseEmulation.setChecked(); if(settings.input.defocus == "Pause") pauseEmulation.setChecked();
if(settings["Input/Defocus"].text() == "Block") blockInput.setChecked(); if(settings.input.defocus == "Block") blockInput.setChecked();
if(settings["Input/Defocus"].text() == "Allow") allowInput.setChecked(); if(settings.input.defocus == "Allow") allowInput.setChecked();
warnOnUnverifiedGames.setText("Warn when loading games that have not been verified").setChecked(settings["Emulator/WarnOnUnverifiedGames"].boolean()).onToggle([&] { warnOnUnverifiedGames.setText("Warn when loading games that have not been verified").setChecked(settings.emulator.warnOnUnverifiedGames).onToggle([&] {
settings["Emulator/WarnOnUnverifiedGames"].setValue(warnOnUnverifiedGames.checked()); settings.emulator.warnOnUnverifiedGames = warnOnUnverifiedGames.checked();
}); });
autoSaveMemory.setText("Auto-save memory periodically").setChecked(settings["Emulator/AutoSaveMemory/Enable"].boolean()).onToggle([&] { autoSaveMemory.setText("Auto-save memory periodically").setChecked(settings.emulator.autoSaveMemory.enable).onToggle([&] {
settings["Emulator/AutoSaveMemory/Enable"].setValue(autoSaveMemory.checked()); settings.emulator.autoSaveMemory.enable = autoSaveMemory.checked();
}); });
autoSaveStateOnUnload.setText("Auto-save undo state when unloading games").setChecked(settings["Emulator/AutoSaveStateOnUnload"].boolean()).onToggle([&] { autoSaveStateOnUnload.setText("Auto-save undo state when unloading games").setChecked(settings.emulator.autoSaveStateOnUnload).onToggle([&] {
settings["Emulator/AutoSaveStateOnUnload"].setValue(autoSaveStateOnUnload.checked()); settings.emulator.autoSaveStateOnUnload = autoSaveStateOnUnload.checked();
if(!autoSaveStateOnUnload.checked()) { if(!autoSaveStateOnUnload.checked()) {
autoLoadStateOnLoad.setEnabled(false).setChecked(false).doToggle(); autoLoadStateOnLoad.setEnabled(false).setChecked(false).doToggle();
} else { } else {
autoLoadStateOnLoad.setEnabled(true); autoLoadStateOnLoad.setEnabled(true);
} }
}).doToggle(); }).doToggle();
autoLoadStateOnLoad.setText("Auto-resume on load").setChecked(settings["Emulator/AutoLoadStateOnLoad"].boolean()).onToggle([&] { autoLoadStateOnLoad.setText("Auto-resume on load").setChecked(settings.emulator.autoLoadStateOnLoad).onToggle([&] {
settings["Emulator/AutoLoadStateOnLoad"].setValue(autoLoadStateOnLoad.checked()); settings.emulator.autoLoadStateOnLoad = autoLoadStateOnLoad.checked();
});
suppressScreenSaver.setText("Suppress screen saver").setChecked(settings["UserInterface/SuppressScreenSaver"].boolean()).onToggle([&] {
settings["UserInterface/SuppressScreenSaver"].setValue(suppressScreenSaver.checked());
Application::setScreenSaver(!suppressScreenSaver.checked());
}); });
optionsSpacer.setColor({192, 192, 192}); optionsSpacer.setColor({192, 192, 192});
hacksLabel.setText("Hacks").setFont(Font().setBold()); hacksLabel.setText("Hacks").setFont(Font().setBold());
fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/Hack/FastPPU"].boolean()).onToggle([&] { fastPPUOption.setText("Fast PPU").setChecked(settings.emulator.hack.fastPPU.enable).onToggle([&] {
settings["Emulator/Hack/FastPPU"].setValue(fastPPUOption.checked()); settings.emulator.hack.fastPPU.enable = fastPPUOption.checked();
if(!fastPPUOption.checked()) { if(!fastPPUOption.checked()) {
noSpriteLimit.setEnabled(false).setChecked(false).doToggle(); noSpriteLimit.setEnabled(false).setChecked(false).doToggle();
hiresMode7.setEnabled(false).setChecked(false).doToggle(); hiresMode7.setEnabled(false).setChecked(false).doToggle();
@ -52,27 +48,27 @@ auto EmulatorSettings::create() -> void {
hiresMode7.setEnabled(true); hiresMode7.setEnabled(true);
} }
}).doToggle(); }).doToggle();
noSpriteLimit.setText("No sprite limit").setChecked(settings["Emulator/Hack/FastPPU/NoSpriteLimit"].boolean()).onToggle([&] { noSpriteLimit.setText("No sprite limit").setChecked(settings.emulator.hack.fastPPU.noSpriteLimit).onToggle([&] {
settings["Emulator/Hack/FastPPU/NoSpriteLimit"].setValue(noSpriteLimit.checked()); settings.emulator.hack.fastPPU.noSpriteLimit = noSpriteLimit.checked();
}); });
hiresMode7.setText("Hires mode 7").setChecked(settings["Emulator/Hack/FastPPU/HiresMode7"].boolean()).onToggle([&] { hiresMode7.setText("Hires mode 7").setChecked(settings.emulator.hack.fastPPU.hiresMode7).onToggle([&] {
settings["Emulator/Hack/FastPPU/HiresMode7"].setValue(hiresMode7.checked()); settings.emulator.hack.fastPPU.hiresMode7 = hiresMode7.checked();
}); });
fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/Hack/FastDSP"].boolean()).onToggle([&] { fastDSPOption.setText("Fast DSP").setChecked(settings.emulator.hack.fastDSP.enable).onToggle([&] {
settings["Emulator/Hack/FastDSP"].setValue(fastDSPOption.checked()); settings.emulator.hack.fastDSP.enable = fastDSPOption.checked();
}); });
superFXLabel.setText("SuperFX clock speed:"); superFXLabel.setText("SuperFX clock speed:");
superFXValue.setAlignment(0.5); superFXValue.setAlignment(0.5);
superFXClock.setLength(71).setPosition((settings["Emulator/Hack/FastSuperFX"].natural() - 100) / 10).onChange([&] { superFXClock.setLength(71).setPosition((settings.emulator.hack.fastSuperFX - 100) / 10).onChange([&] {
settings["Emulator/Hack/FastSuperFX"].setValue({superFXClock.position() * 10 + 100, "%"}); settings.emulator.hack.fastSuperFX = superFXClock.position() * 10 + 100;
superFXValue.setText(settings["Emulator/Hack/FastSuperFX"].text()); superFXValue.setText({settings.emulator.hack.fastSuperFX, "%"});
}).doChange(); }).doChange();
hacksNote.setForegroundColor({224, 0, 0}).setText("Note: some hack setting changes do not take effect until after reloading games."); hacksNote.setForegroundColor({224, 0, 0}).setText("Note: some hack setting changes do not take effect until after reloading games.");
} }
auto EmulatorSettings::updateConfiguration() -> void { auto EmulatorSettings::updateConfiguration() -> void {
emulator->configure("hacks/ppuFast/enable", fastPPUOption.checked()); emulator->configure("Hacks/FastPPU/Enable", fastPPUOption.checked());
emulator->configure("hacks/ppuFast/noSpriteLimit", noSpriteLimit.checked()); emulator->configure("Hacks/FastPPU/NoSpriteLimit", noSpriteLimit.checked());
emulator->configure("hacks/ppuFast/hiresMode7", hiresMode7.checked()); emulator->configure("Hacks/FastPPU/HiresMode7", hiresMode7.checked());
emulator->configure("hacks/dspFast/enable", fastDSPOption.checked()); emulator->configure("Hacks/FastDSP/Enable", fastDSPOption.checked());
} }

View File

@ -13,6 +13,9 @@ auto HotkeySettings::create() -> void {
assignButton.setEnabled(batched.size() == 1); assignButton.setEnabled(batched.size() == 1);
clearButton.setEnabled(batched.size() >= 1); clearButton.setEnabled(batched.size() >= 1);
}); });
mappingList.onSize([&] {
mappingList.resizeColumns();
});
assignButton.setText("Assign").onActivate([&] { assignButton.setText("Assign").onActivate([&] {
assignMapping(); assignMapping();
}); });

View File

@ -7,15 +7,17 @@ auto InputSettings::create() -> void {
portList.onChange([&] { reloadDevices(); }); portList.onChange([&] { reloadDevices(); });
deviceLabel.setText("Device:"); deviceLabel.setText("Device:");
deviceList.onChange([&] { reloadMappings(); }); deviceList.onChange([&] { reloadMappings(); });
turboLabel.setText("Turbo rate:"); turboLabel.setText("Turbo rate:").setToolTip(
"The number of frames to wait between toggling turbo buttons."
);
for(uint frequency : range(1, 9)) { for(uint frequency : range(1, 9)) {
ComboButtonItem item{&turboList}; ComboButtonItem item{&turboList};
item.setText(frequency); item.setText(frequency);
if(frequency == settings["Input/Turbo/Frequency"].natural()) item.setSelected(); if(frequency == settings.input.turbo.frequency) item.setSelected();
} }
turboList.onChange([&] { turboList.setToolTip(turboLabel.toolTip()).onChange([&] {
uint frequency = turboList.selected().text().natural(); uint frequency = turboList.selected().text().natural();
settings["Input/Turbo/Frequency"].setValue(frequency); settings.input.turbo.frequency = frequency;
inputManager.turboCounter = 0; inputManager.turboCounter = 0;
inputManager.turboFrequency = frequency; inputManager.turboFrequency = frequency;
}); });
@ -23,6 +25,7 @@ auto InputSettings::create() -> void {
mappingList.setHeadered(); mappingList.setHeadered();
mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); }); mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); });
mappingList.onChange([&] { updateControls(); }); mappingList.onChange([&] { updateControls(); });
mappingList.onSize([&] { mappingList.resizeColumns(); });
assignMouse1.onActivate([&] { assignMouseInput(0); }); assignMouse1.onActivate([&] { assignMouseInput(0); });
assignMouse2.onActivate([&] { assignMouseInput(1); }); assignMouse2.onActivate([&] { assignMouseInput(1); });
assignMouse3.onActivate([&] { assignMouseInput(2); }); assignMouse3.onActivate([&] { assignMouseInput(2); });

View File

@ -10,12 +10,12 @@ auto PathSettings::create() -> void {
gamesPath.setEditable(false); gamesPath.setEditable(false);
gamesAssign.setText("Assign ...").onActivate([&] { gamesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/Games"].setValue(location); settings.path.games = location;
refreshPaths(); refreshPaths();
} }
}); });
gamesReset.setText("Reset").onActivate([&] { gamesReset.setText("Reset").onActivate([&] {
settings["Path/Games"].setValue(""); settings.path.games = "";
refreshPaths(); refreshPaths();
}); });
@ -23,12 +23,12 @@ auto PathSettings::create() -> void {
patchesPath.setEditable(false); patchesPath.setEditable(false);
patchesAssign.setText("Assign ...").onActivate([&] { patchesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/Patches"].setValue(location); settings.path.patches = location;
refreshPaths(); refreshPaths();
} }
}); });
patchesReset.setText("Reset").onActivate([&] { patchesReset.setText("Reset").onActivate([&] {
settings["Path/Patches"].setValue(""); settings.path.patches = "";
refreshPaths(); refreshPaths();
}); });
@ -36,12 +36,12 @@ auto PathSettings::create() -> void {
savesPath.setEditable(false); savesPath.setEditable(false);
savesAssign.setText("Assign ...").onActivate([&] { savesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/Saves"].setValue(location); settings.path.saves = location;
refreshPaths(); refreshPaths();
} }
}); });
savesReset.setText("Reset").onActivate([&] { savesReset.setText("Reset").onActivate([&] {
settings["Path/Saves"].setValue(""); settings.path.saves = "";
refreshPaths(); refreshPaths();
}); });
@ -49,12 +49,12 @@ auto PathSettings::create() -> void {
cheatsPath.setEditable(false); cheatsPath.setEditable(false);
cheatsAssign.setText("Assign ...").onActivate([&] { cheatsAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/Cheats"].setValue(location); settings.path.cheats = location;
refreshPaths(); refreshPaths();
} }
}); });
cheatsReset.setText("Reset").onActivate([&] { cheatsReset.setText("Reset").onActivate([&] {
settings["Path/Cheats"].setValue(""); settings.path.cheats = "";
refreshPaths(); refreshPaths();
}); });
@ -62,12 +62,12 @@ auto PathSettings::create() -> void {
statesPath.setEditable(false); statesPath.setEditable(false);
statesAssign.setText("Assign ...").onActivate([&] { statesAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/States"].setValue(location); settings.path.states = location;
refreshPaths(); refreshPaths();
} }
}); });
statesReset.setText("Reset").onActivate([&] { statesReset.setText("Reset").onActivate([&] {
settings["Path/States"].setValue(""); settings.path.states = "";
refreshPaths(); refreshPaths();
}); });
@ -75,12 +75,12 @@ auto PathSettings::create() -> void {
screenshotsPath.setEditable(false); screenshotsPath.setEditable(false);
screenshotsAssign.setText("Assign ...").onActivate([&] { screenshotsAssign.setText("Assign ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) {
settings["Path/Screenshots"].setValue(location); settings.path.screenshots = location;
refreshPaths(); refreshPaths();
} }
}); });
screenshotsReset.setText("Reset").onActivate([&] { screenshotsReset.setText("Reset").onActivate([&] {
settings["Path/Screenshots"].setValue(""); settings.path.screenshots = "";
refreshPaths(); refreshPaths();
}); });
@ -88,34 +88,34 @@ auto PathSettings::create() -> void {
} }
auto PathSettings::refreshPaths() -> void { auto PathSettings::refreshPaths() -> void {
if(auto location = settings["Path/Games"].text()) { if(auto location = settings.path.games) {
gamesPath.setText(location).setForegroundColor(); gamesPath.setText(location).setForegroundColor();
} else { } else {
gamesPath.setText("<last recently used>").setForegroundColor({128, 128, 128}); gamesPath.setText("(last recently used)").setForegroundColor({128, 128, 128});
} }
if(auto location = settings["Path/Patches"].text()) { if(auto location = settings.path.patches) {
patchesPath.setText(location).setForegroundColor(); patchesPath.setText(location).setForegroundColor();
} else { } else {
patchesPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128}); patchesPath.setText("(same as loaded game)").setForegroundColor({128, 128, 128});
} }
if(auto location = settings["Path/Saves"].text()) { if(auto location = settings.path.saves) {
savesPath.setText(location).setForegroundColor(); savesPath.setText(location).setForegroundColor();
} else { } else {
savesPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128}); savesPath.setText("(same as loaded game)").setForegroundColor({128, 128, 128});
} }
if(auto location = settings["Path/Cheats"].text()) { if(auto location = settings.path.cheats) {
cheatsPath.setText(location).setForegroundColor(); cheatsPath.setText(location).setForegroundColor();
} else { } else {
cheatsPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128}); cheatsPath.setText("(same as loaded game)").setForegroundColor({128, 128, 128});
} }
if(auto location = settings["Path/States"].text()) { if(auto location = settings.path.states) {
statesPath.setText(location).setForegroundColor(); statesPath.setText(location).setForegroundColor();
} else { } else {
statesPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128}); statesPath.setText("(same as loaded game)").setForegroundColor({128, 128, 128});
} }
if(auto location = settings["Path/Screenshots"].text()) { if(auto location = settings.path.screenshots) {
screenshotsPath.setText(location).setForegroundColor(); screenshotsPath.setText(location).setForegroundColor();
} else { } else {
screenshotsPath.setText("<same as loaded game>").setForegroundColor({128, 128, 128}); screenshotsPath.setText("(same as loaded game)").setForegroundColor({128, 128, 128});
} }
} }

View File

@ -16,81 +16,100 @@ EmulatorSettings emulatorSettings;
DriverSettings driverSettings; DriverSettings driverSettings;
SettingsWindow settingsWindow; SettingsWindow settingsWindow;
Settings::Settings() { auto Settings::load() -> void {
Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")), " ")); Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")), " "));
process(true);
auto set = [&](string name, string value) { file::write(locate("settings.bml"), BML::serialize(*this, " "));
//create node and set to default value only if it does not already exist
if(!operator[](name)) operator()(name).setValue(value);
};
set("Video/Driver", Video::safestDriver());
set("Video/Exclusive", false);
set("Video/Blocking", false);
set("Video/Flush", false);
set("Video/Format", "Default");
set("Video/Shader", "Blur");
set("Video/Luminance", "100%");
set("Video/Saturation", "100%");
set("Video/Gamma", "150%");
set("Audio/Driver", Audio::safestDriver());
set("Audio/Exclusive", false);
set("Audio/Device", "");
set("Audio/Blocking", true);
set("Audio/Dynamic", false);
set("Audio/Frequency", "48000hz");
set("Audio/Latency", 0);
set("Audio/Mute", false);
set("Audio/Skew", "0");
set("Audio/Volume", "100%");
set("Audio/Balance", "50%");
set("Input/Driver", Input::safestDriver());
set("Input/Frequency", 5);
set("Input/Defocus", "Pause");
set("Input/Turbo/Frequency", 4);
set("View/Multiplier", "2");
set("View/Output", "Scale");
set("View/AspectCorrection", true);
set("View/OverscanCropping", true);
set("View/BlurEmulation", true);
set("Path/Games", "");
set("Path/Patches", "");
set("Path/Saves", "");
set("Path/Cheats", "");
set("Path/States", "");
set("Path/Screenshots", "");
set("Path/Recent/SuperFamicom", Path::user());
set("Path/Recent/GameBoy", Path::user());
set("Path/Recent/BSMemory", Path::user());
set("Path/Recent/SufamiTurboA", Path::user());
set("Path/Recent/SufamiTurboB", Path::user());
set("UserInterface/ShowStatusBar", true);
set("UserInterface/SuppressScreenSaver", true);
set("Emulator/WarnOnUnverifiedGames", false);
set("Emulator/AutoSaveMemory/Enable", true);
set("Emulator/AutoSaveMemory/Interval", 30);
set("Emulator/AutoSaveStateOnUnload", false);
set("Emulator/AutoLoadStateOnLoad", false);
set("Emulator/Hack/FastPPU", true);
set("Emulator/Hack/FastPPU/NoSpriteLimit", false);
set("Emulator/Hack/FastPPU/HiresMode7", false);
set("Emulator/Hack/FastDSP", true);
set("Emulator/Hack/FastSuperFX", "100%");
set("Emulator/Cheats/Enable", true);
set("Crashed", false);
} }
auto Settings::save() -> void { auto Settings::save() -> void {
process(false);
file::write(locate("settings.bml"), BML::serialize(*this, " ")); file::write(locate("settings.bml"), BML::serialize(*this, " "));
} }
auto Settings::process(bool load) -> void {
if(load) {
//initialize non-static default settings
video.driver = ruby::Video::safestDriver();
audio.driver = ruby::Audio::safestDriver();
input.driver = ruby::Input::safestDriver();
}
#define bind(type, path, name) \
if(load) { \
if(auto node = operator[](path)) name = node.type(); \
} else { \
operator()(path).setValue(name); \
} \
bind(text, "Video/Driver", video.driver);
bind(boolean, "Video/Exclusive", video.exclusive);
bind(boolean, "Video/Blocking", video.blocking);
bind(boolean, "Video/Flush", video.flush);
bind(text, "Video/Format", video.format);
bind(text, "Video/Shader", video.shader);
bind(natural, "Video/Luminance", video.luminance);
bind(natural, "Video/Saturation", video.saturation);
bind(natural, "Video/Gamma", video.gamma);
bind(text, "Video/Output", video.output);
bind(natural, "Video/Multiplier", video.multiplier);
bind(boolean, "Video/AspectCorrection", video.aspectCorrection);
bind(boolean, "Video/Overscan", video.overscan);
bind(boolean, "Video/Blur", video.blur);
bind(text, "Audio/Driver", audio.driver);
bind(boolean, "Audio/Exclusive", audio.exclusive);
bind(text, "Audio/Device", audio.device);
bind(boolean, "Audio/Blocking", audio.blocking);
bind(boolean, "Audio/Dynamic", audio.dynamic);
bind(natural, "Audio/Frequency", audio.frequency);
bind(natural, "Audio/Latency", audio.latency);
bind(boolean, "Audio/Mute", audio.mute);
bind(integer, "Audio/Skew", audio.skew);
bind(natural, "Audio/Volume", audio.volume);
bind(natural, "Audio/Balance", audio.balance);
bind(text, "Input/Driver", input.driver);
bind(natural, "Input/Frequency", input.frequency);
bind(text, "Input/Defocus", input.defocus);
bind(natural, "Input/Turbo/Frequency", input.turbo.frequency);
bind(text, "Path/Games", path.games);
bind(text, "Path/Patches", path.patches);
bind(text, "Path/Saves", path.saves);
bind(text, "Path/Cheats", path.cheats);
bind(text, "Path/States", path.states);
bind(text, "Path/Screenshots", path.screenshots);
bind(text, "Path/Recent/SuperFamicom", path.recent.superFamicom);
bind(text, "Path/Recent/GameBoy", path.recent.gameBoy);
bind(text, "Path/Recent/BSMemory", path.recent.bsMemory);
bind(text, "Path/Recent/SufamiTurboA", path.recent.sufamiTurboA);
bind(text, "Path/Recent/SufamiTurboB", path.recent.sufamiTurboB);
bind(boolean, "Emulator/WarnOnUnverifiedGames", emulator.warnOnUnverifiedGames);
bind(boolean, "Emulator/AutoSaveMemory/Enable", emulator.autoSaveMemory.enable);
bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval);
bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload);
bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad);
bind(boolean, "Emulator/Hack/FastPPU/Enable", emulator.hack.fastPPU.enable);
bind(boolean, "Emulator/Hack/FastPPU/NoSpriteLimit", emulator.hack.fastPPU.noSpriteLimit);
bind(boolean, "Emulator/Hack/FastPPU/HiresMode7", emulator.hack.fastPPU.hiresMode7);
bind(boolean, "Emulator/Hack/FastDSP/Enable", emulator.hack.fastDSP.enable);
bind(natural, "Emulator/Hack/FastSuperFX", emulator.hack.fastSuperFX);
bind(boolean, "Emulator/Cheats/Enable", emulator.cheats.enable);
bind(boolean, "General/StatusBar", general.statusBar);
bind(boolean, "General/ScreenSaver", general.screenSaver);
bind(boolean, "General/ToolTips", general.toolTips);
bind(boolean, "General/Crashed", general.crashed);
#undef bind
}
auto SettingsWindow::create() -> void { auto SettingsWindow::create() -> void {
layout.setPadding(5); layout.setPadding(5);
panel.append(videoSettings); panel.append(videoSettings);
@ -107,11 +126,6 @@ auto SettingsWindow::create() -> void {
setAlignment({0.0, 1.0}); setAlignment({0.0, 1.0});
setDismissable(); setDismissable();
onSize([&] {
inputSettings.mappingList.resizeColumns();
hotkeySettings.mappingList.resizeColumns();
});
onClose([&] { onClose([&] {
if(inputSettings.activeMapping) inputSettings.cancelMapping(); if(inputSettings.activeMapping) inputSettings.cancelMapping();
if(hotkeySettings.activeMapping) hotkeySettings.cancelMapping(); if(hotkeySettings.activeMapping) hotkeySettings.cancelMapping();
@ -124,7 +138,6 @@ auto SettingsWindow::setVisible(bool visible) -> SettingsWindow& {
inputSettings.refreshMappings(); inputSettings.refreshMappings();
hotkeySettings.refreshMappings(); hotkeySettings.refreshMappings();
Application::processEvents(); Application::processEvents();
doSize();
} }
return Window::setVisible(visible), *this; return Window::setVisible(visible), *this;
} }

View File

@ -1,12 +1,106 @@
struct Settings : Markup::Node { struct Settings : Markup::Node {
Settings(); Settings() { load(); }
~Settings() { save(); }
auto load() -> void;
auto save() -> void; auto save() -> void;
auto process(bool load) -> void;
struct Video {
string driver;
bool exclusive = false;
bool blocking = false;
bool flush = false;
string format = "Default";
string shader = "Blur";
uint luminance = 100;
uint saturation = 100;
uint gamma = 150;
string output = "Scale";
uint multiplier = 2;
bool aspectCorrection = true;
bool overscan = false;
bool blur = false;
} video;
struct Audio {
string driver;
bool exclusive = false;
string device;
bool blocking = true;
bool dynamic = false;
uint frequency = 48000;
uint latency = 0;
bool mute = false;
int skew = 0;
uint volume = 100;
uint balance = 50;
} audio;
struct Input {
string driver;
uint frequency = 5;
string defocus = "Pause";
struct Turbo {
uint frequency = 4;
} turbo;
} input;
struct Path {
string games;
string patches;
string saves;
string cheats;
string states;
string screenshots;
struct Recent {
string superFamicom;
string gameBoy;
string bsMemory;
string sufamiTurboA;
string sufamiTurboB;
} recent;
} path;
struct Emulator {
bool warnOnUnverifiedGames = false;
struct AutoSaveMemory {
bool enable = true;
uint interval = 30;
} autoSaveMemory;
bool autoSaveStateOnUnload = false;
bool autoLoadStateOnLoad = false;
struct Hack {
struct FastPPU {
bool enable = true;
bool noSpriteLimit = false;
bool hiresMode7 = false;
} fastPPU;
struct FastDSP {
bool enable = true;
} fastDSP;
uint fastSuperFX = 100;
} hack;
struct Cheats {
bool enable = true;
} cheats;
} emulator;
struct General {
bool statusBar = true;
bool screenSaver = false;
bool toolTips = true;
bool crashed = false;
} general;
}; };
struct VideoSettings : TabFrameItem { struct VideoSettings : TabFrameItem {
auto create() -> void; auto create() -> void;
public: private:
VerticalLayout layout{this}; VerticalLayout layout{this};
Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2}; Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2};
TableLayout colorLayout{&layout, Size{~0, 0}}; TableLayout colorLayout{&layout, Size{~0, 0}};
@ -26,7 +120,7 @@ public:
struct AudioSettings : TabFrameItem { struct AudioSettings : TabFrameItem {
auto create() -> void; auto create() -> void;
public: private:
VerticalLayout layout{this}; VerticalLayout layout{this};
Label effectsLabel{&layout, Size{~0, 0}, 2}; Label effectsLabel{&layout, Size{~0, 0}, 2};
TableLayout effectsLayout{&layout, Size{~0, 0}}; TableLayout effectsLayout{&layout, Size{~0, 0}};
@ -57,8 +151,9 @@ struct InputSettings : TabFrameItem {
auto assignMouseInput(uint id) -> void; auto assignMouseInput(uint id) -> void;
auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void; auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void;
public:
maybe<InputMapping&> activeMapping; maybe<InputMapping&> activeMapping;
private:
Timer timer; Timer timer;
VerticalLayout layout{this}; VerticalLayout layout{this};
@ -87,8 +182,9 @@ struct HotkeySettings : TabFrameItem {
auto cancelMapping() -> void; auto cancelMapping() -> void;
auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void; auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void;
public:
maybe<InputMapping&> activeMapping; maybe<InputMapping&> activeMapping;
private:
Timer timer; Timer timer;
VerticalLayout layout{this}; VerticalLayout layout{this};
@ -154,7 +250,6 @@ public:
HorizontalLayout autoStateLayout{&layout, Size{~0, 0}}; HorizontalLayout autoStateLayout{&layout, Size{~0, 0}};
CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}}; CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}};
CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}}; CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}};
CheckLabel suppressScreenSaver{&layout, Size{~0, 0}};
Canvas optionsSpacer{&layout, Size{~0, 1}}; Canvas optionsSpacer{&layout, Size{~0, 1}};
Label hacksLabel{&layout, Size{~0, 0}, 2}; Label hacksLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout fastPPULayout{&layout, Size{~0, 0}}; HorizontalLayout fastPPULayout{&layout, Size{~0, 0}};

View File

@ -9,25 +9,25 @@ auto VideoSettings::create() -> void {
colorLayout.column(0).setAlignment(1.0); colorLayout.column(0).setAlignment(1.0);
luminanceLabel.setText("Luminance:"); luminanceLabel.setText("Luminance:");
luminanceValue.setAlignment(0.5); luminanceValue.setAlignment(0.5);
luminanceSlider.setLength(101).setPosition(settings["Video/Luminance"].natural()).onChange([&] { luminanceSlider.setLength(101).setPosition(settings.video.luminance).onChange([&] {
string value = {luminanceSlider.position(), "%"}; string value = {luminanceSlider.position(), "%"};
settings["Video/Luminance"].setValue(value); settings.video.luminance = value.natural();
luminanceValue.setText(value); luminanceValue.setText(value);
program.updateVideoPalette(); program.updateVideoPalette();
}).doChange(); }).doChange();
saturationLabel.setText("Saturation:"); saturationLabel.setText("Saturation:");
saturationValue.setAlignment(0.5); saturationValue.setAlignment(0.5);
saturationSlider.setLength(201).setPosition(settings["Video/Saturation"].natural()).onChange([&] { saturationSlider.setLength(201).setPosition(settings.video.saturation).onChange([&] {
string value = {saturationSlider.position(), "%"}; string value = {saturationSlider.position(), "%"};
settings["Video/Saturation"].setValue(value); settings.video.saturation = value.natural();
saturationValue.setText(value); saturationValue.setText(value);
program.updateVideoPalette(); program.updateVideoPalette();
}).doChange(); }).doChange();
gammaLabel.setText("Gamma:"); gammaLabel.setText("Gamma:");
gammaValue.setAlignment(0.5); gammaValue.setAlignment(0.5);
gammaSlider.setLength(101).setPosition(settings["Video/Gamma"].natural() - 100).onChange([&] { gammaSlider.setLength(101).setPosition(settings.video.gamma - 100).onChange([&] {
string value = {100 + gammaSlider.position(), "%"}; string value = {100 + gammaSlider.position(), "%"};
settings["Video/Gamma"].setValue(value); settings.video.gamma = value.natural();
gammaValue.setText(value); gammaValue.setText(value);
program.updateVideoPalette(); program.updateVideoPalette();
}).doChange(); }).doChange();

View File

@ -107,6 +107,8 @@ auto CheatEditor::create() -> void {
layout.setPadding(5); layout.setPadding(5);
cheatList.setBatchable(); cheatList.setBatchable();
cheatList.setHeadered();
cheatList.setSortable();
cheatList.onActivate([&] { cheatList.onActivate([&] {
editButton.doActivate(); editButton.doActivate();
}); });
@ -121,11 +123,22 @@ auto CheatEditor::create() -> void {
synchronizeCodes(); synchronizeCodes();
} }
}); });
cheatList.onSort([&](TableViewColumn column) {
column.setSorting(column.sorting() == Sort::Ascending ? Sort::Descending : Sort::Ascending);
//cheatList.sort();
});
cheatList.onSize([&] {
cheatList.resizeColumns();
});
findCheatsButton.setText("Find Cheats ...").onActivate([&] { findCheatsButton.setText("Find Cheats ...").onActivate([&] {
cheatDatabase.findCheats(); cheatDatabase.findCheats();
}); });
enableCheats.setText("Enable Cheats").setChecked(settings["Emulator/Cheats/Enable"].boolean()).onToggle([&] { enableCheats.setText("Enable Cheats").setToolTip(
settings["Emulator/Cheats/Enable"].setValue(enableCheats.checked()); "Master enable for all cheat codes.\n"
"When unchecked, no cheat codes will be active.\n\n"
"Use this to bypass game areas that have problems with cheats."
).setChecked(settings.emulator.cheats.enable).onToggle([&] {
settings.emulator.cheats.enable = enableCheats.checked();
if(!enableCheats.checked()) { if(!enableCheats.checked()) {
program.showMessage("All cheat codes disabled"); program.showMessage("All cheat codes disabled");
} else { } else {
@ -152,12 +165,11 @@ auto CheatEditor::create() -> void {
auto CheatEditor::refresh() -> void { auto CheatEditor::refresh() -> void {
cheatList.reset(); cheatList.reset();
cheatList.append(TableViewColumn()); cheatList.append(TableViewColumn());
cheatList.append(TableViewColumn().setExpandable()); cheatList.append(TableViewColumn().setText("Name").setSorting(Sort::Ascending).setExpandable());
for(auto& cheat : cheats) { for(auto& cheat : cheats) {
cheatList.append(TableViewItem() TableViewItem item{&cheatList};
.append(TableViewCell().setCheckable().setChecked(cheat.enable)) item.append(TableViewCell().setCheckable().setChecked(cheat.enable));
.append(TableViewCell().setText(cheat.name)) item.append(TableViewCell().setText(cheat.name));
);
} }
cheatList.resizeColumns().doChange(); cheatList.resizeColumns().doChange();
} }

View File

@ -59,6 +59,9 @@ auto StateManager::create() -> void {
column.setSorting(column.sorting() == Sort::Ascending ? Sort::Descending : Sort::Ascending); column.setSorting(column.sorting() == Sort::Ascending ? Sort::Descending : Sort::Ascending);
stateList.sort(); stateList.sort();
}); });
stateList.onSize([&] {
stateList.resizeColumns();
});
categoryLabel.setText("Category:"); categoryLabel.setText("Category:");
categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "Managed/")); categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "Managed/"));
categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "Quick/")); categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "Quick/"));

View File

@ -26,11 +26,6 @@ auto ToolsWindow::create() -> void {
setAlignment({1.0, 1.0}); setAlignment({1.0, 1.0});
setDismissable(); setDismissable();
onSize([&] {
cheatEditor.cheatList.resizeColumns();
stateManager.stateList.resizeColumns();
});
onClose([&] { onClose([&] {
setVisible(false); setVisible(false);
}); });
@ -44,7 +39,6 @@ auto ToolsWindow::setVisible(bool visible) -> ToolsWindow& {
stateWindow.setVisible(false); stateWindow.setVisible(false);
} else { } else {
Application::processEvents(); Application::processEvents();
doSize();
} }
return *this; return *this;
} }

View File

@ -11,20 +11,37 @@ Presentation::Presentation() {
systemMenu.setVisible(false); systemMenu.setVisible(false);
settingsMenu.setText("Settings"); settingsMenu.setText("Settings");
videoScaleMenu.setText("Video Scale"); sizeMenu.setText("Size");
videoScaleSmall.setText("Small").onActivate([&] { updateSizeMenu();
settings["Video/Windowed/Scale"].setValue("Small"); outputMenu.setText("Output");
centerViewport.setText("Center").onActivate([&] {
settings["View/Output"].setValue("Center");
resizeViewport(); resizeViewport();
}); });
videoScaleMedium.setText("Medium").onActivate([&] { scaleViewport.setText("Scale").onActivate([&] {
settings["Video/Windowed/Scale"].setValue("Medium"); settings["View/Output"].setValue("Scale");
resizeViewport(); resizeViewport();
}); });
videoScaleLarge.setText("Large").onActivate([&] { stretchViewport.setText("Stretch").onActivate([&] {
settings["Video/Windowed/Scale"].setValue("Large"); settings["View/Output"].setValue("Stretch");
resizeViewport(); resizeViewport();
}); });
videoEmulationMenu.setText("Video Emulation"); if(settings["View/Output"].text() == "Center") centerViewport.setChecked();
if(settings["View/Output"].text() == "Scale") scaleViewport.setChecked();
if(settings["View/Output"].text() == "Stretch") stretchViewport.setChecked();
adaptiveSizing.setText("Adaptive Sizing").setChecked(settings["View/Adaptive"].boolean()).onToggle([&] {
settings["View/Adaptive"].setValue(adaptiveSizing.checked());
resizeWindow();
});
aspectCorrection.setText("Aspect Correction").setChecked(settings["View/AspectCorrection"].boolean()).onToggle([&] {
settings["View/AspectCorrection"].setValue(aspectCorrection.checked());
resizeWindow();
});
showOverscanArea.setText("Show Overscan Area").setChecked(settings["View/Overscan"].boolean()).onToggle([&] {
settings["View/Overscan"].setValue(showOverscanArea.checked());
resizeWindow();
});
videoEmulationMenu.setText("Emulation");
blurEmulation.setText("Blurring").setChecked(settings["Video/BlurEmulation"].boolean()).onToggle([&] { blurEmulation.setText("Blurring").setChecked(settings["Video/BlurEmulation"].boolean()).onToggle([&] {
settings["Video/BlurEmulation"].setValue(blurEmulation.checked()); settings["Video/BlurEmulation"].setValue(blurEmulation.checked());
if(emulator) emulator->set("Blur Emulation", blurEmulation.checked()); if(emulator) emulator->set("Blur Emulation", blurEmulation.checked());
@ -37,7 +54,7 @@ Presentation::Presentation() {
settings["Video/ScanlineEmulation"].setValue(scanlineEmulation.checked()); settings["Video/ScanlineEmulation"].setValue(scanlineEmulation.checked());
if(emulator) emulator->set("Scanline Emulation", scanlineEmulation.checked()); if(emulator) emulator->set("Scanline Emulation", scanlineEmulation.checked());
}); });
videoShaderMenu.setText("Video Shader"); videoShaderMenu.setText("Shader");
videoShaderNone.setText("None").onActivate([&] { videoShaderNone.setText("None").onActivate([&] {
settings["Video/Shader"].setValue("None"); settings["Video/Shader"].setValue("None");
program->updateVideoShader(); program->updateVideoShader();
@ -59,10 +76,14 @@ Presentation::Presentation() {
settings["Audio/Mute"].setValue(muteAudio.checked()); settings["Audio/Mute"].setValue(muteAudio.checked());
program->updateAudioEffects(); program->updateAudioEffects();
}); });
showStatusBar.setText("Show Status Bar").setChecked(settings["UserInterface/ShowStatusBar"].boolean()).onToggle([&] { showStatusBar.setText("Show Status Bar").setChecked(settings["View/StatusBar"].boolean()).onToggle([&] {
settings["UserInterface/ShowStatusBar"].setValue(showStatusBar.checked()); settings["View/StatusBar"].setValue(showStatusBar.checked());
statusBar.setVisible(showStatusBar.checked()); if(!showStatusBar.checked()) {
if(visible()) resizeViewport(); layout.remove(statusLayout);
} else {
layout.append(statusLayout, Size{~0, StatusHeight});
}
if(visible()) resizeWindow();
}); });
showSystemSettings.setIcon(Icon::Device::Storage).setText("Systems ...").onActivate([&] { settingsManager->show(0); }); showSystemSettings.setIcon(Icon::Device::Storage).setText("Systems ...").onActivate([&] { settingsManager->show(0); });
showVideoSettings.setIcon(Icon::Device::Display).setText("Video ...").onActivate([&] { settingsManager->show(1); }); showVideoSettings.setIcon(Icon::Device::Display).setText("Video ...").onActivate([&] { settingsManager->show(1); });
@ -113,9 +134,6 @@ Presentation::Presentation() {
aboutWindow->setCentered(*this).setVisible().setFocused(); aboutWindow->setCentered(*this).setVisible().setFocused();
}); });
statusBar.setFont(Font().setBold());
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
viewport.setDroppable().onDrop([&](vector<string> locations) { viewport.setDroppable().onDrop([&](vector<string> locations) {
if(!locations || !directory::exists(locations.first())) return; if(!locations || !directory::exists(locations.first())) return;
program->gameQueue.append(locations.first()); program->gameQueue.append(locations.first());
@ -127,17 +145,42 @@ Presentation::Presentation() {
icon.alphaBlend(0x000000); icon.alphaBlend(0x000000);
iconCanvas.setIcon(icon); iconCanvas.setIcon(icon);
if(!settings["View/StatusBar"].boolean()) {
layout.remove(statusLayout);
}
auto font = Font().setBold();
auto back = Color{ 32, 32, 32};
auto fore = Color{255, 255, 255};
spacerLeft.setBackgroundColor(back);
statusMessage.setFont(font);
statusMessage.setAlignment(0.0);
statusMessage.setBackgroundColor(back);
statusMessage.setForegroundColor(fore);
statusInfo.setFont(font);
statusInfo.setAlignment(1.0);
statusInfo.setBackgroundColor(back);
statusInfo.setForegroundColor(fore);
statusInfo.setText("Unloaded");
spacerRight.setBackgroundColor(back);
onSize([&] { onSize([&] {
resizeViewport(false); resizeViewport();
}); });
onClose([&] { onClose([&] {
program->quit(); program->quit();
}); });
settings["View/Multiplier"].setValue(2);
setTitle({"higan v", Emulator::Version}); setTitle({"higan v", Emulator::Version});
setBackgroundColor({0, 0, 0}); setBackgroundColor({0, 0, 0});
resizeViewport(); resizeWindow();
setCentered(); setCentered();
#if defined(PLATFORM_WINDOWS) #if defined(PLATFORM_WINDOWS)
@ -238,13 +281,46 @@ auto Presentation::updateEmulatorDeviceSelections() -> void {
} }
} }
auto Presentation::clearViewport() -> void { auto Presentation::updateSizeMenu() -> void {
if(!video) return; assert(sizeMenu.actionCount() == 0); //should only be called once
if(!emulator || !emulator->loaded()) { //determine the largest multiplier that can be used by the largest monitor found
viewport.setGeometry({0, 0, geometry().width(), geometry().height()}); uint height = 1;
for(uint monitor : range(Monitor::count())) {
height = max(height, Monitor::workspace(monitor).height());
} }
uint multipliers = max(1, height / 240);
for(uint multiplier : range(1, multipliers + 1)) {
MenuRadioItem item{&sizeMenu};
item.setProperty("multiplier", multiplier);
item.setText({multiplier, "x (", 240 * multiplier, "p)"});
item.onActivate([=] {
settings["View/Multiplier"].setValue(multiplier);
resizeWindow();
});
sizeGroup.append(item);
}
for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(settings["View/Multiplier"].natural() == item.property("multiplier").natural()) {
item.setChecked();
}
}
sizeMenu.append(MenuSeparator());
sizeMenu.append(MenuItem().setIcon(Icon::Action::Remove).setText("Shrink Window To Size").onActivate([&] {
resizeWindow();
}));
sizeMenu.append(MenuItem().setIcon(Icon::Place::Settings).setText("Center Window").onActivate([&] {
setCentered();
}));
}
auto Presentation::clearViewport() -> void {
if(!emulator || !emulator->loaded()) viewportLayout.setPadding();
if(!visible() || !video) return;
uint32_t* output; uint32_t* output;
uint length = 0; uint length = 0;
uint width = 16; uint width = 16;
@ -259,105 +335,126 @@ auto Presentation::clearViewport() -> void {
} }
} }
auto Presentation::resizeViewport(bool resizeWindow) -> void { auto Presentation::resizeViewport() -> void {
//clear video area before resizing to avoid seeing distorted video momentarily uint layoutWidth = viewportLayout.geometry().width();
clearViewport(); uint layoutHeight = viewportLayout.geometry().height();
uint viewportWidth = geometry().width(); uint width = 320;
uint viewportHeight = geometry().height(); uint height = 240;
double emulatorWidth = 320;
double emulatorHeight = 240;
double aspectCorrection = 1.0;
if(emulator) { if(emulator) {
auto display = emulator->displays()[0]; auto display = emulator->displays().first();
emulatorWidth = display.width; width = display.width;
emulatorHeight = display.height; height = display.height;
aspectCorrection = display.aspectCorrection; if(settings["View/AspectCorrection"].boolean()) width *= display.aspectCorrection;
if(!settings["View/Overscan"].boolean()) {
if(display.type == Emulator::Interface::Display::Type::CRT) { if(display.type == Emulator::Interface::Display::Type::CRT) {
uint overscanHorizontal = settings["Video/Overscan/Horizontal"].natural(); uint overscanHorizontal = settings["View/Overscan/Horizontal"].natural();
uint overscanVertical = settings["Video/Overscan/Vertical"].natural(); uint overscanVertical = settings["View/Overscan/Vertical"].natural();
emulatorWidth -= overscanHorizontal * 2; width -= overscanHorizontal * 2;
emulatorHeight -= overscanVertical * 2; height -= overscanVertical * 2;
}
} }
} }
if(!fullScreen()) { if(visible() && !fullScreen()) {
if(settings["Video/Windowed/AspectCorrection"].boolean()) emulatorWidth *= aspectCorrection; uint widthMultiplier = layoutWidth / width;
uint heightMultiplier = layoutHeight / height;
if(resizeWindow) { uint multiplier = max(1, min(widthMultiplier, heightMultiplier));
string viewportScale = "640x480"; settings["View/Multiplier"].setValue(multiplier);
if(settings["Video/Windowed/Scale"].text() == "Small") viewportScale = settings["Video/Windowed/Scale/Small"].text(); for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(settings["Video/Windowed/Scale"].text() == "Medium") viewportScale = settings["Video/Windowed/Scale/Medium"].text(); if(auto property = item.property("multiplier")) {
if(settings["Video/Windowed/Scale"].text() == "Large") viewportScale = settings["Video/Windowed/Scale/Large"].text(); if(property.natural() == multiplier) item.setChecked();
auto resolution = viewportScale.isplit("x", 1L);
viewportWidth = resolution(0).natural();
viewportHeight = resolution(1).natural();
} }
if(settings["Video/Windowed/Adaptive"].boolean() && resizeWindow) {
uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight);
emulatorWidth *= multiplier;
emulatorHeight *= multiplier;
setSize({viewportWidth = emulatorWidth, viewportHeight = emulatorHeight});
} else if(settings["Video/Windowed/IntegralScaling"].boolean()) {
uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight);
emulatorWidth *= multiplier;
emulatorHeight *= multiplier;
if(resizeWindow) setSize({viewportWidth, viewportHeight});
} else {
double multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight);
emulatorWidth *= multiplier;
emulatorHeight *= multiplier;
if(resizeWindow) setSize({viewportWidth, viewportHeight});
}
} else {
if(settings["Video/Fullscreen/AspectCorrection"].boolean()) emulatorWidth *= aspectCorrection;
if(settings["Video/Fullscreen/IntegralScaling"].boolean()) {
uint multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight);
emulatorWidth *= multiplier;
emulatorHeight *= multiplier;
} else {
double multiplier = min(viewportWidth / emulatorWidth, viewportHeight / emulatorHeight);
emulatorWidth *= multiplier;
emulatorHeight *= multiplier;
} }
} }
if(emulator && emulator->loaded()) { if(!emulator || !emulator->loaded()) return clearViewport();
viewport.setGeometry({ if(!video) return;
(viewportWidth - emulatorWidth) / 2, (viewportHeight - emulatorHeight) / 2,
emulatorWidth, emulatorHeight uint viewportWidth;
uint viewportHeight;
if(settings["View/Output"].text() == "Center") {
uint widthMultiplier = layoutWidth / width;
uint heightMultiplier = layoutHeight / height;
uint multiplier = min(widthMultiplier, heightMultiplier);
viewportWidth = width * multiplier;
viewportHeight = height * multiplier;
} else if(settings["View/Output"].text() == "Scale") {
double widthMultiplier = (double)layoutWidth / width;
double heightMultiplier = (double)layoutHeight / height;
double multiplier = min(widthMultiplier, heightMultiplier);
viewportWidth = width * multiplier;
viewportHeight = height * multiplier;
} else if(settings["View/Output"].text() == "Stretch" || 1) {
viewportWidth = layoutWidth;
viewportHeight = layoutHeight;
}
//center viewport within viewportLayout by use of viewportLayout padding
uint paddingWidth = layoutWidth - viewportWidth;
uint paddingHeight = layoutHeight - viewportHeight;
viewportLayout.setPadding({
paddingWidth / 2, paddingHeight / 2,
paddingWidth - paddingWidth / 2, paddingHeight - paddingHeight / 2
}); });
} else {
viewport.setGeometry({0, 0, geometry().width(), geometry().height()}); clearViewport();
}
auto Presentation::resizeWindow() -> void {
if(fullScreen()) return;
if(maximized()) setMaximized(false);
uint width = 320;
uint height = 240;
uint multiplier = max(1, settings["View/Multiplier"].natural());
uint statusHeight = settings["View/StatusBar"].boolean() ? StatusHeight : 0;
if(emulator) {
auto display = emulator->displays().first();
width = display.width;
height = display.height;
if(settings["View/AspectCorrection"].boolean()) width *= display.aspectCorrection;
if(!settings["View/Overscan"].boolean()) {
if(display.type == Emulator::Interface::Display::Type::CRT) {
uint overscanHorizontal = settings["View/Overscan/Horizontal"].natural();
uint overscanVertical = settings["View/Overscan/Vertical"].natural();
width -= overscanHorizontal * 2;
height -= overscanVertical * 2;
}
}
} }
//clear video area again to ensure entire viewport area has been painted in setMinimumSize({width, height + statusHeight});
clearViewport(); setSize({width * multiplier, height * multiplier + statusHeight});
layout.setGeometry(layout.geometry());
resizeViewport();
} }
auto Presentation::toggleFullScreen() -> void { auto Presentation::toggleFullScreen() -> void {
if(!fullScreen()) { if(!fullScreen()) {
statusBar.setVisible(false); if(settings["View/StatusBar"].boolean()) {
layout.remove(statusLayout);
}
menuBar.setVisible(false); menuBar.setVisible(false);
setFullScreen(true); setFullScreen(true);
video->setExclusive(settings["Video/Fullscreen/Exclusive"].boolean()); video->setExclusive(settings["Video/Exclusive"].boolean());
if(video->exclusive()) setVisible(false); if(video->exclusive()) setVisible(false);
if(!input->acquired()) input->acquire(); if(!input->acquired()) input->acquire();
resizeViewport();
} else { } else {
if(input->acquired()) input->release(); if(input->acquired()) input->release();
if(video->exclusive()) setVisible(true); if(video->exclusive()) setVisible(true);
video->setExclusive(false); video->setExclusive(false);
setFullScreen(false); setFullScreen(false);
menuBar.setVisible(true); menuBar.setVisible(true);
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean()); if(settings["View/StatusBar"].boolean()) {
layout.append(statusLayout, Size{~0, StatusHeight});
}
resizeWindow();
setCentered();
} }
//hack: give window geometry time to update after toggling fullscreen and menu/status bars
usleep(20 * 1000);
Application::processEvents();
resizeViewport();
} }
auto Presentation::loadSystems() -> void { auto Presentation::loadSystems() -> void {

View File

@ -9,11 +9,15 @@ struct AboutWindow : Window {
}; };
struct Presentation : Window { struct Presentation : Window {
enum : uint { StatusHeight = 24 };
Presentation(); Presentation();
auto updateEmulatorMenu() -> void; auto updateEmulatorMenu() -> void;
auto updateEmulatorDeviceSelections() -> void; auto updateEmulatorDeviceSelections() -> void;
auto updateSizeMenu() -> void;
auto clearViewport() -> void; auto clearViewport() -> void;
auto resizeViewport(bool resizeWindow = true) -> void; auto resizeViewport() -> void;
auto resizeWindow() -> void;
auto toggleFullScreen() -> void; auto toggleFullScreen() -> void;
auto loadSystems() -> void; auto loadSystems() -> void;
auto loadShaders() -> void; auto loadShaders() -> void;
@ -29,10 +33,17 @@ struct Presentation : Window {
MenuItem powerSystem{&systemMenu}; MenuItem powerSystem{&systemMenu};
MenuItem unloadSystem{&systemMenu}; MenuItem unloadSystem{&systemMenu};
Menu settingsMenu{&menuBar}; Menu settingsMenu{&menuBar};
Menu videoScaleMenu{&settingsMenu}; Menu sizeMenu{&settingsMenu};
MenuItem videoScaleSmall{&videoScaleMenu}; Group sizeGroup;
MenuItem videoScaleMedium{&videoScaleMenu}; Menu outputMenu{&settingsMenu};
MenuItem videoScaleLarge{&videoScaleMenu}; MenuRadioItem centerViewport{&outputMenu};
MenuRadioItem scaleViewport{&outputMenu};
MenuRadioItem stretchViewport{&outputMenu};
Group outputGroup{&centerViewport, &scaleViewport, &stretchViewport};
MenuSeparator outputSeparator{&outputMenu};
MenuCheckItem adaptiveSizing{&outputMenu};
MenuCheckItem aspectCorrection{&outputMenu};
MenuCheckItem showOverscanArea{&outputMenu};
Menu videoEmulationMenu{&settingsMenu}; Menu videoEmulationMenu{&settingsMenu};
MenuCheckItem blurEmulation{&videoEmulationMenu}; MenuCheckItem blurEmulation{&videoEmulationMenu};
MenuCheckItem colorEmulation{&videoEmulationMenu}; MenuCheckItem colorEmulation{&videoEmulationMenu};
@ -85,8 +96,11 @@ struct Presentation : Window {
Widget iconBefore{&iconLayout, Size{128, ~0}, 0}; Widget iconBefore{&iconLayout, Size{128, ~0}, 0};
Canvas iconCanvas{&iconLayout, Size{112, 112}, 0}; Canvas iconCanvas{&iconLayout, Size{112, 112}, 0};
Widget iconAfter{&iconLayout, Size{128, 8}, 0}; Widget iconAfter{&iconLayout, Size{128, 8}, 0};
HorizontalLayout statusLayout{&layout, Size{~0, StatusHeight}, 0};
StatusBar statusBar{this}; Label spacerLeft{&statusLayout, Size{8, ~0}, 0};
Label statusMessage{&statusLayout, Size{~0, ~0}, 0};
Label statusInfo{&statusLayout, Size{100, ~0}, 0};
Label spacerRight{&statusLayout, Size{8, ~0}, 0};
}; };
extern unique_pointer<AboutWindow> aboutWindow; extern unique_pointer<AboutWindow> aboutWindow;

View File

@ -41,7 +41,11 @@ auto Program::load(Emulator::Interface& interface) -> void {
updateAudioEffects(); updateAudioEffects();
presentation->viewportLayout.remove(presentation->iconLayout); presentation->viewportLayout.remove(presentation->iconLayout);
if(settings["View/Adaptive"].boolean()) {
presentation->resizeWindow();
} else {
presentation->resizeViewport(); presentation->resizeViewport();
}
presentation->setTitle(emulator->titles().merge(" + ")); presentation->setTitle(emulator->titles().merge(" + "));
presentation->systemMenu.setText(information.name).setVisible(true); presentation->systemMenu.setText(information.name).setVisible(true);
presentation->toolsMenu.setVisible(true); presentation->toolsMenu.setVisible(true);
@ -62,7 +66,11 @@ auto Program::unload() -> void {
gamePaths.reset(); gamePaths.reset();
presentation->viewportLayout.append(presentation->iconLayout, Size{0, ~0}); presentation->viewportLayout.append(presentation->iconLayout, Size{0, ~0});
if(settings["View/Adaptive"].boolean()) {
presentation->resizeWindow();
} else {
presentation->resizeViewport(); presentation->resizeViewport();
}
presentation->setTitle({"higan v", Emulator::Version}); presentation->setTitle({"higan v", Emulator::Version});
presentation->systemMenu.setVisible(false); presentation->systemMenu.setVisible(false);
presentation->toolsMenu.setVisible(false); presentation->toolsMenu.setVisible(false);

View File

@ -56,16 +56,18 @@ auto Program::videoRefresh(uint displayID, const uint32* data, uint pitch, uint
pitch >>= 2; pitch >>= 2;
if(!settings["View/Overscan"].boolean()) {
auto display = emulator->displays()[displayID]; auto display = emulator->displays()[displayID];
if(display.type == Emulator::Interface::Display::Type::CRT) { if(display.type == Emulator::Interface::Display::Type::CRT) {
uint overscanHorizontal = settings["Video/Overscan/Horizontal"].natural(); uint overscanHorizontal = settings["View/Overscan/Horizontal"].natural();
uint overscanVertical = settings["Video/Overscan/Vertical"].natural(); uint overscanVertical = settings["View/Overscan/Vertical"].natural();
overscanHorizontal *= display.internalWidth / display.width; overscanHorizontal *= display.internalWidth / display.width;
overscanVertical *= display.internalHeight / display.height; overscanVertical *= display.internalHeight / display.height;
data += overscanVertical * pitch + overscanHorizontal; data += overscanVertical * pitch + overscanHorizontal;
width -= overscanHorizontal * 2; width -= overscanHorizontal * 2;
height -= overscanVertical * 2; height -= overscanVertical * 2;
} }
}
if(video->acquire(output, length, width, height)) { if(video->acquire(output, length, width, height)) {
length >>= 2; length >>= 2;
@ -85,7 +87,7 @@ auto Program::videoRefresh(uint displayID, const uint32* data, uint pitch, uint
current = chrono::timestamp(); current = chrono::timestamp();
if(current != previous) { if(current != previous) {
previous = current; previous = current;
statusText = {"FPS: ", frameCounter}; statusInfo = {frameCounter, " FPS"};
frameCounter = 0; frameCounter = 0;
} }
} }

View File

@ -50,11 +50,11 @@ struct Program : Emulator::Platform {
vector<string> gameQueue; //for command-line and drag-and-drop loading vector<string> gameQueue; //for command-line and drag-and-drop loading
vector<string> gamePaths; //for keeping track of loaded folder locations vector<string> gamePaths; //for keeping track of loaded folder locations
time_t autoSaveTime = 0; //for automatically saving RAM periodically uint64 autoSaveTime = 0; //for automatically saving RAM periodically
string statusText; uint64 statusTime = 0; //for status message timeout after two seconds
string statusMessage; string statusMessage;
time_t statusTime = 0; string statusInfo;
}; };
extern unique_pointer<Program> program; extern unique_pointer<Program> program;

View File

@ -105,21 +105,24 @@ auto Program::showMessage(const string& text) -> void {
} }
auto Program::updateStatusText() -> void { auto Program::updateStatusText() -> void {
time_t currentTime = time(nullptr); string message;
if(chrono::timestamp() - statusTime <= 2) {
string text; message = statusMessage;
if((currentTime - statusTime) <= 2) { }
text = statusMessage; if(message != presentation->statusMessage.text()) {
} else if(!emulator || emulator->loaded() == false) { presentation->statusMessage.setText(message);
text = "No game loaded";
} else if(pause || (!focused() && settingsManager->input.pauseEmulation.checked())) {
text = "Paused";
} else {
text = statusText;
} }
if(text != presentation->statusBar.text()) { string info;
presentation->statusBar.setText(text); if(!emulator || !emulator->loaded()) {
info = "Unloaded";
} else if(pause || (!focused() && settingsManager->input.pauseEmulation.checked())) {
info = "Paused";
} else {
info = statusInfo;
}
if(info != presentation->statusInfo.text()) {
presentation->statusInfo.setText(info);
} }
} }

View File

@ -11,19 +11,18 @@ unique_pointer<SettingsManager> settingsManager;
unique_pointer<SystemProperties> systemProperties; unique_pointer<SystemProperties> systemProperties;
Settings::Settings() { Settings::Settings() {
Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")))); Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")), " "));
auto set = [&](const string& name, const string& value) { auto set = [&](const string& name, const string& value) {
//create node and set to default value only if it does not already exist //create node and set to default value only if it does not already exist
if(!operator[](name)) operator()(name).setValue(value); if(!operator[](name)) operator()(name).setValue(value);
}; };
set("UserInterface/ShowStatusBar", true);
set("Library/Location", {Path::user(), "Emulation/"}); set("Library/Location", {Path::user(), "Emulation/"});
set("Library/IgnoreManifests", false); set("Library/IgnoreManifests", false);
set("Video/Driver", ruby::Video::safestDriver()); set("Video/Driver", ruby::Video::safestDriver());
set("Video/Exclusive", false);
set("Video/Synchronize", false); set("Video/Synchronize", false);
set("Video/Shader", "Blur"); set("Video/Shader", "Blur");
set("Video/BlurEmulation", true); set("Video/BlurEmulation", true);
@ -34,21 +33,6 @@ Settings::Settings() {
set("Video/Gamma", 100); set("Video/Gamma", 100);
set("Video/Luminance", 100); set("Video/Luminance", 100);
set("Video/Overscan/Horizontal", 0);
set("Video/Overscan/Vertical", 0);
set("Video/Windowed/AspectCorrection", true);
set("Video/Windowed/IntegralScaling", true);
set("Video/Windowed/Adaptive", true);
set("Video/Windowed/Scale", "Small");
set("Video/Windowed/Scale/Small", "640x480");
set("Video/Windowed/Scale/Medium", "960x720");
set("Video/Windowed/Scale/Large", "1280x960");
set("Video/Fullscreen/AspectCorrection", true);
set("Video/Fullscreen/IntegralScaling", true);
set("Video/Fullscreen/Exclusive", false);
set("Audio/Driver", ruby::Audio::safestDriver()); set("Audio/Driver", ruby::Audio::safestDriver());
set("Audio/Device", ""); set("Audio/Device", "");
set("Audio/Frequency", 48000); set("Audio/Frequency", 48000);
@ -63,6 +47,15 @@ Settings::Settings() {
set("Input/Frequency", 5); set("Input/Frequency", 5);
set("Input/Defocus", "Pause"); set("Input/Defocus", "Pause");
set("View/Multiplier", "2");
set("View/Output", "Scale");
set("View/Adaptive", false);
set("View/AspectCorrection", true);
set("View/Overscan", true);
set("View/Overscan/Horizontal", 0);
set("View/Overscan/Vertical", 8);
set("View/StatusBar", true);
set("Emulation/AutoSaveMemory/Enable", true); set("Emulation/AutoSaveMemory/Enable", true);
set("Emulation/AutoSaveMemory/Interval", 30); set("Emulation/AutoSaveMemory/Interval", 30);
@ -72,7 +65,7 @@ Settings::Settings() {
} }
auto Settings::save() -> void { auto Settings::save() -> void {
file::write(locate("settings.bml"), BML::serialize(*this)); file::write(locate("settings.bml"), BML::serialize(*this, " "));
} }
// //

View File

@ -48,6 +48,8 @@ struct SystemSettings : TabFrameItem {
struct VideoSettings : TabFrameItem { struct VideoSettings : TabFrameItem {
VideoSettings(TabFrame*); VideoSettings(TabFrame*);
auto updateColor(bool initializing = false) -> void;
auto updateOverscan(bool initializing = false) -> void;
VerticalLayout layout{this}; VerticalLayout layout{this};
Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2}; Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2};
@ -72,19 +74,8 @@ struct VideoSettings : TabFrameItem {
Label verticalMaskLabel{&verticalMaskLayout, Size{80, 0}}; Label verticalMaskLabel{&verticalMaskLayout, Size{80, 0}};
Label verticalMaskValue{&verticalMaskLayout, Size{50, 0}}; Label verticalMaskValue{&verticalMaskLayout, Size{50, 0}};
HorizontalSlider verticalMaskSlider{&verticalMaskLayout, Size{~0, 0}}; HorizontalSlider verticalMaskSlider{&verticalMaskLayout, Size{~0, 0}};
Label windowedModeLabel{&layout, Size{~0, 0}, 2}; Label fullscreenLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout windowedModeLayout{&layout, Size{~0, 0}}; CheckLabel fullscreenExclusive{&layout, Size{0, 0}};
CheckLabel windowedModeAspectCorrection{&windowedModeLayout, Size{0, 0}};
CheckLabel windowedModeIntegralScaling{&windowedModeLayout, Size{0, 0}};
CheckLabel windowedModeAdaptive{&windowedModeLayout, Size{0, 0}};
Label fullscreenModeLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout fullscreenModeLayout{&layout, Size{~0, 0}};
CheckLabel fullscreenModeAspectCorrection{&fullscreenModeLayout, Size{0, 0}};
CheckLabel fullscreenModeIntegralScaling{&fullscreenModeLayout, Size{0, 0}};
CheckLabel fullscreenModeExclusive{&fullscreenModeLayout, Size{0, 0}};
auto updateColor(bool initializing = false) -> void;
auto updateViewport(bool initializing = false) -> void;
}; };
struct AudioSettings : TabFrameItem { struct AudioSettings : TabFrameItem {

View File

@ -15,26 +15,21 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
luminanceValue.setAlignment(0.5); luminanceValue.setAlignment(0.5);
luminanceSlider.setLength(101).setPosition(settings["Video/Luminance"].natural()).onChange([&] { updateColor(); }); luminanceSlider.setLength(101).setPosition(settings["Video/Luminance"].natural()).onChange([&] { updateColor(); });
overscanMaskLabel.setFont(Font().setBold()).setText("Overscan Mask"); overscanMaskLabel.setFont(Font().setBold()).setText("Overscan Area");
horizontalMaskLabel.setText("Horizontal:"); horizontalMaskLabel.setText("Horizontal:");
horizontalMaskValue.setAlignment(0.5); horizontalMaskValue.setAlignment(0.5);
horizontalMaskSlider.setLength(25).setPosition(settings["Video/Overscan/Horizontal"].natural()).onChange([&] { updateViewport(); }); horizontalMaskSlider.setLength(25).setPosition(settings["View/Overscan/Horizontal"].natural()).onChange([&] { updateOverscan(); });
verticalMaskLabel.setText("Vertical:"); verticalMaskLabel.setText("Vertical:");
verticalMaskValue.setAlignment(0.5); verticalMaskValue.setAlignment(0.5);
verticalMaskSlider.setLength(25).setPosition(settings["Video/Overscan/Vertical"].natural()).onChange([&] { updateViewport(); }); verticalMaskSlider.setLength(25).setPosition(settings["View/Overscan/Vertical"].natural()).onChange([&] { updateOverscan(); });
windowedModeLabel.setFont(Font().setBold()).setText("Windowed Mode"); fullscreenLabel.setFont(Font().setBold()).setText("Fullscreen");
windowedModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Windowed/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); }); fullscreenExclusive.setText("Exclusive mode").setChecked(settings["Video/Exclusive"].boolean()).onToggle([&] {
windowedModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Windowed/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); }); settings["Video/Exclusive"].setValue(fullscreenExclusive.checked());
windowedModeAdaptive.setText("Adaptive sizing").setChecked(settings["Video/Windowed/Adaptive"].boolean()).onToggle([&] { updateViewport(); }); });
fullscreenModeLabel.setFont(Font().setBold()).setText("Fullscreen Mode");
fullscreenModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Fullscreen/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); });
fullscreenModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Fullscreen/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); });
fullscreenModeExclusive.setText("Exclusive mode").setChecked(settings["Video/Fullscreen/Exclusive"].boolean()).onToggle([&] { updateViewport(); });
updateColor(true); updateColor(true);
updateViewport(true); updateOverscan(true);
} }
auto VideoSettings::updateColor(bool initializing) -> void { auto VideoSettings::updateColor(bool initializing) -> void {
@ -48,20 +43,16 @@ auto VideoSettings::updateColor(bool initializing) -> void {
if(!initializing) program->updateVideoPalette(); if(!initializing) program->updateVideoPalette();
} }
auto VideoSettings::updateViewport(bool initializing) -> void { auto VideoSettings::updateOverscan(bool initializing) -> void {
bool wasAdaptive = settings["Video/Windowed/Adaptive"].boolean(); settings["View/Overscan/Horizontal"].setValue(horizontalMaskSlider.position());
bool isAdaptive = windowedModeAdaptive.checked(); settings["View/Overscan/Vertical"].setValue(verticalMaskSlider.position());
settings["Video/Overscan/Horizontal"].setValue(horizontalMaskSlider.position());
settings["Video/Overscan/Vertical"].setValue(verticalMaskSlider.position());
settings["Video/Windowed/AspectCorrection"].setValue(windowedModeAspectCorrection.checked());
settings["Video/Windowed/IntegralScaling"].setValue(windowedModeIntegralScaling.checked());
settings["Video/Windowed/Adaptive"].setValue(windowedModeAdaptive.checked());
settings["Video/Fullscreen/AspectCorrection"].setValue(fullscreenModeAspectCorrection.checked());
settings["Video/Fullscreen/IntegralScaling"].setValue(fullscreenModeIntegralScaling.checked());
settings["Video/Fullscreen/Exclusive"].setValue(fullscreenModeExclusive.checked());
horizontalMaskValue.setText({horizontalMaskSlider.position()}); horizontalMaskValue.setText({horizontalMaskSlider.position()});
verticalMaskValue.setText({verticalMaskSlider.position()}); verticalMaskValue.setText({verticalMaskSlider.position()});
if(!initializing) presentation->resizeViewport(isAdaptive || wasAdaptive != isAdaptive); if(initializing || settings["View/Overscan"].boolean()) return;
if(settings["View/Adaptive"].boolean()) {
presentation->resizeWindow();
} else {
presentation->resizeViewport();
}
} }

View File

@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
} }
auto pSizable::setGeometry(Geometry geometry) -> void { auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
} }
} }

View File

@ -14,6 +14,7 @@ auto pWidget::construct() -> void {
if(auto p = window->self()) p->_append(self()); if(auto p = window->self()) p->_append(self());
setEnabled(self().enabled(true)); setEnabled(self().enabled(true));
setFont(self().font(true)); setFont(self().font(true));
setToolTip(self().toolTip());
setVisible(self().visible(true)); setVisible(self().visible(true));
} }
} }
@ -62,7 +63,11 @@ auto pWidget::setGeometry(Geometry geometry) -> void {
[cocoaView setFrame:NSMakeRect(geometry.x(), windowHeight - geometry.y() - geometry.height(), geometry.width(), geometry.height())]; [cocoaView setFrame:NSMakeRect(geometry.x(), windowHeight - geometry.y() - geometry.height(), geometry.width(), geometry.height())];
[[cocoaView superview] setNeedsDisplay:YES]; [[cocoaView superview] setNeedsDisplay:YES];
} }
self().doSize(); pSizable::setGeometry(geometry);
}
auto pWidget::setToolTip(const string& toolTip) -> void {
//TODO
} }
auto pWidget::setVisible(bool visible) -> void { auto pWidget::setVisible(bool visible) -> void {

View File

@ -10,6 +10,7 @@ struct pWidget : pSizable {
auto setFocused() -> void override; auto setFocused() -> void override;
auto setFont(const Font& font) -> void override; auto setFont(const Font& font) -> void override;
auto setGeometry(Geometry geometry) -> void override; auto setGeometry(Geometry geometry) -> void override;
auto setToolTip(const string& toolTip) -> void;
auto setVisible(bool visible) -> void override; auto setVisible(bool visible) -> void override;
NSView* cocoaView = nullptr; NSView* cocoaView = nullptr;

View File

@ -72,7 +72,11 @@ auto Application::setScale(float scale) -> void {
auto Application::setScreenSaver(bool screenSaver) -> void { auto Application::setScreenSaver(bool screenSaver) -> void {
state().screenSaver = screenSaver; state().screenSaver = screenSaver;
pApplication::setScreenSaver(screenSaver); if(state().initialized) pApplication::setScreenSaver(screenSaver);
}
auto Application::setToolTips(bool toolTips) -> void {
state().toolTips = toolTips;
} }
//this ensures Application::state is initialized prior to use //this ensures Application::state is initialized prior to use
@ -81,6 +85,10 @@ auto Application::state() -> State& {
return state; return state;
} }
auto Application::toolTips() -> bool {
return state().toolTips;
}
auto Application::unscale(float value) -> float { auto Application::unscale(float value) -> float {
return value * (1.0 / state().scale); return value * (1.0 / state().scale);
} }
@ -135,11 +143,11 @@ auto Application::Cocoa::onQuit(const function<void ()>& callback) -> void {
//======== //========
auto Application::initialize() -> void { auto Application::initialize() -> void {
static bool initialized = false; if(!state().initialized) {
if(initialized == false) {
initialized = true;
hiro::initialize(); hiro::initialize();
return pApplication::initialize(); pApplication::initialize();
state().initialized = true;
pApplication::setScreenSaver(state().screenSaver);
} }
} }

View File

@ -20,6 +20,8 @@ struct Application {
static auto setName(const string& name = "") -> void; static auto setName(const string& name = "") -> void;
static auto setScale(float scale = 1.0) -> void; static auto setScale(float scale = 1.0) -> void;
static auto setScreenSaver(bool screenSaver = true) -> void; static auto setScreenSaver(bool screenSaver = true) -> void;
static auto setToolTips(bool toolTips = true) -> void;
static auto toolTips() -> bool;
static auto unscale(float value) -> float; static auto unscale(float value) -> float;
struct Windows { struct Windows {
@ -45,6 +47,7 @@ struct Application {
//private: //private:
struct State { struct State {
Font font; Font font;
bool initialized = false;
Locale locale; Locale locale;
int modal = 0; int modal = 0;
string name; string name;
@ -52,6 +55,7 @@ struct Application {
bool quit = false; bool quit = false;
float scale = 1.0; float scale = 1.0;
bool screenSaver = true; bool screenSaver = true;
bool toolTips = true;
struct Windows { struct Windows {
function<void (bool)> onModalChange; function<void (bool)> onModalChange;

View File

@ -13,6 +13,7 @@
#include <nall/stdint.hpp> #include <nall/stdint.hpp>
#include <nall/string.hpp> #include <nall/string.hpp>
#include <nall/traits.hpp> #include <nall/traits.hpp>
#include <nall/unique-pointer.hpp>
#include <nall/utility.hpp> #include <nall/utility.hpp>
#include <nall/vector.hpp> #include <nall/vector.hpp>
@ -25,7 +26,7 @@ using nall::set;
using nall::shared_pointer; using nall::shared_pointer;
using nall::shared_pointer_weak; using nall::shared_pointer_weak;
using nall::string; using nall::string;
using nall::string_pascal; using nall::unique_pointer;
using nall::vector; using nall::vector;
namespace hiro { namespace hiro {
@ -1218,84 +1219,8 @@ struct mRadioLabel : mWidget {
#endif #endif
#include "widget/source-edit.hpp" #include "widget/source-edit.hpp"
#include "widget/tab-frame.hpp"
#if defined(Hiro_TabFrame) #include "widget/tab-frame-item.hpp"
struct mTabFrame : mWidget {
Declare(TabFrame)
using mObject::remove;
friend class mTabFrameItem;
auto append(sTabFrameItem item) -> type&;
auto doChange() const -> void;
auto doClose(sTabFrameItem item) const -> void;
auto doMove(sTabFrameItem from, sTabFrameItem to) const -> void;
auto item(uint position) const -> TabFrameItem;
auto itemCount() const -> uint;
auto items() const -> vector<TabFrameItem>;
auto navigation() const -> Navigation;
auto onChange(const function<void ()>& callback = {}) -> type&;
auto onClose(const function<void (TabFrameItem)>& callback = {}) -> type&;
auto onMove(const function<void (TabFrameItem, TabFrameItem)>& callback = {}) -> type&;
auto remove(sTabFrameItem item) -> type&;
auto reset() -> type&;
auto selected() const -> TabFrameItem;
auto setEnabled(bool enabled = true) -> type& override;
auto setFont(const Font& font = {}) -> type& override;
auto setNavigation(Navigation navigation = Navigation::Top) -> type&;
auto setParent(mObject* object = nullptr, int offset = -1) -> type& override;
auto setVisible(bool visible = true) -> type& override;
//private:
struct State {
vector<sTabFrameItem> items;
Navigation navigation = Navigation::Top;
function<void ()> onChange;
function<void (TabFrameItem)> onClose;
function<void (TabFrameItem, TabFrameItem)> onMove;
} state;
auto destruct() -> void override;
};
#endif
#if defined(Hiro_TabFrame)
struct mTabFrameItem : mObject {
Declare(TabFrameItem)
auto append(sSizable sizable) -> type&;
auto closable() const -> bool;
auto icon() const -> image;
auto movable() const -> bool;
auto remove() -> type& override;
auto remove(sSizable sizable) -> type&;
auto reset() -> type&;
auto selected() const -> bool;
auto setClosable(bool closable = true) -> type&;
auto setEnabled(bool enabled = true) -> type& override;
auto setFont(const Font& font = {}) -> type& override;
auto setIcon(const image& icon = {}) -> type&;
auto setMovable(bool movable = true) -> type&;
auto setParent(mObject* object = nullptr, int offset = -1) -> type& override;
auto setSelected() -> type&;
auto setText(const string& text = "") -> type&;
auto setVisible(bool visible = true) -> type& override;
auto sizable() const -> Sizable;
auto text() const -> string;
//private:
struct State {
bool closable = false;
image icon;
bool movable = false;
bool selected = false;
sSizable sizable;
string text;
} state;
auto destruct() -> void override;
};
#endif
#include "widget/table-view.hpp" #include "widget/table-view.hpp"
#include "widget/table-view-column.hpp" #include "widget/table-view-column.hpp"
#include "widget/table-view-item.hpp" #include "widget/table-view-item.hpp"

View File

@ -46,9 +46,9 @@ struct mObject {
auto visible(bool recursive = false) const -> bool; auto visible(bool recursive = false) const -> bool;
//private: //private:
//sizeof(mObject) == 72 //sizeof(mObject) == 88
struct State { struct State {
Font font; //16 Font font; //32
set<Property> properties; //16 set<Property> properties; //16
mObject* parent = nullptr; // 8 mObject* parent = nullptr; // 8
int offset = -1; // 4 int offset = -1; // 4

View File

@ -52,14 +52,16 @@
#define DeclareSharedSizable(Name) \ #define DeclareSharedSizable(Name) \
DeclareSharedObject(Name) \ DeclareSharedObject(Name) \
auto doSize() const { return self().doSize(); } \
auto geometry() const { return self().geometry(); } \ auto geometry() const { return self().geometry(); } \
auto minimumSize() const { return self().minimumSize(); } \ auto minimumSize() const { return self().minimumSize(); } \
auto onSize(const function<void ()>& callback = {}) { return self().onSize(callback), *this; } \
auto setGeometry(Geometry geometry) { return self().setGeometry(geometry), *this; } \ auto setGeometry(Geometry geometry) { return self().setGeometry(geometry), *this; } \
#define DeclareSharedWidget(Name) \ #define DeclareSharedWidget(Name) \
DeclareSharedSizable(Name) \ DeclareSharedSizable(Name) \
auto doSize() const { return self().doSize(); } \ auto setToolTip(const string& toolTip = "") { return self().setToolTip(toolTip), *this; } \
auto onSize(const function<void ()>& callback = {}) { return self().onSize(callback), *this; } \ auto toolTip() const { return self().toolTip(); } \
#if defined(Hiro_Object) #if defined(Hiro_Object)
struct Object : sObject { struct Object : sObject {
@ -75,9 +77,6 @@ struct Group : sGroup {
auto append(sObject object) -> type& { return self().append(object), *this; } auto append(sObject object) -> type& { return self().append(object), *this; }
auto object(unsigned position) const { return self().object(position); } auto object(unsigned position) const { return self().object(position); }
auto objectCount() const { return self().objectCount(); } auto objectCount() const { return self().objectCount(); }
//auto objects() const { return self().objects(); }
auto remove(sObject object) -> type& { return self().remove(object), *this; }
template<typename T = Object> auto objects() const -> vector<T> { template<typename T = Object> auto objects() const -> vector<T> {
vector<T> objects; vector<T> objects;
for(auto object : self().objects()) { for(auto object : self().objects()) {
@ -85,6 +84,7 @@ struct Group : sGroup {
} }
return objects; return objects;
} }
auto remove(sObject object) -> type& { return self().remove(object), *this; }
private: private:
auto _append() {} auto _append() {}

View File

@ -4,6 +4,10 @@ auto mSizable::allocate() -> pObject* {
return new pSizable(*this); return new pSizable(*this);
} }
auto mSizable::doSize() const -> void {
if(state.onSize) return state.onSize();
}
auto mSizable::geometry() const -> Geometry { auto mSizable::geometry() const -> Geometry {
return state.geometry; return state.geometry;
} }
@ -12,6 +16,11 @@ auto mSizable::minimumSize() const -> Size {
return signal(minimumSize); return signal(minimumSize);
} }
auto mSizable::onSize(const function<void ()>& callback) -> type& {
state.onSize = callback;
return *this;
}
auto mSizable::setGeometry(Geometry geometry) -> type& { auto mSizable::setGeometry(Geometry geometry) -> type& {
state.geometry = geometry; state.geometry = geometry;
signal(setGeometry, geometry); signal(setGeometry, geometry);

View File

@ -2,14 +2,17 @@
struct mSizable : mObject { struct mSizable : mObject {
Declare(Sizable) Declare(Sizable)
auto doSize() const -> void;
auto geometry() const -> Geometry; auto geometry() const -> Geometry;
virtual auto minimumSize() const -> Size; virtual auto minimumSize() const -> Size;
auto onSize(const function<void ()>& callback = {}) -> type&;
virtual auto setGeometry(Geometry geometry) -> type&; virtual auto setGeometry(Geometry geometry) -> type&;
//private: //private:
//sizeof(mSizable) == 16 //sizeof(mSizable) == 24
struct State { struct State {
Geometry geometry; Geometry geometry;
function<void ()> onSize;
} state; } state;
}; };
#endif #endif

View File

@ -0,0 +1,37 @@
#if defined(Hiro_TabFrame)
struct mTabFrameItem : mObject {
Declare(TabFrameItem)
auto append(sSizable sizable) -> type&;
auto closable() const -> bool;
auto icon() const -> image;
auto movable() const -> bool;
auto remove() -> type& override;
auto remove(sSizable sizable) -> type&;
auto reset() -> type&;
auto selected() const -> bool;
auto setClosable(bool closable = true) -> type&;
auto setEnabled(bool enabled = true) -> type& override;
auto setFont(const Font& font = {}) -> type& override;
auto setIcon(const image& icon = {}) -> type&;
auto setMovable(bool movable = true) -> type&;
auto setParent(mObject* object = nullptr, int offset = -1) -> type& override;
auto setSelected() -> type&;
auto setText(const string& text = "") -> type&;
auto setVisible(bool visible = true) -> type& override;
auto sizable() const -> Sizable;
auto text() const -> string;
//private:
struct State {
bool closable = false;
image icon;
bool movable = false;
bool selected = false;
sSizable sizable;
string text;
} state;
auto destruct() -> void override;
};
#endif

View File

@ -0,0 +1,38 @@
#if defined(Hiro_TabFrame)
struct mTabFrame : mWidget {
Declare(TabFrame)
using mObject::remove;
friend class mTabFrameItem;
auto append(sTabFrameItem item) -> type&;
auto doChange() const -> void;
auto doClose(sTabFrameItem item) const -> void;
auto doMove(sTabFrameItem from, sTabFrameItem to) const -> void;
auto item(uint position) const -> TabFrameItem;
auto itemCount() const -> uint;
auto items() const -> vector<TabFrameItem>;
auto navigation() const -> Navigation;
auto onChange(const function<void ()>& callback = {}) -> type&;
auto onClose(const function<void (TabFrameItem)>& callback = {}) -> type&;
auto onMove(const function<void (TabFrameItem, TabFrameItem)>& callback = {}) -> type&;
auto remove(sTabFrameItem item) -> type&;
auto reset() -> type&;
auto selected() const -> TabFrameItem;
auto setEnabled(bool enabled = true) -> type& override;
auto setFont(const Font& font = {}) -> type& override;
auto setNavigation(Navigation navigation = Navigation::Top) -> type&;
auto setParent(mObject* object = nullptr, int offset = -1) -> type& override;
auto setVisible(bool visible = true) -> type& override;
//private:
struct State {
vector<sTabFrameItem> items;
Navigation navigation = Navigation::Top;
function<void ()> onChange;
function<void (TabFrameItem)> onClose;
function<void (TabFrameItem, TabFrameItem)> onMove;
} state;
auto destruct() -> void override;
};
#endif

View File

@ -6,15 +6,6 @@ auto mWidget::allocate() -> pObject* {
// //
auto mWidget::doSize() const -> void {
if(state.onSize) return state.onSize();
}
auto mWidget::onSize(const function<void ()>& callback) -> type& {
state.onSize = callback;
return *this;
}
auto mWidget::remove() -> type& { auto mWidget::remove() -> type& {
//TODO: how to implement this after removing mLayout? //TODO: how to implement this after removing mLayout?
//if(auto layout = parentLayout()) layout->remove(layout->sizable(offset())); //if(auto layout = parentLayout()) layout->remove(layout->sizable(offset()));
@ -22,4 +13,16 @@ auto mWidget::remove() -> type& {
return *this; return *this;
} }
auto mWidget::setToolTip(const string& toolTip) -> type& {
state.toolTip = toolTip;
//TODO: allow this option to dynamically control tool tips
if(!Application::state().toolTips) return *this;
signal(setToolTip, toolTip);
return *this;
}
auto mWidget::toolTip() const -> string {
return state.toolTip;
}
#endif #endif

View File

@ -2,14 +2,14 @@
struct mWidget : mSizable { struct mWidget : mSizable {
Declare(Widget) Declare(Widget)
auto doSize() const -> void;
auto onSize(const function<void ()>& callback = {}) -> type&;
auto remove() -> type& override; auto remove() -> type& override;
auto setToolTip(const string& toolTip = "") -> type&;
auto toolTip() const -> string;
//private: //private:
//sizeof(mWidget) == 8 //sizeof(mWidget) == 32
struct State { struct State {
function<void ()> onSize; string toolTip;
} state; } state;
}; };
#endif #endif

View File

@ -166,7 +166,7 @@ auto mVerticalLayout::setGeometry(Geometry geometry) -> type& {
auto cell = this->cell(index); auto cell = this->cell(index);
auto alignment = cell.alignment(); auto alignment = cell.alignment();
if(!alignment) alignment = this->alignment(); if(!alignment) alignment = this->alignment();
if(!alignment) alignment = 0.5; if(!alignment) alignment = 0.0;
float cellWidth = cell.size().width(); float cellWidth = cell.size().width();
float cellHeight = geometryHeight; float cellHeight = geometryHeight;
if(cellWidth == Size::Minimum) cellWidth = cell.sizable()->minimumSize().width(); if(cellWidth == Size::Minimum) cellWidth = cell.sizable()->minimumSize().width();

View File

@ -2,13 +2,6 @@
namespace hiro { namespace hiro {
vector<pWindow*> pApplication::windows;
#if defined(DISPLAY_XORG)
XlibDisplay* pApplication::display = nullptr;
bool pApplication::xdgScreenSaver = false;
#endif
auto pApplication::run() -> void { auto pApplication::run() -> void {
while(!Application::state().quit) { while(!Application::state().quit) {
Application::doMain(); Application::doMain();
@ -22,7 +15,7 @@ auto pApplication::pendingEvents() -> bool {
auto pApplication::processEvents() -> void { auto pApplication::processEvents() -> void {
while(pendingEvents()) gtk_main_iteration_do(false); while(pendingEvents()) gtk_main_iteration_do(false);
for(auto& window : windows) window->_synchronizeGeometry(); for(auto& window : state().windows) window->_synchronizeGeometry();
} }
auto pApplication::quit() -> void { auto pApplication::quit() -> void {
@ -30,21 +23,54 @@ auto pApplication::quit() -> void {
if(gtk_main_level()) gtk_main_quit(); if(gtk_main_level()) gtk_main_quit();
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
XCloseDisplay(display); if(state().display) {
display = nullptr; if(state().screenSaverXDG && state().screenSaverWindow) {
//this needs to run synchronously, so that XUnmapWindow() won't happen before xdg-screensaver is finished
execute("xdg-screensaver", "resume", string{"0x", hex(state().screenSaverWindow)});
XUnmapWindow(state().display, state().screenSaverWindow);
state().screenSaverWindow = 0;
}
XCloseDisplay(state().display);
state().display = nullptr;
}
#endif #endif
} }
auto pApplication::setScreenSaver(bool screenSaver) -> void { auto pApplication::setScreenSaver(bool screenSaver) -> void {
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
for(auto& window : windows) window->_setScreenSaver(screenSaver); if(state().screenSaverXDG && state().screenSaverWindow) {
invoke("xdg-screensaver", screenSaver ? "resume" : "suspend", string{"0x", hex(state().screenSaverWindow)});
}
#endif #endif
} }
auto pApplication::state() -> State& {
static State state;
return state;
}
auto pApplication::initialize() -> void { auto pApplication::initialize() -> void {
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
display = XOpenDisplay(nullptr); state().display = XOpenDisplay(nullptr);
xdgScreenSaver = (bool)execute("xdg-screensaver", "--version").output.find("xdg-screensaver"); state().screenSaverXDG = (bool)execute("xdg-screensaver", "--version").output.find("xdg-screensaver");
if(state().screenSaverXDG) {
auto screen = DefaultScreen(state().display);
auto rootWindow = RootWindow(state().display, screen);
XSetWindowAttributes attributes{};
attributes.background_pixel = BlackPixel(state().display, screen);
attributes.border_pixel = 0;
attributes.override_redirect = true;
state().screenSaverWindow = XCreateWindow(state().display, rootWindow,
0, 0, 1, 1, 0, DefaultDepth(state().display, screen),
InputOutput, DefaultVisual(state().display, screen),
CWBackPixel | CWBorderPixel | CWOverrideRedirect, &attributes
);
//note: hopefully xdg-screensaver does not require the window to be mapped ...
//if it does, we're in trouble: a small 1x1 black pixel window will be visible in said case
XMapWindow(state().display, state().screenSaverWindow);
XFlush(state().display);
}
#endif #endif
//set WM_CLASS to Application::name() //set WM_CLASS to Application::name()

View File

@ -11,12 +11,17 @@ struct pApplication {
static auto initialize() -> void; static auto initialize() -> void;
static vector<pWindow*> windows; struct State {
vector<pWindow*> windows;
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
static XlibDisplay* display; XlibDisplay* display = nullptr;
static bool xdgScreenSaver; XlibWindow screenSaverWindow = 0;
bool screenSaverXDG = false;
#endif #endif
};
static auto state() -> State&;
}; };
} }

View File

@ -8,7 +8,7 @@ auto pKeyboard::poll() -> vector<bool> {
vector<bool> result; vector<bool> result;
char state[256]; char state[256];
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
XQueryKeymap(pApplication::display, state); XQueryKeymap(pApplication::state().display, state);
#endif #endif
for(auto& code : settings.keycodes) { for(auto& code : settings.keycodes) {
result.append(_pressed(state, code)); result.append(_pressed(state, code));
@ -19,7 +19,7 @@ auto pKeyboard::poll() -> vector<bool> {
auto pKeyboard::pressed(unsigned code) -> bool { auto pKeyboard::pressed(unsigned code) -> bool {
char state[256]; char state[256];
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
XQueryKeymap(pApplication::display, state); XQueryKeymap(pApplication::state().display, state);
#endif #endif
return _pressed(state, code); return _pressed(state, code);
} }
@ -226,8 +226,8 @@ auto pKeyboard::_translate(unsigned code) -> signed {
auto pKeyboard::initialize() -> void { auto pKeyboard::initialize() -> void {
auto append = [](unsigned lo, unsigned hi = 0) { auto append = [](unsigned lo, unsigned hi = 0) {
#if defined(DISPLAY_XORG) #if defined(DISPLAY_XORG)
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0; lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::state().display, lo) : 0;
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0; hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::state().display, hi) : 0;
#endif #endif
settings.keycodes.append(lo | (hi << 8)); settings.keycodes.append(lo | (hi << 8));
}; };

View File

@ -13,7 +13,7 @@ auto pMouse::position() -> Position {
XlibWindow root, child; XlibWindow root, child;
int rootx, rooty, winx, winy; int rootx, rooty, winx, winy;
unsigned int mask; unsigned int mask;
XQueryPointer(pApplication::display, DefaultRootWindow(pApplication::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask); XQueryPointer(pApplication::state().display, DefaultRootWindow(pApplication::state().display), &root, &child, &rootx, &rooty, &winx, &winy, &mask);
return {rootx, rooty}; return {rootx, rooty};
#endif #endif
} }
@ -31,7 +31,7 @@ auto pMouse::pressed(Mouse::Button button) -> bool {
XlibWindow root, child; XlibWindow root, child;
int rootx, rooty, winx, winy; int rootx, rooty, winx, winy;
unsigned int mask; unsigned int mask;
XQueryPointer(pApplication::display, DefaultRootWindow(pApplication::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask); XQueryPointer(pApplication::state().display, DefaultRootWindow(pApplication::state().display), &root, &child, &rootx, &rooty, &winx, &winy, &mask);
switch(button) { switch(button) {
case Mouse::Button::Left: return mask & Button1Mask; case Mouse::Button::Left: return mask & Button1Mask;
case Mouse::Button::Middle: return mask & Button2Mask; case Mouse::Button::Middle: return mask & Button2Mask;

View File

@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
} }
auto pSizable::setGeometry(Geometry geometry) -> void { auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
} }
} }

View File

@ -6,7 +6,9 @@ auto pWidget::construct() -> void {
if(!gtkWidget) return; if(!gtkWidget) return;
if(auto window = self().parentWindow(true)) { if(auto window = self().parentWindow(true)) {
if(window->self()) window->self()->_append(self()); if(window->self()) window->self()->_append(self());
setEnabled(self().enabled(true));
setFont(self().font(true)); setFont(self().font(true));
setToolTip(self().toolTip());
setVisible(self().visible(true)); setVisible(self().visible(true));
} }
} }
@ -58,7 +60,11 @@ auto pWidget::setGeometry(Geometry geometry) -> void {
locked = false; locked = false;
} }
} }
self().doSize(); pSizable::setGeometry(geometry);
}
auto pWidget::setToolTip(const string& toolTip) -> void {
gtk_widget_set_tooltip_text(gtkWidget, toolTip);
} }
auto pWidget::setVisible(bool visible) -> void { auto pWidget::setVisible(bool visible) -> void {

View File

@ -11,6 +11,7 @@ struct pWidget : pSizable {
auto setFocused() -> void override; auto setFocused() -> void override;
auto setFont(const Font& font) -> void override; auto setFont(const Font& font) -> void override;
auto setGeometry(Geometry geometry) -> void override; auto setGeometry(Geometry geometry) -> void override;
auto setToolTip(const string& toolTip) -> void;
auto setVisible(bool visible) -> void override; auto setVisible(bool visible) -> void override;
GtkWidget* gtkWidget = nullptr; GtkWidget* gtkWidget = nullptr;

View File

@ -100,7 +100,6 @@ static auto Window_keyRelease(GtkWidget* widget, GdkEventKey* event, pWindow* p)
} }
static auto Window_realize(GtkWidget* widget, pWindow* p) -> void { static auto Window_realize(GtkWidget* widget, pWindow* p) -> void {
p->_setScreenSaver(Application::screenSaver());
} }
static auto Window_sizeAllocate(GtkWidget* widget, GtkAllocation* allocation, pWindow* p) -> void { static auto Window_sizeAllocate(GtkWidget* widget, GtkAllocation* allocation, pWindow* p) -> void {
@ -130,7 +129,6 @@ static auto Window_stateEvent(GtkWidget* widget, GdkEvent* event, pWindow* p) ->
} }
static auto Window_unrealize(GtkWidget* widget, pWindow* p) -> void { static auto Window_unrealize(GtkWidget* widget, pWindow* p) -> void {
p->_setScreenSaver(true);
} }
auto pWindow::construct() -> void { auto pWindow::construct() -> void {
@ -209,13 +207,13 @@ auto pWindow::construct() -> void {
g_object_set_data(G_OBJECT(widget), "hiro::window", (gpointer)this); g_object_set_data(G_OBJECT(widget), "hiro::window", (gpointer)this);
g_object_set_data(G_OBJECT(formContainer), "hiro::window", (gpointer)this); g_object_set_data(G_OBJECT(formContainer), "hiro::window", (gpointer)this);
pApplication::windows.append(this); pApplication::state().windows.append(this);
} }
auto pWindow::destruct() -> void { auto pWindow::destruct() -> void {
for(uint offset : range(pApplication::windows.size())) { for(uint offset : range(pApplication::state().windows.size())) {
if(pApplication::windows[offset] == this) { if(pApplication::state().windows[offset] == this) {
pApplication::windows.remove(offset); pApplication::state().windows.remove(offset);
break; break;
} }
} }
@ -495,19 +493,6 @@ auto pWindow::_setMenuVisible(bool visible) -> void {
gtk_widget_set_visible(gtkMenu, visible); gtk_widget_set_visible(gtkMenu, visible);
} }
auto pWindow::_setScreenSaver(bool screenSaver) -> void {
if(!gtk_widget_get_realized(widget)) return;
#if defined(DISPLAY_XORG)
if(pApplication::xdgScreenSaver) {
if(this->screenSaver != screenSaver) {
this->screenSaver = screenSaver;
invoke("xdg-screensaver", screenSaver ? "resume" : "suspend", string{"0x", hex(handle())});
}
}
#endif
}
auto pWindow::_setStatusEnabled(bool enabled) -> void { auto pWindow::_setStatusEnabled(bool enabled) -> void {
gtk_widget_set_sensitive(gtkStatus, enabled); gtk_widget_set_sensitive(gtkStatus, enabled);
} }

View File

@ -35,7 +35,6 @@ struct pWindow : pObject {
auto _append(mMenu& menu) -> void; auto _append(mMenu& menu) -> void;
auto _menuHeight() const -> int; auto _menuHeight() const -> int;
auto _menuTextHeight() const -> int; auto _menuTextHeight() const -> int;
auto _setScreenSaver(bool screenSaver) -> void;
auto _setIcon(const string& basename) -> bool; auto _setIcon(const string& basename) -> bool;
auto _setMenuEnabled(bool enabled) -> void; auto _setMenuEnabled(bool enabled) -> void;
auto _setMenuFont(const Font& font) -> void; auto _setMenuFont(const Font& font) -> void;

View File

@ -2,8 +2,6 @@
namespace hiro { namespace hiro {
XlibDisplay* pApplication::display = nullptr;
auto pApplication::run() -> void { auto pApplication::run() -> void {
if(Application::state().onMain) { if(Application::state().onMain) {
while(!Application::state().quit) { while(!Application::state().quit) {
@ -26,17 +24,37 @@ auto pApplication::processEvents() -> void {
auto pApplication::quit() -> void { auto pApplication::quit() -> void {
QApplication::quit(); QApplication::quit();
qtApplication = nullptr; //note: deleting QApplication will crash libQtGui qtApplication = nullptr; //note: deleting QApplication will crash libQtGui
if(state().display) {
if(state().screenSaverXDG && state().screenSaverWindow) {
//this needs to run synchronously, so that XUnmapWindow() won't happen before xdg-screensaver is finished
execute("xdg-screensaver", "resume", string{"0x", hex(state().screenSaverWindow)});
XUnmapWindow(state().display, state().screenSaverWindow);
state().screenSaverWindow = 0;
}
XCloseDisplay(state().display);
state().display = nullptr;
}
} }
auto pApplication::setScreenSaver(bool screenSaver) -> void { auto pApplication::setScreenSaver(bool screenSaver) -> void {
//TODO: not implemented #if defined(DISPLAY_XORG)
if(state().screenSaverXDG && state().screenSaverWindow) {
invoke("xdg-screensaver", screenSaver ? "resume" : "suspend", string{"0x", hex(state().screenSaverWindow)});
}
#endif
}
auto pApplication::state() -> State& {
static State state;
return state;
} }
//this is sadly necessary for things like determining window frame geometry //this is sadly necessary for things like determining window frame geometry
//obviously, it is used as sparingly as possible //obviously, it is used as sparingly as possible
auto pApplication::syncX() -> void { auto pApplication::synchronize() -> void {
for(auto n : range(8)) { for(auto n : range(8)) {
#if HIRO_QT==4 #if HIRO_QT==4 && defined(DISPLAY_XORG)
QApplication::syncX(); QApplication::syncX();
#elif HIRO_QT==5 #elif HIRO_QT==5
QApplication::sync(); QApplication::sync();
@ -51,7 +69,28 @@ auto pApplication::initialize() -> void {
setenv("QTCOMPOSE", "/usr/local/lib/X11/locale/", 0); setenv("QTCOMPOSE", "/usr/local/lib/X11/locale/", 0);
#endif #endif
display = XOpenDisplay(0); #if defined(DISPLAY_XORG)
state().display = XOpenDisplay(nullptr);
state().screenSaverXDG = (bool)execute("xdg-screensaver", "--version").output.find("xdg-screensaver");
if(state().screenSaverXDG) {
auto screen = DefaultScreen(state().display);
auto rootWindow = RootWindow(state().display, screen);
XSetWindowAttributes attributes{};
attributes.background_pixel = BlackPixel(state().display, screen);
attributes.border_pixel = 0;
attributes.override_redirect = true;
state().screenSaverWindow = XCreateWindow(state().display, rootWindow,
0, 0, 1, 1, 0, DefaultDepth(state().display, screen),
InputOutput, DefaultVisual(state().display, screen),
CWBackPixel | CWBorderPixel | CWOverrideRedirect, &attributes
);
//note: hopefully xdg-screensaver does not require the window to be mapped ...
//if it does, we're in trouble: a small 1x1 black pixel window will be visible in said case
XMapWindow(state().display, state().screenSaverWindow);
XFlush(state().display);
}
#endif
static auto name = Application::state().name ? Application::state().name : string{"hiro"}; static auto name = Application::state().name ? Application::state().name : string{"hiro"};

View File

@ -10,9 +10,17 @@ struct pApplication {
static auto setScreenSaver(bool screenSaver) -> void; static auto setScreenSaver(bool screenSaver) -> void;
static auto initialize() -> void; static auto initialize() -> void;
static auto syncX() -> void; static auto synchronize() -> void;
static XlibDisplay* display; struct State {
#if defined(DISPLAY_XORG)
XlibDisplay* display = nullptr;
XlibWindow screenSaverWindow = 0;
bool screenSaverXDG = false;
#endif
};
static auto state() -> State&;
}; };
static QApplication* qtApplication = nullptr; static QApplication* qtApplication = nullptr;

View File

@ -3,18 +3,29 @@
namespace hiro { namespace hiro {
auto pKeyboard::poll() -> vector<bool> { auto pKeyboard::poll() -> vector<bool> {
if(Application::state().quit) return {};
vector<bool> result; vector<bool> result;
char state[256]; char state[256];
XQueryKeymap(pApplication::display, state);
#if defined(DISPLAY_XORG)
XQueryKeymap(pApplication::state().display, state);
#endif
for(auto& code : settings.keycodes) { for(auto& code : settings.keycodes) {
result.append(_pressed(state, code)); result.append(_pressed(state, code));
} }
return result; return result;
} }
auto pKeyboard::pressed(unsigned code) -> bool { auto pKeyboard::pressed(unsigned code) -> bool {
char state[256]; char state[256];
XQueryKeymap(pApplication::display, state);
#if defined(DISPLAY_XORG)
XQueryKeymap(pApplication::state().display, state);
#endif
return _pressed(state, code); return _pressed(state, code);
} }
@ -22,22 +33,31 @@ auto pKeyboard::_pressed(const char* state, uint16_t code) -> bool {
uint8_t lo = code >> 0; uint8_t lo = code >> 0;
uint8_t hi = code >> 8; uint8_t hi = code >> 8;
#if defined(DISPLAY_XORG)
if(lo && state[lo >> 3] & (1 << (lo & 7))) return true; if(lo && state[lo >> 3] & (1 << (lo & 7))) return true;
if(hi && state[hi >> 3] & (1 << (hi & 7))) return true; if(hi && state[hi >> 3] & (1 << (hi & 7))) return true;
#endif
return false; return false;
} }
auto pKeyboard::initialize() -> void { auto pKeyboard::initialize() -> void {
auto append = [](unsigned lo, unsigned hi = 0) { auto append = [](unsigned lo, unsigned hi = 0) {
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0; #if defined(DISPLAY_XORG)
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0; lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::state().display, lo) : 0;
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::state().display, hi) : 0;
#endif
settings.keycodes.append(lo << 0 | hi << 8); settings.keycodes.append(lo << 0 | hi << 8);
}; };
#define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; } #define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; }
for(auto& key : Keyboard::keys) { for(auto& key : Keyboard::keys) {
#if defined(DISPLAY_XORG)
#include <hiro/platform/xorg/keyboard.hpp> #include <hiro/platform/xorg/keyboard.hpp>
#endif
//print("[hiro/qt] warning: unhandled key: ", key, "\n");
append(0);
} }
#undef map #undef map
} }

View File

@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
} }
auto pSizable::setGeometry(Geometry geometry) -> void { auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
} }
} }

View File

@ -25,7 +25,9 @@ auto pWidget::construct() -> void {
qtWidget->setParent(container); qtWidget->setParent(container);
} }
setEnabled(self().enabled(true));
setFont(self().font(true)); setFont(self().font(true));
setToolTip(self().toolTip());
setVisible(self().visible(true)); setVisible(self().visible(true));
} }
@ -55,7 +57,12 @@ auto pWidget::setFont(const Font& font) -> void {
auto pWidget::setGeometry(Geometry geometry) -> void { auto pWidget::setGeometry(Geometry geometry) -> void {
if(!qtWidget) return; if(!qtWidget) return;
qtWidget->setGeometry(geometry.x(), geometry.y(), geometry.width(), geometry.height()); qtWidget->setGeometry(geometry.x(), geometry.y(), geometry.width(), geometry.height());
self().doSize(); pSizable::setGeometry(geometry);
}
auto pWidget::setToolTip(const string& toolTip) -> void {
if(!qtWidget) return;
qtWidget->setToolTip(QString::fromUtf8(toolTip));
} }
auto pWidget::setVisible(bool visible) -> void { auto pWidget::setVisible(bool visible) -> void {

View File

@ -10,6 +10,7 @@ struct pWidget : pSizable {
auto setFocused() -> void override; auto setFocused() -> void override;
auto setFont(const Font& font) -> void override; auto setFont(const Font& font) -> void override;
auto setGeometry(Geometry geometry) -> void override; auto setGeometry(Geometry geometry) -> void override;
auto setToolTip(const string& toolTip) -> void;
auto setVisible(bool visible) -> void override; auto setVisible(bool visible) -> void override;
QWidget* qtWidget = nullptr; QWidget* qtWidget = nullptr;

View File

@ -269,7 +269,7 @@ auto pWindow::_statusTextHeight() const -> uint {
} }
auto pWindow::_updateFrameGeometry() -> void { auto pWindow::_updateFrameGeometry() -> void {
pApplication::syncX(); pApplication::synchronize();
QRect border = qtWindow->frameGeometry(); QRect border = qtWindow->frameGeometry();
QRect client = qtWindow->geometry(); QRect client = qtWindow->geometry();
@ -279,12 +279,12 @@ auto pWindow::_updateFrameGeometry() -> void {
settings.geometry.frameHeight = border.height() - client.height(); settings.geometry.frameHeight = border.height() - client.height();
if(qtMenuBar->isVisible()) { if(qtMenuBar->isVisible()) {
pApplication::syncX(); pApplication::synchronize();
settings.geometry.menuHeight = qtMenuBar->height() - _menuTextHeight(); settings.geometry.menuHeight = qtMenuBar->height() - _menuTextHeight();
} }
if(qtStatusBar->isVisible()) { if(qtStatusBar->isVisible()) {
pApplication::syncX(); pApplication::synchronize();
settings.geometry.statusHeight = qtStatusBar->height() - _statusTextHeight(); settings.geometry.statusHeight = qtStatusBar->height() - _statusTextHeight();
} }
} }

View File

@ -61,7 +61,7 @@ auto pApplication::initialize() -> void {
CoInitialize(0); CoInitialize(0);
InitCommonControls(); InitCommonControls();
WNDCLASS wc; WNDCLASS wc{};
#if defined(Hiro_Window) #if defined(Hiro_Window)
wc.cbClsExtra = 0; wc.cbClsExtra = 0;
@ -91,43 +91,29 @@ auto pApplication::initialize() -> void {
RegisterClass(&wc); RegisterClass(&wc);
#endif #endif
#if defined(Hiro_Canvas) #if defined(Hiro_Widget)
wc.cbClsExtra = 0; wc.cbClsExtra = 0;
wc.cbWndExtra = 0; wc.cbWndExtra = 0;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = GetModuleHandle(0); wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = Canvas_windowProc; wc.lpfnWndProc = ToolTip_windowProc;
wc.lpszClassName = L"hiroCanvas"; wc.lpszClassName = L"hiroToolTip";
wc.lpszMenuName = 0; wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW; wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc); RegisterClass(&wc);
#endif #endif
#if defined(Hiro_Label) #if defined(Hiro_Widget)
wc.cbClsExtra = 0; wc.cbClsExtra = 0;
wc.cbWndExtra = 0; wc.cbWndExtra = 0;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = GetModuleHandle(0); wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = Label_windowProc; wc.lpfnWndProc = Default_windowProc;
wc.lpszClassName = L"hiroLabel"; wc.lpszClassName = L"hiroWidget";
wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);
#endif
#if defined(Hiro_Viewport)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = Viewport_windowProc;
wc.lpszClassName = L"hiroViewport";
wc.lpszMenuName = 0; wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW; wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc); RegisterClass(&wc);
@ -137,6 +123,11 @@ auto pApplication::initialize() -> void {
pWindow::initialize(); pWindow::initialize();
} }
auto pApplication::state() -> State& {
static State state;
return state;
}
static auto Application_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> bool { static auto Application_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> bool {
if(msg != WM_KEYDOWN && msg != WM_SYSKEYDOWN && msg != WM_KEYUP && msg != WM_SYSKEYUP) return false; if(msg != WM_KEYDOWN && msg != WM_SYSKEYDOWN && msg != WM_KEYUP && msg != WM_SYSKEYUP) return false;

View File

@ -10,6 +10,11 @@ struct pApplication {
static auto setScreenSaver(bool screenSaver) -> void; static auto setScreenSaver(bool screenSaver) -> void;
static auto initialize() -> void; static auto initialize() -> void;
struct State {
pToolTip* toolTip = nullptr; //active toolTip
};
static auto state() -> State&;
}; };
} }

View File

@ -80,3 +80,11 @@
#if !defined(TBS_TRANSPARENTBKGND) #if !defined(TBS_TRANSPARENTBKGND)
#define TBS_TRANSPARENTBKGND 0x1000 #define TBS_TRANSPARENTBKGND 0x1000
#endif #endif
#if !defined(TTP_STANDARD)
#define TTP_STANDARD 1
#endif
#if !defined(TTSS_NORMAL)
#define TTSS_NORMAL 1
#endif

View File

@ -27,6 +27,8 @@
#include "sizable.cpp" #include "sizable.cpp"
#include "tool-tip.cpp"
#include "widget/widget.cpp" #include "widget/widget.cpp"
#include "widget/button.cpp" #include "widget/button.cpp"
#include "widget/canvas.cpp" #include "widget/canvas.cpp"

View File

@ -49,6 +49,8 @@ static vector<wObject> windows;
#include "sizable.hpp" #include "sizable.hpp"
#include "tool-tip.hpp"
#include "widget/widget.hpp" #include "widget/widget.hpp"
#include "widget/button.hpp" #include "widget/button.hpp"
#include "widget/canvas.hpp" #include "widget/canvas.hpp"

View File

@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
} }
auto pSizable::setGeometry(Geometry geometry) -> void { auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
} }
} }

161
hiro/windows/tool-tip.cpp Normal file
View File

@ -0,0 +1,161 @@
namespace hiro {
static auto CALLBACK ToolTip_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
if(auto toolTip = (pToolTip*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
if(auto result = toolTip->windowProc(hwnd, msg, wparam, lparam)) {
return result();
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
pToolTip::pToolTip(const string& toolTipText) {
text = toolTipText;
htheme = OpenThemeData(hwnd, L"TOOLTIP");
hwnd = CreateWindowEx(
WS_EX_TOOLWINDOW | WS_EX_TOPMOST | (htheme ? WS_EX_LAYERED : 0), L"hiroToolTip", L"",
WS_POPUP, 0, 0, 0, 0,
0, 0, GetModuleHandle(0), 0
);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
tracking.x = -1;
tracking.y = -1;
timeout.setInterval(5000);
timeout.onActivate([&] { hide(); });
}
pToolTip::~pToolTip() {
hide();
if(htheme) { CloseThemeData(htheme); htheme = nullptr; }
if(hwnd) { DestroyWindow(hwnd); hwnd = nullptr; }
}
auto pToolTip::drawLayered() -> void {
auto hdcOutput = GetDC(nullptr);
uint32_t* below = nullptr;
auto hdcBelow = CreateCompatibleDC(hdcOutput);
auto hbmBelow = CreateBitmap(hdcBelow, size.cx, size.cy, below);
SelectObject(hdcBelow, hbmBelow);
RECT rc{};
rc.left = 0, rc.top = 0, rc.right = size.cx, rc.bottom = size.cy;
DrawThemeBackground(htheme, hdcBelow, TTP_STANDARD, TTSS_NORMAL, &rc, nullptr);
uint32_t* above = nullptr;
auto hdcAbove = CreateCompatibleDC(hdcOutput);
auto hbmAbove = CreateBitmap(hdcAbove, size.cx, size.cy, above);
SelectObject(hdcAbove, hbmAbove);
memory::copy<uint32_t>(above, below, size.cx * size.cy);
auto hfont = pFont::create(Font());
SelectObject(hdcAbove, hfont);
SetBkMode(hdcAbove, TRANSPARENT);
SetTextColor(hdcAbove, RGB(0, 0, 0));
utf16_t drawText(text);
rc.left += 6, rc.top += 6, rc.right -= 6, rc.bottom -= 6;
DrawText(hdcAbove, drawText, -1, &rc, DT_LEFT | DT_TOP);
DeleteObject(hfont);
for(uint n : range(size.cx * size.cy)) {
below[n] = (below[n] & 0xff000000) | (above[n] & 0x00ffffff);
}
BLENDFUNCTION blend{};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
POINT zero{};
zero.x = 0, zero.y = 0;
UpdateLayeredWindow(hwnd, hdcOutput, &position, &size, hdcBelow, &zero, RGB(0, 0, 0), &blend, ULW_ALPHA);
DeleteObject(hbmAbove);
DeleteObject(hbmBelow);
DeleteDC(hdcAbove);
DeleteDC(hdcBelow);
ReleaseDC(nullptr, hdcOutput);
}
auto pToolTip::drawOpaque() -> void {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
RECT rc{};
GetClientRect(hwnd, &rc);
auto brush = CreateSolidBrush(RGB(0, 0, 0));
FillRect(ps.hdc, &rc, brush);
DeleteObject(brush);
rc.left += 1, rc.top += 1, rc.right -= 1, rc.bottom -= 1;
brush = CreateSolidBrush(RGB(255, 255, 225));
FillRect(ps.hdc, &rc, brush);
DeleteObject(brush);
rc.left += 5, rc.top += 5, rc.right -= 5, rc.bottom -= 5;
SetBkMode(ps.hdc, TRANSPARENT);
auto font = pFont::create(Font());
SelectObject(ps.hdc, font);
SetTextColor(ps.hdc, RGB(0, 0, 0));
DrawText(ps.hdc, utf16_t(text), -1, &rc, DT_LEFT | DT_TOP);
DeleteObject(font);
EndPaint(hwnd, &ps);
}
auto pToolTip::show() -> void {
if(auto toolTip = pApplication::state().toolTip) {
if(toolTip != this) toolTip->hide();
}
pApplication::state().toolTip = this;
GetCursorPos(&position);
if(position.x == tracking.x && position.y == tracking.y) return;
tracking.x = position.x, tracking.y = position.y;
position.y += 18;
auto textSize = pFont::size(Font(), text ? text : " ");
size.cx = 12 + textSize.width();
size.cy = 12 + textSize.height();
//try to keep the tool-tip onscreen
auto desktop = pDesktop::size();
if(position.x + size.cx >= desktop.width ()) position.x = desktop.width () - size.cx;
if(position.y + size.cy >= desktop.height()) position.y = desktop.height() - size.cy;
if(position.x < 0) position.x = 0;
if(position.y < 0) position.y = 0;
SetWindowPos(hwnd, HWND_TOP, position.x, position.y, size.cx, size.cy, SWP_NOACTIVATE | SWP_SHOWWINDOW);
if(htheme) drawLayered();
timeout.setEnabled(true);
}
auto pToolTip::hide() -> void {
pApplication::state().toolTip = nullptr;
timeout.setEnabled(false);
ShowWindow(hwnd, SW_HIDE);
GetCursorPos(&tracking);
}
auto pToolTip::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
switch(msg) {
case WM_ERASEBKGND:
case WM_PAINT:
if(htheme) break;
drawOpaque();
return msg == WM_ERASEBKGND;
case WM_MOUSEMOVE:
case WM_MOUSELEAVE: {
POINT point{};
GetCursorPos(&point);
if(point.x != tracking.x || point.y != tracking.y) hide();
} break;
case WM_LBUTTONDOWN: case WM_LBUTTONUP:
case WM_MBUTTONDOWN: case WM_MBUTTONUP:
case WM_RBUTTONDOWN: case WM_RBUTTONUP:
hide();
break;
}
return {};
}
}

22
hiro/windows/tool-tip.hpp Normal file
View File

@ -0,0 +1,22 @@
namespace hiro {
struct pToolTip {
pToolTip(const string& text);
~pToolTip();
auto drawLayered() -> void;
auto drawOpaque() -> void;
auto show() -> void;
auto hide() -> void;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT>;
HWND hwnd = nullptr;
HTHEME htheme = nullptr;
POINT position{};
SIZE size{};
POINT tracking{};
string text;
Timer timeout;
};
}

View File

@ -7,31 +7,37 @@ static const uint Windows7 = 0x0601;
static auto Button_CustomDraw(HWND, PAINTSTRUCT&, bool, bool, bool, unsigned, const Font&, const image&, Orientation, const string&) -> void; static auto Button_CustomDraw(HWND, PAINTSTRUCT&, bool, bool, bool, unsigned, const Font&, const image&, Orientation, const string&) -> void;
static auto OsVersion() -> unsigned { static auto OsVersion() -> uint {
OSVERSIONINFO versionInfo{0}; OSVERSIONINFO versionInfo{0};
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&versionInfo); GetVersionEx(&versionInfo);
return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0); return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0);
} }
static auto CreateBitmap(HDC hdc, uint width, uint height, uint32_t*& data) -> HBITMAP {
BITMAPINFO info{};
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biWidth = width;
info.bmiHeader.biHeight = -(int)height; //bitmaps are stored upside down unless we negate height
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biCompression = BI_RGB;
info.bmiHeader.biSizeImage = width * height * sizeof(uint32_t);
void* bits = nullptr;
auto bitmap = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, &bits, nullptr, 0);
data = (uint32_t*)bits;
return bitmap;
}
static auto CreateBitmap(image icon) -> HBITMAP { static auto CreateBitmap(image icon) -> HBITMAP {
icon.alphaMultiply(); //Windows AlphaBlend() requires premultiplied image data icon.alphaMultiply(); //Windows AlphaBlend() requires premultiplied image data
icon.transform(); icon.transform();
HDC hdc = GetDC(0); uint32_t* data = nullptr;
BITMAPINFO bitmapInfo; auto hdc = GetDC(nullptr);
memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); auto bitmap = CreateBitmap(hdc, icon.width(), icon.height(), data);
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); memory::copy(data, icon.data(), icon.size());
bitmapInfo.bmiHeader.biWidth = icon.width(); ReleaseDC(nullptr, hdc);
bitmapInfo.bmiHeader.biHeight = -(signed)icon.height(); //bitmaps are stored upside down unless we negate height return bitmap;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = icon.size();
void* bits = nullptr;
HBITMAP hbitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, &bits, NULL, 0);
if(bits) memory::copy(bits, icon.data(), icon.size());
ReleaseDC(0, hdc);
return hbitmap;
} }
static auto CreateRGB(const Color& color) -> COLORREF { static auto CreateRGB(const Color& color) -> COLORREF {
@ -60,9 +66,21 @@ static auto DropPaths(WPARAM wparam) -> vector<string> {
return paths; return paths;
} }
static auto WINAPI EnumVisibleChildWindowsProc(HWND hwnd, LPARAM lparam) -> BOOL {
auto children = (vector<HWND>*)lparam;
if(IsWindowVisible(hwnd)) children->append(hwnd);
return true;
}
static auto EnumVisibleChildWindows(HWND hwnd) -> vector<HWND> {
vector<HWND> children;
EnumChildWindows(hwnd, EnumVisibleChildWindowsProc, (LPARAM)&children);
return children;
}
static auto GetWindowZOrder(HWND hwnd) -> unsigned { static auto GetWindowZOrder(HWND hwnd) -> unsigned {
unsigned z = 0; uint z = 0;
for(HWND next = hwnd; next != NULL; next = GetWindow(next, GW_HWNDPREV)) z++; for(HWND next = hwnd; next != nullptr; next = GetWindow(next, GW_HWNDPREV)) z++;
return z; return z;
} }
@ -111,6 +129,10 @@ static auto ScrollEvent(HWND hwnd, WPARAM wparam) -> unsigned {
return info.nPos; return info.nPos;
} }
static auto CALLBACK Default_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
return DefWindowProc(hwnd, msg, wparam, lparam);
}
//separate because PopupMenu HWND does not contain GWLP_USERDATA pointing at Window needed for Shared_windowProc //separate because PopupMenu HWND does not contain GWLP_USERDATA pointing at Window needed for Shared_windowProc
static auto CALLBACK Menu_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { static auto CALLBACK Menu_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
switch(msg) { switch(msg) {
@ -383,6 +405,47 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
break; break;
} }
//catch mouse events over disabled windows
case WM_MOUSEMOVE:
case WM_MOUSELEAVE:
case WM_MOUSEHOVER: {
POINT p{};
GetCursorPos(&p);
ScreenToClient(hwnd, &p);
for(auto window : EnumVisibleChildWindows(hwnd)) {
if(auto widget = (mWidget*)GetWindowLongPtr(window, GWLP_USERDATA)) {
auto geometry = widget->geometry();
if(p.x < geometry.x()) continue;
if(p.y < geometry.y()) continue;
if(p.x >= geometry.x() + geometry.width ()) continue;
if(p.y >= geometry.y() + geometry.height()) continue;
if(msg == WM_MOUSEMOVE) {
TRACKMOUSEEVENT event{sizeof(TRACKMOUSEEVENT)};
event.hwndTrack = hwnd;
event.dwFlags = TME_LEAVE | TME_HOVER;
event.dwHoverTime = 1500;
TrackMouseEvent(&event);
POINT p{};
GetCursorPos(&p);
widget->self()->doMouseMove(p.x, p.y);
if(auto toolTip = pApplication::state().toolTip) {
toolTip->windowProc(hwnd, msg, wparam, lparam);
}
}
if(msg == WM_MOUSELEAVE) {
widget->self()->doMouseLeave();
}
if(msg == WM_MOUSEHOVER) {
widget->self()->doMouseHover();
}
}
}
break;
}
#if defined(Hiro_TableView) #if defined(Hiro_TableView)
case AppMessage::TableView_doPaint: { case AppMessage::TableView_doPaint: {
if(auto tableView = (mTableView*)lparam) { if(auto tableView = (mTableView*)lparam) {
@ -402,7 +465,7 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
#endif #endif
} }
return windowProc(hwnd, msg, wparam, lparam); return CallWindowProc(windowProc, hwnd, msg, wparam, lparam);
} }
} }

View File

@ -2,49 +2,12 @@
namespace hiro { namespace hiro {
static auto Button_paintProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam,
bool bordered, bool checked, bool enabled, const Font& font, const image& icon, Orientation orientation, const string& text
) -> LRESULT {
if(msg == WM_PAINT) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
auto state = Button_GetState(hwnd);
Button_CustomDraw(hwnd, ps, bordered, checked, enabled, state, font, icon, orientation, text);
EndPaint(hwnd, &ps);
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
//BUTTON cannot draw borderless buttons on its own
//BS_OWNERDRAW will send WM_DRAWITEM; but will disable hot-tracking notifications
//to gain hot-tracking plus borderless buttons; BUTTON is superclassed and WM_PAINT is hijacked
//note: letting hiro paint bordered buttons will lose the fade animations on Vista+;
//however, it will allow placing icons immediately next to text (original forces icon left alignment)
static auto CALLBACK Button_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
if(auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
if(auto button = dynamic_cast<mButton*>(object)) {
if(auto self = button->self()) {
if(msg == WM_ERASEBKGND) return DefWindowProc(hwnd, msg, wparam, lparam);
if(msg == WM_PAINT) return Button_paintProc(hwnd, msg, wparam, lparam,
button->state.bordered, false, button->enabled(true), button->font(true),
button->state.icon, button->state.orientation, button->state.text
);
return self->windowProc(hwnd, msg, wparam, lparam);
}
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
auto pButton::construct() -> void { auto pButton::construct() -> void {
hwnd = CreateWindow( hwnd = CreateWindow(
L"BUTTON", L"", WS_CHILD | WS_TABSTOP, L"BUTTON", L"", WS_CHILD | WS_TABSTOP,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)Button_windowProc);
pWidget::_setState();
_setState(); _setState();
} }
@ -99,10 +62,35 @@ auto pButton::setVisible(bool visible) -> void {
_setState(); _setState();
} }
//
auto pButton::onActivate() -> void { auto pButton::onActivate() -> void {
self().doActivate(); self().doActivate();
} }
//BUTTON cannot draw borderless buttons on its own
//BS_OWNERDRAW will send WM_DRAWITEM; but will disable hot-tracking notifications
//to gain hot-tracking plus borderless buttons; BUTTON is superclassed and WM_PAINT is hijacked
//note: letting hiro paint bordered buttons will lose the fade animations on Vista+;
//however, it will allow placing icons immediately next to text (original forces icon left alignment)
auto pButton::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
if(msg == WM_PAINT) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
auto buttonState = Button_GetState(hwnd);
Button_CustomDraw(hwnd, ps,
state().bordered, false, self().enabled(true), buttonState,
self().font(true), state().icon, state().orientation, state().text
);
EndPaint(hwnd, &ps);
return false;
}
return pWidget::windowProc(hwnd, msg, wparam, lparam);
}
//
auto pButton::_setState() -> void { auto pButton::_setState() -> void {
InvalidateRect(hwnd, 0, false); InvalidateRect(hwnd, 0, false);
} }

View File

@ -15,10 +15,9 @@ struct pButton : pWidget {
auto setVisible(bool visible) -> void override; auto setVisible(bool visible) -> void override;
auto onActivate() -> void; auto onActivate() -> void;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT> override;
auto _setState() -> void; auto _setState() -> void;
WindowProc windowProc = nullptr;
}; };
} }

View File

@ -2,64 +2,9 @@
namespace hiro { namespace hiro {
static auto CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if(!object) return DefWindowProc(hwnd, msg, wparam, lparam);
auto canvas = dynamic_cast<mCanvas*>(object);
if(!canvas) return DefWindowProc(hwnd, msg, wparam, lparam);
if(msg == WM_DROPFILES) {
if(auto paths = DropPaths(wparam)) canvas->doDrop(paths);
return false;
}
if(msg == WM_GETDLGCODE) {
return DLGC_STATIC | DLGC_WANTCHARS;
}
if(msg == WM_ERASEBKGND) {
if(auto self = canvas->self()) self->_paint();
return true;
}
if(msg == WM_PAINT) {
if(auto self = canvas->self()) self->_paint();
return false;
}
if(msg == WM_MOUSEMOVE) {
TRACKMOUSEEVENT tracker{sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd};
TrackMouseEvent(&tracker);
canvas->doMouseMove({(int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam)});
}
if(msg == WM_MOUSELEAVE) {
canvas->doMouseLeave();
}
if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) {
switch(msg) {
case WM_LBUTTONDOWN: canvas->doMousePress(Mouse::Button::Left); break;
case WM_MBUTTONDOWN: canvas->doMousePress(Mouse::Button::Middle); break;
case WM_RBUTTONDOWN: canvas->doMousePress(Mouse::Button::Right); break;
}
}
if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) {
switch(msg) {
case WM_LBUTTONUP: canvas->doMouseRelease(Mouse::Button::Left); break;
case WM_MBUTTONUP: canvas->doMouseRelease(Mouse::Button::Middle); break;
case WM_RBUTTONUP: canvas->doMouseRelease(Mouse::Button::Right); break;
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
auto pCanvas::construct() -> void { auto pCanvas::construct() -> void {
hwnd = CreateWindow(L"hiroCanvas", L"", WS_CHILD, 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); hwnd = CreateWindow(L"hiroWidget", L"", WS_CHILD, 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setDroppable(state().droppable); setDroppable(state().droppable);
update(); update();
} }
@ -99,26 +44,95 @@ auto pCanvas::update() -> void {
_redraw(); _redraw();
} }
//
auto pCanvas::doMouseLeave() -> void {
return self().doMouseLeave();
}
auto pCanvas::doMouseMove(int x, int y) -> void {
return self().doMouseMove({x, y});
}
auto pCanvas::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
if(msg == WM_DROPFILES) {
if(auto paths = DropPaths(wparam)) self().doDrop(paths);
return false;
}
if(msg == WM_GETDLGCODE) {
return DLGC_STATIC | DLGC_WANTCHARS;
}
if(msg == WM_ERASEBKGND || msg == WM_PAINT) {
_paint();
return msg == WM_ERASEBKGND;
}
if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) {
switch(msg) {
case WM_LBUTTONDOWN: self().doMousePress(Mouse::Button::Left); break;
case WM_MBUTTONDOWN: self().doMousePress(Mouse::Button::Middle); break;
case WM_RBUTTONDOWN: self().doMousePress(Mouse::Button::Right); break;
}
}
if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) {
switch(msg) {
case WM_LBUTTONUP: self().doMouseRelease(Mouse::Button::Left); break;
case WM_MBUTTONUP: self().doMouseRelease(Mouse::Button::Middle); break;
case WM_RBUTTONUP: self().doMouseRelease(Mouse::Button::Right); break;
}
}
return pWidget::windowProc(hwnd, msg, wparam, lparam);
}
//
auto pCanvas::_paint() -> void { auto pCanvas::_paint() -> void {
PAINTSTRUCT ps; PAINTSTRUCT ps;
BeginPaint(hwnd, &ps); BeginPaint(hwnd, &ps);
int sx = 0, sy = 0, dx = 0, dy = 0;
int width = this->width;
int height = this->height;
auto geometry = self().geometry();
if(width <= geometry.width()) {
sx = 0;
dx = (geometry.width() - width) / 2;
} else {
sx = (width - geometry.width()) / 2;
dx = 0;
width = geometry.width();
}
if(height <= geometry.height()) {
sy = 0;
dy = (geometry.height() - height) / 2;
} else {
sy = (height - geometry.height()) / 2;
dy = 0;
height = geometry.height();
}
HDC hdc = CreateCompatibleDC(ps.hdc); HDC hdc = CreateCompatibleDC(ps.hdc);
BITMAPINFO bmi; BITMAPINFO bmi{};
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside now; negative height flips bitmap bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside now; negative height flips bitmap
bmi.bmiHeader.biSizeImage = pixels.size() * sizeof(uint32_t); bmi.bmiHeader.biSizeImage = width * height * sizeof(uint32_t);
void* bits = nullptr; void* bits = nullptr;
HBITMAP bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); HBITMAP bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
if(bits) { if(bits) {
auto source = (const uint8_t*)pixels.data(); for(uint y : range(height)) {
auto target = (uint8_t*)bits; auto source = (const uint8_t*)pixels.data() + (sy + y) * this->width * sizeof(uint32_t) + sx * sizeof(uint32_t);
for(auto n : range(width * height)) { auto target = (uint8_t*)bits + y * width * sizeof(uint32_t);
for(uint x : range(width)) {
target[0] = (source[0] * source[3]) / 255; target[0] = (source[0] * source[3]) / 255;
target[1] = (source[1] * source[3]) / 255; target[1] = (source[1] * source[3]) / 255;
target[2] = (source[2] * source[3]) / 255; target[2] = (source[2] * source[3]) / 255;
@ -126,6 +140,7 @@ auto pCanvas::_paint() -> void {
source += 4, target += 4; source += 4, target += 4;
} }
} }
}
SelectObject(hdc, bitmap); SelectObject(hdc, bitmap);
RECT rc; RECT rc;
@ -133,7 +148,7 @@ auto pCanvas::_paint() -> void {
DrawThemeParentBackground(hwnd, ps.hdc, &rc); DrawThemeParentBackground(hwnd, ps.hdc, &rc);
BLENDFUNCTION bf{AC_SRC_OVER, 0, (BYTE)255, AC_SRC_ALPHA}; BLENDFUNCTION bf{AC_SRC_OVER, 0, (BYTE)255, AC_SRC_ALPHA};
AlphaBlend(ps.hdc, 0, 0, width, height, hdc, 0, 0, width, height, bf); AlphaBlend(ps.hdc, dx, dy, width, height, hdc, 0, 0, width, height, bf);
DeleteObject(bitmap); DeleteObject(bitmap);
DeleteDC(hdc); DeleteDC(hdc);

View File

@ -13,6 +13,10 @@ struct pCanvas : pWidget {
auto setIcon(const image& icon) -> void; auto setIcon(const image& icon) -> void;
auto update() -> void; auto update() -> void;
auto doMouseLeave() -> void override;
auto doMouseMove(int x, int y) -> void override;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT> override;
auto _paint() -> void; auto _paint() -> void;
auto _rasterize() -> void; auto _rasterize() -> void;
auto _redraw() -> void; auto _redraw() -> void;

View File

@ -2,30 +2,11 @@
namespace hiro { namespace hiro {
static auto CALLBACK CheckButton_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
if(auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
if(auto button = dynamic_cast<mCheckButton*>(object)) {
if(auto self = button->self()) {
if(msg == WM_ERASEBKGND) return DefWindowProc(hwnd, msg, wparam, lparam);
if(msg == WM_PAINT) return Button_paintProc(hwnd, msg, wparam, lparam,
button->state.bordered, button->state.checked, button->enabled(true), button->font(true),
button->state.icon, button->state.orientation, button->state.text
);
return self->windowProc(hwnd, msg, wparam, lparam);
}
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
auto pCheckButton::construct() -> void { auto pCheckButton::construct() -> void {
hwnd = CreateWindow(L"BUTTON", L"", hwnd = CreateWindow(L"BUTTON", L"",
WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE, WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)CheckButton_windowProc);
pWidget::_setState();
_setState(); _setState();
setChecked(state().checked); setChecked(state().checked);
} }
@ -85,12 +66,32 @@ auto pCheckButton::setVisible(bool visible) -> void {
_setState(); _setState();
} }
//
auto pCheckButton::onToggle() -> void { auto pCheckButton::onToggle() -> void {
state().checked = !state().checked; state().checked = !state().checked;
setChecked(state().checked); setChecked(state().checked);
self().doToggle(); self().doToggle();
} }
auto pCheckButton::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
if(msg == WM_PAINT) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
auto buttonState = Button_GetState(hwnd);
Button_CustomDraw(hwnd, ps,
state().bordered, state().checked, self().enabled(true), buttonState,
self().font(true), state().icon, state().orientation, state().text
);
EndPaint(hwnd, &ps);
return false;
}
return pWidget::windowProc(hwnd, msg, wparam, lparam);
}
//
auto pCheckButton::_setState() -> void { auto pCheckButton::_setState() -> void {
InvalidateRect(hwnd, 0, false); InvalidateRect(hwnd, 0, false);
} }

View File

@ -16,10 +16,9 @@ struct pCheckButton : pWidget {
auto setVisible(bool visible) -> void override; auto setVisible(bool visible) -> void override;
auto onToggle() -> void; auto onToggle() -> void;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT> override;
auto _setState() -> void; auto _setState() -> void;
WindowProc windowProc = nullptr;
}; };
} }

View File

@ -8,8 +8,7 @@ auto pCheckLabel::construct() -> void {
WS_CHILD | WS_TABSTOP | BS_CHECKBOX, WS_CHILD | WS_TABSTOP | BS_CHECKBOX,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setChecked(state().checked); setChecked(state().checked);
setText(state().text); setText(state().text);
} }

View File

@ -9,8 +9,7 @@ auto pComboButton::construct() -> void {
0, 0, 0, 0, 0, 0, 0, 0,
_parentHandle(), nullptr, GetModuleHandle(0), 0 _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
for(auto& item : state().items) append(item); for(auto& item : state().items) append(item);
} }

View File

@ -6,8 +6,7 @@ auto pFrame::construct() -> void {
hwnd = CreateWindow(L"BUTTON", L"", hwnd = CreateWindow(L"BUTTON", L"",
WS_CHILD | BS_GROUPBOX, WS_CHILD | BS_GROUPBOX,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setText(state().text); setText(state().text);
} }

View File

@ -2,77 +2,20 @@
namespace hiro { namespace hiro {
static auto CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if(!object) return DefWindowProc(hwnd, msg, wparam, lparam);
auto hexEdit = dynamic_cast<mHexEdit*>(object);
if(!hexEdit) return DefWindowProc(hwnd, msg, wparam, lparam);
auto self = hexEdit->self();
if(!self) return DefWindowProc(hwnd, msg, wparam, lparam);
switch(msg) {
case WM_KEYDOWN:
if(self->keyPress(wparam)) return 0;
break;
case WM_MOUSEWHEEL: {
signed offset = -((int16_t)HIWORD(wparam) / WHEEL_DELTA);
self->scrollTo(self->scrollPosition() + offset);
return true;
}
case WM_SIZE: {
RECT rc;
GetClientRect(self->hwnd, &rc);
SetWindowPos(self->scrollBar, HWND_TOP, rc.right - 18, 0, 18, rc.bottom, SWP_SHOWWINDOW);
break;
}
case WM_VSCROLL: {
SCROLLINFO info{sizeof(SCROLLINFO)};
info.fMask = SIF_ALL;
GetScrollInfo((HWND)lparam, SB_CTL, &info);
switch(LOWORD(wparam)) {
case SB_LEFT: info.nPos = info.nMin; break;
case SB_RIGHT: info.nPos = info.nMax; break;
case SB_LINELEFT: info.nPos--; break;
case SB_LINERIGHT: info.nPos++; break;
case SB_PAGELEFT: info.nPos -= info.nMax >> 3; break;
case SB_PAGERIGHT: info.nPos += info.nMax >> 3; break;
case SB_THUMBTRACK: info.nPos = info.nTrackPos; break;
}
info.fMask = SIF_POS;
SetScrollInfo((HWND)lparam, SB_CTL, &info, TRUE);
GetScrollInfo((HWND)lparam, SB_CTL, &info); //get clamped position
self->scrollTo(info.nPos);
return true;
}
}
return self->windowProc(hwnd, msg, wparam, lparam);
}
auto pHexEdit::construct() -> void { auto pHexEdit::construct() -> void {
hwnd = CreateWindowEx( hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE, L"EDIT", L"", WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY | ES_MULTILINE | ES_WANTRETURN, WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY | ES_MULTILINE | ES_WANTRETURN,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
scrollBar = CreateWindowEx( scrollBar = CreateWindowEx(
0, L"SCROLLBAR", L"", 0, L"SCROLLBAR", L"",
WS_VISIBLE | WS_CHILD | SBS_VERT, WS_VISIBLE | WS_CHILD | SBS_VERT,
0, 0, 0, 0, hwnd, nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, hwnd, nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(scrollBar, GWLP_USERDATA, (LONG_PTR)&reference); SetWindowLongPtr(scrollBar, GWLP_USERDATA, (LONG_PTR)&reference);
pWidget::construct();
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HexEdit_windowProc);
pWidget::_setState();
setAddress(state().address); setAddress(state().address);
setBackgroundColor(state().backgroundColor); setBackgroundColor(state().backgroundColor);
setLength(state().length); setLength(state().length);
@ -149,7 +92,7 @@ auto pHexEdit::update() -> void {
Edit_SetSel(hwnd, LOWORD(cursorPosition), HIWORD(cursorPosition)); Edit_SetSel(hwnd, LOWORD(cursorPosition), HIWORD(cursorPosition));
} }
bool pHexEdit::keyPress(unsigned scancode) { auto pHexEdit::keyPress(unsigned scancode) -> bool {
if(!state().onRead) return false; if(!state().onRead) return false;
signed position = LOWORD(Edit_GetSel(hwnd)); signed position = LOWORD(Edit_GetSel(hwnd));
@ -234,25 +177,67 @@ bool pHexEdit::keyPress(unsigned scancode) {
return true; return true;
} }
signed pHexEdit::rows() { auto pHexEdit::rows() -> int {
return (max(1u, state().length) + state().columns - 1) / state().columns; return (max(1u, state().length) + state().columns - 1) / state().columns;
} }
signed pHexEdit::rowsScrollable() { auto pHexEdit::rowsScrollable() -> int {
return max(0u, rows() - state().rows); return max(0u, rows() - state().rows);
} }
signed pHexEdit::scrollPosition() { auto pHexEdit::scrollPosition() -> int {
return state().address / state().columns; return state().address / state().columns;
} }
void pHexEdit::scrollTo(signed position) { auto pHexEdit::scrollTo(signed position) -> void {
if(position > rowsScrollable()) position = rowsScrollable(); if(position > rowsScrollable()) position = rowsScrollable();
if(position < 0) position = 0; if(position < 0) position = 0;
if(position == scrollPosition()) return; if(position == scrollPosition()) return;
self().setAddress(position * state().columns); self().setAddress(position * state().columns);
} }
auto pHexEdit::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
if(msg == WM_KEYDOWN) {
if(keyPress(wparam)) return 0;
}
if(msg == WM_MOUSEWHEEL) {
int offset = -((int16_t)HIWORD(wparam) / WHEEL_DELTA);
scrollTo(scrollPosition() + offset);
return true;
}
if(msg == WM_SIZE) {
RECT rc;
GetClientRect(hwnd, &rc);
SetWindowPos(scrollBar, HWND_TOP, rc.right - 18, 0, 18, rc.bottom, SWP_SHOWWINDOW);
}
if(msg == WM_VSCROLL) {
SCROLLINFO info{sizeof(SCROLLINFO)};
info.fMask = SIF_ALL;
GetScrollInfo((HWND)lparam, SB_CTL, &info);
switch(LOWORD(wparam)) {
case SB_LEFT: info.nPos = info.nMin; break;
case SB_RIGHT: info.nPos = info.nMax; break;
case SB_LINELEFT: info.nPos--; break;
case SB_LINERIGHT: info.nPos++; break;
case SB_PAGELEFT: info.nPos -= info.nMax >> 3; break;
case SB_PAGERIGHT: info.nPos += info.nMax >> 3; break;
case SB_THUMBTRACK: info.nPos = info.nTrackPos; break;
}
info.fMask = SIF_POS;
SetScrollInfo((HWND)lparam, SB_CTL, &info, TRUE);
GetScrollInfo((HWND)lparam, SB_CTL, &info); //get clamped position
scrollTo(info.nPos);
return true;
}
return pWidget::windowProc(hwnd, msg, wparam, lparam);
}
} }
#endif #endif

View File

@ -18,8 +18,8 @@ struct pHexEdit : pWidget {
auto rowsScrollable() -> signed; auto rowsScrollable() -> signed;
auto scrollPosition() -> signed; auto scrollPosition() -> signed;
auto scrollTo(signed position) -> void; auto scrollTo(signed position) -> void;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT> override;
WindowProc windowProc = nullptr;
HWND scrollBar = nullptr; HWND scrollBar = nullptr;
HBRUSH backgroundBrush = nullptr; HBRUSH backgroundBrush = nullptr;
}; };

View File

@ -7,8 +7,7 @@ auto pHorizontalScrollBar::construct() -> void {
L"SCROLLBAR", L"", WS_CHILD | WS_TABSTOP | SBS_HORZ, L"SCROLLBAR", L"", WS_CHILD | WS_TABSTOP | SBS_HORZ,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setLength(state().length); setLength(state().length);
setPosition(state().position); setPosition(state().position);
} }

View File

@ -7,8 +7,7 @@ auto pHorizontalSlider::construct() -> void {
TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_TRANSPARENTBKGND | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_TRANSPARENTBKGND | TBS_NOTICKS | TBS_BOTH | TBS_HORZ,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setLength(state().length); setLength(state().length);
setPosition(state().position); setPosition(state().position);
} }

View File

@ -3,11 +3,10 @@
namespace hiro { namespace hiro {
auto pLabel::construct() -> void { auto pLabel::construct() -> void {
hwnd = CreateWindow(L"hiroLabel", L"", hwnd = CreateWindow(L"hiroWidget", L"",
WS_CHILD | WS_CLIPSIBLINGS, WS_CHILD | WS_CLIPSIBLINGS,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setText(state().text); setText(state().text);
} }
@ -33,20 +32,15 @@ auto pLabel::setForegroundColor(Color color) -> void {
} }
auto pLabel::setText(const string& text) -> void { auto pLabel::setText(const string& text) -> void {
SetWindowText(hwnd, utf16_t(text));
InvalidateRect(hwnd, 0, false); InvalidateRect(hwnd, 0, false);
} }
static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { auto pLabel::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
auto label = (mLabel*)GetWindowLongPtr(hwnd, GWLP_USERDATA); if(msg == WM_GETDLGCODE) {
if(!label) return DefWindowProc(hwnd, msg, wparam, lparam); return DLGC_STATIC | DLGC_WANTCHARS;
auto window = label->parentWindow(true); }
if(!window) return DefWindowProc(hwnd, msg, wparam, lparam);
switch(msg) { if(msg == WM_ERASEBKGND || msg == WM_PAINT) {
case WM_GETDLGCODE: return DLGC_STATIC | DLGC_WANTCHARS;
case WM_ERASEBKGND:
case WM_PAINT: {
PAINTSTRUCT ps; PAINTSTRUCT ps;
BeginPaint(hwnd, &ps); BeginPaint(hwnd, &ps);
RECT rc; RECT rc;
@ -56,24 +50,25 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
auto hbmMemory = CreateCompatibleBitmap(ps.hdc, rc.right - rc.left, rc.bottom - rc.top); auto hbmMemory = CreateCompatibleBitmap(ps.hdc, rc.right - rc.left, rc.bottom - rc.top);
SelectObject(hdcMemory, hbmMemory); SelectObject(hdcMemory, hbmMemory);
uint length = GetWindowTextLength(hwnd); if(auto color = state().backgroundColor) {
wchar_t text[length + 1];
GetWindowText(hwnd, text, length + 1);
text[length] = 0;
//todo: use DrawThemeParentBackground if Label is inside TabFrame
if(auto color = label->backgroundColor()) {
auto brush = CreateSolidBrush(CreateRGB(color)); auto brush = CreateSolidBrush(CreateRGB(color));
FillRect(hdcMemory, &rc, brush); FillRect(hdcMemory, &rc, brush);
DeleteObject(brush); DeleteObject(brush);
} else if(auto brush = window->self()->hbrush) { } else if(self().parentTabFrame(true)) {
DrawThemeParentBackground(hwnd, hdcMemory, &rc);
} else if(auto window = self().parentWindow(true)) {
if(auto color = window->backgroundColor()) {
auto brush = CreateSolidBrush(CreateRGB(color));
FillRect(hdcMemory, &rc, brush); FillRect(hdcMemory, &rc, brush);
DeleteObject(brush);
} else { } else {
DrawThemeParentBackground(hwnd, hdcMemory, &rc); DrawThemeParentBackground(hwnd, hdcMemory, &rc);
} }
}
utf16_t text(state().text);
SetBkMode(hdcMemory, TRANSPARENT); SetBkMode(hdcMemory, TRANSPARENT);
SelectObject(hdcMemory, label->self()->hfont); SelectObject(hdcMemory, hfont);
DrawText(hdcMemory, text, -1, &rc, DT_CALCRECT | DT_END_ELLIPSIS); DrawText(hdcMemory, text, -1, &rc, DT_CALCRECT | DT_END_ELLIPSIS);
uint height = rc.bottom; uint height = rc.bottom;
@ -81,12 +76,12 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
rc.top = (rc.bottom - height) / 2; rc.top = (rc.bottom - height) / 2;
rc.bottom = rc.top + height; rc.bottom = rc.top + height;
uint horizontalAlignment = DT_CENTER; uint horizontalAlignment = DT_CENTER;
if(label->alignment().horizontal() < 0.333) horizontalAlignment = DT_LEFT; if(state().alignment.horizontal() < 0.333) horizontalAlignment = DT_LEFT;
if(label->alignment().horizontal() > 0.666) horizontalAlignment = DT_RIGHT; if(state().alignment.horizontal() > 0.666) horizontalAlignment = DT_RIGHT;
uint verticalAlignment = DT_VCENTER; uint verticalAlignment = DT_VCENTER;
if(label->alignment().vertical() < 0.333) verticalAlignment = DT_TOP; if(state().alignment.vertical() < 0.333) verticalAlignment = DT_TOP;
if(label->alignment().vertical() > 0.666) verticalAlignment = DT_BOTTOM; if(state().alignment.vertical() > 0.666) verticalAlignment = DT_BOTTOM;
if(auto color = label->foregroundColor()) { if(auto color = state().foregroundColor) {
SetTextColor(hdcMemory, CreateRGB(color)); SetTextColor(hdcMemory, CreateRGB(color));
} }
DrawText(hdcMemory, text, -1, &rc, DT_END_ELLIPSIS | horizontalAlignment | verticalAlignment); DrawText(hdcMemory, text, -1, &rc, DT_END_ELLIPSIS | horizontalAlignment | verticalAlignment);
@ -99,9 +94,8 @@ static auto CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
return msg == WM_ERASEBKGND; return msg == WM_ERASEBKGND;
} }
}
return DefWindowProc(hwnd, msg, wparam, lparam); return pWidget::windowProc(hwnd, msg, wparam, lparam);
} }
} }

View File

@ -10,6 +10,8 @@ struct pLabel : pWidget {
auto setBackgroundColor(Color color) -> void; auto setBackgroundColor(Color color) -> void;
auto setForegroundColor(Color color) -> void; auto setForegroundColor(Color color) -> void;
auto setText(const string& text) -> void; auto setText(const string& text) -> void;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT> override;
}; };
} }

View File

@ -8,8 +8,7 @@ auto pLineEdit::construct() -> void {
WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL, WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
setBackgroundColor(state().backgroundColor); setBackgroundColor(state().backgroundColor);
setEditable(state().editable); setEditable(state().editable);
setText(state().text); setText(state().text);

View File

@ -6,8 +6,7 @@ auto pProgressBar::construct() -> void {
hwnd = CreateWindow(PROGRESS_CLASS, L"", hwnd = CreateWindow(PROGRESS_CLASS, L"",
WS_CHILD | PBS_SMOOTH, WS_CHILD | PBS_SMOOTH,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
pWidget::_setState();
SendMessage(hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); SendMessage(hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
SendMessage(hwnd, PBM_SETSTEP, MAKEWPARAM(1, 0), 0); SendMessage(hwnd, PBM_SETSTEP, MAKEWPARAM(1, 0), 0);
setPosition(state().position); setPosition(state().position);

View File

@ -2,30 +2,11 @@
namespace hiro { namespace hiro {
static auto CALLBACK RadioButton_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
if(auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
if(auto button = dynamic_cast<mRadioButton*>(object)) {
if(auto self = button->self()) {
if(msg == WM_ERASEBKGND) return DefWindowProc(hwnd, msg, wparam, lparam);
if(msg == WM_PAINT) return Button_paintProc(hwnd, msg, wparam, lparam,
button->state.bordered, button->state.checked, button->enabled(true), button->font(true),
button->state.icon, button->state.orientation, button->state.text
);
return self->windowProc(hwnd, msg, wparam, lparam);
}
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
auto pRadioButton::construct() -> void { auto pRadioButton::construct() -> void {
hwnd = CreateWindow(L"BUTTON", L"", hwnd = CreateWindow(L"BUTTON", L"",
WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE, WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0); 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)RadioButton_windowProc);
pWidget::_setState();
setGroup(state().group); setGroup(state().group);
_setState(); _setState();
} }
@ -111,12 +92,32 @@ auto pRadioButton::setVisible(bool visible) -> void {
_setState(); _setState();
} }
//
auto pRadioButton::onActivate() -> void { auto pRadioButton::onActivate() -> void {
if(state().checked) return; if(state().checked) return;
self().setChecked(); self().setChecked();
self().doActivate(); self().doActivate();
} }
auto pRadioButton::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> maybe<LRESULT> {
if(msg == WM_PAINT) {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
auto buttonState = Button_GetState(hwnd);
Button_CustomDraw(hwnd, ps,
state().bordered, state().checked, self().enabled(true), buttonState,
self().font(true), state().icon, state().orientation, state().text
);
EndPaint(hwnd, &ps);
return false;
}
return pWidget::windowProc(hwnd, msg, wparam, lparam);
}
//
auto pRadioButton::_setState() -> void { auto pRadioButton::_setState() -> void {
InvalidateRect(hwnd, 0, false); InvalidateRect(hwnd, 0, false);
} }

View File

@ -17,10 +17,9 @@ struct pRadioButton : pWidget {
auto setVisible(bool visible) -> void override; auto setVisible(bool visible) -> void override;
auto onActivate() -> void; auto onActivate() -> void;
auto windowProc(HWND, UINT, WPARAM, LPARAM) -> maybe<LRESULT> override;
auto _setState() -> void; auto _setState() -> void;
WindowProc windowProc = nullptr;
}; };
} }

View File

@ -8,7 +8,7 @@ auto pRadioLabel::construct() -> void {
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
pWidget::_setState(); pWidget::construct();
setGroup(state().group); setGroup(state().group);
setText(state().text); setText(state().text);
} }

View File

@ -3,14 +3,11 @@
namespace hiro { namespace hiro {
static auto CALLBACK TabFrame_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { static auto CALLBACK TabFrame_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
if(auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) { if(auto tabFrame = (mTabFrame*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
if(auto tabFrame = dynamic_cast<mTabFrame*>(object)) {
if(auto self = tabFrame->self()) { if(auto self = tabFrame->self()) {
return Shared_windowProc(self->windowProc, hwnd, msg, wparam, lparam); return Shared_windowProc(self->defaultWindowProc, hwnd, msg, wparam, lparam);
} }
} }
}
return DefWindowProc(hwnd, msg, wparam, lparam); return DefWindowProc(hwnd, msg, wparam, lparam);
} }
@ -19,10 +16,8 @@ auto pTabFrame::construct() -> void {
WC_TABCONTROL, L"", WS_CHILD | WS_TABSTOP, WC_TABCONTROL, L"", WS_CHILD | WS_TABSTOP,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
); );
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference); pWidget::construct();
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)TabFrame_windowProc); SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)TabFrame_windowProc);
pWidget::_setState();
for(auto& item : state().items) append(item); for(auto& item : state().items) append(item);
} }
@ -88,6 +83,8 @@ auto pTabFrame::setVisible(bool visible) -> void {
} }
} }
//
auto pTabFrame::_buildImageList() -> void { auto pTabFrame::_buildImageList() -> void {
unsigned size = pFont::size(hfont, " ").height(); unsigned size = pFont::size(hfont, " ").height();

Some files were not shown because too many files have changed in this diff Show More