Skip to content

Commit b3c8e2c

Browse files
committed
Improved frame drag & sliding/maximizing for 1.2.x
Most issues with dragging or clicking at the top of a non-native frame should now be resolved, along with some sizing issues related to maximizing. Toggling maximize while a sidebar pane is active will now apply to the most-recently-active pane in the main area, instead of hiding it. This was a major overhaul of the plugin's styling to support three different frame styles (obsidian, hidden, and native) with and without maximizing and sliding, and has only been tested on Windows. It is entirely possible I've missed a bug in a configuration or platform I don't normally use (e.g. Mac, mobile, themes w/elaborate restyling, etc.), so please open bug reports on Github if you experience any issues.
1 parent 3b816fb commit b3c8e2c

File tree

6 files changed

+208
-20
lines changed

6 files changed

+208
-20
lines changed

manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"id": "pane-relief",
33
"name": "Pane Relief",
4-
"version": "0.4.2",
5-
"minAppVersion": "0.15.9",
4+
"version": "0.5.0",
5+
"minAppVersion": "1.2.8",
66
"description": "Per-tab history, hotkeys for pane/tab movement, navigation, sliding workspace, and more",
77
"author": "PJ Eby",
88
"authorUrl": "https://github.com/pjeby",

src/focus-lock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export class FocusLock extends Service {
111111

112112
}
113113

114-
function isMain(leaf: WorkspaceLeaf) {
114+
export function isMain(leaf: WorkspaceLeaf) {
115115
const root = leaf?.getRoot();
116116
return !!(root && root !== app.workspace.leftSplit && root !== app.workspace.rightSplit);
117117
}

src/maximizing.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Service, toggleClass } from "@ophidian/core";
22
import { around } from "monkey-around";
33
import { debounce, requireApiVersion, WorkspaceLeaf, WorkspaceTabs } from "obsidian";
4+
import { isMain } from "./focus-lock";
45

56
declare module "obsidian" {
67
interface Workspace {
78
requestActiveLeafEvents(): void
9+
rightSidebarToggleButtonEl: HTMLDivElement
810
}
911
interface WorkspaceTabs {
1012
scrollIntoView(tab: number): void;
@@ -43,7 +45,7 @@ export class Maximizer extends Service {
4345
// Switching from maximized popover to non-popover; de-maximize it first
4446
app.commands.executeCommandById("obsidian-hover-editor:restore-active-popover");
4547
}
46-
if (parent) self.refresh(parent, parent.hasClass("should-maximize") ? leaf.containerEl : null);
48+
if (isMain(leaf) && parent) self.refresh(parent, parent.hasClass("should-maximize") ? leaf.containerEl : null);
4749
return old.call(this, leaf, pushHistory, focus);
4850
}}
4951
}));
@@ -55,6 +57,30 @@ export class Maximizer extends Service {
5557
}
5658
}
5759
}))
60+
61+
// Replace the right sidebar toggle that gets hidden during maximize
62+
app.workspace.onLayoutReady(() => {
63+
const toggle = app.workspace.rightSidebarToggleButtonEl.cloneNode(true) as HTMLDivElement;
64+
toggle.id = "pr-maximize-sb-toggle";
65+
toggle.addEventListener("click", () => app.workspace.rightSplit.toggle());
66+
toggle.ariaLabel = i18next.t(app.workspace.rightSplit.collapsed ? "interface.sidebar-expand" : "interface.sidebar-collapse")
67+
app.workspace.containerEl.parentElement.appendChild(toggle);
68+
this.register(() => toggle.detach());
69+
this.register(around(app.workspace.rightSplit.constructor.prototype, {
70+
expand(old) {
71+
return function() {
72+
toggle.ariaLabel = i18next.t("interface.sidebar-collapse");
73+
return old.call(this);
74+
};
75+
},
76+
collapse(old) {
77+
return function() {
78+
toggle.ariaLabel = i18next.t("interface.sidebar-expand");
79+
return old.call(this);
80+
};
81+
}
82+
}));
83+
})
5884
}
5985

