Create a scalable, glossy / shiny button with Qt Quick

I want to create a glossy button below with Qt Quick (preferably with pure QML, without C ++):

Glossy button

It must be scalable, so I can not use PNG, etc.

My code is:

import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 ApplicationWindow { id: window color: "#cccccc" width: 200 height: 200 Button { id: button width: Math.min(window.width, window.height) - 20 height: width * 0.3 anchors.centerIn: parent text: "Button" style: ButtonStyle { background: Rectangle { gradient: Gradient { GradientStop { position: 0 color: "#bbffffff" } GradientStop { position: 0.6 color: "#00c0f5" } } border.color: "grey" border.width: height * 0.05 radius: height / 5 } label: Label { text: button.text color: "#ddd" font.pixelSize: button.height * 0.5 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } 

Attempt screenshot

I have two problems:

  • I do not know how to create a curved gloss effect.
  • I need to have the text under the radiance, but currently it is above it.
+6
source share
2 answers

This is not possible with a Rectangle. However, you can use Canvas . I will guide you through the process.

Step 1: Flat Colors

Since there are several "layers", we must create an Item to contain them all. We will add layers according to their order Z, starting with flat colors:

 import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 ApplicationWindow { id: window color: "#cccccc" width: 200 height: 200 Button { id: button width: Math.min(window.width, window.height) - 20 height: width * 0.3 anchors.centerIn: parent text: "Button" readonly property real radius: height / 5 style: ButtonStyle { background: Item { Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.beginPath(); ctx.lineWidth = height * 0.1; ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.strokeStyle = "grey"; ctx.stroke(); ctx.fillStyle = "#00c0f5"; ctx.fill(); } } } label: Label { text: button.text color: "white" font.pixelSize: button.height * 0.5 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } 

The Canvas element should fill the button, so we write anchors.fill: parent .

Then we get the 2D context that we use to draw on the canvas. We also call reset() , which clears the canvas before each paint.

The button has rounded corners, so we define the radius property as read-only and set it to the desired value, which in this case is 20% of the button’s height.

Then call beginPath() . This will start a new path and also close all previous paths.

We set the line width for our stroke to 10% of the button height.

Canvas uses QPainter internally. QPainter hits 50% inside the target and 50% outside. We must take this into account when drawing our rounded rectangle, otherwise the stroke will be hidden outside the canvas. We can do this by drawing a rectangle with margins equal to half the width of the line.

After the path of the rounded rectangle was determined, we left the path that we need to stroke and fill.

The result of this step is:

enter image description here

Step 2: shortcut

Since we want the text to be under the gloss of a button, we must define it as follows:

 import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 ApplicationWindow { id: window color: "#cccccc" width: 200 height: 200 Button { id: button width: Math.min(window.width, window.height) - 20 height: width * 0.3 anchors.centerIn: parent text: "Button" readonly property real radius: height / 5 style: ButtonStyle { background: Item { Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.beginPath(); ctx.lineWidth = height * 0.1; ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.strokeStyle = "grey"; ctx.stroke(); ctx.fillStyle = "#00c0f5"; ctx.fill(); } } Label { text: button.text color: "white" font.pixelSize: button.height * 0.5 anchors.centerIn: parent } } label: null } } } 

Note that the style label component is null . This is because we do not want the text to be higher than anything else. If ButtonStyle had a foreground component, this would be optional. Instead, we add the Label element as a child of the background element.

The visual result of this code is identical to the previous step.

Step 3: Glitter Effect

Canvas can draw linear , radial and conical . We will use a linear gradient to draw a glitter effect on our button:

 import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 ApplicationWindow { id: window color: "#cccccc" width: 200 height: 200 Button { id: button width: Math.min(window.width, window.height) - 20 height: width * 0.3 anchors.centerIn: parent text: "Button" readonly property real radius: height / 5 style: ButtonStyle { background: Item { Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.beginPath(); ctx.lineWidth = height * 0.1; ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.strokeStyle = "grey"; ctx.stroke(); ctx.fillStyle = "#00c0f5"; ctx.fill(); } } Label { text: button.text color: "white" font.pixelSize: button.height * 0.5 anchors.centerIn: parent } Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.beginPath(); ctx.lineWidth = height * 0.1; ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.moveTo(0, height * 0.4); ctx.bezierCurveTo(width * 0.25, height * 0.6, width * 0.75, height * 0.6, width, height * 0.4); ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.lineTo(0, height * 0.4); ctx.clip(); ctx.beginPath(); ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); var gradient = ctx.createLinearGradient(0, 0, 0, height); gradient.addColorStop(0, "#bbffffff"); gradient.addColorStop(0.6, "#00ffffff"); ctx.fillStyle = gradient; ctx.fill(); } } } label: null } } } 

