1
0
Fork 0

UI tweaks for the launcher

This commit is contained in:
James Turner 2018-04-09 19:41:44 +01:00
parent 8eb4d76411
commit 71a1348037
16 changed files with 749 additions and 23 deletions

View file

@ -59,7 +59,7 @@ bool PopupWindowTracker::eventFilter(QObject *watched, QEvent *event)
} else {
m_window->close();
setWindow(nullptr);
return true;
// still fall through
}
}

View file

@ -0,0 +1,93 @@
// QmlRadioButtonHelper.hxx - helper for QtQuick radio button impl
//
// Written by James Turner, started April 2018.
//
// Copyright (C) 2015 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "QmlRadioButtonHelper.hxx"
#include <QMetaObject>
#include <QDebug>
QmlRadioButtonGroup::QmlRadioButtonGroup(QObject *parent) : QObject(parent)
{
}
QmlRadioButtonGroupAttached* QmlRadioButtonGroup::qmlAttachedProperties(QObject *object)
{
return new QmlRadioButtonGroupAttached(object);
}
QObject *QmlRadioButtonGroup::selected() const
{
return m_selected;
}
void QmlRadioButtonGroup::setSelected(QObject *selected)
{
if (m_selected == selected)
return;
m_selected = selected;
emit selectedChanged(m_selected);
}
QmlRadioButtonGroupAttached::QmlRadioButtonGroupAttached(QObject *pr) :
QObject(pr)
{
}
QmlRadioButtonGroup *QmlRadioButtonGroupAttached::group() const
{
return m_group;
}
bool QmlRadioButtonGroupAttached::isSelected() const
{
if (!m_group)
return false;
return (m_group->selected() == this);
}
void QmlRadioButtonGroupAttached::setGroup(QmlRadioButtonGroup *group)
{
if (m_group == group)
return;
if (m_group) {
disconnect(m_group, &QmlRadioButtonGroup::selectedChanged,
this, &QmlRadioButtonGroupAttached::onGroupSelectionChanged);
}
m_group = group;
if (m_group) {
connect(m_group, &QmlRadioButtonGroup::selectedChanged,
this, &QmlRadioButtonGroupAttached::onGroupSelectionChanged);
}
emit groupChanged(m_group);
emit isSelectedChanged(isSelected());
}
void QmlRadioButtonGroupAttached::onGroupSelectionChanged()
{
emit isSelectedChanged(isSelected());
}

View file

@ -0,0 +1,78 @@
// QmlRadioButtonHelper.hxx - helper for QtQuick radio button impl
//
// Written by James Turner, started April 2018.
//
// Copyright (C) 2015 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef QMLRADIOBUTTONHELPER_HXX
#define QMLRADIOBUTTONHELPER_HXX
#include <QObject>
#include <QQmlEngine> // for QML_DECLARE_TYPEINFO
class QmlRadioButtonGroup;
class QmlRadioButtonGroupAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QmlRadioButtonGroup* group READ group WRITE setGroup NOTIFY groupChanged)
Q_PROPERTY(bool isSelected READ isSelected NOTIFY isSelectedChanged)
public:
QmlRadioButtonGroupAttached(QObject* pr = nullptr);
QmlRadioButtonGroup* group() const;
bool isSelected() const;
public slots:
void setGroup(QmlRadioButtonGroup* group);
signals:
void groupChanged(QmlRadioButtonGroup* group);
void isSelectedChanged(bool isSelected);
private:
void onGroupSelectionChanged();
QmlRadioButtonGroup* m_group = nullptr;
};
class QmlRadioButtonGroup : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* selected READ selected WRITE setSelected NOTIFY selectedChanged)
public:
explicit QmlRadioButtonGroup(QObject *parent = nullptr);
static QmlRadioButtonGroupAttached *qmlAttachedProperties(QObject *);
QObject* selected() const;
signals:
void selectedChanged(QObject* selected);
public slots:
void setSelected(QObject* selected);
private:
QObject* m_selected = nullptr;
};
QML_DECLARE_TYPEINFO(QmlRadioButtonGroup, QML_HAS_ATTACHED_PROPERTIES)
#endif // QMLRADIOBUTTONHELPER_HXX

View file

