import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtGraphicalEffects 1.12 import QtQuick.VirtualKeyboard 2.4 Window { id: root visible: true width: 480 height: 480 title: "ButtonTask" color: "black" visibility: Window.FullScreen property bool darkMode: true function hideSensorsIfDisabled() { if (!settingsContainer.collectorEnabled && mainTabs.currentIndex > 0) mainTabs.currentIndex = 0 } BackgroundLayer { id: bg anchors.fill: parent } ColumnLayout { anchors.fill: parent anchors.margins: 10 spacing: 6 TabBar { id: mainTabs Layout.fillWidth: true Layout.preferredHeight: 36 background: Rectangle { color: "#161628" radius: 4 } TabButton { text: "Кнопки" width: implicitWidth contentItem: Text { text: parent.text color: parent.checked ? "#ffffff" : "#aaaaaa" font.pixelSize: 14 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } background: Rectangle { color: parent.checked ? "#2a2a40" : "transparent" radius: 4 } } TabButton { id: sensorsTabBtn text: "Датчики" visible: settingsContainer.collectorEnabled width: visible ? implicitWidth : 0 contentItem: Text { text: parent.text color: parent.checked ? "#ffffff" : "#aaaaaa" font.pixelSize: 14 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } background: Rectangle { color: parent.checked ? "#2a2a40" : "transparent" radius: 4 } } } StackLayout { Layout.fillWidth: true Layout.fillHeight: true currentIndex: mainTabs.currentIndex // --- Buttons --- Item { id: content Loader { id: viewLoader anchors.fill: parent sourceComponent: settingsContainer.layoutMode === "list" ? listComp : gridComp } Component { id: gridComp Item { id: gridRoot anchors.fill: parent readonly property int gridColumns: Math.max(1, settingsContainer.layoutColumns) readonly property real cellSide: Math.max(64, width / gridColumns) readonly property int gridRows: buttonsModel.count === 0 ? 0 : Math.ceil(buttonsModel.count / gridColumns) readonly property real gridContentHeight: gridRows * cellSide readonly property bool gridFits: gridContentHeight > 0 && gridContentHeight <= height GridView { id: gridView width: gridRoot.width height: gridRoot.gridFits ? gridRoot.gridContentHeight : gridRoot.height y: gridRoot.gridFits ? (gridRoot.height - gridRoot.gridContentHeight) / 2 : 0 cellWidth: gridRoot.cellSide cellHeight: gridRoot.cellSide model: buttonsModel clip: true interactive: !gridRoot.gridFits boundsBehavior: Flickable.StopAtBounds delegate: ButtonDelegate { width: gridView.cellWidth - settingsContainer.layoutSpacing height: gridView.cellHeight - settingsContainer.layoutSpacing showLabel: settingsContainer.showLabels } } } } Component { id: listComp SwipeView { id: swipeView anchors.fill: parent clip: true interactive: buttonsModel.count > 1 Repeater { model: buttonsModel Item { width: swipeView.width height: swipeView.height ButtonDelegate { anchors.centerIn: parent width: Math.min(parent.width, parent.height) * 0.8 height: width showLabel: settingsContainer.showLabels } } } } } Text { anchors.centerIn: parent visible: buttonsModel.count === 0 text: "Нет кнопок.\nОткройте настройки и добавьте кнопку." color: root.darkMode ? "#cccccc" : "#222222" font.pixelSize: 18 horizontalAlignment: Text.AlignHCenter } } // --- Sensors --- SensorsPage { id: sensorsPage } } } // Hidden hotspot: double-tap the top-right corner to open settings. MouseArea { id: settingsHotspot anchors.right: parent.right anchors.top: parent.top width: 72 height: 72 z: 20 onDoubleClicked: passwordDialog.open() } // Display recovery corner: holding it 10 s re-inits the panel (detected in // C++ RecoveryGesture). This area only shields buttons underneath; a // button reaching into the corner does not react to touches there. MouseArea { id: recoveryHotspot anchors.left: parent.left anchors.bottom: parent.bottom width: 72 height: 72 z: 20 } // Periodic announcement ("удерживайте кнопку"); config section "banner". InfoBanner { id: infoBanner anchors.left: parent.left anchors.right: parent.right anchors.margins: 12 y: settingsContainer.bannerPosition === "top" ? 12 : parent.height - height - 12 z: 30 suppressed: splash.active || root.secureDialogOpen } // Boot splash over everything in the window (dialogs live in the overlay // layer, but none can be open this early). SplashOverlay { id: splash z: 900 } Connections { target: settingsContainer onChanged: root.hideSensorsIfDisabled() } Component.onCompleted: hideSensorsIfDisabled() readonly property bool secureDialogOpen: passwordDialog.opened || settingsDialog.opened onSecureDialogOpenChanged: uiIdle.active = secureDialogOpen Connections { target: uiIdle onIdle: { if (passwordDialog.opened) passwordDialog.close() if (settingsDialog.opened) settingsDialog.close() } } Dialog { id: passwordDialog modal: true title: "Пароль" anchors.centerIn: parent width: Math.min(420, parent.width * 0.8) closePolicy: Popup.CloseOnEscape standardButtons: Dialog.Ok | Dialog.Cancel contentItem: ColumnLayout { spacing: 8 Item { Layout.fillWidth: true Layout.preferredHeight: 40 TextField { id: pwdField anchors.fill: parent placeholderText: "Введите пароль" echoMode: TextInput.Password selectByMouse: false onAccepted: passwordDialog.accept() onTextChanged: { passwordDialog.showLast = length > 0 if (length === 0) maskTimer.stop() else maskTimer.restart() if (cursorPosition !== length) cursorPosition = length } onCursorPositionChanged: { if (cursorPosition !== length) cursorPosition = length } } Rectangle { visible: passwordDialog.showLast && pwdField.length > 0 color: pwdField.palette.base readonly property real cell: Math.max( lastChar.implicitWidth, (pwdField.cursorRectangle.x - pwdField.leftPadding) / pwdField.length) x: pwdField.cursorRectangle.x - cell y: pwdField.cursorRectangle.y width: cell height: pwdField.cursorRectangle.height Text { id: lastChar anchors.centerIn: parent text: pwdField.text.charAt(pwdField.text.length - 1) font: pwdField.font color: pwdField.palette.text } } } Text { id: wrongPwd Layout.fillWidth: true visible: false color: "red" text: "Неверный пароль" } } property bool showLast: false Timer { id: maskTimer interval: 1000 onTriggered: passwordDialog.showLast = false } onOpened: { pwdField.text = "" showLast = false wrongPwd.visible = false pwdField.forceActiveFocus() } onAccepted: { if (btnController.checkPassword(pwdField.text)) { wrongPwd.visible = false pwdField.text = "" settingsDialog.open() } else { wrongPwd.visible = true Qt.callLater(passwordDialog.open) } } } SettingsDialog { id: settingsDialog onBannerPreview: infoBanner.preview() } InputPanel { id: inputPanel parent: Overlay.overlay z: 999 anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom visible: active } }