We draw the same rounded rectangle as in step # 1, except for this time, we fill it with a transparent gradient from top to bottom.

Step # 3-a screenshot

Look good, but not quite yet. The glitter effect stops halfway down the button, and to achieve this with Canvas we need to do some clipping before drawing a gradient rectangle. You can think of cropping with Canvas as similar to the “subtractive” Rectangular Marquee Tool in Photoshop, except for using any shape you define.

If we were lucky and the light curve was concave, we could just add the following lines before drawing a gradient rectangle:

 ctx.beginPath(); ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.moveTo(0, height / 2); ctx.ellipse(-width / 2, height / 2, width * 2, height); ctx.clip(); 

Step # 3-b screenshot

Instead, we draw the curve manually using bezierCurveTo () .

Determining the values ​​to go to bezierCurveTo() not easy, so I would suggest finding the curve you want with a great tool like Craig Buckler Canvas Bézier Curve Example . This will allow you to manipulate the curves until you find what you need, but best of all, it will give you the code that creates these curves. If you want to do the opposite and edit the code to see real-time curves, check out the Bezier HTML Canvas tutorial .

Below I made a small example that strokes the clipping path to facilitate visualization:

 import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 ApplicationWindow { id: window color: "#cccccc" width: 200 height: 200 Button { id: button width: Math.min(window.width, window.height) - 20 height: width * 0.3 anchors.centerIn: parent text: "Button" readonly property real radius: height / 5 style: ButtonStyle { background: Item { Rectangle { anchors.fill: parent color: "transparent" border.color: "black" opacity: 0.25 } Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); var cornerRadius = height / 5; ctx.beginPath(); ctx.moveTo(0, height * 0.4); ctx.bezierCurveTo(width * 0.25, height * 0.6, width * 0.75, height * 0.6, width, height * 0.4); ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.lineTo(0, height * 0.4); ctx.strokeStyle = "red"; ctx.stroke(); } } } label: null } } } 

Step # 3-c screenshot

The inverse to the red area is the area into which we will draw.

So, the code for trimming is as follows:

 import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 ApplicationWindow { id: window color: "#cccccc" width: 200 height: 200 Button { id: button width: Math.min(window.width, window.height) - 20 height: width * 0.3 anchors.centerIn: parent text: "Button" readonly property real radius: height / 5 style: ButtonStyle { background: Item { Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.beginPath(); ctx.lineWidth = height * 0.1; ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.strokeStyle = "grey"; ctx.stroke(); ctx.fillStyle = "#00c0f5"; ctx.fill(); } } Label { text: button.text color: "#ddd" font.pixelSize: button.height * 0.5 anchors.centerIn: parent } Canvas { anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.beginPath(); ctx.lineWidth = height * 0.1; ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); ctx.moveTo(0, height * 0.4); ctx.bezierCurveTo(width * 0.25, height * 0.6, width * 0.75, height * 0.6, width, height * 0.4); ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.lineTo(0, height * 0.4); ctx.clip(); ctx.beginPath(); ctx.roundedRect(ctx.lineWidth / 2, ctx.lineWidth / 2, width - ctx.lineWidth, height - ctx.lineWidth, button.radius, button.radius); var gradient = ctx.createLinearGradient(0, 0, 0, height); gradient.addColorStop(0, "#bbffffff"); gradient.addColorStop(0.6, "#00ffffff"); ctx.fillStyle = gradient; ctx.fill(); } } } label: null } } } 

Step # 3-d screenshot

The button now looks and can be clicked, but it does not have a visual indication of mouse interaction. Add this as well.

Step 4: Make The Image Interactive

This requires only two lines of code. The first line makes the transparent canvas transparent:

 opacity: !button.pressed ? 1 : 0.75 

The second increases the brightness of the text when the button hangs:

 color: button.hovered && !button.pressed ? "white" : "#ddd" 

You can take it even more and separate the style from your own QML file, provide a color property and conveniently allow different color buttons.

+19
source

A QML image supports SVG natively, then it should be as simple as creating an image using the SVG tool ...

+2
source

Source: https://habr.com/ru/post/974107/


All Articles