Home > Blockchain >  Reproducing OS Minimize Behaviour in a Custom Titlle Bar done in QML
Reproducing OS Minimize Behaviour in a Custom Titlle Bar done in QML

Time:11-11

I'm being tasked with creating a customized title bar for our application. It needs to have rounded corners and a settings button, amongst other things. It will run exclusively on windows.

Our application uses Qt and QML for the front end. So the only way I could find how to do this is by making the application window frameless and creating the title bar from scratch.

This is my test code:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

ApplicationWindow {

    id: mainWindow
    visible: true
    visibility: Window.Maximized
    title: qsTr("Hello World")
    flags: Qt.FramelessWindowHint | Qt.Window | Qt.WA_TranslucentBackground
    //flags: Qt.Window | Qt.WA_TranslucentBackground
    color: "#00000000"

    TitleBar {
        id: mainTitleBar
        width: mainWindow.width;
        height: mainWindow.height*0.018
        color: "#aaaaaa"
        onCloseApplication: {
            Qt.quit();
        }
        onMinimizeApplication: {
            mainWindow.visibility = Window.Minimized
        }
    }

    Component.onCompleted: {
        console.log("Size: "   mainWindow.width   "x"   mainWindow.height)
        mainTitleBar.width = mainWindow.width
        mainTitleBar.height = mainWindow.height*0.023;
    }

    Rectangle {
        id: content
        width: mainWindow.width
        height: mainWindow.height - mainTitleBar.height
        anchors.top: mainTitleBar.bottom
        anchors.left: mainTitleBar.left
        color: "#00ff00"
    }

}

And

Here is the title bar code (TitleBar.js file):

import QtQuick 2.0
import QtQuick.Controls 2.0

Rectangle {
    /*
     * Requires setting up of
     * -> width
     * -> height
     * -> title text
     * -> icon path.
     * -> Background color.
     */

    id: vmWindowTitleBar
    border.width: 0
    x: 0
    y: 0
    radius: 20

    signal closeApplication();
    signal minimizeApplication();

    // The purpose of this rectangle is to erase the bottom rounded corners
    Rectangle {
        width: parent.width
        height: parent.height/2;
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        border.width: 0
        color: parent.color
    }

    Text {
        id: titleBarText
        text: "This is The Title Bar"
        anchors.verticalCenter: parent.verticalCenter
        anchors.leftMargin: parent.width*0.018
    }

    Button {
        id: minimizeButton
        width: height
        height: vmWindowTitleBar.height*0.8
        anchors.right: closeButton.right
        anchors.verticalCenter: parent.verticalCenter
        anchors.rightMargin: parent.width*0.018

        background: Rectangle {
            id: btnMinimizeRect
            color: vmWindowTitleBar.color
            anchors.fill: parent
        }

        onPressed:{
            minimizeApplication()
        }

        scale: pressed? 0.8:1;

        contentItem: Canvas {
            id: btnMinimizeCanvas
            contextType: "2d"
            anchors.fill: parent
            onPaint: {
                var ctx = btnMinimizeCanvas.getContext("2d");
                var h = minimizeButton.height;
                var w = minimizeButton.width;
                ctx.reset();
                ctx.strokeStyle = minimizeButton.pressed? "#58595b": "#757575";
                ctx.lineWidth = 6;
                ctx.lineCap = "round"
                ctx.moveTo(0,h);
                ctx.lineTo(w,h);
                ctx.closePath();
                ctx.stroke();
            }
        }

    }

    Button {
        id: closeButton
        //hoverEnabled: false
        width: height
        height: vmWindowTitleBar.height*0.8
        anchors.right: parent.right
        anchors.verticalCenter: parent.verticalCenter
        anchors.rightMargin: parent.width*0.018
        background: Rectangle {
            id: btnCloseRect
            color: vmWindowTitleBar.color
            anchors.fill: parent
        }

        onPressed:{
            closeApplication()
        }

        scale: pressed? 0.8:1;

        Behavior on scale{
            NumberAnimation {
                duration: 10
            }
        }

        contentItem: Canvas {
            id: btnCloseCanvas
            contextType: "2d"
            anchors.fill: parent
            onPaint: {
                var ctx = btnCloseCanvas.getContext("2d");
                var h = closeButton.height;
                var w = closeButton.width;
                ctx.reset();
                ctx.strokeStyle = closeButton.pressed? "#58595b": "#757575";
                ctx.lineWidth = 2;
                ctx.lineCap = "round"
                ctx.moveTo(0,0);
                ctx.lineTo(w,h);
                ctx.moveTo(w,0);
                ctx.lineTo(0,h);
                ctx.closePath();
                ctx.stroke();
            }
        }
    }

}

