Browser UI rework

This commit is contained in:
crschnick 2024-06-23 05:38:44 +00:00
parent 93eb1075ac
commit 6cc7fa180c
12 changed files with 252 additions and 92 deletions

View file

@ -1,33 +1,18 @@
package io.xpipe.app.browser;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreSectionMiniComp;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.comp.store.*;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.scene.control.Button;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.theme.Styles;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
@ -37,22 +22,23 @@ public final class BrowserBookmarkComp extends SimpleComp {
private final ObservableValue<DataStoreEntry> selected;
private final Predicate<StoreEntryWrapper> applicable;
private final BiConsumer<StoreEntryWrapper, BooleanProperty> action;
private final Property<StoreCategoryWrapper> category;
private final Property<String> filter ;
public BrowserBookmarkComp(
ObservableValue<DataStoreEntry> selected,
Predicate<StoreEntryWrapper> applicable,
BiConsumer<StoreEntryWrapper, BooleanProperty> action) {
BiConsumer<StoreEntryWrapper, BooleanProperty> action, Property<StoreCategoryWrapper> category, Property<String> filter
) {
this.selected = selected;
this.applicable = applicable;
this.action = action;
this.category = category;
this.filter = filter;
}
@Override
protected Region createSimple() {
var filterText = new SimpleStringProperty();
var selectedCategory = new SimpleObjectProperty<>(
StoreViewState.get().getActiveCategory().getValue());
BooleanProperty busy = new SimpleBooleanProperty(false);
BiConsumer<StoreSection, Comp<CompStructure<Button>>> augment = (s, comp) -> {
comp.disable(Bindings.createBooleanBinding(
@ -76,31 +62,14 @@ public final class BrowserBookmarkComp extends SimpleComp {
var section = new StoreSectionMiniComp(
StoreSection.createTopLevel(
StoreViewState.get().getAllEntries(), this::filter, filterText, selectedCategory, StoreViewState.get().getEntriesListUpdateObservable()),
StoreViewState.get().getAllEntries(), this::filter, filter, category, StoreViewState.get().getEntriesListUpdateObservable()),
augment,
entryWrapper -> action.accept(entryWrapper, busy),
true);
var category = new DataStoreCategoryChoiceComp(
StoreViewState.get().getAllConnectionsCategory(),
StoreViewState.get().getActiveCategory(),
selectedCategory)
.styleClass(Styles.LEFT_PILL)
.minWidth(Region.USE_PREF_SIZE);
var filter = new FilterComp(filterText).hgrow();
var top = new HorizontalComp(List.of(category, filter))
.styleClass("categories")
.apply(struc -> {
AppFont.medium(struc.get());
struc.get().setFillHeight(true);
})
.createRegion();
var r = section.vgrow().createRegion();
var content = new VBox(top, r);
content.setFillWidth(true);
content.getStyleClass().add("bookmark-list");
return content;
r.getStyleClass().add("bookmark-list");
return r;
}
private boolean filter(StoreEntryWrapper w) {

View file

@ -0,0 +1,45 @@
package io.xpipe.app.browser;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.store.StoreCategoryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.layout.Region;
import lombok.Getter;
import java.util.List;
@Getter
public final class BrowserBookmarkHeaderComp extends SimpleComp {
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue());
private final Property<String> filter = new SimpleStringProperty();
@Override
protected Region createSimple() {
var category = new DataStoreCategoryChoiceComp(
StoreViewState.get().getAllConnectionsCategory(),
StoreViewState.get().getActiveCategory(),
this.category)
.styleClass(Styles.LEFT_PILL)
.apply(struc -> AppFont.medium(struc.get()));
var filter = new FilterComp(this.filter).styleClass(Styles.RIGHT_PILL).apply(struc -> AppFont.medium(struc.get())).hgrow();
var top = new HorizontalComp(List.of(category, filter))
.apply(struc -> struc.get().setFillHeight(true))
.apply(struc -> {
((Region) struc.get().getChildren().get(0)).prefHeightProperty().bind(
((Region) struc.get().getChildren().get(1)).heightProperty());
})
.styleClass("bookmarks-header")
.createRegion();
return top;
}
}

View file

@ -71,7 +71,6 @@ public class BrowserTransferComp extends SimpleComp {
return entry.getFileName() + " (" + name + ")";
},
syncAllDownloaded))
.apply(struc -> struc.get().setMinHeight(150))
.grow(false, true);
var dragNotice = new LabelComp(syncAllDownloaded.flatMap(
aBoolean -> aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles")))
@ -101,7 +100,9 @@ public class BrowserTransferComp extends SimpleComp {
return p;
});
var listBox = new VerticalComp(List.of(list, dragNotice)).padding(new Insets(10, 10, 5, 10));
var listBox = new VerticalComp(List.of(list, dragNotice)).padding(new Insets(10, 10, 5, 10))
.apply(struc -> struc.get().setMinHeight(200))
.apply(struc -> struc.get().setMaxHeight(200));
var stack = LoadingOverlayComp.noProgress(
new StackComp(List.of(backgroundStack, listBox, clearPane))
.apply(DragOverPseudoClassAugment.create())

View file

@ -162,7 +162,7 @@ public class OpenFileSystemComp extends SimpleComp {
var statusBar = new BrowserStatusBarComp(model);
fileListElements.add(statusBar);
}
var fileList = new VerticalComp(fileListElements).apply(struc -> {
var fileList = new VerticalComp(fileListElements).styleClass("browser-content").apply(struc -> {
struc.get().focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
struc.get().getChildren().getFirst().requestFocus();
@ -170,7 +170,7 @@ public class OpenFileSystemComp extends SimpleComp {
});
});
var home = new BrowserOverviewComp(model);
var home = new BrowserOverviewComp(model).styleClass("browser-content");
var stack = new MultiContentComp(Map.of(
home,
model.getCurrentPath().isNull(),

View file

@ -1,6 +1,7 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.browser.BrowserBookmarkComp;
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemComp;
import io.xpipe.app.browser.fs.OpenFileSystemModel;
@ -13,6 +14,7 @@ import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
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.PlatformThread;
import io.xpipe.app.storage.DataStoreEntryRef;
@ -30,6 +32,7 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
@ -93,12 +96,15 @@ public class BrowserChooserComp extends SimpleComp {
});
};
var bookmarkTopBar = new BrowserBookmarkHeaderComp();
var bookmarksList = new BrowserBookmarkComp(
BindingsHelper.map(
model.getSelectedEntry(), v -> v.getEntry().get()),
applicable,
action)
.vgrow();
BindingsHelper.map(
model.getSelectedEntry(), v -> v.getEntry().get()),
applicable,
action,
bookmarkTopBar.getCategory(),
bookmarkTopBar.getFilter());
var stack = Comp.of(() -> {
var s = new StackPane();
model.getSelectedEntry().subscribe(selected -> {
@ -112,7 +118,9 @@ public class BrowserChooserComp extends SimpleComp {
});
return s;
});
var splitPane = new SideSplitPaneComp(bookmarksList, stack)
var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksList)).styleClass("left");
var splitPane = new SideSplitPaneComp(vertical, stack)
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
.styleClass("background")
@ -172,6 +180,7 @@ public class BrowserChooserComp extends SimpleComp {
var r = dialogPane.createRegion();
r.getStyleClass().add("browser");
r.getStyleClass().add("chooser");
return r;
}
}

View file

@ -1,20 +1,24 @@
package io.xpipe.app.browser.session;
import io.xpipe.app.browser.BrowserBookmarkComp;
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
import io.xpipe.app.browser.BrowserTransferComp;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.StackComp;
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.util.ThreadHelper;
import io.xpipe.core.store.ShellStore;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import java.util.List;
import java.util.function.BiConsumer;
@ -60,12 +64,23 @@ public class BrowserSessionComp extends SimpleComp {
});
};
var bookmarkTopBar = new BrowserBookmarkHeaderComp();
var bookmarksList = new BrowserBookmarkComp(
BindingsHelper.map(
model.getSelectedEntry(), v -> v.getEntry().get()),
applicable,
action)
.vgrow();
action,
bookmarkTopBar.getCategory(),
bookmarkTopBar.getFilter());
var bookmarksContainer = new StackComp(List.of(bookmarksList)).styleClass("bookmarks-container");
bookmarksContainer.apply(struc -> {
var rec = new Rectangle();
rec.widthProperty().bind(struc.get().widthProperty());
rec.heightProperty().bind(struc.get().heightProperty());
rec.setArcHeight(7);
rec.setArcWidth(7);
struc.get().getChildren().getFirst().setClip(rec);
}).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
@ -79,19 +94,39 @@ public class BrowserSessionComp extends SimpleComp {
model.getSelectedEntry())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left");
var tabs = new BrowserSessionTabsComp(model);
var split = new SimpleDoubleProperty();
var tabs = new BrowserSessionTabsComp(model, split).apply(struc -> struc.get().setViewOrder(1))
.apply(struc -> struc.get().setPickOnBounds(false));
var splitPane = new SideSplitPaneComp(vertical, tabs)
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
.withOnDividerChange(d -> {
AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d);
split.set(d);
})
.apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
struc.get().setPickOnBounds(false);
});
splitPane.apply(struc -> {
struc.get().skinProperty().subscribe(newValue -> {
if (newValue != null) {
Platform.runLater(() -> {
struc.get().getChildrenUnmodifiable().forEach(node -> {
node.setClip(null);
node.setPickOnBounds(false);
});
struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1));
});
}
});
});
var r = splitPane.createRegion();
r.getStyleClass().add("browser");
return r;
}
}

