mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-08-03 11:26:38 -04:00
Merge c1749cdf2e9b1459f4982545407c7e2d5238eda8 into 9969dc60c5278340b6b9a4d7facdde620e99d1f5
This commit is contained in:
commit
939f2330dd
@ -87,6 +87,9 @@ public final class Config implements Observable {
|
||||
@SerializedName("bgurl")
|
||||
private StringProperty backgroundImageUrl = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("bgImageOpacity")
|
||||
private IntegerProperty backgroundImageOpacity = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("commonDirType")
|
||||
private ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
@ -274,6 +277,18 @@ public final class Config implements Observable {
|
||||
this.backgroundImageUrl.set(backgroundImageUrl);
|
||||
}
|
||||
|
||||
public int getBackgroundImageOpacity() {
|
||||
return backgroundImageOpacity.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImageOpacity(int backgroundImageOpacity) {
|
||||
this.backgroundImageOpacity.set(backgroundImageOpacity);
|
||||
}
|
||||
|
||||
public IntegerProperty backgroundImageOpacityProperty() {
|
||||
return backgroundImageOpacity;
|
||||
}
|
||||
|
||||
public EnumCommonDirectory getCommonDirType() {
|
||||
return commonDirType.get();
|
||||
}
|
||||
|
@ -22,5 +22,5 @@ public enum EnumBackgroundImage {
|
||||
CUSTOM,
|
||||
CLASSIC,
|
||||
NETWORK,
|
||||
TRANSLUCENT
|
||||
TRANSLUCENT,
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ import javafx.event.EventHandler;
|
||||
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.input.MouseButton;
|
||||
@ -55,6 +58,7 @@ import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -73,8 +77,8 @@ import static java.util.stream.Collectors.toList;
|
||||
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";
|
||||
@ -124,18 +128,12 @@ public class DecoratorController {
|
||||
|
||||
// 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();
|
||||
WeakInvalidationListener weakListener = new WeakInvalidationListener(changeBackgroundListener);
|
||||
config().backgroundImageTypeProperty().addListener(weakListener);
|
||||
config().backgroundImageProperty().addListener(weakListener);
|
||||
config().backgroundImageUrlProperty().addListener(weakListener);
|
||||
config().backgroundImageOpacityProperty().addListener(weakListener);
|
||||
|
||||
// pass key events to current dialog / current page
|
||||
decorator.addEventFilter(KeyEvent.ANY, e -> {
|
||||
@ -196,6 +194,16 @@ public class DecoratorController {
|
||||
@SuppressWarnings("FieldCanBeLocal") // Strong reference
|
||||
private final InvalidationListener changeBackgroundListener;
|
||||
|
||||
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();
|
||||
|
||||
@ -224,12 +232,45 @@ public class DecoratorController {
|
||||
image = newBuiltinImage("/assets/img/background-classic.jpg");
|
||||
break;
|
||||
case TRANSLUCENT:
|
||||
return new Background(new BackgroundFill(new Color(1, 1, 1, 0.5), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
return new Background(new BackgroundFill(new Color(1, 1, 1, Lang.clamp(0, config().getBackgroundImageOpacity(), 100) / 100.), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
}
|
||||
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, int opacity) {
|
||||
if (opacity <= 0) {
|
||||
return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
} else if (opacity >= 100) {
|
||||
return new Background(new BackgroundImage(
|
||||
image,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundPosition.DEFAULT,
|
||||
new BackgroundSize(800, 480, false, false, true, true)
|
||||
));
|
||||
} else {
|
||||
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 / 100);
|
||||
pixelWriter.setColor(x, y, newColor);
|
||||
}
|
||||
}
|
||||
|
||||
return new Background(new BackgroundImage(
|
||||
tempImage,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
BackgroundPosition.DEFAULT,
|
||||
new BackgroundSize(800, 480, false, false, true, true)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,10 +19,12 @@ package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
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;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
@ -30,10 +32,7 @@ import javafx.geometry.Pos;
|
||||
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 javafx.scene.text.FontSmoothingType;
|
||||
@ -56,6 +55,20 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class PersonalizationPage extends StackPane {
|
||||
|
||||
private static int snapOpacity(double val) {
|
||||
if (val <= 0) {
|
||||
return 0;
|
||||
} else if (Double.isNaN(val) || val >= 100.) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
int prevTick = (int) (val / 5);
|
||||
int prevTickValue = prevTick * 5;
|
||||
int nextTickValue = (prevTick + 1) * 5;
|
||||
|
||||
return (val - prevTickValue) > (nextTickValue - val) ? nextTickValue : prevTickValue;
|
||||
}
|
||||
|
||||
public PersonalizationPage() {
|
||||
VBox content = new VBox(10);
|
||||
content.setPadding(new Insets(10));
|
||||
@ -127,7 +140,46 @@ public class PersonalizationPage extends StackPane {
|
||||
.then(i18n("launcher.background.default"))
|
||||
.otherwise(config().backgroundImageProperty()));
|
||||
|
||||
componentList.getContent().add(backgroundItem);
|
||||
HBox opacityItem = new HBox(8);
|
||||
{
|
||||
opacityItem.setAlignment(Pos.CENTER);
|
||||
|
||||
Label label = new Label(i18n("settings.launcher.background.settings.opacity"));
|
||||
|
||||
JFXSlider slider = new JFXSlider(0, 100, config().getBackgroundImageOpacity());
|
||||
slider.setShowTickMarks(true);
|
||||
slider.setMajorTickUnit(10);
|
||||
slider.setMinorTickCount(1);
|
||||
slider.setBlockIncrement(5);
|
||||
slider.setSnapToTicks(true);
|
||||
HBox.setHgrow(slider, Priority.ALWAYS);
|
||||
|
||||
Label textOpacity = new Label();
|
||||
|
||||
StringBinding valueBinding = Bindings.createStringBinding(() -> ((int) slider.getValue()) + "%", slider.valueProperty());
|
||||
textOpacity.textProperty().bind(valueBinding);
|
||||
slider.setValueFactory(s -> valueBinding);
|
||||
|
||||
slider.valueProperty().addListener((observable, oldValue, newValue) ->
|
||||
config().setBackgroundImageOpacity(snapOpacity(newValue.doubleValue())));
|
||||
|
||||
config().backgroundImageTypeProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != EnumBackgroundImage.TRANSLUCENT && oldValue != EnumBackgroundImage.TRANSLUCENT)
|
||||
return;
|
||||
|
||||
final int opacity = snapOpacity(slider.getValue());
|
||||
if (newValue == EnumBackgroundImage.TRANSLUCENT) {
|
||||
if (opacity == 100)
|
||||
slider.setValue(50);
|
||||
} else {
|
||||
slider.setValue(100);
|
||||
}
|
||||
});
|
||||
|
||||
opacityItem.getChildren().setAll(label, slider, textOpacity);
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(backgroundItem, opacityItem);
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("launcher.background")), componentList);
|
||||
}
|
||||
|
||||
|
@ -1353,6 +1353,7 @@ settings.launcher.theme=Theme
|
||||
settings.launcher.title_transparent=Transparent Titlebar
|
||||
settings.launcher.turn_off_animations=Disable 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 GiB Minimum / %2$.1f GiB Allocated
|
||||
|
@ -1150,6 +1150,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 GiB / 實際分配 %2$.1f GiB
|
||||
|
@ -1160,6 +1160,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 GiB / 实际分配 %2$.1f GiB
|
||||
|
Loading…
x
Reference in New Issue
Block a user