307 lines
11 KiB
Java
307 lines
11 KiB
Java
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|