ExpandableComponent.qml 12 KB

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