diff --git a/ExampleDemo/Button.h b/ExampleDemo/Button.h index c52fdbb..383e457 100644 --- a/ExampleDemo/Button.h +++ b/ExampleDemo/Button.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include // Custom class definitions struct button; @@ -24,7 +24,7 @@ struct button { } } - void draw(sf::RenderTexture& surface, sf::Text& sftext, float x, float y) { + void draw(IRenderer& renderer, sf::Text& sftext, float x, float y) { if (isHovering) { sprite.setColor(sf::Color(200, 200, 200)); } @@ -34,12 +34,12 @@ struct button { sprite.setPosition(sf::Vector2f(x, y)); sprite.setOrigin(sprite.getGlobalBounds().width / 2.0f, sprite.getGlobalBounds().height / 2.0f); - surface.draw(sprite); + renderer.submit(Clone(sprite)); sftext.setString(text); sftext.setOrigin(sftext.getGlobalBounds().width / 2.0f, sftext.getGlobalBounds().height / 2.0f); sftext.setPosition(sf::Vector2f(x, y - sftext.getGlobalBounds().height / 2.0f)); - surface.draw(sftext); + renderer.submit(Clone(sftext)); } }; diff --git a/ExampleDemo/CustomRenderer.h b/ExampleDemo/CustomRenderer.h new file mode 100644 index 0000000..bcd9701 --- /dev/null +++ b/ExampleDemo/CustomRenderer.h @@ -0,0 +1,190 @@ +#pragma once +#include +#include +#include +#include +#include + +/** + @class Draw3D + @brief A render event with material data that can render 2D graphics as psuedo-3D +*/ +struct Draw3D : sw::RenderSource { + sw::glsl::deferred::MeshData data; // !< aggregate data for the 2D model + + explicit Draw3D(sf::Sprite* spr, + sf::Texture* normal, + sf::Texture* emissive = nullptr, + float metallic = 0, + float specular = 0) : + RenderSource(spr), + data({ spr, normal, emissive, metallic, specular }) + {} + + Draw3D WithZ(float z) { + data.WithZ(z); + return *this; + } +}; + +/** + @class Light + @brief A render event with lighting data for rendering lights + If used in a SimpleRenderer, will draw the light as a circle +*/ +struct Light : sw::RenderSource { + float radius{}; + sf::Vector3f position{}; + sf::Color color{ sf::Color::White }; + sf::CircleShape circle{}; + float specular{}; + float cutoff{}; + + explicit Light(float radius, + sf::Vector3f position, + sf::Color color, + float specular = 0.0f, + float cutoff = 0.0f) : + RenderSource(&circle), + radius(radius), + position(position), + color(color), + specular(specular), + cutoff(cutoff) + { + circle.setPointCount(360); + circle.setRadius(radius); + circle.setFillColor(color); + circle.setPosition({ position.x, position.y }); + + sf::FloatRect bounds = circle.getLocalBounds(); + circle.setOrigin(bounds.width/2, bounds.height/2); + }; +}; + +/** + @brief Transform 2D vector into a 3D vector + @param xy a 2D vector + @param z a float representing the 3rd dimension + @return sf::Vector3f with a z value +*/ +sf::Vector3f WithZ(const sf::Vector2f xy, float z) { + return sf::Vector3f(xy.x, xy.y, z); +} + +/** + @class CustomRenderer + @brief A custom renderer example that uses swoosh shaders to compose a 3D scene + Uses `Draw3D` render event as a tag to draw the sprite multiple times with different G-buffer texture values + Uses `Light` render event as a tag to calculate the final lighting in the scene + The end result is a partial implementation of a deferred renderer commonly used in advanced 3D applications +*/ +class CustomRenderer : public sw::Renderer { + sf::RenderTexture position, diffuse, normal, esm, out; + glsl::deferred::PositionPass positionShader; + glsl::deferred::LightPass lightShader; + glsl::deferred::EmissivePass emissiveShader; + glsl::deferred::MeshPass meshShader; + std::list memForward; + std::list mem3D; + std::list memLight; + +public: + CustomRenderer(const sf::View& view) { + const unsigned int ux = (unsigned int)view.getSize().x; + const unsigned int uy = (unsigned int)view.getSize().y; + const sf::Vector2u size = sf::Vector2u(ux, uy); + + position.create(size.x, size.y); + diffuse.create(size.x, size.y); + normal.create(size.x, size.y); + esm.create(size.x, size.y); + out.create(size.x, size.y); + + meshShader.configure(&diffuse, &normal, &esm); + positionShader.configure(-100, 100, view, &position); + lightShader.configure(view, &position, &diffuse, &normal, &esm); + emissiveShader.configure(&diffuse, &esm); + } + + sw::SystemCompatibilityScore checkSystemCompatibility() const override { + // TODO: show how to grade system specs + return sw::SystemCompatibilityScore::sufficient; + } + + void draw() override { + mem3D.sort([](Draw3D& a, Draw3D& b) { return a.data.getPosition3D().z < b.data.getPosition3D().z; }); + + for (Draw3D& source : mem3D) { + + // bake the currect normals + meshShader.setMeshData(source.data); + meshShader.apply(*this); + + // bake the position data + positionShader.setMeshData(source.data); + positionShader.apply(*this); + } + + lightShader.clearLights(); + bool applyLight = memLight.size() > 0; + for (Light& source : memLight) { + lightShader.addLight(source.radius, + source.position, + source.color, + source.specular, + source.cutoff); + } + + // render light geometry over the scene + if(applyLight) { + lightShader.apply(*this); + } + + // draw emissive lighting + emissiveShader.apply(*this); + + // draw forward rendered content + for (sw::RenderSource& source : memForward) { + out.draw(*source.drawable()); + } + + // clear the buffer data + memForward.clear(); + mem3D.clear(); + memLight.clear(); + } + + void clear(sf::Color color) override { + // for G-buffers the clear color must always be transparent + position.clear(sf::Color::Transparent); + diffuse.clear(sf::Color::Transparent); + normal.clear(sf::Color::Transparent); + esm.clear(sf::Color::Transparent); + out.clear(color); + } + + void setView(const sf::View& view) override { + position.setView(view); + diffuse.setView(view); + normal.setView(view); + esm.setView(view); + out.setView(view); + } + + sf::RenderTexture& getRenderTextureTarget() override { + return out; + } + + void onEvent(const sw::RenderSource& event) override { + memForward.push_back(event); + } + + void onEvent(const Draw3D& event) override { + mem3D.emplace_back(event); + } + + void onEvent(const Light& event) override { + memLight.emplace_back(event); + } +}; \ No newline at end of file diff --git a/ExampleDemo/Demo.cpp b/ExampleDemo/Demo.cpp index 6a75f30..6c8ed6c 100644 --- a/ExampleDemo/Demo.cpp +++ b/ExampleDemo/Demo.cpp @@ -1,14 +1,11 @@ //This file contains the C++ entry main function and demonstrates //using the Activity Controller with SFML's main loop - -#include #include +#include #include -#include "Scenes/MainMenuScene.h" - -#include - -using namespace swoosh; +#include +#include "Scenes/IntroScene.h" +#include "CustomRenderer.h" int main() { @@ -17,16 +14,54 @@ int main() window.setVerticalSyncEnabled(true); window.setMouseCursorVisible(false); - // Create an AC with the current window as our target to draw to - ActivityController app(window); + // 11/23/2022 (NEW BEHAVIOR!) + // Swoosh now enables custom render pipelines and + // can switch between them in real-time + sw::RenderEntries renderOptions; + renderOptions + .enroll("custom", window.getView()) + .enroll("simple", window.getView()); + + // Create an AC with the current window as our target to draw to + sw::ActivityController app(window, renderOptions); // 10/9/2020 // For mobile devices or low-end GPU's, you can request optimized // effects any time by setting the performance quality to // one of the following: { realtime, reduced, mobile } - app.optimizeForPerformance(quality::realtime); + app.optimizeForPerformance(sw::quality::realtime); // app.optimizeForPerformance(quality::mobile); // <-- uncomment me! + // 06/12/2024 + // The AC needs a renderer in order to draw anything. + // This was added to allow programmers to check for platform compatibilities + // before commiting to constructing renderers which will require resources. + // After the options are build, the programmer can iterate through the list + // and check their SystemCompatibilityScores to determine which to use. + app.buildRenderEntries(); + + std::string errors; + if(renderOptions.built() && renderOptions.countValid() > 0) { + for(auto& iter : renderOptions.list()) { + auto score = iter.getInstance().checkSystemCompatibility(); + if(score == sw::SystemCompatibilityScore::sufficient) { + app.activateRenderEntry(iter.getIndex()); + continue; + } + if (score == sw::SystemCompatibilityScore::build_error) { + errors += iter.getError() + "\n"; + } + // For example's sake, we will free insufficient renderers + iter.free(); + } + } else { + // Running this application without a renderer is pointless. + std::cout << "Application cannot render as configured.\n"; + std::cout << "Errors: " << errors << std::endl; + return -1; // Quit + } + + // (DEFAULT BEHAVIOR!) // Add the Main Menu Scene as the first and only scene in our stack // This is our starting point for the user @@ -36,7 +71,8 @@ int main() // Swoosh now supports generating blank activities from window contents! // The segue will copy the window at startup and use it as part of // the screen transition as demonstrated here - app.push::to>(); + app.push::to>(); + // app.push(); // uncomment this and comment the line above for old behavior sf::Texture* cursorTexture = loadTexture(CURSOR_PATH); sf::Sprite cursor; @@ -67,6 +103,18 @@ int main() else if (event.type == sf::Event::GainedFocus) { pause = false; } + else if (event.type == sf::Event::KeyPressed) { + // Toggle to different renderers using F-keys + sf::Keyboard::Key code = event.key.code; + if (code == sf::Keyboard::F1 && app.activateRenderEntry(0)) { + const std::string& name = app.getActiveRenderEntry().getName(); + window.setTitle("Swoosh Demo (renderer=" + name + ")"); + } + else if (code == sf::Keyboard::F2 && app.activateRenderEntry(1)) { + const std::string& name = app.getActiveRenderEntry().getName(); + window.setTitle("Swoosh Demo (renderer=" + name + ")"); + } + } } // do not update segues when the window is frozen @@ -77,12 +125,13 @@ int main() // We clear after updating so that other items can copy the screen's contents window.clear(); - // draw() will directly draw onto the window's render buffer - app.draw(); - + // Track the mouse and create a light source for this pass on the mouse! sf::Vector2f mousepos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); cursor.setPosition(mousepos); + // draw() will directly draw onto the window's render buffer + app.draw(); + // Draw the mouse cursor over everything else window.draw(cursor); diff --git a/ExampleDemo/ResourcePaths.h b/ExampleDemo/ResourcePaths.h index a5c4e6a..fc4fa8e 100644 --- a/ExampleDemo/ResourcePaths.h +++ b/ExampleDemo/ResourcePaths.h @@ -4,21 +4,33 @@ constexpr auto MANUAL_FONT = "resources/manual.ttf"; constexpr auto SFML_PATH = "resources/SFML.png"; constexpr auto MENU_BG_PATH = "resources/menu.png"; -constexpr auto PURPLE_BG_PATH = "resources/SpaceShooterRedux/Backgrounds/purple.png"; +constexpr auto MENU_BG_N_PATH = "resources/menu_n.png"; +constexpr auto GAME_BG_PATH = "resources/SpaceShooterRedux/Backgrounds/purple.png"; +constexpr auto GAME_BG_N_PATH = "resources/SpaceShooterRedux/Backgrounds/purple_n.png"; +constexpr auto GAME_BG_E_PATH = "resources/SpaceShooterRedux/Backgrounds/background_e.png"; constexpr auto CURSOR_PATH = "resources/SpaceShooterRedux/PNG/UI/cursor.png"; constexpr auto STAR_PATH = "resources/star.png"; constexpr auto BLUE_BTN_PATH = "resources/SpaceShooterRedux/PNG/UI/buttonBlue.png"; constexpr auto RED_BTN_PATH = "resources/SpaceShooterRedux/PNG/UI/buttonRed.png"; constexpr auto GREEN_BTN_PATH = "resources/SpaceShooterRedux/PNG/UI/buttonGreen.png"; -constexpr auto METEOR_BIG_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_big1.png"; +constexpr auto METEOR_BIG_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_big4.png"; constexpr auto METEOR_MED_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_med1.png"; constexpr auto METEOR_SMALL_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_small1.png"; constexpr auto METEOR_TINY_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_tiny1.png"; +constexpr auto METEOR_BIG_N_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_big4_n.png"; +constexpr auto METEOR_MED_N_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_med1_n.png"; +constexpr auto METEOR_SMALL_N_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_small1_n.png"; +constexpr auto METEOR_TINY_N_PATH = "resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_tiny1_n.png"; constexpr auto SHIELD_HIGH_PATH = "resources/SpaceShooterRedux/PNG/Effects/shield1.png"; constexpr auto SHIELD_LOW_PATH = "resources/SpaceShooterRedux/PNG/Effects/shield2.png"; -constexpr auto PLAYER_PATH = "resources/SpaceShooterRedux/PNG/playerShip3_orange.png"; +constexpr auto PLAYER_PATH = "resources/SpaceShooterRedux/PNG/playerShip1_orange.png"; +constexpr auto PLAYER_N_PATH= "resources/SpaceShooterRedux/PNG/playerShip1_n.png"; +constexpr auto PLAYER_E_PATH = "resources/SpaceShooterRedux/PNG/playerShip1_e.png"; +constexpr auto PLAYER_TRAIL_PATH = "resources/SpaceShooterRedux/PNG/Lasers/laserBlue08.png"; constexpr auto LASER_BEAM_PATH = "resources/SpaceShooterRedux/PNG/Lasers/laserGreen10.png"; constexpr auto ENEMY_PATH = "resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3.png"; +constexpr auto ENEMY_N_PATH = "resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_n.png"; +constexpr auto ENEMY_E_PATH = "resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_e.png"; constexpr auto EXTRA_LIFE_PATH = "resources/SpaceShooterRedux/PNG/Power-ups/star_gold.png"; const char* NUMERAL_PATH[11] = { @@ -42,6 +54,7 @@ constexpr auto LOSE_SFX_PATH = "resources/SpaceShooterRedux/Bonus/sfx_los constexpr auto LASER1_SFX_PATH = "resources/SpaceShooterRedux/Bonus/sfx_laser1.ogg"; constexpr auto LASER2_SFX_PATH = "resources/SpaceShooterRedux/Bonus/sfx_laser2.ogg"; constexpr auto TWO_TONE_SFX_PATH = "resources/SpaceShooterRedux/Bonus/sfx_twoTone.ogg"; +constexpr auto EXPLODE_SFX_PATH = "resources/SpaceShooterRedux/Bonus/sfx_explode.ogg"; constexpr auto THEME_MUSIC_PATH = "resources/theme.ogg"; constexpr auto INGAME_MUSIC_PATH = "resources/ingame.ogg"; diff --git a/ExampleDemo/SaveFile.h b/ExampleDemo/SaveFile.h index 2bc7be5..4cbd973 100644 --- a/ExampleDemo/SaveFile.h +++ b/ExampleDemo/SaveFile.h @@ -3,11 +3,15 @@ #include #include -struct save { +struct SaveFile { std::vector names; std::vector scores; - const bool empty() { return names.empty(); } + ~SaveFile() { + std::cout << "inside dconstructor!" << std::endl; + } + + const bool empty() const { return names.empty(); } void writeToFile(std::string path) { std::ofstream outfile(path, std::ofstream::trunc); @@ -43,7 +47,7 @@ struct save { scores.clear(); std::ifstream infile(path); - char name[4]; + char name[4]{}; int score; if (!infile) { infile.close(); return; } diff --git a/ExampleDemo/Scenes/AboutScene.h b/ExampleDemo/Scenes/AboutScene.h index cba887f..7472d9a 100644 --- a/ExampleDemo/Scenes/AboutScene.h +++ b/ExampleDemo/Scenes/AboutScene.h @@ -10,27 +10,26 @@ #include #include #include -#include +#include #include #include #include -#define TEXT_BLOCK_INFO "Swoosh is an Activity and Segue mini library\n" \ - "designed to make complex screen transitions\n" \ - "a thing of the past.\n" \ - "This is a proof-of-concept demo showcasing\n" \ - "its features and includes helpful utilities\n" \ - "for your SFML apps or games.\n\n" \ - "Fork at\ngithub.com/TheMaverickProgrammer/Swoosh" - -#define CONTROLS_INFO ">> Left click to shoot\n\n"\ - ">> Right click to boost and dodge\n\n" \ - ">> Collect stars for extra life\n\n" - -using namespace swoosh; -using namespace swoosh::types; - -class AboutScene : public Activity { +const char* TEXT_BLOCK_INFO = +"Swoosh is an Activity and Segue mini library\n" \ +"designed to make complex screen transitions\n" \ +"a thing of the past.\n" \ +"This is a proof-of-concept demo showcasing\n" \ +"its features and includes helpful utilities\n" \ +"for your SFML apps or games.\n\n" \ +"Fork at\ngithub.com/TheMaverickProgrammer/Swoosh"; + +const char* CONTROLS_INFO = +">> Left click to shoot\n\n"\ +">> Right click to boost and dodge\n\n" \ +">> Collect stars for extra life\n\n"; + +class AboutScene : public sw::Activity { private: sf::Texture * btn; sf::Texture * sfmlTexture; @@ -49,12 +48,12 @@ class AboutScene : public Activity { float screenMid; float screenBottom; - Timer timer; + sw::Timer timer; bool inFocus; bool canClick; public: - AboutScene(ActivityController& controller) : Activity(&controller) { + AboutScene(sw::ActivityController& controller) : Activity(&controller) { canClick = false; font.loadFromFile(GAME_FONT); @@ -71,7 +70,7 @@ class AboutScene : public Activity { sfmlTexture = loadTexture(SFML_PATH); sfml = sf::Sprite(*sfmlTexture); sfml.setScale(0.7f, 0.7f); - setOrigin(sfml, 0.60f, 0.60f); + sw::setOrigin(sfml, 0.60f, 0.60f); sf::Vector2u windowSize = getController().getVirtualWindowSize(); setView(windowSize); @@ -94,7 +93,7 @@ class AboutScene : public Activity { } void onUpdate(double elapsed) override { - timer.update(sf::seconds(elapsed)); + timer.update(sf::seconds((float)elapsed)); double offset = 0; if (timer.getElapsed().asSeconds() > 3) { @@ -114,8 +113,12 @@ class AboutScene : public Activity { selectFX.play(); if (goback.text == "FIN") { - using effect = segue, sec<2>>; - getController().pop(); + struct Reason { + std::string message; + }; + + using tx = segue, arg::sec<2>>; + getController().pop(std::string("Goodbye from the AboutScene!"), false, 12); } else { goback.text = "FIN"; @@ -144,25 +147,24 @@ class AboutScene : public Activity { void onResume() override { } - void onDraw(sf::RenderTexture& surface) override { + void onDraw(sw::IRenderer& renderer) override { sf::RenderWindow& window = getController().getWindow(); - surface.clear(sf::Color::Black); - - surface.draw(sfml); + renderer.clear(sf::Color::Black); + renderer.submit(&sfml); text.setFont(manual); text.setPosition(sf::Vector2f(screenMid, 200)); text.setFillColor(sf::Color::White); text.setString(info); - setOrigin(text, 0.5f, 0); + sw::setOrigin(text, 0.5f, 0); - surface.draw(text); + renderer.submit(sw::Immediate(&text)); text.setFont(font); text.setFillColor(sf::Color::Black); - setOrigin(text, 0.5f, 0.5f); - goback.draw(surface, text, screenMid, screenBottom - 40); + sw::setOrigin(text, 0.5f, 0.5f); + goback.draw(renderer, text, screenMid, screenBottom - 40); } void onEnd() override { diff --git a/ExampleDemo/Scenes/GameplayScene.h b/ExampleDemo/Scenes/GameplayScene.h index 698bef6..6f2b9a3 100644 --- a/ExampleDemo/Scenes/GameplayScene.h +++ b/ExampleDemo/Scenes/GameplayScene.h @@ -1,13 +1,14 @@ #pragma once +#include "../CustomRenderer.h" #include "../TextureLoader.h" #include "../Particle.h" #include "../ResourcePaths.h" -#include "../SaveFile.h" +#include "HiScoreScene.h" #include #include -#include +#include #include #include #include @@ -15,23 +16,27 @@ class HiScoreScene; -using namespace swoosh; -using namespace swoosh::game; -using namespace swoosh::types; - -class GameplayScene : public Activity { +class GameplayScene : public sw::Activity { private: - sf::Texture* bgTexture; + sf::Texture* btn; + sf::Texture* bgTexture, * bgNormal, * bgEmissive; sf::Sprite bg; sf::Texture* playerTexture; + sf::Texture* playerNormal; + sf::Texture* playerEsm; particle player; + + sf::Texture* trailTexture; std::vector trails; sf::Texture* enemyTexture; + sf::Texture* enemyNormal; + sf::Texture* enemyEsm; std::vector enemies; - sf::Texture * meteorBig, *meteorMed, *meteorSmall, *meteorTiny, *btn; + sf::Texture * meteorBigN, *meteorMedN, *meteorSmallN, *meteorTinyN; + sf::Texture* meteorBig, * meteorMed, * meteorSmall, * meteorTiny; std::vector meteors; sf::Texture *laserTexture; @@ -55,8 +60,10 @@ class GameplayScene : public Activity { sf::SoundBuffer shieldFX; sf::SoundBuffer gameOverFX; sf::SoundBuffer extraLifeFX; + sf::SoundBuffer explodeFX; sf::Sound laserChannel; sf::Sound shieldChannel; + sf::Sound explodeChannel; sf::Sound extraLifeChannel; sf::Sound gameOverChannel; sf::Music ingameMusic; @@ -71,9 +78,9 @@ class GameplayScene : public Activity { bool mouseRelease; bool inFocus; - save& savefile; + SaveFile& savefile; public: - GameplayScene(ActivityController& controller, save& savefile) : savefile(savefile), Activity(&controller) { + GameplayScene(sw::ActivityController& controller, SaveFile& savefile) : savefile(savefile), Activity(&controller) { mousePressed = mouseRelease = inFocus = isExtraLifeSpawned = false; ingameMusic.openFromFile(INGAME_MUSIC_PATH); @@ -84,17 +91,23 @@ class GameplayScene : public Activity { shieldFX.loadFromFile(SHIELD_DOWN_SFX_PATH); gameOverFX.loadFromFile(LOSE_SFX_PATH); extraLifeFX.loadFromFile(TWO_TONE_SFX_PATH); + explodeFX.loadFromFile(EXPLODE_SFX_PATH); laserChannel.setBuffer(laserFX); shieldChannel.setBuffer(shieldFX); gameOverChannel.setBuffer(gameOverFX); extraLifeChannel.setBuffer(extraLifeFX); + explodeChannel.setBuffer(explodeFX); sf::Vector2u windowSize = getController().getVirtualWindowSize(); setView(windowSize); - bgTexture = loadTexture(PURPLE_BG_PATH); + bgTexture = loadTexture(GAME_BG_PATH); bgTexture->setRepeated(true); + bgNormal = loadTexture(GAME_BG_N_PATH); + bgNormal->setRepeated(true); + bgEmissive = loadTexture(GAME_BG_E_PATH); + bgEmissive->setRepeated(true); bg = sf::Sprite(*bgTexture); bg.setTextureRect({ 0, 0, (int)windowSize.x, (int)windowSize.y }); @@ -103,21 +116,31 @@ class GameplayScene : public Activity { meteorSmall = loadTexture(METEOR_SMALL_PATH); meteorTiny = loadTexture(METEOR_TINY_PATH); + meteorBigN = loadTexture(METEOR_BIG_N_PATH); + meteorMedN = loadTexture(METEOR_MED_N_PATH); + meteorSmallN = loadTexture(METEOR_SMALL_N_PATH); + meteorTinyN = loadTexture(METEOR_TINY_N_PATH); + laserTexture = loadTexture(LASER_BEAM_PATH); shieldTexture = loadTexture(SHIELD_LOW_PATH); enemyTexture = loadTexture(ENEMY_PATH); - extraLifeTexture = loadTexture(EXTRA_LIFE_PATH); + enemyNormal = loadTexture(ENEMY_N_PATH); + enemyEsm = loadTexture(ENEMY_E_PATH); + extraLifeTexture = loadTexture(EXTRA_LIFE_PATH); star = sf::Sprite(*extraLifeTexture); - setOrigin(star, 0.5, 0.5); + sw::setOrigin(star, 0.5, 0.5); playerTexture = loadTexture(PLAYER_PATH); + playerNormal = loadTexture(PLAYER_N_PATH); + playerEsm = loadTexture(PLAYER_E_PATH); player.sprite = sf::Sprite(*playerTexture); + sw::setOrigin(player.sprite, 0.5, 0.5); - setOrigin(player.sprite, 0.5, 0.5); + trailTexture = loadTexture(PLAYER_TRAIL_PATH); shield = sf::Sprite(*shieldTexture); - setOrigin(shield, 0.5, 0.5); + sw::setOrigin(shield, 0.5, 0.5); for (int i = 0; i < 11; i++) { numeralTexture[i] = loadTexture(NUMERAL_PATH[i]); @@ -143,7 +166,7 @@ class GameplayScene : public Activity { particle enemy; enemy.sprite = sf::Sprite(*enemyTexture); - setOrigin(enemy.sprite, 0.5, 0.5); + sw::setOrigin(enemy.sprite, 0.5, 0.5); sf::Vector2u windowSize = getController().getVirtualWindowSize(); @@ -166,6 +189,7 @@ class GameplayScene : public Activity { player.speed = sf::Vector2f(0, 0); player.sprite.setPosition(player.pos); player.friction = sf::Vector2f(0.96f, 0.96f); + sw::setOrigin(player.sprite, 0.5, 0.5); alpha = 0; hasShield = true; } @@ -177,28 +201,58 @@ class GameplayScene : public Activity { } void onUpdate(double elapsed) override { - sf::RenderWindow& window = getController().getWindow(); - auto windowSize = getController().getVirtualWindowSize(); + auto& C = getController(); + sf::RenderWindow& window = C.getWindow(); + auto windowSize = C.getVirtualWindowSize(); // End the game if the player is out of lives OR escape key is pressed if (lives < 0 || sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)) { // Some segues can be customized like Checkerboard effect using custom = CheckerboardCustom<40, 40>; - using effect = segue>; - getController().push>(savefile); + using tx = segue>; + + // NOTE: This will never be called from HiScoreScene because + // that screen _rewinds_ back to the title scene. + // This code is left here to demonstrate that behavior. + // In action, you will never see this function called. + auto onReturn = [this](const sw::Context& context) { + std::cout << "GamePlayScene popped with data: " << context.type() << std::endl; + }; + + C.push>(savefile) + .take(onReturn); } for (auto& m : meteors) { m.pos += sf::Vector2f(m.speed.x * (float)elapsed, m.speed.y * (float)elapsed); m.sprite.setPosition(m.pos); m.sprite.setRotation(m.pos.x); + + const sf::Vector2u window = C.getVirtualWindowSize(); + if (m.pos.x > window.x + 100) { + m.pos.x = -50.0f; + } else if (m.pos.x < -100) { + m.pos.x = (float)window.x + 50.f; + } + + if (m.pos.y > (float)window.y + 100) { + m.pos.y = -50.0f; + } + else if (m.pos.y < -100) { + m.pos.y = (float)window.y + 50.0f; + } } int i = 0; for (auto& t : trails) { t.sprite.setPosition(t.pos); t.sprite.setScale((float)(t.life / t.lifetime), (float)(t.life / t.lifetime)); - t.sprite.setColor(sf::Color(t.sprite.getColor().r, t.sprite.getColor().g, t.sprite.getColor().b, (sf::Uint8)(10.0f * (t.life / t.lifetime)))); + t.sprite.setColor(sf::Color( + t.sprite.getColor().r, + t.sprite.getColor().g, + t.sprite.getColor().b, + (sf::Uint8)(10.0f * (t.life / t.lifetime)) + )); t.life -= elapsed; if (t.life <= 0) { @@ -216,10 +270,11 @@ class GameplayScene : public Activity { if (e.lifetime == 0) { for (auto& l : lasers) { if (e.lifetime != 0) break; // Reward player once - if (doesCollide(l.sprite, e.sprite)) { + if (sw::doesCollide(l.sprite, e.sprite)) { l.life = 0; - e.lifetime = 1; // trigger scale out + e.lifetime = 1.0; // trigger scale out score += 1000; + explodeChannel.play(); } } } @@ -230,7 +285,7 @@ class GameplayScene : public Activity { } if (lives >= 0 && e.lifetime == 0) { - if (alpha >= 255.0 && doesCollide(e.sprite, player.sprite)) { + if (alpha >= 255.0 && sw::doesCollide(e.sprite, player.sprite)) { if (hasShield && !killShield) { shieldChannel.play(); killShield = true; // give us time to protect from other enemies @@ -249,12 +304,12 @@ class GameplayScene : public Activity { e.lifetime = 1.0; // trigger scale out on this enemy } - double angle = angleTo(player.pos, e.pos); + double angle = sw::angleTo(player.pos, e.pos); e.sprite.setRotation(90.0f + (float)angle); e.sprite.setPosition(e.pos); - sf::Vector2f dir = directionTo(player.pos, e.pos); + sf::Vector2f dir = sw::directionTo(player.pos, e.pos); sf::Vector2f delta; delta.x = dir.x * 2.0f; delta.y = dir.y * 2.0f; @@ -280,11 +335,6 @@ class GameplayScene : public Activity { l.sprite.setPosition(l.pos); l.life -= elapsed; - double ratio = 3*l.life / l.lifetime; - ratio = std::min(ratio, 1.0); - - l.sprite.setColor(sf::Color((sf::Uint8)(ratio*l.sprite.getColor().r), (sf::Uint8)(ratio*l.sprite.getColor().g), (sf::Uint8)(ratio*l.sprite.getColor().b), 255)); - if (l.life <= 0) { lasers.erase(lasers.begin() + i); continue; @@ -293,7 +343,7 @@ class GameplayScene : public Activity { i++; } - if (rand() % 50 == 0 && enemies.size() < 10 && inFocus) { + if (rand() % 50 == 0 && enemies.size() < 20 && inFocus) { spawnEnemy(); if (rand() % 30 == 0 && !isExtraLifeSpawned) { @@ -302,7 +352,7 @@ class GameplayScene : public Activity { star.setPosition((float)(rand() % windowSize.x), (float)(rand() % windowSize.y)); // do not spawn on top of player - while (doesCollide(star, player.sprite)) { + while (sw::doesCollide(star, player.sprite)) { star.setPosition((float)(rand() % windowSize.x), (float)(rand() % windowSize.y)); } } @@ -311,7 +361,7 @@ class GameplayScene : public Activity { if (lives < 0) return; // do not update player logic if (isExtraLifeSpawned) { - if (doesCollide(star, player.sprite)) { + if (sw::doesCollide(star, player.sprite)) { isExtraLifeSpawned = false; extraLifeChannel.play(); lives = std::min(lives+1, 9); @@ -320,12 +370,12 @@ class GameplayScene : public Activity { } sf::Vector2f mousepos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); - double angle = angleTo(mousepos, player.pos); + double angle = sw::angleTo(mousepos, player.pos); player.sprite.setRotation(90.0f + (float)angle); if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Right)) { - sf::Vector2f dir = directionTo(mousepos, player.pos); + sf::Vector2f dir = sw::directionTo(mousepos, player.pos); sf::Vector2f delta = player.speed; delta.x += dir.x * 30.0f * (float)elapsed; delta.y += dir.y * 30.0f * (float)elapsed; @@ -333,6 +383,8 @@ class GameplayScene : public Activity { player.speed = delta; particle trail = player; + trail.sprite.setTexture(*trailTexture, true); + sw::setOrigin(trail.sprite, 0.5, 0.5); trail.life = trail.lifetime = 1.0; // secs trails.insert(trails.begin(), trail); } @@ -365,12 +417,12 @@ class GameplayScene : public Activity { if (!mousePressed) { particle laser; laser.sprite = sf::Sprite(*laserTexture); - setOrigin(laser.sprite, 0.5, 0.5); + sw::setOrigin(laser.sprite, 0.5, 0.5); laser.pos = player.pos; laser.sprite.setRotation(90.0f + (float)angle); laser.sprite.setPosition(laser.pos); - sf::Vector2f dir = directionTo(mousepos, laser.pos); + sf::Vector2f dir = sw::directionTo(mousepos, laser.pos); sf::Vector2f delta; delta.x = dir.x * 500.0f; delta.y = dir.y * 500.0f; @@ -389,6 +441,11 @@ class GameplayScene : public Activity { if(hasShield && killShield) hasShield = false; + + // Left here as an example on how safe it is to clear the stack anywhere: + // if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Num0)) { + // C.clearStackSafely(); + // } } void onLeave() override { @@ -406,6 +463,8 @@ class GameplayScene : public Activity { } void onEnter() override { + auto& C = getController(); + std::cout << "DemoScene OnEnter called" << std::endl; for (int i = 50; i > 0; i--) { @@ -436,7 +495,7 @@ class GameplayScene : public Activity { p.sprite = sf::Sprite(*meteorTiny); } - auto windowSize = getController().getVirtualWindowSize(); + auto windowSize = C.getVirtualWindowSize(); p.pos = sf::Vector2f((float)(rand() % windowSize.x), (float)(rand() % windowSize.y)); p.sprite.setPosition(p.pos); p.sprite.setRotation(p.pos.x); @@ -451,31 +510,76 @@ class GameplayScene : public Activity { } - void onDraw(sf::RenderTexture& surface) override { - sf::RenderWindow& window = getController().getWindow(); + void onDraw(sw::IRenderer& renderer) override { + auto& C = getController(); + const bool isCustomRenderer = C.getActiveRenderEntry().getName() == "custom"; + sf::RenderWindow& window = C.getWindow(); + auto windowSize = C.getVirtualWindowSize(); - surface.draw(bg); + // Track the mouse and create a light source for this pass on the mouse! + sf::Vector2f mousepos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); + + // We can filter what we submit to the renderer by checking the current renderer's name or ID + if (isCustomRenderer) { + // Draw a light tracking the cursor + renderer.submit(Light(256.0f, WithZ(mousepos, 10.f), sf::Color(100U, 100U, 150U), 1.0)); + + // Draw a light in the scene so we can see everything + sf::Vector2f center = sf::Vector2f(windowSize.x / 2.0f, windowSize.y / 2.0f); + renderer.submit(Light(1000.0f, WithZ(center, 100.0f), sf::Color(255U, 205U, 255U, 150U))); + } + + renderer.submit(Draw3D(&bg, bgNormal).WithZ(-100)); for (auto& t : trails) { - surface.draw(t.sprite); + renderer.submit(Draw3D(&t.sprite, nullptr, trailTexture)); } for (auto& m : meteors) { - surface.draw(m.sprite); + sf::Texture* normal = meteorTinyN; + const sf::Texture* spriteTexture = m.sprite.getTexture(); + if (spriteTexture == meteorBig) { + normal = meteorBigN; + } else if (spriteTexture == meteorMed) { + normal = meteorMedN; + } + else if (spriteTexture == meteorSmall) { + normal = meteorSmallN; + } + // else - handled by default value of `normal` + + renderer.submit(Draw3D(&m.sprite, normal).WithZ(-50)); } for (auto& e : enemies) { - surface.draw(e.sprite); + renderer.submit(Draw3D(&e.sprite, enemyNormal, enemyEsm)); + + if (e.lifetime > 0) { + const float alpha = std::max(0.f, (float)(e.life / e.lifetime)); + const float beta = 1.0f - alpha; + const sf::Uint8 ch = sf::Uint8(alpha*255); + renderer.submit(Light( + 100.0f + (200.0f*beta), + WithZ(e.sprite.getPosition(), 100.0f), + sf::Color(255, ch, 0, ch), 10.0f, 0.5f) + ); + } } for (auto& l : lasers) { - surface.draw(l.sprite); + renderer.submit(Draw3D(&l.sprite, nullptr, laserTexture).WithZ(-1.0f)); + + if (isCustomRenderer) { + renderer.submit(Light( + 100.0, + WithZ(l.sprite.getPosition(), 9.0f), + sf::Color(0, 215, 0, 255), 20.0f) + ); + } } - auto windowSize = getController().getVirtualWindowSize(); - text.setString(std::string("score: ") + std::to_string(score)); - setOrigin(text, 1, 0); + sw::setOrigin(text, 1, 0); text.setPosition(sf::Vector2f((float)windowSize.x - 50.0f, 0.0f)); if (alpha < 255) { @@ -485,30 +589,34 @@ class GameplayScene : public Activity { text.setFillColor(sf::Color::White); } - surface.draw(text); + renderer.submit(&text); - if (isExtraLifeSpawned) surface.draw(star); + if (isExtraLifeSpawned) renderer.submit(&star); if (lives >= 0) { - surface.draw(player.sprite); + renderer.submit(Draw3D(&player.sprite, playerNormal, playerEsm, 0.5f)); if (hasShield) { shield.setPosition(player.pos); shield.setRotation(player.sprite.getRotation()); - surface.draw(shield); + renderer.submit(&shield); } numeral = sf::Sprite(*numeralTexture[10]); // X numeral.setPosition(player.pos.x, player.pos.y - 100); - surface.draw(numeral); + + // NOTE: `numeral` sprite is re-used so we must clone before submitting! + renderer.submit(sw::Clone(numeral)); numeral = sf::Sprite(*numeralTexture[lives]); numeral.setPosition(player.pos.x + 20, player.pos.y - 100); - surface.draw(numeral); + + // NOTE: The original `numeral` will not affect the clone and vice-versa + renderer.submit(&numeral); playerLife.setPosition(player.pos.x - 40, player.pos.y - 100); - surface.draw(playerLife); + renderer.submit(&playerLife); } } diff --git a/ExampleDemo/Scenes/HiscoreScene.h b/ExampleDemo/Scenes/HiscoreScene.h index 276bd43..c28d15c 100644 --- a/ExampleDemo/Scenes/HiscoreScene.h +++ b/ExampleDemo/Scenes/HiscoreScene.h @@ -6,7 +6,7 @@ #include "../SaveFile.h" #include -#include +#include #include #include #include @@ -14,12 +14,9 @@ #include #include -using namespace swoosh; -using namespace swoosh::types; - class MainMenuScene; -class HiScoreScene : public Activity { +class HiScoreScene : public sw::Activity { private: sf::Texture * meteorBig, * meteorMed, * meteorSmall, * meteorTiny, * btn; @@ -32,22 +29,20 @@ class HiScoreScene : public Activity { sf::SoundBuffer buffer; sf::Sound selectFX; - save& hiscore; - - int lives; - float screenDiv; float screenMid; float screenBottom; - Timer waitTime; + sw::Timer waitTime; double scrollOffset; bool inFocus; public: - HiScoreScene(ActivityController& controller, save& data) : hiscore(data), Activity(&controller) { + SaveFile& saveFile; + + HiScoreScene(sw::ActivityController& controller, SaveFile& save) : saveFile(save), Activity(&controller) { // Proof that this is the same save file in memory as it is passed around the scenes - std::cout << "savefile address is " << &data << std::endl; + std::cout << "savefile address is " << &save << std::endl; // keep our window size dimensions consistent based on the initial window size when the AC was created auto windowSize = getController().getVirtualWindowSize(); @@ -70,9 +65,9 @@ class HiScoreScene : public Activity { screenMid = windowSize.x / 2.0f; screenDiv = windowSize.x / 4.0f; - if (hiscore.empty()) { - hiscore.writeToFile(SAVE_FILE_PATH); - hiscore.loadFromFile(SAVE_FILE_PATH); + if (saveFile.empty()) { + saveFile.writeToFile(SAVE_FILE_PATH); + saveFile.loadFromFile(SAVE_FILE_PATH); } waitTime.start(); @@ -92,7 +87,7 @@ class HiScoreScene : public Activity { } void onUpdate(double elapsed) override { - waitTime.update(sf::seconds(elapsed)); + waitTime.update(sf::seconds((float)elapsed)); goback.update(getController().getWindow()); @@ -100,8 +95,9 @@ class HiScoreScene : public Activity { selectFX.play(); // Rewind lets us pop back to a particular scene in our stack history - using effect = segue>; - bool found = getController().rewind>(); + using fx = segue>; + using tx = fx::to; + const bool found = getController().rewind(saveFile); // should never happen // but your games may need to check so here it is an example @@ -112,7 +108,7 @@ class HiScoreScene : public Activity { if (waitTime.getElapsed().asSeconds() > 3) { // If the scroll offset is greater than the height of all drawn scores - if (scrollOffset > 200 + (hiscore.names.size() * 100)) { + if (scrollOffset > 200 + (saveFile.names.size() * 100)) { // We hit them all, reset scrollOffset = 0; waitTime.reset(); @@ -166,7 +162,9 @@ class HiScoreScene : public Activity { p.sprite = sf::Sprite(*meteorTiny); } - p.pos = sf::Vector2f((float)(rand() % getController().getWindow().getSize().x), (float)(rand() % getController().getWindow().getSize().y)); + p.pos = sf::Vector2f((float)(rand() % getController().getWindow().getSize().x), + (float)(rand() % getController().getWindow().getSize().y)); + p.sprite.setPosition(p.pos); p.sprite.setRotation(p.pos.x); @@ -178,38 +176,38 @@ class HiScoreScene : public Activity { void onResume() override { } - void onDraw(sf::RenderTexture& surface) override { + void onDraw(sw::IRenderer& renderer) override { sf::RenderWindow& window = getController().getWindow(); for (auto& m : meteors) { - surface.draw(m.sprite); + renderer.submit(&m.sprite); } text.setFillColor(sf::Color::Yellow); text.setPosition(sf::Vector2f(screenMid, 100)); text.setString("Hi Scores"); - setOrigin(text, 0.5, 0.5); - surface.draw(text); + sw::setOrigin(text, 0.5, 0.5); + renderer.submit(sw::Immediate(&text)); text.setFillColor(sf::Color::White); - for (int i = 0; i < hiscore.names.size(); i++) { - std::string name = hiscore.names[i]; - int score = hiscore.scores[i]; + for (int i = 0; i < saveFile.names.size(); i++) { + std::string name = saveFile.names[i]; + int score = saveFile.scores[i]; text.setString(name); text.setPosition(sf::Vector2f((float)(screenDiv), (float)(200 + (i*100) - scrollOffset))); - setOrigin(text, 0.5, 0.5); - surface.draw(text); + sw::setOrigin(text, 0.5, 0.5); + renderer.submit(sw::Immediate(&text)); text.setString(std::to_string(score)); text.setPosition(sf::Vector2f((float)(screenDiv * 3), (float)(200 + (i*100) - scrollOffset))); - setOrigin(text, 0.5, 0.5); - surface.draw(text); + sw::setOrigin(text, 0.5, 0.5); + renderer.submit(sw::Immediate(&text)); } text.setFillColor(sf::Color::Black); - goback.draw(surface, text, screenMid, screenBottom - 40); + goback.draw(renderer, text, screenMid, screenBottom - 40); } void onEnd() override { diff --git a/ExampleDemo/Scenes/IntroScene.h b/ExampleDemo/Scenes/IntroScene.h new file mode 100644 index 0000000..37c2820 --- /dev/null +++ b/ExampleDemo/Scenes/IntroScene.h @@ -0,0 +1,99 @@ +#pragma once + +#include "MainMenuScene.h" +#include "../TextureLoader.h" +#include "../Button.h" +#include "../ResourcePaths.h" + +#include +#include +#include +#include +#include +#include + +const char* LOADING = "NOW LOADING"; + +// TODO: what to do visually with this scene? +class IntroScene : public sw::Activity{ +private: + sf::Font font; + sf::Text text; + + sw::Timer timer; + + bool inFocus; +public: + IntroScene(sw::ActivityController& controller) : Activity(&controller) { + font.loadFromFile(GAME_FONT); + text.setFont(font); + text.setString(LOADING); + text.setFillColor(sf::Color::White); + sw::setOrigin(text, 0.5f, 0.5f); + + sf::Vector2u windowSize = getController().getVirtualWindowSize(); + setView(windowSize); + text.setPosition(windowSize.x * 0.5f, windowSize.y * 0.5f); + + inFocus = false; + timer.start(); + } + + void onStart() override { + inFocus = true; + } + + void onUpdate(double elapsed) override { + timer.update(sf::seconds((float)elapsed)); + + // faux paux "loading" time + if (timer.getElapsed().asSeconds() > 3) { + using fx = segue; + using tx = fx::to; + + auto onReturn = [](sw::Context& context) { + // We can check for previous contexts which were adopted + auto& prev = context.previous(); + if (!prev.has_value()) return; + + sw::Context& prevContext = prev.value(); + std::cout << "prevContext typename: " << prevContext.type() << std::endl; + if (!prevContext.has()) return; + auto& [str, b, i] = prevContext.read(); + std::cout << str << ", " << b << ", " << i << std::endl; + + }; + getController() + .push() + .take(onReturn); + + // Reset so we can return and kick off the effect again + timer.reset(); + } + } + + void onLeave() override { + inFocus = false; + } + + void onExit() override { + } + + void onEnter() override { + + } + + void onResume() override { + } + + void onDraw(sw::IRenderer& renderer) override { + + renderer.clear(sf::Color::Black); + renderer.submit(sw::Immediate(&text)); + } + + void onEnd() override { + } + + ~IntroScene() { } +}; diff --git a/ExampleDemo/Scenes/MainMenuScene.h b/ExampleDemo/Scenes/MainMenuScene.h index 820bcc0..8c7bb35 100644 --- a/ExampleDemo/Scenes/MainMenuScene.h +++ b/ExampleDemo/Scenes/MainMenuScene.h @@ -3,6 +3,7 @@ #include "GameplayScene.h" #include "HiscoreScene.h" #include "AboutScene.h" +#include "../CustomRenderer.h" #include "../TextureLoader.h" #include "../Particle.h" #include "../Button.h" @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -40,21 +42,20 @@ // end segue effects #include -#include +#include #include #include #include -#define GAME_TITLE "Swoosh Interactive Demo" -#define PLAY_OPTION "Play" -#define SCORE_OPTION "HiScore" -#define ABOUT_OPTION "About" +const char* GAME_TITLE = "Swoosh Interactive Demo"; +const char* PLAY_OPTION = "Play"; +const char* SCORE_OPTION = "HiScore"; +const char* ABOUT_OPTION = "About"; +const char* QUIT_OPTION = "Quit"; -using namespace swoosh::types; - -class MainMenuScene : public Activity { +class MainMenuScene : public sw::Activity { private: - sf::Texture* bgTexture; + sf::Texture* bgTexture, *bgNormal; sf::Texture* starTexture; sf::Texture* blueButton, *redButton, *greenButton; @@ -74,11 +75,11 @@ class MainMenuScene : public Activity { bool inFocus; bool fadeMusic; - Timer timer; // for onscreen effects. Or we could have stored the total elapsed from the update function - save savefile; + sw::Timer timer; // for onscreen effects. Or we could have stored the total elapsed from the update function + SaveFile savefile; public: - MainMenuScene(ActivityController& controller) : Activity(&controller) { + MainMenuScene(sw::ActivityController& controller) : Activity(&controller) { setView(controller.getVirtualWindowSize()); savefile.loadFromFile(SAVE_FILE_PATH); @@ -87,6 +88,7 @@ class MainMenuScene : public Activity { fadeMusic = false; bgTexture = loadTexture(MENU_BG_PATH); + bgNormal = loadTexture(MENU_BG_N_PATH); bg = sf::Sprite(*bgTexture); starTexture = loadTexture(STAR_PATH); @@ -104,18 +106,22 @@ class MainMenuScene : public Activity { // Create the buttons button menuOption; - menuOption.sprite.setTexture(*blueButton); + menuOption.sprite.setTexture(*greenButton); menuOption.text = PLAY_OPTION; buttons.push_back(menuOption); - menuOption.sprite.setTexture(*redButton); + menuOption.sprite.setTexture(*blueButton); menuOption.text = SCORE_OPTION; buttons.push_back(menuOption); - menuOption.sprite.setTexture(*greenButton); + menuOption.sprite.setTexture(*blueButton); menuOption.text = ABOUT_OPTION; buttons.push_back(menuOption); + menuOption.sprite.setTexture(*redButton); + menuOption.text = QUIT_OPTION; + buttons.push_back(menuOption); + // Load sounds buffer.loadFromFile(SHIELD_UP_SFX_PATH); selectFX.setBuffer(buffer); @@ -132,10 +138,10 @@ class MainMenuScene : public Activity { } void onUpdate(double elapsed) override { - timer.update(sf::seconds(elapsed)); + timer.update(sf::seconds((float)elapsed)); if (!inFocus && fadeMusic) { - themeMusic.setVolume(themeMusic.getVolume() * 0.90f); // fades out the music + themeMusic.setVolume(themeMusic.getVolume() * 0.98f); // fades out the music } int i = 0; @@ -144,9 +150,12 @@ class MainMenuScene : public Activity { p.pos += sf::Vector2f(p.speed.x * (float)elapsed, p.speed.y * (float)elapsed); p.sprite.setPosition(p.pos); - p.sprite.setScale(2.0f*static_cast(p.life / p.lifetime), 2.0f*static_cast(p.life / p.lifetime)); + p.sprite.setScale( + 2.0f*float(p.life / p.lifetime), + 2.0f*float(p.life / p.lifetime) + ); - auto color = p.sprite.getColor(); + sf::Color color = p.sprite.getColor(); color.a = (sf::Uint8)(255.0 * (p.life / p.lifetime)); p.sprite.setColor(color); @@ -168,17 +177,45 @@ class MainMenuScene : public Activity { selectFX.play(); if (b.text == PLAY_OPTION) { - getController().push, sec<3>>::to>(savefile); + using fx = sw::segue; + using tx = fx::to; + getController().push(savefile); + fadeMusic = true; } else if (b.text == SCORE_OPTION) { - using segue = segue>; - using intent = segue::to; - - getController().push(savefile); + using fx = segue>; + using tx = fx::to; + + auto onReturn = + [this](sw::Context& context) { + // Notice that this callback happens ONLY when we return + // _directly_ from the HiScoreScene from this option and not from + // the PLAY_OPTION flow. + if (!context.has()) return; + SaveFile& s = context.read(); + + std::cout << "Recent hiscore was: " << s.scores.back() << std::endl; + }; + + getController() + .push(savefile) // pass savefile into next scene's ctor + .take(onReturn); // when we return, obtain data passed up } else if (b.text == ABOUT_OPTION) { - getController().push>::to>(); + using fx = segue>; + using tx = fx::to; + + // adopt() stores the context data to forward when this scene also + // pops off the stack. The alternative would be to take(Context&), + // then check, somehow store the data manually, and finally pass + // this data back wherever pop() is called. + // adopt() conveniently does this for you. + getController().push().adopt(); + } + else if (b.text == QUIT_OPTION) { + using fx = segue; + getController().pop(); } } } @@ -199,13 +236,9 @@ class MainMenuScene : public Activity { void onEnter() override { std::cout << "MainMenuScene OnEnter called" << std::endl; - } - void onResume() override { - timer.reset(); - inFocus = true; // If fadeMusic == true, then we were coming from demo, the music changes @@ -218,7 +251,7 @@ class MainMenuScene : public Activity { std::cout << "MainMenuScene OnResume called" << std::endl; - for (int i = 100; i > 0; i--) { + for (int i = 50; i > 0; i--) { int randNegative = rand() % 2 == 0 ? -1 : 1; int randSpeedX = rand() % 80; randSpeedX *= randNegative; @@ -226,36 +259,41 @@ class MainMenuScene : public Activity { particle p; p.sprite = sf::Sprite(*starTexture); - p.pos = sf::Vector2f((float)(rand() % getController().getVirtualWindowSize().x), (float)(getController().getVirtualWindowSize().y)); + p.pos = sf::Vector2f( + (float)(rand() % getController().getVirtualWindowSize().x), + (float)(getController().getVirtualWindowSize().y) + ); p.speed = sf::Vector2f((float)randSpeedX, (float)-randSpeedY); p.friction = sf::Vector2f(0.99999f, 0.9999f); p.life = 3.0; p.lifetime = 3.0; p.sprite.setPosition(p.pos); + sw::setOrigin(p.sprite, 0.5, 0.5); particles.push_back(p); } } - void onDraw(sf::RenderTexture& surface) override { - sf::RenderWindow& window = getController().getWindow(); + void onDraw(sw::IRenderer& renderer) override { + const bool isCustomRenderer = + getController().getActiveRenderEntry().getName() == "custom"; - surface.draw(bg); + renderer.submit(Draw3D(&bg, bgNormal)); for (auto& p : particles) { - surface.draw(p.sprite); + renderer.submit(&p.sprite); } int i = 0; menuText.setFillColor(sf::Color::Black); for (auto& b : buttons) { - b.draw(surface, menuText, screenMid, (float)(200 + (i++*100))); + b.draw(renderer, menuText, screenMid, (float)(200 + (i++*100))); } // First set the text as the it would render as a full string menuText.setString(GAME_TITLE); - setOrigin(menuText, 0.5, 0.5); + sw::setOrigin(menuText, 0.5, 0.5); // -30 is a made up number offset to help it look right menuText.setPosition(sf::Vector2f(screenMid-30.f, 100)); @@ -265,13 +303,16 @@ class MainMenuScene : public Activity { double offset = 0; // For each letter in the string, make it jump while preserving placement - for (int i = 0; i < strlen(GAME_TITLE); i++) { + size_t len = strlen(GAME_TITLE); + double frequency = sw::ease::pi * 2.0 / len; + double dt = timer.getElapsed().asSeconds(); + for (int i = 0; i < len; i++) { menuText.setFillColor(sf::Color::White); menuText.setString(GAME_TITLE[i]); - setOrigin(menuText, 0.5, 0.5); // origin is in the center of the letter + sw::setOrigin(menuText, 0.5, 0.5); // origin is in the center of the letter // This creates our wave over all letters - double ratio = (ease::pi) / strlen(GAME_TITLE); + double ratio = (ease::pi) / len; double wave = (std::sin(timer.getElapsed().asSeconds()*2.0+((i+1)*ratio))); // Only add the peaks @@ -288,7 +329,16 @@ class MainMenuScene : public Activity { if (menuText.getString() == ' ') { offset += menuText.getCharacterSize(); } menuText.setPosition(sf::Vector2f((float)(startX + offset), (float)startY)); - surface.draw(menuText); + renderer.submit(sw::Clone(menuText)); + + if (isCustomRenderer) { + + sf::Uint8 r = sf::Uint8(((sin(frequency * i + 2 + dt) + 1.0) / 2.0) * 255U); + sf::Uint8 g = sf::Uint8(((sin(frequency * i + 0 + dt) + 1.0) / 2.0) * 255U); + sf::Uint8 b = sf::Uint8(((sin(frequency * i + 4 + dt) + 1.0) / 2.0) * 255U); + + renderer.submit(Light(160.0, WithZ(menuText.getPosition(), 50.0f), sf::Color(r, g, b, 255), 0.1f)); + } } } diff --git a/ExampleDemo/resources/SpaceShooterRedux/Backgrounds/background_e.png b/ExampleDemo/resources/SpaceShooterRedux/Backgrounds/background_e.png new file mode 100644 index 0000000..4deacc0 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/Backgrounds/background_e.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/Backgrounds/purple_n.png b/ExampleDemo/resources/SpaceShooterRedux/Backgrounds/purple_n.png new file mode 100644 index 0000000..935e029 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/Backgrounds/purple_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/Bonus/sfx_explode.ogg b/ExampleDemo/resources/SpaceShooterRedux/Bonus/sfx_explode.ogg new file mode 100644 index 0000000..3b45e35 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/Bonus/sfx_explode.ogg differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_e.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_e.png new file mode 100644 index 0000000..74e2bca Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_e.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_n.png new file mode 100644 index 0000000..f6a8820 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/Enemies/enemyGreen3_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_big4_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_big4_n.png new file mode 100644 index 0000000..49cd9a4 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_big4_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_med1_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_med1_n.png new file mode 100644 index 0000000..d9479e7 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_med1_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_small1_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_small1_n.png new file mode 100644 index 0000000..a6b28d9 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_small1_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_tiny1_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_tiny1_n.png new file mode 100644 index 0000000..cce14d0 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/Meteors/meteorBrown_tiny1_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_e.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_e.png new file mode 100644 index 0000000..2eb6f9c Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_e.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_n.png new file mode 100644 index 0000000..b7f702f Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_red_e.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_red_e.png new file mode 100644 index 0000000..3695e09 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_red_e.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_red_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_red_n.png new file mode 100644 index 0000000..b7f702f Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip1_red_n.png differ diff --git a/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip2_green_n.png b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip2_green_n.png new file mode 100644 index 0000000..ed35cb5 Binary files /dev/null and b/ExampleDemo/resources/SpaceShooterRedux/PNG/playerShip2_green_n.png differ diff --git a/ExampleDemo/resources/menu_n.png b/ExampleDemo/resources/menu_n.png new file mode 100644 index 0000000..9e56633 Binary files /dev/null and b/ExampleDemo/resources/menu_n.png differ diff --git a/ExampleDemo/resources/theme.ogg b/ExampleDemo/resources/theme.ogg index ebcc4a9..c7020e1 100644 Binary files a/ExampleDemo/resources/theme.ogg and b/ExampleDemo/resources/theme.ogg differ diff --git a/README.md b/README.md index 2e9d12e..080f7f3 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,36 @@ ![logo](https://i.imgur.com/tri24Y5.png) -# Swoosh v1.2.6 -Header-only SFML Activity and Segue Mini Library +# Swoosh v2.0.0 +Screen Transition Management Library +Currently only for SFML. Tested across MSVC, GNU C++, and Clang compilers on Windows, Linux, OSX, and Android operating systems. [See what else comes with Swoosh](https://github.com/TheMaverickProgrammer/Swoosh/wiki/Namespaces) -> 🚨 Critical changes from v1.2.3+ -> 1. queuePop() and queueRewind() are now just pop() and rewind() -> 2. optimizeForPerformance(true/false) is changed to optimizeForPerformance(const quality& mode) -> 3. quality can be { realtime, reduced, mobile } where each is best-to-worst quality but worst-to-best performance depending on your hardware -> 4. Segues can query the controller's quality set with getRequestedQuality() -> 5. Added much-needed doxygen style documentation throughout the entire project -> 6. New [Dream](https://twitter.com/i/status/1315143680903254017) segue effect -> 7. Fixed some bugs with view toggling between activities +> 🚨 Critical changes from v1.2.6 +> 1. `swoosh::` shortened to `sw::` +> 1. `Game.h` renamed to `Utils.h` and drops the `game::` namespace +> 1. `types::` renamed to `arg::` +> 1. `types::segue` alias is now immediately exposed under `sw::` namespace +> 1. A new way to support multiple methods of rendering for various devices with the same code! +> 1. All `pop()` variants can now accept _optional_ data to send back to the previous screen! +> 1. e.g. `template pop(const T& data);` +> 1. All args are perfectly forwarded +> 1. This is **very** useful for sending contextual information to screens (e.g. _why_ we left the screen) +> 1. This input data takes **any** type and stores it inside a special `Context` object. +> 1. All `push()` variants now return a special `PopDataHolder&` type to receive that data sent from `pop(...)`! +> 1. `void PopDataHolder::take(const std::function& callback)` +> 1. This function registers the callback function to run on `pop()` +> 1. `void PopDataHolder::adopt()` +> 1. This function moves the context data from the previous scene to our current scene's context automatically +> 1. `bool Context::is` - Returns `bool` indicating whether the current Context's stored type is exactly `T` +> 1. `T& Context::as` - casts the current Context's stored type as `(T*)` and returns the deref result `T&` +> 1. `std::optional Context::previous(size_t offset=0)` +> 1. If the previous context was adopted via `PopDataHolder::adopt()`, then this optional will contain that Context. +Check with `.has_value()` first! +> 1. If `offset` is provided, and only if the **exact** same number of subsequent contexts were also adopted, then +this will return the Context from that many Activities ago +> > See older changes at the [changelog](https://github.com/TheMaverickProgrammer/Swoosh/wiki/Changelog) # ✨ Get Jump Started @@ -32,10 +49,6 @@ Click the gif for the full video! [![SlideIn Segue](https://media.giphy.com/media/2jsQgGNqmHU3HB3tZN/giphy.gif)](https://streamable.com/qb023) -See the pokemon demo using just Swoosh! - -[![clip](https://media.giphy.com/media/1WbJank711TIIMmVr4/giphy.gif)](https://streamable.com/vyfhq) - --- # § Integrating Swoosh into your SFML app in 2 steps @@ -44,7 +57,7 @@ See the pokemon demo using just Swoosh! ✔️ See [this example](https://github.com/TheMaverickProgrammer/Swoosh/blob/master/ExampleDemo/Demo.cpp) for how you should structure your main loop with the Activty Controller. ### ⚙️ Inheriting the AC (Activity Controller) -You can inherit the activity controller to extend and supply more complex data to your applications. For instance, you could extend the AC to know about your TextureResource class or AudioResource class so that each Activity instance has a way to load your game's media. +You can inherit the activity controller to extend and supply more complex data to your applications. For instance, you could extend the AC to know about your `TextureResource` class or `AudioResource` class so that each Activity instance has a way to load your game's media. ### 📱 Optimizing for Mobile [Skip to this section](https://github.com/TheMaverickProgrammer/Swoosh/blob/master/README.md#-special-topic-mobile-optimization) @@ -64,27 +77,31 @@ Swoosh addresses these issues by wrapping push and pop calls with templated type For example ```c++ -ActivityController controller; +sf::RenderWindow window(sf::VideoMode(800, 600), "Swoosh Demo"); +sw::RenderEntries renderOptions; +renderOptions.enroll("simple", window.getView()); + +// Build our AC with this window and these render options +sw::ActivityController controller(myWindow, myRenderOptions); controller.push(); ... // User selects settings -using types::segue; -controller.push::to>(); +controller.push::to>(); ``` -The syntax is human-readable and flows naturally. Swoosh hides the intricacies from the user so they can focus on what's really important: Writing the application! +The syntax is human-readable and flows naturally. Swoosh hides the intricacies from the user so they can focus on what's really important: **Writing the app!** ### ⏰ Changing Time The `Segue` class takes in two arguments: The next activity type, and the duration for the transition to last. By default the transition is set to 1 second. -This may be too fast or too slow for your needs. The `DurationType` class takes a templated wrapper for SFML time functions. They are found in the `swoosh::types` namespace. +This may be too fast or too slow for your needs. The `DurationType` class takes a templated wrapper for SFML time functions. They are found in the `sw::arg` namespace. For example ```c++ -using namespace swoosh::types; -controller.push, seconds<5>>::to>(); +using namespace sw; // for segue and arg:: namespace +controller.push, arg::seconds<5>>::to>(); ``` ### 🔍 Writing Clearer Code @@ -94,8 +111,15 @@ Although Swoosh is doing a ton behind the scenes for us, we lost clarity. We can clean up the code by creating our own typename aliases. Later, modifying your screen transition effect is as easy as changing one line. ```c++ -using effect = segue, sec<2>>; -getController().push>(); +using namespace sw; + +// fx is short for "effects" +using fx = segue, arg::sec<2>>; + +// tx is short for "transition" +using tx = fx::to; + +getController().push(); ``` Much more elegant! @@ -113,23 +137,29 @@ controller.push({info.getLives(), info.getCoins(), info.getM This is the same for segues ```c++ +using namespace sw; + ActivityController& controller = getController(); LobbyInfo data = queryLobbyServer().get(); // blocking future request -using effect = segue>; +using fx = segue>; +using tx = fx::to; // Go! -controller.push>(data); +controller.push(data); ``` # § Actions & Leaving Activities -The `ActivityController` class can _push_ and _pop_ states but only when it's safe to do so. It does not pop in the middle of a cycle and does not push when in the middle of a segue. +The `sw::ActivityController` class can _push_ and _pop_ states but only when it's safe to do so. It does not pop in the middle of a cycle and does not push when in the middle of a segue. Make sure your activity controller calls are in an Activity's `onUpdate(double elapsed)` function to avoid having _push_ or _pop_ intents discarded. ### Push -```c++ +```cpp +sw::ActivityController controller(myWindow, myRenderOptions); controller.push(); -controller.push::to>(); + +// This transition is short enough to spell out +controller.push::to>(); ``` ### Pop @@ -137,7 +167,7 @@ Pushed activities are added to the stack immediately. However there are steps in ``` controller.pop(); -controller.pop>(); +controller.pop>(); ``` ### Rewinding @@ -150,10 +180,10 @@ This is useful to simulate persistent behavior such as in a top-down adventure g The syntax is close to _push_ except if it succeeds, activities are ended and discarded. ```c++ -using effect = segue; -using action = effect::to; +using fx = sw::segue; +using tx = fx::to; -bool found = controller.rewind(); +bool found = controller.rewind(); if(!found) { // Perhaps we're already in overworld. Certain teleport items cannot be used! @@ -197,7 +227,7 @@ This fact inspired Swoosh to be dependant on a timer. When the timer is up the S added on top of the stack. The time elapsed and total time alloted can be retrieved in the class body to make some cool effects from start to finish. -The class for Segues depends only on one overloaded function `void OnDraw(sf::RenderTexture& surface)`. +The class for Segues depends only on one overloaded function `void onDraw(IRenderer& renderer)`. The constructor must take in the duration, the last activity, and the next activity. ```c++ @@ -223,10 +253,10 @@ _getVirtualWindowSize()_ is useful when wanting to keep your graphics consistent ### Drawing To The Screen Segues are made up of two Activities: the last and the next. For most segues you need to draw one and then the other with some applied effect. -* `drawNextActivity(sf::RenderTexture& surface);` -* `drawLastActivity(sf::RenderTexture& surface);` +* `drawNextActivity(IRenderer& renderer);` +* `drawLastActivity(IRenderer& renderer);` -Both draw their respective activity's contents to a sf::RenderTexture that can be used later. Read on below for an example. +Both draw their respective activity's contents with the active renderer that can be used later. Read on below for an example. [This example](https://github.com/TheMaverickProgrammer/Swoosh/blob/master/src/Segues/PushIn.h) Segue will slide a new screen in while pushing the last scene out. Really cool! @@ -274,7 +304,7 @@ By providing alternative segue effect behavior for the quality modes, you can en If you have a particular structure how your game should end (like a GameOverScreen), it would make sense to have that screen be at the bottom of the stack at ALL times. We can start the player in the main menu and let them make other choices to config their controllers. If the player presses start, we can pop the main menu off the stack and begin the game. With this structure in mind, we might have something like the following: ```cpp -ActivityController ac(window); +sw::ActivityController ac(window); ac.push(); ac.push(); ac.push(); diff --git a/src/Segues/BlackWashFade.h b/src/Segues/BlackWashFade.h index ad76100..8a55e99 100644 --- a/src/Segues/BlackWashFade.h +++ b/src/Segues/BlackWashFade.h @@ -2,7 +2,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class BlendFadeIn @@ -12,24 +12,24 @@ using namespace swoosh; */ class BlackWashFade : public Segue { public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::wideParabola(elapsed, duration, 1.0); if (elapsed <= duration * 0.5) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); } else { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); } sf::RectangleShape whiteout; - whiteout.setSize(sf::Vector2f((float)surface.getTexture().getSize().x, (float)surface.getTexture().getSize().y)); + whiteout.setSize(sf::Vector2f((float)renderer.getTexture().getSize().x, (float)renderer.getTexture().getSize().y)); whiteout.setFillColor(sf::Color(0, 0, 0, (sf::Uint8)(alpha*255))); - surface.draw(whiteout); + renderer.submit(Immediate(&whiteout)); } BlackWashFade(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { /* ... */ } diff --git a/src/Segues/BlendFadeIn.h b/src/Segues/BlendFadeIn.h index a80c5a7..e721066 100644 --- a/src/Segues/BlendFadeIn.h +++ b/src/Segues/BlendFadeIn.h @@ -2,7 +2,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class BlendFadeIn @@ -16,7 +16,7 @@ class BlendFadeIn : public Segue { sf::Texture last, next; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -24,12 +24,12 @@ class BlendFadeIn : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; @@ -38,11 +38,11 @@ class BlendFadeIn : public Segue { sf::Sprite left(temp); if (firstPass || !optimized) { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); } else { temp2 = next; @@ -53,8 +53,8 @@ class BlendFadeIn : public Segue { left.setColor(sf::Color(255, 255, 255, (sf::Uint8)((1.0-alpha) * 255.0))); right.setColor(sf::Color(255, 255, 255, (sf::Uint8)(alpha * 255.0))); - surface.draw(left); - surface.draw(right); + renderer.submit(Immediate(&left)); + renderer.submit(Immediate(&right)); firstPass = false; } diff --git a/src/Segues/BlurFadeIn.h b/src/Segues/BlurFadeIn.h index 1f48b28..263e48f 100644 --- a/src/Segues/BlurFadeIn.h +++ b/src/Segues/BlurFadeIn.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class BlurFadeIn @@ -30,7 +30,7 @@ class BlurFadeIn : public Segue { } public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::wideParabola(elapsed, duration, 1.0); @@ -39,14 +39,14 @@ class BlurFadeIn : public Segue { shader.setPower((float)alpha * 8.f); - surface.clear(this->getLastActivityBGColor()); + renderer.clear(this->getLastActivityBGColor()); sf::Texture temp, temp2; if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; @@ -55,19 +55,19 @@ class BlurFadeIn : public Segue { shader.setTexture(&temp); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } - surface.display(); - temp = sf::Texture(surface.getTexture()); + renderer.display(); + temp = sf::Texture(renderer.getTexture()); - surface.clear(this->getNextActivityBGColor()); + renderer.clear(this->getNextActivityBGColor()); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -76,24 +76,24 @@ class BlurFadeIn : public Segue { shader.setTexture(&temp2); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } - surface.display(); - temp2 = sf::Texture(surface.getTexture()); + renderer.display(); + temp2 = sf::Texture(renderer.getTexture()); - sf::Sprite sprite, sprite2; + static sf::Sprite sprite, sprite2; sprite.setTexture(temp); sprite2.setTexture(temp2); - surface.clear(sf::Color::Transparent); + renderer.clear(sf::Color::Transparent); alpha = ease::linear(elapsed, duration, 1.0); sprite.setColor(sf::Color(255, 255, 255, (sf::Uint8)(255.0 * (1-alpha)))); sprite2.setColor(sf::Color(255, 255, 255, (sf::Uint8)(255.0 * alpha))); - surface.draw(sprite); - surface.draw(sprite2); + renderer.submit(Immediate(&sprite)); + renderer.submit(Immediate(&sprite2)); firstPass = false; } diff --git a/src/Segues/Checkerboard.h b/src/Segues/Checkerboard.h index f0dde8e..a5814c7 100644 --- a/src/Segues/Checkerboard.h +++ b/src/Segues/Checkerboard.h @@ -1,9 +1,10 @@ #pragma once +#include #include #include #include -using namespace swoosh; +using namespace sw; /** @class CheckerboardCustom @@ -21,7 +22,7 @@ class CheckerboardCustom : public Segue { bool firstPass{ true }; std::string checkerboardShader; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -31,15 +32,15 @@ class CheckerboardCustom : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - drawLastActivity(surface); + drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer #ifdef __ANDROID__ - temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture temp.flip(true); #else - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture #endif } else { @@ -48,16 +49,16 @@ class CheckerboardCustom : public Segue { sf::Sprite sprite(temp); if (firstPass || !optimized) { - surface.clear(sf::Color::Transparent); - drawNextActivity(surface); + renderer.clear(sf::Color::Transparent); + drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer #ifdef __ANDROID__ - temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture temp2.flip(true); #else - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture #endif } else { @@ -74,7 +75,7 @@ class CheckerboardCustom : public Segue { states.shader = &shader; } - surface.draw(sprite, states); + renderer.submit(Immediate(&sprite, states)); } CheckerboardCustom(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/CircleClose.h b/src/Segues/CircleClose.h index dba295b..7c1477b 100644 --- a/src/Segues/CircleClose.h +++ b/src/Segues/CircleClose.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class CircleOpen @@ -18,7 +18,7 @@ class CircleClose : public Segue { sf::Texture last, next; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -28,20 +28,20 @@ class CircleClose : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; } if (firstPass || !optimized) { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -55,17 +55,17 @@ class CircleClose : public Segue { shader.setTexture(&temp); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } - surface.display(); - sf::Texture temp3(surface.getTexture()); + renderer.display(); + sf::Texture temp3(renderer.getTexture()); sf::Sprite left(temp2); sf::Sprite right(temp3); - surface.draw(left); - surface.draw(right); + renderer.submit(Immediate(&left)); + renderer.submit(Immediate(&right)); firstPass = false; } diff --git a/src/Segues/CircleOpen.h b/src/Segues/CircleOpen.h index 3a7dff1..4a4dbd4 100644 --- a/src/Segues/CircleOpen.h +++ b/src/Segues/CircleOpen.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class CircleOpen @@ -18,7 +18,7 @@ class CircleOpen : public Segue { sf::Texture next, last; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -28,22 +28,22 @@ class CircleOpen : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); - surface.display(); // flip and ready the buffer + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); + renderer.display(); // flip and ready the buffer - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = next; } if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); - surface.display(); // flip and ready the buffer + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); + renderer.display(); // flip and ready the buffer - last = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = last; @@ -57,16 +57,16 @@ class CircleOpen : public Segue { shader.setTexture(&temp); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } - surface.display(); - sf::Texture temp3(surface.getTexture()); + renderer.display(); + sf::Texture temp3(renderer.getTexture()); sf::Sprite left(temp3); sf::Sprite right(temp2); - surface.draw(right); - surface.draw(left); + renderer.submit(Immediate(&right)); + renderer.submit(Immediate(&left)); firstPass = false; } diff --git a/src/Segues/CrossZoom.h b/src/Segues/CrossZoom.h index be4abd4..3e4c53e 100644 --- a/src/Segues/CrossZoom.h +++ b/src/Segues/CrossZoom.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class CrossZoomCustom @@ -20,7 +20,7 @@ class CrossZoomCustom : public Segue { glsl::CrossZoom shader; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -29,28 +29,26 @@ class CrossZoomCustom : public Segue { sf::Texture temp, temp2; - surface.clear(this->getLastActivityBGColor()); + renderer.clear(this->getLastActivityBGColor()); if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; } - sf::Sprite sprite(temp); - - surface.clear(this->getNextActivityBGColor()); + renderer.clear(this->getNextActivityBGColor()); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); + next = temp2 = sf::Texture(renderer.getTexture()); } else { temp2 = next; @@ -62,7 +60,7 @@ class CrossZoomCustom : public Segue { shader.setTexture2(&temp2); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } firstPass = false; diff --git a/src/Segues/Cube3D.h b/src/Segues/Cube3D.h index ed47796..9faa2c9 100644 --- a/src/Segues/Cube3D.h +++ b/src/Segues/Cube3D.h @@ -3,7 +3,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class Cube3D @@ -13,7 +13,7 @@ using namespace swoosh; If optimized for mobile, will capture the scenes once and use less vertices to increase performance on weak hardware */ -template +template class Cube3D : public Segue { private: sf::Texture last, next; @@ -22,7 +22,7 @@ class Cube3D : public Segue { bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -32,11 +32,11 @@ class Cube3D : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = next; @@ -45,11 +45,11 @@ class Cube3D : public Segue { sf::Sprite sprite(temp); if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - last = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = last; @@ -57,7 +57,7 @@ class Cube3D : public Segue { shader.setUniform("direction", static_cast(direction)); - if (direction == direction::right || direction == direction::up) { + if (direction == arg::direction::right || direction == arg::direction::up) { shader.setUniform("texture", temp); shader.setUniform("texture2", temp2); } @@ -74,9 +74,9 @@ class Cube3D : public Segue { states.shader = &shader; } - surface.clear(getLastActivityBGColor()); + renderer.clear(getLastActivityBGColor()); - surface.draw(sprite, states); + renderer.submit(Immediate(&sprite, states)); firstPass = false; } diff --git a/src/Segues/DiamondTileCircle.h b/src/Segues/DiamondTileCircle.h index 397e994..15ed762 100644 --- a/src/Segues/DiamondTileCircle.h +++ b/src/Segues/DiamondTileCircle.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class DiamondTileCircle @@ -23,7 +23,7 @@ class DiamondTileCircle : public Segue { bool firstPass{ true }, secondPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::wideParabola(elapsed, duration, 1.0); @@ -34,9 +34,9 @@ class DiamondTileCircle : public Segue { if (elapsed < duration * 0.5) { if (firstPass || !optimized) { - this->drawLastActivity(surface); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + this->drawLastActivity(renderer); + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture firstPass = false; } else { @@ -45,9 +45,9 @@ class DiamondTileCircle : public Segue { } else { if (secondPass || !optimized) { - this->drawNextActivity(surface); - surface.display(); // flip and ready the buffer - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + this->drawNextActivity(renderer); + renderer.display(); // flip and ready the buffer + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture secondPass = false; } else { @@ -66,7 +66,7 @@ class DiamondTileCircle : public Segue { states.shader = &shader; } - surface.draw(sprite, states); + renderer.submit(Immediate(&sprite, states)); } DiamondTileCircle(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/DiamondTileSwipe.h b/src/Segues/DiamondTileSwipe.h index 2a285dc..b1c95c5 100644 --- a/src/Segues/DiamondTileSwipe.h +++ b/src/Segues/DiamondTileSwipe.h @@ -3,7 +3,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class DiamondTileSwipe @@ -14,7 +14,7 @@ using namespace swoosh; If optimized for mobile, will capture the scenes once and use less vertices to increase performance on weak hardware */ -template +template class DiamondTileSwipe : public Segue { private: sf::Texture last, next; @@ -22,7 +22,7 @@ class DiamondTileSwipe : public Segue { std::string diamondSwipeShaderProgram; bool firstPass{ true }, secondPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::wideParabola(elapsed, duration, 1.0); @@ -33,9 +33,9 @@ class DiamondTileSwipe : public Segue { if (elapsed < duration * 0.5) { if (firstPass || !optimized) { - this->drawLastActivity(surface); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + this->drawLastActivity(renderer); + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture firstPass = false; } else { @@ -44,9 +44,9 @@ class DiamondTileSwipe : public Segue { } else { if (secondPass || !optimized) { - this->drawNextActivity(surface); - surface.display(); // flip and ready the buffer - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + this->drawNextActivity(renderer); + renderer.display(); // flip and ready the buffer + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture firstPass = false; } else { @@ -66,7 +66,7 @@ class DiamondTileSwipe : public Segue { states.shader = &shader; } - surface.draw(sprite, states); + renderer.submit(Immediate(&sprite, states)); } DiamondTileSwipe(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/Dream.h b/src/Segues/Dream.h index 2351de2..f7abd9e 100644 --- a/src/Segues/Dream.h +++ b/src/Segues/Dream.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class Dream @@ -22,32 +22,32 @@ class DreamCustom : public Segue { bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); const bool optimized = getController().getRequestedQuality() == quality::mobile; const bool useShader = getController().isShadersEnabled(); - sf::Texture temp, temp2; + static sf::Texture temp, temp2; if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; } if (firstPass || !optimized) { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -63,8 +63,8 @@ class DreamCustom : public Segue { states.shader = &shader; } - sf::Sprite sprite(temp2); // dummy. we just need something with the screen size to draw with - surface.draw(sprite, states); + static sf::Sprite sprite(temp2); // dummy. we just need something with the screen size to draw with + renderer.submit(sprite, states); firstPass = false; } diff --git a/src/Segues/HorizontalOpen.h b/src/Segues/HorizontalOpen.h index 0f92a92..ce0d299 100644 --- a/src/Segues/HorizontalOpen.h +++ b/src/Segues/HorizontalOpen.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @class HorizontalOpen @@ -16,16 +16,16 @@ class HorizontalOpen : public Segue { sf::Vector2u windowSize; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp(surface.getTexture()); // Make a copy of the source texture + sf::Texture temp(renderer.getTexture()); // Make a copy of the source texture sf::Sprite top(temp); top.setTextureRect(sf::IntRect(0, 0, windowSize.x, (int)(windowSize.y / 2.0))); @@ -35,17 +35,17 @@ class HorizontalOpen : public Segue { bottom.setTextureRect(sf::IntRect(0, (int)(windowSize.y / 2.0), windowSize.x, windowSize.y)); bottom.setPosition(0.0f, (float)(windowSize.y/2.0f) + ((float)alpha * (bottom.getTextureRect().height-bottom.getTextureRect().top))); - surface.clear(); + renderer.clear(); - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - sf::Texture temp2(surface.getTexture()); + renderer.display(); // flip and ready the buffer + sf::Texture temp2(renderer.getTexture()); sf::Sprite right(temp2); - surface.draw(right); - surface.draw(top); - surface.draw(bottom); + renderer.submit(Immediate(&right)); + renderer.submit(Immediate(&top)); + renderer.submit(Immediate(&bottom)); } HorizontalOpen(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/HorizontalSlice.h b/src/Segues/HorizontalSlice.h index 140083d..2b0526c 100644 --- a/src/Segues/HorizontalSlice.h +++ b/src/Segues/HorizontalSlice.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @class HorizontalSlice @@ -16,16 +16,16 @@ class HorizontalSlice : public Segue { sf::Vector2u windowSize; int direction; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = 1.0 - ease::bezierPopOut(elapsed, duration); - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp(surface.getTexture()); // Make a copy of the source texture + sf::Texture temp(renderer.getTexture()); // Make a copy of the source texture sf::Sprite top(temp); top.setTextureRect(sf::IntRect(0, 0, windowSize.x, windowSize.y / 2)); @@ -35,18 +35,18 @@ class HorizontalSlice : public Segue { bottom.setTextureRect(sf::IntRect(0, windowSize.y / 2, windowSize.x, windowSize.y)); bottom.setPosition((float)(direction * -alpha * bottom.getTexture()->getSize().x), (float)(windowSize.y/2.0f)); - surface.clear(); + renderer.clear(); - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp2(surface.getTexture()); + sf::Texture temp2(renderer.getTexture()); sf::Sprite right(temp2); - surface.draw(right); - surface.draw(top); - surface.draw(bottom); + renderer.submit(Immediate(&right)); + renderer.submit(Immediate(&top)); + renderer.submit(Immediate(&bottom)); } HorizontalSlice(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/Morph.h b/src/Segues/Morph.h index 530b900..ff77c97 100644 --- a/src/Segues/Morph.h +++ b/src/Segues/Morph.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class Moprh @@ -19,7 +19,7 @@ class Morph : public Segue { bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -29,22 +29,22 @@ class Morph : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; } if (firstPass || !optimized) { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -55,7 +55,7 @@ class Morph : public Segue { shader.setTexture2(&temp2); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } firstPass = false; diff --git a/src/Segues/PageTurn.h b/src/Segues/PageTurn.h index 62e0e33..7f33f75 100644 --- a/src/Segues/PageTurn.h +++ b/src/Segues/PageTurn.h @@ -1,11 +1,11 @@ #pragma once #include #include -#include +#include #include #include -using namespace swoosh; +using namespace sw; /** @class PageTurn @@ -33,7 +33,7 @@ class PageTurn : public Segue { } public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -42,13 +42,13 @@ class PageTurn : public Segue { sf::Texture temp, temp2; - surface.clear(this->getLastActivityBGColor()); + renderer.clear(this->getLastActivityBGColor()); if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; @@ -58,21 +58,21 @@ class PageTurn : public Segue { shader.setAlpha((float)alpha); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } - surface.display(); + renderer.display(); - sf::Texture copy(surface.getTexture()); + sf::Texture copy(renderer.getTexture()); sf::Sprite left(copy); // Make a copy of the effect to render later - surface.clear(this->getNextActivityBGColor()); + renderer.clear(this->getNextActivityBGColor()); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); } else { temp2 = next; @@ -80,8 +80,8 @@ class PageTurn : public Segue { sf::Sprite right(temp2); - surface.draw(right); - surface.draw(left); + renderer.submit(Immediate(&right)); + renderer.submit(Immediate(&left)); firstPass = false; } diff --git a/src/Segues/PixelateBlackWashFade.h b/src/Segues/PixelateBlackWashFade.h index 0e5da91..62225c1 100644 --- a/src/Segues/PixelateBlackWashFade.h +++ b/src/Segues/PixelateBlackWashFade.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class PixelateBlackWashFade @@ -19,7 +19,7 @@ class PixelateBlackWashFade : public Segue { bool firstPass{ true }; bool secondPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::wideParabola(elapsed, duration, 1.0); @@ -30,10 +30,10 @@ class PixelateBlackWashFade : public Segue { if (elapsed <= duration * 0.5) { if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); - surface.display(); - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); + renderer.display(); + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture firstPass = false; } @@ -43,10 +43,10 @@ class PixelateBlackWashFade : public Segue { } else { if (secondPass || !optimized) { - surface.clear(this->getNextActivityBGColor()); - this->drawNextActivity(surface); - surface.display(); - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.clear(this->getNextActivityBGColor()); + this->drawNextActivity(renderer); + renderer.display(); + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture secondPass = false; } @@ -60,7 +60,7 @@ class PixelateBlackWashFade : public Segue { shader.setThreshold((float)alpha/15.0f); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } // 10% of segue is a pixelate before darkening @@ -69,9 +69,9 @@ class PixelateBlackWashFade : public Segue { double alpha = ease::wideParabola(elapsed - delay, duration - delay, 1.0); sf::RectangleShape blackout; - blackout.setSize(sf::Vector2f((float)surface.getTexture().getSize().x, (float)surface.getTexture().getSize().y)); + blackout.setSize(sf::Vector2f((float)renderer.getTexture().getSize().x, (float)renderer.getTexture().getSize().y)); blackout.setFillColor(sf::Color(0, 0, 0, (sf::Uint8)(alpha * (double)255))); - surface.draw(blackout); + renderer.submit(Immediate(&blackout)); } } diff --git a/src/Segues/PushIn.h b/src/Segues/PushIn.h index 26c2fd8..9a05aa9 100644 --- a/src/Segues/PushIn.h +++ b/src/Segues/PushIn.h @@ -2,20 +2,20 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class PushIn @brief Pushes the new screen in while pushing the last screen out @param direction. Compile-time constant. A cardinal direction to push from. */ -template +template class PushIn : public Segue { sf::Texture next, last; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -24,12 +24,12 @@ class PushIn : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; @@ -40,21 +40,21 @@ class PushIn : public Segue { int lr = 0; int ud = 0; - if (direction == types::direction::left ) lr = -1; - if (direction == types::direction::right) lr = 1; - if (direction == types::direction::up ) ud = -1; - if (direction == types::direction::down ) ud = 1; + if (direction == arg::direction::left ) lr = -1; + if (direction == arg::direction::right) lr = 1; + if (direction == arg::direction::up ) ud = -1; + if (direction == arg::direction::down ) ud = 1; left.setPosition((float)(lr * alpha * left.getTexture()->getSize().x), (float)(ud * alpha * left.getTexture()->getSize().y)); - surface.clear(this->getNextActivityBGColor()); + renderer.clear(this->getNextActivityBGColor()); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); + next = temp2 = sf::Texture(renderer.getTexture()); } else { temp2 = next; @@ -64,8 +64,8 @@ class PushIn : public Segue { right.setPosition((float)(-lr * (1.0-alpha) * right.getTexture()->getSize().x), (float)(-ud * (1.0-alpha) * right.getTexture()->getSize().y)); - surface.draw(left); - surface.draw(right); + renderer.submit(&left); + renderer.submit(&right); firstPass = false; } diff --git a/src/Segues/RadialCCW.h b/src/Segues/RadialCCW.h index f81ad46..8884f6d 100644 --- a/src/Segues/RadialCCW.h +++ b/src/Segues/RadialCCW.h @@ -4,7 +4,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class RadialCCW @@ -19,7 +19,7 @@ class RadialCCW : public Segue { sf::Texture next, last; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -28,26 +28,26 @@ class RadialCCW : public Segue { sf::Texture temp, temp2; - surface.clear(this->getLastActivityBGColor()); + renderer.clear(this->getLastActivityBGColor()); if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; } - surface.clear(this->getNextActivityBGColor()); + renderer.clear(this->getNextActivityBGColor()); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -58,7 +58,7 @@ class RadialCCW : public Segue { shader.setAlpha((float)alpha); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } firstPass = false; diff --git a/src/Segues/RetroBlit.h b/src/Segues/RetroBlit.h index 4ed6863..9b8104e 100644 --- a/src/Segues/RetroBlit.h +++ b/src/Segues/RetroBlit.h @@ -4,27 +4,20 @@ #include #include -using namespace swoosh; +using namespace sw; /** - @class RetroBlitCustom - @param krows. Compile-time constant of the kernel size vertically. Higher row count = bigger cells and better performance but lower quality. - @param kcols. Compile-time constant of the kernel size horizontally. Higher col count = "" - - krows and kcols determine kernel size that each is interpolated over. - It can be thought of as color tolerance - For a dithering and dissolving effect, using high krows and kcols - For a retro and blocky effect, use less + @class RetroBlit + Reduces the color channel bit rate over time to give the impression of dithering out like some older games */ -template -class RetroBlitCustom : public Segue { +class RetroBlit: public Segue { private: glsl::RetroBlit shader; sf::Texture last, next; bool firstPass{ true }; bool secondPass{ true }; public: - virtual void onDraw(sf::RenderTexture& surface) { + virtual void onDraw(IRenderer& renderer) { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -35,12 +28,12 @@ class RetroBlitCustom : public Segue { if (alpha <= 0.5) { if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture - surface.clear(this->getLastActivityBGColor()); + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture + renderer.clear(this->getLastActivityBGColor()); } else { temp = last; @@ -50,21 +43,21 @@ class RetroBlitCustom : public Segue { shader.setAlpha((0.5f - (float)alpha)/0.5f); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } firstPass = false; } else { if (secondPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture sf::Sprite sprite(temp); - surface.clear(this->getNextActivityBGColor()); + renderer.clear(this->getNextActivityBGColor()); } else { temp = next; @@ -74,21 +67,17 @@ class RetroBlitCustom : public Segue { shader.setAlpha(((float)alpha - 0.5f) / 0.5f); if(useShader) { - shader.apply(surface); + shader.apply(renderer); } secondPass = false; } } - RetroBlitCustom(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next), - shader(kcols, krows) { + RetroBlit(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next), + shader() { /* ... */; } - ~RetroBlitCustom() { ; } -}; - - -//!< Shorthand to use configured RetroBlit with kcols and krows of 10 -using RetroBlit = RetroBlitCustom<10, 10>; + ~RetroBlit() { ; } +}; \ No newline at end of file diff --git a/src/Segues/SlideIn.h b/src/Segues/SlideIn.h index 8568221..9a5fc53 100644 --- a/src/Segues/SlideIn.h +++ b/src/Segues/SlideIn.h @@ -2,7 +2,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class SlideIn @@ -12,20 +12,20 @@ using namespace swoosh; Behavior is the same across all quality modes */ -template +template class SlideIn : public Segue { public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp(surface.getTexture()); // Make a copy of the source texture + sf::Texture temp(renderer.getTexture()); // Make a copy of the source texture sf::Sprite left(temp); @@ -37,18 +37,18 @@ class SlideIn : public Segue { if (direction == direction::up ) ud = -1; if (direction == direction::down ) ud = 1; - surface.clear(); + renderer.clear(); - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - sf::Texture temp2(surface.getTexture()); + renderer.display(); // flip and ready the buffer + sf::Texture temp2(renderer.getTexture()); sf::Sprite right(temp2); right.setPosition((float)-lr * (1.0f-(float)alpha) * right.getTexture()->getSize().x, (float)-ud * (1.0f-(float)alpha) * right.getTexture()->getSize().y); - surface.draw(left); - surface.draw(right); + renderer.submit(Immediate(&left)); + renderer.submit(Immediate(&right)); } SlideIn(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/SwipeIn.h b/src/Segues/SwipeIn.h index e66b1d4..66b160e 100644 --- a/src/Segues/SwipeIn.h +++ b/src/Segues/SwipeIn.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @@ -13,31 +13,31 @@ using namespace swoosh; Behavior is the same across all quality modes */ -template +template class SwipeIn : public Segue { private: sf::Vector2u windowSize; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); - surface.clear(this->getLastActivityBGColor()); - this->drawLastActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp(surface.getTexture()); // Make a copy of the source texture + sf::Texture temp(renderer.getTexture()); // Make a copy of the source texture sf::Sprite bottom(temp); - surface.clear(); + renderer.clear(); - surface.clear(this->getLastActivityBGColor()); - this->drawNextActivity(surface); + renderer.clear(this->getLastActivityBGColor()); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - sf::Texture temp2(surface.getTexture()); + renderer.display(); // flip and ready the buffer + sf::Texture temp2(renderer.getTexture()); sf::Sprite top(temp2); int l = 0; @@ -84,9 +84,9 @@ class SwipeIn : public Segue { top.setTextureRect(sf::IntRect(l, u, r, d)); - surface.clear(); - surface.draw(bottom); - surface.draw(top); + renderer.clear(); + renderer.submit(Immediate(&bottom)); + renderer.submit(Immediate(&top)); } SwipeIn(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/VerticalOpen.h b/src/Segues/VerticalOpen.h index 2f92064..7e2e63e 100644 --- a/src/Segues/VerticalOpen.h +++ b/src/Segues/VerticalOpen.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @@ -17,16 +17,16 @@ class VerticalOpen : public Segue { private: sf::Vector2u windowSize; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp(surface.getTexture()); // Make a copy of the source texture + sf::Texture temp(renderer.getTexture()); // Make a copy of the source texture sf::Sprite left(temp); left.setTextureRect(sf::IntRect(0, 0, (int)(windowSize.x/2.0f), windowSize.y)); @@ -36,17 +36,17 @@ class VerticalOpen : public Segue { right.setTextureRect(sf::IntRect((int)(windowSize.x/2.0f), 0, windowSize.x, windowSize.y)); right.setPosition((float)(windowSize.x/2.0f) + ((float)alpha * (right.getTextureRect().width-right.getTextureRect().left)), 0.0f); - surface.clear(); + renderer.clear(); - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - sf::Texture temp2(surface.getTexture()); + renderer.display(); // flip and ready the buffer + sf::Texture temp2(renderer.getTexture()); sf::Sprite next(temp2); - surface.draw(next); - surface.draw(left); - surface.draw(right); + renderer.submit(Immediate(&next)); + renderer.submit(Immediate(&left)); + renderer.submit(Immediate(&right)); } VerticalOpen(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/VerticalSlice.h b/src/Segues/VerticalSlice.h index 71d2486..cbfa57e 100644 --- a/src/Segues/VerticalSlice.h +++ b/src/Segues/VerticalSlice.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @class VerticalSlice @@ -17,15 +17,15 @@ class VerticalSlice : public Segue { int direction; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = 1.0 - ease::bezierPopOut(elapsed, duration); - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - sf::Texture temp(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + sf::Texture temp(renderer.getTexture()); // Make a copy of the source texture sf::Sprite left(temp); left.setTextureRect(sf::IntRect(0, 0, (int)(windowSize.x/2.0), windowSize.y)); @@ -35,18 +35,18 @@ class VerticalSlice : public Segue { right.setTextureRect(sf::IntRect((int)(windowSize.x/2.0), 0, windowSize.x, windowSize.y)); right.setPosition((float)(windowSize.x/2.0f), (float)(direction * -alpha * (double)right.getTexture()->getSize().y)); - surface.clear(); + renderer.clear(); - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - sf::Texture temp2(surface.getTexture()); + sf::Texture temp2(renderer.getTexture()); sf::Sprite next(temp2); - surface.draw(next); - surface.draw(left); - surface.draw(right); + renderer.submit(Immediate(&next)); + renderer.submit(Immediate(&left)); + renderer.submit(Immediate(&right)); } VerticalSlice(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { diff --git a/src/Segues/WhiteWashFade.h b/src/Segues/WhiteWashFade.h index ec9c296..4e5b710 100644 --- a/src/Segues/WhiteWashFade.h +++ b/src/Segues/WhiteWashFade.h @@ -2,7 +2,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class WhiteWashFade @@ -13,20 +13,20 @@ using namespace swoosh; class WhiteWashFade : public Segue { public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::wideParabola(elapsed, duration, 1.0); if (elapsed <= duration * 0.5) - this->drawLastActivity(surface); + this->drawLastActivity(renderer); else - this->drawNextActivity(surface); + this->drawNextActivity(renderer); sf::RectangleShape whiteout; - whiteout.setSize(sf::Vector2f((float)surface.getTexture().getSize().x, (float)surface.getTexture().getSize().y)); + whiteout.setSize(sf::Vector2f((float)renderer.getTexture().getSize().x, (float)renderer.getTexture().getSize().y)); whiteout.setFillColor(sf::Color(255, 255, 255, (sf::Uint8)(alpha*255))); - surface.draw(whiteout); + renderer.submit(Immediate(&whiteout)); } WhiteWashFade(sf::Time duration, Activity* last, Activity* next) : Segue(duration, last, next) { /* ... */ } diff --git a/src/Segues/ZoomFadeIn.h b/src/Segues/ZoomFadeIn.h index a0bab5d..91e5593 100644 --- a/src/Segues/ZoomFadeIn.h +++ b/src/Segues/ZoomFadeIn.h @@ -3,7 +3,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class ZoomFadeIn @@ -18,7 +18,7 @@ class ZoomFadeIn : public Segue { sf::Texture next, last; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::linear(elapsed, duration, 1.0); @@ -28,11 +28,11 @@ class ZoomFadeIn : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; @@ -40,14 +40,14 @@ class ZoomFadeIn : public Segue { sf::Sprite sprite(temp); - surface.clear(sf::Color::Transparent); + renderer.clear(sf::Color::Transparent); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -63,7 +63,7 @@ class ZoomFadeIn : public Segue { states.shader = &shader; } - surface.draw(sprite, states); + renderer.submit(Immediate(&sprite, states)); firstPass = false; } diff --git a/src/Segues/ZoomFadeInBounce.h b/src/Segues/ZoomFadeInBounce.h index 53893c9..b494777 100644 --- a/src/Segues/ZoomFadeInBounce.h +++ b/src/Segues/ZoomFadeInBounce.h @@ -3,7 +3,7 @@ #include #include -using namespace swoosh; +using namespace sw; /** @class ZoomFadeInBounce @@ -22,7 +22,7 @@ class ZoomFadeInBounce : public Segue { bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::sinuoidBounceOut(elapsed, duration); @@ -32,9 +32,9 @@ class ZoomFadeInBounce : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - this->drawLastActivity(surface); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); + this->drawLastActivity(renderer); + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); } else { temp = last; @@ -42,14 +42,14 @@ class ZoomFadeInBounce : public Segue { sf::Sprite sprite(temp); - surface.clear(sf::Color::Transparent); + renderer.clear(sf::Color::Transparent); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer + renderer.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); // Make a copy of the source texture + next = temp2 = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp2 = next; @@ -65,7 +65,7 @@ class ZoomFadeInBounce : public Segue { states.shader = &shader; } - surface.draw(sprite, states); + renderer.submit(Immediate(&sprite, states)); firstPass = false; } diff --git a/src/Segues/ZoomIn.h b/src/Segues/ZoomIn.h index 25becf4..6b473cd 100644 --- a/src/Segues/ZoomIn.h +++ b/src/Segues/ZoomIn.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @class ZoomIn @@ -19,7 +19,7 @@ class ZoomIn : public Segue { bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::bezierPopIn(elapsed, duration); @@ -28,27 +28,27 @@ class ZoomIn : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + next = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = next; } sf::Sprite left(temp); - game::setOrigin(left, 0.5f, 0.5f); + sw::setOrigin(left, 0.5f, 0.5f); left.setPosition((float)(windowSize.x/2.0f), (float)(windowSize.y/2.0f)); left.setScale((float)alpha, (float)alpha); - surface.clear(); + renderer.clear(); if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp2 = sf::Texture(surface.getTexture()); + renderer.display(); // flip and ready the buffer + last = temp2 = sf::Texture(renderer.getTexture()); } else { temp2 = last; @@ -56,8 +56,8 @@ class ZoomIn : public Segue { sf::Sprite right(temp2); - surface.draw(right); - surface.draw(left); + renderer.submit(Immediate(&right)); + renderer.submit(Immediate(&left)); firstPass = false; } diff --git a/src/Segues/ZoomOut.h b/src/Segues/ZoomOut.h index 63c58d5..5d89292 100644 --- a/src/Segues/ZoomOut.h +++ b/src/Segues/ZoomOut.h @@ -1,9 +1,9 @@ #pragma once #include -#include +#include #include -using namespace swoosh; +using namespace sw; /** @class ZoomIn @@ -18,7 +18,7 @@ class ZoomOut : public Segue { sf::Texture next, last; bool firstPass{ true }; public: - void onDraw(sf::RenderTexture& surface) override { + void onDraw(IRenderer& renderer) override { double elapsed = getElapsed().asMilliseconds(); double duration = getDuration().asMilliseconds(); double alpha = ease::bezierPopOut(elapsed, duration); @@ -27,27 +27,27 @@ class ZoomOut : public Segue { sf::Texture temp, temp2; if (firstPass || !optimized) { - this->drawLastActivity(surface); + this->drawLastActivity(renderer); - surface.display(); // flip and ready the buffer - last = temp = sf::Texture(surface.getTexture()); // Make a copy of the source texture + renderer.display(); // flip and ready the buffer + last = temp = sf::Texture(renderer.getTexture()); // Make a copy of the source texture } else { temp = last; } sf::Sprite left(temp); - game::setOrigin(left, 0.5f, 0.5f); + sw::setOrigin(left, 0.5f, 0.5f); left.setPosition(windowSize.x/2.0f, windowSize.y/2.0f); left.setScale((float)alpha, (float)alpha); - surface.clear(); + renderer.clear(); if (firstPass || !optimized) { - this->drawNextActivity(surface); + this->drawNextActivity(renderer); - surface.display(); // flip and ready the buffer - next = temp2 = sf::Texture(surface.getTexture()); + renderer.display(); // flip and ready the buffer + next = temp2 = sf::Texture(renderer.getTexture()); } else { temp2 = next; @@ -55,8 +55,8 @@ class ZoomOut : public Segue { sf::Sprite right(temp2); - surface.draw(right); - surface.draw(left); + renderer.submit(Immediate(&right)); + renderer.submit(Immediate(&left)); firstPass = false; } diff --git a/src/Swoosh/ActionList.h b/src/Swoosh/ActionList.h index 59f7f0f..2ec66fb 100644 --- a/src/Swoosh/ActionList.h +++ b/src/Swoosh/ActionList.h @@ -2,36 +2,43 @@ #include #include -namespace swoosh { +namespace sw +{ class ActionList; /** @class ActionItem - Your standard ActionItem is non-blocking and seemingly runs concurrent with your other + Your standard ActionItem is non-blocking and seemingly runs concurrent with your other non-blocking action items */ - class ActionItem { + class ActionItem + { friend class ActionList; friend class ClearPreviousActions; friend class ClearAllActions; friend class ConditionalBranchListAction; protected: - bool isBlocking; + bool isBlocking{}; private: - bool isDoneFlag; - std::size_t index; - ActionList *list; + bool isDoneFlag{}; + std::size_t index{}; + ActionList *list{nullptr}; public: - ActionItem() { isBlocking = isDoneFlag = false; index = -1; list = nullptr; } - virtual ~ActionItem() {}; + ActionItem() + { + isBlocking = isDoneFlag = false; + index = -1; + list = nullptr; + } + virtual ~ActionItem(){}; virtual void update(sf::Time elapsed) = 0; - virtual void draw(sf::RenderTexture& surface) = 0; + virtual void draw(sf::RenderTexture &surface) = 0; void markDone() { isDoneFlag = true; } const bool isDone() const { return isDoneFlag; } const std::size_t getIndex() const { return index; } @@ -40,27 +47,28 @@ namespace swoosh { /** @class BlockingActionItem When the action list reaches a BlockingActionItem the action list stops iterating. - Only until the blocking action item is marked for cleanup using `markDone()` + Only until the blocking action item is marked for cleanup using `markDone()` will the action list continue passed. */ - class BlockingActionItem : public ActionItem { + class BlockingActionItem : public ActionItem + { public: - BlockingActionItem() : ActionItem() { + BlockingActionItem() : ActionItem() + { isBlocking = true; } - virtual ~BlockingActionItem() { } + virtual ~BlockingActionItem() {} virtual void update(sf::Time elapsed) = 0; - virtual void draw(sf::RenderTexture& surface) = 0; + virtual void draw(sf::RenderTexture &surface) = 0; }; class ClearPreviousActions; class ClearAllActions; class ConditionalBranchListAction; - /** @class ActionList @@ -76,95 +84,114 @@ namespace swoosh { at cleanup and when an item is marked done using `markDone()` */ - class ActionList { + class ActionList + { friend class ClearPreviousActions; friend class ClearAllActions; friend class ConditionalBranchListAction; + private: - std::vector items; - bool clearFlag; + std::vector items; + bool clearFlag{}; + public: - void updateIndexesFrom(std::size_t pos) { - for (pos; pos < items.size(); pos++) { + void updateIndexesFrom(std::size_t pos) + { + for (pos; pos < items.size(); pos++) + { items[pos]->index++; } } - void insert(std::size_t pos, ActionItem* item) { + void insert(std::size_t pos, ActionItem *item) + { item->list = this; items.insert(items.begin() + pos, item); item->index = pos; updateIndexesFrom(pos); } - - void insert(std::size_t pos, ActionList* other) { + void insert(std::size_t pos, ActionList *other) + { if (other == nullptr) throw std::runtime_error("ActionList is nullptr"); - for (int i = 0; i < other->items.size(); i++) { + for (int i = 0; i < other->items.size(); i++) + { this->insert(pos + i, other->items[i]); } other->items.clear(); } - void add(ActionItem* item) { + void add(ActionItem *item) + { item->list = this; items.push_back(item); item->index = items.size() - 1; } - const bool isEmpty() const { + const bool isEmpty() const + { return items.empty(); } - void clear() { - for (auto item : items) { + void clear() + { + for (auto item : items) + { delete item; } items.clear(); } - void append(ActionList& list) { - for (int i = 0; i < list.items.size(); i++) { + void append(ActionList &list) + { + for (int i = 0; i < list.items.size(); i++) + { items.push_back(list.items[i]); } list.items.clear(); } - void append(ActionList* list) { + void append(ActionList *list) + { if (list == nullptr) throw std::runtime_error("ActionList is nullptr"); - for (int i = 0; i < list->items.size(); i++) { + for (int i = 0; i < list->items.size(); i++) + { items.push_back(list->items[i]); } list->items.clear(); - } - void update(sf::Time elapsed) { - for (int i = 0; i < items.size();) { - if (items[i]->isDone()) { + void update(sf::Time elapsed) + { + for (int i = 0; i < items.size();) + { + if (items[i]->isDone()) + { delete items[i]; items.erase(items.begin() + i); continue; } - items[i]->index = (std::size_t) i; + items[i]->index = (std::size_t)i; items[i]->update(elapsed); - if (clearFlag) { + if (clearFlag) + { clearFlag = false; i = 0; continue; // startover, the list has been modified } - if (items[i]->isBlocking) { + if (items[i]->isBlocking) + { break; } @@ -172,10 +199,13 @@ namespace swoosh { } } - void draw(sf::RenderTexture& surface) { - for (auto item : items) { + void draw(sf::RenderTexture &surface) + { + for (auto item : items) + { item->draw(surface); - if (item->isBlocking) { + if (item->isBlocking) + { break; } } @@ -183,7 +213,8 @@ namespace swoosh { ActionList() { clearFlag = false; } - ~ActionList() { + ~ActionList() + { clear(); } }; @@ -196,26 +227,31 @@ namespace swoosh { ClearPreviousActions is suited for this task. */ - class ClearPreviousActions : public BlockingActionItem { + class ClearPreviousActions : public BlockingActionItem + { friend class ActionList; public: - ClearPreviousActions() { - + ClearPreviousActions() + { } - virtual void update(sf::Time elapsed) { + virtual void update(sf::Time elapsed) + { if (isDone()) return; - for (int i = 0; i < list->items.size();) { - ActionItem* item = list->items[i]; - if (item != this) { + for (int i = 0; i < list->items.size();) + { + ActionItem *item = list->items[i]; + if (item != this) + { delete item; list->items.erase(list->items.begin() + i); continue; } - else { + else + { break; } } @@ -224,7 +260,8 @@ namespace swoosh { markDone(); } - virtual void draw(sf::RenderTexture& surface) { + virtual void draw(sf::RenderTexture &surface) + { } }; @@ -232,25 +269,29 @@ namespace swoosh { @class ClearAllActions @brief You may need to signal a cleanup in the action list to remove everything including non-blocking action items - ClearAllActions is suited for this task. + ClearAllActions is suited for this task. */ - class ClearAllActions : public BlockingActionItem { + class ClearAllActions : public BlockingActionItem + { friend class ActionList; public: - ClearAllActions() { - + ClearAllActions() + { } - virtual void update(sf::Time elapsed) { + virtual void update(sf::Time elapsed) + { if (isDone()) return; // Delete and remove everything but this one - for (int i = 0; i < list->items.size();) { - ActionItem* item = list->items[i]; - if (item != this) { + for (int i = 0; i < list->items.size();) + { + ActionItem *item = list->items[i]; + if (item != this) + { delete item; list->items.erase(list->items.begin() + i); continue; @@ -262,15 +303,16 @@ namespace swoosh { markDone(); } - virtual void draw(sf::RenderTexture& surface) { + virtual void draw(sf::RenderTexture &surface) + { } }; /** @class ConditionalBranchListAction - + @brief Situations may require to branch off into separate lists depending on the query function - + ConditionalBranchListAction is similar to an if-else block where the outcome is to append different action items It takes a lambda function that returns bool and two ActionList pointers. @@ -278,41 +320,49 @@ namespace swoosh { ConditionalBranchListAction will delete the action lists and cleanup for you */ - class ConditionalBranchListAction : public BlockingActionItem { + class ConditionalBranchListAction : public BlockingActionItem + { friend class ActionList; private: - ActionList *branchIfTrue, *branchIfFalse; + ActionList *branchIfTrue{nullptr}, *branchIfFalse{nullptr}; std::function condition; + public: ConditionalBranchListAction(std::function condition, ActionList *branchIfTrue, ActionList *branchIfFalse) - : condition(condition), branchIfTrue(branchIfTrue), branchIfFalse(branchIfFalse) { - + : condition(condition), branchIfTrue(branchIfTrue), branchIfFalse(branchIfFalse) + { } - ~ConditionalBranchListAction() { - if (branchIfFalse) delete branchIfFalse; - if (branchIfTrue) delete branchIfTrue; + ~ConditionalBranchListAction() + { + if (branchIfFalse) + delete branchIfFalse; + if (branchIfTrue) + delete branchIfTrue; } - virtual void update(sf::Time elapsed) { + virtual void update(sf::Time elapsed) + { if (isDone()) return; - if (condition()) { + if (condition()) + { list->insert(getIndex(), branchIfTrue); branchIfFalse->clear(); } - else { + else + { list->insert(getIndex(), branchIfFalse); branchIfTrue->clear(); } - markDone(); } - virtual void draw(sf::RenderTexture& surface) { + virtual void draw(sf::RenderTexture &surface) + { } }; } diff --git a/src/Swoosh/Activity.h b/src/Swoosh/Activity.h index 9268e63..e81543f 100644 --- a/src/Swoosh/Activity.h +++ b/src/Swoosh/Activity.h @@ -1,9 +1,288 @@ #pragma once +#include #include +#include +#include +#include +#include -namespace swoosh { +namespace sw { class ActivityController; /* forward decl */ + // Forward decl. + class PopDataHolder; + class Context; + + namespace { + // FOR INTERNAL USE ONLY. + // + // This utility class is used to manage the data stored in the Context. + // Data stored must be copyable otherwise the compiler will abort. + // + // The bucket remembers its type and can dissolve single type values + // into their immediate return type via `T& read()`. + // + // Multi values are returned as `std::tuple& read()`. + // + // The data in Bucket cleans up after itself. + class Bucket { + friend class Context; + + void (*deleter)(void*) { nullptr }; + void* data{ nullptr }; + std::string underliningTypename; + + // + // Private memory management methods + // + + template + void copy(const std::optional& option) { + if (!option.has_value()) return; + copy(*option); + } + + template + void copy(const UnderliningType& copyable) { + static void (*DeletePolicyPtr)(void*) = + +[](void* data){ + ((UnderliningType*)data)->~UnderliningType(); + free(data); + }; + + // sanity check + cleanup(); + + deleter = DeletePolicyPtr; + data = malloc(sizeof(UnderliningType)); + + UnderliningType* ptr = new (data) UnderliningType; + *ptr = copyable; + underliningTypename = typeid(UnderliningType).name(); + } + + // Initialize data with value T or a tuple + // Iff param is one optional whose value is nullopt, then noop + // Iff param is one optional with a value, then the value is extracted + template + void init(T&& t, Ts&&...ts) { + if constexpr (sizeof...(Ts) == 0) { + static_assert( + std::is_copy_constructible_v, + "Popping with userdata requires copying" + ); + + copy(t); + } + else { + static_assert( + std::is_copy_constructible_v + && (std::is_copy_constructible_v, ...), + "Popping with userdata requires copying" + ); + copy(std::tuple{ t, ts... }); + } + } + + + void cleanup() { + // free allocated memory + if (deleter) (*deleter)(data); + data = nullptr; + } + + bool empty() const { + return data == nullptr; + } + + // Checks the underlining type + template + bool has() const { + if constexpr (sizeof...(Ts) == 0) { + return underliningTypename == typeid(T).name(); + } + else { + return underliningTypename == typeid(std::tuple).name(); + } + } + + // Returns reference to the underlining object or the tuple + template + auto read() -> decltype(auto) { + if constexpr (sizeof...(Ts) == 0) { + return *((T*)data); + } + else { + return *((std::tuple*)data); + } + } + + const std::string& type() const { + return underliningTypename; + } + + // + // public + // + + public: + Bucket() = default; + Bucket(Bucket&& rhs) noexcept { + *this = std::move(rhs); + } + + Bucket& operator=(Bucket&& rhs) noexcept { + std::swap(underliningTypename, rhs.underliningTypename); + std::swap(data, rhs.data); + std::swap(deleter, rhs.deleter); + rhs.underliningTypename.clear(); + rhs.data = nullptr; + rhs.deleter = nullptr; + return *this; + } + + ~Bucket() { + cleanup(); + } + }; + } + + /** + * @class Context + * @brief If a push() later produces data via pop(...), it lives in Context. + */ + class Context { + friend class PopDataHolder; + Context* adopted{ nullptr }; + Bucket mem{}; + + void adopt(Context&& other) { + adopted = new Context(std::move(other)); + } + + public: + Context() = default; + + // Raw str specialization + Context(const char* str) { + mem.init(std::string(str)); + } + + template + Context(Ts&&...ts) { + mem.init(std::forward(ts)...); + } + + Context(Context&& rhs) noexcept { + *this = std::move(rhs); + } + + ~Context() { + // free adopted memory + delete adopted; + adopted = nullptr; + + // Bucket::~Bucket() will invoke + } + + Context& operator=(Context&& rhs) noexcept { + std::swap(mem, rhs.mem); + std::swap(adopted, rhs.adopted); + rhs.adopted = nullptr; + return *this; + } + + template + const bool has() const { + return mem.has(); + } + + template + auto read() -> decltype(auto) { + return mem.read(); + } + + const bool empty() const { + return mem.empty(); + } + + const std::string& type() const { + return mem.type(); + } + + std::optional previous(size_t count = 0) { + Context* prev = adopted; + while (count-- > 0 && prev) { + prev = prev->adopted; + } + + if (prev == nullptr) return std::nullopt; + return std::make_optional(std::move(*prev)); + } + }; + + /** + * @class PopDataHolder + * @brief A construct to handle data from popped activities + */ + class PopDataHolder { + friend class ActivityController; + friend class Activity; + + using CallbackFn = std::function; + CallbackFn callback; + + static PopDataHolder& dummy() { + static PopDataHolder _; return _; + } + + Context context; + bool adopted{}; + + // Default constructor + PopDataHolder() = default; + + // No copies + PopDataHolder(const PopDataHolder&) = delete; + + // No moves + PopDataHolder(PopDataHolder&&) = delete; + + // Carry over context data from another PopDataHolder + // Our context will adopt the data (own) + void carry(PopDataHolder& from) { + context.adopt(std::move(from.context)); + } + + void exec() { + if (!callback) return; + callback(context); + } + + PopDataHolder& reset() { + context = Context(); + callback = nullptr; + adopted = false; + return *this; + } + + template + PopDataHolder& resolve(Args&&... args) { + context = Context(std::forward(args)...); + return *this; + } + + public: + void take(const CallbackFn& fn) { + callback = fn; + } + + void adopt() { + adopted = true; + } + }; + + /** @class Activity @brief An activity is an isolated screen with content drawn onto it or a unique scene in a game @@ -18,15 +297,16 @@ namespace swoosh { - onLeave , called when this activity is leaving the view during a segue - onEnd , called when the activity has finished leaving a view after a segue - onUpdate, called every tick while still in view - - onDraw , called every tick while still in view * + - onDraw , called every tick while still in view (*) - * some segues may optimize and skip draw calls (see: class WhiteWashFade) + (*) some segues may optimize and skip draw calls (see: class WhiteWashFade) */ class Activity { friend class ActivityController; private: - bool started; //!< Flag denotes if an activity should call onStart() or onResume() + bool started{}; //!< Flag denotes if an activity should call onStart() or onResume() + PopDataHolder popDataHolder; //!< Callback handle when returning protected: ActivityController* controller{ nullptr }; //!< Pointer to the activity controller @@ -48,14 +328,14 @@ namespace swoosh { virtual void onResume() = 0; virtual void onEnd() = 0; virtual void onUpdate(double elapsed) = 0; - virtual void onDraw(sf::RenderTexture& surface) = 0; - virtual ~Activity() { ; } + virtual void onDraw(IRenderer& renderer) = 0; + virtual ~Activity() { } void setView(const sf::View& view) { this->view = view; } - void setView(const sf::Vector2u& size) { this->view = sf::View(sf::FloatRect(0.0f, 0.0f, (float)size.x, (float)size.y)); } - void setView(const sf::FloatRect& rect) { this->view = sf::View(rect); } - void setBGColor(const sf::Color color) { this->bgColor = color; } - const sf::View getView() const { return this->view; } - const sf::Color getBGColor() const { return this->bgColor; } + void setView(const sf::Vector2u& size) { view = sf::View(sf::FloatRect(0.0f, 0.0f, (float)size.x, (float)size.y)); } + void setView(const sf::FloatRect& rect) { view = sf::View(rect); } + void setBGColor(const sf::Color color) { bgColor = color; } + const sf::View getView() const { return view; } + const sf::Color getBGColor() const { return bgColor; } ActivityController& getController() { return *controller; } }; } diff --git a/src/Swoosh/ActivityController.h b/src/Swoosh/ActivityController.h index a1d34ef..9545122 100644 --- a/src/Swoosh/ActivityController.h +++ b/src/Swoosh/ActivityController.h @@ -1,73 +1,115 @@ #pragma once - -#include "Activity.h" -#include "Segue.h" -#include "Timer.h" +#include +#include +#include +#include #include #include #include -#include #include #include +#include -namespace swoosh { +namespace sw +{ class CopyWindow; //!< forward decl - class ActivityController { - friend class swoosh::Segue; + class ActivityController + { + friend class sw::Segue; + friend class sw::Activity; + + public: + // Forward decl. + template + class segue; private: - swoosh::Activity* last{ nullptr }; //!< Pointer of the last activity - std::stack activities; //!< Stack of activities - sf::RenderWindow& handle; //!< sfml window reference - sf::Vector2u virtualWindowSize; //!< Window size requested to render with - bool willLeave{}; //!< If true, the activity will leave - bool useShaders{ true }; //!< If false, segues can considerately use shader effects - mutable sf::RenderTexture* surface{ nullptr }; //!< Render surface to draw to + // helper util for `hasPendingChanges` + class SpinWhileTrue + { + bool &value; + + public: + SpinWhileTrue(bool &source) : value(source) + { + // Behave like a spin-lock + // If this true else-where, wait our turn to acquire it + while (value) + { + }; + value = true; + } + + ~SpinWhileTrue() + { + value = false; + } + + void set(const bool newValue) + { + value = newValue; + } + }; + + Activity *last{nullptr}; //!< Pointer of the last activity + std::stack activities; //!< Stack of activities + sf::RenderWindow &handle; //!< sfml window reference + sf::Vector2u virtualWindowSize; //!< Window size requested to render with + bool hasPendingChanges{}; //!< If true, the activity controller is mutating the stack + bool isUpdating{}; //!< If true, the controller is updating activities and may mutate the stack + bool useShaders{true}; //!< If false, segues can considerately use shader effects + bool clearBeforeDraw{true}; //!< If true, clears the render target with the Activity's bg color + mutable IRenderer *renderer{nullptr}; //!< Active render instance to submit draw events to + std::size_t renderIdx{0}; //!< Active render entry index + RenderEntries& renderEntries; //!< All registered render entries //!< Useful for state management and skipping need for dynamic casting - enum class SegueAction : int { - pop = 0, + enum class SegueAction : int + { + none = 0, push, replace, - none + pop, + rewind } segueAction; //!< Useful for state management - enum class StackAction : int { - pop = 0, + enum class StackAction : int + { + none = 0, push, replace, - none + clear, // clears entire stack + pop } stackAction; - quality qualityLevel{ quality::realtime }; //!< requested render quality + quality qualityLevel{quality::realtime}; //!< requested render quality public: /** @brief constructs the activity controller, sets the virtual window size to the window, and initializes default values */ - ActivityController(sf::RenderWindow& window) : handle(window) { - virtualWindowSize = handle.getSize(); + ActivityController(sf::RenderWindow &window, RenderEntries &renderEntries) + : handle(window), renderEntries(renderEntries) + { + assert(renderEntries.count() > 0 && "ActivityController RenderEntries was empty!"); - surface = new sf::RenderTexture(); - surface->create((unsigned int)handle.getSize().x, (unsigned int)handle.getSize().y); - willLeave = false; + virtualWindowSize = handle.getSize(); segueAction = SegueAction::none; stackAction = StackAction::none; - last = nullptr; } /** @brief constructs the activity controller, sets the virtual window size to the user's desired size, and initializes default values */ - ActivityController(sf::RenderWindow& window, sf::Vector2u virtualWindowSize) : handle(window) { - this->virtualWindowSize = virtualWindowSize; + ActivityController(sf::RenderWindow &window, sf::Vector2u virtualWindowSize, RenderEntries &renderEntries) + : handle(window), renderEntries(renderEntries) + { + assert(renderEntries.count() > 0 && "ActivityController RenderEntries was empty!"); - surface = new sf::RenderTexture(); - surface->create((unsigned int)virtualWindowSize.x, (unsigned int)virtualWindowSize.y); - willLeave = false; + this->virtualWindowSize = virtualWindowSize; segueAction = SegueAction::none; stackAction = StackAction::none; @@ -77,55 +119,120 @@ namespace swoosh { /** @brief Deconstructor deletes all activities and surfaces cleanly */ - virtual ~ActivityController() { - if (segueAction != SegueAction::none) { - swoosh::Segue* effect = static_cast(activities.top()); - delete effect; - activities.pop(); - } - - while (!activities.empty()) { - swoosh::Activity* activity = activities.top(); - activities.pop(); - delete activity; - } - - delete surface; + virtual ~ActivityController() + { + executeClearStackSafely(); } - + /** @brief Returns the virtual window size set at construction */ - const sf::Vector2u getVirtualWindowSize() const { + const sf::Vector2u getVirtualWindowSize() const + { return virtualWindowSize; } /** @brief Returns the render window */ - sf::RenderWindow& getWindow() { + sf::RenderWindow &getWindow() + { return handle; } /** - @brief Returns the render surface pointer + @brief Query the number of activities on the stack + */ + const std::size_t getStackSize() const + { + return activities.size(); + } + + /** + @brief Query if the stack is undergoing a change + Useful for multithreaded applications */ - sf::RenderTexture* getSurface() { - return surface; + const bool pendingChanges() const + { + return hasPendingChanges; } /** - @brief Query the number of activities on the stack + @brief Query the number of registered render entries + Note, this counts both valid and invalid render instances */ - const std::size_t getStackSize() const { - return activities.size(); + const std::size_t getNumOfRenderEntries() const + { + return renderEntries.count(); + } + + /** + @brief Returns the first render instance to matching type T + @return T* if instance has a base class T, nullptr if none match + */ + template + T* getRenderInstance() { + assert(renderEntries.built() && "Expected call to buildRenderEntries() first."); + for (auto&& entry : renderEntries.list()) { + T* instance = dynamic_cast(&entry.getInstance()); + if (instance != nullptr) return instance; + } + + return nullptr; + } + + /** + @brief Sets the active render instance to the entry at the given index + @param idx the base-0 index of the instance in the RenderEntries list + @return true if the instance was valid and set, false otherwsie + */ + bool activateRenderEntry(std::size_t idx) + { + assert(renderEntries.built() && "Expected call to buildRenderEntries() first."); + if (idx >= renderEntries.count()) return false; + + renderIdx = idx; + RenderEntry& entry = *std::next(renderEntries.list().begin(), renderIdx); + renderer = &(entry.getInstance()); + + return true; + } + + /** + @brief Fetch the active render entry + @warning Activate a render instance before using! + */ + const RenderEntry& getActiveRenderEntry() const + { + assert(renderer != nullptr && "Expected call to activateRenderEntry() first."); + return *std::next(renderEntries.list().begin(), renderIdx); + } + + /** + @brief Non-const qualified version + */ + RenderEntry& getActiveRenderEntry() + { + assert(renderer != nullptr && "Expected call to activateRenderEntry() first."); + return *std::next(renderEntries.list().begin(), renderIdx); + } + + + /** + @brief Request the activity controller to clear the render target (window or texture) before drawing. + @param enabled. Default is true. If false, the programmer must control clearing the targets themselves. + */ + void clearRenderTargetBeforeDraw(bool enabled) + { + this->clearBeforeDraw = enabled; } /** @brief Request the activity controller and segue effects to use the provided quality mode @param mode. Default is real-time and high performance. See: @quality enum class. */ - void optimizeForPerformance(quality mode) { + void optimizeForPerformance(quality mode) + { qualityLevel = mode; } @@ -133,7 +240,8 @@ namespace swoosh { @brief Returns a quick check if the AC has been configured to use something other than realtime (default) @return false if qualityLevel == quality::realtime (default) */ - const bool isOptimizedForPerformance() const { + const bool isOptimizedForPerformance() const + { return qualityLevel != quality::realtime; } @@ -141,7 +249,8 @@ namespace swoosh { @brief Request the activity controller and segue effects to use shaders @param enabled. Default is shader support. If false, segues should not use shaders. */ - void enableShaders(bool enabled) { + void enableShaders(bool enabled) + { useShaders = enabled; } @@ -149,39 +258,56 @@ namespace swoosh { @brief Returns a quick check if the AC has been configured to use shaders @return true by default unless manually disabled by `enableShaders(false)` */ - const bool isShadersEnabled() const { + const bool isShadersEnabled() const + { return useShaders; } /** @brief Query the requested quality mode - + This should be used by segue effects to provide alternative visuals for various-grade GPUs */ - const quality getRequestedQuality() const { + const quality getRequestedQuality() const + { return qualityLevel; } /** - @class segue + @class SegueImpl @brief This class is used internally to hide a lot of ugliness that makes the segue transition API easier to read */ - template - class segue { + template + class SegueImpl + { public: /** @brief This will start a POP state for the activity controller and creates a segue object onto the stack e.g. queuePop>(); // will transition from the current scene to the last with a fadeout effect */ - void delegateActivityPop(ActivityController& owner) { - swoosh::Activity* last = owner.activities.top(); + template + void delegateActivityPop(ActivityController& owner, Args&&... args) + { + SpinWhileTrue _(owner.hasPendingChanges); + + Activity *last = owner.activities.top(); owner.activities.pop(); - swoosh::Activity* next = owner.activities.top(); + Activity *next = owner.activities.top(); owner.activities.pop(); - swoosh::Segue* effect = new T(DurationType::value(), last, next); + next->popDataHolder.resolve(std::forward(args)...); + + // User asked that data sent to us moves on with the next activity + if (last->popDataHolder.adopted) { + next->popDataHolder.carry(last->popDataHolder); + } + + // React to pop result before segue begins + next->popDataHolder.exec(); + + Segue *effect = new T(DurationType::value(), last, next); sf::Vector2u windowSize = owner.getVirtualWindowSize(); sf::View view(sf::FloatRect(0, 0, (float)windowSize.x, (float)windowSize.y)); effect->setView(view); @@ -191,6 +317,7 @@ namespace swoosh { effect->onStart(); effect->started = true; owner.activities.push(effect); + } /** @@ -199,22 +326,27 @@ namespace swoosh { e.g. segue::to // pretty readable, transition to the level with the desired segue effect */ - template - class to { + template + class to + { public: + using activity_type = U; to() { ; } /** @brief This will start a PUSH state for the activity controller and creates a segue object onto the stack */ - template - void delegateActivityPush(ActivityController& owner, Args&&... args) { - bool hasLast = (owner.activities.size() > 0); - swoosh::Activity* last = hasLast ? owner.activities.top() : owner.generateActivityFromWindow(); - swoosh::Activity* next = new U(owner, std::forward(args)...); + template + PopDataHolder* delegateActivityPush(ActivityController &owner, Args&&... args) + { + SpinWhileTrue _(owner.hasPendingChanges); - swoosh::Segue* effect = new T(DurationType::value(), last, next); + const bool hasLast = (owner.activities.size() > 0); + Activity *last = hasLast ? owner.activities.top() : owner.generateActivityFromWindow(); + Activity *next = new U(owner, std::forward(args)...); + + Segue *effect = new T(DurationType::value(), last, next); sf::Vector2u windowSize = owner.getVirtualWindowSize(); sf::View view(sf::FloatRect(0.f, 0.f, (float)windowSize.x, (float)windowSize.y)); effect->setView(view); @@ -225,6 +357,8 @@ namespace swoosh { effect->onStart(); effect->started = true; owner.activities.push(effect); + + return &last->popDataHolder.reset(); } /** @@ -235,28 +369,36 @@ namespace swoosh { If no activity is found, all the activities are carefully placed back into the stack. If the target ativity is found, the traverse activities are deleted. */ - template - bool delegateActivityRewind(ActivityController& owner, Args&&... args) { - std::stack original; + template + bool delegateActivityRewind(ActivityController &owner, Args&&... args) + { + SpinWhileTrue _(owner.hasPendingChanges); - bool hasMore = (owner.activities.size() > 1); + const bool hasMore = (owner.activities.size() > 1); - if (!hasMore) { return false; } + if (!hasMore) + { + return false; + } - swoosh::Activity* last = owner.activities.top(); + Activity *last = owner.activities.top(); owner.activities.pop(); + Activity *next = owner.activities.top(); - swoosh::Activity* next = owner.activities.top(); + std::stack original; - while (dynamic_cast(next) == 0 && owner.activities.size() > 1) { + while (dynamic_cast(next) == 0 && owner.activities.size() > 1) + { original.push(next); owner.activities.pop(); next = owner.activities.top(); } - if (owner.activities.empty()) { + if (next == nullptr && owner.activities.empty()) + { // We did not find it, push the states back on the list and return false - while (original.size() > 0) { + while (original.size() > 0) + { owner.activities.push(original.top()); original.pop(); } @@ -267,24 +409,39 @@ namespace swoosh { } // We did find it, call on end to everything and free memory - while (original.size() > 0) { - swoosh::Activity* top = original.top(); + // Thenables are invalid + while (original.size() > 0) + { + Activity *top = original.top(); top->onEnd(); + delete top; + original.pop(); } // Remove next from the activity stack owner.activities.pop(); - swoosh::Segue* effect = new T(DurationType::value(), last, next); + Segue *effect = new T(DurationType::value(), last, next); sf::Vector2u windowSize = owner.getVirtualWindowSize(); sf::View view(sf::FloatRect(0.0f, 0.0f, (float)windowSize.x, (float)windowSize.y)); effect->setView(view); - + effect->setActivityViewFunc = &ActivityController::setActivityView; effect->resetViewFunc = &ActivityController::resetView; + // Fill our pop data holder with the event data, if any + next->popDataHolder.resolve(std::forward(args)...); + + // User asked that data sent to us moves on with the next activity + if (last->popDataHolder.adopted) { + next->popDataHolder.carry(last->popDataHolder); + } + + // React to any provided data before the scene starts + next->popDataHolder.exec(); + effect->onStart(); effect->started = true; owner.activities.push(effect); @@ -301,17 +458,20 @@ namespace swoosh { template struct IsSegueType { - static char is_to(swoosh::Activity*) { return 0; } + static char is_to(Activity*) { return 0; } static double is_to(...) { return 0; } - static T* t; + static T *t; // Returns true if type is Segue, false if otherwise - enum { value = sizeof(is_to(t)) != sizeof(char) }; + enum + { + value = sizeof(is_to(t)) != sizeof(char) + }; }; - /** + /** @class ResolvePushSegueIntent @brief This template structure will perform different acttions if the 2nd template argument is true or false */ @@ -322,67 +482,105 @@ namespace swoosh { @class ResolvePushSegueIntent @brief If type is a segue, resolves the intention by calling delegateActivityPush() */ - template + template struct ResolvePushSegueIntent { - template - ResolvePushSegueIntent(ActivityController& owner, Args&&... args) { - if (owner.segueAction == SegueAction::none) { - owner.segueAction = SegueAction::push; - T segueResolve; - segueResolve.delegateActivityPush(owner, std::forward(args)...); + using activity_type = typename T::activity_type; + + PopDataHolder* popDataHolder{ nullptr }; + + template + ResolvePushSegueIntent(ActivityController &owner, Args &&...args) + { + if (owner.segueAction != SegueAction::none) { + popDataHolder = &PopDataHolder::dummy(); + return; } + + owner.segueAction = SegueAction::push; + T segueResolve{}; + + popDataHolder = + &segueResolve + .delegateActivityPush(owner, std::forward(args)...)->reset(); } }; /** @class ResolvePushSegueIntent - @brief If type is NOT a segue, resolves the intention by directly modifying the stack + @brief If type is NOT a segue (Activity), resolves the intention by directly modifying the stack */ - template + template struct ResolvePushSegueIntent { - template - ResolvePushSegueIntent(ActivityController& owner, Args&&... args) { - if (owner.segueAction != SegueAction::none) return; + using activity_type = T; + + PopDataHolder* popDataHolder{ nullptr }; - swoosh::Activity* next = new T(owner, std::forward(args)...); + template + ResolvePushSegueIntent(ActivityController &owner, Args&&...args) + { + popDataHolder = &PopDataHolder::dummy(); - if (owner.last == nullptr && owner.activities.size() != 0) { + if (owner.segueAction != SegueAction::none) { + return; + } + + Activity *next = new T(owner, std::forward(args)...); + + if (owner.last != nullptr) { + popDataHolder = &owner.last->popDataHolder.reset(); + } + else if (owner.activities.size() > 0) { owner.last = owner.activities.top(); + popDataHolder = &owner.last->popDataHolder.reset(); } + SpinWhileTrue _(owner.hasPendingChanges); owner.activities.push(next); owner.stackAction = StackAction::push; } }; + // Shorthand resolver + template + using Intent = ResolvePushSegueIntent::value>; + /** @brief Immediately pushes a segue or activity onto the stack depending on the resolved class type */ - template - void push(Args&&... args) { - ResolvePushSegueIntent::value> intent(*this, std::forward(args)...); + template + PopDataHolder& push(Args&&... args) + { + Intent intent(*this, std::forward(args)...); + return *(intent.popDataHolder); } /** @brief Immediately replaces the current activity on the stack with a segue or activity depending on the resolved class type */ - template - void replace(Args&&... args) { - size_t before = this->activities.size(); - ResolvePushSegueIntent::value> intent(*this, std::forward(args)...); - size_t after = this->activities.size(); + template + void replace(Args&&...args) + { + const size_t before = activities.size(); + + ResolvePushSegueIntent::value> + intent(*this, std::forward(args)...); + + const size_t after = activities.size(); // quick feature hack: // We check if the push intent was resolved (+1 activity stack) // And then figure out which event was called (segue or direct stack modification?) // And flip that flag to become REPLACE - if (before < after) { - if (segueAction != SegueAction::none) { + if (before < after) + { + if (segueAction != SegueAction::none) + { segueAction = SegueAction::replace; } - else if (stackAction != StackAction::none) { + else if (stackAction != StackAction::none) + { stackAction = StackAction::replace; } } @@ -392,34 +590,49 @@ namespace swoosh { @brief Tries to pop the activity of the stack that may be replaced with a segue to transition to the previous activity on the stack @return true if we are able to pop, false if there less than 1 item on the stack or in the middle of a segue effect */ - template - const bool pop() { + template + const bool pop(Args&&... args) + { // Have to have more than 1 on the stack to have a transition effect... - bool hasLast = (activities.size() > 1); - if (!hasLast || segueAction != SegueAction::none) return false; + const size_t activity_len = activities.size(); + const bool hasLast = (activity_len > 1); + if (!hasLast || segueAction != SegueAction::none) + return false; segueAction = SegueAction::pop; - T segueResolve; - segueResolve.delegateActivityPop(*this); + T segueResolve{}; + segueResolve.delegateActivityPop(*this, std::forward(args)...); return true; } /** - @brief Tries to pop the activity of the stack - @return true if we are able to pop, false if there are no more items on the stack or in the middle of a segue effect - */ - const bool pop() { - bool hasMore = (activities.size() > 0); + @brief Tries to pop the activity of the stack + @return boolean representing if the AC could safely pop or not + */ + template + const bool pop(Args&&... args) + { + const bool hasMore = (activities.size() > 0); + + if (!hasMore || segueAction != SegueAction::none) + return false; - if (!hasMore || segueAction != SegueAction::none) return false; + // TODO: popping top() just to push top back on later is ugly. + // Perhaps consider a custom data structure now. + if (activities.size() > 1) { + sw::Activity* last = activities.top(); + activities.pop(); + sw::Activity* next = activities.top(); + next->popDataHolder.resolve(std::forward(args)...); + activities.push(last); + } - willLeave = true; + stackAction = StackAction::pop; return true; } - /** @class ResolveRewindSegueIntent @brief This template structure will perform different acttions if the 2nd template argument is true or false @@ -427,90 +640,139 @@ namespace swoosh { template struct ResolveRewindSegueIntent { - ResolveRewindSegueIntent(ActivityController& owner) { - } + ResolveRewindSegueIntent(ActivityController &owner) {} - bool RewindSuccessful; + bool rewindSuccessful{}; }; /** @class ResolveRewindSegueIntent @brief If type is a segue, resolves the intention by calling delegateActivityRewind() */ - template + template struct ResolveRewindSegueIntent { - bool RewindSuccessful; + bool rewindSuccessful{}; - template - ResolveRewindSegueIntent(ActivityController& owner, Args&&... args) { - if (owner.segueAction == SegueAction::none) { - owner.segueAction = SegueAction::pop; - T segueResolve; - RewindSuccessful = segueResolve.delegateActivityRewind(owner, std::forward(args)...); - } + template + ResolveRewindSegueIntent(ActivityController &owner, Args &&...args) + { + if (owner.segueAction != SegueAction::none) return; + + owner.segueAction = SegueAction::rewind; + T segueResolve{}; + rewindSuccessful = segueResolve.delegateActivityRewind(owner, std::forward(args)...); } }; /** - @class ResolveRewindSegueIntent - @brief If type is not a segue, looks for the matching activity. If it is not found, it does not rewind to that activity. + @class ResolveRewindSegueIntent + @brief If type is not a segue, looks for the matching activity. If it is not found, it does not rewind to that activity. - If a rewind is successful, all spanned activities are deleted. - */ - template + If a rewind is successful, all spanned activities are deleted. + */ + template struct ResolveRewindSegueIntent { - bool RewindSuccessful; + bool rewindSuccessful{}; - template - ResolveRewindSegueIntent(ActivityController& owner, Args&&... args) { - std::stack original; + template + ResolveRewindSegueIntent(ActivityController &owner, Args &&...args) + { + const bool hasLast = (owner.activities.size() > 0); - bool hasLast = (owner.activities.size() > 0); + if (!hasLast) + { + rewindSuccessful = false; + return; + } - if (!hasLast) { RewindSuccessful = false; return; } + SpinWhileTrue _(owner.hasPendingChanges); - swoosh::Activity* next = owner.activities.top(); + Activity* next = owner.activities.top(); + Activity* last = next; - while (dynamic_cast(next) == 0 && owner.activities.size() > 1) { + std::stack original; + + while (dynamic_cast(next) == 0 && owner.activities.size() > 1) + { original.push(next); owner.activities.pop(); next = owner.activities.top(); } - if (owner.activities.empty()) { + if (owner.activities.empty()) + { // We did not find it, push the states back on the list and return false - while (original.size() > 0) { + while (original.size() > 0) + { owner.activities.push(original.top()); original.pop(); } - RewindSuccessful = false; + rewindSuccessful = false; return; } - next->onResume(); + // Fill our pop data holder with the event data, if any + next->popDataHolder.resolve(std::forward(args)...); + + // User asked that data sent to us moves on with the next activity + if (last->popDataHolder.adopted) { + next->popDataHolder.carry(last->popDataHolder); + } + + // React to any provided data before the scene starts + next->popDataHolder.exec(); + + // Cleanup memory + while (original.size() > 0) { + Activity* top = original.top(); + top->onEnd(); + delete top; + original.pop(); + } + + if (next->started) { + next->onResume(); + } + else { + next->onStart(); + } } }; /** - @brief Tries to rewind the activity to a target activity type T in the stack - @return true if we are able to rewind (activity found), false if not found or in the middle of a transition + @brief Tries to rewind the activity to a target activity type T in the stack + @return true if we are able to rewind (activity found), false if not found or in the middle of a transition */ - template - bool rewind(Args&&... args) { - if (this->activities.size() <= 1) return false; + template + const bool rewind(Args&&... args) + { + if (activities.size() <= 1) + return false; ResolveRewindSegueIntent::value> intent(*this, std::forward(args)...); - return intent.RewindSuccessful; + return intent.rewindSuccessful; } /** @brief Returns the current activity pointer. Nullptr if no acitivty exists on the stack. */ - const swoosh::Activity* getCurrentActivity() const { - if (getStackSize() > 0) return activities.top(); + Activity *getCurrentActivity() + { + if (getStackSize() > 0) + return activities.top(); + + return nullptr; + } + + // const-qualified + const Activity* getCurrentActivity() const + { + if (getStackSize() > 0) + return activities.top(); + return nullptr; } @@ -518,29 +780,41 @@ namespace swoosh { @brief Updates the current activity or segue. Will manage the segue transition states. @param elapsed. Time in seconds - If optimized for performance and the quality mode is set to `mobile`, will not update the + If optimized for performance and the quality mode is set to `mobile`, will not update the activities in the segue to help increase performance on lower end hardware */ - void update(double elapsed) { + void update(double elapsed) + { + SpinWhileTrue _(isUpdating); + if (activities.size() == 0) return; - if (willLeave) { + // Stack actions imply no segue + if (stackAction == StackAction::pop) + { executePop(); - willLeave = false; - } - - if (activities.size() == 0) + stackAction = StackAction::none; return; - - if (stackAction == StackAction::push || stackAction == StackAction::replace) { - if (activities.size() > 1 && last) { + } + else if (stackAction == StackAction::clear) + { + executeClearStackSafely(); + return; + } + else if (stackAction == StackAction::push || stackAction == StackAction::replace) + { + if (activities.size() > 1 && last) + { last->onExit(); - if (stackAction == StackAction::replace) { + if (stackAction == StackAction::replace) + { + SpinWhileTrue _(hasPendingChanges); + auto top = activities.top(); - activities.pop(); // top - activities.pop(); // last, to be replaced by top + activities.pop(); // top + activities.pop(); // last, to be replaced by top activities.push(top); // fin delete last; } @@ -554,21 +828,27 @@ namespace swoosh { stackAction = StackAction::none; } - if (segueAction != SegueAction::none) { - swoosh::Segue* segue = static_cast(activities.top()); + // Check for segues + if (segueAction != SegueAction::none) + { + Segue *segue = static_cast(activities.top()); - if (getRequestedQuality() == quality::mobile) { - segue->timer.update(sf::seconds(static_cast(elapsed))); + if (getRequestedQuality() == quality::mobile) + { + segue->timer.update(sf::seconds(float(elapsed))); } - else { + else + { segue->onUpdate(elapsed); } - if (segue->timer.getElapsed().asMilliseconds() >= segue->duration.asMilliseconds()) { - endSegue(segue); + if (segue->timer.getElapsed().asMilliseconds() >= segue->duration.asMilliseconds()) + { + executePopSegue(segue); } } - else { + else + { activities.top()->onUpdate(elapsed); } } @@ -576,52 +856,72 @@ namespace swoosh { /** @brief Draws the current activity or segue and displays the result onto the window */ - void draw() { - if (activities.size() == 0) + void draw() + { + if (activities.size() == 0 || renderer == nullptr) return; - surface->setView(activities.top()->view); - activities.top()->onDraw(*surface); + Activity* top = activities.top(); - surface->display(); + // Prepare buffer for this pass + renderer->clear(sf::Color::Transparent); - // Capture buffer in a drawable context - sf::Sprite post(surface->getTexture()); + // Update viewport + renderer->setView(top->view); - // Fill in the bg color - handle.clear(activities.top()->bgColor); + // Draw to gpu texture + top->onDraw(*renderer); - // drawbuffer on top of the scene + // Perform our draw operations to the target texture in the renderer + renderer->draw(); + + // Prepare to be displayed + renderer->display(); + + // Create a rectangle with a copy of the texture + sf::Texture temp = renderer->getTexture(); + sf::Sprite post(temp); + + // Fill the window with the bg color + if (clearBeforeDraw) + handle.clear(top->bgColor); + + // draw screen handle.draw(post); - // Prepare buffer for next cycle - surface->clear(sf::Color::Transparent); + // Flush renderer-owned memory + renderer->flushMemory(); } /** - @brief Draws the current activity or segue onto an external render buffer - @param external. A render texture buffer to draw the content onto - */ - void draw(sf::RenderTexture& external) { - if (activities.size() == 0) - return; + @brief Pops everything off the stack with respect to the activity + life-cycle (onEnd(), remove, then delete for each entry) + This ensures any activity has proper cleanup in the order the + software should expect. + */ + void clearStackSafely() + { + stackAction = StackAction::clear; - external.setView(activities.top()->view); + if (isUpdating) + return; - // Fill in the bg color - handle.clear(activities.top()->bgColor); + executeClearStackSafely(); + } - activities.top()->onDraw(external); + void buildRenderEntries() { + renderEntries.buildEntries(); } private: /** - @brief Applies an activity's view onto the render surface. This is used internally for segues. + @brief Applies an activity's view onto the renderer. This is used internally for segues. This function is kept here to make the library files header-only and avoid linkage. */ - void setActivityView(sf::RenderTexture& surface, swoosh::Activity* activity) { - surface.setView(activity->getView()); + void setActivityView(IRenderer &renderer, Activity *activity) + { + renderer.setView(activity->getView()); } /** @@ -629,41 +929,55 @@ namespace swoosh { This function is kept here to make the library files header-only and avoid linkage. */ - void resetView(sf::RenderTexture& surface) { - surface.setView(handle.getDefaultView()); + void resetView(IRenderer &renderer) + { + renderer.setView(handle.getDefaultView()); } /** @brief This function properly terminates an active segue and pushes the next activity onto the stack */ - void endSegue(swoosh::Segue* segue) { + void executePopSegue(Segue *segue) + { + SpinWhileTrue _(hasPendingChanges); + segue->onEnd(); + activities.pop(); - swoosh::Activity* next = segue->next; + Activity *next = segue->next; - if (segueAction == SegueAction::pop || segueAction == SegueAction::replace) { + const bool popLike = segueAction == SegueAction::pop + || segueAction == SegueAction::replace + || segueAction == SegueAction::rewind; + + if (popLike) + { // We're removing an item from the stack - swoosh::Activity* last = segue->last; + Activity *last = segue->last; last->onEnd(); - if (next->started) { + if (next->started) + { next->onResume(); } - else { + else + { // We may have never started this activity because it existed // in the activity stack before being used... next->onStart(); next->started = true; } - if (segueAction == SegueAction::replace) { + if (segueAction == SegueAction::replace) + { activities.pop(); // remove last } delete last; } - else if (segueAction == SegueAction::push) { + else if (segueAction == SegueAction::push) + { next->onStart(); // new item on stack first time call next->started = true; } @@ -674,17 +988,67 @@ namespace swoosh { } /** - @brief When pop() is invoked, the pop is not executed immediately. It is deffered until it is safe to pop the activity off the stack. + @brief pop() is defered until it is safe to pop the activity off the stack. */ - void executePop() { - swoosh::Activity* activity = activities.top(); - activity->onEnd(); + void executePop() + { + SpinWhileTrue _(hasPendingChanges); + + Activity* last = activities.top(); + + last->onEnd(); activities.pop(); - if (activities.size() > 0) - activities.top()->onResume(); + if (activities.size() > 0) { + Activity* next = activities.top(); + + // User requested to have data move on with the next activity + if (last->popDataHolder.adopted) { + next->popDataHolder.carry(last->popDataHolder); + } + + // Take pop result + next->popDataHolder.exec(); + + if (next->started) { + next->onResume(); + } + else { + next->onStart(); + } + } + + delete last; + } + + void executeClearStackSafely() + { + SpinWhileTrue _(hasPendingChanges); + + while (activities.size() > 0) + { + if (segueAction != SegueAction::none) + { + Segue *segue = static_cast(activities.top()); + segue->onEnd(); + segue->last->onEnd(); + segue->next->onEnd(); + activities.pop(); // top + activities.pop(); // last + delete segue->last, segue->next, segue; + segueAction = SegueAction::none; + last = nullptr; + continue; + } + + Activity *current = activities.top(); + current->onEnd(); + activities.pop(); + delete current; + } - delete activity; + segueAction = SegueAction::none; + stackAction = StackAction::none; } /** @@ -693,10 +1057,9 @@ namespace swoosh { Used internally */ - inline Activity* generateActivityFromWindow(); + inline Activity *generateActivityFromWindow(); }; // ActivityController - /** @class CopyWindow @@ -707,29 +1070,39 @@ namespace swoosh { This is best suited for all actions: push, pop, and replace. - @warning This is a costly operation in SFML and can cause your program to stall for a split second + @warning This is a costly operation in SFML and can cause your program to stall for a split second depending on your computer. You should also be sure you don't clear your window content until AFTER the AC's update loop */ - class CopyWindow : public Activity { + class CopyWindow : public Activity + { sf::Texture framebuffer; sf::Sprite drawable; bool captured; - void copyWindowContents() { - if(captured) return; + void copyWindowContents() + { + if (captured) + return; // get the window handle auto& window = getController().getWindow(); // get all original view and viewport settings auto& view = window.getView(); - auto viewSize = view.getSize(); - auto viewportIntRect = window.getViewport(view); + auto& viewSize = view.getSize(); + auto& viewportIntRect = window.getViewport(view); // calculate the view based on any viewport adjustments // because we will copy the viewport pixels and we don't want those in our re-rendered image - sf::View newView = sf::View(sf::FloatRect((float)viewportIntRect.left, (float)viewportIntRect.top, (float)viewportIntRect.width, (float)viewportIntRect.height)); + sf::View newView = sf::View( + sf::FloatRect( + (float)viewportIntRect.left, + (float)viewportIntRect.top, + (float)viewportIntRect.width, + (float)viewportIntRect.height + ) + ); // screen size in pixels sf::Vector2u windowSize = window.getSize(); @@ -749,68 +1122,77 @@ namespace swoosh { } public: - CopyWindow(ActivityController& ac) : Activity(&ac) { + CopyWindow(ActivityController &ac) : Activity(&ac) + { captured = false; - } + } virtual ~CopyWindow() { ; } - void onStart() override { copyWindowContents(); }; - void onLeave() override { copyWindowContents(); }; - void onExit() override { }; - void onEnter() override { copyWindowContents(); }; - void onResume() override { }; - void onEnd() override { }; + void onStart() override { copyWindowContents(); }; + void onLeave() override { copyWindowContents(); }; + void onExit() override{}; + void onEnter() override { copyWindowContents(); }; + void onResume() override{}; + void onEnd() override{}; - void onUpdate(double elapsed) override { }; + void onUpdate(double elapsed) override{}; - void onDraw(sf::RenderTexture& surface) override { - surface.draw(drawable); + void onDraw(IRenderer &renderer) override + { + renderer.submit(&drawable); } }; // CopyWindow - // deferred implementation - Activity* ActivityController::generateActivityFromWindow() { + // late implementation + Activity *ActivityController::generateActivityFromWindow() + { return new CopyWindow(*this); } - // useful types in their own namespace - namespace types { - enum class direction : int { - left, right, up, down, max + // segue transition argument types + namespace arg { + enum class direction : int + { + left, + right, + up, + down, + max }; - template + template struct seconds { static sf::Time value() { return sf::seconds(val); } }; - template + template struct milliseconds { static sf::Time value() { return sf::milliseconds(val); } }; - template + template struct microseconds { static sf::Time value() { return sf::microseconds(val); } }; /* - shorthand notations*/ - - template> - using segue = ActivityController::segue; - - template + shorthand notations + */ + template using sec = seconds; - template + template using milli = milliseconds; - template + template using micro = microseconds; } -} + + // Public access to the implementation type + template > + using segue = typename ActivityController::SegueImpl; +} \ No newline at end of file diff --git a/src/Swoosh/Ease.h b/src/Swoosh/Ease.h index 35a0a2d..6e24497 100644 --- a/src/Swoosh/Ease.h +++ b/src/Swoosh/Ease.h @@ -2,7 +2,7 @@ #include #include -namespace swoosh { +namespace sw { namespace ease { static double pi = 3.14159265358979323846; //!< Precalculated pi diff --git a/src/Swoosh/EmbedGLSL.h b/src/Swoosh/EmbedGLSL.h index 8810cf8..fe0b5ca 100644 --- a/src/Swoosh/EmbedGLSL.h +++ b/src/Swoosh/EmbedGLSL.h @@ -24,7 +24,7 @@ #include -namespace swoosh { +namespace sw { namespace glsl{ static std::string formatGLSL(const char* glsl) { std::stringstream ss; @@ -73,4 +73,4 @@ namespace swoosh { } #define SWOOSH_EMBED_TO_STR(...) #__VA_ARGS__ -#define GLSL(version, ...) swoosh::glsl::formatGLSL("#version " #version "\n" SWOOSH_EMBED_TO_STR(#__VA_ARGS__)) +#define GLSL(version, ...) sw::glsl::formatGLSL("#version " #version "\n" SWOOSH_EMBED_TO_STR(#__VA_ARGS__)) diff --git a/src/Swoosh/Events/Events.h b/src/Swoosh/Events/Events.h new file mode 100644 index 0000000..96d6f98 --- /dev/null +++ b/src/Swoosh/Events/Events.h @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include + +// +// Event dispatcher interfaces introduced in Swoosh v2.0.0 +// + +namespace sw { + namespace events { + /** + @class IDispatcher + @brief am abstract class that broadcasts submitted events with base type `E` + */ + template + struct IDispatcher { + virtual ~IDispatcher() {} + + /** + @brief Implementation defined + */ + virtual void broadcast(const char* name, void* src, bool is_base) = 0; + + /** + @brief Sends the event to the correct handling function + @param event Event type const reference + */ + template + void submit(const Event& event) { + broadcast(typeid(Event).name(), (void*)&event, std::is_base_of::type, Event>::value); + } + }; + + /** + @class IReceiver + @brief an abstract class that recieves type-erased data for the given type `T` + */ + template + struct IReceiver { + virtual ~IReceiver() {} + + /** + @brief Implementation defined + */ + virtual void onEvent(const T& src) = 0; + + /** + @brief recasts the void* src type into type `T` + */ + void trampoline(void* src) { + onEvent(*((const T*)src)); + } + }; + + /** + @class ISubscriber + @brief an abstract class that redrects typename data to the correct reciever + */ + template + struct ISubscriber : public IReceiver... { + typedef void(*funptr)(ISubscriber* ptr, void* src); + using typestable = std::map; + typestable types; + + ISubscriber() { + (types.emplace(typeid(Ts).name(), +[](ISubscriber* ptr, void* src) { + ptr->IReceiver::trampoline(src); + }), ...); + } + virtual ~ISubscriber() {} + + /** + @brief Given a type name, find the registered reciever type and invoke its `onEvent()` function + */ + void redirect(const char* name, void* src, bool is_base) { + if (types.count(name)) { + types[name](this, src); + return; + } + + // Try to fall-back on a default handler for base types + if (!is_base) return; + onEvent(*((const E*)src)); + } + + /** + @brief Base event handler. Implementation defined. + */ + virtual void onEvent(const E& other) = 0; + }; + } +} \ No newline at end of file diff --git a/src/Swoosh/Game.h b/src/Swoosh/Game.h deleted file mode 100644 index 08cb0fc..0000000 --- a/src/Swoosh/Game.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include - -/**** - * This file is a collection of common SFML-related utilities that speed up prototyping - * @warning This file may be purged in the future as the library matures - */ -namespace swoosh { - namespace game { - static bool doesCollide(sf::Sprite& a, sf::Sprite& b) { - double mx = a.getPosition().x; - double my = a.getPosition().y; - double mw = a.getGlobalBounds().width; - double mh = a.getGlobalBounds().height; - - double mx2 = b.getPosition().x; - double my2 = b.getPosition().y; - double mw2 = b.getGlobalBounds().width; - double mh2 = b.getGlobalBounds().height; - - return (mx < mx2 + mw2 && mx + mw > mx2 && my < my2 + mh2 && my + mh > my2); - } - - // Degrees - template - static double angleTo(T& a, V& b) { - double angle = atan2(a.y - b.y, a.x - b.x); - - angle = angle * (180.0 / ease::pi); - - if (angle < 0) { - angle = 360.0 - (-angle); - } - - return angle; - } - - template