I'm trying to make a simple software with Gtkmm3.
I want to have a window with a grid inside. At a click on a button inside that grid, a method of the window should be triggered to delete the current grid and replace it with another one.
I'm able to use a method of the grid like this :
button.signal_clicked().connect(sigc::mem_fun(*this, &MyGrid::someMethod));
"this" being MyGrid.
I would like to do something like this :
button.signal_clicked().connect(sigc::mem_fun(*this->get_parent(), &MyWindow::someMethod));
where this->get_parent() would be an instance of MyWindow
My .h:
#ifndef MINIPROJECT_GUI_H
#define MINIPROJECT_GUI_H
#include <gtkmm/button.h>
#include <gtkmm/window.h>
#include <gtkmm/grid.h>
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <gtkmm/label.h>
class WelcomeGrid: public Gtk::Grid
{
Gtk::Label message;
Gtk::Button nextButton; // This button should be connected to Fenetre::infoView()
public:
WelcomeGrid();
void display();
};
class InfoGrid : public Gtk::Grid
{
Gtk::Button button2;// This button should be connected to Fenetre::welcomeView()
Gtk::Label label2;
public:
InfoGrid();
void display();
};
class Fenetre : public Gtk::Window
{
public:
Fenetre();
virtual ~Fenetre(); // Setup window
void welcomeView();
protected:
//Member widgets:
WelcomeGrid welcome;
InfoGrid info;
void infoView(); // Remove the current grid from the window and replace it by infoGrid
void welcomeView(); // Remove the current grid from the window and replace it by WelcomeGrid
};
#endif //MINIPROJECT_GUI_H
My .cpp :
#include "GUI.h"
Fenetre::Fenetre()
{
// Sets the border width of the window.
set_border_width(10);
this->add(welcome);
}
Fenetre::~Fenetre()
{
}
void Fenetre::welcomeView() {
this->remove();
this->add(welcome);
}
void Fenetre::infoView() {
this->remove();
this->add(info);
}
InfoGrid::InfoGrid() {
button2.set_label("Hello.");
button2.signal_clicked().connect(sigc::mem_fun(*this,
&InfoGrid::display));
label2.set_label("Welcome on the Vampire creation interface.");
this->attach(label2, 0, 0, 1, 1);
this->attach(button2,1,1,1,1);
button2.show();
this->show_all();
}
WelcomeGrid::WelcomeGrid() {
nextButton.set_label("Create new character.");
auto a = this->get_parent();
nextButton.signal_clicked().connect(sigc::mem_fun(*this,
&WelcomeGrid::display));
message.set_label("Welcome on the Vampire creation interface.");
this->attach(message, 0, 0, 1, 1);
this->attach(nextButton,1,1,1,1);
// This packs the button into the Window (a container);
this->show_all();
}
void WelcomeGrid::display() {
auto a = this->get_parent();
std::cout << typeid(a).name();
}
void InfoGrid::display() {
std::cout << "coucou";
}
Without any code, it is hard to know what exactly you are looking for. Here is how I would do it: I would keep a reference to the parent Window inside the grid. For example:
#include <iostream>
#include <memory>
#include <sstream>
#include <gtkmm.h>
class MyWindow : public Gtk::Window
{
public:
MyWindow()
: m_grid{std::make_unique<MyGrid>(*this, m_count)}
{
add(*m_grid);
}
// This is called when the grid's button is pressed:
void ReplaceGrid()
{
++m_count;
// Remove the grid from the window:
remove();
// Destroy current grid:
m_grid = nullptr;
// Create a new grid:
m_grid = std::make_unique<MyGrid>(*this, m_count);
// Add it to the window:
add(*m_grid);
show_all();
}
private:
class MyGrid : public Gtk::Grid
{
public:
MyGrid(MyWindow& p_parent, int p_count)
: m_parent{p_parent}
{
// Create button:
std::ostringstream ss;
ss << "Replace me #" << p_count;
m_replaceButton = Gtk::Button(ss.str());
// Attach it to the grid:
attach(m_replaceButton, 0, 0, 1, 1);
// Connect replacement signal, using the parent window:
m_replaceButton.signal_clicked().connect([this]()
{
// Call the parent (the window):
m_parent.ReplaceGrid();
});
}
~MyGrid()
{
std::cout << "Grid destroyed" << std::endl;
}
private:
Gtk::Button m_replaceButton;
// Keep a reference to the parent window in the grid:
MyWindow& m_parent;
};
int m_count = 0;
std::unique_ptr<MyGrid> m_grid;
};
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "so.question.q64594709");
MyWindow w;
w.show_all();
return app->run(w);
}
If you run this code, you will see a window with a grid containing one button. Whenever you click the button, the window:
updates a counter
destroys the current grid
creates a new grid with the updated counter value
You will see the counter value updated on the new grid's button label. In the terminal, the grids destructor will print a message, proving grids have really been switched.
Notice I have used lambdas here to clean up the syntax. I would suggest you do so as well. If you really want to use sigc::men_fun, you can encapsulate the lambda's content into the MyGrid::someMethod method that you mentioned in your question.
Notice also that the grid is a private nested class of the window (no one else needs to know...).
Compiled with GCC:
g++ main.cpp -o example.out `pkg-config gtkmm-3.0 --cflags --libs`
Related
I'm learning how to use cocos2d-x by following the gamesfromscratch tutorial. I got to this part when I noticed this problem with the positioning:
This basic app basically draws on screen the label "You Touched Here" at the position where you click. Whenever I clicked though, the label would appear well above where I clicked.
In the screenshot above, I clicked at the origin. In the output log, you can see that the touch point (specifically: touch->getLocation(), unconverted) is recorded as (0, 166), where it should be (0, 0).
I tried using other position functions, as well as converting the touch coordinates to other coordinate types, but the problem still persisted.
Below is the code for this simple app:
AppDelegate.h
#pragma once
#include "cocos2d.h"
class AppDelegate : private cocos2d::Application {
public:
AppDelegate();
virtual ~AppDelegate();
virtual bool applicationDidFinishLaunching();
virtual void applicationDidEnterBackground();
virtual void applicationWillEnterForeground();
};
AppDelegate.cpp
#include "AppDelegate.h"
// These header files are not used currently
//#include "HelloWorldScene.h"
//#include "GraphicsScene.h"
//#include "TouchScene.h"
#include "TouchScene2.h"
USING_NS_CC;
AppDelegate::AppDelegate() {
}
AppDelegate::~AppDelegate() {
}
bool AppDelegate::applicationDidFinishLaunching() {
auto director = Director::getInstance();
auto glView = director->getOpenGLView();
if (!glView) {
glView = GLViewImpl::create("Hello World");
glView->setFrameSize(640, 480);
director->setOpenGLView(glView);
}
auto scene = TouchScene2::createScene();
director->runWithScene(scene);
return true;
}
void AppDelegate::applicationDidEnterBackground() {
}
void AppDelegate::applicationWillEnterForeground() {
}
TouchScene2.h
#pragma once
#include "cocos2d.h"
class TouchScene2 : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchEnded(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchMoved(cocos2d::Touch*, cocos2d::Event*);
virtual void onTouchCancelled(cocos2d::Touch*, cocos2d::Event*);
CREATE_FUNC(TouchScene2);
private:
cocos2d::Label* labelTouchInfo;
};
TouchScene2.cpp
#include "TouchScene2.h"
USING_NS_CC;
Scene* TouchScene2::createScene()
{
auto scene = Scene::create();
auto layer = TouchScene2::create();
scene->addChild(layer);
return scene;
}
bool TouchScene2::init()
{
if (!Layer::init())
{
return false;
}
labelTouchInfo = Label::createWithSystemFont("Touch or clicksomewhere to begin", "Arial", 30);
labelTouchInfo->setPosition(Vec2(
Director::getInstance()->getVisibleSize().width / 2,
Director::getInstance()->getVisibleSize().height / 2));
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(TouchScene2::onTouchBegan, this);
touchListener->onTouchEnded = CC_CALLBACK_2(TouchScene2::onTouchEnded, this);
touchListener->onTouchMoved = CC_CALLBACK_2(TouchScene2::onTouchMoved, this);
touchListener->onTouchCancelled = CC_CALLBACK_2(TouchScene2::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
this->addChild(labelTouchInfo);
return true;
}
bool TouchScene2::onTouchBegan(Touch* touch, Event* event)
{
std::stringstream output;
output << "Touch Pos: (" << touch->getLocation().x << ", " << touch- >getLocation().y << ")" << std::endl;
log(output.str().c_str());
labelTouchInfo->setPosition(touch->getLocation());
labelTouchInfo->setString("You Touched Here");
return true;
}
void TouchScene2::onTouchEnded(Touch* touch, Event* event)
{
cocos2d::log("touch ended");
}
void TouchScene2::onTouchMoved(Touch* touch, Event* event)
{
cocos2d::log("touch moved");
}
void TouchScene2::onTouchCancelled(Touch* touch, Event* event)
{
cocos2d::log("touch cancelled");
}
One thing to point out is that the tutorial I'm following is several years old (written in 2015 I believe). The author is using version 3.3 beta, while I'm using the latest version 3.17.1. Could this be part of the problem?
And, regardless, how do I fix this issue so that the origin is (0, 0) as it should be?
your TouchScene2.cpp and TouchScene2.hpp seems fine
Problem is in your AppDelegate.cpp where you set
glView->setFrameSize(640, 480);
director->setOpenGLView(glView);
Instead of these try the following code.
You have fixed the FrameSize and hasn't set ContentScaleFactor. Set it as in the Cocos2d-x sample project
director->setOpenGLView(glview);
// Set the design resolution
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
Size frameSize = glview->getFrameSize();
vector<string> searchPath;
if (frameSize.height > mediumResource.size.height)
{
searchPath.push_back(largeResource.directory);
director->setContentScaleFactor(MIN(largeResource.size.height/designResolutionSize.height, largeResource.size.width/designResolutionSize.width));
}
// If the frame's height is larger than the height of small resource size, select medium resource.
else if (frameSize.height > smallResource.size.height)
{
searchPath.push_back(mediumResource.directory);
director->setContentScaleFactor(MIN(mediumResource.size.height/designResolutionSize.height, mediumResource.size.width/designResolutionSize.width));
}
// If the frame's height is smaller than the height of medium resource size, select small resource.
else
{
searchPath.push_back(smallResource.directory);
director->setContentScaleFactor(MIN(smallResource.size.height/designResolutionSize.height, smallResource.size.width/designResolutionSize.width));
}
I am trying to make my own reusable button class in SFML. This would allow me to create a button and add a callback function to it in order to make the creation of buttons much easier.
Here is my hpp file:
#ifndef Button_hpp
#define Button_hpp
#include <stdio.h>
#include <SFML/Graphics.hpp>
#include "View.hpp"
#include "State.hpp"
#include "Window.hpp"
namespace kge {
class Button: public View{
private:
sf::RectangleShape* _buttonOutline;
sf::RenderWindow* _window;
sf::Clock _clock;
std::string _textString;
sf::Text* _text;
public:
Button(Window*, std::string);
~Button();
virtual void update(float td);
std::function<void(void)> callback;
void setPosition(float x, float y);
};
}
#endif /* Button_hpp */
And here is where I generate the buttons:
_restartButton = new kge::Button(_window, "Restart");
_restartButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 300);
_restartButton->callback = ([this](){
State::instance().currentView = new GameView(this->_window);
this->_window->setView(State::instance().currentView);
});
_exitButton = new kge::Button(_window, "Quit");
_exitButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 500);
_exitButton->callback = ([this](){
this->_window->close();
});
Finally, I tell the button to update and do it's checks in my window update, button->update(td)
All my buttons seem to all do the action of the last set callback. In this case, my restart button executes my exit code.
Why is this happening and how would I fix it?
Edit
Here is my generation code:
#ifndef GameOver_hpp
#define GameOver_hpp
#include <stdio.h>
#include "View.hpp"
#include "Button.hpp"
#include "GameView.hpp"
#include "State.hpp"
namespace kge{
class View;
};
class GameOver: public kge::View{
private:
sf::Text* _text;
kge::Button* _restartButton;
kge::Button* _exitButton;
void restartFunction(void){
}
public:
GameOver(kge::Window* screen) : View(screen){
int fontSize = 50;
_text = new sf::Text();
_text->setFont(kge::AssetManager::mainBundle().getFontNamed("mainfont"));
_text->setString("Game Over");
_text->setCharacterSize(fontSize);
_text->setPosition(getCenterOfScreen().x-((9*fontSize)/2), 100);
_text->setFillColor(sf::Color(255,0,0));
_restartButton = new kge::Button(_window, "Restart");
_restartButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 300);
_exitButton = new kge::Button(_window, "Quit");
_exitButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 500);
_restartButton->callback = ([this](){
// State::instance().currentView = new GameView(this->_window);
// this->_window->setView(State::instance().currentView);
puts("Restart");
});
_exitButton->callback = ([this](){
// this->_window->close();
puts("Restart");
});
this->addItemToView(_text);
this->addItemToView(_restartButton);
this->addItemToView(_exitButton);
}
void update(float td){
_restartButton->update(td);
_exitButton->update(td);
}
~GameOver(){
delete _text;
delete _restartButton;
}
};
#endif /* GameOver_hpp */
Note, kge::View is just a custom sf::Drawable class (how I create my own "views")
Edit 2
Button update function:
void Button::update(float td){
if(_clock.getElapsedTime().asMilliseconds() < 400) return;
if(!sf::Mouse::isButtonPressed(sf::Mouse::Left)) return;
if(mouseIsIn(*_buttonOutline, _window)){
callback();
_clock.restart();
}
}
Please note: _clock is an sf::Clock that is stored privately in the button class.
The issue was resolved in chat. To summarize, the mouseIsIn function that #iProgram posted did not check collision correctly, leading to multiple buttons triggering at the same time.
The original function:
bool mouseIsIn(sf::RectangleShape shape, sf::RenderWindow* window){
sf::FloatRect shapeBounds = shape.getGlobalBounds();
sf::Vector2i mousePos = sf::Mouse::getPosition(*window);
sf::FloatRect mouseRect = sf::FloatRect(mousePos.x, mousePos.y, mousePos.x+shapeBounds.width, mousePos.y+shapeBounds.height);
return mouseRect.intersects(shapeBounds);
}
The fixed function:
bool mouseIsIn(sf::RectangleShape shape, sf::RenderWindow* window){
sf::FloatRect shapeBounds = shape.getGlobalBounds();
sf::Vector2i mousePos = sf::Mouse::getPosition(*window);
return shapeBounds.contains(mousePos.x, mousePos.y);
}
I'm trying to create an application using Qt3D that where I can create multiple view windows on the same scene. I started with the code from the Qt3DWindow and the Simple C++ Example and started moving things around. What I figured is that each view window would define its own frame graph (just using a simple QForwardRenderer for now) and camera and then I would add each window's frame graph to the main frame graph in my scene.
Everything appears to be working fine as I create multiple windows, but when I close the windows and start removing frame graphs, the application crashes. It's crashing on a background thread somewhere down in the Qt3DCore or Qt3DRender module and I can't get to the source code. As I understand it I should be able to modify the frame graph dynamically at run time, but is that not thread safe? Are you expected to wholesale replace one frame graph with another as opposed to modifying the active frame graph like I'm doing?
--- Edit ---
I did a little more testing and if I delay destroying the QWindow (i.e., the surface that it's trying to render to) a bit after removing its frame graph from the parent frame graph, I don't get the crash. I do however get some warnings on the console that say:
Qt3D.Renderer.Backend: bool __cdecl Qt3DRender::Render::GraphicsContext::makeCurrent(class QSurface *) makeCurrent failed
My guess is it's a threading issue, that the backend is still trying to use the QSurface to render to after it has been destroyed on the main thread. I don't really like my solution (I just used a single shot timer to delay destroying the window by 1 second), but it's better than crashing.
RenderWindow.h
#ifndef RENDERWINDOW_H
#define RENDERWINDOW_H
#include <QWindow>
#include <Qt3DCore>
#include <Qt3DRender>
#include <Qt3DInput>
#include <Qt3DExtras/QForwardRenderer>
class RenderWindow : public QWindow
{
public:
RenderWindow(QScreen* screen = nullptr);
~RenderWindow();
Qt3DRender::QCamera* camera() const;
Qt3DRender::QFrameGraphNode* frameGraph() const;
protected:
void resizeEvent(QResizeEvent *) override;
private:
// Rendering
Qt3DRender::QFrameGraphNode* mpFrameGraph;
Qt3DRender::QCamera* mpCamera;
static bool msFormatDefined;
};
#endif // RENDERWINDOW_H
RenderWindow.cpp
#include "renderwindow.h"
#include <QDebug>
bool RenderWindow::msFormatDefined = false;
namespace
{
// Different clear colors so that it's obvious each window is using a
// different camera and frame graph.
static QColor sClearColors[] = {
Qt::darkBlue,
Qt::blue,
Qt::darkCyan,
Qt::cyan
};
static int sViewCount = 0;
}
RenderWindow::RenderWindow(QScreen* screen)
: QWindow(screen)
, mpFrameGraph(nullptr)
, mpCamera(new Qt3DRender::QCamera)
{
setSurfaceType(QWindow::OpenGLSurface);
// Set the default surface format once
if (!msFormatDefined)
{
QSurfaceFormat format;
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setDepthBufferSize(24);
format.setSamples(4);
format.setStencilBufferSize(8);
setFormat(format);
QSurfaceFormat::setDefaultFormat(format);
msFormatDefined = true;
}
// Camera
mpCamera->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
mpCamera->setPosition(QVector3D(0, 0, 40.0f));
mpCamera->setViewCenter(QVector3D(0, 0, 0));
// Frame Graph (using forward renderer for now)
Qt3DExtras::QForwardRenderer* renderer = new Qt3DExtras::QForwardRenderer;
renderer->setCamera(mpCamera);
renderer->setSurface(this);
renderer->setClearColor(sClearColors[sViewCount++ % 4]);
mpFrameGraph = renderer;
}
RenderWindow::~RenderWindow()
{
qDebug() << "start ~RenderWindow";
// Unparent objects. Probably not necessary but it makes me feel
// good inside.
mpFrameGraph->setParent(static_cast<Qt3DCore::QNode*>(nullptr));
mpCamera->setParent(static_cast<Qt3DCore::QNode*>(nullptr));
delete mpFrameGraph;
delete mpCamera;
qDebug() << "end ~RenderWindow";
}
Qt3DRender::QCamera* RenderWindow::camera() const
{
return mpCamera;
}
Qt3DRender::QFrameGraphNode* RenderWindow::frameGraph() const
{
return mpFrameGraph;
}
void RenderWindow::resizeEvent(QResizeEvent *)
{
mpCamera->setAspectRatio((float)width()/(float)height());
}
Scene.h
#ifndef SCENE_H
#define SCENE_H
#include <Qt3DCore/QEntity>
#include <Qt3DInput/QInputAspect>
#include <Qt3DRender/QFrameGraphNode>
#include <Qt3DRender/QRenderAspect>
#include <Qt3DRender/QRenderSettings>
class RenderWindow;
class Scene
{
public:
Scene();
~Scene();
Qt3DCore::QEntityPtr rootNode() const;
void addView(RenderWindow* window);
private:
void setupScene();
private:
Qt3DCore::QEntityPtr mpRoot;
// Frame Graph
Qt3DRender::QFrameGraphNode* mpFrameGraph;
Qt3DRender::QRenderSettings* mpRenderSettings;
// Aspects
Qt3DCore::QAspectEngine* mpEngine;
Qt3DRender::QRenderAspect* mpRenderAspect;
Qt3DInput::QInputAspect* mpInputAspect;
};
#endif // SCENE_H
Scene.cpp
#include "scene.h"
#include <QDebug>
#include <QPropertyAnimation>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QClearBuffers>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DExtras/QCylinderMesh>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QTorusMesh>
#include "orbittransformcontroller.h"
#include "RenderWindow.h"
Scene::Scene()
: mpRoot(nullptr)
, mpFrameGraph(new Qt3DRender::QFrameGraphNode)
, mpRenderSettings(new Qt3DRender::QRenderSettings)
, mpEngine(new Qt3DCore::QAspectEngine)
, mpRenderAspect(new Qt3DRender::QRenderAspect)
, mpInputAspect(new Qt3DInput::QInputAspect)
{
mpEngine->registerAspect(mpRenderAspect);
mpRenderSettings->setActiveFrameGraph(mpFrameGraph);
setupScene();
mpRoot->addComponent(mpRenderSettings);
mpEngine->setRootEntity(mpRoot);
}
Scene::~Scene()
{
qDebug() << "start ~Scene";
mpEngine->setRootEntity(Qt3DCore::QEntityPtr());
mpRoot.clear();
delete mpEngine;
// mpRenderSettings and mpFrameGraph are children of the
// root node and are automatically destroyed when it is.
qDebug() << "end ~Scene";
}
Qt3DCore::QEntityPtr Scene::rootNode() const
{
return mpRoot;
}
void Scene::addView(RenderWindow* window)
{
// Add the window's frame graph to the main frame graph
if (window->frameGraph())
{
window->frameGraph()->setParent(mpFrameGraph);
}
}
void Scene::setupScene()
{
mpRoot.reset(new Qt3DCore::QEntity);
Qt3DCore::QEntity* entity = new Qt3DCore::QEntity;
entity->setParent(mpRoot.data());
// Create the material
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(entity);
material->setAmbient(Qt::black);
material->setDiffuse(QColor(196, 196, 32));
material->setSpecular(Qt::white);
// Torrus
Qt3DCore::QEntity *torusEntity = new Qt3DCore::QEntity(entity);
Qt3DExtras::QTorusMesh *torusMesh = new Qt3DExtras::QTorusMesh;
torusMesh->setRadius(5);
torusMesh->setMinorRadius(1);
torusMesh->setRings(100);
torusMesh->setSlices(20);
Qt3DCore::QTransform *torusTransform = new Qt3DCore::QTransform;
torusTransform->setScale3D(QVector3D(1.5, 1, 0.5));
torusTransform->setRotation(QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), -45.0f));
torusEntity->addComponent(torusMesh);
torusEntity->addComponent(torusTransform);
torusEntity->addComponent(material);
// Sphere
Qt3DCore::QEntity *sphereEntity = new Qt3DCore::QEntity(entity);
Qt3DExtras::QSphereMesh *sphereMesh = new Qt3DExtras::QSphereMesh;
sphereMesh->setRadius(3);
Qt3DCore::QTransform *sphereTransform = new Qt3DCore::QTransform;
/*OrbitTransformController *controller = new OrbitTransformController(sphereTransform);
controller->setTarget(sphereTransform);
controller->setRadius(20.0f);
QPropertyAnimation *sphereRotateTransformAnimation = new QPropertyAnimation(sphereTransform);
sphereRotateTransformAnimation->setTargetObject(controller);
sphereRotateTransformAnimation->setPropertyName("angle");
sphereRotateTransformAnimation->setStartValue(QVariant::fromValue(0));
sphereRotateTransformAnimation->setEndValue(QVariant::fromValue(360));
sphereRotateTransformAnimation->setDuration(10000);
sphereRotateTransformAnimation->setLoopCount(-1);
sphereRotateTransformAnimation->start();*/
sphereEntity->addComponent(sphereMesh);
sphereEntity->addComponent(sphereTransform);
sphereEntity->addComponent(material);
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "scene.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void createWindow();
private:
Ui::MainWindow *ui;
Scene* scene;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include <QDebug>
#include "ui_mainwindow.h"
#include "renderwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
scene(new Scene())
{
ui->setupUi(this);
connect(ui->createButton, &QPushButton::clicked, this, &MainWindow::createWindow);
}
MainWindow::~MainWindow()
{
qDebug() << "~MainWindow";
delete scene;
delete ui;
}
void MainWindow::createWindow()
{
RenderWindow* window = new RenderWindow();
scene->addView(window);
window->resize(640, 480);
window->show();
QVector3D pos[] = {
QVector3D(0, 0, 40),
QVector3D(0, 25, -30),
QVector3D(-20, -20, -20),
QVector3D(40, 0, 0)
};
static int count = 0;
window->camera()->setPosition(pos[count++%4]);
window->camera()->setViewCenter(QVector3D(0, 0, 0));
// Delete the window when it is closed.
connect(window, &QWindow::visibilityChanged, this, [=](bool on)
{
if (!on)
window->deleteLater();
});
}
I've thoroughly tested your example and draw the same conclusions. When you destroy the window too quickly, the application crashes, probably because Qt3D still tries to issue some OpenGL commands to the underlying QSurface. I think this is a bug that should be reported.
A 'cleaner' work-around of this problem could be to track the generated 3d windows in the main window. You could maintain a list of pointers to all windows that where generated (and probably closed by the user at the some point). The windows are finally destroyed in the destructor of the main window.
I had exactly the same problem. I was creating a class derived from Qt3DWindow in a dialog box so the user could preview the effects of the choices made, and the program crashed when the dialog exited. In fact on Windows this crash causes the debugger and Qt Creator to crash too!
I tried working around this in a variety of ways and some helped because it turns out that it is a threading issue that was fixed on the 23rd October:
https://github.com/qt/qt3d/commit/3314694004b825263c9b9f2d69bd85da806ccbbc
The fix is now to apply the patch, and recompile Qt. 5.11.3 (or perhaps 5.12) will be out quite soon I expect but this bug is a killer if you are using Qt3D in dialogs.
I have create a button action with cocos2d-x but i dont know how it duplicate my action when button clicked
Here is my code for .h
#include "cocos2d.h"
#include "cocos-ext.h"
#include "CocosGUI.h"
USING_NS_CC;
USING_NS_CC_EXT;
using namespace ui;
class LoginScene : public Scene
{
public:
LoginScene(bool pPortrait=false);
~LoginScene();
virtual void onEnter();
virtual void onExit();
virtual void onLogin();
virtual void onRegister();
protected:
Layout* m_pLayout;
Layer* m_pUILayer;
};
and .cpp
#include "LoginScene.h"
#include "cocostudio/CCSSceneReader.h"
#include "cocostudio/CCSGUIReader.h"
#include "cocostudio/CCActionManagerEx.h"
#include <sqlite3.h>
#include "MainScene.h"
LoginScene::LoginScene(bool pPortrait):m_pLayout(NULL),m_pUILayer(NULL)
{
Scene::init();
}
LoginScene::~LoginScene()
{
}
void LoginScene::onEnter()
{
Scene::onEnter();
m_pUILayer=Layer::create();
m_pUILayer->scheduleUpdate();
addChild(m_pUILayer);
//register root from json
m_pLayout=dynamic_cast<Layout*>(cocostudio::GUIReader::getInstance()->widgetFromJsonFile("LoginScene/LoginScene.json"));
m_pUILayer->addChild(m_pLayout);
//button initialize
Button* btnLogin=static_cast<Button*>(Helper::seekWidgetByName(m_pLayout, "btnLogin"));
btnLogin->addTouchEventListener(CC_CALLBACK_0(LoginScene::onLogin,this));
Button* btnRegister=static_cast<Button*>(Helper::seekWidgetByName(m_pLayout, "btnRegister"));
btnRegister->addTouchEventListener(CC_CALLBACK_0(LoginScene::onRegister, this));
}
void LoginScene::onRegister()
{
CCLOG("checking");
}
void LoginScene::onExit()
{
m_pUILayer->removeFromParent();
cocostudio::GUIReader::destroyInstance();
cocostudio::ActionManagerEx::destroyInstance();
cocostudio::SceneReader::destroyInstance();
Scene::onExit();
}
void LoginScene::onLogin()
{
}
And the AppDelegate.cpp
#include "AppDelegate.h"
#include "LoginScene.h"
USING_NS_CC;
AppDelegate::AppDelegate() {
}
AppDelegate::~AppDelegate()
{
}
bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = GLView::create("My Game");
director->setOpenGLView(glview);
}
// turn on display FPS
director->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0 / 60);
//design Size
auto screenSize=glview->getFrameSize();
auto designSize=Size(960,640);
glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::EXACT_FIT);
// create a scene. it's an autorelease object
auto scene = new LoginScene;
scene->autorelease();
// run
director->runWithScene(scene);
return true;
}
Can anyone tell me my mistake :( now it duplicates the log :(
tks all
I enjoy using Qt3D, but all of the examples I see for it are full window applications. What I can't understand from the examples is how to add a qt3d rendering window to a regular qt gui application.
Basically what I want is a little rendering widget for my Qt5 Gui application.
I've looked into Qtgl widget, but I really want to use the scene management abilities of Qt3D.
How can I render as a sub window inside of a qt Gui window?
Is this possible?
Update
So I added this to my MainWindow.cpp It is loosely based off of this https://www.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer
LoadModelView *view = new LoadModelView(); //Crashes on this. Will not compile with
// LoadModelView(this)
QWidget *container = QWidget::createWindowContainer(view);
container->setFocusPolicy(Qt::TabFocus);
ui->gridLayout->addWidget(container);
which seems right.
my load_model.cpp begins like this:
#include "qglmaterialcollection.h"
#include "qglmaterial.h"
#include "qglscenenode.h"
#include "qgllightmodel.h"
#include "qglabstractscene.h"
#include <QtGui/qmatrix4x4.h>
#include <QPropertyAnimation>
#include <QtCore/qmath.h>
#define DEGREE_TO_RAD (3.1415926/180.0)
LoadModelView::LoadModelView(QWindow *parent)
: QGLView(parent)
, m_pSTLScene(0)
{
loadModels();
camera()->setCenter(QVector3D(0, 0, 0));
camera()->setEye(QVector3D(0, 4, 10));
}
LoadModelView::~LoadModelView()
{
delete m_pSTLScene;
}
void LoadModelView::paintGL(QGLPainter *painter)
{
QMatrix4x4 stlWorld;
stlWorld.setToIdentity();
stlWorld.scale(0.1);
stlWorld.translate(QVector3D(2.0,0.0,0.0));
painter->setStandardEffect(QGL::LitMaterial);
painter->setFaceColor(QGL::AllFaces,QColor(170,202,0));
painter->modelViewMatrix() = camera()->modelViewMatrix() * stlWorld;
m_pSTLScene->mainNode()->draw(painter);
}
void FixNodesRecursive(int matIndex, QGLSceneNode* pNode)
{
if (pNode) {
pNode->setMaterialIndex(matIndex);
// pNode->setEffect(QGL::FlatReplaceTexture2D);
foreach (QGLSceneNode* pCh, pNode->children()) {
FixNodesRecursive(matIndex, pCh);
}
}
}
void LoadModelView::loadModels()
{
{
m_pSTLScene = QGLAbstractScene::loadScene(QLatin1String(":/models/Sheep.stl"), QString(),"CorrectNormals CorrectAcute");
Q_ASSERT(m_pSTLScene!=0);
QGLMaterial *mat = new QGLMaterial;
mat->setAmbientColor(QColor(170,202,0));
mat->setDiffuseColor(QColor(170,202,0));
mat->setShininess(128);
QGLSceneNode* pSTLSceneRoot = m_pSTLScene->mainNode();
int matIndex = pSTLSceneRoot->palette()->addMaterial(mat);
pSTLSceneRoot->setMaterialIndex(matIndex);
pSTLSceneRoot->setEffect(QGL::FlatReplaceTexture2D);
FixNodesRecursive(matIndex,pSTLSceneRoot);
}
}
It crashes with:
This application has requested the runtime to terminate it in an unusual way.
And in the qt application output:
Invalid parameter passed to C runtime function.
EDIT Added the rest of the class in question
I notice that in the example I am adapting https://github.com/Distrotech/qt3d/blob/master/tutorials/qt3d/penguin/main.cpp the window is initialized by saying:
LoadModelView view;
However, saying
LoadModelView *view = new LoadModelView(this)
crashes
You can subclass QGLView class which extends QGLWidget with support for 3D viewing:
class GLView : public QGLView
{
Q_OBJECT
public:
GLView(QWidget *parent = 0);
~GLView();
protected:
void initializeGL(QGLPainter *painter);
void paintGL(QGLPainter *painter);
private:
QGLAbstractScene *m_scene;
QGLSceneNode *m_rootNode;
};
GLView::GLView(QWidget *parent)
: QGLView(parent)
, m_scene(0)
, m_rootNode(0)
{
// Viewing Volume
camera()->setFieldOfView(25);
camera()->setNearPlane(1);
camera()->setFarPlane(1000);
// Position of the camera
camera()->setEye(QVector3D(0, 3, 4));
// Direction that the camera is pointing
camera()->setCenter(QVector3D(0, 3, 0));
}
GLView::~GLView()
{
delete m_scene;
}
void GLView::initializeGL(QGLPainter *painter)
{
// Background color
painter->setClearColor(QColor(70, 70, 70));
// Load the 3d model from the file
m_scene = QGLAbstractScene::loadScene("models/model1/simplemodel.obj");
m_rootNode = m_scene->mainNode();
}
void GLView::paintGL(QGLPainter *painter)
{
m_rootNode->draw(painter);
}
Qt 5.1 introduces the function QWidget::createWindowContainer(). A function that creates a QWidget wrapper for an existing QWindow, allowing it to live inside a QWidget-based application. You can use QWidget::createWindowContainer which creates a QWindow in a QWidget. This allows placing QWindow-subclasses in Widget-Layouts. This way you
can embed your QGLView inside a widget.
This is how I did it on Qt5.10. This example shows a scene with a cuboid. Scene you can than use like a button or so... To use this add QT += 3dextras to your project file.
szene.h
#ifndef SCENE_H
#define SCENE_H
#include <QObject>
#include <QWidget>
class Scene
: public QWidget
{
Q_OBJECT
private:
QWidget *container;
public:
explicit Scene(QWidget *parent = nullptr);
protected:
// reimplementation needed to handle resize events
// http://doc.qt.io/qt-5/qwidget.html#resizeEvent
void
resizeEvent ( QResizeEvent * event );
public slots:
void
resizeView(QSize size);
};
#endif // SCENE_H
scene.cpp
#include "scene.h"
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DExtras/QForwardRenderer>
#include <QQuaternion>
#include <Qt3DCore/QEntity>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QCamera>
#include <Qt3DExtras/QCuboidMesh>
#include <Qt3DExtras/QPhongMaterial>
Scene::Scene(QWidget *parent)
: QWidget(parent)
{
auto view = new Qt3DExtras::Qt3DWindow();
// create a container for Qt3DWindow
container = createWindowContainer(view,this);
// background color
view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x575757)));
// Root entity
auto rootEntity = new Qt3DCore::QEntity();
// Camera
auto camera = new Camera(rootEntity,view);
auto cameraEntity = view->camera();
cameraEntity->setPosition(QVector3D(0, 0, 50.0f));
cameraEntity->setUpVector(QVector3D(0, 1, 0));
cameraEntity->setViewCenter(QVector3D(0, 0, 0));
// Cuboid
auto cuboidMesh = new Qt3DExtras::QCuboidMesh();
// CuboidMesh Transform
auto cuboidTransform = new Qt3DCore::QTransform();
cuboidTransform->setScale(10.0f);
cuboidTransform->setTranslation(QVector3D(0.0f, 0.0f, 0.0f));
cuboidTransform->setRotation(QQuaternion(1,1.5,1,0).normalized());
auto cuboidMaterial = new Qt3DExtras::QPhongMaterial();
cuboidMaterial->setDiffuse(QColor(QRgb(0x005FFF)));
// assamble entity
auto cuboidEntity = new Qt3DCore::QEntity(rootEntity);
cuboidEntity->addComponent(cuboidMesh);
cuboidEntity->addComponent(cuboidMaterial);
cuboidEntity->addComponent(cuboidTransform);
// Set root object of the scene
view->setRootEntity(rootEntity);
}
void
Scene::resizeView(QSize size)
{
container->resize(size);
}
void
Scene::resizeEvent ( QResizeEvent * /*event*/ )
{
resizeView(this->size());
}