traintastic/client/src/board/boardareawidget.cpp

334 Zeilen
9.4 KiB
C++

/**
* client/src/board/boardareawidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2021 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "boardareawidget.hpp"
#include <cmath>
#include <QPainter>
#include <QPaintEvent>
#include <QtMath>
#include <QApplication>
#include "boardwidget.hpp"
#include "tilepainter.hpp"
#include "../network/board.hpp"
#include "../network/abstractproperty.hpp"
#include "../utils/rectf.hpp"
QRect rectToViewport(const QRect& r, const int gridSize)
{
QRect viewport;
viewport.setLeft((r.left() / gridSize) * gridSize);
viewport.setTop((r.top() / gridSize) * gridSize);
viewport.setRight(((r.right() + gridSize - 1) / gridSize) * gridSize);
viewport.setBottom(((r.bottom() + gridSize - 1) / gridSize) * gridSize);
return viewport;
}
constexpr QRectF tileRect(const TileLocation l, const int w, const int h, const int tileSize)
{
return QRectF(l.x * (tileSize - 1), l.y * (tileSize - 1), 1 + w * (tileSize - 1), 1 + h * (tileSize - 1));
}
BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
QWidget(parent),
m_board{board},
m_grid{Grid::Dot},
m_zoomLevel{0},
m_mouseMoveTileId{TileId::None},
m_mouseMoveTileRotate{TileRotate::Deg0}
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setFocusPolicy(Qt::StrongFocus);
setMinimumSize(2000, 2000); // TODO: get size from board
}
void BoardAreaWidget::setGrid(Grid value)
{
if(m_grid != value)
{
m_grid = value;
update();
emit gridChanged(m_grid);
}
}
void BoardAreaWidget::nextGrid()
{
switch(grid())
{
case Grid::None:
setGrid(Grid::Dot);
break;
case Grid::Dot:
setGrid(Grid::Line);
break;
case Grid::Line:
setGrid(Grid::None);
break;
}
}
void BoardAreaWidget::setZoomLevel(int value)
{
value = std::clamp(value, zoomLevelMin, zoomLevelMax);
if(m_zoomLevel != value)
{
m_zoomLevel = value;
// TODO: updateMinimumSize();
update();
emit zoomLevelChanged(m_zoomLevel);
}
}
void BoardAreaWidget::setMouseMoveTileId(TileId id)
{
if(m_mouseMoveTileId == id)
return;
m_mouseMoveTileId = id;
update();
}
void BoardAreaWidget::setMouseMoveTileRotate(TileRotate rotate)
{
if(m_mouseMoveTileRotate == rotate)
return;
m_mouseMoveTileRotate = rotate;
update();
}
TurnoutPosition BoardAreaWidget::getTurnoutPosition(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(const auto* p = object->getProperty("position"))
return p->toEnum<TurnoutPosition>();
return TurnoutPosition::Unknown;
}
SignalAspect BoardAreaWidget::getSignalAspect(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(const auto* p = object->getProperty("aspect"))
return p->toEnum<SignalAspect>();
return SignalAspect::Unknown;
}
TileLocation BoardAreaWidget::pointToTileLocation(const QPoint& p)
{
const int pxPerTile = getTileSize() - 1;
return TileLocation{static_cast<int16_t>(p.x() / pxPerTile), static_cast<int16_t>(p.y() / pxPerTile)};
}
void BoardAreaWidget::leaveEvent(QEvent* event)
{
m_mouseMoveTileLocation = TileLocation::invalid;
emit mouseTileLocationChanged(m_mouseMoveTileLocation.x, m_mouseMoveTileLocation.y);
if(m_mouseMoveTileId != TileId::None)
update();
QWidget::leaveEvent(event);
}
void BoardAreaWidget::keyPressEvent(QKeyEvent* event)
{
if(event->key() == Qt::Key_G && event->modifiers() == Qt::ControlModifier)
nextGrid();
else if(event->matches(QKeySequence::ZoomIn))
zoomIn();
else if(event->matches(QKeySequence::ZoomOut))
zoomOut();
else
QWidget::keyPressEvent(event);
}
void BoardAreaWidget::mousePressEvent(QMouseEvent* event)
{
if(event->button() == Qt::LeftButton)
{
m_mouseLeftButtonPressed = true;
m_mouseLeftButtonPressedTileLocation = pointToTileLocation(event->pos());
}
else if(event->button() == Qt::RightButton)
{
m_mouseRightButtonPressed = true;
m_mouseRightButtonPressedPoint = event->pos();
}
}
void BoardAreaWidget::mouseReleaseEvent(QMouseEvent* event)
{
if(m_mouseLeftButtonPressed && event->button() == Qt::LeftButton)
{
m_mouseLeftButtonPressed = false;
TileLocation tl = pointToTileLocation(event->pos());
if(m_mouseLeftButtonPressedTileLocation == tl) // click
emit tileClicked(tl.x, tl.y);
}
else if(m_mouseRightButtonPressed && event->button() == Qt::RightButton)
{
m_mouseRightButtonPressed = false;
if((event->pos() - m_mouseRightButtonPressedPoint).manhattanLength() < 5 || m_mouseMoveTileId != TileId::None)
emit rightClicked();
}
}
void BoardAreaWidget::mouseMoveEvent(QMouseEvent* event)
{
if(hasMouseTracking())
{
TileLocation tl = pointToTileLocation(event->pos());
if(m_mouseMoveTileLocation != tl)
{
m_mouseMoveTileLocation = tl;
emit mouseTileLocationChanged(tl.x, tl.y);
if(m_mouseMoveTileId != TileId::None)
update();
}
}
QWidget::mouseMoveEvent(event);
}
void BoardAreaWidget::wheelEvent(QWheelEvent* event)
{
if(QApplication::keyboardModifiers() == Qt::ControlModifier && event->angleDelta().y() != 0)
{
if(event->angleDelta().y() < 0)
zoomOut();
else
zoomIn();
event->accept();
}
else
QWidget::wheelEvent(event);
}
void BoardAreaWidget::paintEvent(QPaintEvent* event)
{
const QColor backgroundColor{0x10, 0x10, 0x10};
const QColor backgroundColor50{0x10, 0x10, 0x10, 0x80};
const QColor gridColor{0x40, 0x40, 0x40};
const QColor gridColorHighlight{Qt::white};
const int tileSize = getTileSize();
const int gridSize = tileSize - 1;
QPainter painter(this);
const QRect viewport = rectToViewport(event->rect(), gridSize);
painter.fillRect(viewport, backgroundColor);
// draw grid:
switch(m_grid)
{
case Grid::None:
break;
case Grid::Line:
painter.setPen(gridColor);
for(int y = viewport.top(); y <= viewport.bottom(); y += gridSize)
painter.drawLine(viewport.left(), y, viewport.right(), y);
for(int x = viewport.left(); x <= viewport.right(); x += gridSize)
painter.drawLine(x, viewport.top(), x, viewport.bottom());
break;
case Grid::Dot:
painter.setPen(gridColor);
for(int y = viewport.top(); y <= viewport.bottom(); y += gridSize)
for(int x = viewport.left(); x <= viewport.right(); x += gridSize)
painter.drawPoint(x, y);
break;
}
painter.setRenderHint(QPainter::Antialiasing, true);
// draw tiles:
TilePainter tilePainter{painter, tileSize};
const QRect tiles{viewport.left() / gridSize, viewport.top() / gridSize, viewport.width() / gridSize, viewport.height() / gridSize};
painter.save();
for(auto it : m_board.board().tileData())
if(it.first.x + it.second.width() - 1 >= tiles.left() && it.first.x <= tiles.right() &&
it.first.y + it.second.height() - 1 >= tiles.top() && it.first.y <= tiles.bottom())
{
const TileId id = it.second.id();
const TileRotate a = it.second.rotate();
painter.setBrush(Qt::NoBrush);
const QRectF r = tileRect(it.first, it.second.width(), it.second.height(), tileSize);
switch(id)
{
case TileId::RailStraight:
case TileId::RailCurve45:
case TileId::RailCurve90:
case TileId::RailCross45:
case TileId::RailCross90:
case TileId::RailBufferStop:
tilePainter.draw(id, r, a);
break;
case TileId::RailTurnoutLeft45:
case TileId::RailTurnoutLeft90:
case TileId::RailTurnoutLeftCurved:
case TileId::RailTurnoutRight45:
case TileId::RailTurnoutRight90:
case TileId::RailTurnoutRightCurved:
case TileId::RailTurnoutWye:
case TileId::RailTurnout3Way:
case TileId::RailTurnoutSingleSlip:
case TileId::RailTurnoutDoubleSlip:
tilePainter.drawTurnout(id, r, a, getTurnoutPosition(it.first));
break;
case TileId::RailSensor:
tilePainter.drawSensor(id, r, a); // TODO: add state
break;
case TileId::RailSignal2Aspect:
case TileId::RailSignal3Aspect:
tilePainter.drawSignal(id, r, a, getSignalAspect(it.first));
break;
case TileId::RailBlock:
tilePainter.drawBlock(id, r, a);
break;
case TileId::None:
case TileId::ReservedForFutureExpension:
break;
}
}
painter.restore();
if(m_mouseMoveTileId != TileId::None && m_mouseMoveTileLocation.isValid())
{
const QRectF r = tileRect(m_mouseMoveTileLocation, 1, 1, tileSize);
painter.fillRect(r, backgroundColor50);
painter.setPen(gridColorHighlight);
painter.drawRect(r.adjusted(-0.5, -0.5, 0.5, 0.5));
tilePainter.draw(m_mouseMoveTileId, r, m_mouseMoveTileRotate);
}
}