mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
3b4e8b6d75
commit
93a6a1ce7e
|
@ -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/";
|
||||||
|
|
|
@ -70,18 +70,17 @@ 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();
|
||||||
read = {&Cartridge::readLockOn, this};
|
|
||||||
write = {&Cartridge::writeLockOn, this};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(rom.data[0x120>>1]==0x4761 || rom.data[0x120>>1]==0x6147) {
|
if(patch) {
|
||||||
slot = new Cartridge{depth + 1};
|
read = {&Cartridge::readLockOn, this};
|
||||||
if(!slot->load()) slot.reset();
|
write = {&Cartridge::writeLockOn, this};
|
||||||
read = {&Cartridge::readGameGenie, this};
|
} else {
|
||||||
write = {&Cartridge::writeGameGenie, this};
|
read = {&Cartridge::readGameGenie, 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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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([] {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ struct Presentation : Window {
|
||||||
Group outputGroup{¢erViewport, &scaleViewport, &stretchViewport};
|
Group outputGroup{¢erViewport, &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};
|
||||||
|
|
|
@ -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;
|
||||||
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
|
if(!settings.video.blocking && settings.audio.blocking) {
|
||||||
if(item.checked()) frequency *= item.property("multiplier").real();
|
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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); });
|
||||||
|
|
|
@ -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});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/"));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(display.type == Emulator::Interface::Display::Type::CRT) {
|
if(!settings["View/Overscan"].boolean()) {
|
||||||
uint overscanHorizontal = settings["Video/Overscan/Horizontal"].natural();
|
if(display.type == Emulator::Interface::Display::Type::CRT) {
|
||||||
uint overscanVertical = settings["Video/Overscan/Vertical"].natural();
|
uint overscanHorizontal = settings["View/Overscan/Horizontal"].natural();
|
||||||
emulatorWidth -= overscanHorizontal * 2;
|
uint overscanVertical = settings["View/Overscan/Vertical"].natural();
|
||||||
emulatorHeight -= overscanVertical * 2;
|
width -= overscanHorizontal * 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;
|
||||||
} else {
|
if(settings["View/Output"].text() == "Center") {
|
||||||
viewport.setGeometry({0, 0, geometry().width(), geometry().height()});
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
//clear video area again to ensure entire viewport area has been painted in
|
//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
|
||||||
|
});
|
||||||
|
|
||||||
clearViewport();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMinimumSize({width, height + statusHeight});
|
||||||
|
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 {
|
||||||
|
|
|
@ -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{¢erViewport, &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;
|
||||||
|
|
|
@ -41,7 +41,11 @@ auto Program::load(Emulator::Interface& interface) -> void {
|
||||||
updateAudioEffects();
|
updateAudioEffects();
|
||||||
|
|
||||||
presentation->viewportLayout.remove(presentation->iconLayout);
|
presentation->viewportLayout.remove(presentation->iconLayout);
|
||||||
presentation->resizeViewport();
|
if(settings["View/Adaptive"].boolean()) {
|
||||||
|
presentation->resizeWindow();
|
||||||
|
} else {
|
||||||
|
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});
|
||||||
presentation->resizeViewport();
|
if(settings["View/Adaptive"].boolean()) {
|
||||||
|
presentation->resizeWindow();
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
|
|
@ -56,15 +56,17 @@ auto Program::videoRefresh(uint displayID, const uint32* data, uint pitch, uint
|
||||||
|
|
||||||
pitch >>= 2;
|
pitch >>= 2;
|
||||||
|
|
||||||
auto display = emulator->displays()[displayID];
|
if(!settings["View/Overscan"].boolean()) {
|
||||||
if(display.type == Emulator::Interface::Display::Type::CRT) {
|
auto display = emulator->displays()[displayID];
|
||||||
uint overscanHorizontal = settings["Video/Overscan/Horizontal"].natural();
|
if(display.type == Emulator::Interface::Display::Type::CRT) {
|
||||||
uint overscanVertical = settings["Video/Overscan/Vertical"].natural();
|
uint overscanHorizontal = settings["View/Overscan/Horizontal"].natural();
|
||||||
overscanHorizontal *= display.internalWidth / display.width;
|
uint overscanVertical = settings["View/Overscan/Vertical"].natural();
|
||||||
overscanVertical *= display.internalHeight / display.height;
|
overscanHorizontal *= display.internalWidth / display.width;
|
||||||
data += overscanVertical * pitch + overscanHorizontal;
|
overscanVertical *= display.internalHeight / display.height;
|
||||||
width -= overscanHorizontal * 2;
|
data += overscanVertical * pitch + overscanHorizontal;
|
||||||
height -= overscanVertical * 2;
|
width -= overscanHorizontal * 2;
|
||||||
|
height -= overscanVertical * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(video->acquire(output, length, width, height)) {
|
if(video->acquire(output, length, width, height)) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||||
|
self().doSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
#endif
|
bool screenSaverXDG = false;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto state() -> State&;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||||
|
self().doSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
#include <hiro/platform/xorg/keyboard.hpp>
|
#if defined(DISPLAY_XORG)
|
||||||
|
#include <hiro/platform/xorg/keyboard.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//print("[hiro/qt] warning: unhandled key: ", key, "\n");
|
||||||
|
append(0);
|
||||||
}
|
}
|
||||||
#undef map
|
#undef map
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||||
|
self().doSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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&;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||||
|
self().doSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,31 +44,101 @@ 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);
|
||||||
target[0] = (source[0] * source[3]) / 255;
|
for(uint x : range(width)) {
|
||||||
target[1] = (source[1] * source[3]) / 255;
|
target[0] = (source[0] * source[3]) / 255;
|
||||||
target[2] = (source[2] * source[3]) / 255;
|
target[1] = (source[1] * source[3]) / 255;
|
||||||
target[3] = (source[3]);
|
target[2] = (source[2] * source[3]) / 255;
|
||||||
source += 4, target += 4;
|
target[3] = (source[3]);
|
||||||
|
source += 4, target += 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SelectObject(hdc, bitmap);
|
SelectObject(hdc, bitmap);
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
FillRect(hdcMemory, &rc, brush);
|
|
||||||
} else {
|
|
||||||
DrawThemeParentBackground(hwnd, hdcMemory, &rc);
|
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);
|
||||||
|
DeleteObject(brush);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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->defaultWindowProc, hwnd, msg, wparam, lparam);
|
||||||
return Shared_windowProc(self->windowProc, 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
Loading…
Reference in New Issue