user1054922
user1054922

Reputation: 2165

Resizing windows to match screen size in Qt

In OpenGL terms, what I want to do is modify the projection matrix of a Qt GUI.

Pretend the window is 480x640. It is displayed as normal, and rendered to a texture. I then take that texture, and stretch it across the entire screen.

Does Qt have something like that? I don't want the GUI looking fine and having appropriately-sized text on a 480x640 tablet, but then it gets loaded up on a 1536x2048 tablet and you need a magnifying glass for the text.

I've written my own GUI in OpenGL before, calculating a vid.width/BASEWIDTH, vid.height/BASEHEIGHT ratio and multiplying the modelview matrix of elements to ensure that a GUI always fills a screen and stays the same size -- obviously this only works perfectly providing the aspect ratio is the same, but I digress.

I messed with layouts in Qt Quick for awhile, and it offers some nice anchoring options, but nothing for stuff like scaling up the text if the parent window is larger. Or am I missing something here?

An OpenGL GUI I wrote had a few options for control position coordinates:

Origin for transforms (Top, Center, Bottom, Left, Center, Right) PosIsPercentage (specified whether the position coordinates were to be interpreted as a percentage of screen width/height)

This allowed you to set the position as a distance from any edge of the screen, or you could set PosIsPercentage = true and set the X value to 75 to have the coordinate always be at 3/4ths of whatever the screen size was.

There was also a SizeIsPercentage value, so you could set a button to be 10% of the screen width.

I see some of these options in the Qt Quick designer, but they aren't behaving as I expect.

I know this is hard to explain, so here is an image to demonstrate:

http://www.spaddlewit.com/QtLayoutProblem.png

(not what I'm using Qt for, but a good example of the problem I'm having)

Upvotes: 2

Views: 4897

Answers (2)

user1054922
user1054922

Reputation: 2165

The following works, but it's annoying -- you have to create a scaleWidth and scaleHeight function and wrap any constant coordinates in them. Font sizes scale along the shortest edge of the screen -- this application is a Portrait-only orientation, so it uses scaleWidth(pointSize) for font sizes.

Would be nice to find a solution that's compatible with the QML designer.. is there any way to automatically insert this calculation, maybe afterwards in C++ code at runtime?

import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2

ApplicationWindow {
    id:window
    visible: true
    width: 480
    height: 640

    function scaleWidth(w)
    {
        return w * (width / 480.0)
    }
    function scaleHeight(h)
    {
        return h * (height / 640.0)
    }

    Text {
        id: defaultText
    }

    Image {
        id: image1
        x: 0
        y: 0
        width: window.width
        height: window.height
        fillMode: Image.Stretch
        source: "http://cdn2.landscapehdwalls.com/wallpapers/1/perfect-green-hills-1197-1280x800.jpg"

        Label {
            id: lblTitle
            x: 0
            y: scaleHeight(8)
            text: qsTr("Welcome to the App")
            anchors.horizontalCenter: parent.horizontalCenter
            font.pointSize: scaleWidth(36)
            horizontalAlignment: Text.AlignHCenter
        }

        Label {
            id: lblSubtitle
            x: 0
            text: qsTr("Login to Continue")
            font.pointSize: scaleWidth(24)
            anchors.top: lblTitle.bottom
            anchors.topMargin: scaleHeight(8)
            anchors.horizontalCenter: lblTitle.horizontalCenter
        }

        Item {
            id: itemCenterAlign
            x: 0
            y: 0
            width: 0
            height: 200
            anchors.horizontalCenter: parent.horizontalCenter
        }

        Label {
            id: lblUsername
            x: 0
            text: qsTr("Username:")
            anchors.top: lblSubtitle.bottom
            anchors.topMargin: scaleHeight(64)
            font.bold: true
            font.pointSize: scaleWidth(24)
            anchors.right: itemCenterAlign.left
            anchors.rightMargin: scaleWidth(8)
        }

        TextField {
            id: txtUsername
            width: scaleWidth(224)
            height: scaleHeight(43)
            anchors.left: itemCenterAlign.right
            anchors.leftMargin: scaleWidth(8)
            anchors.top: lblSubtitle.bottom
            anchors.topMargin: scaleHeight(64)
            font.pointSize: scaleWidth(24)
            placeholderText: qsTr("Username")
        }

        Label {
            id: lblPIN
            x: 0
            y: scaleWidth(-8)
            text: qsTr("PIN:")
            font.bold: true
            font.pointSize: scaleWidth(24)
            anchors.topMargin: scaleHeight(12)
            anchors.right: itemCenterAlign.left
            anchors.rightMargin: scaleWidth(8)
            anchors.top: lblUsername.bottom
        }

        TextField {
            id: txtPIN
            x: 0
            y: 0
            width: scaleWidth(224)
            height: scaleHeight(43)
            placeholderText: qsTr("PIN")
            font.pointSize: scaleWidth(24)
            anchors.topMargin: scaleHeight(8)
            anchors.leftMargin: scaleWidth(8)
            anchors.left: itemCenterAlign.right
            anchors.top: txtUsername.bottom
        }

        Row {
            id: row1
            x: 0
            y: scaleHeight(277)
            width: scaleWidth(464)
            height: scaleHeight(115)
            spacing: scaleWidth(8)
            anchors.horizontalCenter: parent.horizontalCenter

            Button {
                id: cmdQuit
                text: qsTr("Quit")
                width: row1.width / 3 - row1.spacing / 2
                height: row1.height
            }
            Button {
                id: cmdGPSOnly
                text: qsTr("GPS Only")
                width: row1.width / 3 - row1.spacing / 2
                height: row1.height
            }
            Button {
                id: cmdLogin
                text: qsTr("Login")
                width: row1.width / 3 - row1.spacing / 2
                height: row1.height
            }
        }

        Button {
            id: cmdAbout
            width: cmdQuit.width
            height: scaleHeight(44)
            text: qsTr("About")
            anchors.top: row1.bottom
            anchors.topMargin: scaleHeight(8)
            anchors.left: row1.left
            anchors.leftMargin: 0
        }

        Label {
            id: lblVersion
            y: 619
            text: qsTr("v3.0.0.0")
            font.pointSize: scaleWidth(16)
            anchors.bottom: parent.bottom
            anchors.bottomMargin: scaleHeight(8)
            anchors.left: parent.left
            anchors.leftMargin: scaleWidth(8)
        }

        Label {
            id: lblBooks
            x: 0
            y: lvBooks.y
            text: qsTr("Books Loaded:")
            horizontalAlignment: Text.AlignRight
            font.pointSize: scaleWidth(24)
            anchors.right: lvBooks.left
            anchors.rightMargin: scaleWidth(8)
        }

        Rectangle
        {
            x: lvBooks.x
            y: lvBooks.y
            width: lvBooks.width
            height: lvBooks.height
            color: "white"
            border.color: "black"
        }

        ListView {
            id: lvBooks
            x: 0
            y: 0
            width: scaleWidth(224)
            height: scaleHeight(160)
            anchors.bottom: parent.bottom
            anchors.bottomMargin: scaleHeight(8)
            anchors.right: parent.right
            anchors.rightMargin: scaleWidth(8)
            model: ListModel {
                ListElement {
                    name: "Book1"
                }

                ListElement {
                    name: "Book2"
                }
            }
            delegate: Item {
                x: 5
                width: scaleWidth(80)
                height: scaleHeight(40)
                Row {
                    Text {
                        text: name
                        font.bold: true
                        font.pointSize: scaleWidth(24)
                        anchors.verticalCenter: parent.verticalCenter
                    }
                    spacing: 0
                }
            }
        }
    }
}

