diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionBaseComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionBaseComp.java index ac580f166..a1dd406ac 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionBaseComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionBaseComp.java @@ -17,12 +17,15 @@ import javafx.beans.value.ObservableBooleanValue; import javafx.css.PseudoClass; import javafx.scene.control.Button; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; public abstract class StoreSectionBaseComp extends RegionBuilder { @@ -66,7 +69,7 @@ public abstract class StoreSectionBaseComp extends RegionBuilder { if (section.getWrapper() != null) { if (section.getDepth() == 1) { - section.getWrapper().getColor().subscribe(val -> { + BindingsHelper.attach(vbox, section.getWrapper().getColor(), val -> { var newList = new ArrayList<>(vbox.getStyleClass()); newList.removeIf(s -> Arrays.stream(DataStoreColor.values()) .anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s))); @@ -81,33 +84,42 @@ public abstract class StoreSectionBaseComp extends RegionBuilder { }); } - section.getWrapper().getPerUser().subscribe(val -> { + BindingsHelper.attach(vbox, section.getWrapper().getPerUser(), val -> { vbox.pseudoClassStateChanged(PseudoClass.getPseudoClass("per-user"), val); }); } } - protected void addVisibilityListeners(VBox root, HBox hbox) { - var children = new ArrayList<>(hbox.getChildren()); - hbox.getChildren().clear(); - root.visibleProperty().subscribe((newValue) -> { - Platform.runLater(() -> { - if (newValue) { - if (!root.isVisible()) { - return; - } - - if (hbox.getChildren().size() == 0) { - hbox.getChildren().addAll(children); - } - } else { - if (root.isVisible()) { - return; - } - - hbox.getChildren().clear(); + protected void addVisibilityListeners(VBox root, Pane pane, Supplier hbox) { + AtomicReference built = new AtomicReference<>(); + Consumer update = (visible) -> { + if (visible) { + if (root.getScene() == null || !root.isVisible()) { + return; } - }); + + if (built.get() == null) { + built.set(hbox.get()); + } + + pane.getChildren().setAll(built.get()); + } else { + if (root.isVisible()) { + return; + } + + pane.getChildren().clear(); + } + }; + + root.visibleProperty().subscribe((newValue) -> { + if (root.getScene() == null) { + update.accept(newValue); + } else { + Platform.runLater(() -> { + update.accept(newValue); + }); + } }); } diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionComp.java index a622034dc..b7b00c05f 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionComp.java @@ -10,13 +10,16 @@ import io.xpipe.app.comp.base.VerticalComp; import io.xpipe.app.util.ThreadHelper; import javafx.beans.binding.Bindings; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import org.int4.fx.builders.pane.StackPaneBuilder; import java.util.ArrayList; import java.util.List; @@ -30,6 +33,27 @@ public class StoreSectionComp extends StoreSectionBaseComp { @Override public VBox createSimple() { var entryButton = StoreEntryComp.customSection(section); + + var paneComp = new StackPaneBuilder(); + paneComp.minHeight(entryButton.getHeight()); + paneComp.maxHeight(entryButton.getHeight()); + paneComp.prefHeight(entryButton.getHeight()); + + var effectiveExpanded = effectiveExpanded(section.getWrapper().getExpanded()); + var content = createChildrenList(c -> StoreSection.customSection(c), Bindings.not(effectiveExpanded)); + + var full = new VerticalComp(List.of(paneComp, RegionBuilder.hseparator().hide(Bindings.not(effectiveExpanded)), content)); + full.style("store-entry-section-comp"); + full.apply(struc -> { + struc.setFillWidth(true); + var pane = ((Pane) struc.getChildren().getFirst()); + addPseudoClassListeners(struc, section.getWrapper().getExpanded()); + addVisibilityListeners(struc, pane, () -> buildContent(entryButton).build()); + }); + return full.build(); + } + + private RegionBuilder buildContent(StoreEntryComp entryButton) { entryButton.hgrow(); entryButton.apply(struc -> { struc.addEventFilter(KeyEvent.KEY_PRESSED, event -> { @@ -78,23 +102,14 @@ public class StoreSectionComp extends StoreSectionBaseComp { buttonList.add(expandButton); var buttons = new VerticalComp(buttonList); var topEntryList = new HorizontalComp(List.of(buttons, entryButton)); - topEntryList.apply(struc -> struc.setAlignment(Pos.CENTER_LEFT)); + topEntryList.apply(struc -> { + struc.setAlignment(Pos.CENTER_LEFT); + }); + topEntryList.minHeight(entryButton.getHeight()); topEntryList.maxHeight(entryButton.getHeight()); topEntryList.prefHeight(entryButton.getHeight()); - var effectiveExpanded = effectiveExpanded(section.getWrapper().getExpanded()); - var content = createChildrenList(c -> StoreSection.customSection(c), Bindings.not(effectiveExpanded)); - - var full = new VerticalComp( - List.of(topEntryList, RegionBuilder.hseparator().hide(Bindings.not(effectiveExpanded)), content)); - full.style("store-entry-section-comp"); - full.apply(struc -> { - struc.setFillWidth(true); - var hbox = ((HBox) struc.getChildren().getFirst()); - addPseudoClassListeners(struc, section.getWrapper().getExpanded()); - addVisibilityListeners(struc, hbox); - }); - return full.build(); + return topEntryList; } } diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionMiniComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionMiniComp.java index 93b55ccfb..2f270a96a 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionMiniComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreSectionMiniComp.java @@ -12,9 +12,11 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import org.int4.fx.builders.common.AbstractRegionBuilder; import io.xpipe.app.comp.BaseRegionBuilder; +import org.int4.fx.builders.pane.StackPaneBuilder; import java.util.ArrayList; import java.util.function.BiConsumer; @@ -43,38 +45,13 @@ public class StoreSectionMiniComp extends StoreSectionBaseComp { @Override public VBox createSimple() { - var list = new ArrayList>(); + var list = new ArrayList>(); if (section.getWrapper() != null) { - var root = new ButtonComp(section.getWrapper().getShownName(), () -> { - action.accept(section); - }); - root.hgrow(); - root.maxWidth(10000); - root.style("item"); - root.apply(struc -> { - struc.setAlignment(Pos.CENTER_LEFT); - struc - .setGraphic(PrettyImageHelper.ofFixedSize( - section.getWrapper().getIconFile(), 16, 16) - .build()); - struc.setMnemonicParsing(false); - }); - augment.accept(section, root); - - var expandButton = createExpandButton(() -> expanded.set(!expanded.get()), 20, expanded); - - var quickAccessButton = createQuickAccessButton(20, action); - - var buttonList = new ArrayList>(); - buttonList.add(expandButton); - buttonList.add(root); - if (section.getDepth() == 1) { - buttonList.add(quickAccessButton); - } - var h = new HorizontalComp(buttonList); - h.apply(struc -> struc.setFillHeight(true)); - h.prefHeight(28); - list.add(h); + var paneComp = new StackPaneBuilder(); + paneComp.minHeight(28); + paneComp.maxHeight(28); + paneComp.prefHeight(28); + list.add(paneComp); } var content = createChildrenList( @@ -88,10 +65,47 @@ public class StoreSectionMiniComp extends StoreSectionBaseComp { struc.setFillWidth(true); addPseudoClassListeners(struc, expanded); if (section.getWrapper() != null) { - var hbox = ((HBox) struc.getChildren().getFirst()); - addVisibilityListeners(struc, hbox); + var pane = ((Pane) struc.getChildren().getFirst()); + addVisibilityListeners(struc, pane, () -> buildContent().build()); } }); return full.build(); } + + private RegionBuilder buildContent() { + var root = new ButtonComp(section.getWrapper().getShownName(), () -> { + action.accept(section); + }); + root.hgrow(); + root.maxWidth(10000); + root.style("item"); + root.apply(struc -> { + struc.setAlignment(Pos.CENTER_LEFT); + struc + .setGraphic(PrettyImageHelper.ofFixedSize( + section.getWrapper().getIconFile(), 16, 16) + .build()); + struc.setMnemonicParsing(false); + }); + augment.accept(section, root); + + var expandButton = createExpandButton(() -> expanded.set(!expanded.get()), 20, expanded); + + var quickAccessButton = createQuickAccessButton(20, action); + + var buttonList = new ArrayList>(); + buttonList.add(expandButton); + buttonList.add(root); + if (section.getDepth() == 1) { + buttonList.add(quickAccessButton); + } + var h = new HorizontalComp(buttonList); + h.apply(struc -> struc.setFillHeight(true)); + + h.minHeight(28); + h.prefHeight(28); + h.maxHeight(28); + + return h; + } } diff --git a/app/src/main/java/io/xpipe/app/platform/BindingsHelper.java b/app/src/main/java/io/xpipe/app/platform/BindingsHelper.java index d59b3e5aa..6992f40da 100644 --- a/app/src/main/java/io/xpipe/app/platform/BindingsHelper.java +++ b/app/src/main/java/io/xpipe/app/platform/BindingsHelper.java @@ -6,13 +6,17 @@ import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.scene.Node; +import javafx.scene.layout.Region; import lombok.Value; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; @SuppressWarnings("InfiniteLoopStatement") @@ -73,6 +77,23 @@ public class BindingsHelper { return prop; } + public static void attach(R node, ObservableValue value, Consumer consumer) { + var listener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, T oldValue, T newValue) { + consumer.accept(newValue); + } + }; + node.sceneProperty().subscribe(scene -> { + if (scene != null) { + consumer.accept(value.getValue()); + value.addListener(listener); + } else { + value.removeListener(listener); + } + }); + } + @Value private static class ReferenceEntry { diff --git a/build.gradle b/build.gradle index 14c461873..22b33a819 100644 --- a/build.gradle +++ b/build.gradle @@ -161,14 +161,15 @@ def getJvmArgs() { jvmRunArgs += ['-XX:+UseStringDeduplication'] // GC config - jvmRunArgs += ['-XX:+UseG1GC', - '-Xms300m', - '-Xmx4G', - '-XX:MinHeapFreeRatio=20', - '-XX:MaxHeapFreeRatio=30', - '-XX:GCTimeRatio=9', - // The default makes GC pauses longer for some reason - '-XX:G1HeapRegionSize=4m' + jvmRunArgs += [ + '-XX:+UseG1GC', + '-Xms200m', + '-Xmx4G', + '-XX:MinHeapFreeRatio=15', + '-XX:MaxHeapFreeRatio=25', + '-XX:GCTimeRatio=9', + // The default makes GC pauses longer for some reason + '-XX:G1HeapRegionSize=4m' ] // Why is this not on by default? ...