diff options
Diffstat (limited to 'source')
-rw-r--r-- | source/application/StarApplicationController.hpp | 2 | ||||
-rw-r--r-- | source/application/StarMainApplication_sdl.cpp | 104 | ||||
-rw-r--r-- | source/client/StarClientApplication.cpp | 2 | ||||
-rw-r--r-- | source/frontend/StarErrorScreen.cpp | 3 | ||||
-rw-r--r-- | source/frontend/StarMainInterface.cpp | 3 | ||||
-rw-r--r-- | source/frontend/StarTitleScreen.cpp | 3 | ||||
-rw-r--r-- | source/windowing/StarGuiContext.cpp | 9 | ||||
-rw-r--r-- | source/windowing/StarGuiContext.hpp | 3 |
8 files changed, 118 insertions, 11 deletions
diff --git a/source/application/StarApplicationController.hpp b/source/application/StarApplicationController.hpp index d19ab4c..b8d433c 100644 --- a/source/application/StarApplicationController.hpp +++ b/source/application/StarApplicationController.hpp @@ -6,6 +6,7 @@ #include "StarP2PNetworkingService.hpp" #include "StarUserGeneratedContentService.hpp" #include "StarDesktopService.hpp" +#include "StarImage.hpp" namespace Star { @@ -40,6 +41,7 @@ public: virtual void setBorderlessWindow() = 0; virtual void setVSyncEnabled(bool vSync) = 0; virtual void setCursorVisible(bool cursorVisible) = 0; + virtual bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) = 0; virtual void setAcceptingTextInput(bool acceptingTextInput) = 0; virtual AudioFormat enableAudio() = 0; diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index d833c04..6831a4f 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -3,6 +3,9 @@ #include "StarSignalHandler.hpp" #include "StarTickRateMonitor.hpp" #include "StarRenderer_opengl20.hpp" +#include "StarTtlCache.hpp" +#include "StarImage.hpp" +#include "StarImageProcessing.hpp" #include "SDL.h" #include "StarPlatformServices_pc.hpp" @@ -278,23 +281,25 @@ public: }; SDL_AudioSpec obtained = {}; - m_audioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); - if (!m_audioDevice) { + m_sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); + if (!m_sdlAudioDevice) { Logger::error("Application: Could not open audio device, no sound available!"); } else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) { - SDL_CloseAudioDevice(m_audioDevice); + SDL_CloseAudioDevice(m_sdlAudioDevice); Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!"); } else { Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, %s sample size buffer", obtained.samples); - SDL_PauseAudioDevice(m_audioDevice, 0); + SDL_PauseAudioDevice(m_sdlAudioDevice, 0); } m_renderer = make_shared<OpenGl20Renderer>(); m_renderer->setScreenSize(m_windowSize); + + m_cursorCache.setTimeToLive(30000); } ~SdlPlatform() { - SDL_CloseAudioDevice(m_audioDevice); + SDL_CloseAudioDevice(m_sdlAudioDevice); m_renderer.reset(); @@ -304,6 +309,11 @@ public: SDL_Quit(); } + void cleanup() { + m_cursorCache.ptr(m_currentCursor); + m_cursorCache.cleanup(); + } + void run() { try { Logger::info("Application: initialization..."); @@ -358,6 +368,8 @@ public: break; } + m_cursorCache.cleanup(); + int64_t spareMilliseconds = round(m_updateTicker.spareTime() * 1000); if (spareMilliseconds > 0) Thread::sleepPrecise(spareMilliseconds); @@ -373,7 +385,7 @@ public: Logger::error("Application: threw exception during shutdown: %s", outputException(e, true)); } - SDL_CloseAudioDevice(m_audioDevice); + SDL_CloseAudioDevice(m_sdlAudioDevice); m_application.reset(); } @@ -502,6 +514,10 @@ private: parent->m_cursorVisible = cursorVisible; } + bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) override { + return parent->setCursorImage(id, image, scale, offset); + } + void setAcceptingTextInput(bool acceptingTextInput) override { if (acceptingTextInput != parent->m_acceptingTextInput) { if (acceptingTextInput) @@ -656,6 +672,65 @@ private: } } + static const size_t MaximumCursorDimensions = 128; + static const size_t MaximumCursorPixelCount = MaximumCursorDimensions * MaximumCursorDimensions; + bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) { + auto imageSize = image->size().piecewiseMultiply(Vec2U::filled(scale)); + if (!scale || imageSize.max() > MaximumCursorDimensions || (size_t)(imageSize[0] * imageSize[1]) > MaximumCursorPixelCount) + return m_cursorVisible = false; + + auto& entry = m_cursorCache.get(m_currentCursor = { scale, offset, id }, [&](auto const&) { + auto entry = std::make_shared<CursorEntry>(); + if (scale != 1) { + List<ImageOperation> operations{ + FlipImageOperation{FlipImageOperation::Mode::FlipY}, // SDL wants an Australian cursor. + BorderImageOperation{1, Vec4B(), Vec4B(), false, false}, // Nearest scaling fucks up and clips half off the edges, work around this with border+crop for now. + ScaleImageOperation{ScaleImageOperation::Mode::Nearest, Vec2F::filled(scale)}, + CropImageOperation{RectI::withSize(Vec2I::filled(ceilf((float)scale / 2)), Vec2I(imageSize))} + }; + auto newImage = std::make_shared<Image>(move(processImageOperations(operations, *image))); + // Fix fully transparent pixels inverting the underlying display pixel on Windows (allowing this could be made configurable per cursor later!) + newImage->forEachPixel([](unsigned x, unsigned y, Vec4B& pixel) { if (!pixel[3]) pixel[0] = pixel[1] = pixel[2] = 0; }); + entry->image = move(newImage); + } + else + entry->image = image; + + auto size = entry->image->size(); + uint32_t pixelFormat; + switch (entry->image->pixelFormat()) { + case PixelFormat::RGB24: // I know this conversion looks wrong, but it's correct. I'm confused too. + pixelFormat = SDL_PIXELFORMAT_BGR888; + break; + case PixelFormat::RGBA32: + pixelFormat = SDL_PIXELFORMAT_ABGR8888; + break; + case PixelFormat::BGR24: + pixelFormat = SDL_PIXELFORMAT_RGB888; + break; + case PixelFormat::BGRA32: + pixelFormat = SDL_PIXELFORMAT_ARGB8888; + break; + default: + pixelFormat = SDL_PIXELFORMAT_UNKNOWN; + } + + entry->sdlSurface.reset(SDL_CreateRGBSurfaceWithFormatFrom( + (void*)entry->image->data(), + size[0], size[1], + entry->image->bitsPerPixel(), + entry->image->bytesPerPixel() * size[0], + pixelFormat) + ); + entry->sdlCursor.reset(SDL_CreateColorCursor(entry->sdlSurface.get(), offset[0] * scale, offset[1] * scale)); + + return entry; + }); + + SDL_SetCursor(entry->sdlCursor.get()); + return m_cursorVisible = true; + } + SignalHandler m_signalHandler; TickRateApproacher m_updateTicker = TickRateApproacher(60.0f, 1.0f); @@ -665,7 +740,22 @@ private: SDL_Window* m_sdlWindow = nullptr; SDL_GLContext m_sdlGlContext = nullptr; - SDL_AudioDeviceID m_audioDevice = 0; + SDL_AudioDeviceID m_sdlAudioDevice = 0; + + typedef std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> SDLSurfaceUPtr; + typedef std::unique_ptr<SDL_Cursor, decltype(&SDL_FreeCursor)> SDLCursorUPtr; + struct CursorEntry { + ImageConstPtr image = nullptr; + SDLSurfaceUPtr sdlSurface; + SDLCursorUPtr sdlCursor; + + CursorEntry() : image(nullptr), sdlSurface(nullptr, SDL_FreeSurface), sdlCursor(nullptr, SDL_FreeCursor) {}; + }; + + typedef tuple<unsigned, Vec2I, String> CursorDescriptor; + + HashTtlCache<CursorDescriptor, std::shared_ptr<CursorEntry>> m_cursorCache; + CursorDescriptor m_currentCursor; Vec2U m_windowSize = {800, 600}; WindowMode m_windowMode = WindowMode::Normal; diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index a80e650..4a156bd 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -159,7 +159,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) m_maxInterfaceScale = assets->json("/interface.config:maxInterfaceScale").toInt(); m_crossoverRes = jsonToVec2F(assets->json("/interface.config:interfaceCrossoverRes")); - appController->setCursorVisible(false); + appController->setCursorVisible(true); AudioFormat audioFormat = appController->enableAudio(); m_mainMixer = make_shared<MainMixer>(audioFormat.sampleRate, audioFormat.channels); diff --git a/source/frontend/StarErrorScreen.cpp b/source/frontend/StarErrorScreen.cpp index 872528d..6809d95 100644 --- a/source/frontend/StarErrorScreen.cpp +++ b/source/frontend/StarErrorScreen.cpp @@ -79,7 +79,8 @@ void ErrorScreen::renderCursor() { cursorPos[0] -= cursorOffset[0] * interfaceScale(); cursorPos[1] -= (cursorSize[1] - cursorOffset[1]) * interfaceScale(); - m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); + if (!m_guiContext->trySetCursor(m_cursor.drawable(), cursorOffset, interfaceScale())) + m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); } float ErrorScreen::interfaceScale() const { diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp index 2be8c2e..a3338ee 100644 --- a/source/frontend/StarMainInterface.cpp +++ b/source/frontend/StarMainInterface.cpp @@ -1377,7 +1377,8 @@ void MainInterface::renderCursor() { Vec2I cursorOffset = m_cursor.offset(); cursorPos[0] -= cursorOffset[0] * interfaceScale(); cursorPos[1] -= (cursorSize[1] - cursorOffset[1]) * interfaceScale(); - m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); + if (!m_guiContext->trySetCursor(m_cursor.drawable(), cursorOffset, interfaceScale())) + m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); if (m_cursorTooltip) { auto assets = Root::singleton().assets(); diff --git a/source/frontend/StarTitleScreen.cpp b/source/frontend/StarTitleScreen.cpp index 84790f0..4571f04 100644 --- a/source/frontend/StarTitleScreen.cpp +++ b/source/frontend/StarTitleScreen.cpp @@ -417,7 +417,8 @@ void TitleScreen::renderCursor() { cursorPos[0] -= cursorOffset[0] * interfaceScale(); cursorPos[1] -= (cursorSize[1] - cursorOffset[1]) * interfaceScale(); - m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); + if (!m_guiContext->trySetCursor(m_cursor.drawable(), cursorOffset, interfaceScale())) + m_guiContext->drawDrawable(m_cursor.drawable(), Vec2F(cursorPos), interfaceScale()); } float TitleScreen::interfaceScale() const { diff --git a/source/windowing/StarGuiContext.cpp b/source/windowing/StarGuiContext.cpp index 10cbefc..463e341 100644 --- a/source/windowing/StarGuiContext.cpp +++ b/source/windowing/StarGuiContext.cpp @@ -307,6 +307,15 @@ void GuiContext::drawImageStretchSet(ImageStretchSet const& imageSet, RectF cons drawInterfaceQuad(imageSet.end, RectF(Vec2F(), Vec2F(textureSize(imageSet.end))), end, color); } +bool GuiContext::trySetCursor(Drawable const& drawable, Vec2I const& offset, int pixelRatio) { + if (!drawable.isImage()) + return false; + + auto assets = Root::singleton().assets(); + auto& imagePath = drawable.imagePart().image; + return applicationController()->setCursorImage(imagePath, assets->image(imagePath), pixelRatio, offset); +} + RectF GuiContext::renderText(String const& s, TextPositioning const& position) { return textPainter()->renderText(s, position); } diff --git a/source/windowing/StarGuiContext.hpp b/source/windowing/StarGuiContext.hpp index 3457148..2f08394 100644 --- a/source/windowing/StarGuiContext.hpp +++ b/source/windowing/StarGuiContext.hpp @@ -90,6 +90,9 @@ public: void drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction = GuiDirection::Horizontal, Vec4B const& color = Vec4B::filled(255)); + // Returns true if the hardware cursor was successfully set to the drawable. Generally fails if the Drawable isn't an image part or the image is too big. + bool trySetCursor(Drawable const& drawable, Vec2I const& offset, int pixelRatio); + RectF renderText(String const& s, TextPositioning const& positioning); RectF renderInterfaceText(String const& s, TextPositioning const& positioning); |