import QtQuick import QtQuick.Controls import QtQuick.Layouts import Flora Dialog { id: root // Наведённый вариант (для живой подсказки), иначе — выбранный property string hoveredPreset: "" function presetById(id) { for (let i = 0; i < presets.length; ++i) if (presets[i].id === id) return presets[i] return presets[1] } property var shownPreset: presetById(hoveredPreset.length > 0 ? hoveredPreset : settingsStore.autosavePreset) property var presets: [ { id: "safe", name: qsTr("Максимум сохранности"), desc: qsTr("На диск — каждые 2 с, версия в историю — после 5 с паузы"), pros: [qsTr("Текст почти невозможно потерять"), qsTr("Подробная история — легко откатить любой момент")], cons: [qsTr("Больше коммитов при долгой печати"), qsTr("Чаще пишет на диск")], meters: [["disk", 5], ["commits", 4], ["load", 4]] }, { id: "balanced", name: qsTr("Сбалансированный"), desc: qsTr("На диск — каждые 5 с, версия в историю — после 15 с паузы"), pros: [qsTr("Разумный баланс потерь и чистоты истории"), qsTr("Версии только после паузы — без мусора")], cons: [qsTr("До 5 секунд текста живёт только в памяти")], meters: [["disk", 3], ["commits", 3], ["load", 3]] }, { id: "eco", name: qsTr("Экономный"), desc: qsTr("На диск — каждые 10 с, версия в историю — после минуты паузы"), pros: [qsTr("Минимум нагрузки — для слабых дисков и огромных хранилищ"), qsTr("История чистая, только вехи")], cons: [qsTr("До 10 секунд текста живёт только в памяти"), qsTr("Промежуточные мысли в историю не попадут")], meters: [["disk", 2], ["commits", 2], ["load", 2]] }, { id: "manual", name: qsTr("Вручную"), desc: qsTr("Без таймеров: Ctrl+S сохраняет на диск и делает версию"), pros: [qsTr("Полный контроль, ноль фоновой работы")], cons: [qsTr("Забыли Ctrl+S — потеряли"), qsTr("История растёт только от ваших рук")], meters: [["disk", 1], ["commits", 1], ["load", 1]] } ] function meterName(key) { if (key === "disk") return qsTr("Запись на диск") if (key === "commits") return qsTr("Коммиты в историю") return qsTr("Фоновая нагрузка") } title: qsTr("Настройки") modal: true width: 640 height: 620 anchors.centerIn: parent closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside standardButtons: Dialog.Close background: Rectangle { color: Theme.surface; border.color: Theme.borderStrong; radius: 10 } ColumnLayout { anchors.fill: parent spacing: 10 // Подсказка: как читать шкалу Rectangle { Layout.fillWidth: true color: Theme.bgAlt border.color: Theme.border radius: 8 Layout.preferredHeight: hintLabel.implicitHeight + 16 Label { id: hintLabel anchors.fill: parent anchors.margins: 8 wrapMode: Text.Wrap color: Theme.textSecondary font.pixelSize: 11 text: qsTr("Как выбирать: чем меньше галочек — тем проще и легче решение. " + "Берите самое простое, которого вам хватает: обычно это «Сбалансированный», " + "для слабого железа или огромных хранилищ — «Экономный».") } } Label { text: qsTr("Автосохранение") font.bold: true color: Theme.text font.pixelSize: 13 } ButtonGroup { id: presetGroup } Repeater { model: root.presets delegate: Rectangle { required property var modelData Layout.fillWidth: true height: 52 radius: 8 color: modelData.id === settingsStore.autosavePreset ? Theme.surfaceSelected : (rowHover.hovered ? Theme.surfaceHover : Theme.bgAlt) border.color: modelData.id === settingsStore.autosavePreset ? Theme.accent : Theme.border border.width: modelData.id === settingsStore.autosavePreset ? 1 : 0 RowLayout { anchors.fill: parent anchors.leftMargin: 8 anchors.rightMargin: 8 spacing: 8 RadioButton { checked: modelData.id === settingsStore.autosavePreset ButtonGroup.group: presetGroup onClicked: settingsStore.autosavePreset = modelData.id } ColumnLayout { Layout.fillWidth: true spacing: 2 Label { text: modelData.name font.bold: true color: Theme.text font.pixelSize: 12 } Label { text: modelData.desc color: Theme.textMuted font.pixelSize: 11 elide: Text.ElideRight Layout.fillWidth: true } } } HoverHandler { id: rowHover onHoveredChanged: { if (hovered) root.hoveredPreset = modelData.id else if (root.hoveredPreset === modelData.id) root.hoveredPreset = "" } } TapHandler { onTapped: settingsStore.autosavePreset = modelData.id } } } // Детали: плюсы/минусы + шкалы выбранного (или наведённого) варианта Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.bgAlt border.color: Theme.border radius: 8 ColumnLayout { anchors.fill: parent anchors.margins: 10 spacing: 6 Label { text: root.shownPreset.name font.bold: true color: Theme.text font.pixelSize: 12 } Repeater { model: root.shownPreset.pros delegate: Label { required property string modelData Layout.fillWidth: true wrapMode: Text.Wrap color: Theme.successText font.pixelSize: 11 text: "+ " + modelData } } Repeater { model: root.shownPreset.cons delegate: Label { required property string modelData Layout.fillWidth: true wrapMode: Text.Wrap color: Theme.errorText font.pixelSize: 11 text: "− " + modelData } } Rectangle { Layout.fillWidth: true; height: 1; color: Theme.divider } Repeater { model: root.shownPreset.meters delegate: RowLayout { id: meterRow required property var modelData property string meterKey: modelData[0] property int meterLevel: modelData[1] Layout.fillWidth: true spacing: 8 Label { text: root.meterName(meterRow.meterKey) color: Theme.textSecondary font.pixelSize: 11 Layout.preferredWidth: 140 } Row { spacing: 2 Repeater { model: 5 Label { required property int index text: "✓" font.pixelSize: 11 font.bold: true color: index < meterRow.meterLevel ? Theme.accent : Theme.textFaint } } } } } Item { Layout.fillHeight: true } } } } }