ExpandableComponent.qml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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. // Note that this only has an effect if the panel is draggable
  33. property int contentAlignment: ExpandableComponent.ContentAlignment.AlignRight
  34. // How much spacing is needed around the contentItem
  35. property alias contentPadding: content.padding
  36. // Adds a title to the content item
  37. property alias contentHeaderTitle: contentHeader.headerTitle
  38. // How much spacing is needed for the contentItem by Y coordinate
  39. property var contentSpacingY: UM.Theme.getSize("narrow_margin").width
  40. // How much padding is needed around the header & button
  41. property alias headerPadding: background.padding
  42. // What icon should be displayed on the right.
  43. property alias iconSource: collapseButton.source
  44. property alias iconColor: collapseButton.color
  45. // The icon size (it's always drawn as a square)
  46. property alias iconSize: collapseButton.height
  47. // Is the "drawer" open?
  48. property alias expanded: contentContainer.visible
  49. // What should the radius of the header be. This is also influenced by the headerCornerSide
  50. property alias headerRadius: background.radius
  51. // On what side should the header corners be shown? 1 is down, 2 is left, 3 is up and 4 is right.
  52. property alias headerCornerSide: background.cornerSide
  53. property alias headerShadowColor: shadow.color
  54. property alias enableHeaderShadow: shadow.visible
  55. property int shadowOffset: 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. // Add this binding since the background color is not updated otherwise
  67. Binding
  68. {
  69. target: background
  70. property: "color"
  71. value:
  72. {
  73. return base.enabled ? (expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  74. }
  75. }
  76. // The panel needs to close when it becomes disabled
  77. Connections
  78. {
  79. target: base
  80. onEnabledChanged:
  81. {
  82. if (!base.enabled && expanded)
  83. {
  84. toggleContent();
  85. updateDragPosition();
  86. }
  87. }
  88. }
  89. implicitHeight: 100 * screenScaleFactor
  90. implicitWidth: 400 * screenScaleFactor
  91. RoundedRectangle
  92. {
  93. id: background
  94. property real padding: UM.Theme.getSize("default_margin").width
  95. color: base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  96. anchors.fill: parent
  97. Label
  98. {
  99. id: disabledLabel
  100. visible: !base.enabled
  101. anchors.fill: parent
  102. leftPadding: background.padding
  103. rightPadding: background.padding
  104. text: ""
  105. font: UM.Theme.getFont("default")
  106. renderType: Text.NativeRendering
  107. verticalAlignment: Text.AlignVCenter
  108. color: UM.Theme.getColor("text")
  109. wrapMode: Text.WordWrap
  110. }
  111. Item
  112. {
  113. anchors.fill: parent
  114. visible: base.enabled
  115. Loader
  116. {
  117. id: headerItemLoader
  118. anchors
  119. {
  120. left: parent.left
  121. right: collapseButton.visible ? collapseButton.left : parent.right
  122. top: parent.top
  123. bottom: parent.bottom
  124. margins: background.padding
  125. }
  126. }
  127. UM.RecolorImage
  128. {
  129. id: collapseButton
  130. anchors
  131. {
  132. right: parent.right
  133. verticalCenter: parent.verticalCenter
  134. margins: background.padding
  135. }
  136. source: UM.Theme.getIcon("pencil")
  137. visible: source != ""
  138. width: UM.Theme.getSize("standard_arrow").width
  139. height: UM.Theme.getSize("standard_arrow").height
  140. color: UM.Theme.getColor("small_button_text")
  141. }
  142. }
  143. MouseArea
  144. {
  145. id: mouseArea
  146. anchors.fill: parent
  147. onClicked: toggleContent()
  148. hoverEnabled: true
  149. onEntered: background.color = headerHoverColor
  150. onExited: background.color = base.enabled ? (base.expanded ? headerActiveColor : headerBackgroundColor) : UM.Theme.getColor("disabled")
  151. }
  152. }
  153. DropShadow
  154. {
  155. id: shadow
  156. // Don't blur the shadow
  157. radius: 0
  158. anchors.fill: background
  159. source: background
  160. verticalOffset: base.shadowOffset
  161. visible: true
  162. color: UM.Theme.getColor("action_button_shadow")
  163. // Should always be drawn behind the background.
  164. z: background.z - 1
  165. }
  166. Cura.RoundedRectangle
  167. {
  168. id: contentContainer
  169. property string dragPreferencesNameX: "_xpos"
  170. property string dragPreferencesNameY: "_ypos"
  171. visible: false
  172. width: childrenRect.width
  173. height: childrenRect.height
  174. // Ensure that the content is located directly below the headerItem
  175. y: dragPreferencesNamePrefix === "" ? (background.height + base.shadowOffset + base.contentSpacingY) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameY)
  176. // Make the content aligned with the rest, using the property contentAlignment to decide whether is right or left.
  177. // In case of right alignment, the 3x padding is due to left, right and padding between the button & text.
  178. x: dragPreferencesNamePrefix === "" ? (contentAlignment == ExpandableComponent.ContentAlignment.AlignRight ? -width + collapseButton.width + headerItemLoader.width + 3 * background.padding : 0) : UM.Preferences.getValue(dragPreferencesNamePrefix + dragPreferencesNameX)
  179. cornerSide: Cura.RoundedRectangle.Direction.All
  180. color: contentBackgroundColor
  181. border.width: UM.Theme.getSize("default_lining").width
  182. border.color: UM.Theme.getColor("lining")
  183. radius: UM.Theme.getSize("default_radius").width
  184. function trySetPosition(posNewX, posNewY)
  185. {
  186. var margin = UM.Theme.getSize("narrow_margin");
  187. var minPt = base.mapFromItem(null, margin.width, margin.height);
  188. var maxPt = base.mapFromItem(null,
  189. CuraApplication.appWidth() - (contentContainer.width + margin.width),
  190. CuraApplication.appHeight() - (contentContainer.height + margin.height));
  191. var initialY = background.height + base.shadowOffset + margin.height;
  192. contentContainer.x = Math.max(minPt.x, Math.min(maxPt.x, posNewX));
  193. contentContainer.y = Math.max(initialY, Math.min(maxPt.y, posNewY));
  194. if (dragPreferencesNamePrefix !== "")
  195. {
  196. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameX, contentContainer.x);
  197. UM.Preferences.setValue(dragPreferencesNamePrefix + dragPreferencesNameY, contentContainer.y);
  198. }
  199. }
  200. ExpandableComponentHeader
  201. {
  202. id: contentHeader
  203. headerTitle: ""
  204. anchors
  205. {
  206. top: parent.top
  207. right: parent.right
  208. left: parent.left
  209. }
  210. MouseArea
  211. {
  212. id: dragRegion
  213. cursorShape: Qt.SizeAllCursor
  214. anchors
  215. {
  216. top: parent.top
  217. bottom: parent.bottom
  218. left: parent.left
  219. right: contentHeader.xPosCloseButton
  220. }
  221. property var clickPos: Qt.point(0, 0)
  222. property bool dragging: false
  223. onPressed:
  224. {
  225. clickPos = Qt.point(mouse.x, mouse.y);
  226. dragging = true
  227. }
  228. onPositionChanged:
  229. {
  230. if(dragging)
  231. {
  232. var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y);
  233. if (delta.x !== 0 || delta.y !== 0)
  234. {
  235. contentContainer.trySetPosition(contentContainer.x + delta.x, contentContainer.y + delta.y);
  236. }
  237. }
  238. }
  239. onReleased:
  240. {
  241. dragging = false
  242. }
  243. onDoubleClicked:
  244. {
  245. dragging = false
  246. contentContainer.trySetPosition(0, 0);
  247. }
  248. Connections
  249. {
  250. target: UM.Preferences
  251. onPreferenceChanged:
  252. {
  253. if
  254. (
  255. preference !== "general/window_height" &&
  256. preference !== "general/window_width" &&
  257. preference !== "general/window_state"
  258. )
  259. {
  260. return;
  261. }
  262. contentContainer.trySetPosition(contentContainer.x, contentContainer.y);
  263. }
  264. }
  265. }
  266. }
  267. Control
  268. {
  269. id: content
  270. anchors.top: contentHeader.bottom
  271. padding: UM.Theme.getSize("default_margin").width
  272. contentItem: Item {}
  273. onContentItemChanged:
  274. {
  275. // Since we want the size of the content to be set by the size of the content,
  276. // we need to do it like this.
  277. content.width = contentItem.width + 2 * content.padding
  278. content.height = contentItem.height + 2 * content.padding
  279. }
  280. }
  281. }
  282. // DO NOT MOVE UP IN THE CODE: This connection has to be here, after the definition of the content item.
  283. // Apparently the order in which these are handled matters and so the height is correctly updated if this is here.
  284. Connections
  285. {
  286. // Since it could be that the content is dynamically populated, we should also take these changes into account.
  287. target: content.contentItem
  288. onWidthChanged: content.width = content.contentItem.width + 2 * content.padding
  289. onHeightChanged:
  290. {
  291. content.height = content.contentItem.height + 2 * content.padding
  292. contentContainer.height = contentHeader.height + content.height
  293. }
  294. }
  295. }