mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
More docking rework
This commit is contained in:
parent
82d2d48e8a
commit
ddfa70d68b
18 changed files with 336 additions and 59 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -57,7 +57,7 @@ public class BrowseStoreAction implements ActionProvider {
|
|||
public void execute() throws Exception {
|
||||
DataStoreEntryRef<FileSystemStore> replacement =
|
||||
ProcessControlProvider.get().replace(entry.ref());
|
||||
BrowserSessionModel.DEFAULT.openFileSystemSync(replacement, null, new SimpleBooleanProperty());
|
||||
BrowserSessionModel.DEFAULT.openFileSystemSync(replacement, null, new SimpleBooleanProperty(), true);
|
||||
AppLayoutModel.get().selectBrowser();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.BrowserTerminalDockTabModel;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
|
@ -27,14 +28,14 @@ public class OpenTerminalAction implements LeafAction {
|
|||
model.getCurrentDirectory() != null
|
||||
? model.getCurrentDirectory().getPath()
|
||||
: null);
|
||||
if (model.getBrowserModel() instanceof BrowserSessionModel sessionModel) {
|
||||
sessionModel.
|
||||
} else {
|
||||
for (var entry : entries) {
|
||||
model.openTerminalAsync(entry.getRawFileEntry().getPath());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (var entry : entries) {
|
||||
model.openTerminalAsync(entry.getRawFileEntry().getPath());
|
||||
if (AppPrefs.get().enableTerminalDocking().get() && model.getBrowserModel() instanceof BrowserSessionModel sessionModel) {
|
||||
sessionModel.splitTab(model,new BrowserTerminalDockTabModel(sessionModel, model, model.getTerminalRequests()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -546,6 +546,10 @@ scriptsIntroBottomTitle=Using scripts
|
|||
scriptsIntroBottomText=There are a variety of sample scripts to start out. You can click on the edit button of the individual scripts to see how they are implemented. Scripts have to be enabled to run and show up in menus, there is a toggle on every script for that.
|
||||
scriptsIntroStart=Get started
|
||||
checkForSecurityUpdates=Check for security updates
|
||||
#force
|
||||
checkForSecurityUpdatesDescription=XPipe can check for potential security updates separately from normal feature updates. When this is enabled, at least important security updates will be recommended for installation even if the normal update check is disabled.\n\nDisabling this setting will result in no external version request being performed, and you won't be notified about any security updates.
|
||||
clickToDock=Click to dock terminal
|
||||
pinTab=Pin tab
|
||||
unpinTab=Unpin tab
|
||||
pinned=Pinned
|
||||
enableTerminalDocking=Enable terminal docking
|
||||
enableTerminalDockingDescription=With terminal docking you can dock terminal windows to the XPipe application window to simulate a somewhat integrated terminal. The terminal windows are then managed by XPipe to always fit into the dock.
|
Loading…
Reference in a new issue