feat(control center): add fractional step sizes to pill widgets

This commit is contained in:
purian23
2026-09-19 01:52:24 -04:00
parent 79abcdfd7b
commit 77e60313ef
9 changed files with 55 additions and 30 deletions
+15 -14
View File
@@ -1,7 +1,8 @@
function packCells(cards, order, columns, isAvailable) {
function packCells(cards, order, columns, isAvailable, step = 1) {
const steps = Math.round(columns / step);
const cells = [];
const heights = [];
for (let c = 0; c < columns; c++)
for (let c = 0; c < steps; c++)
heights.push(0);
let rows = 0;
@@ -15,11 +16,11 @@ function packCells(cards, order, columns, isAvailable) {
continue;
}
const w = Math.max(1, Math.min(columns, card.w || 1));
const h = Math.max(1, card.h || 1);
const w = Math.max(1, Math.min(steps, Math.round((card.w || 1) / step)));
const h = Math.max(1, Math.round((card.h || 1) / step));
let bestX = 0;
let bestY = Infinity;
for (let x = 0; x + w <= columns; x++) {
for (let x = 0; x + w <= steps; x++) {
let y = 0;
for (let c = x; c < x + w; c++)
y = Math.max(y, heights[c]);
@@ -32,21 +33,21 @@ function packCells(cards, order, columns, isAvailable) {
heights[c] = bestY + h;
rows = Math.max(rows, bestY + h);
cells[sourceIndex] = {
"col": bestX,
"row": bestY,
"cols": w,
"rows": h
"col": bestX * step,
"row": bestY * step,
"cols": w * step,
"rows": h * step
};
}
return {
"cells": cells,
"rows": rows
"rows": rows * step
};
}
function packCards(cards, order, columns, width, gap, rowUnit, mirror, isAvailable) {
const packed = packCells(cards, order, columns, isAvailable);
function packCards(cards, order, columns, width, gap, rowUnit, mirror, isAvailable, step = 1) {
const packed = packCells(cards, order, columns, isAvailable, step);
const colW = (width - gap * (columns - 1)) / columns;
const slots = packed.cells.map(cell => {
if (!cell)
@@ -160,8 +161,8 @@ function crossGap(a, aSize, b, bSize) {
return Math.max(0, b - (a + aSize), a - (b + bSize));
}
function dimension(value, minimum, maximum, fallback) {
function dimension(value, minimum, maximum, fallback, step = 1) {
const min = minimum ?? 1;
const max = Math.max(min, maximum ?? fallback ?? min);
return Math.max(min, Math.min(max, Number.isInteger(value) ? value : (fallback ?? min)));
return Math.max(min, Math.min(max, typeof value === "number" && Number.isInteger(value / step) ? value : (fallback ?? min)));
}
@@ -10,6 +10,7 @@ Singleton {
readonly property int minimumColumns: 6
readonly property real sheetPadding: PopoutMetrics.contentPadding
readonly property real columnWidth: tileHeight
readonly property real gridStep: 0.5
property int columnPreview: 0
readonly property int gridColumns: columnPreview > 0 ? columnPreview : clampColumns(SettingsData.controlCenterColumns)
readonly property real sheetWidth: sheetWidthFor(gridColumns)
@@ -27,7 +27,7 @@ DankEditableGrid {
readonly property CcTileSlot draggingSlot: tileRepeater.itemAt(draggingSourceIndex) as CcTileSlot
sourceItems: (SettingsData.controlCenterWidgets || []).map(widget => Object.assign({}, widget, WidgetUtils.clampSize(widget, Infinity)))
slotLayout: GridUtils.packCards(layoutItems.map(widget => Object.assign({}, widget, WidgetUtils.clampSize(widget, columns, maximumRows))), visualOrder, columns, width, CcMetrics.gridGap, cellWidth - CcMetrics.gridGap, I18n.isRtl)
slotLayout: GridUtils.packCards(layoutItems.map(widget => Object.assign({}, widget, WidgetUtils.clampSize(widget, columns, maximumRows))), visualOrder, columns, width, CcMetrics.gridGap, cellWidth - CcMetrics.gridGap, I18n.isRtl, null, CcMetrics.gridStep)
placeholderRadius: draggingSlot?.tileItem?.bodyRadius ?? Theme.fullRadius(width, CcMetrics.tileHeight)
onReorderCommitted: items => model.reorderWidgets(items)
@@ -10,8 +10,8 @@ DankEditableGridSlot {
readonly property var widgetData: JSON.parse(json)
readonly property var sizeSpec: WidgetUtils.sizeSpec(widgetData.id || "", grid.columns, grid.maximumRows)
readonly property int cols: slot?.cols ?? 1
readonly property int rows: slot?.rows ?? 1
readonly property real cols: slot?.cols ?? 1
readonly property real rows: slot?.rows ?? 1
readonly property bool compact: cols <= 2 && rows === 1
readonly property var tileItem: tileLoader.item
@@ -22,10 +22,11 @@ DankEditableGridSlot {
}
onResizeRequested: (requestedWidth, requestedHeight) => {
let width = GridUtils.dimension(Math.round((requestedWidth + CcMetrics.gridGap) / grid.cellWidth), sizeSpec.minW, sizeSpec.maxW, sizeSpec.w);
let height = GridUtils.dimension(Math.round((requestedHeight + CcMetrics.gridGap) / grid.cellWidth), sizeSpec.minH, sizeSpec.maxH, sizeSpec.h);
const step = sizeSpec.step;
let width = GridUtils.dimension(Math.round((requestedWidth + CcMetrics.gridGap) / grid.cellWidth / step) * step, sizeSpec.minW, sizeSpec.maxW, sizeSpec.w, step);
let height = GridUtils.dimension(Math.round((requestedHeight + CcMetrics.gridGap) / grid.cellWidth / step) * step, sizeSpec.minH, sizeSpec.maxH, sizeSpec.h, step);
const current = WidgetUtils.clampSize(widgetData, grid.columns, grid.maximumRows);
if (WidgetUtils.isSliderWidget(widgetData.id) && width === 1 && height === 1) {
if (WidgetUtils.isSliderWidget(widgetData.id) && width < 2 && height < 2) {
if (width !== current.w && sizeSpec.maxH > 1)
height = 2;
else
@@ -14,8 +14,8 @@ Item {
property var host: null
property bool live: true
property bool interactive: true
property int columns: 4
property int rows: 1
property real columns: 4
property real rows: 1
property bool compact: columns <= 2 && rows === 1
property string iconName: ""
property string iconLabel: ""
@@ -17,8 +17,8 @@ Item {
property string title: ""
property string subtitle: ""
property bool active: false
property int columns: 4
property int rows: 1
property real columns: 4
property real rows: 1
property bool compact: columns <= 2 && rows === 1
property bool toggle: !opensPage
property Component expandedContent: null
@@ -4,8 +4,8 @@ import qs.Common
Item {
property CcTile tile: null
readonly property bool live: tile?.live ?? false
readonly property int columns: tile?.columns ?? 4
readonly property int rows: tile?.rows ?? 1
readonly property real columns: tile?.columns ?? 4
readonly property real rows: tile?.rows ?? 1
readonly property color contentColor: tile?.contentColor ?? Theme.surfaceText
readonly property color subtitleColor: tile?.subtitleColor ?? Theme.surfaceVariantText
}
@@ -1,4 +1,5 @@
.import qs.Common as Common
.import qs.Modules.ControlCenter as ControlCenter
.import "../../../Common/GridLayout.js" as GridLayout
function isSliderWidget(id) {
@@ -12,17 +13,18 @@ function sizeSpec(id, columns, rows = Infinity) {
"minW": 1,
"maxW": columns,
"minH": 1,
"maxH": rows
"maxH": rows,
"step": ControlCenter.CcMetrics.gridStep
};
}
function clampSize(widget, columns, rows = Infinity) {
const spec = sizeSpec(widget.id || "", columns, rows);
const size = {
"w": GridLayout.dimension(widget.w, spec.minW, spec.maxW, spec.w),
"h": GridLayout.dimension(widget.h, spec.minH, spec.maxH, spec.h)
"w": GridLayout.dimension(widget.w, spec.minW, spec.maxW, spec.w, spec.step),
"h": GridLayout.dimension(widget.h, spec.minH, spec.maxH, spec.h, spec.step)
};
if (!isSliderWidget(widget.id) || size.w > 1 || size.h > 1)
if (!isSliderWidget(widget.id) || size.w >= 2 || size.h >= 2)
return size;
if (rows > 1)
return {
@@ -209,6 +209,23 @@ ShellRoot {
});
grid.commitSize();
check(SettingsData.controlCenterWidgets[1].w === 6 && SettingsData.controlCenterWidgets[1].h === 12, "width edits preserve the saved height on a smaller screen");
grid.previewSize(1, {
w: 2.5
});
grid.commitSize();
grid.previewSize(1, {
h: 1.5
});
grid.commitSize();
const half = grid.slotLayout.slots[1];
const cell = grid.cellWidth - CcMetrics.gridGap;
check(SettingsData.controlCenterWidgets[1].w === 2.5 && SettingsData.controlCenterWidgets[1].h === 1.5 && half.cols === 2.5 && half.rows === 1.5, "half steps are saved on both axes");
check(Math.abs(half.w - (cell * 2.5 + CcMetrics.gridGap * 1.5)) < 0.01 && Math.abs(half.h - (cell * 1.5 + CcMetrics.gridGap * 0.5)) < 0.01, "half spans pack at half pitch");
check(WidgetUtils.clampSize({
id: "volumeSlider",
w: 1.5,
h: 1
}, 8, 1).w === 2, "sliders under two cells on both axes keep a usable track");
check(WidgetUtils.clampSize({
id: "volumeSlider",
w: 1,
@@ -230,6 +247,9 @@ ShellRoot {
sliderSlot.resizeRequested(grid.cellWidth - CcMetrics.gridGap, grid.cellWidth - CcMetrics.gridGap);
grid.commitSize();
check(SettingsData.controlCenterWidgets[0].w === 2 && SettingsData.controlCenterWidgets[0].h === 1, "shortening a slider retains a usable strip layout");
sliderSlot.resizeRequested(grid.cellWidth * 3.5 - CcMetrics.gridGap, grid.cellWidth - CcMetrics.gridGap);
grid.commitSize();
check(SettingsData.controlCenterWidgets[0].w === 3.5 && sliderSlot.tileItem.columns === 3.5, "resizing snaps to half columns");
grid.destroy();
wait(0);
const tile = tileComponent.createObject(scene);