diff --git a/genius/GNUmakefile b/genius/GNUmakefile index 53046ae8..ced672cb 100644 --- a/genius/GNUmakefile +++ b/genius/GNUmakefile @@ -9,8 +9,9 @@ objects := obj/hiro.o objects += obj/genius.o objects += $(if $(call streq,$(platform),windows),obj/resource.o) -all: $(objects) - $(strip $(compiler) -o out/$(name) $(objects) $(link) $(hirolink)) +default: information $(objects) + $(info Linking out/$(name) ...) + +@$(strip $(compiler) -o out/$(name) $(objects) $(link) $(hirolink)) ifeq ($(platform),macos) rm -rf out/$(name).app mkdir -p out/$(name).app/Contents/MacOS/ @@ -21,13 +22,16 @@ ifeq ($(platform),macos) endif obj/hiro.o: ../hiro/hiro.cpp - $(compiler) $(hiroflags) -o obj/hiro.o -c ../hiro/hiro.cpp + $(info Compiling $< ...) + @$(compiler) $(hiroflags) -o obj/hiro.o -c ../hiro/hiro.cpp obj/genius.o: genius.cpp genius.hpp - $(compiler) $(cppflags) $(flags) -o obj/genius.o -c genius.cpp + $(info Compiling $< ...) + @$(compiler) $(cppflags) $(flags) -o obj/genius.o -c genius.cpp -obj/resource.o: - $(windres) data/$(name).rc obj/resource.o +obj/resource.o: data/$(name).rc + $(info Compiling $< ...) + @$(windres) data/$(name).rc obj/resource.o clean: ifeq ($(platform),macos) diff --git a/higan/GNUmakefile b/higan/GNUmakefile index 801b62ff..6bc45eda 100644 --- a/higan/GNUmakefile +++ b/higan/GNUmakefile @@ -38,16 +38,15 @@ endif compile = \ $(strip \ $(if $(filter %.c,$<), \ - $(compiler) $(cflags) $(flags) $1 -c $< -o $@ -MMD -MP -MF $(@:.o=.d), \ - $(if $(filter %.cpp,$<), \ - $(compiler) $(cppflags) $(flags) $1 -c $< -o $@ -MMD -MP -MF $(@:.o=.d) \ - ) \ - ) \ + $(compiler) $(cflags) $(flags) $1 -c $< -o $@ -MMD -MP -MF $(@:.o=.d), \ + $(if $(filter %.cpp,$<), \ + $(compiler) $(cppflags) $(flags) $1 -c $< -o $@ -MMD -MP -MF $(@:.o=.d) \ + )) \ ) -%.o: $<; $(call compile) - -all: build; +%.o: $< + $(info Compiling $< ...) + @$(call compile) obj/libco.o: ../libco/libco.c obj/emulator.o: emulator/emulator.cpp diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 7b8e1680..5335d2a9 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -13,7 +13,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.45"; + static const string Version = "106.46"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/processor/spc700/spc700.hpp b/higan/processor/spc700/spc700.hpp index f3a24565..4a5f79ba 100644 --- a/higan/processor/spc700/spc700.hpp +++ b/higan/processor/spc700/spc700.hpp @@ -5,7 +5,7 @@ namespace Processor { struct SPC700 { virtual auto idle() -> void = 0; virtual auto read(uint16 address) -> uint8 = 0; - virtual auto write(uint16 addessr, uint8 data) -> void = 0; + virtual auto write(uint16 address, uint8 data) -> void = 0; virtual auto synchronizing() const -> bool = 0; virtual auto readDisassembler(uint16 address) -> uint8 { return 0; } diff --git a/higan/target-bsnes/GNUmakefile b/higan/target-bsnes/GNUmakefile index 964bbcbc..7b525c5b 100644 --- a/higan/target-bsnes/GNUmakefile +++ b/higan/target-bsnes/GNUmakefile @@ -5,10 +5,11 @@ include sfc/GNUmakefile include gb/GNUmakefile include processor/GNUmakefile -ui_objects := ui-bsnes ui-program ui-input ui-presentation -ui_objects += ui-settings ui-tools ui-resource -ui_objects += ruby hiro -ui_objects += $(if $(call streq,$(platform),windows),ui-windows) +objects := ruby hiro $(objects) +objects += ui-bsnes ui-program ui-input ui-presentation +objects += ui-settings ui-tools ui-resource +objects += $(if $(call streq,$(platform),windows),ui-windows) +objects := $(objects:%=obj/%.o) # platform ifeq ($(platform),windows) @@ -33,20 +34,19 @@ endif include ../ruby/GNUmakefile link += $(rubylink) +obj/ruby.o: ../ruby/ruby.cpp $(call rwildcard,../ruby/) + $(info Compiling $< ...) + @$(compiler) $(rubyflags) -c $< -o $@ + # hiro include ../hiro/GNUmakefile link += $(hirolink) -# rules -objects := $(ui_objects) $(objects) -objects := $(objects:%=obj/%.o) - -obj/ruby.o: ../ruby/ruby.cpp $(call rwildcard,../ruby/) - $(compiler) $(rubyflags) -c $< -o $@ - obj/hiro.o: ../hiro/hiro.cpp $(call rwildcard,../hiro/) - $(compiler) $(hiroflags) -c $< -o $@ + $(info Compiling $< ...) + @$(compiler) $(hiroflags) -c $< -o $@ +# rules obj/ui-bsnes.o: $(ui)/bsnes.cpp obj/ui-program.o: $(ui)/program/program.cpp obj/ui-input.o: $(ui)/input/input.cpp @@ -55,12 +55,14 @@ obj/ui-settings.o: $(ui)/settings/settings.cpp obj/ui-tools.o: $(ui)/tools/tools.cpp obj/ui-resource.o: $(ui)/resource/resource.cpp -obj/ui-windows.o: - $(windres) $(ui)/resource/bsnes.rc obj/ui-windows.o +obj/ui-windows.o: $(ui)/resource/bsnes.rc + $(info Compiling $< ...) + @$(windres) $(ui)/resource/bsnes.rc obj/ui-windows.o # targets -build: $(objects) - $(strip $(compiler) -o out/$(name) $(objects) $(link)) +default: information $(objects) + $(info Linking out/$(name) ...) + +@$(strip $(compiler) -o out/$(name) $(objects) $(link)) ifeq ($(platform),macos) rm -rf out/$(name).app mkdir -p out/$(name).app/Contents/MacOS/ diff --git a/higan/target-bsnes/input/hotkeys.cpp b/higan/target-bsnes/input/hotkeys.cpp index eb2009c3..397f722c 100644 --- a/higan/target-bsnes/input/hotkeys.cpp +++ b/higan/target-bsnes/input/hotkeys.cpp @@ -9,6 +9,13 @@ auto InputManager::bindHotkeys() -> void { input->acquired() ? input->release() : input->acquire(); })); + hotkeys.append(InputHotkey("Toggle Cheat Codes").onPress([] { + toolsWindow->cheatEditor.enableCheats.setChecked( + !toolsWindow->cheatEditor.enableCheats.checked() + ); + toolsWindow->cheatEditor.enableCheats.doToggle(); + })); + hotkeys.append(InputHotkey("Save State").onPress([&] { program->saveState({"quick/slot ", stateSlot}); })); @@ -28,7 +35,7 @@ auto InputManager::bindHotkeys() -> void { })); hotkeys.append(InputHotkey("Capture Screenshot").onPress([] { - presentation->captureScreenshot.doActivate(); + program->captureScreenshot(); })); hotkeys.append(InputHotkey("Fast Forward").onPress([] { @@ -43,6 +50,10 @@ auto InputManager::bindHotkeys() -> void { presentation->pauseEmulation.setChecked(!presentation->pauseEmulation.checked()); })); + hotkeys.append(InputHotkey("Frame Advance").onPress([] { + presentation->frameAdvance.doActivate(); + })); + hotkeys.append(InputHotkey("Reset Emulation").onPress([] { program->reset(); })); diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 85806ec1..7e2e4083 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -56,28 +56,24 @@ Presentation::Presentation() { quit.setIcon(Icon::Action::Quit).setText("Quit").onActivate([&] { program->quit(); }); settingsMenu.setText("Settings"); - scaleMenu.setIcon(Icon::Emblem::Image).setText("View"); - smallestScale.setText("Smallest (240p)").onActivate([&] { - settings["View/Size"].setValue("Smallest"); - resizeWindow(); - }); - smallScale.setText("Small (480p)").onActivate([&] { - settings["View/Size"].setValue("Small"); - resizeWindow(); - }); - mediumScale.setText("Medium (720p)").onActivate([&] { - settings["View/Size"].setValue("Medium"); - resizeWindow(); - }); - largeScale.setText("Large (960p)").onActivate([&] { - settings["View/Size"].setValue("Large"); - resizeWindow(); - }); - largestScale.setText("Largest (1200p)").onActivate([&] { - settings["View/Size"].setValue("Largest"); - resizeWindow(); - }); + sizeMenu.setIcon(Icon::Emblem::Image).setText("Size"); + updateSizeMenu(); outputMenu.setIcon(Icon::Emblem::Image).setText("Output"); + centerViewport.setText("Center").onActivate([&] { + settings["View/Output"].setValue("Center"); + resizeViewport(); + }); + scaleViewport.setText("Scale").onActivate([&] { + settings["View/Output"].setValue("Scale"); + resizeViewport(); + }); + stretchViewport.setText("Stretch").onActivate([&] { + settings["View/Output"].setValue("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()); resizeWindow(); @@ -86,16 +82,11 @@ Presentation::Presentation() { settings["View/OverscanCropping"].setValue(overscanCropping.checked()); resizeWindow(); }); - integralScaling.setText("Integral Scaling").setChecked(settings["View/IntegralScaling"].boolean()).onToggle([&] { - settings["View/IntegralScaling"].setValue(integralScaling.checked()); - resizeViewport(); - }); blurEmulation.setText("Blur Emulation").setChecked(settings["View/BlurEmulation"].boolean()).onToggle([&] { settings["View/BlurEmulation"].setValue(blurEmulation.checked()); emulator->set("Blur Emulation", blurEmulation.checked()); }).doToggle(); shaderMenu.setIcon(Icon::Emblem::Image).setText("Shader"); - updateShaders(); synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Blocking"].boolean()).onToggle([&] { settings["Video/Blocking"].setValue(synchronizeVideo.checked()); program->updateVideoBlocking(); @@ -142,17 +133,20 @@ Presentation::Presentation() { program->loadState("quick/undo"); })); speedMenu.setIcon(Icon::Device::Clock).setText("Speed"); - speedSlowest.setText("Slowest (50%)").setProperty("multiplier", "2.0").onActivate([&] { program->updateAudioFrequency(); }); - speedSlow.setText("Slow (75%)").setProperty("multiplier", "1.333").onActivate([&] { program->updateAudioFrequency(); }); - speedNormal.setText("Normal (100%)").setProperty("multiplier", "1.0").onActivate([&] { program->updateAudioFrequency(); }); - speedFast.setText("Fast (150%)").setProperty("multiplier", "0.667").onActivate([&] { program->updateAudioFrequency(); }); - speedFastest.setText("Fastest (200%)").setProperty("multiplier", "0.5").onActivate([&] { program->updateAudioFrequency(); }); + speedSlowest.setText("50% (Slowest)").setProperty("multiplier", "2.0").onActivate([&] { program->updateAudioFrequency(); }); + speedSlow.setText("75% (Slow)").setProperty("multiplier", "1.333").onActivate([&] { program->updateAudioFrequency(); }); + speedNormal.setText("100% (Normal)").setProperty("multiplier", "1.0").onActivate([&] { program->updateAudioFrequency(); }); + speedFast.setText("150% (Fast)").setProperty("multiplier", "0.667").onActivate([&] { program->updateAudioFrequency(); }); + speedFastest.setText("200% (Fastest)").setProperty("multiplier", "0.5").onActivate([&] { program->updateAudioFrequency(); }); pauseEmulation.setText("Pause Emulation").onToggle([&] { if(pauseEmulation.checked()) audio->clear(); }); + frameAdvance.setIcon(Icon::Media::Next).setText("Frame Advance").onActivate([&] { + pauseEmulation.setChecked(false); + program->frameAdvance = true; + }); captureScreenshot.setIcon(Icon::Emblem::Image).setText("Capture Screenshot").onActivate([&] { - if(program->paused()) program->showMessage("The next video frame will be captured"); - program->captureScreenshot = true; + program->captureScreenshot(); }); cheatEditor.setIcon(Icon::Edit::Replace).setText("Cheat Editor ...").onActivate([&] { toolsWindow->show(0); }); stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow->show(1); }); @@ -265,7 +259,7 @@ auto Presentation::drawIcon(uint32_t* output, uint length, uint width, uint heig } auto Presentation::clearViewport() -> void { - if(!video) return; + if(!visible() && !video) return; if(!emulator->loaded()) { viewportLayout.setPadding(); @@ -287,8 +281,6 @@ auto Presentation::clearViewport() -> void { } auto Presentation::resizeViewport() -> void { - if(!emulator->loaded()) return clearViewport(); - uint windowWidth = viewportLayout.geometry().width(); uint windowHeight = viewportLayout.geometry().height(); @@ -296,18 +288,36 @@ auto Presentation::resizeViewport() -> void { uint height = (settings["View/OverscanCropping"].boolean() ? 224.0 : 240.0); uint viewportWidth, viewportHeight; - if(settings["View/IntegralScaling"].boolean()) { + if(!fullScreen()) { + uint widthMultiplier = windowWidth / width; + uint heightMultiplier = windowHeight / height; + uint multiplier = max(1, min(widthMultiplier, heightMultiplier)); + settings["View/Multiplier"].setValue(multiplier); + for(auto item : sizeGroup.objects()) { + if(auto property = item->property("multiplier")) { + if(property.natural() == multiplier) item->setChecked(); + } + } + } + + if(!visible() || !video) return; + if(!emulator->loaded()) return clearViewport(); + + if(settings["View/Output"].text() == "Center") { uint widthMultiplier = windowWidth / width; uint heightMultiplier = windowHeight / height; uint multiplier = min(widthMultiplier, heightMultiplier); viewportWidth = width * multiplier; viewportHeight = height * multiplier; - } else { + } else if(settings["View/Output"].text() == "Scale") { double widthMultiplier = (double)windowWidth / width; double heightMultiplier = (double)windowHeight / height; double multiplier = min(widthMultiplier, heightMultiplier); viewportWidth = width * multiplier; viewportHeight = height * multiplier; + } else if(settings["View/Output"].text() == "Stretch" || 1) { + viewportWidth = windowWidth; + viewportHeight = windowHeight; } //center viewport within viewportLayout by use of viewportLayout padding @@ -322,17 +332,17 @@ auto Presentation::resizeViewport() -> void { } 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 multiplier = 2; - if(settings["View/Size"].text() == "Smallest") multiplier = 1; - if(settings["View/Size"].text() == "Small" ) multiplier = 2; - if(settings["View/Size"].text() == "Medium" ) multiplier = 3; - if(settings["View/Size"].text() == "Large" ) multiplier = 4; - if(settings["View/Size"].text() == "Largest" ) multiplier = 5; + uint multiplier = settings["View/Multiplier"].natural(); + if(!multiplier) multiplier = 2; + setMinimumSize({width, height + StatusHeight}); setSize({width * multiplier, height * multiplier + statusHeight}); resizeViewport(); } @@ -362,8 +372,68 @@ auto Presentation::toggleFullscreenMode() -> void { } } +//generate a list of size multipliers +auto Presentation::updateSizeMenu() -> void { + assert(sizeMenu.actions() == 0); //should only be called once + + //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()) { + 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::updateRecentGames() -> void { loadRecentGame.reset(); + + //remove missing games from list + for(uint index = 0; index < RecentGames;) { + auto games = settings[string{"Game/Recent/", 1 + index}].text(); + bool missing = false; + if(games) { + for(auto& game : games.split("|")) { + if(!inode::exists(game)) missing = true; + } + } + if(missing) { + //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() + ); + } + } else { + index++; + } + } + + //update list for(auto index : range(RecentGames)) { MenuItem item; if(auto game = settings[string{"Game/Recent/", 1 + index}].text()) { @@ -385,6 +455,7 @@ auto Presentation::updateRecentGames() -> void { } loadRecentGame.append(item); } + loadRecentGame.append(MenuSeparator()); loadRecentGame.append(MenuItem().setIcon(Icon::Edit::Clear).setText("Clear List").onActivate([&] { for(auto index : range(RecentGames)) { @@ -434,7 +505,7 @@ auto Presentation::updateShaders() -> void { 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([=] { + item.setText(string{shader}.trimRight(".shader/", 1L)).onActivate([=] { settings["Video/Shader"].setValue({location, shader}); program->updateVideoShader(); }); diff --git a/higan/target-bsnes/presentation/presentation.hpp b/higan/target-bsnes/presentation/presentation.hpp index 60ae2de6..c13147ae 100644 --- a/higan/target-bsnes/presentation/presentation.hpp +++ b/higan/target-bsnes/presentation/presentation.hpp @@ -20,6 +20,7 @@ struct Presentation : Window { auto resizeWindow() -> void; auto updateStatus() -> void; auto toggleFullscreenMode() -> void; + auto updateSizeMenu() -> void; auto clearRecentGames() -> void; auto updateRecentGames() -> void; auto addRecentGame(string location) -> void; @@ -38,16 +39,16 @@ struct Presentation : Window { MenuSeparator quitSeparator{&systemMenu}; MenuItem quit{&systemMenu}; Menu settingsMenu{&menuBar}; - Menu scaleMenu{&settingsMenu}; - MenuItem smallestScale{&scaleMenu}; - MenuItem smallScale{&scaleMenu}; - MenuItem mediumScale{&scaleMenu}; - MenuItem largeScale{&scaleMenu}; - MenuItem largestScale{&scaleMenu}; + 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 aspectCorrection{&outputMenu}; MenuCheckItem overscanCropping{&outputMenu}; - MenuCheckItem integralScaling{&outputMenu}; MenuCheckItem blurEmulation{&outputMenu}; Menu shaderMenu{&settingsMenu}; MenuSeparator settingsSeparatorA{&settingsMenu}; @@ -74,6 +75,7 @@ struct Presentation : Window { MenuRadioItem speedFastest{&speedMenu}; Group speedGroup{&speedSlowest, &speedSlow, &speedNormal, &speedFast, &speedFastest}; MenuCheckItem pauseEmulation{&toolsMenu}; + MenuItem frameAdvance{&toolsMenu}; MenuItem captureScreenshot{&toolsMenu}; MenuSeparator toolsSeparatorB{&toolsMenu}; MenuItem cheatEditor{&toolsMenu}; diff --git a/higan/target-bsnes/program/game.cpp b/higan/target-bsnes/program/game.cpp index 973ef529..2fbf8a5a 100644 --- a/higan/target-bsnes/program/game.cpp +++ b/higan/target-bsnes/program/game.cpp @@ -7,7 +7,8 @@ auto Program::load() -> void { Emulator::audio.reset(2, audio->frequency()); if(emulator->load(media.id)) { gameQueue = {}; - captureScreenshot = false; + screenshot = {}; + frameAdvance = false; if(!verified() && settingsWindow->advanced.warnOnUnverifiedGames.checked()) { //todo: MessageDialog crashes with GTK+; unsure the reason why this happens //once MessageDialog functions, add an "Always" option diff --git a/higan/target-bsnes/program/interface.cpp b/higan/target-bsnes/program/platform.cpp similarity index 94% rename from higan/target-bsnes/program/interface.cpp rename to higan/target-bsnes/program/platform.cpp index 392d2389..f76e4386 100644 --- a/higan/target-bsnes/program/interface.cpp +++ b/higan/target-bsnes/program/platform.cpp @@ -105,6 +105,7 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load { BrowserDialog dialog; + dialog.setParent(*presentation); dialog.setOptions(options); if(id == 1 && name == "Super Famicom" && type == "sfc") { @@ -205,14 +206,13 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig if(height == 480) data += 16 * pitch, height -= 32; } - if(captureScreenshot) { - captureScreenshot = false; - if(auto filename = screenshotPath()) { - if(Encode::BMP::create(filename, (const uint32_t*)data, pitch << 2, width, height, false)) { - showMessage({"Captured screenshot [", Location::file(filename), "]"}); - } - } - } + //this relies on the UI only running between Emulator::Scheduler::Event::Frame events + //this will always be the case; so we can avoid an unnecessary copy or one-frame delay here + //if the core were to exit between a frame event, the next frame might've been only partially rendered + screenshot.data = data; + screenshot.pitch = pitch << 2; + screenshot.width = width; + screenshot.height = height; if(video->lock(output, length, width, height)) { length >>= 2; @@ -225,6 +225,11 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig video->output(); } + if(frameAdvance) { + frameAdvance = false; + presentation->pauseEmulation.setChecked(); + } + static uint frameCounter = 0; static uint64 previous, current; frameCounter++; diff --git a/higan/target-bsnes/program/program.cpp b/higan/target-bsnes/program/program.cpp index f39acaaf..edb96dc0 100644 --- a/higan/target-bsnes/program/program.cpp +++ b/higan/target-bsnes/program/program.cpp @@ -1,5 +1,5 @@ #include "../bsnes.hpp" -#include "interface.cpp" +#include "platform.cpp" #include "game.cpp" #include "game-pak.cpp" #include "game-rom.cpp" diff --git a/higan/target-bsnes/program/program.hpp b/higan/target-bsnes/program/program.hpp index 8a53efbb..0c9bd02e 100644 --- a/higan/target-bsnes/program/program.hpp +++ b/higan/target-bsnes/program/program.hpp @@ -4,7 +4,7 @@ struct Program : Emulator::Platform { auto main() -> void; auto quit() -> void; - //interface.cpp + //platform.cpp auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override; auto load(uint id, string name, string type, string_vector options = {}) -> Emulator::Platform::Load override; auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override; @@ -77,6 +77,7 @@ struct Program : Emulator::Platform { auto showMessage(string text) -> void; auto showFrameRate(string text) -> void; auto updateStatus() -> void; + auto captureScreenshot() -> bool; auto paused() -> bool; auto focused() -> bool; @@ -122,7 +123,15 @@ public: } sufamiTurboA, sufamiTurboB; string_vector gameQueue; - boolean captureScreenshot; + + struct Screenshot { + const uint32* data = nullptr; + uint pitch = 0; + uint width = 0; + uint height = 0; + } screenshot; + + bool frameAdvance = false; uint64 autoSaveTime; diff --git a/higan/target-bsnes/program/utility.cpp b/higan/target-bsnes/program/utility.cpp index 74ff538a..01d2e399 100644 --- a/higan/target-bsnes/program/utility.cpp +++ b/higan/target-bsnes/program/utility.cpp @@ -31,6 +31,20 @@ auto Program::updateStatus() -> void { } } +auto Program::captureScreenshot() -> bool { + if(emulator->loaded() && screenshot.data) { + if(auto filename = screenshotPath()) { + if(Encode::BMP::create(filename, + (const uint32_t*)screenshot.data, screenshot.pitch, screenshot.width, screenshot.height, false + )) { + showMessage({"Captured screenshot [", Location::file(filename), "]"}); + return true; + } + } + } + return false; +} + auto Program::paused() -> bool { if(!emulator->loaded()) return true; if(presentation->pauseEmulation.checked()) return true; diff --git a/higan/target-bsnes/program/video.cpp b/higan/target-bsnes/program/video.cpp index ba1da2eb..e5c66f2b 100644 --- a/higan/target-bsnes/program/video.cpp +++ b/higan/target-bsnes/program/video.cpp @@ -23,6 +23,8 @@ auto Program::updateVideoDriver() -> void { settings["Video/Driver"].setValue("None"); return updateVideoDriver(); } + + presentation->updateShaders(); } auto Program::updateVideoBlocking() -> void { diff --git a/higan/target-bsnes/settings/settings.cpp b/higan/target-bsnes/settings/settings.cpp index af751ae1..8e80187c 100644 --- a/higan/target-bsnes/settings/settings.cpp +++ b/higan/target-bsnes/settings/settings.cpp @@ -40,10 +40,10 @@ Settings::Settings() { set("Input/Frequency", 5); set("Input/Defocus", "Pause"); - set("View/Size", "Small"); + set("View/Multiplier", "2"); + set("View/Output", "Scale"); set("View/AspectCorrection", true); set("View/OverscanCropping", true); - set("View/IntegralScaling", true); set("View/BlurEmulation", true); set("Path/Games", ""); @@ -70,6 +70,7 @@ Settings::Settings() { set("Emulator/Hack/FastPPU/HiresMode7", false); set("Emulator/Hack/FastDSP", true); set("Emulator/Hack/FastSuperFX", "100%"); + set("Emulator/Cheats/Enable", true); set("Crashed", false); } diff --git a/higan/target-bsnes/tools/cheat-editor.cpp b/higan/target-bsnes/tools/cheat-editor.cpp index 11869e56..f934ac0e 100644 --- a/higan/target-bsnes/tools/cheat-editor.cpp +++ b/higan/target-bsnes/tools/cheat-editor.cpp @@ -125,6 +125,15 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { 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()); + if(!enableCheats.checked()) { + program->showMessage("All cheat codes disabled"); + } else { + program->showMessage("Active cheat codes enabled"); + } + synchronizeCodes(); + }); addButton.setText("Add").onActivate([&] { cheatWindow->show(); }); @@ -224,8 +233,10 @@ auto CheatEditor::saveCheats() -> void { auto CheatEditor::synchronizeCodes() -> void { string_vector codes; - for(auto& cheat : cheats) { - if(cheat.enable) codes.append(cheat.code); + if(enableCheats.checked()) { + for(auto& cheat : cheats) { + if(cheat.enable) codes.append(cheat.code); + } } emulator->cheatSet(codes); } diff --git a/higan/target-bsnes/tools/tools.hpp b/higan/target-bsnes/tools/tools.hpp index 70497b94..fd556138 100644 --- a/higan/target-bsnes/tools/tools.hpp +++ b/higan/target-bsnes/tools/tools.hpp @@ -66,6 +66,7 @@ public: HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Button findCheatsButton{&controlLayout, Size{120, 0}}; Widget spacer{&controlLayout, Size{~0, 0}}; + CheckLabel enableCheats{&controlLayout, Size{0, 0}}; Button addButton{&controlLayout, Size{80, 0}}; Button editButton{&controlLayout, Size{80, 0}}; Button removeButton{&controlLayout, Size{80, 0}}; diff --git a/higan/target-higan/GNUmakefile b/higan/target-higan/GNUmakefile index c049dead..036db3fb 100644 --- a/higan/target-higan/GNUmakefile +++ b/higan/target-higan/GNUmakefile @@ -11,10 +11,11 @@ include gba/GNUmakefile include ws/GNUmakefile include processor/GNUmakefile -ui_objects := ui-higan ui-program ui-input -ui_objects += ui-settings ui-tools ui-presentation ui-resource -ui_objects += ruby hiro -ui_objects += $(if $(call streq,$(platform),windows),ui-windows) +objects := ruby hiro $(objects) +objects += ui-higan ui-program ui-input +objects += ui-settings ui-tools ui-presentation ui-resource +objects += $(if $(call streq,$(platform),windows),ui-windows) +objects := $(objects:%=obj/%.o) # platform ifeq ($(platform),windows) @@ -39,20 +40,19 @@ endif include ../ruby/GNUmakefile link += $(rubylink) +obj/ruby.o: ../ruby/ruby.cpp $(call rwildcard,../ruby/) + $(info Compiling $< ...) + @$(compiler) $(rubyflags) -c $< -o $@ + # hiro include ../hiro/GNUmakefile link += $(hirolink) -# rules -objects := $(ui_objects) $(objects) -objects := $(objects:%=obj/%.o) - -obj/ruby.o: ../ruby/ruby.cpp $(call rwildcard,../ruby/) - $(compiler) $(rubyflags) -c $< -o $@ - obj/hiro.o: ../hiro/hiro.cpp $(call rwildcard,../hiro/) - $(compiler) $(hiroflags) -c $< -o $@ + $(info Compiling $< ...) + @$(compiler) $(hiroflags) -c $< -o $@ +# rules obj/ui-higan.o: $(ui)/higan.cpp obj/ui-program.o: $(ui)/program/program.cpp obj/ui-input.o: $(ui)/input/input.cpp @@ -61,12 +61,14 @@ obj/ui-tools.o: $(ui)/tools/tools.cpp obj/ui-presentation.o: $(ui)/presentation/presentation.cpp obj/ui-resource.o: $(ui)/resource/resource.cpp -obj/ui-windows.o: - $(windres) $(ui)/resource/higan.rc obj/ui-windows.o +obj/ui-windows.o: $(ui)/resource/higan.rc + $(info Compiling $< ...) + @$(windres) $(ui)/resource/higan.rc obj/ui-windows.o # targets -build: $(objects) - $(strip $(compiler) -o out/$(name) $(objects) $(link)) +default: information $(objects) + $(info Linking out/$(name) ...) + +@$(strip $(compiler) -o out/$(name) $(objects) $(link)) ifeq ($(platform),macos) rm -rf out/$(name).app mkdir -p out/$(name).app/Contents/MacOS/ diff --git a/higan/target-higan/program/interface.cpp b/higan/target-higan/program/platform.cpp similarity index 100% rename from higan/target-higan/program/interface.cpp rename to higan/target-higan/program/platform.cpp diff --git a/higan/target-higan/program/program.cpp b/higan/target-higan/program/program.cpp index 4e5e3b4f..7505f237 100644 --- a/higan/target-higan/program/program.cpp +++ b/higan/target-higan/program/program.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "interface.cpp" +#include "platform.cpp" #include "medium.cpp" #include "state.cpp" #include "utility.cpp" diff --git a/higan/target-higan/program/program.hpp b/higan/target-higan/program/program.hpp index 4a4248e4..84182a49 100644 --- a/higan/target-higan/program/program.hpp +++ b/higan/target-higan/program/program.hpp @@ -4,7 +4,7 @@ struct Program : Emulator::Platform { auto main() -> void; auto quit() -> void; - //interface.cpp + //platform.cpp auto path(uint id) -> string override; auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override; auto load(uint id, string name, string type, string_vector options = {}) -> Emulator::Platform::Load override; diff --git a/hiro/cocoa/window.cpp b/hiro/cocoa/window.cpp index 3cec6838..9c6514fd 100644 --- a/hiro/cocoa/window.cpp +++ b/hiro/cocoa/window.cpp @@ -330,6 +330,22 @@ auto pWindow::setGeometry(Geometry geometry) -> void { unlock(); } +auto pWindow::setMaximized(bool maximized) -> void { + //todo +} + +auto pWindow::setMaximumSize(Size size) -> void { + //todo +} + +auto pWindow::setMinimized(bool minimized) -> void { + //todo +} + +auto pWindow::setMinimumSize(Size size) -> void { + //todo +} + auto pWindow::setModal(bool modal) -> void { @autoreleasepool { if(modal == true) { diff --git a/hiro/cocoa/window.hpp b/hiro/cocoa/window.hpp index 2e7d87f7..26492f1f 100644 --- a/hiro/cocoa/window.hpp +++ b/hiro/cocoa/window.hpp @@ -44,6 +44,10 @@ struct pWindow : pObject { auto setFocused() -> void override; auto setFullScreen(bool fullScreen) -> void; auto setGeometry(Geometry geometry) -> void; + auto setMaximized(bool maximized) -> void; + auto setMaximumSize(Size size) -> void; + auto setMinimized(bool minimized) -> void; + auto setMinimumSize(Size size) -> void; auto setModal(bool modal) -> void; auto setResizable(bool resizable) -> void; auto setTitle(const string& text) -> void; diff --git a/hiro/core/core.hpp b/hiro/core/core.hpp index 08be56d8..b222840c 100644 --- a/hiro/core/core.hpp +++ b/hiro/core/core.hpp @@ -446,9 +446,10 @@ struct Monitor { Monitor() = delete; static auto count() -> uint; - static auto dpi(uint monitor) -> Position; - static auto geometry(uint monitor) -> Geometry; + static auto dpi(maybe monitor = nothing) -> Position; + static auto geometry(maybe monitor = nothing) -> Geometry; static auto primary() -> uint; + static auto workspace(maybe monitor = nothing) -> Geometry; }; #endif @@ -695,7 +696,11 @@ struct mWindow : mObject { auto fullScreen() const -> bool; auto geometry() const -> Geometry; auto layout() const -> Layout; + auto maximized() const -> bool; + auto maximumSize() const -> Size; auto menuBar() const -> MenuBar; + auto minimized() const -> bool; + auto minimumSize() const -> Size; auto modal() const -> bool; auto onClose(const function& callback = {}) -> type&; auto onDrop(const function& callback = {}) -> type&; @@ -718,6 +723,10 @@ struct mWindow : mObject { auto setFrameSize(Size size) -> type&; auto setFullScreen(bool fullScreen = true) -> type&; auto setGeometry(Geometry geometry) -> type&; + auto setMaximized(bool maximized = true) -> type&; + auto setMaximumSize(Size size = {}) -> type&; + auto setMinimized(bool minimized = true) -> type&; + auto setMinimumSize(Size size = {}) -> type&; auto setModal(bool modal = true) -> type&; auto setPosition(Position position) -> type&; auto setResizable(bool resizable = true) -> type&; @@ -734,6 +743,10 @@ struct mWindow : mObject { bool fullScreen = false; Geometry geometry = {128, 128, 256, 256}; sLayout layout; + bool maximized = false; + Size maximumSize; + bool minimized = false; + Size minimumSize; sMenuBar menuBar; bool modal = false; function onClose; diff --git a/hiro/core/monitor.cpp b/hiro/core/monitor.cpp index a8058c72..63f25c65 100644 --- a/hiro/core/monitor.cpp +++ b/hiro/core/monitor.cpp @@ -4,16 +4,20 @@ auto Monitor::count() -> uint { return pMonitor::count(); } -auto Monitor::dpi(uint monitor) -> Position { - return pMonitor::dpi(monitor); +auto Monitor::dpi(maybe monitor) -> Position { + return pMonitor::dpi(monitor ? monitor() : primary()); } -auto Monitor::geometry(uint monitor) -> Geometry { - return pMonitor::geometry(monitor); +auto Monitor::geometry(maybe monitor) -> Geometry { + return pMonitor::geometry(monitor ? monitor() : primary()); } auto Monitor::primary() -> uint { return pMonitor::primary(); } +auto Monitor::workspace(maybe monitor) -> Geometry { + return pMonitor::workspace(monitor ? monitor() : primary()); +} + #endif diff --git a/hiro/core/shared.hpp b/hiro/core/shared.hpp index 35d002b3..284d3e37 100644 --- a/hiro/core/shared.hpp +++ b/hiro/core/shared.hpp @@ -956,7 +956,11 @@ struct Window : sWindow { auto fullScreen() const { return self().fullScreen(); } auto geometry() const { return self().geometry(); } auto layout() const { return self().layout(); } + auto maximized() const { return self().maximized(); } + auto maximumSize() const { return self().maximumSize(); } auto menuBar() const { return self().menuBar(); } + auto minimized() const { return self().minimized(); } + auto minimumSize() const { return self().minimumSize(); } auto modal() const { return self().modal(); } auto onClose(const function& callback = {}) { return self().onClose(callback), *this; } auto onDrop(const function& callback = {}) { return self().onDrop(callback), *this; } @@ -979,6 +983,10 @@ struct Window : sWindow { auto setFrameSize(Size size) { return self().setFrameSize(size), *this; } auto setFullScreen(bool fullScreen = true) { return self().setFullScreen(fullScreen), *this; } auto setGeometry(Geometry geometry) { return self().setGeometry(geometry), *this; } + auto setMaximized(bool maximized) { return self().setMaximized(maximized), *this; } + auto setMaximumSize(Size size = {}) { return self().setMaximumSize(size), *this; } + auto setMinimized(bool minimized) { return self().setMinimized(minimized), *this; } + auto setMinimumSize(Size size = {}) { return self().setMinimumSize(size), *this; } auto setModal(bool modal = true) { return self().setModal(modal), *this; } auto setPosition(Position position) { return self().setPosition(position), *this; } auto setResizable(bool resizable = true) { return self().setResizable(resizable), *this; } diff --git a/hiro/core/window.cpp b/hiro/core/window.cpp index 0a3b45a2..e7b083a9 100644 --- a/hiro/core/window.cpp +++ b/hiro/core/window.cpp @@ -95,10 +95,26 @@ auto mWindow::layout() const -> Layout { return state.layout; } +auto mWindow::maximized() const -> bool { + return state.maximized; +} + +auto mWindow::maximumSize() const -> Size { + return state.maximumSize; +} + auto mWindow::menuBar() const -> MenuBar { return state.menuBar; } +auto mWindow::minimized() const -> bool { + return state.minimized; +} + +auto mWindow::minimumSize() const -> Size { + return state.minimumSize; +} + auto mWindow::modal() const -> bool { return state.modal; } @@ -245,6 +261,30 @@ auto mWindow::setGeometry(Geometry geometry) -> type& { return *this; } +auto mWindow::setMaximized(bool maximized) -> type& { + state.maximized = maximized; + signal(setMaximized, maximized); + return *this; +} + +auto mWindow::setMaximumSize(Size size) -> type& { + state.maximumSize = size; + signal(setMaximumSize, size); + return *this; +} + +auto mWindow::setMinimized(bool minimized) -> type& { + state.minimized = minimized; + signal(setMinimized, minimized); + return *this; +} + +auto mWindow::setMinimumSize(Size size) -> type& { + state.minimumSize = size; + signal(setMinimumSize, size); + return *this; +} + auto mWindow::setModal(bool modal) -> type& { state.modal = modal; signal(setModal, modal); diff --git a/hiro/gtk/desktop.cpp b/hiro/gtk/desktop.cpp index 958d7220..8ff555f8 100644 --- a/hiro/gtk/desktop.cpp +++ b/hiro/gtk/desktop.cpp @@ -45,6 +45,8 @@ auto pDesktop::workspace() -> Geometry { gdk_screen_get_height(gdk_screen_get_default()) }; #endif + + return {}; } } diff --git a/hiro/gtk/monitor.cpp b/hiro/gtk/monitor.cpp index 8e75c2b9..ac67da5a 100644 --- a/hiro/gtk/monitor.cpp +++ b/hiro/gtk/monitor.cpp @@ -1,25 +1,61 @@ #if defined(Hiro_Monitor) +//GTK 3.22 adds new monitor functions +//using GTK 2.x functions as FreeBSD 10.1 uses GTK 3.8 + namespace hiro { auto pMonitor::count() -> uint { + #if HIRO_GTK==2 || 1 return gdk_screen_get_n_monitors(gdk_screen_get_default()); + #elif HIRO_GTK==3 + return gdk_display_get_n_monitors(gdk_display_get_default()); + #endif } auto pMonitor::dpi(uint monitor) -> Position { - //GTK+ does not support either per-monitor or per-axis DPI reporting + #if HIRO_GTK==2 || 1 + //GTK2 does not support either per-monitor or per-axis DPI reporting float dpi = round(gdk_screen_get_resolution(gdk_screen_get_default())); return {dpi, dpi}; + #elif HIRO_GTK==3 + auto gdkMonitor = gdk_display_get_monitor(gdk_display_get_default(), monitor); + return { + round(gdk_monitor_get_width(gdkMonitor) / (gdk_monitor_get_width_mm(gdkMonitor) / 25.4)), + round(gdk_monitor_get_height(gdkMonitor) / (gdk_monitor_get_height_mm(gdkMonitor) / 25.4)) + }; + #endif } auto pMonitor::geometry(uint monitor) -> Geometry { - GdkRectangle rectangle = {0}; + GdkRectangle rectangle = {}; + #if HIRO_GTK==2 || 1 gdk_screen_get_monitor_geometry(gdk_screen_get_default(), monitor, &rectangle); + #elif HIRO_GTK==3 + auto gdkMonitor = gdk_display_get_monitor(gdk_display_get_default(), monitor); + gdk_monitor_get_geometry(monitor, &rectangle); + #endif return {rectangle.x, rectangle.y, rectangle.width, rectangle.height}; } auto pMonitor::primary() -> uint { + #if HIRO_GTK==2 || 1 return gdk_screen_get_primary_monitor(gdk_screen_get_default()); + #elif HIRO_GTK==3 + return gdk_display_get_primary_monitor(gdk_display_get_default()); + #endif +} + +auto pMonitor::workspace(uint monitor) -> Geometry { + #if HIRO_GTK==2 || 1 + //todo: can this be done on a per-monitor basis with raw Xlib / Win32 APIs? + return pDesktop::workspace(); + #elif HIRO_GTK==3 + auto gdkMonitor = gdk_display_get_monitor(gdk_display_get_default(), monitor); + GdkRectangle rectangle = {}; + gdk_monitor_get_workarea(monitor, &rectangle); + return {rectangle.x, rectangle.y, rectangle.width, rectangle.height}; + #endif } } diff --git a/hiro/gtk/monitor.hpp b/hiro/gtk/monitor.hpp index 70cf8a1d..1effa3da 100644 --- a/hiro/gtk/monitor.hpp +++ b/hiro/gtk/monitor.hpp @@ -7,6 +7,7 @@ struct pMonitor { static auto dpi(uint monitor) -> Position; static auto geometry(uint monitor) -> Geometry; static auto primary() -> uint; + static auto workspace(uint monitor) -> Geometry; }; } diff --git a/hiro/gtk/object.hpp b/hiro/gtk/object.hpp index e1fc4dbb..096743f9 100644 --- a/hiro/gtk/object.hpp +++ b/hiro/gtk/object.hpp @@ -18,10 +18,18 @@ struct pObject { virtual auto setFont(const Font& font) -> void; virtual auto setVisible(bool visible) -> void; - auto locked() const -> bool { return locks != 0 || Application::state.quit; } + auto locked() const -> bool { return locks || Application::state.quit; } auto lock() -> void { ++locks; } auto unlock() -> void { --locks; } + struct Lock { + Lock(pObject& self) : self(self) { self.locks++; } + ~Lock() { self.locks--; } + + pObject& self; + }; + auto acquire() -> Lock { return {*this}; } + mObject& reference; signed locks = 0; }; diff --git a/hiro/gtk/widget/horizontal-scroll-bar.cpp b/hiro/gtk/widget/horizontal-scroll-bar.cpp index 0975665b..fcd81d05 100644 --- a/hiro/gtk/widget/horizontal-scroll-bar.cpp +++ b/hiro/gtk/widget/horizontal-scroll-bar.cpp @@ -33,11 +33,10 @@ auto pHorizontalScrollBar::minimumSize() const -> Size { } auto pHorizontalScrollBar::setLength(unsigned length) -> void { - lock(); + auto lock = acquire(); length += length == 0; gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); - unlock(); } auto pHorizontalScrollBar::setPosition(unsigned position) -> void { diff --git a/hiro/gtk/widget/vertical-scroll-bar.cpp b/hiro/gtk/widget/vertical-scroll-bar.cpp index bc70dd58..81cb45b4 100644 --- a/hiro/gtk/widget/vertical-scroll-bar.cpp +++ b/hiro/gtk/widget/vertical-scroll-bar.cpp @@ -33,11 +33,10 @@ auto pVerticalScrollBar::minimumSize() const -> Size { } auto pVerticalScrollBar::setLength(unsigned length) -> void { - lock(); + auto lock = acquire(); length += length == 0; gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); - unlock(); } auto pVerticalScrollBar::setPosition(unsigned position) -> void { diff --git a/hiro/gtk/window.cpp b/hiro/gtk/window.cpp index 5475a842..e48a4f5a 100644 --- a/hiro/gtk/window.cpp +++ b/hiro/gtk/window.cpp @@ -128,6 +128,10 @@ static auto Window_keyRelease(GtkWidget* widget, GdkEventKey* event, pWindow* p) } static auto Window_sizeAllocate(GtkWidget* widget, GtkAllocation* allocation, pWindow* p) -> void { + //size-allocate is sent before window-state-event when maximizing a window + //this means Window::onSize() handler would have the old maximized state if we used the latter signal + p->_synchronizeState(); + //size-allocate sent from gtk_fixed_move(); detect if layout unchanged and return if(allocation->width == p->lastAllocation.width && allocation->height == p->lastAllocation.height) return; @@ -153,6 +157,20 @@ static auto Window_sizeRequest(GtkWidget* widget, GtkRequisition* requisition, p requisition->height = p->state().geometry.height(); } +static auto Window_stateEvent(GtkWidget* widget, GdkEvent* event, pWindow* p) -> void { + p->_synchronizeState(); + +/*if(event->type == GDK_WINDOW_STATE) { + auto windowStateEvent = (GdkEventWindowState*)event; + if(windowStateEvent->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) { + p->state().maximized = windowStateEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED; + } + if(windowStateEvent->changed_mask & GDK_WINDOW_STATE_ICONIFIED) { + p->state().minimized = windowStateEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED; + } + }*/ +} + auto pWindow::construct() -> void { lastAllocation.width = 0; lastAllocation.height = 0; @@ -204,6 +222,8 @@ auto pWindow::construct() -> void { setDroppable(state().droppable); setGeometry(state().geometry); setResizable(state().resizable); + setMaximized(state().maximized); + setMinimized(state().minimized); setTitle(state().title); g_signal_connect(G_OBJECT(widget), "delete-event", G_CALLBACK(Window_close), (gpointer)this); @@ -224,6 +244,7 @@ auto pWindow::construct() -> void { widgetClass->get_preferred_width = Window_getPreferredWidth; widgetClass->get_preferred_height = Window_getPreferredHeight; #endif + g_signal_connect(G_OBJECT(widget), "window-state-event", G_CALLBACK(Window_stateEvent), (gpointer)this); } auto pWindow::destruct() -> void { @@ -323,10 +344,8 @@ auto pWindow::setGeometry(Geometry geometry) -> void { Geometry margin = frameMargin(); gtk_window_move(GTK_WINDOW(widget), geometry.x() - margin.x(), geometry.y() - margin.y()); - GdkGeometry geom; - geom.min_width = state().resizable ? 1 : state().geometry.width(); - geom.min_height = state().resizable ? 1 : state().geometry.height(); - gtk_window_set_geometry_hints(GTK_WINDOW(widget), GTK_WIDGET(widget), &geom, GDK_HINT_MIN_SIZE); + setMaximumSize(state().maximumSize); + setMinimumSize(state().minimumSize); gtk_widget_set_size_request(formContainer, geometry.width(), geometry.height()); auto time1 = chrono::millisecond(); @@ -337,6 +356,42 @@ auto pWindow::setGeometry(Geometry geometry) -> void { while(chrono::millisecond() - time2 < 20) gtk_main_iteration_do(false); } +auto pWindow::setMaximized(bool maximized) -> void { + auto lock = acquire(); + if(maximized) { + gtk_window_maximize(GTK_WINDOW(widget)); + } else { + gtk_window_unmaximize(GTK_WINDOW(widget)); + } +} + +auto pWindow::setMaximumSize(Size size) -> void { + if(size.height()) size.setHeight(size.height() + _menuHeight() + _statusHeight()); + + GdkGeometry geometry; + geometry.max_width = !state().resizable ? state().geometry.width() : size.width() ? size.width() : 32767; + geometry.max_height = !state().resizable ? state().geometry.height() : size.height() ? size.height() : 32767; + gtk_window_set_geometry_hints(GTK_WINDOW(widget), nullptr, &geometry, GDK_HINT_MAX_SIZE); +} + +auto pWindow::setMinimized(bool minimized) -> void { + auto lock = acquire(); + if(minimized) { + gtk_window_iconify(GTK_WINDOW(widget)); + } else { + gtk_window_deiconify(GTK_WINDOW(widget)); + } +} + +auto pWindow::setMinimumSize(Size size) -> void { + if(size.height()) size.setHeight(size.height() + _menuHeight() + _statusHeight()); + + GdkGeometry geometry; + geometry.min_width = !state().resizable ? state().geometry.width() : size.width() ? size.width() : 1; + geometry.min_height = !state().resizable ? state().geometry.height() : size.height() ? size.height() : 1; + gtk_window_set_geometry_hints(GTK_WINDOW(widget), nullptr, &geometry, GDK_HINT_MIN_SIZE); +} + auto pWindow::setModal(bool modal) -> void { if(modal) { gtk_window_set_modal(GTK_WINDOW(widget), true); @@ -479,6 +534,69 @@ auto pWindow::_statusHeight() const -> signed { return gtk_widget_get_visible(gtkStatus) ? settings.geometry.statusHeight : 0; } +//GTK doesn't add gtk_window_is_maximized() until 3.12; +//and doesn't appear to have a companion gtk_window_is_(hidden,iconic,minimized); +//so we have to do this the hard way +auto pWindow::_synchronizeState() -> void { + if(!gtk_widget_get_realized(widget)) return; + + #if defined(DISPLAY_WINDOWS) + auto window = GDK_WINDOW_HWND(gtk_widget_get_window(widget)); + + bool maximized = IsZoomed(window); + bool minimized = IsIconic(window); + + bool doSize = false; + if(state().minimized != minimized) doSize = true; + + state().maximized = maximized; + state().minimized = minimized; + + if(doSize) self().doSize(); + #endif + + #if defined(DISPLAY_XORG) + auto display = XOpenDisplay(nullptr); + int screen = DefaultScreen(display); + auto window = GDK_WINDOW_XID(gtk_widget_get_window(widget)); + XlibAtom wmState = XInternAtom(display, "_NET_WM_STATE", XlibTrue); + XlibAtom atom; + int format; + unsigned long items, after; + unsigned char* data = nullptr; + int result = XGetWindowProperty( + display, window, wmState, 0, LONG_MAX, XlibFalse, AnyPropertyType, &atom, &format, &items, &after, &data + ); + auto atoms = (unsigned long*)data; + if(result == Success) { + bool maximizedHorizontal = false; + bool maximizedVertical = false; + bool minimized = false; + + for(auto index : range(items)) { + auto memory = XGetAtomName(display, atoms[index]); + auto name = string{memory}; + if(name == "_NET_WM_STATE_MAXIMIZED_HORZ") maximizedHorizontal = true; + if(name == "_NET_WM_STATE_MAXIMIZED_VERT") maximizedVertical = true; + if(name == "_NET_WM_STATE_HIDDEN") minimized = true; + XFree(memory); + } + + bool doSize = false; + //maximize sends size-allocate, which triggers doSize() + if(state().minimized != minimized) doSize = true; + + //windows do not act bizarrely when maximized in only one direction + //so for this reason, consider a window maximized only if it's in both directions + state().maximized = maximizedHorizontal && maximizedVertical; + state().minimized = minimized; + + if(doSize) self().doSize(); + } + XCloseDisplay(display); + #endif +} + } #endif diff --git a/hiro/gtk/window.hpp b/hiro/gtk/window.hpp index 3b79e228..dd0e1e77 100644 --- a/hiro/gtk/window.hpp +++ b/hiro/gtk/window.hpp @@ -20,6 +20,10 @@ struct pWindow : pObject { auto setFocused() -> void override; auto setFullScreen(bool fullScreen) -> void; auto setGeometry(Geometry geometry) -> void; + auto setMaximized(bool maximized) -> void; + auto setMaximumSize(Size size) -> void; + auto setMinimized(bool minimized) -> void; + auto setMinimumSize(Size size) -> void; auto setModal(bool modal) -> void; auto setResizable(bool resizable) -> void; auto setTitle(const string& title) -> void; @@ -37,6 +41,7 @@ struct pWindow : pObject { auto _setStatusText(const string& text) -> void; auto _setStatusVisible(bool visible) -> void; auto _statusHeight() const -> signed; + auto _synchronizeState() -> void; GtkWidget* widget = nullptr; GtkWidget* menuContainer = nullptr; diff --git a/hiro/qt/window.cpp b/hiro/qt/window.cpp index 541e2b72..8f8a5f22 100644 --- a/hiro/qt/window.cpp +++ b/hiro/qt/window.cpp @@ -154,6 +154,22 @@ auto pWindow::setGeometry(Geometry geometry) -> void { unlock(); } +auto pWindow::setMaximized(bool maximized) -> void { + //todo +} + +auto pWindow::setMaximumSize(Size size) -> void { + //todo +} + +auto pWindow::setMinimized(bool minimized) -> void { + //todo +} + +auto pWindow::setMinimumSize(Size size) -> void { + //todo +} + auto pWindow::setModal(bool modal) -> void { if(modal) { //windowModality can only be enabled while window is invisible diff --git a/hiro/qt/window.hpp b/hiro/qt/window.hpp index c9abee81..da618263 100644 --- a/hiro/qt/window.hpp +++ b/hiro/qt/window.hpp @@ -20,6 +20,10 @@ struct pWindow : pObject { auto setFocused() -> void override; auto setFullScreen(bool fullScreen) -> void; auto setGeometry(Geometry geometry) -> void; + auto setMaximized(bool maximized) -> void; + auto setMaximumSize(Size size) -> void; + auto setMinimized(bool minimized) -> void; + auto setMinimumSize(Size size) -> void; auto setModal(bool modal) -> void; auto setResizable(bool resizable) -> void; auto setTitle(const string& text) -> void; diff --git a/hiro/windows/utility.cpp b/hiro/windows/utility.cpp index f415d6b2..41a1d33c 100644 --- a/hiro/windows/utility.cpp +++ b/hiro/windows/utility.cpp @@ -339,24 +339,17 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms break; } - #if defined(Hiro_TableView) - case AppMessage::TableView_doPaint: { - if(auto tableView = (mTableView*)lparam) { - if(auto self = tableView->self()) InvalidateRect(self->hwnd, nullptr, true); - } + case WM_SIZE: { + bool maximized = IsZoomed(pWindow->hwnd); + bool minimized = IsIconic(pWindow->hwnd); + + window->state.maximized = maximized; + window->state.minimized = minimized; + + //todo: call Window::doSize() ? break; } - case AppMessage::TableView_onActivate: { - if(auto tableView = (mTableView*)lparam) tableView->doActivate(); - break; - } - - case AppMessage::TableView_onChange: { - if(auto tableView = (mTableView*)lparam) tableView->doChange(); - } - #endif - case WM_HSCROLL: case WM_VSCROLL: { if(!lparam) break; @@ -389,6 +382,24 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms break; } + + #if defined(Hiro_TableView) + case AppMessage::TableView_doPaint: { + if(auto tableView = (mTableView*)lparam) { + if(auto self = tableView->self()) InvalidateRect(self->hwnd, nullptr, true); + } + break; + } + + case AppMessage::TableView_onActivate: { + if(auto tableView = (mTableView*)lparam) tableView->doActivate(); + break; + } + + case AppMessage::TableView_onChange: { + if(auto tableView = (mTableView*)lparam) tableView->doChange(); + } + #endif } return windowProc(hwnd, msg, wparam, lparam); diff --git a/hiro/windows/window.cpp b/hiro/windows/window.cpp index e0f28ebc..7866b687 100644 --- a/hiro/windows/window.cpp +++ b/hiro/windows/window.cpp @@ -136,6 +136,27 @@ auto pWindow::setGeometry(Geometry geometry) -> void { unlock(); } +auto pWindow::setMaximized(bool maximized) -> void { + if(state().minimized) return; + lock(); + ShowWindow(hwnd, maximized ? SW_MAXIMIZED : SW_SHOWNOACTIVATE); + unlock(); +} + +auto pWindow::setMaximumSize(Size size) -> void { + //todo +} + +auto pWindow::setMinimized(bool minimized) -> void { + lock(); + ShowWindow(hwnd, minimized ? SW_MINIMIZED : state().maximized ? SW_MAXIMIZED : SW_SHOWNOACTIVATE); + unlock(); +} + +auto pWindow::setMinimumSize(Size size) -> void { + //todo +} + auto pWindow::setModal(bool modality) -> void { if(modality) { _modalityUpdate(); diff --git a/hiro/windows/window.hpp b/hiro/windows/window.hpp index 9b130b23..55da425c 100644 --- a/hiro/windows/window.hpp +++ b/hiro/windows/window.hpp @@ -21,6 +21,10 @@ struct pWindow : pObject { auto setFont(const Font& font) -> void override; auto setFullScreen(bool fullScreen) -> void; auto setGeometry(Geometry geometry) -> void; + auto setMaximized(bool maximized) -> void; + auto setMaximumSize(Size size) -> void; + auto setMinimized(bool minimized) -> void; + auto setMinimumSize(Size size) -> void; auto setModal(bool modal) -> void; auto setResizable(bool resizable) -> void; auto setTitle(string text) -> void; diff --git a/icarus/GNUmakefile b/icarus/GNUmakefile index d2748531..45d73e4d 100644 --- a/icarus/GNUmakefile +++ b/icarus/GNUmakefile @@ -9,8 +9,9 @@ objects := obj/hiro.o objects += obj/icarus.o objects += $(if $(call streq,$(platform),windows),obj/resource.o) -all: $(objects) - $(strip $(compiler) -o out/$(name) $(objects) $(link) $(hirolink)) +default: information $(objects) + $(info Linking out/$(name) ...) + +@$(strip $(compiler) -o out/$(name) $(objects) $(link) $(hirolink)) ifeq ($(platform),macos) rm -rf out/$(name).app mkdir -p out/$(name).app/Contents/MacOS/ @@ -21,13 +22,16 @@ ifeq ($(platform),macos) endif obj/hiro.o: ../hiro/hiro.cpp - $(compiler) $(hiroflags) -o obj/hiro.o -c ../hiro/hiro.cpp + $(info Compiling $< ...) + @$(compiler) $(hiroflags) -o obj/hiro.o -c ../hiro/hiro.cpp obj/icarus.o: icarus.cpp $(call rwildcard,core/) $(call rwildcard,heuristics/) $(call rwildcard,ui/) - $(compiler) $(cppflags) $(flags) -o obj/icarus.o -c icarus.cpp + $(info Compiling $< ...) + @$(compiler) $(cppflags) $(flags) -o obj/icarus.o -c icarus.cpp -obj/resource.o: - $(windres) data/$(name).rc obj/resource.o +obj/resource.o: data/$(name).rc + $(info Compiling $< ...) + @$(windres) data/$(name).rc obj/resource.o clean: ifeq ($(platform),macos) diff --git a/nall/GNUmakefile b/nall/GNUmakefile index dd69a74f..8a2935eb 100644 --- a/nall/GNUmakefile +++ b/nall/GNUmakefile @@ -31,11 +31,11 @@ ifeq ($(platform),) # common commands ifeq ($(uname),) - rm = del /q $(subst /,\,$1) - rmdir = del /s /q $(subst /,\,$1) && if exist $(subst /,\,$1) (rmdir /s /q $(subst /,\,$1)) + rm = $(info Deleting $1 ...) @del /q $(subst /,\,$1) + rmdir = $(info Deleting $1 ...) @del /s /q $(subst /,\,$1) && if exist $(subst /,\,$1) (rmdir /s /q $(subst /,\,$1)) else - rm = rm -f $1 - rmdir = rm -rf $1 + rm = $(info Deleting $1 ...) @rm -f $1 + rmdir = $(info Deleting $1 ...) @rm -rf $1 endif endif @@ -75,6 +75,12 @@ else ifeq ($(build),performance) flags += -O3 -DBUILD_PERFORMANCE endif +# link-time optimization +ifeq ($(lto),true) + flags += -fwhole-program -flto -fno-fat-lto-objects + link += -fwhole-program -flto=jobserver +endif + # openmp support ifeq ($(openmp),true) # macOS Xcode does not ship with OpenMP support @@ -129,6 +135,15 @@ endif # paths prefix := $(HOME)/.local +# targets +all: default; + +information: + $(info Compiler Flags:) + $(foreach n,$(sort $(call unique,$(flags))),$(if $(filter-out -I%,$n),$(info $([space]) $n))) + $(info Linker Flags:) + $(foreach n,$(sort $(call unique,$(link))),$(if $(filter-out -l%,$n),$(info $([space]) $n))) + # function rwildcard(directory, pattern) rwildcard = \ $(strip \ diff --git a/nall/string/format.hpp b/nall/string/format.hpp index c5e6fe1c..9f8a6974 100644 --- a/nall/string/format.hpp +++ b/nall/string/format.hpp @@ -71,11 +71,13 @@ auto string_format::append() -> string_format& { template auto print(P&&... p) -> void { string s{forward

(p)...}; fwrite(s.data(), 1, s.size(), stdout); + fflush(stdout); } template auto print(FILE* fp, P&&... p) -> void { string s{forward

(p)...}; fwrite(s.data(), 1, s.size(), fp); + if(fp == stdout || fp == stderr) fflush(fp); } template auto pad(const T& value, long precision, char padchar) -> string {