Skip to content

Commit dbf67ae

Browse files
authored
Shell: Fix search menu zoom (microsoft#1582)
- Clamp zoom between 0.25 to 5, which is the limit for electron/chromium. - Fix clipping of the search menu after zoom. - Fix search menu position on changing divider position. - Disable scrolling of the chat view (if divider becomes too small)
1 parent 916c9be commit dbf67ae

8 files changed

Lines changed: 71 additions & 43 deletions

File tree

ts/packages/shell/src/main/electronSearchMenuUI.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,6 @@ export function initializeSearchMenuUI(shellWindow: ShellWindow) {
132132
}
133133
const view = await searchMenuViewP;
134134
debug(`search-menu-size: ${id} ${JSON.stringify(size)}`);
135-
const bounds = view.getBounds();
136-
const zoomFactor = view.webContents.getZoomFactor();
137-
const bottom = bounds.y + bounds.height;
138-
view.setBounds({
139-
x: bounds.x,
140-
y: bottom - size.height * zoomFactor,
141-
width: size.width * zoomFactor,
142-
height: size.height * zoomFactor,
143-
});
135+
shellWindow.updateOverlayWebContentsView(view, size);
144136
});
145137
}

ts/packages/shell/src/main/shellWindow.ts

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ function setupResizeHandler(mainWindow: BrowserWindow, handler: () => void) {
6464
}
6565

