More docking rework

This commit is contained in:
crschnick 2024-11-05 15:10:14 +00:00
parent 82d2d48e8a
commit ddfa70d68b
18 changed files with 336 additions and 59 deletions

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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")

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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()));
}
}

View file

@ -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() {

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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()));
}
}

View file

@ -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.