From ec0f90f5a7f3508523569b8f69963c1468621e32 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 27 Mar 2026 17:36:02 +0200 Subject: [PATCH 1/5] Draft component added --- app/qml/components/MMSegmentControl.qml | 116 ++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 app/qml/components/MMSegmentControl.qml diff --git a/app/qml/components/MMSegmentControl.qml b/app/qml/components/MMSegmentControl.qml new file mode 100644 index 000000000..e96405505 --- /dev/null +++ b/app/qml/components/MMSegmentControl.qml @@ -0,0 +1,116 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic + +/** + * A three-state segment control for boolean-like selection: All / True / False. + * + * Signals: + * - selectionChanged( int index ) — emitted when the selected segment changes; + * index maps to MMSegmentControl.Options enum values. + * + * Usage: + * MMSegmentControl { + * onSelectionChanged: function( index ) { console.log( index ) } + * } + */ +Item { + id: root + + enum Options { All, True, False } + + /* optional */ property int selectedIndex: MMSegmentControl.Options.All + + /* optional */ property string allText: qsTr( "All" ) + /* optional */ property string trueText: qsTr( "True" ) + /* optional */ property string falseText: qsTr( "False" ) + + signal selectionChanged( int index ) + + implicitWidth: row.implicitWidth + 2 * __style.margin8 + implicitHeight: __style.row45 + + Rectangle { + id: background + + anchors.fill: parent + radius: __style.radius16 + color: __style.lightGreenColor + } + + Row { + id: row + + anchors.fill: parent + anchors.margins: __style.margin4 + + spacing: 0 + + Repeater { + id: repeater + + model: [ + { text: root.allText, index: MMSegmentControl.Options.All }, + { text: root.trueText, index: MMSegmentControl.Options.True }, + { text: root.falseText, index: MMSegmentControl.Options.False } + ] + + delegate: Item { + id: segment + + readonly property bool isSelected: root.selectedIndex === modelData.index + + width: ( row.width - row.spacing * ( repeater.count - 1 ) ) / repeater.count + height: row.height + + Rectangle { + id: segmentBackground + + anchors.fill: parent + radius: __style.radius12 + + color: segment.isSelected ? __style.polarColor : __style.transparentColor + + border.color: segment.isSelected ? __style.forestColor : __style.transparentColor + border.width: segment.isSelected ? 1.5 * __dp : 0 + } + + MMText { + id: label + + anchors.centerIn: parent + + text: modelData.text + font: __style.t4 + color: segment.isSelected ? __style.forestColor : __style.nightColor + + fontSizeMode: Text.Fit + minimumPixelSize: 10 * __dp + horizontalAlignment: Text.AlignHCenter + + leftPadding: __style.margin8 + rightPadding: __style.margin8 + } + + MouseArea { + anchors.fill: parent + onClicked: { + if ( root.selectedIndex !== modelData.index ) { + root.selectedIndex = modelData.index + root.selectionChanged( modelData.index ) + } + } + } + } + } + } +} From b62499c83cfeef48012a643033434279a5d9cd5a Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Mon, 30 Mar 2026 18:12:40 +0300 Subject: [PATCH 2/5] Refined design of the MMSegmentControl Added the component to the checkspage Made the checkspage scrollable --- app/qml/components/MMSegmentControl.qml | 80 ++++++++++++------------- gallery/qml/pages/ChecksPage.qml | 44 +++++++++++++- 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/app/qml/components/MMSegmentControl.qml b/app/qml/components/MMSegmentControl.qml index e96405505..0df11d6da 100644 --- a/app/qml/components/MMSegmentControl.qml +++ b/app/qml/components/MMSegmentControl.qml @@ -6,22 +6,14 @@ * (at your option) any later version. * * * ***************************************************************************/ +pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls -import QtQuick.Controls.Basic /** - * A three-state segment control for boolean-like selection: All / True / False. - * - * Signals: - * - selectionChanged( int index ) — emitted when the selected segment changes; - * index maps to MMSegmentControl.Options enum values. - * - * Usage: - * MMSegmentControl { - * onSelectionChanged: function( index ) { console.log( index ) } - * } + * Three-state segment control: All / True / False. + * Emits selectionChanged( int index ) on tap; index maps to MMSegmentControl.Options. */ Item { id: root @@ -36,28 +28,30 @@ Item { signal selectionChanged( int index ) - implicitWidth: row.implicitWidth + 2 * __style.margin8 - implicitHeight: __style.row45 + implicitHeight: __style.row50 + implicitWidth: { + let maxW = Math.max( allMeasure.implicitWidth, trueMeasure.implicitWidth, falseMeasure.implicitWidth ) + return 3 * ( maxW + 2 * __style.margin20 ) + 2 * __style.margin13 + } - Rectangle { - id: background + MMText { id: allMeasure; text: root.allText; font: __style.t4; visible: false } + MMText { id: trueMeasure; text: root.trueText; font: __style.t4; visible: false } + MMText { id: falseMeasure; text: root.falseText; font: __style.t4; visible: false } + Rectangle { anchors.fill: parent - radius: __style.radius16 - color: __style.lightGreenColor + radius: __style.radius12 + color: __style.primaryColor } Row { - id: row - - anchors.fill: parent - anchors.margins: __style.margin4 + anchors.centerIn: parent + width: parent.width - 2 * __style.margin13 + height: parent.height - 2 * __style.margin8 spacing: 0 Repeater { - id: repeater - model: [ { text: root.allText, index: MMSegmentControl.Options.All }, { text: root.trueText, index: MMSegmentControl.Options.True }, @@ -67,34 +61,37 @@ Item { delegate: Item { id: segment - readonly property bool isSelected: root.selectedIndex === modelData.index + required property var modelData - width: ( row.width - row.spacing * ( repeater.count - 1 ) ) / repeater.count - height: row.height + readonly property bool isSelected: root.enabled && root.selectedIndex === segment.modelData.index + readonly property bool isAllOption: segment.modelData.index === MMSegmentControl.Options.All - Rectangle { - id: segmentBackground + width: parent.width / 3 + height: parent.height + Rectangle { anchors.fill: parent - radius: __style.radius12 + radius: __style.radius8 + + visible: segment.isSelected - color: segment.isSelected ? __style.polarColor : __style.transparentColor + color: segment.isAllOption ? __style.mediumGreenColor : __style.positiveColor - border.color: segment.isSelected ? __style.forestColor : __style.transparentColor - border.width: segment.isSelected ? 1.5 * __dp : 0 + border.color: segment.isAllOption ? __style.transparentColor : __style.forestColor + border.width: segment.isAllOption ? 0 : 1.5 * __dp } MMText { - id: label - anchors.centerIn: parent - text: modelData.text + text: segment.modelData.text font: __style.t4 - color: segment.isSelected ? __style.forestColor : __style.nightColor + color: { + if ( !root.enabled ) return __style.darkGreenColor + if ( segment.isSelected ) return __style.forestColor + return __style.nightColor + } - fontSizeMode: Text.Fit - minimumPixelSize: 10 * __dp horizontalAlignment: Text.AlignHCenter leftPadding: __style.margin8 @@ -103,10 +100,11 @@ Item { MouseArea { anchors.fill: parent + enabled: root.enabled onClicked: { - if ( root.selectedIndex !== modelData.index ) { - root.selectedIndex = modelData.index - root.selectionChanged( modelData.index ) + if ( root.selectedIndex !== segment.modelData.index ) { + root.selectedIndex = segment.modelData.index + root.selectionChanged( segment.modelData.index ) } } } diff --git a/gallery/qml/pages/ChecksPage.qml b/gallery/qml/pages/ChecksPage.qml index 9e097a4b1..f335c58a5 100644 --- a/gallery/qml/pages/ChecksPage.qml +++ b/gallery/qml/pages/ChecksPage.qml @@ -15,9 +15,13 @@ import "../../app/qml/account/components" as MMAccountComponents import "../../app/qml/components" as MMComponents import "../../app/qml/inputs" -Column { - padding: 20 - spacing: 20 +ScrollView { + anchors.fill: parent + + Column { + width: parent.width + padding: 20 + spacing: 20 GroupBox { title: "MMComponents.MMCheckBox" @@ -160,6 +164,39 @@ Column { } } + GroupBox { + title: "MMComponents.MMSegmentControl" + background: Rectangle { + color: "lightGray" + border.color: "gray" + } + label: Label { + color: "black" + text: parent.title + padding: 5 + } + + Column { + spacing: 10 + anchors.fill: parent + + MMComponents.MMSegmentControl {} + + MMComponents.MMSegmentControl { + selectedIndex: MMComponents.MMSegmentControl.Options.True + } + + MMComponents.MMSegmentControl { + selectedIndex: MMComponents.MMSegmentControl.Options.False + onSelectionChanged: function( index ) { console.log( "selected:", index ) } + } + + MMComponents.MMSegmentControl { + enabled: false + } + } + } + GroupBox { title: "MMComponents.MMSwitch" background: Rectangle { @@ -196,3 +233,4 @@ Column { } } } +} From eaf2554bc933d66fe26287c59b0e126f4ebf14e9 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Tue, 31 Mar 2026 09:56:27 +0300 Subject: [PATCH 3/5] Added segment control component to CMakeLists --- app/qml/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt index ad6db84a0..31087d47d 100644 --- a/app/qml/CMakeLists.txt +++ b/app/qml/CMakeLists.txt @@ -55,6 +55,7 @@ set(MM_QML components/MMToolbar.qml components/MMToolbarButton.qml components/MMSingleClickMouseArea.qml + components/MMSegmentControl.qml components/private/MMBaseInput.qml components/private/MMBaseSingleLineInput.qml components/private/MMToolbarLongButton.qml From 433e2be006bcff959b3c34551307aaf07bfe965b Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Tue, 31 Mar 2026 13:21:23 +0300 Subject: [PATCH 4/5] Removed unnecessary comments Matched the font to Figma design --- app/qml/components/MMSegmentControl.qml | 38 ++++++++++--------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/app/qml/components/MMSegmentControl.qml b/app/qml/components/MMSegmentControl.qml index 0df11d6da..2e2252df1 100644 --- a/app/qml/components/MMSegmentControl.qml +++ b/app/qml/components/MMSegmentControl.qml @@ -9,34 +9,29 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Controls -/** - * Three-state segment control: All / True / False. - * Emits selectionChanged( int index ) on tap; index maps to MMSegmentControl.Options. - */ Item { id: root enum Options { All, True, False } - /* optional */ property int selectedIndex: MMSegmentControl.Options.All + property int selectedIndex: MMSegmentControl.Options.All - /* optional */ property string allText: qsTr( "All" ) - /* optional */ property string trueText: qsTr( "True" ) - /* optional */ property string falseText: qsTr( "False" ) + property string allText: qsTr( "All" ) + property string trueText: qsTr( "True" ) + property string falseText: qsTr( "False" ) signal selectionChanged( int index ) implicitHeight: __style.row50 implicitWidth: { let maxW = Math.max( allMeasure.implicitWidth, trueMeasure.implicitWidth, falseMeasure.implicitWidth ) - return 3 * ( maxW + 2 * __style.margin20 ) + 2 * __style.margin13 + return 3 * ( maxW + 2 * __style.margin20 ) + 2 * __style.margin12 } - MMText { id: allMeasure; text: root.allText; font: __style.t4; visible: false } - MMText { id: trueMeasure; text: root.trueText; font: __style.t4; visible: false } - MMText { id: falseMeasure; text: root.falseText; font: __style.t4; visible: false } + MMText { id: allMeasure; text: root.allText; font: __style.t3; visible: false } + MMText { id: trueMeasure; text: root.trueText; font: __style.t3; visible: false } + MMText { id: falseMeasure; text: root.falseText; font: __style.t3; visible: false } Rectangle { anchors.fill: parent @@ -46,11 +41,9 @@ Item { Row { anchors.centerIn: parent - width: parent.width - 2 * __style.margin13 + width: parent.width - 2 * __style.margin12 height: parent.height - 2 * __style.margin8 - spacing: 0 - Repeater { model: [ { text: root.allText, index: MMSegmentControl.Options.All }, @@ -78,24 +71,23 @@ Item { color: segment.isAllOption ? __style.mediumGreenColor : __style.positiveColor border.color: segment.isAllOption ? __style.transparentColor : __style.forestColor - border.width: segment.isAllOption ? 0 : 1.5 * __dp + border.width: segment.isAllOption ? 0 : 1.0 * __dp } MMText { anchors.centerIn: parent text: segment.modelData.text - font: __style.t4 + font: { + // bold only if selected + if ( segment.isSelected ) return __style.t3 + return __style.p5 + } color: { if ( !root.enabled ) return __style.darkGreenColor if ( segment.isSelected ) return __style.forestColor return __style.nightColor } - - horizontalAlignment: Text.AlignHCenter - - leftPadding: __style.margin8 - rightPadding: __style.margin8 } MouseArea { From 7bfcc75cd3d55b968a3bcbea8fcd88be7a45e9d1 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Wed, 1 Apr 2026 09:38:21 +0300 Subject: [PATCH 5/5] Implemented code findings --- app/qml/components/MMSegmentControl.qml | 83 +++++++++++-------------- gallery/qml/pages/ChecksPage.qml | 2 +- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/app/qml/components/MMSegmentControl.qml b/app/qml/components/MMSegmentControl.qml index 2e2252df1..8dcc7a603 100644 --- a/app/qml/components/MMSegmentControl.qml +++ b/app/qml/components/MMSegmentControl.qml @@ -17,26 +17,13 @@ Item { property int selectedIndex: MMSegmentControl.Options.All - property string allText: qsTr( "All" ) - property string trueText: qsTr( "True" ) - property string falseText: qsTr( "False" ) - - signal selectionChanged( int index ) - implicitHeight: __style.row50 - implicitWidth: { - let maxW = Math.max( allMeasure.implicitWidth, trueMeasure.implicitWidth, falseMeasure.implicitWidth ) - return 3 * ( maxW + 2 * __style.margin20 ) + 2 * __style.margin12 - } - - MMText { id: allMeasure; text: root.allText; font: __style.t3; visible: false } - MMText { id: trueMeasure; text: root.trueText; font: __style.t3; visible: false } - MMText { id: falseMeasure; text: root.falseText; font: __style.t3; visible: false } + implicitWidth: 3 * ( __style.row50 + 2 * __style.margin20 ) + 2 * __style.margin12 Rectangle { anchors.fill: parent radius: __style.radius12 - color: __style.primaryColor + color: __style.polarColor } Row { @@ -45,58 +32,62 @@ Item { height: parent.height - 2 * __style.margin8 Repeater { - model: [ - { text: root.allText, index: MMSegmentControl.Options.All }, - { text: root.trueText, index: MMSegmentControl.Options.True }, - { text: root.falseText, index: MMSegmentControl.Options.False } - ] + model: 3 delegate: Item { id: segment - required property var modelData + required property int index - readonly property bool isSelected: root.enabled && root.selectedIndex === segment.modelData.index - readonly property bool isAllOption: segment.modelData.index === MMSegmentControl.Options.All + readonly property bool isSelected: root.enabled && root.selectedIndex === index + readonly property bool isAllOption: index === MMSegmentControl.Options.All width: parent.width / 3 height: parent.height + // button background Rectangle { - anchors.fill: parent + anchors.fill: segment radius: __style.radius8 - visible: segment.isSelected - - color: segment.isAllOption ? __style.mediumGreenColor : __style.positiveColor + color: segment.isSelected ? ( segment.isAllOption ? __style.mediumGreenColor : __style.positiveColor ) : __style.transparentColor - border.color: segment.isAllOption ? __style.transparentColor : __style.forestColor - border.width: segment.isAllOption ? 0 : 1.0 * __dp - } + border.color: ( segment.isSelected && !segment.isAllOption ) ? __style.forestColor : __style.transparentColor + border.width: ( segment.isSelected && !segment.isAllOption ) ? 1.0 * __dp : 0 - MMText { - anchors.centerIn: parent + MMText { + anchors.centerIn: parent - text: segment.modelData.text - font: { - // bold only if selected - if ( segment.isSelected ) return __style.t3 - return __style.p5 - } - color: { - if ( !root.enabled ) return __style.darkGreenColor - if ( segment.isSelected ) return __style.forestColor - return __style.nightColor + text: { + switch ( segment.index ) { + // 0 for All + case MMSegmentControl.Options.All: return qsTr( "All" ) + // 1 for True + case MMSegmentControl.Options.True: return qsTr( "True" ) + // 2 for False + case MMSegmentControl.Options.False: return qsTr( "False" ) + } + return "" + } + font: { + // bold only if selected + if ( segment.isSelected ) return __style.t3 + return __style.p5 + } + color: { + if ( !root.enabled ) return __style.mediumGreyColor + if ( segment.isSelected ) return __style.forestColor + return __style.nightColor + } } } MouseArea { - anchors.fill: parent + anchors.fill: segment enabled: root.enabled onClicked: { - if ( root.selectedIndex !== segment.modelData.index ) { - root.selectedIndex = segment.modelData.index - root.selectionChanged( segment.modelData.index ) + if ( root.selectedIndex !== segment.index ) { + root.selectedIndex = segment.index } } } diff --git a/gallery/qml/pages/ChecksPage.qml b/gallery/qml/pages/ChecksPage.qml index f335c58a5..491ea87df 100644 --- a/gallery/qml/pages/ChecksPage.qml +++ b/gallery/qml/pages/ChecksPage.qml @@ -188,7 +188,7 @@ ScrollView { MMComponents.MMSegmentControl { selectedIndex: MMComponents.MMSegmentControl.Options.False - onSelectionChanged: function( index ) { console.log( "selected:", index ) } + onSelectedIndexChanged: { console.log( "selected:", selectedIndex ) } } MMComponents.MMSegmentControl {