Files
timi-fx-ui/src/main/java/com/imyeyu/fx/ui/components/Navigation.java
2025-07-14 15:08:16 +08:00

244 lines
6.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.imyeyu.fx.ui.components;
import com.imyeyu.fx.TimiFX;
import com.imyeyu.fx.ui.TimiFXUI;
import com.imyeyu.fx.utils.SmoothScroll;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TitledPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import java.util.List;
/**
* 纵向导航组件,可实现二级导航,折叠导航
*
* @author 夜雨
* @since 2022-02-17 00:11
*/
public class Navigation extends ScrollPane implements TimiFXUI {
/** 导航列表项 */
protected final ObservableList<ToggleButton> items;
/** 已选中监听 */
protected final ObjectProperty<ToggleButton> selectedItem;
/** 默认构造器 */
public Navigation() {
items = FXCollections.observableArrayList();
selectedItem = new SimpleObjectProperty<>();
VBox root = new VBox();
root.setBorder(Stroke.BOTTOM);
getStyleClass().addAll("navigation", "sp-border");
setMaxWidth(Double.MAX_VALUE);
setVbarPolicy(ScrollBarPolicy.NEVER);
setFitToWidth(true);
setContent(root);
SmoothScroll.scrollPaneV(this);
// ---------- 事件 ----------
ToggleGroup group = new ToggleGroup();
// 主动选择(代码触发)
selectedItem.addListener((obs, o, newSelectedItem) -> group.selectToggle(newSelectedItem));
// 被动选择(操作触发)
group.selectedToggleProperty().addListener((obs, o, toggle) -> {
if (toggle instanceof ToggleButton btn) {
selectedItem.set(btn);
}
});
ObservableList<Node> childrens = root.getChildren();
// 响应 TimiFXUI
items.addListener((ListChangeListener<ToggleButton>) c -> {
while (c.next()) {
if (c.wasAdded()) {
// 添加
List<? extends ToggleButton> list = c.getAddedSubList();
for (int i = 0; i < list.size(); i++) {
list.get(i).setMaxWidth(Double.MAX_VALUE);
list.get(i).getStyleClass().setAll(CSS.MINECRAFT, "navigation-button");
list.get(i).addEventFilter(MouseEvent.MOUSE_PRESSED, TimiFX.EVENT_CONSUME_TG_BTN);
if (list.get(i).getProperties().get("OWNER") instanceof TitledPane pane) {
// 存在所属组
if (!childrens.contains(pane)) {
// 未添加组
childrens.add(pane);
}
if (pane.getContent() instanceof VBox box) {
box.getChildren().add(list.get(i));
}
} else {
// 单独项
if (!childrens.isEmpty()) {
if (childrens.get(childrens.size() - 1) instanceof TitledPane) {
// 上一项是组导航,添加上边框
list.get(i).getStyleClass().add("after-group");
}
}
childrens.add(list.get(i));
}
// 归组
group.getToggles().add(list.get(i));
}
return;
}
if (c.wasRemoved()) {
// 移除
List<? extends ToggleButton> list = c.getRemoved();
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getProperties().get("OWNER") instanceof TitledPane pane) {
// 存在所属组
if (pane.getContent() instanceof VBox box) {
box.getChildren().remove(list.get(i));
if (box.getChildren().isEmpty()) {
// 该组已没有列表项
childrens.remove(box);
}
}
} else {
// 单独项
childrens.remove(list.get(i));
}
// 从组移除
group.getToggles().remove(list.get(i));
}
}
}
});
}
/**
* 添加导航按钮
*
* @param buttons 导航按钮
*/
public void add(ToggleButton... buttons) {
getItems().addAll(buttons);
}
/**
* 添加默认没有展开的导航组
*
* @param title 标题
* @param buttons 导航项
* @return 构造的折叠面板
*/
public TitledPane addGroup(String title, ToggleButton... buttons) {
return addGroup(title, false, buttons);
}
/**
* 添加默认展开的导航组
*
* @param title 标题
* @param buttons 导航项
* @return 构造的折叠面板
*/
public TitledPane addExpandedGroup(String title, ToggleButton... buttons) {
return addGroup(title, true, buttons);
}
/**
* 添加导航组
*
* @param title 标题
* @param isExpanded true 为默认展开
* @param buttons 导航项
* @return 构造的折叠面板
*/
public TitledPane addGroup(String title, boolean isExpanded, ToggleButton... buttons) {
TitledPane pane = new TitledPane();
pane.setText(title);
return addGroup(pane, isExpanded, buttons);
}
/**
* 添加导航组
*
* @param pane 所属组
* @param isExpanded true 为默认展开
* @param buttons 导航项
* @return 原折叠面板
*/
public TitledPane addGroup(TitledPane pane, boolean isExpanded, ToggleButton... buttons) {
VBox content = new VBox();
content.setPadding(Insets.EMPTY);
pane.setContent(content);
pane.setExpanded(isExpanded);
pane.getStyleClass().add("group-pane");
for (int i = 0; i < buttons.length; i++) {
buttons[i].getProperties().put("OWNER", pane);
items.add(buttons[i]);
}
return pane;
}
/**
* 获取该按钮所属组
*
* @param btn 按钮
* @return 所属组null 时为不属于任何组
*/
public TitledPane getGroup(ToggleButton btn) {
if (btn.getProperties().get("OWNER") instanceof TitledPane pane) {
return pane;
}
return null;
}
/**
* 设置当前激活导航项
*
* @param button 导航项
*/
public void setSelectedItem(ToggleButton button) {
selectedItem.set(button);
}
/**
* 获取当前激活的导航项
*
* @return 当前激活的导航项
*/
public ToggleButton getSelectedItem() {
return selectedItem.get();
}
/**
* 获取激活导航项监听
*
* @return 激活导航项监听
*/
public ObjectProperty<ToggleButton> selectedItem() {
return selectedItem;
}
/**
* 获取导航数据列表
*
* @return 导航数据列表
*/
public ObservableList<ToggleButton> getItems() {
return items;
}
}