docs(ui): document setStyle text-fill black issue and ensure zero inline colors
This commit is contained in:
+58
-58
@@ -1,79 +1,79 @@
|
||||
# UI Troubleshooting & Resolução de Estilos (JavaFX CSS)
|
||||
|
||||
Este documento registra a investigação da causa raiz de problemas visuais na interface gráfica do CyberCore Signer, em especial a falha de renderização de textos escuros sobre fundo escuro no tema escuro.
|
||||
Este documento registra a investigação da causa raiz de problemas visuais na interface gráfica do CyberCore Signer, em especial a falha de renderização de textos com cor preta (`Color.BLACK`) via `setStyle()`, a eliminação definitiva de estilos inline de cor e a transição para o tema claro unificado.
|
||||
|
||||
---
|
||||
|
||||
## 1. O Problema: Texto Preto sobre Fundo Escuro (Contraste ~1.1:1)
|
||||
## 1. O Problema: Texto Renderizado em Preto (`#000000`) via `setStyle`
|
||||
|
||||
### Sintoma
|
||||
No tema escuro (`ThemeManager.AppTheme.DARK` / `.theme-dark`), determinados textos nos diálogos `ExitConfirmationDialog` (texto explicativo da ação), `PdfSignDialog` (nome e metadados do documento PDF selecionado, dica de arraste do selo visual) e `PdfCertificateSignDialog` (opção de memorização segura de PIN) eram renderizados com a cor preta (`#000000`) sobre um fundo escuro (`#090d16` ou `#111827`).
|
||||
|
||||
O contraste medido entre o texto e o fundo situava-se em aproximadamente **1.1:1 a 1.2:1**, violando completamente as diretrizes de acessibilidade WCAG AA (mínimo de 4.5:1 para texto normal) e tornando o texto praticamente invisível.
|
||||
Em diálogos modais como `ExitConfirmationDialog`, `PdfSignDialog` e `PdfCertificateSignDialog`, chamadas como:
|
||||
```java
|
||||
subtitle.setStyle("-fx-text-fill: -fx-text-secondary;");
|
||||
lblDocName.setStyle("-fx-text-fill: -fx-text-primary;");
|
||||
dragTip.setStyle("-fx-text-fill: -fx-text-secondary;");
|
||||
```
|
||||
resultavam em texto renderizado com a cor preta opaca (`#000000`), mesmo quando o token `-fx-text-secondary` já se encontrava definido no arquivo `theme.css`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Investigação Técnica e Causa Raiz
|
||||
|
||||
### 2.1 Lookups Inexistentes no CSS
|
||||
Ao inspecionar o código-fonte Java dessas telas, verificou-se o uso de estilos inline via `setStyle(...)`:
|
||||
```java
|
||||
subtitle.setStyle("-fx-text-fill: -fx-text-secondary; -fx-font-size: 12px;");
|
||||
lblDocName.setStyle("-fx-font-weight: bold; -fx-font-size: 12.5px; -fx-text-fill: -fx-text-primary;");
|
||||
lblDocMeta.setStyle("-fx-font-size: 11px; -fx-text-fill: -fx-text-secondary;");
|
||||
previewBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; ...");
|
||||
chkRememberPin.setStyle("-fx-font-size: 11.5px; -fx-text-fill: -fx-text-primary;");
|
||||
### 2.1 Por que `setStyle("-fx-text-fill: -fx-text-secondary")` renderiza PRETO?
|
||||
No motor de estilização do JavaFX (`com.sun.javafx.css.StyleManager` / `CssStyleHelper`):
|
||||
|
||||
1. **Momento da Avaliação (Timing) de Nós Fora da Árvore da Scene:**
|
||||
Quando um nó (como um `Label` ou `Text`) é instanciado no código Java dentro do construtor de um diálogo e recebe `.setStyle("-fx-text-fill: -fx-text-secondary;")`, ele ainda **não foi anexado ao grafo de cena** (`Scene`).
|
||||
2. **Escopo de Resolução de Lookups Ausente:**
|
||||
Como `-fx-text-secondary` é uma cor pesquisada (*looked-up color*) definida no seletor `.root` da folha de estilo da aplicação, a engine de CSS do JavaFX tenta resolver essa variável subindo a cadeia de nós pais (`parent chain`) até alcançar `.root`. Como o nó está solto na memória (sem parent e sem Scene vinculada no instante da chamada), o lookup não encontra nenhuma definição válida.
|
||||
3. **Mecanismo de Fallback Silencioso do Modena (`Color.BLACK`):**
|
||||
O analisador do JavaFX não lança exceção ao falhar na resolução de uma cor de lookup em estilo inline. Em vez disso, seu conversor (`PaintConverter` / `ColorConverter`) assume o valor padrão de salvaguarda da especificação Modena: **`Color.BLACK` (`rgba(0, 0, 0, 1.0)`)**.
|
||||
4. **Imutabilidade e Prioridade do Estilo Inline:**
|
||||
O estilo inline atribuído via `setStyle(...)` possui a especificidade mais alta na hierarquia CSS do JavaFX. O valor pré-resolvido como `Color.BLACK` fica gravado diretamente no cache de estilo do nó. Quando a Scene finalmente executa a passagem formal de CSS (`applyCss()` / `layout()`), a engine prioriza o estilo inline e não reavalia o lookup que falhou previamente.
|
||||
|
||||
### 2.2 Por que o problema não ocorre com Classes CSS?
|
||||
Quando utilizamos classes semânticas declaradas em folha de estilo (ex.: `lbl.getStyleClass().add("text-muted");`):
|
||||
```css
|
||||
.text-muted {
|
||||
-fx-text-fill: -fx-text-secondary;
|
||||
}
|
||||
```
|
||||
|
||||
Os tokens `-fx-text-primary`, `-fx-text-secondary`, `-fx-bg-primary`, `-fx-border-light` e `-fx-accent` **não estavam declarados** no arquivo de folha de estilos `theme.css`.
|
||||
|
||||
### 2.2 Por que o JavaFX rebaixa para preto (`#000000`)?
|
||||
No motor de estilização do JavaFX (`com.sun.javafx.css` / Modena):
|
||||
1. Quando uma declaração inline em `setStyle(...)` utiliza uma cor de lookup (`-fx-text-fill: <variavel>`), o analisador CSS do JavaFX tenta resolver essa variável percorrendo os ancestrais do nó até o `.root` da cena.
|
||||
2. Caso a variável **não seja encontrada**, o `StyleConverter` do JavaFX não lança exceção em tempo de execução. Em vez disso, seu mecanismo de tolerância a falhas adota o valor fallback padrão da especificação base de controles: **`Color.BLACK` (`rgba(0, 0, 0, 1.0)`)**.
|
||||
3. Em um tema claro, o fundo é branco ou cinza claro (`#ffffff` / `#f1f5f9`), de modo que o texto preto parece ter funcionado por pura coincidência de luminância.
|
||||
4. No tema escuro, no entanto, o fundo da aplicação é escuro (`#090d16` a `#111827`), fazendo com que o fallback para `#000000` destrua o contraste da interface.
|
||||
|
||||
### 2.3 Problemas Adicionais com `setStyle(...)`
|
||||
- **Prioridade Forçada:** `setStyle(...)` tem especificidade de estilo inline (prioridade máxima), sobrescrevendo quaisquer regras de temas `.theme-light` e `.theme-dark` que tentem ajustar o elemento.
|
||||
- **Acoplamento:** Espalha lógica visual e regras de cor pelo código Java, dificultando testes de regressão automatizados e auditorias de contraste.
|
||||
A resolução da regra é postergada até que o nó pertença à hierarquia da Scene e a passagem de CSS do JavaFX seja executada. Nesse momento, o nó tem acesso direto às definições de `.root`, resolvendo perfeitamente o token para a cor correta da paleta (`#475569`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Diretrizes e Solução Adotada
|
||||
## 3. Diretriz Arquitetural: Proibição de Cores em `setStyle()`
|
||||
|
||||
### 3.1 Definição Completa de Tokens e Aliases no `theme.css`
|
||||
Todos os tokens semânticos e seus aliases de compatibilidade são declarados formalmente nos blocos canônicos de tokens:
|
||||
```css
|
||||
.theme-light {
|
||||
-fx-text-primary: -fx-color-text;
|
||||
-fx-text-secondary: -fx-color-text-muted;
|
||||
-fx-bg-primary: -fx-color-surface;
|
||||
-fx-border-light: -fx-color-border-subtle;
|
||||
-fx-accent: -fx-color-primary;
|
||||
}
|
||||
Fica estabelecido como regra obrigatória de desenvolvimento no `cybercore-ui`:
|
||||
|
||||
.theme-dark {
|
||||
-fx-text-primary: -fx-color-text;
|
||||
-fx-text-secondary: -fx-color-text-muted;
|
||||
-fx-bg-primary: -fx-color-surface;
|
||||
-fx-border-light: -fx-color-border;
|
||||
-fx-accent: -fx-color-primary;
|
||||
}
|
||||
```
|
||||
1. **`setStyle()` restrito exclusivamente à Geometria:**
|
||||
- Permitido: dimensões dinâmicas, larguras mínimas, alturas, alinhamentos e paddings específicos de cálculo em tempo de execução (`-fx-min-width`, `-fx-pref-width`, `-fx-padding`, `-fx-alignment`).
|
||||
- **Terminantemente proibido em `setStyle()`:**
|
||||
- `-fx-text-fill`
|
||||
- `-fx-background-color`
|
||||
- `-fx-border-color`
|
||||
- `-fx-fill` / `-fx-stroke`
|
||||
- Códigos hexadecimais (`#...`) ou funções cromáticas (`rgb(...)`, `rgba(...)`)
|
||||
- Nomes de lookups de cor (`-fx-accent`, `-fx-color-*`, etc.)
|
||||
|
||||
### 3.2 Eliminação de Cores Inline
|
||||
É terminantemente proibido utilizar `setStyle(...)` com propriedades que afetem cores:
|
||||
- Proibidos em `setStyle`: `-fx-text-fill`, `-fx-background-color`, `-fx-border-color`, `-fx-fill`, `-fx-stroke` ou códigos `#hex`.
|
||||
- Permitidos em `setStyle` exclusivamente quando estritamente necessário: geometria e layout (`-fx-min-width`, `-fx-pref-width`, `-fx-padding`).
|
||||
2. **Uso Obrigatório de Classes CSS Semânticas:**
|
||||
| Classe CSS | Finalidade | Propriedade Aplicada |
|
||||
|---|---|---|
|
||||
| `.text-primary` | Texto principal | `-fx-text-fill: -fx-text-primary;` |
|
||||
| `.text-secondary`, `.text-muted` | Texto auxiliar / metadados | `-fx-text-fill: -fx-text-secondary;` |
|
||||
| `.text-accent` | Destaque primário | `-fx-text-fill: -fx-accent;` |
|
||||
| `.text-bold` | Tipografia em negrito | `-fx-font-weight: bold;` |
|
||||
| `.text-small` | Texto compacto | `-fx-font-size: 11px;` |
|
||||
| `.text-mono` | Fonte monoespaçada para hashes | `-fx-font-family: monospace;` |
|
||||
| `.surface-box` | Container de superfície branca com borda | `-fx-background-color: -fx-color-surface;` |
|
||||
| `.toolbar-box` | Barra de ferramentas compacta | `-fx-background-color: -fx-color-surface;` |
|
||||
| `.scroll-transparent` | ScrollPane sem fundo cinza | `-fx-background-color: transparent;` |
|
||||
| `.status-ok` / `.status-warn` / `.status-error` | Indicadores de status | Cores semânticas da paleta |
|
||||
|
||||
Toda estilização cromática deve ser feita atribuindo classes semânticas:
|
||||
- `.text-primary`: Texto principal com alta luminância no escuro e baixa no claro.
|
||||
- `.text-secondary`, `.text-muted`: Texto secundário / auxiliar com contraste garantido >= 4.5:1.
|
||||
- `.text-accent`: Destaque com a cor primária da marca.
|
||||
- `.surface-box`, `.toolbar-box`: Caixas de superfície neutra e barras de ferramentas.
|
||||
---
|
||||
|
||||
### 3.3 Testes Automatizados de Guarda-Corpo
|
||||
O projeto mantém testes unitários automatizados em `DesignTokenGuardrailTest` e `CssColorAndContrastGuardrailTest` que:
|
||||
1. Fazem varredura estática de todo o código Java da UI para reprovar qualquer `setStyle` que declare cor ou hexadecimal.
|
||||
2. Validam matematicamente a fórmula de luminância relativa WCAG 2.1 para cada par texto/fundo nos dois temas, garantindo conformidade rigorosa.
|
||||
## 4. Salvaguardas Automatizadas
|
||||
|
||||
O teste de arquitetura [`DesignTokenGuardrailTest.java`](file:///var/www/cybercore/cybercore-signer/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/theme/DesignTokenGuardrailTest.java) atua como barreira de compilação contínua:
|
||||
- Varre todos os arquivos `.java` no módulo `cybercore-ui` e reprova automaticamente o build se qualquer chamada `setStyle()` contiver propriedades ou valores de cor.
|
||||
- Valida matematicamente a relação de contraste WCAG AA (mínimo de 4.5:1 para texto normal) em todas as combinações de tokens de texto sobre superfície/fundo.
|
||||
|
||||
Reference in New Issue
Block a user