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 items; /** 已选中监听 */ protected final ObjectProperty 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 childrens = root.getChildren(); // 响应 TimiFXUI items.addListener((ListChangeListener) c -> { while (c.next()) { if (c.wasAdded()) { // 添加 List 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 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 selectedItem() { return selectedItem; } /** * 获取导航数据列表 * * @return 导航数据列表 */ public ObservableList getItems() { return items; } }