I have a problem with updating the value of a class variable on each frame of gameloop.
I am using wxWidgets for creating a cross-platform window and for graphics as well as gameloop. This is my main Window class which implements rendering and the gameloop.
#include <wx/wx.h>
#include "window.h"
#include "../entity/entity.h"
#include "../../configuration.h"
Window::Window(const wxString & title, const wxPoint & position, const wxSize & size): wxFrame(nullptr, wxID_ANY, title, position, size) {
timer = new wxTimer(this, 1);
Entity entity((width / 2) - 50, 100, 100, 100, wxColour(255, 0, 0));
AddEntity(entity);
Connect(wxEVT_PAINT, wxPaintEventHandler(Window::OnPaint));
Connect(wxEVT_TIMER, wxCommandEventHandler(Window::OnTimer));
Start();
}
void Window::Render(wxPaintEvent & event) {
wxPaintDC dc(this);
for (Entity entity : entities) {
entity.Render(dc);
}
}
void Window::Update(wxCommandEvent & event) {
for (Entity entity : entities) {
entity.Update();
}
}
void Window::AddEntity(Entity & entity) {
entities.push_back(entity);
}
void Window::OnTimer(wxCommandEvent & event) {
Update(event);
}
void Window::OnPaint(wxPaintEvent & event) {
Render(event);
}
void Window::Start() {
isStarted = true;
timer->Start(10);
}
void Window::Stop() {
isPaused = !isPaused;
if (isPaused) {
timer->Stop();
} else {
timer->Start(10);
}
}
Here is the Entity class which represent a rectangle that can be drawn onto the window.
#include <wx/wx.h>
#include <stdlib.h>
#include "entity.h"
#include "../gravity/gravity.h"
Entity::Entity(int _x, int _y, int _width, int _height, wxColour _color) : x( _x ), y( _y ), width( _width ), height( _height ), color( _color ) {
}
void Entity::Render(wxPaintDC & dc) {
wxPen pen(color);
dc.SetPen(pen);
dc.DrawLine(x, y, x + width, y);
dc.DrawLine(x, y + height, x + width, y + height);
dc.DrawLine(x, y, x, y + height);
dc.DrawLine(x + width, y, x + width, y + height);
}
void Entity::Update() {
y += 1;
std::cout << y << std::endl;
}
On each call of the Entity::Update() method, I want to increment the y position and rerender the entity. However, the value of y gets incremented only once and stays the same for the rest of the application lifetime and I can't seem to understand why. I'll be thankful for any help.
When you loop over your entities like this (in Window::Render, Window::Update):
for (Entity entity : entities) {
in each iteration entity will get a copy of the element in entities.
In order to operate on your actual entities via a reference you need to change it to:
//----------v---------------------
for (Entity & entity : entities) {
Another option is to use auto & (auto by itself does not include "reference-ness" and so will force again a copy as in your code):
//---vvvvvv---------------------
for (auto & entity : entities) {
Note that even if you only read data from your elements (and don't modify it) you can change your loop to use const & to save the copies.
Related
So I wanted to add a texture to a map, but the problem with it is that I can't quite get why it doesn't get to the correct place while zooming with different zoom sizes.
I'm normally trying to achieve setting the texture position on the background to my position, keeping myself centered into the frame: for example my texture size is 1500x1600 and I'm located at X140, Y590 in that picture ( yes, the coordinates are retrieved correctly as I've checked with the console ), zooming in with some value and scaling the texture and setting it's position to where I'm at.
The code is the following:
if (!areTexturesInit) {
InitiateTextures();
areTexturesInit = true;
}
wxBitmap bit(imageTest);
wxPaintDC dc(this);
double zoomSize = 0.9; // here I'm applying the zooming proportions ( 0.1 - bigger size of the texture, 0.9 - more zoomed in )
this->SetSize(wxSize(386, 386)); // size of the main frame
backgroundSize.x = GetSize().x; // get the size of the main frame
backgroundSize.y = GetSize().y;
middlePoint.x = (backgroundSize.x / 2); // calculate the middle point of the frame
middlePoint.y = (backgroundSize.y / 2);
mapSizeX = 25600 / -zoomSize; // scale vs zoom size
mapSizeY = 25600 / zoomSize;
Vector3 myPosition;
GetPlayerPosition(&myPosition); // gets my location
float TextureCoordinateX = middlePoint.x + (myPosition.x / mapSizeX) * backgroundSize.x;
float TextureCoordinateY = middlePoint.y - (myPosition.y / mapSizeY) * backgroundSize.y;
dc.DrawBitmap(bit, TextureCoordinateX, TextureCoordinateY);
Vector3 myPosOnMap = PositionToMapPosition(myPosition, myPosition); // calculates my position on the map vs mapSizeX and Y & rotates vector
dc.SetPen(wxPen(wxColor(255, 0, 0), 4));
dc.DrawRectangle(wxRect(myPosOnMap.x, myPosOnMap.y, 2, 2)); // draws me on the map with a red square
The problem is that I think I've messed up the zooming part somewhere.
I've attached some demos so you can see what I'm talking about:
"zoomSize" of 0.9:
"zoomSize" of 0.67 - which kind of works, but I need to change it to different zoomSizes, there being the problem:
Panning and zooming a dc is surprisingly complicated. It requires working with 3 separate coordinate systems and it's really easy to accidentally work in the wrong coordinate system.
Here's an example I wrote a while ago that shows how to do the calculations that allow a dc to be pan and zoomed.
It sounds like you're not interested in the pan part, so you can ignore all the stuff that allows a user to set their own pan. However, it's still necessary to to use a pan vector just for the zoom in order to center the zoom at the correct location.
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include <wx/graphics.h>
#include <wx/dcbuffer.h>
class PanAndZoomCanvas:public wxWindow
{
public:
PanAndZoomCanvas(wxWindow *parent,
wxWindowID id = wxID_ANY,
const wxPoint &pos=wxDefaultPosition,
const wxSize &size=wxDefaultSize,
long style=0,
const wxString &name="PanAndZoomCanvas");
wxRect2DDouble GetUntransformedRect() const;
protected:
void DoDrawCanvas(wxGraphicsContext*);
private:
void OnPaint(wxPaintEvent&);
void OnMouseWheel(wxMouseEvent&);
void OnLeftDown(wxMouseEvent&);
void OnMotion(wxMouseEvent&);
void OnLeftUp(wxMouseEvent&);
void OnCaptureLost(wxMouseCaptureLostEvent&);
void ProcessPan(const wxPoint&,bool);
void FinishPan(bool);
int m_zoomFactor;
wxPoint2DDouble m_panVector;
wxPoint2DDouble m_inProgressPanVector;
wxPoint m_inProgressPanStartPoint;
bool m_panInProgress;
};
PanAndZoomCanvas::PanAndZoomCanvas(wxWindow *parent, wxWindowID id,
const wxPoint &pos, const wxSize &size,
long style, const wxString &name)
:wxWindow(parent, id, pos, size, style, name)
{
Bind(wxEVT_PAINT,&PanAndZoomCanvas::OnPaint,this);
Bind(wxEVT_MOUSEWHEEL,&PanAndZoomCanvas::OnMouseWheel,this);
Bind(wxEVT_LEFT_DOWN,&PanAndZoomCanvas::OnLeftDown,this);
SetBackgroundStyle(wxBG_STYLE_PAINT);
m_zoomFactor = 100;
m_panVector = wxPoint2DDouble(0,0);
m_inProgressPanStartPoint = wxPoint(0,0);
m_inProgressPanVector = wxPoint2DDouble(0,0);
m_panInProgress = false;
}
void PanAndZoomCanvas::DoDrawCanvas(wxGraphicsContext* gc)
{
gc->SetPen(*wxBLACK_PEN);
wxGraphicsPath path = gc->CreatePath();
path.MoveToPoint(100,100);
path.AddLineToPoint(300,100);
path.AddLineToPoint(300,300);
path.CloseSubpath();
gc->StrokePath(path);
}
void PanAndZoomCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
{
wxAutoBufferedPaintDC dc(this);
dc.Clear();
wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
if ( gc )
{
double a = m_zoomFactor / 100.0;
wxPoint2DDouble totalPan = m_panVector + m_inProgressPanVector;
gc->Translate(-totalPan.m_x, -totalPan.m_y);
gc->Scale(a, a);
DoDrawCanvas(gc);
delete gc;
}
}
void PanAndZoomCanvas::OnMouseWheel(wxMouseEvent& event)
{
if ( m_panInProgress )
{
FinishPan(false);
}
int rot = event.GetWheelRotation();
int delta = event.GetWheelDelta();
int oldZoom = m_zoomFactor;
m_zoomFactor += 10*(rot/delta);
if ( m_zoomFactor<10 )
{
m_zoomFactor = 10;
}
if ( m_zoomFactor>800)
{
m_zoomFactor = 800;
}
double a = oldZoom / 100.0;
double b = m_zoomFactor / 100.0;
// Set the panVector so that the point below the cursor in the new
// scaled/panned cooresponds to the same point that is currently below it.
wxPoint2DDouble uvPoint = event.GetPosition();
wxPoint2DDouble stPoint = uvPoint + m_panVector;
wxPoint2DDouble xypoint = stPoint/a;
wxPoint2DDouble newSTPoint = b * xypoint;
m_panVector = newSTPoint - uvPoint;
Refresh();
}
void PanAndZoomCanvas::ProcessPan(const wxPoint& pt, bool refresh)
{
m_inProgressPanVector = m_inProgressPanStartPoint - pt;
if ( refresh )
{
Refresh();
}
}
void PanAndZoomCanvas::FinishPan(bool refresh)
{
if ( m_panInProgress )
{
SetCursor(wxNullCursor);
if ( HasCapture() )
{
ReleaseMouse();
}
Unbind(wxEVT_LEFT_UP, &PanAndZoomCanvas::OnLeftUp, this);
Unbind(wxEVT_MOTION, &PanAndZoomCanvas::OnMotion, this);
Unbind(wxEVT_MOUSE_CAPTURE_LOST, &PanAndZoomCanvas::OnCaptureLost, this);
m_panVector += m_inProgressPanVector;
m_inProgressPanVector = wxPoint2DDouble(0,0);
m_panInProgress = false;
if ( refresh )
{
Refresh();
}
}
}
wxRect2DDouble PanAndZoomCanvas::GetUntransformedRect() const
{
double a = m_zoomFactor / 100.0;
wxSize sz = GetSize();
wxPoint2DDouble zero = m_panVector/a;
return wxRect2DDouble(zero.m_x, zero.m_y, sz.GetWidth()/a, sz.GetHeight()/a);
}
void PanAndZoomCanvas::OnLeftDown(wxMouseEvent& event)
{
wxCursor cursor(wxCURSOR_HAND);
SetCursor(cursor);
m_inProgressPanStartPoint = event.GetPosition();
m_inProgressPanVector = wxPoint2DDouble(0,0);
m_panInProgress = true;
Bind(wxEVT_LEFT_UP, &PanAndZoomCanvas::OnLeftUp, this);
Bind(wxEVT_MOTION, &PanAndZoomCanvas::OnMotion, this);
Bind(wxEVT_MOUSE_CAPTURE_LOST, &PanAndZoomCanvas::OnCaptureLost, this);
CaptureMouse();
}
void PanAndZoomCanvas::OnMotion(wxMouseEvent& event)
{
ProcessPan(event.GetPosition(), true);
}
void PanAndZoomCanvas::OnLeftUp(wxMouseEvent& event)
{
ProcessPan(event.GetPosition(), false);
FinishPan(true);
}
void PanAndZoomCanvas::OnCaptureLost(wxMouseCaptureLostEvent&)
{
FinishPan(true);
}
class MyFrame : public wxFrame
{
public:
MyFrame(wxWindow* parent, int id = wxID_ANY, wxString title = "Demo",
wxPoint pos = wxDefaultPosition, wxSize size = wxDefaultSize,
int style = wxDEFAULT_FRAME_STYLE );
};
MyFrame::MyFrame( wxWindow* parent, int id, wxString title, wxPoint pos
, wxSize size, int style )
:wxFrame( parent, id, title, pos, size, style )
{
PanAndZoomCanvas* canvas = new PanAndZoomCanvas(this);
}
class myApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame* frame = new MyFrame(NULL);
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(myApp);
On windows, this looks like this:
I made an algorithm for resizing a picture(inherited from QGraphicsItem) using vector mathematics (I added points to the corners and using the mouse, the picture is resized while maintaining the aspect ratio QGraphicsItem resize with mouse position and keeping aspect ratio , gif: https://gph.is/g/Zr0WdxJ).
Next, I created a group(inherited from QGraphicsItemGroup) add border dots(inherited from QGraphicsRectItem) and added pictures to the group (via addToGroup).
Is it possible to generalize this algorithm to a group? So that all pictures in the group are resized with border dot position.
this what I want: https://gph.is/g/EJxpeVQ (PureRef app)
and this is what I got: https://gph.is/g/aQnpq5x
here the project if anybody wants to run application: https://github.com/try-hard-factory/familiar/tree/feature/itemgroup-resize
can't resize childs... here the code of some classes:
borderdot.h :
#ifndef BORDERDOT_H
#define BORDERDOT_H
#include <QObject>
#include <QGraphicsRectItem>
class QGraphicsSceneHoverEventPrivate;
class QGraphicsSceneMouseEvent;
class DotSignal : public QObject, public QGraphicsRectItem
{
Q_OBJECT
Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)
public:
explicit DotSignal(QGraphicsItem *parentItem = 0, QObject *parent = 0);
explicit DotSignal(QPointF pos, QGraphicsItem *parentItem = 0, QObject *parent = 0);
~DotSignal();
enum Flags {
Movable = 0x01
};
enum { Type = UserType + 1 };
int type() const override
{
return Type;
}
QPointF previousPosition() const;
void setPreviousPosition(const QPointF previousPosition);
void setDotFlags(unsigned int flags);
signals:
void previousPositionChanged();
void signalMouseRelease();
void signalMove(QGraphicsItem *signalOwner, qreal dx, qreal dy);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
public slots:
private:
unsigned int m_flags;
QPointF m_previousPosition;
};
borderdot.cpp:
#include "borderdot.h"
#include <QBrush>
#include <QColor>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
DotSignal::DotSignal(QGraphicsItem *parentItem, QObject *parent) :
QObject(parent)
{
setZValue(999999999);
// setFlags(ItemIsMovable);
setParentItem(parentItem);
setAcceptHoverEvents(true);
setBrush(QBrush(Qt::black));
setRect(-4,-4,8,8);
setDotFlags(0);
}
DotSignal::DotSignal(QPointF pos, QGraphicsItem *parentItem, QObject *parent) :
QObject(parent)
{
setZValue(999999999);
// setFlags(ItemIsMovable);
setParentItem(parentItem);
setAcceptHoverEvents(true);
setBrush(QBrush(Qt::black));
setRect(-4,-4,8,8);
setPos(pos);
setPreviousPosition(pos);
setDotFlags(0);
}
DotSignal::~DotSignal()
{
}
QPointF DotSignal::previousPosition() const
{
return m_previousPosition;
}
void DotSignal::setPreviousPosition(const QPointF previousPosition)
{
if (m_previousPosition == previousPosition)
return;
m_previousPosition = previousPosition;
emit previousPositionChanged();
}
void DotSignal::setDotFlags(unsigned int flags)
{
m_flags = flags;
}
void DotSignal::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_flags & Movable) {
qDebug()<<"DotSignal::mouseMoveEvent";
auto dx = event->scenePos().x() - m_previousPosition.x();
auto dy = event->scenePos().y() - m_previousPosition.y();
moveBy(dx,dy);
setPreviousPosition(event->scenePos());
emit signalMove(this, dx, dy);
} else {
qDebug()<<"else DotSignal::mouseMoveEvent";
QGraphicsItem::mouseMoveEvent(event);
}
}
void DotSignal::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(m_flags & Movable){
setPreviousPosition(event->scenePos());
} else {
QGraphicsItem::mousePressEvent(event);
}
}
void DotSignal::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
emit signalMouseRelease();
QGraphicsItem::mouseReleaseEvent(event);
}
void DotSignal::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"DotSignal::hoverEnterEvent";
Q_UNUSED(event)
setBrush(QBrush(Qt::red));
}
void DotSignal::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"DotSignal::hoverLeaveEvent";
Q_UNUSED(event)
setBrush(QBrush(Qt::black));
}
itemgroup.h:
#ifndef ITEMGROUP_H
#define ITEMGROUP_H
#include <QObject>
#include <QGraphicsItemGroup>
class DotSignal;
class QGraphicsSceneMouseEvent;
class ItemGroup : public QObject, public QGraphicsItemGroup
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)
public:
enum EItemsType {
eBorderDot = QGraphicsItem::UserType + 1,
};
ItemGroup(uint64_t& zc, QGraphicsItemGroup *parent = nullptr);
~ItemGroup();
enum ActionStates {
ResizeState = 0x01,
RotationState = 0x02
};
enum CornerFlags {
Top = 0x01,
Bottom = 0x02,
Left = 0x04,
Right = 0x08,
TopLeft = Top|Left,
TopRight = Top|Right,
BottomLeft = Bottom|Left,
BottomRight = Bottom|Right
};
enum CornerGrabbers {
GrabberTop = 0,
GrabberBottom,
GrabberLeft,
GrabberRight,
GrabberTopLeft,
GrabberTopRight,
GrabberBottomLeft,
GrabberBottomRight
};
public:
void addItem(QGraphicsItem* item);
void printChilds();
QPointF previousPosition() const;
void setPreviousPosition(const QPointF previousPosition);
signals:
void rectChanged(ItemGroup *rect);
void previousPositionChanged();
void clicked(ItemGroup *rect);
void signalMove(QGraphicsItemGroup *item, qreal dx, qreal dy);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
public:
void clearItemGroup();
bool isContain(const QGraphicsItem* item) const;
bool isEmpty() const;
void incZ();
protected:
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
private:
QPointF shiftMouseCoords_;
uint64_t& zCounter_;
QRectF m_tmpRect;
private:
unsigned int m_cornerFlags;
unsigned int m_actionFlags;
QPointF m_previousPosition;
bool m_leftMouseButtonPressed;
DotSignal *cornerGrabber[8];
void resizeLeft( const QPointF &pt);
void resizeRight( const QPointF &pt);
void resizeBottom(const QPointF &pt);
void resizeTop(const QPointF &pt);
void rotateItem(const QPointF &pt);
void setPositionGrabbers();
void setVisibilityGrabbers();
void hideGrabbers();
};
#endif // ITEMGROUP_H
itemgroup.cpp:
#include <QPainter>
#include <QDebug>
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsRectItem>
#include <math.h>
#include "borderdot.h"
static const double Pi = 3.14159265358979323846264338327950288419717;
static double TwoPi = 2.0 * Pi;
ItemGroup::~ItemGroup()
{
for(int i = 0; i < 8; i++){
delete cornerGrabber[i];
}
}
QPointF ItemGroup::previousPosition() const
{
return m_previousPosition;
}
void ItemGroup::setPreviousPosition(const QPointF previousPosition)
{
if (m_previousPosition == previousPosition)
return;
m_previousPosition = previousPosition;
emit previousPositionChanged();
}
ItemGroup::ItemGroup(uint64_t& zc, QGraphicsItemGroup *parent) :
QGraphicsItemGroup(parent),
zCounter_(zc),
m_cornerFlags(0),
m_actionFlags(ResizeState)
{
setAcceptHoverEvents(true);
setFlags(ItemIsSelectable|ItemSendsGeometryChanges);
for(int i = 0; i < 8; i++){
cornerGrabber[i] = new DotSignal(this);
}
setPositionGrabbers();
}
void ItemGroup::addItem(QGraphicsItem* item)
{
addToGroup(item);
auto childs = childItems();
auto tmp = childs.first()->sceneBoundingRect();
for (auto& it : childs) {
if (it->type() == eBorderDot) continue;
tmp = tmp.united(it->sceneBoundingRect());
}
m_tmpRect = tmp;
}
void ItemGroup::printChilds()
{
auto childs = childItems();
for (auto& it : childs) {
LOG_DEBUG(logger, "CHILDREN: ", it);
}
}
QRectF ItemGroup::boundingRect() const
{
return m_tmpRect;
}
void ItemGroup::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QPointF pt = event->pos();
if(m_actionFlags == ResizeState){
switch (m_cornerFlags) {
case Top:
resizeTop(pt);
break;
case Bottom:
resizeBottom(pt);
break;
case Left:
resizeLeft(pt);
break;
case Right:
resizeRight(pt);
break;
case TopLeft:
resizeTop(pt);
resizeLeft(pt);
break;
case TopRight:
resizeTop(pt);
resizeRight(pt);
break;
case BottomLeft:
resizeBottom(pt);
resizeLeft(pt);
break;
case BottomRight:
resizeBottom(pt);
resizeRight(pt);
break;
default:
if (m_leftMouseButtonPressed) {
setCursor(Qt::ClosedHandCursor);
auto dx = event->scenePos().x() - m_previousPosition.x();
auto dy = event->scenePos().y() - m_previousPosition.y();
moveBy(dx,dy);
setPreviousPosition(event->scenePos());
emit signalMove(this, dx, dy);
}
break;
}
} else {
if (m_leftMouseButtonPressed) {
setCursor(Qt::ClosedHandCursor);
auto dx = event->scenePos().x() - m_previousPosition.x();
auto dy = event->scenePos().y() - m_previousPosition.y();
moveBy(dx,dy);
setPreviousPosition(event->scenePos());
emit signalMove(this, dx, dy);
}
}
QGraphicsItemGroup::mouseMoveEvent(event);
}
void ItemGroup::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
setZValue(++zCounter_);
shiftMouseCoords_ = (this->pos() - mapToScene(event->pos()))/scale();
if (event->button() & Qt::LeftButton) {
m_leftMouseButtonPressed = true;
setPreviousPosition(event->scenePos());
emit clicked(this);
}
QGraphicsItemGroup::mousePressEvent(event);
LOG_DEBUG(logger, "EventPos: (", event->pos().x(),";",event->pos().y(), "), Pos: (", pos().x(),";",pos().y(),")");
}
void ItemGroup::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() & Qt::LeftButton) {
m_leftMouseButtonPressed = false;
}
QGraphicsItemGroup::mouseReleaseEvent(event);
}
void ItemGroup::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"ItemGroup::hoverEnterEvent";
setPositionGrabbers();
setVisibilityGrabbers();
QGraphicsItem::hoverEnterEvent(event);
}
void ItemGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"ItemGroup::hoverLeaveEvent";
m_cornerFlags = 0;
hideGrabbers();
setCursor(Qt::CrossCursor);
QGraphicsItem::hoverLeaveEvent( event );
}
void ItemGroup::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
QPointF pt = event->pos(); // The current position of the mouse
qreal drx = pt.x() - boundingRect().right(); // Distance between the mouse and the right
qreal dlx = pt.x() - boundingRect().left(); // Distance between the mouse and the left
qreal dby = pt.y() - boundingRect().top(); // Distance between the mouse and the top
qreal dty = pt.y() - boundingRect().bottom(); // Distance between the mouse and the bottom
m_cornerFlags = 0;
if( dby < 10 && dby > -10 ) m_cornerFlags |= Top; // Top side
if( dty < 10 && dty > -10 ) m_cornerFlags |= Bottom; // Bottom side
if( drx < 10 && drx > -10 ) m_cornerFlags |= Right; // Right side
if( dlx < 10 && dlx > -10 ) m_cornerFlags |= Left; // Left side
switch (m_cornerFlags) {
case TopLeft:
case TopRight:
case BottomLeft:
case BottomRight: {
setCursor(Qt::BusyCursor);
break;
}
default:
setCursor(Qt::CrossCursor);
break;
}
}
QVariant ItemGroup::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
switch (change) {
case QGraphicsItemGroup::ItemSelectedChange:
m_actionFlags = ResizeState;
break;
default:
break;
}
return QGraphicsItemGroup::itemChange(change, value);
}
void ItemGroup::resizeRight(const QPointF &pt)
{
QRectF tmpRect = boundingRect();
if( pt.x() < tmpRect.left() )
return;
qreal widthOffset = ( pt.x() - tmpRect.left() );
if( widthOffset < 10 ) /// limit
return;
if( widthOffset < 10)
tmpRect.setWidth( -widthOffset );
else
tmpRect.setWidth( widthOffset );
prepareGeometryChange();
m_tmpRect = tmpRect;
update();
setPositionGrabbers();
}
void ItemGroup::resizeTop(const QPointF &pt)
{
QRectF tmpRect = boundingRect();
if( pt.y() > tmpRect.bottom() )
return;
qreal heightOffset = ( pt.y() - tmpRect.bottom() );
if( heightOffset > -11 ) /// limit
return;
if( heightOffset < 0)
tmpRect.setHeight( -heightOffset );
else
tmpRect.setHeight( heightOffset );
tmpRect.translate( 0 , boundingRect().height() - tmpRect.height() );
prepareGeometryChange();
m_tmpRect = tmpRect;
update();
setPositionGrabbers();
}
void ItemGroup::setPositionGrabbers()
{
QRectF tmpRect = boundingRect();
cornerGrabber[GrabberTop]->setPos(tmpRect.left() + tmpRect.width()/2, tmpRect.top());
cornerGrabber[GrabberBottom]->setPos(tmpRect.left() + tmpRect.width()/2, tmpRect.bottom());
cornerGrabber[GrabberLeft]->setPos(tmpRect.left(), tmpRect.top() + tmpRect.height()/2);
cornerGrabber[GrabberRight]->setPos(tmpRect.right(), tmpRect.top() + tmpRect.height()/2);
cornerGrabber[GrabberTopLeft]->setPos(tmpRect.topLeft().x(), tmpRect.topLeft().y());
cornerGrabber[GrabberTopRight]->setPos(tmpRect.topRight().x(), tmpRect.topRight().y());
cornerGrabber[GrabberBottomLeft]->setPos(tmpRect.bottomLeft().x(), tmpRect.bottomLeft().y());
cornerGrabber[GrabberBottomRight]->setPos(tmpRect.bottomRight().x(), tmpRect.bottomRight().y());
}
void ItemGroup::setVisibilityGrabbers()
{
cornerGrabber[GrabberTopLeft]->setVisible(true);
cornerGrabber[GrabberTopRight]->setVisible(true);
cornerGrabber[GrabberBottomLeft]->setVisible(true);
cornerGrabber[GrabberBottomRight]->setVisible(true);
cornerGrabber[GrabberTop]->setVisible(true);
cornerGrabber[GrabberBottom]->setVisible(true);
cornerGrabber[GrabberLeft]->setVisible(true);
cornerGrabber[GrabberRight]->setVisible(true);
}
void ItemGroup::hideGrabbers()
{
for(int i = 0; i < 8; i++){
cornerGrabber[i]->setVisible(false);
}
}
I didn't dig through your code, but yes, it can absolutely be done. The product I work in has a "group" concept, and I had to support this exact thing. I can't spell out code for you, but here's the general concept. The same concept works for resizing a related set of selected items that you want to resize all together.
I created a Resizer class that manages the handles and responds to those movements. The Resizer is instantiated when one or more objects is selected. All of the graphics item classes are my own, derived from QGraphicsItem; if you're using the built-in classes, you may need to subclass them and add your own functions.
Whenever the user moves a handle, the Resizer determines the new geometry of the selected items and updates their sizes. For rectangles and ellipses, this involves calls to setRect; for paths, you'll have to write a scaling algorithm that moves the points in the path to their new locations.
For a grouped object, the concept is identical. You look at the proportions of the member objects' areas to the group's encompassing rectangle, recalculate them, and then update them.
What I found to be the key is to save the starting encompassing rectangle, and then create a resize method that accepts the original encompassing rectangle and the new encompassing rectangle (defined by the handle movement), and then scale from the original to the new rectangle. You'll also need the original rectangle of the individual objects.
Off the top of my head, here's the general idea, and this is assuming you want the items to redraw themselves as you move the handles:
When user initiates first handle movement, grab the current geometry of all of the objects being resized, and the encompassing geometry for the overall group.
As the handle moves, call a new "resize" method with the original encompassing rectangle, and the new one defined by the handle positions, and then have each object resize itself by mapping its original area onto the new area, using the group's before/after encompassing rectangles to define the scaling.
When the movement stops, notify each object being resized that the operation is finished, and this is their new size. (This may not be necessary for you, but it was for me because the final object attributes have to be reflected back to a database.)
This takes some effort, but it's absolutely doable. The main things to think about is to abstract the operations and create your own QGraphicsitem-derived classes with methods to support it. If you're using the built-in classes, you can extend them with an interface class that ensures they all have the required methods your resizing code needs.
In FLTK, is there a way to let users resize widgets during runtime by dragging the borders of the widget's box? I mean, for instance, to resize a Fl_Text_Display or a Fl_Box or a Fl_Pack the same way we usually to that for a window in any OS?
I have gone through all the demos that come with FLTK and I have done quite a bit of searching, but only found examples of resizing via code or for user to resize via button-clicking. I could not find anything that points me to the right direction of how to make the borders of an widget become draggable in order to make widgets resizable by dragging.
You can explore Fl_Group and Fl_Tile resizing capabilities, perhaps using them as wrappers. Unfortunately FLTK doesn't support this out of box for every widget, so you have to make it on your own. If you only need to make a couple of custom widgets, the code below gives you the general idea to start off:
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H>
#include <cmath>
class Resizeable : public Fl_Box
{
public:
Resizeable(int X, int Y, int W, int H)
: Fl_Box(X, Y, W, H, "Resize Me") {}
private:
bool can_resize;
bool is_on_right_bottom_corner;
void draw()
{
Fl_Box::draw();
fl_rect(x(), y(), w(), h(), FL_RED);
int bottom_right_x = w() + x();
int bottom_right_y = h() + y();
fl_polygon(bottom_right_x - 6, bottom_right_y - 1,
bottom_right_x - 1, bottom_right_y - 6,
bottom_right_x -1, bottom_right_y - 1);
}
int handle(int event)
{
switch (event) {
case FL_PUSH: {
can_resize = is_on_right_bottom_corner;
return 1;
}
case FL_RELEASE:
can_resize = false;
return 1;
case FL_DRAG: {
if (can_resize) {
int X = Fl::event_x();
int Y = Fl::event_y();
int W = X > x() + 1 ? X - x() : w();
int H = Y > y() + 1 ? Y - y() : h();
size(W, H);
parent()->redraw();
}
return 1;
}
case FL_MOVE: {
int dist_right_border = std::abs(x() + w() - Fl::event_x());
int dist_bottom_border = std::abs(y() + h() - Fl::event_y());
is_on_right_bottom_corner = (dist_right_border < 10 && dist_bottom_border < 10);
window()->cursor(is_on_right_bottom_corner ? FL_CURSOR_SE : FL_CURSOR_DEFAULT);
return 1;
}
case FL_ENTER:
return 1;
case FL_LEAVE:
window()->cursor(FL_CURSOR_DEFAULT);
return 1;
}
return 0;
}
};
int main()
{
Fl_Double_Window win(300, 300, "Resize Example");
Resizeable res(50, 50, 100, 40);
win.show();
return Fl::run();
}
So, after debugging the program in Qt I realized that the debugger thinks I did not initialized the variables; however, I got the variables in and out from the private class, made the class a pointer and seems like nothing happens. Please let me know what am I missing, I have the same problem in other program but I don't know if is just me or the program.
The code is as follows:
main:
#include "selectionarea.h"
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// QMainWindow *win = new QMainWindow();
QSize winsize(500,500);
SelectionArea area;
area.setStartingLocX(0);
area.setStartingLocY(0);
area.setLength(300);
area.setWidth(300);
area.resize(winsize);
area.show();
return app.exec();
}
selectionarea.cpp
#include "selectionarea.h"
#include <QPainter>
#include <QDebug>
#include <QLabel>
SelectionArea::SelectionArea()
{
}
void SelectionArea::paintEvent(QPaintEvent *)
{
QRect rectangle(getStartingLocx(), getStartingLocy(),
getWidth(), getLength());
/*QRegion Constructs a paint event object with the
* region that needs to be updated. The region is
* specified by paintRegion.*/
QPainter painter(this);
painter.setPen(QPen(Qt::SolidPattern,
2.0,
Qt::SolidLine,
Qt::FlatCap,
Qt::MiterJoin));
painter.drawRect(rectangle);
}
void SelectionArea::setStartingLocX(int x)
{
x=StartingLocX;
qDebug() <<x<<" "<<StartingLocX;
}
int SelectionArea::getStartingLocx()
{
return StartingLocX;
}
void SelectionArea::setStartingLocY(int y)
{
y=StartingLocY;
qDebug() <<y<<" "<<StartingLocY;
}
int SelectionArea::getStartingLocy()
{
return StartingLocY;
}
void SelectionArea::setWidth(int w)
{
w=Width;
qDebug() <<w<<" "<<Width;
}
int SelectionArea::getWidth()
{
return Width;
}
void SelectionArea::setLength(int l)
{
l=Length;
}
int SelectionArea::getLength()
{
return Length;
}
and selectionarea.h
#ifndef SELECTIONAREA_H
#define SELECTIONAREA_H
#include <QPixmap>
#include <QLabel>
#include <QRect>
class SelectionArea : public QLabel
{
int StartingLocX;
int StartingLocY;
int Length;
int Width;
public:
SelectionArea();
~SelectionArea()
{
}
void setStartingLocX(int);
void setStartingLocY(int);
void setLength(int);
void setWidth(int);
int getStartingLocx();
int getStartingLocy();
int getWidth();
int getLength();
virtual void paintEvent(QPaintEvent *);
};
#endif // SELECTIONAREA_H
forgive my ambiguity.
UPDATE:
the application output is
0 0
0 0
0 0
and the window is displayed.
Making my comment the answer
Your setter function should actually look like:
void SelectionArea::setStartingLocX(int x)
{
StartingLocX = x;
}
because you initialize class member variable StartingLocX with the value of x (same for other setter functions). In your version of the function you do the opposite, so that your class member variables remain uninitialized.
You don't initialize the member variables when creating an instance of SelectionArea. You only set them afterwards via the setter functions.
You should initialize them in your constructor using an initializer list.
This might in this case be obsolete, yet it will prevent you from running into strange error cases.
This is very much non-idiomatic C++:
SelectionArea area;
area.setStartingLocX(0);
area.setStartingLocY(0);
area.setLength(300);
area.setWidth(300);
You should refactor the code to read:
SelectionArea area{0, 0, 300, 300};
or even
SelectionArea area{300, 300};
Furthermore, you really should make it a true QObject, not a half-made one. And don't shadow QWidget's own members (like witdth() etc.) with your own of similar names. Getters don't have to have a get prefix, it only adds to visual clutter and is not helpful.
The properties I've added below are likely overkill, just add the ones you're reasonably likely to use. It's still a good idea to simply keep the entire selection as a QRect - that's what it really is.
class SelectionArea : public QLabel
{
Q_OBJECT
Q_PROPERTY(QRect selection READ selection WRITE setSelection USER true)
Q_PROPERTY(QPoint selTopLeft READ selTopLeft WRITE setSelTopLeft STORED false)
Q_PROPERTY(int selX READ selX WRITE setSelX STORED false)
Q_PROPERTY(int selY READ sely WRITE setSelY STORED false)
Q_PROPERTY(QSize selSize READ selSize WRITE setSelSize STORED false)
Q_PROPERTY(int selWidth READ selWidth WRITE setSelWidth STORED false)
Q_PROPERTY(int selHeight READ selHeight WRITE setSelHeight STORED false)
QRect m_selection;
public:
SelectionArea(QWidget * parent = 0) : QLabel(parent) {}
SelectionArea(const QSize & size, QWidget * parent = 0) :
QLabel(parent), m_selection(QPoint(), size) {}
SelectionArea(const QPoint & startingLoc, const QSize & size,
QWidget * parent = 0) :
QLabel(parent), m_selection(startingLoc, size) {}
SelectionArea(int width, int height, QWidget * parent = 0) :
QLabel(parent), m_selection(0, 0, width, height) {}
SelectionArea(int x, int y, int width, int height, QWidget * parent = 0) :
QLabel(parent), m_selection(x, y, width, height) {}
void setSelection(const QRect & r) {
if (m_selection == r) return;
m_selection = r;
update();
}
void setSelTopLeft(const QPoint & p) {
if (m_selection.topLeft() == p) return;
m_selection.moveTo(p);
update();
}
void setSelX(int x) {
if (m_selection.x() == x) return;
m_selection.moveTo(x, m_selection.y());
update();
}
void setSelY(int y) {
if (m_selection.y() == y) return;
m_selection.moveTo(m_selection.x(), y);
update();
}
void setSelSize(const QSize & s) {
if (m_selection.size() == s) return;
m_selection.setSize(s);
update();
}
// etc.
QRect selection() const { return m_selection; }
QPoint selTopLeft() const { return m_selection.topLeft(); }
int selX() const { return m_selection.x(); }
int selY() const { return m_selection.y(); }
QSize selSize() const { return m_selection.size(); }
// etc.
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
};
It's also questionable whether you really want to derive from QLabel. It seems that you override the painting, and you don't really care for the text property directly. You'd be better off inheriting straight from a QFrame instead.
I am trying to add a glow effect to a QLabel so that it looks like the time display in the following picture:
I found out that you can "misuse" a QGraphicsDropShadowEffect for this:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setOffset(0);
dse->setColor(QColor(255, 255, 255));
ui.label->setGraphicsEffect(dse);
However, the resulting effect is too weak, you can barely see it:
Unfortunately, you can not modify the strength of the effect, only color and blur radius.
One idea would be to apply multiple QGraphicsDropShadowEffect to the label, so that it gets more visible due to overlapping. But calling ui.label->setGraphicsEffect(dse); will always delete any previous effects, i.e. I was not able to set multiple QGraphicsEffect to the same object.
Any ideas how you can create a clearly visible glow effect with Qt?
Meanwhile, I tinkered my own graphics effect based on QGraphicsBlurEffect and using parts of this answer. If you know any better solutions, let me know.
qgraphicsgloweffect.h:
#pragma once
#include <QGraphicsEffect>
#include <QGraphicsBlurEffect>
#include <QGraphicsColorizeEffect>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QPainter>
class QGraphicsGlowEffect :
public QGraphicsEffect
{
public:
explicit QGraphicsGlowEffect(QObject *parent = 0);
QRectF boundingRectFor(const QRectF &rect) const;
void setColor(QColor value);
void setStrength(int value);
void setBlurRadius(qreal value);
QColor color() const;
int strength() const;
qreal blurRadius() const;
protected:
void draw(QPainter* painter);
private:
static QPixmap applyEffectToPixmap(QPixmap src, QGraphicsEffect *effect, int extent);
int _extent = 5;
QColor _color = QColor(255, 255, 255);
int _strength = 3;
qreal _blurRadius = 5.0;
};
qgraphicsgloweffect.cpp:
#include "QGraphicsGlowEffect.h"
#include <QtCore\qmath.h>
QGraphicsGlowEffect::QGraphicsGlowEffect(QObject *parent) : QGraphicsEffect(parent)
{
}
void QGraphicsGlowEffect::setColor(QColor value) {
_color = value;
}
void QGraphicsGlowEffect::setStrength(int value) {
_strength = value;
}
void QGraphicsGlowEffect::setBlurRadius(qreal value) {
_blurRadius = value;
_extent = qCeil(value);
updateBoundingRect();
}
QColor QGraphicsGlowEffect::color() const {
return _color;
}
int QGraphicsGlowEffect::strength() const {
return _strength;
}
qreal QGraphicsGlowEffect::blurRadius() const {
return _blurRadius;
}
QRectF QGraphicsGlowEffect::boundingRectFor(const QRectF &rect) const {
return QRect(
rect.left() - _extent,
rect.top() - _extent,
rect.width() + 2 * _extent,
rect.height() + 2 * _extent);
}
void QGraphicsGlowEffect::draw(QPainter* painter) {
QPoint offset;
QPixmap source = sourcePixmap(Qt::LogicalCoordinates, &offset);
QPixmap glow;
QGraphicsColorizeEffect *colorize = new QGraphicsColorizeEffect;
colorize->setColor(_color);
colorize->setStrength(1);
glow = applyEffectToPixmap(source, colorize, 0);
QGraphicsBlurEffect *blur = new QGraphicsBlurEffect;
blur->setBlurRadius(_blurRadius);
glow = applyEffectToPixmap(glow, blur, _extent);
for (int i = 0; i < _strength; i++)
painter->drawPixmap(offset - QPoint(_extent, _extent), glow);
drawSource(painter);
}
QPixmap QGraphicsGlowEffect::applyEffectToPixmap(
QPixmap src, QGraphicsEffect *effect, int extent)
{
if (src.isNull()) return QPixmap();
if (!effect) return src;
QGraphicsScene scene;
QGraphicsPixmapItem item;
item.setPixmap(src);
item.setGraphicsEffect(effect);
scene.addItem(&item);
QSize size = src.size() + QSize(extent * 2, extent * 2);
QPixmap res(size.width(), size.height());
res.fill(Qt::transparent);
QPainter ptr(&res);
scene.render(&ptr, QRectF(), QRectF(-extent, -extent, size.width(), size.height()));
return res;
}
Then you can use it like:
QGraphicsGlowEffect * glow = new QGraphicsGlowEffect();
glow->setStrength(4);
glow->setBlurRadius(7);
ui.label->setGraphicsEffect(glow);
This results in a nice glow effect: