diff --git a/src/main/java/sae/chuzzle/VueGrille.java b/src/main/java/sae/chuzzle/VueGrille.java new file mode 100644 index 0000000..8768302 --- /dev/null +++ b/src/main/java/sae/chuzzle/VueGrille.java @@ -0,0 +1,307 @@ +package sae.chuzzle; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; + +public class VueGrille extends View { + + // ========================================================= + // Constantes + // ========================================================= + + private static final int NB_LIGNES = 6; + private static final int NB_COLONNES = 6; + private static final int NB_TYPES = 7; + + /** Symboles pour le mode daltonien, un par type. */ + private static final String[] SYMBOLES = {"●", "■", "▲", "✚", "★", "♦", "✿"}; + + // ========================================================= + // Données métier + // ========================================================= + + /** Grille reçue depuis MainActivity (tableau brut). */ + private int[][] grille = new int[NB_LIGNES][NB_COLONNES]; + + /** Verrous reçus depuis MainActivity. */ + private boolean[][] verrous = new boolean[NB_LIGNES][NB_COLONNES]; + + private boolean modeDaltonien = false; + + // ========================================================= + // État de l'animation de glissement + // ========================================================= + + /** + * true = on anime une ligne, + * false = on anime une colonne, + * null = pas d'animation en cours. + */ + private Boolean animEstLigne = null; + + /** Index de la ligne ou colonne en cours de glissement. */ + private int animIndex = 0; + + /** + * Décalage courant en pixels (positif = droite/bas, + * négatif = gauche/haut). + */ + private float animDecalagePx = 0f; + + // ========================================================= + // Outils de dessin + // ========================================================= + + private final Paint pinceauCase = new Paint(); + private final Paint pinceauSymbole = new Paint(); + + // ========================================================= + // Constructeurs + // ========================================================= + + public VueGrille(Context contexte) { + super(contexte); + initPinceaux(); + } + + public VueGrille(Context context, AttributeSet attrs) { + super(context, attrs); + initPinceaux(); + } + + private void initPinceaux() { + pinceauCase.setAntiAlias(true); + pinceauCase.setStyle(Paint.Style.FILL); + + pinceauSymbole.setAntiAlias(true); + pinceauSymbole.setColor(0xFF000000); + pinceauSymbole.setTextAlign(Paint.Align.CENTER); + } + + // ========================================================= + // API publique + // ========================================================= + + /** + * Reçoit une copie de la grille depuis MainActivity. + * VueGrille ne sait pas d'où viennent ces données, + * elle sait juste les dessiner. + */ + public void definirGrille(int[][] nouvelleGrille) { + for (int l = 0; l < NB_LIGNES; l++) { + System.arraycopy(nouvelleGrille[l], 0, grille[l], 0, NB_COLONNES); + } + invalidate(); + } + + public void definirModeDaltonien(boolean actif) { + this.modeDaltonien = actif; + invalidate(); + } + + public void definirVerrous(boolean[][] nouveauxVerrous) { + for (int l = 0; l < NB_LIGNES; l++) { + System.arraycopy(nouveauxVerrous[l], 0, verrous[l], 0, NB_COLONNES); + } + invalidate(); + } + + // ========================================================= + // API animation de glissement (appelée par GestionnaireTactile) + // ========================================================= + + /** + * Déclenche l'animation de glissement en temps réel. + * + * @param estLigne true = on déplace une ligne, false = une colonne + * @param index index de la ligne ou colonne (0-based) + * @param decalagePx décalage courant en pixels (peut être négatif) + */ + public void definirGlissement(boolean estLigne, int index, float decalagePx) { + this.animEstLigne = estLigne; + this.animIndex = index; + this.animDecalagePx = decalagePx; + invalidate(); + } + + /** + * Annule l'animation de glissement et revient à l'affichage normal. + */ + public void annulerGlissement() { + this.animEstLigne = null; + invalidate(); + } + + // ========================================================= + // Dessin + // ========================================================= + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + + int largeur = getWidth(); + int hauteur = getHeight(); + + float tailleCase = Math.min( + largeur / (float) NB_COLONNES, + hauteur / (float) NB_LIGNES + ); + + float margeGauche = (largeur - tailleCase * NB_COLONNES) / 2f; + float margeHaut = (hauteur - tailleCase * NB_LIGNES) / 2f; + + pinceauSymbole.setTextSize(tailleCase * 0.4f); + + for (int ligne = 0; ligne < NB_LIGNES; ligne++) { + for (int colonne = 0; colonne < NB_COLONNES; colonne++) { + + // -------------------------------------------------- + // Calcul de l'offset d'animation pour cette case + // -------------------------------------------------- + float offsetX = 0f; + float offsetY = 0f; + + if (animEstLigne != null) { + if (animEstLigne && ligne == animIndex) { + // Décalage horizontal de toute la ligne + offsetX = animDecalagePx; + } else if (!animEstLigne && colonne == animIndex) { + // Décalage vertical de toute la colonne + offsetY = animDecalagePx; + } + } + + // -------------------------------------------------- + // Position de base de la case + // -------------------------------------------------- + float x1 = margeGauche + colonne * tailleCase + 6 + offsetX; + float y1 = margeHaut + ligne * tailleCase + 6 + offsetY; + float x2 = margeGauche + (colonne + 1) * tailleCase - 6 + offsetX; + float y2 = margeHaut + (ligne + 1) * tailleCase - 6 + offsetY; + + // -------------------------------------------------- + // Dessin avec wrap-around (la case qui sort d'un + // côté réapparaît de l'autre côté) + // -------------------------------------------------- + dessinerCase(canvas, ligne, colonne, x1, y1, x2, y2, + tailleCase, margeGauche, margeHaut, offsetX, offsetY); + } + } + } + + /** + * Dessine une case à la position donnée. + * Si la case sort des bords (wrap-around), elle est aussi dessinée + * du côté opposé de la grille. + */ + private void dessinerCase(Canvas canvas, + int ligne, int colonne, + float x1, float y1, float x2, float y2, + float tailleCase, + float margeGauche, float margeHaut, + float offsetX, float offsetY) { + + int type = grille[ligne][colonne]; + + // Bornage de la grille (en pixels) + float borneGaucheGrille = margeGauche; + float bordDroiteGrille = margeGauche + NB_COLONNES * tailleCase; + float borneHautGrille = margeHaut; + float bordBasGrille = margeHaut + NB_LIGNES * tailleCase; + + // Largeur totale de la ligne / hauteur totale de la colonne en pixels + float largeurGrille = NB_COLONNES * tailleCase; + float hauteurGrille = NB_LIGNES * tailleCase; + + // Dessine la case principale + dessinerRectCase(canvas, type, ligne, colonne, x1, y1, x2, y2); + + // Wrap-around horizontal (pour les lignes) + if (offsetX != 0f) { + float wrapX1 = x1, wrapX2 = x2; + + if (x2 > bordDroiteGrille) { + // La case déborde à droite → réapparaît à gauche + wrapX1 = x1 - largeurGrille; + wrapX2 = x2 - largeurGrille; + dessinerRectCase(canvas, type, ligne, colonne, wrapX1, y1, wrapX2, y2); + } else if (x1 < borneGaucheGrille) { + // La case déborde à gauche → réapparaît à droite + wrapX1 = x1 + largeurGrille; + wrapX2 = x2 + largeurGrille; + dessinerRectCase(canvas, type, ligne, colonne, wrapX1, y1, wrapX2, y2); + } + } + + // Wrap-around vertical (pour les colonnes) + if (offsetY != 0f) { + float wrapY1 = y1, wrapY2 = y2; + + if (y2 > bordBasGrille) { + // La case déborde en bas → réapparaît en haut + wrapY1 = y1 - hauteurGrille; + wrapY2 = y2 - hauteurGrille; + dessinerRectCase(canvas, type, ligne, colonne, x1, wrapY1, x2, wrapY2); + } else if (y1 < borneHautGrille) { + // La case déborde en haut → réapparaît en bas + wrapY1 = y1 + hauteurGrille; + wrapY2 = y2 + hauteurGrille; + dessinerRectCase(canvas, type, ligne, colonne, x1, wrapY1, x2, wrapY2); + } + } + } + + /** + * Dessine le rectangle coloré d'une case + verrou + symbole daltonien. + */ + private void dessinerRectCase(Canvas canvas, int type, + int ligne, int colonne, + float x1, float y1, float x2, float y2) { + + // Couleur de fond + definirCouleur(type); + canvas.drawRoundRect(x1, y1, x2, y2, 20, 20, pinceauCase); + + // Assombrir si verrouillée + if (verrous[ligne][colonne]) { + pinceauCase.setARGB(120, 0, 0, 0); + canvas.drawRoundRect(x1, y1, x2, y2, 20, 20, pinceauCase); + } + + float cx = (x1 + x2) / 2f; + float cy = (y1 + y2) / 2f + - (pinceauSymbole.descent() + pinceauSymbole.ascent()) / 2f; + + // Symbole daltonien + if (modeDaltonien) { + canvas.drawText(SYMBOLES[type % NB_TYPES], cx, cy, pinceauSymbole); + } + + // Cadenas par-dessus si verrouillée + if (verrous[ligne][colonne]) { + canvas.drawText("🔒", cx, cy, pinceauSymbole); + } + } + + // ========================================================= + // Utilitaire privé + // ========================================================= + + private void definirCouleur(int type) { + switch (type % NB_TYPES) { + case 0: pinceauCase.setARGB(255, 200, 200, 200); break; + case 1: pinceauCase.setARGB(255, 255, 105, 180); break; + case 2: pinceauCase.setARGB(255, 90, 230, 200); break; + case 3: pinceauCase.setARGB(255, 100, 170, 255); break; + case 4: pinceauCase.setARGB(255, 255, 220, 90); break; + case 5: pinceauCase.setARGB(255, 255, 140, 90); break; + case 6: pinceauCase.setARGB(255, 255, 90, 90); break; + } + } +} \ No newline at end of file