QML on Windows: use platform menu in dark mode, or get qt menu to jump to underlines? - windows

Using both Qt 6.3.0 and 6.4.1, I'm trying to use a menu bar on Windows that (a) renders in dark mode and (b) lets the user jump to a menu item by typing its underlined character, just like a normal Windows app does.
If I use qt.labs platform menus, I can't find a way to make (a) happen, but (b) happens by default.
If I use qt's own menus, (a) happens naturally, but (b) does not.
I set up dark mode in main.cpp as follows (based on this post, among others):
#ifdef Q_OS_WIN
QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",QSettings::NativeFormat);
if(settings.value("AppsUseLightTheme")==0){
app.setStyle(QStyleFactory::create("Fusion"));
QPalette darkPalette;
QColor darkColor = QColor(64,64,64); // QColor(45,45,45);
QColor disabledColor = QColor(127,127,127);
darkPalette.setColor(QPalette::Window, darkColor);
darkPalette.setColor(QPalette::WindowText, Qt::white);
...
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor);
app.setPalette(darkPalette);
app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
#endif
My QML code is this, for platform menus:
import Qt.labs.platform as Platform
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Platform.MenuBar {
id: menuBar
Platform.Menu {
id: fileMenu
title: qsTr("&File")
Platform.MenuItem {
id: openMenuItem;
text: qsTr("&Open...")
onTriggered: fileOpenAction.trigger();
} // open
}
}
...
Or this, for native menus:
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
menuBar: MenuBar {
id: menuBar
Menu {
id: fileMenu
title: qsTr("&File")
MenuItem {
id: openMenuItem;
text: qsTr("&Open...")
onTriggered: fileOpenAction.trigger();
} // open
}
}
...
In both cases, the "Open..." item displays with an underlined O.
With the platform menus, the menu bar and menus are resolutely black on white, even as the rest of the window goes dark (explicit changes to the parent's palette aren't seen either, and the platform menu elements don't have a palette member that can be modified directly). I can type Alt+F to display the menu, and O to select the Open item. Is there a way to make the Windows-native platform menu respect dark mode?
With qt menus, the menu turns dark in dark mode, and I can type Alt+F to display the menu, but typing O does nothing. Is there a way to make the qt menus select a menu item when its underlined character is typed?

You can change the color of your controls by modifying the palette. The modification of the palette is hierarchical. i.e. you can define the palette by setting it in the parent control and the children will inherit, or, you can define the palette in the child control.
Here's a working example:
import QtQuick
import QtQuick.Controls
Page {
background: Rectangle { color: "#444" }
palette.buttonText: "lightgreen" // menu text
palette.button: "black" // menu background
palette.mid: "grey" // menu highlighted background
palette.windowText: "lightgreen" // popup menu text
palette.window: "black" // popup menu background
palette.light: "grey" // popup menu highlighted background
palette.dark: "grey" // popup menu border
MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Open...")
}
MenuItem {
text: qsTr("&Quit")
}
}
Menu {
title: qsTr("&Edit")
palette.window: "#400"
MenuItem {
text: qsTr("&Copy")
}
MenuItem {
text: qsTr("&Paste")
}
}
}
}
You can Try it Online!
References:
https://doc.qt.io/qt-6/qml-qtquick-systempalette.html
https://doc.qt.io/qt-6/qml-qtquick-palette.html

"Solved" by talamaki in the comments: Pressing the letter alone is no longer that way it's done; the proper way is to press Alt+letter, and that works with qt menus — which obey dark mode. Thanks, talamaki!

Related

Baseline alignment of buttons in SwiftUI on macOS

I’m trying to vertically align a Text and a regular Button along their baselines in SwiftUI on macOS. I basically want to replicate this layout from the WiFi preference pane:
I expected that putting the said controls into an HStack with the alignment set to .firstTextBaseline would produce this layout. While this is the case for many other controls, it doesn’t seem to work with non-plain buttons. See the following example:
HStack(alignment: .firstTextBaseline) {
Text("Text")
Text("Large Text")
.font(.title)
Button {} label: {
Text("Button")
}
Button {} label: {
Text("Borderless")
}
.buttonStyle(.borderless)
Button {} label: {
Text("Plain")
}
.buttonStyle(.plain)
Toggle("Toggle", isOn: .constant(true))
Picker("Picker", selection: .constant(0)) {
Text("One").tag(0)
Text("Two").tag(1)
Text("Three").tag(2)
}
.pickerStyle(.radioGroup)
}
.padding()
Am I holding it wrong? Is there a way to levere the baselines just like in AppKit, or do I have to position the button manually?

