Index: sources/gui/qml/components/RangeSlider.qml =================================================================== diff -u -race4047d1007962c136fa32d0531102e22073f32 -rb07e8c91cabe64f9bc29b3653d27083b5cf96cb5 --- sources/gui/qml/components/RangeSlider.qml (.../RangeSlider.qml) (revision ace4047d1007962c136fa32d0531102e22073f32) +++ sources/gui/qml/components/RangeSlider.qml (.../RangeSlider.qml) (revision b07e8c91cabe64f9bc29b3653d27083b5cf96cb5) @@ -1,15 +1,15 @@ /*! * - * Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. + * Copyright (c) 2020-2023 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * - * \file Slider.qml - * \author (last) Behrouz NemaiPour - * \date (last) 04-Jun-2020 - * \author (original) Behrouz NematiPour - * \date (original) 18-Mar-2020 + * \file RangeSlider.qml + * \author (last) Behrouz NematiPour + * \date (last) 14-Mar-2023 + * \author (original) Behrouz NematiPour + * \date (original) 17-Sep-2020 * */ @@ -21,7 +21,11 @@ import "qrc:/globals" /*! - * \brief Denali project ProgressBar + * \brief Denali project RangeSlider + * \details This Component contains two handler for minimum and maximum + * which cannot pass each other. + * Can be used to adjust the minimum and maximum of a range. + * */ RangeRect { id: _root enum HandlerOption { @@ -30,195 +34,309 @@ Max } - property alias minValue : _rangeRect.lowerBound - property alias maxValue : _rangeRect.upperBound - property int curValue : RangeSlider.HandlerOption.None + property alias lowerText : _rangeRect.minText + property alias upperText : _rangeRect.maxText + property alias lowerTextHorizontalCenter: _rangeRect.minTextHorizontalCenter + property alias upperTextHorizontalCenter: _rangeRect.maxTextHorizontalCenter - property real step : 1 - property bool stepSnap : false + property alias minValue : _rangeRect.lowerBound ///< value of the minimum slider handler + property alias maxValue : _rangeRect.upperBound ///< value of the maximum slider handler - property bool ticks : false + property bool maxVerticalEdgeVisible: true + property bool minVerticalEdgeVisible: true - property alias color : _rangeRect.color - property alias bgColor : _root.color + property int gapValue : 0 ///< the gap between minValue and maxValue so the minValue shall always be gap value less than maxValue and vise versa. + property real minValueLowerBound : 0 + property real minValueUpperBound : 0 + property real maxValueLowerBound : 0 + property real maxValueUpperBound : 0 - property alias handler : _handlerRight + property int curHandler : RangeSlider.HandlerOption.None ///< current active slider handler + property int diameter : Variables.progressbarHandler ///< handlers diameter - clip: false - height : Variables.progressbarHeight - touchMargin : 25 + property real step : 1 ///< each step difference + property bool stepSnap : false ///< values within steps are not selectable if true - minimum : 0 - maximum : 0 + property bool ticks : false ///< visible the tick marks - // real-time bound change should effect the current set value - onMinimumChanged: { - if (minValue < minimum ) - minValue = minimum - if (maxValue < minimum ) - maxValue = minimum + property alias color : _rangeRect.color ///< the within range sliding color + property alias bgColor : _root.color ///< the out of range sliding color + + property bool minAdjusted : false ///< first time user adjustment happens + property bool maxAdjusted : false ///< first time user adjustment happens + property bool hasAdjust : false ///< if set to true then component is grayed out until both min and max are adjusted + + property bool showTickmarks : true ///< Indicate whether tickMarks should be shown + property bool isTickMarksRound : true ///< Indicate whether the tickmakrs are round + + property bool isRoundedEnds : true + property bool hasBorder : true + property color borderColor : Colors.borderDisableButton + + onHasAdjustChanged : { + setAdjusted() } - onMaximumChanged: { - if (minValue < maximum ) - minValue = maximum - if (maxValue < maximum ) - maxValue = maximum + onMinAdjustedChanged: { + setAdjusted() } + onMaxAdjustedChanged: { + setAdjusted() + } + /// root attributes + clip : false + color: Colors.transparent + + height : Variables.sliderDefaultBodyHeight + touchMargin : 25 + leftRightTouchMargin: _root.diameter / 2 + + radius : _root.isRoundedEnds ? (height/2) : Variables.rangeRectRadius + + border.width: _root.hasBorder ? Variables.rangeRectBorderWidth : 0 + border.color: (_root.minAdjusted && _root.maxAdjusted) ? Colors.borderButton : Colors.borderDisableButton + + minimum : 0 + maximum : 0 + + /// Lable of the minimum of range minText { visible : true anchors.topMargin: Variables.sliderTextMargin font.pixelSize : Fonts.fontPixelSliderMarker font.bold : false } + ///< Lable of the maximum of range maxText { visible : true anchors.topMargin: Variables.sliderTextMargin font.pixelSize : Fonts.fontPixelSliderMarker font.bold : false } + /// + /// \brief grays out the rangebar and handler if not adjusted and hasAdjust set to true + /// + function setAdjusted() { + function check() { + _rangeRect .color = minAdjusted && maxAdjusted ? Colors.sliderHighlightColor : Colors.createTreatmentInactive + _handlerLeft .color = minAdjusted ? Colors.createTreatmentActive: Colors.createTreatmentInactive + _handlerRight.color = maxAdjusted ? Colors.createTreatmentActive: Colors.createTreatmentInactive + } + function active() { + _rangeRect .color = Colors.createTreatmentActive + _handlerLeft .color = Colors.createTreatmentActive + _handlerRight.color = Colors.createTreatmentActive + } + if ( ! hasAdjust ) { + active() + } + else { + check() + } + } + + /// + /// \brief calculate the value by position x + /// \param x : x cordinate + /// function getValueOfX(x) { return ( x * ( maximum - minimum ) ) / width + minimum } - function _setValue_(x) { - if ( x < 0 ) { value = minimum; return; } - if ( x > width ) { value = maximum; return; } - - value = getValueOfX(x) - - if ( step === 1 ) { /* keep the value and return */ return; } - - var start = 0 - if ( ! stepSnap ) start = minimum - - value = Math.round((value - start) / step) * step + start - - if ( value < minimum ) { value = minimum; return; } - if ( value > maximum ) { value = maximum; return; } + function getXOfValue(vValue) { + return ( ( vValue - minimum ) * width ) / ( maximum - minimum ) } + /// + /// \brief calculate the value by position x of the handler + /// \details calculates the current value withing the range of minimum and maximum regarding the x cordinate of the active slider. + /// \param x : the active slider x cordinate. + /// function setValue(x) { - var value = 0 - if ( x < 0 ) { value = minimum; return value; } - if ( x > width ) { value = maximum; return value; } - value = getValueOfX(x) + let mValue = 0 + if ( x < 0 ) { mValue = minimum; return mValue; } + if ( x > width ) { mValue = maximum; return mValue; } - if ( step === 1 ) { return value; } + mValue = getValueOfX(x) - var start = 0 + if ( step === 1 ) { return mValue; } + + let start = 0 if ( ! stepSnap ) start = minimum + mValue = Math.round((mValue - start) / step) * step + start - value = Math.round((value - start) / step) * step + start + let decimals = Math.round(-Math.log10(step)) + if (decimals > 0) mValue = mValue.toFixed(decimals) - if ( value < minimum ) { value = minimum; return value; } - if ( value > maximum ) { value = maximum; return value; } - return value; + if ( mValue < minimum ) { mValue = minimum; return mValue; } + if ( mValue > maximum ) { mValue = maximum; return mValue; } + return mValue; } + /// + /// \brief updates correct lower or upper bound value regarding the x position + /// \details regarding the current mouse x position selects the correct handler and updated the bound value. + /// \param x : mouse x position. function setBound(x) { - var value = setValue(x) + let mValue = setValue(x) - var minDiff = Math.abs(minValue - value) - var maxDiff = Math.abs(maxValue - value) - console.log(minDiff , minValue , value , maxValue , maxDiff , curValue) - if (minDiff === maxDiff) { - if (curValue === RangeSlider.HandlerOption.Max) { - maxValue = value - } - else { - curValue = RangeSlider.HandlerOption.Min - minValue = value - } + // console.debug( maxValue - minValue, minValue, value, maxValue ) + if ( maxValue - minValue <= gapValue ) { + // max correction if the values get too close together less than defined gap + if ( curHandler === RangeSlider.HandlerOption.Max ) maxValue = minValue + gapValue + // min correction if the values get too close together less than defined gap + if ( curHandler === RangeSlider.HandlerOption.Min ) minValue = maxValue - gapValue + // while value is between min and max do nothing and let the value gets out of the bound and then apply value. + if ( minValue <= mValue && mValue <= maxValue ) return } + + let minDiff = Math.abs(minValue - mValue) + let maxDiff = Math.abs(maxValue - mValue) + + if ( minDiff === maxDiff ) { + if ( curHandler === RangeSlider.HandlerOption.Max ) checkLimitsMaxValueBounds(mValue) + else checkLimitsMinValueBounds(mValue) + } else { - if (minDiff < maxDiff) { - curValue = RangeSlider.HandlerOption.Min - minValue = value + if ( minDiff < maxDiff ) { + // if (minDiff > limitGap) return // not sure if it needs but kept it as an idea. + checkLimitsMinValueBounds(mValue) } else { - curValue = RangeSlider.HandlerOption.Max - maxValue = value + // if (maxDiff > limitGap) return // not sure if it needs but kept it as an idea. + checkLimitsMaxValueBounds(mValue) } } } + function checkLimitsMinValueBounds ( vValue ) { + if ( vValue < minValueLowerBound ) { minValue = minValueLowerBound; return } + if ( vValue > minValueUpperBound ) { minValue = minValueUpperBound; return } + setMinvalue(vValue) + } + + function checkLimitsMaxValueBounds ( vValue ) { + if ( vValue < maxValueLowerBound ) { maxValue = maxValueLowerBound; return } + if ( vValue > maxValueUpperBound ) { maxValue = maxValueUpperBound; return } + setMaxValue(vValue) + } + + function setMinvalue(vValue) { + minAdjusted = true + minValue = vValue + } + + function setMaxValue(vValue) { + maxAdjusted = true + maxValue = vValue + } + + /// The main range rectangle bar - This is the "highlighted" area of the slider RangeRect { id: _rangeRect - property int lowerBound : 0 - property int upperBound : 0 + property alias lowerBound : _rangeRect.minimum + property alias upperBound : _rangeRect.maximum + property real minmaxDiff : parent.maximum - parent.minimum + leftRightTouchMargin: _root.diameter / 2 - x : ((parent.width * (lowerBound - parent.minimum)) / (parent.maximum - parent.minimum)) + x : minmaxDiff ? ((parent.width * (lowerBound - parent.minimum)) / minmaxDiff) : minmaxDiff + width : minmaxDiff ? ((parent.width * (upperBound - lowerBound )) / minmaxDiff) : minmaxDiff height : parent.height - width : ((parent.width * (upperBound - lowerBound)) / (parent.maximum - parent.minimum)) + radius : _root.isRoundedEnds ? (height/2) : Variables.rangeRectRadius - radius: 0 - decimal: _root.decimal - minimum: lowerBound - maximum: upperBound + border.width: _root.hasBorder ? Variables.rangeRectBorderWidth : 0 + border.color: (_root.minAdjusted && _root.maxAdjusted) ? Colors.borderButton : Colors.borderDisableButton - minText.visible: true - maxText.visible: true + decimal : _root.decimal + minText { + visible: false + anchors.topMargin: -40 + } + maxText { + visible: false + anchors.topMargin: -40 + } + // propagation is not working on drag ! // so it has to be implemented here as well onDragged: { - setBound(vMouseEvent.x + _rangeRect.x) + // Add in the x position of the range rect to account for it moving on the + // main slider line and the displacement of the touch margin increase + setBound(vMouseEvent.x + _rangeRect.x - _rangeRect.leftRightTouchMargin) } - onClicked: { - setBound(vMouseEvent.x + _rangeRect.x) + + onReleased: { + // Add in the x position of the range rect to account for it moving on the + // main slider line and the displacement of the touch margin increase + setBound(vMouseEvent.x + _rangeRect.x - _rangeRect.leftRightTouchMargin) } } // used loader for performance since it may not always be required. // and can be a heavy Component + /// Tick mark on the slider regarding the defined step within minimum and maximum in the range Loader { id: _ticksLoader - active : ticks - anchors.fill : parent - sourceComponent : TickMarks { + active : _root.ticks + width : _root.width + height : (_rangeRect.height - Variables.sliderDefaultRoundTickMarkDiameter) + anchors.bottom : _root.bottom + sourceComponent : _root.showTickmarks ? _tickMarkFactory : undefine + } + + Component { id:_tickMarkFactory + TickMarks { decimal : _root.decimal minimum : _root.minimum maximum : _root.maximum step : _root.step stepSnap : _root.stepSnap textColor : _root.color + lineTickMarkHeight: _rangeRect.height + isTickMarkRound : _root.isTickMarksRound + yDisplacement : _root.border.width/2 - 1 // had to subtract 1 to center on the _rangeRect + showEndMarks : false + color: _root.isActive ? Colors.borderButton : Colors.borderDisableButton } } onDragged: { - setBound(vMouseEvent.x) + // Need to account for the extended touch areas + setBound(vMouseEvent.x - _rangeRect.leftRightTouchMargin) } - onClicked: { - setBound(vMouseEvent.x) + onReleased: { + // Need to account for the extended touch areas + setBound(vMouseEvent.x - _rangeRect.leftRightTouchMargin) } - minText.text: qsTr("LOW") - Rectangle { id : minVEdge - visible: true - color: bgColor - anchors.right: _root.left - anchors.top: _root.top - anchors.topMargin: - (height/2 - _rangeRect.height/2) - width: 2 - height: 30 + /// Left most maximum range vertical edge + Rectangle { id : _minVerticalEdge + visible : _root.minVerticalEdgeVisible + color : bgColor + anchors.right : _root.left + anchors.top : _root.top + anchors.topMargin : - (height/2 - _rangeRect.height/2) + width : 2 + height : 30 } - maxText.text: qsTr("HIGH") - Rectangle { id : maxVEdge - visible: true - color: bgColor - anchors.left: _root.right - anchors.top: _root.top - anchors.topMargin: - (height/2 - _rangeRect.height/2) - width: 2 - height: 30 + /// Right most minimum range vertical edge + Rectangle { id : _maxVerticalEdge + visible : _root.maxVerticalEdgeVisible + color : bgColor + anchors.left : _root.right + anchors.top : _root.top + anchors.topMargin : - (height/2 - _rangeRect.height/2) + width : 2 + height : 30 } + /// Left most handler Rectangle { id: _handlerLeft - property real diameter : Variables.progressbarHandler + property real diameter : _root.diameter anchors.verticalCenter : parent.verticalCenter anchors.horizontalCenter: _rangeRect.left @@ -228,24 +346,51 @@ radius : diameter color : Colors.highlightProgressBar border { - width: 4 - color: "white" + width : 4 + color : Colors.textMain } + MouseArea { id: _leftHandleMouseArea + propagateComposedEvents: true // propagate the click to the lower layer + anchors.fill: parent + onPressed: { + mouse.accepted = false // indicate that the current component did not handle the mouse action, propagate + _root.curHandler = RangeSlider.HandlerOption.Min + } + + onReleased: { + mouse.accepted = false // indicate that the current component did not handle the mouse action, propagate + _root.curHandler = RangeSlider.HandlerOption.None + } + } } + /// Right most handler Rectangle { id: _handlerRight - property real diameter : Variables.progressbarHandler + property real diameter : _root.diameter - anchors.verticalCenter : parent.verticalCenter - anchors.horizontalCenter: _rangeRect.right + anchors.verticalCenter : parent.verticalCenter + anchors.horizontalCenter : _rangeRect.right width : diameter height : diameter radius : diameter color : Colors.highlightProgressBar border { - width: 4 - color: "white" + width : 4 + color : Colors.textMain } + MouseArea { id: _rightHandlerMouseArea + propagateComposedEvents: true // propagate the click to the lower layer + anchors.fill: parent + + onPressed:{ + mouse.accepted = false // indicate that the current component did not handle the mouse action, propagate + _root.curHandler = RangeSlider.HandlerOption.Max + } + onReleased: { + mouse.accepted = false // indicate that the current component did not handle the mouse action, propagate + _root.curHandler = RangeSlider.HandlerOption.None + } + } } }