diff --git a/assets/README.md b/assets/README.md index d77f68b..91f8eef 100644 --- a/assets/README.md +++ b/assets/README.md @@ -1,6 +1,6 @@ -# Pasta de Ícones (SVG) e Imagens (PNG) — CyberCore Signer +# Recursos Visuais (Ícones e Imagens em SVG) — CyberCore Signer -Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente os **ícones em SVG** e as **imagens em PNG** do **CyberCore Signer**. +Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente todos os **ícones em SVG** e as **imagens em SVG** do **CyberCore Signer**. --- @@ -31,13 +31,23 @@ assets/ │ ├── seal.svg # Ícone Designer e Gerenciador de Selos Notariais │ ├── check.svg # Ícone Checkmark de sucesso e validação │ ├── error.svg # Ícone de Erro / Alerta em vermelho -│ └── star.svg # Ícone de Estrela notarial / favorita +│ ├── star.svg # Ícone de Estrela notarial / favorita +│ ├── close.svg # Ícone Fechar / Cancelar (cruz geométrica moderna) +│ ├── arrow-right.svg # Ícone Avançar / Seta direcional +│ ├── zoom-in.svg # Ícone Zoom In (+) +│ ├── zoom-out.svg # Ícone Zoom Out (-) +│ ├── zoom-fit.svg # Ícone Ajustar à Tela +│ └── refresh.svg # Ícone Atualizar / Recarregar listas e status │ -└── images/ # Imagens de alta fidelidade em PNG com canal alfa (transparência) - ├── app-icon.png # Ícone oficial do app (256x256 px - Diamante 3D azul Serpro) - ├── app-logo.png # Logotipo horizontal oficial (480x120 px - CyberCore SIGNER) - ├── brand-banner.png # Banner de cabeçalho e marca (600x160 px - Serpro Blue) - └── default-seal.png # Selo de assinatura padrão ICP-Brasil (640x164 px) +└── images/ # Imagens vetoriais SVG editáveis (com cópias PNG de fallback) + ├── app-icon.svg # Ícone oficial do app em SVG (Cristal/Diamante 3D azul) + ├── app-icon.png # Fallback em PNG (256x256 px) + ├── app-logo.svg # Logotipo horizontal oficial em SVG (CyberCore SIGNER) + ├── app-logo.png # Fallback em PNG (480x120 px) + ├── brand-banner.svg # Banner de cabeçalho e marca em SVG (600x160 px) + ├── brand-banner.png # Fallback em PNG (600x160 px) + ├── default-seal.svg # Selo de assinatura padrão ICP-Brasil em SVG (640x164 px) + └── default-seal.png # Fallback em PNG (640x164 px) ``` --- @@ -45,20 +55,21 @@ assets/ ## 🎨 Como Customizar Qualquer Ícone ou Imagem 1. **Ícones:** - - Estão no formato `.svg` padrão. - - Podem ser editados diretamente no seu editor de código (VS Code, etc.) ou em editores vetoriais como **Inkscape, Figma ou Illustrator**. - - Se preferir colocar uma imagem bitmap para um ícone específico, você também pode colocar um `.png` (ex: `pdf.png`). O sistema prioriza `.svg` e aceita `.png` com fallback transparente. + - Todos estão no formato `.svg` padrão da W3C. + - Podem ser abertos e editados diretamente em editores de texto/código (VS Code) ou ferramentas vetoriais como **Inkscape, Figma, Adobe Illustrator**. + - Se preferir usar um arquivo bitmap para algum ícone específico, basta salvar um `.png` correspondente na pasta (ex: `pdf.png`). O sistema detecta e renderiza automaticamente. 2. **Imagens do Sistema:** - - Estão no formato `.png` com canal alfa de 32 bits (fundo transparente). - - Você pode substituir `app-logo.png`, `app-icon.png`, `brand-banner.png` ou `default-seal.png` pelo logotipo e identidade visual da sua instituição ou empresa. + - Tanto o logotipo horizontal (`app-logo.svg`), o ícone do sistema (`app-icon.svg`), o banner institucional (`brand-banner.svg`) e o selo notarial padrão (`default-seal.svg`) agora são arquivos **SVG vetoriais editáveis**. + - Você pode alterar cores, tipografias, brasões e dimensões diretamente nos arquivos SVG. + - O `AssetManager` renderiza os arquivos SVG preservando fidelidade geométrica e suportando gradientes e alinhamentos. -3. **Ordem de Prioridade e Hot-Reloading:** - Quando a aplicação busca um ícone ou imagem, a resolução ocorre na ordem: - 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Qualquer alteração feita aqui entra em vigor imediatamente ao abrir a janela ou reiniciar a aplicação, sem necessidade de recompilar! - 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Configurações específicas por usuário do sistema operacional. - 3. **Resources Empacotados no JAR (`/assets/`)**: Cópia empacotada internamente no `.jar`. - 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique em branco caso algum arquivo seja excluído. +3. **Ordem de Resolução e Carregamento:** + Ao requisitar qualquer elemento gráfico, a aplicação segue a ordem de prioridade: + 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Arquivos `.svg` editados aqui têm prioridade máxima e entram em vigor imediatamente sem necessidade de recompilar. + 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Para customizações no diretório pessoal do usuário. + 3. **Recursos Empacotados no JAR (`/assets/`)**: SVGs e imagens internas empacotadas no pacote de distribuição. + 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique desprovida de grafismos caso algum arquivo seja removido. --- diff --git a/assets/icons/about.svg b/assets/icons/about.svg index f7cc7f1..f6aac90 100644 --- a/assets/icons/about.svg +++ b/assets/icons/about.svg @@ -1 +1,6 @@ -info-circle-fill \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg new file mode 100644 index 0000000..1db6d0c --- /dev/null +++ b/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/close.svg b/assets/icons/close.svg new file mode 100644 index 0000000..3f6cc79 --- /dev/null +++ b/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/moon.svg b/assets/icons/moon.svg index fd47105..9eb6f1a 100644 --- a/assets/icons/moon.svg +++ b/assets/icons/moon.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/assets/icons/refresh.svg b/assets/icons/refresh.svg new file mode 100644 index 0000000..8ee09d9 --- /dev/null +++ b/assets/icons/refresh.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/settings.svg b/assets/icons/settings.svg index e0f4042..0a8dbc2 100644 --- a/assets/icons/settings.svg +++ b/assets/icons/settings.svg @@ -1,8 +1,4 @@ - - - - - - + + diff --git a/assets/icons/zoom-fit.svg b/assets/icons/zoom-fit.svg new file mode 100644 index 0000000..201271c --- /dev/null +++ b/assets/icons/zoom-fit.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/zoom-in.svg b/assets/icons/zoom-in.svg new file mode 100644 index 0000000..6783075 --- /dev/null +++ b/assets/icons/zoom-in.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/zoom-out.svg b/assets/icons/zoom-out.svg new file mode 100644 index 0000000..3882ea9 --- /dev/null +++ b/assets/icons/zoom-out.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/app-icon.svg b/assets/images/app-icon.svg new file mode 100644 index 0000000..10d2692 --- /dev/null +++ b/assets/images/app-icon.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/app-logo.svg b/assets/images/app-logo.svg new file mode 100644 index 0000000..ed887e6 --- /dev/null +++ b/assets/images/app-logo.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CyberCore + + + + SIGNER + + + ICP-Brasil • Assinador Digital Seguro + diff --git a/assets/images/brand-banner.svg b/assets/images/brand-banner.svg new file mode 100644 index 0000000..d133d1c --- /dev/null +++ b/assets/images/brand-banner.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CyberCore Signer + Agente de Assinatura Digital ICP-Brasil A1 & A3 + PADES • XADES • CADES • TIMESTAMPS • CCSP/1 + diff --git a/assets/images/default-seal.svg b/assets/images/default-seal.svg new file mode 100644 index 0000000..4b7c026 --- /dev/null +++ b/assets/images/default-seal.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + ASSINADO + DIGITALMENTE + + + + ★ + + ICP-BRASIL + + + + + + DOCUMENTO ASSINADO DIGITALMENTE POR: + NOME DO TITULAR DO CERTIFICADO + CPF: ***.000.000-** + Data e Hora: 11/10/2026 12:00:00 -03:00 + A conformidade com a ICP-Brasil pode ser verificada no ITI. + diff --git a/docs/screenshots/01_layout_claro.png b/docs/screenshots/01_layout_claro.png index 3e2c1e9..c251c80 100644 Binary files a/docs/screenshots/01_layout_claro.png and b/docs/screenshots/01_layout_claro.png differ diff --git a/docs/screenshots/02_layout_escuro.png b/docs/screenshots/02_layout_escuro.png index 583fa6e..446b484 100644 Binary files a/docs/screenshots/02_layout_escuro.png and b/docs/screenshots/02_layout_escuro.png differ diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java index b4b1cc0..1fc8938 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/assets/AssetManager.java @@ -5,8 +5,14 @@ import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; +import javafx.scene.paint.CycleMethod; +import javafx.scene.paint.LinearGradient; +import javafx.scene.paint.Paint; +import javafx.scene.paint.RadialGradient; +import javafx.scene.paint.Stop; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; @@ -17,7 +23,6 @@ import javafx.scene.shape.Shape; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; -import javafx.scene.transform.Scale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -26,19 +31,20 @@ import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; /** * Gerenciador centralizado de recursos visuais (ícones e imagens). - * Permite que o usuário customize ou substitua facilmente qualquer ícone (SVG/PNG) ou imagem (PNG/JPG) + * Permite que o usuário customize ou substitua facilmente qualquer ícone ou imagem (SVG/PNG) * do CyberCore Signer simplesmente colocando os arquivos na pasta 'assets/' * na raiz do projeto ou no diretório ~/.cybercore/assets/. */ @@ -121,7 +127,7 @@ public final class AssetManager { } /** - * Retorna um Node de ícone customizado (SVG ou PNG) ou invoca o fallback caso não exista imagem customizada. + * Retorna um Node de ícone customizado (prioritariamente SVG, com fallback para PNG ou supplier vetorial). */ public static Node getIconOrDefault(String iconName, double size, Supplier fallbackSupplier) { if (iconName != null && !iconName.isBlank()) { @@ -140,7 +146,20 @@ public final class AssetManager { return userSvg.get(); } - // 3. Se não houver SVG, tenta imagem bitmap (.png, .jpg) nas pastas externas + // 3. Procura SVG no Classpath interno + String resSvgPath = "/assets/icons/" + cleanName + ".svg"; + InputStream resStream = AssetManager.class.getResourceAsStream(resSvgPath); + if (resStream != null) { + try (resStream) { + Node node = parseSvgToNode(resStream, size, size); + if (node != null) { + return node; + } + } catch (Exception ignored) { + } + } + + // 4. Se não houver SVG, tenta imagem bitmap (.png, .jpg) Optional customImg = loadImage("icons", cleanName, size, size); if (customImg.isPresent() && !customImg.get().isError()) { ImageView iv = new ImageView(customImg.get()); @@ -155,28 +174,76 @@ public final class AssetManager { } /** - * Retorna a imagem do logotipo do sistema ou o fallback vetorial. + * Retorna uma imagem institucional ou elemento gráfico (prioritariamente SVG, com fallback para PNG ou supplier). */ - public static Node getLogoOrDefault(double width, double height, Supplier fallbackSupplier) { - // Tenta carregar app-logo bitmap (.png) primeiro - Optional customLogo = loadImage("images", "app-logo", width, height); - if (customLogo.isPresent() && !customLogo.get().isError()) { - ImageView iv = new ImageView(customLogo.get()); - iv.setFitWidth(width); - iv.setFitHeight(height); - iv.setPreserveRatio(true); - return iv; - } + public static Node getImageOrDefault(String imageName, double width, double height, Supplier fallbackSupplier) { + if (imageName != null && !imageName.isBlank()) { + String cleanName = cleanBaseName(imageName); - // Tenta carregar SVG de app-logo se existir - Optional svgLogo = loadSvgIconFromPath(Path.of("assets", "images", "app-logo.svg"), height); - if (svgLogo.isPresent()) { - return svgLogo.get(); + // 1. Prioridade para SVG em ./assets/images/ + Path localSvg = Path.of("assets", "images", cleanName + ".svg"); + if (Files.isRegularFile(localSvg)) { + try (InputStream in = new FileInputStream(localSvg.toFile())) { + Node node = parseSvgToNode(in, width, height); + if (node != null) { + logger.debug("Carregada imagem SVG local: {}", localSvg); + return node; + } + } catch (Exception e) { + logger.warn("Falha ao ler imagem SVG local {}: {}", localSvg, e.getMessage()); + } + } + + // 2. Procura SVG em ~/.cybercore/assets/images/ + String userHome = System.getProperty("user.home", "."); + Path userSvg = Path.of(userHome, ".cybercore", "assets", "images", cleanName + ".svg"); + if (Files.isRegularFile(userSvg)) { + try (InputStream in = new FileInputStream(userSvg.toFile())) { + Node node = parseSvgToNode(in, width, height); + if (node != null) { + logger.debug("Carregada imagem SVG do usuário: {}", userSvg); + return node; + } + } catch (Exception e) { + logger.warn("Falha ao ler imagem SVG do usuário {}: {}", userSvg, e.getMessage()); + } + } + + // 3. Procura SVG no Classpath interno + String resPath = "/assets/images/" + cleanName + ".svg"; + InputStream resStream = AssetManager.class.getResourceAsStream(resPath); + if (resStream != null) { + try (resStream) { + Node node = parseSvgToNode(resStream, width, height); + if (node != null) { + return node; + } + } catch (Exception e) { + logger.warn("Falha ao ler recurso interno SVG {}: {}", resPath, e.getMessage()); + } + } + + // 4. Fallback para imagem bitmap (.png, .jpg) + Optional customImg = loadImage("images", cleanName, width, height); + if (customImg.isPresent() && !customImg.get().isError()) { + ImageView iv = new ImageView(customImg.get()); + iv.setFitWidth(width); + iv.setFitHeight(height); + iv.setPreserveRatio(true); + return iv; + } } return fallbackSupplier != null ? fallbackSupplier.get() : new ImageView(); } + /** + * Retorna a imagem do logotipo do sistema (SVG ou bitmap). + */ + public static Node getLogoOrDefault(double width, double height, Supplier fallbackSupplier) { + return getImageOrDefault("app-logo", width, height, fallbackSupplier); + } + private static String cleanBaseName(String name) { String clean = name; int dotIdx = clean.lastIndexOf('.'); @@ -189,7 +256,7 @@ public final class AssetManager { private static Optional loadSvgIconFromPath(Path path, double size) { if (Files.isRegularFile(path)) { try (InputStream in = new FileInputStream(path.toFile())) { - Node node = parseSvgToNode(in, size); + Node node = parseSvgToNode(in, size, size); if (node != null) { logger.debug("Carregado ícone SVG customizado de: {}", path); return Optional.of(node); @@ -202,9 +269,16 @@ public final class AssetManager { } /** - * Parser analítico leve de SVG padrão para nós gráficos JavaFX com preservação geométrica exata. + * Parser analítico de SVG para nós gráficos JavaFX com suporte a dimensões quadradas. */ public static Node parseSvgToNode(InputStream in, double targetSize) { + return parseSvgToNode(in, targetSize, targetSize); + } + + /** + * Parser analítico completo de SVG para nós gráficos JavaFX com dimensões proporcionais e suporte a defs/gradientes. + */ + public static Node parseSvgToNode(InputStream in, double targetWidth, double targetHeight) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(false); @@ -249,27 +323,34 @@ public final class AssetManager { if (vbWidth <= 0) vbWidth = 48; if (vbHeight <= 0) vbHeight = 48; - Group contentGroup = new Group(); - renderChildren(svg, contentGroup); + Map defsMap = new HashMap<>(); + parseDefs(svg, defsMap); - double maxDim = Math.max(vbWidth, vbHeight); - double scale = (targetSize * 0.90) / maxDim; + Group contentGroup = new Group(); + renderChildren(svg, contentGroup, defsMap); + + double scaleX = targetWidth / vbWidth; + double scaleY = targetHeight / vbHeight; + double scale = Math.min(scaleX, scaleY); + if (scale <= 0) scale = 1.0; double drawnW = vbWidth * scale; double drawnH = vbHeight * scale; - double tx = (targetSize - drawnW) / 2.0 - (vbMinX * scale); - double ty = (targetSize - drawnH) / 2.0 - (vbMinY * scale); + double tx = (targetWidth - drawnW) / 2.0 - (vbMinX * scale); + double ty = (targetHeight - drawnH) / 2.0 - (vbMinY * scale); + contentGroup.setManaged(false); + contentGroup.setLayoutX(0); + contentGroup.setLayoutY(0); contentGroup.getTransforms().setAll( new javafx.scene.transform.Translate(tx, ty), new javafx.scene.transform.Scale(scale, scale, 0, 0) ); - StackPane container = new StackPane(contentGroup); - container.setPrefSize(targetSize, targetSize); - container.setMaxSize(targetSize, targetSize); - container.setMinSize(targetSize, targetSize); - container.setAlignment(Pos.CENTER); + Pane container = new Pane(contentGroup); + container.setPrefSize(targetWidth, targetHeight); + container.setMaxSize(targetWidth, targetHeight); + container.setMinSize(targetWidth, targetHeight); return container; } catch (Exception e) { @@ -278,12 +359,100 @@ public final class AssetManager { } } - private static void renderChildren(Element parent, Group targetGroup) { + private static void parseDefs(Element root, Map defsMap) { + NodeList defsList = root.getElementsByTagName("defs"); + for (int i = 0; i < defsList.getLength(); i++) { + org.w3c.dom.Node defNode = defsList.item(i); + if (defNode instanceof Element defsEl) { + NodeList children = defsEl.getChildNodes(); + for (int j = 0; j < children.getLength(); j++) { + org.w3c.dom.Node c = children.item(j); + if (c instanceof Element gradEl) { + String id = gradEl.getAttribute("id"); + if (!id.isBlank()) { + Paint paint = createPaintFromGradient(gradEl); + if (paint != null) { + defsMap.put(id, paint); + } + } + } + } + } + } + } + + private static Paint createPaintFromGradient(Element gradEl) { + String tag = gradEl.getTagName().toLowerCase(); + List stops = new ArrayList<>(); + NodeList stopNodes = gradEl.getElementsByTagName("stop"); + for (int i = 0; i < stopNodes.getLength(); i++) { + org.w3c.dom.Node s = stopNodes.item(i); + if (s instanceof Element stopEl) { + double offset = parseOffset(stopEl.getAttribute("offset")); + String colorStr = stopEl.getAttribute("stop-color"); + double opacity = parseDouble(stopEl.getAttribute("stop-opacity"), 1.0); + Color baseColor = parseColor(colorStr, Color.BLACK); + Color finalColor = new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), baseColor.getOpacity() * opacity); + stops.add(new Stop(offset, finalColor)); + } + } + + if (stops.isEmpty()) { + stops.add(new Stop(0, Color.web("#0288d1"))); + stops.add(new Stop(1, Color.web("#01579b"))); + } + + if ("lineargradient".equals(tag)) { + double x1 = parseCoordinate(gradEl.getAttribute("x1"), 0); + double y1 = parseCoordinate(gradEl.getAttribute("y1"), 0); + double x2 = parseCoordinate(gradEl.getAttribute("x2"), 1); + double y2 = parseCoordinate(gradEl.getAttribute("y2"), 1); + return new LinearGradient(x1, y1, x2, y2, true, CycleMethod.NO_CYCLE, stops); + } else if ("radialgradient".equals(tag)) { + double cx = parseCoordinate(gradEl.getAttribute("cx"), 0.5); + double cy = parseCoordinate(gradEl.getAttribute("cy"), 0.5); + double r = parseCoordinate(gradEl.getAttribute("r"), 0.5); + return new RadialGradient(0, 0, cx, cy, r, true, CycleMethod.NO_CYCLE, stops); + } + + return null; + } + + private static double parseOffset(String str) { + if (str == null || str.isBlank()) return 0; + String s = str.trim(); + try { + if (s.endsWith("%")) { + return Double.parseDouble(s.substring(0, s.length() - 1)) / 100.0; + } + return Double.parseDouble(s); + } catch (NumberFormatException e) { + return 0; + } + } + + private static double parseCoordinate(String str, double def) { + if (str == null || str.isBlank()) return def; + String s = str.trim(); + try { + if (s.endsWith("%")) { + return Double.parseDouble(s.substring(0, s.length() - 1)) / 100.0; + } + return Double.parseDouble(s); + } catch (NumberFormatException e) { + return def; + } + } + + private static void renderChildren(Element parent, Group targetGroup, Map defsMap) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { org.w3c.dom.Node n = children.item(i); if (n instanceof Element el) { - Node jfxNode = createElementNode(el); + if ("defs".equalsIgnoreCase(el.getTagName())) { + continue; + } + Node jfxNode = createElementNode(el, defsMap); if (jfxNode != null) { targetGroup.getChildren().add(jfxNode); } @@ -291,7 +460,7 @@ public final class AssetManager { } } - private static Node createElementNode(Element el) { + private static Node createElementNode(Element el, Map defsMap) { String tagName = el.getTagName().toLowerCase(); Shape shape = null; @@ -350,12 +519,12 @@ public final class AssetManager { double x = parseDouble(el.getAttribute("x"), 0); double y = parseDouble(el.getAttribute("y"), 0); String content = el.getTextContent(); - Text t = new Text(x, y, content); - double fs = parseDouble(el.getAttribute("font-size"), 10); + Text t = new Text(x, y, content != null ? content.trim() : ""); + double fs = parseDouble(el.getAttribute("font-size"), 12); String fw = el.getAttribute("font-weight"); t.setFont(Font.font("Segoe UI", "bold".equalsIgnoreCase(fw) ? FontWeight.BOLD : FontWeight.NORMAL, fs)); if (el.hasAttribute("fill")) { - t.setFill(parseColor(el.getAttribute("fill"), Color.BLACK)); + t.setFill(parsePaint(el.getAttribute("fill"), defsMap, Color.BLACK)); } if ("middle".equalsIgnoreCase(el.getAttribute("text-anchor"))) { t.setTextOrigin(javafx.geometry.VPos.CENTER); @@ -364,38 +533,40 @@ public final class AssetManager { } case "g" -> { Group g = new Group(); - renderChildren(el, g); + renderChildren(el, g, defsMap); if (el.hasAttribute("stroke")) { - Color strokeCol = parseColor(el.getAttribute("stroke"), null); + Paint strokePaint = parsePaint(el.getAttribute("stroke"), defsMap, null); double sw = parseDouble(el.getAttribute("stroke-width"), 1.0); for (Node child : g.getChildren()) { if (child instanceof Shape s) { - if (strokeCol != null) s.setStroke(strokeCol); + if (strokePaint != null) s.setStroke(strokePaint); s.setStrokeWidth(sw); } } } + applyElementTransform(el, g); return g; } } if (shape != null) { - applyShapeStyles(el, shape); + applyShapeStyles(el, shape, defsMap); + applyElementTransform(el, shape); return shape; } return null; } - private static void applyShapeStyles(Element el, Shape shape) { + private static void applyShapeStyles(Element el, Shape shape, Map defsMap) { if (el.hasAttribute("fill")) { - shape.setFill(parseColor(el.getAttribute("fill"), Color.TRANSPARENT)); + shape.setFill(parsePaint(el.getAttribute("fill"), defsMap, Color.TRANSPARENT)); } else if (shape instanceof Line) { shape.setFill(Color.TRANSPARENT); } if (el.hasAttribute("stroke")) { - shape.setStroke(parseColor(el.getAttribute("stroke"), Color.TRANSPARENT)); + shape.setStroke(parsePaint(el.getAttribute("stroke"), defsMap, Color.TRANSPARENT)); } if (el.hasAttribute("stroke-width")) { @@ -409,6 +580,22 @@ public final class AssetManager { } } + private static Paint parsePaint(String str, Map defsMap, Paint fallback) { + if (str == null || str.isBlank() || "none".equalsIgnoreCase(str)) { + return Color.TRANSPARENT; + } + String trimmed = str.trim(); + if (trimmed.startsWith("url(#") && trimmed.endsWith(")")) { + String id = trimmed.substring(5, trimmed.length() - 1).trim(); + Paint defPaint = defsMap.get(id); + if (defPaint != null) { + return defPaint; + } + return Color.web("#0288d1"); + } + return parseColor(trimmed, fallback instanceof Color ? (Color) fallback : Color.BLACK); + } + private static void parsePoints(String ptsStr, java.util.List pointsList) { String[] tokens = ptsStr.trim().split("[,\\s]+"); for (String t : tokens) { @@ -446,4 +633,52 @@ public final class AssetManager { return defaultValue; } } + + private static void applyElementTransform(Element el, Node node) { + if (!el.hasAttribute("transform")) return; + String tr = el.getAttribute("transform").trim(); + if (tr.isBlank()) return; + + try { + if (tr.contains("rotate(")) { + int start = tr.indexOf("rotate(") + 7; + int close = tr.indexOf(')', start); + if (close > 0) { + String[] args = tr.substring(start, close).trim().split("[,\\s]+"); + if (args.length >= 3) { + double deg = parseDouble(args[0], 0); + double px = parseDouble(args[1], 0); + double py = parseDouble(args[2], 0); + node.getTransforms().add(new javafx.scene.transform.Rotate(deg, px, py)); + } else if (args.length >= 1) { + double deg = parseDouble(args[0], 0); + node.getTransforms().add(new javafx.scene.transform.Rotate(deg)); + } + } + } + if (tr.contains("translate(")) { + int start = tr.indexOf("translate(") + 10; + int close = tr.indexOf(')', start); + if (close > 0) { + String[] args = tr.substring(start, close).trim().split("[,\\s]+"); + double tx = parseDouble(args[0], 0); + double ty = args.length > 1 ? parseDouble(args[1], 0) : 0; + node.getTransforms().add(new javafx.scene.transform.Translate(tx, ty)); + } + } + if (tr.contains("scale(")) { + int start = tr.indexOf("scale(") + 6; + int close = tr.indexOf(')', start); + if (close > 0) { + String[] args = tr.substring(start, close).trim().split("[,\\s]+"); + double sx = parseDouble(args[0], 1.0); + double sy = args.length > 1 ? parseDouble(args[1], sx) : sx; + node.getTransforms().add(new javafx.scene.transform.Scale(sx, sy)); + } + } + } catch (Exception ignored) { + } + } } + + diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java index 12061e0..05779c1 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/components/SvgIcons.java @@ -709,6 +709,10 @@ public final class SvgIcons { } public static Node closeIcon(double size) { + return AssetManager.getIconOrDefault("close", size, () -> buildCloseIcon(size)); + } + + public static Node buildCloseIcon(double size) { StackPane box = new StackPane(); box.setPrefSize(size, size); box.setMaxSize(size, size); @@ -738,6 +742,10 @@ public final class SvgIcons { } public static Node arrowRightIcon(double size, String colorHex) { + return AssetManager.getIconOrDefault("arrow-right", size, () -> buildArrowRightIcon(size, colorHex)); + } + + public static Node buildArrowRightIcon(double size, String colorHex) { StackPane box = new StackPane(); box.setPrefSize(size, size); box.setMaxSize(size, size); @@ -764,6 +772,10 @@ public final class SvgIcons { * Ícones vetoriais de Zoom In (+), Zoom Out (-) e Ajustar à Tela. */ public static Node zoomInIcon(double size) { + return AssetManager.getIconOrDefault("zoom-in", size, () -> buildZoomInIcon(size)); + } + + public static Node buildZoomInIcon(double size) { StackPane box = new StackPane(); box.setPrefSize(size, size); box.setMaxSize(size, size); @@ -786,6 +798,10 @@ public final class SvgIcons { } public static Node zoomOutIcon(double size) { + return AssetManager.getIconOrDefault("zoom-out", size, () -> buildZoomOutIcon(size)); + } + + public static Node buildZoomOutIcon(double size) { StackPane box = new StackPane(); box.setPrefSize(size, size); box.setMaxSize(size, size); @@ -808,6 +824,10 @@ public final class SvgIcons { } public static Node zoomFitIcon(double size) { + return AssetManager.getIconOrDefault("zoom-fit", size, () -> buildZoomFitIcon(size)); + } + + public static Node buildZoomFitIcon(double size) { StackPane box = new StackPane(); box.setPrefSize(size, size); box.setMaxSize(size, size); @@ -828,4 +848,33 @@ public final class SvgIcons { box.getChildren().add(path); return box; } + + /** + * Ícone de Atualização / Sincronização. + */ + public static Node refreshIcon(double size) { + return AssetManager.getIconOrDefault("refresh", size, () -> buildRefreshIcon(size)); + } + + public static Node buildRefreshIcon(double size) { + StackPane box = new StackPane(); + box.setPrefSize(size, size); + box.setMaxSize(size, size); + box.setMinSize(size, size); + box.setAlignment(Pos.CENTER); + + SVGPath path = new SVGPath(); + path.setContent("M 20 4 L 20 9 L 15 9 M 4 20 L 4 15 L 9 15 M 5.8 9 A 7 7 0 0 1 18.5 7.5 L 20 9 M 4 15 L 5.5 16.5 A 7 7 0 0 0 18.2 15"); + path.setStroke(Color.web("#0284c7")); + path.setStrokeWidth(1.8); + path.setStrokeLineCap(javafx.scene.shape.StrokeLineCap.ROUND); + path.setFill(Color.TRANSPARENT); + + double scale = size / 24.0; + path.setScaleX(scale); + path.setScaleY(scale); + + box.getChildren().add(path); + return box; + } } diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java index d9ece1c..c46360c 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/AboutView.java @@ -1,12 +1,15 @@ package br.com.cybercore.signer.ui.view; import br.com.cybercore.signer.ui.CyberCoreUiConstants; +import br.com.cybercore.signer.ui.assets.AssetManager; +import br.com.cybercore.signer.ui.components.SvgIcons; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.VBox; /** - * Painel informativo Sobre o CyberCore Signer. + * Painel informativo Sobre o CyberCore Signer com exibição de identidade visual SVG e dados do agente. */ @SuppressWarnings("this-escape") public final class AboutView extends VBox { @@ -16,13 +19,12 @@ public final class AboutView extends VBox { } private void initView() { - setSpacing(16); + setSpacing(14); setAlignment(Pos.CENTER); getStyleClass().add("content-pane"); - Label brandLabel = new Label(CyberCoreUiConstants.APP_NAME); - brandLabel.getStyleClass().add("brand-title"); - brandLabel.setStyle("-fx-font-size: 24px;"); + // Logotipo oficial vetorial SVG (app-logo.svg) com fallback para cristal 3D + Node logoNode = AssetManager.getLogoOrDefault(320, 80, () -> SvgIcons.createAppCrystalLogo(54)); Label versionLabel = new Label("Versão " + CyberCoreUiConstants.APP_VERSION + " • CCSP/1"); versionLabel.getStyleClass().add("status-badge-active"); @@ -44,9 +46,9 @@ public final class AboutView extends VBox { detailsBox.getChildren().add(lbl); } - Label copyLabel = new Label("© 2026 CyberCore. Todos os direitos reservados."); + Label copyLabel = new Label("© 2026 CyberCore Systems. Todos os direitos reservados."); copyLabel.getStyleClass().add("dialog-subtitle"); - getChildren().addAll(brandLabel, versionLabel, descLabel, detailsBox, copyLabel); + getChildren().addAll(logoNode, versionLabel, descLabel, detailsBox, copyLabel); } } diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java index 092333d..151a495 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ApplicationApprovalDialog.java @@ -39,7 +39,10 @@ public class ApplicationApprovalDialog { root.setPadding(new Insets(24)); root.getStyleClass().add("dialog-container"); - // Cabeçalho + // Cabeçalho com Ícone SVG de Escudo + HBox header = new HBox(14); + header.setAlignment(Pos.CENTER_LEFT); + VBox headerBox = new VBox(4); Label titleLabel = new Label("NOVA APLICAÇÃO WEB SOLICITA ACESSO"); titleLabel.getStyleClass().add("dialog-header"); @@ -48,6 +51,8 @@ public class ApplicationApprovalDialog { subtitleLabel.getStyleClass().add("dialog-subtitle"); headerBox.getChildren().addAll(titleLabel, subtitleLabel); + header.getChildren().addAll(br.com.cybercore.signer.ui.components.SvgIcons.cyberShieldLogo(34), headerBox); + // Painel de Detalhes da Aplicação VBox detailsBox = new VBox(10); detailsBox.getStyleClass().add("dialog-box"); @@ -106,7 +111,7 @@ public class ApplicationApprovalDialog { actionsRow.getChildren().addAll(btnReject, btnAuthorize); - root.getChildren().addAll(headerBox, detailsBox, secWarning, actionsRow); + root.getChildren().addAll(header, detailsBox, secWarning, actionsRow); Scene scene = new Scene(root); ThemeManager.applyTheme(scene); diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java index c568a96..c0968f1 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/CertificatesManagerDialog.java @@ -77,7 +77,7 @@ public final class CertificatesManagerDialog { btnAddA1.getStyleClass().add("btn-primary"); btnAddA1.setOnAction(e -> handleImportA1()); - Button btnRefresh = new Button("Atualizar Lista"); + Button btnRefresh = new Button(" Atualizar Lista", SvgIcons.refreshIcon(14)); btnRefresh.getStyleClass().add("btn-secondary"); btnRefresh.setOnAction(e -> refreshList()); @@ -93,7 +93,7 @@ public final class CertificatesManagerDialog { HBox footer = new HBox(); footer.setAlignment(Pos.CENTER_RIGHT); - Button btnClose = new Button("Fechar"); + Button btnClose = new Button(" Fechar", SvgIcons.closeIcon(13)); btnClose.getStyleClass().add("btn-secondary"); btnClose.setOnAction(e -> stage.close()); footer.getChildren().add(btnClose); diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java index 7bf672c..e6c4d4f 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java @@ -13,6 +13,7 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.Stage; @@ -204,14 +205,20 @@ public final class MainAppView { private void updateThemeToggleButton() { boolean dark = ThemeManager.isDark(); - HBox content = new HBox(6); + HBox content = new HBox(8); content.setAlignment(Pos.CENTER); Node icon = dark ? SvgIcons.sunIcon(15) : SvgIcons.moonIcon(15); + StackPane iconBox = new StackPane(icon); + iconBox.setPrefSize(18, 18); + iconBox.setMinSize(18, 18); + iconBox.setMaxSize(18, 18); + iconBox.setAlignment(Pos.CENTER); + Label label = new Label(dark ? "Modo Claro" : "Modo Escuro"); label.setStyle("-fx-font-size: 11.5px; -fx-font-weight: bold;"); - content.getChildren().addAll(icon, label); + content.getChildren().addAll(iconBox, label); themeToggleBtn.setGraphic(content); } @@ -223,16 +230,22 @@ public final class MainAppView { btn.setPrefHeight(68); btn.setMinHeight(68); - HBox content = new HBox(10); + HBox content = new HBox(12); content.setAlignment(Pos.CENTER_LEFT); Label lbl = new Label(text); lbl.getStyleClass().addAll("cyber-action-text", "serpro-action-text"); lbl.setWrapText(true); + lbl.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(lbl, Priority.ALWAYS); if (icon != null) { - content.getChildren().addAll(icon, lbl); + StackPane iconSlot = new StackPane(icon); + iconSlot.setPrefSize(40, 40); + iconSlot.setMinSize(40, 40); + iconSlot.setMaxSize(40, 40); + iconSlot.setAlignment(Pos.CENTER); + content.getChildren().addAll(iconSlot, lbl); } else { content.getChildren().add(lbl); } @@ -251,16 +264,22 @@ public final class MainAppView { btn.setMinHeight(42); btn.setMaxWidth(Double.MAX_VALUE); - HBox content = new HBox(10); + HBox content = new HBox(12); content.setAlignment(Pos.CENTER_LEFT); Label lbl = new Label(text); lbl.getStyleClass().addAll("cyber-side-text", "serpro-side-text"); - lbl.setWrapText(true); + lbl.setWrapText(false); + lbl.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(lbl, Priority.ALWAYS); if (icon != null) { - content.getChildren().addAll(icon, lbl); + StackPane iconSlot = new StackPane(icon); + iconSlot.setPrefSize(26, 26); + iconSlot.setMinSize(26, 26); + iconSlot.setMaxSize(26, 26); + iconSlot.setAlignment(Pos.CENTER); + content.getChildren().addAll(iconSlot, lbl); } else { content.getChildren().add(lbl); } diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java index c91b0db..07bee99 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java @@ -488,7 +488,7 @@ public final class PdfSignDialog { resizeToolBox.setAlignment(Pos.CENTER_LEFT); resizeToolBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; -fx-border-radius: 6px; -fx-padding: 2px 6px;"); - btnToggleResize = new Button("📏 Redimensionar"); + btnToggleResize = new Button(" Redimensionar", SvgIcons.zoomFitIcon(13)); btnToggleResize.getStyleClass().addAll("btn-secondary", "btn-compact"); btnToggleResize.setTooltip(new Tooltip("Ativar/desativar alça interativa para alterar o tamanho do selo")); btnToggleResize.setOnAction(e -> toggleResizeMode()); @@ -496,7 +496,7 @@ public final class PdfSignDialog { lblSealScaleIndicator = new Label("100%"); lblSealScaleIndicator.setStyle("-fx-font-size: 11px; -fx-font-weight: bold; -fx-min-width: 36px; -fx-alignment: center;"); - btnResetSealScale = new Button("↺ 100%"); + btnResetSealScale = new Button(" 100%", SvgIcons.refreshIcon(12)); btnResetSealScale.getStyleClass().addAll("btn-secondary", "btn-compact"); btnResetSealScale.setTooltip(new Tooltip("Restaurar tamanho padrão original (100%)")); btnResetSealScale.setVisible(false); @@ -507,7 +507,7 @@ public final class PdfSignDialog { Region tbSpacer = new Region(); HBox.setHgrow(tbSpacer, Priority.ALWAYS); - lblPositionFeedback = new Label("👉 Clique na folha para posicionar"); + lblPositionFeedback = new Label("Clique na folha para posicionar"); lblPositionFeedback.setStyle("-fx-font-size: 11.5px; -fx-text-fill: -fx-accent; -fx-font-weight: bold;"); toolbar.getChildren().addAll(pageNavBox, zoomBox, resizeToolBox, tbSpacer, lblPositionFeedback); diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java index e304a75..5c6dc79 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/SignaturePromptDialog.java @@ -5,6 +5,7 @@ import br.com.cybercore.signer.core.domain.ManagedCertificate; import br.com.cybercore.signer.core.domain.SigningContext; import br.com.cybercore.signer.core.security.PinSessionManager; import br.com.cybercore.signer.ui.CyberCoreUiConstants; +import br.com.cybercore.signer.ui.components.SvgIcons; import br.com.cybercore.signer.ui.theme.ThemeManager; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -60,13 +61,23 @@ public class SignaturePromptDialog { root.getStyleClass().add("dialog-container"); root.setPrefWidth(540); - Label header = new Label(isAuthentication ? "🔑 SOLICITAÇÃO DE LOGIN DIGITAL" : "SOLICITAÇÃO DE ASSINATURA"); + HBox headerBox = new HBox(12); + headerBox.setAlignment(Pos.CENTER_LEFT); + + VBox titleBox = new VBox(2); + Label header = new Label(isAuthentication ? "SOLICITAÇÃO DE LOGIN DIGITAL" : "SOLICITAÇÃO DE ASSINATURA"); header.getStyleClass().add("dialog-header"); Label desc = new Label(isAuthentication ? "Uma aplicação web está solicitando autenticação de identidade com seu Certificado Digital ICP-Brasil." : "Uma aplicação web está solicitando a assinatura digital de um documento."); desc.getStyleClass().add("brand-subtitle"); + titleBox.getChildren().addAll(header, desc); + + headerBox.getChildren().addAll( + isAuthentication ? SvgIcons.certificatesIcon() : SvgIcons.cyberShieldLogo(26), + titleBox + ); // Caixa de Detalhes VBox detailsBox = new VBox(12); @@ -231,7 +242,7 @@ public class SignaturePromptDialog { actionsRow.getChildren().addAll(btnCancel, btnConfirm); - root.getChildren().addAll(header, desc, detailsBox, validationLabel, actionsRow); + root.getChildren().addAll(headerBox, detailsBox, validationLabel, actionsRow); Scene scene = new Scene(root); ThemeManager.applyTheme(scene); diff --git a/modules/cybercore-ui/src/main/resources/assets/README.md b/modules/cybercore-ui/src/main/resources/assets/README.md index d77f68b..91f8eef 100644 --- a/modules/cybercore-ui/src/main/resources/assets/README.md +++ b/modules/cybercore-ui/src/main/resources/assets/README.md @@ -1,6 +1,6 @@ -# Pasta de Ícones (SVG) e Imagens (PNG) — CyberCore Signer +# Recursos Visuais (Ícones e Imagens em SVG) — CyberCore Signer -Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente os **ícones em SVG** e as **imagens em PNG** do **CyberCore Signer**. +Esta pasta é o repositório oficial onde você pode visualizar, customizar e substituir livremente todos os **ícones em SVG** e as **imagens em SVG** do **CyberCore Signer**. --- @@ -31,13 +31,23 @@ assets/ │ ├── seal.svg # Ícone Designer e Gerenciador de Selos Notariais │ ├── check.svg # Ícone Checkmark de sucesso e validação │ ├── error.svg # Ícone de Erro / Alerta em vermelho -│ └── star.svg # Ícone de Estrela notarial / favorita +│ ├── star.svg # Ícone de Estrela notarial / favorita +│ ├── close.svg # Ícone Fechar / Cancelar (cruz geométrica moderna) +│ ├── arrow-right.svg # Ícone Avançar / Seta direcional +│ ├── zoom-in.svg # Ícone Zoom In (+) +│ ├── zoom-out.svg # Ícone Zoom Out (-) +│ ├── zoom-fit.svg # Ícone Ajustar à Tela +│ └── refresh.svg # Ícone Atualizar / Recarregar listas e status │ -└── images/ # Imagens de alta fidelidade em PNG com canal alfa (transparência) - ├── app-icon.png # Ícone oficial do app (256x256 px - Diamante 3D azul Serpro) - ├── app-logo.png # Logotipo horizontal oficial (480x120 px - CyberCore SIGNER) - ├── brand-banner.png # Banner de cabeçalho e marca (600x160 px - Serpro Blue) - └── default-seal.png # Selo de assinatura padrão ICP-Brasil (640x164 px) +└── images/ # Imagens vetoriais SVG editáveis (com cópias PNG de fallback) + ├── app-icon.svg # Ícone oficial do app em SVG (Cristal/Diamante 3D azul) + ├── app-icon.png # Fallback em PNG (256x256 px) + ├── app-logo.svg # Logotipo horizontal oficial em SVG (CyberCore SIGNER) + ├── app-logo.png # Fallback em PNG (480x120 px) + ├── brand-banner.svg # Banner de cabeçalho e marca em SVG (600x160 px) + ├── brand-banner.png # Fallback em PNG (600x160 px) + ├── default-seal.svg # Selo de assinatura padrão ICP-Brasil em SVG (640x164 px) + └── default-seal.png # Fallback em PNG (640x164 px) ``` --- @@ -45,20 +55,21 @@ assets/ ## 🎨 Como Customizar Qualquer Ícone ou Imagem 1. **Ícones:** - - Estão no formato `.svg` padrão. - - Podem ser editados diretamente no seu editor de código (VS Code, etc.) ou em editores vetoriais como **Inkscape, Figma ou Illustrator**. - - Se preferir colocar uma imagem bitmap para um ícone específico, você também pode colocar um `.png` (ex: `pdf.png`). O sistema prioriza `.svg` e aceita `.png` com fallback transparente. + - Todos estão no formato `.svg` padrão da W3C. + - Podem ser abertos e editados diretamente em editores de texto/código (VS Code) ou ferramentas vetoriais como **Inkscape, Figma, Adobe Illustrator**. + - Se preferir usar um arquivo bitmap para algum ícone específico, basta salvar um `.png` correspondente na pasta (ex: `pdf.png`). O sistema detecta e renderiza automaticamente. 2. **Imagens do Sistema:** - - Estão no formato `.png` com canal alfa de 32 bits (fundo transparente). - - Você pode substituir `app-logo.png`, `app-icon.png`, `brand-banner.png` ou `default-seal.png` pelo logotipo e identidade visual da sua instituição ou empresa. + - Tanto o logotipo horizontal (`app-logo.svg`), o ícone do sistema (`app-icon.svg`), o banner institucional (`brand-banner.svg`) e o selo notarial padrão (`default-seal.svg`) agora são arquivos **SVG vetoriais editáveis**. + - Você pode alterar cores, tipografias, brasões e dimensões diretamente nos arquivos SVG. + - O `AssetManager` renderiza os arquivos SVG preservando fidelidade geométrica e suportando gradientes e alinhamentos. -3. **Ordem de Prioridade e Hot-Reloading:** - Quando a aplicação busca um ícone ou imagem, a resolução ocorre na ordem: - 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Qualquer alteração feita aqui entra em vigor imediatamente ao abrir a janela ou reiniciar a aplicação, sem necessidade de recompilar! - 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Configurações específicas por usuário do sistema operacional. - 3. **Resources Empacotados no JAR (`/assets/`)**: Cópia empacotada internamente no `.jar`. - 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique em branco caso algum arquivo seja excluído. +3. **Ordem de Resolução e Carregamento:** + Ao requisitar qualquer elemento gráfico, a aplicação segue a ordem de prioridade: + 1. **Pasta Física Local (`./assets/icons/` e `./assets/images/`)**: Arquivos `.svg` editados aqui têm prioridade máxima e entram em vigor imediatamente sem necessidade de recompilar. + 2. **Pasta do Usuário (`~/.cybercore/assets/`)**: Para customizações no diretório pessoal do usuário. + 3. **Recursos Empacotados no JAR (`/assets/`)**: SVGs e imagens internas empacotadas no pacote de distribuição. + 4. **Fallback Vetorial Nativo (`SvgIcons`)**: Garantia de que a interface nunca fique desprovida de grafismos caso algum arquivo seja removido. --- diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/about.svg b/modules/cybercore-ui/src/main/resources/assets/icons/about.svg index dd4abb9..f6aac90 100644 --- a/modules/cybercore-ui/src/main/resources/assets/icons/about.svg +++ b/modules/cybercore-ui/src/main/resources/assets/icons/about.svg @@ -1,5 +1,6 @@ - - - - + + + + + \ No newline at end of file diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/arrow-right.svg b/modules/cybercore-ui/src/main/resources/assets/icons/arrow-right.svg new file mode 100644 index 0000000..1db6d0c --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/close.svg b/modules/cybercore-ui/src/main/resources/assets/icons/close.svg new file mode 100644 index 0000000..3f6cc79 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg b/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg index fd47105..9eb6f1a 100644 --- a/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg +++ b/modules/cybercore-ui/src/main/resources/assets/icons/moon.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/refresh.svg b/modules/cybercore-ui/src/main/resources/assets/icons/refresh.svg new file mode 100644 index 0000000..8ee09d9 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/icons/refresh.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg b/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg index e0f4042..0a8dbc2 100644 --- a/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg +++ b/modules/cybercore-ui/src/main/resources/assets/icons/settings.svg @@ -1,8 +1,4 @@ - - - - - - + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/zoom-fit.svg b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-fit.svg new file mode 100644 index 0000000..201271c --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-fit.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/zoom-in.svg b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-in.svg new file mode 100644 index 0000000..6783075 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-in.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/icons/zoom-out.svg b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-out.svg new file mode 100644 index 0000000..3882ea9 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/icons/zoom-out.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/images/app-icon.svg b/modules/cybercore-ui/src/main/resources/assets/images/app-icon.svg new file mode 100644 index 0000000..10d2692 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/images/app-icon.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/cybercore-ui/src/main/resources/assets/images/app-logo.svg b/modules/cybercore-ui/src/main/resources/assets/images/app-logo.svg new file mode 100644 index 0000000..ed887e6 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/images/app-logo.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CyberCore + + + + SIGNER + + + ICP-Brasil • Assinador Digital Seguro + diff --git a/modules/cybercore-ui/src/main/resources/assets/images/brand-banner.svg b/modules/cybercore-ui/src/main/resources/assets/images/brand-banner.svg new file mode 100644 index 0000000..d133d1c --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/images/brand-banner.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CyberCore Signer + Agente de Assinatura Digital ICP-Brasil A1 & A3 + PADES • XADES • CADES • TIMESTAMPS • CCSP/1 + diff --git a/modules/cybercore-ui/src/main/resources/assets/images/default-seal.svg b/modules/cybercore-ui/src/main/resources/assets/images/default-seal.svg new file mode 100644 index 0000000..4b7c026 --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/assets/images/default-seal.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + ASSINADO + DIGITALMENTE + + + + ★ + + ICP-BRASIL + + + + + + DOCUMENTO ASSINADO DIGITALMENTE POR: + NOME DO TITULAR DO CERTIFICADO + CPF: ***.000.000-** + Data e Hora: 11/10/2026 12:00:00 -03:00 + A conformidade com a ICP-Brasil pode ser verificada no ITI. + diff --git a/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/assets/AssetManagerTest.java b/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/assets/AssetManagerTest.java new file mode 100644 index 0000000..0b26b0d --- /dev/null +++ b/modules/cybercore-ui/src/test/java/br/com/cybercore/signer/ui/assets/AssetManagerTest.java @@ -0,0 +1,58 @@ +package br.com.cybercore.signer.ui.assets; + +import br.com.cybercore.signer.ui.components.SvgIcons; +import javafx.scene.Node; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +class AssetManagerTest { + + @Test + @DisplayName("Deve carregar imagens institucionais em SVG do classpath ou pasta de assets") + void shouldLoadSvgImages() { + Node logo = AssetManager.getImageOrDefault("app-logo", 480, 120, null); + assertThat(logo).isNotNull(); + + Node banner = AssetManager.getImageOrDefault("brand-banner", 600, 160, null); + assertThat(banner).isNotNull(); + + Node icon = AssetManager.getImageOrDefault("app-icon", 256, 256, null); + assertThat(icon).isNotNull(); + + Node seal = AssetManager.getImageOrDefault("default-seal", 640, 164, null); + assertThat(seal).isNotNull(); + } + + @Test + @DisplayName("Deve carregar ícones SVG dinâmicos através do AssetManager") + void shouldLoadSvgIcons() { + String[] iconNames = { + "pdf", "xml", "file", "batch", "attached", "co-sign", + "timestamp", "ltv", "validate", "validate-timestamp", "exit", + "logs", "settings", "tutorial", "cert", "about", "sun", "moon", + "shield", "seal", "check", "error", "star", "close", "arrow-right", + "zoom-in", "zoom-out", "zoom-fit", "refresh" + }; + + for (String name : iconNames) { + Node iconNode = AssetManager.getIconOrDefault(name, 24, null); + assertThat(iconNode).withFailMessage("Ícone SVG não encontrado ou nulo: %s", name).isNotNull(); + } + } + + @Test + @DisplayName("Deve instanciar nós de ícones via fábrica SvgIcons com fallback") + void shouldCreateNodesViaSvgIcons() { + assertThat(SvgIcons.closeIcon(16)).isNotNull(); + assertThat(SvgIcons.arrowRightIcon(16)).isNotNull(); + assertThat(SvgIcons.zoomInIcon(16)).isNotNull(); + assertThat(SvgIcons.zoomOutIcon(16)).isNotNull(); + assertThat(SvgIcons.zoomFitIcon(16)).isNotNull(); + assertThat(SvgIcons.refreshIcon(16)).isNotNull(); + assertThat(SvgIcons.createAppCrystalLogo(48)).isNotNull(); + } +}