ContextMenu in macOS App SwiftUI on Button Tap

I want to tap a button and show a context menu in SwiftUI for a macOS app. I can see the button but when I tap the button nothing happens.
let menuItems = ContextMenu {
Button("Today") {}
Button("Tomorrow") {}
}
Button {
// action
} label: {
Label("Add Date", systemImage: "calendar")
.contextMenu(menuItems)
}
Any ideas?
It seems to me that you just look for menu with button style, like
Menu {
Button("Today") {}
Button("Tomorrow") {}
} label: {
Label("Add Date", systemImage: "calendar")
}
.menuStyle(.borderedButton)
Placing a Button around the context menu means that the button is managing all the click events, and none of them get through to the contextMenu modifier.
You could move the modifier to attach onto the button itself, and you get something that executes the button action on left click, but displays the context menu when right-clicked:
Button {
print("Clicked")
} label: {
Label("Add Date", systemImage: "calendar")
}
.contextMenu(menuItems)

Removing Button Highlight SwiftUI

Is there anyway we could remove the default button highlight in SwiftUI?
I currently have a view that acts as a navigationlink but when I tap on it, I would like it to not have that default highlight (the fade out ).
The main view looks like:
NavigationLink( ... ) {
VStack{
{ ... }
Button(action: ... ){ ... }
}
}
.buttonStyle(PlainButtonStyle())
It's gotten rid of the blue foreground text color, but has not removed the default highlight.
I would like to do this as I have another button inside that view that does a seperate action, but when I click on that button, it highlights the entire view (but doesn't trigger the main view navigationlink!! it only triggers the inner button action)
I am currently using swiftui 2.0
edit:
I couldn't a find a way to remove that button highlight, but I found a different approach. Instead, I would just navigate programmatically by using the isActive version of NavigationLink. So instead it would be :
#State private var showOneLevelIn = false
//this navigationLink is hidden
NavigationLink(destination: OneLevelInView(), isActive: $showOneLevelIn, label: { EmptyView() })
//original view without navigationlink wrapped around
VStack{
{ ... }
Button(action: ... ){ ... }
}
.onTapGesture(count: 1) {
showOneLevelIn = true
}
found from: Use NavigationLink programmatically in SwiftUI
List detects any default button at any subview level. So try to change button style not only for link but for buttons as well
NavigationLink( ... ) {
VStack{
{ ... }
Button(action: ... ){ ... }
.buttonStyle(PlainButtonStyle()) // << here !!
}
}
.buttonStyle(PlainButtonStyle())

Qml Qt Quick Control 2: Font size difference between Text and ComboBox

Let's look at this very simple sample application, built with QT 5.9 on a Windows 10:
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout {
anchors.left: parent.left
anchors.leftMargin: 20
anchors.right: parent.right
anchors.rightMargin: 20
Text {
id: text
text: "This is a sample Text"
}
ComboBox {
model: [
"A",
"B",
"C"
]
}
Text {
text: "Another Text"
}
TextField {
anchors.left: parent.left
anchors.right: parent.right
text: "User Input"
}
}
}
If I run it without any further modifications from the QT Creator, I get a very weird relationship between the Font-Size of the Text and the ComboBox and TextField blocks. It looks like this:
The text is too small, and the ComboBoxes (and their Fonts) are HUGE.
If I change the main function to set the default font size explicitely to the system font size using this code (It's the same when I hardcode the setPointSizeF to 12, which is the supposed standard size on windows):
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
auto font = app.font();
QFontInfo fi(font.defaultFamily());
font.setPointSizeF(fi.pointSizeF());
app.setFont(font);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
It looks like this:
Now the "Relative Dimensions" are more balanced, but overall everything is just "too big". Additionally, if I'm opening the ComboBox, I get again very small text:
Did I miss to set some default here? How can I achieve a more balanced look that fit's better into the Operating Systems' native font sizes?
The combobox delegates use a different font to the application default.
Delegate font can be changed to match the rest of the application as follows:
ComboBox {
id: control
delegate: ItemDelegate {
width: control.width
text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData
font.weight: control.currentIndex === index ? Font.DemiBold : Font.Normal
font.family: control.font.family
font.pointSize: control.font.pointSize
highlighted: control.highlightedIndex === index
hoverEnabled: control.hoverEnabled
}
}
Leave the font sizes as defaults and instead set the widths of your items. You could explicitly set the width of the ComboBox and TextField, or if you want to use the ColumnLayout to have consistent sizing of all the items, see example below,
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout {
anchors.left: parent.left
anchors.leftMargin: 20
width: text.width
Text {
id: text
text: "This is a sample Text"
}
ComboBox {
Layout.fillWidth: true
model: [
"A",
"B",
"C"
]
}
Text {
text: "Another Text"
}
TextField {
Layout.fillWidth: true
text: "User Input"
}
}
}
You can simply try setting: font.pointSize: 12 in both the combobox and text fields. This works for me on Qt 5.9 in Windows 10. I am still trying to figure how to change the font size inside the combobox drop down; will expand this answer when I know.