Upvotes: 0

Mitch
Mitch

Reputation: 24396

Scaling items based on the width and height of the screen works well enough, except when you move to a high DPI device. A better method is to scale items based on the height of the default font. The default font size of a Text item, for example, will always be legible on platforms supported by Qt, no matter the DPI. You can use the same principle to scale font sizes; multiply the default font size by some amount.

Below I've done a quick mock up of the screenshot you linked to:

import QtQuick 2.3
import QtQuick.Controls 1.2

ApplicationWindow {
    id: window
    contentItem.implicitWidth: 640
    contentItem.implicitHeight: 480
    contentItem.minimumWidth: 640
    contentItem.minimumHeight: 480
    contentItem.maximumWidth: 1024
    contentItem.maximumHeight: 768

    /*
        With Qt 5.4, you can also use the new FontMetrics item,
        which saves you the overhead of creating a Text item:

        For example:

        FontMetrics {
            id: fontMetrics
        }

        Then:

        font.pixelSize: fontMetrics.font.pixelSize * 4
        anchors.margins: fontMetrics.implicitHeight * 2
    */
    Text {
        id: defaultText
    }

    Image {
        source: "http://cdn2.landscapehdwalls.com/wallpapers/1/perfect-green-hills-1197-1280x800.jpg"
    }

    Item {
        id: container
        anchors.fill: parent
        anchors.margins: defaultText.implicitHeight * 2

        Column {
            anchors.right: parent.right
            anchors.verticalCenter: parent.verticalCenter
            spacing: container.anchors.margins

            Text {
                id: yourGameText
                text: "Your Game!"
                font.pixelSize: defaultText.font.pixelSize * 4
                wrapMode: Text.Wrap
            }

            ListView {
                interactive: false
                anchors.right: parent.right
                width: yourGameText.width
                height: container.height * 0.3
                model: ["Play Game!", "Options", "Exit"]
                delegate: Button {
                    text: modelData
                    width: ListView.view.width
                }
            }
        }

        Row {
            anchors.left: parent.left
            anchors.bottom: parent.bottom
            spacing: container.anchors.margins

            Image {
                source: "http://www.facebookbrand.com/img/assets/asset.f.logo.lg.png"
                width: defaultText.implicitHeight * 3
                height: width
            }

            Image {
                source: "http://g.twimg.com/Twitter_logo_white.png"
                width: defaultText.implicitHeight * 3
                height: width
            }

            Image {
                source: "http://www.youtube.com/yt/brand/media/image/YouTube-logo-full_color.png"
                width: defaultText.implicitHeight * 3
                height: width
            }
        }
    }
}

Window Size

The first thing I did was set the default, minimum and maximum size of the window.

Scaling

Next, I created an empty Text item which items and text sizes will be based off. It might seem hackish, and it is a bit, but it also works really well. As mentioned in the comment, in Qt 5.4 there will be a FontMetrics type which you can use instead of creating a Text item that will never actually be shown.

Another alternative is to use Screen's pixelDensity property.

Margins

You said you wanted to:

set the position as a distance from any edge of the screen

I did that by creating an Item that fills the window, and then setting the margins from the edges of the window as some factor of the default font's implicit height. This ensures that the content within the item will be the same physical distance (e.g., in millimetres) from the edge of the window regardless of the DPI of the device you're viewing it on. If you'd rather the distance be larger if the window is larger, you can do this instead:

anchors.margins: window.width * 0.1

Font Sizes

Take a look at the Text item within the Column. If you want to ensure the text is also the same physical size on the screen, you can set font.pixelSize to be the default font's size multiplied by some amount. Again, if you'd rather base it off the size of the screen rather than the DPI, you can do this instead:

font.pixelSize: window.height * 0.05

More Information

The Scalability documentation also gives a nice overview on this topic.

Below is a screenshot of the application running:

mockup screenshot

Upvotes: 1

Related Questions