diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h index fd077c4..b4b1b92 100644 --- a/olcPixelGameEngine.h +++ b/olcPixelGameEngine.h @@ -3,7 +3,7 @@ olcPixelGameEngine.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v2.17 | + | OneLoneCoder Pixel Game Engine v2.19 | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ @@ -282,7 +282,23 @@ +Reintroduced sub-pixel decals +Modified DrawPartialDecal() to quantise and correctly sample from tile atlasses +olc::Sprite::GetPixel() - Clamp Mode - + 2.18: +Option to not "dirty" layers with SetDrawTarget() - Thanks TerasKasi! + =Detection for Mac M1, fix for scroll wheel interrogation - Thanks ruarq! + 2.19: Textual Input(of)course Edition! + =Built in font is now olc::Renderable + +EnablePixelTransfer() - Gate if layer content transfers occur (speedup in decal only apps) + +TextEntryEnable() - Enables/Disables text entry mode + +TextEntryGetString() - Gets the current accumulated string in text entry mode + +TextEntryGetCursor() - Gets the current cursor position in text entry mode + +IsTextEntryEnabled() - Returns true if text entry mode is activated + +OnTextEntryComplete() - Override is called when user presses "ENTER" in text entry mode + +Potential for regional keyboard mappings - needs volunteers to do this + +ConsoleShow() - Opens built in command console + +ConsoleClear() - Clears built in command console output + +ConsoleOut() - Stream strings to command console output + +ConsoleCaptureStdOut() - Capture std::cout by redirecting to built-in console + +IsConsoleShowing() - Returns true if console is currently active + +OnConsoleCommand() - Override is called when command is entered into built in console !! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !! @@ -362,7 +378,7 @@ int main() #include #pragma endregion -#define PGE_VER 217 +#define PGE_VER 219 // O------------------------------------------------------------------------------O // | COMPILER CONFIGURATION ODDITIES | @@ -380,6 +396,10 @@ int main() #endif #endif +#if !defined(OLC_KEYBOARD_UK) + #define OLC_KEYBOARD_UK +#endif + #if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS) // C++14 @@ -909,6 +929,11 @@ namespace olc // Called once on application termination, so you can be one clean coder virtual bool OnUserDestroy(); + // Called when a text entry is confirmed with "enter" key + virtual void OnTextEntryComplete(const std::string& sText); + // Called when a console command is executed + virtual bool OnConsoleCommand(const std::string& sCommand); + public: // Hardware Interfaces // Returns true if window is currently in focus bool IsFocused() const; @@ -958,7 +983,7 @@ namespace olc public: // CONFIGURATION ROUTINES // Layer targeting functions - void SetDrawTarget(uint8_t layer); + void SetDrawTarget(uint8_t layer, bool bDirty = true); void EnableLayer(uint8_t layer, bool b); void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); void SetLayerOffset(uint8_t layer, float x, float y); @@ -1071,6 +1096,30 @@ namespace olc // Clip a line segment to visible area bool ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2); + // Dont allow PGE to mark layers as dirty, so pixel graphics don't update + void EnablePixelTransfer(const bool bEnable = true); + + // Command Console Routines + void ConsoleShow(const olc::Key &keyExit, bool bSuspendTime = true); + bool IsConsoleShowing() const; + void ConsoleClear(); + std::stringstream& ConsoleOut(); + void ConsoleCaptureStdOut(const bool bCapture); + + // Text Entry Routines + void TextEntryEnable(const bool bEnable, const std::string& sText = ""); + std::string TextEntryGetString() const; + int32_t TextEntryGetCursor() const; + bool IsTextEntryEnabled() const; + + + + private: + void UpdateTextEntry(); + void UpdateConsole(); + + public: + // Experimental Lightweight 3D Routines ================ #ifdef OLC_ENABLE_EXPERIMENTAL // Set Manual View Matrix @@ -1123,9 +1172,9 @@ namespace olc bool bEnableVSYNC = false; float fFrameTimer = 1.0f; float fLastElapsed = 0.0f; - int nFrameCount = 0; - Sprite* fontSprite = nullptr; - Decal* fontDecal = nullptr; + int nFrameCount = 0; + bool bSuspendTextureTransfer = false; + Renderable fontRenderable; std::vector vLayers; uint8_t nTargetLayer = 0; uint32_t nLastFPS = 0; @@ -1136,6 +1185,27 @@ namespace olc std::chrono::time_point m_tp1, m_tp2; std::vector vFontSpacing; + // Command Console Specific + bool bConsoleShow = false; + bool bConsoleSuspendTime = false; + olc::Key keyConsoleExit = olc::Key::F1; + std::stringstream ssConsoleOutput; + std::streambuf* sbufOldCout = nullptr; + olc::vi2d vConsoleSize; + olc::vi2d vConsoleCursor = { 0,0 }; + olc::vf2d vConsoleCharacterScale = { 1.0f, 2.0f }; + std::vector sConsoleLines; + std::list sCommandHistory; + std::list::iterator sCommandHistoryIt; + + // Text Entry Specific + bool bTextEntryEnable = false; + std::string sTextEntryString = ""; + int32_t nTextEntryCursor = 0; + std::vector> vKeyboardMap; + + + // State of keyboard bool pKeyNewState[256] = { 0 }; bool pKeyOldState[256] = { 0 }; @@ -1769,12 +1839,12 @@ namespace olc } } - void PixelGameEngine::SetDrawTarget(uint8_t layer) + void PixelGameEngine::SetDrawTarget(uint8_t layer, bool bDirty) { if (layer < vLayers.size()) { pDrawTarget = vLayers[layer].pDrawTarget.Sprite(); - vLayers[layer].bUpdate = true; + vLayers[layer].bUpdate = bDirty; nTargetLayer = layer; } } @@ -2105,7 +2175,7 @@ namespace olc { renderer->ClearBuffer(p, bDepth); } olc::Sprite* PixelGameEngine::GetFontSprite() - { return fontSprite; } + { return fontRenderable.Sprite(); } bool PixelGameEngine::ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2) { @@ -2140,6 +2210,11 @@ namespace olc return true; } + void PixelGameEngine::EnablePixelTransfer(const bool bEnable) + { + bSuspendTextureTransfer = !bEnable; + } + void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) { FillRect(pos.x, pos.y, size.x, size.y, p); } @@ -2776,7 +2851,7 @@ namespace olc { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; - DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + DrawPartialDecal(pos + spos, fontRenderable.Decal(), {float(ox) * 8.0f, float(oy) * 8.0f}, {8.0f, 8.0f}, scale, col); spos.x += 8.0f * scale.x; } } @@ -2799,7 +2874,7 @@ namespace olc { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; - DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + DrawPartialDecal(pos + spos, fontRenderable.Decal(), { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); spos.x += float(vFontSpacing[c - 32].y) * scale.x; } } @@ -2822,7 +2897,7 @@ namespace olc { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; - DrawPartialRotatedDecal(pos, fontDecal, fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); spos.x -= 8.0f; } } @@ -2845,7 +2920,7 @@ namespace olc { int32_t ox = (c - 32) % 16; int32_t oy = (c - 32) / 16; - DrawPartialRotatedDecal(pos, fontDecal, fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); spos.x -= float(vFontSpacing[c - 32].y); } } @@ -2899,7 +2974,7 @@ namespace olc { for (uint32_t i = 0; i < 8; i++) for (uint32_t j = 0; j < 8; j++) - if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) for (uint32_t is = 0; is < scale; is++) for (uint32_t js = 0; js < scale; js++) Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); @@ -2908,7 +2983,7 @@ namespace olc { for (uint32_t i = 0; i < 8; i++) for (uint32_t j = 0; j < 8; j++) - if (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) Draw(x + sx + i, y + sy + j, col); } sx += 8 * scale; @@ -2967,7 +3042,7 @@ namespace olc { for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) for (int32_t j = 0; j < 8; j++) - if (fontSprite->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) for (int32_t is = 0; is < int(scale); is++) for (int32_t js = 0; js < int(scale); js++) Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); @@ -2976,7 +3051,7 @@ namespace olc { for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) for (int32_t j = 0; j < 8; j++) - if (fontSprite->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) Draw(x + sx + i, y + sy + j, col); } sx += vFontSpacing[c - 32].y * scale; @@ -3004,6 +3079,204 @@ namespace olc if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; } + std::stringstream& PixelGameEngine::ConsoleOut() + { return ssConsoleOutput; } + + bool PixelGameEngine::IsConsoleShowing() const + { return bConsoleShow; } + + void PixelGameEngine::ConsoleShow(const olc::Key& keyExit, bool bSuspendTime) + { + if (bConsoleShow) + return; + + bConsoleShow = true; + bConsoleSuspendTime = bSuspendTime; + TextEntryEnable(true); + keyConsoleExit = keyExit; + pKeyboardState[keyConsoleExit].bHeld = false; + pKeyboardState[keyConsoleExit].bPressed = false; + pKeyboardState[keyConsoleExit].bReleased = true; + } + + void PixelGameEngine::ConsoleClear() + { sConsoleLines.clear(); } + + void PixelGameEngine::ConsoleCaptureStdOut(const bool bCapture) + { + if(bCapture) + sbufOldCout = std::cout.rdbuf(ssConsoleOutput.rdbuf()); + else + std::cout.rdbuf(sbufOldCout); + } + + void PixelGameEngine::UpdateConsole() + { + if (GetKey(keyConsoleExit).bPressed) + { + TextEntryEnable(false); + bConsoleSuspendTime = false; + bConsoleShow = false; + return; + } + + // Keep Console sizes based in real screen dimensions + vConsoleCharacterScale = olc::vf2d(1.0f, 2.0f) / (olc::vf2d(vViewSize) * vInvScreenSize); + vConsoleSize = (vViewSize / olc::vi2d(8, 16)) - olc::vi2d(2, 4); + + // If console has changed size, simply reset it + if (vConsoleSize.y != sConsoleLines.size()) + { + vConsoleCursor = { 0,0 }; + sConsoleLines.clear(); + sConsoleLines.resize(vConsoleSize.y); + } + + auto TypeCharacter = [&](const char c) + { + if (c >= 32 && c < 127) + { + sConsoleLines[vConsoleCursor.y].append(1, c); + vConsoleCursor.x++; + } + + if( c == '\n' || vConsoleCursor.x >= vConsoleSize.x) + { + vConsoleCursor.y++; vConsoleCursor.x = 0; + } + + if (vConsoleCursor.y >= vConsoleSize.y) + { + vConsoleCursor.y = vConsoleSize.y - 1; + for (size_t i = 1; i < vConsoleSize.y; i++) + sConsoleLines[i - 1] = sConsoleLines[i]; + sConsoleLines[vConsoleCursor.y].clear(); + } + }; + + // Empty out "std::cout", parsing as we go + while (ssConsoleOutput.rdbuf()->sgetc() != -1) + { + char c = ssConsoleOutput.rdbuf()->sbumpc(); + TypeCharacter(c); + } + + // Draw Shadow + GradientFillRectDecal({ 0,0 }, olc::vf2d(vScreenSize), olc::PixelF(0, 0, 0.5f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f)); + + // Draw the console buffer + SetDecalMode(olc::DecalMode::NORMAL); + for (int32_t nLine = 0; nLine < vConsoleSize.y; nLine++) + DrawStringDecal(olc::vf2d( 1, 1 + float(nLine) ) * vConsoleCharacterScale * 8.0f, sConsoleLines[nLine], olc::WHITE, vConsoleCharacterScale); + + // Draw Input State + FillRectDecal(olc::vf2d(1 + float((TextEntryGetCursor() + 1)), 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, olc::vf2d(8, 8) * vConsoleCharacterScale, olc::DARK_CYAN); + DrawStringDecal(olc::vf2d(1, 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, std::string(">") + TextEntryGetString(), olc::YELLOW, vConsoleCharacterScale); + } + + + + void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText) + { + if (bEnable) + { + nTextEntryCursor = int32_t(sText.size()); + sTextEntryString = sText; + bTextEntryEnable = true; + } + else + { + bTextEntryEnable = false; + } + } + + std::string PixelGameEngine::TextEntryGetString() const + { return sTextEntryString; } + + int32_t PixelGameEngine::TextEntryGetCursor() const + { return nTextEntryCursor; } + + bool PixelGameEngine::IsTextEntryEnabled() const + { return bTextEntryEnable; } + + + void PixelGameEngine::UpdateTextEntry() + { + // Check for typed characters + for (const auto& key : vKeyboardMap) + if (GetKey(std::get<0>(key)).bPressed) + { + sTextEntryString.insert(nTextEntryCursor, GetKey(olc::Key::SHIFT).bHeld ? std::get<2>(key) : std::get<1>(key)); + nTextEntryCursor++; + } + + // Check for command characters + if (GetKey(olc::Key::LEFT).bPressed) + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + if (GetKey(olc::Key::RIGHT).bPressed) + nTextEntryCursor = std::min(int32_t(sTextEntryString.size()), nTextEntryCursor + 1); + if (GetKey(olc::Key::BACK).bPressed && nTextEntryCursor > 0) + { + sTextEntryString.erase(nTextEntryCursor-1, 1); + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + } + if (GetKey(olc::Key::DEL).bPressed && nTextEntryCursor < sTextEntryString.size()) + sTextEntryString.erase(nTextEntryCursor, 1); + + if (GetKey(olc::Key::UP).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.begin()) + sCommandHistoryIt--; + + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + } + + if (GetKey(olc::Key::DOWN).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.end()) + { + sCommandHistoryIt++; + if (sCommandHistoryIt != sCommandHistory.end()) + { + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + else + { + nTextEntryCursor = 0; + sTextEntryString = ""; + } + } + } + } + + if (GetKey(olc::Key::ENTER).bPressed) + { + if (bConsoleShow) + { + std::cout << ">" + sTextEntryString + "\n"; + if (OnConsoleCommand(sTextEntryString)) + { + sCommandHistory.push_back(sTextEntryString); + sCommandHistoryIt = sCommandHistory.end(); + } + sTextEntryString.clear(); + nTextEntryCursor = 0; + } + else + { + OnTextEntryComplete(sTextEntryString); + TextEntryEnable(false); + } + } + } + // User must override these functions as required. I have not made // them abstract because I do need a default behaviour to occur if // they are not overwritten @@ -3016,7 +3289,12 @@ namespace olc bool PixelGameEngine::OnUserDestroy() { return true; } + + void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); } + bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; } + + // Externalised API void PixelGameEngine::olc_UpdateViewport() { int32_t ww = vScreenSize.x * vPixelSize.x; @@ -3150,6 +3428,9 @@ namespace olc float fElapsedTime = elapsedTime.count(); fLastElapsed = fElapsedTime; + if (bConsoleSuspendTime) + fElapsedTime = 0.0f; + // Some platforms will need to check for events platform->HandleSystemEvent(); @@ -3185,7 +3466,10 @@ namespace olc nMouseWheelDelta = nMouseWheelDeltaCache; nMouseWheelDeltaCache = 0; - // renderer->ClearBuffer(olc::BLACK, true); + if (bTextEntryEnable) + { + UpdateTextEntry(); + } // Handle Frame Update bool bExtensionBlockFrame = false; @@ -3196,6 +3480,12 @@ namespace olc } for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime); + if (bConsoleShow) + { + SetDrawTarget((uint8_t)0); + UpdateConsole(); + } + // Display Frame renderer->UpdateViewport(vViewPos, vViewSize); renderer->ClearBuffer(olc::BLACK, true); @@ -3213,7 +3503,7 @@ namespace olc if (layer->funcHook == nullptr) { renderer->ApplyTexture(layer->pDrawTarget.Decal()->id); - if (layer->bUpdate) + if (!bSuspendTextureTransfer && layer->bUpdate) { layer->pDrawTarget.Decal()->Update(); layer->bUpdate = false; @@ -3234,6 +3524,8 @@ namespace olc } } + + // Present Graphics to screen renderer->DisplayFrame(); @@ -3270,7 +3562,8 @@ namespace olc data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + fontRenderable.Sprite()->SetPixel(px, py, olc::Pixel(k, k, k, k)); if (++py == 48) { px++; py = 0; } } } - fontDecal = new olc::Decal(fontSprite); + fontRenderable.Decal()->Update(); constexpr std::array vSpacing = { { 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, @@ -3300,6 +3593,32 @@ namespace olc for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 }); + // UK Standard Layout +#ifdef OLC_KEYBOARD_UK + vKeyboardMap = + { + {olc::Key::A, "a", "A"}, {olc::Key::B, "b", "B"}, {olc::Key::C, "c", "C"}, {olc::Key::D, "d", "D"}, {olc::Key::E, "e", "E"}, + {olc::Key::F, "f", "F"}, {olc::Key::G, "g", "G"}, {olc::Key::H, "h", "H"}, {olc::Key::I, "i", "I"}, {olc::Key::J, "j", "J"}, + {olc::Key::K, "k", "K"}, {olc::Key::L, "l", "L"}, {olc::Key::M, "m", "M"}, {olc::Key::N, "n", "N"}, {olc::Key::O, "o", "O"}, + {olc::Key::P, "p", "P"}, {olc::Key::Q, "q", "Q"}, {olc::Key::R, "r", "R"}, {olc::Key::S, "s", "S"}, {olc::Key::T, "t", "T"}, + {olc::Key::U, "u", "U"}, {olc::Key::V, "v", "V"}, {olc::Key::W, "w", "W"}, {olc::Key::X, "x", "X"}, {olc::Key::Y, "y", "Y"}, + {olc::Key::Z, "z", "Z"}, + + {olc::Key::K0, "0", ")"}, {olc::Key::K1, "1", "!"}, {olc::Key::K2, "2", "\""}, {olc::Key::K3, "3", "#"}, {olc::Key::K4, "4", "$"}, + {olc::Key::K5, "5", "%"}, {olc::Key::K6, "6", "^"}, {olc::Key::K7, "7", "&"}, {olc::Key::K8, "8", "*"}, {olc::Key::K9, "9", "("}, + + {olc::Key::NP0, "0", "0"}, {olc::Key::NP1, "1", "1"}, {olc::Key::NP2, "2", "2"}, {olc::Key::NP3, "3", "3"}, {olc::Key::NP4, "4", "4"}, + {olc::Key::NP5, "5", "5"}, {olc::Key::NP6, "6", "6"}, {olc::Key::NP7, "7", "7"}, {olc::Key::NP8, "8", "8"}, {olc::Key::NP9, "9", "9"}, + {olc::Key::NP_MUL, "*", "*"}, {olc::Key::NP_DIV, "/", "/"}, {olc::Key::NP_ADD, "+", "+"}, {olc::Key::NP_SUB, "-", "-"}, {olc::Key::NP_DECIMAL, ".", "."}, + + {olc::Key::PERIOD, ".", ">"}, {olc::Key::EQUALS, "=", "+"}, {olc::Key::COMMA, ",", "<"}, {olc::Key::MINUS, "-", "_"}, {olc::Key::SPACE, " ", " "}, + + {olc::Key::OEM_1, ";", ":"}, {olc::Key::OEM_2, "/", "?"}, {olc::Key::OEM_3, "\'", "@"}, {olc::Key::OEM_4, "[", "{"}, + {olc::Key::OEM_5, "\\", "|"}, {olc::Key::OEM_6, "]", "}"}, {olc::Key::OEM_7, "#", "~"}, + + // {olc::Key::TAB, "\t", "\t"} + }; +#endif } void PixelGameEngine::pgex_Register(olc::PGEX* pgex) @@ -5086,7 +5405,11 @@ namespace olc { static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) { static const SEL deltaYSel = sel_registerName("deltaY"); +#if defined(__aarch64__) // Thanks ruarq! + double deltaY = ((double (*)(id, SEL))objc_msgSend)(theEvent, deltaYSel); +#else double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel); +#endif for (int i = 0; i < abs(deltaY); i++) { if (deltaY > 0) {