Update to v106r57 release.

byuu says:

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

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

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

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

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

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

View File

@ -13,7 +13,7 @@ using namespace nall;
namespace Emulator {
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 License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

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

View File

@ -1,92 +1,58 @@
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 {
return {
"system\n"
" cpu version=", system.cpu.version, "\n"
" 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"
};
Markup::Node document;
process(document, false);
return BML::serialize(document, " ");
}
auto Configuration::read(string name) -> string {
#define bind(id) { \
string key = {string{#id}.transform(".", "/")}; \
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 document = BML::unserialize(read());
return document[name].text();
}
auto Configuration::write(string configuration) -> bool {
*this = {};
auto document = BML::unserialize(configuration);
if(!document) return false;
#define bind(type, id) { \
string key = {string{#id}.transform(".", "/")}; \
if(auto node = document[key]) id = node.type(); \
if(auto document = BML::unserialize(configuration)) {
return process(document, true), true;
}
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);
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;
return false;
}
auto Configuration::write(string name, string value) -> bool {
#define bind(type, id) { \
string key = {string{#id}.transform(".", "/")}; \
if(name == key) return id = Markup::Node().setValue(value).type(), true; \
if(SuperFamicom::system.loaded() && name.beginsWith("System/")) return false;
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -109,12 +109,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
superFamicom.location = gameQueue.takeLeft();
} else {
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"}});
superFamicom.location = dialog.openObject();
}
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)) {
return {id, dialog.option()};
}
@ -126,12 +126,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
gameBoy.location = gameQueue.takeLeft();
} else {
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"}});
gameBoy.location = dialog.openObject();
}
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)) {
return {id, dialog.option()};
}
@ -143,12 +143,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
bsMemory.location = gameQueue.takeLeft();
} else {
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"}});
bsMemory.location = dialog.openObject();
}
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)) {
return {id, dialog.option()};
}
@ -160,12 +160,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
sufamiTurboA.location = gameQueue.takeLeft();
} else {
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"}});
sufamiTurboA.location = dialog.openObject();
}
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)) {
return {id, dialog.option()};
}
@ -177,12 +177,12 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
sufamiTurboB.location = gameQueue.takeLeft();
} else {
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"}});
sufamiTurboB.location = dialog.openObject();
}
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)) {
return {id, dialog.option()};
}
@ -205,7 +205,7 @@ auto Program::videoRefresh(uint display, const uint32* data, uint pitch, uint wi
screenshot.height = height;
pitch >>= 2;
if(presentation.overscanCropping.checked()) {
if(!presentation.showOverscanArea.checked()) {
if(height == 240) data += 8 * pitch, height -= 16;
if(height == 480) data += 16 * pitch, height -= 32;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,27 +7,43 @@ auto AudioSettings::create() -> void {
effectsLabel.setFont(Font().setBold()).setText("Effects");
effectsLayout.setSize({3, 3});
effectsLayout.column(0).setAlignment(1.0);
skewLabel.setText("Skew:");
skewValue.setAlignment(0.5);
skewSlider.setLength(10001).setPosition(settings["Audio/Skew"].integer() + 5000).onChange([&] {
skewLabel.setText("Skew:").setToolTip(
"Adjusts the audio frequency by the skew amount (in hz.)\n\n"
"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};
settings["Audio/Skew"].setValue(value);
settings.audio.skew = value.integer();
skewValue.setText(value);
program.updateAudioFrequency();
}).doChange();
volumeLabel.setText("Volume:");
volumeValue.setAlignment(0.5);
volumeSlider.setLength(201).setPosition(settings["Audio/Volume"].natural()).onChange([&] {
volumeLabel.setText("Volume:").setToolTip(
"Adjusts the audio output volume.\n\n"
"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(), "%"};
settings["Audio/Volume"].setValue(value);
settings.audio.volume = value.natural();
volumeValue.setText(value);
program.updateAudioEffects();
}).doChange();
balanceLabel.setText("Balance:");
balanceValue.setAlignment(0.5);
balanceSlider.setLength(101).setPosition(settings["Audio/Balance"].natural()).onChange([&] {
balanceLabel.setText("Balance:").setToolTip(
"Pans audio to the left (lower values) or right (higher values.)\n\n"
"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(), "%"};
settings["Audio/Balance"].setValue(value);
settings.audio.balance = value.natural();
balanceValue.setText(value);
program.updateAudioEffects();
}).doChange();

View File

@ -13,17 +13,33 @@ auto DriverSettings::create() -> void {
videoDriverUpdate.setText("Change").onActivate([&] { videoDriverChange(); });
videoFormatLabel.setText("Format:");
videoFormatOption.onChange([&] { videoFormatChange(); });
videoExclusiveToggle.setText("Exclusive fullscreen").onToggle([&] {
settings["Video/Exclusive"].setValue(videoExclusiveToggle.checked());
videoExclusiveToggle.setText("Exclusive").setToolTip(
"(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();
});
videoBlockingToggle.setText("Synchronize").onToggle([&] {
settings["Video/Blocking"].setValue(videoBlockingToggle.checked());
videoBlockingToggle.setText("Synchronize").setToolTip(
"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();
presentation.speedMenu.setEnabled(!videoBlockingToggle.checked() && audioBlockingToggle.checked());
});
videoFlushToggle.setText("GPU sync").onToggle([&] {
settings["Video/Flush"].setValue(videoFlushToggle.checked());
videoFlushToggle.setText("GPU sync").setToolTip({
"(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();
});
videoSpacer.setColor({192, 192, 192});
@ -41,17 +57,34 @@ auto DriverSettings::create() -> void {
audioFrequencyOption.onChange([&] { audioFrequencyChange(); });
audioLatencyLabel.setText("Latency:");
audioLatencyOption.onChange([&] { audioLatencyChange(); });
audioExclusiveToggle.setText("Exclusive").onToggle([&] {
settings["Audio/Exclusive"].setValue(audioExclusiveToggle.checked());
audioExclusiveToggle.setText("Exclusive").setToolTip(
"(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();
});
audioBlockingToggle.setText("Synchronize").onToggle([&] {
settings["Audio/Blocking"].setValue(audioBlockingToggle.checked());
audioBlockingToggle.setText("Synchronize").setToolTip(
"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();
presentation.speedMenu.setEnabled(!videoBlockingToggle.checked() && audioBlockingToggle.checked());
});
audioDynamicToggle.setText("Dynamic rate").onToggle([&] {
settings["Audio/Dynamic"].setValue(audioDynamicToggle.checked());
audioDynamicToggle.setText("Dynamic rate").setToolTip(
"(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();
});
audioSpacer.setColor({192, 192, 192});
@ -90,7 +123,7 @@ auto DriverSettings::videoDriverChanged() -> void {
auto DriverSettings::videoDriverChange() -> void {
auto item = videoDriverOption.selected();
settings["Video/Driver"].setValue(item.text());
settings.video.driver = item.text();
if(!emulator->loaded() || item.text() == "None" || MessageDialog(
"Warning: incompatible drivers may cause bsnes to crash.\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") {
program.save();
program.saveUndoState();
settings["Crashed"].setValue(true);
settings.general.crashed = true;
settings.save();
program.updateVideoDriver(settingsWindow);
settings["Crashed"].setValue(false);
settings.general.crashed = false;
settings.save();
videoDriverChanged();
}
@ -120,7 +153,7 @@ auto DriverSettings::videoFormatChanged() -> void {
auto DriverSettings::videoFormatChange() -> void {
auto item = videoFormatOption.selected();
settings["Video/Format"].setValue(item.text());
settings.video.format = item.text();
video.setFormat(item.text());
}
@ -146,7 +179,7 @@ auto DriverSettings::audioDriverChanged() -> void {
auto DriverSettings::audioDriverChange() -> void {
auto item = audioDriverOption.selected();
settings["Audio/Driver"].setValue(item.text());
settings.audio.driver = item.text();
if(!emulator->loaded() || item.text() == "None" || MessageDialog(
"Warning: incompatible drivers may cause bsnes to crash.\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") {
program.save();
program.saveUndoState();
settings["Crashed"].setValue(true);
settings.general.crashed = true;
settings.save();
program.updateAudioDriver(settingsWindow);
settings["Crashed"].setValue(false);
settings.general.crashed = false;
settings.save();
audioDriverChanged();
}
@ -176,7 +209,7 @@ auto DriverSettings::audioDeviceChanged() -> void {
auto DriverSettings::audioDeviceChange() -> void {
auto item = audioDeviceOption.selected();
settings["Audio/Device"].setValue(item.text());
settings.audio.device = item.text();
program.updateAudioDevice();
audioFrequencyChanged();
audioLatencyChanged();
@ -186,7 +219,7 @@ auto DriverSettings::audioFrequencyChanged() -> void {
audioFrequencyOption.reset();
for(auto& frequency : audio.hasFrequencies()) {
ComboButtonItem item{&audioFrequencyOption};
item.setText({(uint)frequency, "hz"});
item.setText({frequency, "hz"});
if(frequency == audio.frequency()) item.setSelected();
}
//audioFrequencyOption.setEnabled(audio->hasFrequency());
@ -195,7 +228,7 @@ auto DriverSettings::audioFrequencyChanged() -> void {
auto DriverSettings::audioFrequencyChange() -> void {
auto item = audioFrequencyOption.selected();
settings["Audio/Frequency"].setValue(item.text());
settings.audio.frequency = item.text().natural();
program.updateAudioFrequency();
}
@ -212,7 +245,7 @@ auto DriverSettings::audioLatencyChanged() -> void {
auto DriverSettings::audioLatencyChange() -> void {
auto item = audioLatencyOption.selected();
settings["Audio/Latency"].setValue(item.text());
settings.audio.latency = item.text().natural();
program.updateAudioLatency();
}
@ -232,7 +265,7 @@ auto DriverSettings::inputDriverChanged() -> void {
auto DriverSettings::inputDriverChange() -> void {
auto item = inputDriverOption.selected();
settings["Input/Driver"].setValue(item.text());
settings.input.driver = item.text();
if(!emulator->loaded() || item.text() == "None" || MessageDialog(
"Warning: incompatible drivers may cause bsnes to crash.\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") {
program.save();
program.saveUndoState();
settings["Crashed"].setValue(true);
settings.general.crashed = true;
settings.save();
program.updateInputDriver(settingsWindow);
settings["Crashed"].setValue(false);
settings.general.crashed = false;
settings.save();
inputDriverChanged();
}

View File

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

View File

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

View File

@ -7,15 +7,17 @@ auto InputSettings::create() -> void {
portList.onChange([&] { reloadDevices(); });
deviceLabel.setText("Device:");
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)) {
ComboButtonItem item{&turboList};
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();
settings["Input/Turbo/Frequency"].setValue(frequency);
settings.input.turbo.frequency = frequency;
inputManager.turboCounter = 0;
inputManager.turboFrequency = frequency;
});
@ -23,6 +25,7 @@ auto InputSettings::create() -> void {
mappingList.setHeadered();
mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); });
mappingList.onChange([&] { updateControls(); });
mappingList.onSize([&] { mappingList.resizeColumns(); });
assignMouse1.onActivate([&] { assignMouseInput(0); });
assignMouse2.onActivate([&] { assignMouseInput(1); });
assignMouse3.onActivate([&] { assignMouseInput(2); });

View File

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

View File

@ -16,81 +16,100 @@ EmulatorSettings emulatorSettings;
DriverSettings driverSettings;
SettingsWindow settingsWindow;
Settings::Settings() {
auto Settings::load() -> void {
Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")), " "));
auto set = [&](string name, string value) {
//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);
process(true);
file::write(locate("settings.bml"), BML::serialize(*this, " "));
}
auto Settings::save() -> void {
process(false);
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 {
layout.setPadding(5);
panel.append(videoSettings);
@ -107,11 +126,6 @@ auto SettingsWindow::create() -> void {
setAlignment({0.0, 1.0});
setDismissable();
onSize([&] {
inputSettings.mappingList.resizeColumns();
hotkeySettings.mappingList.resizeColumns();
});
onClose([&] {
if(inputSettings.activeMapping) inputSettings.cancelMapping();
if(hotkeySettings.activeMapping) hotkeySettings.cancelMapping();
@ -124,7 +138,6 @@ auto SettingsWindow::setVisible(bool visible) -> SettingsWindow& {
inputSettings.refreshMappings();
hotkeySettings.refreshMappings();
Application::processEvents();
doSize();
}
return Window::setVisible(visible), *this;
}

View File

@ -1,12 +1,106 @@
struct Settings : Markup::Node {
Settings();
Settings() { load(); }
~Settings() { save(); }
auto load() -> 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 {
auto create() -> void;
public:
private:
VerticalLayout layout{this};
Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2};
TableLayout colorLayout{&layout, Size{~0, 0}};
@ -26,7 +120,7 @@ public:
struct AudioSettings : TabFrameItem {
auto create() -> void;
public:
private:
VerticalLayout layout{this};
Label effectsLabel{&layout, Size{~0, 0}, 2};
TableLayout effectsLayout{&layout, Size{~0, 0}};
@ -57,8 +151,9 @@ struct InputSettings : TabFrameItem {
auto assignMouseInput(uint id) -> void;
auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void;
public:
maybe<InputMapping&> activeMapping;
private:
Timer timer;
VerticalLayout layout{this};
@ -87,8 +182,9 @@ struct HotkeySettings : TabFrameItem {
auto cancelMapping() -> void;
auto inputEvent(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> void;
public:
maybe<InputMapping&> activeMapping;
private:
Timer timer;
VerticalLayout layout{this};
@ -154,7 +250,6 @@ public:
HorizontalLayout autoStateLayout{&layout, Size{~0, 0}};
CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}};
CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}};
CheckLabel suppressScreenSaver{&layout, Size{~0, 0}};
Canvas optionsSpacer{&layout, Size{~0, 1}};
Label hacksLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout fastPPULayout{&layout, Size{~0, 0}};

View File

@ -9,25 +9,25 @@ auto VideoSettings::create() -> void {
colorLayout.column(0).setAlignment(1.0);
luminanceLabel.setText("Luminance:");
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(), "%"};
settings["Video/Luminance"].setValue(value);
settings.video.luminance = value.natural();
luminanceValue.setText(value);
program.updateVideoPalette();
}).doChange();
saturationLabel.setText("Saturation:");
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(), "%"};
settings["Video/Saturation"].setValue(value);
settings.video.saturation = value.natural();
saturationValue.setText(value);
program.updateVideoPalette();
}).doChange();
gammaLabel.setText("Gamma:");
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(), "%"};
settings["Video/Gamma"].setValue(value);
settings.video.gamma = value.natural();
gammaValue.setText(value);
program.updateVideoPalette();
}).doChange();

View File

@ -107,6 +107,8 @@ auto CheatEditor::create() -> void {
layout.setPadding(5);
cheatList.setBatchable();
cheatList.setHeadered();
cheatList.setSortable();
cheatList.onActivate([&] {
editButton.doActivate();
});
@ -121,11 +123,22 @@ auto CheatEditor::create() -> void {
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([&] {
cheatDatabase.findCheats();
});
enableCheats.setText("Enable Cheats").setChecked(settings["Emulator/Cheats/Enable"].boolean()).onToggle([&] {
settings["Emulator/Cheats/Enable"].setValue(enableCheats.checked());
enableCheats.setText("Enable Cheats").setToolTip(
"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()) {
program.showMessage("All cheat codes disabled");
} else {
@ -152,12 +165,11 @@ auto CheatEditor::create() -> void {
auto CheatEditor::refresh() -> void {
cheatList.reset();
cheatList.append(TableViewColumn());
cheatList.append(TableViewColumn().setExpandable());
cheatList.append(TableViewColumn().setText("Name").setSorting(Sort::Ascending).setExpandable());
for(auto& cheat : cheats) {
cheatList.append(TableViewItem()
.append(TableViewCell().setCheckable().setChecked(cheat.enable))
.append(TableViewCell().setText(cheat.name))
);
TableViewItem item{&cheatList};
item.append(TableViewCell().setCheckable().setChecked(cheat.enable));
item.append(TableViewCell().setText(cheat.name));
}
cheatList.resizeColumns().doChange();
}

View File

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

View File

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

View File

@ -11,20 +11,37 @@ Presentation::Presentation() {
systemMenu.setVisible(false);
settingsMenu.setText("Settings");
videoScaleMenu.setText("Video Scale");
videoScaleSmall.setText("Small").onActivate([&] {
settings["Video/Windowed/Scale"].setValue("Small");
sizeMenu.setText("Size");
updateSizeMenu();
outputMenu.setText("Output");
centerViewport.setText("Center").onActivate([&] {
settings["View/Output"].setValue("Center");
resizeViewport();
});
videoScaleMedium.setText("Medium").onActivate([&] {
settings["Video/Windowed/Scale"].setValue("Medium");
scaleViewport.setText("Scale").onActivate([&] {
settings["View/Output"].setValue("Scale");
resizeViewport();
});
videoScaleLarge.setText("Large").onActivate([&] {
settings["Video/Windowed/Scale"].setValue("Large");
stretchViewport.setText("Stretch").onActivate([&] {
settings["View/Output"].setValue("Stretch");
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([&] {
settings["Video/BlurEmulation"].setValue(blurEmulation.checked());
if(emulator) emulator->set("Blur Emulation", blurEmulation.checked());
@ -37,7 +54,7 @@ Presentation::Presentation() {
settings["Video/ScanlineEmulation"].setValue(scanlineEmulation.checked());
if(emulator) emulator->set("Scanline Emulation", scanlineEmulation.checked());
});
videoShaderMenu.setText("Video Shader");
videoShaderMenu.setText("Shader");
videoShaderNone.setText("None").onActivate([&] {
settings["Video/Shader"].setValue("None");
program->updateVideoShader();
@ -59,10 +76,14 @@ Presentation::Presentation() {
settings["Audio/Mute"].setValue(muteAudio.checked());
program->updateAudioEffects();
});
showStatusBar.setText("Show Status Bar").setChecked(settings["UserInterface/ShowStatusBar"].boolean()).onToggle([&] {
settings["UserInterface/ShowStatusBar"].setValue(showStatusBar.checked());
statusBar.setVisible(showStatusBar.checked());
if(visible()) resizeViewport();
showStatusBar.setText("Show Status Bar").setChecked(settings["View/StatusBar"].boolean()).onToggle([&] {
settings["View/StatusBar"].setValue(showStatusBar.checked());
if(!showStatusBar.checked()) {
layout.remove(statusLayout);
} else {
layout.append(statusLayout, Size{~0, StatusHeight});
}
if(visible()) resizeWindow();
});
showSystemSettings.setIcon(Icon::Device::Storage).setText("Systems ...").onActivate([&] { settingsManager->show(0); });
showVideoSettings.setIcon(Icon::Device::Display).setText("Video ...").onActivate([&] { settingsManager->show(1); });
@ -113,9 +134,6 @@ Presentation::Presentation() {
aboutWindow->setCentered(*this).setVisible().setFocused();
});
statusBar.setFont(Font().setBold());
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
viewport.setDroppable().onDrop([&](vector<string> locations) {
if(!locations || !directory::exists(locations.first())) return;
program->gameQueue.append(locations.first());
@ -127,17 +145,42 @@ Presentation::Presentation() {
icon.alphaBlend(0x000000);
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([&] {
resizeViewport(false);
resizeViewport();
});
onClose([&] {
program->quit();
});
settings["View/Multiplier"].setValue(2);
setTitle({"higan v", Emulator::Version});
setBackgroundColor({0, 0, 0});
resizeViewport();
resizeWindow();
setCentered();
#if defined(PLATFORM_WINDOWS)
@ -238,13 +281,46 @@ auto Presentation::updateEmulatorDeviceSelections() -> void {
}
}
auto Presentation::clearViewport() -> void {
if(!video) return;
auto Presentation::updateSizeMenu() -> void {
assert(sizeMenu.actionCount() == 0); //should only be called once
if(!emulator || !emulator->loaded()) {
viewport.setGeometry({0, 0, geometry().width(), geometry().height()});
//determine the largest multiplier that can be used by the largest monitor found
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;
uint length = 0;
uint width = 16;
@ -259,105 +335,126 @@ auto Presentation::clearViewport() -> void {
}
}
auto Presentation::resizeViewport(bool resizeWindow) -> void {
//clear video area before resizing to avoid seeing distorted video momentarily
clearViewport();
auto Presentation::resizeViewport() -> void {
uint layoutWidth = viewportLayout.geometry().width();
uint layoutHeight = viewportLayout.geometry().height();
uint viewportWidth = geometry().width();
uint viewportHeight = geometry().height();
uint width = 320;
uint height = 240;
double emulatorWidth = 320;
double emulatorHeight = 240;
double aspectCorrection = 1.0;
if(emulator) {
auto display = emulator->displays()[0];
emulatorWidth = display.width;
emulatorHeight = display.height;
aspectCorrection = display.aspectCorrection;
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["Video/Overscan/Horizontal"].natural();
uint overscanVertical = settings["Video/Overscan/Vertical"].natural();
emulatorWidth -= overscanHorizontal * 2;
emulatorHeight -= overscanVertical * 2;
uint overscanHorizontal = settings["View/Overscan/Horizontal"].natural();
uint overscanVertical = settings["View/Overscan/Vertical"].natural();
width -= overscanHorizontal * 2;
height -= overscanVertical * 2;
}
}
}
if(!fullScreen()) {
if(settings["Video/Windowed/AspectCorrection"].boolean()) emulatorWidth *= aspectCorrection;
if(resizeWindow) {
string viewportScale = "640x480";
if(settings["Video/Windowed/Scale"].text() == "Small") viewportScale = settings["Video/Windowed/Scale/Small"].text();
if(settings["Video/Windowed/Scale"].text() == "Medium") viewportScale = settings["Video/Windowed/Scale/Medium"].text();
if(settings["Video/Windowed/Scale"].text() == "Large") viewportScale = settings["Video/Windowed/Scale/Large"].text();
auto resolution = viewportScale.isplit("x", 1L);
viewportWidth = resolution(0).natural();
viewportHeight = resolution(1).natural();
if(visible() && !fullScreen()) {
uint widthMultiplier = layoutWidth / width;
uint heightMultiplier = layoutHeight / height;
uint multiplier = max(1, min(widthMultiplier, heightMultiplier));
settings["View/Multiplier"].setValue(multiplier);
for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(auto property = item.property("multiplier")) {
if(property.natural() == multiplier) item.setChecked();
}
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()) {
viewport.setGeometry({
(viewportWidth - emulatorWidth) / 2, (viewportHeight - emulatorHeight) / 2,
emulatorWidth, emulatorHeight
if(!emulator || !emulator->loaded()) return clearViewport();
if(!video) return;
uint viewportWidth;
uint viewportHeight;
if(settings["View/Output"].text() == "Center") {
uint widthMultiplier = layoutWidth / width;
uint heightMultiplier = layoutHeight / height;
uint multiplier = min(widthMultiplier, heightMultiplier);
viewportWidth = width * multiplier;
viewportHeight = height * multiplier;
} else if(settings["View/Output"].text() == "Scale") {
double widthMultiplier = (double)layoutWidth / width;
double heightMultiplier = (double)layoutHeight / height;
double multiplier = min(widthMultiplier, heightMultiplier);
viewportWidth = width * multiplier;
viewportHeight = height * multiplier;
} else if(settings["View/Output"].text() == "Stretch" || 1) {
viewportWidth = layoutWidth;
viewportHeight = layoutHeight;
}
//center viewport within viewportLayout by use of viewportLayout padding
uint paddingWidth = layoutWidth - viewportWidth;
uint paddingHeight = layoutHeight - viewportHeight;
viewportLayout.setPadding({
paddingWidth / 2, paddingHeight / 2,
paddingWidth - paddingWidth / 2, paddingHeight - paddingHeight / 2
});
} else {
viewport.setGeometry({0, 0, geometry().width(), geometry().height()});
clearViewport();
}
//clear video area again to ensure entire viewport area has been painted in
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 {
if(!fullScreen()) {
statusBar.setVisible(false);
if(settings["View/StatusBar"].boolean()) {
layout.remove(statusLayout);
}
menuBar.setVisible(false);
setFullScreen(true);
video->setExclusive(settings["Video/Fullscreen/Exclusive"].boolean());
video->setExclusive(settings["Video/Exclusive"].boolean());
if(video->exclusive()) setVisible(false);
if(!input->acquired()) input->acquire();
resizeViewport();
} else {
if(input->acquired()) input->release();
if(video->exclusive()) setVisible(true);
video->setExclusive(false);
setFullScreen(false);
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 {

View File

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

View File

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

View File

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

View File

@ -50,11 +50,11 @@ struct Program : Emulator::Platform {
vector<string> gameQueue; //for command-line and drag-and-drop loading
vector<string> 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;
time_t statusTime = 0;
string statusInfo;
};
extern unique_pointer<Program> program;

View File

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

View File

@ -11,19 +11,18 @@ unique_pointer<SettingsManager> settingsManager;
unique_pointer<SystemProperties> systemProperties;
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) {
//create node and set to default value only if it does not already exist
if(!operator[](name)) operator()(name).setValue(value);
};
set("UserInterface/ShowStatusBar", true);
set("Library/Location", {Path::user(), "Emulation/"});
set("Library/IgnoreManifests", false);
set("Video/Driver", ruby::Video::safestDriver());
set("Video/Exclusive", false);
set("Video/Synchronize", false);
set("Video/Shader", "Blur");
set("Video/BlurEmulation", true);
@ -34,21 +33,6 @@ Settings::Settings() {
set("Video/Gamma", 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/Device", "");
set("Audio/Frequency", 48000);
@ -63,6 +47,15 @@ Settings::Settings() {
set("Input/Frequency", 5);
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/Interval", 30);
@ -72,7 +65,7 @@ Settings::Settings() {
}
auto Settings::save() -> void {
file::write(locate("settings.bml"), BML::serialize(*this));
file::write(locate("settings.bml"), BML::serialize(*this, " "));
}
//

View File

@ -48,6 +48,8 @@ struct SystemSettings : TabFrameItem {
struct VideoSettings : TabFrameItem {
VideoSettings(TabFrame*);
auto updateColor(bool initializing = false) -> void;
auto updateOverscan(bool initializing = false) -> void;
VerticalLayout layout{this};
Label colorAdjustmentLabel{&layout, Size{~0, 0}, 2};
@ -72,19 +74,8 @@ struct VideoSettings : TabFrameItem {
Label verticalMaskLabel{&verticalMaskLayout, Size{80, 0}};
Label verticalMaskValue{&verticalMaskLayout, Size{50, 0}};
HorizontalSlider verticalMaskSlider{&verticalMaskLayout, Size{~0, 0}};
Label windowedModeLabel{&layout, Size{~0, 0}, 2};
HorizontalLayout windowedModeLayout{&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;
Label fullscreenLabel{&layout, Size{~0, 0}, 2};
CheckLabel fullscreenExclusive{&layout, Size{0, 0}};
};
struct AudioSettings : TabFrameItem {

View File

@ -15,26 +15,21 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
luminanceValue.setAlignment(0.5);
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:");
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:");
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");
windowedModeAspectCorrection.setText("Aspect correction").setChecked(settings["Video/Windowed/AspectCorrection"].boolean()).onToggle([&] { updateViewport(); });
windowedModeIntegralScaling.setText("Integral scaling").setChecked(settings["Video/Windowed/IntegralScaling"].boolean()).onToggle([&] { updateViewport(); });
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(); });
fullscreenLabel.setFont(Font().setBold()).setText("Fullscreen");
fullscreenExclusive.setText("Exclusive mode").setChecked(settings["Video/Exclusive"].boolean()).onToggle([&] {
settings["Video/Exclusive"].setValue(fullscreenExclusive.checked());
});
updateColor(true);
updateViewport(true);
updateOverscan(true);
}
auto VideoSettings::updateColor(bool initializing) -> void {
@ -48,20 +43,16 @@ auto VideoSettings::updateColor(bool initializing) -> void {
if(!initializing) program->updateVideoPalette();
}
auto VideoSettings::updateViewport(bool initializing) -> void {
bool wasAdaptive = settings["Video/Windowed/Adaptive"].boolean();
bool isAdaptive = windowedModeAdaptive.checked();
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());
auto VideoSettings::updateOverscan(bool initializing) -> void {
settings["View/Overscan/Horizontal"].setValue(horizontalMaskSlider.position());
settings["View/Overscan/Vertical"].setValue(verticalMaskSlider.position());
horizontalMaskValue.setText({horizontalMaskSlider.position()});
verticalMaskValue.setText({verticalMaskSlider.position()});
if(!initializing) presentation->resizeViewport(isAdaptive || wasAdaptive != isAdaptive);
if(initializing || settings["View/Overscan"].boolean()) return;
if(settings["View/Adaptive"].boolean()) {
presentation->resizeWindow();
} else {
presentation->resizeViewport();
}
}

View File

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

View File

@ -14,6 +14,7 @@ auto pWidget::construct() -> void {
if(auto p = window->self()) p->_append(self());
setEnabled(self().enabled(true));
setFont(self().font(true));
setToolTip(self().toolTip());
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 superview] setNeedsDisplay:YES];
}
self().doSize();
pSizable::setGeometry(geometry);
}
auto pWidget::setToolTip(const string& toolTip) -> void {
//TODO
}
auto pWidget::setVisible(bool visible) -> void {

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@
#include <nall/stdint.hpp>
#include <nall/string.hpp>
#include <nall/traits.hpp>
#include <nall/unique-pointer.hpp>
#include <nall/utility.hpp>
#include <nall/vector.hpp>
@ -25,7 +26,7 @@ using nall::set;
using nall::shared_pointer;
using nall::shared_pointer_weak;
using nall::string;
using nall::string_pascal;
using nall::unique_pointer;
using nall::vector;
namespace hiro {
@ -1218,84 +1219,8 @@ struct mRadioLabel : mWidget {
#endif
#include "widget/source-edit.hpp"
#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
#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/tab-frame.hpp"
#include "widget/tab-frame-item.hpp"
#include "widget/table-view.hpp"
#include "widget/table-view-column.hpp"
#include "widget/table-view-item.hpp"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,15 +6,6 @@ auto mWidget::allocate() -> pObject* {
//
auto mWidget::doSize() const -> void {
if(state.onSize) return state.onSize();
}
auto mWidget::onSize(const function<void ()>& callback) -> type& {
state.onSize = callback;
return *this;
}
auto mWidget::remove() -> type& {
//TODO: how to implement this after removing mLayout?
//if(auto layout = parentLayout()) layout->remove(layout->sizable(offset()));
@ -22,4 +13,16 @@ auto mWidget::remove() -> type& {
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

View File

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

View File

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

View File

@ -2,13 +2,6 @@
namespace hiro {
vector<pWindow*> pApplication::windows;
#if defined(DISPLAY_XORG)
XlibDisplay* pApplication::display = nullptr;
bool pApplication::xdgScreenSaver = false;
#endif
auto pApplication::run() -> void {
while(!Application::state().quit) {
Application::doMain();
@ -22,7 +15,7 @@ auto pApplication::pendingEvents() -> bool {
auto pApplication::processEvents() -> void {
while(pendingEvents()) gtk_main_iteration_do(false);
for(auto& window : windows) window->_synchronizeGeometry();
for(auto& window : state().windows) window->_synchronizeGeometry();
}
auto pApplication::quit() -> void {
@ -30,21 +23,54 @@ auto pApplication::quit() -> void {
if(gtk_main_level()) gtk_main_quit();
#if defined(DISPLAY_XORG)
XCloseDisplay(display);
display = nullptr;
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;
}
#endif
}
auto pApplication::setScreenSaver(bool screenSaver) -> void {
#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
}
auto pApplication::state() -> State& {
static State state;
return state;
}
auto pApplication::initialize() -> void {
#if defined(DISPLAY_XORG)
display = XOpenDisplay(nullptr);
xdgScreenSaver = (bool)execute("xdg-screensaver", "--version").output.find("xdg-screensaver");
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
//set WM_CLASS to Application::name()

View File

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

View File

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

View File

@ -13,7 +13,7 @@ auto pMouse::position() -> Position {
XlibWindow root, child;
int rootx, rooty, winx, winy;
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};
#endif
}
@ -31,7 +31,7 @@ auto pMouse::pressed(Mouse::Button button) -> bool {
XlibWindow root, child;
int rootx, rooty, winx, winy;
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) {
case Mouse::Button::Left: return mask & Button1Mask;
case Mouse::Button::Middle: return mask & Button2Mask;

View File

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

View File

@ -6,7 +6,9 @@ auto pWidget::construct() -> void {
if(!gtkWidget) return;
if(auto window = self().parentWindow(true)) {
if(window->self()) window->self()->_append(self());
setEnabled(self().enabled(true));
setFont(self().font(true));
setToolTip(self().toolTip());
setVisible(self().visible(true));
}
}
@ -58,7 +60,11 @@ auto pWidget::setGeometry(Geometry geometry) -> void {
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 {

View File

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

View File

@ -100,7 +100,6 @@ static auto Window_keyRelease(GtkWidget* widget, GdkEventKey* event, pWindow* p)
}
static auto Window_realize(GtkWidget* widget, pWindow* p) -> void {
p->_setScreenSaver(Application::screenSaver());
}
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 {
p->_setScreenSaver(true);
}
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(formContainer), "hiro::window", (gpointer)this);
pApplication::windows.append(this);
pApplication::state().windows.append(this);
}
auto pWindow::destruct() -> void {
for(uint offset : range(pApplication::windows.size())) {
if(pApplication::windows[offset] == this) {
pApplication::windows.remove(offset);
for(uint offset : range(pApplication::state().windows.size())) {
if(pApplication::state().windows[offset] == this) {
pApplication::state().windows.remove(offset);
break;
}
}
@ -495,19 +493,6 @@ auto pWindow::_setMenuVisible(bool visible) -> void {
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 {
gtk_widget_set_sensitive(gtkStatus, enabled);
}

View File

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

View File

@ -2,8 +2,6 @@
namespace hiro {
XlibDisplay* pApplication::display = nullptr;
auto pApplication::run() -> void {
if(Application::state().onMain) {
while(!Application::state().quit) {
@ -26,17 +24,37 @@ auto pApplication::processEvents() -> void {
auto pApplication::quit() -> void {
QApplication::quit();
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 {
//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
//obviously, it is used as sparingly as possible
auto pApplication::syncX() -> void {
auto pApplication::synchronize() -> void {
for(auto n : range(8)) {
#if HIRO_QT==4
#if HIRO_QT==4 && defined(DISPLAY_XORG)
QApplication::syncX();
#elif HIRO_QT==5
QApplication::sync();
@ -51,7 +69,28 @@ auto pApplication::initialize() -> void {
setenv("QTCOMPOSE", "/usr/local/lib/X11/locale/", 0);
#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"};

View File

@ -10,9 +10,17 @@ struct pApplication {
static auto setScreenSaver(bool screenSaver) -> 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;

View File

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

View File

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

View File

@ -25,7 +25,9 @@ auto pWidget::construct() -> void {
qtWidget->setParent(container);
}
setEnabled(self().enabled(true));
setFont(self().font(true));
setToolTip(self().toolTip());
setVisible(self().visible(true));
}
@ -55,7 +57,12 @@ auto pWidget::setFont(const Font& font) -> void {
auto pWidget::setGeometry(Geometry geometry) -> void {
if(!qtWidget) return;
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 {

View File

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

View File

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

View File

@ -61,7 +61,7 @@ auto pApplication::initialize() -> void {
CoInitialize(0);
InitCommonControls();
WNDCLASS wc;
WNDCLASS wc{};
#if defined(Hiro_Window)
wc.cbClsExtra = 0;
@ -91,43 +91,29 @@ auto pApplication::initialize() -> void {
RegisterClass(&wc);
#endif
#if defined(Hiro_Canvas)
#if defined(Hiro_Widget)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = Canvas_windowProc;
wc.lpszClassName = L"hiroCanvas";
wc.lpfnWndProc = ToolTip_windowProc;
wc.lpszClassName = L"hiroToolTip";
wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);
#endif
#if defined(Hiro_Label)
#if defined(Hiro_Widget)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = Label_windowProc;
wc.lpszClassName = L"hiroLabel";
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.lpfnWndProc = Default_windowProc;
wc.lpszClassName = L"hiroWidget";
wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);
@ -137,6 +123,11 @@ auto pApplication::initialize() -> void {
pWindow::initialize();
}
auto pApplication::state() -> State& {
static State state;
return state;
}
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;

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

@ -7,31 +7,37 @@ static const uint Windows7 = 0x0601;
static auto Button_CustomDraw(HWND, PAINTSTRUCT&, bool, bool, bool, unsigned, const Font&, const image&, Orientation, const string&) -> void;
static auto OsVersion() -> unsigned {
static auto OsVersion() -> uint {
OSVERSIONINFO versionInfo{0};
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&versionInfo);
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 {
icon.alphaMultiply(); //Windows AlphaBlend() requires premultiplied image data
icon.transform();
HDC hdc = GetDC(0);
BITMAPINFO bitmapInfo;
memset(&bitmapInfo, 0, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = icon.width();
bitmapInfo.bmiHeader.biHeight = -(signed)icon.height(); //bitmaps are stored upside down unless we negate height
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;
uint32_t* data = nullptr;
auto hdc = GetDC(nullptr);
auto bitmap = CreateBitmap(hdc, icon.width(), icon.height(), data);
memory::copy(data, icon.data(), icon.size());
ReleaseDC(nullptr, hdc);
return bitmap;
}
static auto CreateRGB(const Color& color) -> COLORREF {
@ -60,9 +66,21 @@ static auto DropPaths(WPARAM wparam) -> vector<string> {
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 {
unsigned z = 0;
for(HWND next = hwnd; next != NULL; next = GetWindow(next, GW_HWNDPREV)) z++;
uint z = 0;
for(HWND next = hwnd; next != nullptr; next = GetWindow(next, GW_HWNDPREV)) z++;
return z;
}
@ -111,6 +129,10 @@ static auto ScrollEvent(HWND hwnd, WPARAM wparam) -> unsigned {
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
static auto CALLBACK Menu_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
switch(msg) {
@ -383,6 +405,47 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
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)
case AppMessage::TableView_doPaint: {
if(auto tableView = (mTableView*)lparam) {
@ -402,7 +465,7 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
#endif
}
return windowProc(hwnd, msg, wparam, lparam);
return CallWindowProc(windowProc, hwnd, msg, wparam, lparam);
}
}

View File

@ -2,49 +2,12 @@
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 {
hwnd = CreateWindow(
L"BUTTON", L"", WS_CHILD | WS_TABSTOP,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)Button_windowProc);
pWidget::_setState();
pWidget::construct();
_setState();
}
@ -99,10 +62,35 @@ auto pButton::setVisible(bool visible) -> void {
_setState();
}
//
auto pButton::onActivate() -> void {
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 {
InvalidateRect(hwnd, 0, false);
}

View File

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

View File

@ -2,64 +2,9 @@
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 {
hwnd = CreateWindow(L"hiroCanvas", L"", WS_CHILD, 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
pWidget::_setState();
hwnd = CreateWindow(L"hiroWidget", L"", WS_CHILD, 0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
pWidget::construct();
setDroppable(state().droppable);
update();
}
@ -99,26 +44,95 @@ auto pCanvas::update() -> void {
_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 {
PAINTSTRUCT 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);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
BITMAPINFO bmi{};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biWidth = width;
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;
HBITMAP bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
if(bits) {
auto source = (const uint8_t*)pixels.data();
auto target = (uint8_t*)bits;
for(auto n : range(width * height)) {
for(uint y : range(height)) {
auto source = (const uint8_t*)pixels.data() + (sy + y) * this->width * sizeof(uint32_t) + sx * sizeof(uint32_t);
auto target = (uint8_t*)bits + y * width * sizeof(uint32_t);
for(uint x : range(width)) {
target[0] = (source[0] * source[3]) / 255;
target[1] = (source[1] * source[3]) / 255;
target[2] = (source[2] * source[3]) / 255;
@ -126,6 +140,7 @@ auto pCanvas::_paint() -> void {
source += 4, target += 4;
}
}
}
SelectObject(hdc, bitmap);
RECT rc;
@ -133,7 +148,7 @@ auto pCanvas::_paint() -> void {
DrawThemeParentBackground(hwnd, ps.hdc, &rc);
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);
DeleteDC(hdc);

View File

@ -13,6 +13,10 @@ struct pCanvas : pWidget {
auto setIcon(const image& icon) -> 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 _rasterize() -> void;
auto _redraw() -> void;

View File

@ -2,30 +2,11 @@
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 {
hwnd = CreateWindow(L"BUTTON", L"",
WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)CheckButton_windowProc);
pWidget::_setState();
pWidget::construct();
_setState();
setChecked(state().checked);
}
@ -85,12 +66,32 @@ auto pCheckButton::setVisible(bool visible) -> void {
_setState();
}
//
auto pCheckButton::onToggle() -> void {
state().checked = !state().checked;
setChecked(state().checked);
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 {
InvalidateRect(hwnd, 0, false);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -2,77 +2,20 @@
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 {
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY | ES_MULTILINE | ES_WANTRETURN,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
scrollBar = CreateWindowEx(
0, L"SCROLLBAR", L"",
WS_VISIBLE | WS_CHILD | SBS_VERT,
0, 0, 0, 0, hwnd, nullptr, GetModuleHandle(0), 0
);
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);
setBackgroundColor(state().backgroundColor);
setLength(state().length);
@ -149,7 +92,7 @@ auto pHexEdit::update() -> void {
Edit_SetSel(hwnd, LOWORD(cursorPosition), HIWORD(cursorPosition));
}
bool pHexEdit::keyPress(unsigned scancode) {
auto pHexEdit::keyPress(unsigned scancode) -> bool {
if(!state().onRead) return false;
signed position = LOWORD(Edit_GetSel(hwnd));
@ -234,25 +177,67 @@ bool pHexEdit::keyPress(unsigned scancode) {
return true;
}
signed pHexEdit::rows() {
auto pHexEdit::rows() -> int {
return (max(1u, state().length) + state().columns - 1) / state().columns;
}
signed pHexEdit::rowsScrollable() {
auto pHexEdit::rowsScrollable() -> int {
return max(0u, rows() - state().rows);
}
signed pHexEdit::scrollPosition() {
auto pHexEdit::scrollPosition() -> int {
return state().address / state().columns;
}
void pHexEdit::scrollTo(signed position) {
auto pHexEdit::scrollTo(signed position) -> void {
if(position > rowsScrollable()) position = rowsScrollable();
if(position < 0) position = 0;
if(position == scrollPosition()) return;
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,30 +2,11 @@
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 {
hwnd = CreateWindow(L"BUTTON", L"",
WS_CHILD | WS_TABSTOP | BS_CHECKBOX | BS_PUSHLIKE,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)RadioButton_windowProc);
pWidget::_setState();
pWidget::construct();
setGroup(state().group);
_setState();
}
@ -111,12 +92,32 @@ auto pRadioButton::setVisible(bool visible) -> void {
_setState();
}
//
auto pRadioButton::onActivate() -> void {
if(state().checked) return;
self().setChecked();
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 {
InvalidateRect(hwnd, 0, false);
}

View File

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

View File

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

View File

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

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