6086
onunload() {
@@ -63,6 +89,7 @@ export class Maximizer extends Service {
6389
}
6490

6591
toggleMaximize(leaf = app.workspace.activeLeaf) {
92+
if (!leaf || !isMain(leaf)) leaf = app.workspace.getMostRecentLeaf(app.workspace.rootSplit);
6693
const parent = this.parentForLeaf(leaf);
6794
if (!parent) return;
6895
const popoverEl = parent.matchParent(".hover-popover");
@@ -135,15 +162,15 @@ export class Maximizer extends Service {
135162
if (popovers) for (const popover of popovers) {
136163
if (popover.rootSplit) parents.push(popover.rootSplit.containerEl)
137164
}
138-
return parents;
165+
return parents.map(e => this.parentFor(e));
139166
}
140167

141168
parentForLeaf(leaf: WorkspaceLeaf) {
142169
return this.parentFor(leaf?.containerEl);
143170
}
144171

145172
parentFor(el: Element) {
146-
return el?.matchParent(".workspace-split.mod-root, .hover-popover > .popover-content > .workspace-split");
173+
return el?.matchParent(".workspace, .hover-popover > .popover-content > .workspace-split");
147174
}
148175

149176
}

src/sliding.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { use, addCommands, command, LayoutSetting, PerWindowComponent, WindowManager, LayoutItem, toggleClass, allWindows, PWCFactory } from "@ophidian/core";
2-
import { Plugin, WorkspaceLeaf, WorkspaceRoot, WorkspaceWindow } from "obsidian";
2+
import { around } from "monkey-around";
3+
import { Plugin, WorkspaceLeaf, WorkspaceRoot, WorkspaceWindow, debounce } from "obsidian";
34

45
type SlideSettings = {
56
active: boolean;
@@ -31,6 +32,12 @@ export class SlidingPanes extends PerWindowComponent {
3132

3233
update(active: boolean) {
3334
toggleClass(this.container.containerEl, "is-sliding", active);
35+
const parent = this.container.containerEl.matchParent(".workspace");
36+
if (parent) {
37+
toggleClass(parent, "is-sliding", active);
38+
} else {
39+
this.register(this.container.containerEl.onNodeInserted(() => this.update(this.options.active), true));
40+
}
3441
}
3542

3643
activate(leaf: WorkspaceLeaf) {
@@ -61,6 +68,24 @@ declare module "obsidian" {
6168
class SlidingPanesManager<T extends SlidingPanes> extends WindowManager<T> {
6269
options = new LayoutSetting(this, "pane-relief:sliding-panes", {active: false} as SlideSettings);
6370

71+
// Due to a quirk of how electron handles titlebar draggability (and rendering of
72+
// out-of-view scrolled panes), we need to overlay parts of the title bar to
73+
// ensure they're handled correctly
74+
overlay = app.workspace.containerEl.parentElement.createDiv("prsp-tb-overlay");
75+
76+
requestOverlayUpdate = debounce(() => {
77+
if (!app.workspace.leftSplit.collapsed) {
78+
const r = app.workspace.leftSplit.containerEl.find(".workspace-tabs.mod-top-left-space .workspace-tab-header-spacer").getBoundingClientRect();
79+
this.overlay.style.setProperty("--pr-overlay-width", `${r.width}px`);
80+
this.overlay.style.setProperty("--pr-overlay-left", `${r.left}px`);
81+
}
82+
}, 100, true);
83+
84+
onunload(): void {
85+
super.onunload();
86+
this.overlay.detach();
87+
}
88+
6489
onload() {
6590
super.onload();
6691
window.CodeMirror.getMode({}, "XXX"); // force modes to load, prevents weird sliding
@@ -73,6 +98,15 @@ class SlidingPanesManager<T extends SlidingPanes> extends WindowManager<T> {
7398
this.registerEvent(this.options.onSet(this.onChange, this));
7499
this.registerEvent(this.options.store.onLoadItem(this.onChange, this));
75100
this.registerEvent(this.onLeafChange(leaf => this.forLeaf(leaf).activate(leaf)));
101+
app.workspace.onLayoutReady(() => {
102+
this.registerEvent(app.workspace.on("layout-change", this.requestOverlayUpdate));
103+
this.registerEvent(app.workspace.on("resize", this.requestOverlayUpdate));
104+
const mgr = this;
105+
this.register(around(app.workspace.leftSplit.constructor.prototype, {
106+
expand(old) { return function() { mgr.requestOverlayUpdate(); return old.call(this); }; }
107+
}));
108+
this.requestOverlayUpdate();
109+
});
76110
}
77111

78112
onChange(item: LayoutItem) {

src/styles.scss

Lines changed: 139 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,52 @@ settings:
2626
default: 350px
2727
*/
2828

29+
@use "sass:selector";
30+
31+
$win-hidden-frame: "body.is-hidden-frameless";
32+
$win-native-frame: "body:not(.is-frameless)";
33+
$win-obsidian-frame: "body.is-frameless:not(.is-hidden-frameless)";
34+
35+
$win-any: "body";
36+
$win-main: "body:not(.is-popout-window)";
37+
$win-popout: "body.is-popout-window";
38+
39+
$win-has-titlebar: $win-obsidian-frame;
40+
$win-no-titlebar: "#{$win-hidden-frame}, #{$win-native-frame}";
41+
42+
$win-draggable-header: "body:not(.is-grabbing):not(.is-fullscreen)";
43+
44+
45+
@function when($s1, $selectors...) {
46+
$res: $s1;
47+
@each $sel in $selectors {
48+
$res: selector.unify($res, $sel);
49+
}
50+
@return $res;
51+
}
52+
53+
#{when($win-any)} {
54+
// Default: allow space for toggle + controls
55+
--pr-right-frame-space: calc(var(--ribbon-width) + var(--frame-right-space));
56+
}
57+
#{when($win-popout)} {
58+
// Popout: no toggle, just controls
59+
--pr-right-frame-space: var(--frame-right-space);
60+
}
61+
62+
#{when($win-any, $win-obsidian-frame)},
63+
#{when($win-any, $win-native-frame)} {
64+
// Visible title bar in popout - just toggle
65+
--pr-right-frame-space: var(--ribbon-width);
66+
}
67+
68+
#{when($win-popout, $win-obsidian-frame)},
69+
#{when($win-popout, $win-native-frame)} {
70+
// Visible title bar in popout - no controls
71+
--pr-right-frame-space: 0;
72+
}
73+
74+
2975
/* Ensure popovers are above the menu */
3076
.menu.pane-relief-history-menu ~ .popover.hover-popover {
3177
z-index: var(--layer-menu);
@@ -58,7 +104,7 @@ body:not(.obsidian-themepocalypse).titlebar-button.mod-forward.mod-active:not(:h
58104
{ opacity: 0.75; }
59105

60106
/* Maximizing */
61-
.workspace-split.mod-root.should-maximize,
107+
.workspace.should-maximize .workspace-split.mod-root,
62108
body > .popover.hover-popover .workspace-split.should-maximize
63109
{
64110
.workspace-leaf:not(.is-maximized),
@@ -73,19 +119,54 @@ body > .popover.hover-popover .workspace-split.should-maximize
73119
max-width: unset !important;
74120
left: unset !important;
75121
}
122+
.workspace-tabs.has-maximized {
123+
flex-basis: 100%;
124+
}
76125
.workspace-leaf.is-maximized {
77126
flex-basis: calc(100% - 4px); // 4px is for scrollbar width
78127
.view-header {
79128
display: flex;
80-
body.is-hidden-frameless:not(.is-fullscreen) & {
81-
padding-left: var(--frame-left-space);
82-
padding-right: var(--frame-right-space);
83-
}
84129
.view-header-icon { display: inherit; }
85130
} // always show view header when maximized
86131
}
87132
}
88133

134+
#pr-maximize-sb-toggle {
135+
display: none;
136+
position: fixed;
137+
background: var(--tab-container-background);
138+
right: 0;
139+
top: 0;
140+
padding-right: var(--size-4-2);
141+
padding-left: var(--size-4-2);
142+
.workspace.should-maximize ~ & {
143+
display: block;
144+
#{$win-hidden-frame} & {
145+
right: var(--frame-right-space);
146+
}
147+
#{$win-obsidian-frame} & {
148+
top: var(--titlebar-height);
149+
}
150+
}
151+
}
152+
153+
154+
.workspace-leaf.is-maximized .view-header {
155+
.workspace:not(.is-right-sidedock-open) & {
156+
// leave space for relocated right ribbon toggle
157+
padding-right: calc(var(--pr-right-frame-space) + var(--size-4-2));
158+
}
159+
#{when($win-draggable-header, $win-hidden-frame)} & {
160+
.view-header-title-container {
161+
/* allow dragging of maximized view header */
162+
-webkit-app-region: drag;
163+
& > * {
164+
-webkit-app-region: no-drag;
165+
}
166+
}
167+
}
168+
}
169+
89170
/* Sliding Panes */
90171

91172
:root {
@@ -101,21 +182,71 @@ body.is-mobile {
101182
--pr-sliding-panes-width: var(--pr-sliding-panes-mobile-width);
102183
}
103184

185+
186+
187+
.prsp-tb-overlay {
188+
/*
189+
Overlay to ensure left-sidebar draggable area is draggable, even if
190+
there's a non-draggable header underlaying it from a scrolled pane
191+
*/
192+
display: block;
193+
position: fixed;
194+
pointer-events: none;
195+
top: 0;
196+
#{$win-obsidian-frame} & { top: var(--titlebar-height); }
197+
height: var(--header-height);
198+
width: 0;
199+
-webkit-app-region: drag;
200+
201+
#{$win-draggable-header} .workspace.is-left-sidedock-open.is-sliding:not(.should-maximize) ~ & {
202+
width: var(--pr-overlay-width, 0);
203+
left: var(--pr-overlay-left, 0);
204+
}
205+
206+
#{$win-draggable-header} &::after {
207+
/*
208+
Overlay to ensure left-sidebar region is clickable, even if
209+
there's a draggable header underlaying it from a scrolled pane
210+
*/
211+
width: var(--pr-overlay-left, var(--ribbon-width));
212+
height: var(--header-height);
213+
left: 0;
214+
display: block;
215+
position: fixed;
216+
content: "";
217+
-webkit-app-region: no-drag;
218+
}
219+
220+
.workspace:not(.should-maximize) ~ &::before {
221+
/*
222+
Overlay to ensure right-side window controls are clickable, even if
223+
there's a draggable header underlaying them from a scrolled pane
224+
*/
225+
right: 0;
226+
display: block;
227+
position: fixed;
228+
width: var(--pr-right-frame-space);
229+
height: var(--header-height);
230+
content: "";
231+
-webkit-app-region: no-drag;
232+
}
233+
}
234+
104235
.mod-root.is-sliding:not(.has-maximized) {
105236
overflow-x: auto;
106237
overflow-y: hidden;
107238

108239
/* Keep right sidebar toggle visible on 0.16, courtesy @ebullient */
109-
div.workspace-tabs.mod-top.mod-top-right-space .workspace-tab-header-container {
240+
#{$win-main} & div.workspace-tabs.mod-top.mod-top-right-space .workspace-tab-header-container {
110241
padding-right: var(--ribbon-width);
111242
body.is-hidden-frameless:not(.is-fullscreen) & {
112-
padding-right: calc(var(--ribbon-width) + var(--frame-right-space));
243+
padding-right: var(--pr-right-frame-space);
113244
}
114245
}
115246

116247
/* Allow enough space at top of right sidebar for right ribbon toggle to function */
117248
.workspace.is-right-sidedock-open & + .mod-right-split .workspace-tabs.mod-top .workspace-tab-header-container::after {
118-
width: calc(var(--ribbon-width) + var(--frame-right-space));
249+
width: var(--pr-right-frame-space);
119250
}
120251

121252
div.sidebar-toggle-button.mod-right {
@@ -133,11 +264,6 @@ body.is-mobile {
133264
}
134265
}
135266

136-
.workspace-tabs.mod-top .workspace-tab-header-spacer {
137-
// Turn off draggable space that can block other things in sliding region
138-
-webkit-app-region: no-drag;
139-
}
140-
141267
&>*:not(:last-child:nth-child(2)) { // don't apply when only one pane
142268
width: var(--pr-sliding-panes-width);
143269
flex: none;

versions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"0.5.0": "1.2.8",
23
"0.4.2": "0.15.9",
34
"0.3.5": "0.15.9",
45
"0.2.9": "0.15.9",

0 commit comments

Comments
 (0)