Initial project
This commit is contained in:
318
src/main/java/com/imyeyu/fx/TimiFX.java
Normal file
318
src/main/java/com/imyeyu/fx/TimiFX.java
Normal file
@ -0,0 +1,318 @@
|
||||
package com.imyeyu.fx;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.PopupWindow;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import com.imyeyu.fx.utils.ScreenFX;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.utils.Calc;
|
||||
import com.imyeyu.utils.OS;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <b>TimiFX</b> - JavaFX 开发工具
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-02-14 10:51
|
||||
*/
|
||||
public final class TimiFX {
|
||||
|
||||
/**
|
||||
* 阻止取消选择
|
||||
* <pre>
|
||||
* addEventFilter(MouseEvent.MOUSE_PRESSED, EVENT_CONSUME_TGBTN);
|
||||
* </pre>
|
||||
*/
|
||||
public static final EventHandler<MouseEvent> EVENT_CONSUME_TG_BTN = e -> {
|
||||
if (e.getSource() instanceof ToggleButton btn && btn.isSelected()) {
|
||||
e.consume();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 十六进制颜色含透明度:0xFF00FF00
|
||||
*
|
||||
* @param color 颜色
|
||||
* @return 十六进制颜色字符串
|
||||
*/
|
||||
public static String toHexString(Color color) {
|
||||
int r = Calc.round(color.getRed() * 255) << 24;
|
||||
int g = Calc.round(color.getGreen() * 255) << 16;
|
||||
int b = Calc.round(color.getBlue() * 255) << 8;
|
||||
int a = Calc.round(color.getOpacity() * 255);
|
||||
return String.format("0x%08X", (r + g + b + a));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析十六进制颜色字符串
|
||||
*
|
||||
* @param hexColorString 十六进制颜色字符串
|
||||
* @return 颜色
|
||||
*/
|
||||
public static Color fromHexString(String hexColorString) {
|
||||
return Color.valueOf(hexColorString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新设置 SVG 大小
|
||||
*
|
||||
* @param svg SVG 路径
|
||||
* @param scale 缩放倍率
|
||||
* @return SVG 路径
|
||||
*/
|
||||
public static String resizeSVG(String svg, double scale) {
|
||||
Pattern compile = Pattern.compile("\\d+\\.?\\d+");
|
||||
Matcher matcher = compile.matcher(svg);
|
||||
return matcher.replaceAll(matchResult -> {
|
||||
double d = Double.parseDouble(matchResult.group()) * scale;
|
||||
if (d % 1 == 0) {
|
||||
return String.valueOf((int) d);
|
||||
} else {
|
||||
return "%.2f".formatted(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置相对居中窗体
|
||||
*
|
||||
* @param owner 依赖窗体
|
||||
* @param window 居中窗体
|
||||
*/
|
||||
public static void relativeCenter(Window owner, Window window) {
|
||||
double x = owner.getX() + owner.getWidth() * .5 - window.getWidth() * .5;
|
||||
double y = owner.getY() + owner.getHeight() * .382 - window.getHeight() * .5;
|
||||
|
||||
double idle = Math.abs(owner.getY() + 30 - y);
|
||||
if (y < owner.getY()) {
|
||||
// 防止跃出相对窗体上部分
|
||||
y += idle;
|
||||
}
|
||||
if (ScreenFX.outOfScreen(x, y)) {
|
||||
// 坐标无效或窗体完全越出屏幕,设置到主屏幕
|
||||
relativeCenter4PrimaryScreen(window);
|
||||
}
|
||||
window.setX(x);
|
||||
window.setY(y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置窗体到主屏幕中间(偏上少许)
|
||||
*
|
||||
* @param stage 窗体
|
||||
*/
|
||||
public static void relativeCenter4PrimaryScreen(Window stage) {
|
||||
relativeCenter4Screen(ScreenFX.primary, stage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置窗体到屏幕中间(偏上少许)
|
||||
*
|
||||
* @param screen 屏幕
|
||||
* @param stage 窗体
|
||||
*/
|
||||
public static void relativeCenter4Screen(Screen screen, Window stage) {
|
||||
if (screen == null) {
|
||||
relativeCenter4PrimaryScreen(stage);
|
||||
} else {
|
||||
Rectangle2D r2d = screen.getBounds();
|
||||
double x = r2d.getMinX() + r2d.getWidth() * .5 - stage.getWidth() * .5;
|
||||
double y = r2d.getMinY() + r2d.getHeight() * .382 - stage.getHeight() * .5;
|
||||
|
||||
double idle = Math.abs(r2d.getMinY() + 30 - y);
|
||||
if (y < r2d.getMinY()) {
|
||||
// 防止跃出屏幕上部分
|
||||
y += idle;
|
||||
}
|
||||
if (Double.isNaN(x) || Double.isNaN(y)) {
|
||||
stage.centerOnScreen();
|
||||
} else {
|
||||
stage.setX(x);
|
||||
stage.setY(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 相对于某窗体居中显示新窗体(需预设宽高)
|
||||
*
|
||||
* @param owner 依赖窗体
|
||||
* @param window 需显示的窗体
|
||||
*/
|
||||
public static void showCenter(Window owner, Window window) {
|
||||
if (!window.isShowing()) {
|
||||
relativeCenter(owner, window);
|
||||
}
|
||||
if (Double.isNaN(window.getX()) || Double.isNaN(window.getY())) {
|
||||
final EventHandler<WindowEvent> onShown = window.getOnShown();
|
||||
window.setOnShown(e -> {
|
||||
if (onShown != null) {
|
||||
onShown.handle(e);
|
||||
}
|
||||
window.sizeToScene();
|
||||
relativeCenter(owner, window);
|
||||
});
|
||||
}
|
||||
if (window instanceof Stage stage) {
|
||||
requestTop(stage);
|
||||
} else if (window instanceof PopupWindow popupWindow) {
|
||||
popupWindow.show(owner);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求焦点,执行显示、置顶并聚焦
|
||||
*
|
||||
* @param stage 窗体
|
||||
*/
|
||||
public static void requestTop(Stage stage) {
|
||||
stage.show();
|
||||
if (stage.isIconified()) {
|
||||
stage.setIconified(false);
|
||||
}
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.requestFocus();
|
||||
stage.setAlwaysOnTop(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据布尔值监听动态切换节点 CSS 类
|
||||
*
|
||||
* @param node 节点
|
||||
* @param booleanProperty 布尔值监听
|
||||
* @param onTrue 为 true 时的 css 类
|
||||
* @param onFalse 为 false 时的 css 类
|
||||
*/
|
||||
public static void toggleStyleClass(Node node, ReadOnlyBooleanProperty booleanProperty, String onTrue, String onFalse) {
|
||||
if (booleanProperty.get()) {
|
||||
node.getStyleClass().remove(onFalse);
|
||||
node.getStyleClass().add(onTrue);
|
||||
}
|
||||
booleanProperty.addListener((obs, o, isTrue) -> {
|
||||
if (isTrue) {
|
||||
node.getStyleClass().remove(onFalse);
|
||||
node.getStyleClass().add(onTrue);
|
||||
} else {
|
||||
node.getStyleClass().remove(onTrue);
|
||||
node.getStyleClass().add(onFalse);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据布尔值监听动态切换节点 CSS 类
|
||||
*
|
||||
* @param node 节点
|
||||
* @param booleanBinding 布尔值监听
|
||||
* @param onTrue 为 true 时的 css 类
|
||||
* @param onFalse 为 false 时的 css 类
|
||||
*/
|
||||
public static void toggleStyleClass4Binding(Node node, BooleanBinding booleanBinding, String onTrue, String onFalse) {
|
||||
if (booleanBinding.get()) {
|
||||
node.getStyleClass().remove(onFalse);
|
||||
node.getStyleClass().add(onTrue);
|
||||
}
|
||||
booleanBinding.addListener((obs, o, isTrue) -> {
|
||||
if (isTrue) {
|
||||
node.getStyleClass().remove(onFalse);
|
||||
node.getStyleClass().add(onTrue);
|
||||
} else {
|
||||
node.getStyleClass().remove(onTrue);
|
||||
node.getStyleClass().add(onFalse);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 指向半透
|
||||
*
|
||||
* @param node 节点
|
||||
*/
|
||||
public static void hoverOpacity(Node node) {
|
||||
hoverOpacity(node, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指向半透
|
||||
*
|
||||
* @param handler 触发节点
|
||||
* @param node 节点
|
||||
*/
|
||||
public static void hoverOpacity(Node handler, Node node) {
|
||||
node.opacityProperty().bind(Bindings.when(handler.hoverProperty()).then(.7).otherwise(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指向即聚焦该节点
|
||||
*
|
||||
* @param node 节点
|
||||
*/
|
||||
public static void hoverFocus(Node node) {
|
||||
node.hoverProperty().addListener((obs, o, isHover) -> {
|
||||
if (isHover) {
|
||||
node.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动指定项至控件可视范围的中央
|
||||
*
|
||||
* @param control 控件({@link javafx.scene.control.ListView}, {@link javafx.scene.control.TableView} 等)
|
||||
* @param index 指定项下标
|
||||
*/
|
||||
public static void scrollToCenter(Control control, int index) {
|
||||
try {
|
||||
if (control.getSkin() == null) {
|
||||
return;
|
||||
}
|
||||
// 偏移
|
||||
VirtualFlow<?> flow = Ref.getFieldValue(control.getSkin(), "flow", VirtualFlow.class);
|
||||
if (flow == null) {
|
||||
throw new UnsupportedOperationException("unsupported this control");
|
||||
}
|
||||
int offset = Calc.floor(flow.getHeight() * .5 / flow.getCell(0).getHeight());
|
||||
// 滚动
|
||||
Method method = Ref.getMethod(control.getClass(), "scrollTo", int.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(control, index - offset);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 重启程序,命令需自定
|
||||
*
|
||||
* @param application FX 程序
|
||||
* @param command 重启命令
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public static void doRestart(Application application, String command) throws Exception {
|
||||
Platform.setImplicitExit(true);
|
||||
OS.runAfterShutdown(command);
|
||||
application.stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user