ExpandableComponent.qml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. // 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. Label
  96. {
  97. id: disabledLabel
  98. visible: !base.enabled
  99. anchors.fill: parent
  100. leftPadding: background.padding
  101. rightPadding: background.padding
  102. text: ""
  103. font: UM.Theme.getFont("default")
  104. renderType: Text.NativeRendering
  105. verticalAlignment: Text.AlignVCenter
  106. color: UM.Theme.getColor("text")
  107. wrapMode: Text.WordWrap
  108. }
  109. Item
  110. {
  111. anchors.fill: parent
  112. visible: base.enabled
  113. Loader
  114. {
  115. id: headerItemLoader
  116. anchors
  117. {
  118. left: parent.left
  119. right: collapseButton.visible ? collapseButton.left : parent.right
  120. top: parent.top
  121. bottom: parent.bottom
  122. margins: background.padding
  123. }
  124. }
  125. UM.RecolorImage
  126. {
  127. id: collapseButton
  128. anchors
  129. {
  130. right: parent.right
  131. verticalCenter: parent.verticalCenter
  132. margins: background.padding
  133. }
  134. source: UM.Theme.getIcon("ChevronSingleDown")
  135. visible: source != ""
  136. width: UM.Theme.getSize("standard_arrow").width
  137. height: UM.Theme.getSize("standard_arrow").height
  138. color: UM.Theme.getColor("small_button_text")
  139. }
  140. }
  141. MouseArea
  142. {
  143. id: mouseArea
  144. anchors.fill: parent
  145. onClicked: toggleContent()
  146. hoverEnabled: true
  147. onEntered: background.color = headerHoverColor
  148. onExited: background.color = base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  149. }
  150. }
  151. Cura.RoundedRectangle
  152. {
  153. id: contentContainer
  154. property string dragPreferencesNameX: "_xpos"
  155. property string dragPreferencesNameY: "_ypos"
  156. visible: false
  157. width: childrenRect.width
  158. height: childrenRect.height
  159. // Ensure that the content is located directly below the headerItem
  160. y: dragPreferencesNamePrefix === "" ? (background.height + base.popupOffset + base.contentSpacingY) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameY)
  161. // Make the content aligned with the rest, using the property contentAlignment to decide whether is right or left.
  162. // In case of right alignment, the 3x padding is due to left, right and padding between the button & text.
  163. x: dragPreferencesNamePrefix === "" ? (contentAlignment == ExpandableComponent.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameX)
  164. cornerSide: Cura.RoundedRectangle.Direction.All
  165. color: contentBackgroundColor
  166. border.width: UM.Theme.getSize("default_lining").width
  167. border.color: UM.Theme.getColor("lining")
  168. radius: UM.Theme.getSize("default_radius").width
  169. function trySetPosition(posNewX, posNewY)
  170. {
  171. var margin = UM.Theme.getSize("narrow_margin");
  172. var minPt = base.mapFromItem(null, margin.width, margin.height);
  173. var maxPt = base.mapFromItem(null,
  174. CuraApplication.appWidth() - (contentContainer.width + margin.width),
  175. CuraApplication.appHeight() - (contentContainer.height + margin.height));
  176. var initialY = background.height + base.popupOffset + margin.height;
  177. contentContainer.x = Math.max(minPt.x, Math.min(maxPt.x, posNewX));
  178. contentContainer.y = Math.max(initialY, Math.min(maxPt.y, posNewY));
  179. if (dragPreferencesNamePrefix !== "")
  180. {
  181. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameX, contentContainer.x);
  182. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameY, contentContainer.y);
  183. }
  184. }
  185. ExpandableComponentHeader
  186. {
  187. id: contentHeader
  188. headerTitle: ""
  189. anchors
  190. {
  191. top: parent.top
  192. right: parent.right
  193. left: parent.left
  194. }
  195. MouseArea
  196. {
  197. id: dragRegion
  198. cursorShape: Qt.SizeAllCursor
  199. anchors
  200. {
  201. top: parent.top
  202. bottom: parent.bottom
  203. left: parent.left
  204. right: contentHeader.xPosCloseButton
  205. }
  206. property var clickPos: Qt.point(0, 0)
  207. property bool dragging: false
  208. onPressed:
  209. {
  210. clickPos = Qt.point(mouse.x, mouse.y);
  211. dragging = true
  212. }
  213. onPositionChanged:
  214. {
  215. if(dragging)
  216. {
  217. var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y);
  218. if (delta.x !== 0 || delta.y !== 0)
  219. {
  220. contentContainer.trySetPosition(contentContainer.x + delta.x, contentContainer.y + delta.y);
  221. }
  222. }
  223. }
  224. onReleased:
  225. {
  226. dragging = false
  227. }
  228. onDoubleClicked:
  229. {
  230. dragging = false
  231. contentContainer.trySetPosition(0, 0);
  232. }
  233. Connections
  234. {
  235. target: UM.Preferences
  236. function onPreferenceChanged(preference)
  237. {
  238. if
  239. (
  240. preference !== "general/window_height" &&
  241. preference !== "general/window_width" &&
  242. preference !== "general/window_state"
  243. )
  244. {
  245. return;
  246. }
  247. contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
  248. }
  249. }
  250. }
  251. }
  252. Control
  253. {
  254. id: content
  255. anchors.top: contentHeader.bottom
  256. padding: UM.Theme.getSize("default_margin").width
  257. contentItem: Item {}
  258. onContentItemChanged:
  259. {
  260. // Since we want the size of the content to be set by the size of the content,
  261. // we need to do it like this.
  262. content.width = contentItem.width + 2 * content.padding
  263. content.height = contentItem.height + 2 * content.padding
  264. }
  265. }
  266. }
  267. // DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
  268. // Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
  269. Connections
  270. {
  271. // Since it could be that the content is dynamically populated, we should also take these changes into account.
  272. target: content.contentItem
  273. function onWidthChanged() { content.width = content.contentItem.width + 2 * content.padding }
  274. function onHeightChanged()
  275. {
  276. content.height = content.contentItem.height + 2 * content.padding
  277. contentContainer.height = contentHeader.height + content.height
  278. }
  279. }
  280. }