@ -12,7 +12,7 @@ Item {
footer.height
implicitWidth: ListView.view.width
readonly property bool __isSelected: (_launcher.selectedAircraft == model.uri)
readonly property bool __isSelected: (_launcher.selectedAircraft === model.uri)
property bool __showAlternateText: false
@ -100,13 +100,13 @@ Item {
wrapMode: Text.WordWrap
elide: Text.ElideRight
height: implicitHeight
visible: (model.description != "") || root.__showAlternateText
visible: (model.description !== "") || root.__showAlternateText
}
AircraftDownloadPanel
{
id: downloadPanel
visible: (model.package != undefined)
visible: (model.package !== undefined)
packageSize: model.packageSizeBytes
installStatus: model.packageStatus
downloadedBytes: model.downloadedBytes

View file

@ -93,7 +93,7 @@ FocusScope {
}
Keys.onPressed: {
if ((event.key == Qt.Key_Colon) || (event.key == Qt.Key_Slash)) {
if ((event.key === Qt.Key_Colon) || (event.key === Qt.Key_Slash)) {
nextToFocus.focus = true;
event.accepted = true;
}
@ -136,9 +136,10 @@ FocusScope {
}
onWheel: {
if (wheel.angleDelta > 0) {
var delta = wheel.angleDelta.y
if (delta > 0) {
root.incrementValue()
} else if (wheel.angleDelta < 0) {
} else if (delta < 0) {
root.decrementValue()
}
}

View file

@ -0,0 +1,214 @@
import QtQuick 2.4
import "."
FocusScope {
id: root
property string label
property bool enabled: true
property int value: 0
property alias min: validator.bottom
property int max: validator.top
property alias decimals: validator.decimals
property bool wrap: false
property alias suffix: suffix.text
property alias prefix: prefix.text
property alias maxDigits: edit.maximumLength
property int step: 1
implicitHeight: editFrame.height
// we have a margin between the frame and the label, and on each
implicitWidth: label.width + editFrame.width + Style.margin
signal commit(var newValue);
function incrementValue()
{
if (edit.activeFocus) {
value = Math.min(parseFloat(edit.text) + root.step, root.max)
edit.text = value
} else {
commit(Math.min(value + root.step, root.max))
}
}
function decrementValue()
{
if (edit.activeFocus) {
value = Math.max(parseFloat(edit.text) - root.step, root.min)
edit.text = value
} else {
commit(Math.max(value - root.step, root.min))
}
}
TextMetrics {
id: metrics
text: root.max // use maximum value
}
Text {
id: label
text: root.label
anchors.verticalCenter: editFrame.verticalCenter
color: editFrame.activeFocus ? Style.themeColor :
(root.enabled ? "black" : Style.inactiveThemeColor)
}
MouseArea {
height: root.height
width: root.width
enabled: root.enabled
// use wheel events to adjust up/dowm
onClicked: {
edit.forceActiveFocus();
}
onWheel: {
var delta = wheel.angleDelta.y
if (delta > 0) {
root.incrementValue()
} else if (delta < 0) {
root.decrementValue()
}
}
}
Binding {
when: !edit.activeFocus
target: edit
property: "text"
value: root.value
}
Rectangle {
id: editFrame
clip: true
anchors.left: label.right
anchors.margins: Style.margin
height: edit.implicitHeight + Style.margin
width: edit.width + prefix.width + suffix.width + upDownArea.width + Style.margin * 2
radius: Style.roundRadius
border.color: edit.activeFocus ? Style.frameColor : Style.minorFrameColor
border.width: 1
Text {
id: prefix
visible: root.prefix !== ""
color: Style.baseTextColor
anchors.baseline: edit.baseline
anchors.left: parent.left
anchors.margins: Style.margin
}
TextInput {
id: edit
enabled: root.enabled
anchors.verticalCenter: parent.verticalCenter
anchors.left: prefix.right
selectByMouse: true
width: metrics.width
horizontalAlignment: Text.AlignRight
focus: true
color: enabled && activeFocus ? Style.themeColor : Style.baseTextColor
validator: DoubleValidator {
id: validator
}
Keys.onUpPressed: {
root.incrementValue();
}
Keys.onDownPressed: {
root.decrementValue();
}
onActiveFocusChanged: {
if (activeFocus) {
selectAll();
} else {
commit(parseFloat(text))
}
}
}
Text {
id: suffix
visible: root.suffix !== ""
color: Style.baseTextColor
anchors.baseline: edit.baseline
anchors.right: upDownArea.left
}
Item {
id: upDownArea
// color: "white"
anchors.right: parent.right
anchors.rightMargin: Style.margin
anchors.verticalCenter: editFrame.verticalCenter
height: upDownIcon.implicitHeight
visible: edit.activeFocus
width: upDownIcon.implicitWidth
Image {
id: upDownIcon
// show up/down arrows
source: "qrc:///up-down-arrow"
}
MouseArea {
width: parent.width
height: parent.height / 2
onPressed: {
root.incrementValue();
}
Rectangle {
anchors.fill: parent
opacity: 0.5
color: Style.themeColor
visible: parent.pressed
}
Timer {
id: upRepeat
interval: 250
running: parent.pressed
repeat: true
onTriggered: root.incrementValue()
}
}
MouseArea {
width: parent.width
height: parent.height / 2
anchors.bottom: parent.bottom
onPressed: {
root.decrementValue();
}
Rectangle {
anchors.fill: parent
opacity: 0.5
color: Style.themeColor
visible: parent.pressed
}
Timer {
id: downRepeat
interval: 250
running: parent.pressed
repeat: true
onTriggered: root.decrementValue()
}
}
}
} // of frame rectangle
}

View file

@ -0,0 +1,212 @@
import QtQuick 2.4
import "."
FocusScope {
id: root
property string label
property bool enabled: true
property int value: 0
property alias min: validator.bottom
property int max: validator.top
property bool wrap: false
property alias suffix: suffix.text
property alias prefix: prefix.text
property alias maxDigits: edit.maximumLength
property int step: 1
implicitHeight: editFrame.height
// we have a margin between the frame and the label, and on each
implicitWidth: label.width + editFrame.width + Style.margin
signal commit(var newValue);
function incrementValue()
{
if (edit.activeFocus) {
value = Math.min(parseInt(edit.text) + root.step, root.max)
edit.text = value
} else {
commit(Math.min(value + root.step, root.max))
}
}
function decrementValue()
{
if (edit.activeFocus) {
value = Math.max(parseInt(edit.text) - root.step, root.min)
edit.text = value
} else {
commit(Math.max(value - root.step, root.min))
}
}
TextMetrics {
id: metrics
text: root.max // use maximum value
}
Text {
id: label
text: root.label
anchors.verticalCenter: editFrame.verticalCenter
color: editFrame.activeFocus ? Style.themeColor :
(root.enabled ? "black" : Style.inactiveThemeColor)
}
MouseArea {
height: root.height
width: root.width
enabled: root.enabled
// use wheel events to adjust up/dowm
onClicked: {
edit.forceActiveFocus();
}
onWheel: {
var delta = wheel.angleDelta.y
if (delta > 0) {
root.incrementValue()
} else if (delta < 0) {
root.decrementValue()
}
}
}
Binding {
when: !edit.activeFocus
target: edit
property: "text"
value: root.value
}
Rectangle {
id: editFrame
clip: true
anchors.left: label.right
anchors.margins: Style.margin
height: edit.implicitHeight + Style.margin
width: edit.width + prefix.width + suffix.width + upDownArea.width + Style.margin * 2
radius: Style.roundRadius
border.color: edit.activeFocus ? Style.frameColor : Style.minorFrameColor
border.width: 1
Text {
id: prefix
visible: root.prefix !== ""
color: Style.baseTextColor
anchors.baseline: edit.baseline
anchors.left: parent.left
anchors.margins: Style.margin
}
TextInput {
id: edit
enabled: root.enabled
anchors.verticalCenter: parent.verticalCenter
anchors.left: prefix.right
selectByMouse: true
width: metrics.width
horizontalAlignment: Text.AlignRight
focus: true
color: enabled && activeFocus ? Style.themeColor : Style.baseTextColor
validator: IntValidator {
id: validator
}
Keys.onUpPressed: {
root.incrementValue();
}
Keys.onDownPressed: {
root.decrementValue();
}
onActiveFocusChanged: {
if (activeFocus) {
selectAll();
} else {
commit(parseInt(text))
}
}
}
Text {
id: suffix
visible: root.suffix !== ""
color: Style.baseTextColor
anchors.baseline: edit.baseline
anchors.right: upDownArea.left
}
Item {
id: upDownArea
// color: "white"
anchors.right: parent.right
anchors.rightMargin: Style.margin
anchors.verticalCenter: editFrame.verticalCenter
height: upDownIcon.implicitHeight
visible: edit.activeFocus
width: upDownIcon.implicitWidth
Image {
id: upDownIcon
// show up/down arrows
source: "qrc:///up-down-arrow"
}
MouseArea {
width: parent.width
height: parent.height / 2
onPressed: {
root.incrementValue();
}
Rectangle {
anchors.fill: parent
opacity: 0.5
color: Style.themeColor
visible: parent.pressed
}
Timer {
id: upRepeat
interval: 250
running: parent.pressed
repeat: true
onTriggered: root.incrementValue()
}
}
MouseArea {
width: parent.width
height: parent.height / 2
anchors.bottom: parent.bottom
onPressed: {
root.decrementValue();
}
Rectangle {
anchors.fill: parent
opacity: 0.5
color: Style.themeColor
visible: parent.pressed
}
Timer {
id: downRepeat
interval: 250
running: parent.pressed
repeat: true
onTriggered: root.decrementValue()
}
}
}
} // of frame rectangle
}

View file

@ -7,6 +7,7 @@ FocusScope {
property string placeholder: ""
property alias validator: edit.validator
property alias text: edit.text
property bool enabled: true
property alias suggestedWidthString: metrics.text
readonly property int suggestedWidth: useFullWidth ? root.width
@ -16,6 +17,7 @@ FocusScope {
property bool useFullWidth: false
implicitHeight: editFrame.height
implicitWidth: suggestedWidth + label.implicitWidth + (Style.margin * 3)
TextMetrics {
id: metrics
@ -35,10 +37,8 @@ FocusScope {
anchors.left: label.right
anchors.margins: Style.margin
height: edit.implicitHeight + Style.margin
width: Math.min(root.width - (label.width + Style.margin * 2), Math.max(suggestedWidth, edit.implicitWidth));
width: Math.min(root.width - (label.width + Style.margin), Math.max(suggestedWidth, edit.implicitWidth) + Style.margin * 2);
radius: Style.roundRadius
border.color: edit.activeFocus ? Style.frameColor : Style.minorFrameColor
@ -54,6 +54,7 @@ FocusScope {
anchors.margins: Style.margin
selectByMouse: true
focus: true
color: enabled && activeFocus ? Style.themeColor : Style.baseTextColor
Text {
id: placeholder

View file

@ -14,7 +14,11 @@ Item {
property int currentIndex: 0
property bool __dummy: false
property alias header: choicesHeader.sourceComponent
property string headerText: ""
implicitHeight: Math.max(label.implicitHeight, currentChoiceFrame.height)
implicitWidth: label.implicitWidth + Style.margin + currentChoiceFrame.__naturalWidth
Item {
Repeater {
@ -44,8 +48,16 @@ Item {
__dummy = !__dummy // force update of currentText
}
function haveHeader()
{
return headerText !== "";
}
function currentText()
{
if ((currentIndex == -1) && haveHeader())
return headerText;
var foo = __dummy; // fake propery dependency to update this
var item = internalModel.itemAt(currentIndex);
if (!item) return "";
@ -65,17 +77,19 @@ Item {
Rectangle {
id: currentChoiceFrame
radius: Style.roundRadius
border.color: mouseArea.containsMouse ? Style.themeColor : Style.minorFrameColor
border.color: root.enabled ? (mouseArea.containsMouse ? Style.themeColor : Style.minorFrameColor)
: Style.inactiveThemeColor
border.width: 1
height: currentChoiceText.implicitHeight + Style.margin
clip: true
anchors.left: label.right
anchors.leftMargin: Style.margin
// width of current item, or available space after the label
width: Math.min(root.width - (label.width + Style.margin),
currentChoiceText.implicitWidth + (Style.margin * 2) + upDownIcon.width);
width: Math.min(root.width - (label.width + Style.margin), __naturalWidth);
readonly property int __naturalWidth: currentChoiceText.implicitWidth + (Style.margin * 3) + upDownIcon.width
anchors.verticalCenter: parent.verticalCenter
Text {
@ -102,7 +116,7 @@ Item {
MouseArea {
anchors.fill: parent
id: mouseArea
hoverEnabled: true
hoverEnabled: root.enabled
enabled: root.enabled
onClicked: {
var screenPos = _launcher.mapToGlobal(currentChoiceText, Qt.point(0, -currentChoiceText.height * currentIndex))
@ -138,13 +152,42 @@ Item {
anchors.fill: parent
}
// text repeater
// choice layout column
Column {
id: choicesColumn
spacing: Style.margin
x: Style.margin
y: Style.margin
// optional header component:
Loader {
id: choicesHeader
active: root.haveHeader()
// default component is just a plain text element, same as
// normal items
sourceComponent: Text {
text: root.headerText
height: implicitHeight + Style.margin
width: popupFrame.width
}
height: item ? item.height : 0
width: item ? item.width : 0
// essentially the same mouse area as normal items
MouseArea {
width: popupFrame.width // full width of the popup
height: parent.height
z: -1 // so header can do other mouse behaviours
onClicked: {
root.currentIndex = -1;
popupFrame.visible = false
}
}
} // of header loader
// main item repeater
Repeater {
id: choicesRepeater
model: root.model

View file

@ -0,0 +1,68 @@
import QtQuick 2.4
import FlightGear 1.0
import "."
Item {
id: root
property bool selected
property RadioButtonGroup group // nil by default
signal clicked()
implicitHeight: outerRing.height + Style.margin
implicitWidth: outerRing.width + Style.margin
Binding {
when: root.group != null
target: root
property: "selected"
value: (root.group.selected === root)
}
Rectangle {
id: innerRing
anchors.centerIn: parent
width: radius * 2
height: radius * 2
radius: Style.roundRadius
color: Style.themeColor
visible: selected || mouse.containsMouse
}
Rectangle {
id: outerRing
anchors.centerIn: parent
border.color: Style.themeColor
border.width: 2
color: "transparent"
radius: Style.roundRadius + 4
width: radius * 2
height: radius * 2
}
Rectangle {
id: pressRing
opacity: 0.3
width: outerRing.width * 2
height: outerRing.height * 2
visible: mouse.pressed
radius: width / 2
color: "#7f7f7f"
anchors.centerIn: parent
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (root.group) {
root.group.selected = root;
}
root.clicked()
}
}
}

View file

@ -5,14 +5,15 @@ FocusScope
{
id: root
width:frame.width
width: Style.strutSize * 3
height: frame.height
// property string text
property string placeholder: qsTr("Search")
property bool active: false
signal search(string term)
property bool autoSubmit: true
property alias autoSubmitTimeout: searchTimer.interval
onActiveChanged: {
@ -23,7 +24,7 @@ FocusScope
function clear()
{
buttonText.text = "Search"
buttonText.text = ""
root.focus = false
searchTimer.stop();
root.search("");
@ -34,12 +35,14 @@ FocusScope
id: frame
radius: Style.roundRadius
width: Style.strutSize * 3
width: root.width
height: Math.max(searchIcon.height, buttonText.height) + (Style.roundRadius)
border.width: 1
border.color: (mouse.containsMouse | active) ? Style.themeColor: Style.minorFrameColor
clip: true
TextInput {
id: buttonText
anchors.left: parent.left
@ -51,7 +54,9 @@ FocusScope
focus: true
onTextChanged: {
searchTimer.restart();
if (root.autoSubmit) {
searchTimer.restart();
}
}
onEditingFinished: {
@ -63,7 +68,14 @@ FocusScope
}
}
text: "Search"
text: ""
// placeholder text, hides itself whenever parent has non-empty text
Text {
anchors.fill: parent
visible: parent.text == ""
text: root.placeholder
}
}
Image {

View file

@ -121,7 +121,7 @@ Item {
label: qsTr("Show debugging console")
description: qsTr("Open a console window showing debug output from the application.")
advanced: true
hidden: _osName != "win"
hidden: _osName !== "win"
keywords: ["console", "terminal", "log", "debug"]
setting: "console"
}

View file

@ -7,7 +7,7 @@ Item {
property bool enabled: true
implicitWidth: track.width + label.width + 16
implicitHeight: label.height
implicitHeight: Math.max(label.height, thumb.height)
Rectangle {
id: track

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -86,6 +86,10 @@
<file>qml/PathListDelegate.qml</file>
<file>qml/AddCatalogPanel.qml</file>
<file>qml/LineEdit.qml</file>
<file alias="linear-spinner">qml/icons8-linear-spinner.gif</file>
<file>qml/RadioButton.qml</file>
<file>qml/IntegerSpinbox.qml</file>
<file>qml/DoubleSpinbox.qml</file>
</qresource>
<qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 802 B