View file

@ -15,8 +15,10 @@ import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
@ -40,9 +42,11 @@ import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS;
public class BrowserSessionTabsComp extends SimpleComp {
private final BrowserSessionModel model;
private final ObservableDoubleValue leftPadding;
public BrowserSessionTabsComp(BrowserSessionModel model) {
public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding) {
this.model = model;
this.leftPadding = leftPadding;
}
public Region createSimple() {
@ -67,7 +71,28 @@ public class BrowserSessionTabsComp extends SimpleComp {
Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING);
toggleStyleClass(tabs, DENSE);
var map = new HashMap<BrowserSessionTab<?>, Tab>();
tabs.skinProperty().subscribe(newValue -> {
if (newValue != null) {
Platform.runLater(() -> {
tabs.setClip(null);
tabs.setPickOnBounds(false);
tabs.lookupAll(".tab-header-area").forEach(node -> {
node.setClip(null);
node.setPickOnBounds(false);
});
tabs.lookupAll(".headers-region").forEach(node -> {
node.setClip(null);
node.setPickOnBounds(false);
});
Region headerArea = (Region) tabs.lookup(".tab-header-area");
headerArea.paddingProperty().bind(Bindings.createObjectBinding(() -> new Insets(0, 0, 0, -leftPadding.get() + 2), leftPadding));
});
}
});
var map = new HashMap<BrowserSessionTab<?>, Tab>();
// Restore state
model.getSessionEntries().forEach(v -> {
@ -215,7 +240,8 @@ public class BrowserSessionTabsComp extends SimpleComp {
PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
tab.setContent(model.comp().createRegion());
Comp<?> comp = model.comp();
tab.setContent(comp.createRegion());
var id = UUID.randomUUID().toString();
tab.setId(id);

View file

@ -6,18 +6,17 @@ import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableIntegerValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import lombok.Value;
import java.util.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
@ -111,7 +110,7 @@ public class StoreSection {
public static StoreSection createTopLevel(
DerivedObservableList<StoreEntryWrapper> all,
Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
ObservableValue<String> filterString,
ObservableValue<StoreCategoryWrapper> category,
ObservableIntegerValue updateObservable
) {
@ -127,7 +126,7 @@ public class StoreSection {
var shown = ordered.filtered(
section -> {
// matches filter
return (filterString == null || section.matchesFilter(filterString.get()))
return (filterString == null || section.matchesFilter(filterString.getValue()))
&&
// matches selector
(section.anyMatches(entryFilter))
@ -148,7 +147,7 @@ public class StoreSection {
int depth,
DerivedObservableList<StoreEntryWrapper> all,
Predicate<StoreEntryWrapper> entryFilter,
ObservableStringValue filterString,
ObservableValue<String> filterString,
ObservableValue<StoreCategoryWrapper> category,
ObservableIntegerValue updateObservable) {
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
@ -185,8 +184,8 @@ public class StoreSection {
section -> {
// matches filter
return (filterString == null
|| section.matchesFilter(filterString.get())
|| l.stream().anyMatch(p -> p.matchesFilter(filterString.get())))
|| section.matchesFilter(filterString.getValue())
|| l.stream().anyMatch(p -> p.matchesFilter(filterString.getValue())))
&&
// matches selector
section.anyMatches(entryFilter)

View file

@ -1,18 +1,11 @@
.bookmark-list > .categories {
-fx-padding: 1em;
-fx-background-color: -color-bg-default;
-fx-border-color: -color-border-default;
-fx-border-width: 0 0 1 0;
}
.root.nord .bookmark-list .filter-comp {
.root.nord .bookmarks-header .filter-comp {
-fx-border-radius: 0;
-fx-background-radius: 0;
}
.bookmark-list .filter-comp {
.bookmarks-header .filter-comp {
-fx-border-width: 1;
-fx-border-radius: 0 4px 4px 0;
-fx-background-radius: 0 4px 4px 0;
@ -24,3 +17,20 @@
-fx-font-weight: BOLD;
}
.bookmark-list .store-section-mini-comp:top {
-fx-padding: 0 0 0 2px;
}
.bookmarks-container {
-fx-background-color: -color-border-default, -color-bg-default;
-fx-background-radius: 4 0 0 4;
-fx-background-insets: 0 8 0 6, 1 9 1 7;
-fx-padding: 1 1 1 7;
}
.bookmarks-header {
-fx-min-height: 3.5em;
-fx-pref-height: 3.5em;
-fx-max-height: 3.5em;
-fx-padding: 9 6;
}

View file

@ -8,10 +8,18 @@
}
.download-background {
-fx-border-color: -color-border-default;
-fx-border-width: 1px 0 0 0;
-fx-padding: 1em;
-fx-background-color: -color-bg-default;
}
.transfer {
-fx-padding: 9px 6px;
}
.transfer > * {
-fx-border-radius: 4;
-fx-border-color: -color-border-default;
-fx-border-width: 1;
-fx-background-color: -color-bg-subtle;
}
.transfer .button {
@ -23,11 +31,21 @@
-fx-padding: 0.1em 0.2em;
}
.root.nord .transfer > * {
-fx-background-radius: 0;
-fx-border-radius: 0;
}
.transfer .button:hover {
-fx-background-color: -color-bg-subtle;
-fx-opacity: 1.0;
}
.browser .welcome {
-fx-border-color: -color-border-default, -color-bg-inset;
-fx-border-width: 43 0 0 0, 42 0 0 0;
}
.browser .welcome .button:hover {
-fx-background-color: -color-neutral-muted;
}
@ -36,10 +54,9 @@
-fx-padding: 0.6em 0 0.6em 0;
}
.browser .overview {
.browser .browser-content.overview {
-fx-spacing: 1.5em;
-fx-padding: 1.5em;
-fx-background-color: -color-bg-default;
}
.selected-file-list {
@ -64,8 +81,9 @@
}
.browser .top-bar {
-fx-border-width: 1 0 1 0;
-fx-border-color: -color-border-default;
-fx-min-height: 3.5em;
-fx-pref-height: 3.5em;
-fx-max-height: 3.5em;
-fx-padding: 9px 6px;
}
@ -146,7 +164,7 @@
}
.browser .tab-pane {
-fx-border-width: 0 0 0 1px;
-fx-border-width: 0 0 0 0px;
-fx-border-color: -color-border-default;
}
@ -158,7 +176,9 @@
}
.browser .tab-content-area {
-fx-padding: 0;
-fx-border-width: 2 0 0 0;
-fx-border-color: -color-border-default;
-fx-padding: -1 0 0 0;
}
.browser .singular {
@ -169,6 +189,23 @@
visibility: hidden;
}
.browser .tab-header-area {
-fx-min-height: 42;
-fx-pref-height: 42;
-fx-max-height: 42;
}
.browser .left {
-fx-border-color: -color-border-default, -color-bg-inset;
-fx-border-width: 43 0 0 0, 42 0 0 0;
-fx-background-color: transparent;
}
.browser.chooser .left {
-fx-border-width: 0;
}
.browser .tab-header-area {
-fx-background-color: -color-bg-inset;
}
@ -177,7 +214,29 @@
-fx-background-radius: 0 10 0 0;
}
.browser .browser-content {
-fx-padding: 10 0 0 0;
-fx-border-radius: 10 0 0 0;
-fx-background-radius: 10 0 0 0;
-fx-background-color: -color-bg-subtle;
-fx-border-width: 1 0 0 1;
-fx-border-color: -color-border-default;
}
.browser .table-view {
-fx-border-width: 0 0 0 0px;
-fx-border-color: -color-border-default;
}
.browser .split-pane-divider {
-fx-border-color: -color-border-default, -color-bg-inset;
-fx-padding: 0 3;
-fx-border-width: 43 0 0 0, 42 0 0 0;
-fx-opacity: 1.0;
-fx-background-color: transparent;
}
.browser.chooser .split-pane-divider {
-fx-border-width: 0;
}
@ -187,7 +246,14 @@
.table-view .column-header-background .label {
-fx-font-size: 0.9em;
-fx-font-weight: NORMAL;
-fx-font-weight: bolder;
-fx-opacity: 0.9;
-fx-padding: 0 0 0 12;
}
.browser .table-row-cell {
-fx-border-width: 0 0 0 5;
-fx-border-color: -color-bg-subtle;
}
.browser .table-row-cell:empty {

View file

@ -1,6 +1,6 @@
.scroll-bar:vertical {
-fx-pref-width: 0.3em;
-fx-padding: 0.4em 0 0.3em 0;
-fx-pref-width: 0.4em;
-fx-padding: 0.4em 1 0.3em 1;
-fx-background-color: transparent;
}