mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-08-13 13:01:02 +00:00
More docking rework
This commit is contained in:
@@ -20,7 +20,7 @@ public class ConnectionBrowseExchangeImpl extends ConnectionBrowseExchange {
|
||||
throw new BeaconClientException("Not a file system connection");
|
||||
}
|
||||
BrowserSessionModel.DEFAULT.openFileSystemSync(
|
||||
e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null);
|
||||
e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null, true);
|
||||
AppLayoutModel.get().selectBrowser();
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionModel;
|
||||
import io.xpipe.app.browser.session.BrowserSessionTab;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.terminal.TerminalDockComp;
|
||||
@@ -18,11 +19,14 @@ import java.util.UUID;
|
||||
|
||||
public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||
|
||||
private final BrowserSessionTab origin;
|
||||
private final ObservableList<UUID> terminalRequests;
|
||||
private final TerminalDockModel dockModel = new TerminalDockModel();
|
||||
private TerminalView.Listener listener;
|
||||
|
||||
public BrowserTerminalDockTabModel(BrowserAbstractSessionModel<?> browserModel, ObservableList<UUID> terminalRequests) {
|
||||
public BrowserTerminalDockTabModel(BrowserAbstractSessionModel<?> browserModel, BrowserSessionTab origin, ObservableList<UUID> terminalRequests) {
|
||||
super(browserModel, AppI18n.get("terminal"), null);
|
||||
this.origin = origin;
|
||||
this.terminalRequests = terminalRequests;
|
||||
}
|
||||
|
||||
@@ -38,23 +42,32 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
var sessions = new ArrayList<TerminalView.Session>();
|
||||
var terminals = new ArrayList<TerminalViewInstance>();
|
||||
TerminalView.get().addListener(new TerminalView.Listener() {
|
||||
listener = new TerminalView.Listener() {
|
||||
@Override
|
||||
public void onSessionOpened(TerminalView.Session session) {
|
||||
if (!terminalRequests.contains(session.getRequest())) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tv = terminals.stream().filter(instance -> instance.getTerminalProcess().equals(session.getTerminal())).findFirst();
|
||||
tv.ifPresent(instance -> {
|
||||
dockModel.trackTerminal(instance);
|
||||
});
|
||||
sessions.add(session);
|
||||
var tv = terminals.stream().filter(instance -> sessions.stream().anyMatch(s -> instance.getTerminalProcess().equals(s.getTerminal()))).toList();
|
||||
if (tv.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tv.size() - 1; i++) {
|
||||
dockModel.closeTerminal(tv.get(i));
|
||||
}
|
||||
|
||||
var toTrack = tv.getLast();
|
||||
dockModel.trackTerminal(toTrack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionClosed(TerminalView.Session session) {
|
||||
|
||||
sessions.remove(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,13 +77,28 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||
|
||||
@Override
|
||||
public void onTerminalClosed(TerminalViewInstance instance) {
|
||||
terminals.remove(instance);
|
||||
terminals.remove(instance);
|
||||
if (terminals.isEmpty()) {
|
||||
((BrowserSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
TerminalView.get().addListener(listener);
|
||||
this.browserModel.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
|
||||
dockModel.toggleView(newValue == origin);
|
||||
});
|
||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
dockModel.toggleView(AppLayoutModel.get().getEntries().indexOf(newValue) == 1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
public void close() {
|
||||
if (listener != null) {
|
||||
TerminalView.get().removeListener(listener);
|
||||
}
|
||||
dockModel.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
@@ -81,9 +109,4 @@ terminals.remove(instance);
|
||||
public DataColor getColor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloseable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.comp.base.DialogComp;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
@@ -148,7 +148,7 @@ public class BrowserChooserComp extends DialogComp {
|
||||
});
|
||||
|
||||
var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer)).styleClass("left");
|
||||
var splitPane = new SideSplitPaneComp(vertical, stack)
|
||||
var splitPane = new LeftSplitPaneComp(vertical, stack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth)
|
||||
.styleClass("background")
|
||||
|
||||
@@ -4,13 +4,14 @@ import io.xpipe.app.browser.BrowserBookmarkComp;
|
||||
import io.xpipe.app.browser.BrowserBookmarkHeaderComp;
|
||||
import io.xpipe.app.browser.BrowserTransferComp;
|
||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.AnchorComp;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.fxcomps.impl.VerticalComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
@@ -104,8 +105,9 @@ public class BrowserSessionComp extends SimpleComp {
|
||||
var vertical =
|
||||
new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left");
|
||||
|
||||
var split = new SimpleDoubleProperty();
|
||||
var tabs = new BrowserSessionTabsComp(model, split);
|
||||
var leftSplit = new SimpleDoubleProperty();
|
||||
var rightSplit = new SimpleDoubleProperty();
|
||||
var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit);
|
||||
tabs.apply(struc -> {
|
||||
struc.get().setViewOrder(1);
|
||||
struc.get().setPickOnBounds(false);
|
||||
@@ -114,6 +116,7 @@ public class BrowserSessionComp extends SimpleComp {
|
||||
AnchorPane.setLeftAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
});
|
||||
|
||||
vertical.apply(struc -> {
|
||||
struc.get()
|
||||
.paddingProperty()
|
||||
@@ -126,14 +129,41 @@ public class BrowserSessionComp extends SimpleComp {
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
})
|
||||
.styleClass("tab-loading-indicator");
|
||||
var loadingStack = new AnchorComp(List.of(tabs, loadingIndicator));
|
||||
var splitPane = new SideSplitPaneComp(vertical, loadingStack)
|
||||
|
||||
var pinnedStack = new StackComp(List.of(new LabelComp("a")));
|
||||
pinnedStack.apply(struc -> {
|
||||
model.getEffectiveRightTab().subscribe( (newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (newValue != null) {
|
||||
var r = newValue.comp().createRegion();
|
||||
struc.get().getChildren().add(r);
|
||||
} else {
|
||||
struc.get().getChildren().clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
rightSplit.addListener((observable, oldValue, newValue) -> {
|
||||
struc.get().setMinWidth(newValue.doubleValue());
|
||||
struc.get().setMaxWidth(newValue.doubleValue());
|
||||
struc.get().setPrefWidth(newValue.doubleValue());
|
||||
});
|
||||
|
||||
AnchorPane.setBottomAnchor(struc.get(), 0.0);
|
||||
AnchorPane.setRightAnchor(struc.get(), 0.0);
|
||||
tabs.getHeaderHeight().subscribe(number -> {
|
||||
AnchorPane.setTopAnchor(struc.get(), number.doubleValue());
|
||||
});
|
||||
});
|
||||
|
||||
var loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator));
|
||||
var splitPane = new LeftSplitPaneComp(vertical, loadingStack)
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth())
|
||||
.withOnDividerChange(d -> {
|
||||
AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d);
|
||||
split.set(d);
|
||||
})
|
||||
.apply(struc -> {
|
||||
leftSplit.set(d);
|
||||
});
|
||||
splitPane.apply(struc -> {
|
||||
struc.getLeft().setMinWidth(200);
|
||||
struc.getLeft().setMaxWidth(500);
|
||||
struc.get().setPickOnBounds(false);
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.xpipe.app.browser.BrowserSavedState;
|
||||
import io.xpipe.app.browser.BrowserSavedStateImpl;
|
||||
import io.xpipe.app.browser.BrowserTransferModel;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
@@ -12,14 +13,20 @@ import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab> {
|
||||
@@ -32,6 +39,84 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||
|
||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||
private final Property<Boolean> draggingFiles = new SimpleBooleanProperty();
|
||||
private final Property<BrowserSessionTab> globalPinnedTab = new SimpleObjectProperty<>();
|
||||
private final ObservableMap<BrowserSessionTab, BrowserSessionTab> splits = FXCollections.observableHashMap();
|
||||
private final ObservableValue<BrowserSessionTab> effectiveRightTab = createEffectiveRightTab();
|
||||
|
||||
private ObservableValue<BrowserSessionTab> createEffectiveRightTab() {
|
||||
return Bindings.createObjectBinding(() -> {
|
||||
var current = selectedEntry.getValue();
|
||||
if (!current.isCloseable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var split = splits.get(current);
|
||||
if (split != null) {
|
||||
return split;
|
||||
}
|
||||
|
||||
var global = globalPinnedTab.getValue();
|
||||
if (global == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (global == selectedEntry.getValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return global;
|
||||
}, globalPinnedTab, selectedEntry, splits);
|
||||
}
|
||||
|
||||
public BrowserSessionModel() {
|
||||
sessionEntries.addListener((ListChangeListener<? super BrowserSessionTab>) c -> {
|
||||
var v = globalPinnedTab.getValue();
|
||||
if (v != null && !c.getList().contains(v)) {
|
||||
globalPinnedTab.setValue(null);
|
||||
}
|
||||
|
||||
splits.keySet().removeIf(browserSessionTab -> !c.getList().contains(browserSessionTab));
|
||||
});
|
||||
}
|
||||
|
||||
public void splitTab(BrowserSessionTab tab, BrowserSessionTab split) {
|
||||
if (splits.containsKey(tab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
splits.put(tab, split);
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
split.init();
|
||||
});
|
||||
}
|
||||
|
||||
public void unsplitTab(BrowserSessionTab tab) {
|
||||
if (splits.values().remove(tab)) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
tab.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void pinTab(BrowserSessionTab tab) {
|
||||
if (tab.equals(globalPinnedTab.getValue())) {
|
||||
return;
|
||||
}
|
||||
|
||||
globalPinnedTab.setValue(tab);
|
||||
|
||||
var nextIndex = getSessionEntries().indexOf(tab) + 1;
|
||||
if (nextIndex < getSessionEntries().size()) {
|
||||
getSelectedEntry().setValue(getSessionEntries().get(nextIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public void unpinTab(BrowserSessionTab tab) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
globalPinnedTab.setValue(null);
|
||||
});
|
||||
}
|
||||
|
||||
public void restoreState(BrowserSavedState state) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
@@ -53,7 +138,8 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||
|
||||
public void reset() {
|
||||
synchronized (BrowserSessionModel.this) {
|
||||
for (var o : new ArrayList<>(sessionEntries)) {
|
||||
var all = new ArrayList<>(sessionEntries);
|
||||
for (var o : all) {
|
||||
// Don't close busy connections gracefully
|
||||
// as we otherwise might lock up
|
||||
if (!o.canImmediatelyClose()) {
|
||||
@@ -79,14 +165,15 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
openFileSystemSync(store, path, externalBusy);
|
||||
openFileSystemSync(store, path, externalBusy, true);
|
||||
});
|
||||
}
|
||||
|
||||
public OpenFileSystemModel openFileSystemSync(
|
||||
DataStoreEntryRef<? extends FileSystemStore> store,
|
||||
FailableFunction<OpenFileSystemModel, String, Exception> path,
|
||||
BooleanProperty externalBusy)
|
||||
BooleanProperty externalBusy,
|
||||
boolean select)
|
||||
throws Exception {
|
||||
OpenFileSystemModel model;
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
@@ -96,8 +183,10 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserSessionModel.this) {
|
||||
sessionEntries.add(model);
|
||||
// The tab pane doesn't automatically select new tabs
|
||||
selectedEntry.setValue(model);
|
||||
if (select) {
|
||||
// The tab pane doesn't automatically select new tabs
|
||||
selectedEntry.setValue(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,4 +197,14 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeSync(BrowserSessionTab e) {
|
||||
var split = splits.get(e);
|
||||
if (split != null) {
|
||||
split.close();
|
||||
}
|
||||
|
||||
super.closeSync(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,12 @@ import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableDoubleValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@@ -15,6 +19,7 @@ public abstract class BrowserSessionTab {
|
||||
protected final BrowserAbstractSessionModel<?> browserModel;
|
||||
protected final String name;
|
||||
protected final String tooltip;
|
||||
protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>();
|
||||
|
||||
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, String name, String tooltip) {
|
||||
this.browserModel = browserModel;
|
||||
|
||||
@@ -18,6 +18,7 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableDoubleValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -33,6 +34,7 @@ import atlantafx.base.theme.Styles;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static atlantafx.base.theme.Styles.DENSE;
|
||||
import static atlantafx.base.theme.Styles.toggleStyleClass;
|
||||
@@ -42,13 +44,15 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
|
||||
private final BrowserSessionModel model;
|
||||
private final ObservableDoubleValue leftPadding;
|
||||
private final DoubleProperty rightPadding;
|
||||
|
||||
@Getter
|
||||
private final DoubleProperty headerHeight;
|
||||
|
||||
public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding) {
|
||||
public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding, DoubleProperty rightPadding) {
|
||||
this.model = model;
|
||||
this.leftPadding = leftPadding;
|
||||
this.rightPadding = rightPadding;
|
||||
this.headerHeight = new SimpleDoubleProperty();
|
||||
}
|
||||
|
||||
@@ -258,9 +262,28 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu(TabPane tabs, Tab tab) {
|
||||
private ContextMenu createContextMenu(TabPane tabs, Tab tab, BrowserSessionTab tabModel) {
|
||||
var cm = ContextMenuHelper.create();
|
||||
|
||||
if (tabModel.isCloseable()) {
|
||||
var unsplit = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("unpinTab"));
|
||||
unsplit.visibleProperty().bind(PlatformThread.sync(Bindings.createBooleanBinding(() -> {
|
||||
return model.getGlobalPinnedTab().getValue() != null && model.getGlobalPinnedTab().getValue().equals(tabModel);
|
||||
}, model.getGlobalPinnedTab())));
|
||||
unsplit.setOnAction(event -> {
|
||||
model.unpinTab(tabModel);
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(unsplit);
|
||||
|
||||
var split = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("pinTab"));
|
||||
split.setOnAction(event -> {
|
||||
model.pinTab(tabModel);
|
||||
event.consume();
|
||||
});
|
||||
cm.getItems().add(split);
|
||||
}
|
||||
|
||||
var select = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("selectTab"));
|
||||
select.acceleratorProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
@@ -340,7 +363,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
|
||||
private Tab createTab(TabPane tabs, BrowserSessionTab tabModel) {
|
||||
var tab = new Tab();
|
||||
tab.setContextMenu(createContextMenu(tabs, tab));
|
||||
tab.setContextMenu(createContextMenu(tabs, tab, tabModel));
|
||||
|
||||
tab.setClosable(tabModel.isCloseable());
|
||||
|
||||
@@ -368,10 +391,62 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
},
|
||||
PlatformThread.sync(tabModel.getBusy())));
|
||||
}
|
||||
tab.setText(tabModel.getName());
|
||||
|
||||
if (tabModel.getBrowserModel() instanceof BrowserSessionModel sessionModel) {
|
||||
var global = PlatformThread.sync(sessionModel.getGlobalPinnedTab());
|
||||
tab.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
return tabModel.getName() + (global.getValue() == tabModel ? " (" + AppI18n.get("pinned") + ")" : "");
|
||||
}, global, AppPrefs.get().language()));
|
||||
} else {
|
||||
tab.setText(tabModel.getName());
|
||||
}
|
||||
|
||||
Comp<?> comp = tabModel.comp();
|
||||
tab.setContent(comp.createRegion());
|
||||
var compRegion = comp.createRegion();
|
||||
var empty = new StackPane();
|
||||
empty.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (tabModel.isCloseable() && tabs.getSelectionModel().getSelectedItem() == tab) {
|
||||
rightPadding.setValue(newValue.doubleValue());
|
||||
}
|
||||
});
|
||||
var split = new SplitPane(compRegion);
|
||||
if (tabModel.isCloseable()) {
|
||||
split.getItems().add(empty);
|
||||
}
|
||||
model.getEffectiveRightTab().subscribe(browserSessionTab -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (browserSessionTab != null && split.getItems().size() > 1) {
|
||||
split.getItems().set(1, empty);
|
||||
} else if (browserSessionTab != null && split.getItems().size() == 1) {
|
||||
split.getItems().add(empty);
|
||||
} else if (browserSessionTab == null && split.getItems().size() > 1) {
|
||||
split.getItems().remove(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
tab.setContent(split);
|
||||
|
||||
// var lastSplitRegion = new AtomicReference<Region>();
|
||||
// model.getGlobalPinnedTab().subscribe( (newValue) -> {
|
||||
// PlatformThread.runLaterIfNeeded(() -> {
|
||||
// if (newValue != null) {
|
||||
// var r = newValue.comp().createRegion();
|
||||
// split.getItems().add(r);
|
||||
// lastSplitRegion.set(r);
|
||||
// } else if (split.getItems().size() > 1) {
|
||||
// split.getItems().removeLast();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// model.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
|
||||
// PlatformThread.runLaterIfNeeded(() -> {
|
||||
// if (newValue != null && newValue.equals(model.getGlobalPinnedTab().getValue()) && split.getItems().size() > 1) {
|
||||
// split.getItems().remove(lastSplitRegion.get());
|
||||
// } else if (split.getItems().size() > 1 && !split.getItems().contains(lastSplitRegion.get())) {
|
||||
// split.getItems().add(lastSplitRegion.get());
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
var id = UUID.randomUUID().toString();
|
||||
tab.setId(id);
|
||||
|
||||
+4
-4
@@ -11,14 +11,14 @@ import lombok.Value;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||
public class LeftSplitPaneComp extends Comp<LeftSplitPaneComp.Structure> {
|
||||
|
||||
private final Comp<?> left;
|
||||
private final Comp<?> center;
|
||||
private Double initialWidth;
|
||||
private Consumer<Double> onDividerChange;
|
||||
|
||||
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||
public LeftSplitPaneComp(Comp<?> left, Comp<?> center) {
|
||||
this.left = left;
|
||||
this.center = center;
|
||||
}
|
||||
@@ -58,12 +58,12 @@ public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
|
||||
return new Structure(sidebar, c, r, r.getDividers().getFirst());
|
||||
}
|
||||
|
||||
public SideSplitPaneComp withInitialWidth(double val) {
|
||||
public LeftSplitPaneComp withInitialWidth(double val) {
|
||||
this.initialWidth = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SideSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
|
||||
public LeftSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
|
||||
this.onDividerChange = onDividerChange;
|
||||
return this;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.base.SideSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.core.AppActionLinkDetector;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
@@ -15,7 +15,7 @@ public class StoreLayoutComp extends SimpleComp {
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
|
||||
var struc = new LeftSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp())
|
||||
.withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth())
|
||||
.withOnDividerChange(aDouble -> {
|
||||
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
|
||||
|
||||
@@ -61,11 +61,11 @@ public class TerminalCategory extends AppPrefsCategory {
|
||||
.apply(struc -> struc.get().setPromptText("myterminal -e $CMD"))
|
||||
.hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM)))
|
||||
.addComp(terminalTest)
|
||||
.nameAndDescription("clearTerminalOnInit")
|
||||
.addToggle(prefs.clearTerminalOnInit)
|
||||
.nameAndDescription("enableTerminalDocking")
|
||||
.pref(prefs.enableTerminalDocking)
|
||||
.addToggle(prefs.enableTerminalDocking)
|
||||
.hide(new SimpleBooleanProperty(!TerminalView.isSupported()))
|
||||
.nameAndDescription("clearTerminalOnInit")
|
||||
.addToggle(prefs.clearTerminalOnInit)
|
||||
)
|
||||
.buildComp();
|
||||
}
|
||||
|
||||
@@ -58,18 +58,30 @@ public class TerminalDockComp extends SimpleComp {
|
||||
});
|
||||
s.addEventFilter(WindowEvent.WINDOW_HIDING,event -> {
|
||||
model.onClose();
|
||||
});
|
||||
s.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
|
||||
});
|
||||
stack.setOnMouseClicked(event -> {
|
||||
model.clickView();
|
||||
event.consume();
|
||||
});
|
||||
stack.getStyleClass().add("terminal-dock-comp");
|
||||
return stack;
|
||||
}
|
||||
|
||||
private void update(Region region) {
|
||||
if (region.getScene() == null || region.getScene().getWindow() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = region.localToScreen(region.getBoundsInLocal());
|
||||
var p = region.getPadding();
|
||||
var sx = region.getScene().getWindow().getOutputScaleX();
|
||||
var sy = region.getScene().getWindow().getOutputScaleY();
|
||||
model.resizeView((int) Math.ceil(bounds.getMinX() * sx), (int) Math.ceil(bounds.getMinY() * sy),(int) Math.floor(bounds.getWidth() * sx), (int) Math.floor(bounds.getHeight() * sy));
|
||||
model.resizeView((int) Math.ceil(bounds.getMinX() * sx + p.getLeft()),
|
||||
(int) Math.ceil(bounds.getMinY() * sy + p.getTop()),
|
||||
(int) Math.floor(bounds.getWidth() * sx - p.getRight() - p.getLeft()),
|
||||
(int) Math.floor(bounds.getHeight() * sy - p.getBottom() - p.getTop()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@ import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.Rect;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class TerminalDockModel {
|
||||
|
||||
@@ -16,10 +19,26 @@ public class TerminalDockModel {
|
||||
|
||||
private Rect viewBounds;
|
||||
private boolean viewActive;
|
||||
private final List<TerminalViewInstance> terminalInstances = new ArrayList<>();
|
||||
@Getter
|
||||
private final Set<TerminalViewInstance> terminalInstances = new HashSet<>();
|
||||
|
||||
public TerminalDockModel() {
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
public synchronized void trackTerminal(TerminalViewInstance terminal) {
|
||||
terminalInstances.add(terminal);
|
||||
terminal.alwaysInFront();
|
||||
terminal.updatePosition(viewBounds);
|
||||
}
|
||||
|
||||
public synchronized void closeTerminal(TerminalViewInstance terminal) {
|
||||
if (!terminalInstances.contains(terminal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminal.close();
|
||||
terminalInstances.remove(terminal);
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
@@ -130,6 +149,7 @@ public class TerminalDockModel {
|
||||
|
||||
terminalInstance.close();
|
||||
});
|
||||
terminalInstances.clear();
|
||||
}
|
||||
|
||||
private void updatePositions() {
|
||||
|
||||
@@ -43,12 +43,16 @@ public class TerminalView {
|
||||
private final List<TerminalViewInstance> terminalInstances = new ArrayList<>();
|
||||
private final List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
public synchronized void addListener(Listener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(Listener listener) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return isSupported() && AppPrefs.get().enableTerminalDocking().get();
|
||||
return isSupported();
|
||||
}
|
||||
|
||||
public synchronized void open(UUID request, long pid) {
|
||||
@@ -125,7 +129,7 @@ public class TerminalView {
|
||||
ThreadHelper.createPlatformThread("terminal-view", true, () -> {
|
||||
while (true) {
|
||||
instance.tick();
|
||||
ThreadHelper.sleep(1000);
|
||||
ThreadHelper.sleep(500);
|
||||
}
|
||||
}).start();
|
||||
INSTANCE = instance;
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class WindowsTerminalViewInstance extends TerminalViewInstance {
|
||||
@Override
|
||||
public void updatePosition(Rect bounds) {
|
||||
control.move(bounds);
|
||||
this.lastBounds = bounds;
|
||||
this.lastBounds = queryBounds();
|
||||
this.customBounds = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@
|
||||
-fx-background-color: -color-bg-default;
|
||||
}
|
||||
|
||||
.browser .terminal-dock-comp {
|
||||
-fx-padding: 7 0 7 7;
|
||||
}
|
||||
|
||||
.selected-file-list {
|
||||
-fx-spacing: 5px;
|
||||
-fx-padding: 8px;
|
||||
|
||||
Reference in New Issue
Block a user