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

3
import Common.Styles 1.0
4 5 6 7 8 9 10 11
import Utils 1.0

// ===================================================================
//
// 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 22 23
// The percentage is relative to the container width.
//
// ===================================================================

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

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

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

41 42 43
  property bool _isClosed
  property int _savedContentAWidth

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

50 51
  // -----------------------------------------------------------------

52
  function _getLimitValue (limit) {
53 54 55 56
    if (limit == null) {
      return
    }

57 58 59 60 61 62
    return limit.isDynamic
      ? width * limit.value
      : limit.value
  }

  function _parseLimit (limit) {
63 64 65 66
    if (limit == null) {
      return
    }

67 68
    if (Utils.isString(limit)) {
      var arr = limit.split('%')
Ronan Abhamon's avatar
Ronan Abhamon committed
69

70 71 72 73 74
      if (arr[1] === '') {
        return {
          isDynamic: true,
          value: +arr[0] / 100
        }
75
      }
Ronan Abhamon's avatar
Ronan Abhamon committed
76 77
    }

78 79
    return {
      value: limit
Ronan Abhamon's avatar
Ronan Abhamon committed
80 81 82
    }
  }

83
  function _applyLimits () {
84 85 86 87
    var maximumLeftLimit = _getLimitValue(_maximumLeftLimit)
    var maximumRightLimit = _getLimitValue(_maximumRightLimit)
    var minimumLeftLimit = _getLimitValue(_minimumLeftLimit)
    var minimumRightLimit = _getLimitValue(_minimumRightLimit)
88

89 90 91 92
    var theoreticalBWidth = container.width - contentA.width - handle.width

    // If closed, set correctly the handle position to left or right.
    if (_isClosed) {
93
      contentA.width = (closingEdge !== Qt.LeftEdge)
94 95 96
        ? container.width - handle.width
        : 0
    }
97
    // width(A) < minimum width(A).
98 99 100 101 102 103 104 105 106 107 108 109 110 111
    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
112 113
    } else if (resizeAInPriority) {
      contentA.width = container.width - handle.width - contentB.width
114
    }
115 116
  }

117 118
  // -----------------------------------------------------------------

119 120 121
  function _applyLimitsOnUserMove (offset) {
    var minimumRightLimit = _getLimitValue(_minimumRightLimit)
    var minimumLeftLimit = _getLimitValue(_minimumLeftLimit)
122

123 124 125 126
    // One area is closed.
    if (_isClosed) {
      if (closingEdge === Qt.LeftEdge) {
        if (offset > minimumLeftLimit / 2) {
127
          _open()
128 129 130
        }
      } else {
        if (-offset > minimumRightLimit / 2) {
131
          _open()
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
        }
      }

      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) {
149 150 151 152 153
        if (_isClosed) {
          _open()
        } else {
          _close()
        }
154 155 156 157 158 159
      }
    }
    // width(A) > maximum width(A).
    else if (maximumLeftLimit != null && contentA.width + offset > maximumLeftLimit) {
      contentA.width = maximumLeftLimit
    }
160
    // width(B) < minimum width(B).
161 162 163
    else if (theoreticalBWidth < minimumRightLimit) {
      contentA.width = container.width - handle.width - minimumRightLimit

164
      if (closingEdge !== Qt.LeftEdge && offset > minimumRightLimit / 2) {
165 166 167 168 169
        if (_isClosed) {
          _open()
        } else {
          _close()
        }
170 171 172 173 174 175 176 177 178
      }
    }
    // 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
179 180
    }
  }
Ronan Abhamon's avatar
Ronan Abhamon committed
181

182
  // -----------------------------------------------------------------
183

184
  function _open () {
185 186 187 188
    _isClosed = false
    _applyLimits()
  }

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
  function _close () {
    _isClosed = true
    _savedContentAWidth = contentA.width
    closingTransition.running = true
  }

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

205 206 207 208 209 210 211 212
  function _isVisible (edge) {
    return (
      !_isClosed ||
      openingTransition.running ||
      closingTransition.running
    ) || closingEdge !== edge
  }

213 214
  // -----------------------------------------------------------------

215
  onWidthChanged: _applyLimits()
216

Ronan Abhamon's avatar
Ronan Abhamon committed
217
  Component.onCompleted: {
218 219 220 221 222 223 224
    // Unable to modify this properties after creation.
    // It's a desired choice.
    _maximumLeftLimit = _parseLimit(maximumLeftLimit)
    _maximumRightLimit = _parseLimit(maximumRightLimit)
    _minimumLeftLimit = _parseLimit(minimumLeftLimit)
    _minimumRightLimit = _parseLimit(minimumRightLimit)

225 226 227
    contentA.width = (defaultChildAWidth == null)
      ? _getLimitValue(_minimumLeftLimit)
      : defaultChildAWidth
228

229 230
    _isClosed = defaultClosed
    _savedContentAWidth = contentA.width
Ronan Abhamon's avatar
Ronan Abhamon committed
231 232
  }

233
  Item {
Ronan Abhamon's avatar
Ronan Abhamon committed
234 235 236
    id: contentA

    height: parent.height
237
    visible: _isVisible(Qt.LeftEdge)
Ronan Abhamon's avatar
Ronan Abhamon committed
238 239 240 241 242 243 244 245 246 247 248
  }

  MouseArea {
    id: handle

    property int _mouseStart

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

251
    onDoubleClicked: _inverseClosingState()
252
    onMouseXChanged: pressed &&
253
      _applyLimitsOnUserMove(mouseX - _mouseStart)
Ronan Abhamon's avatar
Ronan Abhamon committed
254 255 256 257 258
    onPressed: _mouseStart = mouseX

    Rectangle {
      anchors.fill: parent
      color: parent.pressed
259
        ? PanedStyle.handle.color.pressed
Ronan Abhamon's avatar
Ronan Abhamon committed
260
        : (parent.containsMouse
261 262 263
           ? PanedStyle.handle.color.hovered
           : PanedStyle.handle.color.normal
          )
Ronan Abhamon's avatar
Ronan Abhamon committed
264 265 266
    }
  }

267
  Item {
Ronan Abhamon's avatar
Ronan Abhamon committed
268 269 270 271
    id: contentB

    anchors.left: handle.right
    height: parent.height
272
    visible: _isVisible(Qt.RightEdge)
273
    width: container.width - contentA.width - handle.width
Ronan Abhamon's avatar
Ronan Abhamon committed
274
  }
275 276

  PropertyAnimation {
277
    id: openingTransition
278

279
    duration: PanedStyle.transitionDuration
280
    property: 'width'
281 282 283 284
    target: contentA
    to: _savedContentAWidth

    onRunningChanged: !running && _open()
285 286 287
  }

  PropertyAnimation {
288
    id: closingTransition
289

290
    duration: PanedStyle.transitionDuration
291 292
    property: 'width'
    target: contentA
293
    to: closingEdge === Qt.LeftEdge ? 0 : container.width - handle.width
294
  }
Ronan Abhamon's avatar
Ronan Abhamon committed
295
}