Use svg logo
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
196
src/main/java/org/example/petshopdesktop/ui/SvgNodeLoader.java
Normal file
196
src/main/java/org/example/petshopdesktop/ui/SvgNodeLoader.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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?>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user