A picture speaks better than words (black rectangle in red ApplicationWindow):
Notice the red unfilled areas on top and right side. The right side red color may be hard to notice but it is there! I want the rectangle which I have colored in black to fill the entire Application Window. See the code:
main.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
visible: true
/* Developing mobile apps you don’t need to set width
and height, because the ApplicationWindow always grabs
the total available space.
*/
//width: 640
//height: 480
color: "#ff0000" // Red color
/* For some reasons i want this Rectangle here
* and it MUST fill the entire window but I notice
* a pixel or two line on top and right of the
* screen.
*/
Rectangle {
id: page
width: window.width; height: window.height
//anchors.fill: parent // same output
color: "#000000" // Black color
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
I don't know what I'm missing here :(
Please share a solution/workaround to this problem.
I also tried this but still the same output:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
Window {
id: window
visible: true
height: Screen.height
width: Screen.width
/* Developing mobile apps you don’t need to set width
and height, because the ApplicationWindow always grabs
the total available space.
*/
//width: 640
//height: 480
color: "#ff0000" // Red color
/* For some reasons i want this Rectangle here
* and it MUST fill the entire window but I notice
* a pixel or two line on top and right of the
* screen.
*/
Rectangle {
id: page
width: window.width; height: window.height
//anchors.fill: parent // same output
color: "#000000" // Black color
}
}
I noticed that commenting following line in main.cpp resolves the issue BUT now all the widgets which I want to show in the UI look really small..! They look fine in the small-screen devices while small in large-screen devices. :(
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
Any solution for this?
EDIT:
This is a confirmed bug which has been reported HERE since almost an year! . If you are facing the same issue then please login to https://bugreports.qt.io/ and Vote THIS bug.
Well, you could use your "own" dp calculation.
main.cpp
int density = 0;
float logicalDensity = 0;
float yDpi = 0; float xDpi = 0;
#if defined(ANDROID)
QAndroidJniObject qtActivity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
QAndroidJniObject resources = qtActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
QAndroidJniObject displayMetrics = resources.callObjectMethod("getDisplayMetrics", "()Landroid/util/DisplayMetrics;");
density = displayMetrics.getField<int>("densityDpi");
logicalDensity = displayMetrics.getField<float>("density");
yDpi = displayMetrics.getField<float>("ydpi");
xDpi = displayMetrics.getField<float>("xdpi");
qDebug() << "Native Android Call =>>> | Logical Density: " << logicalDensity << " | DensityDPI: " << density << " | " << "++++++++++++++++++++++++";
#endif
[...]
// Set Android pixel data for QML context
engine.rootContext()->setContextProperty("densityData", density);
engine.rootContext()->setContextProperty("logicalDensityData",logicalDensity);
engine.rootContext()->setContextProperty("xDpiData",xDpi);
engine.rootContext()->setContextProperty("yDpiData",yDpi);
For your ApplicationWindow component add this:
main.qml
Component.onCompleted: {
Units.pixelDensity = Qt.binding(function() {
if (Qt.platform.os === "android") {
return densityData / 25.4; // densityData is per inch but we need per mm
}
return Screen.pixelDensity
});
function calculateDiagonal() {
if (Qt.platform.os === "android") {
return Math.sqrt(Math.pow(Screen.width, 2) +
Math.pow(Screen.height, 2)) / densityData;
}
return Math.sqrt(Math.pow(Screen.width, 2) +
Math.pow(Screen.height, 2)) / (Screen.pixelDensity * 25.4);
}
Units.multiplier = Qt.binding(function() {
var diagonal = calculateDiagonal();
Device.diagonal = diagonal;
var baseMultiplier = 1;
if (diagonal >= 3.5 && diagonal < 5.1) { //iPhone 1st generation to phablet
return 0.8;
} else if (diagonal >= 5.1 && diagonal < 6.5) {
return 1;
} else if (diagonal >= 6.5 && diagonal < 15.1) {
return baseMultiplier;
} else if (diagonal >= 15.1 && diagonal < 29) {
return 1.4 * baseMultiplier;
} else if (diagonal >= 29 && diagonal < 92) {
return 1.4 * baseMultiplier;
} else {
return 1.4 * baseMultiplier;
}
});
Device.type = Qt.binding(function () {
var diagonal = calculateDiagonal();
Device.diagonal = diagonal;
if (diagonal >= 3.5 && diagonal < 5) { //iPhone 1st generation to phablet
return Device.phone;
} else if (diagonal >= 5 && diagonal < 7.2) {
return Device.phone;
} else if (diagonal >= 7.2 && diagonal < 15.1) {
return Device.tablet;
} else if (diagonal >= 15.1 && diagonal < 29) {
return Device.desktop;
} else if (diagonal >= 29 && diagonal < 92) {
return Device.tv;
} else {
return Device.unknown;
}
});
// Nasty hack because singletons cannot import the module they were declared in, so
// the grid unit cannot be defined in either Device or Units, because it requires both.
// See https://bugreports.qt.io/browse/QTBUG-39703
Units.gridUnit = Qt.binding(function() {
return Device.type === Device.phone || Device.type === Device.phablet
? Units.dp(48) : Device.type == Device.tablet ? Units.dp(56) : Units.dp(64)
});
}
Units.qml
Object {
id: units
/*!
\internal
This holds the pixel density used for converting millimeters into pixels. This is the exact
value from \l Screen:pixelDensity, but that property only works from within a \l Window type,
so this is hardcoded here and we update it from within \l ApplicationWindow
*/
property real pixelDensity: 4.46
property real multiplier: 1.4 //default multiplier, but can be changed by user
/*!
This is the standard function to use for accessing device-independent pixels. You should use
this anywhere you need to refer to distances on the screen.
*/
function dp(number) {
return Math.round(number*((pixelDensity*25.4)/160)*multiplier);
}
function gu(number) {
return number * gridUnit
}
property int gridUnit: dp(64)
}
Device.qml
import QtQuick 2.0
pragma Singleton
/*!
\qmltype Device
\inqmlmodule Material 0.1
\brief A singleton that provides information about the current device.
*/
Object {
id: device
//some kind of enum, by screen size
property int type: desktop
property int diagonal: -1
readonly property int phone: 0
readonly property int phablet: 1
readonly property int tablet: 2
readonly property int desktop: 3
readonly property int tv: 4
readonly property int unknown: 5 //it's either bigger than tv or smaller than phone
readonly property string name: {
switch (type) {
case 0:
return "phone";
case 1:
return "phablet";
case 2:
return "tablet";
case 3:
return "computer";
case 4:
return "TV";
case 5:
return "device";
}
}
readonly property string iconName: {
switch (type) {
case 0:
return "hardware/smartphone";
case 1:
return "hardware/tablet";
case 2:
return "hardware/tablet";
case 3:
return "hardware/desktop_windows";
case 4:
return "hardware/tv";
case 5:
return "hardware/computer";
}
}
readonly property bool isMobile: type == phone || type == phablet || type == tablet
}
Units and Device components are taken from here qml-material project "https://github.com/papyros/qml-material"
After adding this you should be able to wrap every pixel statement into width: Units.dp(30)
Related
I'm designing a video call screen where i have to manage selfViewContainer between hole screen.
On this screen, I already managed most of the parts for horizontal alignments but for vertical it's not working properly for me in devices with a notch.
I want to make minimum padding of 50 from the top while dragging
Here is a code for orange container which is draggable
Widget _selfView() {
return Positioned(
top: _selfViewTop,
left: _selfViewLeft,
child: Draggable(
feedback: _draggableView(),
childWhenDragging: Container(),
child: _draggableView(),
onDragEnd: (dragDetail) {
var screenWidth = AspectSize.getScreenWidth(context: context);
var screenHeight = AspectSize.getScreenHeight(context: context);
_selfViewLeft = dragDetail.offset.dx;
_selfViewTop = dragDetail.offset.dy;
if (_selfViewLeft < (screenWidth / 2)) {
_selfViewLeft = 16.0;
} else if (_selfViewLeft > (screenWidth / 2)) {
_selfViewLeft = (screenWidth) - (_selfViewWidth + 16);
}
if (_selfViewLeft < 1.0) {
_selfViewLeft = 16.0;
} else if ((_selfViewLeft + _selfViewWidth) > screenWidth) {
_selfViewLeft = (screenWidth) - (_selfViewWidth + 16);
}
if (_selfViewTop < 1.0) {
_selfViewTop = 50;
} else if ((_selfViewTop + _selfViewHeight) > screenHeight) {
_selfViewTop = (screenHeight) - (_selfViewWidth + 50);
}
setState(() {});
},
),
);
}
For this UI i used, the Stack widgets for managing containers the orange container is used to show the caller's picture, and the blue container will show pictures of another person.
Please give me some solution for this.
Thank you.
You can use
MediaQuery.of(context).viewPadding
and check whether the padding is greater than zero ,then you will need a safe area as there is a notch
.
And the best way to handle notch behaviour is to use the flutter_device_type package.
the following code in the above package can determine the notch:
if( Device.get().hasNotch ){
//Do some notch business
}
Thank You.
So I was implementing a simple image viewer and wanted to implement pinch to zoom inside a Flickable. After looking at several example, I came across this and thought I could do something similar. However, when I tested the code (Qt 5.13 on Android) it doesn't work as expected. The problem is that when the image is zoomed, the PinchArea stops working and the user is unable to zoom out while the Flickable still allows to "flick" the image (at that zoom level). Here is the code, which I couldn't find any mistake with:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Flickable Image Zoom Example")
Page {
id: imagePage
property string imgUrl: "qrc:/messi.jpg"
property string strHpTitle: ""
property string strThumbnailUrl: ""
anchors.fill: parent
Flickable {
id: imageFlickable
anchors.fill: parent
contentWidth: imageContainer.width; contentHeight: imageContainer.height
clip: true
onHeightChanged: if (imagePreview.status === Image.Ready) imagePreview.fitToScreen();
Item {
id: imageContainer
width: Math.max(imagePreview.width * imagePreview.scale, imageFlickable.width)
height: Math.max(imagePreview.height * imagePreview.scale, imageFlickable.height)
Image {
id: imagePreview
property real prevScale
function fitToScreen() {
scale = Math.min(imageFlickable.width / width, imageFlickable.height / height, 1)
pinchArea.minScale = scale
prevScale = scale
}
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
cache: false
asynchronous: true
source: imagePage.imgUrl
sourceSize.height: 1000;
smooth: !imageFlickable.moving
onStatusChanged: {
if (status == Image.Ready) {
fitToScreen()
}
}
onScaleChanged: {
if ((width * scale) > imageFlickable.width) {
var xoff = (imageFlickable.width / 2 + imageFlickable.contentX) * scale / prevScale;
imageFlickable.contentX = xoff - imageFlickable.width / 2
}
if ((height * scale) > imageFlickable.height) {
var yoff = (imageFlickable.height / 2 + imageFlickable.contentY) * scale / prevScale;
imageFlickable.contentY = yoff - imageFlickable.height / 2
}
prevScale = scale
}
}
}
PinchArea {
id: pinchArea
property real minScale: 1.0
property real maxScale: 3.0
anchors.fill: parent
pinch.target: imagePreview
pinch.minimumScale: minScale
pinch.maximumScale: maxScale
pinch.dragAxis: Pinch.XandYAxis
onPinchFinished: {
imageFlickable.returnToBounds()
}
}
}
}
}
Ok so I found the solution to this problem. The issue (I think) is due to a bug. For some strange reason, the Flickable in my code steals touch events from the PinchArea so the pinching touch events are ignored and passed onto the Flickable. In order to solve the problem, I found a workaround which consists in creating a MouseArea inside the PinchArea, this allows the PinchArea to receive the touch events so that the Flickable doesn't steal them. So with the workaround my code has the following structure:
Flickable{
PinchArea{
MouseArea{ anchors.fill: parent} // this is the workaround
}
Item{}
}
I have reported the bug here and uploaded a small video showing the cause.
I know there is a huge amount of posts everywhere, however nothing realy works or at least not to me...
basicaly I dont use exact numbers for font.pointSize, i use it like:
font.pointSize: Math.min(mainappwindow.width, mainappwindow.height)/10
its to be sure that if resolution changes, the text will always be 10% of all screen (smaller from width or height obviously)...
However, if I compile on windows, the height of the text has 72points (counted, as well as debugged).
But when I compile on Android (720x1280 HD), the same code debugs again 72points, but its way bigger! coutned points and its 113...
after a week of googling and trying whatever i could try (nothing worked), i just conunted the diference ration of 0.638 what I am multiplying if platform is android....
This works fine... however when I changed resolution on the phone from HD to fullHD(1080x1920) its again bigger by ratio 1.632...
Therefore this ratio recalculation wont work on different devices which has different resolution...
I have read all the google sources, QML scaling etc, but I would realy appriciate any pice of code directed to my example...
Thank you, this makes me desperate
How #sk2212, said, you could use Qt::AA_EnableHighDpiScaling
In main.cpp file write this code
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
I'd recomended to check Text propertys: renderType, minimumPixelSize, minimumPointSize and fontSizeMode.
Check renderType and fontSizeMode. Maybe it's your sollution
In Text item try this code. And try it without your custom code
renderType: Text.QtRendering
fontSizeMode: Text.FixedSize
Also, it would be great, if you provide some code.
P.S: I'd recommended to create two versions, one for desktop, and one for phone, there will be less code and easier to maintain your app.
You can use Qt::AA_EnableHighDpiScaling see High DPI Documentation
However sometimes there are some issues with this option so you can implement your own pixel size wrapper:
import QtQuick 2.0
pragma Singleton
Object {
id: units
/*!
\internal
This holds the pixel density used for converting millimeters into pixels. This is the exact
value from \l Screen:pixelDensity, but that property only works from within a \l Window type,
so this is hardcoded here and we update it from within \l ApplicationWindow
*/
property real pixelDensity: 4.46
property real multiplier: 1.4 //default multiplier, but can be changed by user
/*!
This is the standard function to use for accessing device-independent pixels. You should use
this anywhere you need to refer to distances on the screen.
*/
function dp(number) {
return Math.round(number*((pixelDensity*25.4)/160)*multiplier);
}
function gu(number) {
return number * gridUnit
}
property int gridUnit: dp(64)
}
In your main.cpp use native Android code for density calculation:
int density = 0;
float logicalDensity = 0;
float yDpi = 0; float xDpi = 0;
#if defined(ANDROID)
QAndroidJniObject qtActivity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
QAndroidJniObject resources = qtActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
QAndroidJniObject displayMetrics = resources.callObjectMethod("getDisplayMetrics", "()Landroid/util/DisplayMetrics;");
density = displayMetrics.getField<int>("densityDpi");
logicalDensity = displayMetrics.getField<float>("density");
yDpi = displayMetrics.getField<float>("ydpi");
xDpi = displayMetrics.getField<float>("xdpi");
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
// Handle exception here.
env->ExceptionClear();
}
#endif
// Add it as a context property (see main.qml)
engine.rootContext()->setContextProperty("densityData", density);
In your main.qml add in Component.onCompleted something like this:
Units.pixelDensity = Qt.binding(function() {
if (Qt.platform.os === "android") {
return densityData / 25.4; // densityData is per inch but we need per mm
}
return Screen.pixelDensity
});
function calculateDiagonal() {
if (Qt.platform.os === "android") {
return Math.sqrt(Math.pow(Screen.width, 2) +
Math.pow(Screen.height, 2)) / densityData;
}
return Math.sqrt(Math.pow(Screen.width, 2) +
Math.pow(Screen.height, 2)) / (Screen.pixelDensity * 25.4);
}
Units.multiplier = Qt.binding(function() {
var diagonal = calculateDiagonal();
Device.diagonal = diagonal;
var baseMultiplier = 1;
if (diagonal >= 3.5 && diagonal < 5.1) { //iPhone 1st generation to phablet
return 0.8;
} else if (diagonal >= 5.1 && diagonal < 6.5) {
return 1;
} else if (diagonal >= 6.5 && diagonal < 15.1) {
return baseMultiplier;
} else if (diagonal >= 15.1 && diagonal < 29) {
return 1.4 * baseMultiplier;
} else if (diagonal >= 29 && diagonal < 92) {
return 1.4 * baseMultiplier;
} else {
return 1.4 * baseMultiplier;
}
});
Use for every pixel value:
Rectangle {
height: Units.dp(50)
width: Units.dp(30)
}
Here is a minimal version of a code revealing the problem which is:
Moving the racket when playing the game on the Desktop kit (Windows) doesn't affect the speed of ball's movement but when run on an Android device, moving the racket affects the speed of ball's movement as though their movements have been tied together.
What solution is there for that, please?
main.qml:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 720
height: 620
title: qsTr("Movement Test")
Rectangle {
id: table
anchors.fill: parent
color: "gray"
Rectangle {
id: ball
property double xincrement: Math.random() + 0.5
property double yincrement: Math.random() + 0.5
width: 15
height: width
radius: width / 2
color: "white"
x: 300; y: 300
}
Racket {
id: myRacket
x: table.width - 50
y: table.height/3
color: "blue"
}
Timer {
interval: 5; repeat: true; running: true
function collision() {
if((ball.x + ball.width >= myRacket.x &&
ball.x < myRacket.x + myRacket.width) &&
(ball.y + ball.height >= myRacket.y &&
ball.y <= myRacket.y + myRacket.height))
return true
return false
}
onTriggered: {
if(ball.x + ball.width >= table.width)
running = false
else if(ball.x <= 0)
ball.xincrement *= -1
else if (collision())
ball.xincrement *= -1
ball.x = ball.x + (ball.xincrement * 1.5);
ball.y = ball.y + (ball.yincrement * 1.5);
if(ball.y <= 0 || ball.y + ball.height >= table.height)
ball.yincrement *= -1
}
}
}
}
Racket.qml:
import QtQuick 2.9
Rectangle {
id: root
width: 15; height: 65
property int oldY: y
property bool yUwards: false
property bool yDwards: false
onYChanged: {
if(y > oldY) yDwards = true
else if (y < oldY) yUwards = true
oldY = y
}
MouseArea {
anchors.fill: parent
anchors.margins: -root.height
drag.target: root
focus: true
hoverEnabled: true
pressAndHoldInterval: 0
drag.axis: Drag.YAxis
drag.minimumY: table.y
drag.maximumY: table.height - root.height - 10
}
}
Qt tries to render every ~16-17ms. If you set your timer to 5ms, it will try to trigger it 3 - 4 times per frame.
Other things that are happening might hinder it from keeping that pace. If the device is less powerfull this effect might be more visible than on other devices.
To see, whether the Timer achievs the set rate, you can print the current ms part of the time with:
console.log(Qt.formatTime(new Date(), "zzz"))
The logged values shall be 5 appart, if the Timer achives full speed. It will be different from 5 if it doesn't.
The easiest way would be to set a target location where the ball will move to, and animate that movement (using a Animation-Type). The Animation shall take care, that the movement speed will be kept, even in cases where the frame rate might drop.
If you want to do it manually, instead of using the timer, you should use the onAfterRendering-slot (I think of Window). This will be triggered when ever something has moved on the screen and triggered rendering. This is also the ideal moment to check collisions.
You then need to calculate the new position depnding on its velocity, the current position and the elapsed time. Latter you can get either from the JS Date()-object, or you expose it somehow form C++ using a QElapsedTimer
I have huge trouble trying to figure the correct font size on the many different screens that exist.
Currently I have a helper function called getCorrectFontSizeForScreen.
export function getCorrectFontSizeForScreen(currentFontSize){
const maxFontDifferFactor = 6; //the maximum pixels of font size we can go up or
if(Platform.OS === 'ios'){ //iOS part
let devRatio = PixelRatio.get();
this.fontFactor = (((screenWidth*devRatio)/320)*0.55+((screenHeight*devRatio)/640)*0.45)
if(this.fontFactor<=1){
return currentFontSize-float2int(maxFontDifferFactor*0.3);
}else if((this.fontFactor>=1) && (this.fontFactor<=1.6)){
return currentFontSize-float2int(maxFontDifferFactor*0.1);
}else if((this.fontFactor>=1.6) && (this.fontFactor<=2)){
return currentFontSize;
}else if((this.fontFactor>=2) && (this.fontFactor<=3)){
return currentFontSize+float2int(maxFontDifferFactor*0.85);
}else if (this.fontFactor>=3){
return currentFontSize+float2int(maxFontDifferFactor);
}
}else{ //Android part
let scale = screenWidth/375; //got this from the f8 facebook project
this.fontFactor = (((screenWidth)/320)*0.65+((screenHeight)/640)*0.35)
if(this.fontFactor<=1){ //for 0.8 until 1.0 use 8 (800x600 phone this.fontFactor == 0.961)
return float2int(scale * (currentFontSize+8));
}else if((this.fontFactor>=1) && (this.fontFactor<=1.6)){ //for 1.0 until 1.5 use 4 (NEXUS 5 this.fontFactor == 1.055)
return float2int(scale * (currentFontSize+4));
}
else{
return float2int(scale * (currentFontSize+2));
}
}
function float2int (value) {
return value | 0; //Converts a float to an integer
}
and then normalize the font size like this:
const styles = StyleSheet.create({
aText:{
color: 'white',
fontFamily: 'Whatever',
fontSize: getCorrectFontSizeForScreen(14),
}
});
It seems to work well on iOS but not that well on Android... I guess I need more fontFactor groups to form this list with trial and error!!
But I wonder, is there a better way to do this?
What do others do about this?
Thank you!
Sizes in React Native are based on points, not pixels, hence you shouldn't need such a complex logic to change font size according to the device dpi. At the contrary, if you want to undo the scaling automatically applied you should divide the pixel size for the pixel ratio.