File size: 6,908 Bytes
eebc40f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bda4a6
eebc40f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bda4a6
eebc40f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import { useCallback, useRef } from 'react';
import * as d3 from 'd3';

/**
 * Hook spécialisé pour la gestion des états visuels (hover, sélection, transitions)
 * Centralise toute la logique d'affichage des états visuels
 */
export const useVisualState = () => {
  const visualStateRef = useRef({
    isTransitioning: false,
    selectedFont: null,
    hoveredFont: null
  });

  // Fonction pour mettre à jour les états visuels
  const updateVisualStates = useCallback((svg, viewportGroup, selectedFont, hoveredFont, darkMode) => {
    if (!svg || !viewportGroup) return;

    const glyphGroups = viewportGroup.selectAll('.font-glyph-group');
    
    glyphGroups.each(function(d) {
      const group = d3.select(this);
      const isActive = selectedFont && selectedFont.name === d.name;
      const isHovered = hoveredFont && hoveredFont.name === d.name;
      
      // Supprimer tous les cercles existants
      group.select('.active-background-circle').remove();
      group.select('.hover-background-circle').remove();
      
      if (isActive) {
        // Cercle actif
        // Creating active circle
        group.insert('circle', ':first-child')
          .attr('class', 'active-background-circle')
          .attr('r', 12)
          .attr('fill', darkMode ? '#000000' : '#ffffff')
          .attr('stroke', 'none')
          .attr('stroke-width', 0)
          .style('pointer-events', 'none')
          .style('opacity', 1);
        
        // Forcer l'opacité du groupe parent à 1 pour les lettres actives
        group.style('opacity', 1);
        
        // Active circle created
      } else if (isHovered) {
        // Cercle hover
        group.insert('circle', ':first-child')
          .attr('class', 'hover-background-circle')
          .attr('r', 12)
          .attr('fill', darkMode ? '#000000' : '#ffffff')
          .style('pointer-events', 'none')
          .style('opacity', 1);
        
        // Forcer l'opacité du groupe parent à 1 pour les lettres en hover
        group.style('opacity', 1);
      }
    });
  }, []);

  // Fonction pour mettre à jour la taille des glyphes
  const updateGlyphSizes = useCallback((viewportGroup, selectedFont, characterSize) => {
    if (!viewportGroup) return;

    console.log('🎯 updateGlyphSizes called with characterSize:', characterSize);

    const baseSize = 16;
    const currentSize = baseSize * characterSize;

    // NOTE: calculateLogarithmicSize supprimé car non utilisé

    const fontGlyphs = viewportGroup.selectAll('.font-glyph');
    
    fontGlyphs
      .attr('width', d => {
        const isActive = selectedFont && selectedFont.name === d.name;
        const isMerged = d.fusionInfo && d.fusionInfo.merged;
        
        // Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
        if (isMerged) {
          return isActive ? d.__logSize * 2 : d.__logSize;
        }
        
        // Pour les polices normales, utiliser currentSize
        return isActive ? currentSize * 2 : currentSize;
      })
      .attr('height', d => {
        const isActive = selectedFont && selectedFont.name === d.name;
        const isMerged = d.fusionInfo && d.fusionInfo.merged;
        
        // Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
        if (isMerged) {
          return isActive ? d.__logSize * 2 : d.__logSize;
        }
        
        // Pour les polices normales, utiliser currentSize
        return isActive ? currentSize * 2 : currentSize;
      })
      .attr('x', d => {
        const isActive = selectedFont && selectedFont.name === d.name;
        const isMerged = d.fusionInfo && d.fusionInfo.merged;
        
        // Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
        if (isMerged) {
          const logSize = d.__logSize;
          const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2;
          return activeOffset;
        }
        
        // Pour les polices normales, utiliser currentSize
        const activeOffset = isActive ? -(currentSize * 2) / 2 : -currentSize / 2;
        return activeOffset;
      })
      .attr('y', d => {
        const isActive = selectedFont && selectedFont.name === d.name;
        const isMerged = d.fusionInfo && d.fusionInfo.merged;
        
        // Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
        if (isMerged) {
          const logSize = d.__logSize;
          const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2;
          return activeOffset;
        }
        
        // Pour les polices normales, utiliser currentSize
        const activeOffset = isActive ? -(currentSize * 2) / 2 : -currentSize / 2;
        return activeOffset;
      });
  }, []);

  // Fonction pour mettre à jour l'opacité des glyphes
  const updateGlyphOpacity = useCallback((viewportGroup, positions, filter, searchTerm, selectedFont) => {
    if (!viewportGroup) return;

    const glyphGroups = viewportGroup.selectAll('.font-glyph-group');
    
    glyphGroups
      .data(positions)
      .style('opacity', d => {
        const familyMatch = filter === 'all' || d.family === filter;
        const searchMatch = !searchTerm || 
          d.name.toLowerCase().includes(searchTerm.toLowerCase()) || 
          d.family.toLowerCase().includes(searchTerm.toLowerCase());
        const isActive = selectedFont && selectedFont.name === d.name;
        
        // Si la lettre est active, toujours opacité 1, sinon appliquer les filtres
        return isActive ? 1 : (familyMatch && searchMatch ? 1 : 0.2);
      });
  }, []);

  // Fonction pour mettre à jour les couleurs
  const updateGlyphColors = useCallback((viewportGroup, fonts, darkMode) => {
    if (!viewportGroup) return;

    const colorScale = d3.scaleOrdinal(
      darkMode 
        ? ['#ffffff', '#cccccc', '#999999', '#666666', '#333333']
        : ['#000000', '#333333', '#666666', '#999999', '#cccccc']
    );
    const families = [...new Set(fonts.map(d => d.family))];
    families.forEach(family => colorScale(family));

    const fontGlyphs = viewportGroup.selectAll('.font-glyph');
    if (!fontGlyphs.empty()) {
      fontGlyphs
        .attr('fill', darkMode ? '#ffffff' : d => colorScale(d.family))
        .style('fill', darkMode ? '#ffffff' : null)
        .style('color', darkMode ? '#ffffff' : null);
    }
  }, []);

  // Fonction pour démarrer une transition
  const startTransition = useCallback(() => {
    visualStateRef.current.isTransitioning = true;
  }, []);

  // Fonction pour terminer une transition
  const endTransition = useCallback(() => {
    visualStateRef.current.isTransitioning = false;
  }, []);

  return {
    visualStateRef,
    updateVisualStates,
    updateGlyphSizes,
    updateGlyphOpacity,
    updateGlyphColors,
    startTransition,
    endTransition
  };
};