// The resolution list for one display. // // One line per resolution, not per mode: this panel reports 35 modes, most of // which differ only in refresh-rate rounding, and a flat list of // "3840x2160@60.00Hz" strings is a wall of near-identical text rather than a // choice. The rates live in their own row on the page, where changing only the // rate does not mean going back through the resolution you already had. // // Each line carries the two facts that decide the choice: the shape of the // picture, and whether this is the one the panel was built for. // // Applying is the page's job. Every per-display edit on the Displays page goes // through one funnel so the whole record -- position, color, mirror state -- // rides the same keep-or-revert transaction; this reports which resolution was // asked for and lets that funnel do the rest. import QtQuick import qs.config Column { id: root property var monitor: null property bool enabled: true // The mode string for the resolution that was chosen, at the rate closest // to the one in use. signal picked(string mode) spacing: 0 readonly property var grouped: { if (!root.monitor) return []; const buckets = {}; const order = []; for (const mode of root.monitor.modes) { const key = mode.label; if (!buckets[key]) { buckets[key] = { label: key, width: mode.width, height: mode.height, rates: [] }; order.push(key); } buckets[key].rates.push(mode); } return order.map(key => buckets[key]); } // Modes arrive sorted by area, so the first is the largest the panel // advertises -- which is the one it was built for. readonly property string nativeLabel: root.grouped.length > 0 ? root.grouped[0].label : "" function aspectLabel(width: int, height: int): string { if (!width || !height) return ""; const ratio = width / height; const named = [ { ratio: 1, label: "1:1" }, { ratio: 5 / 4, label: "5:4" }, { ratio: 4 / 3, label: "4:3" }, { ratio: 3 / 2, label: "3:2" }, { ratio: 16 / 10, label: "16:10" }, { ratio: 16 / 9, label: "16:9" }, { ratio: 21 / 9, label: "21:9" }, { ratio: 32 / 9, label: "32:9" } ]; for (const entry of named) { if (Math.abs(ratio - entry.ratio) < 0.05) return entry.label; } const reduce = (a, b) => b === 0 ? a : reduce(b, a % b); const divisor = reduce(width, height) || 1; return `${Math.round(width / divisor)}:${Math.round(height / divisor)}`; } // The rate nearest the one in use, so changing the resolution does not // quietly change the refresh rate as well when the panel offers the same // one at the new size. function modeFor(group: var): string { const target = root.monitor ? root.monitor.refreshRate : 0; const rates = group.rates.slice().sort((left, right) => Math.abs(left.refresh - target) - Math.abs(right.refresh - target) || right.refresh - left.refresh); return rates.length > 0 ? rates[0].mode : ""; } Repeater { model: root.grouped SettingRow { id: resolution required property var modelData required property int index readonly property bool isCurrent: !!root.monitor && root.monitor.width === resolution.modelData.width && root.monitor.height === resolution.modelData.height readonly property bool isNative: resolution.modelData.label === root.nativeLabel readonly property string tag: { if (resolution.isCurrent && resolution.isNative) return "Current ยท Native"; if (resolution.isCurrent) return "Current"; return resolution.isNative ? "Native" : ""; } width: parent.width label: resolution.modelData.label controlWidth: 170 divider: resolution.index < root.grouped.length - 1 opacity: root.enabled ? 1 : 0.45 activatable: root.enabled && !resolution.isCurrent onActivated: { const mode = root.modeFor(resolution.modelData); if (mode !== "") root.picked(mode); } Row { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: 10 Text { anchors.verticalCenter: parent.verticalCenter text: root.aspectLabel(resolution.modelData.width, resolution.modelData.height) color: Theme.fgMuted font.family: Theme.fontFamily font.features: Theme.tabularFigures font.pixelSize: Theme.fontSizeSmall } Rectangle { anchors.verticalCenter: parent.verticalCenter visible: resolution.tag !== "" width: visible ? tagLabel.implicitWidth + 18 : 0 height: 21 radius: Theme.pillRadius color: resolution.isCurrent ? Theme.alpha(Theme.accent, 0.1) : Theme.alpha(Theme.fg, 0.06) border.width: 1 border.color: resolution.isCurrent ? Theme.alpha(Theme.accent, 0.25) : Theme.alpha(Theme.fg, 0.1) Text { id: tagLabel anchors.centerIn: parent text: resolution.tag color: resolution.isCurrent ? Theme.accentAlt : Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Math.max(9, Theme.fontSizeSmall - 1) font.weight: Font.DemiBold } } } } } }