fancier dropdown (#2397)

This commit is contained in:
end-4
2025-11-22 16:08:19 +01:00
committed by GitHub
2 changed files with 220 additions and 21 deletions
@@ -1,15 +1,208 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
import QtQuick.Layouts
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
ComboBox {
id: root
Material.theme: Material.System
Material.accent: Appearance.m3colors.m3primary
Material.primary: Appearance.m3colors.m3primary
Material.background: Appearance.m3colors.m3surface
Material.foreground: Appearance.m3colors.m3onSurface
Material.containerStyle: Material.Outlined
property string buttonIcon: ""
property real buttonRadius: height / 2
property color colBackground: Appearance.colors.colSecondaryContainer
property color colBackgroundHover: Appearance.colors.colSecondaryContainerHover
property color colBackgroundActive: Appearance.colors.colSecondaryContainerActive
implicitHeight: 40
Layout.fillWidth: true
background: Rectangle {
radius: root.buttonRadius
color: (root.down && !root.popup.visible) ? root.colBackgroundActive : root.hovered ? root.colBackgroundHover : root.colBackground
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
indicator: MaterialSymbol {
x: root.width - width - 16
y: root.height / 2 - height / 2
text: "keyboard_arrow_down"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnSecondaryContainer
rotation: root.popup.visible ? 180 : 0
Behavior on rotation {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
contentItem: Item {
implicitWidth: buttonLayout.implicitWidth
implicitHeight: buttonLayout.implicitHeight
RowLayout {
id: buttonLayout
anchors.fill: parent
spacing: 8
anchors.leftMargin: 16
anchors.rightMargin: 16
Loader {
Layout.alignment: Qt.AlignVCenter
active: root.buttonIcon.length > 0 || (root.currentIndex >= 0 && typeof root.model[root.currentIndex] === 'object' && root.model[root.currentIndex]?.icon)
visible: active
sourceComponent: MaterialSymbol {
text: {
if (root.currentIndex >= 0 && typeof root.model[root.currentIndex] === 'object' && root.model[root.currentIndex]?.icon) {
return root.model[root.currentIndex].icon;
}
return root.buttonIcon;
}
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnSecondaryContainer
}
}
StyledText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
color: Appearance.colors.colOnSecondaryContainer
text: root.displayText
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
}
delegate: ItemDelegate {
id: itemDelegate
width: ListView.view ? ListView.view.width : root.width
implicitHeight: 40
required property var model
required property int index
property color color: {
if (root.currentIndex === itemDelegate.index) {
if (itemDelegate.down) return Appearance.colors.colSecondaryContainerActive;
if (itemDelegate.hovered) return Appearance.colors.colSecondaryContainerHover;
return Appearance.colors.colSecondaryContainer;
} else {
if (itemDelegate.down) return Appearance.colors.colLayer3Active;
if (itemDelegate.hovered) return Appearance.colors.colLayer3Hover;
return ColorUtils.transparentize(Appearance.colors.colLayer3);
}
}
property color colText: (root.currentIndex === itemDelegate.index) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer3
background: Rectangle {
anchors.fill: parent
radius: Appearance.rounding.small
color: itemDelegate.color
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
contentItem: RowLayout {
spacing: 8
anchors.leftMargin: 12
anchors.rightMargin: 12
Loader {
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: Appearance.font.pixelSize.larger
active: typeof itemDelegate.model === 'object' && itemDelegate.model?.icon?.length > 0
visible: active
sourceComponent: Item {
implicitWidth: icon.implicitWidth
implicitHeight: Appearance.font.pixelSize.larger
MaterialSymbol {
id: icon
anchors.centerIn: parent
text: itemDelegate.model?.icon ?? ""
iconSize: Appearance.font.pixelSize.larger
color: itemDelegate.colText
}
}
}
StyledText {
Layout.fillWidth: true
Layout.preferredHeight: Appearance.font.pixelSize.larger
color: itemDelegate.colText
text: itemDelegate.model[root.textRole]
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
}
popup: Popup {
y: root.height + 4
width: root.width
height: Math.min(listView.contentHeight + topPadding + bottomPadding, 300)
padding: 8
enter: Transition {
PropertyAnimation {
properties: "opacity"
to: 1
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
exit: Transition {
PropertyAnimation {
properties: "opacity"
to: 0
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
background: Item {
StyledRectangularShadow {
target: popupBackground
}
Rectangle {
id: popupBackground
anchors.fill: parent
radius: Appearance.rounding.normal
color: Appearance.colors.colSurfaceContainerHigh
}
}
contentItem: StyledListView {
id: listView
clip: true
implicitHeight: contentHeight
spacing: 2
model: root.popup.visible ? root.delegateModel : null
currentIndex: root.highlightedIndex
}
}
}
@@ -128,7 +128,7 @@ ContentPage {
}
}
}
ContentSection {
icon: "language"
title: Translation.tr("Language")
@@ -137,13 +137,12 @@ ContentPage {
title: Translation.tr("Interface Language")
tooltip: Translation.tr("Select the language for the user interface.\n\"Auto\" will use your system's locale.")
ConfigSelectionArray {
StyledComboBox {
id: languageSelector
currentValue: Config.options.language.ui
onSelected: newValue => {
Config.options.language.ui = newValue;
}
options: [
buttonIcon: "language"
textRole: "displayName"
model: [
{
displayName: Translation.tr("Auto (System)"),
value: "auto"
@@ -153,14 +152,22 @@ ContentPage {
displayName: lang,
value: lang
};
})
]
})]
currentIndex: {
const index = model.findIndex(item => item.value === Config.options.language.ui);
return index !== -1 ? index : 0;
}
onActivated: index => {
Config.options.language.ui = model[index].value;
}
}
}
ContentSubsection {
title: Translation.tr("Generate translation with Gemini")
tooltip: Translation.tr("You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.")
ConfigRow {
MaterialTextArea {
id: localeInput
@@ -195,7 +202,7 @@ ContentPage {
ContentSubsectionLabel {
text: Translation.tr("AI")
}
ConfigSelectionArray {
currentValue: Config.options.policies.ai
onSelected: newValue => {
@@ -278,7 +285,7 @@ ContentPage {
}
}
}
ContentSection {
icon: "nest_clock_farsight_analog"
title: Translation.tr("Time")
@@ -309,7 +316,6 @@ ContentPage {
}
Config.options.time.format = newValue;
}
options: [
{