Now the problem comes with minimizing the application. The first thing I realize is that when using the Qt.FramelessWindowHint flag, the icon does not appear in the Windows Taskbar. Furthermore if I minimize it this happens:

Minimize Behaviour

And If I click on it, it doesn't restore.

So my question is, is there a way to reproduce regular minimize behavior when pressing the minimize button?

Or alternatively, is there a way I can completely customize the title bar of the application so that I can achieve the look and feel set by our UI designer?

NOTE: The current look is just a quick test. I have not set the gradient, font, or the aforementioned settings button.

CodePudding user response:

As for me, playing with frameless windows and transparent background is kind of workaround. As I know, the only way to apply a custom shape to the window is QWindow::setMask. Sinse Window is derived from QWindow you can do that in this way.

For example, in the main.cpp:

QWindow *wnd = qobject_cast<QWindow *>(engine.rootObjects().at(0));
auto f = [wnd]() {
    QPainterPath path;
    path.addRoundedRect(QRectF(0, 0, wnd->geometry().width(), wnd->geometry().height()), 30, 30);
    QRegion region(path.toFillPolygon().toPolygon());
    wnd->setMask(region);
};
QObject::connect(wnd, &QWindow::widthChanged, f);
QObject::connect(wnd, &QWindow::heightChanged, f);
f();

Since you 'cut' the shape from the window itself, excluding title bar and frames you can leave the window flags as is.

CodePudding user response:

Look at this way, I try to create something that you do but change completely your code.

the problem that makes change in your window size after you minimize the window is that you didn't set the initial width and height for the window so when you minimize the app it shows in the minimum width and height.

so you need to add just this in main.qml and set the initial width and height to the maximum.

    width: maximumWidth
    height:maximumHeight

but In the code below I change something else too.

For example, you didn't need to emit signals and then catch them in main.qml you have access to mainWindow in TitleBar.qml.

in TitleBar.qml :

import QtQuick 2.0
import QtQuick.Controls 2.0

Rectangle {
    anchors.fill: parent
    height: 30

    Row {
        id: row
        anchors.fill: parent

        Label {
            id: label
            text: qsTr("Title ")

        }

        Button {
            id: button
            x: parent.width -80
            text: qsTr("close")

            onClicked:
            {
                mainWindow.close()

            }
        }

        Button {
            id: button1
            x: parent.width -160
            width: 90
            text: qsTr("Minimized")

            onClicked:
            {
                mainWindow.showMinimized()
            }
        }
    }


}

and in main.qml :

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

import "."

Window {

    id: mainWindow
    visible: true

    visibility: Window.FullScreen
    title: qsTr("Hello World")
    flags: Qt.FramelessWindowHint | Qt.Window | Qt.WA_TranslucentBackground

    width: maximumWidth
    height:maximumHeight

    Rectangle {
        id: content
        anchors.fill: parent
        x: 0
        y: 20
        width: mainWindow.width
        height: mainWindow.height - mainTitleBar.height
        anchors.top: mainTitleBar.bottom
        anchors.left: mainTitleBar.left
        color: "#00ff00"



    }
    TitleBar {
        id: mainTitleBar
        color: "#aaaaaa"
        anchors.bottomMargin: parent.height -40
        anchors.fill: parent



    }

}
  • Related