修复应用外壳运行时样式与导航契约
This commit is contained in:
@@ -114,15 +114,6 @@ public class WorkUtilsApplication implements CommandLineRunner {
|
|||||||
settingsService.setNavItemsRefresher(ignoredItems -> Platform.runLater(() -> {
|
settingsService.setNavItemsRefresher(ignoredItems -> Platform.runLater(() -> {
|
||||||
if (mainWindow != null) {
|
if (mainWindow != null) {
|
||||||
mainWindow.refreshNavItems(buildNavItems());
|
mainWindow.refreshNavItems(buildNavItems());
|
||||||
// 重新绑定插件面板
|
|
||||||
for (Plugin plugin : pluginManager.getEnabledPlugins()) {
|
|
||||||
PluginDescriptor desc = plugin.getDescriptor();
|
|
||||||
VisualPluginFactory factory = plugin.getVisualFactory();
|
|
||||||
Parent pane = factory != null ? factory.getEmbeddedPane() : null;
|
|
||||||
if (pane != null) {
|
|
||||||
mainWindow.embedPlugin(desc.getId(), desc.getIcon(), desc.getName(), pane);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ public class MainWindow {
|
|||||||
Button menuButton = new Button(null, IconFactory.createButtonIcon(AppIcon.MENU, "切换导航栏"));
|
Button menuButton = new Button(null, IconFactory.createButtonIcon(AppIcon.MENU, "切换导航栏"));
|
||||||
menuButton.setId("menu-button");
|
menuButton.setId("menu-button");
|
||||||
menuButton.setTooltip(new Tooltip("切换导航栏"));
|
menuButton.setTooltip(new Tooltip("切换导航栏"));
|
||||||
|
menuButton.setAccessibleText("切换导航栏");
|
||||||
|
menuButton.setFocusTraversable(true);
|
||||||
menuButton.setOnAction(event -> navigationRail.toggle());
|
menuButton.setOnAction(event -> navigationRail.toggle());
|
||||||
|
|
||||||
Label brand = new Label("WorkUtils", IconFactory.create(AppIcon.APP, 20));
|
Label brand = new Label("WorkUtils", IconFactory.create(AppIcon.APP, 20));
|
||||||
@@ -135,9 +137,12 @@ public class MainWindow {
|
|||||||
HBox.setHgrow(spacerRight, Priority.ALWAYS);
|
HBox.setHgrow(spacerRight, Priority.ALWAYS);
|
||||||
|
|
||||||
Button settingsButton = new Button(
|
Button settingsButton = new Button(
|
||||||
null, IconFactory.createButtonIcon(AppIcon.SETTINGS, "系统设置"));
|
"系统设置", IconFactory.createButtonIcon(AppIcon.SETTINGS, "系统设置"));
|
||||||
settingsButton.setId("settings-button");
|
settingsButton.setId("settings-button");
|
||||||
settingsButton.setTooltip(new Tooltip("系统设置"));
|
settingsButton.setTooltip(new Tooltip("系统设置"));
|
||||||
|
settingsButton.setAccessibleText("系统设置");
|
||||||
|
settingsButton.setContentDisplay(ContentDisplay.LEFT);
|
||||||
|
settingsButton.setFocusTraversable(true);
|
||||||
settingsButton.setOnAction(event -> openSettings.run());
|
settingsButton.setOnAction(event -> openSettings.run());
|
||||||
|
|
||||||
bar.getChildren().addAll(
|
bar.getChildren().addAll(
|
||||||
|
|||||||
@@ -12,14 +12,11 @@ import javafx.css.PseudoClass;
|
|||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ContentDisplay;
|
import javafx.scene.control.ContentDisplay;
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -41,7 +38,6 @@ public final class NavigationRail extends VBox {
|
|||||||
private final List<ItemView> itemViews = new ArrayList<>();
|
private final List<ItemView> itemViews = new ArrayList<>();
|
||||||
private final Map<String, ItemView> viewsByPluginId = new LinkedHashMap<>();
|
private final Map<String, ItemView> viewsByPluginId = new LinkedHashMap<>();
|
||||||
private final VBox itemBox = new VBox();
|
private final VBox itemBox = new VBox();
|
||||||
private final Label brandLabel = new Label("WorkUtils");
|
|
||||||
private final Button collapseButton = new Button();
|
private final Button collapseButton = new Button();
|
||||||
|
|
||||||
private MainWindow.NavItem selectedItem;
|
private MainWindow.NavItem selectedItem;
|
||||||
@@ -53,10 +49,13 @@ public final class NavigationRail extends VBox {
|
|||||||
this.navigator = Objects.requireNonNull(navigator, "navigator must not be null");
|
this.navigator = Objects.requireNonNull(navigator, "navigator must not be null");
|
||||||
|
|
||||||
getStyleClass().addAll(Theme.CSS_NAV_BAR, "navigation-rail");
|
getStyleClass().addAll(Theme.CSS_NAV_BAR, "navigation-rail");
|
||||||
buildHeader();
|
|
||||||
itemBox.getStyleClass().add("navigation-rail-items");
|
itemBox.getStyleClass().add("navigation-rail-items");
|
||||||
VBox.setVgrow(itemBox, Priority.ALWAYS);
|
VBox.setVgrow(itemBox, Priority.ALWAYS);
|
||||||
getChildren().addAll(new Separator(), itemBox);
|
buildBottomToggle();
|
||||||
|
|
||||||
|
Separator separator = new Separator();
|
||||||
|
separator.getStyleClass().add(Theme.CSS_NAV_SEP);
|
||||||
|
getChildren().addAll(itemBox, separator, collapseButton);
|
||||||
|
|
||||||
expanded.addListener((observable, oldValue, newValue) -> updateAppearance());
|
expanded.addListener((observable, oldValue, newValue) -> updateAppearance());
|
||||||
setItems(items);
|
setItems(items);
|
||||||
@@ -131,35 +130,31 @@ public final class NavigationRail extends VBox {
|
|||||||
return view == null ? null : view.tooltip();
|
return view == null ? null : view.tooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildHeader() {
|
private void buildBottomToggle() {
|
||||||
HBox header = new HBox(Theme.S2);
|
collapseButton.setId("navigation-rail-toggle");
|
||||||
header.setAlignment(Pos.CENTER_LEFT);
|
collapseButton.getStyleClass().addAll(Theme.CSS_NAV_CELL, "navigation-rail-toggle");
|
||||||
header.setMinHeight(DesignTokens.HEADER_HEIGHT);
|
collapseButton.setContentDisplay(ContentDisplay.LEFT);
|
||||||
header.setPrefHeight(DesignTokens.HEADER_HEIGHT);
|
collapseButton.setGraphicTextGap(Theme.S3);
|
||||||
header.setMaxHeight(DesignTokens.HEADER_HEIGHT);
|
collapseButton.setMinHeight(DesignTokens.ROW_HEIGHT);
|
||||||
|
collapseButton.setPrefHeight(DesignTokens.ROW_HEIGHT);
|
||||||
brandLabel.setGraphic(IconFactory.create(AppIcon.APP, 18));
|
collapseButton.setMaxHeight(DesignTokens.ROW_HEIGHT);
|
||||||
brandLabel.setContentDisplay(ContentDisplay.LEFT);
|
collapseButton.setMaxWidth(Double.MAX_VALUE);
|
||||||
brandLabel.setFont(Theme.sectionTitle());
|
collapseButton.setFocusTraversable(true);
|
||||||
|
|
||||||
Region spacer = new Region();
|
|
||||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
|
||||||
|
|
||||||
collapseButton.getStyleClass().add("navigation-rail-toggle");
|
|
||||||
collapseButton.setOnAction(event -> toggle());
|
collapseButton.setOnAction(event -> toggle());
|
||||||
header.getChildren().addAll(brandLabel, spacer, collapseButton);
|
|
||||||
getChildren().add(header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addItem(MainWindow.NavItem item, int index) {
|
private void addItem(MainWindow.NavItem item, int index) {
|
||||||
ToggleButton button = new ToggleButton(item.name, IconFactory.createButtonIcon(item.icon, item.name));
|
ToggleButton button = new ToggleButton(item.name, IconFactory.createButtonIcon(item.icon, item.name));
|
||||||
button.setToggleGroup(selection);
|
button.setToggleGroup(selection);
|
||||||
button.setContentDisplay(ContentDisplay.LEFT);
|
button.setContentDisplay(ContentDisplay.LEFT);
|
||||||
|
button.setGraphicTextGap(Theme.S3);
|
||||||
button.setAlignment(Pos.CENTER_LEFT);
|
button.setAlignment(Pos.CENTER_LEFT);
|
||||||
button.setMinHeight(DesignTokens.ROW_HEIGHT);
|
button.setMinHeight(DesignTokens.ROW_HEIGHT);
|
||||||
button.setPrefHeight(DesignTokens.ROW_HEIGHT);
|
button.setPrefHeight(DesignTokens.ROW_HEIGHT);
|
||||||
button.setMaxHeight(DesignTokens.ROW_HEIGHT);
|
button.setMaxHeight(DesignTokens.ROW_HEIGHT);
|
||||||
button.setMaxWidth(Double.MAX_VALUE);
|
button.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
button.setAccessibleText(item.name);
|
||||||
|
button.setFocusTraversable(true);
|
||||||
button.getStyleClass().add(Theme.CSS_NAV_CELL);
|
button.getStyleClass().add(Theme.CSS_NAV_CELL);
|
||||||
if (item.pluginId != null) {
|
if (item.pluginId != null) {
|
||||||
button.setId("nav-" + item.pluginId);
|
button.setId("nav-" + item.pluginId);
|
||||||
@@ -201,12 +196,15 @@ public final class NavigationRail extends VBox {
|
|||||||
setMaxWidth(width);
|
setMaxWidth(width);
|
||||||
pseudoClassStateChanged(COLLAPSED, !isExpanded());
|
pseudoClassStateChanged(COLLAPSED, !isExpanded());
|
||||||
|
|
||||||
brandLabel.setVisible(isExpanded());
|
String collapseLabel = isExpanded() ? "收起" : "展开";
|
||||||
brandLabel.setManaged(isExpanded());
|
String accessibleText = collapseLabel + "导航栏";
|
||||||
collapseButton.setGraphic(IconFactory.createButtonIcon(
|
collapseButton.setGraphic(IconFactory.createButtonIcon(
|
||||||
isExpanded() ? AppIcon.CHEVRON_LEFT : AppIcon.CHEVRON_RIGHT,
|
isExpanded() ? AppIcon.CHEVRON_LEFT : AppIcon.CHEVRON_RIGHT,
|
||||||
isExpanded() ? "折叠导航" : "展开导航"));
|
accessibleText));
|
||||||
collapseButton.setTooltip(new Tooltip(isExpanded() ? "折叠导航" : "展开导航"));
|
collapseButton.setText(isExpanded() ? collapseLabel : null);
|
||||||
|
collapseButton.setAccessibleText(accessibleText);
|
||||||
|
collapseButton.setTooltip(new Tooltip(accessibleText));
|
||||||
|
collapseButton.setAlignment(isExpanded() ? Pos.CENTER_LEFT : Pos.CENTER);
|
||||||
updateItemAppearance();
|
updateItemAppearance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +214,7 @@ public final class NavigationRail extends VBox {
|
|||||||
ToggleButton button = view.button();
|
ToggleButton button = view.button();
|
||||||
button.setText(isExpanded() ? item.name : null);
|
button.setText(isExpanded() ? item.name : null);
|
||||||
button.setTooltip(isExpanded() ? null : view.tooltip());
|
button.setTooltip(isExpanded() ? null : view.tooltip());
|
||||||
|
button.setAccessibleText(item.name);
|
||||||
button.setAlignment(isExpanded() ? Pos.CENTER_LEFT : Pos.CENTER);
|
button.setAlignment(isExpanded() ? Pos.CENTER_LEFT : Pos.CENTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,17 +18,8 @@
|
|||||||
-workutils-success: #10b981;
|
-workutils-success: #10b981;
|
||||||
|
|
||||||
-workutils-primary-light: #dbeafe;
|
-workutils-primary-light: #dbeafe;
|
||||||
-workutils-font-ui: "Segoe UI";
|
-fx-font-family: "Segoe UI";
|
||||||
-workutils-font-mono: "Consolas";
|
-fx-font-size: 14px;
|
||||||
-workutils-font-page-title: 20;
|
|
||||||
-workutils-font-section-title: 15;
|
|
||||||
-workutils-font-body: 14;
|
|
||||||
-workutils-font-secondary: 13;
|
|
||||||
-workutils-row-height: 40;
|
|
||||||
-workutils-control-height: 36;
|
|
||||||
-workutils-header-height: 48;
|
|
||||||
-fx-font-family: -workutils-font-ui;
|
|
||||||
-fx-font-size: -workutils-font-body;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 导航栏 ---- */
|
/* ---- 导航栏 ---- */
|
||||||
@@ -38,145 +29,64 @@
|
|||||||
-fx-border-width: 0 1 0 0;
|
-fx-border-width: 0 1 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-brand {
|
|
||||||
-fx-text-fill: -workutils-text;
|
|
||||||
-fx-padding: 16 14 16 14;
|
|
||||||
-fx-font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-separator {
|
.nav-separator {
|
||||||
-fx-background-color: -workutils-border;
|
-fx-background-color: -workutils-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============ ListView 导航(JavaFX 标准化方案) ============ */
|
/* ============ ToggleButton 导航 ============ */
|
||||||
|
.navigation-rail-items {
|
||||||
.nav-list {
|
-fx-padding: 8px 0;
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-background-insets: 0;
|
|
||||||
-fx-border-width: 0;
|
|
||||||
-fx-padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 强制 ListView 所有内部容器透明,确保空区域不显示杂色 */
|
|
||||||
.nav-list,
|
|
||||||
.nav-list > *,
|
|
||||||
.nav-list .list-view,
|
|
||||||
.nav-list .viewport,
|
|
||||||
.nav-list .virtual-flow,
|
|
||||||
.nav-list .virtual-flow > *,
|
|
||||||
.nav-list .clipped-container,
|
|
||||||
.nav-list .sheet,
|
|
||||||
.nav-list .corner {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-background-insets: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list .list-cell,
|
|
||||||
.nav-list .list-cell:empty,
|
|
||||||
.nav-list .list-cell:filled:empty {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动条美化 */
|
|
||||||
.nav-list .scroll-bar:vertical {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-pref-width: 6;
|
|
||||||
}
|
|
||||||
.nav-list .scroll-bar .thumb {
|
|
||||||
-fx-background-color: -workutils-border;
|
|
||||||
-fx-background-radius: 3;
|
|
||||||
}
|
|
||||||
.nav-list .scroll-bar .thumb:hover {
|
|
||||||
-fx-background-color: -workutils-text-muted;
|
|
||||||
}
|
|
||||||
.nav-list .scroll-bar .increment-button,
|
|
||||||
.nav-list .scroll-bar .decrement-button {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 每个导航项(Cell) */
|
|
||||||
.nav-cell {
|
.nav-cell {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-background-radius: 0;
|
-fx-background-radius: 0;
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
-fx-padding: 10 14 10 14;
|
-fx-border-color: transparent;
|
||||||
-fx-cursor: hand;
|
-fx-border-width: 0 0 0 3px;
|
||||||
-fx-border-width: 0;
|
-fx-border-radius: 0;
|
||||||
/* 平滑过渡背景色和文字色 */
|
-fx-padding: 0 14px 0 11px;
|
||||||
}
|
|
||||||
|
|
||||||
.nav-cell .label {
|
|
||||||
-fx-text-fill: -workutils-text-secondary;
|
-fx-text-fill: -workutils-text-secondary;
|
||||||
/* 深色背景下的文字阴影,提升对比度 */
|
-fx-font-size: 14px;
|
||||||
|
-fx-font-weight: 500;
|
||||||
|
-fx-graphic-text-gap: 12px;
|
||||||
|
-fx-cursor: hand;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移除 JavaFX ListCell 默认焦点环(让选中态边框干净) */
|
.nav-cell .app-icon {
|
||||||
.nav-list .list-cell {
|
-fx-icon-color: -workutils-text-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 强制空 cell 完全透明(含 placeholder、footer 等场景) */
|
|
||||||
.nav-list .list-cell:empty,
|
|
||||||
.nav-list .list-cell:filled:empty {
|
|
||||||
-fx-background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hover 状态 */
|
|
||||||
.nav-cell:hover {
|
.nav-cell:hover {
|
||||||
-fx-background-color: -workutils-card;
|
-fx-background-color: -workutils-card;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 兜底:所有 selected cell(含 empty)默认透明,防止虚拟化复用时空 cell 闪现蓝色 */
|
.nav-cell:selected,
|
||||||
.nav-list .list-cell:selected {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* selected 状态:强制 padding、background-insets、文字色一致
|
|
||||||
关键:用 :filled:selected 而非自定义 class,避免 JavaFX 内置样式覆盖 */
|
|
||||||
.nav-list .list-cell:filled:selected {
|
|
||||||
-fx-background-color: -workutils-primary-light;
|
|
||||||
-fx-background-insets: 0;
|
|
||||||
-fx-padding: 10 14 10 14;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list .list-cell:filled:selected .label {
|
|
||||||
-fx-text-fill: -workutils-primary !important;
|
|
||||||
-fx-font-weight: 600;
|
|
||||||
/* 移除 dropshadow,让文字干净锐利 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list .list-cell:filled:selected .nav-indicator {
|
|
||||||
-fx-fill: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 兼容:之前手动加的 .nav-cell-active 类名也支持 */
|
|
||||||
.nav-cell-active {
|
.nav-cell-active {
|
||||||
-fx-background-color: -workutils-primary-light;
|
-fx-background-color: -workutils-primary-light;
|
||||||
-fx-background-insets: 0;
|
-fx-border-color: -workutils-primary;
|
||||||
-fx-padding: 10 14 10 14;
|
-fx-border-width: 0 0 0 3px;
|
||||||
|
-fx-text-fill: -workutils-primary;
|
||||||
|
-fx-font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-cell:selected .app-icon,
|
||||||
|
.nav-cell-active .app-icon {
|
||||||
|
-fx-icon-color: -workutils-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 按下状态:微妙的反馈 */
|
|
||||||
.nav-cell:pressed {
|
.nav-cell:pressed {
|
||||||
-fx-background-color: -workutils-primary-light;
|
-fx-background-color: -workutils-primary-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ListView 自身获得焦点时也保持透明,不显示蓝色环 */
|
.nav-cell:focused {
|
||||||
/* 左侧指示条 */
|
-fx-border-color: -workutils-primary;
|
||||||
.nav-indicator {
|
-fx-border-width: 1px 1px 1px 3px;
|
||||||
-fx-fill: transparent;
|
|
||||||
-fx-arc-width: 2;
|
|
||||||
-fx-arc-height: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-cell-active .nav-indicator {
|
|
||||||
-fx-fill: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 点击涟漪层 */
|
|
||||||
.nav-version {
|
.nav-version {
|
||||||
-fx-text-fill: -workutils-text-muted;
|
-fx-text-fill: -workutils-text-muted;
|
||||||
-fx-padding: 10 14 10 14;
|
-fx-padding: 10px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 顶栏 ---- */
|
/* ---- 顶栏 ---- */
|
||||||
@@ -184,9 +94,9 @@
|
|||||||
-fx-background-color: -workutils-surface;
|
-fx-background-color: -workutils-surface;
|
||||||
-fx-border-width: 0 0 1 0;
|
-fx-border-width: 0 0 1 0;
|
||||||
-fx-border-color: -workutils-border;
|
-fx-border-color: -workutils-border;
|
||||||
-fx-pref-height: -workutils-header-height;
|
-fx-pref-height: 48px;
|
||||||
-fx-min-height: -workutils-header-height;
|
-fx-min-height: 48px;
|
||||||
-fx-max-height: -workutils-header-height;
|
-fx-max-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 内容区 ---- */
|
/* ---- 内容区 ---- */
|
||||||
@@ -204,18 +114,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
-fx-font-size: -workutils-font-page-title;
|
-fx-font-size: 20px;
|
||||||
-fx-font-weight: 600;
|
-fx-font-weight: 600;
|
||||||
-fx-text-fill: -workutils-text;
|
-fx-text-fill: -workutils-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body-text {
|
.body-text {
|
||||||
-fx-font-size: -workutils-font-body;
|
-fx-font-size: 14px;
|
||||||
-fx-text-fill: -workutils-text;
|
-fx-text-fill: -workutils-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-text {
|
.secondary-text {
|
||||||
-fx-font-size: -workutils-font-secondary;
|
-fx-font-size: 13px;
|
||||||
-fx-text-fill: -workutils-text-secondary;
|
-fx-text-fill: -workutils-text-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +136,8 @@
|
|||||||
.control-default,
|
.control-default,
|
||||||
.text-field-default,
|
.text-field-default,
|
||||||
.combo-default {
|
.combo-default {
|
||||||
-fx-pref-height: -workutils-control-height;
|
-fx-pref-height: 36px;
|
||||||
-fx-min-height: -workutils-control-height;
|
-fx-min-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 按钮增强 ---- */
|
/* ---- 按钮增强 ---- */
|
||||||
@@ -306,8 +216,8 @@
|
|||||||
-fx-border-width: 1;
|
-fx-border-width: 1;
|
||||||
-fx-border-radius: 4;
|
-fx-border-radius: 4;
|
||||||
-fx-background-radius: 4;
|
-fx-background-radius: 4;
|
||||||
-fx-font-family: -workutils-font-mono;
|
-fx-font-family: "Consolas";
|
||||||
-fx-font-size: -workutils-font-body;
|
-fx-font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 状态栏 ---- */
|
/* ---- 状态栏 ---- */
|
||||||
@@ -339,25 +249,25 @@
|
|||||||
-fx-border-color: -workutils-border;
|
-fx-border-color: -workutils-border;
|
||||||
-fx-border-width: 1;
|
-fx-border-width: 1;
|
||||||
-fx-border-radius: 4;
|
-fx-border-radius: 4;
|
||||||
-fx-fixed-cell-size: -workutils-row-height;
|
-fx-fixed-cell-size: 40px;
|
||||||
-fx-font-size: -workutils-font-body;
|
-fx-font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 标题 ---- */
|
/* ---- 标题 ---- */
|
||||||
.section-title {
|
.section-title {
|
||||||
-fx-font-size: -workutils-font-section-title;
|
-fx-font-size: 15px;
|
||||||
-fx-font-weight: 600;
|
-fx-font-weight: 600;
|
||||||
-fx-text-fill: -workutils-text;
|
-fx-text-fill: -workutils-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint-text {
|
.hint-text {
|
||||||
-fx-text-fill: -workutils-text-secondary;
|
-fx-text-fill: -workutils-text-secondary;
|
||||||
-fx-font-size: -workutils-font-secondary;
|
-fx-font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.muted-text {
|
.muted-text {
|
||||||
-fx-text-fill: -workutils-text-muted;
|
-fx-text-fill: -workutils-text-muted;
|
||||||
-fx-font-size: -workutils-font-secondary;
|
-fx-font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger-text {
|
.danger-text {
|
||||||
@@ -421,13 +331,13 @@
|
|||||||
-fx-border-color: -workutils-border;
|
-fx-border-color: -workutils-border;
|
||||||
-fx-border-width: 1;
|
-fx-border-width: 1;
|
||||||
-fx-border-radius: 4;
|
-fx-border-radius: 4;
|
||||||
-fx-fixed-cell-size: -workutils-row-height;
|
-fx-fixed-cell-size: 40px;
|
||||||
-fx-font-size: -workutils-font-body;
|
-fx-font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 代码文本 ---- */
|
/* ---- 代码文本 ---- */
|
||||||
.mono-text {
|
.mono-text {
|
||||||
-fx-font-family: -workutils-font-mono;
|
-fx-font-family: "Consolas";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 主题切换按钮 ---- */
|
/* ---- 主题切换按钮 ---- */
|
||||||
@@ -439,7 +349,7 @@
|
|||||||
/* ---- 紧凑列表单元格 ---- */
|
/* ---- 紧凑列表单元格 ---- */
|
||||||
.compact-list .list-cell {
|
.compact-list .list-cell {
|
||||||
-fx-padding: 3 8 3 8;
|
-fx-padding: 3 8 3 8;
|
||||||
-fx-font-size: -workutils-font-secondary;
|
-fx-font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compact-list .list-cell:selected {
|
.compact-list .list-cell:selected {
|
||||||
@@ -473,7 +383,7 @@
|
|||||||
}
|
}
|
||||||
.apidoc-tab-pane .tab .tab-label {
|
.apidoc-tab-pane .tab .tab-label {
|
||||||
-fx-text-fill: -workutils-text-secondary;
|
-fx-text-fill: -workutils-text-secondary;
|
||||||
-fx-font-size: -workutils-font-body;
|
-fx-font-size: 14px;
|
||||||
-fx-font-weight: normal;
|
-fx-font-weight: normal;
|
||||||
}
|
}
|
||||||
.apidoc-tab-pane .tab:selected {
|
.apidoc-tab-pane .tab:selected {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ====== WorkUtils 插件配置文件 ======
|
# ====== WorkUtils 插件配置文件 ======
|
||||||
# 插件顺序(按逗号分隔,顺序即菜单显示顺序)
|
# 插件顺序(按逗号分隔,顺序即菜单显示顺序)
|
||||||
# 插件状态(disabled 列表,不在列表中的即为启用)
|
# 插件状态(disabled 列表,不在列表中的即为启用)
|
||||||
plugins.order=scan,diff,api-doc,git-sync,git-review,mobile-ui-auto,mybatisexport,mybatisgenerator
|
plugins.order=git-sync,git-review,diff,mybatisexport,mybatisgenerator,scan,api-doc
|
||||||
plugins.disabled=mobile-ui-auto
|
plugins.disabled=mobile-ui-auto
|
||||||
|
|||||||
+2
-2
@@ -41,8 +41,8 @@ class DefaultPluginConfigurationTest {
|
|||||||
|
|
||||||
assertEquals(7, manager.getEnabledPlugins().size());
|
assertEquals(7, manager.getEnabledPlugins().size());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
List.of("scan", "diff", "api-doc", "git-sync", "git-review",
|
List.of("git-sync", "git-review", "diff", "mybatisexport",
|
||||||
"mybatisexport", "mybatisgenerator"),
|
"mybatisgenerator", "scan", "api-doc"),
|
||||||
manager.getEnabledPlugins().stream().map(Plugin::getId).toList());
|
manager.getEnabledPlugins().stream().map(Plugin::getId).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package com.example.workutils.system.ui;
|
||||||
|
|
||||||
|
import javafx.css.CssParser;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TabPane;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.TreeCell;
|
||||||
|
import javafx.scene.control.TreeItem;
|
||||||
|
import javafx.scene.control.TreeView;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.example.workutils.system.ui.FxTestSupport.fx;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class LightThemeCssRuntimeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void realStylesheetAddsNoJavaFxParserErrors() throws Exception {
|
||||||
|
fx(() -> {
|
||||||
|
int errorsBefore = CssParser.errorsProperty().size();
|
||||||
|
|
||||||
|
createFixture();
|
||||||
|
|
||||||
|
List<CssParser.ParseError> newErrors = CssParser.errorsProperty()
|
||||||
|
.subList(errorsBefore, CssParser.errorsProperty().size());
|
||||||
|
assertTrue(newErrors.isEmpty(), () -> "JavaFX CSS parser errors: " + newErrors);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void semanticClassesComputeApprovedTypographyAndDimensions() throws Exception {
|
||||||
|
fx(() -> {
|
||||||
|
Fixture fixture = createFixture();
|
||||||
|
|
||||||
|
assertEquals(20, fixture.pageTitle().getFont().getSize());
|
||||||
|
assertEquals(15, fixture.sectionTitle().getFont().getSize());
|
||||||
|
assertEquals(14, fixture.bodyText().getFont().getSize());
|
||||||
|
assertEquals(13, fixture.secondaryText().getFont().getSize());
|
||||||
|
|
||||||
|
assertEquals(13, fixture.logArea().getFont().getSize());
|
||||||
|
assertEquals(40, fixture.table().getFixedCellSize());
|
||||||
|
assertEquals(40, fixture.tree().getFixedCellSize());
|
||||||
|
assertEquals(13, firstTableCell(fixture.table()).getFont().getSize());
|
||||||
|
assertEquals(13, firstTreeCell(fixture.tree()).getFont().getSize());
|
||||||
|
assertEquals(13, firstListCell(fixture.compactList()).getFont().getSize());
|
||||||
|
assertEquals(14, assertInstanceOf(
|
||||||
|
Label.class, fixture.tabPane().lookup(".tab-label")).getFont().getSize());
|
||||||
|
|
||||||
|
assertEquals(36, fixture.defaultButton().getMinHeight());
|
||||||
|
assertEquals(36, fixture.defaultButton().getPrefHeight());
|
||||||
|
assertEquals(36, fixture.textField().getMinHeight());
|
||||||
|
assertEquals(36, fixture.comboBox().getPrefHeight());
|
||||||
|
assertEquals(48, fixture.header().getMinHeight());
|
||||||
|
assertEquals(48, fixture.header().getPrefHeight());
|
||||||
|
assertEquals(48, fixture.header().getMaxHeight());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Fixture createFixture() {
|
||||||
|
Label pageTitle = labelWithStyle(Theme.CSS_PAGE_TITLE);
|
||||||
|
Label sectionTitle = labelWithStyle(Theme.CSS_SECTION_TITLE);
|
||||||
|
Label bodyText = labelWithStyle(Theme.CSS_BODY_TEXT);
|
||||||
|
Label secondaryText = labelWithStyle(Theme.CSS_SECONDARY_TEXT);
|
||||||
|
|
||||||
|
TextArea logArea = new TextArea("log");
|
||||||
|
logArea.getStyleClass().add(Theme.CSS_LOG_AREA);
|
||||||
|
|
||||||
|
TableView<String> table = new TableView<>();
|
||||||
|
table.getStyleClass().add(Theme.CSS_TABLE);
|
||||||
|
TableColumn<String, String> column = new TableColumn<>("Value");
|
||||||
|
column.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
|
||||||
|
table.getColumns().add(column);
|
||||||
|
table.getItems().add("table row");
|
||||||
|
table.setPrefHeight(120);
|
||||||
|
|
||||||
|
TreeItem<String> treeRoot = new TreeItem<>("tree row");
|
||||||
|
TreeView<String> tree = new TreeView<>(treeRoot);
|
||||||
|
tree.getStyleClass().add(Theme.CSS_PROJECT_TREE);
|
||||||
|
tree.setPrefHeight(120);
|
||||||
|
|
||||||
|
ListView<String> compactList = new ListView<>();
|
||||||
|
compactList.getStyleClass().add("compact-list");
|
||||||
|
compactList.getItems().add("compact row");
|
||||||
|
compactList.setPrefHeight(80);
|
||||||
|
|
||||||
|
TabPane tabPane = new TabPane(new Tab("API tab", new Label("content")));
|
||||||
|
tabPane.getStyleClass().add("apidoc-tab-pane");
|
||||||
|
|
||||||
|
Button defaultButton = new Button("Default");
|
||||||
|
defaultButton.getStyleClass().add(Theme.CSS_CONTROL_DEFAULT);
|
||||||
|
TextField textField = new TextField("text");
|
||||||
|
textField.getStyleClass().add(Theme.CSS_TEXT_FIELD);
|
||||||
|
ComboBox<String> comboBox = new ComboBox<>();
|
||||||
|
comboBox.getStyleClass().add(Theme.CSS_COMBO);
|
||||||
|
Region header = new Region();
|
||||||
|
header.getStyleClass().add(Theme.CSS_HEADER_BAR);
|
||||||
|
|
||||||
|
VBox root = new VBox(pageTitle, sectionTitle, bodyText, secondaryText,
|
||||||
|
logArea, table, tree, compactList, tabPane,
|
||||||
|
defaultButton, textField, comboBox, header);
|
||||||
|
Scene scene = new Scene(root, 900, 1000);
|
||||||
|
Theme.applyCustomCss(scene);
|
||||||
|
root.applyCss();
|
||||||
|
root.layout();
|
||||||
|
|
||||||
|
return new Fixture(pageTitle, sectionTitle, bodyText, secondaryText,
|
||||||
|
logArea, table, tree, compactList, tabPane,
|
||||||
|
defaultButton, textField, comboBox, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label labelWithStyle(String styleClass) {
|
||||||
|
Label label = new Label(styleClass);
|
||||||
|
label.getStyleClass().add(styleClass);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TableCell<?, ?> firstTableCell(TableView<?> table) {
|
||||||
|
return table.lookupAll(".table-cell").stream()
|
||||||
|
.filter(TableCell.class::isInstance)
|
||||||
|
.map(TableCell.class::cast)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TreeCell<?> firstTreeCell(TreeView<?> tree) {
|
||||||
|
return tree.lookupAll(".tree-cell").stream()
|
||||||
|
.filter(TreeCell.class::isInstance)
|
||||||
|
.map(TreeCell.class::cast)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListCell<?> firstListCell(ListView<?> list) {
|
||||||
|
return list.lookupAll(".list-cell").stream()
|
||||||
|
.filter(ListCell.class::isInstance)
|
||||||
|
.map(ListCell.class::cast)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Fixture(
|
||||||
|
Label pageTitle,
|
||||||
|
Label sectionTitle,
|
||||||
|
Label bodyText,
|
||||||
|
Label secondaryText,
|
||||||
|
TextArea logArea,
|
||||||
|
TableView<String> table,
|
||||||
|
TreeView<String> tree,
|
||||||
|
ListView<String> compactList,
|
||||||
|
TabPane tabPane,
|
||||||
|
Button defaultButton,
|
||||||
|
TextField textField,
|
||||||
|
ComboBox<String> comboBox,
|
||||||
|
Region header) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,28 @@ class MainWindowArchitectureTest {
|
|||||||
assertTrue(mainWindow.contains("openSettings.run()"));
|
assertTrue(mainWindow.contains("openSettings.run()"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void refreshCallbackOnlyRebuildsNavigationAndNeverEagerlyEmbedsPluginPanes() throws Exception {
|
||||||
|
String application = source("src/main/java/com/example/workutils/WorkUtilsApplication.java");
|
||||||
|
String refreshCallback = between(application,
|
||||||
|
"settingsService.setNavItemsRefresher",
|
||||||
|
"List<MainWindow.NavItem> navItems = buildNavItems();");
|
||||||
|
String buildNavItems = between(application,
|
||||||
|
"private List<MainWindow.NavItem> buildNavItems()",
|
||||||
|
"// ==================== 内嵌优先打开");
|
||||||
|
String openPlugin = between(application,
|
||||||
|
"private void openPluginEmbedded",
|
||||||
|
"// ==================== 独立窗口模式");
|
||||||
|
|
||||||
|
assertTrue(refreshCallback.contains("mainWindow.refreshNavItems(buildNavItems())"));
|
||||||
|
assertFalse(refreshCallback.contains("getEmbeddedPane()"));
|
||||||
|
assertFalse(refreshCallback.contains("mainWindow.embedPlugin("));
|
||||||
|
assertFalse(refreshCallback.contains("for (Plugin plugin"));
|
||||||
|
assertTrue(buildNavItems.contains("() -> openPluginEmbedded(pluginId, plugin, factory)"));
|
||||||
|
assertTrue(openPlugin.indexOf("plugin.checkConfig()")
|
||||||
|
< openPlugin.indexOf("factory.getEmbeddedPane()"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void headerUsesDesignTokensAndFeatherSemanticsWithoutCustomWindowChrome() throws Exception {
|
void headerUsesDesignTokensAndFeatherSemanticsWithoutCustomWindowChrome() throws Exception {
|
||||||
String mainWindow = source("src/main/java/com/example/workutils/system/ui/MainWindow.java");
|
String mainWindow = source("src/main/java/com/example/workutils/system/ui/MainWindow.java");
|
||||||
|
|||||||
@@ -7,13 +7,17 @@ import com.example.workutils.system.ui.icon.AppIcon;
|
|||||||
import com.example.workutils.system.config.WorkUtilsProperties;
|
import com.example.workutils.system.config.WorkUtilsProperties;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ContentDisplay;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
@@ -62,12 +66,117 @@ class NavigationRailTest {
|
|||||||
|
|
||||||
assertEquals(AppIcon.MENU.glyph(),
|
assertEquals(AppIcon.MENU.glyph(),
|
||||||
assertInstanceOf(FontIcon.class, menu.getGraphic()).getIconCode());
|
assertInstanceOf(FontIcon.class, menu.getGraphic()).getIconCode());
|
||||||
|
assertEquals("切换导航栏", menu.getAccessibleText());
|
||||||
|
assertTrue(menu.isFocusTraversable());
|
||||||
assertEquals(AppIcon.APP.glyph(),
|
assertEquals(AppIcon.APP.glyph(),
|
||||||
assertInstanceOf(FontIcon.class, brand.getGraphic()).getIconCode());
|
assertInstanceOf(FontIcon.class, brand.getGraphic()).getIconCode());
|
||||||
assertEquals(AppIcon.CHEVRON_RIGHT.glyph(), chevron.getIconCode());
|
assertEquals(AppIcon.CHEVRON_RIGHT.glyph(), chevron.getIconCode());
|
||||||
assertEquals("API", title.getText());
|
assertEquals("API", title.getText());
|
||||||
assertEquals(AppIcon.SETTINGS.glyph(),
|
assertEquals(AppIcon.SETTINGS.glyph(),
|
||||||
assertInstanceOf(FontIcon.class, settings.getGraphic()).getIconCode());
|
assertInstanceOf(FontIcon.class, settings.getGraphic()).getIconCode());
|
||||||
|
assertEquals("系统设置", settings.getText());
|
||||||
|
assertEquals("系统设置", settings.getAccessibleText());
|
||||||
|
assertEquals(ContentDisplay.LEFT, settings.getContentDisplay());
|
||||||
|
assertTrue(settings.isFocusTraversable());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toggleButtonItemsComputeApprovedGapSelectionHoverAndFocusVisuals() throws Exception {
|
||||||
|
fx(() -> {
|
||||||
|
MainWindow.NavItem second = new MainWindow.NavItem(
|
||||||
|
AppIcon.DIFF, "文本对比", "对比文本", () -> {
|
||||||
|
}, "diff");
|
||||||
|
NavigationRail rail = new NavigationRail(List.of(API_DOC, second), item -> {
|
||||||
|
});
|
||||||
|
VBox root = new VBox(rail);
|
||||||
|
Scene scene = new Scene(root, DesignTokens.NAV_EXPANDED_WIDTH, 320);
|
||||||
|
Theme.applyCustomCss(scene);
|
||||||
|
root.applyCss();
|
||||||
|
root.layout();
|
||||||
|
|
||||||
|
ToggleButton selected = assertInstanceOf(
|
||||||
|
ToggleButton.class, rail.lookup("#nav-api-doc"));
|
||||||
|
ToggleButton hover = assertInstanceOf(
|
||||||
|
ToggleButton.class, rail.lookup("#nav-diff"));
|
||||||
|
assertEquals(12, selected.getGraphicTextGap());
|
||||||
|
assertEquals("API 接口文档导出", selected.getAccessibleText());
|
||||||
|
assertTrue(selected.isFocusTraversable());
|
||||||
|
|
||||||
|
selected.fire();
|
||||||
|
root.applyCss();
|
||||||
|
root.layout();
|
||||||
|
|
||||||
|
assertEquals(Color.web("#dbeafe"),
|
||||||
|
selected.getBackground().getFills().get(0).getFill());
|
||||||
|
assertEquals(3, selected.getBorder().getStrokes().get(0).getWidths().getLeft());
|
||||||
|
assertEquals(Color.web("#2563eb"),
|
||||||
|
selected.getBorder().getStrokes().get(0).getLeftStroke());
|
||||||
|
assertEquals(Color.web("#2563eb"), selected.getTextFill());
|
||||||
|
assertEquals(Color.web("#2563eb"),
|
||||||
|
assertInstanceOf(FontIcon.class, selected.getGraphic()).getIconColor());
|
||||||
|
|
||||||
|
hover.pseudoClassStateChanged(PseudoClass.getPseudoClass("hover"), true);
|
||||||
|
root.applyCss();
|
||||||
|
assertEquals(Color.web("#f1f5f9"),
|
||||||
|
hover.getBackground().getFills().get(0).getFill());
|
||||||
|
|
||||||
|
selected.pseudoClassStateChanged(PseudoClass.getPseudoClass("focused"), true);
|
||||||
|
root.applyCss();
|
||||||
|
assertEquals(1, selected.getBorder().getStrokes().get(0).getWidths().getTop());
|
||||||
|
assertEquals(1, selected.getBorder().getStrokes().get(0).getWidths().getRight());
|
||||||
|
assertEquals(1, selected.getBorder().getStrokes().get(0).getWidths().getBottom());
|
||||||
|
assertEquals(3, selected.getBorder().getStrokes().get(0).getWidths().getLeft());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void railKeepsCollapseEntryAtBottomWithoutDuplicatingTheApplicationBrand() throws Exception {
|
||||||
|
fx(() -> {
|
||||||
|
NavigationRail rail = new NavigationRail(List.of(API_DOC), item -> {
|
||||||
|
});
|
||||||
|
|
||||||
|
Button collapse = assertInstanceOf(
|
||||||
|
Button.class, rail.lookup("#navigation-rail-toggle"));
|
||||||
|
assertSame(collapse, rail.getChildren().get(rail.getChildren().size() - 1));
|
||||||
|
assertTrue(rail.lookupAll(".nav-brand").isEmpty());
|
||||||
|
assertEquals("收起", collapse.getText());
|
||||||
|
assertEquals("收起导航栏", collapse.getAccessibleText());
|
||||||
|
assertTrue(collapse.isFocusTraversable());
|
||||||
|
|
||||||
|
collapse.fire();
|
||||||
|
|
||||||
|
assertFalse(rail.isExpanded());
|
||||||
|
assertNull(collapse.getText());
|
||||||
|
assertEquals("展开导航栏", collapse.getAccessibleText());
|
||||||
|
assertEquals("展开导航栏", collapse.getTooltip().getText());
|
||||||
|
|
||||||
|
collapse.fire();
|
||||||
|
assertTrue(rail.isExpanded());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void headerMenuAndBottomCollapseEntryDriveTheSameExpandedState() throws Exception {
|
||||||
|
fx(() -> {
|
||||||
|
MainWindow window = new MainWindow(List.of(API_DOC), new WorkUtilsProperties(), () -> {
|
||||||
|
});
|
||||||
|
Parent root = stage(window).getScene().getRoot();
|
||||||
|
NavigationRail rail = assertInstanceOf(
|
||||||
|
NavigationRail.class, root.lookup(".navigation-rail"));
|
||||||
|
Button menu = assertInstanceOf(Button.class, root.lookup("#menu-button"));
|
||||||
|
Button collapse = assertInstanceOf(
|
||||||
|
Button.class, root.lookup("#navigation-rail-toggle"));
|
||||||
|
|
||||||
|
menu.fire();
|
||||||
|
|
||||||
|
assertFalse(rail.isExpanded());
|
||||||
|
assertEquals("展开导航栏", collapse.getAccessibleText());
|
||||||
|
|
||||||
|
collapse.fire();
|
||||||
|
|
||||||
|
assertTrue(rail.isExpanded());
|
||||||
|
assertEquals("收起导航栏", collapse.getAccessibleText());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +333,58 @@ class NavigationRailTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mainWindowRefreshDoesNotRunPluginActionsAndKeepsContentAndSelectionConsistent() throws Exception {
|
||||||
|
fx(() -> {
|
||||||
|
AtomicReference<MainWindow> windowRef = new AtomicReference<>();
|
||||||
|
AtomicInteger firstOpens = new AtomicInteger();
|
||||||
|
AtomicInteger secondOpens = new AtomicInteger();
|
||||||
|
MainWindow.NavItem first = new MainWindow.NavItem(
|
||||||
|
AppIcon.DIFF, "First", "First item", () -> {
|
||||||
|
firstOpens.incrementAndGet();
|
||||||
|
StackPane pane = new StackPane();
|
||||||
|
pane.setId("pane-first");
|
||||||
|
windowRef.get().embedPlugin("first", "legacy", "First", pane);
|
||||||
|
}, "first");
|
||||||
|
MainWindow window = new MainWindow(
|
||||||
|
List.of(first), new WorkUtilsProperties(), () -> {
|
||||||
|
});
|
||||||
|
windowRef.set(window);
|
||||||
|
Parent root = stage(window).getScene().getRoot();
|
||||||
|
ToggleButton firstButton = assertInstanceOf(
|
||||||
|
ToggleButton.class, root.lookup("#nav-first"));
|
||||||
|
firstButton.fire();
|
||||||
|
|
||||||
|
MainWindow.NavItem second = new MainWindow.NavItem(
|
||||||
|
AppIcon.SCANNER, "Second", "Second item", () -> {
|
||||||
|
secondOpens.incrementAndGet();
|
||||||
|
StackPane pane = new StackPane();
|
||||||
|
pane.setId("pane-second");
|
||||||
|
windowRef.get().embedPlugin("second", "legacy", "Second", pane);
|
||||||
|
}, "second");
|
||||||
|
|
||||||
|
window.refreshNavItems(List.of(second));
|
||||||
|
|
||||||
|
NavigationRail rail = assertInstanceOf(
|
||||||
|
NavigationRail.class, root.lookup(".navigation-rail"));
|
||||||
|
ToggleButton secondButton = assertInstanceOf(
|
||||||
|
ToggleButton.class, root.lookup("#nav-second"));
|
||||||
|
assertNull(root.lookup("#nav-first"));
|
||||||
|
assertEquals(1, firstOpens.get());
|
||||||
|
assertEquals(0, secondOpens.get());
|
||||||
|
assertNull(rail.selectedPluginId());
|
||||||
|
assertNotNull(root.lookup(".welcome-page"));
|
||||||
|
assertNull(root.lookup("#pane-first"));
|
||||||
|
|
||||||
|
secondButton.fire();
|
||||||
|
|
||||||
|
assertEquals(1, secondOpens.get());
|
||||||
|
assertEquals("second", rail.selectedPluginId());
|
||||||
|
assertNotNull(root.lookup("#pane-second"));
|
||||||
|
assertNull(root.lookup(".welcome-page"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void collapsedRailUsesExactFixedWidthAndCollapsedPseudoClass() throws Exception {
|
void collapsedRailUsesExactFixedWidthAndCollapsedPseudoClass() throws Exception {
|
||||||
fx(() -> {
|
fx(() -> {
|
||||||
@@ -255,6 +416,7 @@ class NavigationRailTest {
|
|||||||
assertNotNull(tooltip);
|
assertNotNull(tooltip);
|
||||||
assertEquals("API 接口文档导出", tooltip.getText());
|
assertEquals("API 接口文档导出", tooltip.getText());
|
||||||
assertSame(tooltip, apiDocButton.getTooltip());
|
assertSame(tooltip, apiDocButton.getTooltip());
|
||||||
|
assertEquals("API 接口文档导出", apiDocButton.getAccessibleText());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user