ExpandableComponent.qml 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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 enabled: mouseArea.enabled
  29. // Text to show when this component is disabled
  30. property alias disabledText: disabledLabel.text
  31. // Defines the alignment of the content with respect of the headerItem, by default to the right
  32. property int contentAlignment: ExpandableComponent.ContentAlignment.AlignRight
  33. // How much spacing is needed around the contentItem
  34. property alias contentPadding: content.padding
  35. // Adds a title to the content item
  36. property alias contentHeaderTitle: contentHeader.headerTitle
  37. // How much spacing is needed for the contentItem by Y coordinate
  38. property var contentSpacingY: UM.Theme.getSize("narrow_margin").width
  39. // How much padding is needed around the header & button
  40. property alias headerPadding: background.padding
  41. // What icon should be displayed on the right.
  42. property alias iconSource: collapseButton.source
  43. property alias iconColor: collapseButton.color
  44. // The icon size (it's always drawn as a square)
  45. property alias iconSize: collapseButton.height
  46. // Is the "drawer" open?
  47. property alias expanded: contentContainer.visible
  48. // What should the radius of the header be. This is also influenced by the headerCornerSide
  49. property alias headerRadius: background.radius
  50. // On what side should the header corners be shown? 1 is down, 2 is left, 3 is up and 4 is right.
  51. property alias headerCornerSide: background.cornerSide
  52. property alias headerShadowColor: shadow.color
  53. property alias enableHeaderShadow: shadow.visible
  54. property int shadowOffset: 2
  55. function toggleContent()
  56. {
  57. contentContainer.visible = !expanded
  58. }
  59. // Add this binding since the background color is not updated otherwise
  60. Binding
  61. {
  62. target: background
  63. property: "color"
  64. value:
  65. {
  66. return base.enabled ? (expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  67. }
  68. }
  69. // The panel needs to close when it becomes disabled
  70. Connections
  71. {
  72. target: base
  73. onEnabledChanged:
  74. {
  75. if (!base.enabled && expanded)
  76. {
  77. toggleContent()
  78. }
  79. }
  80. }
  81. implicitHeight: 100 * screenScaleFactor
  82. implicitWidth: 400 * screenScaleFactor
  83. RoundedRectangle
  84. {
  85. id: background
  86. property real padding: UM.Theme.getSize("default_margin").width
  87. color: base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  88. anchors.fill: parent
  89. Label
  90. {
  91. id: disabledLabel
  92. visible: !base.enabled
  93. anchors.fill: parent
  94. leftPadding: background.padding
  95. rightPadding: background.padding
  96. text: ""
  97. font: UM.Theme.getFont("default")
  98. renderType: Text.NativeRendering
  99. verticalAlignment: Text.AlignVCenter
  100. color: UM.Theme.getColor("text")
  101. wrapMode: Text.WordWrap
  102. }
  103. Item
  104. {
  105. anchors.fill: parent
  106. visible: base.enabled
  107. Loader
  108. {
  109. id: headerItemLoader
  110. anchors
  111. {
  112. left: parent.left
  113. right: collapseButton.visible ? collapseButton.left : parent.right
  114. top: parent.top
  115. bottom: parent.bottom
  116. margins: background.padding
  117. }
  118. }
  119. UM.RecolorImage
  120. {
  121. id: collapseButton
  122. anchors
  123. {
  124. right: parent.right
  125. verticalCenter: parent.verticalCenter
  126. margins: background.padding
  127. }
  128. source: UM.Theme.getIcon("pencil")
  129. visible: source != ""
  130. width: UM.Theme.getSize("standard_arrow").width
  131. height: UM.Theme.getSize("standard_arrow").height
  132. color: UM.Theme.getColor("small_button_text")
  133. }
  134. }
  135. MouseArea
  136. {
  137. id: mouseArea
  138. anchors.fill: parent
  139. onClicked: toggleContent()
  140. hoverEnabled: true
  141. onEntered: background.color = headerHoverColor
  142. onExited: background.color = base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  143. }
  144. }
  145. DropShadow
  146. {
  147. id: shadow
  148. // Don't blur the shadow
  149. radius: 0
  150. anchors.fill: background
  151. source: background
  152. verticalOffset: base.shadowOffset
  153. visible: true
  154. color: UM.Theme.getColor("action_button_shadow")
  155. // Should always be drawn behind the background.
  156. z: background.z - 1
  157. }
  158. Cura.RoundedRectangle
  159. {
  160. id: contentContainer
  161. visible: false
  162. width: childrenRect.width
  163. height: childrenRect.height
  164. // Ensure that the content is located directly below the headerItem
  165. y: background.height + base.shadowOffset + base.contentSpacingY
  166. // Make the content aligned with the rest, using the property contentAlignment to decide whether is right or left.
  167. // In case of right alignment, the 3x padding is due to left, right and padding between the button & text.
  168. x: contentAlignment == ExpandableComponent.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0
  169. cornerSide: Cura.RoundedRectangle.Direction.All
  170. color: contentBackgroundColor
  171. border.width: UM.Theme.getSize("default_lining").width
  172. border.color: UM.Theme.getColor("lining")
  173. radius: UM.Theme.getSize("default_radius").width
  174. ExpandableComponentHeader
  175. {
  176. id: contentHeader
  177. headerTitle: ""
  178. anchors
  179. {
  180. top: parent.top
  181. right: parent.right
  182. left: parent.left
  183. }
  184. }
  185. Control
  186. {
  187. id: content
  188. anchors.top: contentHeader.bottom
  189. padding: UM.Theme.getSize("default_margin").width
  190. contentItem: Item {}
  191. onContentItemChanged:
  192. {
  193. // Since we want the size of the content to be set by the size of the content,
  194. // we need to do it like this.
  195. content.width = contentItem.width + 2 * content.padding
  196. content.height = contentItem.height + 2 * content.padding
  197. }
  198. }
  199. }
  200. // DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
  201. // Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
  202. Connections
  203. {
  204. // Since it could be that the content is dynamically populated, we should also take these changes into account.
  205. target: content.contentItem
  206. onWidthChanged: content.width = content.contentItem.width + 2 * content.padding
  207. onHeightChanged:
  208. {
  209. content.height = content.contentItem.height + 2 * content.padding
  210. contentContainer.height = contentHeader.height + content.height
  211. }
  212. }
  213. }