2
* Copyright 2012 Canonical Ltd.
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser General Public License as published by
6
* the Free Software Foundation; version 3.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU Lesser General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Author: Florian Boucault <florian.boucault@canonical.com>
21
#include <QtQml/QQmlContext>
22
#include <QtQml/QQmlFile>
23
#include <QtCore/QFileInfo>
24
#include <QtCore/QDir>
25
#include <QtCore/QRegularExpression>
26
#include <QtCore/qmath.h>
27
#include <QtGui/QGuiApplication>
28
#include <QtGui/QScreen>
30
#define ENV_GRID_UNIT_PX "GRID_UNIT_PX"
31
#define DEFAULT_GRID_UNIT_PX 8
33
static float getenvFloat(const char* name, float defaultValue)
35
QByteArray stringValue = qgetenv(name);
37
float value = stringValue.toFloat(&ok);
38
return ok ? value : defaultValue;
45
\inqmlmodule Ubuntu.Components 1.1
46
\ingroup resolution-independence
47
\brief Units of measurement for sizes, spacing, margin, etc.
49
Units provides facilities for measuring UI elements in a variety
50
of units other than just pixels.
52
A global instance of Units is exposed as the \b{units} context property.
57
import Ubuntu.Components 1.2
65
\sa {Resolution Independence}
69
* Note on the interaction between GRID_UNIT_PX and QT_DEVICE_PIXEL_RATIO
71
* In Qt5.4 there is a single means to scale the UI: the QT_DEVICE_PIXEL_RATIO environment
72
* variable. This accepts only integer values, thus allowing a x2 or x3 scaling of any
73
* Qt-based UI, that includes QWidget as well as any QML UI.
75
* Setting QT_DEVICE_PIXEL_RATIO=2 implies one density-independent pixel corresponds to 2
76
* physical pixels. Developers describe their UI in terms of density-independent pixels.
77
* Qt scales accordingly.
79
* The Ubuntu UI Toolkit has solved the scaling problem with the GRID_UNIT_PX variable.
80
* It offers more flexibility, but only scales QML applications written to use the UITK
81
* (since it uses this Units class) as it is built on top of QML.
83
* There are additional areas in Qt where QT_DEVICE_PIXEL_RATIO causes correct scaling which
84
* GRID_UNIT_PX cannot, for example:
85
* 1. cacheBuffer for ListView/GridViews - specified in density-independent pixels
86
* 2. gesture recognition matches what is on screen better, as it is density-independent
89
* In order to get the best of both worlds, Ubuntu will set both GRID_UNIT_PX and
90
* QT_DEVICE_PIXEL_RATIO. Thus all Qt apps will scale reasonably well, with UITK-based apps
91
* scaling perfectly for any desired scale (i.e. non-integer scales).
93
* However UITK developers can just use this Units class as usual, and will be almost totally
94
* isolated from Qt's own scaling concept.
97
UCUnits::UCUnits(QObject *parent) :
99
m_devicePixelRatio(qGuiApp->devicePixelRatio())
101
// If GRID_UNIT_PX set, always use it. If not, 1GU := DEFAULT_GRID_UNIT_PX * m_devicePixelRatio
102
if (qEnvironmentVariableIsSet(ENV_GRID_UNIT_PX)) {
103
m_gridUnit = getenvFloat(ENV_GRID_UNIT_PX, DEFAULT_GRID_UNIT_PX);
105
m_gridUnit = DEFAULT_GRID_UNIT_PX * m_devicePixelRatio;
110
\qmlproperty real Units::gridUnit
112
The number of pixels 1 grid unit corresponds to.
114
float UCUnits::gridUnit()
119
void UCUnits::setGridUnit(float gridUnit)
121
m_gridUnit = gridUnit;
122
Q_EMIT gridUnitChanged();
126
\qmlmethod real Units::dp(real value)
128
Returns the number of pixels \a value density independent pixels correspond to.
130
// Density-independent pixels (and not physical pixels) because Qt sizes in terms of density-independent pixels.
131
float UCUnits::dp(float value)
133
const float ratio = m_gridUnit / DEFAULT_GRID_UNIT_PX;
135
// for values under 2dp, return only multiples of the value
136
return qRound(value * qFloor(ratio)) / m_devicePixelRatio;
138
return qRound(value * ratio) / m_devicePixelRatio;
143
\qmlmethod real Units::gu(real value)
145
Returns the number of pixels \a value grid units correspond to.
147
// Density-independent pixels (and not physical pixels) because Qt sizes in terms of density-independent pixels.
149
float UCUnits::gu(float value)
151
return qRound(value * m_gridUnit) / m_devicePixelRatio;
154
QString UCUnits::resolveResource(const QUrl& url)
160
QString path = QQmlFile::urlToLocalFileOrQrc(url);
162
if (path.isEmpty()) {
166
QFileInfo fileInfo(path);
167
if (fileInfo.exists() && !fileInfo.isFile()) {
171
QString prefix = fileInfo.dir().absolutePath() + QDir::separator() + fileInfo.baseName();
172
QString suffix = "." + fileInfo.completeSuffix();
174
/* Use file with expected grid unit suffix if it exists.
175
For example, if m_gridUnit = 10, look for resource@10.png.
178
path = prefix + suffixForGridUnit(m_gridUnit) + suffix;
179
if (QFile::exists(path)) {
180
return QString("1") + "/" + path;
183
/* No file with expected grid unit suffix exists.
184
List all the files of the form fileBaseName@[0-9]*.fileSuffix and select
185
the most appropriate one privileging downscaling high resolution assets
186
over upscaling low resolution assets.
188
The most appropriate file has a grid unit suffix greater than the target
189
grid unit (m_gridUnit) yet as small as possible.
190
If no file with a grid unit suffix greater than the target grid unit
191
exists, then select one with a grid unit suffix as close as possible to
192
the target grid unit.
194
For example, if m_gridUnit = 10 and the available files are
195
resource@9.png, resource@14.png and resource@18.png, the most appropriate
196
file would be resource@14.png since it is above 10 and smaller
197
than resource@18.png.
199
QStringList nameFilters;
200
nameFilters << fileInfo.baseName() + "@[0-9]*" + suffix;
201
QStringList files = fileInfo.dir().entryList(nameFilters, QDir::Files);
203
if (!files.empty()) {
204
float selectedGridUnitSuffix = gridUnitSuffixFromFileName(files.first());
206
Q_FOREACH (const QString& fileName, files) {
207
float gridUnitSuffix = gridUnitSuffixFromFileName(fileName);
208
if ((selectedGridUnitSuffix >= m_gridUnit && gridUnitSuffix >= m_gridUnit && gridUnitSuffix < selectedGridUnitSuffix)
209
|| (selectedGridUnitSuffix < m_gridUnit && gridUnitSuffix > selectedGridUnitSuffix)) {
210
selectedGridUnitSuffix = gridUnitSuffix;
214
path = prefix + suffixForGridUnit(selectedGridUnitSuffix) + suffix;
215
float scaleFactor = m_gridUnit / selectedGridUnitSuffix;
216
return QString::number(scaleFactor) + "/" + path;
219
path = prefix + suffix;
220
if (QFile::exists(path)) {
221
return QString("1") + "/" + path;
227
QString UCUnits::suffixForGridUnit(float gridUnit)
229
return "@" + QString::number(gridUnit);
232
float UCUnits::gridUnitSuffixFromFileName(const QString& fileName)
234
QRegularExpression re("^.*@([0-9]*).*$");
235
QRegularExpressionMatch match = re.match(fileName);
236
if (match.hasMatch()) {
237
return match.captured(1).toFloat();