Rework bindings

This commit is contained in:
crschnick 2024-04-04 06:32:11 +00:00
parent 4d04fbd874
commit cd2933eef8
43 changed files with 340 additions and 435 deletions

View file

@ -8,10 +8,9 @@ import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope;
@ -122,7 +121,7 @@ public class BrowserComp extends SimpleComp {
private Comp<?> createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
Bindings.isNotEmpty(model.getOpenFileSystems()),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
Bindings.createBooleanBinding(
() -> {
@ -292,7 +291,7 @@ public class BrowserComp extends SimpleComp {
if (color != null) {
c.getStyleClass().add(color.getId());
}
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
new TooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
c.addEventHandler(
DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater(

View file

@ -9,7 +9,6 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.HumanReadableFormat;
@ -505,7 +504,7 @@ final class BrowserFileListComp extends SimpleComp {
.get();
var quickAccess = new BrowserQuickAccessButtonComp(
() -> getTableRow().getItem(), fileList.getFileSystemModel())
.hide(BindingsHelper.persist(Bindings.createBooleanBinding(
.hide(Bindings.createBooleanBinding(
() -> {
var item = getTableRow().getItem();
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
@ -514,7 +513,7 @@ final class BrowserFileListComp extends SimpleComp {
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
return notDir || isParentLink;
},
itemProperty())))
itemProperty()).not().not())
.createRegion();
editing.addListener((observable, oldValue, newValue) -> {

View file

@ -1,6 +1,6 @@
package io.xpipe.app.browser;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
@ -33,7 +33,7 @@ public final class BrowserFileListModel {
private final ObservableList<BrowserEntry> previousSelection = FXCollections.observableArrayList();
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
private final ObservableList<FileSystem.FileEntry> selectedRaw =
BindingsHelper.mappedContentBinding(selection, entry -> entry.getRawFileEntry());
ListBindingsHelper.mappedContentBinding(selection, entry -> entry.getRawFileEntry());
private final Property<BrowserEntry> draggedOverDirectory = new SimpleObjectProperty<>();
private final Property<Boolean> draggedOverEmpty = new SimpleBooleanProperty();

View file

@ -3,7 +3,7 @@ package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
@ -28,7 +28,7 @@ public class BrowserFilterComp extends Comp<BrowserFilterComp.Structure> {
var expanded = new SimpleBooleanProperty();
var text = new TextFieldComp(filterString, false).createRegion();
var button = new Button();
new FancyTooltipAugment<>("app.search").augment(button);
new TooltipAugment<>("app.search").augment(button);
text.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue && filterString.getValue() == null) {
if (button.isFocused()) {

View file

@ -4,6 +4,7 @@ import atlantafx.base.theme.Styles;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
@ -15,7 +16,9 @@ public class BrowserGreetingComp extends SimpleComp {
protected Region createSimple() {
var r = new Label(getText());
AppLayoutModel.get().getSelected().addListener((observableValue, entry, t1) -> {
r.setText(getText());
PlatformThread.runLaterIfNeeded(() -> {
r.setText(getText());
});
});
AppFont.setSize(r, 7);
r.getStyleClass().add(Styles.TEXT_BOLD);

View file

@ -3,7 +3,7 @@ package io.xpipe.app.browser;
import io.xpipe.app.browser.icon.BrowserIconDirectoryType;
import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BooleanScope;
@ -50,7 +50,7 @@ public class BrowserModel {
return;
}
BindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
ListBindingsHelper.bindContent(selection, newValue.getFileList().getSelection());
});
}

View file

@ -4,7 +4,7 @@ import io.xpipe.app.comp.base.SimpleTitledPaneComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellControl;
@ -66,7 +66,7 @@ public class BrowserOverviewComp extends SimpleComp {
var rootsOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(roots), false);
var rootsPane = new SimpleTitledPaneComp(AppI18n.observable("roots"), rootsOverview);
var recent = BindingsHelper.mappedContentBinding(
var recent = ListBindingsHelper.mappedContentBinding(
model.getSavedState().getRecentDirectories(),
s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s.getDirectory()));
var recentOverview = new BrowserFileOverviewComp(model, recent, true);

View file

@ -52,9 +52,9 @@ public class BrowserSelectionListComp extends SimpleComp {
protected Region createSimple() {
var c = new ListBoxViewComp<>(list, list, entry -> {
return Comp.of(() -> {
var wv = PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 20)
var image = PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24)
.createRegion();
var l = new Label(null, wv);
var l = new Label(null, image);
l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
l.textProperty().bind(PlatformThread.sync(nameTransformation.apply(entry)));
return l;

View file

@ -59,7 +59,7 @@ public class BrowserStatusBarComp extends SimpleComp {
private Comp<?> createClipboardStatus() {
var cc = BrowserClipboard.currentCopyClipboard;
var ccCount = (BindingsHelper.persist(Bindings.createStringBinding(
var ccCount = Bindings.createStringBinding(
() -> {
if (cc.getValue() != null && cc.getValue().getEntries().size() > 0) {
return cc.getValue().getEntries().size() + " file"
@ -68,7 +68,7 @@ public class BrowserStatusBarComp extends SimpleComp {
return null;
}
},
cc)));
cc);
return new LabelComp(ccCount);
}
@ -86,7 +86,7 @@ public class BrowserStatusBarComp extends SimpleComp {
.count();
},
model.getFileList().getAll());
var selectedComp = new LabelComp(BindingsHelper.persist(Bindings.createStringBinding(
var selectedComp = new LabelComp(Bindings.createStringBinding(
() -> {
if (selectedCount.getValue().intValue() == 0) {
return null;
@ -95,7 +95,7 @@ public class BrowserStatusBarComp extends SimpleComp {
}
},
selectedCount,
allCount)));
allCount));
return selectedComp;
}

View file

@ -6,7 +6,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.process.OsType;
@ -39,11 +39,11 @@ public class BrowserTransferComp extends SimpleComp {
protected Region createSimple() {
var background = new LabelComp(AppI18n.observable("transferDescription"))
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline")))
.visible(BindingsHelper.persist(Bindings.isEmpty(model.getItems())));
.visible(Bindings.isEmpty(model.getItems()));
var backgroundStack =
new StackComp(List.of(background)).grow(true, true).styleClass("download-background");
var binding = BindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
var binding = ListBindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry());
var list = new BrowserSelectionListComp(
binding,
entry -> Bindings.createStringBinding(
@ -70,20 +70,20 @@ public class BrowserTransferComp extends SimpleComp {
.flatMap(aBoolean ->
aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left")))
.hide(PlatformThread.sync(BindingsHelper.persist(Bindings.isEmpty(model.getItems()))))
.hide(PlatformThread.sync(Bindings.isEmpty(model.getItems())))
.grow(true, false)
.apply(struc -> struc.get().setPadding(new Insets(8)));
var downloadButton = new IconButtonComp("mdi2d-download", () -> {
model.download();
})
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())))
.hide(Bindings.isEmpty(model.getItems()))
.disable(PlatformThread.sync(model.getAllDownloaded()))
.apply(new FancyTooltipAugment<>("downloadStageDescription"));
.apply(new TooltipAugment<>("downloadStageDescription"));
var clearButton = new IconButtonComp("mdi2c-close", () -> {
model.clear();
})
.hide(BindingsHelper.persist(Bindings.isEmpty(model.getItems())));
.hide(Bindings.isEmpty(model.getItems()));
var clearPane = Comp.derive(
new HorizontalComp(List.of(downloadButton, clearButton))
.apply(struc -> struc.get().setSpacing(10)),

View file

@ -13,6 +13,7 @@ import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.PrettySvgComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
@ -62,7 +63,7 @@ public class BrowserWelcomeComp extends SimpleComp {
return new VBox(hbox);
}
var list = BindingsHelper.filteredContentBinding(state.getEntries(), e -> {
var list = ListBindingsHelper.filteredContentBinding(state.getEntries(), e -> {
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
if (entry.isEmpty()) {
return false;
@ -76,7 +77,7 @@ public class BrowserWelcomeComp extends SimpleComp {
});
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
var headerBinding = BindingsHelper.mappedBinding(empty,b -> {
var headerBinding = BindingsHelper.flatMap(empty,b -> {
if (b) {
return AppI18n.observable("browserWelcomeEmpty");
} else {

View file

@ -9,7 +9,6 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.Shortcuts;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -97,7 +96,7 @@ public class OpenFileSystemComp extends SimpleComp {
home,
model.getCurrentPath().isNull(),
fileList,
BindingsHelper.persist(model.getCurrentPath().isNull().not())));
model.getCurrentPath().isNull().not()));
return stack.createRegion();
}
}

View file

@ -2,7 +2,7 @@ package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.Shortcuts;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.LicenseProvider;
@ -39,7 +39,7 @@ public interface LeafAction extends BrowserAction {
if (getShortcut() != null) {
Shortcuts.addShortcut(b, getShortcut());
}
new FancyTooltipAugment<>(new SimpleStringProperty(getName(model, selected))).augment(b);
new TooltipAugment<>(new SimpleStringProperty(getName(model, selected))).augment(b);
var graphic = getIcon(model, selected);
if (graphic != null) {
b.setGraphic(graphic);

View file

@ -7,7 +7,6 @@ import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import javafx.beans.binding.Bindings;
@ -25,14 +24,15 @@ public class AppLayoutComp extends Comp<CompStructure<Pane>> {
var multi = new MultiContentComp(model.getEntries().stream()
.collect(Collectors.toMap(
entry -> entry.comp(),
entry -> PlatformThread.sync(Bindings.createBooleanBinding(
entry -> Bindings.createBooleanBinding(
() -> {
return model.getSelected().getValue().equals(entry);
},
model.getSelected())))));
model.getSelected())))
);
var pane = new BorderPane();
var sidebar = new SideMenuBarComp(model.getSelectedInternal(), model.getEntries());
var sidebar = new SideMenuBarComp(model.getSelected(), model.getEntries());
pane.setCenter(multi.createRegion());
pane.setRight(sidebar.createRegion());
pane.getStyleClass().add("background");

View file

@ -4,7 +4,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.scene.control.Button;
@ -37,7 +37,7 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
.createRegion();
button.visibleProperty()
.bind(BindingsHelper.anyMatch(cm.getItems().stream()
.bind(ListBindingsHelper.anyMatch(cm.getItems().stream()
.map(menuItem -> menuItem.getGraphic().visibleProperty())
.toList()));

View file

@ -2,7 +2,6 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.SimpleStringProperty;
@ -37,9 +36,10 @@ public class FontIconComp extends Comp <FontIconComp.Structure>{
public FontIconComp.Structure createBase() {
var fi = new FontIcon();
var obs = PlatformThread.sync(icon);
BindingsHelper.linkPersistently(fi, obs);
obs.subscribe(val -> {
fi.setIconLiteral(val);
icon.subscribe(val -> {
PlatformThread.runLaterIfNeeded(() -> {
fi.setIconLiteral(val);
});
});
var pane = new StackPane(fi);

View file

@ -3,7 +3,7 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
@ -88,7 +88,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
}
if (!listView.getChildren().equals(newShown)) {
BindingsHelper.setContent(listView.getChildren(), newShown);
ListBindingsHelper.setContent(listView.getChildren(), newShown);
}
};

View file

@ -24,9 +24,11 @@ public class MultiContentComp extends SimpleComp {
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> entry : content.entrySet()) {
var region = entry.getKey().createRegion();
stack.getChildren().add(region);
PlatformThread.sync(entry.getValue()).subscribe(val -> {
region.setManaged(val);
region.setVisible(val);
entry.getValue().subscribe(val -> {
PlatformThread.runLaterIfNeeded(() -> {
region.setManaged(val);
region.setVisible(val);
});
});
}
return stack;

View file

@ -37,7 +37,7 @@ public class OsLogoComp extends SimpleComp {
@Override
protected Region createSimple() {
var img = BindingsHelper.persist(Bindings.createObjectBinding(
var img = Bindings.createObjectBinding(
() -> {
if (state.getValue() != SystemStateComp.State.SUCCESS) {
return null;
@ -51,7 +51,7 @@ public class OsLogoComp extends SimpleComp {
return getImage(ons.getOsName());
},
wrapper.getPersistentState(),
state));
state);
var hide = BindingsHelper.map(img, s -> s != null);
return new StackComp(
List.of(new SystemStateComp(state).hide(hide), new PrettyImageComp(img, 24, 24).visible(hide)))

View file

@ -7,7 +7,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
@ -73,7 +73,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
var e = entries.get(i);
var b = new IconButtonComp(e.icon(), () -> value.setValue(e));
b.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + i]));
b.apply(new FancyTooltipAugment<>(e.name()));
b.apply(new TooltipAugment<>(e.name()));
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
struc.get().pseudoClassStateChanged(selected, value.getValue().equals(e));
@ -133,7 +133,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
UserReportComp.show(event.build());
})
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size()]))
.apply(new FancyTooltipAugment<>("reportIssue"))
.apply(new TooltipAugment<>("reportIssue"))
.apply(simpleBorders)
.accessibleTextKey("reportIssue");
b.apply(struc -> {
@ -145,7 +145,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 1]))
.apply(new FancyTooltipAugment<>("visitGithubRepository"))
.apply(new TooltipAugment<>("visitGithubRepository"))
.apply(simpleBorders)
.accessibleTextKey("visitGithubRepository");
b.apply(struc -> {
@ -157,7 +157,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 2]))
.apply(new FancyTooltipAugment<>("discord"))
.apply(new TooltipAugment<>("discord"))
.apply(simpleBorders)
.accessibleTextKey("discord");
b.apply(struc -> {
@ -169,7 +169,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2t-translate", () -> Hyperlinks.open(Hyperlinks.TRANSLATE))
.shortcut(new KeyCodeCombination(KeyCode.values()[KeyCode.DIGIT1.ordinal() + entries.size() + 3]))
.apply(new FancyTooltipAugment<>("translate"))
.apply(new TooltipAugment<>("translate"))
.apply(simpleBorders)
.accessibleTextKey("translate");
b.apply(struc -> {
@ -180,7 +180,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"))
.apply(new TooltipAugment<>("updateAvailableTooltip"))
.accessibleTextKey("updateAvailableTooltip");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);

View file

@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
@ -32,13 +31,13 @@ public class StoreToggleComp extends SimpleComp {
@Override
protected Region createSimple() {
var disable = section.getWrapper().getValidity().map(state -> state != DataStoreEntry.Validity.COMPLETE);
var visible = BindingsHelper.persist(Bindings.createBooleanBinding(
var visible = Bindings.createBooleanBinding(
() -> {
return section.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.COMPLETE
&& section.getShowDetails().get();
},
section.getWrapper().getValidity(),
section.getShowDetails()));
section.getShowDetails());
var t = new ToggleSwitchComp(value, AppI18n.observable(nameKey))
.visible(visible)
.disable(disable);

View file

@ -4,7 +4,6 @@ import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
@ -47,9 +46,9 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
});
var header = new Label();
BindingsHelper.bindStrong(header.textProperty(), PlatformThread.sync(name));
header.textProperty().bind(PlatformThread.sync(name));
var desc = new Label();
BindingsHelper.bindStrong(desc.textProperty(), PlatformThread.sync(description));
desc.textProperty().bind(PlatformThread.sync(description));
AppFont.small(desc);
desc.setOpacity(0.65);
var text = new VBox(header, desc);

View file

@ -13,7 +13,6 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.*;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
@ -100,8 +99,7 @@ public abstract class StoreEntryComp extends SimpleComp {
var loading = LoadingOverlayComp.noProgress(
Comp.of(() -> button),
BindingsHelper.persist(
wrapper.getInRefresh().and(wrapper.getObserving().not())));
wrapper.getInRefresh().and(wrapper.getObserving().not()));
return loading.createRegion();
}
@ -173,7 +171,7 @@ public abstract class StoreEntryComp extends SimpleComp {
var imageComp = PrettyImageHelper.ofFixedSize(img, w, h);
var storeIcon = imageComp.createRegion();
if (wrapper.getValidity().getValue().isUsable()) {
new FancyTooltipAugment<>(new SimpleStringProperty(
new TooltipAugment<>(new SimpleStringProperty(
wrapper.getEntry().getProvider().getDisplayName()))
.augment(storeIcon);
}
@ -211,7 +209,7 @@ public abstract class StoreEntryComp extends SimpleComp {
});
button.accessibleText(
actionProvider.getName(wrapper.getEntry().ref()).getValue());
button.apply(new FancyTooltipAugment<>(
button.apply(new TooltipAugment<>(
actionProvider.getName(wrapper.getEntry().ref())));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
button.hide(Bindings.not(p.getValue()));
@ -247,7 +245,7 @@ public abstract class StoreEntryComp extends SimpleComp {
settingsButton.accessibleText("More");
settingsButton.apply(new ContextMenuAugment<>(
event -> event.getButton() == MouseButton.PRIMARY, null, () -> StoreEntryComp.this.createContextMenu()));
settingsButton.apply(new FancyTooltipAugment<>("more"));
settingsButton.apply(new TooltipAugment<>("more"));
return settingsButton;
}

View file

@ -5,7 +5,6 @@ import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
@ -50,16 +49,16 @@ public class StoreEntryListComp extends SimpleComp {
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
map.put(
createList(),
BindingsHelper.persist(Bindings.not(Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren()))));
Bindings.not(Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
map.put(new StoreIntroComp(), showIntro);
map.put(
new StoreNotFoundComp(),
BindingsHelper.persist(Bindings.and(
Bindings.and(
Bindings.not(Bindings.isEmpty(StoreViewState.get().getAllEntries())),
Bindings.isEmpty(
StoreViewState.get().getCurrentTopLevelSection().getShownChildren()))));
StoreViewState.get().getCurrentTopLevelSection().getShownChildren())));
return new MultiContentComp(map).createRegion();
}
}

View file

@ -5,10 +5,11 @@ import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import javafx.beans.binding.Bindings;
@ -55,7 +56,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
label.textProperty().bind(name);
label.getStyleClass().add("name");
var all = BindingsHelper.filteredContentBinding(
var all = ListBindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
storeEntryWrapper -> {
var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot();
@ -66,7 +67,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
.equals(storeRoot);
},
StoreViewState.get().getActiveCategory());
var shownList = BindingsHelper.filteredContentBinding(
var shownList = ListBindingsHelper.filteredContentBinding(
all,
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
@ -178,7 +179,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
sortMode));
});
alphabetical.accessibleTextKey("sortAlphabetical");
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.apply(new TooltipAugment<>("sortAlphabetical"));
return alphabetical;
}
@ -217,7 +218,7 @@ public class StoreEntryListStatusComp extends SimpleComp {
sortMode));
});
date.accessibleTextKey("sortLastUsed");
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.apply(new TooltipAugment<>("sortLastUsed"));
return date;
}

View file

@ -2,6 +2,7 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
@ -64,10 +65,10 @@ public class StoreSection {
var c = Comparator.<StoreSection>comparingInt(
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
var mappedSortMode = BindingsHelper.mappedBinding(
var mappedSortMode = BindingsHelper.flatMap(
category,
storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
return BindingsHelper.orderedContentBinding(
return ListBindingsHelper.orderedContentBinding(
list,
(o1, o2) -> {
var current = mappedSortMode.getValue();
@ -86,16 +87,16 @@ public class StoreSection {
Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
ObservableValue<StoreCategoryWrapper> category) {
var topLevel = BindingsHelper.filteredContentBinding(
var topLevel = ListBindingsHelper.filteredContentBinding(
all,
section -> {
return DataStorage.get().isRootEntry(section.getEntry());
},
category);
var cached = BindingsHelper.cachedMappedContentBinding(
topLevel, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
var cached = ListBindingsHelper.cachedMappedContentBinding(
topLevel, topLevel, storeEntryWrapper -> create(storeEntryWrapper, 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var shown = BindingsHelper.filteredContentBinding(
var shown = ListBindingsHelper.filteredContentBinding(
ordered,
section -> {
var showFilter = filterString == null || section.shouldShow(filterString.get());
@ -121,7 +122,7 @@ public class StoreSection {
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
}
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
var allChildren = ListBindingsHelper.filteredContentBinding(all, other -> {
// Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
@ -131,10 +132,10 @@ public class StoreSection {
// This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
});
var cached = BindingsHelper.cachedMappedContentBinding(
allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
var cached = ListBindingsHelper.cachedMappedContentBinding(
allChildren, allChildren, entry1 -> create(entry1, depth + 1, all, entryFilter, filterString, category));
var ordered = sorted(cached, category);
var filtered = BindingsHelper.filteredContentBinding(
var filtered = ListBindingsHelper.filteredContentBinding(
ordered,
section -> {
var showFilter = filterString == null || section.shouldShow(filterString.get());

View file

@ -7,7 +7,7 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
@ -39,11 +39,11 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
}
private Comp<CompStructure<Button>> createQuickAccessButton() {
var quickAccessDisabled = BindingsHelper.persist(Bindings.createBooleanBinding(
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().isEmpty();
},
section.getShownChildren()));
section.getShownChildren());
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
ThreadHelper.runFailableAsync(() -> {
w.executeDefaultAction();
@ -90,8 +90,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
return "Expand " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getShownChildren()).isEqualTo(0)))
.disable(Bindings.size(section.getShownChildren()).isEqualTo(0))
.styleClass("expand-button")
.maxHeight(100)
.vgrow();
@ -130,7 +129,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = BindingsHelper.filteredContentBinding(
var listSections = ListBindingsHelper.filteredContentBinding(
section.getShownChildren(),
storeSection -> section.getAllChildren().size() <= 20
|| section.getWrapper().getExpanded().get(),
@ -142,22 +141,22 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
.minHeight(0)
.hgrow();
var expanded = BindingsHelper.persist(Bindings.createBooleanBinding(
var expanded = Bindings.createBooleanBinding(
() -> {
return section.getWrapper().getExpanded().get()
&& section.getShownChildren().size() > 0;
},
section.getWrapper().getExpanded(),
section.getShownChildren()));
section.getShownChildren());
var full = new VerticalComp(List.of(
topEntryList,
Comp.separator().hide(BindingsHelper.persist(expanded.not())),
Comp.separator().hide(expanded.not()),
new HorizontalComp(List.of(content))
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(BindingsHelper.persist(Bindings.or(
.hide(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getShownChildren()).isEqualTo(0))))));
Bindings.size(section.getShownChildren()).isEqualTo(0)))));
return full.styleClass("store-entry-section-comp")
.apply(struc -> {
struc.get().setFillWidth(true);

View file

@ -8,7 +8,7 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
@ -100,16 +100,15 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
+ section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getAllChildren()).isEqualTo(0)))
.disable(Bindings.size(section.getAllChildren()).isEqualTo(0))
.grow(false, true)
.styleClass("expand-button");
var quickAccessDisabled = BindingsHelper.persist(Bindings.createBooleanBinding(
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().isEmpty();
},
section.getShownChildren()));
section.getShownChildren());
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
action.accept(w);
};
@ -133,7 +132,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
// section is actually expanded
var listSections = section.getWrapper() != null
? BindingsHelper.filteredContentBinding(
? ListBindingsHelper.filteredContentBinding(
section.getShownChildren(),
storeSection -> section.getAllChildren().size() <= 20 || expanded.get(),
expanded,
@ -148,9 +147,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
list.add(new HorizontalComp(List.of(content))
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))
.hide(BindingsHelper.persist(Bindings.or(
.hide(Bindings.or(
Bindings.not(expanded),
Bindings.size(section.getAllChildren()).isEqualTo(0)))));
Bindings.size(section.getAllChildren()).isEqualTo(0))));
var vert = new VerticalComp(list);
if (condensedStyle) {

View file

@ -1,7 +1,7 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
@ -273,7 +273,7 @@ public class StoreViewState {
return o1.getName().compareToIgnoreCase(o2.getName());
}
};
return BindingsHelper.filteredContentBinding(
return ListBindingsHelper.filteredContentBinding(
categories, cat -> root == null || cat.getRoot().equals(root))
.sorted(comparator);
}

View file

@ -3,7 +3,6 @@ package io.xpipe.app.core;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -98,7 +97,7 @@ public class AppGreetings {
alert.getButtonTypes().add(buttonType);
Button button = (Button) alert.getDialogPane().lookupButton(buttonType);
button.disableProperty().bind(BindingsHelper.persist(accepted.not()));
button.disableProperty().bind(accepted.not());
}
alert.getButtonTypes().add(ButtonType.CANCEL);

View file

@ -2,7 +2,7 @@ package io.xpipe.app.core;
import io.xpipe.app.comp.base.ModalOverlayComp;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
@ -116,7 +116,7 @@ public class AppI18n {
|| caller.equals(ModuleHelper.class)
|| caller.equals(ModalOverlayComp.class)
|| caller.equals(AppI18n.class)
|| caller.equals(FancyTooltipAugment.class)
|| caller.equals(TooltipAugment.class)
|| caller.equals(PrefsChoiceValue.class)
|| caller.equals(Translatable.class)
|| caller.equals(AppWindowHelper.class)

View file

@ -5,7 +5,6 @@ import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.comp.DeveloperTabComp;
import io.xpipe.app.comp.store.StoreLayoutComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefsComp;
import io.xpipe.app.util.LicenseProvider;
import javafx.beans.property.Property;
@ -30,13 +29,11 @@ public class AppLayoutModel {
private final List<Entry> entries;
private final Property<Entry> selected;
private final ObservableValue<Entry> selectedWrapper;
public AppLayoutModel(SavedState savedState) {
this.savedState = savedState;
this.entries = createEntryList();
this.selected = new SimpleObjectProperty<>(entries.get(1));
this.selectedWrapper = PlatformThread.sync(selected);
}
public static AppLayoutModel get() {
@ -53,14 +50,10 @@ public class AppLayoutModel {
INSTANCE = null;
}
public Property<Entry> getSelectedInternal() {
public Property<Entry> getSelected() {
return selected;
}
public ObservableValue<Entry> getSelected() {
return selectedWrapper;
}
public void selectBrowser() {
selected.setValue(entries.getFirst());
}

View file

@ -4,7 +4,8 @@ import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.Shortcuts;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
@ -143,6 +144,7 @@ public abstract class Comp<S extends CompStructure<?>> {
public Comp<S> hide(ObservableValue<Boolean> o) {
return apply(struc -> {
var region = struc.get();
BindingsHelper.preserve(region, o);
o.subscribe(n -> {
if (!n) {
region.setVisible(true);
@ -188,11 +190,11 @@ public abstract class Comp<S extends CompStructure<?>> {
}
public Comp<S> tooltip(ObservableValue<String> text) {
return apply(new FancyTooltipAugment<>(text));
return apply(new TooltipAugment<>(text));
}
public Comp<S> tooltipKey(String key) {
return apply(new FancyTooltipAugment<>(key));
return apply(new TooltipAugment<>(key));
}
public Region createRegion() {
@ -201,6 +203,8 @@ public abstract class Comp<S extends CompStructure<?>> {
public S createStructure() {
S struc = createBase();
// Make comp last at least as long as region
BindingsHelper.preserve(struc.get(), this);
if (augments != null) {
for (var a : augments) {
a.augment(struc);

View file

@ -4,7 +4,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.Translatable;
import javafx.beans.property.Property;
@ -77,7 +77,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
list.add(null);
}
BindingsHelper.setContent(cb.getItems(), list);
ListBindingsHelper.setContent(cb.getItems(), list);
});
cb.valueProperty().addListener((observable, oldValue, newValue) -> {

View file

@ -6,7 +6,6 @@ import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.ContextualFileReference;
@ -65,7 +64,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
.styleClass(Styles.CENTER_PILL)
.grow(false, true);
var canGitShare = BindingsHelper.persist(Bindings.createBooleanBinding(
var canGitShare = Bindings.createBooleanBinding(
() -> {
if (!AppPrefs.get().enableGitStorage().get()
|| filePath.getValue() == null
@ -76,7 +75,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
return true;
},
filePath,
AppPrefs.get().enableGitStorage()));
AppPrefs.get().enableGitStorage());
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
if (filePath.getValue() == null || filePath.getValue().isBlank() || !canGitShare.get()) {
return;
@ -107,7 +106,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp {
ErrorEvent.fromThrowable(e).handle();
}
});
gitShareButton.apply(new FancyTooltipAugment<>("gitShareFileTooltip"));
gitShareButton.apply(new TooltipAugment<>("gitShareFileTooltip"));
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton, gitShareButton))

View file

@ -27,11 +27,11 @@ public class DataStoreFlowChoiceComp extends SimpleComp {
map.put(DataFlow.INPUT_OUTPUT, AppI18n.observable("app.inout"));
return new ToggleGroupComp<>(selected, new SimpleObjectProperty<>(map))
.apply(struc -> {
new FancyTooltipAugment<>("app.inputDescription")
new TooltipAugment<>("app.inputDescription")
.augment(struc.get().getChildren().get(0));
new FancyTooltipAugment<>("app.outputDescription")
new TooltipAugment<>("app.outputDescription")
.augment(struc.get().getChildren().get(1));
new FancyTooltipAugment<>("app.inoutDescription")
new TooltipAugment<>("app.inoutDescription")
.augment(struc.get().getChildren().get(2));
})
.createRegion();

View file

@ -25,7 +25,6 @@ public class LabelComp extends Comp<CompStructure<Label>> {
@Override
public CompStructure<Label> createBase() {
var label = new Label();
BindingsHelper.linkPersistently(label,text);
text.subscribe(t -> {
PlatformThread.runLaterIfNeeded(() -> label.setText(t));
});

View file

@ -11,7 +11,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.ListBindingsHelper;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.util.ContextMenuHelper;
@ -76,7 +76,7 @@ public class StoreCategoryComp extends SimpleComp {
showing.bind(cm.showingProperty());
return cm;
}));
var shownList = BindingsHelper.filteredContentBinding(
var shownList = ListBindingsHelper.filteredContentBinding(
category.getContainedEntries(),
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
@ -91,9 +91,8 @@ public class StoreCategoryComp extends SimpleComp {
Comp.hspacer(4),
Comp.of(() -> name),
Comp.hspacer(),
count.hide(BindingsHelper.persist(hover.or(showing).or(focus))),
settings.hide(
BindingsHelper.persist(hover.not().and(showing.not()).and(focus.not())))));
count.hide(hover.or(showing).or(focus)),
settings.hide(hover.not().and(showing.not()).and(focus.not()))));
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
var categoryButton = new ButtonComp(null, h.createRegion(), category::select)

View file

@ -3,22 +3,21 @@ package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.Shortcuts;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tooltip;
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
private final ObservableValue<String> text;
public FancyTooltipAugment(ObservableValue<String> text) {
public TooltipAugment(ObservableValue<String> text) {
this.text = PlatformThread.sync(text);
}
public FancyTooltipAugment(String key) {
public TooltipAugment(String key) {
this.text = AppI18n.observable(key);
}
@ -31,9 +30,9 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
var binding = Bindings.createStringBinding(() -> {
return text.getValue() + "\n\n" + s.getValue() + ": " + Shortcuts.getDisplayShortcut(region).getDisplayText();
}, text, s);
BindingsHelper.bindStrong(tt.textProperty(), binding);
tt.textProperty().bind(binding);
} else {
BindingsHelper.bindStrong(tt.textProperty(),text);
tt.textProperty().bind(text);
}
tt.setStyle("-fx-font-size: 11pt;");
tt.setWrapText(true);

View file

@ -1,103 +1,50 @@
package io.xpipe.app.fxcomps.util;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.Observable;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ListBinding;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import lombok.Value;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
@SuppressWarnings("InfiniteLoopStatement")
public class BindingsHelper {
private static final Set<ReferenceEntry> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>());
/*
TODO: Proper cleanup. Maybe with a separate thread?
*/
private static final Map<WeakReference<Object>, Set<javafx.beans.Observable>> BINDINGS = new ConcurrentHashMap<>();
static {
ThreadHelper.createPlatformThread("referenceGC", true, () -> {
while (true) {
for (ReferenceEntry reference : REFERENCES) {
if (reference.canGc()) {
/*
TODO: Figure out why some bindings are garbage collected, even if they shouldn't
*/
// REFERENCES.remove(reference);
REFERENCES.remove(reference);
}
}
ThreadHelper.sleep(1000);
// Use for testing
// System.gc();
}
})
.start();
}
public static <T> void bindStrong(Property<T> property, ObservableValue<T> value) {
property.bind(value);
linkPersistently(property, value);
}
public static <T, V> void bindExclusive(
Property<V> selected, Map<V, ? extends Property<T>> map, Property<T> toBind) {
selected.addListener((c, o, n) -> {
toBind.unbind();
toBind.bind(map.get(n));
});
toBind.bind(map.get(selected.getValue()));
}
public static void linkPersistently(Object source, Object target) {
public static void preserve(Object source, Object target) {
REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target));
}
public static <T extends Binding<?>> T persist(T binding) {
var dependencies = new HashSet<javafx.beans.Observable>();
while (dependencies.addAll(binding.getDependencies().stream()
.map(o -> (javafx.beans.Observable) o)
.toList())) {}
dependencies.add(binding);
BINDINGS.put(new WeakReference<>(binding), dependencies);
return binding;
}
public static <T extends ListBinding<?>> T persist(T binding) {
var dependencies = new HashSet<javafx.beans.Observable>();
while (dependencies.addAll(binding.getDependencies().stream()
.map(o -> (javafx.beans.Observable) o)
.toList())) {}
dependencies.add(binding);
BINDINGS.put(new WeakReference<>(binding), dependencies);
return binding;
}
public static <T> void bindContent(ObservableList<T> l1, ObservableList<? extends T> l2) {
setContent(l1, l2);
l2.addListener((ListChangeListener<? super T>) c -> {
setContent(l1, l2);
});
}
public static <T, U> ObservableValue<U> map(
ObservableValue<T> observableValue, Function<? super T, ? extends U> mapper) {
return persist(Bindings.createObjectBinding(
return Bindings.createObjectBinding(
() -> {
return mapper.apply(observableValue.getValue());
},
observableValue));
observableValue);
}
public static <T, U> ObservableValue<U> flatMap(
@ -110,229 +57,10 @@ public class BindingsHelper {
observableValue.addListener((observable, oldValue, newValue) -> {
runnable.run();
});
linkPersistently(observableValue, prop);
preserve(prop, observableValue);
return prop;
}
public static <T, U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
return BindingsHelper.persist(Bindings.createBooleanBinding(
() -> {
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
},
l.toArray(ObservableValue[]::new)));
}
public static <T, V> void bindMappedContent(ObservableList<T> l1, ObservableList<V> l2, Function<V, T> map) {
Runnable runnable = () -> {
setContent(l1, l2.stream().map(map).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
}
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().map(map).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
linkPersistently(l2, l1);
return l1;
}
public static <T, V> ObservableList<T> cachedMappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
var cache = new HashMap<V, T>();
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
cache.keySet().removeIf(t -> !l2.contains(t));
setContent(
l1,
l2.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
return cache.get(v);
})
.toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
linkPersistently(l2, l1);
return l1;
}
public static <T, V> ObservableList<T> cachedMappedContentBinding(
ObservableList<V> all, ObservableList<V> shown, Function<V, T> map) {
var cache = new HashMap<V, T>();
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
cache.keySet().removeIf(t -> !all.contains(t));
setContent(
l1,
shown.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
return cache.get(v);
})
.toList());
};
runnable.run();
shown.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
linkPersistently(all, l1);
linkPersistently(shown, l1);
return l1;
}
public static <T, U> ObservableValue<U> mappedBinding(
ObservableValue<T> observableValue, Function<? super T, ? extends ObservableValue<? extends U>> mapper) {
var binding = (Binding<U>) observableValue.flatMap(mapper);
return persist(binding);
}
public static <V> ObservableList<V> orderedContentBinding(
ObservableList<V> l2, Comparator<V> comp, Observable... observables) {
return orderedContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Comparator<>() {
@Override
public int compare(V o1, V o2) {
return comp.compare(o1, o2);
}
};
},
observables));
}
// public static <T,U> ObservableValue<U> mappedBinding(ObservableValue<T> observableValue, Function<? super T, ?
// extends ObservableValue<? extends U>> mapper) {
// var v = new SimpleObjectProperty<U>();
// observableValue, val -> {
// v.unbind();
// v.bind(mapper.apply(val));
// });
// return v;
// }
public static <V> ObservableList<V> orderedContentBinding(
ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().sorted(comp.getValue()).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
comp.addListener((observable, oldValue, newValue) -> {
runnable.run();
});
linkPersistently(l2, l1);
return l1;
}
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate) {
return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate));
}
public static <V> ObservableList<V> filteredContentBinding(
ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
return filteredContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(V v) {
return predicate.test(v);
}
};
},
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
}
public static <V> ObservableList<V> filteredContentBinding(
ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(
l1,
predicate.getValue() != null
? l2.stream().filter(predicate.getValue()).toList()
: l2);
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
predicate.addListener((c, o, n) -> {
runnable.run();
});
linkPersistently(l2, l1);
return l1;
}
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
if (target.equals(newList)) {
return;
}
if (target.size() == 0) {
target.setAll(newList);
return;
}
if (newList.size() == 0) {
target.clear();
return;
}
var targetSet = new HashSet<>(target);
var newSet = new HashSet<>(newList);
// Only add missing element
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
var l = new HashSet<>(newSet);
l.removeAll(targetSet);
if (l.size() > 0) {
var found = l.iterator().next();
var index = newList.indexOf(found);
target.add(index, found);
return;
}
}
// Only remove not needed element
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
var l = new HashSet<>(targetSet);
l.removeAll(newSet);
if (l.size() > 0) {
target.remove(l.iterator().next());
return;
}
}
// Other cases are more difficult
target.setAll(newList);
}
@Value
private static class ReferenceEntry {

View file

@ -0,0 +1,190 @@
package io.xpipe.app.fxcomps.util;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
public class ListBindingsHelper {
public static <T> void bindContent(ObservableList<T> l1, ObservableList<? extends T> l2) {
setContent(l1, l2);
l2.addListener((ListChangeListener<? super T>) c -> {
setContent(l1, l2);
});
}
public static <T, U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
return Bindings.createBooleanBinding(
() -> {
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
},
l.toArray(ObservableValue[]::new));
}
public static <T, V> ObservableList<T> mappedContentBinding(ObservableList<V> l2, Function<V, T> map) {
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().map(map).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
BindingsHelper.preserve(l1, l2);
return l1;
}
public static <T, V> ObservableList<T> cachedMappedContentBinding(
ObservableList<V> all, ObservableList<V> shown, Function<V, T> map) {
var cache = new HashMap<V, T>();
ObservableList<T> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
cache.keySet().removeIf(t -> !all.contains(t));
setContent(
l1,
shown.stream()
.map(v -> {
if (!cache.containsKey(v)) {
cache.put(v, map.apply(v));
}
return cache.get(v);
})
.toList());
};
runnable.run();
shown.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
BindingsHelper.preserve(l1, all);
BindingsHelper.preserve(l1, shown);
return l1;
}
public static <V> ObservableList<V> orderedContentBinding(
ObservableList<V> l2, Comparator<V> comp, Observable... observables) {
return orderedContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Comparator<>() {
@Override
public int compare(V o1, V o2) {
return comp.compare(o1, o2);
}
};
},
observables));
}
public static <V> ObservableList<V> orderedContentBinding(
ObservableList<V> l2, ObservableValue<Comparator<V>> comp) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(l1, l2.stream().sorted(comp.getValue()).toList());
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
comp.addListener((observable, oldValue, newValue) -> {
runnable.run();
});
BindingsHelper.preserve(l1, l2);
return l1;
}
public static <V> ObservableList<V> filteredContentBinding(ObservableList<V> l2, Predicate<V> predicate) {
return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate));
}
public static <V> ObservableList<V> filteredContentBinding(
ObservableList<V> l2, Predicate<V> predicate, Observable... observables) {
return filteredContentBinding(
l2,
Bindings.createObjectBinding(
() -> {
return new Predicate<>() {
@Override
public boolean test(V v) {
return predicate.test(v);
}
};
},
Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new)));
}
public static <V> ObservableList<V> filteredContentBinding(
ObservableList<V> l2, ObservableValue<Predicate<V>> predicate) {
ObservableList<V> l1 = FXCollections.observableList(new ArrayList<>());
Runnable runnable = () -> {
setContent(
l1,
predicate.getValue() != null
? l2.stream().filter(predicate.getValue()).toList()
: l2);
};
runnable.run();
l2.addListener((ListChangeListener<? super V>) c -> {
runnable.run();
});
predicate.addListener((c, o, n) -> {
runnable.run();
});
BindingsHelper.preserve(l1, l2);
return l1;
}
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
if (target.equals(newList)) {
return;
}
if (target.size() == 0) {
target.setAll(newList);
return;
}
if (newList.size() == 0) {
target.clear();
return;
}
var targetSet = new HashSet<>(target);
var newSet = new HashSet<>(newList);
// Only add missing element
if (target.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
var l = new HashSet<>(newSet);
l.removeAll(targetSet);
if (l.size() > 0) {
var found = l.iterator().next();
var index = newList.indexOf(found);
target.add(index, found);
return;
}
}
// Only remove not needed element
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
var l = new HashSet<>(targetSet);
l.removeAll(newSet);
if (l.size() > 0) {
target.remove(l.iterator().next());
return;
}
}
// Other cases are more difficult
target.setAll(newList);
}
}

View file

@ -85,7 +85,6 @@ public class PlatformThread {
ov.removeListener(invListenerMap.getOrDefault(listener, listener));
}
};
BindingsHelper.linkPersistently(obs, ov);
return obs;
}
@ -272,7 +271,6 @@ public class PlatformThread {
ol.removeListener(invListenerMap.getOrDefault(listener, listener));
}
};
BindingsHelper.linkPersistently(obs, ol);
return obs;
}

View file

@ -8,7 +8,6 @@ import io.xpipe.app.fxcomps.impl.ChoiceComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.OptionsBuilder;
@ -66,14 +65,14 @@ public class TerminalCategory extends AppPrefsCategory {
Hyperlinks.open(t.getWebsite());
});
var visitVisible = BindingsHelper.persist(Bindings.createBooleanBinding(() -> {
var visitVisible = Bindings.createBooleanBinding(() -> {
var t = prefs.terminalType().getValue();
if (t == null || t.getWebsite() == null) {
return false;
}
return true;
}, prefs.terminalType()));
}, prefs.terminalType());
visit.visible(visitVisible);
return new HorizontalComp(List.of(c, visit)).apply(struc -> {