Cura/resources/qml/ExpandableComponent.qml
Ghostkeeper 3d6b58d5c0
Always show down arrow regardless of expanded state
This is something our UX designer wants to change for all expandable header bar menus. The down arrow indicates that something will pop-up downwards once clicked. It is unnecessary to feedback the state of the expansion in the icon.

Contributes to CURA-8008.
2021-07-12 11:19:04 +02:00

336 lines
12 KiB
QML

// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.3
import UM 1.2 as UM
import Cura 1.0 as Cura
// The expandable component has 2 major sub components:
// * The headerItem; Always visible and should hold some info about what happens if the component is expanded
// * The contentItem; The content that needs to be shown if the component is expanded.
Item
{
id: base
// Enumeration with the different possible alignments of the content with respect of the headerItem
enum ContentAlignment
{
AlignLeft,
AlignRight
}
// The headerItem holds the QML item that is always displayed.
property alias headerItem: headerItemLoader.sourceComponent
// The contentItem holds the QML item that is shown when the "open" button is pressed
property alias contentItem: content.contentItem
property color contentBackgroundColor: UM.Theme.getColor("action_button")
property color headerBackgroundColor: UM.Theme.getColor("action_button")
property color headerActiveColor: UM.Theme.getColor("secondary")
property color headerHoverColor: UM.Theme.getColor("action_button_hovered")
property alias enabled: mouseArea.enabled
// Text to show when this component is disabled
property alias disabledText: disabledLabel.text
// Defines the alignment of the content with respect of the headerItem, by default to the right
// Note that this only has an effect if the panel is draggable
property int contentAlignment: ExpandableComponent.ContentAlignment.AlignRight
// How much spacing is needed around the contentItem
property alias contentPadding: content.padding
// Adds a title to the content item
property alias contentHeaderTitle: contentHeader.headerTitle
// How much spacing is needed for the contentItem by Y coordinate
property var contentSpacingY: UM.Theme.getSize("narrow_margin").width
// How much padding is needed around the header & button
property alias headerPadding: background.padding
property alias headerBackgroundBorder: background.border
// Whether or not to show the background border
property bool enableHeaderBackgroundBorder: true
// What icon should be displayed on the right.
property alias iconSource: collapseButton.source
property alias iconColor: collapseButton.color
// The icon size (it's always drawn as a square)
property alias iconSize: collapseButton.height
// Is the "drawer" open?
property alias expanded: contentContainer.visible
// What should the radius of the header be. This is also influenced by the headerCornerSide
property alias headerRadius: background.radius
// On what side should the header corners be shown? 1 is down, 2 is left, 3 is up and 4 is right.
property alias headerCornerSide: background.cornerSide
property int popupOffset: 2
// Prefix used for the dragged position preferences. Preferences not used if empty. Don't translate!
property string dragPreferencesNamePrefix: ""
function toggleContent()
{
contentContainer.visible = !expanded
}
function updateDragPosition()
{
contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
}
onEnabledChanged:
{
if (!base.enabled && expanded)
{
toggleContent();
updateDragPosition();
}
}
// Add this binding since the background color is not updated otherwise
Binding
{
target: background
property: "color"
value:
{
return base.enabled ? (expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
}
}
implicitHeight: 100 * screenScaleFactor
implicitWidth: 400 * screenScaleFactor
RoundedRectangle
{
id: background
property real padding: UM.Theme.getSize("default_margin").width
border.width: base.enableHeaderBackgroundBorder ? UM.Theme.getSize("default_lining").width : 0
border.color: UM.Theme.getColor("lining")
color: base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
anchors.fill: parent
Label
{
id: disabledLabel
visible: !base.enabled
anchors.fill: parent
leftPadding: background.padding
rightPadding: background.padding
text: ""
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("text")
wrapMode: Text.WordWrap
}
Item
{
anchors.fill: parent
visible: base.enabled
Loader
{
id: headerItemLoader
anchors
{
left: parent.left
right: collapseButton.visible ? collapseButton.left : parent.right
top: parent.top
bottom: parent.bottom
margins: background.padding
}
}
UM.RecolorImage
{
id: collapseButton
anchors
{
right: parent.right
verticalCenter: parent.verticalCenter
margins: background.padding
}
source: UM.Theme.getIcon("ChevronSingleDown")
visible: source != ""
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
color: UM.Theme.getColor("small_button_text")
}
}
MouseArea
{
id: mouseArea
anchors.fill: parent
onClicked: toggleContent()
hoverEnabled: true
onEntered: background.color = headerHoverColor
onExited: background.color = base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
}
}
Cura.RoundedRectangle
{
id: contentContainer
property string dragPreferencesNameX: "_xpos"
property string dragPreferencesNameY: "_ypos"
visible: false
width: childrenRect.width
height: childrenRect.height
// Ensure that the content is located directly below the headerItem
y: dragPreferencesNamePrefix === "" ? (background.height + base.popupOffset + base.contentSpacingY) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameY)
// Make the content aligned with the rest, using the property contentAlignment to decide whether is right or left.
// In case of right alignment, the 3x padding is due to left, right and padding between the button & text.
x: dragPreferencesNamePrefix === "" ? (contentAlignment == ExpandableComponent.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameX)
cornerSide: Cura.RoundedRectangle.Direction.All
color: contentBackgroundColor
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
radius: UM.Theme.getSize("default_radius").width
function trySetPosition(posNewX, posNewY)
{
var margin = UM.Theme.getSize("narrow_margin");
var minPt = base.mapFromItem(null, margin.width, margin.height);
var maxPt = base.mapFromItem(null,
CuraApplication.appWidth() - (contentContainer.width + margin.width),
CuraApplication.appHeight() - (contentContainer.height + margin.height));
var initialY = background.height + base.popupOffset + margin.height;
contentContainer.x = Math.max(minPt.x, Math.min(maxPt.x, posNewX));
contentContainer.y = Math.max(initialY, Math.min(maxPt.y, posNewY));
if (dragPreferencesNamePrefix !== "")
{
UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameX, contentContainer.x);
UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameY, contentContainer.y);
}
}
ExpandableComponentHeader
{
id: contentHeader
headerTitle: ""
anchors
{
top: parent.top
right: parent.right
left: parent.left
}
MouseArea
{
id: dragRegion
cursorShape: Qt.SizeAllCursor
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.left
right: contentHeader.xPosCloseButton
}
property var clickPos: Qt.point(0, 0)
property bool dragging: false
onPressed:
{
clickPos = Qt.point(mouse.x, mouse.y);
dragging = true
}
onPositionChanged:
{
if(dragging)
{
var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y);
if (delta.x !== 0 || delta.y !== 0)
{
contentContainer.trySetPosition(contentContainer.x + delta.x, contentContainer.y + delta.y);
}
}
}
onReleased:
{
dragging = false
}
onDoubleClicked:
{
dragging = false
contentContainer.trySetPosition(0, 0);
}
Connections
{
target: UM.Preferences
function onPreferenceChanged(preference)
{
if
(
preference !== "general/window_height" &&
preference !== "general/window_width" &&
preference !== "general/window_state"
)
{
return;
}
contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
}
}
}
}
Control
{
id: content
anchors.top: contentHeader.bottom
padding: UM.Theme.getSize("default_margin").width
contentItem: Item {}
onContentItemChanged:
{
// Since we want the size of the content to be set by the size of the content,
// we need to do it like this.
content.width = contentItem.width + 2 * content.padding
content.height = contentItem.height + 2 * content.padding
}
}
}
// DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
// Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
Connections
{
// Since it could be that the content is dynamically populated, we should also take these changes into account.
target: content.contentItem
function onWidthChanged() { content.width = content.contentItem.width + 2 * content.padding }
function onHeightChanged()
{
content.height = content.contentItem.height + 2 * content.padding
contentContainer.height = contentHeader.height + content.height
}
}
}