6666
type BottomAlignedPosition = { left: number; bottom: number };
67+
type OverlayData = BottomAlignedPosition & {
68+
width: number;
69+
height: number;
70+
};
71+
6772
export class ShellWindow {
6873
public static getInstance(): ShellWindow | undefined {
6974
return this.instance;
@@ -74,7 +79,7 @@ export class ShellWindow {
7479
public readonly chatView: WebContentsView;
7580
private readonly overlayWebContentsViews: Map<
7681
WebContentsView,
77-
BottomAlignedPosition
82+
OverlayData
7883
> = new Map();
7984

8085
private verticalLayout: boolean;
@@ -106,34 +111,67 @@ export class ShellWindow {
106111
return activeBrowserView.webContentsView;
107112
}
108113

114+
private updateOverlayWebContentsViewBounds() {
115+
for (const [view, data] of this.overlayWebContentsViews.entries()) {
116+
this.setOverlayWebContentsViewBounds(view, data);
117+
}
118+
}
119+
109120
private setOverlayWebContentsViewBounds(
110121
view: WebContentsView,
111-
position: BottomAlignedPosition,
122+
data: OverlayData,
112123
) {
113124
const chatBounds = this.chatView.getBounds();
114-
const current = view.getBounds();
115125
const zoomFactor = this.chatView.webContents.zoomFactor;
116-
const left = chatBounds.x + position.left * zoomFactor;
117-
const bottom = chatBounds.y + position.bottom * zoomFactor;
118-
126+
const left = chatBounds.x + data.left * zoomFactor;
127+
const bottom =
128+
chatBounds.y + chatBounds.height - data.bottom * zoomFactor;
129+
const width = data.width * zoomFactor;
130+
const height = data.height * zoomFactor;
119131
const newBounds = {
120132
x: left,
121-
y: bottom - current.height,
122-
width: current.width,
123-
height: current.height,
133+
y: bottom - height,
134+
width,
135+
height,
124136
};
125-
view.webContents.setZoomFactor(zoomFactor);
126137
view.setBounds(newBounds);
138+
view.webContents.setZoomFactor(zoomFactor);
127139
}
128140

129141
public updateOverlayWebContentsView(
130142
view: WebContentsView,
131-
position?: BottomAlignedPosition,
143+
update?: Partial<OverlayData>,
132144
) {
133-
if (position) {
134-
this.overlayWebContentsViews.set(view, position);
135-
this.mainWindow.contentView.addChildView(view);
136-
this.setOverlayWebContentsViewBounds(view, position);
145+
if (update) {
146+
let data = this.overlayWebContentsViews.get(view);
147+
if (data === undefined) {
148+
this.mainWindow.contentView.addChildView(view);
149+
const bounds = view.getBounds();
150+
data = {
151+
left: 0,
152+
bottom: 0,
153+
width: bounds.width,
154+
height: bounds.height,
155+
};
156+
157+
this.overlayWebContentsViews.set(view, data);
158+
}
159+
160+
if (update.left) {
161+
data.left = update.left;
162+
}
163+
if (update.bottom) {
164+
data.bottom = update.bottom;
165+
}
166+
167+
if (update.width) {
168+
data.width = update.width;
169+
}
170+
if (update.height) {
171+
data.height = update.height;
172+
}
173+
174+
this.setOverlayWebContentsViewBounds(view, data);
137175
} else {
138176
this.overlayWebContentsViews.delete(view);
139177
this.mainWindow.contentView.removeChildView(view);
@@ -212,6 +250,7 @@ export class ShellWindow {
212250

213251
const chatView = createChatView(state);
214252
this.setupZoomHandlers(chatView.webContents, (zoomFactor) => {
253+
this.updateOverlayWebContentsViewBounds();
215254
this.updateZoomInTitle(zoomFactor);
216255
});
217256

@@ -600,9 +639,7 @@ export class ShellWindow {
600639
debugShellWindow(`Chat view bounds: ${JSON.stringify(chatViewBounds)}`);
601640
this.chatView.setBounds(chatViewBounds);
602641

603-
for (const [view, position] of this.overlayWebContentsViews.entries()) {
604-
this.setOverlayWebContentsViewBounds(view, position);
605-
}
642+
this.updateOverlayWebContentsViewBounds();
606643

607644
const dividerLayout = {
608645
verticalLayout,
@@ -1041,10 +1078,10 @@ export class ShellWindow {
10411078
onZoomChanged?: (zoomFactor: number) => void,
10421079
) {
10431080
// limit zoom factor to reasonable numbers
1044-
if (zoomFactor < 0.1) {
1045-
zoomFactor = 0.1;
1046-
} else if (zoomFactor > 10) {
1047-
zoomFactor = 10;
1081+
if (zoomFactor < 0.25) {
1082+
zoomFactor = 0.25;
1083+
} else if (zoomFactor > 5) {
1084+
zoomFactor = 5;
10481085
}
10491086

10501087
webContents.zoomFactor = zoomFactor;

ts/packages/shell/src/renderer/assets/styles.less

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ body {
1111
height: 100%;
1212
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
1313
font-size: 14px;
14+
overflow: hidden;
1415
}
1516

1617
.dark-mode {
@@ -22,7 +23,6 @@ body {
2223

2324
.chat-container {
2425
background-color: #222;
25-
2626
.scroll_enabled {
2727
background-color: #222;
2828
}
@@ -195,6 +195,8 @@ body {
195195
display: flex;
196196
flex-direction: column;
197197
height: 100%;
198+
bottom: 0px;
199+
position: absolute;
198200
background-color: @chat-bg;
199201
}
200202

ts/packages/shell/src/renderer/src/chatView.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,6 @@ export class ChatView {
147147
});
148148
this.inputContainer = this.chatInput.getInputContainer();
149149

150-
// Track zoom changes to adjust search menu position
151-
const resizeObserver = new ResizeObserver(() => {
152-
this.partialCompletion?.update(false);
153-
});
154-
resizeObserver.observe(this.inputContainer);
155-
156150
this.topDiv.appendChild(this.messageDiv);
157151

158152
// Add the input div at the bottom so it's always visible

ts/packages/shell/src/renderer/src/partial.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ export class PartialCompletion {
180180

181181
const position = this.getSearchMenuPosition(prefix);
182182
if (position !== undefined) {
183+
debug(
184+
`Partial completion update: '${prefix}' @ ${JSON.stringify(position)}`,
185+
);
183186
this.searchMenu.updatePrefix(prefix, position);
184187
} else {
185188
this.searchMenu.hide();
@@ -312,7 +315,7 @@ export class PartialCompletion {
312315
}
313316

314317
const { top } = this.container.getBoundingClientRect();
315-
return { left: x, bottom: top };
318+
return { left: x, bottom: window.innerHeight - top };
316319
}
317320

318321
private cancelCompletionMenu() {

ts/packages/shell/src/renderer/src/searchMenuUI/localSearchMenuUI.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,8 @@ export class LocalSearchMenuUI implements SearchMenuUI {
8888
}
8989

9090
private setPosition(position: SearchMenuPosition) {
91-
const height = document.documentElement.offsetHeight;
9291
this.searchContainer.style.left = `${position.left}px`;
93-
this.searchContainer.style.bottom = `${height - position.bottom}px`;
92+
this.searchContainer.style.bottom = `${position.bottom}px`;
9493
}
9594

9695
public adjustSelection(deltaY: number) {

ts/packages/shell/src/renderer/src/searchMenuView.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ ipcRenderer.on("search-menu-update", (_event, data) => {
2222
searchMenuUI.update(data);
2323

2424
const elm = document.body.children[0] as HTMLElement;
25-
// 2px border all around.
25+
// 2px outline all around.
2626
ipcRenderer.send("search-menu-size", {
2727
width: elm.offsetWidth + 4,
2828
height: elm.offsetHeight + 4,
@@ -32,6 +32,7 @@ ipcRenderer.on("search-menu-update", (_event, data) => {
3232
ipcRenderer.on("search-menu-close", () => {
3333
debug("search-menu-close");
3434
searchMenuUI?.close();
35+
searchMenuUI = undefined;
3536
});
3637

3738
ipcRenderer.on("search-menu-adjust-selection", (_, deltaY) => {

ts/packages/shell/src/renderer/src/templateEditor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ class FieldScalar extends FieldBase {
481481
return undefined;
482482
}
483483
const rect = this.editUI.div.getBoundingClientRect();
484-
return { left: rect.left, bottom: rect.top };
484+
return { left: rect.left, bottom: window.innerHeight - rect.top };
485485
}
486486

487487
private createSearchMenu(

0 commit comments

Comments
 (0)