Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持修改主页背景的不透明度 #3205

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public static Config fromJson(String json) throws JsonParseException {
@SerializedName("bgurl")
private StringProperty backgroundImageUrl = new SimpleStringProperty();

@SerializedName("bgImageOpacity")
private DoubleProperty backgroundImageOpacity = new SimpleDoubleProperty(1.00);

@SerializedName("commonDirType")
private ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);

Expand Down Expand Up @@ -277,6 +280,18 @@ public void setBackgroundImageUrl(String backgroundImageUrl) {
this.backgroundImageUrl.set(backgroundImageUrl);
}

public Double getBackgroundImageOpacity(){
return backgroundImageOpacity.get();
}

public void setBackgroundImageOpacity(double backgroundImageOpacity){
this.backgroundImageOpacity.set(backgroundImageOpacity);
}

public DoubleProperty backgroundImageOpacityProperty() {
return backgroundImageOpacity;
}

public EnumCommonDirectory getCommonDirType() {
return commonDirType.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
Expand Down Expand Up @@ -68,8 +71,8 @@
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.io.FileUtils.getExtension;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;

public class DecoratorController {
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
Expand Down Expand Up @@ -98,14 +101,17 @@ public DecoratorController(Stage stage, Node mainPage) {

// Setup background
decorator.setContentBackground(getBackground());
changeBackgroundListener = o -> {
final int currentCount = ++this.changeBackgroundCount;
CompletableFuture.supplyAsync(this::getBackground, Schedulers.io())
.thenAcceptAsync(background -> {
if (this.changeBackgroundCount == currentCount)
decorator.setContentBackground(background);
}, Schedulers.javafx());
changeBackgroundListener = o -> updateBackground();
changeSettingsListener = (observable, oldValue, newValue) -> {
double newValueDouble = newValue.doubleValue();
if (Double.isNaN(lastValue) || Math.abs(newValueDouble - lastValue) > threshold) {
updateBackground();
lastValue = newValueDouble;
} else if (config().getBackgroundImageOpacity() == 0 || config().getBackgroundImageOpacity() == 1) {
updateBackground();
}
};
config().backgroundImageOpacityProperty().addListener(changeSettingsListener);
WeakInvalidationListener weakListener = new WeakInvalidationListener(changeBackgroundListener);
config().backgroundImageTypeProperty().addListener(weakListener);
config().backgroundImageProperty().addListener(weakListener);
Expand Down Expand Up @@ -155,8 +161,24 @@ public Decorator getDecorator() {
//FXThread
private int changeBackgroundCount = 0;

private double lastValue = Double.NaN;
private final double threshold = 0.05;//Determine how often the background is refreshed when the opacity is modified


@SuppressWarnings("FieldCanBeLocal") // Strong reference
private final InvalidationListener changeBackgroundListener;
@SuppressWarnings("FieldCanBeLocal")
private final ChangeListener<Number> changeSettingsListener;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应当使用 ReferenceHolder

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我略微看了一下你的代码,感觉有点问题。之后再详细看


private void updateBackground() {
LOG.info("updateBackground");
final int currentCount = ++this.changeBackgroundCount;
CompletableFuture.supplyAsync(this::getBackground, Schedulers.io())
.thenAcceptAsync(background -> {
if (this.changeBackgroundCount == currentCount)
decorator.setContentBackground(background);
}, Schedulers.javafx());
}

private Background getBackground() {
EnumBackgroundImage imageType = config().getBackgroundImageType();
Expand Down Expand Up @@ -197,7 +219,47 @@ private Background getBackground() {
if (image == null) {
image = loadDefaultBackgroundImage();
}
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
return createBackgroundWithOpacity(image, config().getBackgroundImageOpacity());
}

private Background createBackgroundWithOpacity(Image image, double opacity) {
if (opacity == 0){
return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY));
}
if (opacity == 1) {
return new Background(new BackgroundImage(
image,
BackgroundRepeat.NO_REPEAT,
BackgroundRepeat.NO_REPEAT,
BackgroundPosition.DEFAULT,
new BackgroundSize(800, 480, false, false, true, true)
));
}
LOG.info(String.valueOf(opacity));
BackgroundImage backgroundImage = getImageWithOpacity(image, opacity);

return new Background(backgroundImage);
}

private static BackgroundImage getImageWithOpacity(Image image, double opacity) {
WritableImage tempImage = new WritableImage((int) image.getWidth(), (int) image.getHeight());
PixelReader pixelReader = image.getPixelReader();
PixelWriter pixelWriter = tempImage.getPixelWriter();
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
Color color = pixelReader.getColor(x, y);
Color newColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getOpacity() * opacity);
pixelWriter.setColor(x, y, newColor);
}
}

return new BackgroundImage(
tempImage,
BackgroundRepeat.NO_REPEAT,
BackgroundRepeat.NO_REPEAT,
BackgroundPosition.DEFAULT,
new BackgroundSize(800, 480, false, false, true, true)
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui.main;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.effects.JFXDepthManager;
import javafx.application.Platform;
Expand All @@ -28,10 +29,7 @@
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
Expand All @@ -43,7 +41,10 @@
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
Expand Down Expand Up @@ -126,6 +127,66 @@ public PersonalizationPage() {
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("launcher.background")), componentList);
}

{
VBox bgSettings = new VBox(16);
bgSettings.getStyleClass().add("card-non-transparent");
{
HBox hbox = new HBox(8);
hbox.setAlignment(Pos.CENTER);
hbox.setPadding(new Insets(0, 0, 0, 10));

Label label1 = new Label(i18n("settings.launcher.background.settings.opacity"));
Label label2 = new Label("%");

double opa = config().getBackgroundImageOpacity();
JFXSlider slider = new JFXSlider(0, 1, opa);
HBox.setHgrow(slider, Priority.ALWAYS);

JFXTextField textOpacity = new JFXTextField();
textOpacity.setText(BigDecimal.valueOf(opa * 100).setScale(2, RoundingMode.HALF_UP).toString());
textOpacity.setPrefWidth(60);

AtomicReference<Double> lastValidOpacity = new AtomicReference<>(slider.getValue());
slider.valueProperty().addListener((observable, oldValue, newValue) -> {
double opacity = newValue.doubleValue();
textOpacity.setText(BigDecimal.valueOf(opacity * 100).setScale(2, RoundingMode.HALF_UP).toString());
lastValidOpacity.set(opacity);
config().setBackgroundImageOpacity(opacity);
});
textOpacity.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue){
try {
String text = textOpacity.getText().trim();
double opacity = Double.parseDouble(text) / 100;
if (opacity >= 0 && opacity <= 1) {
slider.setValue(opacity);
} else if (opacity < 0) {
slider.setValue(0);
textOpacity.setText("0.00");
} else if (opacity > 1) {
slider.setValue(1);
textOpacity.setText("100.00");
}
} catch (NumberFormatException ignored) {
slider.setValue(lastValidOpacity.get());
textOpacity.setText(BigDecimal.valueOf(lastValidOpacity.get() * 100).setScale(2, RoundingMode.HALF_UP).toString());
}
}
});

