Commit 3c4667bf authored by Volker Krause's avatar Volker Krause
Browse files

Rewrite the train coach element rendering

The previous approach using an image provide and a BorderImage never worked
reliably with a device pixel ratio other than one. Instead both of those
are now merged together into a custom item, which gives us full control
over how exactly to scale the various patches.

This should fix all high DPI glitches in the train layout view.
parent 09e7a7a7
Pipeline #132053 passed with stages
in 2 minutes and 57 seconds
......@@ -4,9 +4,8 @@
add_library(kpublictransportquickplugin)
target_sources(kpublictransportquickplugin PRIVATE
kpublictransportquickplugin.cpp
styledsvgimageprovider.cpp
styledframesvgitem.cpp
kpublictransportquickplugin.h
styledsvgimageprovider.h
assets/assets.qrc
)
......
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.5
import org.kde.kpublictransport 1.0
import org.kde.kpublictransport.ui 1.0
/** Displays a single vehicle section. */
BorderImage {
StyledFrameSvgItem {
/** The vehicle section to show. */
property var section
......@@ -55,29 +56,28 @@ BorderImage {
break;
}
}
return ":/org.kde.kpublictransport/ui/assets/" + svgName + ".svg";
}
var fillColor = secondClassBackground;
backgroundColor: {
switch (section.type) {
case VehicleSection.Engine:
case VehicleSection.PowerCar:
fillColor = inaccessibleBackground;
break;
return inaccessibleBackground;
case VehicleSection.RestaurantCar:
fillColor = restaurantBackground;
break;
return restaurantBackground;
case VehicleSection.PassengerCar:
case VehicleSection.ControlCar:
if (section.classes == VehicleSection.FirstClass)
fillColor = firstClassBackground;
return firstClassBackground;
else if (section.classes & VehicleSection.FirstClass)
fillColor = Qt.tint(secondClassBackground, Qt.rgba(firstClassBackground.r, firstClassBackground.g, firstClassBackground.b, 0.5));
break;
return Qt.tint(secondClassBackground, Qt.rgba(firstClassBackground.r, firstClassBackground.g, firstClassBackground.b, 0.5));
}
return "image://org.kde.kpublictransport.styledsvg/" + svgName + "?lineColor=" + textColor + "&fillColor=" + fillColor;
return secondClassBackground;
}
lineColor: textColor
border.top: {
borderTop: {
switch (section.type) {
case VehicleSection.PowerCar:
case VehicleSection.ControlCar:
......@@ -87,5 +87,5 @@ BorderImage {
}
return 36;
}
border.bottom: implicitHeight - border.top - 4
borderBottom: 48 - borderTop - 2
}
......@@ -5,26 +5,13 @@
*/
#include "kpublictransportquickplugin.h"
#include "styledsvgimageprovider.h"
#include "styledframesvgitem.h"
using namespace KPublicTransport;
class Dummy {
Q_GADGET
};
void KPublicTransportQuickPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
{
Q_UNUSED(uri);
engine->addImageProvider(QStringLiteral("org.kde.kpublictransport.styledsvg"), new StyledSvgImageProvider);
}
void KPublicTransportQuickPlugin::registerTypes(const char* uri)
{
Q_UNUSED(uri);
// we need to register at least one thing with our module name, otherwise the QML engine chokes on this plugin
// we however only want to inject our image provider here...
qmlRegisterUncreatableType<Dummy>("org.kde.kpublictransport.ui", 1, 0, "DummyType", {});
qmlRegisterType<StyledFrameSvgItem>(uri, 1, 0, "StyledFrameSvgItem");
}
#include "kpublictransportquickplugin.moc"
......@@ -15,7 +15,6 @@ class KPublicTransportQuickPlugin : public QQmlExtensionPlugin
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void initializeEngine(QQmlEngine *engine, const char *uri) override;
void registerTypes(const char *uri) override;
};
......
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "styledsvgimageprovider.h"
#include "styledframesvgitem.h"
#include <QBuffer>
#include <QDebug>
#include <QFile>
#include <QGuiApplication>
#include <QImageReader>
#include <QPainter>
#include <QQuickWindow>
#include <QUrlQuery>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
using namespace KPublicTransport;
StyledSvgImageProvider::StyledSvgImageProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
StyledFrameSvgItem::StyledFrameSvgItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
const auto updateFunc = [this]() { update(); };
connect(this, &StyledFrameSvgItem::borderTopChanged, this, updateFunc);
connect(this, &StyledFrameSvgItem::borderBottomChanged, this, updateFunc);
connect(this, &StyledFrameSvgItem::borderLeftChanged, this, updateFunc);
connect(this, &StyledFrameSvgItem::borderRightChanged, this, updateFunc);
connect(this, &StyledFrameSvgItem::colorChanged, this, &StyledFrameSvgItem::invalidateImage);
connect(this, &StyledFrameSvgItem::sourceChanged, this, &StyledFrameSvgItem::invalidateImage);
setImplicitSize(48, 48);
}
StyledSvgImageProvider::~StyledSvgImageProvider() = default;
StyledFrameSvgItem::~StyledFrameSvgItem() = default;
void StyledFrameSvgItem::paint(QPainter *painter)
{
loadImage();
if (m_image.isNull()) {
return;
}
const auto dpr = window()->devicePixelRatio();
const double xSrcOffsets[4] = { 0.0, m_borderLeft * dpr, m_image.width() - m_borderRight * dpr, (double)m_image.width() };
const double ySrcOffsets[4] = { 0.0, m_borderTop * dpr, m_image.height() - m_borderBottom * dpr, (double)m_image.height() };
const double xDestOffsets[4] = { 0.0, m_borderLeft, width() - m_borderRight, width() };
const double yDestOffsets[4] = { 0.0, m_borderTop, height() - m_borderBottom, height() };
for (int xi = 0; xi < 3; ++xi) {
for (int yi = 0; yi < 3; ++yi) {
QRectF srcRect(QPointF(xSrcOffsets[xi], ySrcOffsets[yi]), QPointF(xSrcOffsets[xi + 1], ySrcOffsets[yi + 1]));
QRectF destRect(QPointF(xDestOffsets[xi], yDestOffsets[yi]), QPointF(xDestOffsets[xi + 1], yDestOffsets[yi + 1]));
if (srcRect.width() <= 0 || srcRect.height() <= 0) {
continue;
}
painter->drawImage(destRect, m_image, srcRect);
}
}
}
QImage StyledSvgImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
void StyledFrameSvgItem::invalidateImage()
{
qDebug() << id << requestedSize;
m_image = {};
update();
}
// decode id
const auto idx = id.indexOf(QLatin1Char('?'));
const auto svgName = idx >= 0 ? id.left(idx) : id;
const QUrlQuery svgArgs(idx < 0 ? QString() : id.mid(idx + 1));
qDebug() << svgName << svgArgs.queryItems();
void StyledFrameSvgItem::loadImage()
{
if (!m_image.isNull() || m_source.isEmpty()) {
return;
}
// find SVG
const QString svgFileName = QLatin1String(":/org.kde.kpublictransport/ui/assets/") + svgName + QLatin1String(".svg");
QFile svgFile(svgFileName);
QFile svgFile(m_source);
if (!svgFile.open(QFile::ReadOnly)) {
qWarning() << "Failed to open SVG file:" << svgFile.fileName() << svgFile.errorString();
return {};
return;
}
// prepare CSS
QFile cssFile(QStringLiteral(":/org.kde.kpublictransport/ui/assets/style.css"));
if (!cssFile.open(QFile::ReadOnly)) {
qWarning() << "Failed to open CSS file:" << cssFile.fileName() << cssFile.errorString();
return {};
return;
}
auto css = QString::fromUtf8(cssFile.readAll());
const auto cssItems = svgArgs.queryItems();
for (const auto &cssItem : cssItems) {
css.replace(QLatin1String("{{") + cssItem.first + QLatin1String("}}"), cssItem.second);
}
css.replace(QLatin1String("{{fillColor}}"), m_fillColor.name(QColor::HexArgb));
css.replace(QLatin1String("{{lineColor}}"), m_lineColor.name(QColor::HexArgb));
// inject CSS (inspired by KIconLoader)
QByteArray processedContents;
......@@ -81,9 +115,8 @@ QImage StyledSvgImageProvider::requestImage(const QString &id, QSize *size, cons
buffer.open(QIODevice::ReadOnly);
buffer.seek(0);
QImageReader imgReader(&buffer, "svg");
imgReader.setScaledSize(imgReader.size() * qGuiApp->devicePixelRatio());
auto img = imgReader.read();
img.setDevicePixelRatio(qGuiApp->devicePixelRatio());
*size = imgReader.size();
return img;
imgReader.setScaledSize(imgReader.size() * window()->devicePixelRatio());
m_image = imgReader.read();
m_image.setDevicePixelRatio(window()->devicePixelRatio());
setImplicitSize(imgReader.size().width(), m_image.size().height());
}
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPUBLICTRANSPORT_STYLEDFRAMESVGITEM_H
#define KPUBLICTRANSPORT_STYLEDFRAMESVGITEM_H
#include <QImage>
#include <QQuickPaintedItem>
namespace KPublicTransport {
/** Styled SVG border-image element. */
class StyledFrameSvgItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(double borderTop MEMBER m_borderTop NOTIFY borderTopChanged)
Q_PROPERTY(double borderBottom MEMBER m_borderBottom NOTIFY borderBottomChanged)
Q_PROPERTY(double borderLeft MEMBER m_borderLeft NOTIFY borderLeftChanged)
Q_PROPERTY(double borderRight MEMBER m_borderRight NOTIFY borderRightChanged)
Q_PROPERTY(QColor lineColor MEMBER m_lineColor NOTIFY colorChanged)
Q_PROPERTY(QColor backgroundColor MEMBER m_fillColor NOTIFY colorChanged) // fillColor is already a property in the base class
Q_PROPERTY(QString source MEMBER m_source NOTIFY sourceChanged)
public:
explicit StyledFrameSvgItem(QQuickItem *parent = nullptr);
~StyledFrameSvgItem();
void paint(QPainter *painter) override;
Q_SIGNALS:
void borderTopChanged();
void borderBottomChanged();
void borderLeftChanged();
void borderRightChanged();
void colorChanged();
void sourceChanged();
private:
void invalidateImage();
void loadImage();
double m_borderTop = 0;
double m_borderBottom = 0;
double m_borderLeft = 0;
double m_borderRight = 0;
QColor m_lineColor;
QColor m_fillColor;
QString m_source;
QImage m_image;
};
}
#endif // KPUBLICTRANSPORT_STYLEDFRAMESVGITEM_H
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPUBLICTRANSPORT_STYLEDSVGIMAGEPROVIDER_H
#define KPUBLICTRANSPORT_STYLEDSVGIMAGEPROVIDER_H
#include <QQuickImageProvider>
namespace KPublicTransport {
/** Image provider injecting a CSS style sheet into an SVG, based on URI parameters. */
class StyledSvgImageProvider : public QQuickImageProvider
{
public:
explicit StyledSvgImageProvider();
~StyledSvgImageProvider() override;
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
};
}
#endif // KPUBLICTRANSPORT_STYLEDSVGIMAGEPROVIDER_H
......@@ -20,10 +20,19 @@ Rectangle {
anchors.fill: parent
Image {
source: "image://org.kde.kpublictransport.styledsvg/passengercar?lineColor=#00ff00&fillColor=#ff0000"
source: "qrc:///org.kde.kpublictransport/ui/assets/passengercar.svg"
}
BorderImage {
source: "image://org.kde.kpublictransport.styledsvg/passengercar?lineColor=#00ff00&fillColor=#ff0000"
source: "qrc:///org.kde.kpublictransport/ui/assets/passengercar.svg"
}
StyledFrameSvgItem {
source: ":/org.kde.kpublictransport/ui/assets/passengercar.svg"
backgroundColor: "red"
lineColor: "#00ff00"
borderTop: 35
borderBottom: 12
Layout.preferredHeight: 128
}
VehicleSectionItem {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment