优化 TwoLineListItem (#5447)

This commit is contained in:
Glavo
2026-02-06 21:47:30 +08:00
committed by GitHub
parent 2b8e8306d7
commit 792412b4c7
2 changed files with 104 additions and 64 deletions

View File

@@ -18,126 +18,166 @@
package org.jackhuang.hmcl.ui.construct; package org.jackhuang.hmcl.ui.construct;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.AggregatedObservableList;
public class TwoLineListItem extends VBox { public class TwoLineListItem extends VBox {
private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private static final String DEFAULT_STYLE_CLASS = "two-line-list-item";
private static Label createTagLabel(String tag) { private final HBox firstLine;
Label tagLabel = new Label(); private HBox secondLine;
tagLabel.setText(tag);
HBox.setMargin(tagLabel, new Insets(0, 8, 0, 0));
return tagLabel;
}
private final StringProperty title = new SimpleStringProperty(this, "title");
private final ObservableList<Label> tags = FXCollections.observableArrayList();
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
private final Label lblSubtitle;
private final Label lblTitle; private final Label lblTitle;
private Label lblSubtitle;
private final AggregatedObservableList<Node> firstLineChildren; public TwoLineListItem() {
getStyleClass().add(DEFAULT_STYLE_CLASS);
setMouseTransparent(true);
lblTitle = new Label();
lblTitle.getStyleClass().add("title");
this.firstLine = new HBox(lblTitle);
firstLine.getStyleClass().add("first-line");
this.getChildren().setAll(firstLine);
}
public TwoLineListItem(String titleString, String subtitleString) { public TwoLineListItem(String titleString, String subtitleString) {
this(); this();
title.set(titleString); setTitle(titleString);
subtitle.set(subtitleString); setSubtitle(subtitleString);
} }
public TwoLineListItem() { private void initSecondLine() {
setMouseTransparent(true); if (secondLine == null) {
HBox firstLine = new HBox();
firstLine.getStyleClass().add("first-line");
lblTitle = new Label();
lblTitle.getStyleClass().add("title");
lblTitle.textProperty().bind(title);
firstLineChildren = new AggregatedObservableList<>();
firstLineChildren.appendList(FXCollections.singletonObservableList(lblTitle));
firstLineChildren.appendList(tags);
Bindings.bindContent(firstLine.getChildren(), firstLineChildren.getAggregatedList());
lblSubtitle = new Label(); lblSubtitle = new Label();
lblSubtitle.getStyleClass().add("subtitle"); lblSubtitle.getStyleClass().add("subtitle");
lblSubtitle.textProperty().bind(subtitle);
HBox secondLine = new HBox(); secondLine = new HBox(lblSubtitle);
secondLine.getChildren().setAll(lblSubtitle); }
}
getChildren().setAll(firstLine, secondLine); private final StringProperty title = new StringPropertyBase() {
@Override
public Object getBean() {
return TwoLineListItem.this;
}
FXUtils.onChangeAndOperate(subtitle, subtitleString -> { @Override
if (subtitleString == null) getChildren().setAll(firstLine); public String getName() {
else getChildren().setAll(firstLine, secondLine); return "title";
}); }
getStyleClass().add(DEFAULT_STYLE_CLASS); @Override
protected void invalidated() {
lblTitle.setText(get());
}
};
public StringProperty titleProperty() {
return title;
} }
public String getTitle() { public String getTitle() {
return title.get(); return title.get();
} }
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) { public void setTitle(String title) {
this.title.set(title); this.title.set(title);
} }
public String getSubtitle() { private StringProperty subtitle;
return subtitle.get();
}
public StringProperty subtitleProperty() { public StringProperty subtitleProperty() {
if (subtitle == null) {
subtitle = new StringPropertyBase() {
@Override
public Object getBean() {
return TwoLineListItem.this;
}
@Override
public String getName() {
return "subtitle";
}
@Override
protected void invalidated() {
String subtitle = get();
if (subtitle != null) {
initSecondLine();
lblSubtitle.setText(subtitle);
if (getChildren().size() == 1)
getChildren().add(secondLine);
} else if (secondLine != null) {
lblSubtitle.setText(null);
if (getChildren().size() > 1)
getChildren().setAll(firstLine);
}
}
};
}
return subtitle; return subtitle;
} }
public void setSubtitle(String subtitle) { public String getSubtitle() {
this.subtitle.set(subtitle); return subtitle != null ? subtitleProperty().get() : null;
} }
public Label getSubtitleLabel() { public void setSubtitle(String subtitle) {
return lblSubtitle; if (this.subtitle == null && subtitle == null)
return;
subtitleProperty().set(subtitle);
} }
public Label getTitleLabel() { public Label getTitleLabel() {
return lblTitle; return lblTitle;
} }
public Label getSubtitleLabel() {
initSecondLine();
return lblSubtitle;
}
private ObservableList<Label> tags;
public ObservableList<Label> getTags() {
if (tags == null) {
tags = FXCollections.observableArrayList();
var tagsBox = new HBox(8);
tagsBox.getStyleClass().add("tags");
Bindings.bindContent(tagsBox.getChildren(), tags);
firstLine.getChildren().setAll(lblTitle, tagsBox);
}
return tags;
}
public void addTag(String tag) { public void addTag(String tag) {
Label tagLabel = createTagLabel(tag); var tagLabel = new Label(tag);
tagLabel.getStyleClass().add("tag"); tagLabel.getStyleClass().add("tag");
getTags().add(tagLabel); getTags().add(tagLabel);
} }
public void addTagWarning(String tag) { public void addTagWarning(String tag) {
Label tagLabel = createTagLabel(tag); var tagLabel = new Label(tag);
tagLabel.getStyleClass().add("tag-warning"); tagLabel.getStyleClass().add("tag-warning");
getTags().add(tagLabel); getTags().add(tagLabel);
} }
public ObservableList<Label> getTags() {
return tags;
}
@Override @Override
public String toString() { public String toString() {
return getTitle(); return "TwoLineListItem[title=%s, subtitle=%s, tags=%s]".formatted(getTitle(), getSubtitle(), tags);
} }
} }

View File

@@ -324,7 +324,7 @@
-fx-fill: -monet-on-surface-variant; -fx-fill: -monet-on-surface-variant;
} }
.two-line-list-item > .first-line > .tag { .two-line-list-item > .first-line > .tags > .tag {
-fx-text-fill: -monet-on-secondary-container; -fx-text-fill: -monet-on-secondary-container;
-fx-background-color: -monet-secondary-container; -fx-background-color: -monet-secondary-container;
-fx-padding: 2; -fx-padding: 2;
@@ -332,7 +332,7 @@
-fx-font-size: 12px; -fx-font-size: 12px;
} }
.two-line-list-item > .first-line > .tag-warning { .two-line-list-item > .first-line > .tags > .tag-warning {
-fx-text-fill: -monet-on-error-container; -fx-text-fill: -monet-on-error-container;
-fx-background-color: -fixed-warning-tag-background; -fx-background-color: -fixed-warning-tag-background;
-fx-padding: 2; -fx-padding: 2;