ExpandablePopup.qml 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // Copyright (c) 2018 Ultimaker B.V.
  2. // Cura is released under the terms of the LGPLv3 or higher.
  3. import QtQuick 2.7
  4. import QtQuick.Controls 2.3
  5. import UM 1.2 as UM
  6. import Cura 1.0 as Cura
  7. import QtGraphicalEffects 1.0 // For the dropshadow
  8. // The expandable component has 2 major sub components:
  9. // * The headerItem; Always visible and should hold some info about what happens if the component is expanded
  10. // * The contentItem; The content that needs to be shown if the component is expanded.
  11. Item
  12. {
  13. id: base
  14. // Enumeration with the different possible alignments of the content with respect of the headerItem
  15. enum ContentAlignment
  16. {
  17. AlignLeft,
  18. AlignRight
  19. }
  20. // The headerItem holds the QML item that is always displayed.
  21. property alias headerItem: headerItemLoader.sourceComponent
  22. // The contentItem holds the QML item that is shown when the "open" button is pressed
  23. property alias contentItem: content.contentItem
  24. property color contentBackgroundColor: UM.Theme.getColor("action_button")
  25. property color headerBackgroundColor: UM.Theme.getColor("action_button")
  26. property color headerActiveColor: UM.Theme.getColor("secondary")
  27. property color headerHoverColor: UM.Theme.getColor("action_button_hovered")
  28. property alias mouseArea: headerMouseArea
  29. property alias enabled: headerMouseArea.enabled
  30. // Text to show when this component is disabled
  31. property alias disabledText: disabledLabel.text
  32. // Defines the alignment of the content with respect of the headerItem, by default to the right
  33. property int contentAlignment: ExpandablePopup.ContentAlignment.AlignRight
  34. // How much spacing is needed around the contentItem
  35. property alias contentPadding: content.padding
  36. // How much padding is needed around the header & button
  37. property alias headerPadding: background.padding
  38. // What icon should be displayed on the right.
  39. property alias iconSource: collapseButton.source
  40. property alias iconColor: collapseButton.color
  41. // The icon size (it's always drawn as a square)
  42. property alias iconSize: collapseButton.height
  43. // Is the "drawer" open?
  44. readonly property alias expanded: content.visible
  45. property alias expandedHighlightColor: expandedHighlight.color
  46. // What should the radius of the header be. This is also influenced by the headerCornerSide
  47. property alias headerRadius: background.radius
  48. // On what side should the header corners be shown? 1 is down, 2 is left, 3 is up and 4 is right.
  49. property alias headerCornerSide: background.cornerSide
  50. // Change the contentItem close behaviour
  51. property alias contentClosePolicy : content.closePolicy
  52. property alias headerShadowColor: shadow.color
  53. property alias enableHeaderShadow: shadow.visible
  54. property int shadowOffset: 2
  55. function toggleContent()
  56. {
  57. if (content.visible)
  58. {
  59. content.close()
  60. }
  61. else
  62. {
  63. content.open()
  64. }
  65. }
  66. // Add this binding since the background color is not updated otherwise
  67. Binding
  68. {
  69. target: background
  70. property: "color"
  71. value: base.enabled ? headerBackgroundColor : UM.Theme.getColor("disabled")
  72. }
  73. // The panel needs to close when it becomes disabled
  74. Connections
  75. {
  76. target: base
  77. onEnabledChanged:
  78. {
  79. if (!base.enabled && expanded)
  80. {
  81. toggleContent()
  82. }
  83. }
  84. }
  85. implicitHeight: 100 * screenScaleFactor
  86. implicitWidth: 400 * screenScaleFactor
  87. RoundedRectangle
  88. {
  89. id: background
  90. property real padding: UM.Theme.getSize("default_margin").width
  91. color: base.enabled ? headerBackgroundColor : UM.Theme.getColor("disabled")
  92. anchors.fill: parent
  93. Label
  94. {
  95. id: disabledLabel
  96. visible: !base.enabled
  97. leftPadding: background.padding
  98. text: ""
  99. font: UM.Theme.getFont("default")
  100. renderType: Text.NativeRendering
  101. verticalAlignment: Text.AlignVCenter
  102. color: UM.Theme.getColor("text")
  103. height: parent.height
  104. }
  105. Item
  106. {
  107. anchors.fill: parent
  108. visible: base.enabled
  109. MouseArea
  110. {
  111. id: headerMouseArea
  112. anchors.fill: parent
  113. onClicked: toggleContent()
  114. hoverEnabled: true
  115. onEntered: background.color = headerHoverColor
  116. onExited: background.color = base.enabled ? headerBackgroundColor : UM.Theme.getColor("disabled")
  117. }
  118. Loader
  119. {
  120. id: headerItemLoader
  121. anchors
  122. {
  123. left: parent.left
  124. right: collapseButton.visible ? collapseButton.left : parent.right
  125. top: parent.top
  126. bottom: parent.bottom
  127. margins: background.padding
  128. }
  129. }
  130. // A highlight that is shown when the content is expanded
  131. Rectangle
  132. {
  133. id: expandedHighlight
  134. width: parent.width
  135. height: UM.Theme.getSize("thick_lining").height
  136. color: UM.Theme.getColor("primary")
  137. visible: expanded
  138. anchors.bottom: parent.bottom
  139. }
  140. UM.RecolorImage
  141. {
  142. id: collapseButton
  143. anchors
  144. {
  145. right: parent.right
  146. verticalCenter: parent.verticalCenter
  147. margins: background.padding
  148. }
  149. source: expanded ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left")
  150. visible: source != ""
  151. width: UM.Theme.getSize("standard_arrow").width
  152. height: UM.Theme.getSize("standard_arrow").height
  153. color: UM.Theme.getColor("small_button_text")
  154. }
  155. }
  156. }
  157. DropShadow
  158. {
  159. id: shadow
  160. // Don't blur the shadow
  161. radius: 0
  162. anchors.fill: background
  163. source: background
  164. verticalOffset: base.shadowOffset
  165. visible: true
  166. color: UM.Theme.getColor("action_button_shadow")
  167. // Should always be drawn behind the background.
  168. z: background.z - 1
  169. }
  170. Popup
  171. {
  172. id: content
  173. // Ensure that the content is located directly below the headerItem
  174. y: background.height + base.shadowOffset
  175. // Make the content aligned with the rest, using the property contentAlignment to decide whether is right or left.
  176. // In case of right alignment, the 3x padding is due to left, right and padding between the button & text.
  177. x: contentAlignment == ExpandablePopup.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0
  178. padding: UM.Theme.getSize("default_margin").width
  179. closePolicy: Popup.CloseOnPressOutsideParent
  180. background: Cura.RoundedRectangle
  181. {
  182. cornerSide: Cura.RoundedRectangle.Direction.Down
  183. color: contentBackgroundColor
  184. border.width: UM.Theme.getSize("default_lining").width
  185. border.color: UM.Theme.getColor("lining")
  186. radius: UM.Theme.getSize("default_radius").width
  187. height: contentItem.implicitHeight || content.height
  188. }
  189. contentItem: Item {}
  190. onContentItemChanged:
  191. {
  192. // Since we want the size of the content to be set by the size of the content,
  193. // we need to do it like this.
  194. content.width = contentItem.width + 2 * content.padding
  195. content.height = contentItem.height + 2 * content.padding
  196. }
  197. }
  198. // DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
  199. // Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
  200. Connections
  201. {
  202. // Since it could be that the content is dynamically populated, we should also take these changes into account.
  203. target: content.contentItem
  204. onWidthChanged: content.width = content.contentItem.width + 2 * content.padding
  205. onHeightChanged: content.height = content.contentItem.height + 2 * content.padding
  206. }
  207. }