数据属性类型
+ * @param 绑定类型
+ * @param 组件类型
+ */
+public abstract class BindingTableCell, N extends Node> extends TableCell implements TimiFXUI {
+
+ /** 组件 */
+ protected final N node;
+
+ /** 当前绑定 */
+ protected P nowBind;
+
+ /** 默认构造器 */
+ public BindingTableCell() {
+ node = component();
+ onInit(node);
+ node.focusedProperty().addListener((obs, o, isFocused) -> {
+ if (isFocused) {
+ getTableView().getSelectionModel().clearAndSelect(getIndex());
+ }
+ });
+ setPadding(Insets.EMPTY);
+ }
+
+ /**
+ * 构建组件
+ *
+ * @return 组件
+ */
+ protected abstract N component();
+
+ /**
+ * 组件属性绑定类
+ *
+ * @param node 组件
+ * @return 属性绑定类
+ */
+ protected abstract P componentValue(N node);
+
+ /**
+ * 双向绑定属性,组件对数据的双向绑定属性
+ *
+ * @param s 数据对象
+ * @return 监听属性
+ */
+ protected abstract P property(S s);
+
+ @Override
+ protected void updateItem(T item, boolean empty) {
+ super.updateItem(item, empty);
+ if (empty || item == null || getTableRow() == null || getTableRow().getItem() == null) {
+ setGraphic(null);
+ } else {
+ // 动态绑定
+ P thisBind = property(getTableRow().getItem());
+ if (nowBind == null) {
+ nowBind = thisBind;
+ componentValue(node).bindBidirectional(nowBind);
+ onUpdateBinding(getTableRow().getItem());
+ } else {
+ if (nowBind != thisBind) {
+ componentValue(node).unbindBidirectional(nowBind);
+ nowBind = thisBind;
+ componentValue(node).bindBidirectional(nowBind);
+ onUpdateBinding(getTableRow().getItem());
+ }
+ }
+ setGraphic(node);
+ }
+ }
+
+ /**
+ * 构造完成触发
+ *
+ * @param node 组件
+ */
+ protected void onInit(N node) {
+ // 子类实现
+ }
+
+ /**
+ * 发生更新绑定时触发
+ *
+ * @param s 数据对象
+ */
+ protected void onUpdateBinding(S s) {
+ // 子类实现
+ }
+}
diff --git a/src/main/java/com/imyeyu/fx/ui/components/table/CheckBoxTableCell.java b/src/main/java/com/imyeyu/fx/ui/components/table/CheckBoxTableCell.java
new file mode 100644
index 0000000..42563b0
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/ui/components/table/CheckBoxTableCell.java
@@ -0,0 +1,29 @@
+package com.imyeyu.fx.ui.components.table;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.geometry.Pos;
+import javafx.scene.control.CheckBox;
+
+/**
+ * 复选框单元格
+ *
+ * @author 夜雨
+ * @since 2023-03-14 14:56
+ */
+public abstract class CheckBoxTableCell extends BindingTableCell {
+
+ /** 默认构造器 */
+ public CheckBoxTableCell() {
+ setAlignment(Pos.CENTER);
+ }
+
+ @Override
+ protected final CheckBox component() {
+ return new CheckBox();
+ }
+
+ @Override
+ protected final BooleanProperty componentValue(CheckBox node) {
+ return node.selectedProperty();
+ }
+}
diff --git a/src/main/java/com/imyeyu/fx/ui/components/table/TextFieldTableCell.java b/src/main/java/com/imyeyu/fx/ui/components/table/TextFieldTableCell.java
new file mode 100644
index 0000000..f3c5c3f
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/ui/components/table/TextFieldTableCell.java
@@ -0,0 +1,38 @@
+package com.imyeyu.fx.ui.components.table;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import javafx.beans.property.StringProperty;
+import javafx.scene.control.TextField;
+
+/**
+ * 表格可编辑单元格,相对原始的可编辑效果,此单元格没有输入框的样式,就像 Excel 那样
+ *
+ *
+ * TableColumn<String, String> col = new TableColumn<>("col");
+ * col.setCellFactory(cell -> new TextFieldTableCell<>() {
+ *
+ * @Override
+ * protected StringProperty property(Item item) {
+ * // 给出此可编辑单元格的具体映射属性
+ * return item.valueProperty();
+ * }
+ * });
+ *
+ *
+ * @author 夜雨
+ * @since 2022-05-09 22:21
+ */
+public abstract class TextFieldTableCell extends BindingTableCell implements TimiFXUI {
+
+ @Override
+ protected final TextField component() {
+ TextField textField = new TextField();
+ textField.getStyleClass().add(CSS.BORDER_N);
+ return textField;
+ }
+
+ @Override
+ protected final StringProperty componentValue(TextField textField) {
+ return textField.textProperty();
+ }
+}
diff --git a/src/main/java/com/imyeyu/fx/ui/components/table/package-info.java b/src/main/java/com/imyeyu/fx/ui/components/table/package-info.java
new file mode 100644
index 0000000..e7081f7
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/ui/components/table/package-info.java
@@ -0,0 +1,2 @@
+/** 表格属性绑定单元格 */
+package com.imyeyu.fx.ui.components.table;
\ No newline at end of file
diff --git a/src/main/resources/lang/timi-fx-ui/de_DE.lang b/src/main/resources/lang/timi-fx-ui/de_DE.lang
new file mode 100644
index 0000000..667bb0c
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/de_DE.lang
@@ -0,0 +1,53 @@
+alert.apply=@Anwenden
+alert.cancel=@Abbrechen
+alert.close=@Schließen
+alert.confirm=@Bestätigen
+alert.feedback=Feedback
+alert.finish=@Fertigstellen
+alert.next=Nächster Schritt
+alert.no=@No
+alert.ok=@OK
+alert.previous=Vorheriger Schritt
+alert.save=@Speichern
+alert.skip=@Skip
+alert.title.information=@Info
+alert.yes=@Ja
+apply=Anwendung
+cancel=Abbrechen
+close=schließen
+confirm=bestätigen
+confirmation=Anfrage
+copy=Kopie
+cut=Schere
+delete=löschen
+error=Fehler
+file.destroy=Sind Sie sicher, dass Sie die ausgewählten Elemente löschen?
+file.mkdir=Einen neuen Ordner erstellen
+file.rename=@Umbenennen
+file.select=Datei auswählen
+file.show_hide=Versteckte Dateien anzeigen
+file.tips.destroy_fail=Löschen fehlgeschlagen {0}
+file.tips.not_found_target=Ziel existiert nicht {0}
+file.tips.select_directory=Bitte wählen Sie einen Ordner
+finish=komplett
+home=Startseite
+info=Informationen
+loading=Laden
+name=Name
+no=nein
+now=heute
+now_tick=dieser Moment
+ok=gut
+paste=Paste
+redo=Wiederholen
+refresh=Aktualisieren
+rename=umbenennen
+replace=ersetzen
+replace_all=Alle ersetzen
+save=konservieren
+skip=überspringen
+undo=widerrufen
+version.fail={0}-Überprüfen auf neue Version fehlgeschlagen, klicken Sie auf Wiederholen
+warning=Warnung
+wrap=Zeilenumbruch
+yes=ja
diff --git a/src/main/resources/lang/timi-fx-ui/en_US.lang b/src/main/resources/lang/timi-fx-ui/en_US.lang
new file mode 100644
index 0000000..cf45de9
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/en_US.lang
@@ -0,0 +1,53 @@
+alert.apply=@Apply
+alert.cancel=@Cancel
+alert.close=@Close
+alert.confirm=@Confirm
+alert.feedback=Feedback
+alert.finish=@Finish
+alert.next=Next step
+alert.no=@No
+alert.ok=@OK
+alert.previous=Previous step
+alert.save=@Save
+alert.skip=@Skip
+alert.title.information=@Info
+alert.yes=@Yes
+apply=Apply
+cancel=Cancel
+close=Close
+confirm=Confirm
+confirmation=Inquiry
+copy=Copy
+cut=Cut
+delete=Delete
+error=Error
+file.destroy=Are you sure to delete the selected items?
+file.mkdir=Create a new folder
+file.rename=@Rename
+file.select=Select file
+file.show_hide=Show hidden files
+file.tips.destroy_fail=Delete failed {0}
+file.tips.not_found_target=Target does not exist {0}
+file.tips.select_directory=Please select a folder
+finish=Finish
+home=Home
+info=Info
+loading=Loading
+name=Name
+no=No
+now=Now
+now_tick=This moment
+ok=OK
+paste=Paste
+redo=Redo
+refresh=Refresh
+rename=Rename
+replace=Replace
+replace_all=Replace All
+save=Preserve
+skip=Skip
+undo=Undo
+version.fail={0} - Check for new version failed, click retry
+warning=Warning
+wrap=Wrap
+yes=Yes
diff --git a/src/main/resources/lang/timi-fx-ui/ja_JP.lang b/src/main/resources/lang/timi-fx-ui/ja_JP.lang
new file mode 100644
index 0000000..f6aac69
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/ja_JP.lang
@@ -0,0 +1,53 @@
+alert.apply=@apply
+alert.cancel=@cancel
+alert.close=@close
+alert.confirm=@confirm
+alert.feedback=フィードバック
+alert.finish=@finish
+alert.next=次のステップ
+alert.no=@no
+alert.ok=@ok
+alert.previous=前へ
+alert.save=@save
+alert.skip=@skip
+alert.title.information=@info
+alert.yes=@yes
+apply=適用#テキヨウ#
+cancel=キャンセル
+close=閉じる
+confirm=確認
+confirmation=に質問
+copy=レプリケーション
+cut=せん断
+delete=削除#サクジョ#
+error=エラー
+file.destroy=オプションの削除を確認しますか?
+file.mkdir=新規フォルダ
+file.rename=@rename
+file.select=ファイルを選択
+file.show_hide=隠しファイルを表示
+file.tips.destroy_fail=削除に失敗しました{0}
+file.tips.not_found_target=ターゲットは存在しません{0}
+file.tips.select_directory=フォルダを選択してください
+finish=完了
+home=ホーム・ページ
+info=情報#ジョウホウ#
+loading=ロード中..。
+name=の名前をあげる
+no=いいえ
+now=現在
+now_tick=今は
+ok=よし
+paste=貼り付け
+redo=やり直す
+refresh=リフレッシュ
+rename=名前を変更
+replace=置換
+replace_all=すべて置換
+save=保存#ホゾン#
+skip=スキップ
+undo=元に戻す
+version.fail=-新しいバージョンのチェックに失敗しました。再試行をクリックしてください
+warning=に警告
+wrap=折り返し
+yes=はい
diff --git a/src/main/resources/lang/timi-fx-ui/ko_KR.lang b/src/main/resources/lang/timi-fx-ui/ko_KR.lang
new file mode 100644
index 0000000..fea8e8e
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/ko_KR.lang
@@ -0,0 +1,53 @@
+alert.apply=@apply
+alert.cancel=@cancel
+alert.close=@close
+alert.confirm=@confirm
+alert.feedback=피드백
+alert.finish=@finish
+alert.next=다음 단계
+alert.no=@no
+alert.ok=@ok
+alert.previous=이전 단계
+alert.save=@save
+alert.skip=@skip
+alert.title.information=@info
+alert.yes=@yes
+apply=적용
+cancel=취소
+close=닫기
+confirm=확인
+confirmation=문의
+copy=복제
+cut=잘라내기
+delete=삭제
+error=오류
+file.destroy=선택 항목을 삭제하시겠습니까?
+file.mkdir=새 폴더
+file.rename=@rename
+file.select=파일 선택
+file.show_hide=숨겨진 파일 표시
+file.tips.destroy_fail=제거 실패 {0}
+file.tips.not_found_target=대상이 없습니다. {0}
+file.tips.select_directory=폴더를 선택하십시오.
+finish=완료
+home=홈 페이지
+info=정보
+loading=로드 중...
+name=이름
+no=아니오
+now=지금
+now_tick=지금
+ok=좋아요.
+paste=붙여넣기
+redo=다시 실행
+refresh=새로 고침
+rename=이름 바꾸기
+replace=대체
+replace_all=모두 바꾸기
+save=저장
+skip=건너뛰기
+undo=취소
+version.fail={0} - 새 버전을 검사하는 데 실패했습니다. 다시 시도하려면 클릭하십시오.
+warning=경고
+wrap=줄 바꿈
+yes=예
diff --git a/src/main/resources/lang/timi-fx-ui/ru_RU.lang b/src/main/resources/lang/timi-fx-ui/ru_RU.lang
new file mode 100644
index 0000000..9129dfd
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/ru_RU.lang
@@ -0,0 +1,53 @@
+alert.apply=@ apply
+alert.cancel=@ Cancel
+alert.close=@ close
+alert.confirm=@ Confirm
+alert.feedback=Обратная связь
+alert.finish=@ finish
+alert.next=Следующий шаг
+alert.no=@ no
+alert.ok=@ ОК
+alert.previous=Предыдущий шаг
+alert.save=@ save
+alert.skip=@ skip
+alert.title.information=@ info
+alert.yes=@ Yes
+apply=Применение
+cancel=Отменить
+close=Закрыть
+confirm=Подтверждение
+confirmation=Запрос
+copy=Копирование
+cut=Вырезание
+delete=Удалить
+error=Ошибка
+file.destroy=Подтвердить удаление выбранных опций?
+file.mkdir=Создать папку
+file.rename=@ rename
+file.select=Выбрать файл
+file.show_hide=Показать скрытые файлы
+file.tips.destroy_fail=Ошибка удаления {0}
+file.tips.not_found_target=Цель не существует {0}
+file.tips.select_directory=Выберите папку
+finish=Завершено
+home=Домашняя страница
+info=Информация
+loading=Загрузка...
+name=Имя
+no=Нет
+now=А теперь...
+now_tick=В данный момент
+ok=Ладно.
+paste=Вставить
+redo=Повторить
+refresh=Обновить
+rename=Переименовать
+replace=Замена
+replace_all=Заменить все
+save=Сохранить
+skip=Пропустить
+undo=Отмена
+version.fail={0} - Ошибка проверки новой версии, нажмите для повторного тестирования
+warning=предупреждение
+wrap=Смена строк
+yes=Да.
diff --git a/src/main/resources/lang/timi-fx-ui/zh_CN.lang b/src/main/resources/lang/timi-fx-ui/zh_CN.lang
new file mode 100644
index 0000000..8c05fbb
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/zh_CN.lang
@@ -0,0 +1,53 @@
+alert.apply=@apply
+alert.cancel=@cancel
+alert.close=@close
+alert.confirm=@confirm
+alert.feedback=反馈
+alert.finish=@finish
+alert.next=下一步
+alert.no=@no
+alert.ok=@ok
+alert.previous=上一步
+alert.save=@save
+alert.skip=@skip
+alert.title.information=@info
+alert.yes=@yes
+apply=应用
+cancel=取消
+close=关闭
+confirm=确认
+confirmation=询问
+copy=复制
+cut=剪切
+delete=删除
+error=错误
+file.destroy=是否确认删除已选项?
+file.mkdir=新建文件夹
+file.rename=@rename
+file.select=选择文件
+file.show_hide=显示隐藏文件
+file.tips.destroy_fail=删除失败 {0}
+file.tips.not_found_target=目标不存在 {0}
+file.tips.select_directory=请选择文件夹
+finish=完成
+home=主页
+info=信息
+loading=加载中..
+name=名称
+no=否
+now=现在
+now_tick=此刻
+ok=好
+paste=粘贴
+redo=重做
+refresh=刷新
+rename=重命名
+replace=替换
+replace_all=替换全部
+save=保存
+skip=跳过
+undo=撤销
+version.fail={0} - 检查新版本失败,点击重试
+warning=警告
+wrap=换行
+yes=是
diff --git a/src/main/resources/lang/timi-fx-ui/zh_TW.lang b/src/main/resources/lang/timi-fx-ui/zh_TW.lang
new file mode 100644
index 0000000..126f09a
--- /dev/null
+++ b/src/main/resources/lang/timi-fx-ui/zh_TW.lang
@@ -0,0 +1,53 @@
+alert.apply=@apply
+alert.cancel=@cancel
+alert.close=@close
+alert.confirm=@confirm
+alert.feedback=反饋
+alert.finish=@finish
+alert.next=下一步
+alert.no=@no
+alert.ok=@ok
+alert.previous=上一步
+alert.save=@save
+alert.skip=@skip
+alert.title.information=@info
+alert.yes=@yes
+apply=應用
+cancel=取消
+close=關閉
+confirm=確認
+confirmation=詢問
+copy=複製
+cut=剪切
+delete=删除
+error=錯誤
+file.destroy=是否確認删除已選項?
+file.mkdir=新建資料夾
+file.rename=@rename
+file.select=選擇檔案
+file.show_hide=顯示隱藏文件
+file.tips.destroy_fail=删除失敗{0}
+file.tips.not_found_target=目標不存在{0}
+file.tips.select_directory=請選擇資料夾
+finish=完成
+home=主頁
+info=訊息
+loading=加載中..
+name=名稱
+no=否
+now=現在
+now_tick=此刻
+ok=好
+paste=粘貼
+redo=重做
+refresh=刷新
+rename=重命名
+replace=替換
+replace_all=替換全部
+save=保存
+skip=跳過
+undo=撤銷
+version.fail={0} -檢查新版本失敗,點擊重試
+warning=警告
+wrap=換行
+yes=是
diff --git a/src/main/resources/timifx/MinecraftAE.ttf b/src/main/resources/timifx/MinecraftAE.ttf
new file mode 100644
index 0000000..b3e7e3c
Binary files /dev/null and b/src/main/resources/timifx/MinecraftAE.ttf differ
diff --git a/src/main/resources/timifx/dialog-confirmation16x.png b/src/main/resources/timifx/dialog-confirmation16x.png
new file mode 100644
index 0000000..ecddcda
Binary files /dev/null and b/src/main/resources/timifx/dialog-confirmation16x.png differ
diff --git a/src/main/resources/timifx/dialog-error16x.png b/src/main/resources/timifx/dialog-error16x.png
new file mode 100644
index 0000000..ec8b58d
Binary files /dev/null and b/src/main/resources/timifx/dialog-error16x.png differ
diff --git a/src/main/resources/timifx/dialog-information16x.png b/src/main/resources/timifx/dialog-information16x.png
new file mode 100644
index 0000000..e62885c
Binary files /dev/null and b/src/main/resources/timifx/dialog-information16x.png differ
diff --git a/src/main/resources/timifx/dialog-warning-danger16x.png b/src/main/resources/timifx/dialog-warning-danger16x.png
new file mode 100644
index 0000000..1441955
Binary files /dev/null and b/src/main/resources/timifx/dialog-warning-danger16x.png differ
diff --git a/src/main/resources/timifx/dialog-warning16x.png b/src/main/resources/timifx/dialog-warning16x.png
new file mode 100644
index 0000000..ac32e1d
Binary files /dev/null and b/src/main/resources/timifx/dialog-warning16x.png differ
diff --git a/src/main/resources/timifx/font.css b/src/main/resources/timifx/font.css
new file mode 100644
index 0000000..5b9fe89
--- /dev/null
+++ b/src/main/resources/timifx/font.css
@@ -0,0 +1,22 @@
+@font-face {
+ src: url('./MinecraftAE.ttf');
+}
+
+/* Minecraft AE 字体在 16 32 64 128 像素时表现为最清晰 */
+.text,
+.label,
+.button,
+.chart *,
+.slider *,
+.tab-pane,
+.combo-box,
+.check-box,
+.tree-view,
+.text-area,
+.text-field,
+.list-view > *,
+.table-view > *,
+.minecraft-ae {
+ -fx-font-size: 16;
+ -fx-font-family: 'Minecraft AE';
+}
\ No newline at end of file
diff --git a/src/main/resources/timifx/icon.png b/src/main/resources/timifx/icon.png
new file mode 100644
index 0000000..232ef32
Binary files /dev/null and b/src/main/resources/timifx/icon.png differ
diff --git a/src/main/resources/timifx/style.css b/src/main/resources/timifx/style.css
new file mode 100644
index 0000000..f39fc72
--- /dev/null
+++ b/src/main/resources/timifx/style.css
@@ -0,0 +1,816 @@
+/******************************************************************************
+ * *
+ * Pixel Size to Font Size *
+ * *
+ * 1px: 0.083333em *
+ * 2px: 0.166667em *
+ * 3px: 0.25em *
+ * 4px: 0.333333em *
+ * 5px: 0.416667em *
+ * 6px: 0.5em *
+ * 7px: 0.583333em *
+ * 8px: 0.666667em *
+ * 9px: 0.75em *
+ * 10px: 0.833333em *
+ * 11px: 0.916667em *
+ * 12px: 1.0em *
+ * *
+ ******************************************************************************/
+
+/*
+ - TimiFX 的样式表 -
+
+ 1. 主体颜色为灰色,辅助色为靛青 #177CB0
+ 2. 基本组件在前,复杂组件在后,公共类最后
+
+*/
+* {
+ -fx-tab-size: 4;
+ -fx-font-smoothing-type: gray;
+}
+.root {
+ -timi-fx-color: #177CB0;
+ -timi-fx-icon-color: #333;
+ -timi-fx-border-color: #B5B5B5;
+ -timi-fx-popup-shadow: dropshadow(three-pass-box, rgba(0, 0, 0, .3), 5, 0, 0, 1);
+ -timi-fx-opacity-hover: .7;
+ -timi-fx-selected-color: #7CC4FF;
+ -timi-fx-opacity-disabled: .4;
+
+ -fx-accent: -timi-fx-color;
+ -fx-focus-color: transparent;
+ -fx-focused-base: transparent;
+ -fx-text-box-border: -timi-fx-border-color;
+ -fx-faint-focus-color: transparent;
+ -fx-shadow-highlight-color: transparent;
+ -fx-cell-focus-inner-border: transparent;
+ -fx-control-inner-background: #FFF;
+}
+/* --------------------------- 聚焦边距 --------------------------- */
+.text-area:focused,
+.text-field:focused {
+ -fx-background-insets: 1, 2;
+}
+.button:focused {
+ -fx-background-insets: 0;
+}
+.list-view:focused {
+ -fx-background-insets: 0, 0, 2;
+}
+.table-view:focused {
+ -fx-background-insets: 0, 0, 1;
+}
+/* --------------------------- 只读 --------------------------- */
+.text-area:readonly,
+.text-field:readonly,
+.time-picker:readonly {
+ -fx-text-fill: #666;
+}
+/* --------------------------- 控件 --------------------------- */
+/* 可选标签 */
+.selectable-label * {
+ -fx-background-color: transparent;
+}
+.selectable-label .scroll-pane {
+ -fx-hbar-policy: never;
+ -fx-vbar-policy: never;
+}
+/* 按钮 */
+.button {
+ -fx-padding: .25em .833333em; /* 3 10 */
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-body-color;
+ -fx-background-insets: 0;
+ -fx-background-radius: 0;
+}
+.toggle-button {
+ -fx-padding: .25em .833333em; /* 3 10 */
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: transparent;
+ -fx-background-insets: 0;
+ -fx-background-radius: .083333em;
+}
+.toggle-button:selected {
+ -fx-background-color: -timi-fx-selected-color;
+}
+/* 图标按钮 */
+.icon-button {
+ -fx-border-width: 1;
+ -fx-border-color: transparent;
+}
+/* 切换图标按钮 */
+.toggle-icon:disabled {
+ -fx-opacity: -timi-fx-opacity-disabled;
+}
+.toggle-icon:disabled .icon {
+ -fx-opacity: -timi-fx-opacity-disabled;
+}
+/* 文本框 */
+.text-field {
+ -fx-padding: .25em .333333em; /* 3 4 */
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-control-inner-background;
+ -fx-background-insets: 0;
+ -fx-background-radius: 0;
+}
+/* 文本域 */
+.text-area {
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-control-inner-background;
+ -fx-background-radius: 0;
+}
+.text-area:focused {
+ -fx-background-color: -fx-focus-color, -fx-control-inner-background;
+}
+.text-area .content {
+ -fx-padding: .25em .388888em; /* 3 4.5 */
+ -fx-background-color: -fx-control-inner-background;
+}
+.text-area .scroll-pane {
+ -fx-padding: 0;
+}
+/* 文本域编辑器 */
+.text-area-editor .find-field {
+ -fx-background-color: #FFF;
+ -fx-background-insets: 0;
+}
+.text-area-editor .replace-field {
+ -fx-padding: .25em .333333em .25em 1.25em; /* 3 4 3 15 */
+}
+/* 滑动选择 */
+.slider > .track {
+ -fx-padding: .25em;
+ -fx-background-radius: 0;
+}
+.slider > .thumb {
+ -fx-padding: .5em .3em;
+ -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
+ -fx-background-insets: 0 0 -1 0, 0, 1, 2;
+ -fx-background-radius: 0;
+}
+.slider:focused > .thumb {
+ -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
+ -fx-background-insets: 0 0 -1 0, 0, 1, 2;
+}
+/* 树形结构 */
+.tree-view {
+ -fx-padding: 0;
+}
+.tree-view .tree-disclosure-node {
+ -fx-padding: .166667em .583333em 0 .333333em;
+}
+.tree-view .tree-cell .arrow,
+.tree-view .tree-cell:expanded .arrow {
+ -fx-shape: "M303.5,391.5h1v1h1v1h1v1h1v1h1v1h-1v1h-1v1h-1v1h-1v1h-1Z";
+ -fx-min-width: 5;
+ -fx-min-height: 9;
+ -fx-pref-width: 5;
+ -fx-pref-height: 9;
+}
+/* 列表 */
+.list-view {
+ -fx-padding: 0;
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-control-inner-background;
+ -fx-background-insets: 0;
+}
+.list-view:focused {
+ -fx-background-color: -fx-control-inner-background;
+ -fx-background-insets: 0;
+}
+/* 表格 */
+.table-view {
+ -fx-padding: 0;
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-control-inner-background;
+ -fx-background-insets: 1;
+}
+.table-view .arrow,
+.tree-table-view .arrow {
+ -fx-shape: "M0,0h9v1H8v1H7v1H6v1H5v1H4V4H3V3H2V2H1V1H0V0z";
+ -fx-min-width: 9;
+ -fx-min-height: 5;
+ -fx-pref-width: 9;
+ -fx-pref-height: 5;
+}
+.table-view .text-field {
+ -fx-background-insets: 0;
+}
+.table-view .text-field:focused {
+ -fx-background-insets: 1;
+}
+/* 下拉选择,菜单按钮,颜色选择,日期选择 */
+.combo-box,
+.menu-button,
+.date-picker,
+.color-picker {
+ -fx-border-width: 1;
+ -fx-border-color: -fx-text-box-border;
+ -fx-background-color: -fx-body-color;
+ -fx-background-insets: 0;
+ -fx-background-radius: 0;
+}
+.combo-box .text-input,
+.date-picker .text-input {
+ -fx-padding: 4;
+ -fx-background-color: -fx-text-box-border, #FFF;
+ -fx-background-insets: 0, 0 1 0 0;
+ -fx-background-radius: 0;
+}
+.combo-box > .list-cell {
+ -fx-padding: .25em;
+}
+.menu-button.icon > .label {
+ -fx-padding: .166667em .083333em .25em .333333em; /* 2 1 3 4 */
+}
+.menu-button.empty > .label {
+ -fx-padding: 0;
+}
+/* 下拉箭头 */
+.combo-box-base .text-field {
+ -fx-border-width: 0;
+}
+.combo-box .arrow-button,
+.menu-button .arrow-button {
+ -fx-padding: 0 .5em;
+ -fx-background-radius: 0;
+ -fx-background-insets: 0;
+}
+.combo-box .arrow-button .arrow,
+.menu-button .arrow-button .arrow,
+.color-picker .arrow-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;
+}
+.combo-box .combo-box-popup .list-view {
+ -fx-effect: -timi-fx-popup-shadow;
+ -fx-padding: 0;
+ -fx-translate-y: -1;
+ -fx-border-width: 1 1 0 1;
+ -fx-background-color: -timi-fx-border-color, #FFF;
+ -fx-background-insets: 0, 0 0 1 0;
+}
+.combo-box .combo-box-popup .list-view .list-cell {
+ -fx-border-color: transparent;
+ -fx-background-color: transparent;
+}
+.combo-box .combo-box-popup .list-view .list-cell:filled:hover {
+ -fx-text-fill: #FFF;
+ -fx-background-color: -timi-fx-color;
+}
+.combo-box .combo-box-popup .list-view .list-cell:filled:selected,
+.combo-box .combo-box-popup .list-view .list-cell:filled:selected:hover {
+ -fx-text-fill: #FFF;
+ -fx-background-color: -timi-fx-color;
+}
+.combo-box .combo-box-popup .list-view .scroll-bar:vertical {
+ -fx-border-width: 0 0 1 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-background;
+}
+/* 单选,多选 */
+.check-box,
+.radio-button,
+.check-box:hover,
+.radio-button:hover {
+ -fx-padding: .166667em .333333em .166667em 0; /* 3 4 3 0 */
+}
+.check-box > .box,
+.radio-button > .radio {
+ -fx-padding: 0;
+ -fx-pref-width: 18;
+ -fx-pref-height: 18;
+ -fx-background-color: -fx-focus-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
+ -fx-background-insets: -1.4, 0, 1, 2;
+ -fx-background-radius: 0, 0, 0, 1;
+}
+.check-box .mark,
+.radio-button .dot {
+ -fx-shape: "M3,7v2h1v1h1v1h1v1h1v-1h1v-1h1V9h1V8h1V7h1V6h1V4h-2v1h-1v1H9v1H8v1H7v1H6V8H5V7H3z";
+ -fx-padding: 0;
+ -fx-max-width: 10;
+ -fx-max-height: 8;
+ -fx-pref-width: 10;
+ -fx-pref-height: 8;
+}
+/* 弹出菜单 */
+.context-menu {
+ -fx-color: -fx-base;
+ -fx-effect: -timi-fx-popup-shadow;
+ -fx-padding: 0;
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: #FFF;
+ -fx-background-insets: 0;
+}
+.context-menu .menu-item:focused {
+ -fx-background-color: -timi-fx-selected-color;
+}
+.context-menu .menu-item:focused .label {
+ -fx-text-fill: #333;
+}
+.context-menu .separator {
+ -fx-padding: 0;
+}
+/* 托盘菜单 */
+.tray-menu {
+ -fx-border-width: 0;
+}
+/* 菜单 */
+.menu > .right-container > .arrow {
+ -fx-shape: "M303.5,391.5h1v1h1v1h1v1h1v1h1v1h-1v1h-1v1h-1v1h-1v1h-1Z";
+ -fx-min-width: 5;
+ -fx-min-height: 9;
+ -fx-pref-width: 5;
+ -fx-pref-height: 9;
+}
+.radio-menu-item:checked > .left-container > .radio {
+ -fx-shape: "M3,7v2h1v1h1v1h1v1h1v-1h1v-1h1V9h1V8h1V7h1V6h1V4h-2v1h-1v1H9v1H8v1H7v1H6V8H5V7H3z";
+}
+.check-menu-item:checked > .left-container > .check {
+ -fx-shape: "M3,7v2h1v1h1v1h1v1h1v-1h1v-1h1V9h1V8h1V7h1V6h1V4h-2v1h-1v1H9v1H8v1H7v1H6V8H5V7H3z";
+}
+/* 日期选择 */
+.date-picker:focused {
+ -fx-background-color: -fx-control-inner-background;
+}
+.date-picker > .arrow-button {
+ -fx-padding: 0 8;
+ -fx-background-insets: 0;
+}
+.date-picker > .arrow-button > .arrow {
+ -fx-shape: "M300,391v2h11v-2Z M303,394v2h2v-2Z M306,394v2h2v-2Z M309,394v2h2v-2Z M309,397v2h2v-2Z M306,397v2h2v-2Z M305,397v2h-2v-2Z M300,397h2v2h-2Z M300,400v2h2v-2Z M303,400v2h2v-2Z M306,400v2h2v-2Z";
+ -fx-min-width: 11;
+ -fx-min-height: 11;
+ -fx-pref-width: 11;
+ -fx-pref-height: 11;
+ -fx-background-color: -timi-fx-icon-color;
+}
+.date-picker-popup {
+ -fx-effect: -timi-fx-popup-shadow;
+ -fx-translate-y: -1;
+ -fx-background-color: -fx-box-border;
+ -fx-background-insets: 0;
+}
+.date-picker-popup > .month-year-pane {
+ -fx-padding: .25em;
+}
+.date-picker-popup > * > .spinner {
+ -fx-spacing: .166667em;
+}
+.date-picker-popup > * > .spinner > .left-button,
+.date-picker-popup > * > .spinner > .right-button {
+ -fx-padding: 0 .333333em .333333em .333333em;
+ -fx-border-width: 0;
+}
+.date-picker-popup > * > .spinner > .button > .left-arrow,
+.date-picker-popup > * > .spinner > .button > .right-arrow {
+ -fx-min-width: 8;
+ -fx-min-height: 13;
+ -fx-pref-width: 8;
+ -fx-pref-height: 13;
+ -fx-background-color: -timi-fx-icon-color;
+}
+.date-picker-popup > * > .spinner > .button > .left-arrow {
+ -fx-shape: "M298,394h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h2v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v1h1v1h1v1h1v1h1v1h1v1h1v1h-2v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1Z";
+}
+.date-picker-popup > * > .spinner > .button > .right-arrow {
+ -fx-shape: "M306,394h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h1v1h1v1h1v1h1v1h1v1h1v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v1h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1Z";
+}
+/* 颜色选择器 */
+.color-palette {
+ -fx-translate-y: -1;
+ -fx-background-radius: 0;
+}
+.custom-color-dialog {
+ -fx-border-width: 1 0 0 0;
+ -fx-border-color: -timi-fx-border-color;
+}
+.custom-color-dialog > .color-rect-pane > .color-bar > #color-bar-indicator {
+ -fx-border-radius: 0;
+}
+.custom-color-dialog .controls-pane .toggle-button {
+ -fx-padding: .25em .5em;
+ -fx-background-radius: 0;
+}
+.custom-color-dialog .controls-pane .current-new-color-grid #current-new-color-border {
+ -fx-border-color: derive(-fx-base, -20%);
+ -fx-border-width: 1px;
+}
+.custom-color-dialog .controls-pane .customcolor-controls-background {
+ -fx-background-color: -fx-text-box-border, -fx-control-inner-background;
+ -fx-background-insets: 12px 0 0 0, 13px 1px 1px 1px;
+ -fx-background-radius: 0;
+}
+/* 滚动条 */
+.scroll-bar {
+ -fx-padding: 0;
+}
+.scroll-bar .decrement-arrow,
+.scroll-bar .increment-arrow,
+.scroll-bar .decrement-button,
+.scroll-bar .increment-button {
+ visibility: hidden;
+ -fx-min-width: 0;
+ -fx-min-height: 0;
+ -fx-pref-width: 0;
+ -fx-pref-height: 0;
+}
+.scroll-bar:vertical,
+.scroll-bar:horizontal {
+ -fx-padding: 0;
+ -fx-background-color: transparent;
+}
+.scroll-bar:vertical {
+ -fx-pref-width: 7;
+}
+.scroll-bar:horizontal {
+ -fx-pref-height: 7;
+}
+.scroll-bar:vertical .thumb,
+.scroll-bar:horizontal .thumb {
+ -fx-background-color: -timi-fx-color;
+ -fx-background-insets: 0;
+ -fx-background-radius: 0;
+}
+/* 列表滚动条 */
+.tree-view .scroll-bar:vertical,
+.list-view .scroll-bar:vertical,
+.table-view .scroll-bar:vertical {
+ -fx-border-width: 0 0 0 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-background;
+}
+
+.tree-view .scroll-bar:horizontal,
+.list-view .scroll-bar:horizontal,
+.table-view .scroll-bar:horizontal {
+ -fx-border-width: 1 0 0 0;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-background;
+}
+/* 进度条 */
+.progress-bar {
+ -fx-padding: 1;
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-indeterminate-bar-flip: true;
+ -fx-indeterminate-bar-length: 120;
+ -fx-indeterminate-bar-escape: true;
+ -fx-indeterminate-bar-animation-time: 4;
+}
+.progress-bar .track {
+ -fx-background-color: transparent;
+ -fx-background-radius: 0;
+}
+.progress-bar .bar {
+ -fx-background-color: -timi-fx-color;
+ -fx-background-insets: 0;
+ -fx-background-radius: 0;
+}
+.progress-bar:indeterminate .bar {
+ -fx-background-color: linear-gradient(to left, transparent, -timi-fx-color);
+}
+/* 标签进度 */
+.label-progress-bar .bar {
+ -fx-background-color: #7ACAF4;
+}
+.label-progress-bar:indeterminate .bar {
+ -fx-background-color: linear-gradient(to left, transparent, #7ACAF4);
+}
+/* 滑动选择 - 进度 */
+.progress-slider .track {
+ -fx-padding: 0;
+}
+.progress-slider .progress-bar .bar {
+ -fx-pref-height: 6;
+ -fx-background-insets: 0;
+}
+/* 分页 */
+.x-pagination .button,
+.x-pagination .toggle-button {
+ -fx-padding: 0;
+ -fx-pref-width: 28;
+ -fx-pref-height: 26;
+}
+/* 标题组件 */
+.titled-pane {
+ -fx-animate: false;
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -timi-fx-border-color, #F1F1F1;
+ -fx-background-insets: 0, 0 0 1 0;
+}
+.titled-pane > .title {
+ -fx-padding: .333333em .833333em; /* 4 10 */
+ -fx-background-color: -fx-inner-border, -fx-body-color;
+ -fx-background-insets: 0, 1;
+ -fx-background-radius: 0, 0;
+}
+.titled-pane > .title > .arrow-button {
+ -fx-padding: 0;
+ -fx-translate-x: -4;
+ -fx-translate-y: -1;
+}
+.titled-pane > .title > .arrow-button > .arrow {
+ -fx-shape: "M300,393v1h1v1h1v1h1v1h1v1h1v-1h1v-1h1v-1h1v-1h1v-1Z";
+ -fx-padding: 0;
+ -fx-min-width: 9;
+ -fx-min-height: 5;
+ -fx-pref-width: 9;
+ -fx-pref-height: 5;
+}
+.titled-pane > .content {
+ -fx-padding: 0;
+ -fx-border-width: 2 0 0 0;
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: #FFF;
+ -fx-background-insets: 0;
+}
+.titled-group .titled-pane {
+ -fx-border-width: 0 0 1 0;
+ -fx-background-insets: 0, 0;
+}
+/* 图表 */
+.chart {
+ -fx-padding: 0 0 6 0;
+}
+.chart-content {
+ -fx-padding: 0 24 4 6;
+}
+.chart-legend {
+ -fx-padding: 4 8;
+ -fx-background-radius: 0, 0;
+}
+.area-legend-symbol {
+ -fx-padding: 6;
+ -fx-background-radius: 0;
+ -fx-background-insets: 0, 6;
+}
+/* 导航 */
+.navigation {
+ -fx-border-width: 1;
+ -fx-border-color: -timi-fx-border-color;
+}
+.navigation .group-pane {
+ -fx-animated: false;
+ -fx-border-width: 1 0 0 0;
+}
+.navigation .group-pane.bottom-line {
+ -fx-border-width: 1 0 1 0;
+}
+.navigation .group-pane .content {
+ -fx-padding: 0;
+}
+.navigation .navigation-button {
+ -fx-padding: .25em .833333em; /* 3 10 */
+ -fx-border-width: 0;
+ -fx-border-color: -timi-fx-border-color;
+}
+.navigation .navigation-button.after-group { /* 紧挨着上一个导航组时,添加上边框 */
+ -fx-border-width: 1 0 0 0;
+}
+.navigation .navigation-button:hover {
+ -fx-background-color: #CCC;
+}
+.navigation .navigation-button:selected {
+ -fx-text-fill: #FFF;
+ -fx-background-color: -timi-fx-color;
+}
+.navigation .navigation-button:disabled {
+ -fx-opacity: -timi-fx-opacity-disabled;
+}
+/* 可编辑表格 */
+.editable-table .table-row-cell:odd {
+ -fx-background-color: -fx-table-cell-border-color, -fx-control-inner-background;
+}
+.editable-table .table-row-cell:filled:selected,
+.editable-table .table-row-cell:filled:selected:focused {
+ -fx-padding: 0;
+ -fx-text-fill: -fx-selection-bar-text;
+ -fx-background: -timi-fx-selected-color;
+ -fx-border-width: 0;
+ -fx-background-color: -timi-fx-selected-color;
+ -fx-background-insets: 0;
+ -fx-table-cell-border-color: -timi-fx-selected-color;
+}
+.editable-table .table-cell {
+ -fx-padding: 0;
+ -fx-cell-size: 0;
+ -fx-border-width: .083333em .083333em .083333em 0; /* 1 1 1 0 */
+}
+.editable-table .table-row-cell .text-field {
+ -fx-background-color: -timi-fx-color, #FFF;
+ -fx-background-insets: 0, 0;
+}
+.editable-table .table-row-cell .text-field:focused {
+ -fx-background-insets: 0, 1;
+}
+/* 选项卡 */
+.tab-pane {
+ -fx-border-color: -timi-fx-border-color;
+}
+.tab-pane > .tab-header-area > .headers-region > .tab {
+ -fx-padding: 0 0 0 .5em;
+ -fx-background-insets: 0 0 1 0, 0 1 1 0, 1 2 1 1;
+ -fx-background-radius: 0, 0, 0;
+}
+.tab-pane > .tab-header-area > .headers-region > .tab:selected {
+ -fx-background-insets: 0 1 0 0, 0 1 0 0;
+}
+.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
+ -fx-padding: 0 .5em 0 0;
+}
+.tab-pane:top > .tab-header-area {
+ -fx-padding: 0;
+}
+/* 分割线 */
+.separator > .line {
+ -fx-border-style: null;
+ -fx-border-width: 0;
+}
+.separator:horizontal > .line {
+ -fx-padding: 0;
+ -fx-min-height: 1;
+ -fx-max-height: 1;
+ -fx-pref-height: 1;
+ -fx-background-color: -timi-fx-border-color;
+}
+/* --------------------------- 布局 --------------------------- */
+/* 滚动 */
+.scroll-pane {
+ -fx-padding: 0;
+ -fx-background-insets: 0;
+}
+.scroll-pane .corner {
+ -fx-background-color: transparent;
+}
+/* 分割 */
+.split-pane {
+ -fx-padding: 0;
+ -fx-background-color: transparent;
+ -fx-background-insets: 0;
+}
+/* --------------------------- 可选类 --------------------------- */
+/* 其他 */
+.bg-tp,
+.bg-tp .viewport {
+ -fx-background-color: transparent;
+ -fx-background-insets: 0;
+}
+.bg-white {
+ -fx-background-color: #FFF;
+}
+.bg-black {
+ -fx-background-color: #000;
+}
+.bg-default {
+ -fx-background-color: #F4F4F4;
+}
+.bg-button-static {
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-body-color;
+}
+.bg-button-static:disabled {
+ -fx-opacity: -timi-fx-opacity-disabled;
+}
+.bg-button {
+ -fx-border-color: -timi-fx-border-color;
+ -fx-background-color: -fx-body-color;
+}
+.bg-button:hover {
+ -fx-color: -fx-hover-base;
+}
+.bg-button:armed {
+ -fx-color: -fx-pressed-base;
+}
+.bg-button:disabled {
+ -fx-opacity: -timi-fx-opacity-disabled;
+}
+.hover-opacity:hover {
+ -fx-opacity: -timi-fx-opacity-hover;
+}
+/* 滚动面板的滚动条边框 */
+.sp-border .scroll-bar:vertical {
+ -fx-border-width: 0 0 0 1;
+ -fx-border-color: -timi-fx-border-color;
+}
+.sp-border .scroll-bar:horizontal {
+ -fx-border-width: 1 0 0 0;
+ -fx-border-color: -timi-fx-border-color;
+}
+.sp-border .corner {
+ -fx-border-width: 1 0 0 1;
+ -fx-border-color: -timi-fx-border-color;
+}
+/* 内边距 */
+.padding-n {
+ -fx-padding: 0;
+}
+/* 字体颜色 */
+.white {
+ -fx-text-fill: #FFF;
+}
+.red {
+ -fx-text-fill: #F30;
+}
+.brown {
+ -fx-text-fill: #A67D7B;
+}
+.black {
+ -fx-text-fill: #000;
+}
+.orange {
+ -fx-text-fill: #F60;
+}
+.yellow {
+ -fx-text-fill: #FF0;
+}
+.green {
+ -fx-text-fill: #393;
+}
+.dark-green {
+ -fx-text-fill: #373;
+}
+.gray {
+ -fx-text-fill: #666;
+}
+.blue {
+ -fx-text-fill: #008DCB;
+}
+.light-blue {
+ -fx-text-fill: #DDEAF0;
+}
+.gray-white {
+ -fx-text-fill: #F4F4F4;
+}
+.light-gray {
+ -fx-text-fill: #B5B5B5;
+}
+.dark-gray {
+ -fx-text-fill: #333;
+}
+.pink {
+ -fx-text-fill: #FF7A9B;
+}
+.transparent {
+ -fx-text-fill: #FFFFFF00;
+}
+/* 边框控制 */
+.border-all {
+ -fx-border-width: 1;
+}
+.border-n {
+ -fx-border-width: 0;
+}
+.border-t {
+ -fx-border-width: 1 0 0 0;
+}
+.border-r {
+ -fx-border-width: 0 1 0 0;
+}
+.border-b {
+ -fx-border-width: 0 0 1 0;
+}
+.border-l {
+ -fx-border-width: 0 0 0 1;
+}
+.border-tr {
+ -fx-border-width: 1 1 0 0;
+}
+.border-rb {
+ -fx-border-width: 0 1 1 0;
+}
+.border-bl {
+ -fx-border-width: 0 0 1 1;
+}
+.border-lt {
+ -fx-border-width: 1 0 0 1;
+}
+.border-trb {
+ -fx-border-width: 1 1 1 0;
+}
+.border-rbl {
+ -fx-border-width: 0 1 1 1;
+}
+.border-blt {
+ -fx-border-width: 1 0 1 1;
+}
+.border-ltr {
+ -fx-border-width: 1 1 0 1;
+}
+.border-tb {
+ -fx-border-width: 1 0 1 0;
+}
+.border-lr {
+ -fx-border-width: 0 1 0 1;
+}
\ No newline at end of file
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/TimiFXExamples.java b/src/test/java/com/imyeyu/fx/ui/examples/TimiFXExamples.java
new file mode 100644
index 0000000..faa3bce
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/TimiFXExamples.java
@@ -0,0 +1,66 @@
+package com.imyeyu.fx.ui.examples;
+
+import com.imyeyu.inject.InjectApp;
+import javafx.application.Application;
+import javafx.beans.property.ObjectProperty;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import com.imyeyu.config.ConfigLoader;
+import com.imyeyu.fx.config.BindingsConfig;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.bean.Config;
+import com.imyeyu.fx.ui.examples.ctrl.Main;
+import com.imyeyu.inject.annotation.TimiInjectApplication;
+import com.imyeyu.java.bean.Language;
+import com.imyeyu.lang.multi.ResourcesMultilingual;
+
+import java.util.Map;
+
+/**
+ * TimiFX 示例程序
+ *
+ * @author 夜雨
+ * @since 2022-05-03 14:52
+ */
+@Slf4j
+@TimiInjectApplication
+public class TimiFXExamples {
+
+ /** 版本号 */
+ public static final String VERSION = "1.1.0";
+
+ @Getter
+ private static Config config;
+
+ @Getter
+ private static ConfigLoader configLoader;
+
+ @Getter
+ private static InjectApp injectApp;
+
+ public static void main(String[] args) {
+ try {
+ injectApp = new InjectApp(TimiFXExamples.class);
+ {
+ configLoader = new ConfigLoader<>("TimiFXExamples.yaml", Config.class);
+ for (Map.Entry, BindingsConfig.PropertyConverter, ?>> item : BindingsConfig.DEFAULT_CONVERTER_MAP.entrySet()) {
+ configLoader.addConverter(item.getKey(), item.getValue());
+ }
+ configLoader.addConverter(ObjectProperty.class, BindingsConfig.OBJECT);
+ config = configLoader.load();
+
+ ResourcesMultilingual multilingual = TimiFXUI.MULTILINGUAL;
+ multilingual.addAll("lang/timi-fx-ui/%s.lang");
+ multilingual.addAll("lang/%s.lang");
+ multilingual.setActivated(Language.zh_CN);
+
+ // 禁止系统 DPI 缩放
+ System.setProperty("prism.allowhidpi", "false");
+ System.setProperty("glass.win.minHiDPI", "1");
+ }
+ Application.launch(Main.class);
+ } catch (Exception e) {
+ log.error("fatal error", e);
+ }
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/bean/Config.java b/src/test/java/com/imyeyu/fx/ui/examples/bean/Config.java
new file mode 100644
index 0000000..66b69f3
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/bean/Config.java
@@ -0,0 +1,34 @@
+package com.imyeyu.fx.ui.examples.bean;
+
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.ObjectProperty;
+import lombok.Data;
+import com.imyeyu.java.bean.Language;
+
+/**
+ * @author 夜雨
+ * @version 2024-05-07 20:34
+ */
+@Data
+public class Config {
+
+ private ObjectProperty language;
+
+ private DoubleProperty width;
+
+ private DoubleProperty height;
+
+ private Interpolator interpolator;
+
+ /**
+ *
+ *
+ * @author 夜雨
+ * @version 2024-05-07 20:35
+ */
+ @Data
+ public static class Interpolator {
+
+ private DoubleProperty duration;
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/AbstractDemoPane.java b/src/test/java/com/imyeyu/fx/ui/examples/component/AbstractDemoPane.java
new file mode 100644
index 0000000..fbed343
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/AbstractDemoPane.java
@@ -0,0 +1,71 @@
+package com.imyeyu.fx.ui.examples.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.XHyperlink;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * 抽象示例面板
+ *
+ * @author 夜雨
+ * @since 2022-08-29 15:44
+ */
+public abstract class AbstractDemoPane extends AbstractPane {
+
+ /** 标题 */
+ protected Label title;
+
+ /** 提示 */
+ protected Label tips;
+
+ /** 文档 */
+ protected XHyperlink document;
+
+ /** 源码 */
+ protected XHyperlink source;
+
+ public AbstractDemoPane() {
+ // 标题
+ title = new Label();
+ title.setBorder(Stroke.BOTTOM);
+ title.setPadding(new Insets(4, 6, 4, 6));
+ title.setMaxWidth(Double.MAX_VALUE);
+ title.setBackground(BG.WHITE);
+
+ // 文档
+ Label labelDocument = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("document"));
+ document = new XHyperlink();
+
+ // 源码
+ Label labelSource = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("source"));
+ source = new XHyperlink();
+
+ // 提示
+ tips = TimiFXUI.label();
+ tips.setPadding(new Insets(4, 6, 4, 6));
+ tips.setWrapText(true);
+ tips.setMaxWidth(Double.MAX_VALUE);
+ tips.visibleProperty().bind(tips.textProperty().isNotEmpty());
+ tips.managedProperty().bind(tips.visibleProperty());
+ tips.textProperty().addListener((obs, o, newTips) -> {
+ if (!newTips.startsWith("\t")) {
+ tips.setText("\t" + newTips);
+ }
+ });
+
+ setTop(new VBox() {{
+ getChildren().addAll(title, new GridPane() {{
+ setHgap(6);
+ setVgap(3);
+ setBorder(Stroke.BOTTOM);
+ setPadding(new Insets(3, 6, 3, 6));
+
+ addRow(0, labelDocument, document);
+ addRow(1, labelSource, source);
+ }}, tips);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/AbstractPane.java b/src/test/java/com/imyeyu/fx/ui/examples/component/AbstractPane.java
new file mode 100644
index 0000000..50e42f6
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/AbstractPane.java
@@ -0,0 +1,50 @@
+package com.imyeyu.fx.ui.examples.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.ui.examples.service.PageService;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.StaticInject;
+import javafx.scene.layout.BorderPane;
+
+/**
+ * 抽象面板
+ *
+ * @author 夜雨
+ * @since 2022-08-26 14:55
+ */
+@StaticInject
+public abstract class AbstractPane extends BorderPane implements TimiFXUI {
+
+ @Inject
+ private static PageService pageService;
+
+ /** 显示时触发,UI 线程 */
+ protected void onShow() {
+ // 子类实现
+ }
+
+ /** 隐藏时触发,UI 线程 */
+ protected void onHide() {
+ // 子类实现
+ }
+
+ /**
+ * 跳转页面
+ *
+ * @param page 页面
+ */
+ protected final void toPage(SidebarItem page) {
+ pageService.to(page);
+ }
+
+ /** 显示面板事件,由调用者触发,通常是侧边导航 */
+ public final void show() {
+ onShow();
+ }
+
+ /** 隐藏面板事件,由侧边导航触发 */
+ public final void hide() {
+ onHide();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/RootLayout.java b/src/test/java/com/imyeyu/fx/ui/examples/component/RootLayout.java
new file mode 100644
index 0000000..46ab742
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/RootLayout.java
@@ -0,0 +1,30 @@
+package com.imyeyu.fx.ui.examples.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.component.sidebar.Sidebar;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.InvokeForInjected;
+import javafx.scene.layout.BorderPane;
+
+/**
+ * 根布局
+ *
+ * @author 夜雨
+ * @since 2022-08-26 14:57
+ */
+@Component
+public class RootLayout extends BorderPane implements TimiFXUI {
+
+ @Inject
+ private Sidebar sidebar;
+
+ public RootLayout() {
+ setBorder(Stroke.TOP);
+ }
+
+ @InvokeForInjected
+ public void init() {
+ setLeft(sidebar);
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/TimiVersionLabel.java b/src/test/java/com/imyeyu/fx/ui/examples/component/TimiVersionLabel.java
new file mode 100644
index 0000000..8207a83
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/TimiVersionLabel.java
@@ -0,0 +1,68 @@
+package com.imyeyu.fx.ui.examples.component;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.imyeyu.fx.task.RunAsync;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.VersionLabel;
+import com.imyeyu.java.TimiJava;
+import com.imyeyu.java.bean.timi.TimiCode;
+import com.imyeyu.java.bean.timi.TimiException;
+import org.apache.hc.client5.http.fluent.Request;
+
+/**
+ * Timi 通用版本标签
+ *
+ * @author 夜雨
+ * @since 2022-05-31 23:24
+ */
+public class TimiVersionLabel extends VersionLabel {
+
+ private final String nowVersion;
+ private final String appName;
+
+ public TimiVersionLabel(String nowVersion, String appName) {
+ super(TimiFXUI.MULTILINGUAL.textArgs("version.checking", nowVersion));
+
+ this.nowVersion = nowVersion;
+ this.appName = appName;
+
+ RunAsync.later(() -> checkVersion(nowVersion), 2000);
+ }
+
+ @Override
+ protected JsonObject run() {
+ try {
+ Thread.sleep(2000);
+ String api = "https://api.imyeyu.com/versions/" + appName;
+ return JsonParser.parseString(Request.get(api).execute().returnContent().asString()).getAsJsonObject();
+ } catch (Exception e) {
+ throw new TimiException(TimiCode.ERROR, e.getMessage());
+ }
+ }
+
+ @Override
+ protected String onReturn(JsonObject resp) {
+ int code = resp.get("code").getAsInt();
+ if (code == 20000) {
+ JsonObject data = resp.get("data").getAsJsonObject();
+ setUpdateURL(data.get("url").getAsString());
+ if (data.has("content") && TimiJava.isNotEmpty(data.get("content").getAsString())) {
+ content.setText(data.get("content").getAsString());
+ }
+ return data.get("version").getAsString();
+ } else {
+ throw new TimiException(TimiCode.fromCode(code), resp.get("msg").getAsString());
+ }
+ }
+
+ @Override
+ protected String updateText(String newVersion) {
+ return TimiFXUI.MULTILINGUAL.textArgs("version.new", nowVersion, newVersion);
+ }
+
+ @Override
+ protected String failText(Throwable e) {
+ return TimiFXUI.MULTILINGUAL.textArgs("version.fail", nowVersion);
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/animation/InterpolatorPane.java b/src/test/java/com/imyeyu/fx/ui/examples/component/animation/InterpolatorPane.java
new file mode 100644
index 0000000..183c9e8
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/animation/InterpolatorPane.java
@@ -0,0 +1,112 @@
+package com.imyeyu.fx.ui.examples.component.animation;
+
+import com.imyeyu.fx.bean.Interpolates;
+import com.imyeyu.fx.ui.MinecraftFont;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.SelectableLabel;
+import com.imyeyu.fx.utils.BgFill;
+import com.sun.scenario.animation.SplineInterpolator;
+import javafx.animation.TranslateTransition;
+import javafx.geometry.Insets;
+import javafx.scene.Cursor;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.util.Duration;
+import lombok.Getter;
+
+/**
+ * 动画插值器示例组件
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:44
+ */
+public class InterpolatorPane extends BorderPane implements TimiFXUI {
+
+ @Getter
+ private final TranslateTransition transition;
+
+ private final Region block;
+
+ public InterpolatorPane(Interpolates interpolator) {
+ SplineInterpolator si = interpolator.getValue();
+
+ // 二次曲线
+ Canvas canvas = new Canvas();
+ canvas.setWidth(64);
+ canvas.setHeight(64);
+
+ // 插值
+ SelectableLabel label = new SelectableLabel(TimiFXUI.MULTILINGUAL.textArgs("fx.example.interpolator.demo", interpolator, si.getX1(), si.getY1(), si.getX2(), si.getY2()));
+
+ // 方块
+ block = new Region();
+ block.setBackground(BgFill.test());
+
+ // 轨道
+ HBox track = new HBox(block);
+ track.setBorder(Stroke.TOP);
+ track.setCursor(Cursor.HAND);
+ track.setBackground(BG.WHITE);
+
+ block.maxWidthProperty().bind(block.heightProperty());
+ block.prefWidthProperty().bind(block.heightProperty());
+
+ setMargin(label, new Insets(2, 4, 2, 4));
+ setBorder(Stroke.DEFAULT);
+ setPrefWidth(520);
+ setBackground(BG.TITLE);
+ setLeft(canvas);
+ setCenter(new BorderPane() {{
+ setBorder(Stroke.LEFT);
+ setTop(label);
+ setCenter(track);
+ }});
+
+ transition = new TranslateTransition();
+ transition.setNode(block);
+ transition.setFromX(0);
+ transition.toXProperty().bind(track.widthProperty().subtract(block.widthProperty()));
+ transition.setDuration(Duration.seconds(2));
+ transition.setInterpolator(interpolator.getValue());
+
+ // ---------- 事件 ----------
+
+ // 绘制图示
+ {
+ double[][] list = interpolator.buildBezierPoint(canvas.getWidth(), canvas.getWidth() * .5);
+ GraphicsContext g = canvas.getGraphicsContext2D();
+ g.setFill(Colorful.WHITE);
+ g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
+ g.setLineWidth(2);
+
+ // 文本
+ g.setStroke(Colorful.LIGHT_GRAY);
+ g.setFont(MinecraftFont.X16());
+ g.strokeText("P", 4, 14);
+ g.strokeText("T", canvas.getWidth() - 12, canvas.getHeight() - 6);
+
+ // 坐标轴
+ g.strokeLine(0, canvas.getHeight() * .5, canvas.getWidth(), canvas.getHeight() * .5);
+ g.strokeLine(canvas.getWidth() * .5, 0, canvas.getWidth() * .5, canvas.getHeight());
+
+ // 曲线
+ g.setStroke(Colorful.DARK_GRAY);
+ g.moveTo(0, canvas.getHeight());
+ for (int i = 0; i < list.length; i++) {
+ g.lineTo(list[i][0], canvas.getHeight() - list[i][1]);
+ }
+ g.stroke();
+ }
+
+ // 点击重播
+ track.setOnMouseClicked(e -> transition.play());
+ }
+
+ /** 重置位置 */
+ public void reset() {
+ block.setTranslateX(0);
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/sidebar/Sidebar.java b/src/test/java/com/imyeyu/fx/ui/examples/component/sidebar/Sidebar.java
new file mode 100644
index 0000000..064a8a6
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/sidebar/Sidebar.java
@@ -0,0 +1,116 @@
+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.examples.service.PageService;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.inject.annotation.Inject;
+import javafx.scene.control.ToggleButton;
+
+/**
+ * 导航
+ *
+ * @author 夜雨
+ * @since 2022-08-26 14:56
+ */
+@Component
+public class Sidebar extends Navigation {
+
+ @Inject
+ private PageService pageService;
+
+ public Sidebar() {
+ Item welcome = new Item(SidebarItem.WELCOME);
+ Item style = new Item(SidebarItem.STYLE);
+ Item extendTools = new Item(SidebarItem.EXTEND_TOOLS);
+ Item bindingConfig = new Item(SidebarItem.BINDING_CONFIG);
+ Item alert = new Item(SidebarItem.ALERT);
+ Item popupTips = new Item(SidebarItem.POPUP_TIPS);
+ Item runAsync = new Item(SidebarItem.RUN_ASYNC);
+ Item animationRenderer = new Item(SidebarItem.ANIMATION_RENDERER);
+
+ add(welcome, style, extendTools, bindingConfig, alert, popupTips, runAsync, animationRenderer);
+
+ // 动画
+ Item interpolator = new Item(SidebarItem.INTERPOLATOR);
+ Item smoothScroll = new Item(SidebarItem.SMOOTH_SCROLL);
+ addGroup(TimiFXUI.MULTILINGUAL.text("animation"), 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
+ };
+ 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);
+
+ // 其他
+ 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);
+
+ getStyleClass().add(CSS.BORDER_R);
+ setMinWidth(140);
+
+ // ---------- 事件 ----------
+
+ // 跳转
+ selectedItem.addListener((obs, o, item) -> {
+ if (item instanceof Item i) {
+ pageService.to(i.item);
+ }
+ });
+ }
+
+ /**
+ * 设置选中
+ *
+ * @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));
+ return;
+ }
+ }
+ throw new NullPointerException("not found item page for " + item);
+ }
+
+ /**
+ * 列表项
+ *
+ * @author 夜雨
+ * @since 2022-02-22 19:45
+ */
+ private static class Item extends ToggleButton {
+
+ /** 列表项对象 */
+ final SidebarItem item;
+
+ public Item(SidebarItem item) {
+ this.item = item;
+ setText(item.text);
+ }
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/component/sidebar/SidebarItem.java b/src/test/java/com/imyeyu/fx/ui/examples/component/sidebar/SidebarItem.java
new file mode 100644
index 0000000..c7df1ea
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/component/sidebar/SidebarItem.java
@@ -0,0 +1,160 @@
+package com.imyeyu.fx.ui.examples.component.sidebar;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.TimiFXExamples;
+import com.imyeyu.fx.ui.examples.component.AbstractPane;
+import com.imyeyu.fx.ui.examples.view.pages.AlertDemo;
+import com.imyeyu.fx.ui.examples.view.pages.AnimationRendererDemo;
+import com.imyeyu.fx.ui.examples.view.pages.BindingsConfigDemo;
+import com.imyeyu.fx.ui.examples.view.pages.ExtendDemo;
+import com.imyeyu.fx.ui.examples.view.pages.PopupTipsDemo;
+import com.imyeyu.fx.ui.examples.view.pages.RunAsyncDemo;
+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;
+import com.imyeyu.fx.ui.examples.view.pages.component.IconButtonDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.IconPickerDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.LabelProgressBarDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.NavigationDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.NumberFieldDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.ProgressSliderDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.SelectableLabelDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.TextAreaEditorDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.TitleLabelDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.ToggleIconDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.XPaginationDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.XTabPaneDemo;
+import com.imyeyu.fx.ui.examples.view.pages.component.XTreeViewDemo;
+import com.imyeyu.fx.ui.examples.view.pages.other.DraggableNodeDemo;
+import com.imyeyu.fx.ui.examples.view.pages.other.DraggableWindowDemo;
+import com.imyeyu.fx.ui.examples.view.pages.other.ScreenFXDemo;
+import com.imyeyu.fx.ui.examples.view.pages.other.TrayFXDemo;
+import javafx.scene.Node;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 导航项
+ *
+ * @author 夜雨
+ * @since 2022-08-26 14:56
+ */
+@Getter
+@AllArgsConstructor
+public enum SidebarItem {
+
+ /** 欢迎页 */
+ WELCOME(Welcome.class, TimiFXUI.MULTILINGUAL.text("fx.example.welcome")),
+
+ /** 样式 */
+ STYLE(Style.class, TimiFXUI.MULTILINGUAL.text("style")),
+
+ /** 扩展工具 */
+ EXTEND_TOOLS(ExtendDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools")),
+
+ /** 配置绑定 */
+ BINDING_CONFIG(BindingsConfigDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.binding_config")),
+
+ /** 弹窗 */
+ ALERT(AlertDemo.class, TimiFXUI.MULTILINGUAL.text("alert")),
+
+ /** 弹出提示 */
+ POPUP_TIPS(PopupTipsDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.popup_tips")),
+
+ /** 异步任务 */
+ RUN_ASYNC(RunAsyncDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.run_async")),
+
+ /** 异步任务 */
+ ANIMATION_RENDERER(AnimationRendererDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.animation_renderer")),
+
+ // ---------- 动画 ----------
+
+ /** 动画插值器 */
+ INTERPOLATOR(InterpolatorDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.interpolator")),
+
+ /** 平滑滚动 */
+ SMOOTH_SCROLL(SmoothScrollDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.smooth_scroll")),
+
+ // ---------- 组件 ----------
+
+ /** 复选框选择器 */
+ 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")),
+
+ /** 表格可编辑单元格 */
+ EDITABLE_TABLE_CELL(EditableTableCellDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.editable_table_cell")),
+
+ /** 文件树视图 */
+ FILE_TREE_VIEW(FileTreeViewDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.file_tree_view")),
+
+ /** 图标按钮 */
+ ICON_BUTTON(IconButtonDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.icon_button")),
+
+ /** 图标选择器 */
+ ICON_PICKER(IconPickerDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.icon_picker")),
+
+ /** 标签进度 */
+ LABEL_PROGRESS_BAR(LabelProgressBarDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.label_progress_bar")),
+
+ /** 导航 */
+ NAVIGATION(NavigationDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.navigation")),
+
+ /** 数字输入 */
+ NUMBER_FIELD(NumberFieldDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.number_field")),
+
+ /** 进度调整 */
+ PROGRESS_SLIDER(ProgressSliderDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.progress_slider")),
+
+ /** 可选标签 */
+ SELECTABLE_LABEL(SelectableLabelDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.selectable_label")),
+
+ /** 文本域编辑器 */
+ TEXT_AREA_EDITOR(TextAreaEditorDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor")),
+
+ /** 标题标签 */
+ TITLE_LABEL(TitleLabelDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.title_label")),
+
+ /** 切换图标 */
+ TOGGLE_ICON(ToggleIconDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.toggle_icon")),
+
+ /** 分页 */
+ X_PAGINATION(XPaginationDemo.class, TimiFXUI.MULTILINGUAL.text("pagination")),
+
+ /** 标签面板 */
+ X_TAB_PANE(XTabPaneDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.x_tab_pane")),
+
+ /** 树形视图 */
+ X_TREE_VIEW(XTreeViewDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.x_tree_view")),
+
+ // ---------- 其他 ----------
+
+ /** 可拖动组件 */
+ DRAGGABLE_NODE(DraggableNodeDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.draggable_node")),
+
+ /** 可拖动窗体 */
+ DRAGGABLE_WINDOW(DraggableWindowDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.draggable_window")),
+
+ /** 多屏操作 */
+ SCREEN(ScreenFXDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.screen")),
+
+ /** 托盘操作 */
+ TRAY(TrayFXDemo.class, TimiFXUI.MULTILINGUAL.text("fx.example.tray"));
+
+ /** 页面控制反转类 */
+ final Class extends AbstractPane> page;
+
+ /** 文本 */
+ final String text;
+
+ /** @return 从 TimiInject 控制反转对象获取该页面 */
+ public Node getIOCPage() {
+ return TimiFXExamples.getInjectApp().injector().di(page);
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/ctrl/Main.java b/src/test/java/com/imyeyu/fx/ui/examples/ctrl/Main.java
new file mode 100644
index 0000000..aebb0d1
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/ctrl/Main.java
@@ -0,0 +1,67 @@
+package com.imyeyu.fx.ui.examples.ctrl;
+
+import com.imyeyu.fx.ui.components.TrayFX;
+import com.imyeyu.fx.ui.examples.TimiFXExamples;
+import com.imyeyu.fx.ui.examples.component.sidebar.Sidebar;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.ui.examples.service.PageService;
+import com.imyeyu.fx.ui.examples.view.ViewMain;
+import com.imyeyu.inject.TimiInject;
+import com.imyeyu.inject.annotation.IOCReturn;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.SuperInject;
+import javafx.stage.Stage;
+
+import java.awt.SplashScreen;
+
+/**
+ * 主控
+ *
+ * @author 夜雨
+ * @since 2022-05-03 15:43
+ */
+@SuperInject
+public class Main extends ViewMain {
+
+ @Inject
+ private TrayFX trayFX;
+
+ @Inject
+ private Sidebar sidebar;
+
+ @Inject
+ private PageService pageService;
+
+ private Stage stage;
+
+ @Override
+ public void start(Stage stage) {
+ this.stage = stage;
+
+ TimiInject.run(TimiFXExamples.getInjectApp()).ioc(this);
+ super.start(stage);
+
+ sidebar.setSelected(SidebarItem.WELCOME);
+
+ // 主窗体尺寸
+ config.getWidth().bind(stage.widthProperty());
+ config.getHeight().bind(stage.heightProperty());
+
+ if (SplashScreen.getSplashScreen() != null) {
+ SplashScreen.getSplashScreen().close();
+ }
+ }
+
+ @Override
+ public void stop() {
+ TimiFXExamples.getConfigLoader().dump();
+ trayFX.remove();
+ stage.close();
+ }
+
+ /** 主窗体 */
+ @IOCReturn
+ public Stage getStage() {
+ return stage;
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/service/PageService.java b/src/test/java/com/imyeyu/fx/ui/examples/service/PageService.java
new file mode 100644
index 0000000..808f76b
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/service/PageService.java
@@ -0,0 +1,62 @@
+package com.imyeyu.fx.ui.examples.service;
+
+import com.imyeyu.fx.ui.examples.component.AbstractPane;
+import com.imyeyu.fx.ui.examples.component.RootLayout;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.Service;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 页面服务
+ *
+ * @author 夜雨
+ * @since 2021-12-26 10:57
+ */
+@Slf4j
+@Service
+public class PageService {
+
+ @Inject
+ private RootLayout root;
+
+ private SidebarItem prev;
+
+ private final ObjectProperty activatedPageProperty;
+
+ public PageService() {
+ activatedPageProperty = new SimpleObjectProperty<>();
+ activatedPageProperty.addListener((obs, prev, now) -> {
+ this.prev = prev;
+ if (now != null && now.getIOCPage() instanceof AbstractPane pane) {
+ pane.show();
+ root.setCenter(pane);
+ }
+ if (prev != null && prev.getIOCPage() instanceof AbstractPane pane) {
+ pane.hide();
+ }
+ });
+ }
+
+ /**
+ * 跳转页面
+ *
+ * @param page 页面
+ */
+ public void to(SidebarItem page) {
+ activatedPageProperty.set(page);
+ }
+
+ /** 返回上一个页面 */
+ public void back() {
+ to(prev);
+ }
+
+ /** @return 当前页面监听 */
+ public ReadOnlyObjectProperty activatedPageProperty() {
+ return activatedPageProperty;
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/util/Resources.java b/src/test/java/com/imyeyu/fx/ui/examples/util/Resources.java
new file mode 100644
index 0000000..aed2fa3
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/util/Resources.java
@@ -0,0 +1,32 @@
+package com.imyeyu.fx.ui.examples.util;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.TrayFX;
+import com.imyeyu.fx.ui.examples.TimiFXExamples;
+import com.imyeyu.fx.ui.examples.bean.Config;
+import com.imyeyu.inject.annotation.IOCReturn;
+import javafx.scene.image.Image;
+
+/**
+ * 静态资源
+ *
+ * @author 夜雨
+ * @since 2022-08-26 15:23
+ */
+@com.imyeyu.inject.annotation.Resources
+public class Resources implements TimiFXUI {
+
+ public static final Image ICON_X64 = new Image(RESOURCE + "icon.png", 64, 64, true, false);
+
+ /** @return 配置 */
+ @IOCReturn
+ public Config config() {
+ return TimiFXExamples.getConfig();
+ }
+
+ /** @return 托盘 */
+ @IOCReturn
+ public TrayFX trayFX() {
+ return TrayFX.getInstance();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/ViewMain.java b/src/test/java/com/imyeyu/fx/ui/examples/view/ViewMain.java
new file mode 100644
index 0000000..930a647
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/ViewMain.java
@@ -0,0 +1,44 @@
+package com.imyeyu.fx.ui.examples.view;
+
+import javafx.application.Application;
+import javafx.scene.PerspectiveCamera;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.stage.Stage;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.bean.Config;
+import com.imyeyu.fx.ui.examples.component.RootLayout;
+import com.imyeyu.inject.annotation.Inject;
+
+/**
+ * 主界面
+ *
+ * @author 夜雨
+ * @since 2022-05-03 14:53
+ */
+public abstract class ViewMain extends Application implements TimiFXUI {
+
+ @Inject
+ protected Config config;
+
+ @Inject
+ private RootLayout root;
+
+ @Override
+ public void start(final Stage stage) {
+ PerspectiveCamera perspectiveCamera = new PerspectiveCamera(false);
+ perspectiveCamera.setTranslateX(0);
+ perspectiveCamera.setTranslateY(0);
+ perspectiveCamera.setTranslateZ(0);
+
+ Scene scene = new Scene(root);
+ scene.getStylesheets().addAll(CSS_STYLE, CSS_FONT);
+ scene.setCamera(perspectiveCamera);
+ stage.setTitle(TimiFXUI.MULTILINGUAL.text("fx.example.title"));
+ stage.getIcons().add(new Image(RESOURCE + "icon.png"));
+ stage.setScene(scene);
+ stage.setWidth(config.getWidth().get());
+ stage.setHeight(config.getHeight().get());
+ stage.show();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/AlertDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/AlertDemo.java
new file mode 100644
index 0000000..95b7020
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/AlertDemo.java
@@ -0,0 +1,225 @@
+package com.imyeyu.fx.ui.examples.view.pages;
+
+import com.imyeyu.fx.task.RunAsync;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.alert.AbstractAlert;
+import com.imyeyu.fx.ui.components.alert.AlertButton;
+import com.imyeyu.fx.ui.components.alert.AlertConfirm;
+import com.imyeyu.fx.ui.components.alert.AlertFileBlendSelector;
+import com.imyeyu.fx.ui.components.alert.AlertFilePathSelector;
+import com.imyeyu.fx.ui.components.alert.AlertFileSelector;
+import com.imyeyu.fx.ui.components.alert.AlertLoading;
+import com.imyeyu.fx.ui.components.alert.AlertTextArea;
+import com.imyeyu.fx.ui.components.alert.AlertTextField;
+import com.imyeyu.fx.ui.components.alert.AlertTips;
+import com.imyeyu.fx.ui.components.alert.AlertType;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.java.bean.timi.TimiCode;
+import com.imyeyu.java.bean.timi.TimiException;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.ListView;
+import javafx.scene.control.SelectionMode;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+
+/**
+ * 弹出窗体
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:39
+ */
+@Component
+public class AlertDemo extends AbstractDemoPane {
+
+ public AlertDemo() {
+ title.setText(SidebarItem.ALERT.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/alert/package-summary.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/alert");
+
+ // 一般
+ Button info = new Button(TimiFXUI.MULTILINGUAL.text("info"));
+ info.getStyleClass().add(CSS.BORDER_BLT);
+ Button warning = new Button(TimiFXUI.MULTILINGUAL.text("warning"));
+ warning.getStyleClass().add(CSS.BORDER_BLT);
+ Button warningDanger = new Button(TimiFXUI.MULTILINGUAL.text("warning"));
+ warningDanger.getStyleClass().add(CSS.BORDER_BLT);
+ Button error = new Button(TimiFXUI.MULTILINGUAL.text("error"));
+ error.getStyleClass().add(CSS.BORDER_BLT);
+ Button deadlyError = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.alert.deadly_error"));
+
+ // 询问
+ Button confirmInfo = new Button(TimiFXUI.MULTILINGUAL.text("info"));
+ confirmInfo.getStyleClass().add(CSS.BORDER_BLT);
+ Button confirmWarning = new Button(TimiFXUI.MULTILINGUAL.text("warning"));
+ confirmWarning.getStyleClass().add(CSS.BORDER_BLT);
+ Button confirmWarningDanger = new Button(TimiFXUI.MULTILINGUAL.text("warning"));
+ confirmWarningDanger.getStyleClass().add(CSS.BORDER_BLT);
+ Button confirmError = new Button(TimiFXUI.MULTILINGUAL.text("error"));
+ confirmError.getStyleClass().add(CSS.BORDER_BLT);
+ Button confirmCancel = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.alert.can_cancel"));
+
+ // 输入
+ Button textField = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.alert.text_field"));
+ textField.getStyleClass().add(CSS.BORDER_BLT);
+ Button textArea = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.alert.text_area"));
+
+ // 文件
+ Button file = new Button(TimiFXUI.MULTILINGUAL.text("file.select"));
+ file.getStyleClass().add(CSS.BORDER_BLT);
+ Button directory = new Button(TimiFXUI.MULTILINGUAL.text("file.select.directory"));
+ directory.getStyleClass().add(CSS.BORDER_BLT);
+ Button fileBlend = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.alert.file_blend"));
+
+ // 加载中
+ Button loading = new Button(TimiFXUI.MULTILINGUAL.text("loading"));
+ loading.getStyleClass().add(CSS.BORDER_BLT);
+
+ // 自定义
+ Button custom = new Button(TimiFXUI.MULTILINGUAL.text("custom"));
+
+ setCenter(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.KEY);
+ setHgap(12);
+ setVgap(6);
+ setPadding(new Insets(20));
+
+ int row = 0;
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("commonly")), new HBox() {{
+ getChildren().addAll(info, warning, warningDanger, error, deadlyError);
+ }});
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("confirmation")), new HBox() {{
+ getChildren().addAll(confirmInfo, confirmWarning, confirmWarningDanger, confirmError, confirmCancel);
+ }});
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("input")), new HBox(textField, textArea));
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("file")), new HBox() {{
+ setSpacing(6);
+ setAlignment(Pos.CENTER_LEFT);
+ getChildren().addAll(new HBox(file, directory, fileBlend), TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.alert.multi_tips")));
+ }});
+ addRow(row, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("other")), new HBox(loading, custom));
+ }});
+
+ // ---------- 事件 ----------
+
+ String text = "TimiFX";
+
+ // 一般
+ info.setOnAction(e -> AlertTips.info(getScene().getWindow(), text));
+ warning.setOnAction(e -> AlertTips.warn(getScene().getWindow(), text));
+ warningDanger.setOnAction(e -> AlertTips.warnDanger(getScene().getWindow(), text));
+ error.setOnAction(e -> AlertTips.error(getScene().getWindow(), text));
+ deadlyError.setOnAction(e -> AlertTextArea.error(getScene().getWindow(), new TimiException(TimiCode.ERROR)));
+
+ AlertConfirm alertConfirm = new AlertConfirm(text) {
+
+ @Override
+ protected void onConfirm() {
+ AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("yes"));
+ }
+
+ @Override
+ protected void onCancel() {
+ AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("cancel"));
+ }
+ };
+ alertConfirm.setOnCloseRequest(e -> AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("cancel")));
+
+ // 询问
+ confirmInfo.setOnAction(e -> {
+ alertConfirm.setType(AlertType.INFORMATION);
+ alertConfirm.autoSize().showRelativeCenter(getScene().getWindow());
+ });
+ confirmWarning.setOnAction(e -> {
+ alertConfirm.setType(AlertType.WARNING);
+ alertConfirm.autoSize().showRelativeCenter(getScene().getWindow());
+ });
+ confirmWarningDanger.setOnAction(e -> {
+ alertConfirm.setType(AlertType.WARNING_DANGER);
+ alertConfirm.autoSize().showRelativeCenter(getScene().getWindow());
+ });
+ confirmError.setOnAction(e -> {
+ alertConfirm.setType(AlertType.ERROR);
+ alertConfirm.autoSize().showRelativeCenter(getScene().getWindow());
+ });
+ confirmCancel.setOnAction(e -> {
+ AlertTips alert = new AlertTips(text);
+ alert.setType(AlertType.WARNING_DANGER);
+ alert.setButton(AlertButton.yes(), AlertButton.no(), AlertButton.cancel());
+ alert.setOnActionEvent(action -> {
+ switch (action) {
+ case YES -> AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("yes"));
+ case NO -> AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("no"));
+ case CANCEL -> AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("cancel"));
+ }
+ return true;
+ });
+ alert.setOnCloseRequest(closeE -> AlertTips.info(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("cancel")));
+ alert.autoSize().showRelativeCenter(getScene().getWindow());
+ });
+
+ // 输入
+ textField.setOnAction(e -> AlertTextField.confirm(getScene().getWindow(), text, value -> AlertTips.info(getScene().getWindow(), value)));
+ textArea.setOnAction(e -> AlertTextArea.info(getScene().getWindow(), text));
+
+ // 文件
+ file.setOnAction(e -> new AlertFileSelector(SelectionMode.SINGLE).showRelativeCenter(getScene().getWindow()));
+ directory.setOnAction(e -> new AlertFilePathSelector(SelectionMode.SINGLE).showRelativeCenter(getScene().getWindow()));
+ fileBlend.setOnAction(e -> new AlertFileBlendSelector(SelectionMode.MULTIPLE).showRelativeCenter(getScene().getWindow()));
+
+ // 加载中
+ loading.setOnAction(e -> {
+ AlertLoading alert = new AlertLoading(TimiFXUI.MULTILINGUAL.text("loading"));
+ alert.showRelativeCenter(getScene().getWindow());
+ RunAsync.later(alert::close, 2000);
+ });
+
+ // 自定义
+ CustomAlert customAlert = new CustomAlert();
+ custom.setOnAction(e -> customAlert.showRelativeCenter(getScene().getWindow()));
+ }
+
+ /**
+ * 自定义窗体
+ *
+ * @author 夜雨
+ * @since 2022-09-02 15:21
+ */
+ private static class CustomAlert extends AbstractAlert {
+
+ public CustomAlert() {
+ ListView list = new ListView<>();
+ ComboBox comboBox = new ComboBox<>();
+ CheckBox checkBox = new CheckBox("CheckBox");
+ checkBox.setSelected(true);
+
+ root.setPadding(new Insets(8, 12, 8, 12));
+ root.setTop(TimiFXUI.label("Custom Tips"));
+ root.setCenter(list);
+
+ setType(AlertType.INFORMATION);
+ setTitle(TimiFXUI.MULTILINGUAL.text("custom"));
+ setWidth(520);
+ setHeight(460);
+ setRightButtons(AlertButton.confirm(), AlertButton.cancel());
+
+ btnPane.setPadding(new Insets(12, 0, 0, 0));
+ leftButtons.getChildren().addAll(comboBox, checkBox);
+ for (int i = 0; i < 20; i++) {
+ list.getItems().add("item " + i);
+ comboBox.getItems().add("item " + i);
+ }
+ comboBox.getSelectionModel().select(0);
+
+ // ---------- 事件 ----------
+
+ setOnActionEvent(action -> true);
+ }
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/AnimationRendererDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/AnimationRendererDemo.java
new file mode 100644
index 0000000..37d029b
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/AnimationRendererDemo.java
@@ -0,0 +1,173 @@
+package com.imyeyu.fx.ui.examples.view.pages;
+
+import com.imyeyu.fx.BindingUtils;
+import com.imyeyu.fx.TimiFX;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.SelectableLabel;
+import com.imyeyu.fx.ui.components.TextAreaEditor;
+import com.imyeyu.fx.ui.components.alert.AlertTips;
+import com.imyeyu.fx.ui.components.popup.PopupTipsService;
+import com.imyeyu.fx.ui.examples.TimiFXExamples;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.ui.examples.ctrl.Main;
+import com.imyeyu.fx.utils.AnimationRenderer;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.inject.TimiInject;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.io.IO;
+import com.imyeyu.utils.OS;
+import javafx.geometry.Insets;
+import javafx.geometry.Point3D;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.Slider;
+import javafx.scene.control.SplitPane;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.paint.PhongMaterial;
+import javafx.scene.shape.Box;
+import javafx.scene.shape.CullFace;
+import javafx.scene.shape.DrawMode;
+
+/**
+ * 动画渲染器
+ *
+ * @author 夜雨
+ * @since 2023-04-13 10:46
+ */
+@Component
+public class AnimationRendererDemo extends AbstractDemoPane implements OS.FileSystem {
+
+ private final AnimationRenderer renderer;
+
+ public AnimationRendererDemo() {
+ title.setText(SidebarItem.ANIMATION_RENDERER.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/AnimationRenderer.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/AnimationRenderer.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.animation_renderer.tips"));
+
+ // 动画旋转立方体
+ Box box = new Box(128, 128, 128);
+ box.setDrawMode(DrawMode.LINE);
+ box.setCullFace(CullFace.BACK);
+ box.setMaterial(new PhongMaterial(Colorful.BLACK));
+ box.setTranslateY(60);
+ box.setRotationAxis(new Point3D(0, 64, 0));
+ renderer = new AnimationRenderer();
+ renderer.addRenderCallback(deltaSecond -> box.setRotate(box.getRotate() + 90 * deltaSecond));
+
+ String value = System.getProperty("javafx.animation.fullspeed");
+ boolean isFullSpeed = value != null && value.equalsIgnoreCase("true");
+
+ // 当前状态
+ Label labelStatus = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.animation_renderer.status"));
+ SelectableLabel status = new SelectableLabel("-Djavafx.animation.fullspeed=" + isFullSpeed);
+
+ // 重启
+ Button restart = new Button(TimiFXUI.MULTILINGUAL.textArgs("fx.example.animation_renderer.restart", TimiFXUI.MULTILINGUAL.text(isFullSpeed ? "disable" : "enable")));
+
+ // 示例
+ Label labelDemo = TimiFXUI.title(TimiFXUI.MULTILINGUAL.text("example"));
+ TextAreaEditor demo = new TextAreaEditor();
+ demo.getStyleClass().add(CSS.BORDER_N);
+ demo.setEditable(false);
+ demo.setText("""
+Box box = new Box(128, 128, 128);
+box.setDrawMode(DrawMode.LINE);
+box.setCullFace(CullFace.BACK);
+box.setMaterial(new PhongMaterial(RED));
+box.setRotationAxis(new Point3D(0, 64, 0));
+
+// 每秒 90 度旋转一个 3D 立方体,默认 60 FPS
+AnimationRenderer renderer = new AnimationRenderer();
+renderer.addRenderCallback(deltaSecond -> {
+ box.setRotate(box.getRotate() + 90 * deltaSecond);
+});
+
+// 控制 FPS
+Slider fps = new Slider(5, 240, 60);
+fps.valueProperty().addListener((obs, o, newFps) -> {
+ renderer.setPrefFPS(newFps.intValue())
+});
+ """.trim());
+
+ // 预设 FPS
+ Label labelPrefFPS = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.animation_renderer.pref_fps"));
+ Slider prefFPS = new Slider(5, 240, 60);
+ prefFPS.valueProperty().addListener((obs, o, newFps) -> renderer.setPrefFPS(newFps.intValue()));
+ PopupTipsService.installBindingText(prefFPS, BindingUtils.integerStringBinding(prefFPS.valueProperty()));
+ renderer.setPrefFPS((int) prefFPS.getValue());
+
+ // 当前渲染 FPS
+ Label labelFPS = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fps"));
+ Label fps = new Label();
+ fps.textProperty().bind(renderer.fpsProperty().asString("%d"));
+
+ Label labelMPF = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("mpf"));
+ Label mpf = new Label();
+ mpf.textProperty().bind(renderer.mpfProperty().asString("%.2f ms"));
+
+ setCenter(new BorderPane() {{
+ setTop(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.VALUE_FILL);
+ setVgap(6);
+ setHgap(6);
+ setPadding(new Insets(20));
+
+ addRow(0, labelStatus, status);
+ add(restart, 1, 1);
+ }});
+ setCenter(new BorderPane() {{
+ setBorder(Stroke.TOP);
+ setTop(labelDemo);
+ setCenter(new SplitPane() {{
+ setBorder(Stroke.TOP);
+ setDividerPositions(.5);
+ getItems().addAll(demo, new BorderPane() {{
+ setTop(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.VALUE_FILL);
+ setHgap(6);
+ setVgap(6);
+ setPadding(new Insets(8, 12, 8, 12));
+ setBorder(Stroke.BOTTOM);
+
+ int row = 0;
+ addRow(row++, labelPrefFPS, prefFPS);
+ addRow(row++, labelFPS, fps);
+ addRow(row, labelMPF, mpf);
+ }});
+ setCenter(box);
+ }});
+ }});
+ }});
+ }});
+
+ // ---------- 事件 ----------
+
+ restart.setOnAction(e -> {
+ // JRE
+ String jre = System.getProperty("java.home") + SEP + "bin" + SEP + "java";
+ // 启动参数
+ String param = " -Djavafx.animation.fullspeed=%s -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar ".formatted(!isFullSpeed);
+ // 启动 Jar
+ String jar = IO.getJarAbsolutePath(getClass());
+ // 重启
+ try {
+ TimiFX.doRestart(TimiFXExamples.getInjectApp().injector().di(Main.class), jre + param + jar);
+ } catch (Exception ex) {
+ AlertTips.error(getScene().getWindow(), TimiFXUI.MULTILINGUAL.text("tips.restart.error"));
+ }
+ });
+ }
+
+ @Override
+ protected void onShow() {
+ renderer.start();
+ }
+
+ @Override
+ protected void onHide() {
+ renderer.stop();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/BindingsConfigDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/BindingsConfigDemo.java
new file mode 100644
index 0000000..b3ff82a
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/BindingsConfigDemo.java
@@ -0,0 +1,103 @@
+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.examples.bean.Config;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.InvokeForInjected;
+import javafx.beans.binding.Bindings;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * 配置绑定示例
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:31
+ */
+@Component
+public class BindingsConfigDemo extends AbstractDemoPane {
+
+ @Inject
+ private Config config;
+
+ private final Label stageSize, interpolatorDuration;
+
+ public BindingsConfigDemo() {
+ title.setText(SidebarItem.BINDING_CONFIG.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/config/BindingsConfig.html");
+ 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");
+
+ // 窗体尺寸
+ stageSize = new Label();
+
+ // 动画插值器持续时间
+ interpolatorDuration = new Label();
+
+ // 代码
+ TextAreaEditor code = new TextAreaEditor();
+ code.setEditable(false);
+ code.setText("""
+// config 为 timi-java 的配置对象
+
+// 窗体尺寸,单向绑定
+BindingsConfig.cfg(config).bindDoubleProperty(stage.widthProperty(), Config.section("Main").key("Width"));
+BindingsConfig.cfg(config).bindDoubleProperty(stage.heightProperty(), Config.section("Main").key("Height"));
+
+// 对组件双向绑定,duration 为 Slider
+BindingsConfig.cfg(config).bindDoubleProperty(duration, Config.section("Interpolator").key("Duration"));
+ """.trim());
+
+ setCenter(new VBox() {{
+ setSpacing(12);
+ getChildren().addAll(new GridPane() {{
+ setVgap(4);
+ setHgap(6);
+ 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);
+ }}, new BorderPane() {{
+ setPadding(new Insets(20));
+ setTop(new TitleLabel(TimiFXUI.MULTILINGUAL.text("example")));
+ setCenter(new GridPane() {{
+ setVgap(4);
+ setHgap(6);
+ setPadding(new Insets(20));
+
+ addRow(0, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.binding_config.stage_size")), stageSize);
+ addRow(1, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.binding_config.interpolator_duration")), interpolatorDuration);
+ }});
+ 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);
+ }});
+ }});
+ }});
+ }
+
+ @InvokeForInjected
+ private void config() {
+ stageSize.textProperty().bind(Bindings.createStringBinding(() -> "[%s, %s]".formatted(config.getWidth().get(), config.getHeight().get()), config.getWidth(), config.getHeight()));
+ interpolatorDuration.textProperty().bind(config.getInterpolator().getDuration().asString());
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ExtendDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ExtendDemo.java
new file mode 100644
index 0000000..a4b2f31
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ExtendDemo.java
@@ -0,0 +1,118 @@
+package com.imyeyu.fx.ui.examples.view.pages;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+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;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Insets;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * 扩展类
+ *
+ * @author 夜雨
+ * @since 2022-09-06 00:07
+ */
+@Component
+public class ExtendDemo extends AbstractDemoPane {
+
+ public ExtendDemo() {
+ title.setText(SidebarItem.EXTEND_TOOLS.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/package-summary.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.tips"));
+ tips.setBorder(Stroke.BOTTOM);
+
+ setCenter(new ScrollPane() {{
+ setPadding(new Insets(20));
+ setFitToWidth(true);
+ setContent(new VBox() {{
+ setPadding(new Insets(32, 20, 10, 20));
+ setSpacing(6);
+ getChildren().addAll(new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.x_border"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/XBorder.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/XBorder.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.bg_fill"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/BgFill.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/BgFill.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.bg_image"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/BgImage.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/BgImage.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.column"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/Column.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/Column.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.row"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/Row.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/Row.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.directory_selector"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/DirectorySelector.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/DirectorySelector.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.file_selector"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/FileSelector.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/FileSelector.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.logic_bindings"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/LogicBindings.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/LogicBindings.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.no_selection_model"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/NoSelectionModel.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/NoSelectionModel.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.text_flower"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/TextFlower.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/TextFlower.java");
+ }}, new Item() {{
+ title.setText(TimiFXUI.MULTILINGUAL.text("fx.example.extend_tools.x_anchor_pane"));
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/extend/XAnchorPane.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/extend/XAnchorPane.java");
+ }});
+ }});
+ SmoothScroll.scrollPane(this);
+ }});
+ }
+
+ /**
+ * 列表项
+ *
+ * @author 夜雨
+ * @since 2022-09-06 02:17
+ */
+ private static class Item extends BorderPane {
+
+ /** 标题 */
+ final TitleLabel title = new TitleLabel();
+
+ /** 文档 */
+ final XHyperlink document = new XHyperlink();
+
+ /** 源码 */
+ final XHyperlink source = new XHyperlink();
+
+ public Item() {
+ setPadding(new Insets(0, 0, 32, 0));
+ setTop(title);
+ setCenter(new GridPane() {{
+ setHgap(6);
+ setVgap(4);
+ setPadding(new Insets(10, 0, 10, 20));
+
+ addRow(0, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("document")), document);
+ addRow(1, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("source")), source);
+ }});
+ }
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/PopupTipsDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/PopupTipsDemo.java
new file mode 100644
index 0000000..1093c3f
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/PopupTipsDemo.java
@@ -0,0 +1,77 @@
+package com.imyeyu.fx.ui.examples.view.pages;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.popup.PopupTipsService;
+import com.imyeyu.fx.ui.components.popup.tips.AbstractPopupTips;
+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.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.control.ListView;
+import javafx.scene.image.Image;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+
+/**
+ * 弹出提示
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:40
+ */
+@Component
+public class PopupTipsDemo extends AbstractDemoPane {
+
+ public PopupTipsDemo() {
+ title.setText(SidebarItem.POPUP_TIPS.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/popup/PopupTipsService.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/popup/AbstractPopupTips.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.popup_tips.tips"));
+
+ // 一般
+ Button text = new Button(TimiFXUI.MULTILINGUAL.text("text"));
+ PopupTipsService.installText(text, TimiFXUI.MULTILINGUAL.text("fx.example.demo.text"));
+
+ // 长文本
+ Button textLong = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.popup_tips.text_long"));
+ textLong.getStyleClass().add(CSS.BORDER_TRB);
+ PopupTipsService.installText(textLong, TimiFXUI.MULTILINGUAL.text("fx.example.demo.text_long"));
+
+ // 图片
+ Button image = new Button(TimiFXUI.MULTILINGUAL.text("image"));
+ image.getStyleClass().add(CSS.BORDER_TRB);
+ PopupTipsService.installImage(image, new Image(RESOURCE + "icon.png", 64, 64, false, false));
+
+ // 自定义
+ Button custom = new Button(TimiFXUI.MULTILINGUAL.text("custom"));
+ custom.getStyleClass().add(CSS.BORDER_TRB);
+
+ // 保持显示
+ Button keepShow = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.popup_tips.keep_show"));
+ keepShow.getStyleClass().add(CSS.BORDER_TRB);
+ {
+ ListView list = new ListView<>();
+ for (int i = 0; i < 20; i++) {
+ list.getItems().add("item " + i);
+ }
+ BorderPane customPane = new BorderPane();
+ BorderPane.setMargin(list, new Insets(12, 0, 0, 0));
+ customPane.setTop(new Button(TimiFXUI.MULTILINGUAL.text("custom")));
+ customPane.setCenter(list);
+ customPane.setPadding(new Insets(20));
+ customPane.setPrefHeight(320);
+ customPane.setBackground(BG.DEFAULT);
+ AbstractPopupTips customTips = new AbstractPopupTips<>(customPane);
+ PopupTipsService.installTips(custom, customTips);
+ PopupTipsService.installTips(keepShow, customTips);
+
+ keepShow.setOnAction(e -> customTips.setKeepShow(true));
+ }
+
+ setCenter(new VBox() {{
+ setPadding(new Insets(20));
+ getChildren().add(new HBox(text, textLong, image, custom, keepShow));
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/RunAsyncDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/RunAsyncDemo.java
new file mode 100644
index 0000000..0758b9e
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/RunAsyncDemo.java
@@ -0,0 +1,77 @@
+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;
+import com.imyeyu.utils.Time;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+
+/**
+ * 异步线程
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:37
+ */
+@Component
+public class RunAsyncDemo extends AbstractDemoPane {
+
+ public RunAsyncDemo() {
+ title.setText(SidebarItem.RUN_ASYNC.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/service/RunAsync.java");
+
+ 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);
+ code.setText("""
+// 每秒执行一次并返回,参数 3 (FX 线程)的入参是参数 2 (非 FX 线程)的返回
+// RunAsyncScheduled.call(Duration.seconds(1), () -> "result", result -> {});
+
+RunAsyncScheduled.call(Duration.seconds(1), () -> {
+ long now = System.currentTimeMillis();
+ return now;
+}, millis -> {
+ String datetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(millis);
+ label.setText(datetime);
+});
+ """.trim());
+
+ setCenter(new BorderPane() {{
+ setMargin(code, new Insets(4, 0, 0, 0));
+ setPadding(new Insets(20));
+ setTop(new VBox() {{
+ setMargin(time, new Insets(20, 0, 0, 0));
+ setSpacing(4);
+ getChildren().addAll(title, tips0, tips1, time, description);
+ }});
+ setCenter(code);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/Style.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/Style.java
new file mode 100644
index 0000000..1f3ccec
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/Style.java
@@ -0,0 +1,188 @@
+package com.imyeyu.fx.ui.examples.view.pages;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+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;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.effect.DropShadow;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Paint;
+
+/**
+ * 主题样式
+ *
+ * @author 夜雨
+ * @since 2022-08-26 15:44
+ */
+@Component
+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");
+ 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");
+ Label labelTimiFXDescription = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("description"));
+ Label timiFXDescription = new Label(TimiFXUI.MULTILINGUAL.text("fx.example.style.timifx"));
+
+ // 标题颜色
+ TitleLabel titleColor = new TitleLabel(TimiFXUI.MULTILINGUAL.text("color"));
+
+ // 聚焦颜色
+ Label labelFocusColor = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("focus"));
+ Label colorFocus = colorLabel(TimiFXUI.MULTILINGUAL.text("default"), Colorful.FOCUSED_DEFAULT);
+ colorFocus.setTextFill(Colorful.WHITE);
+ Label colorFocusLight = colorLabel(TimiFXUI.MULTILINGUAL.text("light"), Colorful.FOCUSED_LIGHT);
+ Label colorFocusDark = colorLabel(TimiFXUI.MULTILINGUAL.text("dark"), Colorful.FOCUSED_DARK);
+ colorFocusDark.setTextFill(Colorful.WHITE);
+
+ // 图标颜色
+ Label labelIconColor = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("icon"));
+ Label colorIcon = colorLabel(TimiFXUI.MULTILINGUAL.text("default"), Colorful.ICON);
+ colorIcon.setTextFill(Colorful.WHITE);
+ Label colorIconHover = colorLabel(TimiFXUI.MULTILINGUAL.text("hover"), Colorful.ICON_HOVER);
+
+ // 边框颜色
+ Label labelBorderColor = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("border"));
+ Label colorBorder = colorLabel(TimiFXUI.MULTILINGUAL.text("default"), Colorful.BORDER);
+ Label colorBorderDisable = colorLabel(TimiFXUI.MULTILINGUAL.text("disable"), Paint.valueOf("#E1E1E1"));
+
+ // 背景颜色
+ Label labelBackgroundColor = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("background"));
+ Label backgroundColor = colorLabel(TimiFXUI.MULTILINGUAL.text("default"), BG.DEFAULT);
+ backgroundColor.setBorder(Stroke.DEFAULT);
+ Label backgroundColorTitle = colorLabel(TimiFXUI.MULTILINGUAL.text("title"), BG.TITLE);
+ backgroundColorTitle.setPrefWidth(100);
+ Label backgroundColorTitleFill = colorLabel(TimiFXUI.MULTILINGUAL.text("fx.example.style.color.bg.title_fill"), BG.TITLE_FILL);
+ backgroundColorTitleFill.setPrefWidth(100);
+
+ // 投影
+ TitleLabel titleShadow = new TitleLabel(TimiFXUI.MULTILINGUAL.text("shadow"));
+ Label shadowPopup = shadowLabel(TimiFXUI.MULTILINGUAL.text("fx.example.style.shadow.popup"), Shadow.POPUP);
+ Label shadowImage = shadowLabel(TimiFXUI.MULTILINGUAL.text("image"), Shadow.IMAGE);
+ Label shadowDown = shadowLabel(TimiFXUI.MULTILINGUAL.text("fx.example.style.shadow.down"), Shadow.DOWN);
+
+ // 样式文件
+ 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");
+
+ setCenter(new VBox() {{
+ setSpacing(16);
+ setPadding(new Insets(20));
+
+ final Insets CONTENT_PADDING = new Insets(10, 20, 4, 20);
+ getChildren().addAll(new BorderPane() {{
+ setTop(titleTimiFX);
+ setCenter(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.VALUE_FILL);
+ setVgap(8);
+ setHgap(10);
+ setPadding(CONTENT_PADDING);
+
+ int row = 0;
+ addRow(row++, labelTimiFXDocument, timiFXDocument);
+ addRow(row++, labelTimiFXSource, timiFXSource);
+ addRow(row, labelTimiFXDescription, timiFXDescription);
+ }});
+ }}, new BorderPane() {{
+ setTop(titleColor);
+ setCenter(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.VALUE_FILL);
+ setVgap(8);
+ setHgap(10);
+ setPadding(CONTENT_PADDING);
+
+ int row = 0;
+ addRow(row++, labelFocusColor, new HBox() {{
+ setSpacing(8);
+ getChildren().addAll(colorFocus, colorFocusLight, colorFocusDark);
+ }});
+ addRow(row++, labelIconColor, new HBox() {{
+ setSpacing(8);
+ getChildren().addAll(colorIcon, colorIconHover);
+ }});
+ addRow(row++, labelBorderColor, new HBox() {{
+ setSpacing(8);
+ getChildren().addAll(colorBorder, colorBorderDisable);
+ }});
+ addRow(row, labelBackgroundColor, new HBox() {{
+ setSpacing(8);
+ getChildren().addAll(backgroundColor, backgroundColorTitle, backgroundColorTitleFill);
+ }});
+ }});
+ }}, new BorderPane() {{
+ setTop(titleShadow);
+ setCenter(new HBox() {{
+ BorderPane.setMargin(this, CONTENT_PADDING);
+ setPadding(new Insets(20));
+ setSpacing(24);
+ setBorder(Stroke.DEFAULT);
+ setBackground(BG.WHITE);
+ getChildren().addAll(shadowPopup, shadowImage, shadowDown);
+ }});
+ }}, new BorderPane() {{
+ setTop(styleFile);
+ setCenter(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.VALUE_FILL);
+ setVgap(8);
+ setHgap(10);
+ setPadding(CONTENT_PADDING);
+ addRow(0, labelFile, file);
+ }});
+ }});
+ }});
+ }
+
+ /**
+ * 构造背景颜色标签
+ *
+ * @param text 文本
+ * @param color 颜色
+ * @return 标签
+ */
+ private Label colorLabel(String text, Paint color) {
+ return colorLabel(text, new BgFill(color).build());
+ }
+
+ /**
+ * 构造背景颜色标签
+ *
+ * @param text 文本
+ * @param color 背景颜色
+ * @return 标签
+ */
+ private Label colorLabel(String text, Background color) {
+ Label label = new Label(text);
+ label.setBorder(Stroke.TP);
+ label.setPadding(new Insets(4, 16, 4, 16));
+ label.setBackground(color);
+ return label;
+ }
+
+ /**
+ * 构造投影标签
+ *
+ * @param text 文本
+ * @param shadow 投影
+ * @return 标签
+ */
+ private Label shadowLabel(String text, DropShadow shadow) {
+ Label label = new Label(text);
+ label.setEffect(shadow);
+ label.setBorder(Stroke.DEFAULT);
+ label.setPadding(new Insets(4, 16, 4, 16));
+ label.setBackground(BG.DEFAULT);
+ return label;
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/Welcome.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/Welcome.java
new file mode 100644
index 0000000..ad9ee23
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/Welcome.java
@@ -0,0 +1,174 @@
+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;
+import com.imyeyu.fx.ui.examples.bean.Config;
+import com.imyeyu.fx.ui.examples.component.AbstractPane;
+import com.imyeyu.fx.ui.examples.component.TimiVersionLabel;
+import com.imyeyu.fx.ui.examples.ctrl.Main;
+import com.imyeyu.fx.ui.examples.util.Resources;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.inject.TimiInject;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.InvokeForInjected;
+import com.imyeyu.io.IO;
+import com.imyeyu.java.bean.Language;
+import com.imyeyu.utils.OS;
+import com.imyeyu.utils.Time;
+import javafx.geometry.HPos;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Label;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.TextAlignment;
+import javafx.stage.Stage;
+import javafx.util.StringConverter;
+
+import java.util.Date;
+
+/**
+ * 欢迎页
+ *
+ * @author 夜雨
+ * @since 2022-08-26 14:55
+ */
+@Component
+public class Welcome extends AbstractPane implements OS.FileSystem {
+
+ @Inject
+ private Config config;
+
+ @Inject
+ private Stage stage;
+
+ private final ComboBox language;
+
+ public Welcome() {
+ Label title = new Label(TimiFXUI.MULTILINGUAL.text("fx.example.title"), new ImageView(Resources.ICON_X64));
+ title.setMaxWidth(Double.MAX_VALUE);
+ title.setAlignment(Pos.CENTER);
+ title.setBackground(BG.TITLE_FILL);
+ title.setBorder(Stroke.BOTTOM);
+ 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"));
+ tips.setAlignment(Pos.CENTER);
+
+ // 语言
+ Label labelLang = new Label(TimiFXUI.MULTILINGUAL.text("lang"));
+ labelLang.setTextFill(Colorful.GRAY);
+
+ language = new ComboBox<>();
+ language.getItems().addAll(Language.values());
+ language.setConverter(new StringConverter<>() {
+
+ @Override
+ public String toString(Language language) {
+ return language.getName();
+ }
+
+ @Override
+ public Language fromString(String string) {
+ return null;
+ }
+ });
+
+ // 版权
+ TextFlower license = new TextFlower().matcher(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.license"));
+ license.setTextAlignment(TextAlignment.CENTER);
+
+ // 开发者
+ Label develop = new Label(TimiFXUI.MULTILINGUAL.textArgs("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.textArgs("copyright", "夜雨", Time.yearFull.format(new Date())));
+ copyright.setAlignment(Pos.CENTER);
+ Label versionTimiFX = new Label(TimiFXUI.MULTILINGUAL.textArgs("fx.example.welcome.version.timifx", "0.0.1"));
+ TimiVersionLabel version = new TimiVersionLabel(TimiFXExamples.VERSION, TimiFXExamples.class.getSimpleName()) {{
+ version.setGraphic(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.version")));
+ }};
+
+ setTop(title);
+ setCenter(new VBox() {{
+ setSpacing(20);
+ setPadding(new Insets(20));
+ setAlignment(Pos.TOP_CENTER);
+ getChildren().addAll(docSource, 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);
+ setVgap(6);
+ setPadding(new Insets(12, 0, 4, 0));
+
+ int row = 0;
+ addRow(row++, new Label("timi-java"), TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.timijava")));
+ addRow(row, new Label("timi-fx-icon"), TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.welcome.timifx_icon")));
+ }});
+ }});
+ }});
+ setBottom(new VBox() {{
+ setSpacing(6);
+ setPadding(new Insets(16));
+ setAlignment(Pos.CENTER);
+ getChildren().addAll(new HBox() {{
+ setSpacing(6);
+ setAlignment(Pos.CENTER);
+ getChildren().addAll(TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("lang")), language);
+ }});
+ getChildren().addAll(versionTimiFX, version, develop, license, blog, copyright);
+ }});
+ }
+
+ @InvokeForInjected
+ private void injected() {
+ language.valueProperty().bind(config.getLanguage());
+
+ // 修改语言
+ language.valueProperty().addListener((obs, o, newLang) -> {
+ new AlertConfirm(TimiFXUI.MULTILINGUAL.text("tips.restart")) {
+
+ @Override
+ protected void onConfirm() {
+ // JRE
+ String jre = System.getProperty("java.home") + SEP + "bin" + SEP + "java";
+ // 启动参数
+ String param = " -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar ";
+ // 启动 Jar
+ String jar = IO.getJarAbsolutePath(getClass());
+ // 重启
+ try {
+ TimiFX.doRestart(TimiFXExamples.getInjectApp().injector().di(Main.class), jre + param + jar);
+ } catch (Exception ex) {
+ AlertTips.error(this, TimiFXUI.MULTILINGUAL.text("tips.restart.error"));
+ }
+ }
+ }.showRelativeCenter(getScene().getWindow());
+ });
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/animation/InterpolatorDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/animation/InterpolatorDemo.java
new file mode 100644
index 0000000..0208de3
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/animation/InterpolatorDemo.java
@@ -0,0 +1,132 @@
+package com.imyeyu.fx.ui.examples.view.pages.animation;
+
+import com.imyeyu.fx.bean.Interpolates;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.bean.Config;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.animation.InterpolatorPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.SmoothScroll;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.InvokeForInjected;
+import javafx.beans.binding.Bindings;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.Slider;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+import javafx.util.StringConverter;
+
+/**
+ * 动画插值器
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:29
+ */
+@Component
+public class InterpolatorDemo extends AbstractDemoPane {
+
+ @Inject
+ private Config config;
+
+ private final Slider duration;
+
+ public InterpolatorDemo() {
+ title.setText(SidebarItem.INTERPOLATOR.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/bean/Interpolators.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/bean/Interpolators.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.interpolator.tips"));
+
+ // 播放
+ Button play = new Button(TimiFXUI.MULTILINGUAL.text("play"));
+
+ // 重置
+ Button reset = new Button(TimiFXUI.MULTILINGUAL.text("reset"));
+ reset.getStyleClass().add(CSS.BORDER_TRB);
+
+ // 持续时间
+ duration = new Slider();
+ duration.setMin(0);
+ duration.setMax(3000);
+ duration.setPrefWidth(460);
+ duration.setSnapToTicks(true);
+ duration.setMajorTickUnit(400);
+ duration.setShowTickMarks(true);
+ duration.setMinorTickCount(7);
+ duration.setShowTickLabels(true);
+ duration.setLabelFormatter(new StringConverter<>() {
+
+ @Override
+ public String toString(Double ms) {
+ return "%.0f".formatted(ms);
+ }
+
+ @Override
+ public Double fromString(String string) {
+ return null;
+ }
+ });
+ Label value = TimiFXUI.label();
+ value.textProperty().bind(duration.valueProperty().asString("%.0f ms"));
+
+ // 插值器列表
+ Interpolates[] interpolators = Interpolates.values();
+ InterpolatorPane[] panes = new InterpolatorPane[interpolators.length];
+ for (int i = 0; i < interpolators.length; i++) {
+ panes[i] = new InterpolatorPane(interpolators[i]);
+ panes[i].getTransition().durationProperty().bind(Bindings.createObjectBinding(() -> Duration.millis(duration.getValue()), duration.valueProperty()));
+ }
+
+ setCenter(new BorderPane() {{
+ setTop(new HBox() {{
+ setSpacing(32);
+ setPadding(new Insets(20, 20, 10, 20));
+ getChildren().addAll(new HBox(play, reset), new HBox() {{
+ setPadding(new Insets(5, 0, 0, 0));
+ setSpacing(16);
+ getChildren().addAll(TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("duration")), duration, value);
+ }});
+ }});
+ setCenter(new ScrollPane() {{
+ getStyleClass().add(CSS.SP_BORDER);
+ setBorder(Stroke.TOP);
+ setFitToWidth(true);
+ setContent(new VBox() {{
+ setSpacing(32);
+ setFillWidth(false);
+ setAlignment(Pos.TOP_CENTER);
+ setPadding(new Insets(12, 0, 12, 0));
+ getChildren().addAll(panes);
+ }});
+ SmoothScroll.scrollPane(this);
+ }});
+ }});
+
+ // ---------- 事件 ----------
+
+ // 播放
+ play.setOnAction(e -> {
+ for (int i = 0; i < panes.length; i++) {
+ panes[i].getTransition().play();
+ }
+ });
+
+ // 重置
+ reset.setOnAction(e -> {
+ for (int i = 0; i < panes.length; i++) {
+ panes[i].reset();
+ }
+ });
+ }
+
+ @InvokeForInjected
+ private void config() {
+ duration.valueProperty().bindBidirectional(config.getInterpolator().getDuration());
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/animation/SmoothScrollDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/animation/SmoothScrollDemo.java
new file mode 100644
index 0000000..24468e3
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/animation/SmoothScrollDemo.java
@@ -0,0 +1,134 @@
+package com.imyeyu.fx.ui.examples.view.pages.animation;
+
+import com.imyeyu.fx.task.RunAsyncScheduled;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.fx.utils.SmoothScroll;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.utils.Calc;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.geometry.HPos;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+
+/**
+ * 平滑滚动
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:29
+ */
+@Component
+public class SmoothScrollDemo extends AbstractDemoPane {
+
+ public SmoothScrollDemo() {
+ title.setText(SidebarItem.SMOOTH_SCROLL.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/util/SmoothScroll.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/util/SmoothScroll.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.smooth_scroll.tips"));
+
+ // 下拉框
+ ComboBox comboBoxDef = new ComboBox<>();
+ ComboBox comboBox = new ComboBox<>();
+ SmoothScroll.comboBox(comboBox);
+
+ // 滚动面板
+ VBox spDefVB = new VBox();
+ VBox spVB = new VBox();
+ ScrollPane scrollPaneDef = new ScrollPane(spDefVB);
+ ScrollPane scrollPane = new ScrollPane(spVB);
+ scrollPaneDef.setBorder(Stroke.LT);
+ scrollPane.setBorder(Stroke.LT);
+ SmoothScroll.scrollPane(scrollPane);
+
+ // 文本域
+ TextArea textAreaDef = new TextArea();
+ TextArea textArea = new TextArea();
+ textAreaDef.getStyleClass().add(CSS.BORDER_LT);
+ textArea.getStyleClass().add(CSS.BORDER_LT);
+ SmoothScroll.textarea(textArea);
+
+ // 列表
+ ListView listViewDef = new ListView<>();
+ ListView listView = new ListView<>();
+ listViewDef.getStyleClass().add(CSS.BORDER_LT);
+ listView.getStyleClass().add(CSS.BORDER_LT);
+ SmoothScroll.virtual(listView);
+
+ // 表格
+ TableView tableViewDef = new TableView<>();
+ TableView tableView = new TableView<>();
+ tableViewDef.getStyleClass().add(CSS.BORDER_LT);
+ tableView.getStyleClass().add(CSS.BORDER_LT);
+ TableColumn col = new TableColumn<>("col");
+ col.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue()));
+ tableViewDef.getColumns().add(col);
+ tableView.getColumns().add(col);
+ SmoothScroll.virtual(tableView);
+
+ setCenter(new GridPane() {{
+ Column key = Column.build(HPos.RIGHT).percentWidth(10);
+ Column value = Column.build(HPos.CENTER).percentWidth(18).fill();
+ getColumnConstraints().addAll(key, value, value, value, value, value);
+
+ add(new StackPane() {{
+ setBorder(Stroke.TOP);
+ setPadding(new Insets(0, 6, 0, 0));
+ setAlignment(Pos.CENTER_RIGHT);
+ getChildren().add(new Label(TimiFXUI.MULTILINGUAL.text("default")));
+ }}, 0, 1, 1, 1);
+ add(new StackPane() {{
+ setBorder(Stroke.TOP);
+ setPadding(new Insets(0, 6, 0, 0));
+ setAlignment(Pos.CENTER_RIGHT);
+ getChildren().add(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.smooth_scroll")));
+ }}, 0, 2, 1, 1);
+
+ int col = 1;
+ addColumn(col++, new Label("ComboBox"), new StackPane() {{
+ setBorder(Stroke.LT);
+ getChildren().add(comboBoxDef);
+ }}, new StackPane() {{
+ setBorder(Stroke.LT);
+ getChildren().add(comboBox);
+ }});
+ addColumn(col++, new Label("ScrollPane"), scrollPaneDef, scrollPane);
+ addColumn(col++, new Label("TextArea"), textAreaDef, textArea);
+ addColumn(col++, new Label("ListView"), listViewDef, listView);
+ addColumn(col, new Label("TableView"), tableViewDef, tableView);
+ }});
+
+ String text;
+ for (int i = 0; i < 50; i++) {
+ text = "item " + i;
+
+ comboBoxDef.getItems().add(text);
+ comboBox.getItems().add(text);
+ spDefVB.getChildren().add(new Label(text));
+ spVB.getChildren().add(new Label(text));
+ textAreaDef.appendText(text + "\r\n");
+ textArea.appendText(text + "\r\n");
+ listViewDef.getItems().add(text);
+ listView.getItems().add(text);
+ tableViewDef.getItems().add(text);
+ tableView.getItems().add(text);
+ }
+
+ comboBoxDef.getSelectionModel().select(0);
+ comboBox.getSelectionModel().select(0);
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/CheckBoxPickerDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/CheckBoxPickerDemo.java
new file mode 100644
index 0000000..0bf309d
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/CheckBoxPickerDemo.java
@@ -0,0 +1,51 @@
+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 picker = new CheckBoxPicker<>();
+ picker.setPrefWidth(390);
+ picker.getSelectedItems().addListener((ListChangeListener) c -> {
+ List 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.textArgs("fx.example.check_box_picker.item", i));
+ }
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/DateTimePickerDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/DateTimePickerDemo.java
new file mode 100644
index 0000000..2cfe980
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/DateTimePickerDemo.java
@@ -0,0 +1,39 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.components.DateTimePicker;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.utils.Time;
+import javafx.geometry.Insets;
+import javafx.scene.layout.FlowPane;
+
+/**
+ * 时间日期选择器
+ *
+ * @author 夜雨
+ * @since 2022-08-29 16:17
+ */
+@Component
+public class DateTimePickerDemo extends AbstractDemoPane {
+
+ private final DateTimePicker picker;
+
+ public DateTimePickerDemo() {
+ title.setText(SidebarItem.DATE_TIME_PICKER.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/DateTimePicker.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/DateTimePicker.java");
+
+ picker = new DateTimePicker();
+
+ setCenter(new FlowPane() {{
+ setPadding(new Insets(20));
+ getChildren().add(picker);
+ }});
+ }
+
+ @Override
+ protected void onShow() {
+ picker.setValue(Time.now());
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/EditableTableCellDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/EditableTableCellDemo.java
new file mode 100644
index 0000000..1614728
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/EditableTableCellDemo.java
@@ -0,0 +1,111 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.inject.annotation.Component;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.scene.control.cell.TextFieldTableCell;
+import javafx.scene.layout.GridPane;
+
+/**
+ * 可编辑表格单元格
+ *
+ * @author 夜雨
+ * @since 2022-08-29 16:56
+ */
+@Component
+public class EditableTableCellDemo extends AbstractDemoPane {
+
+ public EditableTableCellDemo() {
+ title.setText(SidebarItem.EDITABLE_TABLE_CELL.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/EditableTableCell.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/EditableTableCell.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.editable_table_cell.tips"));
+
+ // 默认
+ Label labelDef = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("default"));
+ labelDef.setAlignment(Pos.CENTER);
+ TableColumn- defCol = new TableColumn<>("col");
+ defCol.setCellFactory(TextFieldTableCell.forTableColumn());
+ defCol.setCellValueFactory(new PropertyValueFactory<>("value"));
+ TableView
- def = new TableView<>();
+ def.setEditable(true);
+ def.getColumns().add(TimiFXUI.emptyTableColumn(40, Item.class, Object.class));
+ def.getColumns().add(defCol);
+ defCol.prefWidthProperty().bind(def.widthProperty().subtract(50));
+
+ // TFX
+ Label labelTFX = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.better"));
+ labelTFX.setAlignment(Pos.CENTER);
+ TableColumn
- tfxCol = new TableColumn<>("col");
+ tfxCol.setCellValueFactory(new PropertyValueFactory<>("value"));
+ tfxCol.setCellFactory(cell -> new com.imyeyu.fx.ui.components.table.TextFieldTableCell<>() {
+
+ @Override
+ protected StringProperty property(Item item) {
+ return item.value;
+ }
+ });
+ TableView
- tfx = new TableView<>();
+ tfx.setEditable(true);
+ tfx.getStyleClass().add("editable-table");
+ tfx.getColumns().add(TimiFXUI.emptyTableColumn(40, Item.class, Object.class));
+ tfx.getColumns().add(tfxCol);
+ tfxCol.prefWidthProperty().bind(tfx.widthProperty().subtract(50));
+
+ setCenter(new GridPane() {{
+ getColumnConstraints().addAll(Column.VALUE_FILL, Column.VALUE_FILL);
+ setVgap(6);
+ setHgap(20);
+ setPadding(new Insets(32));
+
+ addRow(0, labelDef, labelTFX);
+ addRow(1, def, tfx);
+ }});
+
+ for (int i = 0; i < 32; i++) {
+ def.getItems().add(new Item(String.valueOf(i)));
+ tfx.getItems().add(new Item(String.valueOf(i)));
+ }
+ }
+
+ /**
+ * 数据对象
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:10
+ */
+ public static class Item {
+
+ /** 可监听值 */
+ StringProperty value;
+
+ public Item(String value) {
+ this.value = new SimpleStringProperty(value);
+ }
+
+ /** @param value 设置当前值 */
+ public void setValue(String value) {
+ this.value.set(value);
+ }
+
+ /** @return 当前值 */
+ public String getValue() {
+ return value.get();
+ }
+
+ /** @return 值监听 */
+ public StringProperty valueProperty() {
+ return value;
+ }
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/FileTreeViewDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/FileTreeViewDemo.java
new file mode 100644
index 0000000..9ce664d
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/FileTreeViewDemo.java
@@ -0,0 +1,69 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.FileTreeView;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.fx.utils.Row;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.layout.GridPane;
+
+import java.io.File;
+
+/**
+ * 文件树
+ *
+ * @author 夜雨
+ * @since 2022-08-29 17:38
+ */
+@Component
+public class FileTreeViewDemo extends AbstractDemoPane {
+
+ public FileTreeViewDemo() {
+ title.setText(SidebarItem.FILE_TREE_VIEW.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/FileTreeView.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/FileTreeView.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.file_tree_view.tips"));
+
+ // 一般
+ Label labelDefault = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("default"));
+ labelDefault.setAlignment(Pos.CENTER);
+ FileTreeView def = new FileTreeView();
+ def.setBorder(Stroke.DEFAULT);
+
+ // 文件夹
+ Label labelDirectory = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.file_tree_view.directory"));
+ labelDirectory.setAlignment(Pos.CENTER);
+ FileTreeView dir = new FileTreeView();
+ dir.setBorder(Stroke.EX_LEFT);
+ dir.addItemFilter(File::isDirectory);
+
+ // 自定义过滤
+ Label labelFilter = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.file_tree_view.filter"));
+ labelFilter.setAlignment(Pos.CENTER);
+ FileTreeView filter = new FileTreeView();
+ filter.setBorder(Stroke.EX_LEFT);
+ filter.addItemFilter(file -> file.isDirectory() || file.getName().endsWith(".txt"));
+
+ // 显示隐藏文件
+ Label labelHide = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.file_tree_view.hide"));
+ labelHide.setAlignment(Pos.CENTER);
+ FileTreeView hide = new FileTreeView();
+ hide.setBorder(Stroke.EX_LEFT);
+ hide.setShowHide(true);
+
+ setCenter(new GridPane() {{
+ getColumnConstraints().addAll(Column.VALUE_FILL, Column.VALUE_FILL, Column.VALUE_FILL, Column.VALUE_FILL);
+ getRowConstraints().addAll(Row.build(), Row.build().alwaysPriority());
+ setVgap(5);
+ setPadding(new Insets(32));
+
+ addRow(0, labelDefault, labelDirectory, labelFilter, labelHide);
+ addRow(1, def, dir, filter, hide);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/IconButtonDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/IconButtonDemo.java
new file mode 100644
index 0000000..7d69d8d
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/IconButtonDemo.java
@@ -0,0 +1,50 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.icon.TimiFXIcon;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.IconButton;
+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.geometry.Insets;
+import javafx.scene.layout.GridPane;
+
+/**
+ * 图标按钮
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:48
+ */
+@Component
+public class IconButtonDemo extends AbstractDemoPane {
+
+ public IconButtonDemo() {
+ title.setText(SidebarItem.ICON_BUTTON.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/IconButton.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/IconButton.java");
+
+ // 一般
+ IconButton commonly = new IconButton(TimiFXUI.MULTILINGUAL.text("fx.example.icon_button"), TimiFXIcon.fromName("HAMMER"));
+
+ // 按钮背景
+ IconButton commonlyWithBG = new IconButton(TimiFXUI.MULTILINGUAL.text("fx.example.icon_button"), TimiFXIcon.fromName("HAMMER")).withBackground();
+
+ // 无文本
+ IconButton notText = new IconButton(TimiFXIcon.fromName("HAMMER"));
+
+ // 无文本按钮背景
+ IconButton notTextWithBG = new IconButton(TimiFXIcon.fromName("HAMMER")).withBackground();
+
+ setCenter(new GridPane() {{
+ setVgap(4);
+ setHgap(6);
+ setPadding(new Insets(20));
+
+ int row = 0;
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("commonly")), commonly);
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.icon_button.with_bg")), commonlyWithBG);
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.icon_button.not_text")), notText);
+ addRow(row, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.icon_button.not_text_with_bg")), notTextWithBG);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/IconPickerDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/IconPickerDemo.java
new file mode 100644
index 0000000..bda94d3
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/IconPickerDemo.java
@@ -0,0 +1,47 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+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;
+import javafx.geometry.Insets;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.TextFlow;
+
+/**
+ * 图标选择
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:48
+ */
+@Component
+public class IconPickerDemo extends AbstractDemoPane {
+
+ public IconPickerDemo() {
+ title.setText(SidebarItem.ICON_PICKER.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/IconPicker.html");
+ 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");
+
+ if (getTop() instanceof VBox top) {
+ top.getChildren().add(new TextFlow() {{
+ setPadding(new Insets(3, 6, 3, 6));
+ getChildren().addAll(TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.icon_picker.tips")), index);
+ }});
+ }
+
+ // 选择器
+ IconPicker picker = new IconPicker();
+ picker.setPrefWidth(260);
+
+ setCenter(new FlowPane() {{
+ setPadding(new Insets(20));
+ getChildren().add(picker);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/LabelProgressBarDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/LabelProgressBarDemo.java
new file mode 100644
index 0000000..f1076e5
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/LabelProgressBarDemo.java
@@ -0,0 +1,63 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.LabelProgressBar;
+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.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.geometry.Insets;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+
+/**
+ * 标签进度
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:49
+ */
+@Component
+public class LabelProgressBarDemo extends AbstractDemoPane {
+
+ private final Timeline tl;
+
+ public LabelProgressBarDemo() {
+ title.setText(SidebarItem.LABEL_PROGRESS_BAR.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/LabelProgressBar.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/LabelProgressBar.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.label_progress_bar.tips"));
+
+ // 静态
+ LabelProgressBar text = new LabelProgressBar();
+ text.setText(TimiFXUI.MULTILINGUAL.text("fx.example.label_progress_bar"));
+ text.setMaxWidth(Double.MAX_VALUE);
+
+ // 动态
+ LabelProgressBar dynamic = new LabelProgressBar();
+ dynamic.textProperty().bind(dynamic.progressProperty().multiply(100).asString("%.2f %%"));
+ dynamic.setMaxWidth(Double.MAX_VALUE);
+ tl = new Timeline();
+ tl.getKeyFrames().add(new KeyFrame(Duration.ZERO, new KeyValue(dynamic.progressProperty(), 0)));
+ tl.getKeyFrames().add(new KeyFrame(Duration.seconds(10), new KeyValue(dynamic.progressProperty(), 1)));
+ tl.setCycleCount(Timeline.INDEFINITE);
+ tl.play();
+
+ setCenter(new VBox() {{
+ setSpacing(6);
+ setPadding(new Insets(20));
+ getChildren().addAll(text, dynamic);
+ }});
+ }
+
+ @Override
+ protected void onShow() {
+ tl.play();
+ }
+
+ @Override
+ protected void onHide() {
+ tl.pause();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/NavigationDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/NavigationDemo.java
new file mode 100644
index 0000000..1550bf6
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/NavigationDemo.java
@@ -0,0 +1,79 @@
+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.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.fx.utils.Row;
+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;
+
+/**
+ * 导航组件
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:49
+ */
+@Component
+public class NavigationDemo extends AbstractDemoPane {
+
+ public NavigationDemo() {
+ title.setText(SidebarItem.NAVIGATION.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/Navigation.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/Navigation.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.navigation.tips"));
+
+ // 一般
+ 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"));
+
+ // 二级
+ 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"));
+
+ // 默认展开
+ 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"));
+
+ // 多个二级
+ 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"));
+
+ setCenter(new GridPane() {{
+ Column col = Column.build().width(120);
+ getColumnConstraints().addAll(col, col, col, col);
+ getRowConstraints().addAll(Row.build(), Row.build().alwaysPriority());
+ setVgap(5);
+ setHgap(5);
+ setPadding(new Insets(32));
+
+ addRow(0, labelCommonly, labelSecond, labelExpanded, labelMulti);
+ addRow(1, commonly, second, expanded, multi);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/NumberFieldDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/NumberFieldDemo.java
new file mode 100644
index 0000000..27ded3b
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/NumberFieldDemo.java
@@ -0,0 +1,35 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.components.NumberField;
+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.geometry.Insets;
+import javafx.scene.control.Slider;
+import javafx.scene.layout.FlowPane;
+
+/**
+ * 数字输入
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:49
+ */
+@Component
+public class NumberFieldDemo extends AbstractDemoPane {
+
+ public NumberFieldDemo() {
+ title.setText(SidebarItem.NUMBER_FIELD.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/NumberField.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/NumberField.java");
+
+ NumberField numberField = new NumberField();
+ numberField.setPrefWidth(220);
+
+ setCenter(new FlowPane() {{
+ setPadding(new Insets(20));
+ getChildren().addAll(numberField, new Slider(0, 100, 50) {{
+ numberField.valueProperty().bindBidirectional(valueProperty());
+ }});
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/ProgressSliderDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/ProgressSliderDemo.java
new file mode 100644
index 0000000..4f66350
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/ProgressSliderDemo.java
@@ -0,0 +1,41 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.ProgressSlider;
+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.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+
+/**
+ * 拖拽进度组件
+ *
+ * @author 夜雨
+ * @since 2022-08-30 09:36
+ */
+@Component
+public class ProgressSliderDemo extends AbstractDemoPane {
+
+ public ProgressSliderDemo() {
+ title.setText(SidebarItem.PROGRESS_SLIDER.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/ProgressSlider.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/ProgressSlider.java");
+
+ // 组件
+ ProgressSlider slider = new ProgressSlider();
+ slider.setPrefWidth(360);
+ slider.setValue(.5);
+
+ // 当前值
+ Label value = TimiFXUI.label();
+ value.textProperty().bind(slider.valueProperty().asString("%.2f"));
+
+ setCenter(new FlowPane() {{
+ setHgap(5);
+ setPadding(new Insets(20));
+ getChildren().addAll(slider, value);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/SelectableLabelDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/SelectableLabelDemo.java
new file mode 100644
index 0000000..12ce120
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/SelectableLabelDemo.java
@@ -0,0 +1,48 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.SelectableLabel;
+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.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.control.Slider;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+
+/**
+ * 可选标签
+ *
+ * @author 夜雨
+ * @since 2022-08-29 21:50
+ */
+@Component
+public class SelectableLabelDemo extends AbstractDemoPane {
+
+ public SelectableLabelDemo() {
+ title.setText(SidebarItem.SELECTABLE_LABEL.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/SelectableLabel.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/SelectableLabel.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.selectable_label.tips"));
+
+ // 宽度调整
+ Label labelWidth = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("width"));
+ Slider width = new Slider();
+ width.setValue(50);
+
+ // 标签
+ SelectableLabel selectableLabel = new SelectableLabel(TimiFXUI.MULTILINGUAL.text("fx.example.demo.text_long"));
+ selectableLabel.setMinWidth(64);
+ selectableLabel.maxWidthProperty().bind(widthProperty().subtract(40).multiply(width.valueProperty().multiply(.01)));
+
+ setCenter(new VBox() {{
+ setSpacing(32);
+ setPadding(new Insets(20));
+ getChildren().addAll(new HBox() {{
+ setSpacing(5);
+ getChildren().addAll(labelWidth, width);
+ }}, selectableLabel);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/TextAreaEditorDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/TextAreaEditorDemo.java
new file mode 100644
index 0000000..92c294c
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/TextAreaEditorDemo.java
@@ -0,0 +1,63 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.TextAreaEditor;
+import com.imyeyu.fx.ui.components.TextAreaEditorField;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * 文本编辑器
+ *
+ * @author 夜雨
+ * @since 2022-08-30 09:32
+ */
+@Component
+public class TextAreaEditorDemo extends AbstractDemoPane {
+
+ public TextAreaEditorDemo() {
+ title.setText(SidebarItem.TEXT_AREA_EDITOR.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/TextAreaEditor.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/TextAreaEditor.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.tips"));
+
+ Label labelField = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.field"));
+ TextAreaEditorField field = new TextAreaEditorField();
+ field.setTitle("TextAreaEditorField");
+
+ TextAreaEditor editor = new TextAreaEditor();
+ editor.getStyleClass().add(CSS.BORDER_L);
+
+ setCenter(new BorderPane() {{
+ setBorder(Stroke.TOP);
+ setLeft(new BorderPane() {{
+ setPadding(new Insets(12, 20, 12, 20));
+ setTop(new GridPane() {{
+ getColumnConstraints().addAll(Column.KEY, Column.VALUE_FILL);
+ setVgap(8);
+ setHgap(10);
+
+ int row = 0;
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.key_tips.ctrl_shift_enter")), new Label("Ctrl + Shift + ENTER"));
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.key_tips.shift_enter")), new Label("Shift + ENTER"));
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.key_tips.ctrl_shift_u")), new Label("Ctrl + Shift + U"));
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.key_tips.ctrl_f")), new Label("Ctrl + F"));
+ addRow(row, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.text_area_editor.key_tips.ctrl_d")), new Label("Ctrl + D"));
+ }});
+ setCenter(new VBox() {{
+ setPadding(new Insets(24, 0, 0, 0));
+ setSpacing(6);
+ getChildren().addAll(labelField, field);
+ }});
+ }});
+ setCenter(editor);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/TitleLabelDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/TitleLabelDemo.java
new file mode 100644
index 0000000..bab289c
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/TitleLabelDemo.java
@@ -0,0 +1,43 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.TitleLabel;
+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.geometry.Insets;
+import javafx.scene.layout.VBox;
+
+/**
+ * 标题标签
+ *
+ * @author 夜雨
+ * @since 2022-09-06 23:57
+ */
+@Component
+public class TitleLabelDemo extends AbstractDemoPane {
+
+ public TitleLabelDemo() {
+ title.setText(SidebarItem.TITLE_LABEL.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/TitleLabel.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/TitleLabel.java");
+
+ // 默认
+ TitleLabel t0 = new TitleLabel(TimiFXUI.MULTILINGUAL.text("title"));
+
+ // 自定义颜色
+ TitleLabel t1 = new TitleLabel(TimiFXUI.MULTILINGUAL.text("fx.example.title_label.color"));
+ t1.setLineColor(Colorful.PINK);
+
+ // 自定义间距
+ TitleLabel t2 = new TitleLabel(TimiFXUI.MULTILINGUAL.text("fx.example.title_label.spacing"));
+ t2.setSpacing(32);
+ t2.setLineColor(Colorful.BLUE);
+
+ setCenter(new VBox() {{
+ setSpacing(32);
+ setPadding(new Insets(20));
+ getChildren().addAll(t0, t1, t2);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/ToggleIconDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/ToggleIconDemo.java
new file mode 100644
index 0000000..b42a6a8
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/ToggleIconDemo.java
@@ -0,0 +1,52 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.icon.TimiFXIcon;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.ToggleIcon;
+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.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.GridPane;
+
+/**
+ * 切换图标
+ *
+ * @author 夜雨
+ * @since 2022-08-30 09:34
+ */
+@Component
+public class ToggleIconDemo extends AbstractDemoPane {
+
+ public ToggleIconDemo() {
+ title.setText(SidebarItem.TOGGLE_ICON.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/ToggleIcon.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/ToggleIcon.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.toggle_icon.tips"));
+
+ // 默认
+ Label labelDef = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("default"));
+ ToggleIcon def = new ToggleIcon(TimiFXIcon.fromName("ARROW_0_W"));
+
+ // 跟随状态变换
+ Label labelDiff = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.toggle_icon.diff"));
+ ToggleIcon diff = new ToggleIcon(TimiFXIcon.fromName("ARROW_0_W"), TimiFXIcon.fromName("ARROW_0_E"));
+
+ // 按钮背景
+ Label labelWithBG = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.toggle_icon.with_bg"));
+ ToggleIcon withBG = new ToggleIcon(TimiFXIcon.fromName("ARROW_0_W")).withBackground();
+ ToggleIcon withBGDiff = new ToggleIcon(TimiFXIcon.fromName("ARROW_0_W"), TimiFXIcon.fromName("ARROW_0_E")).withBackground();
+
+ setCenter(new GridPane() {{
+ setHgap(5);
+ setVgap(5);
+ setPadding(new Insets(20));
+
+ int row = 0;
+ addRow(row++, labelDef, def);
+ addRow(row++, labelDiff, diff);
+ addRow(row, labelWithBG, withBG, withBGDiff);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XPaginationDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XPaginationDemo.java
new file mode 100644
index 0000000..dffdf9c
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XPaginationDemo.java
@@ -0,0 +1,67 @@
+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.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.Column;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.control.Slider;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * 分页组件
+ *
+ * @author 夜雨
+ * @since 2022-08-30 09:34
+ */
+@Component
+public class XPaginationDemo extends AbstractDemoPane {
+
+ public XPaginationDemo() {
+ title.setText(SidebarItem.X_PAGINATION.getText());
+ 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();
+
+ // 总数
+ Label total = new Label();
+ Slider totalSlider = new Slider(1, 1000, 100);
+ pagination.lengthProperty().bind(totalSlider.valueProperty());
+ total.textProperty().bind(totalSlider.valueProperty().asString("%.0f"));
+
+ // 当前页数据量
+ Label size = new Label();
+ Slider sizeSlider = new Slider(1, 200, 10);
+ pagination.sizeProperty().bind(sizeSlider.valueProperty());
+ size.textProperty().bind(pagination.sizeProperty().asString());
+
+ // 区块数量(页数)
+ Label chunk = new Label();
+ chunk.textProperty().bind(pagination.chunkProperty().asString());
+
+ // 激活下标
+ Label index = new Label();
+ index.textProperty().bind(pagination.indexProperty().asString());
+
+ setCenter(new VBox() {{
+ setPadding(new Insets(20));
+ getChildren().addAll(pagination, new GridPane() {{
+ getColumnConstraints().addAll(Column.build(), Column.build().width(40));
+ setHgap(10);
+ setVgap(8);
+ setPadding(new Insets(20 , 0, 0, 0));
+
+ int row = 0;
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.x_pagination.total")), total, totalSlider);
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.x_pagination.size")), size, sizeSlider);
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.x_pagination.chunk")), chunk);
+ addRow(row, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.x_pagination.index")), index);
+ }});
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XTabPaneDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XTabPaneDemo.java
new file mode 100644
index 0000000..f7d2ae7
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XTabPaneDemo.java
@@ -0,0 +1,45 @@
+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.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.control.Tab;
+import javafx.scene.layout.BorderPane;
+
+import java.util.UUID;
+
+/**
+ * 标签面板
+ *
+ * @author 夜雨
+ * @since 2022-08-30 09:36
+ */
+@Component
+public class XTabPaneDemo extends AbstractDemoPane {
+
+ public XTabPaneDemo() {
+ title.setText(SidebarItem.X_TAB_PANE.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/XTabPane.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/XTabPane.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.x_tab_pane.tips"));
+
+ Button add = new Button(TimiFXUI.MULTILINGUAL.text("add"));
+
+ XTabPane tabPane = new XTabPane();
+
+ setCenter(new BorderPane() {{
+ setMargin(add, new Insets(0, 0, 20, 0));
+ setPadding(new Insets(20));
+ setTop(add);
+ setCenter(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();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XTreeViewDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XTreeViewDemo.java
new file mode 100644
index 0000000..37244d2
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/component/XTreeViewDemo.java
@@ -0,0 +1,31 @@
+package com.imyeyu.fx.ui.examples.view.pages.component;
+
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.FileTreeView;
+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.geometry.Insets;
+
+/**
+ * 多根节点树
+ *
+ * @author 夜雨
+ * @since 2022-08-30 09:41
+ */
+@Component
+public class XTreeViewDemo extends AbstractDemoPane {
+
+ public XTreeViewDemo() {
+ title.setText(SidebarItem.X_TREE_VIEW.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/component/XTreeView.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/component/XTreeView.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.x_tree_view.tips"));
+
+ FileTreeView fileTreeView = new FileTreeView();
+ fileTreeView.setBorder(Stroke.DEFAULT);
+
+ setMargin(fileTreeView, new Insets(20));
+ setCenter(fileTreeView);
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/DraggableNodeDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/DraggableNodeDemo.java
new file mode 100644
index 0000000..738b9aa
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/DraggableNodeDemo.java
@@ -0,0 +1,69 @@
+package com.imyeyu.fx.ui.examples.view.pages.other;
+
+import com.imyeyu.fx.draggable.DraggableNode;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.TextAreaEditor;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.BorderStroke;
+import com.imyeyu.inject.annotation.Component;
+import javafx.geometry.Orientation;
+import javafx.scene.control.Label;
+import javafx.scene.control.SplitPane;
+import javafx.scene.layout.StackPane;
+
+/**
+ * @author 夜雨
+ * @since 2023-04-13 10:40
+ */
+@Component
+public class DraggableNodeDemo extends AbstractDemoPane {
+
+ public DraggableNodeDemo() {
+ title.setText(SidebarItem.DRAGGABLE_NODE.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/util/DraggableNode.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/util/DraggableNode.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.draggable_node.tips"));
+
+ TextAreaEditor demo = new TextAreaEditor();
+ demo.getStyleClass().add(CSS.BORDER_T);
+ demo.setEditable(false);
+ demo.setText("""
+StackPane box = new StackPane();
+
+StackPane pane = new StackPane(new Label("Drag Me"));
+pane.setBorder(new XBorder(FOCUSED_DEFAULT).width(2).build());
+pane.setMaxSize(200, 40);
+pane.setEffect(SHADOW_POPUP);
+pane.setBackground(BG_WHITE);
+box.getChildren().add(pane);
+
+DraggableNode draggable = new DraggableNode(pane);
+draggable.minXProperty().bind(box.widthProperty().multiply(.5).negate().add(pane.widthProperty().multiply(.5)));
+draggable.minYProperty().bind(box.heightProperty().multiply(.5).negate().add(pane.heightProperty().multiply(.5)));
+draggable.maxXProperty().bind(box.widthProperty().multiply(.5).subtract(pane.widthProperty().multiply(.5)));
+draggable.maxYProperty().bind(box.heightProperty().multiply(.5).subtract(pane.heightProperty().multiply(.5)));
+ """.trim());
+
+ StackPane box = new StackPane();
+
+ StackPane pane = new StackPane(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.drag_me")));
+ pane.setBorder(new BorderStroke(Colorful.FOCUSED_DEFAULT).width(2).build());
+ pane.setMaxSize(200, 40);
+ pane.setEffect(Shadow.POPUP);
+ pane.setBackground(BG.WHITE);
+ box.getChildren().add(pane);
+
+ DraggableNode draggable = new DraggableNode(pane);
+ draggable.minXProperty().bind(box.widthProperty().multiply(.5).negate().add(pane.widthProperty().multiply(.5)));
+ draggable.minYProperty().bind(box.heightProperty().multiply(.5).negate().add(pane.heightProperty().multiply(.5)));
+ draggable.maxXProperty().bind(box.widthProperty().multiply(.5).subtract(pane.widthProperty().multiply(.5)));
+ draggable.maxYProperty().bind(box.heightProperty().multiply(.5).subtract(pane.heightProperty().multiply(.5)));
+
+ setCenter(new SplitPane() {{
+ setOrientation(Orientation.VERTICAL);
+ setDividerPositions(.5);
+ getItems().addAll(demo, box);
+ }});
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/DraggableWindowDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/DraggableWindowDemo.java
new file mode 100644
index 0000000..7b0e757
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/DraggableWindowDemo.java
@@ -0,0 +1,103 @@
+package com.imyeyu.fx.ui.examples.view.pages.other;
+
+import com.imyeyu.fx.draggable.DraggableWindow;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.TextAreaEditor;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.utils.BorderStroke;
+import com.imyeyu.inject.annotation.Component;
+import javafx.beans.binding.Bindings;
+import javafx.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.stage.Popup;
+
+/**
+ * @author 夜雨
+ * @since 2023-04-13 17:39
+ */
+@Component
+public class DraggableWindowDemo extends AbstractDemoPane {
+
+ private final Popup popup;
+
+ public DraggableWindowDemo() {
+ title.setText(SidebarItem.DRAGGABLE_NODE.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/util/DraggableWindow.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/util/DraggableWindow.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.draggable_window.tips"));
+
+ popup = new Popup();
+ popup.getContent().add(new BorderPane() {{
+ setBorder(new BorderStroke(Colorful.RED).width(2).build());
+ setBackground(BG.WHITE);
+ setTop(new StackPane() {{
+ setBorder(Stroke.BOTTOM);
+ setPadding(new Insets(4, 0, 4, 0));
+ setBackground(BG.TITLE_FILL);
+ getChildren().add(new Label(TimiFXUI.MULTILINGUAL.text("fx.example.drag_me")));
+
+ new DraggableWindow(popup, this);
+ }});
+ setBottom(TimiFXUI.label("TimiFX"));
+ setMargin(getBottom(), new Insets(12, 60, 12, 60));
+ }});
+
+ Button toggle = new Button();
+ toggle.textProperty().bind(Bindings.when(popup.showingProperty()).then(TimiFXUI.MULTILINGUAL.text("close")).otherwise(TimiFXUI.MULTILINGUAL.text("show")));
+
+ TextAreaEditor demo = getTextAreaEditor();
+
+ setCenter(new BorderPane() {{
+ setPadding(new Insets(20));
+ setTop(new VBox() {{
+ setPadding(new Insets(0, 0, 12, 0));
+ getChildren().add(toggle);
+ }});
+ setCenter(demo);
+ }});
+
+ // ---------- 事件 ----------
+
+ toggle.setOnAction(e -> {
+ if (popup.isShowing()) {
+ popup.hide();
+ } else {
+ popup.show(getScene().getWindow());
+ popup.centerOnScreen();
+ }
+ });
+ }
+
+ private static TextAreaEditor getTextAreaEditor() {
+ TextAreaEditor demo = new TextAreaEditor();
+ demo.setEditable(false);
+ demo.setText("""
+Popup popup = new Popup();
+popup.getContent().add(new BorderPane() {{
+ setBorder(new XBorder(RED).width(2).build());
+ setBackground(BG_WHITE);
+ setTop(new StackPane() {{
+ setBorder(BORDER_BOTTOM);
+ setPadding(new Insets(4, 0, 4, 0));
+ setBackground(BG_TITLE_FILL);
+ getChildren().add(new Label("Drag Me"));
+
+ new DraggableWindow(popup, this);
+ }});
+ setBottom(TimiFXUI.label("TimiFX"));
+ setMargin(getBottom(), new Insets(12, 60, 12, 60));
+}});
+ """.trim());
+ return demo;
+ }
+
+ @Override
+ protected void onHide() {
+ popup.hide();
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/ScreenFXDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/ScreenFXDemo.java
new file mode 100644
index 0000000..052000d
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/ScreenFXDemo.java
@@ -0,0 +1,44 @@
+package com.imyeyu.fx.ui.examples.view.pages.other;
+
+import com.imyeyu.fx.task.RunAsync;
+import com.imyeyu.fx.ui.ScreenIdentify;
+import com.imyeyu.fx.ui.TimiFXUI;
+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.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.layout.HBox;
+
+/**
+ * 多屏操作
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:39
+ */
+@Component
+public class ScreenFXDemo extends AbstractDemoPane {
+
+ public ScreenFXDemo() {
+ title.setText(SidebarItem.SCREEN.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/util/ScreenFX.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/util/ScreenFX.java");
+
+ // 标识
+ Button identify = new Button(TimiFXUI.MULTILINGUAL.text("fx.example.screen.show_identify"));
+
+ setCenter(new HBox() {{
+ setPadding(new Insets(20));
+ getChildren().add(identify);
+ }});
+
+ // ---------- 事件 ----------
+
+ // 显示标识
+ identify.setOnAction(e -> {
+ ScreenIdentify screenIdentify = new ScreenIdentify();
+ screenIdentify.showIdentify();
+ RunAsync.later(screenIdentify::hideIdentify, 4000);
+ });
+ }
+}
diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/TrayFXDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/TrayFXDemo.java
new file mode 100644
index 0000000..3c37100
--- /dev/null
+++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/other/TrayFXDemo.java
@@ -0,0 +1,119 @@
+package com.imyeyu.fx.ui.examples.view.pages.other;
+
+import com.imyeyu.fx.TimiFX;
+import com.imyeyu.fx.icon.TimiFXIcon;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.fx.ui.components.TextAreaEditor;
+import com.imyeyu.fx.ui.components.TrayFX;
+import com.imyeyu.fx.ui.components.popup.PopupTipsService;
+import com.imyeyu.fx.ui.examples.component.AbstractDemoPane;
+import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem;
+import com.imyeyu.fx.ui.examples.ctrl.Main;
+import com.imyeyu.inject.annotation.Component;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.InvokeForInjected;
+import javafx.geometry.Insets;
+import javafx.geometry.VPos;
+import javafx.scene.control.Button;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+
+import java.awt.TrayIcon;
+
+/**
+ * 系统托盘
+ *
+ * @author 夜雨
+ * @since 2022-09-01 14:39
+ */
+@Component
+public class TrayFXDemo extends AbstractDemoPane {
+
+ @Inject
+ private Main main;
+
+ @Inject
+ private TrayFX trayFX;
+
+ private final Button show, hide;
+
+ public TrayFXDemo() {
+ title.setText(SidebarItem.TRAY.getText());
+ document.sync("https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/util/TrayFX.html");
+ source.sync("https://git.imyeyu.net/Timi/timi-fx/src/master/src/main/java/net/imyeyu/timifx/util/TrayFX.java");
+ tips.setText(TimiFXUI.MULTILINGUAL.text("fx.example.tray.tips"));
+
+ show = new Button(TimiFXUI.MULTILINGUAL.text("show"));
+ hide = new Button(TimiFXUI.MULTILINGUAL.text("remove"));
+ hide.getStyleClass().add(CSS.BORDER_TRB);
+
+ Label labelSendMsg = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.tray.send_msg"));
+
+ TextField msgTitle = new TextField();
+
+ // 消息类型
+ ComboBox msgType = new ComboBox<>();
+ msgType.getItems().setAll(TrayIcon.MessageType.values());
+ msgType.setValue(TrayIcon.MessageType.NONE);
+
+ // 消息
+ Label labelMsg = TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("content"));
+ TextAreaEditor msg = new TextAreaEditor();
+
+ // 发送
+ Button msgSend = new Button(TimiFXUI.MULTILINGUAL.text("send"));
+ PopupTipsService.installText(msgSend, TimiFXUI.MULTILINGUAL.text("fx.example.tray.send_msg.tips"));
+
+ setCenter(new GridPane() {{
+ setMargin(labelSendMsg, new Insets(26, 0, 0, 0));
+ setValignment(labelSendMsg, VPos.TOP);
+ setVgap(8);
+ setHgap(12);
+ setPadding(new Insets(20));
+
+ int row = 0;
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("fx.example.tray.icon")), new HBox(show, hide));
+ addRow(row, labelSendMsg, new GridPane() {{
+ setMargin(labelMsg, new Insets(5, 0, 0, 0));
+ setValignment(labelMsg, VPos.TOP);
+ setVgap(8);
+ setHgap(12);
+ setPadding(new Insets(20));
+
+ int row = 0;
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("title")), msgTitle);
+ addRow(row++, TimiFXUI.label(TimiFXUI.MULTILINGUAL.text("type")), msgType);
+ addRow(row++, labelMsg, msg);
+ add(msgSend, 1, row);
+ }});
+ }});
+
+ // ---------- 事件 ----------
+
+ show.setOnAction(e -> trayFX.show((RESOURCE + "icon.png").substring(1)));
+ hide.setOnAction(e -> trayFX.remove());
+ msgSend.setOnAction(e -> {
+ if (trayFX.isShowing()) {
+ trayFX.sendMessage(msgTitle.getText(), msg.getText(), msgType.getValue());
+ }
+ });
+ }
+
+ @InvokeForInjected
+ public void init() {
+ show.disableProperty().bind(trayFX.showingProperty());
+ hide.disableProperty().bind(trayFX.showingProperty().not());
+
+ MenuItem show = new MenuItem(TimiFXUI.MULTILINGUAL.text("show"));
+ MenuItem exit = new MenuItem(TimiFXUI.MULTILINGUAL.text("exit"), TimiFXIcon.fromName("FAIL", Colorful.RED));
+
+ show.setOnAction(e -> TimiFX.requestTop(main.getStage()));
+ exit.setOnAction(e -> main.stop());
+
+ trayFX.addMenu(0, show, TimiFXUI.sep(), exit);
+ }
+}
diff --git a/src/test/resources/TimiFXExamples.yaml b/src/test/resources/TimiFXExamples.yaml
new file mode 100644
index 0000000..dff1925
--- /dev/null
+++ b/src/test/resources/TimiFXExamples.yaml
@@ -0,0 +1,5 @@
+language: zh_CN
+width: 1300
+height: 850
+interpolator:
+ duration: 1000
\ No newline at end of file
diff --git a/src/test/resources/lang/de_DE.lang b/src/test/resources/lang/de_DE.lang
new file mode 100644
index 0000000..1525229
--- /dev/null
+++ b/src/test/resources/lang/de_DE.lang
@@ -0,0 +1,191 @@
+add=Hinzufügen
+alert=Popup-Benachrichtigung
+animation=Animation
+background=Hintergrund
+blog=Persönlicher Blog
+border=Rahmen
+color=Farbe
+commonly=häufig
+component=Montage
+content=Inhalt
+copyright=Urheberrecht
+custom=kundenspezifisch
+dark=dunkel
+default=Standard
+description=erklären
+developer.arg=Entwickler: {0}
+disable=Deaktiviert
+document=Dokument
+duration=Dauer
+enable=Aktivieren
+example=Beispiel
+exit=Beenden
+file=Datei
+file.select.directory=Ordner auswählen
+focus=Fokussierung
+fps=Framerate
+fx.example.alert.can_cancel=Abbruchbar
+fx.example.alert.deadly_error=Schwerwiegender Fehler
+fx.example.alert.file_blend=Gemischte Auswahl
+fx.example.alert.multi_tips=Unterstützt Einzel-, Mehrfachauswahl- und Mixed-Level-Auswahl
+fx.example.alert.text_area=Textfeld
+fx.example.alert.text_field=Textfeld
+fx.example.animation_renderer=Steuerbarer FPS-Renderer
+fx.example.animation_renderer.pref_fps=Voreingestellte Bildrate
+fx.example.animation_renderer.restart=Neustart und {0}
+fx.example.animation_renderer.status=aktueller Zustand
+fx.example.animation_renderer.tips=Ein einfacher und steuerbarer Renderer, der standardmäßig die Bildwiederholrate synchronisiert. Die JVM muss die folgenden Startparameter hinzufügen, um die Bildwiederholrate zu durchbrechen.
+fx.example.better=Nach der Optimierung
+fx.example.binding_config=Bindung einrichten
+fx.example.binding_config.description=Die obigen numerischen Aktualisierungen ändern synchron die Werte in der Konfigurationsdatei. Wie in der Datei TimiFXExamples.ini zu sehen ist, kann JavaFX und FF7A9B schnell eine Ein- oder Zwei-Wege-Bindung für jede hörbare Eigenschaft durch diese Klasse erreichen.
+fx.example.binding_config.interpolator_duration=Dauer des Animationsinterpolators
+fx.example.binding_config.stage_size=Fenstergröße
+fx.example.binding_config.tips=Diese Funktion hängt vom Konfigurationssystem von [timi-java, https://doc.imyeyu.net/timi-java] ab,Informationen zur Konfiguration des timi Java Systems finden Sie unter folgendem Link.
+fx.example.check_box_picker=Auswahl mehrerer Auswahlfelder
+fx.example.check_box_picker.item=Option {0}
+fx.example.date_time_picker=Detaillierte Zeitauswahl
+fx.example.demo.text=Vorsichtig jeden Morgen und Abend, nie vergessen einander
+fx.example.demo.text_long=Der See ist Ihr Blick, Träume füllen den Himmel mit Sternen.
\n\
+Mood ist eine Legende, die im Laufe der Geschichte unverändert wartet.
\n\
+Wachstum ist eine Tür aus Blättern, Kindheit hat eine Gruppe von lieben Menschen.
\n\
+Der Frühling ist eine Reise, der Besitz der sich ständig verändernden Welt.
\n\
+Diese Leute, die ich liebe, diese flüchtigen Winde.
\n\
+Diese ewigen Gelübde gehen immer und immer wieder.
\n\
+Jene, die mich lieben, diese Tränen, die sich beruhigen.
\n\
+Diese ewigen Gelübde gehen immer und immer wieder.
\n\
+Wir alle hatten ein unschuldiges und trauriges Gesicht.
\n\
+Den Sonnenschein in den Händen halten, blicken wir in die Ferne.
\n\
+Sanft Tag für Tag, Jahr für Jahr.
\n\
+Wenn wir erwachsen werden, werden wir immer noch unsere Wünsche singen.
\n\
+Sanft Tag für Tag, Jahr für Jahr.
\n\
+Wenn wir erwachsen werden, werden wir immer noch unsere Wünsche singen.
\n\
+Wenn wir erwachsen werden, werden wir immer noch unsere Wünsche singen.
+fx.example.drag_me=Ziehen Sie mich
+fx.example.draggable_node=Dragable Komponenten
+fx.example.draggable_node.tips=Ermöglichen Sie Komponenten, auf Mausziehen zu reagieren und dynamische Begrenzungen zu unterstützen.
+fx.example.draggable_window=Dragable Form
+fx.example.draggable_window.tips=Aktivieren Sie eine Komponente, um Formularziehen auszulösen, normalerweise für randlose Formulare, mit Unterstützung für dynamische Begrenzung.
+fx.example.editable_table_cell=Bearbeitbare Zellen
+fx.example.editable_table_cell.tips=Tabellenbearbeitbare Zellen haben im Vergleich zum ursprünglichen bearbeitbaren Effekt nicht den Stil eines Eingabefelds, genau wie Excel
+fx.example.extend_tools=Erweiterungswerkzeuge
+fx.example.extend_tools.bg_fill=Schnelles Erstellen eines gefüllten Hintergrunds
+fx.example.extend_tools.bg_image=Schnelle Konstruktion des Bildhintergrunds
+fx.example.extend_tools.column=Schnelles Erstellen von GridPanel-Spalteneigenschaften
+fx.example.extend_tools.directory_selector=Schneller Aufbau der Ordnerauswahl
+fx.example.extend_tools.file_selector=Schnelle Auswahl von Konstruktionsdateien
+fx.example.extend_tools.logic_bindings=Berechnung der multiplen Booleschen Wertbindung
+fx.example.extend_tools.no_selection_model=Auswahlmodus für Listenansicht deaktivieren
+fx.example.extend_tools.row=Schnelles Erstellen von GridPanel-Zeileneigenschaften
+fx.example.extend_tools.text_flower=Schnelles Erstellen von Textfluss, Unterstützung beim Parsen von Rich Text
+fx.example.extend_tools.tips=Diese Tools werden große Hilfe beim Aufbau von JavaFX-Schnittstellen bieten.
+fx.example.extend_tools.x_anchor_pane=Ankerlayout schnell konfigurieren
+fx.example.extend_tools.x_border=Schnell Grenzen erstellen
+fx.example.file_tree_view=Dateibaum
+fx.example.file_tree_view.directory=Nur Ordner anzeigen
+fx.example.file_tree_view.filter=Filter (nur txt-Dateien anzeigen)
+fx.example.file_tree_view.hide=Versteckte Dateien anzeigen
+fx.example.file_tree_view.tips=Der Dateiknoten wird asynchron geladen und nur eine Ebene wird geladen. Wenn der Ordner also nicht erweitert wird, kann er immer erweitert werden, auch wenn sich keine Dateien darin befinden.
+fx.example.icon_button=Symbolschaltflächen
+fx.example.icon_button.not_text=Kein Text
+fx.example.icon_button.not_text_with_bg=Kein Text mit Hintergrund
+fx.example.icon_button.with_bg=Mit Hintergrund
+fx.example.icon_picker=Symbolauswahl
+fx.example.icon_picker.tips=Diese Symbolliste stammt vom timi fx icon font icon icon icon icon icon:
+fx.example.interpolator=Animation in Zeitlupe
+fx.example.interpolator.demo={0} ({1}, {2}, {3}, {4})
+fx.example.interpolator.tips=Lineare Animation hat keine Seele, und Bezier Zeitlupenfunktionsanimation kann Bewegung lebendiger machen.
+fx.example.label_progress_bar=Fortschritt der Kennzeichnung
+fx.example.label_progress_bar.tips=Die Ontologie ist ein Fortschrittsbalken mit integrierten Tag-Komponenten
+fx.example.navigation=Navigationsmenü
+fx.example.navigation.expanded=Default Entfalten
+fx.example.navigation.multi=Mehrere Sekundarstufen
+fx.example.navigation.second=zweite Ebene
+fx.example.navigation.tips=Das Navigationsmenü auf der linken Seite dieses Programms ist diese Komponente.
+fx.example.number_field=Zahleneingabefeld
+fx.example.popup_tips=Popup-Eingabeaufforderung
+fx.example.popup_tips.keep_show=Klicken, um die Anzeige beizubehalten
+fx.example.popup_tips.text_long=Langer Text
+fx.example.popup_tips.tips=Popup-Eingabeaufforderungen können auf jeder Komponente installiert werden, und der Popup-Inhalt kann vollständig angepasst werden. Diese Komponente ist ein Einzelteil, mit nur einer Popup-Eingabeaufforderung zu jeder Zeit.
+fx.example.progress_slider=Gleitende Fortschrittsanpassung
+fx.example.run_async=Asynchrone Aufgaben
+fx.example.run_async.description=Die obige Zeit ist von [RunAsyncScheduled, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html].[call, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call(javafx.util.Duration,net.imyeyu.timijava.bean.Runback,net.imyeyu.timijava.bean.Callback)]() Zeitgesteuerte asynchrone Updates, wenn Hardware-Systemressourcen knapp sind, was zu Rendering führt FPS Wenn es extrem niedrig ist, kann es eine Sekunde überspringen, aber seine Zeit wird absolut genau aus dem System genommen.
+fx.example.run_async.tips0=Die Funktion von JavaFX Komponenten sollte innerhalb des JavaFX Threads liegen, [Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html].[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]() Es ist möglich, von einem Nicht-JavaFX-Thread zu einem JavaFX-Thread überall für Komponentenaktualisierungsoperationen zu wechseln, aber dies wird dringend abgeraten, wenn es missbraucht wird [Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html].[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]() Unsachgemäße Kontrolle, übermäßige Warteschlangen für UI-Updates können Blockaden, nicht reagierende Programme und schlechte Benutzererfahrung verursachen. JavaFX bietet [Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html] Es ist eine Lösung für dieses Problem, in der Regel mit seiner Erweiterungsklasse [Service, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html] 和 [ScheduledService, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html]。Diese Abhängigkeit [RunAsync, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html] Es ist das Ergebnis der Vereinfachung basierend auf diesen beiden Klassen, bitte beachten Sie die Dokumentation für die Verwendung.
+fx.example.run_async.tips1=Achtung: Aufgrund [Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html] Es werden nicht alle UI-Updates ausgeführt, und wenn hochfrequente Aktualisierungsanforderungen größer sind als das Rendern von FPS, werden einige ungültige Updates verworfen. Daher ist es wichtig, keine Rechenarbeit in der Update-Logik zu machen.
+fx.example.screen=Bedienung mit mehreren Bildschirmen
+fx.example.screen.show_identify=Multiscreen-Identifikation anzeigen
+fx.example.selectable_label=Optionale Tags
+fx.example.selectable_label.tips=Dieses Label kann Text auswählen und Zeilenumbruch unterstützen. Das ursprüngliche Objekt ist TextArea. Entfernen Sie den Stil und wickeln Sie die Zeile adaptiv ein.
+fx.example.smooth_scroll=Glattes Scrollen
+fx.example.smooth_scroll.tips=Glattes ScrollPanel oder virtuelles Scrollfenster (z. B. ListView oder TableView). Beachten Sie, dass, wenn es sich um ein virtuelles Scrollfenster handelt, die Listenelemente in diesem Bedienfeld einheitlich in der Höhe festgelegt werden müssen, andernfalls kann die virtuelle Höhe nicht vorhergesagt werden. Diese Funktion erfordert eine erhebliche GPU-Leistung.
+fx.example.style.color.bg.title_fill=Titelfüllung
+fx.example.style.file=CSS-Style-Dateien
+fx.example.style.shadow.down=Untere Maske
+fx.example.style.shadow.popup=Randlose Form
+fx.example.style.timifx=Diese Schnittstelle ist eine öffentliche universelle Schnittstelle mit vielen gängigen Parametern und Funktionsmethoden, und statische Variablen können von dieser Schnittstelle direkt referenziert werden.
+fx.example.text_area_editor=Texteditor
+fx.example.text_area_editor.field=Popup-Editor-Formular für Textfelder unterstützen
+fx.example.text_area_editor.key_tips.ctrl_d=Fokuszeilen löschen
+fx.example.text_area_editor.key_tips.ctrl_f=Suche öffnen
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=Eine neue Linie nach oben öffnen
+fx.example.text_area_editor.key_tips.ctrl_shift_u=Ausgewählte Groß- und Kleinschreibung umschalten
+fx.example.text_area_editor.key_tips.shift_enter=Neue Linie nach unten öffnen
+fx.example.text_area_editor.tips=Ein komplexer Texteditor, der Suche und Funktionserweiterung unterstützt
+fx.example.title=TimiFX-Beispielprogramm
+fx.example.title_label=Titelbezeichnung
+fx.example.title_label.color=Benutzerdefinierte Farben
+fx.example.title_label.spacing=Benutzerdefinierter Abstand
+fx.example.toggle_icon=Symbole wechseln
+fx.example.toggle_icon.diff=Symbole nach Status wechseln
+fx.example.toggle_icon.tips=Die Symbolform von ToggleButton kann keinen Schaltflächenstil haben
+fx.example.toggle_icon.with_bg=Knopfstil
+fx.example.tray=Systemablage
+fx.example.tray.icon=Tray-Symbol
+fx.example.tray.send_msg=Systemnachrichten senden
+fx.example.tray.send_msg.tips=Effektiv bei der Anzeige von Tray-Symbolen
+fx.example.tray.tips=Einzelinstanzobjekt. Dieses Tray wird von AWT konstruiert, aber das Menü ist eine Komponente der JavaFX UI. Wenn das Programm geschlossen wird, muss das Symbol manuell entfernt werden, und das Betriebssystem hört nicht, ob das Tray-Programm noch läuft.
+fx.example.welcome=Willkommen zur Nutzung
+fx.example.welcome.license=Open Source Protokoll:[MIT LICENSE, https://res.imyeyu.net/licenses/MIT.txt]
+fx.example.welcome.timifx_icon=Bibliothek für Schriftarten im Pixelstil
+fx.example.welcome.timijava=Universelle Schnittstellen, universelle Reflexionen, universelle Werkzeuge, Konfigurationssysteme und mehrsprachige Systeme usw.
+fx.example.welcome.tips=Pflege nativer, rechtwinkliger Komponenten, verschmolzener Grenzen
+fx.example.welcome.tips0=Nativ beibehalten: Diese JavaFX-Abhängigkeitsbibliothek hat die native Benutzeroberfläche nicht neu gestaltet, und die meisten Komponenten behalten ihren nativen Stil (ich kann kein ausgezeichnetes Design erzielen)
+fx.example.welcome.tips1=Rechtwinklige Komponente: Die Verschmelzungsgrenze erfordert ein rechtwinkliges Bauteildesign, und das Bauteil kann neben anderen Komponenten liegen
+fx.example.welcome.tips2=Fusion-Rahmen: Freigabe von Grenzen, Verwendung von Java-Code oder CSS zum schnellen Festlegen der vier Grenzen von Komponenten und Freigabe von Teilgrenzen zwischen Komponenten
+fx.example.welcome.tips3=Dieses Programm ist nur für [timi-fx, https://git.imyeyu.net/Timi/timi-fx] Die Wirksamkeit und Gebrauchsanweisungen entnehmen Sie bitte dem Demonstrationsprogramm.[Dokument, https://doc.imyeyu.net/timi-fx],[timi-fx, https://doc.imyeyu.net/timi-fx] verlassen Sie sich auf [timi-java, https://doc.imyeyu.net/timi-java] und [timi-fx-icon, https://doc.imyeyu.net/timi-fx-icon]
+fx.example.welcome.top=[Dokument, https://doc.imyeyu.net/timi-fx/] |
[Quellcode, https://git.imyeyu.net/Timi/timi-fx]
+fx.example.welcome.version=Demo-Programmversion:
+fx.example.welcome.version.timifx=timi-fx version: {0}
+fx.example.x_pagination.chunk=Aktuelle Seitenzahl
+fx.example.x_pagination.index=Aktueller Aktivierungsindex
+fx.example.x_pagination.size=Einzelseitiges Datenvolumen
+fx.example.x_pagination.total=Datenvolumen insgesamt
+fx.example.x_tab_pane=Beschriftungsfenster
+fx.example.x_tab_pane.tips=Der Stil wurde angepasst und am Ende der Beschriftung eine Schaltfläche zum Hinzufügen hinzugefügt, die standardmäßig keine Ereignisse enthält.
+fx.example.x_tree_view=Mehrere Stammknotenbäume
+fx.example.x_tree_view.tips=JavaFX TreeView hat und hat standardmäßig nur einen Stammknoten, XTreeView erlaubt mehrere Stammknoten, und die Dateibaumliste ist das beste Beispiel (es sei denn, Sie haben nur eine Festplattenpartition).
+hover=Punkt
+icon=Symbol
+image=Bild
+important=wichtig
+input=Eingabe
+lang=Sprache
+light=hell
+mpf=Zeit der Frame-Generierung
+other=andere
+pagination=Paging
+play=spielen
+remove=entfernen
+reset=Zurücksetzen
+send=senden
+shadow=Projektion
+show=Anzeige
+source=Quellcode
+style=Stil
+text=Text
+tips.restart=Die Anwendung muss neu gestartet werden. Möchten Sie sie jetzt neu starten?
+tips.restart.error=Selbstneustart fehlgeschlagen, bitte versuchen Sie den manuellen Neustart
+title=Titel
+type=Typ
+version.checking={0} Auf Aktualisierungen prüfen
+version.new={0}, neue Version: {1}
+width=Breite
diff --git a/src/test/resources/lang/en_US.lang b/src/test/resources/lang/en_US.lang
new file mode 100644
index 0000000..47afc8e
--- /dev/null
+++ b/src/test/resources/lang/en_US.lang
@@ -0,0 +1,177 @@
+add=Add
+alert=pop-up notification
+animation=animation
+background=Background
+blog=Personal blog
+border=frame
+color=Color
+commonly=commonly
+component=Assembly
+content=Content
+copyright=copyright
+custom=Custom
+dark=dark
+default=Default
+description=Explain
+developer.arg=Developer: {0}
+disable=Disabled
+document=Document
+duration=Duration
+enable=Enable
+example=Example
+exit=Exit
+file=File
+file.select.directory=Select folder
+focus=focusing
+fps=FPS
+fx.example.alert.can_cancel=Cancelable
+fx.example.alert.deadly_error=Fatal error
+fx.example.alert.file_blend=Mixed selection
+fx.example.alert.multi_tips=Supports single selection, multiple selection, and mixed level selection
+fx.example.alert.text_area=Text Field
+fx.example.alert.text_field=Text box
+fx.example.animation_renderer=Controllable FPS renderer
+fx.example.animation_renderer.pref_fps=Preset frame rate
+fx.example.animation_renderer.restart=Restart and {0}
+fx.example.animation_renderer.status=current state
+fx.example.animation_renderer.tips=A simple and controllable renderer that synchronizes the display refresh rate by default. The JVM needs to add the following startup parameters to break through the display refresh rate.
+fx.example.better=After optimization
+fx.example.binding_config=Configure binding
+fx.example.binding_config.description=The above value updates will synchronously modify the values in the configuration file. In TimiFXExamples As can be seen from the. ini file, JavaFX '# FF7A9B, any wiretap attribute' can be quickly bound in one or two directions through this class.
+fx.example.binding_config.interpolator_duration=Animation Interpolator Duration
+fx.example.binding_config.stage_size=Window size
+fx.example.binding_config.tips=This function depends on [timi java, https://doc.imyeyu.net/timi-java ]See the following link for details about the timi java configuration system.
+fx.example.check_box_picker=Multiple selection box selector
+fx.example.check_box_picker.item=Option {0}
+fx.example.date_time_picker=Detailed time selector
+fx.example.demo.text=Careful every morning and night, never forgetting each other
+fx.example.demo.text_long=The lake is your eyes, and your dreams are full of stars\ The mood is a legend, waiting forever\ Growth is a door of leaves. There are a group of dear people in childhood\ Spring is a long journey, and it is the possession of the vicissitudes of life\ The people I love, the wind that's gone\ Those eternal vows are repeated\ Those who love me, those precipitation tears\ Those eternal vows are repeated\ We all had an innocent and sad face\ Holding the sun in our hands, we look far away\ Gently day by day, year by year\ Will we sing again when we grow up\ Gently day by day, year by year\ Will we sing again when we grow up\ Will we sing again when we grow up.
+fx.example.drag_me=Drag Me
+fx.example.draggable_node=Draggable components
+fx.example.draggable_node.tips=Enable components to respond to mouse dragging and support dynamic limiting.
+fx.example.draggable_window=Draggable Form
+fx.example.draggable_window.tips=Enable a component to trigger form dragging, usually for borderless forms, with support for dynamic limiting.
+fx.example.editable_table_cell=Editable Cells
+fx.example.editable_table_cell.tips=Table editable cells, compared to the original editable effect, this cell does not have the style of an input box, just like Excel
+fx.example.extend_tools=Extension tools
+fx.example.extend_tools.bg_fill=Quickly construct a filled background
+fx.example.extend_tools.bg_image=Quick construction of image background
+fx.example.extend_tools.column=Quickly construct GridPanel column properties
+fx.example.extend_tools.directory_selector=Quick construction of folder selector
+fx.example.extend_tools.file_selector=Quick construction file selector
+fx.example.extend_tools.logic_bindings=Multiple Boolean value binding calculation
+fx.example.extend_tools.no_selection_model=Disable list view selection mode
+fx.example.extend_tools.row=Quickly construct GridPanel row properties
+fx.example.extend_tools.text_flower=Quickly construct text flow, support parsing rich text
+fx.example.extend_tools.tips=These tools will provide great assistance in building JavaFX interfaces.
+fx.example.extend_tools.x_anchor_pane=Quickly configure anchor layout
+fx.example.extend_tools.x_border=Quickly construct borders
+fx.example.file_tree_view=File Tree
+fx.example.file_tree_view.directory=Only display folders
+fx.example.file_tree_view.filter=Filter (only display. txt files)
+fx.example.file_tree_view.hide=Show hidden files
+fx.example.file_tree_view.tips=The file node is loaded asynchronously and only one layer is loaded, so when the folder is not expanded, it can always be expanded, even if there are no files inside.
+fx.example.icon_button=Icon buttons
+fx.example.icon_button.not_text=No text
+fx.example.icon_button.not_text_with_bg=No text with background
+fx.example.icon_button.with_bg=With background
+fx.example.icon_picker=Icon selector
+fx.example.icon_picker.tips=This icon list comes from the timi fx icon font icon:
+fx.example.interpolator=Slow motion animation
+fx.example.interpolator.demo={0} ({1}, {2}, {3}, {4})
+fx.example.interpolator.tips=Linear animation has no soul, and Bezier slow motion function animation can make motion more vivid.
+fx.example.label_progress_bar=Label Progress
+fx.example.label_progress_bar.tips=The ontology is a progress bar with built-in tag components
+fx.example.navigation=Navigation menu
+fx.example.navigation.expanded=Default Unfold
+fx.example.navigation.multi=Multiple secondary levels
+fx.example.navigation.second=second level
+fx.example.navigation.tips=The navigation menu used on the left side of this program is this component.
+fx.example.number_field=Number input box
+fx.example.popup_tips=Pop up prompt
+fx.example.popup_tips.keep_show=Click to maintain display
+fx.example.popup_tips.text_long=Long Text
+fx.example.popup_tips.tips=Pop up prompts can be installed on any component, and the pop-up content can be fully customized. This component is a singleton, with only one pop-up prompt at any time.
+fx.example.progress_slider=Progress sliding adjustment
+fx.example.run_async=Asynchronous tasks
+fx.example.run_async.description=The above time is determined by [RunAsyncScheduled, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html ]\.\ [call, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call (javafx. util. Duration, net. myeyu. timijava. bean. Runback, net. myeyu. timijava. bean. Callback)].
+fx.example.run_async.tips0=Operating JavaFX components should be in the JavaFX thread, [Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html ]\.\ [runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater (java. lang. Runnable)] () You can switch from a non JavaFX thread to a JavaFX thread anywhere to update components. However, this is not recommended. If you abuse [Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html ]\.\ [runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater (java. lang. Runnable)] () Causes improper control. Excessive UI update queues may cause blocking, unresponsive programs, and poor user experience. [Task provided by JavaFX, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html ]\It is a solution to this problem, usually using its extension class [Service, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html ]\And [ScheduledService, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html ]\。 This dependency [RunAsync, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html ]\It is the result of simplification based on these two classes. Please refer to the document for usage.
+fx.example.run_async.tips1=Note: Due to [Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html ]\Not all UI updates will be executed. If the high-frequency update request is greater than the rendering FPS, some invalid updates will be discarded. Therefore, it is important not to do computing business in the update logic.
+fx.example.screen=Multi screen operation
+fx.example.screen.show_identify=Display multi screen identification
+fx.example.selectable_label=Optional tags
+fx.example.selectable_label.tips=This label can select text and support line wrapping. The original object is TextArea. Remove the style and adaptively wrap the line
+fx.example.smooth_scroll=Smooth scrolling
+fx.example.smooth_scroll.tips=Smooth scrolling ScrollPane or virtual scrolling panel (ListView or TableView, etc.). Note that if it is a virtual scrolling panel, the panel list items need to be uniformly fixed in height, otherwise the virtual height cannot be predicted. This feature requires more GPU performance.
+fx.example.style.color.bg.title_fill=Title Fill
+fx.example.style.file=CSS Style Files
+fx.example.style.shadow.down=Lower Mask
+fx.example.style.shadow.popup=Borderless Form
+fx.example.style.timifx=This interface is a public universal interface with many common parameters and functional methods, and static variables can be directly referenced by this interface.
+fx.example.text_area_editor=text editor
+fx.example.text_area_editor.field=Support text box pop-up editor form
+fx.example.text_area_editor.key_tips.ctrl_d=Delete Focus Rows
+fx.example.text_area_editor.key_tips.ctrl_f=Open Search
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=Open a new line upwards
+fx.example.text_area_editor.key_tips.ctrl_shift_u=Switch Selected Case
+fx.example.text_area_editor.key_tips.shift_enter=Open a new line downwards
+fx.example.text_area_editor.tips=A complex text editor that supports search and function expansion
+fx.example.title=TimiFX Sample Program
+fx.example.title_label=Title label
+fx.example.title_label.color=Custom Colors
+fx.example.title_label.spacing=Custom spacing
+fx.example.toggle_icon=Switch icons
+fx.example.toggle_icon.diff=Switch icons based on status
+fx.example.toggle_icon.tips=The icon form of ToggleButton can have no button style
+fx.example.toggle_icon.with_bg=Button Style
+fx.example.tray=System tray
+fx.example.tray.icon=Tray icon
+fx.example.tray.send_msg=Send system messages
+fx.example.tray.send_msg.tips=Effective when displaying tray icons
+fx.example.tray.tips=Single instance object. This tray is constructed by AWT, but the menu is a component of JavaFX UI. When the program is closed, the icon must be manually removed, and the operating system will not listen to whether the tray program is still running.
+fx.example.welcome=Welcome to use
+fx.example.welcome.license=Open source protocol:[MIT LICENSE, https://res.imyeyu.net/licenses/MIT.txt ]
+fx.example.welcome.timifx_icon=Pixel style font icon library
+fx.example.welcome.timijava=Universal interfaces, universal reflections, universal tools, configuration systems, and multilingual systems, etc
+fx.example.welcome.tips=Maintain native, right angled components, fused borders
+fx.example.welcome.tips0=Keep Native: This JavaFX dependency library has not redesigned the native UI, and most components maintain their native style (I cannot achieve excellent design)
+fx.example.welcome.tips1=Right angle component: The fusion border requires a right angle component design, and the component can be adjacent to other components
+fx.example.welcome.tips2=Fusion border: Border sharing, using Java code or CSS to quickly set the four borders of components, and sharing partial borders between components
+fx.example.welcome.tips3=This program is only [timi fx, https://git.imyeyu.net/Timi/timi-fx ]\Please refer to the [document, https://doc.imyeyu.net/timi-fx ]\,\[timi-fx, https://doc.imyeyu.net/timi-fx ]\Depends on [timi java, https://doc.imyeyu.net/timi-java ]\And [timi fx icon, https://doc.imyeyu.net/timi-fx-icon ]
+fx.example.welcome.top=[Document, https://doc.imyeyu.net/timi-fx/ ]|[Source code, https://git.imyeyu.net/Timi/timi-fx ]
+fx.example.welcome.version=Demo program version:
+fx.example.welcome.version.timifx=timi-fx version: {0}
+fx.example.x_pagination.chunk=Current number of pages
+fx.example.x_pagination.index=Current activation index
+fx.example.x_pagination.size=Single page data volume
+fx.example.x_pagination.total=Total data volume
+fx.example.x_tab_pane=Label panel
+fx.example.x_tab_pane.tips=Adjusted the style and added an add button at the end of the label, which defaults to no events
+fx.example.x_tree_view=Multiple root node trees
+fx.example.x_tree_view.tips=JavaFX TreeView has and only has one root node by default, XTreeView allows multiple root nodes, and the file tree list is the best example (unless you only have one disk partition).
+hover=point
+icon=Icon
+image=Image
+important=important
+input=Input
+lang=Language
+light=bright
+mpf=MPF
+other=Other
+pagination=paging
+play=Play
+remove=Remove
+reset=Reset
+send=Send
+shadow=projection
+show=Show
+source=Source code
+style=Style
+text=Text
+tips.restart=Application needs to be restarted. Do you want to restart it now?
+tips.restart.error=Self restart failed, please try manual restart
+title=Title
+type=Type
+version.checking={0} - Checking for updates
+version.new={0}, new version: {1}
+width=Width
diff --git a/src/test/resources/lang/ja_JP.lang b/src/test/resources/lang/ja_JP.lang
new file mode 100644
index 0000000..c0eeaba
--- /dev/null
+++ b/src/test/resources/lang/ja_JP.lang
@@ -0,0 +1,177 @@
+add=追加
+alert=だんまど
+animation=アニメーション
+background=背景
+blog=個人ブログ
+border=枠線
+color=カラー
+commonly=一般
+component=コンポーネント
+content=内容
+copyright=著作権
+custom=カスタム#カスタム#
+dark=暗い
+default=デフォルト
+description=説明
+developer.arg=開発者:
+disable=無効化
+document=ドキュメント
+duration=継続時間
+enable=有効化
+example=例
+exit=終了
+file=ファイル
+file.select.directory=フォルダの選択
+focus=しゅうそく
+fps=フレームレート
+fx.example.alert.can_cancel=キャンセル可能
+fx.example.alert.deadly_error=致命的なエラー
+fx.example.alert.file_blend=ブレンド選択
+fx.example.alert.multi_tips=単一選択、複数選択、混合階層選択をサポートする
+fx.example.alert.text_area=テキストフィールド
+fx.example.alert.text_field=テキストボックス
+fx.example.animation_renderer=制御可能なFPSレンダラー
+fx.example.animation_renderer.pref_fps=プリセットフレームレート
+fx.example.animation_renderer.restart=再起動して{0}
+fx.example.animation_renderer.status=現在の状態
+fx.example.animation_renderer.tips=シンプルで使いやすく制御可能なレンダラー、デフォルトの同期ディスプレイのリフレッシュレート、JVMはディスプレイのリフレッシュレートを突破するために以下の起動パラメータを追加する必要があります。
+fx.example.better=最適化後
+fx.example.binding_config=バインドの設定
+fx.example.binding_config.description=以上の数値更新は、TimiFXExamples.で設定ファイルの値を同期的に変更します。iniファイルには、JavaFX `#FF 7 A 9 B、任意のリスニング可能な属性`、このクラスを通じて単方向または双方向バインドを迅速に実現することができます。
+fx.example.binding_config.interpolator_duration=アニメーション補間器の期間
+fx.example.binding_config.stage_size=フォームサイズ
+fx.example.binding_config.tips=この機能は[timi-java,https://doc.imyeyu.net/timi-java]の構成システムを参照してください。timi-java構成システムの内容は次のリンクを参照してください。
+fx.example.check_box_picker=マルチセレクタ
+fx.example.check_box_picker.item=オプション{0}
+fx.example.date_time_picker=詳細時間セレクタ
+fx.example.demo.text=朝に向かってはしきりに大切にし,夜になっても忘れない
+fx.example.demo.text_long=湖はあなたの目つきで、満天の星を夢見ています。気持ちは伝説で、いつまでも変わらず待っています。成長は木の葉の扉で、子供の頃は親愛なる人たちがいました。春は道のりで、滄海桑田の所有です。私の愛する人たち、過ぎ去った風。永遠の誓いをもう一度。私を愛してくれた人、沈殿した涙。永遠の誓いをもう一度。私たちは無邪気で悲しい顔をしたことがあります。私たちは太陽の光を手にして遠くを見ています。そっと一日一日、一年また一年。長い間、私たちはまた願いを歌うことができるかどうか。そっと一日一日、一年また一年。長い間、私たちはまた願いを歌うことができるかどうか。長い間、私たちはまた願いを歌うことができるだろうか。
+fx.example.drag_me=私をドラッグ
+fx.example.draggable_node=ドラッグ可能コンポーネント
+fx.example.draggable_node.tips=マウスのドラッグにコンポーネントを応答させ、ダイナミックリミットをサポートします。
+fx.example.draggable_window=ドラッグ可能なフォーム
+fx.example.draggable_window.tips=コンポーネントにフォームのドラッグをトリガーさせます。通常は枠なしフォームで使用され、動的リミットをサポートします。
+fx.example.editable_table_cell=編集可能なセル
+fx.example.editable_table_cell.tips=表の編集可能なセルは、Excelのように入力ボックスのスタイルがない元の編集可能な効果に対して
+fx.example.extend_tools=拡張ツール
+fx.example.extend_tools.bg_fill=ハッチングバックグラウンドの高速構築
+fx.example.extend_tools.bg_image=画像の背景をすばやく作成
+fx.example.extend_tools.column=GridPane列のクイック構築プロパティ
+fx.example.extend_tools.directory_selector=フォルダセレクタのクイック構築
+fx.example.extend_tools.file_selector=クイック構築ファイルセレクタ
+fx.example.extend_tools.logic_bindings=複数のブール値バインド計算
+fx.example.extend_tools.no_selection_model=リストビュー選択モードの無効化
+fx.example.extend_tools.row=GridPane行のクイック構築プロパティ
+fx.example.extend_tools.text_flower=リッチテキストの解析をサポートするテキストフローの高速構築
+fx.example.extend_tools.tips=これらのツールは、JavaFXインタフェースの構築に役立ちます。
+fx.example.extend_tools.x_anchor_pane=アンカーポイントレイアウトの高速構成
+fx.example.extend_tools.x_border=クイック構築ボーダー
+fx.example.file_tree_view=ファイルツリー
+fx.example.file_tree_view.directory=フォルダのみ表示
+fx.example.file_tree_view.filter=フィルタリング(.txtファイルのみ表示)
+fx.example.file_tree_view.hide=隠しファイルを表示
+fx.example.file_tree_view.tips=ファイルノードは非同期的にロードされ、1つのレベルだけがロードされるため、ファイルが入っていなくても、フォルダは展開されたままになります。
+fx.example.icon_button=アイコンボタン
+fx.example.icon_button.not_text=テキストなし
+fx.example.icon_button.not_text_with_bg=テキストなし背景あり
+fx.example.icon_button.with_bg=背景あり
+fx.example.icon_picker=アイコンセレクタ
+fx.example.icon_picker.tips=このアイコンのリストはtimi-fx-iconフォントアイコンに由来します。
+fx.example.interpolator=イージングアニメーション
+fx.example.interpolator.demo={0} ({1}, {2}, {3}, {4})
+fx.example.interpolator.tips=線形アニメーションには魂がありません。ベジェ緩動関数アニメーションは動きをより生き生きとさせることができます。
+fx.example.label_progress_bar=ラベルの進行状況
+fx.example.label_progress_bar.tips=本体はプログレスバーで、内蔵ラベルコンポーネント
+fx.example.navigation=ナビゲーションメニュー
+fx.example.navigation.expanded=デフォルトの配置
+fx.example.navigation.multi=複数の2レベル
+fx.example.navigation.second=にだん
+fx.example.navigation.tips=このプログラムの左側にあるナビゲーションメニューがこのコンポーネントです。
+fx.example.number_field=数値入力ボックス
+fx.example.popup_tips=ポップアッププロンプト
+fx.example.popup_tips.keep_show=クリックして表示を維持
+fx.example.popup_tips.text_long=長いテキスト
+fx.example.popup_tips.tips=ポップアッププロンプトは任意のコンポーネントにインストールでき、ポップアップコンテンツは完全にカスタマイズできます。このコンポーネントはシングルケースで、いつでもポップアッププロンプトは1つだけです。
+fx.example.progress_slider=プログレススライド調整
+fx.example.run_async=非同期タスク
+fx.example.run_async.description=以上の時間は[RunAsyncScheduled,https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html]\.\[call, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call(javafx.util.Duration,net.imyeyu.timijava.bean.Runback,net.imyeyu.timijava.bean.Callback)]>()タイミング非同期更新では、ハードウェアシステムリソースの緊張によりレンダリングFPSが極端に低い場合、1秒スキップすることがありますが、その時間はシステムから絶対的に正確に取得されています。
+fx.example.run_async.tips0=JavaFXコンポーネントを操作するには、JavaFXスレッドに[Platform、https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html]\.\[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]()コンポーネントの更新操作を行うために、JavaFX以外のスレッドからJavaFXスレッドに切り替えることはどこでもできますが、これは非常に推奨されていません。https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html]\.\[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]>()制御が不適切になり、UI更新キューが多すぎるとブロックされ、プログラムが応答せず、ユーザー体験が悪い可能性があります。JavaFXが提供する[Task,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html]はこの問題を解決するためのソリューションで、通常は拡張クラス[Serviceを使用します。https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html]と[ScheduledService、https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html]\。本依存性[RunAsync,https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html]\は、この2つのクラスに基づいて簡略化された結果です。使用方法はドキュメントを参照してください。
+fx.example.run_async.tips1=注意:[Task,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html]\すべてのUI更新は実行されません。高周波更新要求がレンダリングFPSより大きいと無効な更新の一部が破棄されるので、更新ロジックで計算業務をしないでください。
+fx.example.screen=マルチスクリーンオペレーション
+fx.example.screen.show_identify=マルチスクリーンIDの表示
+fx.example.selectable_label=オプションのラベル
+fx.example.selectable_label.tips=このラベルはテキストを選択し、改行をサポートします。元のオブジェクトはTextAreaでスタイルを削除し、改行に適応します
+fx.example.smooth_scroll=スムーズスクロール
+fx.example.smooth_scroll.tips=SmoothスクロールScrollPaneまたは仮想スクロールパネル(ListViewまたはTableViewなど)は、仮想スクロールパネルの場合、パネルリスト項目は固定高さを統一する必要があり、そうでないと仮想高さを予測できないことに注意してください。この機能にはGPUパフォーマンスが多く必要です。
+fx.example.style.color.bg.title_fill=ヘッダーの塗りつぶし
+fx.example.style.file=CSSスタイルファイル
+fx.example.style.shadow.down=下面マスク
+fx.example.style.shadow.popup=枠線なしフォーム
+fx.example.style.timifx=このインタフェースは共通の汎用インタフェースであり、多くの汎用パラメータと機能方法があり、静的変数はこのインタフェースの直接参照を実現することができます。
+fx.example.text_area_editor=テキストエディタ
+fx.example.text_area_editor.field=テキストボックスのポップアップウィンドウエディタフォームをサポートする
+fx.example.text_area_editor.key_tips.ctrl_d=フォーカス行の削除
+fx.example.text_area_editor.key_tips.ctrl_f=検索を開く
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=新しい行を上に開く
+fx.example.text_area_editor.key_tips.ctrl_shift_u=選択した大文字と小文字を切り替え
+fx.example.text_area_editor.key_tips.shift_enter=新しい行を下へ
+fx.example.text_area_editor.tips=検索、機能拡張をサポートする複雑なテキストエディタ
+fx.example.title=TimiFXサンプルプログラム
+fx.example.title_label=タイトルラベル
+fx.example.title_label.color=カスタム色
+fx.example.title_label.spacing=カスタム間隔
+fx.example.toggle_icon=アイコンの切り替え
+fx.example.toggle_icon.diff=状態に応じたアイコンの切り替え
+fx.example.toggle_icon.tips=ToggleButtonのアイコン形式で、ボタンなしスタイルが可能
+fx.example.toggle_icon.with_bg=ボタンスタイル
+fx.example.tray=システムトレイ
+fx.example.tray.icon=トレイアイコン
+fx.example.tray.send_msg=システムメッセージの送信
+fx.example.tray.send_msg.tips=トレイアイコンを表示するときに有効
+fx.example.tray.tips=単一例のオブジェクト。このトレイはAWTで構成されていますが、メニューはJavaFX UIのコンポーネントです。プログラムを閉じるときはアイコンを手動で削除する必要があり、オペレーティングシステムはトレイプログラムがまだ実行されているかどうかを傍受しません。
+fx.example.welcome=ようこそ
+fx.example.welcome.license=オープンソースプロトコル:[MIT LICENSE,https://res.imyeyu.net/licenses/MIT.txt]
+fx.example.welcome.timifx_icon=ピクセル風フォントアイコンライブラリ
+fx.example.welcome.timijava=汎用インタフェース、汎用反射、汎用ツール、構成システム、多言語システムなど
+fx.example.welcome.tips=ネイティブ、直角コンポーネント、境界線の融合を維持する
+fx.example.welcome.tips0=ネイティブの維持:このJavaFX依存ライブラリはネイティブUIの再設計を行っておらず、多くのコンポーネントはネイティブスタイルを維持しています(私は優れた設計をすることができません)
+fx.example.welcome.tips1=直角コンポーネント:境界を融合するには直角コンポーネント設計が必要で、コンポーネントは他のコンポーネントと隣接することができます
+fx.example.welcome.tips2=融合ボーダー:ボーダー共有、JavaコードまたはCSSを使用したコンポーネントの四角形のクイック設定、コンポーネント間の一部のボーダーの共有
+fx.example.welcome.tips3=本プログラムは[timi-fxのみ、https://git.imyeyu.net/Timi/timi-fx]の効果デモプログラム、使用方法は[ドキュメント、https://doc.imyeyu.net/timi-fx]\,\[timi-fx, https://doc.imyeyu.net/timi-fx]依存[timi-java,https://doc.imyeyu.net/timi-java]と[timi-fx-icon,https://doc.imyeyu.net/timi-fx-icon]
+fx.example.welcome.top=[ドキュメント、https://doc.imyeyu.net/timi-fx/]|[ソースコード、https://git.imyeyu.net/Timi/timi-fx]
+fx.example.welcome.version=プレゼンテーションのバージョン:
+fx.example.welcome.version.timifx=timi-fxバージョン:{0}
+fx.example.x_pagination.chunk=現在のページ数
+fx.example.x_pagination.index=現在アクティブな下標
+fx.example.x_pagination.size=シングルページデータ量
+fx.example.x_pagination.total=総データ量
+fx.example.x_tab_pane=タブパネル
+fx.example.x_tab_pane.tips=スタイルを調整し、ラベルの最後に追加ボタンを追加しました。このボタンにはデフォルトでイベントはありません
+fx.example.x_tree_view=複数のルートノードツリー
+fx.example.x_tree_view.tips=JavaFX TreeViewにはデフォルトでルートノードが1つしかありません。XTreeViewでは複数のルートノードが許可されており、ファイルツリーリストは最良の例です(ディスクパーティションが1つしかない場合を除き)。
+hover=指向性
+icon=アイコン
+image=画像
+important=重要
+input=インプット
+lang=言語
+light=明るい
+mpf=フレーム生成時間
+other=その他
+pagination=改ページ
+play=再生
+remove=除去じょきょ
+reset=リセット
+send=送信
+shadow=投影
+show=表示
+source=ソースコード
+style=スタイル
+text=テキスト
+tips.restart=アプリケーションを再起動する必要があります。すぐに再起動しますか?
+tips.restart.error=自己再起動に失敗しました。手動で再起動してみてください
+title=タイトル
+type=を選択してオプションを設定します。
+version.checking=-更新をチェックしています
+version.new={0}、新バージョン:{1}
+width=幅
diff --git a/src/test/resources/lang/ko_KR.lang b/src/test/resources/lang/ko_KR.lang
new file mode 100644
index 0000000..ce7eccc
--- /dev/null
+++ b/src/test/resources/lang/ko_KR.lang
@@ -0,0 +1,177 @@
+add=추가
+alert=탄창
+animation=애니메이션
+background=배경
+blog=개인 블로그
+border=테두리
+color=색상
+commonly=보통
+component=구성 요소
+content=내용
+copyright=저작권
+custom=사용자 지정
+dark=어둡다
+default=기본
+description=설명
+developer.arg=개발자: {0}
+disable=비활성화
+document=문서
+duration=기간
+enable=활성화
+example=예제
+exit=종료
+file=파일
+file.select.directory=폴더 선택
+focus=초점
+fps=프레임 속도
+fx.example.alert.can_cancel=취소 가능
+fx.example.alert.deadly_error=치명적 오류
+fx.example.alert.file_blend=혼합 선택
+fx.example.alert.multi_tips=단일 선택, 다중 선택, 혼합 계층 선택 지원
+fx.example.alert.text_area=텍스트 필드
+fx.example.alert.text_field=텍스트 상자
+fx.example.animation_renderer=제어 가능한 FPS 렌더러
+fx.example.animation_renderer.pref_fps=사전 설정 프레임 속도
+fx.example.animation_renderer.restart=재부팅 및 {0}
+fx.example.animation_renderer.status=현재 상태
+fx.example.animation_renderer.tips=사용하기 쉽고 제어 가능한 렌더러, 기본 동기화 모니터 주사율, JVM은 모니터 주사율을 돌파하기 위해 다음과 같은 부팅 매개변수를 추가해야 한다.
+fx.example.better=최적화 후
+fx.example.binding_config=바인딩 구성
+fx.example.binding_config.description=위의 수치 업데이트는 TimiFXExamples에서 구성 파일의 값을 동시에 수정합니다.ini 파일에서 볼 수 있듯이 JavaFX'#FF7A9B, 모든 감청 가능한 속성`은 이 클래스를 통해 단방향 또는 양방향 바인딩을 빠르게 실현할 수 있습니다.
+fx.example.binding_config.interpolator_duration=모션 보간기 기간
+fx.example.binding_config.stage_size=창 크기
+fx.example.binding_config.tips=이 기능은 [timi-java,https://doc.imyeyu.net/timi-java]의 구성 시스템입니다. timi-java 구성 시스템에 대한 내용은 다음 링크를 참조하십시오.
+fx.example.check_box_picker=다중 선택 상자 선택기
+fx.example.check_box_picker.item=옵션 {0}
+fx.example.date_time_picker=상세 시간 선택기
+fx.example.demo.text=아침마다 늘 아끼고 밤마다 서로 잊지 않다
+fx.example.demo.text_long=호수는 너의 눈빛, 온 하늘에 별을 꿈꾼다.\마음은 전설이다. 영원토록 기다린다. \성장은 나뭇잎의 문이다. 어린 시절에는 사랑하는 사람들이 있었다.\봄은 한구간, 상전벽해의 소유.\내가 사랑하는 사람들, 떠나간 바람들.\그 영원한 맹세들을 한 번 한 번.\나를 사랑하는 사람들, 침전된 눈물. \그 영원한 맹세들을 한 번 한 번.\우리는 모두 천진하고 슬픈 얼굴을 한 적이 있다.\햇빛을 손에 쥐고 우리는 먼 곳을 바라보고 있다.\살며시 하루하루, 일년 또 일년.\어른이 되어서도 우리는 다시 소원을 부를 수 있을까.\살며시 하루하루, 일년 또 일년.\어른이 되어서도 우리는 다시 소원을 부를 수 있을까.\어른이 되어서도 우리는 다시 소원을 부를 수 있을까.
+fx.example.drag_me=나를 드래그합니다.
+fx.example.draggable_node=드래그 가능 어셈블리
+fx.example.draggable_node.tips=마우스 드래그에 응답하여 어셈블리가 동적 위치 제한을 지원하도록 합니다.
+fx.example.draggable_window=드래그 가능 창
+fx.example.draggable_window.tips=구성 요소로 하여금 창 드래그를 트리거하게 합니다. 일반적으로 테두리가 없는 창에서 사용되며 동적 위치 제한을 지원합니다.
+fx.example.editable_table_cell=편집 가능 셀
+fx.example.editable_table_cell.tips=테이블편집 가능 셀, Excel과 같은 입력란 스타일이 없는 원본 편집 가능 효과
+fx.example.extend_tools=확장 도구
+fx.example.extend_tools.bg_fill=빠른 구성 채우기 배경
+fx.example.extend_tools.bg_image=빠른 구성 이미지 배경
+fx.example.extend_tools.column=빠른 GridPane 열 속성 구성
+fx.example.extend_tools.directory_selector=빠른 구성 폴더 선택기
+fx.example.extend_tools.file_selector=빠른 구성 파일 선택기
+fx.example.extend_tools.logic_bindings=다중 부울 값 바인딩 계산
+fx.example.extend_tools.no_selection_model=목록 보기 선택 모드 비활성화
+fx.example.extend_tools.row=빠른 GridPane 행 속성 구성
+fx.example.extend_tools.text_flower=리치 텍스트 해석을 지원하는 빠른 텍스트 흐름 구성
+fx.example.extend_tools.tips=이러한 도구는 JavaFX 인터페이스를 구축하는 데 큰 도움이 될 것입니다.
+fx.example.extend_tools.x_anchor_pane=빠른 앵커 배치 구성
+fx.example.extend_tools.x_border=빠른 구성 프레임
+fx.example.file_tree_view=파일 트리
+fx.example.file_tree_view.directory=폴더만 표시
+fx.example.file_tree_view.filter=필터링(.txt 파일만 표시)
+fx.example.file_tree_view.hide=숨겨진 파일 표시
+fx.example.file_tree_view.tips=파일 노드는 비동기적으로 로드되고 한 레이어만 로드되므로 폴더가 확장되지 않은 경우 폴더는 항상 확장될 수 있습니다.
+fx.example.icon_button=아이콘 단추
+fx.example.icon_button.not_text=텍스트 없음
+fx.example.icon_button.not_text_with_bg=텍스트 없음 배경 있음
+fx.example.icon_button.with_bg=배경이 있다
+fx.example.icon_picker=아이콘 선택기
+fx.example.icon_picker.tips=이 아이콘 목록은 timi-fx-icon 글꼴 아이콘에서 파생됩니다.
+fx.example.interpolator=애니메이션 부드럽게
+fx.example.interpolator.demo={0} ({1}, {2}, {3}, {4})
+fx.example.interpolator.tips=선형 애니메이션은 영혼이 없으며 베지어 부드럽게 함수 애니메이션은 움직임을 더 생생하게 만들 수 있습니다.
+fx.example.label_progress_bar=태그 진행
+fx.example.label_progress_bar.tips=본체는 진행률 표시줄, 내장 라벨 구성 요소
+fx.example.navigation=탐색 메뉴
+fx.example.navigation.expanded=기본 확장
+fx.example.navigation.multi=다중 레벨 2
+fx.example.navigation.second=2급
+fx.example.navigation.tips=프로그램 왼쪽에 있는 탐색 메뉴가 이 구성 요소입니다.
+fx.example.number_field=숫자 입력란
+fx.example.popup_tips=팝업 프롬프트
+fx.example.popup_tips.keep_show=클릭하면 표시가 유지됩니다
+fx.example.popup_tips.text_long=긴 텍스트
+fx.example.popup_tips.tips=팝업 프롬프트는 모든 구성 요소에 설치할 수 있으며 팝업 내용을 완전히 사용자 정의할 수 있습니다. 이 구성 요소는 단일 예이며 언제든지 하나의 팝업 프롬프트만 있습니다.
+fx.example.progress_slider=진행 슬라이딩 조정
+fx.example.run_async=비동기식 작업
+fx.example.run_async.description=이상의 시간은 \[RunAsyncScheduled,https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html]\.\[call, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call(javafx.util.Duration, net.imyeyu.timijava.bean.Runback, net.imyeyu.timijava.bean.Callback)] \() 비동기식 업데이트는 하드웨어 시스템 리소스가 부족하여 렌더링 FPS가 매우 낮을 때 1초를 건너뛸 수 있지만 그 시간은 시스템에서 절대적으로 정확합니다.
+fx.example.run_async.tips0=JavaFX 어셈블리를 조작하려면 JavaFX 스레드에 있어야 합니다. \[Platform,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html]\.\[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]\()는 어디에서나 비 JavaFX 스레드에서 JavaFX 스레드로 전환하여 어셈블리 업데이트 작업을 수행할 수 있지만 \[Platform을 남용하면 매우 권장되지 않습니다.https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html]\.\[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]\() 잘못 제어되어 UI 업데이트 대기열이 너무 많으면 프로그램이 응답하지 않고 사용자 환경이 나빠질 수 있습니다.JavaFX에서 제공하는 \[Task,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html]\ 이 문제를 해결하는 방안입니다. 보통 그의 확장 클래스 \ [Service,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html]\ 및 \[ScheduledService,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html]\。본 종속\[RunAsync,https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html]\ 이 두 가지 클래스에 따라 단순화된 결과입니다. 사용 방법은 문서를 보십시오.
+fx.example.run_async.tips1=참고: \ [Task,https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html]\ 모든 UI 업데이트가 수행되지는 않습니다. 만약 고주파 업데이트 요청이 렌더링 FPS보다 크면 일부 잘못된 업데이트가 무시됩니다. 따라서 업데이트 논리에서 계산 업무를 하지 마십시오.
+fx.example.screen=멀티 스크린 작업
+fx.example.screen.show_identify=다중 화면 ID 표시
+fx.example.selectable_label=옵션 레이블
+fx.example.selectable_label.tips=이 레이블은 텍스트를 선택하여 줄 바꿈을 지원합니다. 원래 객체는 TextArea에서 스타일을 제거하고 줄 바꿈에 적응합니다.
+fx.example.smooth_scroll=부드럽게 스크롤
+fx.example.smooth_scroll.tips=ScrollPane 또는 가상 스크롤 패널(ListView 또는 TableView 등)을 부드럽게 스크롤합니다. 가상 스크롤 패널의 경우 이 패널 목록 항목은 고정 높이를 통일해야 합니다. 그렇지 않으면 가상 높이를 예측할 수 없습니다.이 기능에는 많은 GPU 성능이 필요합니다.
+fx.example.style.color.bg.title_fill=제목 채우기
+fx.example.style.file=CSS 스타일 파일
+fx.example.style.shadow.down=아래 마스크
+fx.example.style.shadow.popup=테두리 없는 창
+fx.example.style.timifx=이 인터페이스는 공통 공통 인터페이스이며 많은 공통 매개 변수와 기능 방법이 있으며 정적 변수는 이 인터페이스를 직접 참조할 수 있습니다.
+fx.example.text_area_editor=텍스트 편집기
+fx.example.text_area_editor.field=텍스트 상자 팝업 창 편집기 창 지원
+fx.example.text_area_editor.key_tips.ctrl_d=초점 행 삭제
+fx.example.text_area_editor.key_tips.ctrl_f=검색 열기
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=새 행 위로 열기
+fx.example.text_area_editor.key_tips.ctrl_shift_u=선택한 대 / 소문자 전환
+fx.example.text_area_editor.key_tips.shift_enter=새 행 아래로 이동
+fx.example.text_area_editor.tips=검색, 기능 확장을 지원하는 복잡한 텍스트 편집기
+fx.example.title=TimiFX 샘플 프로그램
+fx.example.title_label=제목 태그
+fx.example.title_label.color=사용자 색상
+fx.example.title_label.spacing=사용자 간격
+fx.example.toggle_icon=아이콘 전환
+fx.example.toggle_icon.diff=상태별 아이콘 전환
+fx.example.toggle_icon.tips=ToggleButton의 아이콘 형식, 버튼 스타일 없음 가능
+fx.example.toggle_icon.with_bg=버튼 스타일
+fx.example.tray=시스템 트레이
+fx.example.tray.icon=트레이 아이콘
+fx.example.tray.send_msg=시스템 메시지 보내기
+fx.example.tray.send_msg.tips=트레이 아이콘을 표시할 때 유효
+fx.example.tray.tips=단일 객체이 트레이는 AWT로 구성되지만 메뉴는 JavaFX UI의 구성 요소입니다.프로그램이 종료될 때 수동으로 아이콘을 제거해야 합니다. 운영 체제에서는 트레이 프로그램이 실행 중인지 여부를 감지하지 않습니다.
+fx.example.welcome=시작
+fx.example.welcome.license=오픈 소스 프로토콜: [MIT LICENSE,https://res.imyeyu.net/licenses/MIT.txt]
+fx.example.welcome.timifx_icon=픽셀풍 글꼴 아이콘 라이브러리
+fx.example.welcome.timijava=일반 인터페이스, 일반 반사, 일반 도구, 구성 시스템 및 다국어 시스템 등
+fx.example.welcome.tips=원형 유지, 직각 어셈블리, 융합 프레임
+fx.example.welcome.tips0=네이티브 유지: 이 JavaFX 종속 라이브러리는 네이티브 UI를 재설계하지 않았으며 대부분의 구성 요소는 네이티브 스타일을 유지합니다 (훌륭한 디자인은 할 수 없습니다).
+fx.example.welcome.tips1=직각 어셈블리: 융합 프레임에는 직각 어셈블리 설계가 필요하며 다른 어셈블리와 바로 붙을 수 있습니다.
+fx.example.welcome.tips2=융합 테두리: 테두리 공유, Java 코드 또는 CSS를 사용하여 구성 요소 4 테두리를 빠르게 설정하고 구성 요소 간에 부분 테두리를 공유합니다.
+fx.example.welcome.tips3=이 프로그램은 \ [timi-fx,https://git.imyeyu.net/Timi/timi-fx]\의 효과 데모 프로그램입니다. 사용 방법은 \ [문서,https://doc.imyeyu.net/timi-fx]\,\[timi-fx, https://doc.imyeyu.net/timi-fx]\ 의존 \ [timi-java,https://doc.imyeyu.net/timi-java]\ 및 \ [timi-fx-icon,https://doc.imyeyu.net/timi-fx-icon]
+fx.example.welcome.top=[문서,https://doc.imyeyu.net/timi-fx/] [소스,https://git.imyeyu.net/Timi/timi-fx]
+fx.example.welcome.version=데모 버전:
+fx.example.welcome.version.timifx=timi-fx 버전: {0}
+fx.example.x_pagination.chunk=현재 페이지 수
+fx.example.x_pagination.index=현재 활성화 아래 첨자
+fx.example.x_pagination.size=단일 페이지 데이터 양
+fx.example.x_pagination.total=총 데이터 양
+fx.example.x_tab_pane=레이블 패널
+fx.example.x_tab_pane.tips=스타일이 조정되고 태그 마지막에 추가 버튼이 추가됩니다. 이 버튼은 기본적으로 이벤트가 없습니다.
+fx.example.x_tree_view=다중 루트 노드 트리
+fx.example.x_tree_view.tips=JavaFX TreeView는 기본적으로 루트 노드가 하나만 있고 XTreeView는 여러 루트 노드를 허용하며 파일 트리 목록이 가장 좋은 예입니다 (디스크 파티션이 하나만 없는 경우).
+hover=지향
+icon=아이콘
+image=그림
+important=중요
+input=입력
+lang=언어
+light=밝기
+mpf=프레임 생성 시간
+other=기타
+pagination=페이지 나누기
+play=재생
+remove=제거
+reset=재설정
+send=발송
+shadow=투영
+show=표시
+source=소스 코드
+style=스타일
+text=텍스트
+tips.restart=애플리케이션을 재부팅해야 합니다. 지금 재부팅하시겠습니까?
+tips.restart.error=자동 재부팅 실패, 수동 재부팅 시도
+title=제목
+type=유형
+version.checking={0} - 업데이트 확인 중
+version.new={0}, 새 버전: {1}
+width=너비
diff --git a/src/test/resources/lang/ru_RU.lang b/src/test/resources/lang/ru_RU.lang
new file mode 100644
index 0000000..22fdca1
--- /dev/null
+++ b/src/test/resources/lang/ru_RU.lang
@@ -0,0 +1,177 @@
+add=Добавить
+alert=Пулевое окно
+animation=Анимация
+background=Справочная информация
+blog=Личный блог
+border=Границы
+color=Цвет
+commonly=Общие вопросы
+component=Компонент
+content=Содержание
+copyright=Авторское право
+custom=Настройка
+dark=Тёмный
+default=По умолчанию
+description=Примечания
+developer.arg=Разработчик: {0}
+disable=Запретить
+document=Документация
+duration=Продолжительность
+enable=Включить
+example=Примеры
+exit=Выход
+file=Документация
+file.select.directory=Выбрать папку
+focus=Фокус
+fps=Частота кадров
+fx.example.alert.can_cancel=Можно отменить
+fx.example.alert.deadly_error=Смертельная ошибка
+fx.example.alert.file_blend=Смешанный выбор
+fx.example.alert.multi_tips=Поддерживает одноуровневый, многоуровневый, смешанный выбор уровней
+fx.example.alert.text_area=Текстовое поле
+fx.example.alert.text_field=Текст
+fx.example.animation_renderer=Управляемый рендеринг FPS
+fx.example.animation_renderer.pref_fps=Установить частоту кадров
+fx.example.animation_renderer.restart=Перезагрузить и {0}
+fx.example.animation_renderer.status=Текущее состояние
+fx.example.animation_renderer.tips=Простой и простой в использовании и управляемый рендеринг, скорость обновления синхронного дисплея по умолчанию, JVM должен добавить следующие параметры запуска, чтобы преодолеть скорость обновления дисплея.
+fx.example.better=После оптимизации
+fx.example.binding_config=Настроить привязки
+fx.example.binding_config.description=Вышеприведенное обновление будет синхронизировано с изменением значений в профиле TimiFXExamples. Как видно из файла ini, JavaFX \ \ \ \ \ \ 35dy FF7A9B, любой контролируемый атрибут 'может быть быстро реализован либо в одном, либо в другом направлении через класс.
+fx.example.binding_config.interpolator_duration=Продолжительность анимационного интерполятора
+fx.example.binding_config.stage_size=Размер окна
+fx.example.binding_config.tips=Эта функция зависит от Timi - java, https://doc.imyeyu.net/timi-java ] содержимое системы конфигурации timi - java описано в следующих ссылках.
+fx.example.check_box_picker=Выбор параметров
+fx.example.check_box_picker.item=Параметры {0}
+fx.example.date_time_picker=Выбор времени
+fx.example.demo.text=Чао часто жалеет, не забывает ночью
+fx.example.demo.text_long=озеро это твой взгляд, мечта полна звёзд. сердце - Это сказка, которую все время ждут. в детстве было много любимых людей. весна - это путь, которым обладает море те, кого я люблю, те, что уходят те вечные клятвы снова и снова. те, кто любит меня, те осадочные слёзы. те вечные клятвы снова и снова. у всех нас было наивное и грустное лицо. рукой за солнце мы смотрим вдаль. день за днем, год за годом. сколько начинать всё с нуля? \ и есть ли смысл?! день за днем, год за годом. сколько начинать всё с нуля? \ и есть ли смысл?! И если мы повзрослеем, будем ли мы еще желать.
+fx.example.drag_me=Перетащите меня.
+fx.example.draggable_node=Перетаскиваемый блок
+fx.example.draggable_node.tips=Позволяет компонентам реагировать на перетаскивание мышью, поддерживая динамические ограничения.
+fx.example.draggable_window=Перетаскиваемые окна
+fx.example.draggable_window.tips=Пусть компонент запускает перетаскивание окна, как правило, используется в окнах без границ, поддерживая динамические ограничения.
+fx.example.editable_table_cell=Изменить ячейки
+fx.example.editable_table_cell.tips=Таблицы могут редактировать ячейки, относительно оригинальные редактируемые эффекты, которые не имеют стиля входного поля, как Excel
+fx.example.extend_tools=Расширенные инструменты
+fx.example.extend_tools.bg_fill=Быстрое заполнение фона
+fx.example.extend_tools.bg_image=Быстрое создание фона изображения
+fx.example.extend_tools.column=Быстрое создание атрибутов столбца GridPane
+fx.example.extend_tools.directory_selector=Быстрый выбор папок
+fx.example.extend_tools.file_selector=Быстрый выбор файлов
+fx.example.extend_tools.logic_bindings=вычисление с несколькими логическими привязками
+fx.example.extend_tools.no_selection_model=Отключить режим выбора списка
+fx.example.extend_tools.row=Быстрое создание свойств строки GridPane
+fx.example.extend_tools.text_flower=Быстрое построение текстового потока, поддержка анализа богатого текста
+fx.example.extend_tools.tips=Эти инструменты помогут в создании интерфейса JavaFX.
+fx.example.extend_tools.x_anchor_pane=Быстрое расположение якорных точек
+fx.example.extend_tools.x_border=Границы быстрого построения
+fx.example.file_tree_view=Дерево файлов
+fx.example.file_tree_view.directory=Показать только папки
+fx.example.file_tree_view.filter=Фильтрация (Показать только файлы.txt)
+fx.example.file_tree_view.hide=Показать скрытые файлы
+fx.example.file_tree_view.tips=Узел файла загружается асинхронно и загружает только один слой, поэтому папка всегда может быть развернута, когда она не развернута, даже если в ней нет файла.
+fx.example.icon_button=Кнопка значка
+fx.example.icon_button.not_text=Нет текста
+fx.example.icon_button.not_text_with_bg=Нет текста с фоном
+fx.example.icon_button.with_bg=Предыстория
+fx.example.icon_picker=Выбор значков
+fx.example.icon_picker.tips=Этот список иконок основан на иконках шрифта timi - fx - icon:
+fx.example.interpolator=Медленная анимация
+fx.example.interpolator.demo={0} ({1}, {2}, {3}, {4})
+fx.example.interpolator.tips=Линейная анимация не имеет души, и анимация функции замедления Безеля может сделать движение более ярким.
+fx.example.label_progress_bar=Продвижение тегов
+fx.example.label_progress_bar.tips=Корпус как полоса прогресса, встроенный компонент тегов
+fx.example.navigation=Меню Навигация
+fx.example.navigation.expanded=Развертывание по умолчанию
+fx.example.navigation.multi=Несколько уровней
+fx.example.navigation.second=Второй уровень
+fx.example.navigation.tips=Меню навигации, используемое слева от этой программы, является этим компонентом.
+fx.example.number_field=Цифровой ввод
+fx.example.popup_tips=Показать подсказку
+fx.example.popup_tips.keep_show=Нажмите, чтобы сохранить отображение
+fx.example.popup_tips.text_long=Длинный текст
+fx.example.popup_tips.tips=всплывающие подсказки могут быть установлены на любой компонент, всплывающий контент может быть полностью настроен, этот компонент является единичным случаем, и в любой момент есть только один всплывающий подсказка.
+fx.example.progress_slider=Корректировка хода скольжения
+fx.example.run_async=Асинхронная задача
+fx.example.run_async.description=выше, чем \ [RunAsync Scheduled,] https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html ] \ \ s [Кэл, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call (Javafx.util.Duration, net.imyeyu.timijava.bean.Runback, net.imyeyu.timijava.bean.Callback)] периодически обновляется асинхронно, и, когда ограниченность ресурсов аппаратной системы приводит к образованию рисунка FPS, он может пропускать секунды, но его время является абсолютно точным.
+fx.example.run_async.tips0=все компоненты JavaFX должны находиться в дискуссии JavaFX, \ [Platform,] https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html ] \ \ s [рун латтер, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater (java.lang.Runnable) \ Можно переключить дискуссии JavaFX из неаvaFX на JavaFX, чтобы обновить компонент, но это не рекомендуется, если используется \ Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html ] \ \ s [рун латтер, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater (java.lang.Runnable) \ приводит к неправильному управлению, чрезмерное количество обновления UI может создавать помехи, программа не отвечает, пользователь плохо себя чувствует. JavaFX предлагает \ [Task,] https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html ] \ это решение проблемы, обычно используя его расширенный класс - \ [Service,] https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html ] \ и \ [ScheduledService,] https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html ] \. зависимость \ [RunAsync,] https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html ] \ в соответствии с этими двумя категориями результаты упрощения, пожалуйста, посмотрите документ.
+fx.example.run_async.tips1=Внимание: благодаря \ [таск,] https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html ] \ не будет выполнять все обновления UI, и если запрос на ВЧ - обновление больше, чем отрисовывать FPS, то часть будет оставлена без изменений, так что не следует делать вычисления в обновленной логике.
+fx.example.screen=Многоэкранная операция
+fx.example.screen.show_identify=Показать многоэкранную идентификацию
+fx.example.selectable_label=Дополнительные вкладки
+fx.example.selectable_label.tips=На этой вкладке можно настроить текст, поддерживать перенос строк, оригинальный объект - Textarea удалить стиль и настроить перенос строк
+fx.example.smooth_scroll=Скользящая прокрутка
+fx.example.smooth_scroll.tips=сглаживание прокрутки ScrollPane или виртуальные панели прокрутки (ListView или TableView и т.д. Эта функция требует больших возможностей GPU.
+fx.example.style.color.bg.title_fill=Заголовок заполнен
+fx.example.style.file=Файл стиля CSS
+fx.example.style.shadow.down=Нижняя крышка
+fx.example.style.shadow.popup=Окно без рамок
+fx.example.style.timifx=Этот интерфейс является общедоступным интерфейсом общего назначения, который имеет множество общих параметров и функциональных методов, а статические переменные могут выполнять прямые ссылки на этот интерфейс.
+fx.example.text_area_editor=Текстовый редактор
+fx.example.text_area_editor.field=Окно редактора всплывающих окон
+fx.example.text_area_editor.key_tips.ctrl_d=Удалить строку фокусировки
+fx.example.text_area_editor.key_tips.ctrl_f=Открыть поиск
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=Новая линия вверх
+fx.example.text_area_editor.key_tips.ctrl_shift_u=Переключить выделенный размер записи
+fx.example.text_area_editor.key_tips.shift_enter=Открыть новую строку вниз
+fx.example.text_area_editor.tips=Сложный текстовый редактор, поддерживающий поиск, расширение функций
+fx.example.title=Пример программы TimiFX
+fx.example.title_label=Заголовок
+fx.example.title_label.color=Настройка цвета
+fx.example.title_label.spacing=Пользовательский интервал
+fx.example.toggle_icon=Переключить значок
+fx.example.toggle_icon.diff=Переключить значок по состоянию
+fx.example.toggle_icon.tips=Форма значка ToggleButton, которая может быть без кнопок
+fx.example.toggle_icon.with_bg=Стиль кнопки
+fx.example.tray=Системный лоток
+fx.example.tray.icon=Значок лотка
+fx.example.tray.send_msg=Отправить системные сообщения
+fx.example.tray.send_msg.tips=Показывать значки в лотке
+fx.example.tray.tips=Индивидуальный объект. Этот лоток построен AWT, но меню является компонентом JavaFX UI. При закрытии программы значок должен быть удален вручную, и операционная система не будет отслеживать, работает ли программа лотка.
+fx.example.welcome=Добро пожаловать в использование
+fx.example.welcome.license=[MIT LICENSE, https://res.imyeyu.net/licenses/MIT.txt ]
+fx.example.welcome.timifx_icon=Библиотека пикселей
+fx.example.welcome.timijava=Универсальные интерфейсы, универсальные рефлексы, универсальные инструменты, системы конфигурации, многоязычные системы и т.д.
+fx.example.welcome.tips=Сохранить исходный, прямоугольный компонент, рамку слияния
+fx.example.welcome.tips0=Сохраняйте оригинальность: эта библиотека зависимостей JavaFX не перерабатывает оригинальный UI, и большинство компонентов остаются оригинальными (я не могу сделать хороший дизайн)
+fx.example.welcome.tips1=Компоненты под прямым углом: рамка слияния требует конструкции компонентов под прямым углом, которые могут быть расположены рядом с другими компонентами
+fx.example.welcome.tips2=Границы слияния: общие границы, использование кода Java или CSS для быстрой настройки четырех границ компонентов, общие границы частей между компонентами
+fx.example.welcome.tips3=Эта программа только \ [timi - fx,] https://git.imyeyu.net/Timi/timi-fx ] \ демонстрационная программа, используйте режим \ [документация,] https://doc.imyeyu.net/timi-fx ] \, \ [timi - fx,] https://doc.imyeyu.net/timi-fx [timi - java,] https://doc.imyeyu.net/timi-java ] \ и \ [timi - fx - icon, https://doc.imyeyu.net/timi-fx-icon ]
+fx.example.welcome.top=< TEXT > [Документация https://doc.imyeyu.net/timi-fx/ ] > < https://git.imyeyu.net/Timi/timi-fx ]
+fx.example.welcome.version=Демонстрационная версия:
+fx.example.welcome.version.timifx=версия timi - fx: {0}
+fx.example.x_pagination.chunk=Текущее количество страниц
+fx.example.x_pagination.index=Текущая активация
+fx.example.x_pagination.size=Объем данных на одной странице
+fx.example.x_pagination.total=Общий объем данных
+fx.example.x_tab_pane=Панель вкладок
+fx.example.x_tab_pane.tips=Изменить стиль и добавить кнопку добавления в конце вкладки, которая по умолчанию не содержит никаких событий
+fx.example.x_tree_view=Дерево с несколькими корневыми узлами
+fx.example.x_tree_view.tips=В JavaFX TreeView есть и только один корневой узел по умолчанию, XTreeView позволяет несколько корневых узлов, а список дерева файлов является лучшим примером (если у вас нет только одного раздела диска).
+hover=Направление
+icon=Значок
+image=Фотографии
+important=Важное
+input=Ввод
+lang=Язык
+light=Яркий
+mpf=Время создания кадра
+other=Прочее
+pagination=Страницы
+play=Трансляция
+remove=Удалить
+reset=Сбросить
+send=Отправить
+shadow=Проекция
+show=Показать
+source=Исходный код
+style=Стиль
+text=Текст
+tips.restart=Необходимо перезагрузить приложение, перезагрузить его немедленно?
+tips.restart.error=Ошибка перезагрузки, попробуйте перезагрузить вручную
+title=Заголовок
+type=Тип
+version.checking={0} - Проверяется обновление
+version.new={0}, новая версия: {1}
+width=Ширина
diff --git a/src/test/resources/lang/zh_CN.lang b/src/test/resources/lang/zh_CN.lang
new file mode 100644
index 0000000..c100d1b
--- /dev/null
+++ b/src/test/resources/lang/zh_CN.lang
@@ -0,0 +1,191 @@
+add=添加
+alert=弹窗
+animation=动画
+background=背景
+blog=个人博客
+border=边框
+color=颜色
+commonly=一般
+component=组件
+content=内容
+copyright=版权
+custom=自定义
+dark=暗
+default=默认
+description=说明
+developer.arg=开发者:{0}
+disable=禁用
+document=文档
+duration=持续时间
+enable=启用
+example=示例
+exit=退出
+file=文件
+file.select.directory=选择文件夹
+focus=聚焦
+fps=帧率
+fx.example.alert.can_cancel=可取消
+fx.example.alert.deadly_error=致命错误
+fx.example.alert.file_blend=混合选择
+fx.example.alert.multi_tips=支持单选、多选,混合层级选择
+fx.example.alert.text_area=文本域
+fx.example.alert.text_field=文本框
+fx.example.animation_renderer=可控 FPS 渲染器
+fx.example.animation_renderer.pref_fps=预设帧率
+fx.example.animation_renderer.restart=重启并{0}
+fx.example.animation_renderer.status=当前状态
+fx.example.animation_renderer.tips=简单易用且可控的渲染器,默认同步显示器刷新率,JVM 需要添加以下启动参数才可以突破显示器刷新率。
+fx.example.better=优化后
+fx.example.binding_config=配置绑定
+fx.example.binding_config.description=以上数值更新会同步修改配置文件中的值,在 TimiFXExamples.ini 文件中可以看到,JavaFX `#FF7A9B, 任何可监听属性`都可通过本类快速实现单向或双向绑定。
+fx.example.binding_config.interpolator_duration=动画插值器持续时间
+fx.example.binding_config.stage_size=窗体尺寸
+fx.example.binding_config.tips=此功能依赖 [timi-java, https://doc.imyeyu.net/timi-java] 的配置系统,有关 timi-java 配置系统的内容见以下链接。
+fx.example.check_box_picker=多选框选择器
+fx.example.check_box_picker.item=选项 {0}
+fx.example.date_time_picker=详细时间选择器
+fx.example.demo.text=朝朝频顾惜,夜夜不相忘
+fx.example.demo.text_long=湖水是你的眼神,梦想满天星辰。\n\
+心情是一个传说,亘古不变地等候。\n\
+成长是一扇树叶的门,童年有一群亲爱的人。\n\
+春天是一段路程,沧海桑田的拥有。\n\
+那些我爱的人,那些离逝的风。\n\
+那些永远的誓言一遍一遍。\n\
+那些爱我的人,那些沉淀的泪。\n\
+那些永远的誓言一遍一遍。\n\
+我们都曾有过一张天真而忧伤的脸。\n\
+手握阳光我们望着遥远。\n\
+轻轻地一天天,一年又一年。\n\
+长大间,我们是否还会再唱起心愿。\n\
+轻轻地一天天,一年又一年。\n\
+长大间,我们是否还会再唱起心愿。\n\
+长大间,我们是否还会再唱起心愿。
+fx.example.drag_me=拖动我
+fx.example.draggable_node=可拖动组件
+fx.example.draggable_node.tips=让组件响应鼠标拖动,支持动态限位。
+fx.example.draggable_window=可拖动窗体
+fx.example.draggable_window.tips=让某组件触发窗体拖动,通常是无边框窗体使用,支持动态限位。
+fx.example.editable_table_cell=可编辑单元格
+fx.example.editable_table_cell.tips=表格可编辑单元格,相对原始的可编辑效果,此单元格没有输入框的样式,就像 Excel 那样
+fx.example.extend_tools=扩展工具
+fx.example.extend_tools.bg_fill=快速构造填充背景
+fx.example.extend_tools.bg_image=快速构造图像背景
+fx.example.extend_tools.column=快速构造 GridPane 列属性
+fx.example.extend_tools.directory_selector=快速构造文件夹选择器
+fx.example.extend_tools.file_selector=快速构造文件选择器
+fx.example.extend_tools.logic_bindings=多项布尔值绑定计算
+fx.example.extend_tools.no_selection_model=禁用列表视图选择模式
+fx.example.extend_tools.row=快速构造 GridPane 行属性
+fx.example.extend_tools.text_flower=快速构造文本流,支持解析富文本
+fx.example.extend_tools.tips=这些工具将对构建 JavaFX 界面提供极大帮助。
+fx.example.extend_tools.x_anchor_pane=快速配置锚点布局
+fx.example.extend_tools.x_border=快速构造边框
+fx.example.file_tree_view=文件树
+fx.example.file_tree_view.directory=仅显示文件夹
+fx.example.file_tree_view.filter=过滤(仅显示 .txt 文件)
+fx.example.file_tree_view.hide=显示隐藏文件
+fx.example.file_tree_view.tips=文件节点为异步加载,并只加载一层,所以在未展开文件夹时,文件夹始终可被展开,即使里面没有文件。
+fx.example.icon_button=图标按钮
+fx.example.icon_button.not_text=无文本
+fx.example.icon_button.not_text_with_bg=无文本有背景
+fx.example.icon_button.with_bg=有背景
+fx.example.icon_picker=图标选择器
+fx.example.icon_picker.tips=此图标列表来源于 timi-fx-icon 字体图标:
+fx.example.interpolator=缓动动画
+fx.example.interpolator.demo={0} ({1}, {2}, {3}, {4})
+fx.example.interpolator.tips=线性动画是没有灵魂的,贝塞尔缓动函数动画可以让运动更生动。
+fx.example.label_progress_bar=标签进度
+fx.example.label_progress_bar.tips=本体为进度条,内置标签组件
+fx.example.navigation=导航菜单
+fx.example.navigation.expanded=默认展开
+fx.example.navigation.multi=多个二级
+fx.example.navigation.second=二级
+fx.example.navigation.tips=本程序左侧使用的导航菜单就是本组件。
+fx.example.number_field=数字输入框
+fx.example.popup_tips=弹出提示
+fx.example.popup_tips.keep_show=点击可保持显示
+fx.example.popup_tips.text_long=长文本
+fx.example.popup_tips.tips=弹出提示可以安装到任何组件上,弹出内容可完全自定义,此组件是单例,任何时刻只有一个弹出提示。
+fx.example.progress_slider=进度滑动调整
+fx.example.run_async=异步任务
+fx.example.run_async.description=以上时间由 [RunAsyncScheduled, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html].[call, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call(javafx.util.Duration,net.imyeyu.timijava.bean.Runback,net.imyeyu.timijava.bean.Callback)]() 定时异步更新,当硬件系统资源紧张导致渲染 FPS 极低时,可能会跳过一秒,但它的时间是绝对准确地取于系统。
+fx.example.run_async.tips0=操作 JavaFX 组件都应在 JavaFX 线程里,[Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html].[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]() 可以在任何地方从非 JavaFX 线程切换到 JavaFX 线程,从而进行组件更新操作,但这是非常不推荐的,如果滥用 [Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html].[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater(java.lang.Runnable)]() 导致控制不当,过多的 UI 更新队列可能会造成阻塞,程序无响应,用户体验差。JavaFX 提供的 [Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html] 是解决这一问题的方案,通常使用他的扩展类 [Service, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html] 和 [ScheduledService, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html]。本依赖 [RunAsync, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html] 是根据这两个类做简化的结果,使用方式请看文档。
+fx.example.run_async.tips1=注意:由于 [Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html] 并不会执行所有 UI 更新,如果高频更新请求大于渲染 FPS 将舍弃部分无效更新,所以一定不要在更新逻辑里做计算业务。
+fx.example.screen=多屏操作
+fx.example.screen.show_identify=显示多屏标识
+fx.example.selectable_label=可选标签
+fx.example.selectable_label.tips=此标签可以选中文本,支持换行,原对象是 TextArea 去掉样式并自适应换行
+fx.example.smooth_scroll=平滑滚动
+fx.example.smooth_scroll.tips=平滑滚动 ScrollPane 或虚拟滚动面板(ListView 或 TableView 等),注意,如果是虚拟滚动面板,该面板列表项需要统一固定高度,否则无法预测虚拟高度。此功能需要较多 GPU 性能。
+fx.example.style.color.bg.title_fill=标题填充
+fx.example.style.file=CSS 样式文件
+fx.example.style.shadow.down=下边遮罩
+fx.example.style.shadow.popup=无边框窗体
+fx.example.style.timifx=此接口为公共通用接口,有很多通用参数和功能方法,静态变量可实现此接口直接引用。
+fx.example.text_area_editor=文本编辑器
+fx.example.text_area_editor.field=支持文本框弹窗编辑器窗体
+fx.example.text_area_editor.key_tips.ctrl_d=删除聚焦行
+fx.example.text_area_editor.key_tips.ctrl_f=打开搜索
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=向上开新行
+fx.example.text_area_editor.key_tips.ctrl_shift_u=切换选中大小写
+fx.example.text_area_editor.key_tips.shift_enter=向下开新行
+fx.example.text_area_editor.tips=复杂的文本编辑器,支持搜索、功能扩展
+fx.example.title=TimiFX 示例程序
+fx.example.title_label=标题标签
+fx.example.title_label.color=自定义颜色
+fx.example.title_label.spacing=自定义间距
+fx.example.toggle_icon=切换图标
+fx.example.toggle_icon.diff=根据状态切换图标
+fx.example.toggle_icon.tips=ToggleButton 的图标形式,可以无按钮样式
+fx.example.toggle_icon.with_bg=按钮样式
+fx.example.tray=系统托盘
+fx.example.tray.icon=托盘图标
+fx.example.tray.send_msg=发送系统消息
+fx.example.tray.send_msg.tips=在显示托盘图标时有效
+fx.example.tray.tips=单例对象。此托盘由 AWT 构造,但菜单是 JavaFX UI 的组件。程序关闭时必须手动移除图标,操作系统不会监听托盘程序是否还在运行。
+fx.example.welcome=欢迎使用
+fx.example.welcome.license=开源协议:[MIT LICENSE, https://res.imyeyu.net/licenses/MIT.txt]
+fx.example.welcome.timifx_icon=像素风字体图标库
+fx.example.welcome.timijava=通用接口、通用反射、通用工具、配置系统和多语言系统等
+fx.example.welcome.tips=保持原生,直角组件,融合边框
+fx.example.welcome.tips0=保持原生:本 JavaFX 依赖库没有对原生 UI 进行重新设计,多数组件保持原生样式(我无法做到优秀的设计)
+fx.example.welcome.tips1=直角组件:融合边框需要直角组件设计,组件可以和其他组件紧挨着
+fx.example.welcome.tips2=融合边框:边框共享,使用 Java 代码或 CSS 快速设置组件四边框,组件之间共享部分边框
+fx.example.welcome.tips3=本程序仅为 [timi-fx, https://git.imyeyu.net/Timi/timi-fx] 的效果演示程序,使用方式请看[文档, https://doc.imyeyu.net/timi-fx],[timi-fx, https://doc.imyeyu.net/timi-fx] 依赖 [timi-java, https://doc.imyeyu.net/timi-java] 和 [timi-fx-icon, https://doc.imyeyu.net/timi-fx-icon]
+fx.example.welcome.top=[文档, https://doc.imyeyu.net/timi-fx/] | [源码, https://git.imyeyu.net/Timi/timi-fx]
+fx.example.welcome.version=演示程序版本:
+fx.example.welcome.version.timifx=timi-fx 版本:{0}
+fx.example.x_pagination.chunk=当前页数
+fx.example.x_pagination.index=当前激活下标
+fx.example.x_pagination.size=单页数据量
+fx.example.x_pagination.total=总数据量
+fx.example.x_tab_pane=标签面板
+fx.example.x_tab_pane.tips=调整了样式,并在标签最后增加一个添加按钮,此按钮默认没有任何事件
+fx.example.x_tree_view=多个根节点树
+fx.example.x_tree_view.tips=JavaFX TreeView 默认有且只有一个根节点,XTreeView 允许多个根节点,文件树列表是最好的示例(除非你只有一个磁盘分区)。
+hover=指向
+icon=图标
+image=图片
+important=重要
+input=输入
+lang=语言
+light=亮
+mpf=帧生成时间
+other=其他
+pagination=分页
+play=播放
+remove=移除
+reset=复位
+send=发送
+shadow=投影
+show=显示
+source=源码
+style=样式
+text=文本
+tips.restart=需要重启应用程序,是否立即重启?
+tips.restart.error=自重启失败,请尝试手动重启
+title=标题
+type=类型
+version.checking={0} - 正在检查更新
+version.new={0},新版本:{1}
+width=宽度
diff --git a/src/test/resources/lang/zh_TW.lang b/src/test/resources/lang/zh_TW.lang
new file mode 100644
index 0000000..5450ffd
--- /dev/null
+++ b/src/test/resources/lang/zh_TW.lang
@@ -0,0 +1,177 @@
+add=添加
+alert=彈窗
+animation=動畫
+background=背景
+blog=個人部落格
+border=邊框
+color=顏色
+commonly=一般
+component=組件
+content=內容
+copyright=版權
+custom=自定義
+dark=暗
+default=默認
+description=說明
+developer.arg=開發者:{0}
+disable=禁用
+document=檔案
+duration=持續時間
+enable=啟用
+example=示例
+exit=退出
+file=文件
+file.select.directory=選擇資料夾
+focus=聚焦
+fps=幀率
+fx.example.alert.can_cancel=可取消
+fx.example.alert.deadly_error=致命錯誤
+fx.example.alert.file_blend=混合選擇
+fx.example.alert.multi_tips=支持單選、多選,混合層級選擇
+fx.example.alert.text_area=文本域
+fx.example.alert.text_field=文字方塊
+fx.example.animation_renderer=可控FPS渲染器
+fx.example.animation_renderer.pref_fps=預設幀率
+fx.example.animation_renderer.restart=重啓並{0}
+fx.example.animation_renderer.status=當前狀態
+fx.example.animation_renderer.tips=簡單易用且可控的渲染器,默認同步顯示器刷新率,JVM需要添加以下啟動參數才可以突破顯示器刷新率。
+fx.example.better=優化後
+fx.example.binding_config=配寘綁定
+fx.example.binding_config.description=以上數值更新會同步修改設定檔中的值,在TimiFXExamples.ini檔案中可以看到,JavaFX `#FF7A9B,任何可監聽内容`都可通過本類快速實現單向或雙向綁定。
+fx.example.binding_config.interpolator_duration=動畫插值器持續時間
+fx.example.binding_config.stage_size=表單尺寸
+fx.example.binding_config.tips=此功能依賴[timi-java, https://doc.imyeyu.net/timi-java ]的配寘系統,有關timi-java配寘系統的內容見以下連結。
+fx.example.check_box_picker=多選框選擇器
+fx.example.check_box_picker.item=選項{0}
+fx.example.date_time_picker=詳細時間選擇器
+fx.example.demo.text=朝朝頻顧惜,夜夜不相忘
+fx.example.demo.text_long=湖水是你的眼神,夢想滿天星辰。\ 心情是一個傳說,亘古不變地等候。\ 成長是一扇樹葉的門,童年有一群親愛的人。\ 春天是一段路程,滄海桑田的擁有。\ 那些我愛的人,那些離逝的風。\ 那些永遠的誓言一遍一遍。\ 那些愛我的人,那些沉澱的淚。\ 那些永遠的誓言一遍一遍。\ 我們都曾有過一張天真而憂傷的臉。\ 手握陽光我們望著遙遠。\ 輕輕地一天天,一年又一年。\ 長大間,我們是否還會再唱起心願。\ 輕輕地一天天,一年又一年。\ 長大間,我們是否還會再唱起心願。\ 長大間,我們是否還會再唱起心願。
+fx.example.drag_me=拖動我
+fx.example.draggable_node=可拖動組件
+fx.example.draggable_node.tips=讓組件響應滑鼠拖動,支持動態限比特。
+fx.example.draggable_window=可拖動表單
+fx.example.draggable_window.tips=讓某組件觸發表單拖動,通常是無邊框表單使用,支持動態限比特。
+fx.example.editable_table_cell=可編輯儲存格
+fx.example.editable_table_cell.tips=表格可編輯儲存格,相對原始的可編輯效果,此儲存格沒有輸入框的樣式,就像Excel那樣
+fx.example.extend_tools=擴展工具
+fx.example.extend_tools.bg_fill=快速構造填充背景
+fx.example.extend_tools.bg_image=快速構造影像背景
+fx.example.extend_tools.column=快速構造GridPane列内容
+fx.example.extend_tools.directory_selector=快速構造資料夾選擇器
+fx.example.extend_tools.file_selector=快速構造檔案選擇器
+fx.example.extend_tools.logic_bindings=多項布林值綁定計算
+fx.example.extend_tools.no_selection_model=禁用清單視圖選擇模式
+fx.example.extend_tools.row=快速構造GridPane行内容
+fx.example.extend_tools.text_flower=快速構造文字流,支持解析富文字
+fx.example.extend_tools.tips=這些工具將對構建JavaFX介面提供極大幫助。
+fx.example.extend_tools.x_anchor_pane=快速配寘錨點佈局
+fx.example.extend_tools.x_border=快速構造邊框
+fx.example.file_tree_view=檔案樹
+fx.example.file_tree_view.directory=僅顯示資料夾
+fx.example.file_tree_view.filter=過濾(僅顯示.txt檔案)
+fx.example.file_tree_view.hide=顯示隱藏文件
+fx.example.file_tree_view.tips=檔案節點為非同步加載,並只加載一層,所以在未展開資料夾時,資料夾始終可被展開,即使裡面沒有檔案。
+fx.example.icon_button=圖標按鈕
+fx.example.icon_button.not_text=無文本
+fx.example.icon_button.not_text_with_bg=無文本有背景
+fx.example.icon_button.with_bg=有背景
+fx.example.icon_picker=圖標選擇器
+fx.example.icon_picker.tips=此圖標清單來源於timi-fx-icon字體圖標:
+fx.example.interpolator=緩動動畫
+fx.example.interpolator.demo={0}({1},{2},{3},{4})
+fx.example.interpolator.tips=線性動畫是沒有靈魂的,貝塞爾緩動函數動畫可以讓運動更生動。
+fx.example.label_progress_bar=標籤進度
+fx.example.label_progress_bar.tips=本體為進度條,內寘標籤組件
+fx.example.navigation=導航菜單
+fx.example.navigation.expanded=默認展開
+fx.example.navigation.multi=多個二級
+fx.example.navigation.second=二級
+fx.example.navigation.tips=本程式左側使用的導航菜單就是本組件。
+fx.example.number_field=數位輸入框
+fx.example.popup_tips=彈出提示
+fx.example.popup_tips.keep_show=點擊可保持顯示
+fx.example.popup_tips.text_long=長文字
+fx.example.popup_tips.tips=彈出提示可以安裝到任何組件上,彈出內容可完全自定義,此組件是單例,任何時刻只有一個彈出提示。
+fx.example.progress_slider=進度滑動調整
+fx.example.run_async=非同步任務
+fx.example.run_async.description=以上時間由\[RunAsyncScheduled, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html ]\.\ [call, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsyncScheduled.html#call (javafx.util.Duration,net.imyeyu.timijava.bean.Runback,net.imyeyu.timijava.bean.Callback)]\()定時非同步更新,當硬體系統資源緊張導致渲染FPS極低時,可能會跳過一秒,但它的時間是絕對準確地取於系統。
+fx.example.run_async.tips0=操作JavaFX組件都應在JavaFX線程裏,\[Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html ]\.\[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater (java.lang.Runnable)]\()可以在任何地方從非JavaFX線程切換到JavaFX線程,從而進行組件更新操作,但這是非常不推薦的,如果濫用\[Platform, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html ]\.\[runLater, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/application/Platform.html#runLater (java.lang.Runnable)]\()導致控制不當,過多的UI更新隊列可能會造成阻塞,程式無響應,用戶體驗差。 JavaFX提供的\[Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html ]\是解决這一問題的方案,通常使用他的擴展類\[Service, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Service.html ]\和\[ScheduledService, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/ScheduledService.html ]\。 本依賴\[RunAsync, https://doc.imyeyu.net/timi-fx/net/imyeyu/timifx/service/RunAsync.html ]\是根據這兩個類做簡化的結果,使用管道請看檔案。
+fx.example.run_async.tips1=注意:由於\[Task, https://doc.imyeyu.net/openjfx-17/javafx.graphics/javafx/concurrent/Task.html ]\並不會執行所有UI更新,如果高頻更新請求大於渲染FPS將捨棄部分無效更新,所以一定不要在更新邏輯裏做計算業務。
+fx.example.screen=多屏操作
+fx.example.screen.show_identify=顯示多屏標識
+fx.example.selectable_label=可選標籤
+fx.example.selectable_label.tips=此標籤可以選中文字,支持換行,原對象是TextArea去掉樣式並自我調整換行
+fx.example.smooth_scroll=平滑滾動
+fx.example.smooth_scroll.tips=平滑滾動ScrollPane或虛擬滾動面板(ListView或TableView等),注意,如果是虛擬滾動面板,該面板列表項需要統一固定高度,否則無法預測虛擬高度。 此功能需要較多GPU效能。
+fx.example.style.color.bg.title_fill=標題填充
+fx.example.style.file=CSS樣式檔案
+fx.example.style.shadow.down=下邊遮罩
+fx.example.style.shadow.popup=無邊框表單
+fx.example.style.timifx=此介面為公共通用介面,有很多通用參數和功能方法,靜態變數可實現此介面直接引用。
+fx.example.text_area_editor=文字編輯器
+fx.example.text_area_editor.field=支持文字方塊彈窗編輯器表單
+fx.example.text_area_editor.key_tips.ctrl_d=删除聚焦行
+fx.example.text_area_editor.key_tips.ctrl_f=打開蒐索
+fx.example.text_area_editor.key_tips.ctrl_shift_enter=向上開新行
+fx.example.text_area_editor.key_tips.ctrl_shift_u=切換選中大小寫
+fx.example.text_area_editor.key_tips.shift_enter=向下開新行
+fx.example.text_area_editor.tips=複雜的文字編輯器,支持蒐索、功能擴展
+fx.example.title=TimiFX示例程式
+fx.example.title_label=標題標籤
+fx.example.title_label.color=自定義顏色
+fx.example.title_label.spacing=自定義間距
+fx.example.toggle_icon=切換圖標
+fx.example.toggle_icon.diff=根據狀態切換圖標
+fx.example.toggle_icon.tips=ToggleButton的圖標形式,可以無按鈕樣式
+fx.example.toggle_icon.with_bg=按鈕樣式
+fx.example.tray=系統託盤
+fx.example.tray.icon=託盤圖標
+fx.example.tray.send_msg=發送系統消息
+fx.example.tray.send_msg.tips=在顯示託盤圖標時有效
+fx.example.tray.tips=單例對象。 此託盤由AWT構造,但選單是JavaFX UI的組件。 程式關閉時必須手動移除圖標,作業系統不會監聽託盤程式是否還在運行。
+fx.example.welcome=歡迎使用
+fx.example.welcome.license=開源協定:[MIT LICENSE, https://res.imyeyu.net/licenses/MIT.txt ]
+fx.example.welcome.timifx_icon=點數風字體圖標庫
+fx.example.welcome.timijava=通用介面、通用反射、通用工具、配寘系統和多語言系統等
+fx.example.welcome.tips=保持原生,直角組件,融合邊框
+fx.example.welcome.tips0=保持原生:本JavaFX依賴庫沒有對原生UI進行重新設計,多數組件保持原生樣式(我無法做到優秀的設計)
+fx.example.welcome.tips1=直角組件:融合邊框需要直角組件設計,組件可以和其他組件緊挨著
+fx.example.welcome.tips2=融合邊框:邊框共亯,使用Java程式碼或CSS快速設定組件四邊框,組件之間共亯部分邊框
+fx.example.welcome.tips3=本程式僅為\[timi-fx, https://git.imyeyu.net/Timi/timi-fx ]\的效果演示程式,使用管道請看\[檔案, https://doc.imyeyu.net/timi-fx ]\,\[timi-fx, https://doc.imyeyu.net/timi-fx ]\依賴\[timi-java, https://doc.imyeyu.net/timi-java ]\和\[timi-fx-icon, https://doc.imyeyu.net/timi-fx-icon ]
+fx.example.welcome.top=[檔案, https://doc.imyeyu.net/timi-fx/ ] | [源碼, https://git.imyeyu.net/Timi/timi-fx ]
+fx.example.welcome.version=演示程式版本:
+fx.example.welcome.version.timifx=timi-fx版本:{0}
+fx.example.x_pagination.chunk=當前頁數
+fx.example.x_pagination.index=當前啟動下標
+fx.example.x_pagination.size=單頁數據量
+fx.example.x_pagination.total=總數據量
+fx.example.x_tab_pane=標籤面板
+fx.example.x_tab_pane.tips=調整了樣式,並在標籤最後新增一個添加按鈕,此按鈕默認沒有任何事件
+fx.example.x_tree_view=多個根節點樹
+fx.example.x_tree_view.tips=JavaFX TreeView默認有且只有一個根節點,XTreeView允許多個根節點,檔案樹清單是最好的示例(除非你只有一個磁碟分割)。
+hover=指向
+icon=圖標
+image=圖片
+important=重要
+input=輸入
+lang=語言
+light=亮
+mpf=幀生成時間
+other=其他
+pagination=分頁
+play=播放
+remove=移除
+reset=復位
+send=發送
+shadow=投影
+show=顯示
+source=源碼
+style=樣式
+text=文字
+tips.restart=需要重啓應用程序,是否立即重啓?
+tips.restart.error=自重啓失敗,請嘗試手動重啓
+title=標題
+type=類型
+version.checking={0} -正在檢查更新
+version.new={0},新版本:{1}
+width=寬度
diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml
new file mode 100644
index 0000000..dc8b054
--- /dev/null
+++ b/src/test/resources/logback.xml
@@ -0,0 +1,21 @@
+
+
+
+ [%d{HH:mm:ss.SSS}][%-5level][%-32logger{32}] %msg%n
+
+
+
+ ./logs/debug.log
+
+ [%d{HH:mm:ss.SSS}][%-5level][%-32logger{32}] %msg%n
+
+
+ ./logs/debug/debug.%d{yyyy-MM-dd}.log
+ 30
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/splash-screen.png b/src/test/resources/splash-screen.png
new file mode 100644
index 0000000..78f6a1b
Binary files /dev/null and b/src/test/resources/splash-screen.png differ