ExpandableComponent.qml 12 KB

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