QML drag one component as if it has the top stacking order (largest z)

QT5.5, QML:
I am using the example "Qt Quick Examples - Drag and Drop"
In this example, you could see that if we drag the red tile "1", it appears under other tiles when we drag it.
I don't like this effect since when we drag one item, I hope it always appears on the top of whole GUI.
What I tried is:
Once the mouse is pressed, we set the item's z as the largest value. And when the mouse is released, set the z to the small value (or better we could save the original value and reset it back to its original valule)
MouseArea {
id: mouseArea
width: 64; height: 64
anchors.centerIn: parent
drag.target: tile
onPressed: {
tile.z = 100;
}
onReleased: {
tile.z = 0;
parent = tile.Drag.target !== null ? tile.Drag.target : root
}
}
But the code does not work.
Actually by using console.log, I could see the z value changes, but the dragging ghost image still appears at the bottom.
So I guess when onPressed is implemented, the drag mechanism has used its original z value and gets no chance to access the updated z value.
So any idea to have a more decent drag movement?
Thanks!
The z value of items only applies to siblings and the immediate (direct) parent:
Items with a higher stacking value are drawn on top of siblings with a lower stacking order. Items with the same stacking value are drawn bottom up in the order they appear. Items with a negative stacking value are drawn under their parent's content.
Let's use a small example to test the parent/child scenario:
import QtQuick 2.3
import QtQuick.Window 2.0
Window {
visible: true
width: 200
height: 200
title: qsTr("Hello World")
flags: Qt.FramelessWindowHint
Rectangle {
color: "salmon"
width: 64
height: 64
anchors.centerIn: parent
Text {
text: "1"
}
Rectangle {
color: "steelblue"
x: 32
y: 32
width: 64
height: 64
Text {
text: "2"
}
Rectangle {
color: "orchid"
x: 16
y: -16
width: 64
height: 64
z: -2
Text {
text: "3"
}
}
}
}
}
We set the z value of the third Rectangle to -2, hoping that it would go behind the first one, but since it's a child of the second and not the first, it's out of reach. This is what's happening in the drag and drop example: the items are too far apart in terms of ancestory.
To elaborate further on this, let's take DragTile.qml from the Drag and Drop example and modify it in a similar way to yours (this is a nicer way of achieving the same thing, by the way):
states: State {
when: mouseArea.drag.active
ParentChange { target: tile; parent: root }
AnchorChanges { target: tile; anchors.verticalCenter: undefined; anchors.horizontalCenter: undefined }
PropertyChanges {
target: tile
z: 100
}
}
This also won't work. To see what's going on, we can use an awesome little environment variable called QSG_VISUALIZE. Specifically, we want to use the overdraw visualisation, which gives us a nice rotating 3D box containing our scene. Even with our PropertyChanges above, you can see that the stacking order of the item is unchanged:
To ensure that the item is rendered on top of everything, you need to parent it to an item that is actually above everything. We can do this by adding an Item to the end of tiles.qml:
Item {
id: dragContainer
anchors.fill: parent
}
Add a property to the DragTile component that gives the delegate access to the container:
property Item dragParent
Then, assign the container in tiles.qml:
delegate: DragTile { colorKey: "red"; dragParent: dragContainer }
Next, modify the parent property of the ParentChange in DragTile.qml:
ParentChange { target: tile; parent: dragParent }
The end result:
Note that I intentionally excluded the "back" button, but if you wanted to make it go above that as well for some reason, you can just move dragContainer higher up the hierarchy.

Resources