Advertisement

Potential Article Idea

Started by August 01, 2013 04:03 AM
2 comments, last by Servant of the Lord 11 years, 1 month ago

I'm not sure whether this deems its own article or if it should be a thread or journal post. Let me know your thoughts.

Recently, I tried to integrate SFML into a Qt widget. I got most of the way through it using SFML 2.0 (when I finished they just released 2.1, but no worries. 2.1 works as well) and Qt 5.1, but couldn't get it to fully compile (using the 1.6 tutorial gave type conversion issues and then the solution for that gave vtable issues). After scouring the internet and getting very little search results or instructions that say to dig through source code to try to put together a solution, it turned out a small line of code, the problem was solved.

What does the SFML 2.1 + Qt 5.1 integration warrant?

I'd say post it as a journal entry, merely because it'll be out of date in a few months anyway, but it'll be useful to others googling about the problem.

Heheh, I've integrated SFML 2.0 with Qt 5.0 as well. I need to update to 2.1 and 5.1, but will hold off on that for now. I'd love to see your implementation just incase you caught anything I missed.

Does your Qt interface start to lag when your SFML widget is in view? Mine goes unbearably slow when in debug mode, but even in release mode the Qt interface is still moderately laggy (by about a half of a second - I haven't measured exact measurements). I'm not exactly sure what causes it, and it's rather low priority on my tasklist at the moment.

Well, if we start a discussion here about it, then maybe I can save you the trouble of writing it up on your journal! laugh.png

Shall I post my implementation as well? It's also mostly based on the SFML 1.6 tutorial.

Advertisement

I'm still working with the widget, learning the ins and outs, so I don't have a comment for the lag yet, but I haven't noticed it.

I used the SFML 1.6 code in full and went from there. I'll just start up the code blocks here or open a journal. Code isn't on this computer.

And feel free to post your implementation.

Mine looks like this:

ApplicationWindow.h


#ifndef ENGINE_PLATFORM_APPLICATIONWINDOW_H
#define ENGINE_PLATFORM_APPLICATIONWINDOW_H

#include <SFML/Graphics.hpp>
#include <QtWidgets/QWidget>

#include "Engine/Assorted/MeasurementUnits.h"
#include "Common/Types/WindowSize.h"

#include "Common/System/Macroes/BuildNote.h"

DEPENDS_ON_QT;
DEPENDS_ON_SFML;

//Warning: The sf::RenderWindow is not created until after construction, when the first showEvent() occures.
//This means any loading of SFML resources like images must take place *after* the first showEvent() of AreaWindow.

BUILD_NOTE("Should sf::RenderWindow be privately inheritted, instead of publicly?")

//This is the window that actually contains the game itself.
//Draw to it using SFML, using the sf::RenderTarget provided by ApplicationStructure::onDraw().
class ApplicationWindow : public QWidget, public sf::RenderWindow
{
	Q_OBJECT
public:
	ApplicationWindow(QWidget *parent = nullptr);
	~ApplicationWindow();
	
	//Manually set the size of the QWidget, forcing it to be 'size'.
	void SetWindowSize(WindowSize size);
	//Manually set the virtual size. (If set too high, more AreaCells will display then is currently loaded)
	void SetVirtualSize(VirtualWindowSize size);

	//Resets the camera size. Called automaticly when the virtual size changes.
	void ResetCamera();

	WindowSize GetWindowSize();
	VirtualWindowSize GetVirtualSize();

	//Sets the center position of the camera in the world. Do so each frame before drawing the world.
	void SetWorldCamera(VisiblePosInWorld cameraCenter);
	//Changes the camera mode, in preperation for drawing.
	void SetCameraMode(CameraMode cameraMode);
	//Sets a custom camera view. You must keep track of 'customView' yourself, ownership is not transfered.
	void SetCustomCamera(const sf::View &customView);

	//Sets the zooming of the camera, when using CameraMode::World.
	void SetWorldZoom(float amount);
	float GetWorldZoom() const;

	//Converts a pixel screen position in WindowSize to a position in the world itself.
	//Note: Don't call "ConvertToPosInWorld(ConvertToVirtualPos(pos))", just call "ConvertToPosInWorld(pos)".
	PosInWorld ConvertToPosInWorld(const PosInWindow &posInWindow) const;
	PosInWorld ConvertToPosInWorld(const PosInWindow &posInWindow, const sf::View &cameraView) const;
	//Converts a pixel screen position in WindowSize to the equivilent pixel position in VirtualWindowSize.
	PosInVirtualWindow ConvertToVirtualPos(const PosInWindow &posInWindow);
	//Converts from a pixel screen position to a global monitor position.
	PosInMonitor ConvertToPosInMonitor(const PosInWindow &posInWindow);

	//Keeps the window at a proper aspect ratio.
	int heightForWidth(int width);

private:
	virtual QPaintEngine *paintEngine() const;

	//void focusInEvent(QFocusEvent *);
	//void focusOutEvent(QFocusEvent *);
	//void keyPressEvent(QKeyEvent *);
	
	void enterEvent(QEvent *) override;
	//void mousePressEvent(QMouseEvent *event) override;
	
	void showEvent(QShowEvent*) override;
	void paintEvent(QPaintEvent*) override;

	void resizeEvent(QResizeEvent*) override;

private:
	bool isInitialized; //True if the SFML window has been initialized on the QWidget.

	float worldZoom; //The amount zoomed in by the worldView camera.

	//sf::RenderWindow only stores a *pointer* to the sf::View, so we need to keep a local variable so it doesn't go out of scope.
	//One is the view in the world (for world drawing), the other is the view of the screen (for GUI drawing).
	sf::View worldView;
	sf::View screenView;

	WindowSize windowSize;
	VirtualWindowSize virtualSize;

signals:
	void Draw(sf::RenderTarget&);
};

ApplicationWindow.cpp


#include "ApplicationWindow.h"

#include <QtGui/QResizeEvent>

#include "Common/Logger/Log.h"
#include "Common/String/Conversion.h"
#include "Common/Logic/Property/PropertyMap.h"

ApplicationWindow::ApplicationWindow(QWidget *parent)
           : QWidget(parent), isInitialized(false)
{
    //Setup some states to allow direct rendering into the widget.
    QWidget::setAttribute(Qt::WA_PaintOnScreen);
    QWidget::setAttribute(Qt::WA_OpaquePaintEvent);
    QWidget::setAttribute(Qt::WA_NoSystemBackground);
    QWidget::setAttribute(Qt::WA_PaintUnclipped);

    //Set strong focus to enable keyboard events to be received.
    QWidget::setFocusPolicy(Qt::StrongFocus);

    this->worldZoom = 1.0f;
	
	//I forget why I have this commented out. I think it was preventing QLineEdits from recieving keystrokes.
	//this->grabKeyboard();
}

ApplicationWindow::~ApplicationWindow()
{

}

void ApplicationWindow::SetWindowSize(WindowSize size)
{
    this->windowSize = size;

    Log::Message message(MSG_SOURCE("AreaWindow", Log::Severity::Log));
    message << "Setting the window size to " << Log_HighlightCyan(this->windowSize.ToString())
            << ", with a ratio of " << Log_HighlightCyan(ResolutionRatio(this->windowSize)) << ".\n" << Log::FlushStream;

#warning I can't do this here. ApplicationWindow will-be/should-be resized by it's parent.
    QWidget::resize(this->windowSize.ToQSize());
    QWidget::setFixedSize(this->windowSize.ToQSize());
}

//Manually set the virtual size. (If set too high, more AreaCells will display then is currently loaded)
void ApplicationWindow::SetVirtualSize(VirtualWindowSize size)
{
    this->virtualSize = size;

    Log::Message message(MSG_SOURCE("AreaWindow", Log::Severity::Log));
    message << "Setting the virtual window size to " << Log_HighlightCyan(this->virtualSize.ToString())
            << ", with a ratio of " << Log_HighlightCyan(ResolutionRatio(this->virtualSize)) << "." << Log::FlushStream;

    float virtualWidth = float(this->virtualSize.width);
    float virtualHeight = float(this->virtualSize.height);

    this->worldView.setSize(virtualWidth, virtualHeight);
    this->SetWorldZoom(this->worldZoom); //Re-apply the zoom.

    this->screenView.setCenter((virtualWidth * 0.5f), (virtualHeight * 0.5f));
    this->screenView.setSize(virtualWidth, virtualHeight);
}

WindowSize ApplicationWindow::GetWindowSize()
{
    return this->windowSize;
}

VirtualWindowSize ApplicationWindow::GetVirtualSize()
{
    return this->virtualSize;
}

//Sets the center position of the camera in the world. Do so each frame before drawing the world.
void ApplicationWindow::SetWorldCamera(VisiblePosInWorld cameraCenter)
{
    //Sets the center of the view.
    this->worldView.setCenter(float(cameraCenter.x), float(cameraCenter.y));
}

//Changes the camera mode, in preperation for drawing.
void ApplicationWindow::SetCameraMode(CameraMode cameraMode)
{
    if(cameraMode == CameraMode::World)
    {
        sf::RenderWindow::setView(this->worldView);
    }
    else
    {
        sf::RenderWindow::setView(this->screenView);
    }
}

//Sets a custom camera view. You must keep track of 'customView' yourself, ownership is not transfered.
void ApplicationWindow::SetCustomCamera(const sf::View &customView)
{
    sf::RenderWindow::setView(customView);
}

//Sets the zooming of the camera, when using CameraMode::World.
void ApplicationWindow::SetWorldZoom(float amount)
{
	BUILD_NOTE("Zoom out, zoom in using scroll-wheel")
    BUILD_NOTE("Different levels: 100%, 200%, 300%, 400%, 500%")

    this->worldZoom = amount;

    float virtualWidth = static_cast<float>(this->virtualSize.width);
    float virtualHeight = static_cast<float>(this->virtualSize.height);

    this->worldView.setSize((virtualWidth * this->worldZoom),
                            (virtualHeight * this->worldZoom));
}

float ApplicationWindow::GetWorldZoom() const
{
    return this->worldZoom;
}

//Converts a pixel screen position in WindowSize to a position in the world itself.
//Note: Don't call "ConvertToPosInWorld(ConvertToVirtualPos(pos))", just call "ConvertToPosInWorld(pos)".
PosInWorld ApplicationWindow::ConvertToPosInWorld(const PosInWindow &posInWindow, const sf::View &cameraView) const
{
    PosInWorld posInWorld;
    posInWorld.FromSfmlVector2f(this->mapPixelToCoords({posInWindow.x, posInWindow.y}, cameraView));

    return posInWorld;
}

PosInWorld ApplicationWindow::ConvertToPosInWorld(const PosInWindow &posInWindow) const
{
    return this->ConvertToPosInWorld(posInWindow, this->worldView);
}

//Converts a pixel position in WindowSize to the equivilent pixel position in VirtualWindowSize.
PosInVirtualWindow ApplicationWindow::ConvertToVirtualPos(const PosInWindow &posInWindow)
{
    WindowSize windowSize = this->GetWindowSize();
    VirtualWindowSize virtualSize = this->GetVirtualSize();

    float horizontalMultiplyer = float(virtualSize.width) / float(windowSize.width);
    float verticalMultiplyer = float(virtualSize.height) / float(windowSize.height);

    return PosInVirtualWindow(int(float(posInWindow.x) * horizontalMultiplyer),
                              int(float(posInWindow.y) * verticalMultiplyer));
}

//Converts from a pixel screen position to a global monitor position.
PosInMonitor ApplicationWindow::ConvertToPosInMonitor(const PosInWindow &posInWindow)
{
    PosInMonitor posInMonitor;
    posInMonitor.FromQPoint(QWidget::mapToGlobal(posInWindow.ToQPoint()));
    return posInMonitor;
}

int ApplicationWindow::heightForWidth(int width)
{
    float aspectRatio = float(this->windowSize.width) / float(this->windowSize.height);

    int height = int(float(width) * aspectRatio);
    return height;
}

QPaintEngine *ApplicationWindow::paintEngine() const
{
    //We make the paintEvent function return a null paint engine. This functions works together with
    //the WA_PaintOnScreen flag to tell Qt that we're not using any of its built-in paint engines.
    return nullptr;
}

/*void ApplicationWindow::focusInEvent(QFocusEvent *)
{
	std::cout << "QWidget::focusInEvent()" << std::endl;
}

void ApplicationWindow::focusOutEvent(QFocusEvent *)
{
	std::cout << "QWidget::focusOutEvent()" << std::endl;
}

void ApplicationWindow::keyPressEvent(QKeyEvent *)
{
	this->worldZoom = 1.0f;
}

void ApplicationWindow::mouseMoveEvent(QMouseEvent *event)
{
	
}*/

void ApplicationWindow::enterEvent(QEvent *)
{
	BUILD_NOTE("This will accidentally take focus away from the TileSelectionWindow search box")
	//Make sure the window takes focus the second the mouse goes over it.
	QWidget::setFocus();
}

/*void ApplicationWindow::mousePressEvent(QMouseEvent *event)
{
	
}*/

void ApplicationWindow::showEvent(QShowEvent*)
{
    if(!this->isInitialized)
    {
        //Under X11, we need to flush the commands sent to the server to ensure that
        //SFML will get an updated view of the windows
        #ifdef Q_WS_X11
            XFlush(QX11Info::display());
        #endif

        //Create the SFML window with the widget handle
        sf::RenderWindow::create((HWND)this->winId());

        //Assigns our view to the window. The SFML window stores a pointer to it, so we can just update the view and the window will automaticly know.
        sf::RenderWindow::setView(this->worldView);

        this->isInitialized = true;
    }
}

void ApplicationWindow::paintEvent(QPaintEvent *event)
{
    //Clear the window.
	const cColor &windowClearColor = GlobalProperties.Get<cColor>("WindowClearColor");
    sf::RenderWindow::clear(windowClearColor.ToSfmlColor());

    //Start the camera off on World mode.
    this->SetCameraMode(CameraMode::World);

    //Let the game know we are ready to draw, and give it the sf::RenderTarget.
    emit Draw(*this);

    //Display on screen.
    sf::RenderWindow::display();
}

void ApplicationWindow::resizeEvent(QResizeEvent *resizeEvent)
{
	return;
    //Resize the SFML window to the new widget size.
    //sf::RenderWindow::setSize({resizeEvent->size().width(), resizeEvent->size().height()});
}

I wrote it many months ago, so I forget why I had some parts commented out and why I did things the way I did. I'm going to have to revisit it in a week or two though, to get resizing handled properly.

This topic is closed to new replies.

Advertisement