diff --git a/src/main/java/com/light/GitManagerApp.java b/src/main/java/com/light/GitManagerApp.java index 9ca069629c4c29713cd02d18323ec25475641292..76f09a73224083db66d9cc39d3ffd4558cc7c1e4 100644 --- a/src/main/java/com/light/GitManagerApp.java +++ b/src/main/java/com/light/GitManagerApp.java @@ -1,14 +1,15 @@ package com.light; +import atlantafx.base.theme.Dracula; import atlantafx.base.theme.PrimerLight; import com.light.layout.ContentPane; import com.light.layout.MenuPane; +import com.light.util.FxUtil; import com.light.util.NodeUtils; import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; @@ -20,10 +21,13 @@ 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 start(Stage stage) throws Exception { // 主题 Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); +// Application.setUserAgentStylesheet(new Dracula().getUserAgentStylesheet()); // 根节点 StackPane root = new StackPane(); @@ -49,6 +53,7 @@ public class GitManagerApp extends Application { stage.setScene(scene); stage.show(); + scene.getStylesheets().add(STYLE_SHEET); LOGGER.info("项目启动完成。。。"); } diff --git a/src/main/java/com/light/component/LevelTableCell.java b/src/main/java/com/light/component/LevelTableCell.java new file mode 100644 index 0000000000000000000000000000000000000000..cfe17dae98bbeee6162bb6ba581f0e02206b65fb --- /dev/null +++ b/src/main/java/com/light/component/LevelTableCell.java @@ -0,0 +1,40 @@ +package com.light.component; + +import com.light.model.GitProject; +import javafx.geometry.Pos; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.layout.HBox; +import javafx.util.Callback; +import org.kordamp.ikonli.antdesignicons.AntDesignIconsOutlined; +import org.kordamp.ikonli.javafx.FontIcon; + +/** + * 学习等级列 - 星级 + */ +public class LevelTableCell extends TableCell { + + private final HBox hBox = new HBox(); + + public static Callback, TableCell> forTableColumn() { + return param -> new LevelTableCell(); + } + + public LevelTableCell() { + hBox.setAlignment(Pos.CENTER_LEFT); + } + + @Override + protected void updateItem(Integer item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null || getTableRow() == null) { + setGraphic(null); + } else { + hBox.getChildren().clear(); + for (int i = 0; i < item; i++) { + hBox.getChildren().add(new FontIcon(AntDesignIconsOutlined.STAR)); + } + setGraphic(hBox); + } + } +} diff --git a/src/main/java/com/light/component/OperationTableCell.java b/src/main/java/com/light/component/OperationTableCell.java new file mode 100644 index 0000000000000000000000000000000000000000..2db4141d35ebf254700f60dcdf6feabd5fda44f5 --- /dev/null +++ b/src/main/java/com/light/component/OperationTableCell.java @@ -0,0 +1,63 @@ +package com.light.component; + +import atlantafx.base.theme.Styles; +import com.light.model.GitProject; +import com.light.util.FxDataUtil; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.layout.HBox; +import javafx.util.Callback; +import org.apache.commons.lang3.StringUtils; + +/** + * 操作列 - 按钮 + */ +public class OperationTableCell extends TableCell { + Button updateButton = new Button("更新"); + Button detailButton = new Button("详情"); + HBox operationBox = new HBox(updateButton, detailButton); + private SimpleDoubleProperty rate; + + public static Callback, TableCell> forTableColumn() { + return param -> new OperationTableCell(); + } + + public OperationTableCell() { + updateButton.getStyleClass().addAll(Styles.FLAT, Styles.ACCENT); + updateButton.setMnemonicParsing(true); + detailButton.getStyleClass().addAll(Styles.FLAT, Styles.SUCCESS); + detailButton.setMnemonicParsing(true); + operationBox.setAlignment(Pos.CENTER_LEFT); + initEvent(); + } + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || getTableRow() == null || StringUtils.isBlank(item)) { + setGraphic(null); + } else { + rate = null; + GitProject project = getTableRow().getItem(); + if (null != project) { + rate = project.downloadRate(); + } + setGraphic(operationBox); + } + } + + private void initEvent() { + // TODO Auto-generated method stub + updateButton.setOnMouseClicked(event -> { + rate.set(0.0); + FxDataUtil.UPDATE_PROPERTY.set(FxDataUtil.UPDATE_NUMBER.incrementAndGet()); + for (int i = 0; i <= 100; i++) { + double dRate = (double) i / 100; + rate.set(dRate); + } + }); + } +} diff --git a/src/main/java/com/light/component/TooltipTableRow.java b/src/main/java/com/light/component/TooltipTableRow.java new file mode 100644 index 0000000000000000000000000000000000000000..ec59b3c6524fdc94bbbd01bdf5d8ab5b39ca19b1 --- /dev/null +++ b/src/main/java/com/light/component/TooltipTableRow.java @@ -0,0 +1,79 @@ +package com.light.component; + +import atlantafx.base.controls.Card; +import atlantafx.base.controls.Tile; +import atlantafx.base.theme.Styles; +import com.light.model.GitProject; +import javafx.geometry.Orientation; +import javafx.scene.control.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.util.Callback; +import org.kordamp.ikonli.antdesignicons.AntDesignIconsOutlined; +import org.kordamp.ikonli.javafx.FontIcon; + +/** + * 自定义行 - 提示框 + */ +public class TooltipTableRow extends TableRow { + + private final Tooltip tooltip = new Tooltip(); + + private final Tile name = new Tile(); + private final Text author = new Text(); + + private final Label remoteAddr = new Label(); + private final Label localAddr = new Label(); + private final Label level = new Label(); + private final Label remark = new Label(); + private final HBox levelBox = new HBox(); + + public static Callback, TableRow> forTableRow() { + return param -> new TooltipTableRow(); + } + + public TooltipTableRow() { + Card toolTipCard = new Card(); + tooltip.setGraphic(toolTipCard); + tooltip.getStyleClass().removeAll("tooltip"); + toolTipCard.getStyleClass().add(Styles.ELEVATED_1); + toolTipCard.setMinWidth(300); + toolTipCard.setMaxWidth(300); + toolTipCard.setHeader(name); + + // 分割线 + Separator separator = new Separator(Orientation.HORIZONTAL); + + VBox bodyVbox = new VBox(5); + toolTipCard.setBody(bodyVbox); + bodyVbox.getChildren().addAll(separator, author, remoteAddr, localAddr, levelBox, remark); + } + + @Override + protected void updateItem(GitProject item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setTooltip(null); + } else { + refreshData(item); + setTooltip(tooltip); + } + } + + private void refreshData(GitProject item) { + levelBox.getChildren().clear(); + // 项目名称 + name.setTitle(item.name()); + name.setDescription(item.description()); + author.setText("仓库 作者: " + item.author()); + remoteAddr.setText("仓库 地址: " + item.remote()); + localAddr.setText("本地 地址: " + item.local()); + level.setText("学习 等级: "); + levelBox.getChildren().add(level); + for (int i = 0; i < item.level(); i++) { + levelBox.getChildren().add(new FontIcon(AntDesignIconsOutlined.STAR)); + } + remark.setText("仓库 备注: " + item.remark()); + } +} diff --git a/src/main/java/com/light/enums/Level.java b/src/main/java/com/light/enums/Level.java new file mode 100644 index 0000000000000000000000000000000000000000..cb3ecadd0d91af1adbce1d431e4266868914218d --- /dev/null +++ b/src/main/java/com/light/enums/Level.java @@ -0,0 +1,36 @@ +package com.light.enums; + +import javafx.scene.Node; +import org.kordamp.ikonli.Ikon; +import org.kordamp.ikonli.antdesignicons.AntDesignIconsFilled; + +import java.util.Arrays; +import java.util.List; + +public enum Level { + PRIMARY(AntDesignIconsFilled.MESSAGE), + SUCCESS(AntDesignIconsFilled.CHECK_CIRCLE), + INFO(AntDesignIconsFilled.INFO_CIRCLE), + WARN(AntDesignIconsFilled.EXCLAMATION_CIRCLE), + DANGER(AntDesignIconsFilled.CLOSE_CIRCLE); + + Level(Ikon ikon) { + this.ikon = ikon; + } + + private Ikon ikon; + + public Ikon getIkon() { + return ikon; + } + + public String getStyleClass() { + return name().toLowerCase(); + } + + public void resetStyleClass(Node node) { + List strings = Arrays.stream(values()).map(Level::getStyleClass).toList(); + node.getStyleClass().removeAll(strings);// 删除之前的StyleClass + node.getStyleClass().add(getStyleClass()); // 添加新的StyleClass + } +} diff --git a/src/main/java/com/light/layout/MenuPane.java b/src/main/java/com/light/layout/MenuPane.java index f2cd33c1f4ad74ed33b74129d5e8127552630c1b..9ad67ffa436698009debbabae35978b276bb35a9 100644 --- a/src/main/java/com/light/layout/MenuPane.java +++ b/src/main/java/com/light/layout/MenuPane.java @@ -1,12 +1,20 @@ package com.light.layout; +import atlantafx.base.theme.Styles; +import com.light.util.FxDataUtil; +import com.light.util.FxUtil; +import com.light.view.HomeView; +import com.light.view.ManagerView; import javafx.geometry.Orientation; +import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.Separator; +import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; +import javafx.scene.text.Text; import org.kordamp.ikonli.antdesignicons.AntDesignIconsFilled; import org.kordamp.ikonli.antdesignicons.AntDesignIconsOutlined; import org.kordamp.ikonli.bootstrapicons.BootstrapIcons; @@ -16,7 +24,7 @@ import java.util.Optional; public class MenuPane extends StackPane { - private static final String STYLE_SHEET = MenuPane.class.getResource("/css/menu.css").toExternalForm(); + private static final String STYLE_SHEET = FxUtil.getResource("/css/menu.css"); // 顶部导航栏 private final NavItem setting = new NavItem(new FontIcon(AntDesignIconsFilled.SETTING), "设置", null); @@ -28,10 +36,14 @@ public class MenuPane extends StackPane { // 底部导航栏 private final Label downloadLabel = new Label("正在下载", new FontIcon(BootstrapIcons.ARROW_DOWN)); + private final Text downloadLabelText = new Text(FxDataUtil.DOWNLOAD_PROPERTY.getValue().toString()); + private final HBox downloadBox = new HBox(10, downloadLabel, downloadLabelText); private final Label updateLabel = new Label("正在更新", new FontIcon(BootstrapIcons.ARROW_DOWN)); - private final VBox bottomMenu = new VBox(downloadLabel, updateLabel); + private final Text updateLabelText = new Text(FxDataUtil.UPDATE_PROPERTY.getValue().toString()); + private final HBox updateBox = new HBox(10, updateLabel, updateLabelText); + private final VBox bottomMenu = new VBox(downloadBox, updateBox); - private VBox asideContainer = new VBox(topMenu, new Separator(Orientation.HORIZONTAL), dynamicMenu, new Separator(Orientation.HORIZONTAL), bottomMenu); + private final VBox asideContainer = new VBox(topMenu, new Separator(Orientation.HORIZONTAL), dynamicMenu, new Separator(Orientation.HORIZONTAL), bottomMenu); private final ContentPane contentPane; @@ -41,8 +53,8 @@ public class MenuPane extends StackPane { // 菜单栏标题 dynamicMenu.getItems().addAll( - new NavItem(new FontIcon(AntDesignIconsOutlined.HOME), "首页", null), - new NavItem(new FontIcon(AntDesignIconsOutlined.PARTITION), "管理", null), + new NavItem(new FontIcon(AntDesignIconsOutlined.DOWNLOAD), "下载", new HomeView()), + new NavItem(new FontIcon(AntDesignIconsOutlined.PARTITION), "管理", new ManagerView()), new NavItem(new FontIcon(AntDesignIconsOutlined.EDIT), "笔记", null) ); dynamicMenu.getSelectionModel().selectFirst(); @@ -63,11 +75,31 @@ public class MenuPane extends StackPane { this.downloadLabel.getStyleClass().add("download-label"); this.updateLabel.getStyleClass().add("update-label"); + // 直接设置样式 + downloadBox.setAlignment(Pos.CENTER_LEFT); + downloadLabelText.getStyleClass().addAll(Styles.TEXT, Styles.SUCCESS); + updateBox.setAlignment(Pos.CENTER_LEFT); + updateLabelText.getStyleClass().addAll(Styles.TEXT, Styles.WARNING); + + initEvent(); + } + + /** + * 初始化监听事件 + */ + private void initEvent() { // 加上监听 dynamicMenu.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { Optional.ofNullable(newValue).ifPresent(menuItem -> { contentPane.setContent(menuItem.getContent());//设置内容 }); }); + + FxDataUtil.DOWNLOAD_PROPERTY.addListener((observable, oldValue, newValue) -> { + downloadLabelText.setText(newValue.toString()); + }); + FxDataUtil.UPDATE_PROPERTY.addListener((observable, oldValue, newValue) -> { + updateLabelText.setText(newValue.toString()); + }); } } diff --git a/src/main/java/com/light/model/GitProject.java b/src/main/java/com/light/model/GitProject.java new file mode 100644 index 0000000000000000000000000000000000000000..8faa3e978278fe3d62c89b500eb6ead835321f99 --- /dev/null +++ b/src/main/java/com/light/model/GitProject.java @@ -0,0 +1,29 @@ +package com.light.model; + +import com.light.util.FxDataUtil; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public record GitProject(String id, + String name, + String author, + String branch, + String createTime, + String updateTime, + String remote, + String local, + String description, + String remark, + int level, + SimpleDoubleProperty downloadRate, + SimpleBooleanProperty selected) { + + public void addSelectedListener() { + downloadRate.addListener((observable, oldValue, newValue) -> { + if (newValue != null && newValue.doubleValue() >= 1.0) { + selected.set(false); + FxDataUtil.UPDATE_PROPERTY.set(FxDataUtil.UPDATE_NUMBER.decrementAndGet()); + } + }); + } +} diff --git a/src/main/java/com/light/util/AnimationUtils.java b/src/main/java/com/light/util/AnimationUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d065ea1e83ff727d0ee003700d0fd0b2e3805bad --- /dev/null +++ b/src/main/java/com/light/util/AnimationUtils.java @@ -0,0 +1,106 @@ +package com.light.util; + +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.value.WritableValue; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; + +/** + * 简单动画工具类 + */ +public class AnimationUtils { + + /** + * 并行动画 + *
+ * 注意:target,startValue,endValue 长度必须一致 + * + * @param duration 执行时间,默认从0开始 + * @param targets + * @param startValue 开始值 + * @param endValue 结束值 + * @param finishedHandler 动画结束事件 + * @param + * @return + */ + public static Timeline parallel(Duration duration, + WritableValue[] targets, + T[] startValue, + T[] endValue, + EventHandler finishedHandler) { + return parallel(duration, targets, startValue, endValue, finishedHandler, false, Duration.ZERO); + } + + /** + * 并行动画 + *
+ * 注意:target,startValue,endValue 长度必须一致 + * + * @param duration 执行时间,默认从0开始 + * @param targets + * @param startValue 开始值 + * @param endValue 结束值 + * @param finishedHandler 动画结束事件 + * @param autoReverse 自动倒带 + * @param + * @return + */ + public static Timeline parallel(Duration duration, + WritableValue[] targets, + T[] startValue, + T[] endValue, + EventHandler finishedHandler, + boolean autoReverse) { + return parallel(duration, targets, startValue, endValue, finishedHandler, autoReverse, Duration.ZERO); + } + + /** + * 并行动画 + *
+ * 注意:target,startValue,endValue 长度必须一致 + * + * @param duration 执行时间,默认从0开始 + * @param targets + * @param startValue 开始值 + * @param endValue 结束值 + * @param finishedHandler 动画结束事件 + * @param autoReverse 自动倒带 + * @param pause 动画完成之前的暂停时间*2 + * @param + * @return + */ + public static Timeline parallel(Duration duration, + WritableValue[] targets, + T[] startValue, + T[] endValue, + EventHandler finishedHandler, + boolean autoReverse, + Duration pause) { + final Timeline timeline = new Timeline(); + List startKeyValues = new ArrayList<>(); + List endKeyValues = new ArrayList<>(); + for (int i = 0; i < targets.length; i++) { + WritableValue writableValue = targets[i]; + startKeyValues.add(new KeyValue(writableValue, startValue[i], Interpolator.LINEAR)); + endKeyValues.add(new KeyValue(writableValue, endValue[i], Interpolator.LINEAR)); + } + KeyFrame startKeyFrame = new KeyFrame(Duration.ZERO, startKeyValues.toArray(new KeyValue[0])); + KeyFrame endKeyFrame = new KeyFrame(duration, endKeyValues.toArray(new KeyValue[0])); + timeline.getKeyFrames().setAll(startKeyFrame, endKeyFrame); + if (pause.toMillis() != 0) { + timeline.getKeyFrames().add(new KeyFrame(duration.add(pause))); // 暂停动画 + } + timeline.setCycleCount(autoReverse ? 2 : 1); + timeline.setAutoReverse(autoReverse); + timeline.setOnFinished(finishedHandler); + return timeline; + } + +} diff --git a/src/main/java/com/light/util/FxDataUtil.java b/src/main/java/com/light/util/FxDataUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..1cbf64f460603919766b9b27e85df124b51cc279 --- /dev/null +++ b/src/main/java/com/light/util/FxDataUtil.java @@ -0,0 +1,28 @@ +package com.light.util; + +import com.light.model.GitProject; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 全局数据工具类 + */ +public final class FxDataUtil { + + /** + * 正在下载数量 + */ + public static final AtomicInteger DOWNLOAD_NUMBER = new AtomicInteger(0); + public static final SimpleIntegerProperty DOWNLOAD_PROPERTY = new SimpleIntegerProperty(DOWNLOAD_NUMBER.intValue()); + /** + * 正在更新数量 + */ + public static final AtomicInteger UPDATE_NUMBER = new AtomicInteger(0); + public static final SimpleIntegerProperty UPDATE_PROPERTY = new SimpleIntegerProperty(UPDATE_NUMBER.intValue()); + + public static final ObservableList GIT_PROJECT_OBSERVABLE_LIST = FXCollections.observableArrayList(); + +} diff --git a/src/main/java/com/light/util/FxUtil.java b/src/main/java/com/light/util/FxUtil.java index c8bb44005a2cf1b27a72aa086fdc4a1252c9baab..853701b04fda741d4ea8db8075bb8fb46b9e48a3 100644 --- a/src/main/java/com/light/util/FxUtil.java +++ b/src/main/java/com/light/util/FxUtil.java @@ -20,18 +20,43 @@ import java.util.concurrent.Callable; */ public class FxUtil { + /** + * 获取资源文件 + * @param resources + * @return + */ public static String getResource(String resources) { return FxUtil.class.getResource(resources).toExternalForm(); } + /** + * 获取图片 + * @param resources + * @return + */ public static Image getImage(String resources) { return new Image(getResource(resources)); } + /** + * 获取图片组件 + * + * @param resources + * @param size + * @return + */ public static ImageView getImageView(String resources, double size) { return getImageView(resources, size, size); } + /** + * 获取图片组件 + * + * @param resources + * @param height + * @param width + * @return + */ public static ImageView getImageView(String resources, double height, double width) { ImageView imageView = new ImageView(getResource(resources)); imageView.setFitHeight(height); diff --git a/src/main/java/com/light/util/NoticeUtils.java b/src/main/java/com/light/util/NoticeUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..994f0c13368218a1d46576c769aec2009e9a2c6e --- /dev/null +++ b/src/main/java/com/light/util/NoticeUtils.java @@ -0,0 +1,80 @@ +package com.light.util; + +import com.light.enums.Level; +import javafx.animation.Timeline; +import javafx.beans.value.WritableValue; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.stage.Window; +import javafx.util.Duration; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.util.Optional; + +/** + * 消息提示工具类 + */ +public class NoticeUtils { + + public static void show(Window window, String message, Level level) { + if (window == null) { + window = FxUtil.getFocusedWindow(); + } + Optional.ofNullable(window) + .map(Window::getScene) + .map(Scene::getRoot) + .filter(root -> { + Class nodeClass = root.getClass(); + return StackPane.class == nodeClass; + }) + .map(root -> (StackPane) root) + .ifPresentOrElse( + root -> addMessageNode(root, message, level), + () -> System.err.println("未获取到: Scene | Root | StackPane")); + } + + private static void addMessageNode(StackPane root, String message, Level level) { + Label messageNode = new Label(message); + messageNode.setGraphic(FontIcon.of(level.getIkon())); + messageNode.getStyleClass().addAll("cf-message", level.getStyleClass()); + messageNode.setOpacity(0); + StackPane.setAlignment(messageNode, Pos.TOP_CENTER); + StackPane.setMargin(messageNode, new Insets(30)); + root.getChildren().add(messageNode); + //启动动画 + play(messageNode); + } + + /** + * 开始动画 + * + * @param node + */ + private static void play(Node node) { + Timeline parallel = AnimationUtils.parallel(Duration.millis(250), + new WritableValue[]{node.opacityProperty(), node.translateYProperty()}, + new Object[]{0.0, -20}, + new Object[]{1.0, 0}, + event -> { + // 动画完成,移除该节点。 + node.setDisable(true); // 解决有残影问题 + StackPane parent = (StackPane) node.getParent(); + parent.getChildren().remove(node); + }, true, Duration.seconds(1.5)); + parallel.play(); + // 鼠标移入暂停动画 + node.hoverProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + parallel.pause(); + } else { + parallel.play(); + } + }); + } + +} diff --git a/src/main/java/com/light/view/HomeView.java b/src/main/java/com/light/view/HomeView.java new file mode 100644 index 0000000000000000000000000000000000000000..c57ba3af23ebdea7e500aed33a2665cf024a0a85 --- /dev/null +++ b/src/main/java/com/light/view/HomeView.java @@ -0,0 +1,65 @@ +package com.light.view; + +import com.light.enums.Level; +import javafx.collections.FXCollections; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.apache.commons.lang3.StringUtils; + +import static com.light.util.NoticeUtils.show; + +public class HomeView extends BorderPane { + + //下载新项目 两个输入框 克隆/下载地址 下载到哪里 一个分支复选框 一个下载按钮 + //添加本地项目 展示一个输入框 让用户键入代码存放路径 + public HomeView() { + /** + * 下载新项目 + * 输入框 下载地址 + * 输入框 目的地 + * 复选框 分支 + * 按钮 下载 + */ + //纵向布局 + VBox downloadVBox = new VBox(10); + downloadVBox.setAlignment(Pos.CENTER); + //下载框 + var downloadPath = new TextField(); + downloadPath.setPromptText("请输入下载地址"); + downloadPath.setMaxWidth(600); + //目的地框 + var targetPath = new TextField(); + targetPath.setPromptText("请输入目的地"); + targetPath.setMaxWidth(600); + //分支复选框 + ChoiceBox choiceBox = new ChoiceBox<>(FXCollections.observableArrayList("master", "dev", "A", "B", "C")); + choiceBox.setMaxWidth(600); + choiceBox.setValue("master"); + choiceBox.setOnAction(e -> { + System.out.println(choiceBox.getSelectionModel().getSelectedItem()); + }); + //下载按钮 + Button downloadButton = new Button("下载"); + downloadButton.setOnAction(btn -> { + System.out.println("用户下载路径:" + downloadPath.getText()); + System.out.println("用户目标路径:" + targetPath.getText()); + //错误提示 + try { + if (StringUtils.isEmpty(downloadPath.getText()) || StringUtils.isEmpty(targetPath.getText())) { + show(null, "请输入路径", Level.WARN); + } else { + //todo 输入正确,读取文件夹中内容 + } + } catch (Exception e) { + show(null, "下载失败,请稍后重试", Level.DANGER); + } + }); + downloadVBox.getChildren().addAll(downloadPath, targetPath, choiceBox, downloadButton); + this.setCenter(downloadVBox); + } + +} \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..c509130f31d9a789b0eb943ed77d8671aac8bfcc --- /dev/null +++ b/src/main/java/com/light/view/ManagerView.java @@ -0,0 +1,240 @@ +package com.light.view; + +import atlantafx.base.controls.CustomTextField; +import atlantafx.base.theme.Styles; +import atlantafx.base.theme.Tweaks; +import com.light.component.LevelTableCell; +import com.light.component.OperationTableCell; +import com.light.component.TooltipTableRow; +import com.light.enums.Level; +import com.light.model.GitProject; +import com.light.util.FxDataUtil; +import com.light.util.NoticeUtils; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.control.cell.CheckBoxTableCell; +import javafx.scene.control.cell.ChoiceBoxTableCell; +import javafx.scene.control.cell.ProgressBarTableCell; +import javafx.scene.image.Image; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; +import org.apache.commons.lang3.StringUtils; +import org.kordamp.ikonli.bootstrapicons.BootstrapIcons; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.io.File; +import java.util.List; + +public class ManagerView extends StackPane { + + // 上半部分:搜索、更新 + private CustomTextField searchField; + private final Button updateButton = new Button("更新"); + private final Button addLocalButton = new Button("添加本地项目"); + private final HBox hBox = new HBox(5); + + // 表格 + private final CheckBox selectAll = new CheckBox(); + private TableView tableView; + + // 整体 + private final VBox vBox = new VBox(10); + + public ManagerView() { + this.getChildren().add(vBox); + initialize(); + initData(); + initEvent(); + } + + public void initialize() { + searchField = new CustomTextField(); + searchField.setLeft(new FontIcon(BootstrapIcons.SEARCH)); + searchField.setPromptText("请输入仓库名称或者作者名称进行搜索..."); + addLocalButton.setMnemonicParsing(true); + updateButton.setMnemonicParsing(true); + hBox.getChildren().addAll(searchField, addLocalButton, updateButton); + hBox.setPadding(new Insets(5, 0, 0, 0)); + HBox.setHgrow(searchField, Priority.ALWAYS); + + // 初始化表格 + initTable(); + + vBox.getChildren().addAll(hBox, tableView); + vBox.setPadding(new Insets(0, 1, 0, 1)); + VBox.setVgrow(tableView, Priority.ALWAYS); + } + + public void initTable() { + var select = new TableColumn(); + select.setGraphic(selectAll); + select.setSortable(false); + select.setCellFactory(CheckBoxTableCell.forTableColumn(select)); + select.setCellValueFactory(param -> param.getValue().selected()); + select.setPrefWidth(40d); + select.setMaxWidth(40d); + select.setMinWidth(40d); + + var id = new TableColumn("序号"); + id.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().id())); + id.setPrefWidth(50d); + id.setMaxWidth(50d); + id.setMinWidth(50d); + + var warehouse = new TableColumn("仓库"); + warehouse.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().name())); + + var author = new TableColumn("作者"); + author.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().author())); + + var branch = new TableColumn("分支"); + branch.setSortable(false); + branch.setCellFactory(ChoiceBoxTableCell.forTableColumn("master", "develop")); + branch.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().branch())); + + var level = new TableColumn("学习等级"); + level.setCellFactory(LevelTableCell.forTableColumn()); + level.setCellValueFactory(c -> new SimpleIntegerProperty(c.getValue().level()).asObject()); + + var process = new TableColumn("更新进度"); + process.setSortable(false); + process.setCellFactory(ProgressBarTableCell.forTableColumn()); + process.setCellValueFactory(c -> c.getValue().downloadRate().asObject()); + + var operation = new TableColumn("操作"); + operation.setSortable(false); + operation.setCellFactory(OperationTableCell.forTableColumn()); + operation.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().id())); + operation.setPrefWidth(120d); + operation.setMaxWidth(120d); + operation.setMinWidth(120d); + + tableView = new TableView<>(FxDataUtil.GIT_PROJECT_OBSERVABLE_LIST); + tableView.getColumns().setAll(select, id, warehouse, author, branch, level, process, operation); + tableView.setRowFactory(TooltipTableRow.forTableRow()); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.getStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.BORDERED); + tableView.getSelectionModel().selectFirst(); + tableView.setMaxWidth(Double.MAX_VALUE); + tableView.setMinHeight(300); + tableView.setEditable(true); + } + + public void initData() { + // TODO 查H2数据库获取数据 + 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(); + FxDataUtil.GIT_PROJECT_OBSERVABLE_LIST.add(gitProject); + } + } + + public void initEvent() { + // 全选按钮事件 + selectAll.setOnAction(event -> { + tableView.getItems().forEach( + item -> item.selected().set(selectAll.isSelected()) + ); + event.consume(); + }); + + /** + * 添加本地项目按钮事件 + * 输入框 让用户键入代码存放路径 + * 按钮 加载 + */ + addLocalButton.setOnAction(btn -> { + //输入框横向布局 + HBox inputHBox = new HBox(10); + inputHBox.setAlignment(Pos.CENTER); + //错误提示横向布局 + HBox noticeHBox = new HBox(10); + noticeHBox.setAlignment(Pos.CENTER); + + //输入框 + TextField textField = new TextField(); + textField.setPromptText("请输入代码存放路径"); + textField.setMinWidth(550); + //设置打开窗口时焦点不在输入框中 + textField.setFocusTraversable(false); + //加载按钮 + Button load = new Button("加载"); + inputHBox.getChildren().addAll(textField, load); + Scene scene = new Scene(inputHBox, 700, 100); + Stage projectPathStage = new Stage(); + projectPathStage.initOwner(getScene().getWindow()); + //设置此窗口在优先级最高 屏蔽其他窗口 + projectPathStage.initModality(Modality.WINDOW_MODAL); + projectPathStage.getIcons().add(new Image("/icons/git.png")); + projectPathStage.setTitle("添加本地项目"); + projectPathStage.setScene(scene); + projectPathStage.show(); + + load.setOnAction(loadBtn -> { + System.out.println("用户输入路径:" + textField.getText()); + //错误提示 + try { + if (StringUtils.isEmpty(textField.getText())) { + NoticeUtils.show(getScene().getWindow(), "请输入路径", Level.WARN); + } else { + File file = new File(textField.getText()); + if (file.exists()) { + //todo 输入正确,读取文件夹中内容 + + } else { + NoticeUtils.show(getScene().getWindow(), "该路径不存在或路径下无内容", Level.DANGER); + } + } + } catch (Exception e) { + NoticeUtils.show(getScene().getWindow(), "路径不正确", Level.DANGER); + } + }); + }); + + // 搜索框执行逻辑 + searchField.setOnKeyReleased(event -> { + String text = searchField.getText(); + if (StringUtils.isNotBlank(text)) { + List list; + if (FxDataUtil.GIT_PROJECT_OBSERVABLE_LIST.size() > 10000) { + list = FxDataUtil.GIT_PROJECT_OBSERVABLE_LIST. + parallelStream().filter(param -> param.name().contains(text) || param.author().contains(text)).toList(); + } else { + list = FxDataUtil.GIT_PROJECT_OBSERVABLE_LIST. + stream().filter(param -> param.name().contains(text) || param.author().contains(text)).toList(); + } + tableView.setItems(FXCollections.observableList(list)); + } else { + tableView.setItems(FxDataUtil.GIT_PROJECT_OBSERVABLE_LIST); + } + }); + + // 全部更新按钮 + updateButton.setOnMouseClicked(event -> { + List list = tableView.getItems().stream().filter(param -> param.selected().get()).toList(); + if (!list.isEmpty()) { + updateButton.setDisable(true); + FxDataUtil.UPDATE_PROPERTY.set(FxDataUtil.UPDATE_NUMBER.addAndGet(list.size())); + } + list.forEach(param -> { + param.downloadRate().set(0.0); + for (int i = 0; i <= 100; i++) { + double dRate = (double) i / 100; + param.downloadRate().set(dRate); + } + }); + }); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f359b55985c735897466ef180dd106743a23c4b1..7463f1472c34979e2ca7883c648cab7154718d38 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -11,4 +11,5 @@ open module com.leo { requires org.jetbrains.annotations; requires org.kordamp.ikonli.antdesignicons; requires org.kordamp.ikonli.bootstrapicons; + requires org.kordamp.ikonli.core; } diff --git a/src/main/resources/css/menu.css b/src/main/resources/css/menu.css index e11cca9bb3e2121e55b140279576d95e106c70e2..3abb4969cc598d3315ac5b74a8e5986fe9bf11cb 100644 --- a/src/main/resources/css/menu.css +++ b/src/main/resources/css/menu.css @@ -96,7 +96,6 @@ .download-label, .update-label { -fx-font-size: 14px; - -fx-text-fill: -cf-text-color; } .download-label > .ikonli-font-icon { @@ -107,4 +106,25 @@ .update-label > .ikonli-font-icon { -fx-icon-size: 14px; -fx-icon-color: -cf-success-color; -} \ No newline at end of file +} + +.cf-message{ + -fx-alignment: center-left; + -fx-min-height: 40px; + -fx-graphic-text-gap: 8px; + -fx-padding: 0 10px; + -fx-background-color: rgb(168, 70, 70); + -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{ + -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 diff --git a/src/main/resources/css/root.css b/src/main/resources/css/root.css new file mode 100644 index 0000000000000000000000000000000000000000..6d0966286ea99c91ca3d06cf128e85e8ef940036 --- /dev/null +++ b/src/main/resources/css/root.css @@ -0,0 +1,22 @@ +@import "colors.css"; + +.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-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{ + -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