4pragma ComponentBehavior: Bound
7import QtQuick.Templates as T
53 enum Variant { Standard, Connected }
60 enum SelectionMode { NoSelection, SingleSelection, MultiSelection }
69 enum Size { ExtraSmall, Small, Medium, Large, ExtraLarge }
75 enum Shape { Round, Square }
82 property bool selectionRequired:
false
88 property list<int> selectedIndexes: []
91 signal selectionChanged(list<int> selectedIndexes)
93 property bool __updatingSelection:
false
94 property var __effectiveSelection: []
95 property var __selectedObjects: []
96 property var __knownChildren: []
97 property bool __geometryInitialized:
false
98 readonly
property int __standardVariant:
ButtonGroup.Standard
99 readonly
property int __connectedVariant:
ButtonGroup.Connected
100 readonly
property int __singleSelectionMode:
ButtonGroup.SingleSelection
101 readonly
property int __multiSelectionMode:
ButtonGroup.MultiSelection
103 implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding
104 implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
107 Accessible.role: Accessible.Grouping
109 contentItem: ButtonGroupLayout {
114 function __arraysEqual(a, b) {
115 if (a.length !== b.length)
117 for (let i = 0; i < a.length; ++i) {
124 function __indexOf(item) {
125 for (let i = 0; i < count; ++i) {
126 if (itemAt(i) === item)
132 function __isSupported(item) {
133 return item && item.__buttonGroup !== undefined;
136 function __firstRequiredIndex() {
137 for (let i = 0; i < count; ++i) {
138 const item = itemAt(i);
139 if (__isSupported(item) && item.enabled && item.visible)
142 for (let i = 0; i < count; ++i) {
143 if (__isSupported(itemAt(i)))
149 function __canonicalIndexes(candidate) {
154 for (let i = 0; i < candidate.length; ++i) {
155 const value = Number(candidate[i]);
156 if (Number.isInteger(value) && value >= 0 && value < count
157 && __isSupported(itemAt(value)) && !unique[value]) {
158 unique[value] =
true;
162 result.sort((a, b) => a - b);
163 if (selectionMode ===
ButtonGroup.SingleSelection && result.length > 1)
165 if (selectionRequired && result.length === 0) {
166 const fallback = __firstRequiredIndex();
168 result.push(fallback);
173 function __commitSelection(candidate) {
174 if (__updatingSelection)
176 const canonical = __canonicalIndexes(candidate);
177 const changed = !__arraysEqual(canonical, __effectiveSelection);
178 __updatingSelection =
true;
179 if (!__arraysEqual(Array.from(selectedIndexes), canonical))
180 selectedIndexes = canonical;
181 for (let i = 0; i < count; ++i) {
182 const item = itemAt(i);
183 if (__isSupported(item))
184 item.checked = canonical.indexOf(i) >= 0;
186 __effectiveSelection = canonical.slice();
187 __selectedObjects = canonical.map(index => itemAt(index));
188 __updatingSelection =
false;
190 selectionChanged(canonical);
191 __updateFocusPolicies();
195 function __childCheckedChanged(item) {
196 if (__updatingSelection)
198 const index = __indexOf(item);
201 let candidate = __effectiveSelection.slice();
205 else if (candidate.indexOf(index) < 0)
206 candidate.push(index);
208 candidate = candidate.filter(value => value !== index);
210 __commitSelection(candidate);
213 function __configureChild(item) {
214 if (!__isSupported(item))
216 item.__buttonGroup = control;
219 item.checkable = selectionMode !==
ButtonGroup.NoSelection;
222 function __releaseChild(item) {
223 if (!item || item.__buttonGroup !== control)
225 item.__buttonGroup = null;
226 item.__groupTopLeftRadius = -1;
227 item.__groupTopRightRadius = -1;
228 item.__groupBottomLeftRadius = -1;
229 item.__groupBottomRightRadius = -1;
230 item.width = Qt.binding(() => item.implicitWidth);
235 function __childrenChanged() {
236 Qt.callLater(function() {
237 const preserved = [];
238 for (
const item of __selectedObjects) {
239 const index = __indexOf(item);
241 preserved.push(index);
243 __commitSelection(preserved);
244 __updateFocusPolicies();
249 function __syncChildren() {
251 for (let i = 0; i < count; ++i)
252 current.push(itemAt(i));
253 for (
const oldItem of __knownChildren) {
254 if (current.indexOf(oldItem) < 0)
255 __releaseChild(oldItem);
257 for (
const item of current)
258 __configureChild(item);
259 __knownChildren = current;
263 function __childGeometryChanged() {
267 function __childFocused(item) {
268 const index = __indexOf(item);
270 currentIndex = index;
271 __updateFocusPolicies();
275 function __focusableIndexes() {
277 for (let i = 0; i < count; ++i) {
278 const item = itemAt(i);
279 if (__isSupported(item) && item.enabled && item.visible)
285 function __updateFocusPolicies() {
286 const focusable = __focusableIndexes();
287 if (focusable.length > 0 && focusable.indexOf(currentIndex) < 0)
288 currentIndex = focusable[0];
289 for (let i = 0; i < count; ++i) {
290 const item = itemAt(i);
291 if (!__isSupported(item))
294 item.focusPolicy = Qt.StrongFocus;
296 item.focusPolicy = i === currentIndex ? Qt.StrongFocus : Qt.ClickFocus;
300 function __handleKey(item, event) {
303 const indexes = __focusableIndexes();
304 if (indexes.length === 0)
307 if (event.key === Qt.Key_Home)
309 else if (event.key === Qt.Key_End)
310 target = indexes[indexes.length - 1];
311 else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
312 const physical = indexes.slice().sort((a, b) => itemAt(a).x - itemAt(b).x);
313 let position = physical.indexOf(__indexOf(item));
314 const delta =
event.key === Qt.Key_Right ? 1 : -1;
315 position = (position + delta + physical.length) % physical.length;
316 target = physical[position];
320 currentIndex = target;
321 itemAt(target).forceActiveFocus(Qt.TabFocusReason);
322 __updateFocusPolicies();
326 function __squareOuterRadius(height) {
327 const tokens = MD.Tokens.button;
328 let value = tokens.containerShapeSquareSmall.topLeft;
330 case ButtonGroup.ExtraSmall: value = tokens.containerShapeSquareExtraSmall.topLeft;
break;
331 case ButtonGroup.Medium: value = tokens.containerShapeSquareMedium.topLeft;
break;
332 case ButtonGroup.Large: value = tokens.containerShapeSquareLarge.topLeft;
break;
333 case ButtonGroup.ExtraLarge: value = tokens.containerShapeSquareExtraLarge.topLeft;
break;
335 return Math.min(value, height / 2);
338 function __resolveCorner(value, width, height) {
339 return value === MD.Tokens.shape.cornerValueFull ? Math.min(width, height) / 2 : value;
342 function __applyConnectedCorners(items) {
343 for (let i = 0; i < count; ++i) {
344 const child = itemAt(i);
345 if (!child || child.__buttonGroup === undefined)
347 child.__groupTopLeftRadius = -1;
348 child.__groupTopRightRadius = -1;
349 child.__groupBottomLeftRadius = -1;
350 child.__groupBottomRightRadius = -1;
352 if (variant !==
ButtonGroup.Connected || items.length <= 1)
354 for (let i = 0; i < items.length; ++i) {
355 const child = items[i];
356 const height = child.background ? child.background.height : child.height;
357 const full = Math.min(child.width, height)
358 * MD.Tokens.buttonGroup.selectedInnerCornerPercentage / 100;
360 ? __resolveCorner(MD.Tokens.buttonGroup.connectedContainerShape.topLeft,
362 : __squareOuterRadius(height);
363 const inner = child.pressed ? MD.Tokens.buttonGroup.pressedInnerCorner
364 : MD.Tokens.buttonGroup.connectedInnerCorner;
365 let left = i === 0 ? outer : inner;
366 let right = i === items.length - 1 ? outer : inner;
372 if (child.checked && !child.pressed)
374 child.__groupTopLeftRadius = left;
375 child.__groupBottomLeftRadius = left;
376 child.__groupTopRightRadius = right;
377 child.__groupBottomRightRadius = right;
381 onSelectedIndexesChanged: {
382 if (!__updatingSelection)
383 __commitSelection(Array.from(selectedIndexes));
385 onVariantChanged: layout.updateGeometry()
386 onSelectionModeChanged: {
387 for (let i = 0; i < count; ++i) {
388 const item = itemAt(i);
389 if (item && item.__buttonGroup !== undefined)
390 item.checkable = selectionMode !==
ButtonGroup.NoSelection;
392 __commitSelection(__effectiveSelection);
394 onSelectionRequiredChanged: __commitSelection(__effectiveSelection)
396 for (let i = 0; i < count; ++i)
397 __configureChild(itemAt(i));
401 for (let i = 0; i < count; ++i)
402 __configureChild(itemAt(i));
405 onMirroredChanged: layout.updateGeometry()
406 onContentChildrenChanged: __syncChildren()
407 onCountChanged: __syncChildren()
409 Component.onCompleted: {
410 for (let i = 0; i < count; ++i)
411 __configureChild(itemAt(i));
412 __knownChildren = Array.from(contentChildren);
413 __commitSelection(Array.from(selectedIndexes));