/* * This source file is part of RmlUi, the HTML/CSS Interface Middleware * * For the latest information, see http://github.com/mikke89/RmlUi * * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd * Copyright (c) 2019-2023 The RmlUi Team, and contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "RmlUi_Backend.h" #include "RmlUi_Platform_SFML.h" #include "RmlUi_Renderer_GL2.h" #include #include #include #include #include #include #include /** Custom render interface example for the SFML/GL2 backend. Overloads the OpenGL2 render interface to load textures through SFML's built-in texture loading functionality. */ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { public: // -- Inherited from Rml::RenderInterface -- void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override { if (texture) { sf::Texture::bind((sf::Texture*)texture); texture = RenderInterface_GL2::TextureEnableWithoutBinding; } RenderInterface_GL2::RenderGeometry(handle, translation, texture); } Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); if (!file_handle) return false; file_interface->Seek(file_handle, 0, SEEK_END); size_t buffer_size = file_interface->Tell(file_handle); file_interface->Seek(file_handle, 0, SEEK_SET); using Rml::byte; Rml::UniquePtr buffer(new byte[buffer_size]); file_interface->Read(buffer.get(), buffer_size, file_handle); file_interface->Close(file_handle); sf::Image image; if (!image.loadFromMemory(buffer.get(), buffer_size)) return false; // Convert colors to premultiplied alpha, which is necessary for correct alpha compositing. for (unsigned int x = 0; x < image.getSize().x; x++) { for (unsigned int y = 0; y < image.getSize().y; y++) { sf::Color color = image.getPixel(x, y); color.r = (sf::Uint8)((color.r * color.a) / 255); color.g = (sf::Uint8)((color.g * color.a) / 255); color.b = (sf::Uint8)((color.b * color.a) / 255); image.setPixel(x, y, color); } } sf::Texture* texture = new sf::Texture(); texture->setSmooth(true); if (!texture->loadFromImage(image)) { delete texture; return false; } texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y); return (Rml::TextureHandle)texture; } Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) override { sf::Texture* texture = new sf::Texture(); texture->setSmooth(true); if (!texture->create(source_dimensions.x, source_dimensions.y)) { delete texture; return false; } texture->update(source.data(), source_dimensions.x, source_dimensions.y, 0, 0); return (Rml::TextureHandle)texture; } void ReleaseTexture(Rml::TextureHandle texture_handle) override { delete (sf::Texture*)texture_handle; } }; // Updates the viewport and context dimensions, should be called whenever the window size changes. static void UpdateWindowDimensions(sf::RenderWindow& window, RenderInterface_GL2_SFML& render_interface, Rml::Context* context) { const int width = (int)window.getSize().x; const int height = (int)window.getSize().y; if (context) context->SetDimensions(Rml::Vector2i(width, height)); sf::View view(sf::FloatRect(0.f, 0.f, (float)width, (float)height)); window.setView(view); render_interface.SetViewport(width, height); } /** Global data used by this backend. Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). */ struct BackendData { SystemInterface_SFML system_interface; RenderInterface_GL2_SFML render_interface; sf::RenderWindow window; bool running = true; }; static Rml::UniquePtr data; bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize) { RMLUI_ASSERT(!data); data = Rml::MakeUnique(); // Create the window. sf::RenderWindow out_window; sf::ContextSettings context_settings; context_settings.stencilBits = 8; context_settings.antialiasingLevel = 2; const sf::Uint32 style = (allow_resize ? sf::Style::Default : (sf::Style::Titlebar | sf::Style::Close)); data->window.create(sf::VideoMode(width, height), window_name, style, context_settings); data->window.setVerticalSyncEnabled(true); if (!data->window.isOpen()) { data.reset(); return false; } // Optionally apply the SFML window to the system interface so that it can change its mouse cursor. data->system_interface.SetWindow(&data->window); UpdateWindowDimensions(data->window, data->render_interface, nullptr); return true; } void Backend::Shutdown() { data.reset(); } Rml::SystemInterface* Backend::GetSystemInterface() { RMLUI_ASSERT(data); return &data->system_interface; } Rml::RenderInterface* Backend::GetRenderInterface() { RMLUI_ASSERT(data); return &data->render_interface; } bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save) { RMLUI_ASSERT(data && context); // SFML does not seem to provide a way to wait for events with a timeout. (void)power_save; // The contents of this function is intended to be copied directly into your main loop. bool result = data->running; data->running = true; sf::Event ev; while (data->window.pollEvent(ev)) { switch (ev.type) { case sf::Event::Resized: UpdateWindowDimensions(data->window, data->render_interface, context); break; case sf::Event::KeyPressed: { const Rml::Input::KeyIdentifier key = RmlSFML::ConvertKey(ev.key.code); const int key_modifier = RmlSFML::GetKeyModifierState(); const float native_dp_ratio = 1.f; // See if we have any global shortcuts that take priority over the context. if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true)) break; // Otherwise, hand the event over to the context by calling the input handler as normal. if (!RmlSFML::InputHandler(context, ev)) break; // The key was not consumed by the context either, try keyboard shortcuts of lower priority. if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false)) break; } break; case sf::Event::Closed: result = false; break; default: RmlSFML::InputHandler(context, ev); break; } } return result; } void Backend::RequestExit() { RMLUI_ASSERT(data); data->running = false; } void Backend::BeginFrame() { RMLUI_ASSERT(data); sf::RenderWindow& window = data->window; window.resetGLStates(); window.clear(); data->render_interface.BeginFrame(); #if 0 // Draw a simple shape with SFML for demonstration purposes. Make sure to push and pop GL states as appropriate. sf::Vector2f circle_position(100.f, 100.f); window.pushGLStates(); sf::CircleShape circle(50.f); circle.setPosition(circle_position); circle.setFillColor(sf::Color::Blue); circle.setOutlineColor(sf::Color::Red); circle.setOutlineThickness(10.f); window.draw(circle); window.popGLStates(); #endif } void Backend::PresentFrame() { RMLUI_ASSERT(data); data->render_interface.EndFrame(); data->window.display(); // Optional, used to mark frames during performance profiling. RMLUI_FrameMark; }