ExpandableComponent.qml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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.5 as UM
  6. import Cura 1.0 as Cura
  7. // The expandable component has 2 major sub components:
  8. // * The headerItem; Always visible and should hold some info about what happens if the component is expanded
  9. // * The contentItem; The content that needs to be shown if the component is expanded.
  10. Item
  11. {
  12. id: base
  13. // Enumeration with the different possible alignments of the content with respect of the headerItem
  14. enum ContentAlignment
  15. {
  16. AlignLeft,
  17. AlignRight
  18. }
  19. // The headerItem holds the QML item that is always displayed.
  20. property alias headerItem: headerItemLoader.sourceComponent
  21. // The contentItem holds the QML item that is shown when the "open" button is pressed
  22. property alias contentItem: content.contentItem
  23. property color contentBackgroundColor: UM.Theme.getColor("action_button")
  24. property color headerBackgroundColor: UM.Theme.getColor("action_button")
  25. property color headerActiveColor: UM.Theme.getColor("expandable_active")
  26. property color headerHoverColor: UM.Theme.getColor("expandable_hover")
  27. property alias enabled: mouseArea.enabled
  28. // Text to show when this component is disabled
  29. property alias disabledText: disabledLabel.text
  30. // Defines the alignment of the content with respect of the headerItem, by default to the right
  31. // Note that this only has an effect if the panel is draggable
  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. property alias headerBackgroundBorder: background.border
  42. // Whether or not to show the background border
  43. property bool enableHeaderBackgroundBorder: true
  44. // What icon should be displayed on the right.
  45. property alias iconSource: collapseButton.source
  46. property alias iconColor: collapseButton.color
  47. // The icon size (it's always drawn as a square)
  48. property alias iconSize: collapseButton.height
  49. // Is the "drawer" open?
  50. property alias expanded: contentContainer.visible
  51. // What should the radius of the header be. This is also influenced by the headerCornerSide
  52. property alias headerRadius: background.radius
  53. // On what side should the header corners be shown? 1 is down, 2 is left, 3 is up and 4 is right.
  54. property alias headerCornerSide: background.cornerSide
  55. // Distance between the header and the content.
  56. property int popupOffset: 2 * UM.Theme.getSize("default_lining").height
  57. // Prefix used for the dragged position preferences. Preferences not used if empty. Don't translate!
  58. property string dragPreferencesNamePrefix: ""
  59. function toggleContent()
  60. {
  61. contentContainer.visible = !expanded
  62. }
  63. function updateDragPosition()
  64. {
  65. contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
  66. }
  67. onEnabledChanged:
  68. {
  69. if (!base.enabled && expanded)
  70. {
  71. toggleContent();
  72. updateDragPosition();
  73. }
  74. }
  75. // Add this binding since the background color is not updated otherwise
  76. Binding
  77. {
  78. target: background
  79. property: "color"
  80. value:
  81. {
  82. return base.enabled ? (expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  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. border.width: base.enableHeaderBackgroundBorder ? UM.Theme.getSize("default_lining").width : 0
  92. border.color: UM.Theme.getColor("lining")
  93. color: base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  94. anchors.fill: parent
  95. UM.Label
  96. {
  97. id: disabledLabel
  98. visible: !base.enabled
  99. anchors.fill: parent
  100. leftPadding: background.padding
  101. rightPadding: background.padding
  102. text: ""
  103. wrapMode: Text.WordWrap
  104. }
  105. Item
  106. {
  107. anchors.fill: parent
  108. visible: base.enabled
  109. Loader
  110. {
  111. id: headerItemLoader
  112. anchors
  113. {
  114. left: parent.left
  115. right: collapseButton.visible ? collapseButton.left : parent.right
  116. top: parent.top
  117. bottom: parent.bottom
  118. margins: background.padding
  119. }
  120. }
  121. UM.RecolorImage
  122. {
  123. id: collapseButton
  124. anchors
  125. {
  126. right: parent.right
  127. verticalCenter: parent.verticalCenter
  128. margins: background.padding
  129. }
  130. source: UM.Theme.getIcon("ChevronSingleDown")
  131. visible: source != ""
  132. width: UM.Theme.getSize("standard_arrow").width
  133. height: UM.Theme.getSize("standard_arrow").height
  134. color: UM.Theme.getColor("small_button_text")
  135. }
  136. }
  137. MouseArea
  138. {
  139. id: mouseArea
  140. anchors.fill: parent
  141. onClicked: toggleContent()
  142. hoverEnabled: true
  143. onEntered: background.color = headerHoverColor
  144. onExited: background.color = base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  145. }
  146. }
  147. Cura.RoundedRectangle
  148. {
  149. id: contentContainer
  150. property string dragPreferencesNameX: "_xpos"
  151. property string dragPreferencesNameY: "_ypos"
  152. visible: false
  153. width: childrenRect.width
  154. height: childrenRect.height
  155. // Ensure that the content is located directly below the headerItem
  156. y: dragPreferencesNamePrefix === "" ? (background.height + base.popupOffset + base.contentSpacingY) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameY)
  157. // Make the content aligned with the rest, using the property contentAlignment to decide whether is right or left.
  158. // In case of right alignment, the 3x padding is due to left, right and padding between the button & text.
  159. x: dragPreferencesNamePrefix === "" ? (contentAlignment == ExpandableComponent.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameX)
  160. cornerSide: Cura.RoundedRectangle.Direction.All
  161. color: contentBackgroundColor
  162. border.width: UM.Theme.getSize("default_lining").width
  163. border.color: UM.Theme.getColor("lining")
  164. radius: UM.Theme.getSize("default_radius").width
  165. function trySetPosition(posNewX, posNewY)
  166. {
  167. var margin = UM.Theme.getSize("narrow_margin");
  168. var minPt = base.mapFromItem(null, margin.width, margin.height);
  169. var maxPt = base.mapFromItem(null,
  170. CuraApplication.appWidth() - (contentContainer.width + margin.width),
  171. CuraApplication.appHeight() - (contentContainer.height + margin.height));
  172. var initialY = background.height + base.popupOffset + margin.height;
  173. contentContainer.x = Math.max(minPt.x, Math.min(maxPt.x, posNewX));
  174. contentContainer.y = Math.max(initialY, Math.min(maxPt.y, posNewY));
  175. if (dragPreferencesNamePrefix !== "")
  176. {
  177. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameX, contentContainer.x);
  178. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameY, contentContainer.y);
  179. }
  180. }
  181. ExpandableComponentHeader
  182. {
  183. id: contentHeader
  184. headerTitle: ""
  185. anchors
  186. {
  187. top: parent.top
  188. right: parent.right
  189. left: parent.left
  190. }
  191. MouseArea
  192. {
  193. id: dragRegion
  194. cursorShape: Qt.SizeAllCursor
  195. anchors
  196. {
  197. top: parent.top
  198. bottom: parent.bottom
  199. left: parent.left
  200. right: contentHeader.xPosCloseButton
  201. }
  202. property var clickPos: Qt.point(0, 0)
  203. property bool dragging: false
  204. onPressed:
  205. {
  206. clickPos = Qt.point(mouse.x, mouse.y);
  207. dragging = true
  208. }
  209. onPositionChanged:
  210. {
  211. if(dragging)
  212. {
  213. var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y);
  214. if (delta.x !== 0 || delta.y !== 0)
  215. {
  216. contentContainer.trySetPosition(contentContainer.x + delta.x, contentContainer.y + delta.y);
  217. }
  218. }
  219. }
  220. onReleased:
  221. {
  222. dragging = false
  223. }
  224. onDoubleClicked:
  225. {
  226. dragging = false
  227. contentContainer.trySetPosition(0, 0);
  228. }
  229. Connections
  230. {
  231. target: UM.Preferences
  232. function onPreferenceChanged(preference)
  233. {
  234. if
  235. (
  236. preference !== "general/window_height" &&
  237. preference !== "general/window_width" &&
  238. preference !== "general/window_state"
  239. )
  240. {
  241. return;
  242. }
  243. contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
  244. }
  245. }
  246. }
  247. }
  248. Control
  249. {
  250. id: content
  251. anchors.top: contentHeader.bottom
  252. padding: UM.Theme.getSize("default_margin").width
  253. contentItem: Item {}
  254. onContentItemChanged:
  255. {
  256. // Since we want the size of the content to be set by the size of the content,
  257. // we need to do it like this.
  258. content.width = contentItem.width + 2 * content.padding
  259. content.height = contentItem.height + 2 * content.padding
  260. }
  261. }
  262. }
  263. // DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
  264. // Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
  265. Connections
  266. {
  267. // Since it could be that the content is dynamically populated, we should also take these changes into account.
  268. target: content.contentItem
  269. function onWidthChanged() { content.width = content.contentItem.width + 2 * content.padding }
  270. function onHeightChanged()
  271. {
  272. content.height = content.contentItem.height + 2 * content.padding
  273. contentContainer.height = contentHeader.height + content.height
  274. }
  275. }
  276. }