Compare commits
4 Commits
dbb39b3d2f
...
57e6b13292
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57e6b13292 | ||
|
|
b55a66d6df | ||
|
|
867c5f6602 | ||
|
|
0a5bb393cb |
@@ -1,147 +0,0 @@
|
||||
package com.imyeyu.fx.ui.components;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.java.bean.CallbackArgReturn;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.TilePane;
|
||||
import javafx.stage.Popup;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多选选择器,文本框弹出复选框组件进行多项选择
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-12-29 17:05
|
||||
*/
|
||||
public class CheckBoxPicker<T> extends TextField implements TimiFXUI {
|
||||
|
||||
/** 数据列表 */
|
||||
@Getter
|
||||
private final ObservableList<T> items;
|
||||
private final CheckBoxListPopup<T> checkBoxListPopup;
|
||||
|
||||
/** 默认构造器 */
|
||||
public CheckBoxPicker() {
|
||||
items = FXCollections.observableList(new ArrayList<>());
|
||||
|
||||
checkBoxListPopup = new CheckBoxListPopup<>(items);
|
||||
|
||||
setEditable(false);
|
||||
|
||||
// ---------- 事件 ----------
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
Bounds b = localToScreen(getLayoutBounds());
|
||||
checkBoxListPopup.show(this, b.getMinX() - 5, b.getMaxY() - 6);
|
||||
});
|
||||
|
||||
checkBoxListPopup.root.prefWidthProperty().bind(widthProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置复选框工厂,工厂入参数据对象,返回复选框组件
|
||||
*
|
||||
* @param factory 复选框工厂
|
||||
*/
|
||||
public void setCheckBoxFactory(CallbackArgReturn<T, CheckBox> factory) {
|
||||
checkBoxListPopup.checkBoxFactory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已选项
|
||||
*
|
||||
* @return 已选项
|
||||
*/
|
||||
public ObservableList<T> getSelectedItems() {
|
||||
return checkBoxListPopup.selectedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复选框列表弹窗
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author 夜雨
|
||||
* @since 2021-12-29 19:40
|
||||
*/
|
||||
private static class CheckBoxListPopup<T> extends Popup implements TimiFXUI {
|
||||
|
||||
private static final Insets PADDING = new Insets(6, 8, 6, 8);
|
||||
|
||||
private final TilePane root;
|
||||
private final Map<T, CheckBox> cache; // 数据映射缓存
|
||||
|
||||
final ObservableList<T> selectedItems;
|
||||
CallbackArgReturn<T, CheckBox> checkBoxFactory;
|
||||
|
||||
/**
|
||||
* 默认构造
|
||||
*
|
||||
* @param items 数据列表
|
||||
*/
|
||||
public CheckBoxListPopup(ObservableList<T> items) {
|
||||
cache = new HashMap<>();
|
||||
selectedItems = FXCollections.observableList(new ArrayList<>());
|
||||
|
||||
root = new TilePane();
|
||||
root.setHgap(8);
|
||||
root.setEffect(Shadow.POPUP);
|
||||
root.setBorder(Stroke.DEFAULT);
|
||||
root.setPadding(PADDING);
|
||||
root.setMinWidth(300);
|
||||
root.setBackground(BG.DEFAULT);
|
||||
root.setTileAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
setAutoHide(true);
|
||||
getContent().add(root);
|
||||
|
||||
// ---------- 事件 ----------
|
||||
|
||||
// 列表更新
|
||||
items.addListener((ListChangeListener<T>) c -> {
|
||||
if (c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
// 添加
|
||||
List<? extends T> list = c.getAddedSubList();
|
||||
for (T t : list) {
|
||||
CheckBox box;
|
||||
if (checkBoxFactory != null) {
|
||||
box = checkBoxFactory.handler(t);
|
||||
} else {
|
||||
box = new CheckBox(t.toString());
|
||||
}
|
||||
box.selectedProperty().addListener((obs, o, isSelected) -> {
|
||||
if (isSelected) {
|
||||
selectedItems.add(t);
|
||||
} else {
|
||||
selectedItems.remove(t);
|
||||
}
|
||||
});
|
||||
cache.put(t, box);
|
||||
root.getChildren().add(box);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (c.wasRemoved()) {
|
||||
// 移除
|
||||
List<? extends T> list = c.getRemoved();
|
||||
for (T t : list) {
|
||||
root.getChildren().remove(cache.get(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ import java.util.List;
|
||||
* @author 夜雨
|
||||
* @since 2022-05-26 14:32
|
||||
*/
|
||||
public class FileTreeView extends XTreeView<File> implements TimiFXUI.Colorful {
|
||||
public class FileTreeView extends TreeView<File> implements TimiFXUI.Colorful {
|
||||
|
||||
/** 正在查找节点监听 */
|
||||
protected final BooleanBinding findingItem;
|
||||
|
||||
@@ -21,13 +21,13 @@ import java.util.regex.Pattern;
|
||||
* 超链标签
|
||||
*
|
||||
* <pre>
|
||||
* new XHyperlink("个人博客", "<a href="https://www.imyeyu.net">https://www.imyeyu.net</a>");
|
||||
* new Hyperlink("个人博客", "<a href="https://www.imyeyu.net">https://www.imyeyu.net</a>");
|
||||
* </pre>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-08-30 10:51
|
||||
*/
|
||||
public class XHyperlink extends Label implements TimiFXUI, TimiFXUI.Colorful {
|
||||
public class Hyperlink extends Label implements TimiFXUI, TimiFXUI.Colorful {
|
||||
|
||||
private static final Pattern URL_PATTERN = Pattern.compile("https?://(www\\.)?[-a-zA-Z\\d@:%._+~#=]{1,256}\\.[a-zA-Z\\d()]{1,6}\\b([-a-zA-Z\\d()@:%_+.~#?&/=]*)");
|
||||
|
||||
@@ -35,7 +35,7 @@ public class XHyperlink extends Label implements TimiFXUI, TimiFXUI.Colorful {
|
||||
protected final StringProperty url;
|
||||
|
||||
/** 默认构造器 */
|
||||
public XHyperlink() {
|
||||
public Hyperlink() {
|
||||
this("", "");
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class XHyperlink extends Label implements TimiFXUI, TimiFXUI.Colorful {
|
||||
*
|
||||
* @param url 链接
|
||||
*/
|
||||
public XHyperlink(String url) {
|
||||
public Hyperlink(String url) {
|
||||
this(url, url);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class XHyperlink extends Label implements TimiFXUI, TimiFXUI.Colorful {
|
||||
* @param text 显示文本
|
||||
* @param url 链接
|
||||
*/
|
||||
public XHyperlink(String text, String url) {
|
||||
public Hyperlink(String text, String url) {
|
||||
this(null, text, url);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class XHyperlink extends Label implements TimiFXUI, TimiFXUI.Colorful {
|
||||
* @param text 显示文本
|
||||
* @param url 链接
|
||||
*/
|
||||
public XHyperlink(Node icon, String text, String url) {
|
||||
public Hyperlink(Node icon, String text, String url) {
|
||||
super(text);
|
||||
|
||||
this.url = new SimpleStringProperty(url);
|
||||
@@ -1,236 +0,0 @@
|
||||
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 lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 纵向导航组件,可实现二级导航,折叠导航
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-02-17 00:11
|
||||
*/
|
||||
public class Navigation extends ScrollPane implements TimiFXUI {
|
||||
|
||||
/** 导航数据列表 */
|
||||
@Getter
|
||||
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> children = root.getChildren();
|
||||
|
||||
// 响应 TimiFXUI
|
||||
items.addListener((ListChangeListener<ToggleButton>) c -> {
|
||||
while (c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
// 添加
|
||||
List<? extends ToggleButton> list = c.getAddedSubList();
|
||||
for (ToggleButton toggleButton : list) {
|
||||
toggleButton.setMaxWidth(Double.MAX_VALUE);
|
||||
toggleButton.getStyleClass().setAll(CSS.MINECRAFT, "navigation-button");
|
||||
toggleButton.addEventFilter(MouseEvent.MOUSE_PRESSED, TimiFX.EVENT_CONSUME_TG_BTN);
|
||||
|
||||
if (toggleButton.getProperties().get("OWNER") instanceof TitledPane pane) {
|
||||
// 存在所属组
|
||||
if (!children.contains(pane)) {
|
||||
// 未添加组
|
||||
children.add(pane);
|
||||
}
|
||||
if (pane.getContent() instanceof VBox box) {
|
||||
box.getChildren().add(toggleButton);
|
||||
}
|
||||
} else {
|
||||
// 单独项
|
||||
if (!children.isEmpty()) {
|
||||
if (children.getLast() instanceof TitledPane) {
|
||||
// 上一项是组导航,添加上边框
|
||||
toggleButton.getStyleClass().add("after-group");
|
||||
}
|
||||
}
|
||||
children.add(toggleButton);
|
||||
}
|
||||
// 归组
|
||||
group.getToggles().add(toggleButton);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (c.wasRemoved()) {
|
||||
// 移除
|
||||
List<? extends ToggleButton> list = c.getRemoved();
|
||||
for (ToggleButton toggleButton : list) {
|
||||
if (toggleButton.getProperties().get("OWNER") instanceof TitledPane pane) {
|
||||
// 存在所属组
|
||||
if (pane.getContent() instanceof VBox box) {
|
||||
box.getChildren().remove(toggleButton);
|
||||
if (box.getChildren().isEmpty()) {
|
||||
// 该组已没有列表项
|
||||
children.remove(box);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 单独项
|
||||
children.remove(toggleButton);
|
||||
}
|
||||
// 从组移除
|
||||
group.getToggles().remove(toggleButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航按钮
|
||||
*
|
||||
* @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 (ToggleButton button : buttons) {
|
||||
button.getProperties().put("OWNER", pane);
|
||||
items.add(button);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import java.util.List;
|
||||
/**
|
||||
* 分页组件,支持省略页,滚动页,如 [<][1]..[5][6][7][8][9]..[20][>]
|
||||
* <pre>
|
||||
* XPagination pagination = new XPagination();
|
||||
* Pagination pagination = new Pagination();
|
||||
* pagination.setSize(10); // 单页数据量
|
||||
* pagination.setLength(200); // 总数据量
|
||||
* pagination.indexProperty((obs, o, newIndex) -> {
|
||||
@@ -36,7 +36,7 @@ import java.util.List;
|
||||
* @author 夜雨
|
||||
* @since 2021-12-22 15:25
|
||||
*/
|
||||
public class XPagination extends HBox implements TimiFXUI {
|
||||
public class Pagination extends HBox implements TimiFXUI {
|
||||
|
||||
private static final String STYLE_CLASS = "x-pagination";
|
||||
|
||||
@@ -58,7 +58,7 @@ public class XPagination extends HBox implements TimiFXUI {
|
||||
private final IntegerProperty cp; // chunkProperty 页面数量 [1, N]
|
||||
|
||||
/** 默认构造 */
|
||||
public XPagination() {
|
||||
public Pagination() {
|
||||
// 基本参数
|
||||
lp = new SimpleLongProperty();
|
||||
ip = new SimpleIntegerProperty();
|
||||
@@ -329,7 +329,7 @@ public class XPagination extends HBox implements TimiFXUI {
|
||||
// 选中更新激活下标
|
||||
selectedProperty().addListener((obs, o, isSelected) -> {
|
||||
if (isSelected) {
|
||||
XPagination.this.ip.set(indexProperty.get());
|
||||
Pagination.this.ip.set(indexProperty.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,17 +4,15 @@ import com.imyeyu.fx.icon.TimiFXIcon;
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import javafx.animation.TranslateTransition;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.skin.TabPaneSkin;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,17 +24,7 @@ import java.util.List;
|
||||
* @since 2022-07-24 10:54
|
||||
*/
|
||||
@Getter
|
||||
public class XTabPane extends TabPane implements TimiFXUI, TimiFXUI.Colorful {
|
||||
|
||||
/** 添加按钮 */
|
||||
protected final IconButton add;
|
||||
|
||||
/** 默认构造 */
|
||||
public XTabPane() {
|
||||
add = new IconButton(TimiFXIcon.fromName("PLUS")).withBackground();
|
||||
add.getStyleClass().add(CSS.BORDER_RB);
|
||||
add.setPrefWidth(20);
|
||||
}
|
||||
public class TabPane extends javafx.scene.control.TabPane implements TimiFXUI, TimiFXUI.Colorful {
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
@@ -45,24 +33,7 @@ public class XTabPane extends TabPane implements TimiFXUI, TimiFXUI.Colorful {
|
||||
try {
|
||||
StackPane tabHeaderArea = Ref.getFieldValue(tabPaneSkin, "tabHeaderArea", StackPane.class);
|
||||
StackPane headersRegion = Ref.getFieldValue(tabHeaderArea, "headersRegion", StackPane.class);
|
||||
StackPane headersBackground = Ref.getFieldValue(tabHeaderArea, "headerBackground", StackPane.class);
|
||||
|
||||
// 添加按钮
|
||||
TranslateTransition transition = new TranslateTransition();
|
||||
transition.setNode(add);
|
||||
transition.setDuration(Duration.millis(150));
|
||||
headersRegion.widthProperty().addListener((obs, oldWidth, newWidth) -> {
|
||||
if (oldWidth.doubleValue() < newWidth.doubleValue()) {
|
||||
transition.setFromX(add.getTranslateX());
|
||||
transition.setToX(newWidth.intValue());
|
||||
transition.play();
|
||||
} else {
|
||||
add.setTranslateX(newWidth.intValue());
|
||||
}
|
||||
});
|
||||
add.prefHeightProperty().bind(headersBackground.heightProperty());
|
||||
StackPane.setAlignment(add, Pos.CENTER_LEFT);
|
||||
headersBackground.getChildren().add(add);
|
||||
Rectangle headerClip = Ref.getFieldValue(tabHeaderArea, "headerClip", Rectangle.class);
|
||||
|
||||
// 关闭按钮调整
|
||||
CallbackArg<Node> resizeCloseButton = tabHeaderSkin -> {
|
||||
@@ -82,7 +53,7 @@ public class XTabPane extends TabPane implements TimiFXUI, TimiFXUI.Colorful {
|
||||
closeBtn.getChildren().add(icon);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
ObservableList<Node> tabList = headersRegion.getChildren();
|
||||
@@ -92,8 +63,8 @@ public class XTabPane extends TabPane implements TimiFXUI, TimiFXUI.Colorful {
|
||||
headersRegion.getChildren().addListener((ListChangeListener<Node>) c -> {
|
||||
if (c.next()) {
|
||||
List<? extends Node> list = c.getAddedSubList();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
resizeCloseButton.handler(list.get(i));
|
||||
for (Node node : list) {
|
||||
resizeCloseButton.handler(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,334 +0,0 @@
|
||||
package com.imyeyu.fx.ui.components;
|
||||
|
||||
import com.imyeyu.fx.icon.TimiFXIcon;
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 文本流组件,支持插入超链文本,解析富文本:{@link #matcher(String)}
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-09-05 10:41
|
||||
*/
|
||||
public class TextFlower extends TextFlow implements TimiFXUI {
|
||||
|
||||
/** 默认构造器 */
|
||||
public TextFlower() {
|
||||
getStyleClass().add(CSS.MINECRAFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本开始,添加一个制表符
|
||||
*
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower textStart() {
|
||||
getChildren().add(new Text("\t"));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加文本,左侧补充一个制表符,通常是段落开始
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower textStart(String text) {
|
||||
getChildren().add(new Text("\t" + text));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加文本
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower text(String text) {
|
||||
getChildren().add(new Text(text));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加文本,左侧补充空格
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower textLSP(String text) {
|
||||
getChildren().add(new Text(" " + text));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加文本,右侧补充空格
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower textRSP(String text) {
|
||||
getChildren().add(new Text(text + " "));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加链接文本
|
||||
*
|
||||
* @param text 显示文本
|
||||
* @param link 访问链接
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower link(String text, String link) {
|
||||
getChildren().add(new XHyperlink(text, link));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加链接文本
|
||||
*
|
||||
* @param icon 图标
|
||||
* @param text 显示文本
|
||||
* @param link 访问链接
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower link(Node icon, String text, String link) {
|
||||
getChildren().add(new XHyperlink(icon, text, link));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加链接,显示文本为链接
|
||||
*
|
||||
* @param value 内容
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower syncLink(String value) {
|
||||
getChildren().add(new XHyperlink(value, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最后一个文本节点为链接
|
||||
*
|
||||
* @param link 链接
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower asLink(String link) {
|
||||
Node text = getChildren().removeLast();
|
||||
if (text instanceof Text t) {
|
||||
getChildren().add(new XHyperlink(t.getText(), link));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 富文本匹配解析字符串,转义使用 '\'
|
||||
* <br>
|
||||
* <p>格式标准:
|
||||
* <ul>
|
||||
* <li>超链:[可选文本,连接]</li>
|
||||
* <li>图标:<可选颜色, timi-fx-icon 图标名称></li>
|
||||
* <li>重点:`[可选样式,内容]`</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 字符串
|
||||
* @return 本实例
|
||||
*/
|
||||
public TextFlower matcher(String value) {
|
||||
if (TimiJava.isNotEmpty(value)) {
|
||||
getChildren().addAll(RichMatcher.parse(value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 富文本匹配
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-10-10 11:23
|
||||
*/
|
||||
private static class RichMatcher {
|
||||
|
||||
/**
|
||||
* 匹配正则
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-10-10 11:58
|
||||
*/
|
||||
private enum Regex {
|
||||
|
||||
LINK("\\[(.*?)]"),
|
||||
|
||||
ICON("<(.*?)>"),
|
||||
|
||||
SPAN("`(.*?)`");
|
||||
|
||||
final Pattern pattern;
|
||||
|
||||
Regex(String regex) {
|
||||
this.pattern = Pattern.compile(regex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重点内容样式
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-10-10 11:58
|
||||
*/
|
||||
private enum Style {
|
||||
|
||||
UNDERLINE("u", "underline");
|
||||
|
||||
private final String[] matches;
|
||||
|
||||
Style(String... matches) {
|
||||
this.matches = matches;
|
||||
}
|
||||
|
||||
static Style fromMatcher(String matcher) {
|
||||
Style[] values = values();
|
||||
for (Style value : values) {
|
||||
String[] matches = value.matches;
|
||||
for (String match : matches) {
|
||||
if (match.equalsIgnoreCase(matcher)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析富文本匹配
|
||||
*
|
||||
* @param data 文本内容
|
||||
* @return 匹配节点列表
|
||||
*/
|
||||
static List<Node> parse(String data) {
|
||||
List<Node> result = new ArrayList<>();
|
||||
|
||||
Regex[] regexes = Regex.values();
|
||||
|
||||
Map<String, Node> nodeMap = new HashMap<>();
|
||||
|
||||
int[] insertI = {0};
|
||||
String value = data;
|
||||
Matcher matcher;
|
||||
for (int i = 0; i < regexes.length; i++) {
|
||||
final int j = i;
|
||||
matcher = regexes[i].pattern.matcher(value);
|
||||
|
||||
value = matcher.replaceAll(matchResult -> {
|
||||
if (matchResult.start() - 1 != -1) {
|
||||
if (data.charAt(matchResult.start() - 1) == '\\') {
|
||||
return matchResult.group();
|
||||
}
|
||||
}
|
||||
nodeMap.put(String.valueOf(insertI[0]), node(regexes[j], value(regexes[j], matchResult.group())));
|
||||
return "[" + insertI[0]++ + "]";
|
||||
});
|
||||
}
|
||||
|
||||
// 二次解析
|
||||
matcher = Pattern.compile("\\[(.*?)]").matcher(value);
|
||||
int end = 0;
|
||||
while (matcher.find()) {
|
||||
if (matcher.start() - 1 != -1) {
|
||||
if (data.charAt(matcher.start() - 1) == '\\') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (end != matcher.start()) {
|
||||
result.add(new Text(value.substring(end, matcher.start())));
|
||||
}
|
||||
result.add(nodeMap.get(value(Regex.LINK, matcher.group())));
|
||||
end = matcher.end();
|
||||
}
|
||||
result.add(new Text(value.substring(end)));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析正则匹配值
|
||||
*
|
||||
* @param regex 匹配正则
|
||||
* @param matcherResult 匹配结果
|
||||
* @return 匹配值
|
||||
*/
|
||||
private static String value(Regex regex, String matcherResult) {
|
||||
return switch (regex) {
|
||||
case LINK, ICON, SPAN -> matcherResult.substring(1, matcherResult.length() - 1);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析节点
|
||||
*
|
||||
* @param regex 匹配正则
|
||||
* @param value 匹配值
|
||||
* @return 节点
|
||||
*/
|
||||
private static Node node(Regex regex, String value) {
|
||||
return switch (regex) {
|
||||
// 超链
|
||||
case LINK -> {
|
||||
int sp = value.indexOf(",");
|
||||
if (sp == -1) {
|
||||
yield new XHyperlink(value.trim());
|
||||
} else {
|
||||
while (value.charAt(sp - 1) == '\\') {
|
||||
sp = value.indexOf(",", sp + 1);
|
||||
}
|
||||
yield new XHyperlink(value.substring(0, sp).trim(), value.substring(sp + 1).trim());
|
||||
}
|
||||
}
|
||||
// 图标
|
||||
case ICON -> {
|
||||
Text icon;
|
||||
int sp = value.lastIndexOf(",");
|
||||
if (sp == -1) {
|
||||
icon = TimiFXIcon.fromName(value);
|
||||
} else {
|
||||
icon = TimiFXIcon.fromName(value.substring(sp + 1).trim());
|
||||
icon.setFill(Paint.valueOf(value.substring(0, sp).trim()));
|
||||
}
|
||||
yield icon;
|
||||
}
|
||||
// 重点内容
|
||||
case SPAN -> {
|
||||
int sp = value.indexOf(",");
|
||||
if (sp == -1) {
|
||||
yield new Text(value);
|
||||
} else {
|
||||
Text text = new Text(value.substring(sp + 1).trim());
|
||||
String[] styles = value.substring(0, sp).trim().split(" ");
|
||||
for (String s : styles) {
|
||||
Style style = Style.fromMatcher(s);
|
||||
if (style == null) {
|
||||
text.setFill(Paint.valueOf(s));
|
||||
} else {
|
||||
if (style == Style.UNDERLINE) {
|
||||
text.setUnderline(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
yield text;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.imyeyu.fx.ui.components;
|
||||
import com.imyeyu.fx.utils.SmoothScroll;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
|
||||
/**
|
||||
* 不显示根节点的树形结构,实现多个根节点
|
||||
@@ -11,12 +10,12 @@ import javafx.scene.control.TreeView;
|
||||
* @author 夜雨
|
||||
* @since 2021-04-26 01:34
|
||||
*/
|
||||
public class XTreeView<T> extends TreeView<T> {
|
||||
public class TreeView<T> extends javafx.scene.control.TreeView<T> {
|
||||
|
||||
private final TreeItem<T> dummyRoot = new TreeItem<>();
|
||||
|
||||
/** 默认构造 */
|
||||
public XTreeView() {
|
||||
public TreeView() {
|
||||
dummyRoot.setExpanded(true);
|
||||
setRoot(dummyRoot);
|
||||
setShowRoot(false);
|
||||
@@ -0,0 +1,570 @@
|
||||
package com.imyeyu.fx.ui.components.navigation;
|
||||
|
||||
import com.imyeyu.fx.TimiFX;
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.utils.SmoothScroll;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
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.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 纵向导航组件,可实现多级折叠导航
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-06-16
|
||||
*/
|
||||
public class Navigation extends ScrollPane implements TimiFXUI {
|
||||
|
||||
/** 默认缩进像素 */
|
||||
static final double DEFAULT_INDENT_SIZE = 16;
|
||||
|
||||
/** 导航项原始标题属性 */
|
||||
private static final String RAW_ITEM_TEXT = "RAW_ITEM_TEXT";
|
||||
|
||||
/** 导航项原始图标属性 */
|
||||
private static final String RAW_ITEM_GRAPHIC = "RAW_ITEM_GRAPHIC";
|
||||
|
||||
/** 导航项图标同步锁属性 */
|
||||
private static final String GRAPHIC_SYNCING = "GRAPHIC_SYNCING";
|
||||
|
||||
/** 导航项是否已初始化 */
|
||||
private static final String INITIALIZED = "INITIALIZED";
|
||||
|
||||
/** 导航数据列表 */
|
||||
@Getter
|
||||
protected final ObservableList<Node> items;
|
||||
|
||||
/** 已选中监听 */
|
||||
protected final ObjectProperty<ToggleButton> selectedItem;
|
||||
|
||||
/** 缩进像素 */
|
||||
private final DoubleProperty indentSize;
|
||||
|
||||
/** 根容器 */
|
||||
private final VBox contentBox;
|
||||
|
||||
/** 选择组 */
|
||||
private final ToggleGroup toggleGroup;
|
||||
|
||||
/**
|
||||
* 默认构造器
|
||||
*/
|
||||
public Navigation() {
|
||||
items = FXCollections.observableArrayList();
|
||||
selectedItem = new SimpleObjectProperty<>();
|
||||
indentSize = new SimpleDoubleProperty(DEFAULT_INDENT_SIZE);
|
||||
contentBox = new VBox();
|
||||
toggleGroup = new ToggleGroup();
|
||||
|
||||
initView();
|
||||
initSelection();
|
||||
initPropertyListener();
|
||||
initItemsListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图
|
||||
*/
|
||||
private void initView() {
|
||||
contentBox.setBorder(Stroke.BOTTOM);
|
||||
|
||||
getStyleClass().addAll("navigation", "sp-border");
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
setVbarPolicy(ScrollBarPolicy.NEVER);
|
||||
setFitToWidth(true);
|
||||
setContent(contentBox);
|
||||
|
||||
SmoothScroll.scrollPaneV(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化选中同步
|
||||
*/
|
||||
private void initSelection() {
|
||||
selectedItem.addListener((obs, o, newSelectedItem) -> {
|
||||
if (newSelectedItem == null) {
|
||||
toggleGroup.selectToggle(null);
|
||||
return;
|
||||
}
|
||||
if (toggleGroup.getToggles().contains(newSelectedItem)) {
|
||||
toggleGroup.selectToggle(newSelectedItem);
|
||||
}
|
||||
});
|
||||
toggleGroup.selectedToggleProperty().addListener((obs, o, toggle) -> {
|
||||
if (toggle instanceof ToggleButton button) {
|
||||
selectedItem.set(button);
|
||||
return;
|
||||
}
|
||||
selectedItem.set(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化属性监听
|
||||
*/
|
||||
private void initPropertyListener() {
|
||||
indentSize.addListener((obs, o, n) -> rebuild());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化导航数据监听
|
||||
*/
|
||||
private void initItemsListener() {
|
||||
items.addListener(this::handleItemsChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理导航数据变更
|
||||
*
|
||||
* @param change 变更内容
|
||||
*/
|
||||
private void handleItemsChanged(ListChangeListener.Change<? extends Node> change) {
|
||||
while (change.next()) {
|
||||
if (change.wasAdded()) {
|
||||
for (Node node : change.getAddedSubList()) {
|
||||
bindGroup(node);
|
||||
}
|
||||
}
|
||||
if (change.wasRemoved()) {
|
||||
for (Node node : change.getRemoved()) {
|
||||
unbindGroup(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定导航组刷新回调
|
||||
*
|
||||
* @param node 子节点
|
||||
*/
|
||||
private void bindGroup(Node node) {
|
||||
if (node instanceof NavigationGroup group) {
|
||||
group.setStructureChangedAction(this::rebuild);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑导航组刷新回调
|
||||
*
|
||||
* @param node 子节点
|
||||
*/
|
||||
private void unbindGroup(Node node) {
|
||||
if (node instanceof NavigationGroup group) {
|
||||
group.setStructureChangedAction(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建导航结构
|
||||
*/
|
||||
private void rebuild() {
|
||||
contentBox.getChildren().clear();
|
||||
toggleGroup.getToggles().clear();
|
||||
|
||||
for (Node node : items) {
|
||||
Node attachedNode = buildNode(node, 0);
|
||||
if (attachedNode != null) {
|
||||
contentBox.getChildren().add(attachedNode);
|
||||
}
|
||||
}
|
||||
|
||||
refreshStandaloneItemStyle();
|
||||
if (selectedItem.get() != null && !toggleGroup.getToggles().contains(selectedItem.get())) {
|
||||
selectedItem.set(null);
|
||||
}
|
||||
if (selectedItem.get() != null) {
|
||||
toggleGroup.selectToggle(selectedItem.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建节点
|
||||
*
|
||||
* @param node 节点
|
||||
* @param level 层级
|
||||
* @return 可挂载节点
|
||||
*/
|
||||
private Node buildNode(Node node, int level) {
|
||||
if (node instanceof NavigationGroup group) {
|
||||
return buildGroup(group, level);
|
||||
}
|
||||
if (node instanceof ToggleButton button) {
|
||||
return buildItem(button, level);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建导航组
|
||||
*
|
||||
* @param group 导航组
|
||||
* @param level 层级
|
||||
* @return 导航组节点
|
||||
*/
|
||||
private NavigationGroup buildGroup(NavigationGroup group, int level) {
|
||||
int currentLevel = level + group.getLevelOffset();
|
||||
group.applyLevel(currentLevel, getIndentSize());
|
||||
if (!group.getStyleClass().contains("group-pane")) {
|
||||
group.getStyleClass().add("group-pane");
|
||||
}
|
||||
|
||||
VBox groupContent = group.getContentBox();
|
||||
groupContent.getChildren().clear();
|
||||
for (Node child : group.getItems()) {
|
||||
Node childNode = buildNode(child, currentLevel + 1);
|
||||
if (childNode != null) {
|
||||
groupContent.getChildren().add(childNode);
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建导航项
|
||||
*
|
||||
* @param button 导航项
|
||||
* @param level 层级
|
||||
* @return 导航项节点
|
||||
*/
|
||||
private ToggleButton buildItem(ToggleButton button, int level) {
|
||||
configureButton(button, level);
|
||||
toggleGroup.getToggles().add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新独立导航项样式
|
||||
*/
|
||||
private void refreshStandaloneItemStyle() {
|
||||
List<Node> children = contentBox.getChildren();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
Node node = children.get(i);
|
||||
if (node instanceof ToggleButton button) {
|
||||
button.getStyleClass().remove("after-group");
|
||||
if (0 < i && children.get(i - 1) instanceof TitledPane) {
|
||||
button.getStyleClass().add("after-group");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置导航项
|
||||
*
|
||||
* @param button 导航项
|
||||
* @param level 层级
|
||||
*/
|
||||
private void configureButton(ToggleButton button, int level) {
|
||||
cacheItemState(button);
|
||||
button.setText(resolveItemText(button));
|
||||
button.setMaxWidth(Double.MAX_VALUE);
|
||||
button.setAlignment(Pos.CENTER_LEFT);
|
||||
applyButtonIndent(button, Math.max(0, level) * getIndentSize());
|
||||
button.getStyleClass().setAll(CSS.MINECRAFT, "navigation-button");
|
||||
|
||||
if (!Boolean.TRUE.equals(button.getProperties().get(INITIALIZED))) {
|
||||
button.addEventFilter(MouseEvent.MOUSE_PRESSED, TimiFX.EVENT_CONSUME_TG_BTN);
|
||||
button.graphicProperty().addListener((obs, o, n) -> {
|
||||
if (!Boolean.TRUE.equals(button.getProperties().get(GRAPHIC_SYNCING))) {
|
||||
button.getProperties().put(RAW_ITEM_GRAPHIC, n);
|
||||
}
|
||||
});
|
||||
button.getProperties().put(INITIALIZED, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存导航项原始状态
|
||||
*
|
||||
* @param button 导航项
|
||||
*/
|
||||
private void cacheItemState(ToggleButton button) {
|
||||
if (!(button.getProperties().get(RAW_ITEM_TEXT) instanceof String)) {
|
||||
button.getProperties().put(RAW_ITEM_TEXT, button.getText());
|
||||
}
|
||||
if (!button.getProperties().containsKey(RAW_ITEM_GRAPHIC)) {
|
||||
button.getProperties().put(RAW_ITEM_GRAPHIC, button.getGraphic());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导航项原始标题
|
||||
*
|
||||
* @param button 导航项
|
||||
* @return 原始标题
|
||||
*/
|
||||
private String resolveItemText(ToggleButton button) {
|
||||
cacheItemState(button);
|
||||
return (String) button.getProperties().get(RAW_ITEM_TEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用导航项缩进
|
||||
*
|
||||
* @param button 导航项
|
||||
* @param indentWidth 缩进宽度
|
||||
*/
|
||||
private void applyButtonIndent(ToggleButton button, double indentWidth) {
|
||||
Node rawGraphic = (Node) button.getProperties().get(RAW_ITEM_GRAPHIC);
|
||||
if (0 == indentWidth) {
|
||||
button.getProperties().put(GRAPHIC_SYNCING, true);
|
||||
button.setGraphic(rawGraphic);
|
||||
button.getProperties().put(GRAPHIC_SYNCING, false);
|
||||
return;
|
||||
}
|
||||
|
||||
Region spacer = new Region();
|
||||
spacer.setMinWidth(indentWidth);
|
||||
spacer.setPrefWidth(indentWidth);
|
||||
spacer.setMaxWidth(indentWidth);
|
||||
|
||||
HBox graphicBox = new HBox();
|
||||
graphicBox.getChildren().add(spacer);
|
||||
if (rawGraphic != null) {
|
||||
graphicBox.getChildren().add(rawGraphic);
|
||||
}
|
||||
|
||||
button.getProperties().put(GRAPHIC_SYNCING, true);
|
||||
button.setGraphic(graphicBox);
|
||||
button.getProperties().put(GRAPHIC_SYNCING, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航按钮
|
||||
*
|
||||
* @param buttons 导航按钮
|
||||
*/
|
||||
public void add(ToggleButton... buttons) {
|
||||
getItems().addAll(buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航组
|
||||
*
|
||||
* @param groups 导航组
|
||||
*/
|
||||
public void add(NavigationGroup... groups) {
|
||||
getItems().addAll(groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航节点
|
||||
*
|
||||
* @param nodes 导航节点
|
||||
*/
|
||||
public void add(Node... nodes) {
|
||||
getItems().addAll(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加默认没有展开的导航组
|
||||
*
|
||||
* @param title 标题
|
||||
* @param buttons 导航项
|
||||
* @return 构造的导航组
|
||||
*/
|
||||
public NavigationGroup addGroup(String title, ToggleButton... buttons) {
|
||||
return addGroup(title, 0, false, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加默认展开的导航组
|
||||
*
|
||||
* @param title 标题
|
||||
* @param buttons 导航项
|
||||
* @return 构造的导航组
|
||||
*/
|
||||
public NavigationGroup addExpandedGroup(String title, ToggleButton... buttons) {
|
||||
return addGroup(title, 0, true, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加默认没有展开的导航组
|
||||
*
|
||||
* @param title 标题
|
||||
* @param level 缩进偏移量
|
||||
* @param buttons 导航项
|
||||
* @return 构造的导航组
|
||||
*/
|
||||
public NavigationGroup addGroup(String title, int level, ToggleButton... buttons) {
|
||||
return addGroup(title, level, false, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加默认展开的导航组
|
||||
*
|
||||
* @param title 标题
|
||||
* @param level 缩进偏移量
|
||||
* @param buttons 导航项
|
||||
* @return 构造的导航组
|
||||
*/
|
||||
public NavigationGroup addExpandedGroup(String title, int level, ToggleButton... buttons) {
|
||||
return addGroup(title, level, true, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航组
|
||||
*
|
||||
* @param title 标题
|
||||
* @param level 缩进偏移量
|
||||
* @param isExpanded true 为默认展开
|
||||
* @param buttons 导航项
|
||||
* @return 构造的导航组
|
||||
*/
|
||||
public NavigationGroup addGroup(String title, int level, boolean isExpanded, ToggleButton... buttons) {
|
||||
NavigationGroup group = new NavigationGroup(title).levelOffset(level).expanded(isExpanded);
|
||||
group.add(buttons);
|
||||
add(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航组
|
||||
*
|
||||
* @param group 导航组
|
||||
* @return 原导航组
|
||||
*/
|
||||
public NavigationGroup addGroup(NavigationGroup group) {
|
||||
add(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航组
|
||||
*
|
||||
* @param group 导航组
|
||||
* @param isExpanded true 为默认展开
|
||||
* @param buttons 导航项
|
||||
* @return 原导航组
|
||||
*/
|
||||
public NavigationGroup addGroup(NavigationGroup group, boolean isExpanded, ToggleButton... buttons) {
|
||||
return addGroup(group, group.getLevelOffset(), isExpanded, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航组
|
||||
*
|
||||
* @param group 导航组
|
||||
* @param level 缩进偏移量
|
||||
* @param isExpanded true 为默认展开
|
||||
* @param buttons 导航项
|
||||
* @return 原导航组
|
||||
*/
|
||||
public NavigationGroup addGroup(NavigationGroup group, int level, boolean isExpanded, ToggleButton... buttons) {
|
||||
group.levelOffset(level).expanded(isExpanded).add(buttons);
|
||||
add(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该按钮所属组
|
||||
*
|
||||
* @param button 按钮
|
||||
* @return 所属组,null 时为不属于任何组
|
||||
*/
|
||||
public NavigationGroup getGroup(ToggleButton button) {
|
||||
for (Node node : items) {
|
||||
NavigationGroup group = findGroup(node, button);
|
||||
if (group != null) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缩进像素
|
||||
*
|
||||
* @param indentSize 缩进像素
|
||||
*/
|
||||
public void setIndentSize(double indentSize) {
|
||||
this.indentSize.set(Math.max(0, indentSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缩进像素
|
||||
*
|
||||
* @return 缩进像素
|
||||
*/
|
||||
public double getIndentSize() {
|
||||
return indentSize.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缩进像素属性
|
||||
*
|
||||
* @return 缩进像素属性
|
||||
*/
|
||||
public DoubleProperty indentSize() {
|
||||
return indentSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找按钮所属导航组
|
||||
*
|
||||
* @param node 当前节点
|
||||
* @param button 目标按钮
|
||||
* @return 所属导航组
|
||||
*/
|
||||
private NavigationGroup findGroup(Node node, ToggleButton button) {
|
||||
if (!(node instanceof NavigationGroup group)) {
|
||||
return null;
|
||||
}
|
||||
for (Node child : group.getItems()) {
|
||||
if (child == button) {
|
||||
return group;
|
||||
}
|
||||
NavigationGroup nestedGroup = findGroup(child, button);
|
||||
if (nestedGroup != null) {
|
||||
return nestedGroup;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package com.imyeyu.fx.ui.components.navigation;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 导航组
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-06-16
|
||||
*/
|
||||
public class NavigationGroup extends TitledPane {
|
||||
|
||||
/** 子节点列表 */
|
||||
@Getter
|
||||
private final ObservableList<Node> items;
|
||||
|
||||
/** 内容容器 */
|
||||
private final VBox contentBox;
|
||||
|
||||
/** 结构变更回调 */
|
||||
private Runnable structureChangedAction;
|
||||
|
||||
/** 原始标题 */
|
||||
private String rawTitle;
|
||||
|
||||
/** 原始图标 */
|
||||
private Node rawGraphic;
|
||||
|
||||
/** 标题同步锁 */
|
||||
private boolean textSyncing;
|
||||
|
||||
/** 图标同步锁 */
|
||||
private boolean graphicSyncing;
|
||||
|
||||
/** 标题缩进偏移量 */
|
||||
private int levelOffset;
|
||||
|
||||
/**
|
||||
* 默认构造器
|
||||
*/
|
||||
public NavigationGroup() {
|
||||
items = FXCollections.observableArrayList();
|
||||
contentBox = new VBox();
|
||||
structureChangedAction = () -> {};
|
||||
rawTitle = "";
|
||||
|
||||
contentBox.setPadding(Insets.EMPTY);
|
||||
setContent(contentBox);
|
||||
getStyleClass().add("group-pane");
|
||||
items.addListener(this::handleItemsChanged);
|
||||
initRawStateListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过标题创建导航组
|
||||
*
|
||||
* @param title 标题
|
||||
*/
|
||||
public NavigationGroup(String title) {
|
||||
this();
|
||||
setText(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航项
|
||||
*
|
||||
* @param buttons 导航项
|
||||
* @return 当前导航组
|
||||
*/
|
||||
public NavigationGroup add(ToggleButton... buttons) {
|
||||
items.addAll(buttons);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加导航组
|
||||
*
|
||||
* @param groups 导航组
|
||||
* @return 当前导航组
|
||||
*/
|
||||
public NavigationGroup add(NavigationGroup... groups) {
|
||||
items.addAll(groups);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子节点
|
||||
*
|
||||
* @param nodes 子节点
|
||||
* @return 当前导航组
|
||||
*/
|
||||
public NavigationGroup add(Node... nodes) {
|
||||
items.addAll(nodes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否默认展开
|
||||
*
|
||||
* @param expanded true 为默认展开
|
||||
* @return 当前导航组
|
||||
*/
|
||||
public NavigationGroup expanded(boolean expanded) {
|
||||
setExpanded(expanded);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题缩进偏移量
|
||||
*
|
||||
* @param levelOffset 偏移量
|
||||
* @return 当前导航组
|
||||
*/
|
||||
public NavigationGroup levelOffset(int levelOffset) {
|
||||
this.levelOffset = Math.max(0, levelOffset);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容容器
|
||||
*
|
||||
* @return 内容容器
|
||||
*/
|
||||
VBox getContentBox() {
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标题缩进偏移量
|
||||
*
|
||||
* @return 标题缩进偏移量
|
||||
*/
|
||||
int getLevelOffset() {
|
||||
return levelOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置结构变更回调
|
||||
*
|
||||
* @param structureChangedAction 回调
|
||||
*/
|
||||
void setStructureChangedAction(Runnable structureChangedAction) {
|
||||
this.structureChangedAction = structureChangedAction == null ? () -> {} : structureChangedAction;
|
||||
for (Node node : items) {
|
||||
if (node instanceof NavigationGroup group) {
|
||||
group.setStructureChangedAction(this::notifyStructureChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用显示层级
|
||||
*
|
||||
* @param level 层级
|
||||
* @param indentSize 缩进像素
|
||||
*/
|
||||
void applyLevel(int level, double indentSize) {
|
||||
textSyncing = true;
|
||||
super.setText(rawTitle);
|
||||
textSyncing = false;
|
||||
applyGraphicIndent(Math.max(0, level) * indentSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理子节点变更
|
||||
*
|
||||
* @param change 变更内容
|
||||
*/
|
||||
private void handleItemsChanged(ListChangeListener.Change<? extends Node> change) {
|
||||
while (change.next()) {
|
||||
if (change.wasAdded()) {
|
||||
for (Node node : change.getAddedSubList()) {
|
||||
if (node instanceof NavigationGroup group) {
|
||||
group.setStructureChangedAction(this::notifyStructureChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (change.wasRemoved()) {
|
||||
for (Node node : change.getRemoved()) {
|
||||
if (node instanceof NavigationGroup group) {
|
||||
group.setStructureChangedAction(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyStructureChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发结构变更通知
|
||||
*/
|
||||
private void notifyStructureChanged() {
|
||||
structureChangedAction.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化原始状态监听
|
||||
*/
|
||||
private void initRawStateListener() {
|
||||
rawTitle = getText() == null ? "" : getText();
|
||||
rawGraphic = getGraphic();
|
||||
|
||||
textProperty().addListener((obs, o, n) -> {
|
||||
if (!textSyncing) {
|
||||
rawTitle = n == null ? "" : n;
|
||||
}
|
||||
});
|
||||
graphicProperty().addListener((obs, o, n) -> {
|
||||
if (!graphicSyncing) {
|
||||
rawGraphic = n;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用标题缩进
|
||||
*
|
||||
* @param indentWidth 缩进宽度
|
||||
*/
|
||||
private void applyGraphicIndent(double indentWidth) {
|
||||
if (0 == indentWidth) {
|
||||
graphicSyncing = true;
|
||||
super.setGraphic(rawGraphic);
|
||||
graphicSyncing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Region spacer = new Region();
|
||||
spacer.setMinWidth(indentWidth);
|
||||
spacer.setPrefWidth(indentWidth);
|
||||
spacer.setMaxWidth(indentWidth);
|
||||
|
||||
HBox graphicBox = new HBox();
|
||||
graphicBox.getChildren().add(spacer);
|
||||
if (rawGraphic != null) {
|
||||
graphicBox.getChildren().add(rawGraphic);
|
||||
}
|
||||
graphicSyncing = true;
|
||||
super.setGraphic(graphicBox);
|
||||
graphicSyncing = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.imyeyu.fx.ui.components.navigation;
|
||||
|
||||
import javafx.scene.control.ToggleButton;
|
||||
|
||||
/**
|
||||
* 导航项
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-06-16
|
||||
*/
|
||||
public class NavigationItem extends ToggleButton {
|
||||
|
||||
/**
|
||||
* 默认构造器
|
||||
*/
|
||||
public NavigationItem() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过标题创建导航项
|
||||
*
|
||||
* @param text 标题
|
||||
*/
|
||||
public NavigationItem(String text) {
|
||||
super(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/** 导航组件 */
|
||||
package com.imyeyu.fx.ui.components.navigation;
|
||||
@@ -630,6 +630,24 @@
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
||||
-fx-padding: 0 .5em 0 0;
|
||||
}
|
||||
.tab-pane > .tab-header-area > .control-buttons-tab {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
.tab-pane > .tab-header-area > .control-buttons-tab > .container {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
.tab-pane > .tab-header-area > .control-buttons-tab > .container > .tab-down-button {
|
||||
-fx-padding: 0 .5em;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
.tab-pane > .tab-header-area > .control-buttons-tab > .container > .tab-down-button > .arrow {
|
||||
-fx-shape: "M301.5,393.5v1h1v1h1v1h1v1h1v1h1v-1h1v-1h1v-1h1v-1h1v-1Z";
|
||||
-fx-min-width: 9;
|
||||
-fx-min-height: 5;
|
||||
-fx-pref-width: 9;
|
||||
-fx-pref-height: 5;
|
||||
}
|
||||
.tab-pane:top > .tab-header-area {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.fx.ui.examples.component;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.XHyperlink;
|
||||
import com.imyeyu.fx.ui.components.Hyperlink;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
@@ -22,10 +22,10 @@ public abstract class AbstractDemoPane extends AbstractPane {
|
||||
protected Label tips;
|
||||
|
||||
/** 文档 */
|
||||
protected XHyperlink document;
|
||||
protected Hyperlink document;
|
||||
|
||||
/** 源码 */
|
||||
protected XHyperlink source;
|
||||
protected Hyperlink source;
|
||||
|
||||
public AbstractDemoPane() {
|
||||
// 标题
|
||||
@@ -37,11 +37,11 @@ public abstract class AbstractDemoPane extends AbstractPane {
|
||||
|
||||
// 文档
|
||||
Label labelDocument = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("document"));
|
||||
document = new XHyperlink();
|
||||
document = new Hyperlink();
|
||||
|
||||
// 源码
|
||||
Label labelSource = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("source"));
|
||||
source = new XHyperlink();
|
||||
source = new Hyperlink();
|
||||
|
||||
// 提示
|
||||
tips = TimiFXUI.label();
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.imyeyu.fx.ui.examples.component.sidebar;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.Navigation;
|
||||
import com.imyeyu.fx.ui.components.navigation.Navigation;
|
||||
import com.imyeyu.fx.ui.components.navigation.NavigationGroup;
|
||||
import com.imyeyu.fx.ui.components.navigation.NavigationItem;
|
||||
import com.imyeyu.fx.ui.examples.service.PageService;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导航
|
||||
@@ -15,6 +19,9 @@ import javafx.scene.control.ToggleButton;
|
||||
@Component
|
||||
public class Sidebar extends Navigation {
|
||||
|
||||
/** 侧边栏项目映射 */
|
||||
private final Map<SidebarItem, Item> itemMap = new EnumMap<>(SidebarItem.class);
|
||||
|
||||
public Sidebar(PageService pageService) {
|
||||
|
||||
Item welcome = new Item(SidebarItem.WELCOME);
|
||||
@@ -31,23 +38,22 @@ public class Sidebar extends Navigation {
|
||||
// 动画
|
||||
Item interpolator = new Item(SidebarItem.INTERPOLATOR);
|
||||
Item smoothScroll = new Item(SidebarItem.SMOOTH_SCROLL);
|
||||
addGroup(TimiFXUI.MULTILINGUAL.text("animation"), interpolator, smoothScroll);
|
||||
add(new NavigationGroup(TimiFXUI.MULTILINGUAL.text("animation")).add(interpolator, smoothScroll));
|
||||
|
||||
// 组件
|
||||
SidebarItem[] components = {SidebarItem.CHECK_BOX_PICKER, SidebarItem.DATE_TIME_PICKER, SidebarItem.EDITABLE_TABLE_CELL, SidebarItem.FILE_TREE_VIEW, SidebarItem.ICON_BUTTON, SidebarItem.ICON_PICKER, SidebarItem.LABEL_PROGRESS_BAR, SidebarItem.NAVIGATION, SidebarItem.NUMBER_FIELD, SidebarItem.PROGRESS_SLIDER, SidebarItem.SELECTABLE_LABEL, SidebarItem.TEXT_AREA_EDITOR, SidebarItem.TITLE_LABEL, SidebarItem.TOGGLE_ICON, SidebarItem.X_PAGINATION, SidebarItem.X_TAB_PANE, SidebarItem.X_TREE_VIEW};
|
||||
SidebarItem[] components = {SidebarItem.DATE_TIME_PICKER, SidebarItem.EDITABLE_TABLE_CELL, SidebarItem.FILE_TREE_VIEW, SidebarItem.ICON_BUTTON, SidebarItem.ICON_PICKER, SidebarItem.LABEL_PROGRESS_BAR, SidebarItem.NAVIGATION, SidebarItem.NUMBER_FIELD, SidebarItem.PROGRESS_SLIDER, SidebarItem.SELECTABLE_LABEL, SidebarItem.TEXT_AREA_EDITOR, SidebarItem.TITLE_LABEL, SidebarItem.TOGGLE_ICON, SidebarItem.X_PAGINATION, SidebarItem.X_TAB_PANE, SidebarItem.X_TREE_VIEW};
|
||||
Item[] componentItems = new Item[components.length];
|
||||
for (int i = 0; i < componentItems.length; i++) {
|
||||
componentItems[i] = new Item(components[i]);
|
||||
}
|
||||
addGroup(TimiFXUI.MULTILINGUAL.text("component"), componentItems);
|
||||
add(new NavigationGroup(TimiFXUI.MULTILINGUAL.text("component")).add(componentItems));
|
||||
|
||||
// 其他
|
||||
Item draggableNode = new Item(SidebarItem.DRAGGABLE_NODE);
|
||||
Item draggableWindow = new Item(SidebarItem.DRAGGABLE_WINDOW);
|
||||
Item screen = new Item(SidebarItem.SCREEN);
|
||||
Item tray = new Item(SidebarItem.TRAY);
|
||||
addGroup(TimiFXUI.MULTILINGUAL.text("other"), draggableNode, draggableWindow, screen, tray);
|
||||
|
||||
add(new NavigationGroup(TimiFXUI.MULTILINGUAL.text("other")).add(draggableNode, draggableWindow, screen, tray));
|
||||
getStyleClass().add(CSS.BORDER_R);
|
||||
setMinWidth(140);
|
||||
|
||||
@@ -67,13 +73,12 @@ public class Sidebar extends Navigation {
|
||||
* @param item 选中项
|
||||
*/
|
||||
public void setSelected(SidebarItem item) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i) instanceof Item it && it.item == item) {
|
||||
setSelectedItem(items.get(i));
|
||||
Item selected = itemMap.get(item);
|
||||
if (selected != null) {
|
||||
setSelectedItem(selected);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new NullPointerException("not found item page for " + item);
|
||||
throw new NullPointerException("not found item page for %s".formatted(item));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,14 +87,20 @@ public class Sidebar extends Navigation {
|
||||
* @author 夜雨
|
||||
* @since 2022-02-22 19:45
|
||||
*/
|
||||
private static class Item extends ToggleButton {
|
||||
private class Item extends NavigationItem {
|
||||
|
||||
/** 列表项对象 */
|
||||
final SidebarItem item;
|
||||
|
||||
/**
|
||||
* 创建侧边栏项目
|
||||
*
|
||||
* @param item 侧边栏项目
|
||||
*/
|
||||
public Item(SidebarItem item) {
|
||||
this.item = item;
|
||||
setText(item.text);
|
||||
itemMap.put(item, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.imyeyu.fx.ui.examples.view.pages.Style;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.Welcome;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.animation.InterpolatorDemo;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.animation.SmoothScrollDemo;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.component.CheckBoxPickerDemo;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.component.DateTimePickerDemo;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.component.EditableTableCellDemo;
|
||||
import com.imyeyu.fx.ui.examples.view.pages.component.FileTreeViewDemo;
|
||||
@@ -82,9 +81,6 @@ public enum SidebarItem {
|
||||
|
||||
// ---------- 组件 ----------
|
||||
|
||||
/** 复选框选择器 */
|
||||
CHECK_BOX_PICKER(CheckBoxPickerDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.check_box_picker")),
|
||||
|
||||
/** 详细时间选择器 */
|
||||
DATE_TIME_PICKER(DateTimePickerDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.date_time_picker")),
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ package com.imyeyu.fx.ui.examples.view.pages;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.TextAreaEditor;
|
||||
import com.imyeyu.fx.ui.components.TextFlower;
|
||||
import com.imyeyu.fx.ui.components.TitleLabel;
|
||||
import com.imyeyu.fx.ui.components.XHyperlink;
|
||||
import com.imyeyu.fx.ui.components.Hyperlink;
|
||||
import com.imyeyu.fx.ui.examples.bean.Config;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
@@ -36,10 +35,9 @@ public class BindingsConfigDemo extends AbstractDemoPane {
|
||||
source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/config/BindingsConfig.java");
|
||||
|
||||
// timi-java 说明
|
||||
TextFlower timijavaTips = new TextFlower().matcher(TimiFXUI.MULTILINGUAL.text("fx.example.binding_config.tips"));
|
||||
XHyperlink timijavaDescription = new XHyperlink("https://www.imyeyu.net/article/public/aid117.html#配置系统");
|
||||
XHyperlink timijavaDocument = new XHyperlink("https://doc.imyeyu.net/timi-java/net/imyeyu/timijava/config/package-summary.html");
|
||||
XHyperlink timijavaSource = new XHyperlink("https://git.imyeyu.net/Timi/timi-java/src/master/src/main/java/net/imyeyu/timijava/config");
|
||||
Hyperlink timijavaDescription = new Hyperlink("https://www.imyeyu.net/article/public/aid117.html#配置系统");
|
||||
Hyperlink timijavaDocument = new Hyperlink("https://doc.imyeyu.net/timi-java/net/imyeyu/timijava/config/package-summary.html");
|
||||
Hyperlink timijavaSource = new Hyperlink("https://git.imyeyu.net/Timi/timi-java/src/master/src/main/java/net/imyeyu/timijava/config");
|
||||
|
||||
// 窗体尺寸
|
||||
stageSize = new Label();
|
||||
@@ -69,7 +67,6 @@ BindingsConfig.cfg(config).bindDoubleProperty(duration, Config.section("Interpol
|
||||
setPadding(new Insets(20));
|
||||
|
||||
int row = 0;
|
||||
add(timijavaTips, 0, row++, 2, 1);
|
||||
addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("description")), timijavaDescription);
|
||||
addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("document")), timijavaDocument);
|
||||
addRow(row, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("source")), timijavaSource);
|
||||
@@ -86,7 +83,6 @@ BindingsConfig.cfg(config).bindDoubleProperty(duration, Config.section("Interpol
|
||||
}});
|
||||
setBottom(new BorderPane() {{
|
||||
setMargin(code, new Insets(12, 0, 0, 0));
|
||||
setTop(new TextFlower().matcher(TimiFXUI.MULTILINGUAL.text("fx.example.binding_config.description")));
|
||||
setCenter(code);
|
||||
}});
|
||||
}});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.Hyperlink;
|
||||
import com.imyeyu.fx.ui.components.TitleLabel;
|
||||
import com.imyeyu.fx.ui.components.XHyperlink;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.fx.utils.SmoothScroll;
|
||||
@@ -97,10 +97,10 @@ public class ExtendDemo extends AbstractDemoPane {
|
||||
final TitleLabel title = new TitleLabel();
|
||||
|
||||
/** 文档 */
|
||||
final XHyperlink document = new XHyperlink();
|
||||
final Hyperlink document = new Hyperlink();
|
||||
|
||||
/** 源码 */
|
||||
final XHyperlink source = new XHyperlink();
|
||||
final Hyperlink source = new Hyperlink();
|
||||
|
||||
public Item() {
|
||||
setPadding(new Insets(0, 0, 32, 0));
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.imyeyu.fx.ui.examples.view.pages;
|
||||
import com.imyeyu.fx.task.RunAsyncScheduled;
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.TextAreaEditor;
|
||||
import com.imyeyu.fx.ui.components.TextFlower;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
@@ -31,22 +30,11 @@ public class RunAsyncDemo extends AbstractDemoPane {
|
||||
Label title = new Label(TimiFXUI.MULTILINGUAL.text("important"));
|
||||
title.setTextFill(Colorful.RED);
|
||||
|
||||
// 提示 0
|
||||
TextFlower tips0 = new TextFlower().textStart();
|
||||
tips0.matcher(TimiFXUI.MULTILINGUAL.text("fx.example.run_async.tips0"));
|
||||
|
||||
// 提示 1
|
||||
TextFlower tips1 = new TextFlower().textStart();
|
||||
tips1.matcher(TimiFXUI.MULTILINGUAL.text("fx.example.run_async.tips1"));
|
||||
|
||||
// 时间示例
|
||||
Label time = new Label();
|
||||
RunAsyncScheduled.call(Duration.seconds(1), Time::now, t -> time.setText(Time.toDateTime(t)));
|
||||
|
||||
// 扩展说明
|
||||
TextFlower description = new TextFlower();
|
||||
description.matcher(TimiFXUI.MULTILINGUAL.text("fx.example.run_async.description"));
|
||||
|
||||
// 代码示例
|
||||
TextAreaEditor code = new TextAreaEditor();
|
||||
code.setEditable(false);
|
||||
@@ -69,7 +57,7 @@ RunAsyncScheduled.call(Duration.seconds(1), () -> {
|
||||
setTop(new VBox() {{
|
||||
setMargin(time, new Insets(20, 0, 0, 0));
|
||||
setSpacing(4);
|
||||
getChildren().addAll(title, tips0, tips1, time, description);
|
||||
getChildren().addAll(title, time);
|
||||
}});
|
||||
setCenter(code);
|
||||
}});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.Hyperlink;
|
||||
import com.imyeyu.fx.ui.components.TitleLabel;
|
||||
import com.imyeyu.fx.ui.components.XHyperlink;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractPane;
|
||||
import com.imyeyu.fx.utils.BgFill;
|
||||
import com.imyeyu.fx.utils.Column;
|
||||
@@ -29,9 +29,9 @@ public class Style extends AbstractPane {
|
||||
public Style() {
|
||||
TitleLabel titleTimiFX = new TitleLabel("TimiFX");
|
||||
Label labelTimiFXDocument = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("document"));
|
||||
XHyperlink timiFXDocument = new XHyperlink("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/TimiFX.html");
|
||||
Hyperlink timiFXDocument = new Hyperlink("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/TimiFX.html");
|
||||
Label labelTimiFXSource = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("source"));
|
||||
XHyperlink timiFXSource = new XHyperlink("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/TimiFX.java");
|
||||
Hyperlink timiFXSource = new Hyperlink("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/TimiFX.java");
|
||||
Label labelTimiFXDescription = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("description"));
|
||||
Label timiFXDescription = new Label(TimiFXUI.MULTILINGUAL.text("fx.example.style.timifx"));
|
||||
|
||||
@@ -75,7 +75,7 @@ public class Style extends AbstractPane {
|
||||
// 样式文件
|
||||
TitleLabel styleFile = new TitleLabel(TimiFXUI.MULTILINGUAL.text("fx.example.style.file"));
|
||||
Label labelFile = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("source"));
|
||||
XHyperlink file = new XHyperlink("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/resources/timifx/style.css");
|
||||
Hyperlink file = new Hyperlink("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/resources/timifx/style.css");
|
||||
|
||||
setCenter(new VBox() {{
|
||||
setSpacing(16);
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.imyeyu.fx.ui.examples.view.pages;
|
||||
import com.imyeyu.fx.TimiFX;
|
||||
import com.imyeyu.fx.ui.MinecraftFont;
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.TextFlower;
|
||||
import com.imyeyu.fx.ui.components.alert.AlertConfirm;
|
||||
import com.imyeyu.fx.ui.components.alert.AlertTips;
|
||||
import com.imyeyu.fx.ui.examples.TimiFXExamples;
|
||||
@@ -52,10 +51,6 @@ public class Welcome extends AbstractPane implements OS.FileSystem {
|
||||
title.setPadding(new Insets(8, 0, 8, 0));
|
||||
MinecraftFont.css(title, MinecraftFont.X32);
|
||||
|
||||
// 文档和源码
|
||||
TextFlower docSource = new TextFlower();
|
||||
docSource.matcher(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.top"));
|
||||
docSource.setTextAlignment(TextAlignment.CENTER);
|
||||
|
||||
// 提示
|
||||
Label tips = new Label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.tips"));
|
||||
@@ -80,15 +75,9 @@ public class Welcome extends AbstractPane implements OS.FileSystem {
|
||||
}
|
||||
});
|
||||
|
||||
// 版权
|
||||
TextFlower license = new TextFlower().matcher(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.license"));
|
||||
license.setTextAlignment(TextAlignment.CENTER);
|
||||
|
||||
// 开发者
|
||||
Label develop = new Label(TimiFXUI.MULTILINGUAL.text("developer.arg"));
|
||||
develop.setAlignment(Pos.CENTER);
|
||||
TextFlower blog = new TextFlower().matcher(TimiFXUI.MULTILINGUAL.text("blog"));
|
||||
blog.setTextAlignment(TextAlignment.CENTER);
|
||||
Label copyright = new Label(TimiFXUI.MULTILINGUAL.text("copyright"));
|
||||
copyright.setAlignment(Pos.CENTER);
|
||||
Label versionTimiFX = new Label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.version.timifx"));
|
||||
@@ -101,15 +90,11 @@ public class Welcome extends AbstractPane implements OS.FileSystem {
|
||||
setSpacing(20);
|
||||
setPadding(new Insets(20));
|
||||
setAlignment(Pos.TOP_CENTER);
|
||||
getChildren().addAll(docSource, tips, new VBox() {{
|
||||
getChildren().addAll(tips, new VBox() {{
|
||||
setPadding(new Insets(40));
|
||||
getChildren().add(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.tips0")));
|
||||
getChildren().add(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.tips1")));
|
||||
getChildren().add(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.tips2")));
|
||||
getChildren().add(new TextFlower() {{
|
||||
setPadding(new Insets(40, 0, 0, 0));
|
||||
matcher(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.tips3"));
|
||||
}});
|
||||
getChildren().add(new GridPane() {{
|
||||
getColumnConstraints().addAll(Column.build(HPos.RIGHT), Column.VALUE);
|
||||
setHgap(8);
|
||||
@@ -131,7 +116,7 @@ public class Welcome extends AbstractPane implements OS.FileSystem {
|
||||
setAlignment(Pos.CENTER);
|
||||
getChildren().addAll(TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("lang")), language);
|
||||
}});
|
||||
getChildren().addAll(versionTimiFX, version, develop, license, blog, copyright);
|
||||
getChildren().addAll(versionTimiFX, version, develop, copyright);
|
||||
}});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages.component;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.CheckBoxPicker;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 多选框选择器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-08-29 15:27
|
||||
*/
|
||||
@Component
|
||||
public class CheckBoxPickerDemo extends AbstractDemoPane {
|
||||
|
||||
public CheckBoxPickerDemo() {
|
||||
title.setText(SidebarItem.CHECK_BOX_PICKER.getText());
|
||||
document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/CheckBoxPicker.html");
|
||||
source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/CheckBoxPicker.java");
|
||||
|
||||
// 选择器
|
||||
CheckBoxPicker<String> picker = new CheckBoxPicker<>();
|
||||
picker.setPrefWidth(390);
|
||||
picker.getSelectedItems().addListener((ListChangeListener<String>) c -> {
|
||||
List<String> items = picker.getSelectedItems();
|
||||
picker.clear();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
picker.appendText(items.get(i));
|
||||
if (i < items.size() - 1) {
|
||||
picker.appendText(", ");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setCenter(new FlowPane() {{
|
||||
setPadding(new Insets(20));
|
||||
getChildren().add(picker);
|
||||
}});
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
picker.getItems().add(TimiFXUI.MULTILINGUAL.text("fx.example.check_box_picker.item"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages.component;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.Hyperlink;
|
||||
import com.imyeyu.fx.ui.components.IconPicker;
|
||||
import com.imyeyu.fx.ui.components.XHyperlink;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
@@ -26,7 +26,7 @@ public class IconPickerDemo extends AbstractDemoPane {
|
||||
source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/IconPicker.java");
|
||||
|
||||
// 图标主页
|
||||
XHyperlink index = new XHyperlink("https://www.imyeyu.net/article/public/aid119.html");
|
||||
Hyperlink index = new Hyperlink("https://www.imyeyu.net/article/public/aid119.html");
|
||||
|
||||
if (getTop() instanceof VBox top) {
|
||||
top.getChildren().add(new TextFlow() {{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages.component;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.Navigation;
|
||||
import com.imyeyu.fx.ui.components.navigation.Navigation;
|
||||
import com.imyeyu.fx.ui.components.navigation.NavigationGroup;
|
||||
import com.imyeyu.fx.ui.components.navigation.NavigationItem;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.fx.utils.Column;
|
||||
@@ -10,7 +12,6 @@ import com.imyeyu.inject.annotation.Component;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
/**
|
||||
@@ -32,40 +33,55 @@ public class NavigationDemo extends AbstractDemoPane {
|
||||
Label labelCommonly = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("commonly"));
|
||||
labelCommonly.setAlignment(Pos.CENTER);
|
||||
Navigation commonly = new Navigation();
|
||||
commonly.add(new ToggleButton("item 0"));
|
||||
commonly.add(new ToggleButton("item 1"));
|
||||
commonly.add(new ToggleButton("item 2"));
|
||||
commonly.setPrefWidth(240);
|
||||
commonly.add(new NavigationItem("item 0"));
|
||||
commonly.add(new NavigationItem("item 1"));
|
||||
commonly.add(new NavigationItem("item 2"));
|
||||
|
||||
// 二级
|
||||
Label labelSecond = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"));
|
||||
labelSecond.setAlignment(Pos.CENTER);
|
||||
Navigation second = new Navigation();
|
||||
second.add(new ToggleButton("item 0"));
|
||||
second.add(new ToggleButton("item 1"));
|
||||
second.add(new ToggleButton("item 2"));
|
||||
second.addGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"), new ToggleButton("item 0"), new ToggleButton("item 1"));
|
||||
second.add(new NavigationItem("item 0"));
|
||||
second.add(new NavigationItem("item 1"));
|
||||
second.add(new NavigationItem("item 2"));
|
||||
second.add(new NavigationGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"))
|
||||
.add(new NavigationItem("item 0"), new NavigationItem("item 1")));
|
||||
|
||||
// 默认展开
|
||||
Label labelExpanded = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.expanded"));
|
||||
labelExpanded.setAlignment(Pos.CENTER);
|
||||
Navigation expanded = new Navigation();
|
||||
expanded.add(new ToggleButton("item 0"));
|
||||
expanded.add(new ToggleButton("item 1"));
|
||||
expanded.add(new ToggleButton("item 2"));
|
||||
expanded.addExpandedGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"), new ToggleButton("item 0"), new ToggleButton("item 1"));
|
||||
expanded.add(new NavigationItem("item 0"));
|
||||
expanded.add(new NavigationItem("item 1"));
|
||||
expanded.add(new NavigationItem("item 2"));
|
||||
expanded.add(new NavigationGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"))
|
||||
.expanded(true)
|
||||
.add(new NavigationItem("item 0"), new NavigationItem("item 1")));
|
||||
|
||||
// 多个二级
|
||||
// 三级
|
||||
Label labelMulti = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.multi"));
|
||||
labelMulti.setAlignment(Pos.CENTER);
|
||||
Navigation multi = new Navigation();
|
||||
multi.add(new ToggleButton("item 0"));
|
||||
multi.add(new ToggleButton("item 1"));
|
||||
multi.add(new ToggleButton("item 2"));
|
||||
multi.addExpandedGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"), new ToggleButton("item 0"), new ToggleButton("item 1"));
|
||||
multi.addExpandedGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"), new ToggleButton("item 0"), new ToggleButton("item 1"));
|
||||
multi.add(new NavigationItem("item 0"));
|
||||
multi.add(new NavigationItem("item 1"));
|
||||
multi.add(new NavigationItem("item 2"));
|
||||
multi.add(
|
||||
new NavigationGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"))
|
||||
.expanded(true)
|
||||
.add(
|
||||
new NavigationItem("item 0"),
|
||||
new NavigationGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.multi"))
|
||||
.expanded(true)
|
||||
.add(new NavigationItem("item 0"), new NavigationItem("item 1"))
|
||||
),
|
||||
new NavigationGroup(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.second"))
|
||||
.expanded(true)
|
||||
.add(new NavigationItem("item 0"), new NavigationItem("item 1"))
|
||||
);
|
||||
|
||||
setCenter(new GridPane() {{
|
||||
Column col = Column.build().width(120);
|
||||
Column col = Column.build().width(220);
|
||||
getColumnConstraints().addAll(col, col, col, col);
|
||||
getRowConstraints().addAll(Row.build(), Row.build().alwaysPriority());
|
||||
setVgap(5);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages.component;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.XPagination;
|
||||
import com.imyeyu.fx.ui.components.Pagination;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.fx.utils.Column;
|
||||
@@ -26,7 +26,7 @@ public class XPaginationDemo extends AbstractDemoPane {
|
||||
document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/XPagination.html");
|
||||
source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/XPagination.java");
|
||||
|
||||
XPagination pagination = new XPagination();
|
||||
Pagination pagination = new Pagination();
|
||||
|
||||
// 总数
|
||||
Label total = new Label();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.imyeyu.fx.ui.examples.view.pages.component;
|
||||
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import com.imyeyu.fx.ui.components.XTabPane;
|
||||
import com.imyeyu.fx.ui.components.TabPane;
|
||||
import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
|
||||
import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
@@ -10,8 +10,6 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 标签面板
|
||||
*
|
||||
@@ -29,17 +27,15 @@ public class XTabPaneDemo extends AbstractDemoPane {
|
||||
|
||||
Button add = new Button(TimiFXUI.MULTILINGUAL.text("add"));
|
||||
|
||||
XTabPane tabPane = new XTabPane();
|
||||
TabPane tabPane = new TabPane();
|
||||
tabPane.setTabClosingPolicy(javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS);
|
||||
|
||||
setCenter(new BorderPane() {{
|
||||
setMargin(add, new Insets(0, 0, 20, 0));
|
||||
setPadding(new Insets(20));
|
||||
setTop(add);
|
||||
setCenter(tabPane);
|
||||
setBottom(tabPane);
|
||||
}});
|
||||
|
||||
add.setOnAction(e -> tabPane.getAdd().fire());
|
||||
tabPane.getAdd().setOnAction(e -> tabPane.getTabs().add(new Tab(UUID.randomUUID().toString().substring(0, 8))));
|
||||
tabPane.getAdd().fire();
|
||||
add.setOnAction(e -> tabPane.getTabs().add(new Tab("test顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user