Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/application/StarApplicationController.hpp2
-rw-r--r--source/application/StarMainApplication_sdl.cpp104
-rw-r--r--source/client/StarClientApplication.cpp2
-rw-r--r--source/frontend/StarErrorScreen.cpp3
-rw-r--r--source/frontend/StarMainInterface.cpp3
-rw-r--r--source/frontend/StarTitleScreen.cpp3
-rw-r--r--source/windowing/StarGuiContext.cpp9
-rw-r--r--source/windowing/StarGuiContext.hpp3
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);