Diferència entre revisions de la pàgina «Ús d'arxius vectorials SVG al QML (Qt6)»

De binefa.com
Salta a la navegació Salta a la cerca
Línia 805: Línia 805:
 
En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer `main.qml`, cap a una estructura modular on components reutilitzables (`AmpMeter`, `Botonera`, `BarraLliscant`) s'organitzen en components de més alt nivell (`Vista`, `Controlador`), millorant la mantenibilitat i la claredat del codi.
 
En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer `main.qml`, cap a una estructura modular on components reutilitzables (`AmpMeter`, `Botonera`, `BarraLliscant`) s'organitzen en components de més alt nivell (`Vista`, `Controlador`), millorant la mantenibilitat i la claredat del codi.
  
\= Modularitat i separació de responsabilitats =
 
L'evolució dels codis, prenent com a referència inicial '''ampMeter00\_05''', mostra una progressió cap a una major modularitat i control de la interfície d'usuari a través de la introducció de nous components QML i la separació de responsabilitats.
 
  
\=== '''ampMeter00\_05''' (Punt de referència inicial per a la modularitat i separació de responsabilitats) ===
+
= Modularitat i separació de responsabilitats =
En aquesta versió, el fitxer \<code\>main.qml\</code\> configura una finestra amb un \<code\>Rectangle\</code\> blanc. Inclou dos components QML personalitzats: \<code\>AmpMeter\</code\> i \<code\>Botonera\</code\>.
+
L'evolució dels codis, prenent com a referència inicial '''ampMeter00 _05''', mostra una progressió cap a una major modularitat i control de la interfície d'usuari a través de la introducció de nous components QML i la separació de responsabilitats.
  
  * El component '''\<code\>AmpMeter\</code\>''' es centra a la pantalla i gestiona la visualització del fons i l'agulla d'un amperímetre. La rotació de l'agulla es controla mitjançant una propietat \<code\>rotation\</code\> exposada i animada amb una \<code\>NumberAnimation\</code\> que utilitza \<code\>Easing.InOutBack\</code\>.
+
=== '''ampMeter00 _05''' (Punt de referència inicial per a la modularitat i separació de responsabilitats) ===
  * El component '''\<code\>Botonera\</code\>''' es col·loca a la part inferior i es centra horitzontalment. S'alimenta d'un \<code\>ListModel\</code\> anomenat \<code\>buttonModel\</code\> que conté les etiquetes i els valors de rotació per als botons. Quan un botó és clicat, emet un senyal \<code\>buttonClicked\</code\> amb el seu valor, i aquest valor s'assigna a la propietat \<code\>rotation\</code\> del component \<code\>ampMeter\</code\>.
+
En aquesta versió, el fitxer  <code >main.qml </code > configura una finestra amb un <code >Rectangle </code > blanc. Inclou dos components QML personalitzats:  <code >AmpMeter </code > i <code >Botonera </code >.
  
\=== '''ampMeter00\_06''' (Introducció de Múltiples AmpMeters) ===
+
  * El component ''' <code >AmpMeter </code >''' es centra a la pantalla i gestiona la visualització del fons i l'agulla d'un amperímetre. La rotació de l'agulla es controla mitjançant una propietat  <code >rotation </code > exposada i animada amb una  <code >NumberAnimation </code > que utilitza  <code >Easing.InOutBack </code >.
Aquesta versió es titula "ampMeter00\_06 - Triple" i incrementa l'amplada de la finestra a 960 píxels. La millora principal és la capacitat de mostrar i controlar '''tres components \<code\>AmpMeter\</code\> simultàniament'''.
+
  * El component ''' <code >Botonera </code >''' es col·loca a la part inferior i es centra horitzontalment. S'alimenta d'un  <code >ListModel </code > anomenat  <code >buttonModel </code > que conté les etiquetes i els valors de rotació per als botons. Quan un botó és clicat, emet un senyal  <code >buttonClicked </code > amb el seu valor, i aquest valor s'assigna a la propietat  <code >rotation </code > del component  <code >ampMeter </code >.
  
  * Els tres \<code\>AmpMeter\</code\> (\<code\>ampMeter1\</code\>, \<code\>ampMeter2\</code\>, \<code\>ampMeter3\</code\>) estan organitzats en una \<code\>Row\</code\> (fila) centrada, amb un desplaçament vertical per deixar espai als controls. Cadascun té dimensions i escala definides de manera individual (per exemple, \<code\>ampMeter1\</code\> i \<code\>ampMeter3\</code\> amb \<code\>scale: 0.5\</code\>, i \<code\>ampMeter2\</code\> amb \<code\>scale: 0.8\</code\>).
+
=== '''ampMeter00 _06''' (Introducció de Múltiples AmpMeters) ===
  * El \<code\>ListModel\</code\> dels botons es manté, però el gestor \<code\>onButtonClicked\</code\> a \<code\>main.qml\</code\> ara actualitza la propietat \<code\>rotation\</code\> dels '''tres''' components \<code\>AmpMeter\</code\> de forma idèntica (\<code\>ampMeter1.rotation = value\</code\>, \<code\>ampMeter2.rotation = value\</code\>, \<code\>ampMeter3.rotation = value\</code\>).
+
Aquesta versió es titula "ampMeter00 _06 - Triple" i incrementa l'amplada de la finestra a 960 píxels. La millora principal és la capacitat de mostrar i controlar '''tres components <code >AmpMeter </code > simultàniament'''.
  * Els components \<code\>Botonera.qml\</code\> i \<code\>AmpMeter.qml\</code\> conserven la seva estructura anterior.
 
  
\=== '''ampMeter00\_07''' (Incorporació d'un lliscador o ''slider'') ===
+
  * Els tres  <code >AmpMeter </code > ( <code >ampMeter1 </code >,  <code >ampMeter2 </code >,  <code >ampMeter3 </code >) estan organitzats en una  <code >Row </code > (fila) centrada, amb un desplaçament vertical per deixar espai als controls. Cadascun té dimensions i escala definides de manera individual (per exemple,  <code >ampMeter1 </code > i  <code >ampMeter3 </code > amb  <code >scale: 0.5 </code >, i  <code >ampMeter2 </code > amb  <code >scale: 0.8 </code >).
 +
  * El  <code >ListModel </code > dels botons es manté, però el gestor  <code >onButtonClicked </code > a  <code >main.qml </code > ara actualitza la propietat  <code >rotation </code > dels '''tres''' components  <code >AmpMeter </code > de forma idèntica ( <code >ampMeter1.rotation = value </code >,  <code >ampMeter2.rotation = value </code >,  <code >ampMeter3.rotation = value </code >).
 +
  * Els components  <code >Botonera.qml </code > i  <code >AmpMeter.qml </code > conserven la seva estructura anterior.
 +
 
 +
=== '''ampMeter00 _07''' (Incorporació d'un lliscador o ''slider'') ===
 
Continuant amb la configuració "Triple", aquesta versió afegeix un nou element de control: un slider (lliscador).
 
Continuant amb la configuració "Triple", aquesta versió afegeix un nou element de control: un slider (lliscador).
  
   * Es mantenen els tres components \<code\>AmpMeter\</code\> en fila.
+
   * Es mantenen els tres components <code >AmpMeter </code > en fila.
   * S'introdueix un \<code\>Column\</code\> (\<code\>sliderContainer\</code\>) que conté la lògica del slider, situat just a sobre dels botons de control.
+
   * S'introdueix un <code >Column </code > ( <code >sliderContainer </code >) que conté la lògica del slider, situat just a sobre dels botons de control.
   * Aquest slider consisteix en un \<code\>Rectangle\</code\> que serveix de pista i un altre \<code\>Rectangle\</code\> (\<code\>sliderHandle\</code\>) que fa de mànec lliscant.
+
   * Aquest slider consisteix en un <code >Rectangle </code > que serveix de pista i un altre <code >Rectangle </code > ( <code >sliderHandle </code >) que fa de mànec lliscant.
   * Un \<code\>MouseArea\</code\> permet arrossegar el \<code\>sliderHandle\</code\> només en l'eix X (\<code\>drag.axis: Drag.XAxis\</code\>).
+
   * Un <code >MouseArea </code > permet arrossegar el <code >sliderHandle </code > només en l'eix X ( <code >drag.axis: Drag.XAxis </code >).
   * El gestor \<code\>onPositionChanged\</code\> calcula un valor (\<code\>value\</code\>) entre -60 i +60, el converteix en un angle de rotació entre -45 i +45, i l'aplica '''exclusivament a \<code\>ampMeter2\</code\>''' (\<code\>ampMeter2.rotation = rotation\</code\>).
+
   * El gestor <code >onPositionChanged </code > calcula un valor ( <code >value </code >) entre -60 i +60, el converteix en un angle de rotació entre -45 i +45, i l'aplica '''exclusivament a <code >ampMeter2 </code >''' ( <code >ampMeter2.rotation = rotation </code >).
 
   * Es mostren etiquetes per als valors extrems (-60 i +60) i el valor actual del slider.
 
   * Es mostren etiquetes per als valors extrems (-60 i +60) i el valor actual del slider.
   * La \<code\>Botonera\</code\> ara només controla \<code\>ampMeter1\</code\> i \<code\>ampMeter3\</code\>, ja que \<code\>ampMeter2\</code\> és controlat pel nou slider.
+
   * La <code >Botonera </code > ara només controla <code >ampMeter1 </code > i <code >ampMeter3 </code >, ja que <code >ampMeter2 </code > és controlat pel nou slider.
  
\=== '''ampMeter00\_08''' (Modularització del lliscador amb \<code\>BarraLliscant.qml\</code\>) ===
+
=== '''ampMeter00 _08''' (Modularització del lliscador amb <code >BarraLliscant.qml </code >) ===
En aquesta etapa, la lògica del lliscador es refactoritza en un component QML independent anomenat '''\<code\>BarraLliscant.qml\</code\>'''.
+
En aquesta etapa, la lògica del lliscador es refactoritza en un component QML independent anomenat ''' <code >BarraLliscant.qml </code >'''.
  
   * A \<code\>main.qml\</code\>, el \<code\>sliderContainer\</code\> es reemplaça per una instància del component \<code\>BarraLliscant\</code\>.
+
   * A <code >main.qml </code >, el <code >sliderContainer </code > es reemplaça per una instància del component <code >BarraLliscant </code >.
   * \<code\>BarraLliscant.qml\</code\> encapsula tota la interfície i la lògica del lliscador, incloent les etiquetes, la pista, el mànec, el càlcul del valor i la visualització del valor actual.
+
   * <code >BarraLliscant.qml </code > encapsula tota la interfície i la lògica del lliscador, incloent les etiquetes, la pista, el mànec, el càlcul del valor i la visualització del valor actual.
   * Defineix propietats configurables com \<code\>minValue\</code\>, \<code\>maxValue\</code\>, \<code\>sliderWidth\</code\> i \<code\>sliderHeight\</code\>.
+
   * Defineix propietats configurables com <code >minValue </code >, <code >maxValue </code >, <code >sliderWidth </code > i <code >sliderHeight </code >.
   * Emet un senyal \<code\>valueChanged(real value)\</code\> quan el valor del lliscador canvia.
+
   * Emet un senyal <code >valueChanged(real value) </code > quan el valor del lliscador canvia.
   * A \<code\>main.qml\</code\>, el senyal \<code\>onValueChanged\</code\> de la \<code\>BarraLliscant\</code\> s'utilitza per convertir el valor del lliscador en rotació i aplicar-lo a \<code\>ampMeter2\</code\>.
+
   * A <code >main.qml </code >, el senyal <code >onValueChanged </code > de la <code >BarraLliscant </code > s'utilitza per convertir el valor del lliscador en rotació i aplicar-lo a <code >ampMeter2 </code >.
   * El component \<code\>AmpMeter.qml\</code\> introdueix propietats \<code\>fondoSource\</code\> i \<code\>agullaSource\</code\> per personalitzar les imatges, tot i que inicialment mantenen els mateixos valors per defecte.
+
   * El component <code >AmpMeter.qml </code > introdueix propietats <code >fondoSource </code > i <code >agullaSource </code > per personalitzar les imatges, tot i que inicialment mantenen els mateixos valors per defecte.
   * La \<code\>Botonera\</code\> continua controlant \<code\>ampMeter1\</code\> i \<code\>ampMeter3\</code\>.
+
   * La <code >Botonera </code > continua controlant <code >ampMeter1 </code > i <code >ampMeter3 </code >.
  
\=== '''ampMeter00\_09''' (Modularització completa amb \<code\>Vista.qml\</code\> i \<code\>Controlador.qml\</code\>) ===
+
=== '''ampMeter00 _09''' (Modularització completa amb <code >Vista.qml </code > i <code >Controlador.qml </code >) ===
Aquesta és la versió més modularitzada, amb el títol "ampMeter00\_09 - Modular". \<code\>main.qml\</code\> se simplifica enormement delegant les responsabilitats a dos nous components de nivell superior: '''\<code\>Vista\</code\>''' i '''\<code\>Controlador\</code\>'''.
+
Aquesta és la versió més modularitzada, amb el títol "ampMeter00 _09 - Modular". <code >main.qml </code > se simplifica enormement delegant les responsabilitats a dos nous components de nivell superior: ''' <code >Vista </code >''' i ''' <code >Controlador </code >'''.
  
   * '''\<code\>Vista.qml\</code\>''': Aquest component agrupa els tres components \<code\>AmpMeter\</code\> en una \<code\>Row\</code\>.
+
   * ''' <code >Vista.qml </code >''': Aquest component agrupa els tres components <code >AmpMeter </code > en una <code >Row </code >.
    \*\* Exposa les propietats \<code\>ampMeter1Rotation\</code\>, \<code\>ampMeter2Rotation\</code\>, \<code\>ampMeter3Rotation\</code\> com àlies per controlar la rotació de cada \<code\>AmpMeter\</code\> des del seu pare.
+
    * * Exposa les propietats <code >ampMeter1Rotation </code >, <code >ampMeter2Rotation </code >, <code >ampMeter3Rotation </code > com àlies per controlar la rotació de cada <code >AmpMeter </code > des del seu pare.
    \*\* Permet personalitzar l'escala de cada \<code\>AmpMeter\</code\> (\<code\>ampMeter1Scale\</code\>, \<code\>ampMeter2Scale\</code\>, \<code\>ampMeter3Scale\</code\>).
+
    * * Permet personalitzar l'escala de cada <code >AmpMeter </code > ( <code >ampMeter1Scale </code >, <code >ampMeter2Scale </code >, <code >ampMeter3Scale </code >).
    \*\* Una novetat important és que cada \<code\>AmpMeter\</code\> dins de la \<code\>Vista\</code\> pot tenir un tipus d'animació d'''easing''' diferent (\<code\>Easing.InOutQuad\</code\>, \<code\>Easing.InOutBack\</code\>, \<code\>Easing.InOutCubic\</code\>), cosa que requereix una nova propietat \<code\>easingType\</code\> a \<code\>AmpMeter.qml\</code\>.
+
    * * Una novetat important és que cada <code >AmpMeter </code > dins de la <code >Vista </code > pot tenir un tipus d'animació d'''easing''' diferent ( <code >Easing.InOutQuad </code >, <code >Easing.InOutBack </code >, <code >Easing.InOutCubic </code >), cosa que requereix una nova propietat <code >easingType </code > a <code >AmpMeter.qml </code >.
   * '''\<code\>Controlador.qml\</code\>''': Aquest component agrupa la \<code\>BarraLliscant\</code\> i la \<code\>Botonera\</code\> en una \<code\>Column\</code\>.
+
   * ''' <code >Controlador.qml </code >''': Aquest component agrupa la <code >BarraLliscant </code > i la <code >Botonera </code > en una <code >Column </code >.
    \*\* Rep el \<code\>buttonModel\</code\> per a la \<code\>Botonera\</code\>.
+
    * * Rep el <code >buttonModel </code > per a la <code >Botonera </code >.
    \*\* Emet senyals \<code\>sliderValueChanged(real value)\</code\> i \<code\>buttonClicked(real value)\</code\> per comunicar els esdeveniments dels seus components interns al seu pare.
+
    * * Emet senyals <code >sliderValueChanged(real value) </code > i <code >buttonClicked(real value) </code > per comunicar els esdeveniments dels seus components interns al seu pare.
    \*\* Connecta el senyal \<code\>onValueChanged\</code\> de la seva \<code\>BarraLliscant\</code\> interna al seu propi \<code\>sliderValueChanged\</code\>.
+
    * * Connecta el senyal <code >onValueChanged </code > de la seva <code >BarraLliscant </code > interna al seu propi <code >sliderValueChanged </code >.
    \*\* Connecta el senyal \<code\>onButtonClicked\</code\> de la seva \<code\>Botonera\</code\> interna al seu propi \<code\>buttonClicked\</code\>.
+
    * * Connecta el senyal <code >onButtonClicked </code > de la seva <code >Botonera </code > interna al seu propi <code >buttonClicked </code >.
   * A \<code\>main.qml\</code\>, el gestor \<code\>onSliderValueChanged\</code\> del \<code\>Controlador\</code\> ara actualitza \<code\>vista.ampMeter2Rotation\</code\>.
+
   * A <code >main.qml </code >, el gestor <code >onSliderValueChanged </code > del <code >Controlador </code > ara actualitza <code >vista.ampMeter2Rotation </code >.
   * El gestor \<code\>onButtonClicked\</code\> del \<code\>Controlador\</code\> actualitza \<code\>vista.ampMeter1Rotation\</code\> i \<code\>vista.ampMeter3Rotation\</code\>.
+
   * El gestor <code >onButtonClicked </code > del <code >Controlador </code > actualitza <code >vista.ampMeter1Rotation </code > i <code >vista.ampMeter3Rotation </code >.
  
En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer \<code\>main.qml\</code\>, cap a una estructura modular on components reutilitzables (\<code\>AmpMeter\</code\>, \<code\>Botonera\</code\>, \<code\>BarraLliscant\</code\>) s'organitzen en components de més alt nivell (\<code\>Vista\</code\>, \<code\>Controlador\</code\>), millorant la mantenibilitat i la claredat del codi.
+
En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer <code >main.qml </code >, cap a una estructura modular on components reutilitzables ( <code >AmpMeter </code >, <code >Botonera </code >, <code >BarraLliscant </code >) s'organitzen en components de més alt nivell ( <code >Vista </code >, <code >Controlador </code >), millorant la mantenibilitat i la claredat del codi.

Revisió del 14:12, 10 set 2025

Exemple d'ús d'arxius SVG

Codis s'aquesta pàgina al GitHub

Explicació de main.qml d'ampMeter00_00

El fitxer main.qml de ampMeter00_00 estableix la base d'una aplicació Qt Quick que simula un amperímetre simple [1].

Estructura bàsica de la finestra

  • import QtQuick: Importa el mòdul Qt Quick necessari per als elements de la interfície d'usuari [1].
  • Window: Defineix la finestra principal de l'aplicació [1].
    • width: 640, height: 480: Estableix les dimensions de la finestra [1].
    • visible: true: Fa que la finestra sigui visible quan s'inicia l'aplicació [1].
    • title: qsTr("ampMeter00"): Defineix el títol de la finestra com "ampMeter00" [1].

Element arrel (Rectangle)

  • Rectangle { id: root ... }: Dins la finestra, hi ha un Rectangle anomenat root que serveix com a contenidor principal [1].
  • anchors.fill: parent: Fa que aquest rectangle ompli completament la finestra pare [1].
  • color: "white": Estableix el color de fons del rectangle com a blanc [1].

Imatge de fons de l'amperímetre

  • Image { id: fonsSvg ... }: S'afegeix un element Image per al fons de l'amperímetre [1].
  • source: "qrc:/material/AmpMeterFons_c.svg": Carrega la imatge de fons des dels recursos de l'aplicació [1].
  • fillMode: Image.PreserveAspectFit: Aquesta propietat assegura que la imatge mantingui les seves proporcions originals mentre s'ajusta dins de l'espai disponible [1].
  • anchors.centerIn: parent: Centra la imatge de fons dins del seu element pare (root rectangle) [1].

Imatge de l'agulla de l'amperímetre

  • Image { id: agullaSvg ... }: S'afegeix un altre element Image per representar l'agulla de l'amperímetre [2].
  • source: "qrc:/material/AmpMeterAgulla_c.svg": Carrega la imatge de l'agulla [2].
  • fillMode: Image.PreserveAspectFit: Igual que amb el fons, manté les proporcions de l'agulla [2].
  • anchors.centerIn: parent: Centra l'agulla dins del seu pare (root rectangle) [2].
  • rotation: 45: Estableix una rotació inicial de 45 graus per a l'agulla [2]. Aquest és el punt clau de control en aquesta primera versió, ja que la rotació es defineix de manera estàtica [2].

En resum, ampMeter00_00 presenta una interfície d'usuari bàsica amb un fons i una agulla d'amperímetre, on l'agulla es mostra en una posició fixa de 45 graus [1, 2].

main.qml:

// main.qml
import QtQuick // [1]
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_00")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        Image {
            id: fonsSvg
            source: "qrc:/material/AmpMeterFons_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
        }
        Image { // [2]
            id: agullaSvg
            source: "qrc:/material/AmpMeterAgulla_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
            rotation: 45
        }
    }
}

Canvis Progressius en el Codi

A continuació, s'expliquen els canvis introduïts en cada iteració del projecte:

Interactivitat i animació (d'ampMeter00_00 a ampMeter00_01)

La versió ampMeter00_01 introdueix la interactivitat i l'animació per a l'agulla de l'amperímetre.

  • Rotació Inicial i Opacitat de l'Agulla:
    • La rotation inicial de l'agulla (agullaSvg) es canvia a 0.
    • S'afegeix una propietat opacity: 0.8 a l'agulla.
  • Animació de Rotació (Behavior):
    • S'afegeix un Behavior on rotation a l'agulla. Això significa que cada vegada que la propietat rotation de l'agulla canvia, s'executarà una animació.
    • NumberAnimation { duration: 300; easing.type: Easing.InOutBack }: L'animació durarà 300 mil·lisegons i utilitzarà una funció d'acceleració Easing.InOutBack per a un efecte més dinàmic.
  • Botons de Control:
    • S'afegeix una fila de botons (Row) a la part inferior de la pantalla, anomenada controlButtons.
    • Aquests botons es centren horitzontalment (anchors.horizontalCenter: parent.horizontalCenter) i es col·loquen a 20 píxels de la part inferior (anchors.bottomMargin: 20).
    • Hi ha un spacing: 20 entre els botons.
    • Cada botó és un Rectangle gris amb un width i height de 60 píxels i un radius de 30 (creant cercles).
    • Cada Rectangle conté un Text blanc amb la seva etiqueta (per exemple, "-60", "0", "60").
    • Cada botó té una MouseArea que detecta clics. Quan es clica, la rotation de l'agulla (agullaSvg.rotation) es defineix a un valor específic (-45, -20, 0, 20, 45).
    • El cursorShape de la MouseArea es defineix com Qt.PointingHandCursor per indicar que és un element clicable.

Aquesta versió permet a l'usuari controlar la rotació de l'agulla mitjançant botons, amb una animació suau cada vegada que l'agulla canvia de posició.

main.qml:

// main.qml
import QtQuick
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_01")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        Image {
            id: fonsSvg
            source: "qrc:/material/AmpMeterFons_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
        }
        Image {
            id: agullaSvg
            source: "qrc:/material/AmpMeterAgulla_c.svg"
            fillMode: Image.PreserveAspectFit
            opacity: 0.8
            anchors.centerIn: parent
            rotation: 0

            Behavior on rotation {
                NumberAnimation {
                    duration: 300
                    easing.type: Easing.InOutBack
                }
            }
        }

        Row {
            id: controlButtons
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: 20
            spacing: 20

            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "-60"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = -45
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "-30"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = -20
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "0"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = 0
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "30"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = 20
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "60"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = 45
                    cursorShape: Qt.PointingHandCursor
                }
            }
        }
    }
}

Animació automàtica i contínua (d'ampMeter00_01 a ampMeter00_02)

La versió ampMeter00_02 afegeix una animació automàtica i contínua per a l'agulla.

  • Canvi de Funció d'Acceleració:
    • La funció d'acceleració (easing.type) del NumberAnimation de l'agulla es canvia de Easing.InOutBack a Easing.InOutQuad.
  • Animació Seqüencial:
    • S'afegeix una SequentialAnimation on rotation a l'agulla (agullaSvg).
    • loops: Animation.Infinite: Aquesta animació es repetirà indefinidament.
    • Consisteix en dues RotationAnimations:
      • Una que fa girar l'agulla fins a 45 graus durant 1000 mil·lisegons.
      • Una segona que la fa girar fins a -45 graus durant 500 mil·lisegons.
    • Aquesta animació fa que l'agulla es mogui contínuament entre 45 i -45 graus quan no es controla manualment.
  • Els botons de control manual romanen igual, permetent a l'usuari sobrescriure l'animació automàtica.

main.qml:

// main.qml
import QtQuick
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_02")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        Image {
            id: fonsSvg
            source: "qrc:/material/AmpMeterFons_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
        }
        Image {
            id: agullaSvg
            source: "qrc:/material/AmpMeterAgulla_c.svg"
            fillMode: Image.PreserveAspectFit
            opacity: 0.8
            anchors.centerIn: parent
            rotation: 0

            Behavior on rotation {
                NumberAnimation {
                    duration: 300
                    easing.type: Easing.InOutQuad
                }
            }

            SequentialAnimation on rotation {
                loops: Animation.Infinite

                RotationAnimation {
                    to: 45
                    duration: 1000
                }

                RotationAnimation {
                    to: -45
                    duration: 500
                }
            }
        }

        Row {
            id: controlButtons
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: 20
            spacing: 20

            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "-60"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = -45
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "-30"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = -20
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "0"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = 0
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "30"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = 20
                    cursorShape: Qt.PointingHandCursor
                }
            }
            Rectangle {
                width: 60
                height: 60
                radius: 30
                color: "gray"

                Text {
                    anchors.centerIn: parent
                    text: "60"
                    color: "white"
                    font.pixelSize: 14
                    font.bold: true
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: agullaSvg.rotation = 45
                    cursorShape: Qt.PointingHandCursor
                }
            }
        }
    }
}

Model de dades ListModel i repetició Repeater (d'ampMeter00_02 a ampMeter00_03)

La versió ampMeter00_03 refactoritza el codi dels botons utilitzant un model de dades (ListModel) i un Repeater, millorant la modularitat i la facilitat de manteniment.

  • Eliminació de l'Animació Seqüencial:
    • La SequentialAnimation on rotation s'elimina.
  • Model de Dades per a Botons (ListModel):
    • Es defineix un ListModel amb id: buttonModel.
    • Conté ListElements, cadascun amb una label (el text del botó) i un value (el valor de rotació corresponent). Per exemple, { label: "-60"; value: -45 }.
  • Generació de Botons amb Repeater:
    • En lloc de declarar cada botó individualment, s'utilitza un Repeater amb model: buttonModel dins del Row de controlButtons.
    • El Repeater crea un Rectangle per a cada element del model.
    • El Text de cada botó utilitza model.label i l'esdeveniment onClicked de la MouseArea utilitza agullaSvg.rotation = model.value.

Aquest canvi redueix la duplicitat de codi i facilita l'addició o modificació de botons en el futur.

main.qml:

// main.qml
import QtQuick
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_03")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        Image {
            id: fonsSvg
            source: "qrc:/material/AmpMeterFons_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
        }
        Image {
            id: agullaSvg
            source: "qrc:/material/AmpMeterAgulla_c.svg"
            fillMode: Image.PreserveAspectFit
            opacity: 0.8
            anchors.centerIn: parent
            rotation: 0

            Behavior on rotation {
                NumberAnimation {
                    duration: 300
                    easing.type: Easing.InOutBack
                }
            }
        }

        ListModel {
            id: buttonModel
            ListElement { label: "-60"; value: -45 }
            ListElement { label: "-30"; value: -20 }
            ListElement { label: "0"; value: 0 }
            ListElement { label: "30"; value: 20 }
            ListElement { label: "60"; value: 45 }
        }

        Row {
            id: controlButtons
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: 20
            spacing: 20

            Repeater {
                model: buttonModel

                Rectangle {
                    width: 60
                    height: 60
                    radius: 30
                    color: "gray"

                    Text {
                        anchors.centerIn: parent
                        text: model.label
                        color: "white"
                        font.pixelSize: 14
                        font.bold: true
                    }

                    MouseArea {
                        anchors.fill: parent
                        onClicked: agullaSvg.rotation = model.value
                        cursorShape: Qt.PointingHandCursor
                    }
                }
            }
        }
    }
}

Component Botonera. QML reutilitzable (d'ampMeter00_03 a ampMeter00_04)

La versió ampMeter00_04 millora la modularitat extraient la lògica dels botons en un component QML reutilitzable anomenat Botonera.qml.

  • Creació del Component Botonera.qml:
    • Es crea un nou fitxer Botonera.qml que conté la implementació del Row i el Repeater per als botons.
    • Aquest component té una propietat property alias model: repeater.model que permet al component pare passar-li el ListModel.
    • Defineix un signal buttonClicked(real value) que s'emet quan es prem un botó, enviant el value de rotació.
    • Dins del MouseArea de cada botó, onClicked ara crida botonera.buttonClicked(model.value).
  • Ús del Component Botonera a main.qml:
    • A main.qml, el Row original de botons es reemplaça per una instància del nou component Botonera.
    • controlButtons (la instància de Botonera) rep el buttonModel mitjançant model: buttonModel.
    • Captura el senyal onButtonClicked del component Botonera i actualitza la rotació de l'agulla amb agullaSvg.rotation = value.

Aquesta refactorització permet reutilitzar el bloc de botons en altres parts de l'aplicació o en altres projectes, fent el codi de main.qml més net i centrat en la composició.

main.qml:

// main.qml
import QtQuick
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_04")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        Image {
            id: fonsSvg
            source: "qrc:/material/AmpMeterFons_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
        }
        Image {
            id: agullaSvg
            source: "qrc:/material/AmpMeterAgulla_c.svg"
            fillMode: Image.PreserveAspectFit
            opacity: 0.8
            anchors.centerIn: parent
            rotation: 0

            Behavior on rotation {
                NumberAnimation {
                    duration: 300
                    easing.type: Easing.InOutBack
                }
            }
        }

        ListModel {
            id: buttonModel
            ListElement { label: "-60"; value: -45 }
            ListElement { label: "-30"; value: -20 }
            ListElement { label: "0"; value: 0 }
            ListElement { label: "30"; value: 20 }
            ListElement { label: "60"; value: 45 }
        }

        Botonera {
            id: controlButtons
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: 20
            model: buttonModel
            onButtonClicked: function(value) {
                agullaSvg.rotation = value
            }
        }
    }
}

Botonera.qml:

// Botonera.qml
import QtQuick

Row {
    id: botonera
    spacing: 20

    property alias model: repeater.model
    signal buttonClicked(real value)

    Repeater {
        id: repeater

        Rectangle {
            width: 60
            height: 60
            radius: 30
            color: "gray"

            Text {
                anchors.centerIn: parent
                text: model.label
                color: "white"
                font.pixelSize: 14
                font.bold: true
            }

            MouseArea {
                anchors.fill: parent
                onClicked: botonera.buttonClicked(model.value)
                cursorShape: Qt.PointingHandCursor
            }
        }
    }
}

Component AmpMeter (d' ampMeter00_04 a ampMeter00_05)

La versió ampMeter00_05 continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un component QML propi anomenat AmpMeter.qml.

  • Creació del Component AmpMeter.qml:
    • Es crea un nou fitxer AmpMeter.qml que conté el fons (fonsSvg) i l'agulla (agullaSvg) de l'amperímetre.
    • Exposa una propietat property alias rotation: agullaSvg.rotation per permetre al component pare controlar directament la rotació de l'agulla interna.
    • També defineix propietats per personalitzar les imatges de fons i agulla (fonsSource, agullaSource) i l'opacitat de l'agulla (agullaOpacity).
    • El Behavior on rotation per a l'agulla es troba ara dins d'aquest component.
  • Ús del Component AmpMeter a main.qml:
    • A main.qml, les dues imatges originals (fonsSvg i agullaSvg) es reemplacen per una instància del nou component AmpMeter.
    • L'esdeveniment onButtonClicked del Botonera ara actualitza la rotació de l'amperímetre amb ampMeter.rotation = value, utilitzant la propietat exposada pel component AmpMeter.

Aquesta última refactorització crea un component autònom (AmpMeter) que conté tota la lògica i la presentació de l'amperímetre, fent que main.qml sigui extremadament simple i es basi en la composició de components.

main.qml:

// main.qml
import QtQuick
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_05")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        AmpMeter {
            id: ampMeter
            anchors.centerIn: parent
            width: parent.width
            height: parent.height
        }

        ListModel {
            id: buttonModel
            ListElement { label: "-60"; value: -45 }
            ListElement { label: "-30"; value: -20 }
            ListElement { label: "0"; value: 0 }
            ListElement { label: "30"; value: 20 }
            ListElement { label: "60"; value: 45 }
        }

        Botonera {
            id: controlButtons
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: 20
            model: buttonModel
            onButtonClicked: function(value) {
                ampMeter.rotation = value
            }
        }
    }
}

Botonera.qml:

// Botonera.qml
import QtQuick

Row {
    id: botonera
    spacing: 20

    property alias model: repeater.model
    signal buttonClicked(real value)

    Repeater {
        id: repeater

        Rectangle {
            width: 60
            height: 60
            radius: 30
            color: "gray"

            Text {
                anchors.centerIn: parent
                text: model.label
                color: "white"
                font.pixelSize: 14
                font.bold: true
            }

            MouseArea {
                anchors.fill: parent
                onClicked: botonera.buttonClicked(model.value)
                cursorShape: Qt.PointingHandCursor
            }
        }
    }
}

AmpMeter.qml:

// AmpMeter.qml
import QtQuick

Item {
    id: ampMeter

    property alias rotation: agullaSvg.rotation
    property string fonsSource: "qrc:/material/AmpMeterFons_c.svg"
    property string agullaSource: "qrc:/material/AmpMeterAgulla_c.svg"
    property real agullaOpacity: 0.8

    Image {
        id: fonsSvg
        source: ampMeter.fonsSource
        fillMode: Image.PreserveAspectFit
        anchors.centerIn: parent
    }

    Image {
        id: agullaSvg
        source: ampMeter.agullaSource
        fillMode: Image.PreserveAspectFit
        opacity: ampMeter.agullaOpacity
        anchors.centerIn: parent

        rotation: 0

        Behavior on rotation {
            NumberAnimation {
                duration: 300
                easing.type: Easing.InOutBack
            }
        }
    }
}

Modularitat i separació de responsabilitats

L'evolució dels codis, prenent com a referència inicial ampMeter00_05, mostra una progressió cap a una major modularitat i control de la interfície d'usuari a través de la introducció de nous components QML i la separació de responsabilitats.

ampMeter00_05 (Punt de referència inicial per a la modularitat i separació de responsabilitats)

En aquesta versió, el fitxer `main.qml` configura una finestra amb un `Rectangle` blanc. Inclou dos components QML personalitzats: `AmpMeter` i `Botonera`.

  • El component `AmpMeter` es centra a la pantalla i gestiona la visualització del fons i l'agulla d'un amperímetre. La rotació de l'agulla es controla mitjançant una propietat `rotation` exposada i animada amb una `NumberAnimation` que utilitza `Easing.InOutBack`.
  • El component `Botonera` es col·loca a la part inferior i es centra horitzontalment. S'alimenta d'un `ListModel` anomenat `buttonModel` que conté les etiquetes i els valors de rotació per als botons. Quan un botó és clicat, emet un senyal `buttonClicked` amb el seu valor, i aquest valor s'assigna a la propietat `rotation` del component `ampMeter`.

ampMeter00_06 (Introducció de Múltiples AmpMeters)

Aquesta versió es titula "ampMeter00_06 - Triple" i incrementa l'amplada de la finestra a 960 píxels. La millora principal és la capacitat de mostrar i controlar tres components `AmpMeter` simultàniament.

  • Els tres `AmpMeter` (`ampMeter1`, `ampMeter2`, `ampMeter3`) estan organitzats en una `Row` (fila) centrada, amb un desplaçament vertical per deixar espai als controls. Cadascun té dimensions i escala definides de manera individual (per exemple, `ampMeter1` i `ampMeter3` amb `scale: 0.5`, i `ampMeter2` amb `scale: 0.8`).
  • El `ListModel` dels botons es manté, però el gestor `onButtonClicked` a `main.qml` ara actualitza la propietat `rotation` dels tres components `AmpMeter` de forma idèntica (`ampMeter1.rotation = value`, `ampMeter2.rotation = value`, `ampMeter3.rotation = value`).
  • Els components `Botonera.qml` i `AmpMeter.qml` conserven la seva estructura anterior.

ampMeter00_07 (Incorporació d'un lliscador o slider)

Continuant amb la configuració "Triple", aquesta versió afegeix un nou element de control: un slider (lliscador).

  • Es mantenen els tres components `AmpMeter` en fila.
  • S'introdueix un `Column` (`sliderContainer`) que conté la lògica del slider, situat just a sobre dels botons de control.
  • Aquest slider consisteix en un `Rectangle` que serveix de pista i un altre `Rectangle` (`sliderHandle`) que fa de mànec lliscant.
  • Un `MouseArea` permet arrossegar el `sliderHandle` només en l'eix X (`drag.axis: Drag.XAxis`).
  • El gestor `onPositionChanged` calcula un valor (`value`) entre -60 i +60, el converteix en un angle de rotació entre -45 i +45, i l'aplica exclusivament a `ampMeter2` (`ampMeter2.rotation = rotation`).
  • Es mostren etiquetes per als valors extrems (-60 i +60) i el valor actual del slider.
  • La `Botonera` ara només controla `ampMeter1` i `ampMeter3`, ja que `ampMeter2` és controlat pel nou slider.

ampMeter00_08 (Modularització del lliscador amb `BarraLliscant.qml`)

En aquesta etapa, la lògica del lliscador es refactoritza en un component QML independent anomenat `BarraLliscant.qml`.

  • A `main.qml`, el `sliderContainer` es reemplaça per una instància del component `BarraLliscant`.
  • `BarraLliscant.qml` encapsula tota la interfície i la lògica del lliscador, incloent les etiquetes, la pista, el mànec, el càlcul del valor i la visualització del valor actual.
  • Defineix propietats configurables com `minValue`, `maxValue`, `sliderWidth` i `sliderHeight`.
  • Emet un senyal `valueChanged(real value)` quan el valor del lliscador canvia.
  • A `main.qml`, el senyal `onValueChanged` de la `BarraLliscant` s'utilitza per convertir el valor del lliscador en rotació i aplicar-lo a `ampMeter2`.
  • El component `AmpMeter.qml` introdueix propietats `fondoSource` i `agullaSource` per personalitzar les imatges, tot i que inicialment mantenen els mateixos valors per defecte.
  • La `Botonera` continua controlant `ampMeter1` i `ampMeter3`.

ampMeter00_09 (Modularització completa amb `Vista.qml` i `Controlador.qml`)

Aquesta és la versió més modularitzada, amb el títol "ampMeter00_09 - Modular". `main.qml` se simplifica enormement delegant les responsabilitats a dos nous components de nivell superior: `Vista` i `Controlador`.

  • `Vista.qml`: Aquest component agrupa els tres components `AmpMeter` en una `Row`.
    • Exposa les propietats `ampMeter1Rotation`, `ampMeter2Rotation`, `ampMeter3Rotation` com àlies per controlar la rotació de cada `AmpMeter` des del seu pare.
    • Permet personalitzar l'escala de cada `AmpMeter` (`ampMeter1Scale`, `ampMeter2Scale`, `ampMeter3Scale`).
    • Una novetat important és que cada `AmpMeter` dins de la `Vista` pot tenir un tipus d'animació d'easing diferent (`Easing.InOutQuad`, `Easing.InOutBack`, `Easing.InOutCubic`), cosa que requereix una nova propietat `easingType` a `AmpMeter.qml`.
  • `Controlador.qml`: Aquest component agrupa la `BarraLliscant` i la `Botonera` en una `Column`.
    • Rep el `buttonModel` per a la `Botonera`.
    • Emet senyals `sliderValueChanged(real value)` i `buttonClicked(real value)` per comunicar els esdeveniments dels seus components interns al seu pare.
    • Connecta el senyal `onValueChanged` de la seva `BarraLliscant` interna al seu propi `sliderValueChanged`.
    • Connecta el senyal `onButtonClicked` de la seva `Botonera` interna al seu propi `buttonClicked`.
  • A `main.qml`, el gestor `onSliderValueChanged` del `Controlador` ara actualitza `vista.ampMeter2Rotation`.
  • El gestor `onButtonClicked` del `Controlador` actualitza `vista.ampMeter1Rotation` i `vista.ampMeter3Rotation`.

En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer `main.qml`, cap a una estructura modular on components reutilitzables (`AmpMeter`, `Botonera`, `BarraLliscant`) s'organitzen en components de més alt nivell (`Vista`, `Controlador`), millorant la mantenibilitat i la claredat del codi.


= Modularitat i separació de responsabilitats =

L'evolució dels codis, prenent com a referència inicial ampMeter00 _05, mostra una progressió cap a una major modularitat i control de la interfície d'usuari a través de la introducció de nous components QML i la separació de responsabilitats.

=== ampMeter00 _05 (Punt de referència inicial per a la modularitat i separació de responsabilitats) ===

En aquesta versió, el fitxer main.qml configura una finestra amb un Rectangle blanc. Inclou dos components QML personalitzats: AmpMeter i Botonera .

 * El component  AmpMeter  es centra a la pantalla i gestiona la visualització del fons i l'agulla d'un amperímetre. La rotació de l'agulla es controla mitjançant una propietat  rotation  exposada i animada amb una  NumberAnimation  que utilitza  Easing.InOutBack .
 * El component  Botonera  es col·loca a la part inferior i es centra horitzontalment. S'alimenta d'un  ListModel  anomenat  buttonModel  que conté les etiquetes i els valors de rotació per als botons. Quan un botó és clicat, emet un senyal  buttonClicked  amb el seu valor, i aquest valor s'assigna a la propietat  rotation  del component  ampMeter .
=== ampMeter00 _06 (Introducció de Múltiples AmpMeters) ===

Aquesta versió es titula "ampMeter00 _06 - Triple" i incrementa l'amplada de la finestra a 960 píxels. La millora principal és la capacitat de mostrar i controlar tres components AmpMeter simultàniament.

 * Els tres  AmpMeter  ( ampMeter1 ,  ampMeter2 ,  ampMeter3 ) estan organitzats en una  Row  (fila) centrada, amb un desplaçament vertical per deixar espai als controls. Cadascun té dimensions i escala definides de manera individual (per exemple,  ampMeter1  i  ampMeter3  amb  scale: 0.5 , i  ampMeter2  amb  scale: 0.8 ).
 * El  ListModel  dels botons es manté, però el gestor  onButtonClicked  a  main.qml  ara actualitza la propietat  rotation  dels tres components  AmpMeter  de forma idèntica ( ampMeter1.rotation = value ,  ampMeter2.rotation = value ,  ampMeter3.rotation = value ).
 * Els components  Botonera.qml  i  AmpMeter.qml  conserven la seva estructura anterior.
=== ampMeter00 _07 (Incorporació d'un lliscador o slider) ===

Continuant amb la configuració "Triple", aquesta versió afegeix un nou element de control: un slider (lliscador).

 * Es mantenen els tres components  AmpMeter  en fila.
 * S'introdueix un  Column  ( sliderContainer ) que conté la lògica del slider, situat just a sobre dels botons de control.
 * Aquest slider consisteix en un  Rectangle  que serveix de pista i un altre  Rectangle  ( sliderHandle ) que fa de mànec lliscant.
 * Un  MouseArea  permet arrossegar el  sliderHandle  només en l'eix X ( drag.axis: Drag.XAxis ).
 * El gestor  onPositionChanged  calcula un valor ( value ) entre -60 i +60, el converteix en un angle de rotació entre -45 i +45, i l'aplica exclusivament a  ampMeter2  ( ampMeter2.rotation = rotation ).
 * Es mostren etiquetes per als valors extrems (-60 i +60) i el valor actual del slider.
 * La  Botonera  ara només controla  ampMeter1  i  ampMeter3 , ja que  ampMeter2  és controlat pel nou slider.
=== ampMeter00 _08 (Modularització del lliscador amb  BarraLliscant.qml ) ===

En aquesta etapa, la lògica del lliscador es refactoritza en un component QML independent anomenat BarraLliscant.qml .

 * A  main.qml , el  sliderContainer  es reemplaça per una instància del component  BarraLliscant .
 *  BarraLliscant.qml  encapsula tota la interfície i la lògica del lliscador, incloent les etiquetes, la pista, el mànec, el càlcul del valor i la visualització del valor actual.
 * Defineix propietats configurables com  minValue ,  maxValue ,  sliderWidth  i  sliderHeight .
 * Emet un senyal  valueChanged(real value)  quan el valor del lliscador canvia.
 * A  main.qml , el senyal  onValueChanged  de la  BarraLliscant  s'utilitza per convertir el valor del lliscador en rotació i aplicar-lo a  ampMeter2 .
 * El component  AmpMeter.qml  introdueix propietats  fondoSource  i  agullaSource  per personalitzar les imatges, tot i que inicialment mantenen els mateixos valors per defecte.
 * La  Botonera  continua controlant  ampMeter1  i  ampMeter3 .
=== ampMeter00 _09 (Modularització completa amb  Vista.qml  i  Controlador.qml ) ===

Aquesta és la versió més modularitzada, amb el títol "ampMeter00 _09 - Modular". main.qml se simplifica enormement delegant les responsabilitats a dos nous components de nivell superior: Vista i Controlador .

 *  Vista.qml : Aquest component agrupa els tres components  AmpMeter  en una  Row .
    * * Exposa les propietats  ampMeter1Rotation ,  ampMeter2Rotation ,  ampMeter3Rotation  com àlies per controlar la rotació de cada  AmpMeter  des del seu pare.
    * * Permet personalitzar l'escala de cada  AmpMeter  ( ampMeter1Scale ,  ampMeter2Scale ,  ampMeter3Scale ).
    * * Una novetat important és que cada  AmpMeter  dins de la  Vista  pot tenir un tipus d'animació deasing diferent ( Easing.InOutQuad ,  Easing.InOutBack ,  Easing.InOutCubic ), cosa que requereix una nova propietat  easingType  a  AmpMeter.qml .
 *  Controlador.qml : Aquest component agrupa la  BarraLliscant  i la  Botonera  en una  Column .
    * * Rep el  buttonModel  per a la  Botonera .
    * * Emet senyals  sliderValueChanged(real value)  i  buttonClicked(real value)  per comunicar els esdeveniments dels seus components interns al seu pare.
    * * Connecta el senyal  onValueChanged  de la seva  BarraLliscant  interna al seu propi  sliderValueChanged .
    * * Connecta el senyal  onButtonClicked  de la seva  Botonera  interna al seu propi  buttonClicked .
 * A  main.qml , el gestor  onSliderValueChanged  del  Controlador  ara actualitza  vista.ampMeter2Rotation .
 * El gestor  onButtonClicked  del  Controlador  actualitza  vista.ampMeter1Rotation  i  vista.ampMeter3Rotation .

En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer main.qml , cap a una estructura modular on components reutilitzables ( AmpMeter , Botonera , BarraLliscant ) s'organitzen en components de més alt nivell ( Vista , Controlador ), millorant la mantenibilitat i la claredat del codi.