Paned.qml 8.22 KB
Newer Older
Ronan Abhamon's avatar
Ronan Abhamon committed
1 2
import QtQuick 2.7

3
import Common.Styles 1.0
4 5
import Utils 1.0

6
// =============================================================================
7 8 9 10 11
//
// Paned is one container divided in two areas.
// The division between the areas can be modified with a handle.
//
// Each area can have a fixed or dynamic minimum width.
12
// See `minimumLeftLimit` and `minimumRightLimit` attributes.
13
//
14 15 16
// Note: Paned supports too `maximumLeftLimit` and
// `maximumRightLimit`.
//
17
// To create a dynamic minimum width, it's necessary to give
18 19
// a percentage to `minmimumLeftLimit` or `minimumRightLimit` like:
// `minimumLeftLimit: '66%'`.
20 21
// The percentage is relative to the container width.
//
22
// =============================================================================
23

Ronan Abhamon's avatar
Ronan Abhamon committed
24 25 26
Item {
  id: container

27 28
  // ---------------------------------------------------------------------------

Ronan Abhamon's avatar
Ronan Abhamon committed
29 30
  property alias childA: contentA.data
  property alias childB: contentB.data
31
  property bool defaultClosed: false
32
  property bool resizeAInPriority: false
33
  property int closingEdge: Qt.LeftEdge // `LeftEdge` or `RightEdge`.
34
  property int defaultChildAWidth
Ronan Abhamon's avatar
Ronan Abhamon committed
35

36
  // User limits: string or int values.
37 38 39 40 41
  // By default: no limits.
  property var maximumLeftLimit
  property var maximumRightLimit
  property var minimumLeftLimit: 0
  property var minimumRightLimit: 0
42

43 44
  property bool _isClosed

45
  // Internal limits.
46 47 48 49
  property var _maximumLeftLimit
  property var _maximumRightLimit
  property var _minimumLeftLimit
  property var _minimumRightLimit
50

51
  // ---------------------------------------------------------------------------
52
  // Public functions.
53
  // ---------------------------------------------------------------------------
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

  function isClosed () {
    return _isClosed
  }

  function open () {
    if (_isClosed) {
      openingTransition.running = true
    }
  }

  function close () {
    if (!_isClosed) {
      _close()
    }
  }

71
  // ---------------------------------------------------------------------------
72
  // Private functions.
73
  // ---------------------------------------------------------------------------
74

75
  function _getLimitValue (limit) {
76 77 78 79
    if (limit == null) {
      return
    }

80 81 82 83 84 85
    return limit.isDynamic
      ? width * limit.value
      : limit.value
  }

  function _parseLimit (limit) {
86 87 88 89
    if (limit == null) {
      return
    }

90 91
    if (Utils.isString(limit)) {
      var arr = limit.split('%')
Ronan Abhamon's avatar
Ronan Abhamon committed
92

93 94 95 96 97
      if (arr[1] === '') {
        return {
          isDynamic: true,
          value: +arr[0] / 100
        }
98
      }
Ronan Abhamon's avatar
Ronan Abhamon committed
99 100
    }

101 102
    return {
      value: limit
Ronan Abhamon's avatar
Ronan Abhamon committed
103 104 105
    }
  }

106
  function _applyLimits () {
107 108 109 110
    var maximumLeftLimit = _getLimitValue(_maximumLeftLimit)
    var maximumRightLimit = _getLimitValue(_maximumRightLimit)
    var minimumLeftLimit = _getLimitValue(_minimumLeftLimit)
    var minimumRightLimit = _getLimitValue(_minimumRightLimit)
111

112 113 114 115
    var theoreticalBWidth = container.width - contentA.width - handle.width

    // If closed, set correctly the handle position to left or right.
    if (_isClosed) {
116
      contentA.width = (closingEdge !== Qt.LeftEdge)
117 118 119
        ? container.width - handle.width
        : 0
    }
120
    // width(A) < minimum width(A).
121 122 123 124 125 126 127 128 129 130 131 132 133 134
    else if (contentA.width < minimumLeftLimit) {
      contentA.width = minimumLeftLimit
    }
    // width(A) > maximum width(A).
    else if (maximumLeftLimit != null && contentA.width > maximumLeftLimit) {
      contentA.width = maximumLeftLimit
    }
    // width(B) < minimum width(B).
    else if (theoreticalBWidth < minimumRightLimit) {
      contentA.width = container.width - handle.width - minimumRightLimit
    }
    // width(B) > maximum width(B).
    else if (maximumRightLimit != null && theoreticalBWidth > maximumRightLimit) {
      contentA.width = container.width - handle.width - maximumRightLimit
135 136
    } else if (resizeAInPriority) {
      contentA.width = container.width - handle.width - contentB.width
137
    }
138 139
  }

140
  // ---------------------------------------------------------------------------
141

142 143 144
  function _applyLimitsOnUserMove (offset) {
    var minimumRightLimit = _getLimitValue(_minimumRightLimit)
    var minimumLeftLimit = _getLimitValue(_minimumLeftLimit)
145

146 147 148 149
    // One area is closed.
    if (_isClosed) {
      if (closingEdge === Qt.LeftEdge) {
        if (offset > minimumLeftLimit / 2) {
150
          _open()
151 152 153
        }
      } else {
        if (-offset > minimumRightLimit / 2) {
154
          _open()
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
        }
      }

      return
    }

    // Check limits.
    var maximumLeftLimit = _getLimitValue(_maximumLeftLimit)
    var maximumRightLimit = _getLimitValue(_maximumRightLimit)

    var theoreticalBWidth = container.width - offset - contentA.width - handle.width

    // width(A) < minimum width(A).
    if (contentA.width + offset < minimumLeftLimit) {
      contentA.width = minimumLeftLimit

      if (closingEdge === Qt.LeftEdge && -offset > minimumLeftLimit / 2) {
172 173 174 175 176
        if (_isClosed) {
          _open()
        } else {
          _close()
        }
177 178 179 180 181 182
      }
    }
    // width(A) > maximum width(A).
    else if (maximumLeftLimit != null && contentA.width + offset > maximumLeftLimit) {
      contentA.width = maximumLeftLimit
    }
183
    // width(B) < minimum width(B).
184 185 186
    else if (theoreticalBWidth < minimumRightLimit) {
      contentA.width = container.width - handle.width - minimumRightLimit

187
      if (closingEdge !== Qt.LeftEdge && offset > minimumRightLimit / 2) {
188 189 190 191 192
        if (_isClosed) {
          _open()
        } else {
          _close()
        }
193 194 195 196 197 198 199 200 201
      }
    }
    // width(B) > maximum width(B).
    else if (maximumRightLimit != null && theoreticalBWidth > maximumRightLimit) {
      contentA.width = container.width - handle.width - maximumRightLimit
    }
    // Resize A/B.
    else {
      contentA.width = contentA.width + offset
202 203
    }
  }
Ronan Abhamon's avatar
Ronan Abhamon committed
204

205
  // ---------------------------------------------------------------------------
206

207
  function _open () {
208 209 210 211
    _isClosed = false
    _applyLimits()
  }

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
  function _close () {
    _isClosed = true
    closingTransition.running = true
  }

  function _inverseClosingState () {
    if (!_isClosed) {
      // Save state and close.
      _close()
    } else {
      // Restore old state.
      openingTransition.running = true
    }
  }

227 228 229 230 231 232 233 234
  function _isVisible (edge) {
    return (
      !_isClosed ||
      openingTransition.running ||
      closingTransition.running
    ) || closingEdge !== edge
  }

235
  // ---------------------------------------------------------------------------
236

237
  onWidthChanged: _applyLimits()
238

Ronan Abhamon's avatar
Ronan Abhamon committed
239
  Component.onCompleted: {
240
    // Unable to modify these properties after creation.
241 242 243 244 245 246
    // It's a desired choice.
    _maximumLeftLimit = _parseLimit(maximumLeftLimit)
    _maximumRightLimit = _parseLimit(maximumRightLimit)
    _minimumLeftLimit = _parseLimit(minimumLeftLimit)
    _minimumRightLimit = _parseLimit(minimumRightLimit)

247 248 249
    contentA.width = (defaultChildAWidth == null)
      ? _getLimitValue(_minimumLeftLimit)
      : defaultChildAWidth
250

251
    _isClosed = defaultClosed
Ronan Abhamon's avatar
Ronan Abhamon committed
252 253
  }

254
  Item {
Ronan Abhamon's avatar
Ronan Abhamon committed
255 256 257
    id: contentA

    height: parent.height
258
    visible: _isVisible(Qt.LeftEdge)
Ronan Abhamon's avatar
Ronan Abhamon committed
259 260 261 262 263 264 265 266 267 268 269
  }

  MouseArea {
    id: handle

    property int _mouseStart

    anchors.left: contentA.right
    cursorShape: Qt.SplitHCursor
    height: parent.height
    hoverEnabled: true
270
    width: PanedStyle.handle.width
Ronan Abhamon's avatar
Ronan Abhamon committed
271

272
    onDoubleClicked: _inverseClosingState()
273
    onMouseXChanged: pressed &&
274
      _applyLimitsOnUserMove(mouseX - _mouseStart)
Ronan Abhamon's avatar
Ronan Abhamon committed
275 276 277 278 279
    onPressed: _mouseStart = mouseX

    Rectangle {
      anchors.fill: parent
      color: parent.pressed
280
        ? PanedStyle.handle.color.pressed
Ronan Abhamon's avatar
Ronan Abhamon committed
281
        : (parent.containsMouse
282 283 284
           ? PanedStyle.handle.color.hovered
           : PanedStyle.handle.color.normal
          )
Ronan Abhamon's avatar
Ronan Abhamon committed
285 286 287
    }
  }

288
  Item {
Ronan Abhamon's avatar
Ronan Abhamon committed
289 290 291 292
    id: contentB

    anchors.left: handle.right
    height: parent.height
293
    visible: _isVisible(Qt.RightEdge)
294
    width: container.width - contentA.width - handle.width
Ronan Abhamon's avatar
Ronan Abhamon committed
295
  }
296 297

  PropertyAnimation {
298
    id: openingTransition
299

300
    duration: PanedStyle.transitionDuration
301
    property: 'width'
302
    target: contentA
303 304 305
    to: closingEdge === Qt.LeftEdge
      ? minimumLeftLimit
      : container.width - minimumRightLimit - handle.width
306 307

    onRunningChanged: !running && _open()
308 309 310
  }

  PropertyAnimation {
311
    id: closingTransition
312

313
    duration: PanedStyle.transitionDuration
314 315
    property: 'width'
    target: contentA
316
    to: closingEdge === Qt.LeftEdge ? 0 : container.width - handle.width
317
  }
Ronan Abhamon's avatar
Ronan Abhamon committed
318
}