import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import qt.koriolan.shipheart 1.0 // ============================================================================ // home.qml (client shipheart_wasm) - 4 domaines (circuit/batterie/capteur/ // reservoir), via l'API réelle (DataProvider). Écriture (consigne) sur // circuit uniquement pour l'instant. // // Architecture : les onglets (familles) partagent UN SEUL ListModel (forme // identique dans les 4 domaines : id/libelle/etat/actif). Les ÉLÉMENTS, en // revanche, utilisent UN MODÈLE SÉPARÉ PAR DOMAINE - piège QML rencontré en // pratique : un ListModel fige les champs reconnus ("rôles") dès sa première // utilisation, même après clear() ; réutiliser un même modèle pour des formes // JSON différentes (tension/intensite pour circuit, valeur/unite pour // capteur...) laisse les champs propres à chaque domaine à 'undefined'. // ============================================================================ Shipheart_page { id: page_accueil readonly property var domaines: ["circuit", "batterie", "capteur", "reservoir"] readonly property var domaines_libelles: [qsTr("Circuits"), qsTr("Batteries"), qsTr("Capteurs"), qsTr("Réservoirs")] property int domaine_actif_index: 0 readonly property string domaine_actif: domaines[domaine_actif_index] readonly property int espace_onglets: 10 readonly property int rayon_onglets: 20 property int id_groupe: 0 ListModel { id: modele_onglets } // Un ListModel par domaine, pour éviter le piège des "rôles" figés de QML : ListModel { id: modele_elements_circuit } ListModel { id: modele_elements_batterie } ListModel { id: modele_elements_capteur } ListModel { id: modele_elements_reservoir } property var modele_elements_actuel: modele_elements_circuit function modele_elements_pour(type) { switch (type) { case "circuit": return modele_elements_circuit; case "batterie": return modele_elements_batterie; case "capteur": return modele_elements_capteur; case "reservoir": return modele_elements_reservoir; } return null; } property DataProvider dataProvider: DataProvider {} function charger_domaine(index) { domaine_actif_index = index; id_groupe = 0; modele_onglets.clear(); modele_elements_actuel = modele_elements_pour(domaine_actif); modele_elements_actuel.clear(); dataProvider.get_familles(domaine_actif); } Connections { target: dataProvider function onFamillesRecues(type, familles) { if (type !== domaine_actif) return; // Réponse d'un domaine quitté entretemps - ignorée modele_onglets.clear(); for (var i = 0; i < familles.length; i++) { var f = familles[i]; modele_onglets.append({ "id": f.id, "libelle": f.nom, "etat": f.etat, "actif": (f.id === id_groupe) }); if (id_groupe === 0 && i === 0) { id_groupe = f.id; dataProvider.get_elements(domaine_actif, id_groupe); } } } function onElementsRecus(type, famille, elements) { if (type !== domaine_actif || famille !== id_groupe) return; var m = modele_elements_pour(type); m.clear(); for (var j = 0; j < elements.length; j++) m.append(elements[j]); } function onCircuitModifie(id, succes) { if (!succes) console.log("Échec de la commande sur le circuit " + id); if (domaine_actif === "circuit" && id_groupe > 0) dataProvider.get_elements("circuit", id_groupe); } function onErreur(message) { console.log("DataProvider erreur : " + message); } } Component.onCompleted: charger_domaine(0) Timer { interval: 1000 running: true repeat: true onTriggered: { if (id_groupe > 0) { dataProvider.get_elements(domaine_actif, id_groupe); dataProvider.get_familles(domaine_actif); } } } ColumnLayout { anchors.fill: parent spacing: 10 //----------------------------------------------------------- // Sélecteur de domaine : Row { Layout.fillWidth: true height: shipheart_hauteur_bandeau_haut spacing: espace_onglets Repeater { model: domaines_libelles delegate: Rectangle { width: (page_accueil.width - 3 * espace_onglets) / 4 height: shipheart_hauteur_bandeau_haut radius: rayon_onglets color: (index === domaine_actif_index) ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton Text { anchors.centerIn: parent text: modelData color: couleur_blanc_casse font.pixelSize: 13 font.bold: (index === domaine_actif_index) } MouseArea { anchors.fill: parent onClicked: charger_domaine(index) } } } } //----------------------------------------------------------- // Onglets (familles du domaine actif) : Rectangle { Layout.fillWidth: true height: shipheart_hauteur_bandeau_haut color: "transparent" visible: modele_onglets.count > 1 GridView { id: grille_onglets anchors.fill: parent cellWidth: width / Math.max(1, Math.min(modele_onglets.count, 5)) cellHeight: shipheart_hauteur_bandeau_haut model: modele_onglets clip: true delegate: Rectangle { width: grille_onglets.cellWidth - 2 height: grille_onglets.cellHeight radius: rayon_onglets color: model.actif ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton border.color: (model.etat === 2) ? "red" : (model.etat === 1) ? "orange" : "transparent" border.width: 2 Text { text: model.libelle anchors.centerIn: parent color: couleur_blanc_casse font.pixelSize: 14 font.bold: model.actif } MouseArea { anchors.fill: parent onClicked: { id_groupe = model.id; for (var k = 0; k < modele_onglets.count; k++) { modele_onglets.set(k, {"actif": (modele_onglets.get(k).id === model.id)}); } dataProvider.get_elements(domaine_actif, id_groupe); } } } } } //----------------------------------------------------------- // Éléments de la famille sélectionnée, dans le domaine actif : GridView { id: elements_grid Layout.fillWidth: true Layout.fillHeight: true cellWidth: width / 2 cellHeight: height / 4 model: modele_elements_actuel clip: true delegate: Rectangle { width: elements_grid.cellWidth - 4 height: elements_grid.cellHeight - 4 radius: 10 color: shipheart_couleur_fond_bouton border.color: (model.etat === 2) ? "red" : (model.etat === 1) ? "orange" : "transparent" border.width: 2 Column { anchors.centerIn: parent spacing: 4 Text { text: model.nom color: couleur_blanc_casse font.pixelSize: 14 font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: { switch (domaine_actif) { case "circuit": return model.tension.toFixed(1) + " V " + model.intensite.toFixed(2) + " A"; case "batterie": return model.niveau.toFixed(0) + " % " + model.tension.toFixed(2) + " V"; case "capteur": return model.valeur + (model.unite ? (" " + model.unite) : ""); case "reservoir": return model.niveau.toFixed(0) + " % (" + model.capacite.toFixed(0) + " L)"; default: return ""; } } color: couleur_blanc_casse font.pixelSize: 12 anchors.horizontalCenter: parent.horizontalCenter } // Indication visuelle discrète que l'élément est cliquable (circuits) : Text { visible: (domaine_actif === "circuit") text: (model.consigne > 0) ? qsTr("● Marche (toucher pour arrêter)") : qsTr("○ Arrêt (toucher pour démarrer)") color: (model.consigne > 0) ? "lightgreen" : couleur_blanc_casse font.pixelSize: 10 anchors.horizontalCenter: parent.horizontalCenter } } MouseArea { anchors.fill: parent enabled: (domaine_actif === "circuit") onClicked: { var nouvelle_consigne = (model.consigne > 0) ? 0 : 100; dataProvider.set_circuit(model.id, nouvelle_consigne); } } } Label { anchors.centerIn: parent visible: elements_grid.count === 0 text: qsTr("Aucun élément dans ce groupe.") color: couleur_rouge_transparent } } } }