slider.setValueFactory(slider1 -> Bindings.createStringBinding(() -> String.format("%.2f", slider1.getValue()), slider1.valueProperty()));

HBox.setMargin(label2, new Insets(0, 10, 0, 0));

hbox.getChildren().setAll(label1, slider, textOpacity, label2);
bgSettings.getChildren().add(hbox);
}
//hide the opacity setting when selecting a translucency type
config().backgroundImageTypeProperty().addListener((observable, oldValue, newValue) -> bgSettings.setVisible(newValue != EnumBackgroundImage.TRANSLUCENT));
content.getChildren().add(bgSettings);
}


{
ComponentList logPane = new ComponentSublist();
logPane.setTitle(i18n("settings.launcher.log"));
Expand Down
1 change: 1 addition & 0 deletions HMCL/src/main/resources/assets/lang/I18N.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@ settings.launcher.theme=Theme
settings.launcher.title_transparent=Transparent titlebar
settings.launcher.turn_off_animations=Turn off animation (applies after restart)
settings.launcher.version_list_source=Version List
settings.launcher.background.settings.opacity=Opacity

settings.memory=Memory
settings.memory.allocate.auto=%1$.1f GB Minimum / %2$.1f GB Allocated
Expand Down
1 change: 1 addition & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@ settings.launcher.theme=主題
settings.launcher.title_transparent=標題欄透明
settings.launcher.turn_off_animations=關閉動畫 (重啟後生效)
settings.launcher.version_list_source=版本列表來源
settings.launcher.background.settings.opacity=不透明度

settings.memory=遊戲記憶體
settings.memory.allocate.auto=最低分配 %1$.1f GB / 實際分配 %2$.1f GB
Expand Down
1 change: 1 addition & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,7 @@ settings.launcher.theme=主题
settings.launcher.title_transparent=标题栏透明
settings.launcher.turn_off_animations=关闭动画 (重启后生效)
settings.launcher.version_list_source=版本列表源
settings.launcher.background.settings.opacity=不透明度

settings.memory=游戏内存
settings.memory.allocate.auto=最低分配 %1$.1f GB / 实际分配 %2$.1f GB
Expand Down