mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2024-11-23 08:59:40 +00:00
Update to v106r57 release.
byuu says: I've added tool tips to hiro for Windows, GTK, and Qt. I'm unsure how to add them for Cocoa. I wasted am embarrassing ~14 hours implementing tool tips from scratch on Windows, because the `TOOLTIPS_CLASS` widget just absolutely refused to show up, no matter what I tried. As such, they're not quite 100% native, but I would really appreciate any patch submissions to help improve my implementation. I added tool tips to all of the confusing settings in bsnes. And of course, for those of you who don't like them, there's a configuration file setting to turn them off globally. I also improved Mega Drive handling of the Game Genie a bit, and restructured the way the Settings class works in bsnes. Starting now, I'm feature-freezing bsnes and higan. From this point forward: - polishing up and fixing bugs caused by the ruby/hiro changes - adding DRC to XAudio2, and maybe exclusive mode to WGL - correcting FEoEZ (English) to load and work again out of the box Once that's done, a final beta of bsnes will go out, I'll fix any reported bugs that I'm able to, and then v107 should be ready. This time with higan being functional, but marked as v107 beta. v108 will restore higan to production status again, alongside bsnes.
This commit is contained in:
parent
3b4e8b6d75
commit
93a6a1ce7e
@ -13,7 +13,7 @@ using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
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/";
|
||||
|
@ -70,18 +70,17 @@ 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();
|
||||
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();
|
||||
read = {&Cartridge::readGameGenie, this};
|
||||
write = {&Cartridge::writeGameGenie, this};
|
||||
if(patch) {
|
||||
read = {&Cartridge::readLockOn, this};
|
||||
write = {&Cartridge::writeLockOn, this};
|
||||
} else {
|
||||
read = {&Cartridge::readGameGenie, this};
|
||||
write = {&Cartridge::writeGameGenie, this};
|
||||
}
|
||||
}
|
||||
|
||||
//easter egg: power draw increases with each successively stacked cartridge
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ struct Configuration {
|
||||
bool enable = false;
|
||||
} dspFast;
|
||||
} hacks;
|
||||
|
||||
private:
|
||||
auto process(Markup::Node document, bool load) -> void;
|
||||
};
|
||||
|
||||
extern Configuration configuration;
|
||||
|
@ -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;
|
||||
|
@ -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([] {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ struct Presentation : Window {
|
||||
Group outputGroup{¢erViewport, &scaleViewport, &stretchViewport};
|
||||
MenuSeparator outputSeparator{&outputMenu};
|
||||
MenuCheckItem aspectCorrection{&outputMenu};
|
||||
MenuCheckItem overscanCropping{&outputMenu};
|
||||
MenuCheckItem showOverscanArea{&outputMenu};
|
||||
MenuCheckItem blurEmulation{&outputMenu};
|
||||
Menu shaderMenu{&settingsMenu};
|
||||
MenuSeparator settingsSeparatorA{&settingsMenu};
|
||||
|
@ -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();
|
||||
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
|
||||
if(item.checked()) frequency *= item.property("multiplier").real();
|
||||
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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ namespace: Presentation
|
||||
input: Load Recent Game
|
||||
value: 最新ゲームを読み込み
|
||||
map
|
||||
input: Empty
|
||||
input: empty
|
||||
value: なし
|
||||
map
|
||||
input: Clear List
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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); });
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}};
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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/"));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(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(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(emulator && emulator->loaded()) {
|
||||
viewport.setGeometry({
|
||||
(viewportWidth - emulatorWidth) / 2, (viewportHeight - emulatorHeight) / 2,
|
||||
emulatorWidth, emulatorHeight
|
||||
});
|
||||
} else {
|
||||
viewport.setGeometry({0, 0, geometry().width(), geometry().height()});
|
||||
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;
|
||||
}
|
||||
|
||||
//clear video area again to ensure entire viewport area has been painted in
|
||||
//center viewport within viewportLayout by use of viewportLayout padding
|
||||
uint paddingWidth = layoutWidth - viewportWidth;
|
||||
uint paddingHeight = layoutHeight - viewportHeight;
|
||||
viewportLayout.setPadding({
|
||||
paddingWidth / 2, paddingHeight / 2,
|
||||
paddingWidth - paddingWidth / 2, paddingHeight - paddingHeight / 2
|
||||
});
|
||||
|
||||
clearViewport();
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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{¢erViewport, &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;
|
||||
|
@ -41,7 +41,11 @@ auto Program::load(Emulator::Interface& interface) -> void {
|
||||
updateAudioEffects();
|
||||
|
||||
presentation->viewportLayout.remove(presentation->iconLayout);
|
||||
presentation->resizeViewport();
|
||||
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});
|
||||
presentation->resizeViewport();
|
||||
if(settings["View/Adaptive"].boolean()) {
|
||||
presentation->resizeWindow();
|
||||
} else {
|
||||
presentation->resizeViewport();
|
||||
}
|
||||
presentation->setTitle({"higan v", Emulator::Version});
|
||||
presentation->systemMenu.setVisible(false);
|
||||
presentation->toolsMenu.setVisible(false);
|
||||
|
@ -56,15 +56,17 @@ auto Program::videoRefresh(uint displayID, const uint32* data, uint pitch, uint
|
||||
|
||||
pitch >>= 2;
|
||||
|
||||
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();
|
||||
overscanHorizontal *= display.internalWidth / display.width;
|
||||
overscanVertical *= display.internalHeight / display.height;
|
||||
data += overscanVertical * pitch + overscanHorizontal;
|
||||
width -= overscanHorizontal * 2;
|
||||
height -= overscanVertical * 2;
|
||||
if(!settings["View/Overscan"].boolean()) {
|
||||
auto display = emulator->displays()[displayID];
|
||||
if(display.type == Emulator::Interface::Display::Type::CRT) {
|
||||
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)) {
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, " "));
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||
}
|
||||
|
||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||
self().doSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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() {}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
37
hiro/core/widget/tab-frame-item.hpp
Normal file
37
hiro/core/widget/tab-frame-item.hpp
Normal 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
|
38
hiro/core/widget/tab-frame.hpp
Normal file
38
hiro/core/widget/tab-frame.hpp
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
|
@ -11,12 +11,17 @@ 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;
|
||||
#endif
|
||||
#if defined(DISPLAY_XORG)
|
||||
XlibDisplay* display = nullptr;
|
||||
XlibWindow screenSaverWindow = 0;
|
||||
bool screenSaverXDG = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
static auto state() -> State&;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||
}
|
||||
|
||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||
self().doSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
#include <hiro/platform/xorg/keyboard.hpp>
|
||||
#if defined(DISPLAY_XORG)
|
||||
#include <hiro/platform/xorg/keyboard.hpp>
|
||||
#endif
|
||||
|
||||
//print("[hiro/qt] warning: unhandled key: ", key, "\n");
|
||||
append(0);
|
||||
}
|
||||
#undef map
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ auto pSizable::minimumSize() const -> Size {
|
||||
}
|
||||
|
||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||
self().doSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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&;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -27,6 +27,8 @@
|
||||
|
||||
#include "sizable.cpp"
|
||||
|
||||
#include "tool-tip.cpp"
|
||||
|
||||
#include "widget/widget.cpp"
|
||||
#include "widget/button.cpp"
|
||||
#include "widget/canvas.cpp"
|
||||
|
@ -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"
|
||||
|
@ -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
161
hiro/windows/tool-tip.cpp
Normal 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
22
hiro/windows/tool-tip.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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,31 +44,101 @@ 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)) {
|
||||
target[0] = (source[0] * source[3]) / 255;
|
||||
target[1] = (source[1] * source[3]) / 255;
|
||||
target[2] = (source[2] * source[3]) / 255;
|
||||
target[3] = (source[3]);
|
||||
source += 4, target += 4;
|
||||
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;
|
||||
target[3] = (source[3]);
|
||||
source += 4, target += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectObject(hdc, bitmap);
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
FillRect(hdcMemory, &rc, brush);
|
||||
} else {
|
||||
} 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 self = tabFrame->self()) {
|
||||
return Shared_windowProc(self->windowProc, hwnd, msg, wparam, lparam);
|
||||
}
|
||||
if(auto tabFrame = (mTabFrame*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
|
||||
if(auto self = tabFrame->self()) {
|
||||
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
Loading…
Reference in New Issue
Block a user