ExpandableComponent.qml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. // Prefix used for the dragged position preferences. Preferences not used if empty. Don't translate!
  56. property string dragPreferencesNamePrefix: ""
  57. function toggleContent()
  58. {
  59. contentContainer.visible = !expanded
  60. }
  61. function updateDragPosition()
  62. {
  63. contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
  64. }
  65. // Add this binding since the background color is not updated otherwise
  66. Binding
  67. {
  68. target: background
  69. property: "color"
  70. value:
  71. {
  72. return base.enabled ? (expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  73. }
  74. }
  75. // The panel needs to close when it becomes disabled
  76. Connections
  77. {
  78. target: base
  79. onEnabledChanged:
  80. {
  81. if (!base.enabled && expanded)
  82. {
  83. toggleContent();
  84. updateDragPosition();
  85. }
  86. }
  87. }
  88. implicitHeight: 100 * screenScaleFactor
  89. implicitWidth: 400 * screenScaleFactor
  90. RoundedRectangle
  91. {
  92. id: background
  93. property real padding: UM.Theme.getSize("default_margin").width
  94. color: base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  95. anchors.fill: parent
  96. Label
  97. {
  98. id: disabledLabel
  99. visible: !base.enabled
  100. anchors.fill: parent
  101. leftPadding: background.padding
  102. rightPadding: background.padding
  103. text: ""
  104. font: UM.Theme.getFont("default")
  105. renderType: Text.NativeRendering
  106. verticalAlignment: Text.AlignVCenter
  107. color: UM.Theme.getColor("text")
  108. wrapMode: Text.WordWrap
  109. }
  110. Item
  111. {
  112. anchors.fill: parent
  113. visible: base.enabled
  114. Loader
  115. {
  116. id: headerItemLoader
  117. anchors
  118. {
  119. left: parent.left
  120. right: collapseButton.visible ? collapseButton.left : parent.right
  121. top: parent.top
  122. bottom: parent.bottom
  123. margins: background.padding
  124. }
  125. }
  126. UM.RecolorImage
  127. {
  128. id: collapseButton
  129. anchors
  130. {
  131. right: parent.right
  132. verticalCenter: parent.verticalCenter
  133. margins: background.padding
  134. }
  135. source: UM.Theme.getIcon("pencil")
  136. visible: source != ""
  137. width: UM.Theme.getSize("standard_arrow").width
  138. height: UM.Theme.getSize("standard_arrow").height
  139. color: UM.Theme.getColor("small_button_text")
  140. }
  141. }
  142. MouseArea
  143. {
  144. id: mouseArea
  145. anchors.fill: parent
  146. onClicked: toggleContent()
  147. hoverEnabled: true
  148. onEntered: background.color = headerHoverColor
  149. onExited: background.color = base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  150. }
  151. }
  152. DropShadow
  153. {
  154. id: shadow
  155. // Don't blur the shadow
  156. radius: 0
  157. anchors.fill: background
  158. source: background
  159. verticalOffset: base.shadowOffset
  160. visible: true
  161. color: UM.Theme.getColor("action_button_shadow")
  162. // Should always be drawn behind the background.
  163. z: background.z - 1
  164. }
  165. Cura.RoundedRectangle
  166. {
  167. id: contentContainer
  168. property string dragPreferencesNameX: "_xpos"
  169. property string dragPreferencesNameY: "_ypos"
  170. visible: false
  171. width: childrenRect.width
  172. height: childrenRect.height
  173. // Ensure that the content is located directly below the headerItem
  174. y: dragPreferencesNamePrefix === "" ? (background.height + base.shadowOffset + base.contentSpacingY) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameY)
  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: dragPreferencesNamePrefix === "" ? (contentAlignment == ExpandableComponent.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameX)
  178. cornerSide: Cura.RoundedRectangle.Direction.All
  179. color: contentBackgroundColor
  180. border.width: UM.Theme.getSize("default_lining").width
  181. border.color: UM.Theme.getColor("lining")
  182. radius: UM.Theme.getSize("default_radius").width
  183. function trySetPosition(posNewX, posNewY)
  184. {
  185. var margin = UM.Theme.getSize("narrow_margin");
  186. var minPt = base.mapFromItem(null, margin.width, margin.height);
  187. var maxPt = base.mapFromItem(null,
  188. CuraApplication.appWidth() - (contentContainer.width + margin.width),
  189. CuraApplication.appHeight() - (contentContainer.height + margin.height));
  190. var initialY = background.height + base.shadowOffset + margin.height;
  191. contentContainer.x = Math.max(minPt.x, Math.min(maxPt.x, posNewX));
  192. contentContainer.y = Math.max(initialY, Math.min(maxPt.y, posNewY));
  193. if (dragPreferencesNamePrefix !== "")
  194. {
  195. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameX, contentContainer.x);
  196. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameY, contentContainer.y);
  197. }
  198. }
  199. ExpandableComponentHeader
  200. {
  201. id: contentHeader
  202. headerTitle: ""
  203. anchors
  204. {
  205. top: parent.top
  206. right: parent.right
  207. left: parent.left
  208. }
  209. MouseArea
  210. {
  211. id: dragRegion
  212. anchors
  213. {
  214. top: parent.top
  215. bottom: parent.bottom
  216. left: parent.left
  217. right: contentHeader.xPosCloseButton
  218. }
  219. property var clickPos: Qt.point(0, 0)
  220. onPressed:
  221. {
  222. clickPos = Qt.point(mouse.x, mouse.y);
  223. }
  224. onPositionChanged:
  225. {
  226. var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y);
  227. if (delta.x !== 0 || delta.y !== 0)
  228. {
  229. contentContainer.trySetPosition(contentContainer.x + delta.x, contentContainer.y + delta.y);
  230. }
  231. }
  232. Connections
  233. {
  234. target: UM.Preferences
  235. onPreferenceChanged:
  236. {
  237. if
  238. (
  239. preference !== "general/window_height" &&
  240. preference !== "general/window_width" &&
  241. preference !== "general/window_state"
  242. )
  243. {
  244. return;
  245. }
  246. contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
  247. }
  248. }
  249. }
  250. }
  251. Control
  252. {
  253. id: content
  254. anchors.top: contentHeader.bottom
  255. padding: UM.Theme.getSize("default_margin").width
  256. contentItem: Item {}
  257. onContentItemChanged:
  258. {
  259. // Since we want the size of the content to be set by the size of the content,
  260. // we need to do it like this.
  261. content.width = contentItem.width + 2 * content.padding
  262. content.height = contentItem.height + 2 * content.padding
  263. }
  264. }
  265. }
  266. Component.onCompleted:
  267. {
  268. updateDragPosition();
  269. }
  270. // DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
  271. // Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
  272. Connections
  273. {
  274. // Since it could be that the content is dynamically populated, we should also take these changes into account.
  275. target: content.contentItem
  276. onWidthChanged: content.width = content.contentItem.width + 2 * content.padding
  277. onHeightChanged:
  278. {
  279. content.height = content.contentItem.height + 2 * content.padding
  280. contentContainer.height = contentHeader.height + content.height
  281. }
  282. }
  283. }