From 9e466b041b6e0b4aa4bba23e73edaf337ec1ba2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=99=BD=E4=B8=8E=E5=AD=9F=E6=B5=A9=E7=84=B6?= <1063889643@qq.com> Date: Mon, 9 Oct 2023 18:12:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=BC=95=E5=85=A5=E6=87=92=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/light/GitManagerApp.java | 3 +- src/main/java/com/light/layout/MenuPane.java | 19 +++++++-- src/main/java/com/light/layout/NavItem.java | 7 ++-- src/main/java/com/light/util/Lazy.java | 40 +++++++++++++++++++ src/main/java/com/light/view/ManagerView.java | 2 +- 5 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/light/util/Lazy.java diff --git a/src/main/java/com/light/GitManagerApp.java b/src/main/java/com/light/GitManagerApp.java index 76f09a7..f79ac78 100644 --- a/src/main/java/com/light/GitManagerApp.java +++ b/src/main/java/com/light/GitManagerApp.java @@ -25,6 +25,7 @@ public class GitManagerApp extends Application { @Override public void start(Stage stage) throws Exception { + long startTime = System.currentTimeMillis(); // 主题 Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); // Application.setUserAgentStylesheet(new Dracula().getUserAgentStylesheet()); @@ -54,7 +55,7 @@ public class GitManagerApp extends Application { stage.show(); scene.getStylesheets().add(STYLE_SHEET); - LOGGER.info("项目启动完成。。。"); + LOGGER.info("项目启动完成。。。{}", System.currentTimeMillis() - startTime); } public static void main(String[] args) { diff --git a/src/main/java/com/light/layout/MenuPane.java b/src/main/java/com/light/layout/MenuPane.java index 9ad67ff..0e9c013 100644 --- a/src/main/java/com/light/layout/MenuPane.java +++ b/src/main/java/com/light/layout/MenuPane.java @@ -3,6 +3,7 @@ package com.light.layout; import atlantafx.base.theme.Styles; import com.light.util.FxDataUtil; import com.light.util.FxUtil; +import com.light.util.Lazy; import com.light.view.HomeView; import com.light.view.ManagerView; import javafx.geometry.Orientation; @@ -47,14 +48,21 @@ public class MenuPane extends StackPane { private final ContentPane contentPane; + // 子菜单 + private final Lazy homeViewLazy; + private final Lazy managerViewLazy; + public MenuPane(ContentPane contentPane) { this.contentPane = contentPane; initialize(); + managerViewLazy = new Lazy<>(ManagerView::new); + homeViewLazy = new Lazy<>(HomeView::new); + // 菜单栏标题 dynamicMenu.getItems().addAll( - new NavItem(new FontIcon(AntDesignIconsOutlined.DOWNLOAD), "下载", new HomeView()), - new NavItem(new FontIcon(AntDesignIconsOutlined.PARTITION), "管理", new ManagerView()), + new NavItem(new FontIcon(AntDesignIconsOutlined.DOWNLOAD), "下载", homeViewLazy), + new NavItem(new FontIcon(AntDesignIconsOutlined.PARTITION), "管理", managerViewLazy), new NavItem(new FontIcon(AntDesignIconsOutlined.EDIT), "笔记", null) ); dynamicMenu.getSelectionModel().selectFirst(); @@ -91,7 +99,7 @@ public class MenuPane extends StackPane { // 加上监听 dynamicMenu.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { Optional.ofNullable(newValue).ifPresent(menuItem -> { - contentPane.setContent(menuItem.getContent());//设置内容 + contentPane.setContent(menuItem.getContent().get());//设置内容 }); }); @@ -101,5 +109,10 @@ public class MenuPane extends StackPane { FxDataUtil.UPDATE_PROPERTY.addListener((observable, oldValue, newValue) -> { updateLabelText.setText(newValue.toString()); }); + + // 设置 + setting.setOnMouseClicked(event -> { + setting.getContent(); + }); } } diff --git a/src/main/java/com/light/layout/NavItem.java b/src/main/java/com/light/layout/NavItem.java index 76a946d..edd90a3 100644 --- a/src/main/java/com/light/layout/NavItem.java +++ b/src/main/java/com/light/layout/NavItem.java @@ -1,5 +1,6 @@ package com.light.layout; +import com.light.util.Lazy; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; @@ -24,9 +25,9 @@ public class NavItem extends HBox { /** * 内容界面 */ - private Node content; + private Lazy content; - public NavItem(FontIcon fontIcon, String title, Node content) { + public NavItem(FontIcon fontIcon, String title, Lazy content) { this.iconLabel.setGraphic(fontIcon); this.titleLabel.setText(title); this.content = content; @@ -45,7 +46,7 @@ public class NavItem extends HBox { prefWidthProperty().bind(this.widthProperty().subtract(1)); } - public Node getContent() { + public Lazy getContent() { return content; } } diff --git a/src/main/java/com/light/util/Lazy.java b/src/main/java/com/light/util/Lazy.java new file mode 100644 index 0000000..393094b --- /dev/null +++ b/src/main/java/com/light/util/Lazy.java @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: MIT */ + +package com.light.util; + +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Auxiliary object wrapper to support lazy initialization. + * DO NOT override {@code hashCode()} / {@code equals()}, because each instance + * of this object must remain unique. + */ +public class Lazy implements Supplier { + + protected final Supplier supplier; + protected @Nullable T value = null; + + public Lazy(Supplier supplier) { + this.supplier = Objects.requireNonNull(supplier, "supplier"); + } + + @Override + public T get() { + if (value == null) { + value = supplier.get(); + } + return value; + } + + public boolean initialized() { + return value != null; + } + + @Override + public String toString() { + return String.valueOf(value); + } +} \ No newline at end of file diff --git a/src/main/java/com/light/view/ManagerView.java b/src/main/java/com/light/view/ManagerView.java index c509130..613f037 100644 --- a/src/main/java/com/light/view/ManagerView.java +++ b/src/main/java/com/light/view/ManagerView.java @@ -133,7 +133,7 @@ public class ManagerView extends StackPane { public void initData() { // TODO 查H2数据库获取数据 - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 1000000; i++) { GitProject gitProject = new GitProject(i + "", "g" + i + "it", "project", "develop", "2023-09-30", "2023-09-30", "https://gitee.com/code-poison/git-manager-client-fx.git", "D:\\workspace\\workspace-dev\\git-manager-client-fx", "测试一下效果", "", i % 5, new SimpleDoubleProperty(0.0), new SimpleBooleanProperty(false)); gitProject.addSelectedListener(); -- Gitee From 2e6e4df2583349ce9301606a30e4a14841390991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=99=BD=E4=B8=8E=E5=AD=9F=E6=B5=A9=E7=84=B6?= <1063889643@qq.com> Date: Mon, 9 Oct 2023 18:23:33 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=BC=95=E5=85=A5=E6=87=92=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/light/view/ManagerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/light/view/ManagerView.java b/src/main/java/com/light/view/ManagerView.java index 613f037..c509130 100644 --- a/src/main/java/com/light/view/ManagerView.java +++ b/src/main/java/com/light/view/ManagerView.java @@ -133,7 +133,7 @@ public class ManagerView extends StackPane { public void initData() { // TODO 查H2数据库获取数据 - for (int i = 0; i < 1000000; i++) { + for (int i = 0; i < 100; i++) { GitProject gitProject = new GitProject(i + "", "g" + i + "it", "project", "develop", "2023-09-30", "2023-09-30", "https://gitee.com/code-poison/git-manager-client-fx.git", "D:\\workspace\\workspace-dev\\git-manager-client-fx", "测试一下效果", "", i % 5, new SimpleDoubleProperty(0.0), new SimpleBooleanProperty(false)); gitProject.addSelectedListener(); -- Gitee From a09cc4d8b1d84a96e1a87cfb863af377fed97073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=99=BD=E4=B8=8E=E5=AD=9F=E6=B5=A9=E7=84=B6?= <1063889643@qq.com> Date: Mon, 9 Oct 2023 20:37:32 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=BC=95=E5=85=A5=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E9=81=AE=E7=BD=A9=E5=B1=82=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/light/GitManagerApp.java | 34 ++- src/main/java/com/light/layout/MenuPane.java | 18 +- .../java/com/light/layout/ModalDialog.java | 70 +++++++ .../java/com/light/theme/ThemeDialog.java | 65 ++++++ .../java/com/light/theme/ThemeThumbnail.java | 194 ++++++++++++++++++ .../java/com/light/util/FileResource.java | 75 +++++++ src/main/java/com/light/util/FxDataUtil.java | 13 ++ src/main/java/com/light/util/NodeUtils.java | 1 + src/main/resources/css/root.css | 48 ++++- 9 files changed, 495 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/light/layout/ModalDialog.java create mode 100644 src/main/java/com/light/theme/ThemeDialog.java create mode 100644 src/main/java/com/light/theme/ThemeThumbnail.java create mode 100644 src/main/java/com/light/util/FileResource.java diff --git a/src/main/java/com/light/GitManagerApp.java b/src/main/java/com/light/GitManagerApp.java index f79ac78..2179a7e 100644 --- a/src/main/java/com/light/GitManagerApp.java +++ b/src/main/java/com/light/GitManagerApp.java @@ -1,9 +1,10 @@ package com.light; -import atlantafx.base.theme.Dracula; -import atlantafx.base.theme.PrimerLight; +import atlantafx.base.controls.ModalPane; +import atlantafx.base.theme.*; import com.light.layout.ContentPane; import com.light.layout.MenuPane; +import com.light.util.FxDataUtil; import com.light.util.FxUtil; import com.light.util.NodeUtils; import javafx.application.Application; @@ -17,22 +18,43 @@ import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.light.util.FxDataUtil.THEME_LIST; +import static com.light.util.NodeUtils.MAIN_MODAL_ID; + public class GitManagerApp extends Application { public static final Logger LOGGER = LoggerFactory.getLogger(GitManagerApp.class); private static final String STYLE_SHEET = FxUtil.getResource("/css/root.css"); + @Override + public void init() throws Exception { + super.init(); + // 初始化主题 + THEME_LIST.add(new PrimerLight()); + THEME_LIST.add(new PrimerDark()); + THEME_LIST.add(new NordLight()); + THEME_LIST.add(new NordDark()); + THEME_LIST.add(new CupertinoLight()); + THEME_LIST.add(new CupertinoDark()); + THEME_LIST.add(new Dracula()); + } + @Override public void start(Stage stage) throws Exception { long startTime = System.currentTimeMillis(); - // 主题 - Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); -// Application.setUserAgentStylesheet(new Dracula().getUserAgentStylesheet()); + + // 设置主题 + Application.setUserAgentStylesheet(THEME_LIST.get(0).getUserAgentStylesheet()); + FxDataUtil.CURRENT_THEME_NAME.set(THEME_LIST.get(0).getName()); // 根节点 StackPane root = new StackPane(); + // 遮罩层 + var modalPane = new ModalPane(); + modalPane.setId(MAIN_MODAL_ID); + // 左右布局 HBox container = new HBox(); // 内容 @@ -42,7 +64,7 @@ public class GitManagerApp extends Application { container.getChildren().addAll(menuPane, contentPane); HBox.setHgrow(contentPane, Priority.ALWAYS); - root.getChildren().add(container); + root.getChildren().addAll(modalPane, container); NodeUtils.setAnchors(root, Insets.EMPTY); // 场景 diff --git a/src/main/java/com/light/layout/MenuPane.java b/src/main/java/com/light/layout/MenuPane.java index 0e9c013..68e0cad 100644 --- a/src/main/java/com/light/layout/MenuPane.java +++ b/src/main/java/com/light/layout/MenuPane.java @@ -1,6 +1,7 @@ package com.light.layout; import atlantafx.base.theme.Styles; +import com.light.theme.ThemeDialog; import com.light.util.FxDataUtil; import com.light.util.FxUtil; import com.light.util.Lazy; @@ -28,7 +29,7 @@ public class MenuPane extends StackPane { private static final String STYLE_SHEET = FxUtil.getResource("/css/menu.css"); // 顶部导航栏 - private final NavItem setting = new NavItem(new FontIcon(AntDesignIconsFilled.SETTING), "设置", null); + private final NavItem setting = new NavItem(new FontIcon(AntDesignIconsFilled.SETTING), "设置", new Lazy<>(ThemeDialog::new)); private final NavItem notification = new NavItem(new FontIcon(AntDesignIconsFilled.NOTIFICATION), "通知", null); private final VBox topMenu = new VBox(setting, notification); @@ -48,16 +49,13 @@ public class MenuPane extends StackPane { private final ContentPane contentPane; - // 子菜单 - private final Lazy homeViewLazy; - private final Lazy managerViewLazy; - public MenuPane(ContentPane contentPane) { this.contentPane = contentPane; initialize(); - managerViewLazy = new Lazy<>(ManagerView::new); - homeViewLazy = new Lazy<>(HomeView::new); + // 子菜单 + Lazy managerViewLazy = new Lazy<>(ManagerView::new); + Lazy homeViewLazy = new Lazy<>(HomeView::new); // 菜单栏标题 dynamicMenu.getItems().addAll( @@ -99,7 +97,8 @@ public class MenuPane extends StackPane { // 加上监听 dynamicMenu.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { Optional.ofNullable(newValue).ifPresent(menuItem -> { - contentPane.setContent(menuItem.getContent().get());//设置内容 + // 设置内容 + Optional.ofNullable(menuItem.getContent()).ifPresentOrElse(content -> contentPane.setContent(content.get()), () -> contentPane.setContent(null)); }); }); @@ -112,7 +111,8 @@ public class MenuPane extends StackPane { // 设置 setting.setOnMouseClicked(event -> { - setting.getContent(); + ThemeDialog themeDialog = (ThemeDialog) setting.getContent().get(); + themeDialog.show(getScene()); }); } } diff --git a/src/main/java/com/light/layout/ModalDialog.java b/src/main/java/com/light/layout/ModalDialog.java new file mode 100644 index 0000000..c3d2994 --- /dev/null +++ b/src/main/java/com/light/layout/ModalDialog.java @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: MIT */ + +package com.light.layout; + +import atlantafx.base.controls.Card; +import atlantafx.base.controls.ModalPane; +import atlantafx.base.controls.Spacer; +import atlantafx.base.controls.Tile; +import atlantafx.base.layout.ModalBox; +import atlantafx.base.theme.Tweaks; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +import static com.light.util.NodeUtils.MAIN_MODAL_ID; + +/** + * 遮罩层对话框 + */ +public abstract class ModalDialog extends ModalBox { + protected final Card content = new Card(); + protected final Tile header = new Tile(); + + public ModalDialog() { + super("#" + MAIN_MODAL_ID); + createView(); + } + + public void show(Scene scene) { + var modalPane = (ModalPane) scene.lookup("#" + MAIN_MODAL_ID); + modalPane.show(this); + } + + protected void createView() { + content.setHeader(header); + content.getStyleClass().add(Tweaks.EDGE_TO_EDGE); + + // IMPORTANT: this guarantees client will use correct width and height + setMinWidth(USE_PREF_SIZE); + setMaxWidth(USE_PREF_SIZE); + setMinHeight(USE_PREF_SIZE); + setMaxHeight(USE_PREF_SIZE); + + AnchorPane.setTopAnchor(content, 0d); + AnchorPane.setRightAnchor(content, 0d); + AnchorPane.setBottomAnchor(content, 0d); + AnchorPane.setLeftAnchor(content, 0d); + + addContent(content); + getStyleClass().add("modal-dialog"); + } + + protected HBox createDefaultFooter() { + var closeBtn = new Button("Close"); + closeBtn.getStyleClass().add("form-action"); + closeBtn.setCancelButton(true); + closeBtn.setOnAction(e -> close()); + + var footer = new HBox(10, new Spacer(), closeBtn); + footer.getStyleClass().add("footer"); + footer.setAlignment(Pos.CENTER_RIGHT); + VBox.setVgrow(footer, Priority.NEVER); + + return footer; + } +} diff --git a/src/main/java/com/light/theme/ThemeDialog.java b/src/main/java/com/light/theme/ThemeDialog.java new file mode 100644 index 0000000..88e1b02 --- /dev/null +++ b/src/main/java/com/light/theme/ThemeDialog.java @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: MIT */ + +package com.light.theme; + +import atlantafx.base.theme.Theme; +import com.light.layout.ModalDialog; +import com.light.util.FxDataUtil; +import javafx.application.Application; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.TilePane; +import javafx.scene.layout.VBox; + +import java.util.Objects; + +import static com.light.util.FxDataUtil.THEME_LIST; + +/** + * 主题对话框 + */ +public final class ThemeDialog extends ModalDialog { + + private final TilePane thumbnailsPane = new TilePane(20, 20); + private final ToggleGroup thumbnailsGroup = new ToggleGroup(); + + public ThemeDialog() { + super(); + + header.setTitle("选择主题"); + content.setBody(createContent()); + content.setFooter(null); + + updateThumbnails(); + + thumbnailsGroup.selectedToggleProperty().addListener((obs, old, val) -> { + if (val != null && val.getUserData() instanceof Theme theme) { + Application.setUserAgentStylesheet(theme.getUserAgentStylesheet()); + } + }); + } + + private VBox createContent() { + thumbnailsPane.setAlignment(Pos.TOP_CENTER); + thumbnailsPane.setPrefColumns(3); + thumbnailsPane.setStyle("-color-thumbnail-border:-color-border-subtle;"); + var root = new VBox(thumbnailsPane); + root.setPadding(new Insets(20)); + return root; + } + + private void updateThumbnails() { + thumbnailsPane.getChildren().clear(); + THEME_LIST.forEach(theme -> { + var thumbnail = new ThemeThumbnail(theme); + thumbnail.setToggleGroup(thumbnailsGroup); + thumbnail.setUserData(theme); + thumbnail.setSelected(Objects.equals( + FxDataUtil.CURRENT_THEME_NAME.get(), + theme.getName() + )); + thumbnailsPane.getChildren().add(thumbnail); + }); + } +} diff --git a/src/main/java/com/light/theme/ThemeThumbnail.java b/src/main/java/com/light/theme/ThemeThumbnail.java new file mode 100644 index 0000000..3f5cd71 --- /dev/null +++ b/src/main/java/com/light/theme/ThemeThumbnail.java @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: MIT */ + +package com.light.theme; + +import atlantafx.base.theme.Styles; +import atlantafx.base.theme.Theme; +import com.light.util.FileResource; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.css.PseudoClass; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Circle; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.attribute.FileTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.LinkOption.NOFOLLOW_LINKS; + +/** + * 主题缩略图 + */ +public final class ThemeThumbnail extends VBox implements Toggle { + + private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); + private final RadioButton toggle; // internal helper node to manage selected state + + private final Theme theme; + + private Map colors; + + public ThemeThumbnail(Theme theme) { + super(); + + toggle = new RadioButton(); + this.theme = theme; + + try { + Map colors = this.parseColors(); + + var circles = new HBox( + createCircle(colors.get("-color-fg-default"), colors.get("-color-fg-default"), false), + createCircle(colors.get("-color-fg-default"), colors.get("-color-accent-emphasis"), true), + createCircle(colors.get("-color-fg-default"), colors.get("-color-success-emphasis"), true), + createCircle(colors.get("-color-fg-default"), colors.get("-color-danger-emphasis"), true), + createCircle(colors.get("-color-fg-default"), colors.get("-color-warning-emphasis"), true) + ); + circles.setAlignment(Pos.CENTER); + + var nameLbl = new Label(theme.getName()); + nameLbl.getStyleClass().add(Styles.TEXT_CAPTION); + Styles.appendStyle(nameLbl, "-fx-text-fill", colors.get("-color-fg-muted")); + + setStyle(""" + -fx-background-radius: 10px, 8px; + -fx-background-insets: 0, 3px + """ + ); + Styles.appendStyle( + this, + "-fx-background-color", + "-color-thumbnail-border," + colors.get("-color-bg-default") + ); + setOnMouseClicked(e -> setSelected(true)); + getStyleClass().add("theme-thumbnail"); + getChildren().setAll(nameLbl, circles); + + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + + selectedProperty().addListener( + (obs, old, val) -> pseudoClassStateChanged(SELECTED, val) + ); + } + + private Circle createCircle(String borderColor, String bgColor, boolean overlap) { + var circle = new Circle(10); + Styles.appendStyle(circle, "-fx-stroke", borderColor); + Styles.appendStyle(circle, "-fx-fill", bgColor); + if (overlap) { + HBox.setMargin(circle, new Insets(0, 0, 0, -5)); + } + return circle; + } + + @Override + public ToggleGroup getToggleGroup() { + return toggle.getToggleGroup(); + } + + @Override + public void setToggleGroup(ToggleGroup toggleGroup) { + toggle.setToggleGroup(toggleGroup); + } + + @Override + public ObjectProperty toggleGroupProperty() { + return toggle.toggleGroupProperty(); + } + + @Override + public boolean isSelected() { + return toggle.isSelected(); + } + + @Override + public void setSelected(boolean selected) { + toggle.setSelected(selected); + } + + @Override + public BooleanProperty selectedProperty() { + return toggle.selectedProperty(); + } + + @Override + public void setUserData(Object value) { + toggle.setUserData(value); + } + + public Map parseColors() throws IOException { + FileResource file = FileResource.createInternal(theme.getUserAgentStylesheet(), Theme.class); + return file.internal() ? parseColorsForClasspath(file) : parseColorsForFilesystem(file); + } + + private static final Pattern COLOR_PATTERN = + Pattern.compile("\s*?(-color-(fg|bg|accent|success|danger|warning)-.+?):\s*?(.+?);"); + + private static final int PARSE_LIMIT = 250; + + private Map parseColors(BufferedReader br) throws IOException { + Map colors = new HashMap<>(); + + String line; + int lineCount = 0; + + while ((line = br.readLine()) != null) { + Matcher matcher = COLOR_PATTERN.matcher(line); + if (matcher.matches()) { + colors.put(matcher.group(1), matcher.group(3)); + } + + lineCount++; + if (lineCount > PARSE_LIMIT) { + break; + } + } + + return colors; + } + + private Map parseColorsForClasspath(FileResource file) throws IOException { + try (var br = new BufferedReader(new InputStreamReader(file.getInputStream(), UTF_8))) { + colors = parseColors(br); + } + return colors; + } + + private FileTime lastModified; + + private Map parseColorsForFilesystem(FileResource file) throws IOException { + // return cached colors if file wasn't changed since the last read + FileTime fileTime = Files.getLastModifiedTime(file.toPath(), NOFOLLOW_LINKS); + if (Objects.equals(fileTime, lastModified)) { + return colors; + } + + try (var br = new BufferedReader(new InputStreamReader(file.getInputStream(), UTF_8))) { + colors = parseColors(br); + } + + // don't save time before parsing is finished to avoid + // remembering operation that might end up with an error + lastModified = fileTime; + + return colors; + } +} \ No newline at end of file diff --git a/src/main/java/com/light/util/FileResource.java b/src/main/java/com/light/util/FileResource.java new file mode 100644 index 0000000..b04de5d --- /dev/null +++ b/src/main/java/com/light/util/FileResource.java @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: MIT */ + +package com.light.util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +public final class FileResource { + + private final String location; + private final boolean internal; + private final Class anchor; + + private FileResource(String location, boolean internal, Class anchor) { + this.location = location; + this.internal = internal; + this.anchor = anchor; + } + + public String location() { + return location; + } + + public boolean internal() { + return internal; + } + + public boolean exists() { + return internal ? anchor.getResource(location) != null : Files.exists(toPath()); + } + + public Path toPath() { + return Paths.get(location); + } + + public URI toURI() { + // the latter adds "file://" scheme to the URI + return internal ? URI.create(location) : Paths.get(location).toUri(); + } + + public String getFilename() { + return Paths.get(location).getFileName().toString(); + } + + public InputStream getInputStream() throws IOException { + if (internal) { + var is = anchor.getResourceAsStream(location); + if (is == null) { + throw new IOException("Resource not found: " + location); + } + return is; + } + return new FileInputStream(toPath().toFile()); + } + + /////////////////////////////////////////////////////////////////////////// + + public static FileResource createInternal(String location) { + return createInternal(location, FileResource.class); + } + + public static FileResource createInternal(String location, Class anchor) { + return new FileResource(location, true, Objects.requireNonNull(anchor)); + } + + public static FileResource createExternal(String location) { + return new FileResource(location, false, null); + } +} \ No newline at end of file diff --git a/src/main/java/com/light/util/FxDataUtil.java b/src/main/java/com/light/util/FxDataUtil.java index 1cbf64f..8091777 100644 --- a/src/main/java/com/light/util/FxDataUtil.java +++ b/src/main/java/com/light/util/FxDataUtil.java @@ -1,10 +1,14 @@ package com.light.util; +import atlantafx.base.theme.Theme; import com.light.model.GitProject; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** @@ -25,4 +29,13 @@ public final class FxDataUtil { public static final ObservableList GIT_PROJECT_OBSERVABLE_LIST = FXCollections.observableArrayList(); + /** + * 当前主题民初 + */ + public static final SimpleStringProperty CURRENT_THEME_NAME = new SimpleStringProperty(); + + /** + * 所有主题 + */ + public static final List THEME_LIST = new ArrayList<>(7); } diff --git a/src/main/java/com/light/util/NodeUtils.java b/src/main/java/com/light/util/NodeUtils.java index eeebc21..e30d736 100644 --- a/src/main/java/com/light/util/NodeUtils.java +++ b/src/main/java/com/light/util/NodeUtils.java @@ -5,6 +5,7 @@ import javafx.scene.Node; import javafx.scene.layout.AnchorPane; public final class NodeUtils { + public static final String MAIN_MODAL_ID = "modal-pane"; /** * 设置子节点距AnchorPane锚点布局四边的距离 diff --git a/src/main/resources/css/root.css b/src/main/resources/css/root.css index 6d09662..26a7725 100644 --- a/src/main/resources/css/root.css +++ b/src/main/resources/css/root.css @@ -1,22 +1,54 @@ @import "colors.css"; -.cf-message{ +.cf-message { -fx-alignment: center-left; -fx-min-height: 40px; -fx-graphic-text-gap: 8px; -fx-padding: 0 10px; - -fx-background-color: rgb(255,255,255); - -fx-background-radius:3px; + -fx-background-color: rgb(255, 255, 255); + -fx-background-radius: 3px; -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.2), 10.0, 0, 0, 0); -fx-text-fill: -cf-text-color; -fx-font-size: 14px; -fx-wrap-text: true; } -.cf-message > .ikonli-font-icon{ + +.cf-message > .ikonli-font-icon { -fx-icon-color: -cf-primary-color; -fx-icon-size: 18px; } -.cf-message.success > .ikonli-font-icon{-fx-icon-color: -cf-success-color;} -.cf-message.info > .ikonli-font-icon{-fx-icon-color: -cf-info-color;} -.cf-message.warn > .ikonli-font-icon{-fx-icon-color: -cf-warn-color;} -.cf-message.danger > .ikonli-font-icon{-fx-icon-color: -cf-danger-color;} \ No newline at end of file + +.cf-message.success > .ikonli-font-icon { + -fx-icon-color: -cf-success-color; +} + +.cf-message.info > .ikonli-font-icon { + -fx-icon-color: -cf-info-color; +} + +.cf-message.warn > .ikonli-font-icon { + -fx-icon-color: -cf-warn-color; +} + +.cf-message.danger > .ikonli-font-icon { + -fx-icon-color: -cf-danger-color; +} + +/*主题缩略图*/ +.theme-thumbnail { + -fx-spacing: 20px; + -fx-padding: 20px; + -fx-alignment: CENTER; +} + +.theme-thumbnail:hover { + -color-thumbnail-border: -color-accent-muted; +} + +.theme-thumbnail:selected { + -color-thumbnail-border: -color-accent-emphasis; +} + +.theme-thumbnail > .label { + -fx-underline: true; +} \ No newline at end of file -- Gitee