Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b44404c469 | ||
|
|
0d53d521bf | ||
|
|
a9c14bfa0b | ||
|
|
6ef823b5ba | ||
|
|
d577e1fe30 | ||
|
|
db1ef60add |
No files matched your search
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: QQ 群
|
||||
url: https://docs.hmcl.net/groups.html
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。
|
||||
- name: Discord 服务器
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 服务器。
|
||||
@@ -0,0 +1,79 @@
|
||||
name: Check Update
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '30 * * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- channel: dev
|
||||
task: checkUpdateDev
|
||||
- channel: stable
|
||||
task: checkUpdateStable
|
||||
runs-on: ubuntu-latest
|
||||
name: check-update-${{ matrix.channel }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
- name: Fetch last version
|
||||
run: ./gradlew ${{ matrix.task }} --no-daemon --info --stacktrace
|
||||
- name: Check for existing tags
|
||||
run: if [ -z "$(git tag -l "$HMCL_TAG_NAME")" ]; then echo "continue=true" >> $GITHUB_ENV; fi
|
||||
- name: Download artifacts
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh.sha256"
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
if [ "${{ matrix.channel }}" = "stable" ]; then
|
||||
echo "**This version is a stable version.**" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
fi
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/${{ matrix.channel }}.html" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
HMCL-${{ env.HMCL_VERSION }}.exe
|
||||
HMCL-${{ env.HMCL_VERSION }}.jar
|
||||
HMCL-${{ env.HMCL_VERSION }}.sh
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
prerelease: true
|
||||
@@ -1,125 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '30 * * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- channel: dev
|
||||
task: checkUpdateDev
|
||||
- channel: stable
|
||||
task: checkUpdateStable
|
||||
runs-on: ubuntu-latest
|
||||
name: check-update-${{ matrix.channel }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
- name: Fetch last version
|
||||
run: ./gradlew ${{ matrix.task }} --no-daemon --info --stacktrace
|
||||
- name: Check for existing tags
|
||||
run: if [ -z "$(git tag -l "$HMCL_TAG_NAME")" ]; then echo "continue=true" >> $GITHUB_ENV; fi
|
||||
- name: Download artifacts
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh.sha256"
|
||||
- name: Verify artifacts
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
export JAR_SHA256=$(cat HMCL-$HMCL_VERSION.jar.sha256 | tr -d '\n')
|
||||
export EXE_SHA256=$(cat HMCL-$HMCL_VERSION.exe.sha256 | tr -d '\n')
|
||||
export SH_SHA256=$(cat HMCL-$HMCL_VERSION.sh.sha256 | tr -d '\n')
|
||||
|
||||
echo "$JAR_SHA256 HMCL-$HMCL_VERSION.jar" | sha256sum -c
|
||||
echo "$EXE_SHA256 HMCL-$HMCL_VERSION.exe" | sha256sum -c
|
||||
echo "$SH_SHA256 HMCL-$HMCL_VERSION.sh" | sha256sum -c
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
# GitHub Release Note
|
||||
echo " **[Changelog](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)** (Chinese)" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
|
||||
# CNB Release Note
|
||||
echo "[更新日志](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)" >> CNB_RELEASE_NOTE
|
||||
echo "" >> CNB_RELEASE_NOTE
|
||||
echo "| 文件 | SHA-256 校验码 |" >> CNB_RELEASE_NOTE
|
||||
echo "| :--- | --- |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/${{ github.repository }}/releases/download
|
||||
CNB_DOWNLOAD_BASE_URL: https://cnb.cool/HMCL-dev/HMCL/-/releases/download
|
||||
- name: Create GitHub release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
gh release create "${{ env.HMCL_TAG_NAME }}" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.exe" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.jar" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.sh" \
|
||||
--target "${{ env.HMCL_COMMIT_SHA }}" \
|
||||
--title "${{ env.HMCL_TAG_NAME }}" \
|
||||
--notes-file RELEASE_NOTE \
|
||||
--prerelease
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install git-cnb
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: go install "cnb.cool/looc/git-cnb@$GIT_CNB_VERSION"
|
||||
env:
|
||||
GIT_CNB_VERSION: '1.1.2'
|
||||
- name: Create CNB release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
echo "Uploading tags to CNB"
|
||||
git fetch --tags
|
||||
git push "https://cnb:${{ secrets.CNB_SYNC_TOKEN }}@cnb.cool/$CNB_REPO.git" "$HMCL_TAG_NAME"
|
||||
|
||||
echo "Creating CNB release"
|
||||
~/go/bin/git-cnb release create \
|
||||
--repo "$CNB_REPO" \
|
||||
--tag "$HMCL_TAG_NAME" \
|
||||
--name "HMCL $HMCL_VERSION" \
|
||||
--body "$(cat CNB_RELEASE_NOTE)" \
|
||||
--prerelease true
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.jar"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.jar"
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.exe"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.exe"
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.sh"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.sh"
|
||||
env:
|
||||
CNB_TOKEN: ${{ secrets.CNB_SYNC_TOKEN }}
|
||||
CNB_REPO: HMCL-dev/HMCL
|
||||
+5
-12
@@ -58,7 +58,6 @@ dependencies {
|
||||
implementation("libs:JFoenix")
|
||||
implementation(libs.twelvemonkeys.imageio.webp)
|
||||
implementation(libs.java.info)
|
||||
implementation(libs.monet.fx)
|
||||
|
||||
if (launcherExe.isBlank()) {
|
||||
implementation(libs.hmclauncher)
|
||||
@@ -118,6 +117,10 @@ tasks.checkstyleMain {
|
||||
exclude("**/org/jackhuang/hmcl/ui/image/apng/**")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
|
||||
}
|
||||
|
||||
val addOpens = listOf(
|
||||
"java.base/java.lang",
|
||||
"java.base/java.lang.reflect",
|
||||
@@ -125,24 +128,15 @@ val addOpens = listOf(
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.base/com.sun.javafx.event",
|
||||
"javafx.base/com.sun.javafx.runtime",
|
||||
"javafx.base/javafx.beans.property",
|
||||
"javafx.graphics/javafx.css",
|
||||
"javafx.graphics/javafx.stage",
|
||||
"javafx.graphics/com.sun.glass.ui",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.graphics/com.sun.javafx.util",
|
||||
"javafx.graphics/com.sun.prism",
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.graphics/com.sun.javafx.tk.quantum",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"jdk.attach/sun.tools.attach",
|
||||
)
|
||||
|
||||
tasks.compileJava {
|
||||
options.compilerArgs.addAll(addOpens.map { "--add-exports=$it=ALL-UNNAMED" })
|
||||
}
|
||||
|
||||
val hmclProperties = buildList {
|
||||
add("hmcl.version" to project.version.toString())
|
||||
add("hmcl.add-opens" to addOpens.joinToString(" "))
|
||||
@@ -208,8 +202,7 @@ tasks.shadowJar {
|
||||
"Main-Class" to "org.jackhuang.hmcl.Main",
|
||||
"Multi-Release" to "true",
|
||||
"Add-Opens" to addOpens.joinToString(" "),
|
||||
"Enable-Native-Access" to "ALL-UNNAMED",
|
||||
"Enable-Final-Field-Mutation" to "ALL-UNNAMED",
|
||||
"Enable-Native-Access" to "ALL-UNNAMED"
|
||||
)
|
||||
|
||||
if (launcherExe.isNotBlank()) {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.controls;
|
||||
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
/**
|
||||
* JFXClippedPane is a StackPane that clips its content if exceeding the pane bounds.
|
||||
*
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2018-06-02
|
||||
*/
|
||||
public class JFXClippedPane extends StackPane {
|
||||
|
||||
private final Region clip = new Region();
|
||||
|
||||
public JFXClippedPane() {
|
||||
super();
|
||||
init();
|
||||
}
|
||||
|
||||
public JFXClippedPane(Node... children) {
|
||||
super(children);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setClip(clip);
|
||||
clip.setBackground(new Background(new BackgroundFill(Color.BLACK, new CornerRadii(2), Insets.EMPTY)));
|
||||
backgroundProperty().addListener(observable -> JFXNodeUtils.updateBackground(getBackground(), clip));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
clip.resizeRelocate(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.controls;
|
||||
|
||||
import com.jfoenix.skins.JFXColorPickerSkin;
|
||||
import javafx.css.CssMetaData;
|
||||
import javafx.css.SimpleStyleableBooleanProperty;
|
||||
import javafx.css.Styleable;
|
||||
import javafx.css.StyleableBooleanProperty;
|
||||
import javafx.css.converter.BooleanConverter;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JFXColorPicker is the metrial design implementation of color picker.
|
||||
*
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2016-03-09
|
||||
*/
|
||||
public class JFXColorPicker extends ColorPicker {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public JFXColorPicker() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public JFXColorPicker(Color color) {
|
||||
super(color);
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new JFXColorPickerSkin(this);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the style class to 'jfx-color-picker'.
|
||||
* <p>
|
||||
* This is the selector class from which CSS can be used to style
|
||||
* this control.
|
||||
*/
|
||||
private static final String DEFAULT_STYLE_CLASS = "jfx-color-picker";
|
||||
|
||||
private double[] preDefinedColors = null;
|
||||
|
||||
public double[] getPreDefinedColors() {
|
||||
return preDefinedColors;
|
||||
}
|
||||
|
||||
public void setPreDefinedColors(double[] preDefinedColors) {
|
||||
this.preDefinedColors = preDefinedColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* disable animation on button action
|
||||
*/
|
||||
private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION,
|
||||
JFXColorPicker.this,
|
||||
"disableAnimation",
|
||||
false);
|
||||
|
||||
public final StyleableBooleanProperty disableAnimationProperty() {
|
||||
return this.disableAnimation;
|
||||
}
|
||||
|
||||
public final Boolean isDisableAnimation() {
|
||||
return disableAnimation != null && this.disableAnimationProperty().get();
|
||||
}
|
||||
|
||||
public final void setDisableAnimation(final Boolean disabled) {
|
||||
this.disableAnimationProperty().set(disabled);
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
|
||||
private static final CssMetaData<JFXColorPicker, Boolean> DISABLE_ANIMATION =
|
||||
new CssMetaData<JFXColorPicker, Boolean>("-jfx-disable-animation",
|
||||
BooleanConverter.getInstance(), false) {
|
||||
@Override
|
||||
public boolean isSettable(JFXColorPicker control) {
|
||||
return control.disableAnimation == null || !control.disableAnimation.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableBooleanProperty getStyleableProperty(JFXColorPicker control) {
|
||||
return control.disableAnimationProperty();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;
|
||||
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||
new ArrayList<>(ColorPicker.getClassCssMetaData());
|
||||
Collections.addAll(styleables, DISABLE_ANIMATION);
|
||||
CHILD_STYLEABLES = Collections.unmodifiableList(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
|
||||
return getClassCssMetaData();
|
||||
}
|
||||
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.CHILD_STYLEABLES;
|
||||
}
|
||||
}
|
||||
@@ -1,800 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.controls;
|
||||
|
||||
import com.jfoenix.converters.RipplerMaskTypeConverter;
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import javafx.animation.*;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.*;
|
||||
import javafx.css.converter.BooleanConverter;
|
||||
import javafx.css.converter.PaintConverter;
|
||||
import javafx.css.converter.SizeConverter;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.shape.Shape;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* JFXRippler is the material design implementation of a ripple effect.
|
||||
* the ripple effect can be applied to any node in the scene. JFXRippler is
|
||||
* a {@link StackPane} container that holds a specified node (control node) and a ripple generator.
|
||||
* <p>
|
||||
* UPDATE NOTES:
|
||||
* - fireEventProgrammatically(Event) method has been removed as the ripple controller is
|
||||
* the control itself, so you can trigger manual ripple by firing mouse event on the control
|
||||
* instead of JFXRippler
|
||||
*
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2016-03-09
|
||||
*/
|
||||
@DefaultProperty(value = "control")
|
||||
public class JFXRippler extends StackPane {
|
||||
public enum RipplerPos {
|
||||
FRONT, BACK
|
||||
}
|
||||
|
||||
public enum RipplerMask {
|
||||
CIRCLE, RECT, FIT
|
||||
}
|
||||
|
||||
protected RippleGenerator rippler;
|
||||
protected Pane ripplerPane;
|
||||
protected Node control;
|
||||
|
||||
protected static final double RIPPLE_MAX_RADIUS = 300;
|
||||
|
||||
private boolean enabled = true;
|
||||
private boolean forceOverlay = false;
|
||||
private final Interpolator rippleInterpolator = Interpolator.SPLINE(0.0825,
|
||||
0.3025,
|
||||
0.0875,
|
||||
0.9975); //0.1, 0.54, 0.28, 0.95);
|
||||
|
||||
/// creates empty rippler node
|
||||
public JFXRippler() {
|
||||
this(null, RipplerMask.RECT, RipplerPos.FRONT);
|
||||
}
|
||||
|
||||
/// creates a rippler for the specified control
|
||||
public JFXRippler(Node control) {
|
||||
this(control, RipplerMask.RECT, RipplerPos.FRONT);
|
||||
}
|
||||
|
||||
/// creates a rippler for the specified control
|
||||
///
|
||||
/// @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control)
|
||||
public JFXRippler(Node control, RipplerPos pos) {
|
||||
this(control, RipplerMask.RECT, pos);
|
||||
}
|
||||
|
||||
/// creates a rippler for the specified control and apply the specified mask to it
|
||||
///
|
||||
/// @param mask can be either rectangle/cricle
|
||||
public JFXRippler(Node control, RipplerMask mask) {
|
||||
this(control, mask, RipplerPos.FRONT);
|
||||
}
|
||||
|
||||
/// creates a rippler for the specified control, mask and position.
|
||||
///
|
||||
/// @param mask can be either rectangle/cricle
|
||||
/// @param pos can be either FRONT/BACK (position the ripple effect infront of or behind the control)
|
||||
public JFXRippler(Node control, RipplerMask mask, RipplerPos pos) {
|
||||
initialize();
|
||||
|
||||
setMaskType(mask);
|
||||
setPosition(pos);
|
||||
createRippleUI();
|
||||
setControl(control);
|
||||
|
||||
// listen to control position changed
|
||||
position.addListener(observable -> updateControlPosition());
|
||||
|
||||
setPickOnBounds(false);
|
||||
setCache(true);
|
||||
setCacheHint(CacheHint.SPEED);
|
||||
setCacheShape(true);
|
||||
}
|
||||
|
||||
protected final void createRippleUI() {
|
||||
// create rippler panels
|
||||
rippler = new RippleGenerator();
|
||||
ripplerPane = new StackPane();
|
||||
ripplerPane.setMouseTransparent(true);
|
||||
ripplerPane.getChildren().add(rippler);
|
||||
getChildren().add(ripplerPane);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Setters / Getters *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
public void setControl(Node control) {
|
||||
if (control != null) {
|
||||
this.control = control;
|
||||
// position control
|
||||
positionControl(control);
|
||||
// add control listeners to generate / release ripples
|
||||
initControlListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Override this method to create JFXRippler for a control outside the ripple
|
||||
protected void positionControl(Node control) {
|
||||
if (this.position.get() == RipplerPos.BACK) {
|
||||
getChildren().add(control);
|
||||
} else {
|
||||
getChildren().add(0, control);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateControlPosition() {
|
||||
if (this.position.get() == RipplerPos.BACK) {
|
||||
ripplerPane.toBack();
|
||||
} else {
|
||||
ripplerPane.toFront();
|
||||
}
|
||||
}
|
||||
|
||||
public Node getControl() {
|
||||
return control;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enable) {
|
||||
this.enabled = enable;
|
||||
}
|
||||
|
||||
// methods that can be changed by extending the rippler class
|
||||
|
||||
/// generate the clipping mask
|
||||
///
|
||||
/// @return the mask node
|
||||
protected Node getMask() {
|
||||
double borderWidth = ripplerPane.getBorder() != null ? ripplerPane.getBorder().getInsets().getTop() : 0;
|
||||
Bounds bounds = control.getBoundsInParent();
|
||||
double width = control.getLayoutBounds().getWidth();
|
||||
double height = control.getLayoutBounds().getHeight();
|
||||
double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());
|
||||
double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());
|
||||
double diffMaxX = Math.abs(control.getBoundsInLocal().getMaxX() - control.getLayoutBounds().getMaxX());
|
||||
double diffMaxY = Math.abs(control.getBoundsInLocal().getMaxY() - control.getLayoutBounds().getMaxY());
|
||||
Node mask;
|
||||
switch (getMaskType()) {
|
||||
case RECT:
|
||||
mask = new Rectangle(bounds.getMinX() + diffMinX - snappedLeftInset(),
|
||||
bounds.getMinY() + diffMinY - snappedTopInset(),
|
||||
width - 2 * borderWidth,
|
||||
height - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane
|
||||
break;
|
||||
case CIRCLE:
|
||||
double radius = Math.min((width / 2) - 2 * borderWidth, (height / 2) - 2 * borderWidth);
|
||||
mask = new Circle((bounds.getMinX() + diffMinX + bounds.getMaxX() - diffMaxX) / 2 - snappedLeftInset(),
|
||||
(bounds.getMinY() + diffMinY + bounds.getMaxY() - diffMaxY) / 2 - snappedTopInset(),
|
||||
radius,
|
||||
Color.BLUE);
|
||||
break;
|
||||
case FIT:
|
||||
mask = new Region();
|
||||
if (control instanceof Shape) {
|
||||
((Region) mask).setShape((Shape) control);
|
||||
} else if (control instanceof Region) {
|
||||
((Region) mask).setShape(((Region) control).getShape());
|
||||
JFXNodeUtils.updateBackground(((Region) control).getBackground(), (Region) mask);
|
||||
}
|
||||
mask.resize(width, height);
|
||||
mask.relocate(bounds.getMinX() + diffMinX, bounds.getMinY() + diffMinY);
|
||||
break;
|
||||
default:
|
||||
mask = new Rectangle(bounds.getMinX() + diffMinX - snappedLeftInset(),
|
||||
bounds.getMinY() + diffMinY - snappedTopInset(),
|
||||
width - 2 * borderWidth,
|
||||
height - 2 * borderWidth); // -0.1 to prevent resizing the anchor pane
|
||||
break;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* compute the ripple radius
|
||||
*
|
||||
* @return the ripple radius size
|
||||
*/
|
||||
protected double computeRippleRadius() {
|
||||
double width2 = control.getLayoutBounds().getWidth() * control.getLayoutBounds().getWidth();
|
||||
double height2 = control.getLayoutBounds().getHeight() * control.getLayoutBounds().getHeight();
|
||||
return Math.min(Math.sqrt(width2 + height2), RIPPLE_MAX_RADIUS) * 1.1 + 5;
|
||||
}
|
||||
|
||||
protected void setOverLayBounds(Rectangle overlay) {
|
||||
overlay.setWidth(control.getLayoutBounds().getWidth());
|
||||
overlay.setHeight(control.getLayoutBounds().getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* init mouse listeners on the control
|
||||
*/
|
||||
protected void initControlListeners() {
|
||||
// if the control got resized the overlay rect must be rest
|
||||
control.layoutBoundsProperty().addListener(observable -> resetRippler());
|
||||
if (getChildren().contains(control)) {
|
||||
control.boundsInParentProperty().addListener(observable -> resetRippler());
|
||||
}
|
||||
control.addEventHandler(MouseEvent.MOUSE_PRESSED,
|
||||
(event) -> createRipple(event.getX(), event.getY()));
|
||||
// create fade out transition for the ripple
|
||||
control.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> releaseRipple());
|
||||
}
|
||||
|
||||
/**
|
||||
* creates Ripple effect
|
||||
*/
|
||||
protected void createRipple(double x, double y) {
|
||||
if (!isRipplerDisabled()) {
|
||||
rippler.setGeneratorCenterX(x);
|
||||
rippler.setGeneratorCenterY(y);
|
||||
rippler.createRipple();
|
||||
}
|
||||
}
|
||||
|
||||
protected void releaseRipple() {
|
||||
rippler.releaseRipple();
|
||||
}
|
||||
|
||||
/**
|
||||
* creates Ripple effect in the center of the control
|
||||
*
|
||||
* @return a runnable to release the ripple when needed
|
||||
*/
|
||||
public Runnable createManualRipple() {
|
||||
if (!isRipplerDisabled()) {
|
||||
rippler.setGeneratorCenterX(control.getLayoutBounds().getWidth() / 2);
|
||||
rippler.setGeneratorCenterY(control.getLayoutBounds().getHeight() / 2);
|
||||
rippler.createRipple();
|
||||
return () -> {
|
||||
// create fade out transition for the ripple
|
||||
releaseRipple();
|
||||
};
|
||||
}
|
||||
return () -> {
|
||||
};
|
||||
}
|
||||
|
||||
/// show/hide the ripple overlay
|
||||
///
|
||||
/// @param forceOverlay used to hold the overlay after ripple action
|
||||
public void setOverlayVisible(boolean visible, boolean forceOverlay) {
|
||||
this.forceOverlay = forceOverlay;
|
||||
setOverlayVisible(visible);
|
||||
}
|
||||
|
||||
/// show/hide the ripple overlay
|
||||
/// NOTE: setting overlay visibility to false will reset forceOverlay to false
|
||||
public void setOverlayVisible(boolean visible) {
|
||||
if (visible) {
|
||||
showOverlay();
|
||||
} else {
|
||||
forceOverlay = false;
|
||||
hideOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this method will be set to private in future versions of JFoenix,
|
||||
* user the method {@link #setOverlayVisible(boolean)}
|
||||
*/
|
||||
public void showOverlay() {
|
||||
if (rippler.overlayRect != null) {
|
||||
rippler.overlayRect.outAnimation.stop();
|
||||
}
|
||||
rippler.createOverlay();
|
||||
rippler.overlayRect.inAnimation.play();
|
||||
}
|
||||
|
||||
public void hideOverlay() {
|
||||
if (!forceOverlay) {
|
||||
if (rippler.overlayRect != null) {
|
||||
rippler.overlayRect.inAnimation.stop();
|
||||
}
|
||||
if (rippler.overlayRect != null) {
|
||||
rippler.overlayRect.outAnimation.play();
|
||||
}
|
||||
} else {
|
||||
System.err.println("Ripple Overlay is forced!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ripples on the screen every 0.3 seconds or whenever
|
||||
* the createRipple method is called. Ripples grow and fade out
|
||||
* over 0.6 seconds
|
||||
*/
|
||||
protected final class RippleGenerator extends Group {
|
||||
|
||||
private double generatorCenterX = 0;
|
||||
private double generatorCenterY = 0;
|
||||
private OverLayRipple overlayRect;
|
||||
private final AtomicBoolean generating = new AtomicBoolean(false);
|
||||
private boolean cacheRipplerClip = false;
|
||||
private boolean resetClip = false;
|
||||
private final Queue<Ripple> ripplesQueue = new LinkedList<>();
|
||||
|
||||
RippleGenerator() {
|
||||
// improve in performance, by preventing
|
||||
// redrawing the parent when the ripple effect is triggered
|
||||
this.setManaged(false);
|
||||
this.setCache(true);
|
||||
this.setCacheHint(CacheHint.SPEED);
|
||||
}
|
||||
|
||||
void createRipple() {
|
||||
if (enabled) {
|
||||
if (!generating.getAndSet(true)) {
|
||||
// create overlay once then change its color later
|
||||
createOverlay();
|
||||
if (this.getClip() == null || (getChildren().size() == 1 && !cacheRipplerClip) || resetClip) {
|
||||
this.setClip(getMask());
|
||||
}
|
||||
this.resetClip = false;
|
||||
|
||||
// create the ripple effect
|
||||
final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY);
|
||||
getChildren().add(ripple);
|
||||
ripplesQueue.add(ripple);
|
||||
|
||||
// animate the ripple
|
||||
overlayRect.outAnimation.stop();
|
||||
overlayRect.inAnimation.play();
|
||||
ripple.inAnimation.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseRipple() {
|
||||
Ripple ripple = ripplesQueue.poll();
|
||||
if (ripple != null) {
|
||||
ripple.inAnimation.stop();
|
||||
ripple.outAnimation = new Timeline(
|
||||
new KeyFrame(Duration.millis(Math.min(800, (0.9 * 500) / ripple.getScaleX()))
|
||||
, ripple.outKeyValues));
|
||||
ripple.outAnimation.setOnFinished((event) -> getChildren().remove(ripple));
|
||||
ripple.outAnimation.play();
|
||||
if (generating.getAndSet(false)) {
|
||||
if (overlayRect != null) {
|
||||
overlayRect.inAnimation.stop();
|
||||
if (!forceOverlay) {
|
||||
overlayRect.outAnimation.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cacheRippleClip(boolean cached) {
|
||||
cacheRipplerClip = cached;
|
||||
}
|
||||
|
||||
void createOverlay() {
|
||||
if (overlayRect == null) {
|
||||
overlayRect = new OverLayRipple();
|
||||
overlayRect.setClip(getMask());
|
||||
getChildren().add(0, overlayRect);
|
||||
overlayRect.fillProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
if (ripplerFill.get() instanceof Color) {
|
||||
return new Color(((Color) ripplerFill.get()).getRed(),
|
||||
((Color) ripplerFill.get()).getGreen(),
|
||||
((Color) ripplerFill.get()).getBlue(),
|
||||
0.2);
|
||||
} else {
|
||||
return Color.TRANSPARENT;
|
||||
}
|
||||
}, ripplerFill));
|
||||
}
|
||||
}
|
||||
|
||||
void setGeneratorCenterX(double generatorCenterX) {
|
||||
this.generatorCenterX = generatorCenterX;
|
||||
}
|
||||
|
||||
void setGeneratorCenterY(double generatorCenterY) {
|
||||
this.generatorCenterY = generatorCenterY;
|
||||
}
|
||||
|
||||
private final class OverLayRipple extends Rectangle {
|
||||
// Overlay ripple animations
|
||||
Animation inAnimation = new Timeline(new KeyFrame(Duration.millis(300),
|
||||
new KeyValue(opacityProperty(), 1, Interpolator.EASE_IN)));
|
||||
|
||||
Animation outAnimation = new Timeline(new KeyFrame(Duration.millis(300),
|
||||
new KeyValue(opacityProperty(), 0, Interpolator.EASE_OUT)));
|
||||
|
||||
OverLayRipple() {
|
||||
super();
|
||||
setOverLayBounds(this);
|
||||
this.getStyleClass().add("jfx-rippler-overlay");
|
||||
// update initial position
|
||||
if (JFXRippler.this.getChildrenUnmodifiable().contains(control)) {
|
||||
double diffMinX = Math.abs(control.getBoundsInLocal().getMinX() - control.getLayoutBounds().getMinX());
|
||||
double diffMinY = Math.abs(control.getBoundsInLocal().getMinY() - control.getLayoutBounds().getMinY());
|
||||
Bounds bounds = control.getBoundsInParent();
|
||||
this.setX(bounds.getMinX() + diffMinX - snappedLeftInset());
|
||||
this.setY(bounds.getMinY() + diffMinY - snappedTopInset());
|
||||
}
|
||||
// set initial attributes
|
||||
setOpacity(0);
|
||||
setCache(true);
|
||||
setCacheHint(CacheHint.SPEED);
|
||||
setCacheShape(true);
|
||||
setManaged(false);
|
||||
}
|
||||
}
|
||||
|
||||
private final class Ripple extends Circle {
|
||||
|
||||
KeyValue[] outKeyValues;
|
||||
Animation outAnimation = null;
|
||||
Animation inAnimation = null;
|
||||
|
||||
private Ripple(double centerX, double centerY) {
|
||||
super(centerX,
|
||||
centerY,
|
||||
ripplerRadius.get().doubleValue() == Region.USE_COMPUTED_SIZE ?
|
||||
computeRippleRadius() : ripplerRadius.get().doubleValue(), null);
|
||||
setCache(true);
|
||||
setCacheHint(CacheHint.SPEED);
|
||||
setCacheShape(true);
|
||||
setManaged(false);
|
||||
setSmooth(true);
|
||||
|
||||
KeyValue[] inKeyValues = new KeyValue[isRipplerRecenter() ? 4 : 2];
|
||||
outKeyValues = new KeyValue[isRipplerRecenter() ? 5 : 3];
|
||||
|
||||
inKeyValues[0] = new KeyValue(scaleXProperty(), 0.9, rippleInterpolator);
|
||||
inKeyValues[1] = new KeyValue(scaleYProperty(), 0.9, rippleInterpolator);
|
||||
|
||||
outKeyValues[0] = new KeyValue(this.scaleXProperty(), 1, rippleInterpolator);
|
||||
outKeyValues[1] = new KeyValue(this.scaleYProperty(), 1, rippleInterpolator);
|
||||
outKeyValues[2] = new KeyValue(this.opacityProperty(), 0, rippleInterpolator);
|
||||
|
||||
if (isRipplerRecenter()) {
|
||||
double dx = (control.getLayoutBounds().getWidth() / 2 - centerX) / 1.55;
|
||||
double dy = (control.getLayoutBounds().getHeight() / 2 - centerY) / 1.55;
|
||||
inKeyValues[2] = outKeyValues[3] = new KeyValue(translateXProperty(),
|
||||
Math.signum(dx) * Math.min(Math.abs(dx),
|
||||
this.getRadius() / 2),
|
||||
rippleInterpolator);
|
||||
inKeyValues[3] = outKeyValues[4] = new KeyValue(translateYProperty(),
|
||||
Math.signum(dy) * Math.min(Math.abs(dy),
|
||||
this.getRadius() / 2),
|
||||
rippleInterpolator);
|
||||
}
|
||||
inAnimation = new Timeline(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(scaleXProperty(),
|
||||
0,
|
||||
rippleInterpolator),
|
||||
new KeyValue(scaleYProperty(),
|
||||
0,
|
||||
rippleInterpolator),
|
||||
new KeyValue(translateXProperty(),
|
||||
0,
|
||||
rippleInterpolator),
|
||||
new KeyValue(translateYProperty(),
|
||||
0,
|
||||
rippleInterpolator),
|
||||
new KeyValue(opacityProperty(),
|
||||
1,
|
||||
rippleInterpolator)
|
||||
), new KeyFrame(Duration.millis(900), inKeyValues));
|
||||
|
||||
setScaleX(0);
|
||||
setScaleY(0);
|
||||
if (ripplerFill.get() instanceof Color) {
|
||||
Color circleColor = new Color(((Color) ripplerFill.get()).getRed(),
|
||||
((Color) ripplerFill.get()).getGreen(),
|
||||
((Color) ripplerFill.get()).getBlue(),
|
||||
0.3);
|
||||
setStroke(circleColor);
|
||||
setFill(circleColor);
|
||||
} else {
|
||||
setStroke(ripplerFill.get());
|
||||
setFill(ripplerFill.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
getChildren().clear();
|
||||
rippler.overlayRect = null;
|
||||
generating.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetOverLay() {
|
||||
if (rippler.overlayRect != null) {
|
||||
rippler.overlayRect.inAnimation.stop();
|
||||
final RippleGenerator.OverLayRipple oldOverlay = rippler.overlayRect;
|
||||
rippler.overlayRect.outAnimation.setOnFinished((finish) -> rippler.getChildren().remove(oldOverlay));
|
||||
rippler.overlayRect.outAnimation.play();
|
||||
rippler.overlayRect = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetClip() {
|
||||
this.rippler.resetClip = true;
|
||||
}
|
||||
|
||||
protected void resetRippler() {
|
||||
resetOverLay();
|
||||
resetClip();
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Stylesheet Handling *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
/**
|
||||
* Initialize the style class to 'jfx-rippler'.
|
||||
* <p>
|
||||
* This is the selector class from which CSS can be used to style
|
||||
* this control.
|
||||
*/
|
||||
private static final String DEFAULT_STYLE_CLASS = "jfx-rippler";
|
||||
|
||||
private void initialize() {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* the ripple recenter property, by default it's false.
|
||||
* if true the ripple effect will show gravitational pull to the center of its control
|
||||
*/
|
||||
private final StyleableObjectProperty<Boolean> ripplerRecenter = new SimpleStyleableObjectProperty<>(
|
||||
StyleableProperties.RIPPLER_RECENTER,
|
||||
JFXRippler.this,
|
||||
"ripplerRecenter",
|
||||
false);
|
||||
|
||||
public Boolean isRipplerRecenter() {
|
||||
return ripplerRecenter != null && ripplerRecenter.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Boolean> ripplerRecenterProperty() {
|
||||
return this.ripplerRecenter;
|
||||
}
|
||||
|
||||
public void setRipplerRecenter(Boolean radius) {
|
||||
this.ripplerRecenter.set(radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* the ripple radius size, by default it will be automatically computed.
|
||||
*/
|
||||
private final StyleableObjectProperty<Number> ripplerRadius = new SimpleStyleableObjectProperty<>(
|
||||
StyleableProperties.RIPPLER_RADIUS,
|
||||
JFXRippler.this,
|
||||
"ripplerRadius",
|
||||
Region.USE_COMPUTED_SIZE);
|
||||
|
||||
public Number getRipplerRadius() {
|
||||
return ripplerRadius == null ? Region.USE_COMPUTED_SIZE : ripplerRadius.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Number> ripplerRadiusProperty() {
|
||||
return this.ripplerRadius;
|
||||
}
|
||||
|
||||
public void setRipplerRadius(Number radius) {
|
||||
this.ripplerRadius.set(radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* the default color of the ripple effect
|
||||
*/
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,
|
||||
JFXRippler.this,
|
||||
"ripplerFill",
|
||||
Color.rgb(0,
|
||||
200,
|
||||
255));
|
||||
|
||||
public Paint getRipplerFill() {
|
||||
return ripplerFill == null ? Color.rgb(0, 200, 255) : ripplerFill.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Paint> ripplerFillProperty() {
|
||||
return this.ripplerFill;
|
||||
}
|
||||
|
||||
public void setRipplerFill(Paint color) {
|
||||
this.ripplerFill.set(color);
|
||||
}
|
||||
|
||||
/// mask property used for clipping the rippler.
|
||||
/// can be either CIRCLE/RECT
|
||||
private final StyleableObjectProperty<RipplerMask> maskType = new SimpleStyleableObjectProperty<>(
|
||||
StyleableProperties.MASK_TYPE,
|
||||
JFXRippler.this,
|
||||
"maskType",
|
||||
RipplerMask.RECT);
|
||||
|
||||
public RipplerMask getMaskType() {
|
||||
return maskType == null ? RipplerMask.RECT : maskType.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<RipplerMask> maskTypeProperty() {
|
||||
return this.maskType;
|
||||
}
|
||||
|
||||
public void setMaskType(RipplerMask type) {
|
||||
this.maskType.set(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* the ripple disable, by default it's false.
|
||||
* if true the ripple effect will be hidden
|
||||
*/
|
||||
private final StyleableBooleanProperty ripplerDisabled = new SimpleStyleableBooleanProperty(
|
||||
StyleableProperties.RIPPLER_DISABLED,
|
||||
JFXRippler.this,
|
||||
"ripplerDisabled",
|
||||
false);
|
||||
|
||||
public Boolean isRipplerDisabled() {
|
||||
return ripplerDisabled != null && ripplerDisabled.get();
|
||||
}
|
||||
|
||||
public StyleableBooleanProperty ripplerDisabledProperty() {
|
||||
return this.ripplerDisabled;
|
||||
}
|
||||
|
||||
public void setRipplerDisabled(Boolean disabled) {
|
||||
this.ripplerDisabled.set(disabled);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* indicates whether the ripple effect is infront of or behind the node
|
||||
*/
|
||||
protected ObjectProperty<RipplerPos> position = new SimpleObjectProperty<>();
|
||||
|
||||
public void setPosition(RipplerPos pos) {
|
||||
this.position.set(pos);
|
||||
}
|
||||
|
||||
public RipplerPos getPosition() {
|
||||
return position == null ? RipplerPos.FRONT : position.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<RipplerPos> positionProperty() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
private static final CssMetaData<JFXRippler, Boolean> RIPPLER_RECENTER =
|
||||
new CssMetaData<>("-jfx-rippler-recenter",
|
||||
BooleanConverter.getInstance(), false) {
|
||||
@Override
|
||||
public boolean isSettable(JFXRippler control) {
|
||||
return control.ripplerRecenter == null || !control.ripplerRecenter.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Boolean> getStyleableProperty(JFXRippler control) {
|
||||
return control.ripplerRecenterProperty();
|
||||
}
|
||||
};
|
||||
private static final CssMetaData<JFXRippler, Boolean> RIPPLER_DISABLED =
|
||||
new CssMetaData<>("-jfx-rippler-disabled",
|
||||
BooleanConverter.getInstance(), false) {
|
||||
@Override
|
||||
public boolean isSettable(JFXRippler control) {
|
||||
return control.ripplerDisabled == null || !control.ripplerDisabled.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Boolean> getStyleableProperty(JFXRippler control) {
|
||||
return control.ripplerDisabledProperty();
|
||||
}
|
||||
};
|
||||
private static final CssMetaData<JFXRippler, Paint> RIPPLER_FILL =
|
||||
new CssMetaData<>("-jfx-rippler-fill",
|
||||
PaintConverter.getInstance(), Color.rgb(0, 200, 255)) {
|
||||
@Override
|
||||
public boolean isSettable(JFXRippler control) {
|
||||
return control.ripplerFill == null || !control.ripplerFill.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Paint> getStyleableProperty(JFXRippler control) {
|
||||
return control.ripplerFillProperty();
|
||||
}
|
||||
};
|
||||
private static final CssMetaData<JFXRippler, Number> RIPPLER_RADIUS =
|
||||
new CssMetaData<>("-jfx-rippler-radius",
|
||||
SizeConverter.getInstance(), Region.USE_COMPUTED_SIZE) {
|
||||
@Override
|
||||
public boolean isSettable(JFXRippler control) {
|
||||
return control.ripplerRadius == null || !control.ripplerRadius.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Number> getStyleableProperty(JFXRippler control) {
|
||||
return control.ripplerRadiusProperty();
|
||||
}
|
||||
};
|
||||
private static final CssMetaData<JFXRippler, RipplerMask> MASK_TYPE =
|
||||
new CssMetaData<>("-jfx-mask-type",
|
||||
RipplerMaskTypeConverter.getInstance(), RipplerMask.RECT) {
|
||||
@Override
|
||||
public boolean isSettable(JFXRippler control) {
|
||||
return control.maskType == null || !control.maskType.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<RipplerMask> getStyleableProperty(JFXRippler control) {
|
||||
return control.maskTypeProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||
new ArrayList<>(StackPane.getClassCssMetaData());
|
||||
Collections.addAll(styleables,
|
||||
RIPPLER_RECENTER,
|
||||
RIPPLER_RADIUS,
|
||||
RIPPLER_FILL,
|
||||
MASK_TYPE,
|
||||
RIPPLER_DISABLED
|
||||
);
|
||||
STYLEABLES = Collections.unmodifiableList(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||
return getClassCssMetaData();
|
||||
}
|
||||
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.STYLEABLES;
|
||||
}
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.controls;
|
||||
|
||||
import com.jfoenix.skins.JFXToggleButtonSkin;
|
||||
import javafx.css.*;
|
||||
import javafx.css.converter.BooleanConverter;
|
||||
import javafx.css.converter.PaintConverter;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JFXToggleButton is the material design implementation of a toggle button.
|
||||
* important CSS Selectors:
|
||||
* <p>
|
||||
* .jfx-toggle-button{
|
||||
* -fx-toggle-color: color-value;
|
||||
* -fx-untoggle-color: color-value;
|
||||
* -fx-toggle-line-color: color-value;
|
||||
* -fx-untoggle-line-color: color-value;
|
||||
* }
|
||||
* <p>
|
||||
* To change the rippler color when toggled:
|
||||
* <p>
|
||||
* .jfx-toggle-button .jfx-rippler{
|
||||
* -fx-rippler-fill: color-value;
|
||||
* }
|
||||
* <p>
|
||||
* .jfx-toggle-button:selected .jfx-rippler{
|
||||
* -fx-rippler-fill: color-value;
|
||||
* }
|
||||
*
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2016-03-09
|
||||
*/
|
||||
public class JFXToggleButton extends ToggleButton {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public JFXToggleButton() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new JFXToggleButtonSkin(this);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
// it's up for the user to add this behavior
|
||||
// toggleColor.addListener((o, oldVal, newVal) -> {
|
||||
// // update line color in case not set by the user
|
||||
// if(newVal instanceof Color)
|
||||
// toggleLineColor.set(((Color)newVal).desaturate().desaturate().brighter());
|
||||
// });
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* styleable Properties *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
/**
|
||||
* Initialize the style class to 'jfx-toggle-button'.
|
||||
* <p>
|
||||
* This is the selector class from which CSS can be used to style
|
||||
* this control.
|
||||
*/
|
||||
private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button";
|
||||
|
||||
/**
|
||||
* default color used when the button is toggled
|
||||
*/
|
||||
private final StyleableObjectProperty<Paint> toggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.TOGGLE_COLOR,
|
||||
JFXToggleButton.this,
|
||||
"toggleColor",
|
||||
Color.valueOf(
|
||||
"#009688"));
|
||||
|
||||
public Paint getToggleColor() {
|
||||
return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Paint> toggleColorProperty() {
|
||||
return this.toggleColor;
|
||||
}
|
||||
|
||||
public void setToggleColor(Paint color) {
|
||||
this.toggleColor.set(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* default color used when the button is not toggled
|
||||
*/
|
||||
private StyleableObjectProperty<Paint> untoggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNTOGGLE_COLOR,
|
||||
JFXToggleButton.this,
|
||||
"unToggleColor",
|
||||
Color.valueOf(
|
||||
"#FAFAFA"));
|
||||
|
||||
public Paint getUnToggleColor() {
|
||||
return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Paint> unToggleColorProperty() {
|
||||
return this.untoggleColor;
|
||||
}
|
||||
|
||||
public void setUnToggleColor(Paint color) {
|
||||
this.untoggleColor.set(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* default line color used when the button is toggled
|
||||
*/
|
||||
private final StyleableObjectProperty<Paint> toggleLineColor = new SimpleStyleableObjectProperty<>(
|
||||
StyleableProperties.TOGGLE_LINE_COLOR,
|
||||
JFXToggleButton.this,
|
||||
"toggleLineColor",
|
||||
Color.valueOf("#77C2BB"));
|
||||
|
||||
public Paint getToggleLineColor() {
|
||||
return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Paint> toggleLineColorProperty() {
|
||||
return this.toggleLineColor;
|
||||
}
|
||||
|
||||
public void setToggleLineColor(Paint color) {
|
||||
this.toggleLineColor.set(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* default line color used when the button is not toggled
|
||||
*/
|
||||
private final StyleableObjectProperty<Paint> untoggleLineColor = new SimpleStyleableObjectProperty<>(
|
||||
StyleableProperties.UNTOGGLE_LINE_COLOR,
|
||||
JFXToggleButton.this,
|
||||
"unToggleLineColor",
|
||||
Color.valueOf("#999999"));
|
||||
|
||||
public Paint getUnToggleLineColor() {
|
||||
return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get();
|
||||
}
|
||||
|
||||
public StyleableObjectProperty<Paint> unToggleLineColorProperty() {
|
||||
return this.untoggleLineColor;
|
||||
}
|
||||
|
||||
public void setUnToggleLineColor(Paint color) {
|
||||
this.untoggleLineColor.set(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default size of the toggle button.
|
||||
*/
|
||||
private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty(
|
||||
StyleableProperties.SIZE,
|
||||
JFXToggleButton.this,
|
||||
"size",
|
||||
10.0);
|
||||
|
||||
public double getSize() {
|
||||
return size.get();
|
||||
}
|
||||
|
||||
public StyleableDoubleProperty sizeProperty() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public void setSize(double size) {
|
||||
this.size.set(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the visual indicator for focus
|
||||
*/
|
||||
private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS,
|
||||
JFXToggleButton.this,
|
||||
"disableVisualFocus",
|
||||
false);
|
||||
|
||||
public final StyleableBooleanProperty disableVisualFocusProperty() {
|
||||
return this.disableVisualFocus;
|
||||
}
|
||||
|
||||
public final Boolean isDisableVisualFocus() {
|
||||
return disableVisualFocus != null && this.disableVisualFocusProperty().get();
|
||||
}
|
||||
|
||||
public final void setDisableVisualFocus(final Boolean disabled) {
|
||||
this.disableVisualFocusProperty().set(disabled);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* disable animation on button action
|
||||
*/
|
||||
private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION,
|
||||
JFXToggleButton.this,
|
||||
"disableAnimation",
|
||||
!AnimationUtils.isAnimationEnabled());
|
||||
|
||||
public final StyleableBooleanProperty disableAnimationProperty() {
|
||||
return this.disableAnimation;
|
||||
}
|
||||
|
||||
public final Boolean isDisableAnimation() {
|
||||
return disableAnimation != null && this.disableAnimationProperty().get();
|
||||
}
|
||||
|
||||
public final void setDisableAnimation(final Boolean disabled) {
|
||||
this.disableAnimationProperty().set(disabled);
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
private static final CssMetaData<JFXToggleButton, Paint> TOGGLE_COLOR =
|
||||
new CssMetaData<>("-jfx-toggle-color",
|
||||
PaintConverter.getInstance(), Color.valueOf("#009688")) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return control.toggleColor == null || !control.toggleColor.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {
|
||||
return control.toggleColorProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final CssMetaData<JFXToggleButton, Paint> UNTOGGLE_COLOR =
|
||||
new CssMetaData<>("-jfx-untoggle-color",
|
||||
PaintConverter.getInstance(), Color.valueOf("#FAFAFA")) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return control.untoggleColor == null || !control.untoggleColor.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {
|
||||
return control.unToggleColorProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final CssMetaData<JFXToggleButton, Paint> TOGGLE_LINE_COLOR =
|
||||
new CssMetaData<>("-jfx-toggle-line-color",
|
||||
PaintConverter.getInstance(), Color.valueOf("#77C2BB")) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return control.toggleLineColor == null || !control.toggleLineColor.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {
|
||||
return control.toggleLineColorProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final CssMetaData<JFXToggleButton, Paint> UNTOGGLE_LINE_COLOR =
|
||||
new CssMetaData<>("-jfx-untoggle-line-color",
|
||||
PaintConverter.getInstance(), Color.valueOf("#999999")) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return control.untoggleLineColor == null || !control.untoggleLineColor.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Paint> getStyleableProperty(JFXToggleButton control) {
|
||||
return control.unToggleLineColorProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final CssMetaData<JFXToggleButton, Number> SIZE =
|
||||
new CssMetaData<>("-jfx-size",
|
||||
StyleConverter.getSizeConverter(), 10.0) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return !control.size.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Number> getStyleableProperty(JFXToggleButton control) {
|
||||
return control.sizeProperty();
|
||||
}
|
||||
};
|
||||
private static final CssMetaData<JFXToggleButton, Boolean> DISABLE_VISUAL_FOCUS =
|
||||
new CssMetaData<>("-jfx-disable-visual-focus",
|
||||
BooleanConverter.getInstance(), false) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return control.disableVisualFocus == null || !control.disableVisualFocus.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) {
|
||||
return control.disableVisualFocusProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final CssMetaData<JFXToggleButton, Boolean> DISABLE_ANIMATION =
|
||||
new CssMetaData<>("-jfx-disable-animation",
|
||||
BooleanConverter.getInstance(), false) {
|
||||
@Override
|
||||
public boolean isSettable(JFXToggleButton control) {
|
||||
return control.disableAnimation == null || !control.disableAnimation.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) {
|
||||
return control.disableAnimationProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;
|
||||
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||
new ArrayList<>(ToggleButton.getClassCssMetaData());
|
||||
Collections.addAll(styleables,
|
||||
SIZE,
|
||||
TOGGLE_COLOR,
|
||||
UNTOGGLE_COLOR,
|
||||
TOGGLE_LINE_COLOR,
|
||||
UNTOGGLE_LINE_COLOR,
|
||||
DISABLE_VISUAL_FOCUS,
|
||||
DISABLE_ANIMATION
|
||||
);
|
||||
CHILD_STYLEABLES = Collections.unmodifiableList(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
|
||||
return getClassCssMetaData();
|
||||
}
|
||||
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.CHILD_STYLEABLES;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.controls.behavior;
|
||||
|
||||
import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
|
||||
import javafx.scene.control.ComboBoxBase;
|
||||
import javafx.scene.control.PopupControl;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen
|
||||
* @version 2.0
|
||||
* @since 2017-10-05
|
||||
*/
|
||||
public class JFXGenericPickerBehavior<T> extends ComboBoxBaseBehavior<T> {
|
||||
|
||||
public JFXGenericPickerBehavior(ComboBoxBase<T> var1) {
|
||||
super(var1);
|
||||
}
|
||||
|
||||
public void onAutoHide(PopupControl var1) {
|
||||
if (!var1.isShowing() && this.getNode().isShowing()) {
|
||||
this.getNode().hide();
|
||||
}
|
||||
if (!this.getNode().isShowing()) {
|
||||
super.onAutoHide(var1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by FernFlower decompiler)
|
||||
//
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXRippler;
|
||||
import com.jfoenix.controls.JFXRippler.RipplerMask;
|
||||
import com.jfoenix.controls.JFXRippler.RipplerPos;
|
||||
import com.jfoenix.transitions.CachedTransition;
|
||||
import com.jfoenix.transitions.JFXFillTransition;
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.skin.CheckBoxSkin;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
import javafx.scene.layout.BorderStrokeStyle;
|
||||
import javafx.scene.layout.BorderWidths;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
|
||||
public class JFXCheckBoxSkin extends CheckBoxSkin {
|
||||
private final StackPane box = new StackPane();
|
||||
private final StackPane mark = new StackPane();
|
||||
private final double lineThick = 2.0;
|
||||
private final double padding = 10.0;
|
||||
private final JFXRippler rippler;
|
||||
private final AnchorPane container = new AnchorPane();
|
||||
private final double labelOffset = -8.0;
|
||||
private final Transition transition;
|
||||
private boolean invalid = true;
|
||||
private JFXFillTransition select;
|
||||
|
||||
public JFXCheckBoxSkin(JFXCheckBox control) {
|
||||
super(control);
|
||||
this.box.setMinSize(18.0, 18.0);
|
||||
this.box.setPrefSize(18.0, 18.0);
|
||||
this.box.setMaxSize(18.0, 18.0);
|
||||
this.box.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, new CornerRadii(2.0), Insets.EMPTY)));
|
||||
this.box.setBorder(new Border(new BorderStroke(Themes.getColorScheme().getOnSurfaceVariant(), BorderStrokeStyle.SOLID, new CornerRadii(2.0), new BorderWidths(this.lineThick))));
|
||||
StackPane boxContainer = new StackPane();
|
||||
boxContainer.getChildren().add(this.box);
|
||||
boxContainer.setPadding(new Insets(this.padding));
|
||||
this.rippler = new JFXRippler(boxContainer, RipplerMask.CIRCLE, RipplerPos.BACK);
|
||||
this.updateRippleColor();
|
||||
SVGPath shape = new SVGPath();
|
||||
shape.setContent("M384 690l452-452 60 60-512 512-238-238 60-60z");
|
||||
this.mark.setShape(shape);
|
||||
this.mark.setMaxSize(15.0, 12.0);
|
||||
this.mark.setStyle("-fx-background-color:-monet-on-primary; -fx-border-color:-monet-on-primary; -fx-border-width:2px;");
|
||||
this.mark.setVisible(false);
|
||||
this.mark.setScaleX(0.0);
|
||||
this.mark.setScaleY(0.0);
|
||||
boxContainer.getChildren().add(this.mark);
|
||||
this.container.getChildren().add(this.rippler);
|
||||
AnchorPane.setRightAnchor(this.rippler, this.labelOffset);
|
||||
control.selectedProperty().addListener((o, oldVal, newVal) -> {
|
||||
this.updateRippleColor();
|
||||
this.playSelectAnimation(newVal);
|
||||
});
|
||||
control.focusedProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
if (!this.getSkinnable().isPressed()) {
|
||||
this.rippler.showOverlay();
|
||||
}
|
||||
} else {
|
||||
this.rippler.hideOverlay();
|
||||
}
|
||||
});
|
||||
control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay());
|
||||
this.updateChildren();
|
||||
this.registerChangeListener(control.checkedColorProperty(), ignored -> {
|
||||
if (select != null) {
|
||||
select.stop();
|
||||
}
|
||||
this.createFillTransition();
|
||||
updateColors();
|
||||
});
|
||||
this.registerChangeListener(control.unCheckedColorProperty(), ignored -> updateColors());
|
||||
this.transition = new CheckBoxTransition();
|
||||
this.createFillTransition();
|
||||
}
|
||||
|
||||
private void updateRippleColor() {
|
||||
var control = (JFXCheckBox) this.getSkinnable();
|
||||
this.rippler.setRipplerFill(control.isSelected()
|
||||
? control.getCheckedColor()
|
||||
: control.getUnCheckedColor());
|
||||
}
|
||||
|
||||
private void updateColors() {
|
||||
var control = (JFXCheckBox) getSkinnable();
|
||||
boolean isSelected = control.isSelected();
|
||||
JFXNodeUtils.updateBackground(box.getBackground(), box, isSelected ? control.getCheckedColor() : Color.TRANSPARENT);
|
||||
rippler.setRipplerFill(isSelected ? control.getCheckedColor() : control.getUnCheckedColor());
|
||||
final BorderStroke borderStroke = box.getBorder().getStrokes().get(0);
|
||||
box.setBorder(new Border(new BorderStroke(
|
||||
isSelected ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(),
|
||||
borderStroke.getTopStyle(),
|
||||
borderStroke.getRadii(),
|
||||
borderStroke.getWidths())));
|
||||
}
|
||||
|
||||
protected void updateChildren() {
|
||||
super.updateChildren();
|
||||
if (this.container != null) {
|
||||
this.getChildren().remove(1);
|
||||
this.getChildren().add(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.box.minWidth(-1.0)) + this.labelOffset + 2.0 * this.padding;
|
||||
}
|
||||
|
||||
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeY(this.box.prefWidth(-1.0)) + this.labelOffset + 2.0 * this.padding;
|
||||
}
|
||||
|
||||
protected void layoutChildren(double x, double y, double w, double h) {
|
||||
CheckBox checkBox = this.getSkinnable();
|
||||
double boxWidth = this.snapSizeX(this.container.prefWidth(-1.0));
|
||||
double boxHeight = this.snapSizeY(this.container.prefHeight(-1.0));
|
||||
double computeWidth = Math.min(checkBox.prefWidth(-1.0), checkBox.minWidth(-1.0)) + this.labelOffset + 2.0 * this.padding;
|
||||
double labelWidth = Math.min(computeWidth - boxWidth, w - this.snapSizeX(boxWidth)) + this.labelOffset + 2.0 * this.padding;
|
||||
double labelHeight = Math.min(checkBox.prefHeight(labelWidth), h);
|
||||
double maxHeight = Math.max(boxHeight, labelHeight);
|
||||
double xOffset = computeXOffset(w, labelWidth + boxWidth, checkBox.getAlignment().getHpos()) + x;
|
||||
double yOffset = computeYOffset(h, maxHeight, checkBox.getAlignment().getVpos()) + x;
|
||||
if (this.invalid) {
|
||||
if (this.getSkinnable().isSelected()) {
|
||||
this.playSelectAnimation(true);
|
||||
}
|
||||
|
||||
this.invalid = false;
|
||||
}
|
||||
|
||||
this.layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, checkBox.getAlignment());
|
||||
this.container.resize(boxWidth, boxHeight);
|
||||
this.positionInArea(this.container, xOffset, yOffset, boxWidth, maxHeight, 0.0, checkBox.getAlignment().getHpos(), checkBox.getAlignment().getVpos());
|
||||
}
|
||||
|
||||
static double computeXOffset(double width, double contentWidth, HPos hpos) {
|
||||
return switch (hpos) {
|
||||
case LEFT -> 0.0;
|
||||
case CENTER -> (width - contentWidth) / 2.0;
|
||||
case RIGHT -> width - contentWidth;
|
||||
};
|
||||
}
|
||||
|
||||
static double computeYOffset(double height, double contentHeight, VPos vpos) {
|
||||
return switch (vpos) {
|
||||
case TOP -> 0.0;
|
||||
case CENTER -> (height - contentHeight) / 2.0;
|
||||
case BOTTOM -> height - contentHeight;
|
||||
default -> 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
private void playSelectAnimation(Boolean selection) {
|
||||
if (selection == null) {
|
||||
selection = false;
|
||||
}
|
||||
|
||||
JFXCheckBox control = (JFXCheckBox) this.getSkinnable();
|
||||
this.transition.setRate(selection ? 1.0 : -1.0);
|
||||
this.select.setRate(selection ? 1.0 : -1.0);
|
||||
this.transition.play();
|
||||
this.select.play();
|
||||
this.box.setBorder(new Border(new BorderStroke(
|
||||
selection ? control.getCheckedColor() : Themes.getColorScheme().getOnSurfaceVariant(),
|
||||
BorderStrokeStyle.SOLID,
|
||||
new CornerRadii(2.0),
|
||||
new BorderWidths(this.lineThick))));
|
||||
}
|
||||
|
||||
private void createFillTransition() {
|
||||
this.select = new JFXFillTransition(Duration.millis(120.0), this.box, Color.TRANSPARENT,
|
||||
(Color) ((JFXCheckBox) this.getSkinnable()).getCheckedColor());
|
||||
this.select.setInterpolator(Interpolator.EASE_OUT);
|
||||
}
|
||||
|
||||
private final class CheckBoxTransition extends CachedTransition {
|
||||
CheckBoxTransition() {
|
||||
super(JFXCheckBoxSkin.this.mark,
|
||||
new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.visibleProperty(), false, Interpolator.EASE_OUT),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.scaleXProperty(), (double) 0.5F, Interpolator.EASE_OUT),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.scaleYProperty(), (double) 0.5F, Interpolator.EASE_OUT)),
|
||||
new KeyFrame(Duration.millis(400.0),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.visibleProperty(), true, Interpolator.EASE_OUT),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.scaleXProperty(), (double) 0.5F, Interpolator.EASE_OUT),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.scaleYProperty(), (double) 0.5F, Interpolator.EASE_OUT)),
|
||||
new KeyFrame(Duration.millis(1000.0),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.visibleProperty(), true, Interpolator.EASE_OUT),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.scaleXProperty(), 1, Interpolator.EASE_OUT),
|
||||
new KeyValue(JFXCheckBoxSkin.this.mark.scaleYProperty(), 1, Interpolator.EASE_OUT))
|
||||
)
|
||||
);
|
||||
this.setCycleDuration(Duration.seconds(0.12));
|
||||
this.setDelay(Duration.seconds(0.05));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,623 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXColorPicker;
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.Event;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PopupControl;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.shape.StrokeType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen FUTURE WORK: this UI will get re-designed to match material design guidlines
|
||||
*/
|
||||
final class JFXColorPalette extends Region {
|
||||
|
||||
private static final int SQUARE_SIZE = 15;
|
||||
|
||||
// package protected for testing purposes
|
||||
JFXColorGrid colorPickerGrid;
|
||||
final JFXButton customColorLink = new JFXButton(i18n("color.custom"));
|
||||
JFXCustomColorPickerDialog customColorDialog = null;
|
||||
|
||||
private final JFXColorPicker colorPicker;
|
||||
private final GridPane customColorGrid = new GridPane();
|
||||
private final Label customColorLabel = new Label(i18n("color.recent"));
|
||||
|
||||
private PopupControl popupControl;
|
||||
private ColorSquare focusedSquare;
|
||||
|
||||
private Color mouseDragColor = null;
|
||||
private boolean dragDetected = false;
|
||||
|
||||
private final ColorSquare hoverSquare = new ColorSquare();
|
||||
|
||||
public JFXColorPalette(final JFXColorPicker colorPicker) {
|
||||
getStyleClass().add("color-palette-region");
|
||||
this.colorPicker = colorPicker;
|
||||
colorPickerGrid = new JFXColorGrid();
|
||||
colorPickerGrid.getChildren().get(0).requestFocus();
|
||||
customColorLabel.setAlignment(Pos.CENTER_LEFT);
|
||||
customColorLink.setPrefWidth(colorPickerGrid.prefWidth(-1));
|
||||
customColorLink.setAlignment(Pos.CENTER);
|
||||
customColorLink.setFocusTraversable(true);
|
||||
customColorLink.setOnAction(ev -> {
|
||||
if (customColorDialog == null) {
|
||||
customColorDialog = new JFXCustomColorPickerDialog(popupControl);
|
||||
customColorDialog.customColorProperty().addListener((ov, t1, t2) -> {
|
||||
colorPicker.setValue(customColorDialog.customColorProperty().get());
|
||||
});
|
||||
customColorDialog.setOnSave(() -> {
|
||||
Color customColor = customColorDialog.customColorProperty().get();
|
||||
buildCustomColors();
|
||||
colorPicker.getCustomColors().add(customColor);
|
||||
updateSelection(customColor);
|
||||
Event.fireEvent(colorPicker, new ActionEvent());
|
||||
colorPicker.hide();
|
||||
});
|
||||
}
|
||||
customColorDialog.setCurrentColor(colorPicker.valueProperty().get());
|
||||
if (popupControl != null) {
|
||||
popupControl.setAutoHide(false);
|
||||
}
|
||||
customColorDialog.show();
|
||||
customColorDialog.setOnHidden(event -> {
|
||||
if (popupControl != null) {
|
||||
popupControl.setAutoHide(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
initNavigation();
|
||||
customColorGrid.getStyleClass().add("color-picker-grid");
|
||||
customColorGrid.setVisible(false);
|
||||
|
||||
buildCustomColors();
|
||||
|
||||
colorPicker.getCustomColors().addListener((Change<? extends Color> change) -> buildCustomColors());
|
||||
VBox paletteBox = new VBox();
|
||||
paletteBox.getStyleClass().add("color-palette");
|
||||
paletteBox.getChildren().addAll(colorPickerGrid);
|
||||
if (colorPicker.getPreDefinedColors() == null) {
|
||||
paletteBox.getChildren().addAll(customColorLabel, customColorGrid, customColorLink);
|
||||
}
|
||||
|
||||
hoverSquare.setMouseTransparent(true);
|
||||
hoverSquare.getStyleClass().addAll("hover-square");
|
||||
setFocusedSquare(null);
|
||||
|
||||
getChildren().addAll(paletteBox, hoverSquare);
|
||||
}
|
||||
|
||||
private void setFocusedSquare(ColorSquare square) {
|
||||
hoverSquare.setVisible(square != null);
|
||||
|
||||
if (square == focusedSquare) {
|
||||
return;
|
||||
}
|
||||
focusedSquare = square;
|
||||
|
||||
hoverSquare.setVisible(focusedSquare != null);
|
||||
if (focusedSquare == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!focusedSquare.isFocused()) {
|
||||
focusedSquare.requestFocus();
|
||||
}
|
||||
|
||||
hoverSquare.rectangle.setFill(focusedSquare.rectangle.getFill());
|
||||
|
||||
Bounds b = square.localToScene(square.getLayoutBounds());
|
||||
|
||||
double x = b.getMinX();
|
||||
double y = b.getMinY();
|
||||
|
||||
double xAdjust;
|
||||
double scaleAdjust = hoverSquare.getScaleX() == 1.0 ? 0 : hoverSquare.getWidth() / 4.0;
|
||||
|
||||
if (colorPicker.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
|
||||
x = focusedSquare.getLayoutX();
|
||||
xAdjust = -focusedSquare.getWidth() + scaleAdjust;
|
||||
} else {
|
||||
xAdjust = focusedSquare.getWidth() / 2.0 + scaleAdjust;
|
||||
}
|
||||
|
||||
hoverSquare.setLayoutX(snapPositionX(x) - xAdjust);
|
||||
hoverSquare.setLayoutY(snapPositionY(y) - focusedSquare.getHeight() / 2.0 + (hoverSquare.getScaleY() == 1.0 ? 0 : focusedSquare.getHeight() / 4.0));
|
||||
}
|
||||
|
||||
private void buildCustomColors() {
|
||||
final ObservableList<Color> customColors = colorPicker.getCustomColors();
|
||||
customColorGrid.getChildren().clear();
|
||||
if (customColors.isEmpty()) {
|
||||
customColorLabel.setVisible(false);
|
||||
customColorLabel.setManaged(false);
|
||||
customColorGrid.setVisible(false);
|
||||
customColorGrid.setManaged(false);
|
||||
return;
|
||||
} else {
|
||||
customColorLabel.setVisible(true);
|
||||
customColorLabel.setManaged(true);
|
||||
customColorGrid.setVisible(true);
|
||||
customColorGrid.setManaged(true);
|
||||
}
|
||||
|
||||
int customColumnIndex = 0;
|
||||
int customRowIndex = 0;
|
||||
int remainingSquares = customColors.size() % NUM_OF_COLUMNS;
|
||||
int numEmpty = (remainingSquares == 0) ? 0 : NUM_OF_COLUMNS - remainingSquares;
|
||||
|
||||
for (int i = 0; i < customColors.size(); i++) {
|
||||
Color c = customColors.get(i);
|
||||
ColorSquare square = new ColorSquare(c, i, true);
|
||||
customColorGrid.add(square, customColumnIndex, customRowIndex);
|
||||
customColumnIndex++;
|
||||
if (customColumnIndex == NUM_OF_COLUMNS) {
|
||||
customColumnIndex = 0;
|
||||
customRowIndex++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < numEmpty; i++) {
|
||||
ColorSquare emptySquare = new ColorSquare();
|
||||
customColorGrid.add(emptySquare, customColumnIndex, customRowIndex);
|
||||
customColumnIndex++;
|
||||
}
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
private void initNavigation() {
|
||||
setOnKeyPressed(ke -> {
|
||||
switch (ke.getCode()) {
|
||||
case SPACE:
|
||||
case ENTER:
|
||||
// select the focused color
|
||||
if (focusedSquare != null) {
|
||||
focusedSquare.selectColor(ke);
|
||||
}
|
||||
ke.consume();
|
||||
break;
|
||||
default: // no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setPopupControl(PopupControl pc) {
|
||||
this.popupControl = pc;
|
||||
}
|
||||
|
||||
public JFXColorGrid getColorGrid() {
|
||||
return colorPickerGrid;
|
||||
}
|
||||
|
||||
public boolean isCustomColorDialogShowing() {
|
||||
return customColorDialog != null && customColorDialog.isVisible();
|
||||
}
|
||||
|
||||
class ColorSquare extends StackPane {
|
||||
Rectangle rectangle;
|
||||
boolean isEmpty;
|
||||
|
||||
public ColorSquare() {
|
||||
this(null, -1, false);
|
||||
}
|
||||
|
||||
public ColorSquare(Color color, int index) {
|
||||
this(color, index, false);
|
||||
}
|
||||
|
||||
public ColorSquare(Color color, int index, boolean isCustom) {
|
||||
// Add style class to handle selected color square
|
||||
getStyleClass().add("color-square");
|
||||
if (color != null) {
|
||||
setFocusTraversable(true);
|
||||
focusedProperty().addListener((s, ov, nv) -> setFocusedSquare(nv ? this : null));
|
||||
addEventHandler(MouseEvent.MOUSE_ENTERED, event -> setFocusedSquare(ColorSquare.this));
|
||||
addEventHandler(MouseEvent.MOUSE_EXITED, event -> setFocusedSquare(null));
|
||||
addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
|
||||
if (!dragDetected && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
|
||||
if (!isEmpty) {
|
||||
Color fill = (Color) rectangle.getFill();
|
||||
colorPicker.setValue(fill);
|
||||
colorPicker.fireEvent(new ActionEvent());
|
||||
updateSelection(fill);
|
||||
event.consume();
|
||||
}
|
||||
colorPicker.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
rectangle = new Rectangle(SQUARE_SIZE, SQUARE_SIZE);
|
||||
if (color == null) {
|
||||
rectangle.setFill(Color.WHITE);
|
||||
isEmpty = true;
|
||||
} else {
|
||||
rectangle.setFill(color);
|
||||
}
|
||||
|
||||
rectangle.setStrokeType(StrokeType.INSIDE);
|
||||
|
||||
String tooltipStr = JFXNodeUtils.colorToHex(color);
|
||||
Tooltip.install(this, new Tooltip((tooltipStr == null) ? "" : tooltipStr));
|
||||
|
||||
rectangle.getStyleClass().add("color-rect");
|
||||
getChildren().add(rectangle);
|
||||
}
|
||||
|
||||
public void selectColor(KeyEvent event) {
|
||||
if (rectangle.getFill() != null) {
|
||||
if (rectangle.getFill() instanceof Color) {
|
||||
colorPicker.setValue((Color) rectangle.getFill());
|
||||
colorPicker.fireEvent(new ActionEvent());
|
||||
}
|
||||
event.consume();
|
||||
}
|
||||
colorPicker.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// The skin can update selection if colorpicker value changes..
|
||||
public void updateSelection(Color color) {
|
||||
setFocusedSquare(null);
|
||||
|
||||
for (ColorSquare c : colorPickerGrid.getSquares()) {
|
||||
if (c.rectangle.getFill().equals(color)) {
|
||||
setFocusedSquare(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// check custom colors
|
||||
for (Node n : customColorGrid.getChildren()) {
|
||||
ColorSquare c = (ColorSquare) n;
|
||||
if (c.rectangle.getFill().equals(color)) {
|
||||
setFocusedSquare(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JFXColorGrid extends GridPane {
|
||||
|
||||
private final List<ColorSquare> squares;
|
||||
final int NUM_OF_COLORS;
|
||||
final int NUM_OF_ROWS;
|
||||
|
||||
public JFXColorGrid() {
|
||||
getStyleClass().add("color-picker-grid");
|
||||
setId("ColorCustomizerColorGrid");
|
||||
int columnIndex = 0;
|
||||
int rowIndex = 0;
|
||||
squares = FXCollections.observableArrayList();
|
||||
double[] limitedColors = colorPicker.getPreDefinedColors();
|
||||
limitedColors = limitedColors == null ? RAW_VALUES : limitedColors;
|
||||
NUM_OF_COLORS = limitedColors.length / 3;
|
||||
NUM_OF_ROWS = (int) Math.ceil((double) NUM_OF_COLORS / (double) NUM_OF_COLUMNS);
|
||||
final int numColors = limitedColors.length / 3;
|
||||
Color[] colors = new Color[numColors];
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
colors[i] = new Color(limitedColors[i * 3] / 255,
|
||||
limitedColors[(i * 3) + 1] / 255, limitedColors[(i * 3) + 2] / 255,
|
||||
1.0);
|
||||
ColorSquare cs = new ColorSquare(colors[i], i);
|
||||
squares.add(cs);
|
||||
}
|
||||
|
||||
for (ColorSquare square : squares) {
|
||||
add(square, columnIndex, rowIndex);
|
||||
columnIndex++;
|
||||
if (columnIndex == NUM_OF_COLUMNS) {
|
||||
columnIndex = 0;
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
setOnMouseDragged(t -> {
|
||||
if (!dragDetected) {
|
||||
dragDetected = true;
|
||||
mouseDragColor = colorPicker.getValue();
|
||||
}
|
||||
int xIndex = clamp(0,
|
||||
(int) t.getX() / (SQUARE_SIZE + 1), NUM_OF_COLUMNS - 1);
|
||||
int yIndex = clamp(0,
|
||||
(int) t.getY() / (SQUARE_SIZE + 1), NUM_OF_ROWS - 1);
|
||||
int index = xIndex + yIndex * NUM_OF_COLUMNS;
|
||||
colorPicker.setValue((Color) squares.get(index).rectangle.getFill());
|
||||
updateSelection(colorPicker.getValue());
|
||||
});
|
||||
addEventHandler(MouseEvent.MOUSE_RELEASED, t -> {
|
||||
if (colorPickerGrid.getBoundsInLocal().contains(t.getX(), t.getY())) {
|
||||
updateSelection(colorPicker.getValue());
|
||||
colorPicker.fireEvent(new ActionEvent());
|
||||
colorPicker.hide();
|
||||
} else {
|
||||
// restore color as mouse release happened outside the grid.
|
||||
if (mouseDragColor != null) {
|
||||
colorPicker.setValue(mouseDragColor);
|
||||
updateSelection(mouseDragColor);
|
||||
}
|
||||
}
|
||||
dragDetected = false;
|
||||
});
|
||||
}
|
||||
|
||||
public List<ColorSquare> getSquares() {
|
||||
return squares;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefWidth(double height) {
|
||||
return (SQUARE_SIZE + 1) * NUM_OF_COLUMNS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width) {
|
||||
return (SQUARE_SIZE + 1) * NUM_OF_ROWS;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int NUM_OF_COLUMNS = 10;
|
||||
private static final double[] RAW_VALUES = {
|
||||
// WARNING: always make sure the number of colors is a divisable by NUM_OF_COLUMNS
|
||||
250, 250, 250, // first row
|
||||
245, 245, 245,
|
||||
238, 238, 238,
|
||||
224, 224, 224,
|
||||
189, 189, 189,
|
||||
158, 158, 158,
|
||||
117, 117, 117,
|
||||
97, 97, 97,
|
||||
66, 66, 66,
|
||||
33, 33, 33,
|
||||
// second row
|
||||
236, 239, 241,
|
||||
207, 216, 220,
|
||||
176, 190, 197,
|
||||
144, 164, 174,
|
||||
120, 144, 156,
|
||||
96, 125, 139,
|
||||
84, 110, 122,
|
||||
69, 90, 100,
|
||||
55, 71, 79,
|
||||
38, 50, 56,
|
||||
// third row
|
||||
255, 235, 238,
|
||||
255, 205, 210,
|
||||
239, 154, 154,
|
||||
229, 115, 115,
|
||||
239, 83, 80,
|
||||
244, 67, 54,
|
||||
229, 57, 53,
|
||||
211, 47, 47,
|
||||
198, 40, 40,
|
||||
183, 28, 28,
|
||||
// forth row
|
||||
252, 228, 236,
|
||||
248, 187, 208,
|
||||
244, 143, 177,
|
||||
240, 98, 146,
|
||||
236, 64, 122,
|
||||
233, 30, 99,
|
||||
216, 27, 96,
|
||||
194, 24, 91,
|
||||
173, 20, 87,
|
||||
136, 14, 79,
|
||||
// fifth row
|
||||
243, 229, 245,
|
||||
225, 190, 231,
|
||||
206, 147, 216,
|
||||
186, 104, 200,
|
||||
171, 71, 188,
|
||||
156, 39, 176,
|
||||
142, 36, 170,
|
||||
123, 31, 162,
|
||||
106, 27, 154,
|
||||
74, 20, 140,
|
||||
// sixth row
|
||||
237, 231, 246,
|
||||
209, 196, 233,
|
||||
179, 157, 219,
|
||||
149, 117, 205,
|
||||
126, 87, 194,
|
||||
103, 58, 183,
|
||||
94, 53, 177,
|
||||
81, 45, 168,
|
||||
69, 39, 160,
|
||||
49, 27, 146,
|
||||
// seventh row
|
||||
232, 234, 246,
|
||||
197, 202, 233,
|
||||
159, 168, 218,
|
||||
121, 134, 203,
|
||||
92, 107, 192,
|
||||
63, 81, 181,
|
||||
57, 73, 171,
|
||||
48, 63, 159,
|
||||
40, 53, 147,
|
||||
26, 35, 126,
|
||||
// eigth row
|
||||
227, 242, 253,
|
||||
187, 222, 251,
|
||||
144, 202, 249,
|
||||
100, 181, 246,
|
||||
66, 165, 245,
|
||||
33, 150, 243,
|
||||
30, 136, 229,
|
||||
25, 118, 210,
|
||||
21, 101, 192,
|
||||
13, 71, 161,
|
||||
// ninth row
|
||||
225, 245, 254,
|
||||
179, 229, 252,
|
||||
129, 212, 250,
|
||||
79, 195, 247,
|
||||
41, 182, 246,
|
||||
3, 169, 244,
|
||||
3, 155, 229,
|
||||
2, 136, 209,
|
||||
2, 119, 189,
|
||||
1, 87, 155,
|
||||
// tenth row
|
||||
224, 247, 250,
|
||||
178, 235, 242,
|
||||
128, 222, 234,
|
||||
77, 208, 225,
|
||||
38, 198, 218,
|
||||
0, 188, 212,
|
||||
0, 172, 193,
|
||||
0, 151, 167,
|
||||
0, 131, 143,
|
||||
0, 96, 100,
|
||||
// eleventh row
|
||||
224, 242, 241,
|
||||
178, 223, 219,
|
||||
128, 203, 196,
|
||||
77, 182, 172,
|
||||
38, 166, 154,
|
||||
0, 150, 136,
|
||||
0, 137, 123,
|
||||
0, 121, 107,
|
||||
0, 105, 92,
|
||||
0, 77, 64,
|
||||
// twelfth row
|
||||
232, 245, 233,
|
||||
200, 230, 201,
|
||||
165, 214, 167,
|
||||
129, 199, 132,
|
||||
102, 187, 106,
|
||||
76, 175, 80,
|
||||
67, 160, 71,
|
||||
56, 142, 60,
|
||||
46, 125, 50,
|
||||
27, 94, 32,
|
||||
|
||||
// thirteenth row
|
||||
241, 248, 233,
|
||||
220, 237, 200,
|
||||
197, 225, 165,
|
||||
174, 213, 129,
|
||||
156, 204, 101,
|
||||
139, 195, 74,
|
||||
124, 179, 66,
|
||||
104, 159, 56,
|
||||
85, 139, 47,
|
||||
51, 105, 30,
|
||||
// fourteenth row
|
||||
249, 251, 231,
|
||||
240, 244, 195,
|
||||
230, 238, 156,
|
||||
220, 231, 117,
|
||||
212, 225, 87,
|
||||
205, 220, 57,
|
||||
192, 202, 51,
|
||||
175, 180, 43,
|
||||
158, 157, 36,
|
||||
130, 119, 23,
|
||||
|
||||
// fifteenth row
|
||||
255, 253, 231,
|
||||
255, 249, 196,
|
||||
255, 245, 157,
|
||||
255, 241, 118,
|
||||
255, 238, 88,
|
||||
255, 235, 59,
|
||||
253, 216, 53,
|
||||
251, 192, 45,
|
||||
249, 168, 37,
|
||||
245, 127, 23,
|
||||
|
||||
// sixteenth row
|
||||
255, 248, 225,
|
||||
255, 236, 179,
|
||||
255, 224, 130,
|
||||
255, 213, 79,
|
||||
255, 202, 40,
|
||||
255, 193, 7,
|
||||
255, 179, 0,
|
||||
255, 160, 0,
|
||||
255, 143, 0,
|
||||
255, 111, 0,
|
||||
|
||||
// seventeenth row
|
||||
255, 243, 224,
|
||||
255, 224, 178,
|
||||
255, 204, 128,
|
||||
255, 183, 77,
|
||||
255, 167, 38,
|
||||
255, 152, 0,
|
||||
251, 140, 0,
|
||||
245, 124, 0,
|
||||
239, 108, 0,
|
||||
230, 81, 0,
|
||||
|
||||
// eighteenth row
|
||||
251, 233, 231,
|
||||
255, 204, 188,
|
||||
255, 171, 145,
|
||||
255, 138, 101,
|
||||
255, 112, 67,
|
||||
255, 87, 34,
|
||||
244, 81, 30,
|
||||
230, 74, 25,
|
||||
216, 67, 21,
|
||||
191, 54, 12,
|
||||
|
||||
// nineteenth row
|
||||
239, 235, 233,
|
||||
215, 204, 200,
|
||||
188, 170, 164,
|
||||
161, 136, 127,
|
||||
141, 110, 99,
|
||||
121, 85, 72,
|
||||
109, 76, 65,
|
||||
93, 64, 55,
|
||||
78, 52, 46,
|
||||
62, 39, 35,
|
||||
};
|
||||
|
||||
private static int clamp(int min, int value, int max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXClippedPane;
|
||||
import com.jfoenix.controls.JFXColorPicker;
|
||||
import com.jfoenix.controls.JFXRippler;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.*;
|
||||
import javafx.css.converter.BooleanConverter;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.skin.ComboBoxPopupControl;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen
|
||||
*/
|
||||
public final class JFXColorPickerSkin extends JFXGenericPickerSkin<Color> {
|
||||
|
||||
private final Label displayNode;
|
||||
private final JFXClippedPane colorBox;
|
||||
private JFXColorPalette popupContent;
|
||||
StyleableBooleanProperty colorLabelVisible = new SimpleStyleableBooleanProperty(StyleableProperties.COLOR_LABEL_VISIBLE,
|
||||
JFXColorPickerSkin.this,
|
||||
"colorLabelVisible",
|
||||
true);
|
||||
|
||||
private final ObjectProperty<Color> textFill = new SimpleObjectProperty<>(Color.BLACK);
|
||||
private final ObjectProperty<Background> colorBoxBackground = new SimpleObjectProperty<>(null);
|
||||
|
||||
public JFXColorPickerSkin(final ColorPicker colorPicker) {
|
||||
super(colorPicker);
|
||||
|
||||
// create displayNode
|
||||
displayNode = new Label("");
|
||||
displayNode.getStyleClass().add("color-label");
|
||||
displayNode.setMouseTransparent(true);
|
||||
displayNode.textFillProperty().bind(textFill);
|
||||
|
||||
// label graphic
|
||||
colorBox = new JFXClippedPane(displayNode);
|
||||
colorBox.getStyleClass().add("color-box");
|
||||
colorBox.setManaged(false);
|
||||
colorBox.backgroundProperty().bind(colorBoxBackground);
|
||||
initColor();
|
||||
final JFXRippler rippler = new JFXRippler(colorBox, JFXRippler.RipplerMask.FIT);
|
||||
rippler.ripplerFillProperty().bind(textFill);
|
||||
getChildren().setAll(rippler);
|
||||
JFXDepthManager.setDepth(getSkinnable(), 1);
|
||||
getSkinnable().setPickOnBounds(false);
|
||||
|
||||
colorPicker.focusedProperty().addListener(observable -> {
|
||||
if (colorPicker.isFocused()) {
|
||||
if (!getSkinnable().isPressed()) {
|
||||
rippler.setOverlayVisible(true);
|
||||
}
|
||||
} else {
|
||||
rippler.setOverlayVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
registerChangeListener(colorPicker.valueProperty(), obs -> updateColor());
|
||||
|
||||
colorLabelVisible.addListener(invalidate -> {
|
||||
if (colorLabelVisible.get()) {
|
||||
displayNode.setText(JFXNodeUtils.colorToHex(getSkinnable().getValue()));
|
||||
} else {
|
||||
displayNode.setText("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
double width = 100;
|
||||
String displayNodeText = displayNode.getText();
|
||||
displayNode.setText("#DDDDDD");
|
||||
width = Math.max(width, super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset));
|
||||
displayNode.setText(displayNodeText);
|
||||
return width + rightInset + leftInset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (colorBox == null) {
|
||||
reflectUpdateDisplayArea();
|
||||
}
|
||||
return topInset + colorBox.prefHeight(width) + bottomInset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren(double x, double y, double w, double h) {
|
||||
super.layoutChildren(x, y, w, h);
|
||||
double hInsets = snappedLeftInset() + snappedRightInset();
|
||||
double vInsets = snappedTopInset() + snappedBottomInset();
|
||||
double width = w + hInsets;
|
||||
double height = h + vInsets;
|
||||
colorBox.resizeRelocate(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node getPopupContent() {
|
||||
if (popupContent == null) {
|
||||
popupContent = new JFXColorPalette((JFXColorPicker) getSkinnable());
|
||||
}
|
||||
return popupContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
final ColorPicker colorPicker = (ColorPicker) getSkinnable();
|
||||
popupContent.updateSelection(colorPicker.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getDisplayNode() {
|
||||
return displayNode;
|
||||
}
|
||||
|
||||
private void updateColor() {
|
||||
final ColorPicker colorPicker = (ColorPicker) getSkinnable();
|
||||
Color color = colorPicker.getValue();
|
||||
Color circleColor = color == null ? Color.WHITE : color;
|
||||
// update picker box color
|
||||
if (((JFXColorPicker) getSkinnable()).isDisableAnimation()) {
|
||||
JFXNodeUtils.updateBackground(colorBox.getBackground(), colorBox, circleColor);
|
||||
} else {
|
||||
Circle colorCircle = new Circle();
|
||||
colorCircle.setFill(circleColor);
|
||||
colorCircle.setManaged(false);
|
||||
colorCircle.setLayoutX(colorBox.getWidth() / 4);
|
||||
colorCircle.setLayoutY(colorBox.getHeight() / 2);
|
||||
colorBox.getChildren().add(colorCircle);
|
||||
Timeline animateColor = new Timeline(new KeyFrame(Duration.millis(240),
|
||||
new KeyValue(colorCircle.radiusProperty(),
|
||||
200,
|
||||
Interpolator.EASE_BOTH)));
|
||||
animateColor.setOnFinished((finish) -> {
|
||||
colorBoxBackground.set(new Background(new BackgroundFill(colorCircle.getFill(), new CornerRadii(3), Insets.EMPTY)));
|
||||
colorBox.getChildren().remove(colorCircle);
|
||||
});
|
||||
animateColor.play();
|
||||
}
|
||||
// update label color
|
||||
textFill.set(circleColor.grayscale().getRed() < 0.5 ? Color.valueOf(
|
||||
"rgba(255, 255, 255, 0.87)") : Color.valueOf("rgba(0, 0, 0, 0.87)"));
|
||||
if (colorLabelVisible.get()) {
|
||||
displayNode.setText(JFXNodeUtils.colorToHex(circleColor));
|
||||
} else {
|
||||
displayNode.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
private void initColor() {
|
||||
final ColorPicker colorPicker = (ColorPicker) getSkinnable();
|
||||
Color color = colorPicker.getValue();
|
||||
Color circleColor = color == null ? Color.WHITE : color;
|
||||
// update picker box color
|
||||
colorBoxBackground.set(new Background(new BackgroundFill(circleColor, new CornerRadii(3), Insets.EMPTY)));
|
||||
// update label color
|
||||
textFill.set(circleColor.grayscale().getRed() < 0.5 ? Color.valueOf(
|
||||
"rgba(255, 255, 255, 0.87)") : Color.valueOf("rgba(0, 0, 0, 0.87)"));
|
||||
if (colorLabelVisible.get()) {
|
||||
displayNode.setText(JFXNodeUtils.colorToHex(circleColor));
|
||||
} else {
|
||||
displayNode.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Stylesheet Handling *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
private static final class StyleableProperties {
|
||||
private static final CssMetaData<ColorPicker, Boolean> COLOR_LABEL_VISIBLE =
|
||||
new CssMetaData<ColorPicker, Boolean>("-fx-color-label-visible",
|
||||
BooleanConverter.getInstance(), Boolean.TRUE) {
|
||||
|
||||
@Override
|
||||
public boolean isSettable(ColorPicker n) {
|
||||
final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin();
|
||||
return skin.colorLabelVisible == null || !skin.colorLabelVisible.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Boolean> getStyleableProperty(ColorPicker n) {
|
||||
final JFXColorPickerSkin skin = (JFXColorPickerSkin) n.getSkin();
|
||||
return skin.colorLabelVisible;
|
||||
}
|
||||
};
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||
new ArrayList<>(ComboBoxPopupControl.getClassCssMetaData());
|
||||
styleables.add(COLOR_LABEL_VISIBLE);
|
||||
STYLEABLES = Collections.unmodifiableList(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.STYLEABLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||
return getClassCssMetaData();
|
||||
}
|
||||
|
||||
protected TextField getEditor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected javafx.util.StringConverter<Color> getConverter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,629 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import com.jfoenix.transitions.CachedTransition;
|
||||
import javafx.animation.Animation.Status;
|
||||
import javafx.animation.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.effect.ColorAdjust;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Path;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen & Bassel El Mabsout this UI allows the user to pick a color using HSL color system
|
||||
*/
|
||||
final class JFXColorPickerUI extends Pane {
|
||||
|
||||
private CachedTransition selectorTransition;
|
||||
private int pickerSize = 400;
|
||||
// sl circle selector size
|
||||
private final int selectorSize = 20;
|
||||
private final double centerX;
|
||||
private final double centerY;
|
||||
private final double huesRadius;
|
||||
private final double slRadius;
|
||||
private double currentHue = 0;
|
||||
|
||||
private final ImageView huesCircleView;
|
||||
private final ImageView slCircleView;
|
||||
private final Pane colorSelector;
|
||||
private final Pane selector;
|
||||
private CurveTransition colorsTransition;
|
||||
|
||||
public JFXColorPickerUI(int pickerSize) {
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
|
||||
this.pickerSize = pickerSize;
|
||||
this.centerX = (double) pickerSize / 2;
|
||||
this.centerY = (double) pickerSize / 2;
|
||||
final double pickerRadius = (double) pickerSize / 2;
|
||||
this.huesRadius = pickerRadius * 0.9;
|
||||
final double huesSmallR = pickerRadius * 0.8;
|
||||
final double huesLargeR = pickerRadius;
|
||||
this.slRadius = pickerRadius * 0.7;
|
||||
|
||||
// Create Hues Circle
|
||||
huesCircleView = new ImageView(getHuesCircle(pickerSize, pickerSize));
|
||||
// clip to smooth the edges
|
||||
Circle outterCircle = new Circle(centerX, centerY, huesLargeR - 2);
|
||||
Circle innterCircle = new Circle(centerX, centerY, huesSmallR + 2);
|
||||
huesCircleView.setClip(Path.subtract(outterCircle, innterCircle));
|
||||
this.getChildren().add(huesCircleView);
|
||||
|
||||
// create Hues Circle Selector
|
||||
Circle r1 = new Circle(pickerRadius - huesSmallR);
|
||||
Circle r2 = new Circle(pickerRadius - huesRadius);
|
||||
colorSelector = new Pane();
|
||||
colorSelector.setStyle("-fx-border-color:#424242; -fx-border-width:1px; -fx-background-color:rgba(255, 255, 255, 0.87);");
|
||||
colorSelector.setPrefSize(pickerRadius - huesSmallR, pickerRadius - huesSmallR);
|
||||
colorSelector.setShape(Path.subtract(r1, r2));
|
||||
colorSelector.setCache(true);
|
||||
colorSelector.setMouseTransparent(true);
|
||||
colorSelector.setPickOnBounds(false);
|
||||
this.getChildren().add(colorSelector);
|
||||
|
||||
// add Hues Selection Listeners
|
||||
huesCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {
|
||||
if (colorsTransition != null) {
|
||||
colorsTransition.stop();
|
||||
}
|
||||
double dx = event.getX() - centerX;
|
||||
double dy = event.getY() - centerY;
|
||||
double theta = Math.atan2(dy, dx);
|
||||
double x = centerX + huesRadius * Math.cos(theta);
|
||||
double y = centerY + huesRadius * Math.sin(theta);
|
||||
colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));
|
||||
colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);
|
||||
colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);
|
||||
});
|
||||
huesCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
|
||||
double dx = event.getX() - centerX;
|
||||
double dy = event.getY() - centerY;
|
||||
double theta = Math.atan2(dy, dx);
|
||||
double x = centerX + huesRadius * Math.cos(theta);
|
||||
double y = centerY + huesRadius * Math.sin(theta);
|
||||
colorsTransition = new CurveTransition(new Point2D(colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,
|
||||
colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2),
|
||||
new Point2D(x, y));
|
||||
colorsTransition.play();
|
||||
});
|
||||
colorSelector.translateXProperty()
|
||||
.addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (newVal.intValue() + colorSelector.getPrefWidth() / 2),
|
||||
(int) (colorSelector.getTranslateY() + colorSelector
|
||||
.getPrefHeight() / 2)));
|
||||
colorSelector.translateYProperty()
|
||||
.addListener((o, oldVal, newVal) -> updateHSLCircleColor((int) (colorSelector.getTranslateX() + colorSelector
|
||||
.getPrefWidth() / 2), (int) (newVal.intValue() + colorSelector.getPrefHeight() / 2)));
|
||||
|
||||
|
||||
// Create SL Circle
|
||||
slCircleView = new ImageView(getSLCricle(pickerSize, pickerSize));
|
||||
slCircleView.setClip(new Circle(centerX, centerY, slRadius - 2));
|
||||
slCircleView.setPickOnBounds(false);
|
||||
this.getChildren().add(slCircleView);
|
||||
|
||||
// create SL Circle Selector
|
||||
selector = new Pane();
|
||||
Circle c1 = new Circle(selectorSize / 2);
|
||||
Circle c2 = new Circle((selectorSize / 2) * 0.5);
|
||||
selector.setShape(Path.subtract(c1, c2));
|
||||
selector.setStyle(
|
||||
"-fx-border-color:#424242; -fx-border-width:1px;-fx-background-color:rgba(255, 255, 255, 0.87);");
|
||||
selector.setPrefSize(selectorSize, selectorSize);
|
||||
selector.setMinSize(selectorSize, selectorSize);
|
||||
selector.setMaxSize(selectorSize, selectorSize);
|
||||
selector.setCache(true);
|
||||
selector.setMouseTransparent(true);
|
||||
this.getChildren().add(selector);
|
||||
|
||||
|
||||
// add SL selection Listeners
|
||||
slCircleView.addEventHandler(MouseEvent.MOUSE_DRAGGED, (event) -> {
|
||||
if (selectorTransition != null) {
|
||||
selectorTransition.stop();
|
||||
}
|
||||
if (Math.pow(event.getX() - centerX, 2) + Math.pow(event.getY() - centerY, 2) < Math.pow(slRadius - 2, 2)) {
|
||||
selector.setTranslateX(event.getX() - selector.getPrefWidth() / 2);
|
||||
selector.setTranslateY(event.getY() - selector.getPrefHeight() / 2);
|
||||
} else {
|
||||
double dx = event.getX() - centerX;
|
||||
double dy = event.getY() - centerY;
|
||||
double theta = Math.atan2(dy, dx);
|
||||
double x = centerX + (slRadius - 2) * Math.cos(theta);
|
||||
double y = centerY + (slRadius - 2) * Math.sin(theta);
|
||||
selector.setTranslateX(x - selector.getPrefWidth() / 2);
|
||||
selector.setTranslateY(y - selector.getPrefHeight() / 2);
|
||||
}
|
||||
});
|
||||
slCircleView.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
|
||||
selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),
|
||||
new KeyValue(selector.translateXProperty(),
|
||||
event.getX() - selector.getPrefWidth() / 2,
|
||||
Interpolator.EASE_BOTH),
|
||||
new KeyValue(selector.translateYProperty(),
|
||||
event.getY() - selector.getPrefHeight() / 2,
|
||||
Interpolator.EASE_BOTH)))) {
|
||||
{
|
||||
setCycleDuration(Duration.millis(160));
|
||||
setDelay(Duration.seconds(0));
|
||||
}
|
||||
};
|
||||
selectorTransition.play();
|
||||
});
|
||||
// add slCircleView listener
|
||||
selector.translateXProperty()
|
||||
.addListener((o, oldVal, newVal) -> setColorAtLocation(newVal.intValue() + selectorSize / 2,
|
||||
(int) selector.getTranslateY() + selectorSize / 2));
|
||||
selector.translateYProperty()
|
||||
.addListener((o, oldVal, newVal) -> setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,
|
||||
newVal.intValue() + selectorSize / 2));
|
||||
|
||||
|
||||
// initial color selection
|
||||
double dx = 20 - centerX;
|
||||
double dy = 20 - centerY;
|
||||
double theta = Math.atan2(dy, dx);
|
||||
double x = centerX + huesRadius * Math.cos(theta);
|
||||
double y = centerY + huesRadius * Math.sin(theta);
|
||||
colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(dy, dx)));
|
||||
colorSelector.setTranslateX(x - colorSelector.getPrefWidth() / 2);
|
||||
colorSelector.setTranslateY(y - colorSelector.getPrefHeight() / 2);
|
||||
selector.setTranslateX(centerX - selector.getPrefWidth() / 2);
|
||||
selector.setTranslateY(centerY - selector.getPrefHeight() / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of Color Nodes that needs to be updated when picking a color
|
||||
*/
|
||||
private final ObservableList<Node> colorNodes = FXCollections.observableArrayList();
|
||||
|
||||
public void addColorSelectionNode(Node... nodes) {
|
||||
colorNodes.addAll(nodes);
|
||||
}
|
||||
|
||||
public void removeColorSelectionNode(Node... nodes) {
|
||||
colorNodes.removeAll(nodes);
|
||||
}
|
||||
|
||||
private void updateHSLCircleColor(int x, int y) {
|
||||
// transform color to HSL space
|
||||
Color color = huesCircleView.getImage().getPixelReader().getColor(x, y);
|
||||
double max = Math.max(color.getRed(), Math.max(color.getGreen(), color.getBlue()));
|
||||
double min = Math.min(color.getRed(), Math.min(color.getGreen(), color.getBlue()));
|
||||
double hue = 0;
|
||||
if (max != min) {
|
||||
double d = max - min;
|
||||
if (max == color.getRed()) {
|
||||
hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);
|
||||
} else if (max == color.getGreen()) {
|
||||
hue = (color.getBlue() - color.getRed()) / d + 2;
|
||||
} else if (max == color.getBlue()) {
|
||||
hue = (color.getRed() - color.getGreen()) / d + 4;
|
||||
}
|
||||
hue /= 6;
|
||||
}
|
||||
currentHue = map(hue, 0, 1, 0, 255);
|
||||
|
||||
// refresh the HSL circle
|
||||
refreshHSLCircle();
|
||||
}
|
||||
|
||||
private void refreshHSLCircle() {
|
||||
ColorAdjust colorAdjust = new ColorAdjust();
|
||||
colorAdjust.setHue(map(currentHue + (currentHue < 127.5 ? 1 : -1) * 127.5, 0, 255, -1, 1));
|
||||
slCircleView.setEffect(colorAdjust);
|
||||
setColorAtLocation((int) selector.getTranslateX() + selectorSize / 2,
|
||||
(int) selector.getTranslateY() + selectorSize / 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* this method is used to move selectors to a certain color
|
||||
*/
|
||||
private boolean allowColorChange = true;
|
||||
private ParallelTransition pTrans;
|
||||
|
||||
public void moveToColor(Color color) {
|
||||
allowColorChange = false;
|
||||
double max = Math.max(color.getRed(),
|
||||
Math.max(color.getGreen(), color.getBlue())), min = Math.min(color.getRed(),
|
||||
Math.min(color.getGreen(),
|
||||
color.getBlue()));
|
||||
double hue = 0;
|
||||
double l = (max + min) / 2;
|
||||
double s = 0;
|
||||
if (max == min) {
|
||||
hue = s = 0; // achromatic
|
||||
} else {
|
||||
double d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
if (max == color.getRed()) {
|
||||
hue = (color.getGreen() - color.getBlue()) / d + (color.getGreen() < color.getBlue() ? 6 : 0);
|
||||
} else if (max == color.getGreen()) {
|
||||
hue = (color.getBlue() - color.getRed()) / d + 2;
|
||||
} else if (max == color.getBlue()) {
|
||||
hue = (color.getRed() - color.getGreen()) / d + 4;
|
||||
}
|
||||
hue /= 6;
|
||||
}
|
||||
currentHue = map(hue, 0, 1, 0, 255);
|
||||
|
||||
// Animate Hue
|
||||
double theta = map(currentHue, 0, 255, -Math.PI, Math.PI);
|
||||
double x = centerX + huesRadius * Math.cos(theta);
|
||||
double y = centerY + huesRadius * Math.sin(theta);
|
||||
colorsTransition = new CurveTransition(
|
||||
new Point2D(
|
||||
colorSelector.getTranslateX() + colorSelector.getPrefWidth() / 2,
|
||||
colorSelector.getTranslateY() + colorSelector.getPrefHeight() / 2
|
||||
),
|
||||
new Point2D(x, y));
|
||||
|
||||
// Animate SL
|
||||
s = map(s, 0, 1, 0, 255);
|
||||
l = map(l, 0, 1, 0, 255);
|
||||
Point2D point = getPointFromSL((int) s, (int) l, slRadius);
|
||||
double pX = centerX - point.getX();
|
||||
double pY = centerY - point.getY();
|
||||
|
||||
double endPointX;
|
||||
double endPointY;
|
||||
if (Math.pow(pX - centerX, 2) + Math.pow(pY - centerY, 2) < Math.pow(slRadius - 2, 2)) {
|
||||
endPointX = pX - selector.getPrefWidth() / 2;
|
||||
endPointY = pY - selector.getPrefHeight() / 2;
|
||||
} else {
|
||||
double dx = pX - centerX;
|
||||
double dy = pY - centerY;
|
||||
theta = Math.atan2(dy, dx);
|
||||
x = centerX + (slRadius - 2) * Math.cos(theta);
|
||||
y = centerY + (slRadius - 2) * Math.sin(theta);
|
||||
endPointX = x - selector.getPrefWidth() / 2;
|
||||
endPointY = y - selector.getPrefHeight() / 2;
|
||||
}
|
||||
selectorTransition = new CachedTransition(selector, new Timeline(new KeyFrame(Duration.millis(1000),
|
||||
new KeyValue(selector.translateXProperty(),
|
||||
endPointX,
|
||||
Interpolator.EASE_BOTH),
|
||||
new KeyValue(selector.translateYProperty(),
|
||||
endPointY,
|
||||
Interpolator.EASE_BOTH)))) {
|
||||
{
|
||||
setCycleDuration(Duration.millis(160));
|
||||
setDelay(Duration.seconds(0));
|
||||
}
|
||||
};
|
||||
|
||||
if (pTrans != null) {
|
||||
pTrans.stop();
|
||||
}
|
||||
pTrans = new ParallelTransition(colorsTransition, selectorTransition);
|
||||
pTrans.setOnFinished((finish) -> {
|
||||
if (pTrans.getStatus() == Status.STOPPED) {
|
||||
allowColorChange = true;
|
||||
}
|
||||
});
|
||||
pTrans.play();
|
||||
|
||||
refreshHSLCircle();
|
||||
}
|
||||
|
||||
private void setColorAtLocation(int x, int y) {
|
||||
if (allowColorChange) {
|
||||
Color color = getColorAtLocation(x, y);
|
||||
String colorString = "rgb(" + color.getRed() * 255 + "," + color.getGreen() * 255 + "," + color.getBlue() * 255 + ");";
|
||||
for (Node node : colorNodes)
|
||||
node.setStyle("-fx-background-color:" + colorString + "; -fx-fill:" + colorString + ";");
|
||||
}
|
||||
}
|
||||
|
||||
private Color getColorAtLocation(double x, double y) {
|
||||
double dy = x - centerX;
|
||||
double dx = y - centerY;
|
||||
return getColor(dx, dy);
|
||||
}
|
||||
|
||||
private Image getHuesCircle(int width, int height) {
|
||||
WritableImage raster = new WritableImage(width, height);
|
||||
PixelWriter pixelWriter = raster.getPixelWriter();
|
||||
Point2D center = new Point2D((double) width / 2, (double) height / 2);
|
||||
double rsmall = 0.8 * width / 2;
|
||||
double rbig = (double) width / 2;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
double dx = x - center.getX();
|
||||
double dy = y - center.getY();
|
||||
double distance = Math.sqrt((dx * dx) + (dy * dy));
|
||||
double o = Math.atan2(dy, dx);
|
||||
if (distance > rsmall && distance < rbig) {
|
||||
double H = map(o, -Math.PI, Math.PI, 0, 255);
|
||||
double S = 255;
|
||||
double L = 152;
|
||||
pixelWriter.setColor(x, y, HSL2RGB(H, S, L));
|
||||
}
|
||||
}
|
||||
}
|
||||
return raster;
|
||||
}
|
||||
|
||||
private Image getSLCricle(int width, int height) {
|
||||
WritableImage raster = new WritableImage(width, height);
|
||||
PixelWriter pixelWriter = raster.getPixelWriter();
|
||||
Point2D center = new Point2D((double) width / 2, (double) height / 2);
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
double dy = x - center.getX();
|
||||
double dx = y - center.getY();
|
||||
pixelWriter.setColor(x, y, getColor(dx, dy));
|
||||
}
|
||||
}
|
||||
return raster;
|
||||
}
|
||||
|
||||
private double clamp(double from, double small, double big) {
|
||||
return Math.min(Math.max(from, small), big);
|
||||
}
|
||||
|
||||
private Color getColor(double dx, double dy) {
|
||||
double distance = Math.sqrt((dx * dx) + (dy * dy));
|
||||
double rverysmall = 0.65 * ((double) pickerSize / 2);
|
||||
Color pixelColor = Color.BLUE;
|
||||
|
||||
if (distance <= rverysmall * 1.1) {
|
||||
double angle = -Math.PI / 2.;
|
||||
double angle1 = angle + 2 * Math.PI / 3.;
|
||||
double angle2 = angle1 + 2 * Math.PI / 3.;
|
||||
double x1 = rverysmall * Math.sin(angle1);
|
||||
double y1 = rverysmall * Math.cos(angle1);
|
||||
double x2 = rverysmall * Math.sin(angle2);
|
||||
double y2 = rverysmall * Math.cos(angle2);
|
||||
dx += 0.01;
|
||||
double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(x2, y2), new Point2D(dx, dy));
|
||||
double xArc = circle[0];
|
||||
double yArc = 0;
|
||||
double arcR = circle[2];
|
||||
double Arco = Math.atan2(dx - xArc, dy - yArc);
|
||||
double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);
|
||||
double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);
|
||||
|
||||
double finalX = xArc > 0 ? xArc - arcR : xArc + arcR;
|
||||
|
||||
double saturation = map(finalX, -rverysmall, rverysmall, 255, 0);
|
||||
|
||||
double lightness = 255;
|
||||
double diffAngle = Arco2 - Arco1;
|
||||
double diffArco = Arco - Arco1;
|
||||
if (dx < x1) {
|
||||
diffAngle = diffAngle < 0 ? 2 * Math.PI + diffAngle : diffAngle;
|
||||
diffAngle = Math.abs(2 * Math.PI - diffAngle);
|
||||
diffArco = diffArco < 0 ? 2 * Math.PI + diffArco : diffArco;
|
||||
diffArco = Math.abs(2 * Math.PI - diffArco);
|
||||
}
|
||||
lightness = map(diffArco, 0, diffAngle, 0, 255);
|
||||
|
||||
|
||||
if (distance > rverysmall) {
|
||||
saturation = 255 - saturation;
|
||||
if (lightness < 0 && dy < 0) {
|
||||
lightness = 255;
|
||||
}
|
||||
}
|
||||
lightness = clamp(lightness, 0, 255);
|
||||
if ((saturation < 10 && dx < x1) || (saturation > 240 && dx > x1)) {
|
||||
saturation = 255 - saturation;
|
||||
}
|
||||
saturation = clamp(saturation, 0, 255);
|
||||
pixelColor = HSL2RGB(currentHue, saturation, lightness);
|
||||
}
|
||||
return pixelColor;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Hues Animation *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
private final class CurveTransition extends Transition {
|
||||
Point2D from;
|
||||
double fromTheta;
|
||||
double toTheta;
|
||||
|
||||
public CurveTransition(Point2D from, Point2D to) {
|
||||
this.from = from;
|
||||
double fromDx = from.getX() - centerX;
|
||||
double fromDy = from.getY() - centerY;
|
||||
fromTheta = Math.atan2(fromDy, fromDx);
|
||||
double toDx = to.getX() - centerX;
|
||||
double toDy = to.getY() - centerY;
|
||||
toTheta = Math.atan2(toDy, toDx);
|
||||
setInterpolator(Interpolator.EASE_BOTH);
|
||||
setDelay(Duration.millis(0));
|
||||
setCycleDuration(Duration.millis(240));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void interpolate(double frac) {
|
||||
double dif = Math.min(Math.abs(toTheta - fromTheta), 2 * Math.PI - Math.abs(toTheta - fromTheta));
|
||||
if (dif == 2 * Math.PI - Math.abs(toTheta - fromTheta)) {
|
||||
int dir = -1;
|
||||
if (toTheta < fromTheta) {
|
||||
dir = 1;
|
||||
}
|
||||
dif = dir * dif;
|
||||
} else {
|
||||
dif = toTheta - fromTheta;
|
||||
}
|
||||
|
||||
Point2D newP = rotate(from, new Point2D(centerX, centerY), frac * dif);
|
||||
colorSelector.setRotate(90 + Math.toDegrees(Math.atan2(newP.getY() - centerY, newP.getX() - centerX)));
|
||||
colorSelector.setTranslateX(newP.getX() - colorSelector.getPrefWidth() / 2);
|
||||
colorSelector.setTranslateY(newP.getY() - colorSelector.getPrefHeight() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Util methods *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
private double map(double val, double min1, double max1, double min2, double max2) {
|
||||
return min2 + (max2 - min2) * ((val - min1) / (max1 - min1));
|
||||
}
|
||||
|
||||
private Color HSL2RGB(double hue, double sat, double lum) {
|
||||
hue = map(hue, 0, 255, 0, 359);
|
||||
sat = map(sat, 0, 255, 0, 1);
|
||||
lum = map(lum, 0, 255, 0, 1);
|
||||
double v;
|
||||
double red, green, blue;
|
||||
double m;
|
||||
double sv;
|
||||
int sextant;
|
||||
double fract, vsf, mid1, mid2;
|
||||
|
||||
red = lum; // default to gray
|
||||
green = lum;
|
||||
blue = lum;
|
||||
v = (lum <= 0.5) ? (lum * (1.0 + sat)) : (lum + sat - lum * sat);
|
||||
m = lum + lum - v;
|
||||
sv = (v - m) / v;
|
||||
hue /= 60.0; //get into range 0..6
|
||||
sextant = (int) Math.floor(hue); // int32 rounds up or down.
|
||||
fract = hue - sextant;
|
||||
vsf = v * sv * fract;
|
||||
mid1 = m + vsf;
|
||||
mid2 = v - vsf;
|
||||
|
||||
if (v > 0) {
|
||||
switch (sextant) {
|
||||
case 0:
|
||||
red = v;
|
||||
green = mid1;
|
||||
blue = m;
|
||||
break;
|
||||
case 1:
|
||||
red = mid2;
|
||||
green = v;
|
||||
blue = m;
|
||||
break;
|
||||
case 2:
|
||||
red = m;
|
||||
green = v;
|
||||
blue = mid1;
|
||||
break;
|
||||
case 3:
|
||||
red = m;
|
||||
green = mid2;
|
||||
blue = v;
|
||||
break;
|
||||
case 4:
|
||||
red = mid1;
|
||||
green = m;
|
||||
blue = v;
|
||||
break;
|
||||
case 5:
|
||||
red = v;
|
||||
green = m;
|
||||
blue = mid2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new Color(red, green, blue, 1);
|
||||
}
|
||||
|
||||
private double[] circleFrom3Points(Point2D a, Point2D b, Point2D c) {
|
||||
double ax, ay, bx, by, cx, cy, x1, y11, dx1, dy1, x2, y2, dx2, dy2, ox, oy, dx, dy, radius; // Variables Used and to Declared
|
||||
ax = a.getX();
|
||||
ay = a.getY(); //first Point X and Y
|
||||
bx = b.getX();
|
||||
by = b.getY(); // Second Point X and Y
|
||||
cx = c.getX();
|
||||
cy = c.getY(); // Third Point X and Y
|
||||
|
||||
////****************Following are Basic Procedure**********************///
|
||||
x1 = (bx + ax) / 2;
|
||||
y11 = (by + ay) / 2;
|
||||
dy1 = bx - ax;
|
||||
dx1 = -(by - ay);
|
||||
|
||||
x2 = (cx + bx) / 2;
|
||||
y2 = (cy + by) / 2;
|
||||
dy2 = cx - bx;
|
||||
dx2 = -(cy - by);
|
||||
|
||||
ox = (y11 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2) / (dx1 * dy2 - dy1 * dx2);
|
||||
oy = (ox - x1) * dy1 / dx1 + y11;
|
||||
|
||||
dx = ox - ax;
|
||||
dy = oy - ay;
|
||||
radius = Math.sqrt(dx * dx + dy * dy);
|
||||
return new double[]{ox, oy, radius};
|
||||
}
|
||||
|
||||
private Point2D getPointFromSL(int saturation, int lightness, double radius) {
|
||||
double dy = map(saturation, 0, 255, -radius, radius);
|
||||
double angle = 0.;
|
||||
double angle1 = angle + 2 * Math.PI / 3.;
|
||||
double angle2 = angle1 + 2 * Math.PI / 3.;
|
||||
double x1 = radius * Math.sin(angle1);
|
||||
double y1 = radius * Math.cos(angle1);
|
||||
double x2 = radius * Math.sin(angle2);
|
||||
double y2 = radius * Math.cos(angle2);
|
||||
double dx = 0;
|
||||
double[] circle = circleFrom3Points(new Point2D(x1, y1), new Point2D(dx, dy), new Point2D(x2, y2));
|
||||
double xArc = circle[0];
|
||||
double yArc = circle[1];
|
||||
double arcR = circle[2];
|
||||
double Arco1 = Math.atan2(x1 - xArc, y1 - yArc);
|
||||
double Arco2 = Math.atan2(x2 - xArc, y2 - yArc);
|
||||
double ArcoFinal = map(lightness, 0, 255, Arco2, Arco1);
|
||||
double finalX = xArc + arcR * Math.sin(ArcoFinal);
|
||||
double finalY = yArc + arcR * Math.cos(ArcoFinal);
|
||||
if (dy < y1) {
|
||||
ArcoFinal = map(lightness, 0, 255, Arco1, Arco2 + 2 * Math.PI);
|
||||
finalX = -xArc - arcR * Math.sin(ArcoFinal);
|
||||
finalY = yArc + arcR * Math.cos(ArcoFinal);
|
||||
}
|
||||
return new Point2D(finalX, finalY);
|
||||
}
|
||||
|
||||
private Point2D rotate(Point2D a, Point2D center, double angle) {
|
||||
double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math
|
||||
.sin(angle);
|
||||
double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math
|
||||
.cos(angle);
|
||||
return new Point2D(resultX, resultY);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,520 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import com.jfoenix.transitions.CachedTransition;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.*;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static javafx.animation.Interpolator.EASE_BOTH;
|
||||
|
||||
/// @author Shadi Shaheen
|
||||
final class JFXCustomColorPicker extends Pane {
|
||||
|
||||
ObjectProperty<RecentColorPath> selectedPath = new SimpleObjectProperty<>();
|
||||
private MoveTo startPoint;
|
||||
private CubicCurveTo curve0To;
|
||||
private CubicCurveTo outerCircleCurveTo;
|
||||
private CubicCurveTo curve1To;
|
||||
private CubicCurveTo innerCircleCurveTo;
|
||||
private final ArrayList<CubicCurve> curves = new ArrayList<>();
|
||||
|
||||
private final double distance = 200;
|
||||
private final double centerX = distance;
|
||||
private final double centerY = distance;
|
||||
private final double radius = 110;
|
||||
|
||||
private static final int shapesNumber = 13;
|
||||
private final ArrayList<RecentColorPath> shapes = new ArrayList<>();
|
||||
private CachedTransition showAnimation;
|
||||
private final JFXColorPickerUI hslColorPicker;
|
||||
|
||||
public JFXCustomColorPicker() {
|
||||
this.setPickOnBounds(false);
|
||||
this.setMinSize(distance * 2, distance * 2);
|
||||
|
||||
final DoubleProperty rotationAngle = new SimpleDoubleProperty(2.1);
|
||||
|
||||
// draw recent colors shape using cubic curves
|
||||
init(rotationAngle, centerX + 53, centerY + 162);
|
||||
|
||||
hslColorPicker = new JFXColorPickerUI((int) distance);
|
||||
hslColorPicker.setLayoutX(centerX - distance / 2);
|
||||
hslColorPicker.setLayoutY(centerY - distance / 2);
|
||||
this.getChildren().add(hslColorPicker);
|
||||
// add recent colors shapes
|
||||
final int shapesStartIndex = this.getChildren().size();
|
||||
final int shapesEndIndex = shapesStartIndex + shapesNumber;
|
||||
for (int i = 0; i < shapesNumber; i++) {
|
||||
final double angle = 2 * i * Math.PI / shapesNumber;
|
||||
final RecentColorPath path = new RecentColorPath(startPoint,
|
||||
curve0To,
|
||||
outerCircleCurveTo,
|
||||
curve1To,
|
||||
innerCircleCurveTo);
|
||||
shapes.add(path);
|
||||
path.setPickOnBounds(false);
|
||||
final Rotate rotate = new Rotate(Math.toDegrees(angle), centerX, centerY);
|
||||
path.getTransforms().add(rotate);
|
||||
this.getChildren().add(shapesStartIndex, path);
|
||||
path.setFill(Color.valueOf(getDefaultColor(i)));
|
||||
path.setFocusTraversable(true);
|
||||
path.addEventHandler(MouseEvent.MOUSE_CLICKED, (event) -> {
|
||||
path.requestFocus();
|
||||
selectedPath.set(path);
|
||||
});
|
||||
}
|
||||
|
||||
// add selection listeners
|
||||
selectedPath.addListener((o, oldVal, newVal) -> {
|
||||
if (oldVal != null) {
|
||||
hslColorPicker.removeColorSelectionNode(oldVal);
|
||||
oldVal.playTransition(-1);
|
||||
}
|
||||
// re-arrange children
|
||||
while (this.getChildren().indexOf(newVal) != shapesEndIndex - 1) {
|
||||
final Node temp = this.getChildren().get(shapesEndIndex - 1);
|
||||
this.getChildren().remove(shapesEndIndex - 1);
|
||||
this.getChildren().add(shapesStartIndex, temp);
|
||||
}
|
||||
// update path fill according to the color picker
|
||||
newVal.setStroke(Color.rgb(255, 255, 255, 0.87));
|
||||
newVal.playTransition(1);
|
||||
hslColorPicker.moveToColor((Color) newVal.getFill());
|
||||
hslColorPicker.addColorSelectionNode(newVal);
|
||||
});
|
||||
// init selection
|
||||
selectedPath.set((RecentColorPath) this.getChildren().get(shapesStartIndex));
|
||||
}
|
||||
|
||||
public int getShapesNumber() {
|
||||
return shapesNumber;
|
||||
}
|
||||
|
||||
public int getSelectedIndex() {
|
||||
if (selectedPath.get() != null) {
|
||||
return shapes.indexOf(selectedPath.get());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setColor(final Color color) {
|
||||
shapes.get(getSelectedIndex()).setFill(color);
|
||||
hslColorPicker.moveToColor(color);
|
||||
}
|
||||
|
||||
public Color getColor(final int index) {
|
||||
if (index >= 0 && index < shapes.size()) {
|
||||
return (Color) shapes.get(index).getFill();
|
||||
} else {
|
||||
return Color.WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
public void preAnimate() {
|
||||
final CubicCurve firstCurve = curves.get(0);
|
||||
final double x = firstCurve.getStartX();
|
||||
final double y = firstCurve.getStartY();
|
||||
firstCurve.setStartX(centerX);
|
||||
firstCurve.setStartY(centerY);
|
||||
|
||||
final CubicCurve secondCurve = curves.get(1);
|
||||
final double x1 = secondCurve.getStartX();
|
||||
final double y1 = secondCurve.getStartY();
|
||||
secondCurve.setStartX(centerX);
|
||||
secondCurve.setStartY(centerY);
|
||||
|
||||
final double cx1 = firstCurve.getControlX1();
|
||||
final double cy1 = firstCurve.getControlY1();
|
||||
firstCurve.setControlX1(centerX + radius);
|
||||
firstCurve.setControlY1(centerY + radius / 2);
|
||||
|
||||
final KeyFrame keyFrame = new KeyFrame(Duration.millis(1000),
|
||||
new KeyValue(firstCurve.startXProperty(), x, EASE_BOTH),
|
||||
new KeyValue(firstCurve.startYProperty(), y, EASE_BOTH),
|
||||
new KeyValue(secondCurve.startXProperty(), x1, EASE_BOTH),
|
||||
new KeyValue(secondCurve.startYProperty(), y1, EASE_BOTH),
|
||||
new KeyValue(firstCurve.controlX1Property(), cx1, EASE_BOTH),
|
||||
new KeyValue(firstCurve.controlY1Property(), cy1, EASE_BOTH)
|
||||
);
|
||||
final Timeline timeline = new Timeline(keyFrame);
|
||||
showAnimation = new CachedTransition(this, timeline) {
|
||||
{
|
||||
setCycleDuration(Duration.millis(240));
|
||||
setDelay(Duration.millis(0));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void animate() {
|
||||
showAnimation.play();
|
||||
}
|
||||
|
||||
private void init(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {
|
||||
|
||||
final Circle innerCircle = new Circle(centerX, centerY, radius, Color.TRANSPARENT);
|
||||
final Circle outerCircle = new Circle(centerX, centerY, radius * 2, Color.web("blue", 0.5));
|
||||
|
||||
// Create a composite shape of 4 cubic curves
|
||||
// create 2 cubic curves of the shape
|
||||
createQuadraticCurve(rotationAngle, initControlX1, initControlY1);
|
||||
|
||||
// inner circle curve
|
||||
final CubicCurve innerCircleCurve = new CubicCurve();
|
||||
innerCircleCurve.startXProperty().bind(curves.get(0).startXProperty());
|
||||
innerCircleCurve.startYProperty().bind(curves.get(0).startYProperty());
|
||||
innerCircleCurve.endXProperty().bind(curves.get(1).startXProperty());
|
||||
innerCircleCurve.endYProperty().bind(curves.get(1).startYProperty());
|
||||
curves.get(0).startXProperty().addListener((o, oldVal, newVal) -> {
|
||||
final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),
|
||||
curves.get(0).getStartY(),
|
||||
innerCircle,
|
||||
shapesNumber,
|
||||
-1);
|
||||
innerCircleCurve.setControlX1(controlPoint.getX());
|
||||
innerCircleCurve.setControlY1(controlPoint.getY());
|
||||
});
|
||||
curves.get(0).startYProperty().addListener((o, oldVal, newVal) -> {
|
||||
final Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),
|
||||
newVal.doubleValue(),
|
||||
innerCircle,
|
||||
shapesNumber,
|
||||
-1);
|
||||
innerCircleCurve.setControlX1(controlPoint.getX());
|
||||
innerCircleCurve.setControlY1(controlPoint.getY());
|
||||
});
|
||||
curves.get(1).startXProperty().addListener((o, oldVal, newVal) -> {
|
||||
final Point2D controlPoint = makeControlPoint(newVal.doubleValue(),
|
||||
curves.get(1).getStartY(),
|
||||
innerCircle,
|
||||
shapesNumber,
|
||||
1);
|
||||
innerCircleCurve.setControlX2(controlPoint.getX());
|
||||
innerCircleCurve.setControlY2(controlPoint.getY());
|
||||
});
|
||||
curves.get(1).startYProperty().addListener((o, oldVal, newVal) -> {
|
||||
final Point2D controlPoint = makeControlPoint(curves.get(1).getStartX(),
|
||||
newVal.doubleValue(),
|
||||
innerCircle,
|
||||
shapesNumber,
|
||||
1);
|
||||
innerCircleCurve.setControlX2(controlPoint.getX());
|
||||
innerCircleCurve.setControlY2(controlPoint.getY());
|
||||
});
|
||||
Point2D controlPoint = makeControlPoint(curves.get(0).getStartX(),
|
||||
curves.get(0).getStartY(),
|
||||
innerCircle,
|
||||
shapesNumber,
|
||||
-1);
|
||||
innerCircleCurve.setControlX1(controlPoint.getX());
|
||||
innerCircleCurve.setControlY1(controlPoint.getY());
|
||||
controlPoint = makeControlPoint(curves.get(1).getStartX(),
|
||||
curves.get(1).getStartY(),
|
||||
innerCircle,
|
||||
shapesNumber,
|
||||
1);
|
||||
innerCircleCurve.setControlX2(controlPoint.getX());
|
||||
innerCircleCurve.setControlY2(controlPoint.getY());
|
||||
|
||||
// outer circle curve
|
||||
final CubicCurve outerCircleCurve = new CubicCurve();
|
||||
outerCircleCurve.startXProperty().bind(curves.get(0).endXProperty());
|
||||
outerCircleCurve.startYProperty().bind(curves.get(0).endYProperty());
|
||||
outerCircleCurve.endXProperty().bind(curves.get(1).endXProperty());
|
||||
outerCircleCurve.endYProperty().bind(curves.get(1).endYProperty());
|
||||
controlPoint = makeControlPoint(curves.get(0).getEndX(),
|
||||
curves.get(0).getEndY(),
|
||||
outerCircle,
|
||||
shapesNumber,
|
||||
-1);
|
||||
outerCircleCurve.setControlX1(controlPoint.getX());
|
||||
outerCircleCurve.setControlY1(controlPoint.getY());
|
||||
controlPoint = makeControlPoint(curves.get(1).getEndX(), curves.get(1).getEndY(), outerCircle, shapesNumber, 1);
|
||||
outerCircleCurve.setControlX2(controlPoint.getX());
|
||||
outerCircleCurve.setControlY2(controlPoint.getY());
|
||||
|
||||
startPoint = new MoveTo();
|
||||
startPoint.xProperty().bind(curves.get(0).startXProperty());
|
||||
startPoint.yProperty().bind(curves.get(0).startYProperty());
|
||||
|
||||
curve0To = new CubicCurveTo();
|
||||
curve0To.controlX1Property().bind(curves.get(0).controlX1Property());
|
||||
curve0To.controlY1Property().bind(curves.get(0).controlY1Property());
|
||||
curve0To.controlX2Property().bind(curves.get(0).controlX2Property());
|
||||
curve0To.controlY2Property().bind(curves.get(0).controlY2Property());
|
||||
curve0To.xProperty().bind(curves.get(0).endXProperty());
|
||||
curve0To.yProperty().bind(curves.get(0).endYProperty());
|
||||
|
||||
outerCircleCurveTo = new CubicCurveTo();
|
||||
outerCircleCurveTo.controlX1Property().bind(outerCircleCurve.controlX1Property());
|
||||
outerCircleCurveTo.controlY1Property().bind(outerCircleCurve.controlY1Property());
|
||||
outerCircleCurveTo.controlX2Property().bind(outerCircleCurve.controlX2Property());
|
||||
outerCircleCurveTo.controlY2Property().bind(outerCircleCurve.controlY2Property());
|
||||
outerCircleCurveTo.xProperty().bind(outerCircleCurve.endXProperty());
|
||||
outerCircleCurveTo.yProperty().bind(outerCircleCurve.endYProperty());
|
||||
|
||||
curve1To = new CubicCurveTo();
|
||||
curve1To.controlX1Property().bind(curves.get(1).controlX2Property());
|
||||
curve1To.controlY1Property().bind(curves.get(1).controlY2Property());
|
||||
curve1To.controlX2Property().bind(curves.get(1).controlX1Property());
|
||||
curve1To.controlY2Property().bind(curves.get(1).controlY1Property());
|
||||
curve1To.xProperty().bind(curves.get(1).startXProperty());
|
||||
curve1To.yProperty().bind(curves.get(1).startYProperty());
|
||||
|
||||
innerCircleCurveTo = new CubicCurveTo();
|
||||
innerCircleCurveTo.controlX1Property().bind(innerCircleCurve.controlX2Property());
|
||||
innerCircleCurveTo.controlY1Property().bind(innerCircleCurve.controlY2Property());
|
||||
innerCircleCurveTo.controlX2Property().bind(innerCircleCurve.controlX1Property());
|
||||
innerCircleCurveTo.controlY2Property().bind(innerCircleCurve.controlY1Property());
|
||||
innerCircleCurveTo.xProperty().bind(innerCircleCurve.startXProperty());
|
||||
innerCircleCurveTo.yProperty().bind(innerCircleCurve.startYProperty());
|
||||
}
|
||||
|
||||
private void createQuadraticCurve(final DoubleProperty rotationAngle, final double initControlX1, final double initControlY1) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
||||
double angle = 2 * i * Math.PI / shapesNumber;
|
||||
double xOffset = radius * Math.cos(angle);
|
||||
double yOffset = radius * Math.sin(angle);
|
||||
final double startx = centerX + xOffset;
|
||||
final double starty = centerY + yOffset;
|
||||
|
||||
final double diffStartCenterX = startx - centerX;
|
||||
final double diffStartCenterY = starty - centerY;
|
||||
final double sinRotAngle = Math.sin(rotationAngle.get());
|
||||
final double cosRotAngle = Math.cos(rotationAngle.get());
|
||||
final double startXR = cosRotAngle * diffStartCenterX - sinRotAngle * diffStartCenterY + centerX;
|
||||
final double startYR = sinRotAngle * diffStartCenterX + cosRotAngle * diffStartCenterY + centerY;
|
||||
|
||||
angle = 2 * i * Math.PI / shapesNumber;
|
||||
xOffset = distance * Math.cos(angle);
|
||||
yOffset = distance * Math.sin(angle);
|
||||
|
||||
final double endx = centerX + xOffset;
|
||||
final double endy = centerY + yOffset;
|
||||
|
||||
final CubicCurve curvedLine = new CubicCurve();
|
||||
curvedLine.setStartX(startXR);
|
||||
curvedLine.setStartY(startYR);
|
||||
curvedLine.setControlX1(startXR);
|
||||
curvedLine.setControlY1(startYR);
|
||||
curvedLine.setControlX2(endx);
|
||||
curvedLine.setControlY2(endy);
|
||||
curvedLine.setEndX(endx);
|
||||
curvedLine.setEndY(endy);
|
||||
curvedLine.setStroke(Color.FORESTGREEN);
|
||||
curvedLine.setStrokeWidth(1);
|
||||
curvedLine.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
curvedLine.setFill(Color.TRANSPARENT);
|
||||
curvedLine.setMouseTransparent(true);
|
||||
rotationAngle.addListener((o, oldVal, newVal) -> {
|
||||
final double newstartXR = ((cosRotAngle * diffStartCenterX) - (sinRotAngle * diffStartCenterY)) + centerX;
|
||||
final double newstartYR = (sinRotAngle * diffStartCenterX) + (cosRotAngle * diffStartCenterY) + centerY;
|
||||
curvedLine.setStartX(newstartXR);
|
||||
curvedLine.setStartY(newstartYR);
|
||||
});
|
||||
|
||||
curves.add(curvedLine);
|
||||
|
||||
if (i == 0) {
|
||||
curvedLine.setControlX1(initControlX1);
|
||||
curvedLine.setControlY1(initControlY1);
|
||||
} else {
|
||||
final CubicCurve firstCurve = curves.get(0);
|
||||
final double curveTheta = 2 * curves.indexOf(curvedLine) * Math.PI / shapesNumber;
|
||||
|
||||
curvedLine.controlX1Property().bind(Bindings.createDoubleBinding(() -> {
|
||||
final double a = firstCurve.getControlX1() - centerX;
|
||||
final double b = Math.sin(curveTheta) * (firstCurve.getControlY1() - centerY);
|
||||
return ((Math.cos(curveTheta) * a) - b) + centerX;
|
||||
}, firstCurve.controlX1Property(), firstCurve.controlY1Property()));
|
||||
|
||||
curvedLine.controlY1Property().bind(Bindings.createDoubleBinding(() -> {
|
||||
final double a = Math.sin(curveTheta) * (firstCurve.getControlX1() - centerX);
|
||||
final double b = Math.cos(curveTheta) * (firstCurve.getControlY1() - centerY);
|
||||
return a + b + centerY;
|
||||
}, firstCurve.controlX1Property(), firstCurve.controlY1Property()));
|
||||
|
||||
|
||||
curvedLine.controlX2Property().bind(Bindings.createDoubleBinding(() -> {
|
||||
final double a = firstCurve.getControlX2() - centerX;
|
||||
final double b = firstCurve.getControlY2() - centerY;
|
||||
return ((Math.cos(curveTheta) * a) - (Math.sin(curveTheta) * b)) + centerX;
|
||||
}, firstCurve.controlX2Property(), firstCurve.controlY2Property()));
|
||||
|
||||
curvedLine.controlY2Property().bind(Bindings.createDoubleBinding(() -> {
|
||||
final double a = Math.sin(curveTheta) * (firstCurve.getControlX2() - centerX);
|
||||
final double b = Math.cos(curveTheta) * (firstCurve.getControlY2() - centerY);
|
||||
return a + b + centerY;
|
||||
}, firstCurve.controlX2Property(), firstCurve.controlY2Property()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getDefaultColor(final int i) {
|
||||
String color = "#FFFFFF";
|
||||
switch (i) {
|
||||
case 0:
|
||||
color = "#8F3F7E";
|
||||
break;
|
||||
case 1:
|
||||
color = "#B5305F";
|
||||
break;
|
||||
case 2:
|
||||
color = "#CE584A";
|
||||
break;
|
||||
case 3:
|
||||
color = "#DB8D5C";
|
||||
break;
|
||||
case 4:
|
||||
color = "#DA854E";
|
||||
break;
|
||||
case 5:
|
||||
color = "#E9AB44";
|
||||
break;
|
||||
case 6:
|
||||
color = "#FEE435";
|
||||
break;
|
||||
case 7:
|
||||
color = "#99C286";
|
||||
break;
|
||||
case 8:
|
||||
color = "#01A05E";
|
||||
break;
|
||||
case 9:
|
||||
color = "#4A8895";
|
||||
break;
|
||||
case 10:
|
||||
color = "#16669B";
|
||||
break;
|
||||
case 11:
|
||||
color = "#2F65A5";
|
||||
break;
|
||||
case 12:
|
||||
color = "#4E6A9C";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
private Point2D rotate(final Point2D a, final Point2D center, final double angle) {
|
||||
final double resultX = center.getX() + (a.getX() - center.getX()) * Math.cos(angle) - (a.getY() - center.getY()) * Math
|
||||
.sin(angle);
|
||||
final double resultY = center.getY() + (a.getX() - center.getX()) * Math.sin(angle) + (a.getY() - center.getY()) * Math
|
||||
.cos(angle);
|
||||
return new Point2D(resultX, resultY);
|
||||
}
|
||||
|
||||
private Point2D makeControlPoint(final double endX, final double endY, final Circle circle, final int numSegments, int direction) {
|
||||
final double controlPointDistance = (4.0 / 3.0) * Math.tan(Math.PI / (2 * numSegments)) * circle.getRadius();
|
||||
final Point2D center = new Point2D(circle.getCenterX(), circle.getCenterY());
|
||||
final Point2D end = new Point2D(endX, endY);
|
||||
Point2D perp = rotate(center, end, direction * Math.PI / 2.);
|
||||
Point2D diff = perp.subtract(end);
|
||||
diff = diff.normalize();
|
||||
diff = scale(diff, controlPointDistance);
|
||||
return end.add(diff);
|
||||
}
|
||||
|
||||
private Point2D scale(final Point2D a, final double scale) {
|
||||
return new Point2D(a.getX() * scale, a.getY() * scale);
|
||||
}
|
||||
|
||||
final class RecentColorPath extends Path {
|
||||
PathClickTransition transition;
|
||||
|
||||
RecentColorPath(final PathElement... elements) {
|
||||
super(elements);
|
||||
this.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
this.setStrokeWidth(0);
|
||||
this.setStrokeType(StrokeType.CENTERED);
|
||||
this.setCache(true);
|
||||
JFXDepthManager.setDepth(this, 2);
|
||||
this.transition = new PathClickTransition(this);
|
||||
}
|
||||
|
||||
void playTransition(final double rate) {
|
||||
transition.setRate(rate);
|
||||
transition.play();
|
||||
}
|
||||
}
|
||||
|
||||
private final class PathClickTransition extends CachedTransition {
|
||||
PathClickTransition(final Path path) {
|
||||
super(JFXCustomColorPicker.this, new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),
|
||||
JFXDepthManager.getShadowAt(2).radiusProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),
|
||||
JFXDepthManager.getShadowAt(2).spreadProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),
|
||||
JFXDepthManager.getShadowAt(2).offsetXProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),
|
||||
JFXDepthManager.getShadowAt(2).offsetYProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(path.strokeWidthProperty(), 0, EASE_BOTH)
|
||||
),
|
||||
new KeyFrame(Duration.millis(1000),
|
||||
new KeyValue(((DropShadow) path.getEffect()).radiusProperty(),
|
||||
JFXDepthManager.getShadowAt(5).radiusProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(((DropShadow) path.getEffect()).spreadProperty(),
|
||||
JFXDepthManager.getShadowAt(5).spreadProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(((DropShadow) path.getEffect()).offsetXProperty(),
|
||||
JFXDepthManager.getShadowAt(5).offsetXProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(((DropShadow) path.getEffect()).offsetYProperty(),
|
||||
JFXDepthManager.getShadowAt(5).offsetYProperty().get(),
|
||||
EASE_BOTH),
|
||||
new KeyValue(path.strokeWidthProperty(), 2, EASE_BOTH)
|
||||
)
|
||||
)
|
||||
);
|
||||
// reduce the number to increase the shifting , increase number to reduce shifting
|
||||
setCycleDuration(Duration.millis(120));
|
||||
setDelay(Duration.seconds(0));
|
||||
setAutoReverse(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import com.jfoenix.transitions.JFXFillTransition;
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.*;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen
|
||||
*/
|
||||
public class JFXCustomColorPickerDialog extends StackPane {
|
||||
private final Stage dialog = new Stage();
|
||||
// used for concurrency control and preventing FX-thread over use
|
||||
private final AtomicInteger concurrencyController = new AtomicInteger(-1);
|
||||
|
||||
private final ObjectProperty<Color> currentColorProperty = new SimpleObjectProperty<>(Color.WHITE);
|
||||
private final ObjectProperty<Color> customColorProperty = new SimpleObjectProperty<>(Color.TRANSPARENT);
|
||||
private Runnable onSave;
|
||||
|
||||
private final Scene customScene;
|
||||
private final JFXCustomColorPicker curvedColorPicker;
|
||||
private ParallelTransition paraTransition;
|
||||
private final JFXDecorator pickerDecorator;
|
||||
private boolean systemChange = false;
|
||||
private boolean userChange = false;
|
||||
private boolean initOnce = true;
|
||||
private final Runnable initRun;
|
||||
|
||||
public JFXCustomColorPickerDialog(Window owner) {
|
||||
getStyleClass().add("custom-color-dialog");
|
||||
if (owner != null) {
|
||||
dialog.initOwner(owner);
|
||||
}
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.initStyle(StageStyle.TRANSPARENT);
|
||||
dialog.setResizable(false);
|
||||
|
||||
// create JFX Decorator
|
||||
pickerDecorator = new JFXDecorator(dialog, this, false, false, false);
|
||||
pickerDecorator.setOnCloseButtonAction(this::updateColor);
|
||||
pickerDecorator.setPickOnBounds(false);
|
||||
customScene = new Scene(pickerDecorator, Color.TRANSPARENT);
|
||||
StyleSheets.init(customScene);
|
||||
curvedColorPicker = new JFXCustomColorPicker();
|
||||
|
||||
StackPane pane = new StackPane(curvedColorPicker);
|
||||
pane.setPadding(new Insets(18));
|
||||
|
||||
VBox container = new VBox();
|
||||
container.getChildren().add(pane);
|
||||
|
||||
JFXTabPane tabs = new JFXTabPane();
|
||||
|
||||
JFXTextField rgbField = new JFXTextField();
|
||||
JFXTextField hsbField = new JFXTextField();
|
||||
JFXTextField hexField = new JFXTextField();
|
||||
|
||||
rgbField.getStyleClass().add("custom-color-field");
|
||||
rgbField.setPromptText("RGB Color");
|
||||
rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
|
||||
|
||||
hsbField.getStyleClass().add("custom-color-field");
|
||||
hsbField.setPromptText("HSB Color");
|
||||
hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
|
||||
|
||||
hexField.getStyleClass().add("custom-color-field");
|
||||
hexField.setPromptText("#HEX Color");
|
||||
hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
|
||||
|
||||
StackPane tabContent = new StackPane();
|
||||
tabContent.getChildren().add(rgbField);
|
||||
tabContent.setMinHeight(100);
|
||||
|
||||
Tab rgbTab = new Tab("RGB");
|
||||
rgbTab.setContent(tabContent);
|
||||
Tab hsbTab = new Tab("HSB");
|
||||
hsbTab.setContent(hsbField);
|
||||
Tab hexTab = new Tab("HEX");
|
||||
hexTab.setContent(hexField);
|
||||
|
||||
tabs.getTabs().add(hexTab);
|
||||
tabs.getTabs().add(rgbTab);
|
||||
tabs.getTabs().add(hsbTab);
|
||||
|
||||
curvedColorPicker.selectedPath.addListener((o, oldVal, newVal) -> {
|
||||
if (paraTransition != null) {
|
||||
paraTransition.stop();
|
||||
}
|
||||
Region tabsHeader = (Region) tabs.lookup(".tab-header-background");
|
||||
pane.backgroundProperty().unbind();
|
||||
tabsHeader.backgroundProperty().unbind();
|
||||
JFXFillTransition fillTransition = new JFXFillTransition(Duration.millis(240),
|
||||
pane,
|
||||
(Color) oldVal.getFill(),
|
||||
(Color) newVal.getFill());
|
||||
JFXFillTransition tabsFillTransition = new JFXFillTransition(Duration.millis(240),
|
||||
tabsHeader,
|
||||
(Color) oldVal.getFill(),
|
||||
(Color) newVal.getFill());
|
||||
paraTransition = new ParallelTransition(fillTransition, tabsFillTransition);
|
||||
paraTransition.setOnFinished((finish) -> {
|
||||
tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
}, newVal.fillProperty()));
|
||||
pane.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return new Background(new BackgroundFill(newVal.getFill(), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
}, newVal.fillProperty()));
|
||||
});
|
||||
paraTransition.play();
|
||||
});
|
||||
|
||||
initRun = () -> {
|
||||
// change tabs labels font color according to the selected color
|
||||
pane.backgroundProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (concurrencyController.getAndSet(1) == -1) {
|
||||
Color fontColor = ((Color) newVal.getFills().get(0).getFill()).grayscale()
|
||||
.getRed() > 0.5 ? Color.valueOf(
|
||||
"rgba(40, 40, 40, 0.87)") : Color.valueOf("rgba(255, 255, 255, 0.87)");
|
||||
// for (Node tabNode : tabs.lookupAll(".tab")) {
|
||||
// for (Node node : tabNode.lookupAll(".tab-label")) {
|
||||
// ((Label) node).setTextFill(fontColor);
|
||||
// }
|
||||
// for (Node node : tabNode.lookupAll(".jfx-rippler")) {
|
||||
// ((JFXRippler) node).setRipplerFill(fontColor);
|
||||
// }
|
||||
// }
|
||||
// ((Pane) tabs.lookup(".tab-selected-line")).setBackground(new Background(new BackgroundFill(fontColor, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
pickerDecorator.lookupAll(".jfx-decorator-button").forEach(button -> {
|
||||
((JFXButton) button).setRipplerFill(fontColor);
|
||||
((SVGGlyph) ((JFXButton) button).getGraphic()).setFill(fontColor);
|
||||
});
|
||||
|
||||
Color newColor = (Color) newVal.getFills().get(0).getFill();
|
||||
String hex = String.format("#%02X%02X%02X",
|
||||
(int) (newColor.getRed() * 255),
|
||||
(int) (newColor.getGreen() * 255),
|
||||
(int) (newColor.getBlue() * 255));
|
||||
String rgb = String.format("rgba(%d, %d, %d, 1)",
|
||||
(int) (newColor.getRed() * 255),
|
||||
(int) (newColor.getGreen() * 255),
|
||||
(int) (newColor.getBlue() * 255));
|
||||
String hsb = String.format("hsl(%d, %d%%, %d%%)",
|
||||
(int) (newColor.getHue()),
|
||||
(int) (newColor.getSaturation() * 100),
|
||||
(int) (newColor.getBrightness() * 100));
|
||||
|
||||
if (!userChange) {
|
||||
systemChange = true;
|
||||
rgbField.setText(rgb);
|
||||
hsbField.setText(hsb);
|
||||
hexField.setText(hex);
|
||||
systemChange = false;
|
||||
}
|
||||
concurrencyController.getAndSet(-1);
|
||||
}
|
||||
});
|
||||
|
||||
// initial selected colors
|
||||
Platform.runLater(() -> {
|
||||
pane.setBackground(new Background(new BackgroundFill(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)));
|
||||
((Region) tabs.lookup(".tab-header-background")).setBackground(new Background(new BackgroundFill(
|
||||
curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY)));
|
||||
Region tabsHeader = (Region) tabs.lookup(".tab-header-background");
|
||||
pane.backgroundProperty().unbind();
|
||||
tabsHeader.backgroundProperty().unbind();
|
||||
tabsHeader.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY));
|
||||
}, curvedColorPicker.selectedPath.get().fillProperty()));
|
||||
pane.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return new Background(new BackgroundFill(curvedColorPicker.selectedPath.get().getFill(),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY));
|
||||
}, curvedColorPicker.selectedPath.get().fillProperty()));
|
||||
|
||||
// bind text field line color
|
||||
rgbField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return pane.getBackground().getFills().get(0).getFill();
|
||||
}, pane.backgroundProperty()));
|
||||
hsbField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return pane.getBackground().getFills().get(0).getFill();
|
||||
}, pane.backgroundProperty()));
|
||||
hexField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return pane.getBackground().getFills().get(0).getFill();
|
||||
}, pane.backgroundProperty()));
|
||||
|
||||
|
||||
((Pane) pickerDecorator.lookup(".jfx-decorator-buttons-container")).backgroundProperty()
|
||||
.bind(Bindings.createObjectBinding(() -> {
|
||||
return new Background(new BackgroundFill(
|
||||
pane.getBackground()
|
||||
.getFills()
|
||||
.get(0)
|
||||
.getFill(),
|
||||
CornerRadii.EMPTY,
|
||||
Insets.EMPTY));
|
||||
}, pane.backgroundProperty()));
|
||||
|
||||
((Pane) pickerDecorator.lookup(".jfx-decorator-content-container")).borderProperty()
|
||||
.bind(Bindings.createObjectBinding(() -> {
|
||||
return new Border(new BorderStroke(
|
||||
pane.getBackground()
|
||||
.getFills()
|
||||
.get(0)
|
||||
.getFill(),
|
||||
BorderStrokeStyle.SOLID,
|
||||
CornerRadii.EMPTY,
|
||||
new BorderWidths(0,
|
||||
4,
|
||||
4,
|
||||
4)));
|
||||
}, pane.backgroundProperty()));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
container.getChildren().add(tabs);
|
||||
|
||||
this.getChildren().add(container);
|
||||
this.setPadding(new Insets(0));
|
||||
|
||||
dialog.setScene(customScene);
|
||||
final EventHandler<KeyEvent> keyEventListener = key -> {
|
||||
switch (key.getCode()) {
|
||||
case ESCAPE:
|
||||
close();
|
||||
break;
|
||||
case ENTER:
|
||||
updateColor();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
dialog.addEventHandler(KeyEvent.ANY, keyEventListener);
|
||||
}
|
||||
|
||||
private void updateColor() {
|
||||
close();
|
||||
this.customColorProperty.set(curvedColorPicker.getColor(curvedColorPicker.getSelectedIndex()));
|
||||
this.onSave.run();
|
||||
}
|
||||
|
||||
private void updateColorFromUserInput(String colorWebString) {
|
||||
if (!systemChange) {
|
||||
userChange = true;
|
||||
try {
|
||||
curvedColorPicker.setColor(Color.valueOf(colorWebString));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// if color is not valid then do nothing
|
||||
}
|
||||
userChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
dialog.setScene(null);
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
public void setCurrentColor(Color currentColor) {
|
||||
this.currentColorProperty.set(currentColor);
|
||||
}
|
||||
|
||||
Color getCurrentColor() {
|
||||
return currentColorProperty.get();
|
||||
}
|
||||
|
||||
ObjectProperty<Color> customColorProperty() {
|
||||
return customColorProperty;
|
||||
}
|
||||
|
||||
void setCustomColor(Color color) {
|
||||
customColorProperty.set(color);
|
||||
}
|
||||
|
||||
Color getCustomColor() {
|
||||
return customColorProperty.get();
|
||||
}
|
||||
|
||||
public Runnable getOnSave() {
|
||||
return onSave;
|
||||
}
|
||||
|
||||
public void setOnSave(Runnable onSave) {
|
||||
this.onSave = onSave;
|
||||
}
|
||||
|
||||
public void setOnHidden(EventHandler<WindowEvent> onHidden) {
|
||||
dialog.setOnHidden(onHidden);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
dialog.setOpacity(0);
|
||||
// pickerDecorator.setOpacity(0);
|
||||
if (dialog.getOwner() != null) {
|
||||
dialog.widthProperty().addListener(positionAdjuster);
|
||||
dialog.heightProperty().addListener(positionAdjuster);
|
||||
positionAdjuster.invalidated(null);
|
||||
}
|
||||
if (dialog.getScene() == null) {
|
||||
dialog.setScene(customScene);
|
||||
}
|
||||
curvedColorPicker.preAnimate();
|
||||
dialog.show();
|
||||
if (initOnce) {
|
||||
initRun.run();
|
||||
initOnce = false;
|
||||
}
|
||||
|
||||
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(120),
|
||||
new KeyValue(dialog.opacityProperty(),
|
||||
1,
|
||||
Interpolator.EASE_BOTH)));
|
||||
timeline.setOnFinished((finish) -> curvedColorPicker.animate());
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
|
||||
// add option to show color picker using JFX Dialog
|
||||
private InvalidationListener positionAdjuster = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable ignored) {
|
||||
if (Double.isNaN(dialog.getWidth()) || Double.isNaN(dialog.getHeight())) {
|
||||
return;
|
||||
}
|
||||
dialog.widthProperty().removeListener(positionAdjuster);
|
||||
dialog.heightProperty().removeListener(positionAdjuster);
|
||||
fixPosition();
|
||||
}
|
||||
};
|
||||
|
||||
private void fixPosition() {
|
||||
Window w = dialog.getOwner();
|
||||
Screen s = com.sun.javafx.util.Utils.getScreen(w);
|
||||
Rectangle2D sb = s.getBounds();
|
||||
double xR = w.getX() + w.getWidth();
|
||||
double xL = w.getX() - dialog.getWidth();
|
||||
double x;
|
||||
double y;
|
||||
if (sb.getMaxX() >= xR + dialog.getWidth()) {
|
||||
x = xR;
|
||||
} else if (sb.getMinX() <= xL) {
|
||||
x = xL;
|
||||
} else {
|
||||
x = Math.max(sb.getMinX(), sb.getMaxX() - dialog.getWidth());
|
||||
}
|
||||
y = Math.max(sb.getMinY(), Math.min(sb.getMaxY() - dialog.getHeight(), w.getY()));
|
||||
dialog.setX(x);
|
||||
dialog.setY(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
if (dialog.getMinWidth() > 0 && dialog.getMinHeight() > 0) {
|
||||
return;
|
||||
}
|
||||
double minWidth = Math.max(0, computeMinWidth(getHeight()) + (dialog.getWidth() - customScene.getWidth()));
|
||||
double minHeight = Math.max(0, computeMinHeight(getWidth()) + (dialog.getHeight() - customScene.getHeight()));
|
||||
dialog.setMinWidth(minWidth);
|
||||
dialog.setMinHeight(minHeight);
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.adapters.ReflectionHelper;
|
||||
import com.jfoenix.controls.behavior.JFXGenericPickerBehavior;
|
||||
import com.sun.javafx.binding.ExpressionHelper;
|
||||
import com.sun.javafx.event.EventHandlerManager;
|
||||
import com.sun.javafx.stage.WindowEventDispatcher;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanPropertyBase;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.control.ComboBoxBase;
|
||||
import javafx.scene.control.PopupControl;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.skin.ComboBoxBaseSkin;
|
||||
import javafx.scene.control.skin.ComboBoxPopupControl;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public abstract class JFXGenericPickerSkin<T> extends ComboBoxPopupControl<T> {
|
||||
|
||||
private final EventHandler<MouseEvent> mouseEnteredEventHandler;
|
||||
private final EventHandler<MouseEvent> mousePressedEventHandler;
|
||||
private final EventHandler<MouseEvent> mouseReleasedEventHandler;
|
||||
private final EventHandler<MouseEvent> mouseExitedEventHandler;
|
||||
|
||||
protected JFXGenericPickerBehavior<T> behavior;
|
||||
|
||||
// reference of the arrow button node in getChildren (not the actual field)
|
||||
protected Pane arrowButton;
|
||||
protected PopupControl popup;
|
||||
|
||||
public JFXGenericPickerSkin(ComboBoxBase<T> comboBoxBase) {
|
||||
super(comboBoxBase);
|
||||
behavior = new JFXGenericPickerBehavior<T>(comboBoxBase);
|
||||
|
||||
removeParentFakeFocusListener(comboBoxBase);
|
||||
|
||||
this.mouseEnteredEventHandler = event -> behavior.mouseEntered(event);
|
||||
this.mousePressedEventHandler = event -> {
|
||||
behavior.mousePressed(event);
|
||||
event.consume();
|
||||
};
|
||||
this.mouseReleasedEventHandler = event -> {
|
||||
behavior.mouseReleased(event);
|
||||
event.consume();
|
||||
};
|
||||
this.mouseExitedEventHandler = event -> behavior.mouseExited(event);
|
||||
|
||||
arrowButton = (Pane) getChildren().get(0);
|
||||
|
||||
parentArrowEventHandlerTerminator.accept("mouseEnteredEventHandler", MouseEvent.MOUSE_ENTERED);
|
||||
parentArrowEventHandlerTerminator.accept("mousePressedEventHandler", MouseEvent.MOUSE_PRESSED);
|
||||
parentArrowEventHandlerTerminator.accept("mouseReleasedEventHandler", MouseEvent.MOUSE_RELEASED);
|
||||
parentArrowEventHandlerTerminator.accept("mouseExitedEventHandler", MouseEvent.MOUSE_EXITED);
|
||||
this.unregisterChangeListeners(comboBoxBase.editableProperty());
|
||||
|
||||
updateArrowButtonListeners();
|
||||
registerChangeListener(comboBoxBase.editableProperty(), obs -> {
|
||||
updateArrowButtonListeners();
|
||||
reflectUpdateDisplayArea();
|
||||
});
|
||||
|
||||
removeParentPopupHandlers();
|
||||
|
||||
popup = ReflectionHelper.getFieldContent(ComboBoxPopupControl.class, this, "popup");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (this.behavior != null) {
|
||||
this.behavior.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Reflections internal API *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
private final BiConsumer<String, EventType<?>> parentArrowEventHandlerTerminator = (handlerName, eventType) -> {
|
||||
try {
|
||||
EventHandler handler = ReflectionHelper.getFieldContent(ComboBoxBaseSkin.class, this, handlerName);
|
||||
arrowButton.removeEventHandler(eventType, handler);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
private static final VarHandle READ_ONLY_BOOLEAN_PROPERTY_BASE_HELPER =
|
||||
findVarHandle(ReadOnlyBooleanPropertyBase.class, "helper", ExpressionHelper.class);
|
||||
|
||||
/// @author Glavo
|
||||
private static VarHandle findVarHandle(Class<?> targetClass, String fieldName, Class<?> type) {
|
||||
try {
|
||||
return MethodHandles.privateLookupIn(targetClass, MethodHandles.lookup()).findVarHandle(targetClass, fieldName, type);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
LOG.warning("Failed to get var handle", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeParentFakeFocusListener(ComboBoxBase<T> comboBoxBase) {
|
||||
// handle FakeFocusField cast exception
|
||||
try {
|
||||
final ReadOnlyBooleanProperty focusedProperty = comboBoxBase.focusedProperty();
|
||||
//noinspection unchecked
|
||||
ExpressionHelper<Boolean> value = (ExpressionHelper<Boolean>) READ_ONLY_BOOLEAN_PROPERTY_BASE_HELPER.get(focusedProperty);
|
||||
ChangeListener<? super Boolean>[] changeListeners = ReflectionHelper.getFieldContent(value.getClass(), value, "changeListeners");
|
||||
// remove parent focus listener to prevent editor class cast exception
|
||||
for (int i = changeListeners.length - 1; i > 0; i--) {
|
||||
if (changeListeners[i] != null && changeListeners[i].getClass().getName().contains("ComboBoxPopupControl")) {
|
||||
focusedProperty.removeListener(changeListeners[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeParentPopupHandlers() {
|
||||
try {
|
||||
PopupControl popup = ReflectionHelper.invoke(ComboBoxPopupControl.class, this, "getPopup");
|
||||
popup.setOnAutoHide(event -> behavior.onAutoHide(popup));
|
||||
WindowEventDispatcher dispatcher = ReflectionHelper.invoke(Window.class, popup, "getInternalEventDispatcher");
|
||||
Map compositeEventHandlersMap = ReflectionHelper.getFieldContent(EventHandlerManager.class, dispatcher.getEventHandlerManager(), "eventHandlerMap");
|
||||
compositeEventHandlersMap.remove(MouseEvent.MOUSE_CLICKED);
|
||||
// CompositeEventHandler compositeEventHandler = (CompositeEventHandler) compositeEventHandlersMap.get(MouseEvent.MOUSE_CLICKED);
|
||||
// Object obj = fieldConsumer.apply(()->CompositeEventHandler.class.getDeclaredField("firstRecord"),compositeEventHandler);
|
||||
// EventHandler handler = (EventHandler) fieldConsumer.apply(() -> obj.getClass().getDeclaredField("eventHandler"), obj);
|
||||
// popup.removeEventHandler(MouseEvent.MOUSE_CLICKED, handler);
|
||||
popup.addEventHandler(MouseEvent.MOUSE_CLICKED, click -> behavior.onAutoHide(popup));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArrowButtonListeners() {
|
||||
if (getSkinnable().isEditable()) {
|
||||
arrowButton.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);
|
||||
arrowButton.addEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);
|
||||
arrowButton.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);
|
||||
arrowButton.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);
|
||||
} else {
|
||||
arrowButton.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);
|
||||
arrowButton.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);
|
||||
arrowButton.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);
|
||||
arrowButton.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Reflections internal API for ComboBoxPopupControl *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
private final HashMap<String, Method> parentCachedMethods = new HashMap<>();
|
||||
|
||||
Function<String, Method> methodSupplier = name -> {
|
||||
if (!parentCachedMethods.containsKey(name)) {
|
||||
try {
|
||||
Method method = ComboBoxPopupControl.class.getDeclaredMethod(name);
|
||||
method.setAccessible(true);
|
||||
parentCachedMethods.put(name, method);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return parentCachedMethods.get(name);
|
||||
};
|
||||
|
||||
final Consumer<Method> methodInvoker = method -> {
|
||||
try {
|
||||
method.invoke(this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
final Function<Method, Object> methodReturnInvoker = method -> {
|
||||
try {
|
||||
return method.invoke(this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
protected void reflectUpdateDisplayArea() {
|
||||
methodInvoker.accept(methodSupplier.apply("updateDisplayArea"));
|
||||
}
|
||||
|
||||
protected void reflectSetTextFromTextFieldIntoComboBoxValue() {
|
||||
methodInvoker.accept(methodSupplier.apply("setTextFromTextFieldIntoComboBoxValue"));
|
||||
}
|
||||
|
||||
protected TextField reflectGetEditableInputNode() {
|
||||
return (TextField) methodReturnInvoker.apply(methodSupplier.apply("getEditableInputNode"));
|
||||
}
|
||||
|
||||
protected void reflectUpdateDisplayNode() {
|
||||
methodInvoker.accept(methodSupplier.apply("updateDisplayNode"));
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,7 @@ public class JFXListViewSkin<T> extends ListViewSkin<T> {
|
||||
flow = (VirtualFlow<ListCell<T>>) getChildren().get(0);
|
||||
JFXDepthManager.setDepth(flow, listView.depthProperty().get());
|
||||
listView.depthProperty().addListener((o, oldVal, newVal) -> JFXDepthManager.setDepth(flow, newVal));
|
||||
|
||||
if (!Boolean.TRUE.equals(listView.getProperties().get("no-smooth-scrolling"))) {
|
||||
FXUtils.smoothScrolling(flow);
|
||||
}
|
||||
FXUtils.smoothScrolling(flow);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by FernFlower decompiler)
|
||||
//
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXRippler;
|
||||
import com.jfoenix.controls.JFXRippler.RipplerMask;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.skin.RadioButtonSkin;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
|
||||
public class JFXRadioButtonSkin extends RadioButtonSkin {
|
||||
private static final double PADDING = 15.0;
|
||||
|
||||
private boolean invalid = true;
|
||||
private final JFXRippler rippler;
|
||||
private final Circle radio;
|
||||
private final Circle dot;
|
||||
private Timeline timeline;
|
||||
private final AnchorPane container = new AnchorPane();
|
||||
private final double labelOffset = -10.0;
|
||||
|
||||
public JFXRadioButtonSkin(JFXRadioButton control) {
|
||||
super(control);
|
||||
double radioRadius = 7.0;
|
||||
this.radio = new Circle(radioRadius);
|
||||
this.radio.getStyleClass().setAll("radio");
|
||||
this.radio.setStrokeWidth(2.0);
|
||||
this.radio.setFill(Color.TRANSPARENT);
|
||||
|
||||
this.dot = new Circle(4);
|
||||
this.dot.getStyleClass().setAll("dot");
|
||||
this.dot.fillProperty().bind(control.selectedColorProperty());
|
||||
this.dot.setScaleX(0.0);
|
||||
this.dot.setScaleY(0.0);
|
||||
|
||||
StackPane boxContainer = new StackPane();
|
||||
boxContainer.getChildren().addAll(this.radio, this.dot);
|
||||
boxContainer.setPadding(new Insets(PADDING));
|
||||
this.rippler = new JFXRippler(boxContainer, RipplerMask.CIRCLE);
|
||||
this.container.getChildren().add(this.rippler);
|
||||
AnchorPane.setRightAnchor(this.rippler, this.labelOffset);
|
||||
|
||||
this.updateChildren();
|
||||
control.focusedProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
if (!this.getSkinnable().isPressed()) {
|
||||
this.rippler.showOverlay();
|
||||
}
|
||||
} else {
|
||||
this.rippler.hideOverlay();
|
||||
}
|
||||
|
||||
});
|
||||
control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay());
|
||||
this.registerChangeListener(control.selectedColorProperty(), ignored -> updateColors());
|
||||
this.registerChangeListener(control.unSelectedColorProperty(), ignored -> updateColors());
|
||||
this.registerChangeListener(control.selectedProperty(), ignored -> {
|
||||
updateColors();
|
||||
this.playAnimation();
|
||||
});
|
||||
}
|
||||
|
||||
protected void updateChildren() {
|
||||
super.updateChildren();
|
||||
if (this.radio != null) {
|
||||
this.removeRadio();
|
||||
this.getChildren().add(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
protected void layoutChildren(double x, double y, double w, double h) {
|
||||
RadioButton radioButton = this.getSkinnable();
|
||||
double contWidth = this.snapSizeX(this.container.prefWidth(-1.0)) + (double) (this.invalid ? 2 : 0);
|
||||
double contHeight = this.snapSizeY(this.container.prefHeight(-1.0)) + (double) (this.invalid ? 2 : 0);
|
||||
double computeWidth = Math.min(radioButton.prefWidth(-1.0), radioButton.minWidth(-1.0)) + this.labelOffset + 2.0 * this.PADDING;
|
||||
double labelWidth = Math.min(computeWidth - contWidth, w - this.snapSizeX(contWidth)) + this.labelOffset + 2.0 * PADDING;
|
||||
double labelHeight = Math.min(radioButton.prefHeight(labelWidth), h);
|
||||
double maxHeight = Math.max(contHeight, labelHeight);
|
||||
double xOffset = computeXOffset(w, labelWidth + contWidth, radioButton.getAlignment().getHpos()) + x;
|
||||
double yOffset = computeYOffset(h, maxHeight, radioButton.getAlignment().getVpos()) + x;
|
||||
if (this.invalid) {
|
||||
this.initializeComponents();
|
||||
this.invalid = false;
|
||||
}
|
||||
|
||||
this.layoutLabelInArea(xOffset + contWidth, yOffset, labelWidth, maxHeight, radioButton.getAlignment());
|
||||
((Text) this.getChildren().get(this.getChildren().get(0) instanceof Text ? 0 : 1)).textProperty().set(this.getSkinnable().textProperty().get());
|
||||
this.container.resize(this.snapSizeX(contWidth), this.snapSizeY(contHeight));
|
||||
this.positionInArea(this.container, xOffset, yOffset, contWidth, maxHeight, 0.0, radioButton.getAlignment().getHpos(), radioButton.getAlignment().getVpos());
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
this.updateColors();
|
||||
this.playAnimation();
|
||||
}
|
||||
|
||||
private void playAnimation() {
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
if (this.timeline == null) {
|
||||
this.timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(Duration.millis(200.0),
|
||||
new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH))
|
||||
);
|
||||
} else {
|
||||
this.timeline.stop();
|
||||
}
|
||||
this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0);
|
||||
this.timeline.play();
|
||||
} else {
|
||||
double endScale = this.getSkinnable().isSelected() ? 1.0 : 0.0;
|
||||
this.dot.setScaleX(endScale);
|
||||
this.dot.setScaleY(endScale);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeRadio() {
|
||||
this.getChildren().removeIf(node -> "radio".equals(node.getStyleClass().get(0)));
|
||||
}
|
||||
|
||||
private void updateColors() {
|
||||
var control = (JFXRadioButton) getSkinnable();
|
||||
boolean isSelected = control.isSelected();
|
||||
Color unSelectedColor = control.getUnSelectedColor();
|
||||
Color selectedColor = control.getSelectedColor();
|
||||
rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
|
||||
radio.setStroke(isSelected ? selectedColor : unSelectedColor);
|
||||
}
|
||||
|
||||
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.minWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;
|
||||
}
|
||||
|
||||
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.prefWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;
|
||||
}
|
||||
|
||||
static double computeXOffset(double width, double contentWidth, HPos hpos) {
|
||||
return switch (hpos) {
|
||||
case LEFT -> 0.0;
|
||||
case CENTER -> (width - contentWidth) / 2.0;
|
||||
case RIGHT -> width - contentWidth;
|
||||
};
|
||||
}
|
||||
|
||||
static double computeYOffset(double height, double contentHeight, VPos vpos) {
|
||||
return switch (vpos) {
|
||||
case TOP, BASELINE -> 0.0;
|
||||
case CENTER -> (height - contentHeight) / 2.0;
|
||||
case BOTTOM -> height - contentHeight;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXRippler;
|
||||
import com.jfoenix.controls.JFXRippler.RipplerMask;
|
||||
import com.jfoenix.controls.JFXRippler.RipplerPos;
|
||||
import com.jfoenix.controls.JFXToggleButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import com.jfoenix.transitions.JFXAnimationTimer;
|
||||
import com.jfoenix.transitions.JFXKeyFrame;
|
||||
import com.jfoenix.transitions.JFXKeyValue;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.skin.ToggleButtonSkin;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* <h1>Material Design ToggleButton Skin</h1>
|
||||
*
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2016-03-09
|
||||
*/
|
||||
public class JFXToggleButtonSkin extends ToggleButtonSkin {
|
||||
|
||||
|
||||
private Runnable releaseManualRippler = null;
|
||||
|
||||
private JFXAnimationTimer timer;
|
||||
private final Circle circle;
|
||||
private final Line line;
|
||||
|
||||
public JFXToggleButtonSkin(JFXToggleButton toggleButton) {
|
||||
super(toggleButton);
|
||||
|
||||
double circleRadius = toggleButton.getSize();
|
||||
|
||||
line = new Line();
|
||||
line.setStroke(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());
|
||||
line.setStartX(0);
|
||||
line.setStartY(0);
|
||||
line.setEndX(circleRadius * 2 + 2);
|
||||
line.setEndY(0);
|
||||
line.setStrokeWidth(circleRadius * 1.5);
|
||||
line.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
line.setSmooth(true);
|
||||
|
||||
circle = new Circle();
|
||||
circle.setFill(getSkinnable().isSelected() ? toggleButton.getToggleColor() : toggleButton.getUnToggleColor());
|
||||
circle.setCenterX(-circleRadius);
|
||||
circle.setCenterY(0);
|
||||
circle.setRadius(circleRadius);
|
||||
circle.setSmooth(true);
|
||||
JFXDepthManager.setDepth(circle, 1);
|
||||
|
||||
StackPane circlePane = new StackPane();
|
||||
circlePane.getChildren().add(circle);
|
||||
circlePane.setPadding(new Insets(circleRadius * 1.5));
|
||||
|
||||
JFXRippler rippler = new JFXRippler(circlePane, RipplerMask.CIRCLE, RipplerPos.BACK);
|
||||
rippler.setRipplerFill(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());
|
||||
rippler.setTranslateX(computeTranslation(circleRadius, line));
|
||||
|
||||
final StackPane main = new StackPane();
|
||||
main.getChildren().setAll(line, rippler);
|
||||
main.setCursor(Cursor.HAND);
|
||||
|
||||
// show focus traversal effect
|
||||
getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
releaseManualRippler = rippler.createManualRipple();
|
||||
} else if (releaseManualRippler != null) {
|
||||
releaseManualRippler.run();
|
||||
}
|
||||
});
|
||||
toggleButton.focusedProperty().addListener((o, oldVal, newVal) -> {
|
||||
if (!toggleButton.isDisableVisualFocus()) {
|
||||
if (newVal) {
|
||||
if (!getSkinnable().isPressed()) {
|
||||
rippler.setOverlayVisible(true);
|
||||
}
|
||||
} else {
|
||||
rippler.setOverlayVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
toggleButton.pressedProperty().addListener(observable -> rippler.setOverlayVisible(false));
|
||||
|
||||
// add change listener to selected property
|
||||
getSkinnable().selectedProperty().addListener(observable -> {
|
||||
rippler.setRipplerFill(toggleButton.isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor());
|
||||
if (!toggleButton.isDisableAnimation()) {
|
||||
timer.reverseAndContinue();
|
||||
} else {
|
||||
rippler.setTranslateX(computeTranslation(circleRadius, line));
|
||||
}
|
||||
});
|
||||
|
||||
getSkinnable().setGraphic(main);
|
||||
|
||||
timer = new JFXAnimationTimer(
|
||||
new JFXKeyFrame(Duration.millis(100),
|
||||
JFXKeyValue.builder()
|
||||
.setTarget(rippler.translateXProperty())
|
||||
.setEndValueSupplier(() -> computeTranslation(circleRadius, line))
|
||||
.setInterpolator(Interpolator.EASE_BOTH)
|
||||
.setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())
|
||||
.build(),
|
||||
|
||||
JFXKeyValue.builder()
|
||||
.setTarget(line.strokeProperty())
|
||||
.setEndValueSupplier(() -> getSkinnable().isSelected() ?
|
||||
((JFXToggleButton) getSkinnable()).getToggleLineColor()
|
||||
: ((JFXToggleButton) getSkinnable()).getUnToggleLineColor())
|
||||
.setInterpolator(Interpolator.EASE_BOTH)
|
||||
.setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())
|
||||
.build(),
|
||||
|
||||
JFXKeyValue.builder()
|
||||
.setTarget(circle.fillProperty())
|
||||
.setEndValueSupplier(() -> getSkinnable().isSelected() ?
|
||||
((JFXToggleButton) getSkinnable()).getToggleColor()
|
||||
: ((JFXToggleButton) getSkinnable()).getUnToggleColor())
|
||||
.setInterpolator(Interpolator.EASE_BOTH)
|
||||
.setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation())
|
||||
.build()
|
||||
)
|
||||
);
|
||||
timer.setCacheNodes(circle, line);
|
||||
|
||||
registerChangeListener(toggleButton.toggleColorProperty(), observableValue -> {
|
||||
if (getSkinnable().isSelected()) {
|
||||
circle.setFill(((JFXToggleButton) getSkinnable()).getToggleColor());
|
||||
}
|
||||
});
|
||||
registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> {
|
||||
if (!getSkinnable().isSelected()) {
|
||||
circle.setFill(((JFXToggleButton) getSkinnable()).getUnToggleColor());
|
||||
}
|
||||
});
|
||||
registerChangeListener(toggleButton.toggleLineColorProperty(), observableValue -> {
|
||||
if (getSkinnable().isSelected()) {
|
||||
line.setStroke(((JFXToggleButton) getSkinnable()).getToggleLineColor());
|
||||
}
|
||||
});
|
||||
registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> {
|
||||
if (!getSkinnable().isSelected()) {
|
||||
line.setStroke(((JFXToggleButton) getSkinnable()).getUnToggleLineColor());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private double computeTranslation(double circleRadius, Line line) {
|
||||
return (getSkinnable().isSelected() ? 1 : -1) * ((line.getLayoutBounds().getWidth() / 2) - circleRadius + 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
timer.dispose();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.transitions;
|
||||
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class CacheMemento {
|
||||
private boolean cache;
|
||||
private boolean cacheShape;
|
||||
private boolean snapToPixel;
|
||||
private CacheHint cacheHint = CacheHint.DEFAULT;
|
||||
private final Node node;
|
||||
private final AtomicBoolean isCached = new AtomicBoolean(false);
|
||||
|
||||
public CacheMemento(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* this method will cache the node only if it wasn't cached before
|
||||
*/
|
||||
public void cache() {
|
||||
if (!isCached.getAndSet(true)) {
|
||||
this.cache = node.isCache();
|
||||
this.cacheHint = node.getCacheHint();
|
||||
node.setCache(true);
|
||||
node.setCacheHint(CacheHint.SPEED);
|
||||
if (node instanceof Region region) {
|
||||
this.cacheShape = region.isCacheShape();
|
||||
this.snapToPixel = region.isSnapToPixel();
|
||||
region.setCacheShape(true);
|
||||
region.setSnapToPixel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void restore() {
|
||||
if (isCached.getAndSet(false)) {
|
||||
node.setCache(cache);
|
||||
node.setCacheHint(cacheHint);
|
||||
if (node instanceof Region region) {
|
||||
region.setCacheShape(cacheShape);
|
||||
region.setSnapToPixel(snapToPixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.transitions;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.scene.Node;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Custom AnimationTimer that can be created the same way as a timeline,
|
||||
* however it doesn't behave the same yet. it only animates in one direction,
|
||||
* it doesn't support animation 0 -> 1 -> 0.5
|
||||
*
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2017-09-21
|
||||
*/
|
||||
|
||||
public class JFXAnimationTimer extends AnimationTimer {
|
||||
|
||||
private Set<AnimationHandler> animationHandlers = new HashSet<>();
|
||||
private long startTime = -1;
|
||||
private boolean running = false;
|
||||
private List<CacheMemento> caches = new ArrayList<>();
|
||||
private double totalElapsedMilliseconds;
|
||||
|
||||
public JFXAnimationTimer(JFXKeyFrame... keyFrames) {
|
||||
for (JFXKeyFrame keyFrame : keyFrames) {
|
||||
Duration duration = keyFrame.getDuration();
|
||||
final Set<JFXKeyValue<?>> keyValuesSet = keyFrame.getValues();
|
||||
if (!keyValuesSet.isEmpty()) {
|
||||
animationHandlers.add(new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final HashMap<JFXKeyFrame, AnimationHandler> mutableFrames = new HashMap<>();
|
||||
|
||||
public void addKeyFrame(JFXKeyFrame keyFrame) throws Exception {
|
||||
if (isRunning()) {
|
||||
throw new Exception("Can't update animation timer while running");
|
||||
}
|
||||
Duration duration = keyFrame.getDuration();
|
||||
final Set<JFXKeyValue<?>> keyValuesSet = keyFrame.getValues();
|
||||
if (!keyValuesSet.isEmpty()) {
|
||||
final AnimationHandler handler = new AnimationHandler(duration, keyFrame.getAnimateCondition(), keyFrame.getValues());
|
||||
animationHandlers.add(handler);
|
||||
mutableFrames.put(keyFrame, handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeKeyFrame(JFXKeyFrame keyFrame) throws Exception {
|
||||
if (isRunning()) {
|
||||
throw new Exception("Can't update animation timer while running");
|
||||
}
|
||||
AnimationHandler handler = mutableFrames.get(keyFrame);
|
||||
animationHandlers.remove(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
running = true;
|
||||
startTime = -1;
|
||||
for (AnimationHandler animationHandler : animationHandlers) {
|
||||
animationHandler.init();
|
||||
}
|
||||
for (CacheMemento cache : caches) {
|
||||
cache.cache();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
startTime = startTime == -1 ? now : startTime;
|
||||
totalElapsedMilliseconds = (now - startTime) / 1000000.0;
|
||||
boolean stop = true;
|
||||
for (AnimationHandler handler : animationHandlers) {
|
||||
handler.animate(totalElapsedMilliseconds);
|
||||
if (!handler.finished) {
|
||||
stop = false;
|
||||
}
|
||||
}
|
||||
if (stop) {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this method will pause the timer and reverse the animation if the timer already
|
||||
* started otherwise it will start the animation.
|
||||
*/
|
||||
public void reverseAndContinue() {
|
||||
if (isRunning()) {
|
||||
super.stop();
|
||||
for (AnimationHandler handler : animationHandlers) {
|
||||
handler.reverse(totalElapsedMilliseconds);
|
||||
}
|
||||
startTime = -1;
|
||||
super.start();
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
running = false;
|
||||
for (AnimationHandler handler : animationHandlers) {
|
||||
handler.clear();
|
||||
}
|
||||
for (CacheMemento cache : caches) {
|
||||
cache.restore();
|
||||
}
|
||||
if (onFinished != null) {
|
||||
onFinished.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void applyEndValues() {
|
||||
if (isRunning()) {
|
||||
super.stop();
|
||||
}
|
||||
for (AnimationHandler handler : animationHandlers) {
|
||||
handler.applyEndValues();
|
||||
}
|
||||
startTime = -1;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
private Runnable onFinished = null;
|
||||
|
||||
public void setOnFinished(Runnable onFinished) {
|
||||
this.onFinished = onFinished;
|
||||
}
|
||||
|
||||
public void setCacheNodes(Node... nodesToCache) {
|
||||
caches.clear();
|
||||
if (nodesToCache != null) {
|
||||
for (Node node : nodesToCache) {
|
||||
caches.add(new CacheMemento(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
caches.clear();
|
||||
for (AnimationHandler handler : animationHandlers) {
|
||||
handler.dispose();
|
||||
}
|
||||
animationHandlers.clear();
|
||||
}
|
||||
|
||||
static class AnimationHandler {
|
||||
private final double duration;
|
||||
private double currentDuration;
|
||||
private final Set<JFXKeyValue<?>> keyValues;
|
||||
private Supplier<Boolean> animationCondition = null;
|
||||
private boolean finished = false;
|
||||
|
||||
private final HashMap<WritableValue<?>, Object> initialValuesMap = new HashMap<>();
|
||||
private final HashMap<WritableValue<?>, Object> endValuesMap = new HashMap<>();
|
||||
|
||||
AnimationHandler(Duration duration, Supplier<Boolean> animationCondition, Set<JFXKeyValue<?>> keyValues) {
|
||||
this.duration = duration.toMillis();
|
||||
currentDuration = this.duration;
|
||||
this.keyValues = keyValues;
|
||||
this.animationCondition = animationCondition;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
finished = animationCondition != null && !animationCondition.get();
|
||||
for (JFXKeyValue<?> keyValue : keyValues) {
|
||||
if (keyValue.getTarget() != null) {
|
||||
// replaced putIfAbsent for mobile compatibility
|
||||
if (!initialValuesMap.containsKey(keyValue.getTarget())) {
|
||||
initialValuesMap.put(keyValue.getTarget(), keyValue.getTarget().getValue());
|
||||
}
|
||||
if (!endValuesMap.containsKey(keyValue.getTarget())) {
|
||||
endValuesMap.put(keyValue.getTarget(), keyValue.getEndValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reverse(double now) {
|
||||
finished = animationCondition != null && !animationCondition.get();
|
||||
currentDuration = duration - (currentDuration - now);
|
||||
// update initial values
|
||||
for (JFXKeyValue<?> keyValue : keyValues) {
|
||||
final WritableValue<?> target = keyValue.getTarget();
|
||||
if (target != null) {
|
||||
initialValuesMap.put(target, target.getValue());
|
||||
endValuesMap.put(target, keyValue.getEndValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now in milliseconds
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void animate(double now) {
|
||||
// if animate condition for the key frame is not met then do nothing
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
if (now <= currentDuration) {
|
||||
for (JFXKeyValue<?> keyValue : keyValues) {
|
||||
if (keyValue.isValid()) {
|
||||
@SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget();
|
||||
final Object endValue = endValuesMap.get(target);
|
||||
if (endValue != null && target != null && !target.getValue().equals(endValue)) {
|
||||
target.setValue(keyValue.getInterpolator().interpolate(initialValuesMap.get(target), endValue, now / currentDuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
for (JFXKeyValue<?> keyValue : keyValues) {
|
||||
if (keyValue.isValid()) {
|
||||
@SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget();
|
||||
if (target != null) {
|
||||
// set updated end value instead of cached
|
||||
final Object endValue = keyValue.getEndValue();
|
||||
if (endValue != null) {
|
||||
target.setValue(endValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
currentDuration = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void applyEndValues() {
|
||||
for (JFXKeyValue<?> keyValue : keyValues) {
|
||||
if (keyValue.isValid()) {
|
||||
@SuppressWarnings("rawtypes") final WritableValue target = keyValue.getTarget();
|
||||
if (target != null) {
|
||||
final Object endValue = keyValue.getEndValue();
|
||||
if (endValue != null && !target.getValue().equals(endValue)) {
|
||||
target.setValue(endValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
initialValuesMap.clear();
|
||||
endValuesMap.clear();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
clear();
|
||||
keyValues.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.transitions;
|
||||
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2017-09-21
|
||||
*/
|
||||
|
||||
public class JFXKeyFrame {
|
||||
|
||||
private Duration duration;
|
||||
private Set<JFXKeyValue<?>> keyValues = new CopyOnWriteArraySet<>();
|
||||
private Supplier<Boolean> animateCondition = null;
|
||||
|
||||
public JFXKeyFrame(Duration duration, JFXKeyValue<?>... keyValues) {
|
||||
this.duration = duration;
|
||||
for (final JFXKeyValue<?> keyValue : keyValues) {
|
||||
if (keyValue != null) {
|
||||
this.keyValues.add(keyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JFXKeyFrame() {
|
||||
|
||||
}
|
||||
|
||||
public final Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public final Set<JFXKeyValue<?>> getValues() {
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
public Supplier<Boolean> getAnimateCondition() {
|
||||
return animateCondition;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private Duration duration;
|
||||
private final Set<JFXKeyValue<?>> keyValues = new CopyOnWriteArraySet<>();
|
||||
private Supplier<Boolean> animateCondition = null;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder setDuration(Duration duration) {
|
||||
this.duration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeyValues(JFXKeyValue<?>... keyValues) {
|
||||
for (final JFXKeyValue<?> keyValue : keyValues) {
|
||||
if (keyValue != null) {
|
||||
this.keyValues.add(keyValue);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAnimateCondition(Supplier<Boolean> animateCondition) {
|
||||
this.animateCondition = animateCondition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyFrame build() {
|
||||
JFXKeyFrame jFXKeyFrame = new JFXKeyFrame();
|
||||
jFXKeyFrame.duration = this.duration;
|
||||
jFXKeyFrame.keyValues = this.keyValues;
|
||||
jFXKeyFrame.animateCondition = this.animateCondition;
|
||||
return jFXKeyFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.transitions;
|
||||
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.beans.value.WritableValue;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen
|
||||
* @version 1.0
|
||||
* @since 2017-09-21
|
||||
*/
|
||||
|
||||
public final class JFXKeyValue<T> {
|
||||
|
||||
private WritableValue<T> target;
|
||||
private Supplier<WritableValue<T>> targetSupplier;
|
||||
private Supplier<T> endValueSupplier;
|
||||
private T endValue;
|
||||
private Supplier<Boolean> animateCondition = () -> true;
|
||||
private Interpolator interpolator;
|
||||
|
||||
private JFXKeyValue() {
|
||||
}
|
||||
|
||||
// this builder is created to ensure type inference from method arguments
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public T getEndValue() {
|
||||
return endValue == null ? endValueSupplier.get() : endValue;
|
||||
}
|
||||
|
||||
public WritableValue<T> getTarget() {
|
||||
return target == null ? targetSupplier.get() : target;
|
||||
}
|
||||
|
||||
public Interpolator getInterpolator() {
|
||||
return interpolator;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return animateCondition == null || animateCondition.get();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
public <T> JFXKeyValueBuilder<T> setTarget(WritableValue<T> target) {
|
||||
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
|
||||
builder.setTarget(target);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public <T> JFXKeyValueBuilder<T> setTargetSupplier(Supplier<WritableValue<T>> targetSupplier) {
|
||||
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
|
||||
builder.setTargetSupplier(targetSupplier);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public <T> JFXKeyValueBuilder<T> setEndValueSupplier(Supplier<T> endValueSupplier) {
|
||||
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
|
||||
builder.setEndValueSupplier(endValueSupplier);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public <T> JFXKeyValueBuilder<T> setEndValue(T endValue) {
|
||||
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
|
||||
builder.setEndValue(endValue);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public <T> JFXKeyValueBuilder<T> setAnimateCondition(Supplier<Boolean> animateCondition) {
|
||||
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
|
||||
builder.setAnimateCondition(animateCondition);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public <T> JFXKeyValueBuilder<T> setInterpolator(Interpolator interpolator) {
|
||||
JFXKeyValueBuilder<T> builder = new JFXKeyValueBuilder<>();
|
||||
builder.setInterpolator(interpolator);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class JFXKeyValueBuilder<T> {
|
||||
|
||||
private WritableValue<T> target;
|
||||
private Supplier<WritableValue<T>> targetSupplier;
|
||||
private Supplier<T> endValueSupplier;
|
||||
private T endValue;
|
||||
private Supplier<Boolean> animateCondition = () -> true;
|
||||
private Interpolator interpolator = Interpolator.EASE_BOTH;
|
||||
|
||||
private JFXKeyValueBuilder() {
|
||||
}
|
||||
|
||||
public JFXKeyValueBuilder<T> setTarget(WritableValue<T> target) {
|
||||
this.target = target;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyValueBuilder<T> setTargetSupplier(Supplier<WritableValue<T>> targetSupplier) {
|
||||
this.targetSupplier = targetSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyValueBuilder<T> setEndValueSupplier(Supplier<T> endValueSupplier) {
|
||||
this.endValueSupplier = endValueSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyValueBuilder<T> setEndValue(T endValue) {
|
||||
this.endValue = endValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyValueBuilder<T> setAnimateCondition(Supplier<Boolean> animateCondition) {
|
||||
this.animateCondition = animateCondition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyValueBuilder<T> setInterpolator(Interpolator interpolator) {
|
||||
this.interpolator = interpolator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JFXKeyValue<T> build() {
|
||||
JFXKeyValue<T> jFXKeyValue = new JFXKeyValue<>();
|
||||
jFXKeyValue.target = this.target;
|
||||
jFXKeyValue.interpolator = this.interpolator;
|
||||
jFXKeyValue.targetSupplier = this.targetSupplier;
|
||||
jFXKeyValue.endValue = this.endValue;
|
||||
jFXKeyValue.endValueSupplier = this.endValueSupplier;
|
||||
jFXKeyValue.animateCondition = this.animateCondition;
|
||||
return jFXKeyValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.jfoenix.utils;
|
||||
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/// @author Shadi Shaheen
|
||||
/// @version 1.0
|
||||
/// @since 2017-02-11
|
||||
public final class JFXNodeUtils {
|
||||
|
||||
public static void updateBackground(Background newBackground, Region nodeToUpdate) {
|
||||
updateBackground(newBackground, nodeToUpdate, Color.BLACK);
|
||||
}
|
||||
|
||||
public static void updateBackground(Background newBackground, Region nodeToUpdate, Paint fill) {
|
||||
if (newBackground != null && !newBackground.getFills().isEmpty()) {
|
||||
final BackgroundFill[] fills = new BackgroundFill[newBackground.getFills().size()];
|
||||
for (int i = 0; i < newBackground.getFills().size(); i++) {
|
||||
BackgroundFill bf = newBackground.getFills().get(i);
|
||||
fills[i] = new BackgroundFill(fill, bf.getRadii(), bf.getInsets());
|
||||
}
|
||||
nodeToUpdate.setBackground(new Background(fills));
|
||||
}
|
||||
}
|
||||
|
||||
public static String colorToHex(Color c) {
|
||||
if (c != null) {
|
||||
return String.format((Locale) null, "#%02X%02X%02X",
|
||||
Math.round(c.getRed() * 255),
|
||||
Math.round(c.getGreen() * 255),
|
||||
Math.round(c.getBlue() * 255));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private JFXNodeUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,13 +20,11 @@ package org.jackhuang.hmcl;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.SambaException;
|
||||
@@ -56,7 +54,6 @@ import java.net.CookieManager;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
@@ -82,18 +79,6 @@ public final class Launcher extends Application {
|
||||
LOG.info("Dark Mode: " + Optional.ofNullable(FXUtils.DARK_MODE).map(ObservableBooleanValue::get).orElse(false));
|
||||
LOG.info("Reduced Motion: " + Objects.requireNonNullElse(FXUtils.REDUCED_MOTION, false));
|
||||
|
||||
if (Screen.getScreens().isEmpty()) {
|
||||
LOG.info("No screen");
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder("Screens:");
|
||||
int count = 0;
|
||||
for (Screen screen : Screen.getScreens()) {
|
||||
builder.append("\n - Screen ").append(++count).append(": ");
|
||||
appendScreen(builder, screen);
|
||||
}
|
||||
LOG.info(builder.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
@@ -146,38 +131,6 @@ public final class Launcher extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendScreen(StringBuilder builder, Screen screen) {
|
||||
Rectangle2D bounds = screen.getBounds();
|
||||
double scale = screen.getOutputScaleX();
|
||||
|
||||
builder.append(Math.round(bounds.getWidth() * scale));
|
||||
builder.append('x');
|
||||
builder.append(Math.round(bounds.getHeight() * scale));
|
||||
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||
|
||||
if (scale != 1.0) {
|
||||
builder.append(" @ ");
|
||||
builder.append(decimalFormat.format(scale));
|
||||
builder.append('x');
|
||||
}
|
||||
|
||||
double dpi = screen.getDpi();
|
||||
builder.append(' ');
|
||||
builder.append(decimalFormat.format(dpi));
|
||||
builder.append("dpi");
|
||||
|
||||
builder.append(" in ")
|
||||
.append(Math.round(Math.sqrt(bounds.getWidth() * bounds.getWidth() + bounds.getHeight() * bounds.getHeight()) / dpi))
|
||||
.append('"');
|
||||
|
||||
builder.append(" (").append(decimalFormat.format(bounds.getMinX()))
|
||||
.append(", ").append(decimalFormat.format(bounds.getMinY()))
|
||||
.append(", ").append(decimalFormat.format(bounds.getMaxX()))
|
||||
.append(", ").append(decimalFormat.format(bounds.getMaxY()))
|
||||
.append(")");
|
||||
}
|
||||
|
||||
private static ButtonType showAlert(AlertType alertType, String contentText, ButtonType... buttons) {
|
||||
return new Alert(alertType, contentText, buttons).showAndWait().orElse(null);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
String region = locale.getCountry();
|
||||
|
||||
return switch (LocaleUtils.getRootLanguage(locale)) {
|
||||
case "ar" -> "ar_SA";
|
||||
case "es" -> "es_ES";
|
||||
case "ja" -> "ja_JP";
|
||||
case "ru" -> "ru_RU";
|
||||
|
||||
@@ -25,20 +25,20 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -55,8 +55,8 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
@@ -387,7 +387,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions.Builder getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -418,6 +418,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setWidth(vs.getWidth())
|
||||
.setHeight(vs.getHeight())
|
||||
.setFullscreen(vs.isFullscreen())
|
||||
.setServerIp(vs.getServerIp())
|
||||
.setWrapper(vs.getWrapper())
|
||||
.setPreLaunchCommand(vs.getPreLaunchCommand())
|
||||
.setPostExitCommand(vs.getPostExitCommand())
|
||||
@@ -427,17 +428,12 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
.setRenderer(vs.getRenderer())
|
||||
.setEnableDebugLogOutput(vs.isEnableDebugLogOutput())
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
|
||||
.setJavaAgents(javaAgents)
|
||||
.setJavaArguments(javaArguments);
|
||||
|
||||
if (StringUtils.isNotBlank(vs.getServerIp())) {
|
||||
builder.setQuickPlayOption(new QuickPlayOption.MultiPlayer(vs.getServerIp()));
|
||||
}
|
||||
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxyType(config().getProxyType());
|
||||
builder.setProxyHost(config().getProxyHost());
|
||||
@@ -464,7 +460,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
if (vs.isAutoMemory() && builder.getJavaArguments().stream().anyMatch(it -> it.startsWith("-Xmx")))
|
||||
builder.setMaxMemory(null);
|
||||
|
||||
return builder;
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,6 @@ import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
@@ -52,7 +51,6 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
@@ -62,6 +60,7 @@ import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
@@ -82,8 +81,6 @@ public final class LauncherHelper {
|
||||
private final VersionSetting setting;
|
||||
private LauncherVisibility launcherVisibility;
|
||||
private boolean showLogs;
|
||||
private QuickPlayOption quickPlayOption;
|
||||
private boolean disableOfflineSkin = false;
|
||||
|
||||
public LauncherHelper(Profile profile, Account account, String selectedVersion) {
|
||||
this.profile = Objects.requireNonNull(profile);
|
||||
@@ -114,14 +111,6 @@ public final class LauncherHelper {
|
||||
launcherVisibility = LauncherVisibility.KEEP;
|
||||
}
|
||||
|
||||
public void setQuickPlayOption(QuickPlayOption quickPlayOption) {
|
||||
this.quickPlayOption = quickPlayOption;
|
||||
}
|
||||
|
||||
public void setDisableOfflineSkin() {
|
||||
disableOfflineSkin = true;
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
@@ -199,15 +188,8 @@ public final class LauncherHelper {
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(
|
||||
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
|
||||
if (disableOfflineSkin) {
|
||||
launchOptionsBuilder.setDaemon(false);
|
||||
}
|
||||
if (quickPlayOption != null) {
|
||||
launchOptionsBuilder.setQuickPlayOption(quickPlayOption);
|
||||
}
|
||||
LaunchOptions launchOptions = launchOptionsBuilder.create();
|
||||
|
||||
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModsDirectory(selectedVersion), 10));
|
||||
|
||||
@@ -657,13 +639,10 @@ public final class LauncherHelper {
|
||||
return future;
|
||||
}
|
||||
|
||||
private Task<AuthInfo> logIn(Account account) {
|
||||
private static Task<AuthInfo> logIn(Account account) {
|
||||
return Task.composeAsync(() -> {
|
||||
try {
|
||||
if (disableOfflineSkin && account instanceof OfflineAccount offlineAccount)
|
||||
return Task.completed(offlineAccount.logInWithoutSkin());
|
||||
else
|
||||
return Task.completed(account.logIn());
|
||||
return Task.completed(account.logIn());
|
||||
} catch (CredentialExpiredException e) {
|
||||
LOG.info("Credential has expired", e);
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.theme.ThemeColor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.gson.*;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -276,34 +276,19 @@ public final class Config extends ObservableSetting {
|
||||
|
||||
// UI
|
||||
|
||||
@SerializedName("themeBrightness")
|
||||
private final StringProperty themeBrightness = new SimpleStringProperty("light");
|
||||
|
||||
public StringProperty themeBrightnessProperty() {
|
||||
return themeBrightness;
|
||||
}
|
||||
|
||||
public String getThemeBrightness() {
|
||||
return themeBrightness.get();
|
||||
}
|
||||
|
||||
public void setThemeBrightness(String themeBrightness) {
|
||||
this.themeBrightness.set(themeBrightness);
|
||||
}
|
||||
|
||||
@SerializedName("theme")
|
||||
private final ObjectProperty<ThemeColor> themeColor = new SimpleObjectProperty<>(ThemeColor.DEFAULT);
|
||||
private final ObjectProperty<Theme> theme = new SimpleObjectProperty<>();
|
||||
|
||||
public ObjectProperty<ThemeColor> themeColorProperty() {
|
||||
return themeColor;
|
||||
public ObjectProperty<Theme> themeProperty() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public ThemeColor getThemeColor() {
|
||||
return themeColor.get();
|
||||
public Theme getTheme() {
|
||||
return theme.get();
|
||||
}
|
||||
|
||||
public void setThemeColor(ThemeColor themeColor) {
|
||||
this.themeColor.set(themeColor);
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme.set(theme);
|
||||
}
|
||||
|
||||
@SerializedName("fontFamily")
|
||||
@@ -493,7 +478,7 @@ public final class Config extends ObservableSetting {
|
||||
}
|
||||
|
||||
@SerializedName("downloadType")
|
||||
private final StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_DIRECT_PROVIDER_ID);
|
||||
private final StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_RAW_PROVIDER_ID);
|
||||
|
||||
public StringProperty downloadTypeProperty() {
|
||||
return downloadType;
|
||||
@@ -523,7 +508,7 @@ public final class Config extends ObservableSetting {
|
||||
}
|
||||
|
||||
@SerializedName("versionListSource")
|
||||
private final StringProperty versionListSource = new SimpleStringProperty(DownloadProviders.DEFAULT_AUTO_PROVIDER_ID);
|
||||
private final StringProperty versionListSource = new SimpleStringProperty("balanced");
|
||||
|
||||
public StringProperty versionListSourceProperty() {
|
||||
return versionListSource;
|
||||
|
||||
@@ -21,10 +21,9 @@ import javafx.beans.InvalidationListener;
|
||||
import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.FetchTask;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
@@ -32,88 +31,104 @@ import java.io.FileNotFoundException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class DownloadProviders {
|
||||
private DownloadProviders() {
|
||||
}
|
||||
|
||||
public static final String DEFAULT_AUTO_PROVIDER_ID = "balanced";
|
||||
public static final String DEFAULT_DIRECT_PROVIDER_ID = "mojang";
|
||||
private static final DownloadProviderWrapper provider;
|
||||
|
||||
private static final DownloadProviderWrapper PROVIDER_WRAPPER;
|
||||
public static final Map<String, DownloadProvider> providersById;
|
||||
public static final Map<String, DownloadProvider> rawProviders;
|
||||
private static final AdaptedDownloadProvider fileDownloadProvider = new AdaptedDownloadProvider();
|
||||
|
||||
private static final DownloadProvider DEFAULT_PROVIDER;
|
||||
public static final Map<String, DownloadProvider> DIRECT_PROVIDERS;
|
||||
public static final Map<String, DownloadProvider> AUTO_PROVIDERS;
|
||||
private static final MojangDownloadProvider MOJANG;
|
||||
private static final BMCLAPIDownloadProvider BMCLAPI;
|
||||
|
||||
public static final String DEFAULT_PROVIDER_ID = "balanced";
|
||||
public static final String DEFAULT_RAW_PROVIDER_ID = "bmclapi";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final InvalidationListener observer;
|
||||
|
||||
static {
|
||||
String bmclapiRoot = System.getProperty("hmcl.bmclapi.override", "https://bmclapi2.bangbang93.com");
|
||||
BMCLAPIDownloadProvider bmclapiRaw = new BMCLAPIDownloadProvider(bmclapiRoot);
|
||||
String bmclapiRoot = "https://bmclapi2.bangbang93.com";
|
||||
String bmclapiRootOverride = System.getProperty("hmcl.bmclapi.override");
|
||||
if (bmclapiRootOverride != null) bmclapiRoot = bmclapiRootOverride;
|
||||
|
||||
DownloadProvider mojang = new MojangDownloadProvider();
|
||||
DownloadProvider bmclapi = new AutoDownloadProvider(bmclapiRaw, mojang);
|
||||
|
||||
DEFAULT_PROVIDER = mojang;
|
||||
DIRECT_PROVIDERS = Lang.mapOf(
|
||||
pair("mojang", mojang),
|
||||
pair("bmclapi", bmclapi)
|
||||
MOJANG = new MojangDownloadProvider();
|
||||
BMCLAPI = new BMCLAPIDownloadProvider(bmclapiRoot);
|
||||
rawProviders = Map.of(
|
||||
"mojang", MOJANG,
|
||||
"bmclapi", BMCLAPI
|
||||
);
|
||||
|
||||
AUTO_PROVIDERS = Lang.mapOf(
|
||||
pair("balanced", LocaleUtils.IS_CHINA_MAINLAND ? bmclapi : mojang),
|
||||
pair("official", LocaleUtils.IS_CHINA_MAINLAND ? new AutoDownloadProvider(
|
||||
List.of(mojang, bmclapiRaw),
|
||||
List.of(bmclapiRaw, mojang)
|
||||
) : mojang),
|
||||
pair("mirror", bmclapi)
|
||||
);
|
||||
AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider();
|
||||
fileProvider.setDownloadProviderCandidates(List.of(BMCLAPI, MOJANG));
|
||||
BalancedDownloadProvider balanced = new BalancedDownloadProvider(MOJANG, BMCLAPI);
|
||||
|
||||
PROVIDER_WRAPPER = new DownloadProviderWrapper(DEFAULT_PROVIDER);
|
||||
providersById = Map.of(
|
||||
"official", new AutoDownloadProvider(MOJANG, fileProvider),
|
||||
"balanced", new AutoDownloadProvider(balanced, fileProvider),
|
||||
"mirror", new AutoDownloadProvider(BMCLAPI, fileProvider));
|
||||
|
||||
observer = FXUtils.observeWeak(() -> {
|
||||
FetchTask.setDownloadExecutorConcurrency(
|
||||
config().getAutoDownloadThreads() ? DEFAULT_CONCURRENCY : config().getDownloadThreads());
|
||||
}, config().autoDownloadThreadsProperty(), config().downloadThreadsProperty());
|
||||
|
||||
provider = new DownloadProviderWrapper(MOJANG);
|
||||
}
|
||||
|
||||
static void init() {
|
||||
InvalidationListener onChangeDownloadThreads = observable -> {
|
||||
FetchTask.setDownloadExecutorConcurrency(config().getAutoDownloadThreads()
|
||||
? DEFAULT_CONCURRENCY
|
||||
: config().getDownloadThreads());
|
||||
};
|
||||
config().autoDownloadThreadsProperty().addListener(onChangeDownloadThreads);
|
||||
config().downloadThreadsProperty().addListener(onChangeDownloadThreads);
|
||||
onChangeDownloadThreads.invalidated(null);
|
||||
|
||||
InvalidationListener onChangeDownloadSource = observable -> {
|
||||
String versionListSource = Objects.requireNonNullElse(config().getVersionListSource(), "");
|
||||
if (config().isAutoChooseDownloadType()) {
|
||||
String versionListSource = config().getVersionListSource();
|
||||
DownloadProvider downloadProvider = versionListSource != null
|
||||
? AUTO_PROVIDERS.getOrDefault(versionListSource, DEFAULT_PROVIDER)
|
||||
: DEFAULT_PROVIDER;
|
||||
PROVIDER_WRAPPER.setProvider(downloadProvider);
|
||||
DownloadProvider currentDownloadProvider = providersById.get(versionListSource);
|
||||
if (currentDownloadProvider == null)
|
||||
currentDownloadProvider = Objects.requireNonNull(providersById.get(DEFAULT_PROVIDER_ID),
|
||||
"default provider is null");
|
||||
|
||||
provider.setProvider(currentDownloadProvider);
|
||||
} else {
|
||||
String downloadType = config().getDownloadType();
|
||||
PROVIDER_WRAPPER.setProvider(downloadType != null
|
||||
? DIRECT_PROVIDERS.getOrDefault(downloadType, DEFAULT_PROVIDER)
|
||||
: DEFAULT_PROVIDER);
|
||||
provider.setProvider(fileDownloadProvider);
|
||||
}
|
||||
};
|
||||
config().versionListSourceProperty().addListener(onChangeDownloadSource);
|
||||
config().autoChooseDownloadTypeProperty().addListener(onChangeDownloadSource);
|
||||
config().downloadTypeProperty().addListener(onChangeDownloadSource);
|
||||
|
||||
onChangeDownloadSource.invalidated(null);
|
||||
|
||||
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
|
||||
DownloadProvider primary = Objects.requireNonNullElseGet(
|
||||
rawProviders.get(Objects.requireNonNullElse(downloadType, "")),
|
||||
() -> rawProviders.get(DEFAULT_RAW_PROVIDER_ID));
|
||||
|
||||
List<DownloadProvider> providers = new ArrayList<>(rawProviders.size());
|
||||
providers.add(primary);
|
||||
for (DownloadProvider provider : rawProviders.values()) {
|
||||
if (provider != primary)
|
||||
providers.add(provider);
|
||||
}
|
||||
|
||||
fileDownloadProvider.setDownloadProviderCandidates(providers);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current primary preferred download provider
|
||||
*/
|
||||
public static DownloadProvider getDownloadProvider() {
|
||||
return PROVIDER_WRAPPER;
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static String localizeErrorMessage(Throwable exception) {
|
||||
|
||||
@@ -22,12 +22,6 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.glavo.monetfx.Brightness;
|
||||
import org.glavo.monetfx.ColorRole;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.jackhuang.hmcl.theme.Theme;
|
||||
import org.jackhuang.hmcl.theme.ThemeColor;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -39,6 +33,7 @@ import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
@@ -47,7 +42,6 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public final class StyleSheets {
|
||||
private static final int FONT_STYLE_SHEET_INDEX = 0;
|
||||
private static final int THEME_STYLE_SHEET_INDEX = 1;
|
||||
private static final int BRIGHTNESS_SHEET_INDEX = 2;
|
||||
|
||||
private static final ObservableList<String> stylesheets;
|
||||
|
||||
@@ -55,16 +49,12 @@ public final class StyleSheets {
|
||||
String[] array = new String[]{
|
||||
getFontStyleSheet(),
|
||||
getThemeStyleSheet(),
|
||||
getBrightnessStyleSheet(),
|
||||
"/assets/css/root.css"
|
||||
};
|
||||
stylesheets = FXCollections.observableList(Arrays.asList(array));
|
||||
|
||||
FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
|
||||
Themes.colorSchemeProperty().addListener(o -> {
|
||||
stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet());
|
||||
stylesheets.set(BRIGHTNESS_SHEET_INDEX, getBrightnessStyleSheet());
|
||||
});
|
||||
config().themeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet()));
|
||||
}
|
||||
|
||||
private static String toStyleSheetUri(String styleSheet, String fallback) {
|
||||
@@ -136,55 +126,31 @@ public final class StyleSheets {
|
||||
return toStyleSheetUri(builder.toString(), defaultCss);
|
||||
}
|
||||
|
||||
private static String getBrightnessStyleSheet() {
|
||||
return Themes.getColorScheme().getBrightness() == Brightness.LIGHT
|
||||
? "/assets/css/brightness-light.css"
|
||||
: "/assets/css/brightness-dark.css";
|
||||
}
|
||||
|
||||
private static void addColor(StringBuilder builder, String name, Color color) {
|
||||
builder.append(" ").append(name)
|
||||
.append(": ").append(ThemeColor.getColorDisplayName(color)).append(";\n");
|
||||
}
|
||||
|
||||
private static void addColor(StringBuilder builder, String name, Color color, double opacity) {
|
||||
builder.append(" ").append(name)
|
||||
.append(": ").append(ThemeColor.getColorDisplayNameWithOpacity(color, opacity)).append(";\n");
|
||||
}
|
||||
|
||||
private static void addColor(StringBuilder builder, ColorScheme scheme, ColorRole role, double opacity) {
|
||||
builder.append(" ").append(role.getVariableName()).append("-transparent-%02d".formatted((int) (100 * opacity)))
|
||||
.append(": ").append(ThemeColor.getColorDisplayNameWithOpacity(scheme.getColor(role), opacity))
|
||||
.append(";\n");
|
||||
private static String rgba(Color color, double opacity) {
|
||||
return String.format("rgba(%d, %d, %d, %.1f)",
|
||||
(int) Math.ceil(color.getRed() * 256),
|
||||
(int) Math.ceil(color.getGreen() * 256),
|
||||
(int) Math.ceil(color.getBlue() * 256),
|
||||
opacity);
|
||||
}
|
||||
|
||||
private static String getThemeStyleSheet() {
|
||||
final String blueCss = "/assets/css/blue.css";
|
||||
|
||||
if (Theme.DEFAULT.equals(Themes.getTheme()))
|
||||
Theme theme = config().getTheme();
|
||||
if (theme == null || theme.getPaint().equals(Theme.BLUE.getPaint()))
|
||||
return blueCss;
|
||||
|
||||
ColorScheme scheme = Themes.getColorScheme();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("* {\n");
|
||||
for (ColorRole colorRole : ColorRole.ALL) {
|
||||
addColor(builder, colorRole.getVariableName(), scheme.getColor(colorRole));
|
||||
}
|
||||
|
||||
addColor(builder, "-monet-primary-seed", scheme.getPrimaryColorSeed());
|
||||
|
||||
addColor(builder, scheme, ColorRole.PRIMARY, 0.5);
|
||||
addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.5);
|
||||
addColor(builder, scheme, ColorRole.SURFACE, 0.5);
|
||||
addColor(builder, scheme, ColorRole.SURFACE, 0.8);
|
||||
addColor(builder, scheme, ColorRole.ON_SURFACE_VARIANT, 0.38);
|
||||
addColor(builder, scheme, ColorRole.SURFACE_CONTAINER_LOW, 0.8);
|
||||
addColor(builder, scheme, ColorRole.SECONDARY_CONTAINER, 0.8);
|
||||
addColor(builder, scheme, ColorRole.INVERSE_SURFACE, 0.8);
|
||||
|
||||
builder.append("}\n");
|
||||
return toStyleSheetUri(builder.toString(), blueCss);
|
||||
return toStyleSheetUri(".root {" +
|
||||
"-fx-base-color:" + theme.getColor() + ';' +
|
||||
"-fx-base-darker-color: derive(-fx-base-color, -10%);" +
|
||||
"-fx-base-check-color: derive(-fx-base-color, 30%);" +
|
||||
"-fx-rippler-color:" + rgba(theme.getPaint(), 0.3) + ';' +
|
||||
"-fx-base-rippler-color: derive(" + rgba(theme.getPaint(), 0.3) + ", 100%);" +
|
||||
"-fx-base-disabled-text-fill:" + rgba(theme.getForegroundColor(), 0.7) + ";" +
|
||||
"-fx-base-text-fill:" + Theme.getColorDisplayName(theme.getForegroundColor()) + ";" +
|
||||
"-theme-thumb:" + rgba(theme.getPaint(), 0.7) + ";" +
|
||||
'}', blueCss);
|
||||
}
|
||||
|
||||
public static void init(Scene scene) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
@JsonAdapter(Theme.TypeAdapter.class)
|
||||
public final class Theme {
|
||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||
public static final Color BLACK = Color.web("#292929");
|
||||
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
||||
Color.web("#3D6DA3"), // blue
|
||||
Color.web("#283593"), // dark blue
|
||||
Color.web("#43A047"), // green
|
||||
Color.web("#E67E22"), // orange
|
||||
Color.web("#9C27B0"), // purple
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
public static Theme getTheme() {
|
||||
Theme theme = config().getTheme();
|
||||
return theme == null ? BLUE : theme;
|
||||
}
|
||||
|
||||
private final Color paint;
|
||||
private final String color;
|
||||
private final String name;
|
||||
|
||||
Theme(String name, String color) {
|
||||
this.name = name;
|
||||
this.color = Objects.requireNonNull(color);
|
||||
this.paint = Color.web(color);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public Color getPaint() {
|
||||
return paint;
|
||||
}
|
||||
|
||||
public boolean isCustom() {
|
||||
return name.startsWith("#");
|
||||
}
|
||||
|
||||
public boolean isLight() {
|
||||
return paint.grayscale().getRed() >= 0.5;
|
||||
}
|
||||
|
||||
public Color getForegroundColor() {
|
||||
return isLight() ? Color.BLACK : Color.WHITE;
|
||||
}
|
||||
|
||||
public static Theme custom(String color) {
|
||||
if (!color.startsWith("#"))
|
||||
throw new IllegalArgumentException();
|
||||
return new Theme(color, color);
|
||||
}
|
||||
|
||||
public static Optional<Theme> getTheme(String name) {
|
||||
if (name == null)
|
||||
return Optional.empty();
|
||||
else if (name.startsWith("#"))
|
||||
try {
|
||||
Color.web(name);
|
||||
return Optional.of(custom(name));
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
else {
|
||||
String color = null;
|
||||
switch (name.toLowerCase(Locale.ROOT)) {
|
||||
case "blue":
|
||||
return Optional.of(BLUE);
|
||||
case "darker_blue":
|
||||
color = "#283593";
|
||||
break;
|
||||
case "green":
|
||||
color = "#43A047";
|
||||
break;
|
||||
case "orange":
|
||||
color = "#E67E22";
|
||||
break;
|
||||
case "purple":
|
||||
color = "#9C27B0";
|
||||
break;
|
||||
case "red":
|
||||
color = "#F44336";
|
||||
}
|
||||
if (color != null)
|
||||
return Optional.of(new Theme(name, color));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static String getColorDisplayName(Color c) {
|
||||
return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null;
|
||||
}
|
||||
|
||||
private static ObjectBinding<Color> FOREGROUND_FILL;
|
||||
|
||||
public static ObjectBinding<Color> foregroundFillBinding() {
|
||||
if (FOREGROUND_FILL == null)
|
||||
FOREGROUND_FILL = Bindings.createObjectBinding(
|
||||
() -> Theme.getTheme().getForegroundColor(),
|
||||
config().themeProperty()
|
||||
);
|
||||
|
||||
return FOREGROUND_FILL;
|
||||
}
|
||||
|
||||
public static Color blackFill() {
|
||||
return BLACK;
|
||||
}
|
||||
|
||||
public static Color whiteFill() {
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {
|
||||
@Override
|
||||
public void write(JsonWriter out, Theme value) throws IOException {
|
||||
out.value(value.getName().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Theme read(JsonReader in) throws IOException {
|
||||
return getTheme(in.nextString()).orElse(Theme.BLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,20 +459,6 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
showLogsProperty.set(showLogs);
|
||||
}
|
||||
|
||||
private final BooleanProperty enableDebugLogOutputProperty = new SimpleBooleanProperty(this, "enableDebugLogOutput", false);
|
||||
|
||||
public BooleanProperty enableDebugLogOutputProperty() {
|
||||
return enableDebugLogOutputProperty;
|
||||
}
|
||||
|
||||
public boolean isEnableDebugLogOutput() {
|
||||
return enableDebugLogOutputProperty.get();
|
||||
}
|
||||
|
||||
public void setEnableDebugLogOutput(boolean u) {
|
||||
this.enableDebugLogOutputProperty.set(u);
|
||||
}
|
||||
|
||||
// Minecraft settings.
|
||||
|
||||
private final StringProperty serverIpProperty = new SimpleStringProperty(this, "serverIp", "");
|
||||
@@ -790,7 +776,6 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
obj.addProperty("notCheckJVM", src.isNotCheckJVM());
|
||||
obj.addProperty("notPatchNatives", src.isNotPatchNatives());
|
||||
obj.addProperty("showLogs", src.isShowLogs());
|
||||
obj.addProperty("enableDebugLogOutput", src.isEnableDebugLogOutput());
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("processPriority", src.getProcessPriority().ordinal());
|
||||
@@ -862,7 +847,6 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotPatchNatives(Optional.ofNullable(obj.get("notPatchNatives")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setEnableDebugLogOutput(Optional.ofNullable(obj.get("enableDebugLogOutput")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(parseJsonPrimitive(obj.getAsJsonPrimitive("launcherVisibility"), LauncherVisibility.class, LauncherVisibility.HIDE));
|
||||
vs.setProcessPriority(parseJsonPrimitive(obj.getAsJsonPrimitive("processPriority"), ProcessPriority.class, ProcessPriority.NORMAL));
|
||||
vs.setUseNativeGLFW(Optional.ofNullable(obj.get("useNativeGLFW")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.theme;
|
||||
|
||||
import org.glavo.monetfx.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/// @author Glavo
|
||||
public final class Theme {
|
||||
|
||||
public static final Theme DEFAULT = new Theme(ThemeColor.DEFAULT, Brightness.DEFAULT, ColorStyle.FIDELITY, Contrast.DEFAULT);
|
||||
|
||||
private final ThemeColor primaryColorSeed;
|
||||
private final Brightness brightness;
|
||||
private final ColorStyle colorStyle;
|
||||
private final Contrast contrast;
|
||||
|
||||
public Theme(ThemeColor primaryColorSeed,
|
||||
Brightness brightness,
|
||||
ColorStyle colorStyle,
|
||||
Contrast contrast
|
||||
) {
|
||||
this.primaryColorSeed = primaryColorSeed;
|
||||
this.brightness = brightness;
|
||||
this.colorStyle = colorStyle;
|
||||
this.contrast = contrast;
|
||||
}
|
||||
|
||||
public ColorScheme toColorScheme() {
|
||||
return ColorScheme.newBuilder()
|
||||
.setPrimaryColorSeed(primaryColorSeed.color())
|
||||
.setColorStyle(colorStyle)
|
||||
.setBrightness(brightness)
|
||||
.setSpecVersion(ColorSpecVersion.SPEC_2025)
|
||||
.setContrast(contrast)
|
||||
.build();
|
||||
}
|
||||
|
||||
public ThemeColor primaryColorSeed() {
|
||||
return primaryColorSeed;
|
||||
}
|
||||
|
||||
public Brightness brightness() {
|
||||
return brightness;
|
||||
}
|
||||
|
||||
public ColorStyle colorStyle() {
|
||||
return colorStyle;
|
||||
}
|
||||
|
||||
public Contrast contrast() {
|
||||
return contrast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this || obj instanceof Theme that
|
||||
&& this.primaryColorSeed.color().equals(that.primaryColorSeed.color())
|
||||
&& this.brightness.equals(that.brightness)
|
||||
&& this.colorStyle.equals(that.colorStyle)
|
||||
&& this.contrast.equals(that.contrast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(primaryColorSeed, brightness, colorStyle, contrast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Theme[" +
|
||||
"primaryColorSeed=" + primaryColorSeed + ", " +
|
||||
"brightness=" + brightness + ", " +
|
||||
"colorStyle=" + colorStyle + ", " +
|
||||
"contrast=" + contrast + ']';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.theme;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// @author Glavo
|
||||
@JsonAdapter(ThemeColor.TypeAdapter.class)
|
||||
@JsonSerializable
|
||||
public record ThemeColor(@NotNull String name, @NotNull Color color) {
|
||||
|
||||
public static final ThemeColor DEFAULT = new ThemeColor("blue", Color.web("#5C6BC0"));
|
||||
|
||||
public static final List<ThemeColor> STANDARD_COLORS = List.of(
|
||||
DEFAULT,
|
||||
new ThemeColor("darker_blue", Color.web("#283593")),
|
||||
new ThemeColor("green", Color.web("#43A047")),
|
||||
new ThemeColor("orange", Color.web("#E67E22")),
|
||||
new ThemeColor("purple", Color.web("#9C27B0")),
|
||||
new ThemeColor("red", Color.web("#B71C1C"))
|
||||
);
|
||||
|
||||
public static String getColorDisplayName(Color c) {
|
||||
return c != null ? String.format("#%02X%02X%02X",
|
||||
Math.round(c.getRed() * 255.0D),
|
||||
Math.round(c.getGreen() * 255.0D),
|
||||
Math.round(c.getBlue() * 255.0D))
|
||||
: null;
|
||||
}
|
||||
|
||||
public static String getColorDisplayNameWithOpacity(Color c, double opacity) {
|
||||
return c != null ? String.format("#%02X%02X%02X%02X",
|
||||
Math.round(c.getRed() * 255.0D),
|
||||
Math.round(c.getGreen() * 255.0D),
|
||||
Math.round(c.getBlue() * 255.0D),
|
||||
Math.round(opacity * 255.0))
|
||||
: null;
|
||||
}
|
||||
|
||||
public static @Nullable ThemeColor of(String name) {
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
if (!name.startsWith("#")) {
|
||||
for (ThemeColor color : STANDARD_COLORS) {
|
||||
if (name.equalsIgnoreCase(color.name()))
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return new ThemeColor(name, Color.web(name));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static ThemeColor of(Color color) {
|
||||
return color != null ? new ThemeColor(getColorDisplayName(color), color) : null;
|
||||
}
|
||||
|
||||
private static final class BidirectionalBinding implements InvalidationListener, WeakListener {
|
||||
private final WeakReference<ColorPicker> colorPickerRef;
|
||||
private final WeakReference<Property<ThemeColor>> propertyRef;
|
||||
private final int hashCode;
|
||||
|
||||
private boolean updating = false;
|
||||
|
||||
private BidirectionalBinding(ColorPicker colorPicker, Property<ThemeColor> property) {
|
||||
this.colorPickerRef = new WeakReference<>(colorPicker);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
this.hashCode = System.identityHashCode(colorPicker) ^ System.identityHashCode(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable sourceProperty) {
|
||||
if (!updating) {
|
||||
final ColorPicker colorPicker = colorPickerRef.get();
|
||||
final Property<ThemeColor> property = propertyRef.get();
|
||||
|
||||
if (colorPicker == null || property == null) {
|
||||
if (colorPicker != null) {
|
||||
colorPicker.valueProperty().removeListener(this);
|
||||
}
|
||||
|
||||
if (property != null) {
|
||||
property.removeListener(this);
|
||||
}
|
||||
} else {
|
||||
updating = true;
|
||||
try {
|
||||
if (property == sourceProperty) {
|
||||
ThemeColor newValue = property.getValue();
|
||||
colorPicker.setValue(newValue != null ? newValue.color() : null);
|
||||
} else {
|
||||
Color newValue = colorPicker.getValue();
|
||||
property.setValue(newValue != null ? ThemeColor.of(newValue) : null);
|
||||
}
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasGarbageCollected() {
|
||||
return colorPickerRef.get() == null || propertyRef.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof BidirectionalBinding that))
|
||||
return false;
|
||||
|
||||
final ColorPicker colorPicker = this.colorPickerRef.get();
|
||||
final Property<ThemeColor> property = this.propertyRef.get();
|
||||
|
||||
final ColorPicker thatColorPicker = that.colorPickerRef.get();
|
||||
final Property<?> thatProperty = that.propertyRef.get();
|
||||
|
||||
if (colorPicker == null || property == null || thatColorPicker == null || thatProperty == null)
|
||||
return false;
|
||||
|
||||
return colorPicker == thatColorPicker && property == thatProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindBidirectional(ColorPicker colorPicker, Property<ThemeColor> property) {
|
||||
var binding = new BidirectionalBinding(colorPicker, property);
|
||||
|
||||
colorPicker.valueProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
|
||||
ThemeColor themeColor = property.getValue();
|
||||
colorPicker.setValue(themeColor != null ? themeColor.color() : null);
|
||||
|
||||
colorPicker.valueProperty().addListener(binding);
|
||||
property.addListener(binding);
|
||||
}
|
||||
|
||||
static final class TypeAdapter extends com.google.gson.TypeAdapter<ThemeColor> {
|
||||
@Override
|
||||
public void write(JsonWriter out, ThemeColor value) throws IOException {
|
||||
out.value(value.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeColor read(JsonReader in) throws IOException {
|
||||
return Objects.requireNonNullElse(of(in.nextString()), ThemeColor.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.theme;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.glavo.monetfx.Brightness;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.glavo.monetfx.Contrast;
|
||||
import org.glavo.monetfx.beans.property.ColorSchemeProperty;
|
||||
import org.glavo.monetfx.beans.property.ReadOnlyColorSchemeProperty;
|
||||
import org.glavo.monetfx.beans.property.SimpleColorSchemeProperty;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/// @author Glavo
|
||||
public final class Themes {
|
||||
|
||||
private static final ObjectExpression<Theme> theme = new ObjectBinding<>() {
|
||||
{
|
||||
List<Observable> observables = new ArrayList<>();
|
||||
|
||||
observables.add(config().themeBrightnessProperty());
|
||||
observables.add(config().themeColorProperty());
|
||||
if (FXUtils.DARK_MODE != null) {
|
||||
observables.add(FXUtils.DARK_MODE);
|
||||
}
|
||||
bind(observables.toArray(new Observable[0]));
|
||||
}
|
||||
|
||||
private Brightness getBrightness() {
|
||||
String themeBrightness = config().getThemeBrightness();
|
||||
if (themeBrightness == null)
|
||||
return Brightness.DEFAULT;
|
||||
|
||||
return switch (themeBrightness.toLowerCase(Locale.ROOT).trim()) {
|
||||
case "auto" -> {
|
||||
if (FXUtils.DARK_MODE != null) {
|
||||
yield FXUtils.DARK_MODE.get() ? Brightness.DARK : Brightness.LIGHT;
|
||||
} else {
|
||||
yield Brightness.DEFAULT;
|
||||
}
|
||||
}
|
||||
case "dark" -> Brightness.DARK;
|
||||
case "light" -> Brightness.LIGHT;
|
||||
default -> Brightness.DEFAULT;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Theme computeValue() {
|
||||
ThemeColor themeColor = Objects.requireNonNullElse(config().getThemeColor(), ThemeColor.DEFAULT);
|
||||
|
||||
return new Theme(themeColor, getBrightness(), Theme.DEFAULT.colorStyle(), Contrast.DEFAULT);
|
||||
}
|
||||
};
|
||||
private static final ColorSchemeProperty colorScheme = new SimpleColorSchemeProperty();
|
||||
private static final BooleanBinding darkMode = Bindings.createBooleanBinding(
|
||||
() -> colorScheme.get().getBrightness() == Brightness.DARK,
|
||||
colorScheme
|
||||
);
|
||||
|
||||
static {
|
||||
ChangeListener<Theme> listener = (observable, oldValue, newValue) -> {
|
||||
if (!Objects.equals(oldValue, newValue)) {
|
||||
colorScheme.set(newValue != null ? newValue.toColorScheme() : Theme.DEFAULT.toColorScheme());
|
||||
}
|
||||
};
|
||||
listener.changed(theme, null, theme.get());
|
||||
theme.addListener(listener);
|
||||
}
|
||||
|
||||
public static ObjectExpression<Theme> themeProperty() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public static Theme getTheme() {
|
||||
return themeProperty().get();
|
||||
}
|
||||
|
||||
public static ReadOnlyColorSchemeProperty colorSchemeProperty() {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
public static ColorScheme getColorScheme() {
|
||||
return colorScheme.get();
|
||||
}
|
||||
|
||||
private static final ObjectBinding<Color> titleFill = Bindings.createObjectBinding(
|
||||
() -> config().isTitleTransparent()
|
||||
? getColorScheme().getOnSurface()
|
||||
: getColorScheme().getOnPrimaryContainer(),
|
||||
colorSchemeProperty(),
|
||||
config().titleTransparentProperty()
|
||||
);
|
||||
|
||||
public static ObservableValue<Color> titleFillProperty() {
|
||||
return titleFill;
|
||||
}
|
||||
|
||||
public static BooleanBinding darkModeProperty() {
|
||||
return darkMode;
|
||||
}
|
||||
|
||||
private Themes() {
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.CacheFileTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -134,7 +135,6 @@ public final class FXUtils {
|
||||
public static final @Nullable ObservableMap<String, Object> PREFERENCES;
|
||||
public static final @Nullable ObservableBooleanValue DARK_MODE;
|
||||
public static final @Nullable Boolean REDUCED_MOTION;
|
||||
public static final @Nullable ReadOnlyObjectProperty<Color> ACCENT_COLOR;
|
||||
|
||||
public static final @Nullable MethodHandle TEXT_TRUNCATED_PROPERTY;
|
||||
|
||||
@@ -151,7 +151,6 @@ public final class FXUtils {
|
||||
|
||||
ObservableMap<String, Object> preferences = null;
|
||||
ObservableBooleanValue darkMode = null;
|
||||
ReadOnlyObjectProperty<Color> accentColorProperty = null;
|
||||
Boolean reducedMotion = null;
|
||||
if (JAVAFX_MAJOR_VERSION >= 22) {
|
||||
try {
|
||||
@@ -163,19 +162,14 @@ public final class FXUtils {
|
||||
preferences = preferences0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var colorSchemeProperty = (ReadOnlyObjectProperty<? extends Enum<?>>)
|
||||
lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
|
||||
.invoke(preferences);
|
||||
var colorSchemeProperty =
|
||||
(ReadOnlyObjectProperty<? extends Enum<?>>)
|
||||
lookup.findVirtual(preferencesClass, "colorSchemeProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
|
||||
.invoke(preferences);
|
||||
|
||||
darkMode = Bindings.createBooleanBinding(() ->
|
||||
"DARK".equals(colorSchemeProperty.get().name()), colorSchemeProperty);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var accentColorProperty0 = (ReadOnlyObjectProperty<Color>)
|
||||
lookup.findVirtual(preferencesClass, "accentColorProperty", MethodType.methodType(ReadOnlyObjectProperty.class))
|
||||
.invoke(preferences);
|
||||
accentColorProperty = accentColorProperty0;
|
||||
|
||||
if (JAVAFX_MAJOR_VERSION >= 24) {
|
||||
reducedMotion = (boolean)
|
||||
lookup.findVirtual(preferencesClass, "isReducedMotion", MethodType.methodType(boolean.class))
|
||||
@@ -188,7 +182,6 @@ public final class FXUtils {
|
||||
PREFERENCES = preferences;
|
||||
DARK_MODE = darkMode;
|
||||
REDUCED_MOTION = reducedMotion;
|
||||
ACCENT_COLOR = accentColorProperty;
|
||||
|
||||
MethodHandle textTruncatedProperty = null;
|
||||
if (JAVAFX_MAJOR_VERSION >= 23) {
|
||||
@@ -1188,14 +1181,7 @@ public final class FXUtils {
|
||||
|
||||
public static Task<Image> getRemoteImageTask(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
return new CacheFileTask(url)
|
||||
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
||||
.setSignificance(Task.TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
public static Task<Image> getRemoteImageTask(URI uri, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
return new CacheFileTask(uri)
|
||||
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
||||
.setSignificance(Task.TaskSignificance.MINOR);
|
||||
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth));
|
||||
}
|
||||
|
||||
public static ObservableValue<Image> newRemoteImage(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
@@ -1228,7 +1214,7 @@ public final class FXUtils {
|
||||
public static JFXButton newToggleButton4(SVG icon) {
|
||||
JFXButton button = new JFXButton();
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(icon.createIcon());
|
||||
button.setGraphic(icon.createIcon(Theme.blackFill(), -1));
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -1461,11 +1447,11 @@ public final class FXUtils {
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
org.w3c.dom.Node node = children.item(i);
|
||||
|
||||
if (node instanceof Element element) {
|
||||
if (node instanceof Element) {
|
||||
Element element = (Element) node;
|
||||
if ("a".equals(element.getTagName())) {
|
||||
String href = element.getAttribute("href");
|
||||
Text text = new Text(element.getTextContent());
|
||||
text.getStyleClass().add("hyperlink");
|
||||
onClicked(text, () -> {
|
||||
String link = href;
|
||||
try {
|
||||
@@ -1475,6 +1461,7 @@ public final class FXUtils {
|
||||
hyperlinkAction.accept(link);
|
||||
});
|
||||
text.setCursor(Cursor.HAND);
|
||||
text.setFill(Color.web("#0070E0"));
|
||||
text.setUnderline(true);
|
||||
texts.add(text);
|
||||
} else if ("b".equals(element.getTagName())) {
|
||||
|
||||
@@ -292,7 +292,7 @@ public class GameCrashWindow extends Stage {
|
||||
private final class View extends VBox {
|
||||
|
||||
View() {
|
||||
this.getStyleClass().add("game-crash-window");
|
||||
setStyle("-fx-background-color: white");
|
||||
|
||||
HBox titlePane = new HBox();
|
||||
{
|
||||
@@ -395,13 +395,10 @@ public class GameCrashWindow extends Stage {
|
||||
reasonTitle.getStyleClass().add("two-line-item-second-large-title");
|
||||
|
||||
ScrollPane reasonPane = new ScrollPane(reasonTextFlow);
|
||||
reasonTextFlow.getStyleClass().add("crash-reason-text-flow");
|
||||
reasonPane.setFitToWidth(true);
|
||||
reasonPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||
|
||||
feedbackTextFlow.getStyleClass().add("crash-reason-text-flow");
|
||||
|
||||
gameDirPane.setPadding(new Insets(8));
|
||||
VBox.setVgrow(gameDirPane, Priority.ALWAYS);
|
||||
FXUtils.onChangeAndOperate(feedbackTextFlow.visibleProperty(), visible -> {
|
||||
@@ -414,7 +411,6 @@ public class GameCrashWindow extends Stage {
|
||||
}
|
||||
|
||||
HBox toolBar = new HBox();
|
||||
VBox.setMargin(toolBar, new Insets(0, 0, 4, 0));
|
||||
{
|
||||
JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs"));
|
||||
exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo());
|
||||
@@ -426,6 +422,7 @@ public class GameCrashWindow extends Stage {
|
||||
helpButton.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));
|
||||
FXUtils.installFastTooltip(helpButton, i18n("logwindow.help"));
|
||||
|
||||
|
||||
toolBar.setPadding(new Insets(8));
|
||||
toolBar.setSpacing(8);
|
||||
toolBar.getStyleClass().add("jfx-tool-bar");
|
||||
|
||||
@@ -36,6 +36,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -395,7 +396,7 @@ public class InstallerItem extends Control {
|
||||
pane.getChildren().add(buttonsContainer);
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.setGraphic(SVG.CLOSE.createIcon());
|
||||
removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1));
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
if (control.id.equals(MINECRAFT.getPatchId())) {
|
||||
removeButton.setVisible(false);
|
||||
@@ -416,8 +417,8 @@ public class InstallerItem extends Control {
|
||||
JFXButton installButton = new JFXButton();
|
||||
installButton.graphicProperty().bind(Bindings.createObjectBinding(() ->
|
||||
control.resolvedStateProperty.get() instanceof InstallableState ?
|
||||
SVG.ARROW_FORWARD.createIcon() :
|
||||
SVG.UPDATE.createIcon(),
|
||||
SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), -1) :
|
||||
SVG.UPDATE.createIcon(Theme.blackFill(), -1),
|
||||
control.resolvedStateProperty
|
||||
));
|
||||
installButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
@@ -23,11 +23,10 @@ import javafx.collections.ObservableList;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Control;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.construct.SpinnerPane.FAILED_ACTION;
|
||||
|
||||
public class ListPageBase<T> extends Control implements TransitionPane.Cacheable {
|
||||
public class ListPageBase<T> extends Control {
|
||||
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
|
||||
private final StringProperty failedReason = new SimpleStringProperty(this, "failed");
|
||||
|
||||
@@ -27,6 +27,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
@@ -66,7 +67,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
JFXButton btnAdd = FXUtils.newRaisedButton("");
|
||||
FXUtils.setLimitWidth(btnAdd, 40);
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.setGraphic(SVG.ADD.createIcon());
|
||||
btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1));
|
||||
btnAdd.setOnAction(e -> skinnable.add());
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
@@ -74,7 +75,7 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
FXUtils.setLimitHeight(btnRefresh, 40);
|
||||
btnRefresh.getStyleClass().add("jfx-button-raised-round");
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon());
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1));
|
||||
btnRefresh.setOnAction(e -> skinnable.refresh());
|
||||
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.sun.jna.Pointer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -29,7 +28,6 @@ import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
@@ -39,21 +37,17 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Dwmapi;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -90,36 +84,6 @@ public final class LogWindow extends Stage {
|
||||
private final LogWindowImpl impl;
|
||||
private final ManagedProcess gameProcess;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object windowsDarkModeListenerHolder;
|
||||
|
||||
{
|
||||
if (OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_11) && NativeUtils.USE_JNA && Dwmapi.INSTANCE != null) {
|
||||
this.addEventFilter(WindowEvent.WINDOW_SHOWN, new EventHandler<>() {
|
||||
@Override
|
||||
public void handle(WindowEvent event) {
|
||||
LogWindow.this.removeEventFilter(WindowEvent.WINDOW_SHOWN, this);
|
||||
|
||||
windowsDarkModeListenerHolder = FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {
|
||||
if (LogWindow.this.isShowing()) {
|
||||
WindowsNativeUtils.getWindowHandle(LogWindow.this).ifPresent(handle -> {
|
||||
if (handle == WinTypes.HANDLE.INVALID_VALUE)
|
||||
return;
|
||||
|
||||
Dwmapi.INSTANCE.DwmSetWindowAttribute(
|
||||
new WinTypes.HANDLE(Pointer.createConstant(handle)),
|
||||
WinConstants.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
new WinTypes.BOOLByReference(new WinTypes.BOOL(darkMode)),
|
||||
WinTypes.BOOL.SIZE
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public LogWindow(ManagedProcess gameProcess) {
|
||||
this(gameProcess, new CircularArrayList<>());
|
||||
}
|
||||
@@ -204,7 +168,6 @@ public final class LogWindow extends Stage {
|
||||
LogWindowImpl() {
|
||||
getStyleClass().add("log-window");
|
||||
|
||||
listView.getProperties().put("no-smooth-scrolling", true);
|
||||
listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size())));
|
||||
|
||||
for (int i = 0; i < LEVELS.length; i++) {
|
||||
@@ -301,6 +264,7 @@ public final class LogWindow extends Stage {
|
||||
|
||||
VBox vbox = new VBox(3);
|
||||
vbox.setPadding(new Insets(3, 0, 3, 0));
|
||||
vbox.setStyle("-fx-background-color: white");
|
||||
getChildren().setAll(vbox);
|
||||
|
||||
{
|
||||
@@ -322,7 +286,8 @@ public final class LogWindow extends Stage {
|
||||
HBox hBox = new HBox(3);
|
||||
for (int i = 0; i < LEVELS.length; i++) {
|
||||
ToggleButton button = new ToggleButton();
|
||||
button.getStyleClass().addAll("log-toggle", LEVELS[i].name().toLowerCase(Locale.ROOT));
|
||||
button.setStyle("-fx-background-color: " + FXUtils.toWeb(LEVELS[i].getColor()) + ";");
|
||||
button.getStyleClass().add("log-toggle");
|
||||
button.textProperty().bind(control.buttonText[i]);
|
||||
button.setSelected(true);
|
||||
control.showLevel[i].bind(button.selectedProperty());
|
||||
|
||||
@@ -153,25 +153,24 @@ public enum SVG {
|
||||
return new Group(path);
|
||||
}
|
||||
|
||||
public Node createIcon(double size) {
|
||||
public Node createIcon(ObservableValue<? extends Paint> fill, double size) {
|
||||
SVGPath p = new SVGPath();
|
||||
p.setContent(path);
|
||||
p.getStyleClass().add("svg");
|
||||
p.setContent(path);
|
||||
if (fill != null)
|
||||
p.fillProperty().bind(fill);
|
||||
|
||||
return createIcon(p, size);
|
||||
}
|
||||
|
||||
public Node createIcon() {
|
||||
public Node createIcon(Paint fill, double size) {
|
||||
SVGPath p = new SVGPath();
|
||||
p.setContent(path);
|
||||
p.getStyleClass().add("svg");
|
||||
return createIcon(p, -1);
|
||||
p.setContent(path);
|
||||
if (fill != null)
|
||||
p.fillProperty().set(fill);
|
||||
|
||||
return createIcon(p, size);
|
||||
}
|
||||
|
||||
public Node createIcon(ObservableValue<? extends Paint> color) {
|
||||
SVGPath p = new SVGPath();
|
||||
p.setContent(path);
|
||||
p.getStyleClass().add("svg");
|
||||
p.fillProperty().bind(color);
|
||||
return createIcon(p, -1);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import javafx.animation.Animation.Status;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.IndexedCell;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
@@ -51,8 +52,8 @@ final class ScrollUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static final double DEFAULT_SPEED = 1.0;
|
||||
private static final double DEFAULT_TRACK_PAD_ADJUSTMENT = 7.0;
|
||||
private ScrollUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given ScrollEvent comes from a trackpad.
|
||||
@@ -67,10 +68,16 @@ final class ScrollUtils {
|
||||
* @see ScrollEvent#getDeltaY()
|
||||
*/
|
||||
public static boolean isTrackPad(ScrollEvent event, ScrollDirection scrollDirection) {
|
||||
return switch (scrollDirection) {
|
||||
case UP, DOWN -> Math.abs(event.getDeltaY()) < 10;
|
||||
case LEFT, RIGHT -> Math.abs(event.getDeltaX()) < 10;
|
||||
};
|
||||
switch (scrollDirection) {
|
||||
case UP:
|
||||
case DOWN:
|
||||
return Math.abs(event.getDeltaY()) < 10;
|
||||
case LEFT:
|
||||
case RIGHT:
|
||||
return Math.abs(event.getDeltaX()) < 10;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +117,7 @@ final class ScrollUtils {
|
||||
* default speed value of 1.
|
||||
*/
|
||||
public static void addSmoothScrolling(ScrollPane scrollPane) {
|
||||
addSmoothScrolling(scrollPane, DEFAULT_SPEED);
|
||||
addSmoothScrolling(scrollPane, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +126,7 @@ final class ScrollUtils {
|
||||
* with a default trackPadAdjustment of 7.
|
||||
*/
|
||||
public static void addSmoothScrolling(ScrollPane scrollPane, double speed) {
|
||||
addSmoothScrolling(scrollPane, speed, DEFAULT_TRACK_PAD_ADJUSTMENT);
|
||||
addSmoothScrolling(scrollPane, speed, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,12 +143,12 @@ final class ScrollUtils {
|
||||
|
||||
/// @author Glavo
|
||||
public static void addSmoothScrolling(VirtualFlow<?> virtualFlow) {
|
||||
addSmoothScrolling(virtualFlow, DEFAULT_SPEED);
|
||||
addSmoothScrolling(virtualFlow, 1);
|
||||
}
|
||||
|
||||
/// @author Glavo
|
||||
public static void addSmoothScrolling(VirtualFlow<?> virtualFlow, double speed) {
|
||||
addSmoothScrolling(virtualFlow, speed, DEFAULT_TRACK_PAD_ADJUSTMENT);
|
||||
addSmoothScrolling(virtualFlow, speed, 7);
|
||||
}
|
||||
|
||||
/// @author Glavo
|
||||
@@ -173,16 +180,16 @@ final class ScrollUtils {
|
||||
}
|
||||
};
|
||||
if (scrollPane.getContent().getParent() != null) {
|
||||
scrollPane.getContent().getParent().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
scrollPane.getContent().getParent().addEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
scrollPane.getContent().getParent().addEventHandler(ScrollEvent.ANY, scrollHandler);
|
||||
}
|
||||
scrollPane.getContent().parentProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.removeEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
oldValue.removeEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
oldValue.removeEventHandler(ScrollEvent.ANY, scrollHandler);
|
||||
}
|
||||
if (newValue != null) {
|
||||
newValue.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
newValue.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
newValue.addEventHandler(ScrollEvent.ANY, scrollHandler);
|
||||
}
|
||||
});
|
||||
@@ -225,6 +232,7 @@ final class ScrollUtils {
|
||||
final double[] derivatives = new double[FRICTIONS.length];
|
||||
|
||||
Timeline timeline = new Timeline();
|
||||
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
|
||||
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
|
||||
final EventHandler<ScrollEvent> scrollHandler = event -> {
|
||||
if (event.getEventType() == ScrollEvent.SCROLL) {
|
||||
@@ -232,6 +240,7 @@ final class ScrollUtils {
|
||||
if (scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.RIGHT) {
|
||||
return;
|
||||
}
|
||||
scrollDirectionHolder.value = scrollDirection;
|
||||
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
|
||||
|
||||
derivatives[0] += scrollDirection.intDirection * currentSpeed;
|
||||
@@ -241,7 +250,7 @@ final class ScrollUtils {
|
||||
event.consume();
|
||||
}
|
||||
};
|
||||
virtualFlow.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
virtualFlow.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
|
||||
virtualFlow.addEventFilter(ScrollEvent.ANY, scrollHandler);
|
||||
|
||||
timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {
|
||||
@@ -253,7 +262,15 @@ final class ScrollUtils {
|
||||
}
|
||||
|
||||
double dy = derivatives[derivatives.length - 1];
|
||||
virtualFlow.scrollPixels(dy);
|
||||
|
||||
int cellCount = virtualFlow.getCellCount();
|
||||
IndexedCell<?> firstVisibleCell = virtualFlow.getFirstVisibleCell();
|
||||
double height = firstVisibleCell != null ? firstVisibleCell.getHeight() * cellCount : 0.0;
|
||||
|
||||
double delta = height > 0.0
|
||||
? dy / height
|
||||
: (scrollDirectionHolder.value == ScrollDirection.DOWN ? 0.001 : -0.001);
|
||||
virtualFlow.setPosition(Math.min(Math.max(virtualFlow.getPosition() + delta, 0), 1));
|
||||
|
||||
if (Math.abs(dy) < 0.001) {
|
||||
timeline.stop();
|
||||
@@ -261,7 +278,4 @@ final class ScrollUtils {
|
||||
}));
|
||||
timeline.setCycleCount(Animation.INDEFINITE);
|
||||
}
|
||||
|
||||
private ScrollUtils() {
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
@@ -84,10 +85,20 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
return stackPane;
|
||||
}
|
||||
|
||||
public static JFXButton createToolbarButton(String text, SVG svg, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-tool-bar-button");
|
||||
ret.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
|
||||
ret.setText(text);
|
||||
ret.setOnAction(e -> onClick.run());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-tool-bar-button");
|
||||
ret.setGraphic(wrap(svg.createIcon()));
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1)));
|
||||
ret.setText(text);
|
||||
ret.setOnAction(e -> {
|
||||
onClick.run();
|
||||
@@ -99,7 +110,8 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
public static JFXButton createDecoratorButton(String tooltip, SVG svg, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-decorator-button");
|
||||
ret.setGraphic(wrap(svg.createIcon()));
|
||||
ret.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1)));
|
||||
FXUtils.installFastTooltip(ret, tooltip);
|
||||
ret.setOnAction(e -> onClick.run());
|
||||
return ret;
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -48,7 +48,7 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
maxHeightProperty().bind(Controllers.getScene().heightProperty().multiply(0.7));
|
||||
|
||||
setHeading(new Label(i18n("update.changelog")));
|
||||
setBody(new JFXSpinner());
|
||||
setBody(new ProgressIndicator());
|
||||
|
||||
String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html";
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// @author Glavo
|
||||
public final class WindowsNativeUtils {
|
||||
|
||||
public static OptionalLong getWindowHandle(Stage stage) {
|
||||
try {
|
||||
Class<?> windowStageClass = Class.forName("com.sun.javafx.tk.quantum.WindowStage");
|
||||
Class<?> glassWindowClass = Class.forName("com.sun.glass.ui.Window");
|
||||
Class<?> tkStageClass = Class.forName("com.sun.javafx.tk.TKStage");
|
||||
|
||||
Object tkStage = MethodHandles.privateLookupIn(Window.class, MethodHandles.lookup())
|
||||
.findVirtual(Window.class, "getPeer", MethodType.methodType(tkStageClass))
|
||||
.invoke(stage);
|
||||
|
||||
MethodHandles.Lookup windowStageLookup = MethodHandles.privateLookupIn(windowStageClass, MethodHandles.lookup());
|
||||
MethodHandle getPlatformWindow = windowStageLookup.findVirtual(windowStageClass, "getPlatformWindow", MethodType.methodType(glassWindowClass));
|
||||
Object platformWindow = getPlatformWindow.invoke(tkStage);
|
||||
|
||||
long handle = (long) MethodHandles.privateLookupIn(glassWindowClass, MethodHandles.lookup())
|
||||
.findVirtual(glassWindowClass, "getNativeWindow", MethodType.methodType(long.class))
|
||||
.invoke(platformWindow);
|
||||
|
||||
return OptionalLong.of(handle);
|
||||
} catch (Throwable ex) {
|
||||
LOG.warning("Failed to get window handle", ex);
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private WindowsNativeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -68,10 +68,6 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
};
|
||||
|
||||
public AccountAdvancedListItem() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public AccountAdvancedListItem(Account account) {
|
||||
tooltip = new Tooltip();
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
|
||||
@@ -80,13 +76,9 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
|
||||
setActionButtonVisible(false);
|
||||
|
||||
if (account != null) {
|
||||
this.accountProperty().set(account);
|
||||
} else {
|
||||
FXUtils.onScroll(this, Accounts.getAccounts(),
|
||||
accounts -> accounts.indexOf(accountProperty().get()),
|
||||
Accounts::setSelectedAccount);
|
||||
}
|
||||
FXUtils.onScroll(this, Accounts.getAccounts(),
|
||||
accounts -> accounts.indexOf(account.get()),
|
||||
Accounts::setSelectedAccount);
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> accountProperty() {
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -116,10 +117,10 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
});
|
||||
btnMove.getStyleClass().add("toggle-icon4");
|
||||
if (skinnable.getAccount().isPortable()) {
|
||||
btnMove.setGraphic(SVG.PUBLIC.createIcon());
|
||||
btnMove.setGraphic(SVG.PUBLIC.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
|
||||
} else {
|
||||
btnMove.setGraphic(SVG.OUTPUT.createIcon());
|
||||
btnMove.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
|
||||
}
|
||||
spinnerMove.setContent(btnMove);
|
||||
@@ -145,7 +146,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
.start();
|
||||
});
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon());
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
|
||||
spinnerRefresh.setContent(btnRefresh);
|
||||
right.getChildren().add(spinnerRefresh);
|
||||
@@ -162,7 +163,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
}
|
||||
});
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnUpload.setGraphic(SVG.CHECKROOM.createIcon());
|
||||
btnUpload.setGraphic(SVG.CHECKROOM.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
|
||||
btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));
|
||||
spinnerUpload.setContent(btnUpload);
|
||||
@@ -174,7 +175,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
|
||||
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon());
|
||||
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
|
||||
spinnerCopyUUID.setContent(btnCopyUUID);
|
||||
right.getChildren().add(spinnerCopyUUID);
|
||||
@@ -183,7 +184,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.DELETE.createIcon());
|
||||
btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1));
|
||||
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -157,7 +158,7 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
e.consume();
|
||||
});
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(14));
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
|
||||
item.setRightGraphic(btnRemove);
|
||||
|
||||
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.game.OAuthServer;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -464,7 +465,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
JFXButton btnAddServer = new JFXButton();
|
||||
btnAddServer.setGraphic(SVG.ADD.createIcon(20));
|
||||
btnAddServer.setGraphic(SVG.ADD.createIcon(Theme.blackFill(), 20));
|
||||
btnAddServer.getStyleClass().add("toggle-icon4");
|
||||
btnAddServer.setOnAction(e -> {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
|
||||
@@ -26,6 +26,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Paint;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -108,8 +109,8 @@ public class AdvancedListBox extends ScrollPane {
|
||||
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
|
||||
item.setOnAction(e -> tabHeader.select(tab));
|
||||
|
||||
Node unselectedIcon = unselectedGraphic.createIcon(20);
|
||||
Node selectedIcon = selectedGraphic.createIcon(20);
|
||||
Node unselectedIcon = unselectedGraphic.createIcon((Paint) null, 20);
|
||||
Node selectedIcon = selectedGraphic.createIcon((Paint) null, 20);
|
||||
|
||||
TransitionPane leftGraphic = new TransitionPane();
|
||||
leftGraphic.setAlignment(Pos.CENTER);
|
||||
|
||||
@@ -21,6 +21,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -43,6 +44,7 @@ public class ClassTitle extends StackPane {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(vbox.widthProperty());
|
||||
rectangle.setHeight(1.0);
|
||||
rectangle.setFill(Color.GRAY);
|
||||
vbox.getChildren().add(rectangle);
|
||||
getChildren().setAll(vbox);
|
||||
getStyleClass().add("class-title");
|
||||
|
||||
@@ -30,7 +30,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
@@ -79,7 +79,7 @@ final class ComponentListCell extends StackPane {
|
||||
|
||||
VBox groupNode = new VBox();
|
||||
|
||||
Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(20);
|
||||
Node expandIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(Theme.blackFill(), 20);
|
||||
expandIcon.setMouseTransparent(true);
|
||||
HBox.setMargin(expandIcon, new Insets(0, 8, 0, 8));
|
||||
|
||||
@@ -99,14 +99,12 @@ final class ComponentListCell extends StackPane {
|
||||
if (!overrideHeaderLeft) {
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(list.titleProperty());
|
||||
label.getStyleClass().add("title-label");
|
||||
labelVBox.getChildren().add(label);
|
||||
|
||||
if (list.isHasSubtitle()) {
|
||||
Label subtitleLabel = new Label();
|
||||
subtitleLabel.textProperty().bind(list.subtitleProperty());
|
||||
subtitleLabel.getStyleClass().add("subtitle-label");
|
||||
subtitleLabel.textFillProperty().bind(Themes.colorSchemeProperty().getOnSurfaceVariant());
|
||||
labelVBox.getChildren().add(subtitleLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -59,7 +60,7 @@ public class FileItem extends BorderPane {
|
||||
setLeft(left);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
right.setGraphic(SVG.EDIT.createIcon(16));
|
||||
right.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 16));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
right.setOnAction(e -> onExplore());
|
||||
FXUtils.installFastTooltip(right, i18n("button.edit"));
|
||||
|
||||
@@ -27,6 +27,7 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -81,7 +82,7 @@ public class FileSelector extends HBox {
|
||||
FXUtils.bindString(customField, valueProperty());
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(15));
|
||||
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 15));
|
||||
selectButton.setOnAction(e -> {
|
||||
if (directory) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
|
||||
@@ -28,8 +28,6 @@ import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
// Referenced in root.css
|
||||
@SuppressWarnings("unused")
|
||||
public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
private ScrollBar scrollBar;
|
||||
private Region group;
|
||||
|
||||
@@ -26,8 +26,10 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -44,9 +46,29 @@ public class HintPane extends VBox {
|
||||
setFillWidth(true);
|
||||
getStyleClass().addAll("hint", type.name().toLowerCase(Locale.ROOT));
|
||||
|
||||
HBox hbox = new HBox(type.getIcon().createIcon(16), new Text(type.getDisplayName()));
|
||||
SVG svg;
|
||||
switch (type) {
|
||||
case INFO:
|
||||
svg = SVG.INFO;
|
||||
break;
|
||||
case ERROR:
|
||||
svg = SVG.ERROR;
|
||||
break;
|
||||
case SUCCESS:
|
||||
svg = SVG.CHECK_CIRCLE;
|
||||
break;
|
||||
case WARNING:
|
||||
svg = SVG.WARNING;
|
||||
break;
|
||||
case QUESTION:
|
||||
svg = SVG.HELP;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box message type " + type);
|
||||
}
|
||||
|
||||
HBox hbox = new HBox(svg.createIcon(Theme.blackFill(), 16), new Text(type.getDisplayName()));
|
||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||
hbox.setSpacing(2);
|
||||
flow.getChildren().setAll(label);
|
||||
getChildren().setAll(hbox, flow);
|
||||
label.textProperty().bind(text);
|
||||
|
||||
@@ -18,13 +18,14 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
public final class IconedMenuItem extends IconedItem {
|
||||
public class IconedMenuItem extends IconedItem {
|
||||
|
||||
public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) {
|
||||
super(icon != null ? FXUtils.limitingSize(icon.createIcon(14), 14, 14) : null, text);
|
||||
super(icon != null ? FXUtils.limitingSize(icon.createIcon(Theme.blackFill(), 14), 14, 14) : null, text);
|
||||
|
||||
getStyleClass().setAll("iconed-menu-item");
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -110,7 +111,7 @@ public class IconedTwoLineListItem extends HBox {
|
||||
if (externalLinkButton == null) {
|
||||
externalLinkButton = new JFXButton();
|
||||
externalLinkButton.getStyleClass().add("toggle-icon4");
|
||||
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon());
|
||||
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1));
|
||||
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
|
||||
}
|
||||
return externalLinkButton;
|
||||
|
||||
@@ -32,6 +32,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
@@ -53,12 +54,12 @@ public final class ImagePickerItem extends BorderPane {
|
||||
imageView.setPreserveRatio(true);
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.EDIT.createIcon(20));
|
||||
selectButton.setGraphic(SVG.EDIT.createIcon(Theme.blackFill(), 20));
|
||||
selectButton.onActionProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.CLOSE.createIcon(20));
|
||||
deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
|
||||
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/// @author Glavo
|
||||
public final class JFXCheckBoxTableCell<S, T> extends TableCell<S, T> {
|
||||
public static <S> Callback<TableColumn<S, Boolean>, TableCell<S, Boolean>> forTableColumn(
|
||||
final TableColumn<S, Boolean> column) {
|
||||
return list -> new JFXCheckBoxTableCell<>();
|
||||
}
|
||||
|
||||
private final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
private BooleanProperty booleanProperty;
|
||||
|
||||
public JFXCheckBoxTableCell() {
|
||||
this.getStyleClass().add("jfx-checkbox-table-cell");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
checkBox.disableProperty().unbind();
|
||||
} else {
|
||||
setGraphic(checkBox);
|
||||
|
||||
if (booleanProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
if (getTableColumn().getCellObservableValue(getIndex()) instanceof BooleanProperty obsValue) {
|
||||
booleanProperty = obsValue;
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty);
|
||||
}
|
||||
|
||||
checkBox.disableProperty().bind(Bindings.not(
|
||||
getTableView().editableProperty().and(
|
||||
getTableColumn().editableProperty()).and(
|
||||
editableProperty())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
@@ -26,7 +27,7 @@ public final class JFXHyperlink extends Hyperlink {
|
||||
public JFXHyperlink(String text) {
|
||||
super(text);
|
||||
|
||||
setGraphic(SVG.OPEN_IN_NEW.createIcon(16));
|
||||
setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16));
|
||||
}
|
||||
|
||||
public void setExternalLink(String externalLink) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
@@ -38,7 +39,6 @@ public class MenuUpDownButton extends Control {
|
||||
private final StringProperty text = new SimpleStringProperty(this, "text");
|
||||
|
||||
public MenuUpDownButton() {
|
||||
this.getStyleClass().add("menu-up-down-button");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,10 +78,11 @@ public class MenuUpDownButton extends Control {
|
||||
HBox content = new HBox(8);
|
||||
content.setAlignment(Pos.CENTER);
|
||||
Label label = new Label();
|
||||
label.setStyle("-fx-text-fill: black;");
|
||||
label.textProperty().bind(control.text);
|
||||
|
||||
Node up = SVG.ARROW_DROP_UP.createIcon(16);
|
||||
Node down = SVG.ARROW_DROP_DOWN.createIcon(16);
|
||||
Node up = SVG.ARROW_DROP_UP.createIcon(Theme.blackFill(), 16);
|
||||
Node down = SVG.ARROW_DROP_DOWN.createIcon(Theme.blackFill(), 16);
|
||||
|
||||
JFXButton button = new JFXButton();
|
||||
button.setGraphic(content);
|
||||
|
||||
@@ -28,6 +28,7 @@ import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -46,21 +47,11 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public final class MessageDialogPane extends HBox {
|
||||
|
||||
public enum MessageType {
|
||||
ERROR(SVG.ERROR),
|
||||
INFO(SVG.INFO),
|
||||
WARNING(SVG.WARNING),
|
||||
QUESTION(SVG.HELP),
|
||||
SUCCESS(SVG.CHECK_CIRCLE);
|
||||
|
||||
private final SVG icon;
|
||||
|
||||
MessageType(SVG icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public SVG getIcon() {
|
||||
return icon;
|
||||
}
|
||||
ERROR,
|
||||
INFO,
|
||||
WARNING,
|
||||
QUESTION,
|
||||
SUCCESS;
|
||||
|
||||
public String getDisplayName() {
|
||||
return i18n("message." + name().toLowerCase(Locale.ROOT));
|
||||
@@ -80,7 +71,27 @@ public final class MessageDialogPane extends HBox {
|
||||
graphic.setTranslateY(10);
|
||||
graphic.setMinSize(40, 40);
|
||||
graphic.setMaxSize(40, 40);
|
||||
graphic.setGraphic(type.getIcon().createIcon(40));
|
||||
SVG svg;
|
||||
switch (type) {
|
||||
case INFO:
|
||||
svg = SVG.INFO;
|
||||
break;
|
||||
case ERROR:
|
||||
svg = SVG.ERROR;
|
||||
break;
|
||||
case SUCCESS:
|
||||
svg = SVG.CHECK_CIRCLE;
|
||||
break;
|
||||
case WARNING:
|
||||
svg = SVG.WARNING;
|
||||
break;
|
||||
case QUESTION:
|
||||
svg = SVG.HELP;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box message type " + type);
|
||||
}
|
||||
graphic.setGraphic(svg.createIcon(Theme.blackFill(), 40));
|
||||
|
||||
VBox vbox = new VBox();
|
||||
HBox.setHgrow(vbox, Priority.ALWAYS);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXColorPicker;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
@@ -31,15 +30,12 @@ import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.theme.ThemeColor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -305,27 +301,17 @@ public final class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
|
||||
public static final class PaintOption<T> extends Option<T> {
|
||||
private final ColorPicker colorPicker = new JFXColorPicker();
|
||||
private final ColorPicker colorPicker = new ColorPicker();
|
||||
|
||||
public PaintOption(String title, T data) {
|
||||
super(title, data);
|
||||
}
|
||||
|
||||
public PaintOption<T> setCustomColors(List<Color> colors) {
|
||||
colorPicker.getCustomColors().setAll(colors);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintOption<T> bindBidirectional(Property<Paint> property) {
|
||||
FXUtils.bindPaint(colorPicker, property);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintOption<T> bindThemeColorBidirectional(Property<ThemeColor> property) {
|
||||
ThemeColor.bindBidirectional(colorPicker, property);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createItem(ToggleGroup group) {
|
||||
BorderPane pane = new BorderPane();
|
||||
|
||||
@@ -51,9 +51,8 @@ public class OptionToggleButton extends StackPane {
|
||||
Label titleLabel = new Label();
|
||||
titleLabel.textProperty().bind(title);
|
||||
Label subtitleLabel = new Label();
|
||||
subtitleLabel.setWrapText(true);
|
||||
subtitleLabel.setMouseTransparent(true);
|
||||
subtitleLabel.getStyleClass().add("subtitle");
|
||||
subtitleLabel.setWrapText(true);
|
||||
subtitleLabel.textProperty().bind(subtitle);
|
||||
pane.setCenter(left);
|
||||
left.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
@@ -38,7 +38,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -51,7 +50,7 @@ public class RipplerContainer extends StackPane {
|
||||
private static final Duration DURATION = Duration.millis(200);
|
||||
|
||||
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL, this, "ripplerFill", null);
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);
|
||||
|
||||
private final StackPane buttonContainer = new StackPane();
|
||||
@@ -137,10 +136,7 @@ public class RipplerContainer extends StackPane {
|
||||
}
|
||||
|
||||
private void interpolateBackground(double frac) {
|
||||
Color onSurface = Themes.getColorScheme().getOnSurface();
|
||||
setBackground(new Background(new BackgroundFill(
|
||||
Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04),
|
||||
CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, frac * 0.04), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
}
|
||||
|
||||
protected void updateChildren() {
|
||||
|
||||
@@ -50,8 +50,6 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
private final TaskListPane taskListPane;
|
||||
|
||||
public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) {
|
||||
this.getStyleClass().add("task-executor-dialog-layout");
|
||||
|
||||
FXUtils.setLimitWidth(this, 500);
|
||||
FXUtils.setLimitHeight(this, 300);
|
||||
|
||||
@@ -76,7 +74,6 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
bottom.setLeft(lblProgress);
|
||||
|
||||
btnCancel = new JFXButton(i18n("button.cancel"));
|
||||
btnCancel.getStyleClass().add("dialog-cancel");
|
||||
bottom.setRight(btnCancel);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
@@ -309,7 +310,7 @@ public final class TaskListPane extends StackPane {
|
||||
}
|
||||
|
||||
private void updateLeftIcon(StageNode.Status status) {
|
||||
left.getChildren().setAll(status.svg.createIcon(STATUS_ICON_SIZE));
|
||||
left.getChildren().setAll(status.svg.createIcon(Theme.blackFill(), STATUS_ICON_SIZE));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -373,8 +373,9 @@ public class DecoratorController {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
// FIXME: Get WorldPage working first, and revisit this later
|
||||
page.closePage();
|
||||
if (page.isPageCloseable()) {
|
||||
page.closePage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import javafx.stage.Stage;
|
||||
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -233,19 +233,19 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
{
|
||||
JFXButton btnHelp = new JFXButton();
|
||||
btnHelp.setFocusTraversable(false);
|
||||
btnHelp.setGraphic(SVG.HELP.createIcon(Themes.titleFillProperty()));
|
||||
btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnHelp.getStyleClass().add("jfx-decorator-button");
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
btnMin.setFocusTraversable(false);
|
||||
btnMin.setGraphic(SVG.MINIMIZE.createIcon(Themes.titleFillProperty()));
|
||||
btnMin.setGraphic(SVG.MINIMIZE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnMin.getStyleClass().add("jfx-decorator-button");
|
||||
btnMin.setOnAction(e -> skinnable.minimize());
|
||||
|
||||
JFXButton btnClose = new JFXButton();
|
||||
btnClose.setFocusTraversable(false);
|
||||
btnClose.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
|
||||
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnClose.getStyleClass().add("jfx-decorator-button");
|
||||
btnClose.setOnAction(e -> skinnable.close());
|
||||
|
||||
@@ -265,8 +265,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
private Node createNavBar(Decorator skinnable, double leftPaneWidth, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) {
|
||||
BorderPane navBar = new BorderPane();
|
||||
navBar.getStyleClass().add("navigation-bar");
|
||||
|
||||
{
|
||||
HBox navLeft = new HBox();
|
||||
navLeft.setAlignment(Pos.CENTER_LEFT);
|
||||
@@ -275,8 +273,9 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
if (canBack) {
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setFocusTraversable(false);
|
||||
backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Themes.titleFillProperty()));
|
||||
backNavButton.setGraphic(SVG.ARROW_BACK.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||
backNavButton.visibleProperty().set(canBack);
|
||||
|
||||
@@ -286,13 +285,14 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
if (canClose) {
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
closeNavButton.setFocusTraversable(false);
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
if (showCloseAsHome)
|
||||
closeNavButton.setGraphic(SVG.HOME.createIcon(Themes.titleFillProperty()));
|
||||
closeNavButton.setGraphic(SVG.HOME.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Themes.titleFillProperty()));
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
|
||||
navLeft.getChildren().add(closeNavButton);
|
||||
}
|
||||
@@ -304,7 +304,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
BorderPane center = new BorderPane();
|
||||
if (title != null) {
|
||||
Label titleLabel = new Label();
|
||||
titleLabel.textFillProperty().bind(Themes.titleFillProperty());
|
||||
BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT);
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
if (titleNode == null) {
|
||||
@@ -334,8 +333,9 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
HBox navRight = new HBox();
|
||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton refreshNavButton = new JFXButton();
|
||||
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Themes.titleFillProperty()));
|
||||
refreshNavButton.setGraphic(SVG.REFRESH.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
refreshNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
|
||||
@@ -200,10 +200,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
tab.select(modpackTab, false);
|
||||
}
|
||||
|
||||
public void showResourcepackDownloads() {
|
||||
tab.select(resourcePackTab, false);
|
||||
}
|
||||
|
||||
public DownloadListPage showModDownloads() {
|
||||
tab.select(modTab, false);
|
||||
return modTab.getNode();
|
||||
|
||||
@@ -126,7 +126,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
exported.setBackgroundImageType(config().getBackgroundImageType());
|
||||
exported.setBackgroundImage(config().getBackgroundImage());
|
||||
exported.setThemeColor(config().getThemeColor());
|
||||
exported.setTheme(config().getTheme());
|
||||
exported.setDownloadType(config().getDownloadType());
|
||||
exported.setPreferredLoginType(config().getPreferredLoginType());
|
||||
exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -109,9 +108,9 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
private void onNext() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("modpack.wizard.step.initialization.save"));
|
||||
if (controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {
|
||||
if (controller.getSettings().get(MODPACK_TYPE) == ModpackTypeSelectionPage.MODPACK_TYPE_MODRINTH) {
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.mrpack"));
|
||||
fileChooser.setInitialFileName(name.get() + (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS ? "" : ".mrpack"));
|
||||
fileChooser.setInitialFileName(name.get() + ".mrpack");
|
||||
} else {
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
fileChooser.setInitialFileName(name.get() + ".zip");
|
||||
|
||||
@@ -21,14 +21,12 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem;
|
||||
@@ -107,12 +105,6 @@ public final class AboutPage extends StackPane {
|
||||
getChildren().setAll(scrollPane);
|
||||
}
|
||||
|
||||
private static Image loadImage(String url) {
|
||||
return url.startsWith("/")
|
||||
? FXUtils.newBuiltinImage(url)
|
||||
: new Image(url);
|
||||
}
|
||||
|
||||
private static ComponentList loadIconedTwoLineList(String path) {
|
||||
ComponentList componentList = new ComponentList();
|
||||
|
||||
@@ -130,14 +122,10 @@ public final class AboutPage extends StackPane {
|
||||
IconedTwoLineListItem item = new IconedTwoLineListItem();
|
||||
|
||||
if (obj.has("image")) {
|
||||
JsonElement image = obj.get("image");
|
||||
if (image.isJsonPrimitive()) {
|
||||
item.setImage(loadImage(image.getAsString()));
|
||||
} else if (image.isJsonObject()) {
|
||||
item.imageProperty().bind(Bindings.when(Themes.darkModeProperty())
|
||||
.then(loadImage(image.getAsJsonObject().get("dark").getAsString()))
|
||||
.otherwise(loadImage(image.getAsJsonObject().get("light").getAsString())));
|
||||
}
|
||||
String image = obj.get("image").getAsString();
|
||||
item.setImage(image.startsWith("/")
|
||||
? FXUtils.newBuiltinImage(image)
|
||||
: new Image(image));
|
||||
}
|
||||
|
||||
if (obj.has("title"))
|
||||
|
||||
@@ -78,7 +78,7 @@ public class DownloadSettingsPage extends StackPane {
|
||||
versionListSourcePane.setRight(cboVersionListSource);
|
||||
FXUtils.setLimitWidth(cboVersionListSource, 400);
|
||||
|
||||
cboVersionListSource.getItems().setAll(DownloadProviders.AUTO_PROVIDERS.keySet());
|
||||
cboVersionListSource.getItems().setAll(DownloadProviders.providersById.keySet());
|
||||
selectedItemPropertyFor(cboVersionListSource).bindBidirectional(config().versionListSourceProperty());
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class DownloadSettingsPage extends StackPane {
|
||||
downloadSourcePane.setRight(cboDownloadSource);
|
||||
FXUtils.setLimitWidth(cboDownloadSource, 420);
|
||||
|
||||
cboDownloadSource.getItems().setAll(DownloadProviders.DIRECT_PROVIDERS.keySet());
|
||||
cboDownloadSource.getItems().setAll(DownloadProviders.rawProviders.keySet());
|
||||
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem;
|
||||
@@ -43,41 +41,32 @@ public class FeedbackPage extends SpinnerPane {
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
setContent(scrollPane);
|
||||
|
||||
ComponentList groups = new ComponentList();
|
||||
ComponentList community = new ComponentList();
|
||||
{
|
||||
IconedTwoLineListItem users = new IconedTwoLineListItem();
|
||||
users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
users.setTitle(i18n("contact.chat.qq_group"));
|
||||
users.setSubtitle(i18n("contact.chat.qq_group.statement"));
|
||||
users.setTitle(i18n("feedback.qq_group"));
|
||||
users.setSubtitle(i18n("feedback.qq_group.statement"));
|
||||
users.setExternalLink(Metadata.GROUPS_URL);
|
||||
|
||||
IconedTwoLineListItem github = new IconedTwoLineListItem();
|
||||
github.setImage(FXUtils.newBuiltinImage("/assets/img/github.png"));
|
||||
github.setTitle(i18n("feedback.github"));
|
||||
github.setSubtitle(i18n("feedback.github.statement"));
|
||||
github.setExternalLink("https://github.com/HMCL-dev/HMCL/issues/new/choose");
|
||||
|
||||
IconedTwoLineListItem discord = new IconedTwoLineListItem();
|
||||
discord.setImage(FXUtils.newBuiltinImage("/assets/img/discord.png"));
|
||||
discord.setTitle(i18n("contact.chat.discord"));
|
||||
discord.setSubtitle(i18n("contact.chat.discord.statement"));
|
||||
discord.setTitle(i18n("feedback.discord"));
|
||||
discord.setSubtitle(i18n("feedback.discord.statement"));
|
||||
discord.setExternalLink("https://discord.gg/jVvC7HfM6U");
|
||||
|
||||
groups.getContent().setAll(users, discord);
|
||||
}
|
||||
|
||||
ComponentList feedback = new ComponentList();
|
||||
{
|
||||
IconedTwoLineListItem github = new IconedTwoLineListItem();
|
||||
github.imageProperty().bind(Bindings.when(Themes.darkModeProperty())
|
||||
.then(FXUtils.newBuiltinImage("/assets/img/github-white.png"))
|
||||
.otherwise(FXUtils.newBuiltinImage("/assets/img/github.png")));
|
||||
github.setTitle(i18n("contact.feedback.github"));
|
||||
github.setSubtitle(i18n("contact.feedback.github.statement"));
|
||||
github.setExternalLink("https://github.com/HMCL-dev/HMCL/issues/new/choose");
|
||||
|
||||
feedback.getContent().setAll(github);
|
||||
community.getContent().setAll(users, github, discord);
|
||||
}
|
||||
|
||||
content.getChildren().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("contact.chat")),
|
||||
groups,
|
||||
ComponentList.createComponentListTitle(i18n("contact.feedback")),
|
||||
feedback
|
||||
ComponentList.createComponentListTitle(i18n("feedback.channel")),
|
||||
community
|
||||
);
|
||||
|
||||
this.setContent(content);
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
@@ -262,7 +263,7 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(24), 24, 24));
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
revealButton.setOnAction(e -> control.onReveal());
|
||||
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
|
||||
|
||||
@@ -275,12 +276,12 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
null
|
||||
));
|
||||
if (java.isManaged()) {
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_FOREVER.createIcon(24), 24, 24));
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.uninstall"));
|
||||
if (JavaRuntime.CURRENT_JAVA != null && java.getBinary().equals(JavaRuntime.CURRENT_JAVA.getBinary()))
|
||||
removeButton.setDisable(true);
|
||||
} else {
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(24), 24, 24));
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.disable"));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
@@ -159,7 +160,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(24), 24, 24));
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
revealButton.setOnAction(e -> skinnable.onReveal());
|
||||
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
|
||||
|
||||
@@ -168,7 +169,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(24), 24, 24));
|
||||
removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
removeButton.setOnAction(e -> skinnable.onRemove());
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
|
||||
|
||||
@@ -176,7 +177,7 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
} else {
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(24), 24, 24));
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
restoreButton.setOnAction(e -> skinnable.onRestore());
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD)
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP, SVG.HELP_FILL)
|
||||
.addNavigationDrawerTab(tab, feedbackTab, i18n("contact"), SVG.FEEDBACK, SVG.FEEDBACK_FILL)
|
||||
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.FEEDBACK, SVG.FEEDBACK_FILL)
|
||||
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFO, SVG.INFO_FILL);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
@@ -50,9 +50,9 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -121,7 +121,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
titleLabel.setRotate(180);
|
||||
}
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
titleLabel.textFillProperty().bind(Themes.titleFillProperty());
|
||||
titleNode.getChildren().setAll(titleIcon, titleLabel);
|
||||
|
||||
state.setValue(new State(null, titleNode, false, false, true));
|
||||
@@ -153,7 +152,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
});
|
||||
btnHide.getStyleClass().add("announcement-close-button");
|
||||
btnHide.setGraphic(SVG.CLOSE.createIcon(20));
|
||||
btnHide.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20));
|
||||
titleBar.setRight(btnHide);
|
||||
|
||||
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
|
||||
@@ -188,17 +187,20 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
StackPane.setAlignment(hBox, Pos.CENTER_LEFT);
|
||||
StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));
|
||||
{
|
||||
Label lblIcon = new Label();
|
||||
lblIcon.setGraphic(SVG.UPDATE.createIcon(Theme.whiteFill(), 20));
|
||||
|
||||
TwoLineListItem prompt = new TwoLineListItem();
|
||||
prompt.setSubtitle(i18n("update.bubble.subtitle"));
|
||||
prompt.setPickOnBounds(false);
|
||||
prompt.titleProperty().bind(BindingMapping.of(latestVersionProperty()).map(latestVersion ->
|
||||
latestVersion == null ? "" : i18n("update.bubble.title", latestVersion.getVersion())));
|
||||
|
||||
hBox.getChildren().setAll(SVG.UPDATE.createIcon(20), prompt);
|
||||
hBox.getChildren().setAll(lblIcon, prompt);
|
||||
}
|
||||
|
||||
JFXButton closeUpdateButton = new JFXButton();
|
||||
closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(10));
|
||||
closeUpdateButton.setGraphic(SVG.CLOSE.createIcon(Theme.whiteFill(), 10));
|
||||
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
|
||||
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
|
||||
StackPane.setMargin(closeUpdateButton, new Insets(5));
|
||||
@@ -275,7 +277,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
menuButton.setOnAction(e -> onMenu());
|
||||
menuButton.setClip(new Rectangle(211, -100, 100, 200));
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.ARROW_DROP_UP.createIcon(30);
|
||||
Node svg = SVG.ARROW_DROP_UP.createIcon(Theme.foregroundFillBinding(), 30);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(6);
|
||||
@@ -346,7 +348,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private void launch() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, profile.getSelectedVersion());
|
||||
Versions.launch(profile, profile.getSelectedVersion(), null);
|
||||
}
|
||||
|
||||
private void launchNoGame() {
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXSlider;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -32,11 +35,12 @@ import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontSmoothingType;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.FontManager;
|
||||
import org.jackhuang.hmcl.theme.ThemeColor;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
@@ -44,7 +48,6 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -78,25 +81,6 @@ public class PersonalizationPage extends StackPane {
|
||||
getChildren().setAll(scrollPane);
|
||||
|
||||
ComponentList themeList = new ComponentList();
|
||||
{
|
||||
BorderPane brightnessPane = new BorderPane();
|
||||
themeList.getContent().add(brightnessPane);
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.brightness"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
|
||||
brightnessPane.setLeft(left);
|
||||
|
||||
JFXComboBox<String> cboBrightness = new JFXComboBox<>(FXCollections.observableArrayList(
|
||||
FXUtils.DARK_MODE != null
|
||||
? List.of("auto", "light", "dark")
|
||||
: List.of("light", "dark")
|
||||
));
|
||||
cboBrightness.setConverter(FXUtils.stringConverter(name -> i18n("settings.launcher.brightness." + name)));
|
||||
cboBrightness.valueProperty().bindBidirectional(config().themeBrightnessProperty());
|
||||
brightnessPane.setRight(cboBrightness);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane themePane = new BorderPane();
|
||||
themeList.getContent().add(themePane);
|
||||
@@ -109,9 +93,10 @@ public class PersonalizationPage extends StackPane {
|
||||
themeColorPickerContainer.setMinHeight(30);
|
||||
themePane.setRight(themeColorPickerContainer);
|
||||
|
||||
ColorPicker picker = new JFXColorPicker();
|
||||
picker.getCustomColors().setAll(ThemeColor.STANDARD_COLORS.stream().map(ThemeColor::color).toList());
|
||||
ThemeColor.bindBidirectional(picker, config().themeColorProperty());
|
||||
ColorPicker picker = new ColorPicker(Color.web(Theme.getTheme().getColor()));
|
||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||
picker.setOnAction(e ->
|
||||
config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue()))));
|
||||
themeColorPickerContainer.getChildren().setAll(picker);
|
||||
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
||||
}
|
||||
@@ -282,7 +267,7 @@ public class PersonalizationPage extends StackPane {
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
|
||||
clearButton.setOnAction(e -> cboFont.setValue(null));
|
||||
|
||||
hBox.getChildren().setAll(cboFont, clearButton);
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.scene.input.MouseButton;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -41,7 +39,6 @@ import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
@@ -67,8 +64,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
private MainPage mainPage = null;
|
||||
@@ -147,13 +144,6 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
// first item in left sidebar
|
||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||
accountListItem.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.SECONDARY) {
|
||||
if (!Accounts.getAccounts().isEmpty())
|
||||
showAccountListPopupMenu(accountListItem);
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||
|
||||
// second item in left sidebar
|
||||
@@ -244,39 +234,13 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
|
||||
.add(launcherSettingsItem)
|
||||
.add(terracottaItem)
|
||||
.addNavigationDrawerItem(i18n("contact.chat"), SVG.CHAT, () -> {
|
||||
Controllers.getSettingsPage().showFeedback();
|
||||
Controllers.navigate(Controllers.getSettingsPage());
|
||||
});
|
||||
.addNavigationDrawerItem(i18n("chat"), SVG.CHAT, () -> FXUtils.openLink(Metadata.GROUPS_URL));
|
||||
|
||||
// the root page, with the sidebar in left, navigator in center.
|
||||
setLeft(sideBar);
|
||||
setCenter(getSkinnable().getMainPage());
|
||||
}
|
||||
|
||||
public void showAccountListPopupMenu(
|
||||
AccountAdvancedListItem accountListItem
|
||||
) {
|
||||
PopupMenu popupMenu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(popupMenu);
|
||||
AdvancedListBox scrollPane = new AdvancedListBox();
|
||||
scrollPane.getStyleClass().add("no-padding");
|
||||
scrollPane.setPrefWidth(220);
|
||||
scrollPane.setPrefHeight(-1);
|
||||
scrollPane.setMaxHeight(260);
|
||||
|
||||
for (Account account : Accounts.getAccounts()) {
|
||||
AccountAdvancedListItem item = new AccountAdvancedListItem(account);
|
||||
item.setOnAction(e -> {
|
||||
Accounts.setSelectedAccount(account);
|
||||
popup.hide();
|
||||
});
|
||||
scrollPane.add(item);
|
||||
}
|
||||
|
||||
popupMenu.getContent().add(scrollPane);
|
||||
popup.show(accountListItem, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, accountListItem.getWidth(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkedModpack = false;
|
||||
|
||||
@@ -17,40 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentSublist;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
@@ -63,7 +45,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
@@ -71,254 +52,79 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class SettingsPage extends ScrollPane {
|
||||
public final class SettingsPage extends SettingsView {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ToggleGroup updateChannelGroup;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final InvalidationListener updateListener;
|
||||
private InvalidationListener updateListener;
|
||||
|
||||
public SettingsPage() {
|
||||
this.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setPadding(new Insets(32, 10, 32, 10));
|
||||
this.setContent(rootPane);
|
||||
FXUtils.smoothScrolling(this);
|
||||
// ==== Languages ====
|
||||
cboLanguage.getItems().setAll(SupportedLocale.getSupportedLocales());
|
||||
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
{
|
||||
StackPane sponsorPane = new StackPane();
|
||||
sponsorPane.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(sponsorPane, this::onSponsor);
|
||||
sponsorPane.setPadding(new Insets(8, 0, 8, 0));
|
||||
disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());
|
||||
// ====
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
fileCommonLocationSublist.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
ColumnConstraints col = new ColumnConstraints();
|
||||
col.setHgrow(Priority.SOMETIMES);
|
||||
col.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
// ==== Update ====
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
updateListener = any -> {
|
||||
btnUpdate.setVisible(UpdateChecker.isOutdated());
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col);
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSub.setText(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row);
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSub.setText(i18n("update.checking"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
{
|
||||
Label label = new Label(i18n("sponsor.hmcl"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
} else {
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
sponsorPane.getChildren().setAll(gridPane);
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
|
||||
{
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
ToggleGroup updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
|
||||
updateChannel.set(UpdateChannel.getChannel());
|
||||
|
||||
final Label lblUpdate;
|
||||
final Label lblUpdateSub;
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdate.getStyleClass().add("title-label");
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnUpdate = new JFXButton();
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.UPDATE.createIcon(20));
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
|
||||
updateListener = any -> {
|
||||
btnUpdate.setVisible(UpdateChecker.isOutdated());
|
||||
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSub.setText(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSub.setText(i18n("update.checking"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll("title-label");
|
||||
} else {
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll("title-label");
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox();
|
||||
content.setSpacing(8);
|
||||
|
||||
JFXRadioButton chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
JFXRadioButton chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
|
||||
updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
|
||||
Label noteWrapper = new Label(i18n("update.note"));
|
||||
VBox.setMargin(noteWrapper, new Insets(10, 0, 0, 0));
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
|
||||
updatePane.getContent().add(content);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
OptionToggleButton previewPane = new OptionToggleButton();
|
||||
previewPane.setTitle(i18n("update.preview"));
|
||||
previewPane.setSubtitle(i18n("update.preview.subtitle"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
|
||||
updateChannel.set(UpdateChannel.getChannel());
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
updateChannel.addListener(checkUpdateListener);
|
||||
previewPane.selectedProperty().addListener(checkUpdateListener);
|
||||
|
||||
settingsPane.getContent().add(previewPane);
|
||||
}
|
||||
|
||||
{
|
||||
MultiFileItem<EnumCommonDirectory> fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumCommonDirectory.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.cache_directory.choose"))
|
||||
.setDirectory(true)
|
||||
.bindBidirectional(config().commonDirectoryProperty())
|
||||
));
|
||||
|
||||
ComponentSublist fileCommonLocationSublist = new ComponentSublist();
|
||||
fileCommonLocationSublist.getContent().add(fileCommonLocation);
|
||||
fileCommonLocationSublist.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocationSublist.setHasSubtitle(true);
|
||||
fileCommonLocationSublist.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
|
||||
settingsPane.getContent().add(fileCommonLocationSublist);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane languagePane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.language"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
languagePane.setLeft(left);
|
||||
|
||||
SupportedLocale currentLocale = I18n.getLocale();
|
||||
JFXComboBox<SupportedLocale> cboLanguage = new JFXComboBox<>();
|
||||
cboLanguage.setConverter(stringConverter(locale -> {
|
||||
if (locale.isDefault())
|
||||
return locale.getDisplayName(currentLocale);
|
||||
else if (locale.isSameLanguage(currentLocale))
|
||||
return locale.getDisplayName(locale);
|
||||
else
|
||||
return locale.getDisplayName(currentLocale) + " - " + locale.getDisplayName(locale);
|
||||
}));
|
||||
cboLanguage.getItems().setAll(SupportedLocale.getSupportedLocales());
|
||||
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||
|
||||
FXUtils.setLimitWidth(cboLanguage, 300);
|
||||
languagePane.setRight(cboLanguage);
|
||||
|
||||
settingsPane.getContent().add(languagePane);
|
||||
}
|
||||
|
||||
{
|
||||
OptionToggleButton disableAutoGameOptionsPane = new OptionToggleButton();
|
||||
disableAutoGameOptionsPane.setTitle(i18n("settings.launcher.disable_auto_game_options"));
|
||||
disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());
|
||||
|
||||
settingsPane.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.debug"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
|
||||
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
|
||||
openLogFolderButton.setOnAction(e -> openLogFolder());
|
||||
openLogFolderButton.getStyleClass().add("jfx-button-border");
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnAction(e -> onExportLogs());
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, logButton);
|
||||
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
|
||||
debugPane.setRight(buttonBox);
|
||||
|
||||
settingsPane.getContent().add(debugPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().add(settingsPane);
|
||||
}
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
updateChannel.addListener(checkUpdateListener);
|
||||
previewPane.selectedProperty().addListener(checkUpdateListener);
|
||||
// ====
|
||||
}
|
||||
|
||||
private void openLogFolder() {
|
||||
FXUtils.openFolder(LOG.getLogFile().getParent());
|
||||
}
|
||||
|
||||
private void onUpdate() {
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
RemoteVersion target = UpdateChecker.getLatestVersion();
|
||||
if (target == null) {
|
||||
return;
|
||||
@@ -362,7 +168,8 @@ public final class SettingsPage extends ScrollPane {
|
||||
}
|
||||
}
|
||||
|
||||
private void onExportLogs() {
|
||||
@Override
|
||||
protected void onExportLogs() {
|
||||
thread(() -> {
|
||||
String nameBase = "hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss"));
|
||||
List<Path> recentLogFiles = LOG.findRecentLogFiles(5);
|
||||
@@ -442,11 +249,13 @@ public final class SettingsPage extends ScrollPane {
|
||||
});
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
@Override
|
||||
protected void onSponsor() {
|
||||
FXUtils.openLink("https://github.com/HMCL-dev/HMCL");
|
||||
}
|
||||
|
||||
private void clearCacheDirectory() {
|
||||
@Override
|
||||
protected void clearCacheDirectory() {
|
||||
String commonDirectory = Settings.instance().getCommonDirectory();
|
||||
if (commonDirectory != null) {
|
||||
FileUtils.cleanDirectoryQuietly(Path.of(commonDirectory, "cache"));
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentSublist;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public abstract class SettingsView extends StackPane {
|
||||
protected final JFXComboBox<SupportedLocale> cboLanguage;
|
||||
protected final OptionToggleButton disableAutoGameOptionsPane;
|
||||
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
protected final ComponentSublist fileCommonLocationSublist;
|
||||
protected final Label lblUpdate;
|
||||
protected final Label lblUpdateSub;
|
||||
protected final JFXRadioButton chkUpdateStable;
|
||||
protected final JFXRadioButton chkUpdateDev;
|
||||
protected final JFXButton btnUpdate;
|
||||
protected final OptionToggleButton previewPane;
|
||||
protected final ScrollPane scroll;
|
||||
|
||||
public SettingsView() {
|
||||
scroll = new ScrollPane();
|
||||
getChildren().setAll(scroll);
|
||||
scroll.setStyle("-fx-font-size: 14;");
|
||||
scroll.setFitToWidth(true);
|
||||
|
||||
{
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setPadding(new Insets(32, 10, 32, 10));
|
||||
{
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
{
|
||||
StackPane sponsorPane = new StackPane();
|
||||
sponsorPane.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(sponsorPane, this::onSponsor);
|
||||
sponsorPane.setPadding(new Insets(8, 0, 8, 0));
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
|
||||
ColumnConstraints col = new ColumnConstraints();
|
||||
col.setHgrow(Priority.SOMETIMES);
|
||||
col.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col);
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row);
|
||||
|
||||
{
|
||||
Label label = new Label(i18n("sponsor.hmcl"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
|
||||
sponsorPane.getChildren().setAll(gridPane);
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20));
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox();
|
||||
content.setSpacing(8);
|
||||
|
||||
chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
|
||||
TextFlow noteWrapper = new TextFlow(new Text(i18n("update.note")));
|
||||
VBox.setMargin(noteWrapper, new Insets(10, 0, 0, 0));
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
|
||||
updatePane.getContent().add(content);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
previewPane = new OptionToggleButton();
|
||||
previewPane.setTitle(i18n("update.preview"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
FXUtils.installFastTooltip(previewPane, i18n("update.preview.tooltip"));
|
||||
|
||||
settingsPane.getContent().add(previewPane);
|
||||
}
|
||||
|
||||
{
|
||||
fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocationSublist = new ComponentSublist();
|
||||
fileCommonLocationSublist.getContent().add(fileCommonLocation);
|
||||
fileCommonLocationSublist.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocationSublist.setHasSubtitle(true);
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumCommonDirectory.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.cache_directory.choose"))
|
||||
.setDirectory(true)
|
||||
.bindBidirectional(config().commonDirectoryProperty())
|
||||
));
|
||||
|
||||
{
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
}
|
||||
|
||||
settingsPane.getContent().add(fileCommonLocationSublist);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane languagePane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.language"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
languagePane.setLeft(left);
|
||||
|
||||
SupportedLocale currentLocale = I18n.getLocale();
|
||||
cboLanguage = new JFXComboBox<>();
|
||||
cboLanguage.setConverter(stringConverter(locale -> {
|
||||
if (locale.isDefault())
|
||||
return locale.getDisplayName(currentLocale);
|
||||
else if (locale.isSameLanguage(currentLocale))
|
||||
return locale.getDisplayName(locale);
|
||||
else
|
||||
return locale.getDisplayName(currentLocale) + " - " + locale.getDisplayName(locale);
|
||||
}));
|
||||
|
||||
FXUtils.setLimitWidth(cboLanguage, 300);
|
||||
languagePane.setRight(cboLanguage);
|
||||
|
||||
settingsPane.getContent().add(languagePane);
|
||||
}
|
||||
|
||||
{
|
||||
disableAutoGameOptionsPane = new OptionToggleButton();
|
||||
disableAutoGameOptionsPane.setTitle(i18n("settings.launcher.disable_auto_game_options"));
|
||||
|
||||
settingsPane.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.debug"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
|
||||
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
|
||||
openLogFolderButton.setOnAction(e -> openLogFolder());
|
||||
openLogFolderButton.getStyleClass().add("jfx-button-border");
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnAction(e -> onExportLogs());
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, logButton);
|
||||
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
|
||||
debugPane.setRight(buttonBox);
|
||||
|
||||
settingsPane.getContent().add(debugPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().add(settingsPane);
|
||||
}
|
||||
scroll.setContent(rootPane);
|
||||
}
|
||||
}
|
||||
|
||||
public void openLogFolder() {
|
||||
FXUtils.openFolder(LOG.getLogFile().getParent());
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
|
||||
protected abstract void onExportLogs();
|
||||
|
||||
protected abstract void onSponsor();
|
||||
|
||||
protected abstract void clearCacheDirectory();
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
@@ -63,7 +64,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
btnRemove.setOnAction(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(14));
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
|
||||
@@ -40,11 +40,13 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.terracotta.TerracottaManager;
|
||||
@@ -57,10 +59,14 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentSublist;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
@@ -167,7 +173,6 @@ public class TerracottaControllerPage extends StackPane {
|
||||
progressProperty.set(0);
|
||||
|
||||
TextFlow body = FXUtils.segmentToTextFlow(i18n("terracotta.confirm.desc"), Controllers::onHyperlinkAction);
|
||||
body.getStyleClass().add("terracotta-hint");
|
||||
body.setLineSpacing(4);
|
||||
|
||||
LineButton download = LineButton.of();
|
||||
@@ -211,7 +216,6 @@ public class TerracottaControllerPage extends StackPane {
|
||||
progressProperty.set(1);
|
||||
|
||||
TextFlow flow = FXUtils.segmentToTextFlow(i18n("terracotta.confirm.desc"), Controllers::onHyperlinkAction);
|
||||
flow.getStyleClass().add("terracotta-hint");
|
||||
flow.setLineSpacing(4);
|
||||
|
||||
LineButton host = LineButton.of();
|
||||
@@ -232,10 +236,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
MessageDialogPane.MessageType.QUESTION
|
||||
).addAction(i18n("version.launch"), () -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, profile.getSelectedVersion(), launcherHelper -> {
|
||||
launcherHelper.setKeep();
|
||||
launcherHelper.setDisableOfflineSkin();
|
||||
});
|
||||
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
|
||||
}).addCancel(i18n("terracotta.status.waiting.host.launch.skip"), () -> {
|
||||
TerracottaState.HostScanning s1 = TerracottaManager.setScanning();
|
||||
if (s1 != null) {
|
||||
@@ -286,7 +287,6 @@ public class TerracottaControllerPage extends StackPane {
|
||||
progressProperty.set(-1);
|
||||
|
||||
TextFlow body = FXUtils.segmentToTextFlow(i18n("terracotta.status.scanning.desc"), Controllers::onHyperlinkAction);
|
||||
body.getStyleClass().add("terracotta-hint");
|
||||
body.setLineSpacing(4);
|
||||
|
||||
LineButton room = LineButton.of();
|
||||
@@ -530,13 +530,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
UI_STATE.addListener(new WeakChangeListener<>(listener));
|
||||
|
||||
VBox content = new VBox(10);
|
||||
content.getChildren().add(ComponentList.createComponentListTitle(i18n("terracotta.status")));
|
||||
if (!LocaleUtils.IS_CHINA_MAINLAND) {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
hintPane.setText(i18n("terracotta.unsupported.region"));
|
||||
content.getChildren().add(hintPane);
|
||||
}
|
||||
content.getChildren().add(transition);
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("terracotta.status")), transition);
|
||||
content.setPadding(new Insets(10));
|
||||
content.setFillWidth(true);
|
||||
|
||||
@@ -565,7 +559,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
Label description = new Label(link.description().getText(I18n.getLocale().getCandidateLocales()));
|
||||
HBox placeholder = new HBox();
|
||||
HBox.setHgrow(placeholder, Priority.ALWAYS);
|
||||
Node icon = SVG.OPEN_IN_NEW.createIcon(16);
|
||||
Node icon = SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), 16);
|
||||
node.getChildren().setAll(description, placeholder, icon);
|
||||
|
||||
String url = link.link();
|
||||
@@ -631,9 +625,9 @@ public class TerracottaControllerPage extends StackPane {
|
||||
}
|
||||
|
||||
HBox secondLine = new HBox();
|
||||
secondLine.getStyleClass().add("second-line");
|
||||
{
|
||||
Text text = new Text(button.subTitle.get());
|
||||
text.setFill(new Color(0, 0, 0, 0.5));
|
||||
|
||||
TextFlow lblSubtitle = new TextFlow(text);
|
||||
lblSubtitle.getStyleClass().add("subtitle");
|
||||
@@ -676,11 +670,11 @@ public class TerracottaControllerPage extends StackPane {
|
||||
}
|
||||
|
||||
public void setLeftIcon(SVG left) {
|
||||
this.left.set(left.createIcon(28));
|
||||
this.left.set(left.createIcon(Theme.blackFill(), 28));
|
||||
}
|
||||
|
||||
public void setRightIcon(SVG right) {
|
||||
this.right.set(right.createIcon(28));
|
||||
this.right.set(right.createIcon(Theme.blackFill(), 28));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
|
||||
@@ -70,10 +71,7 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, profile.getSelectedVersion(), launcherHelper -> {
|
||||
launcherHelper.setKeep();
|
||||
launcherHelper.setDisableOfflineSkin();
|
||||
});
|
||||
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
|
||||
}, item -> {
|
||||
instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(),
|
||||
instanceName -> item.setSubtitle(StringUtils.isNotBlank(instanceName) ? instanceName : i18n("version.empty"))
|
||||
|
||||
@@ -35,7 +35,6 @@ import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
@@ -61,24 +60,16 @@ import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
@@ -532,7 +523,6 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
var iconCache = new WeakHashMap<String, WeakReference<CompletableFuture<Image>>>();
|
||||
listView.setCellFactory(x -> new FloatListCell<>(listView) {
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final ImageView imageView = new ImageView();
|
||||
@@ -559,64 +549,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
dataItem.getCategories().stream()
|
||||
.map(category -> getSkinnable().getLocalizedCategory(category))
|
||||
.forEach(content::addTag);
|
||||
loadIcon(dataItem);
|
||||
}
|
||||
|
||||
private void loadIcon(RemoteMod mod) {
|
||||
if (StringUtils.isBlank(mod.getIconUrl())) {
|
||||
imageView.setImage(null);
|
||||
return;
|
||||
}
|
||||
|
||||
WeakReference<CompletableFuture<Image>> cacheRef = iconCache.get(mod.getIconUrl());
|
||||
CompletableFuture<Image> cache;
|
||||
if (cacheRef != null && (cache = cacheRef.get()) != null) {
|
||||
loadIcon(cache, mod.getIconUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
URI iconUrl = NetworkUtils.toURIOrNull(mod.getIconUrl());
|
||||
if (iconUrl == null) {
|
||||
imageView.setImage(null);
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture<Image> future = new CompletableFuture<>();
|
||||
WeakReference<CompletableFuture<Image>> futureRef = new WeakReference<>(future);
|
||||
iconCache.put(mod.getIconUrl(), futureRef);
|
||||
|
||||
FXUtils.getRemoteImageTask(iconUrl, 80, 80, true, true)
|
||||
.whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
future.complete(result);
|
||||
} else {
|
||||
LOG.warning("Failed to load image from " + iconUrl, exception);
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}).start();
|
||||
loadIcon(future, mod.getIconUrl());
|
||||
}
|
||||
|
||||
private void loadIcon(@NotNull CompletableFuture<Image> future,
|
||||
@NotNull String iconUrl) {
|
||||
Image image;
|
||||
try {
|
||||
image = future.getNow(null);
|
||||
} catch (CancellationException | CompletionException ignored) {
|
||||
imageView.setImage(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
imageView.setImage(image);
|
||||
} else {
|
||||
imageView.setImage(null);
|
||||
future.thenAcceptAsync(result -> {
|
||||
RemoteMod item = getItem();
|
||||
if (item != null && iconUrl.equals(item.getIconUrl())) {
|
||||
this.imageView.setImage(result);
|
||||
}
|
||||
}, Schedulers.javafx());
|
||||
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(dataItem.getIconUrl(), 80, 80, true, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -393,15 +394,15 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Alpha:
|
||||
content.addTag(i18n("mods.channel.alpha"));
|
||||
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(24));
|
||||
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE.createIcon(Theme.blackFill(), 24));
|
||||
break;
|
||||
case Beta:
|
||||
content.addTag(i18n("mods.channel.beta"));
|
||||
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(24));
|
||||
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE.createIcon(Theme.blackFill(), 24));
|
||||
break;
|
||||
case Release:
|
||||
content.addTag(i18n("mods.channel.release"));
|
||||
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(24));
|
||||
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE.createIcon(Theme.blackFill(), 24));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,12 +85,8 @@ public class GameListItem extends Control {
|
||||
Versions.openFolder(profile, version);
|
||||
}
|
||||
|
||||
public void testGame() {
|
||||
Versions.testGame(profile, version);
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
Versions.launch(profile, version);
|
||||
Versions.testGame(profile, version);
|
||||
}
|
||||
|
||||
public void modifyGameSettings() {
|
||||
|
||||
@@ -26,6 +26,7 @@ import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
@@ -44,7 +45,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.testGame(), popup),
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), () -> currentSkinnable.generateLaunchScript(), popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.SETTINGS, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup),
|
||||
@@ -80,17 +81,17 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
JFXButton btnUpgrade = new JFXButton();
|
||||
btnUpgrade.setOnAction(e -> skinnable.update());
|
||||
btnUpgrade.getStyleClass().add("toggle-icon4");
|
||||
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(24), 24, 24));
|
||||
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnLaunch = new JFXButton();
|
||||
btnLaunch.setOnAction(e -> skinnable.testGame());
|
||||
btnLaunch.setOnAction(e -> skinnable.launch());
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
|
||||
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(24), 24, 24));
|
||||
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
|
||||
right.getChildren().add(btnLaunch);
|
||||
}
|
||||
@@ -105,7 +106,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(24), 24, 24));
|
||||
btnManage.setGraphic(FXUtils.limitingSize(SVG.MORE_VERT.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
|
||||
right.getChildren().add(btnManage);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -42,7 +43,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.VersionLoadable {
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.VersionLoadable, TransitionPane.Cacheable {
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private Version version;
|
||||
|
||||
@@ -39,13 +39,13 @@ public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
|
||||
.map(type ->
|
||||
Task.supplyAsync(() -> mod.checkUpdates(gameVersion, type.getRemoteModRepository()))
|
||||
.setSignificance(TaskSignificance.MAJOR)
|
||||
.setName(String.format("%s (%s)", mod.getFileName(), type.name())).withCounter("update.checking")
|
||||
.setName(String.format("%s (%s)", mod.getFileName(), type.name())).withCounter("mods.check_updates")
|
||||
)
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
setStage("update.checking");
|
||||
setStage("mods.check_updates");
|
||||
getProperties().put("total", dependents.size() * RemoteMod.Type.values().length);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
@@ -49,7 +50,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware {
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware, TransitionPane.Cacheable {
|
||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
@@ -247,8 +248,8 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
Controllers.navigateForward(new ModUpdatesPage(modManager, result));
|
||||
}
|
||||
})
|
||||
.withStagesHint(Collections.singletonList("update.checking")),
|
||||
i18n("mods.check_updates"), TaskCancellationAction.NORMAL);
|
||||
.withStagesHint(Collections.singletonList("mods.check_updates")),
|
||||
i18n("update.checking"), TaskCancellationAction.NORMAL);
|
||||
|
||||
if (profile.getRepository().isModpack(instanceId)) {
|
||||
Controllers.confirm(
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -151,7 +152,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createToolbarButton2(i18n("mods.add"), SVG.ADD, skinnable::add),
|
||||
createToolbarButton2(i18n("button.reveal_dir"), SVG.FOLDER_OPEN, skinnable::openModFolder),
|
||||
createToolbarButton2(i18n("mods.check_updates.button"), SVG.UPDATE, skinnable::checkUpdates),
|
||||
createToolbarButton2(i18n("mods.check_updates"), SVG.UPDATE, skinnable::checkUpdates),
|
||||
createToolbarButton2(i18n("download"), SVG.DOWNLOAD, skinnable::download),
|
||||
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar))
|
||||
);
|
||||
@@ -590,15 +591,15 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
imageView.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(24), 24, 24));
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
|
||||
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(24), 24, 24));
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
|
||||
infoButton.getStyleClass().add("toggle-icon4");
|
||||
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24));
|
||||
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||
|
||||
container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton);
|
||||
|
||||
|
||||
@@ -18,15 +18,16 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
@@ -37,7 +38,6 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTableCell;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -72,10 +72,9 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
TableColumn<ModUpdateObject, Boolean> enabledColumn = new TableColumn<>();
|
||||
var allEnabledBox = new JFXCheckBox();
|
||||
enabledColumn.setStyle("-fx-alignment: CENTER;");
|
||||
CheckBox allEnabledBox = new CheckBox();
|
||||
enabledColumn.setGraphic(allEnabledBox);
|
||||
enabledColumn.setCellFactory(JFXCheckBoxTableCell.forTableColumn(enabledColumn));
|
||||
enabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(enabledColumn));
|
||||
setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);
|
||||
enabledColumn.setEditable(true);
|
||||
enabledColumn.setMaxWidth(40);
|
||||
@@ -102,7 +101,6 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
TableView<ModUpdateObject> table = new TableView<>(objects);
|
||||
table.setEditable(true);
|
||||
table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn);
|
||||
setMargin(table, new Insets(10, 10, 5, 10));
|
||||
|
||||
setCenter(table);
|
||||
|
||||
@@ -113,13 +111,12 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
JFXButton exportListButton = FXUtils.newRaisedButton(i18n("button.export"));
|
||||
exportListButton.setOnAction(e -> exportList());
|
||||
|
||||
JFXButton nextButton = FXUtils.newRaisedButton(i18n("mods.check_updates.confirm"));
|
||||
JFXButton nextButton = FXUtils.newRaisedButton(i18n("mods.check_updates.update"));
|
||||
nextButton.setOnAction(e -> updateMods());
|
||||
|
||||
JFXButton cancelButton = FXUtils.newRaisedButton(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent()));
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
onEscPressed(table, cancelButton::fire);
|
||||
|
||||
actions.getChildren().setAll(exportListButton, nextButton, cancelButton);
|
||||
setBottom(actions);
|
||||
@@ -150,7 +147,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
Controllers.dialog(i18n("install.success"));
|
||||
}
|
||||
}),
|
||||
i18n("mods.check_updates"),
|
||||
i18n("mods.check_updates.update"),
|
||||
TaskCancellationAction.NORMAL);
|
||||
}
|
||||
|
||||
@@ -279,7 +276,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
private final List<LocalModFile> failedMods = new ArrayList<>();
|
||||
|
||||
ModUpdateTask(ModManager modManager, List<Pair<LocalModFile, RemoteMod.Version>> mods) {
|
||||
setStage("mods.check_updates.confirm");
|
||||
setStage("mods.check_updates.update");
|
||||
getProperties().put("total", mods.size());
|
||||
|
||||
this.dependents = new ArrayList<>();
|
||||
@@ -311,7 +308,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
failedMods.add(local);
|
||||
}
|
||||
})
|
||||
.withCounter("mods.check_updates.confirm"));
|
||||
.withCounter("mods.check_updates.update"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.resourcepack.ResourcepackFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcepackListPage extends ListPageBase<ResourcepackListPage.ResourcepackInfoObject> implements VersionPage.VersionLoadable {
|
||||
private Path resourcepackDirectory;
|
||||
|
||||
public ResourcepackListPage() {
|
||||
FXUtils.applyDragListener(this, file -> file.getFileName().toString().endsWith(".zip"), this::addFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ResourcepackListPageSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.resourcepackDirectory = profile.getRepository().getResourcepacksDirectory(version);
|
||||
|
||||
try {
|
||||
if (!Files.exists(resourcepackDirectory)) {
|
||||
Files.createDirectories(resourcepackDirectory);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to create resourcepack directory" + resourcepackDirectory, e);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (resourcepackDirectory == null || !Files.isDirectory(resourcepackDirectory)) return;
|
||||
setLoading(true);
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
try (Stream<Path> stream = Files.list(resourcepackDirectory)) {
|
||||
return stream.sorted(Comparator.comparing(FileUtils::getName))
|
||||
.flatMap(item -> {
|
||||
try {
|
||||
return Stream.of(ResourcepackFile.parse(item)).filter(Objects::nonNull).map(ResourcepackInfoObject::new);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load resourcepack " + item, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
getItems().setAll(result);
|
||||
} else {
|
||||
LOG.warning("Failed to load resourcepacks", exception);
|
||||
getItems().clear();
|
||||
}
|
||||
setLoading(false);
|
||||
})).start();
|
||||
}
|
||||
|
||||
public void addFiles(List<Path> files) {
|
||||
if (resourcepackDirectory == null) return;
|
||||
|
||||
try {
|
||||
for (Path file : files) {
|
||||
Path target = resourcepackDirectory.resolve(file.getFileName());
|
||||
if (!Files.exists(target)) {
|
||||
Files.copy(file, target);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to add resourcepacks", e);
|
||||
Controllers.dialog(i18n("resourcepack.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void onAddFiles() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("resourcepack.add"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("resourcepack"), "*.zip"));
|
||||
List<Path> files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
if (files != null && !files.isEmpty()) {
|
||||
addFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
Controllers.getDownloadPage().showResourcepackDownloads();
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
}
|
||||
|
||||
private static final class ResourcepackListPageSkin extends SkinBase<ResourcepackListPage> {
|
||||
private final JFXListView<ResourcepackInfoObject> listView;
|
||||
|
||||
private ResourcepackListPageSkin(ResourcepackListPage control) {
|
||||
super(control);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
pane.setPadding(new Insets(10));
|
||||
pane.getStyleClass().addAll("notice-pane");
|
||||
|
||||
ComponentList root = new ComponentList();
|
||||
root.getStyleClass().add("no-padding");
|
||||
listView = new JFXListView<>();
|
||||
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.setAlignment(Pos.CENTER_LEFT);
|
||||
toolbar.setPickOnBounds(false);
|
||||
toolbar.getChildren().setAll(
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, control::refresh),
|
||||
createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, control::onAddFiles),
|
||||
createToolbarButton2(i18n("resourcepack.download"), SVG.DOWNLOAD, control::onDownload)
|
||||
);
|
||||
root.getContent().add(toolbar);
|
||||
|
||||
SpinnerPane center = new SpinnerPane();
|
||||
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||
center.getStyleClass().add("large-spinner-pane");
|
||||
center.loadingProperty().bind(control.loadingProperty());
|
||||
|
||||
Holder<Object> lastCell = new Holder<>();
|
||||
listView.setCellFactory(x -> new ResourcepackListCell(listView, lastCell, control));
|
||||
Bindings.bindContent(listView.getItems(), control.getItems());
|
||||
|
||||
center.setContent(listView);
|
||||
root.getContent().add(center);
|
||||
|
||||
pane.getChildren().setAll(root);
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourcepackInfoObject {
|
||||
private final ResourcepackFile file;
|
||||
private WeakReference<Image> iconCache;
|
||||
|
||||
public ResourcepackInfoObject(ResourcepackFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public ResourcepackFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
Image getIcon() {
|
||||
Image image = null;
|
||||
if (iconCache != null && (image = iconCache.get()) != null) {
|
||||
return image;
|
||||
}
|
||||
byte[] iconData = file.getIcon();
|
||||
if (iconData != null) {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {
|
||||
image = new Image(inputStream, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resourcepack icon " + file.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 ||
|
||||
(Math.abs(image.getWidth() - image.getHeight()) >= 1)) {
|
||||
image = FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
iconCache = new WeakReference<>(image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResourcepackListCell extends MDListCell<ResourcepackInfoObject> {
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final JFXButton btnReveal = new JFXButton();
|
||||
private final JFXButton btnDelete = new JFXButton();
|
||||
private final ResourcepackListPage page;
|
||||
|
||||
public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, Holder<Object> lastCell, ResourcepackListPage page) {
|
||||
super(listView, lastCell);
|
||||
|
||||
this.page = page;
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
HBox left = new HBox(8);
|
||||
left.setAlignment(Pos.CENTER);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
left.getChildren().add(imageView);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
FXUtils.setLimitWidth(left, 48);
|
||||
root.setLeft(left);
|
||||
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
root.setCenter(content);
|
||||
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
|
||||
HBox right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
root.setRight(right);
|
||||
|
||||
getContainer().getChildren().add(new RipplerContainer(root));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(ResourcepackListPage.ResourcepackInfoObject item, boolean empty) {
|
||||
if (empty || item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourcepackFile file = item.getFile();
|
||||
imageView.setImage(item.getIcon());
|
||||
|
||||
content.setTitle(file.getName());
|
||||
LocalModFile.Description description = file.getDescription();
|
||||
content.setSubtitle(description != null ? description.toString() : "");
|
||||
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getPath()));
|
||||
|
||||
btnDelete.setOnAction(event ->
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
|
||||
() -> onDelete(file), null));
|
||||
}
|
||||
|
||||
private void onDelete(ResourcepackFile file) {
|
||||
try {
|
||||
if (Files.isDirectory(file.getPath())) {
|
||||
FileUtils.deleteDirectory(file.getPath());
|
||||
} else {
|
||||
Files.delete(file.getPath());
|
||||
}
|
||||
page.refresh();
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("resourcepack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
LOG.warning("Failed to delete resourcepack", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,11 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.schematic.LitematicFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -59,7 +61,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable {
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable, TransitionPane.Cacheable {
|
||||
|
||||
private static String translateAuthorName(String author) {
|
||||
if (I18n.isUseChinese() && "hsds".equals(author)) {
|
||||
@@ -239,7 +241,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
StackPane icon = new StackPane();
|
||||
icon.setPrefSize(size, size);
|
||||
icon.setMaxSize(size, size);
|
||||
icon.getChildren().add(getIcon().createIcon(size));
|
||||
icon.getChildren().add(getIcon().createIcon(Theme.blackFill(), size));
|
||||
return icon;
|
||||
}
|
||||
|
||||
@@ -579,12 +581,12 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
|
||||
btnReveal.setOnAction(event -> item.onReveal());
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), -1));
|
||||
btnDelete.setOnAction(event ->
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
|
||||
item::onDelete, null));
|
||||
|
||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user