Use svg logo

This commit is contained in:
2026-03-11 12:09:16 -06:00
parent 4a96501f73
commit fbec4fd1c1
5 changed files with 213 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ module org.example.petshopdesktop {
requires javafx.fxml;
requires java.sql;
requires java.net.http;
requires java.xml;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.annotation;
@@ -14,6 +15,7 @@ module org.example.petshopdesktop {
opens org.example.petshopdesktop.controllers.dialogcontrollers to javafx.fxml;
opens org.example.petshopdesktop.controllers to javafx.fxml;
opens org.example.petshopdesktop.auth to javafx.fxml;
opens org.example.petshopdesktop.ui to javafx.fxml;
opens org.example.petshopdesktop.api.dto.common to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.auth to com.fasterxml.jackson.databind;

View File

@@ -8,7 +8,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Modality;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.auth.LoginRequest;
@@ -16,6 +16,7 @@ import org.example.petshopdesktop.api.dto.auth.LoginResponse;
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
import org.example.petshopdesktop.auth.Role;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.ui.SvgNodeLoader;
import org.example.petshopdesktop.util.ActivityLogger;
public class LoginController {
@@ -29,9 +30,13 @@ public class LoginController {
@FXML
private Label lblError;
@FXML
private StackPane logoContainer;
@FXML
public void initialize() {
lblError.setText("");
logoContainer.getChildren().setAll(SvgNodeLoader.loadSquare("/org/example/petshopdesktop/images/leons-pet-store-badge-text.svg", 190));
}
@FXML

View File

@@ -0,0 +1,196 @@
package org.example.petshopdesktop.ui;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.FillRule;
import javafx.scene.shape.Shape;
import javafx.scene.shape.SVGPath;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public final class SvgNodeLoader {
private SvgNodeLoader() {
}
public static StackPane loadSquare(String resourcePath, double size) {
SvgData svgData = readSvg(resourcePath);
Group translated = new Group(svgData.content());
translated.setTranslateX(-svgData.minX());
translated.setTranslateY(-svgData.minY());
double scale = Math.min(size / svgData.width(), size / svgData.height());
translated.setScaleX(scale);
translated.setScaleY(scale);
StackPane pane = new StackPane(translated);
pane.setMinSize(size, size);
pane.setPrefSize(size, size);
pane.setMaxSize(size, size);
return pane;
}
private static SvgData readSvg(String resourcePath) {
try (Reader reader = requireResource(resourcePath)) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
Document document = factory.newDocumentBuilder().parse(new InputSource(reader));
Element root = document.getDocumentElement();
double[] viewBox = parseViewBox(root.getAttribute("viewBox"));
Map<String, Map<String, String>> styles = parseStyles(root);
Group content = new Group();
appendChildren(root, content, styles);
return new SvgData(content, viewBox[0], viewBox[1], viewBox[2], viewBox[3]);
} catch (Exception e) {
throw new IllegalStateException("Unable to load SVG: " + resourcePath, e);
}
}
private static InputStreamReader requireResource(String resourcePath) {
var stream = SvgNodeLoader.class.getResourceAsStream(resourcePath);
if (stream == null) {
throw new IllegalArgumentException("Resource not found: " + resourcePath);
}
return new InputStreamReader(stream, StandardCharsets.UTF_8);
}
private static double[] parseViewBox(String viewBox) {
String[] parts = viewBox.trim().split("\\s+");
if (parts.length != 4) {
throw new IllegalArgumentException("Invalid viewBox: " + viewBox);
}
return new double[]{
Double.parseDouble(parts[0]),
Double.parseDouble(parts[1]),
Double.parseDouble(parts[2]),
Double.parseDouble(parts[3])
};
}
private static Map<String, Map<String, String>> parseStyles(Element root) {
Map<String, Map<String, String>> styles = new HashMap<>();
NodeList styleNodes = root.getElementsByTagName("style");
for (int i = 0; i < styleNodes.getLength(); i++) {
String css = styleNodes.item(i).getTextContent();
for (String rule : css.split("}")) {
String trimmedRule = rule.trim();
if (trimmedRule.isEmpty()) {
continue;
}
String[] selectorAndBody = trimmedRule.split("\\{", 2);
if (selectorAndBody.length != 2) {
continue;
}
String selector = selectorAndBody[0].trim();
if (!selector.startsWith(".")) {
continue;
}
Map<String, String> properties = new LinkedHashMap<>();
for (String declaration : selectorAndBody[1].split(";")) {
String trimmedDeclaration = declaration.trim();
if (trimmedDeclaration.isEmpty()) {
continue;
}
String[] keyValue = trimmedDeclaration.split(":", 2);
if (keyValue.length == 2) {
properties.put(keyValue[0].trim(), keyValue[1].trim());
}
}
styles.put(selector.substring(1), properties);
}
}
return styles;
}
private static void appendChildren(Element parent, Group target, Map<String, Map<String, String>> styles) {
NodeList childNodes = parent.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
org.w3c.dom.Node child = childNodes.item(i);
if (!(child instanceof Element element)) {
continue;
}
switch (element.getTagName()) {
case "g" -> {
Group group = new Group();
appendChildren(element, group, styles);
target.getChildren().add(group);
}
case "circle" -> target.getChildren().add(buildCircle(element, styles));
case "path" -> target.getChildren().add(buildPath(element, styles));
default -> {
}
}
}
}
private static Circle buildCircle(Element element, Map<String, Map<String, String>> styles) {
Circle circle = new Circle(
Double.parseDouble(element.getAttribute("cx")),
Double.parseDouble(element.getAttribute("cy")),
Double.parseDouble(element.getAttribute("r"))
);
applyShapeStyle(circle, element, styles);
return circle;
}
private static SVGPath buildPath(Element element, Map<String, Map<String, String>> styles) {
SVGPath path = new SVGPath();
path.setContent(element.getAttribute("d"));
applyShapeStyle(path, element, styles);
return path;
}
private static void applyShapeStyle(Shape shape, Element element, Map<String, Map<String, String>> styles) {
Map<String, String> styleMap = new LinkedHashMap<>();
String className = element.getAttribute("class");
if (!className.isBlank()) {
for (String cssClass : className.trim().split("\\s+")) {
Map<String, String> classStyles = styles.get(cssClass);
if (classStyles != null) {
styleMap.putAll(classStyles);
}
}
}
copyAttribute(element, styleMap, "fill");
copyAttribute(element, styleMap, "stroke");
copyAttribute(element, styleMap, "stroke-width");
copyAttribute(element, styleMap, "fill-rule");
if (styleMap.containsKey("fill") && !"none".equalsIgnoreCase(styleMap.get("fill"))) {
shape.setFill(Paint.valueOf(styleMap.get("fill")));
} else {
shape.setFill(null);
}
if (styleMap.containsKey("stroke") && !"none".equalsIgnoreCase(styleMap.get("stroke"))) {
shape.setStroke(Paint.valueOf(styleMap.get("stroke")));
}
if (styleMap.containsKey("stroke-width")) {
shape.setStrokeWidth(Double.parseDouble(styleMap.get("stroke-width")));
}
if (styleMap.containsKey("fill-rule") && shape instanceof SVGPath svgPath) {
svgPath.setFillRule("evenodd".equalsIgnoreCase(styleMap.get("fill-rule")) ? FillRule.EVEN_ODD : FillRule.NON_ZERO);
}
}
private static void copyAttribute(Element element, Map<String, String> styleMap, String attribute) {
String value = element.getAttribute(attribute);
if (!value.isBlank()) {
styleMap.put(attribute, value);
}
}
private record SvgData(Group content, double minX, double minY, double width, double height) {
}
}

View File

@@ -5,9 +5,10 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.effect.DropShadow?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.paint.Color?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" prefHeight="420.0" prefWidth="380.0" spacing="16.0"
@@ -19,14 +20,14 @@
<Insets bottom="40.0" left="50.0" right="50.0" top="40.0" />
</padding>
<children>
<ImageView fitHeight="190.0" fitWidth="190.0" preserveRatio="true" smooth="true" style="-fx-effect: dropshadow(gaussian, rgba(15,23,42,0.22), 18, 0.16, 0, 6);">
<image>
<Image url="@images/leons-pet-store-badge-text.png" />
</image>
<StackPane fx:id="logoContainer" maxHeight="190.0" maxWidth="190.0" minHeight="190.0" minWidth="190.0" prefHeight="190.0" prefWidth="190.0">
<effect>
<DropShadow blurType="GAUSSIAN" color="0x0f172838" height="18.0" offsetY="6.0" radius="9.0" spread="0.16" width="18.0" />
</effect>
<VBox.margin>
<Insets bottom="35.0" />
</VBox.margin>
</ImageView>
</StackPane>
<Label text="Username" textFill="#cccccc">
<font>

View File

@@ -9,6 +9,7 @@
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>