Section rework

This commit is contained in:
crschnick
2025-03-09 09:13:55 +00:00
parent 39ee41e7b4
commit b77921549b
8 changed files with 229 additions and 287 deletions
@@ -68,10 +68,10 @@ public abstract class StoreEntryComp extends SimpleComp {
}
}
public static StoreEntryComp customSection(StoreSection e, boolean topLevel) {
public static StoreEntryComp customSection(StoreSection e) {
var prov = e.getWrapper().getEntry().getProvider();
if (prov != null) {
return prov.customEntryComp(e, topLevel);
return prov.customEntryComp(e, e.getDepth() == 1);
} else {
var forceCondensed = AppPrefs.get() != null
&& AppPrefs.get().condenseConnectionDisplay().get();
@@ -27,7 +27,7 @@ public class StoreEntryListComp extends SimpleComp {
.getAllChildren()
.getList(),
(StoreSection e) -> {
var custom = StoreSection.customSection(e, true).hgrow();
var custom = StoreSection.customSection(e).hgrow();
return custom;
},
true);
@@ -53,12 +53,12 @@ public class StoreSection {
}
}
public static Comp<?> customSection(StoreSection e, boolean topLevel) {
public static Comp<?> customSection(StoreSection e) {
var prov = e.getWrapper().getEntry().getProvider();
if (prov != null) {
return prov.customSectionComp(e, topLevel);
return prov.customSectionComp(e);
} else {
return new StoreSectionComp(e, topLevel);
return new StoreSectionComp(e);
}
}
@@ -0,0 +1,161 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.augment.GrowAugment;
import io.xpipe.app.comp.base.HorizontalComp;
import io.xpipe.app.comp.base.IconButtonComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.comp.base.VerticalComp;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.util.LabelGraphic;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
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.VBox;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class StoreSectionBaseComp extends Comp<CompStructure<VBox>> {
private static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
private static final PseudoClass TOP = PseudoClass.getPseudoClass("top");
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
protected final StoreSection section;
public StoreSectionBaseComp(StoreSection section) {
this.section = section;
}
protected ObservableBooleanValue effectiveExpanded(ObservableBooleanValue expanded) {
return section.getWrapper() != null ? Bindings.createBooleanBinding(
() -> {
return expanded.get()
&& section.getShownChildren().getList().size() > 0;
},
expanded,
section.getShownChildren().getList()) : new SimpleBooleanProperty(true);
}
protected void addPseudoClassListeners(VBox vbox, ObservableBooleanValue expanded) {
effectiveExpanded(expanded).subscribe(val -> {
vbox.pseudoClassStateChanged(EXPANDED, val);
});
vbox.pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
vbox.pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
vbox.pseudoClassStateChanged(ROOT, section.getDepth() == 0);
vbox.pseudoClassStateChanged(SUB, section.getDepth() > 1);
vbox.pseudoClassStateChanged(TOP, section.getDepth() == 1);
if (section.getWrapper() != null) {
if (section.getDepth() == 1) {
section.getWrapper().getColor().subscribe(val -> {
var newList = new ArrayList<>(vbox.getStyleClass());
newList.removeIf(s -> Arrays.stream(DataColor.values()).anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
newList.remove("gray");
newList.add("color-box");
if (val != null) {
newList.add(val.getId());
} else {
newList.add("gray");
}
vbox.getStyleClass().setAll(newList);
});
}
section.getWrapper().getPerUser().subscribe(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) -> {
if (newValue) {
hbox.getChildren().addAll(children);
} else {
hbox.getChildren().removeAll(children);
}
});
}
protected ListBoxViewComp<StoreSection> createChildrenList(Function<StoreSection, Comp<?>> function, ObservableBooleanValue hide) {
var content = new ListBoxViewComp<>(
section.getShownChildren().getList(),
section.getAllChildren().getList(),
(StoreSection e) -> {
return function.apply(e).grow(true, false);
},
section.getWrapper() == null);
content.minHeight(0);
content.hgrow();
content.styleClass("children-content");
content.hide(hide);
return content;
}
protected Comp<CompStructure<Button>> createExpandButton(Runnable action, int width, ObservableBooleanValue expanded) {
var icon = Bindings.createObjectBinding(() -> new LabelGraphic.IconGraphic(
expanded.get() && section.getShownChildren().getList().size() > 0 ?
"mdal-keyboard_arrow_down" :
"mdal-keyboard_arrow_right"), expanded, section.getShownChildren().getList());
var expandButton = new IconButtonComp(icon,
action);
expandButton
.minWidth(width)
.prefWidth(width)
.accessibleText(Bindings.createStringBinding(
() -> {
return "Expand " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
.styleClass("expand-button")
.maxHeight(100);
return expandButton;
}
protected Comp<CompStructure<Button>> createQuickAccessButton(int width, Consumer<StoreSection> r) {
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().getList().isEmpty();
},
section.getShownChildren().getList());
var quickAccessButton = new StoreQuickAccessButtonComp(section, r)
.styleClass("quick-access-button")
.minWidth(width)
.prefWidth(width)
.maxHeight(100)
.accessibleText(Bindings.createStringBinding(
() -> {
return "Access " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(quickAccessDisabled);
return quickAccessButton;
}
}
@@ -12,6 +12,7 @@ import io.xpipe.app.util.LabelGraphic;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@@ -26,85 +27,15 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class StoreSectionComp extends Comp<CompStructure<VBox>> {
public class StoreSectionComp extends StoreSectionBaseComp {
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
private final StoreSection section;
private final boolean topLevel;
public StoreSectionComp(StoreSection section, boolean topLevel) {
this.section = section;
this.topLevel = topLevel;
}
private Comp<CompStructure<Button>> createQuickAccessButton() {
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().getList().isEmpty();
},
section.getShownChildren().getList());
Consumer<StoreSection> quickAccessAction = w -> {
ThreadHelper.runFailableAsync(() -> {
w.getWrapper().executeDefaultAction();
});
};
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
.vgrow()
.styleClass("quick-access-button")
.apply(struc -> struc.get().setMinWidth(30))
.apply(struc -> struc.get().setPrefWidth(30))
.maxHeight(100)
.accessibleText(Bindings.createStringBinding(
() -> {
return "Access " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(quickAccessDisabled)
.focusTraversableForAccessibility()
.tooltipKey("accessSubConnections", new KeyCodeCombination(KeyCode.RIGHT));
return quickAccessButton;
}
private Comp<CompStructure<Button>> createExpandButton() {
var expandButton = new IconButtonComp(
Bindings.createObjectBinding(
() -> new LabelGraphic.IconGraphic(
section.getWrapper().getExpanded().get()
&& section.getShownChildren()
.getList()
.size()
> 0
? "mdal-keyboard_arrow_down"
: "mdal-keyboard_arrow_right"),
section.getWrapper().getExpanded(),
section.getShownChildren().getList()),
() -> {
section.getWrapper().toggleExpanded();
});
expandButton
.apply(struc -> struc.get().setMinWidth(30))
.apply(struc -> struc.get().setPrefWidth(30))
.focusTraversableForAccessibility()
.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE))
.accessibleText(Bindings.createStringBinding(
() -> {
return "Expand " + section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
.styleClass("expand-button")
.maxHeight(100)
.vgrow();
return expandButton;
public StoreSectionComp(StoreSection section) {
super(section);
}
@Override
public CompStructure<VBox> createBase() {
var entryButton = StoreEntryComp.customSection(section, topLevel);
var entryButton = StoreEntryComp.customSection(section);
entryButton.hgrow();
entryButton.apply(struc -> {
struc.get().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
@@ -123,8 +54,17 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
});
});
var quickAccessButton = createQuickAccessButton();
var expandButton = createExpandButton();
var quickAccessButton = createQuickAccessButton(30, c -> {
ThreadHelper.runFailableAsync(() -> {
c.getWrapper().executeDefaultAction();
});
});
quickAccessButton.focusTraversableForAccessibility();
quickAccessButton.tooltipKey("accessSubConnections", new KeyCodeCombination(KeyCode.RIGHT));
var expandButton = createExpandButton(() -> section.getWrapper().toggleExpanded(), 30, section.getWrapper().getExpanded());
expandButton.focusTraversableForAccessibility();
expandButton.tooltipKey("expand", new KeyCodeCombination(KeyCode.SPACE));
var buttonList = new ArrayList<Comp<?>>();
if (entryButton.isFullSize()) {
buttonList.add(quickAccessButton);
@@ -137,95 +77,21 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
topEntryList.maxHeight(entryButton.getHeight());
topEntryList.prefHeight(entryButton.getHeight());
// 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.getShownChildren()
.filtered(
storeSection -> section.getAllChildren().getList().size() <= 20
|| section.getWrapper().getExpanded().get(),
section.getWrapper().getExpanded(),
section.getAllChildren().getList());
var content = new ListBoxViewComp<>(
listSections.getList(),
section.getAllChildren().getList(),
(StoreSection e) -> {
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
},
false);
content.minHeight(0).hgrow();
var effectiveExpanded = effectiveExpanded(section.getWrapper().getExpanded());
var content = createChildrenList(c -> StoreSection.customSection(c), Bindings.not(effectiveExpanded));
var expanded = Bindings.createBooleanBinding(
() -> {
return section.getWrapper().getExpanded().get()
&& section.getShownChildren().getList().size() > 0;
},
section.getWrapper().getExpanded(),
section.getShownChildren().getList());
var full = new VerticalComp(List.of(
topEntryList,
Comp.separator().hide(expanded.not()),
content.styleClass("children-content")
.hide(Bindings.or(
Bindings.not(section.getWrapper().getExpanded()),
Bindings.size(section.getShownChildren().getList())
.isEqualTo(0)))));
Comp.separator().hide(Bindings.not(effectiveExpanded)),
content));
full.styleClass("store-entry-section-comp");
full.apply(struc -> {
var hbox = ((HBox) struc.get().getChildren().getFirst());
var buttonsRegion = hbox.getChildren().getFirst();
var storeRegion = hbox.getChildren().get(1);
hbox.getChildren().remove(buttonsRegion);
hbox.getChildren().remove(storeRegion);
struc.get().visibleProperty().subscribe((newValue) -> {
if (newValue) {
if (!hbox.getChildren().contains(buttonsRegion)) {
hbox.getChildren().add(buttonsRegion);
}
if (!hbox.getChildren().contains(storeRegion)) {
hbox.getChildren().add(storeRegion);
}
} else {
hbox.getChildren().remove(storeRegion);
hbox.getChildren().remove(buttonsRegion);
}
});
});
return full.styleClass("store-entry-section-comp")
.apply(struc -> {
struc.get().setFillWidth(true);
expanded.subscribe(val -> {
struc.get().pseudoClassStateChanged(EXPANDED, val);
});
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
struc.get().pseudoClassStateChanged(ROOT, topLevel);
struc.get().pseudoClassStateChanged(SUB, !topLevel);
section.getWrapper().getColor().subscribe(val -> {
if (!topLevel) {
return;
}
var newList = new ArrayList<>(struc.get().getStyleClass());
newList.removeIf(s -> Arrays.stream(DataColor.values())
.anyMatch(
dataStoreColor -> dataStoreColor.getId().equals(s)));
newList.remove("gray");
newList.add("color-box");
if (val != null) {
newList.add(val.getId());
} else {
newList.add("gray");
}
struc.get().getStyleClass().setAll(newList);
});
section.getWrapper().getPerUser().subscribe(val -> {
struc.get().pseudoClassStateChanged(PseudoClass.getPseudoClass("per-user"), val);
});
var hbox = ((HBox) struc.get().getChildren().getFirst());
addPseudoClassListeners(struc.get(), section.getWrapper().getExpanded());
addVisibilityListeners(struc.get(), hbox);
})
.createStructure();
return full.createStructure();
}
}
@@ -12,23 +12,18 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
public class StoreSectionMiniComp extends StoreSectionBaseComp {
public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth");
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth");
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
private static final PseudoClass TOP = PseudoClass.getPseudoClass("top");
private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub");
private final StoreSection section;
private final BooleanProperty expanded;
private final BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment;
private final Consumer<StoreSection> action;
@@ -36,142 +31,62 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
StoreSection section,
BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment,
Consumer<StoreSection> action) {
this.section = section;
super(section);
this.augment = augment;
this.action = action;
this.expanded = new SimpleBooleanProperty(section.getWrapper() == null || section.getWrapper().getExpanded().getValue());
}
@Override
public CompStructure<VBox> createBase() {
var list = new ArrayList<Comp<?>>();
BooleanProperty expanded;
if (section.getWrapper() != null) {
var root = new ButtonComp(section.getWrapper().getShownName(), () -> {})
.apply(struc -> {
struc.get()
.setGraphic(PrettyImageHelper.ofFixedSize(
var root = new ButtonComp(section.getWrapper().getShownName(), () -> {
action.accept(section);
});
root.hgrow();
root.maxWidth(2000);
root.styleClass("item");
root.apply(struc -> {
struc.get().setAlignment(Pos.CENTER_LEFT);
struc.get().setGraphic(PrettyImageHelper.ofFixedSize(
section.getWrapper().getIconFile(), 16, 16)
.createRegion());
})
.apply(struc -> {
struc.get().setAlignment(Pos.CENTER_LEFT);
})
.apply(struc -> {
struc.get().setOnAction(event -> {
action.accept(section);
event.consume();
});
})
.grow(true, false)
.apply(struc -> struc.get().setMnemonicParsing(false))
.styleClass("item");
struc.get().setMnemonicParsing(false);
});
augment.accept(section, root);
expanded =
new SimpleBooleanProperty(section.getWrapper().getExpanded().get()
&& section.getShownChildren().getList().size() > 0);
var button = new IconButtonComp(
Bindings.createObjectBinding(
() -> new LabelGraphic.IconGraphic(
expanded.get() ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right"),
expanded),
() -> {
expanded.set(!expanded.get());
})
.apply(struc -> struc.get().setMinWidth(20))
.apply(struc -> struc.get().setPrefWidth(20))
.focusTraversable()
.accessibleText(Bindings.createStringBinding(
() -> {
return "Expand "
+ section.getWrapper().getName().getValue();
},
section.getWrapper().getName()))
.disable(Bindings.size(section.getShownChildren().getList()).isEqualTo(0))
.grow(false, true)
.styleClass("expand-button");
var expandButton = createExpandButton(() -> expanded.set(!expanded.get()), 20, expanded);
expandButton.focusTraversable();
var quickAccessDisabled = Bindings.createBooleanBinding(
() -> {
return section.getShownChildren().getList().isEmpty();
},
section.getShownChildren().getList());
Consumer<StoreSection> quickAccessAction = action;
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
.vgrow()
.styleClass("quick-access-button")
.maxHeight(100)
.disable(quickAccessDisabled);
var quickAccessButton = createQuickAccessButton(20, action);
var buttonList = new ArrayList<Comp<?>>();
buttonList.add(button);
buttonList.add(expandButton);
buttonList.add(root);
if (section.getDepth() == 1) {
buttonList.add(quickAccessButton);
}
list.add(new HorizontalComp(buttonList).apply(struc -> struc.get().setFillHeight(true)));
} else {
expanded = new SimpleBooleanProperty(true);
var h = new HorizontalComp(buttonList);
h.apply(struc -> struc.get().setFillHeight(true));
h.prefHeight(28);
list.add(h);
}
// 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
? section.getShownChildren()
.filtered(
storeSection ->
section.getAllChildren().getList().size() <= 20 || expanded.get(),
expanded,
section.getAllChildren().getList())
: section.getShownChildren();
var content = new ListBoxViewComp<>(
listSections.getList(),
section.getAllChildren().getList(),
(StoreSection e) -> {
return new StoreSectionMiniComp(e, this.augment, this.action);
},
section.getWrapper() == null)
.minHeight(0)
.hgrow();
var content = createChildrenList(c -> new StoreSectionMiniComp(c, this.augment, this.action), Bindings.not(expanded));
list.add(content);
list.add(content.styleClass("children-content")
.hide(Bindings.or(
Bindings.not(expanded),
Bindings.size(section.getAllChildren().getList()).isEqualTo(0))));
var vert = new VerticalComp(list);
return vert.styleClass("store-section-mini-comp")
.apply(struc -> {
var full = new VerticalComp(list);
full.styleClass("store-section-mini-comp");
full.apply(struc -> {
struc.get().setFillWidth(true);
expanded.subscribe(val -> {
struc.get().pseudoClassStateChanged(EXPANDED, val);
});
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
struc.get().pseudoClassStateChanged(ROOT, section.getDepth() == 0);
struc.get().pseudoClassStateChanged(TOP, section.getDepth() == 1);
struc.get().pseudoClassStateChanged(SUB, section.getDepth() > 1);
})
.apply(struc -> {
addPseudoClassListeners(struc.get(), expanded);
if (section.getWrapper() != null) {
section.getWrapper().getColor().subscribe(val -> {
if (section.getDepth() != 1) {
return;
}
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataColor.values())
.anyMatch(dataStoreColor ->
dataStoreColor.getId().equals(s)));
struc.get().getStyleClass().remove("gray");
struc.get().getStyleClass().add("color-box");
if (val != null) {
struc.get().getStyleClass().add(val.getId());
} else {
struc.get().getStyleClass().add("gray");
}
});
var hbox = ((HBox) struc.get().getChildren().getFirst());
addVisibilityListeners(struc.get(), hbox);
}
})
.createStructure();
return full.createStructure();
}
}
@@ -93,8 +93,8 @@ public interface DataStoreProvider {
return StoreEntryComp.create(s, null, preferLarge);
}
default StoreSectionComp customSectionComp(StoreSection section, boolean topLevel) {
return new StoreSectionComp(section, topLevel);
default StoreSectionComp customSectionComp(StoreSection section) {
return new StoreSectionComp(section);
}
default boolean shouldShowScan() {
@@ -133,7 +133,7 @@
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2, 0.5, 0, 1);
}
.store-entry-section-comp:root {
.store-entry-section-comp:top {
-fx-border-radius: 4px;
-fx-background-radius: 4px;
}