Skip to content

Commit 02d726c

Browse files
authored
feat(window): add Maximize Width and Maximize Height commands (#454)
Adds two Window Management commands matching Raycast's built-in set: - Window: Maximize Width — spans the full work-area width while keeping the window's current vertical position and height. - Window: Maximize Height — spans the full work-area height while keeping the window's current horizontal position and width. Both are wired through every execution path: the native Swift helper (window-adjust), the main-process JS fallback (computeWindowManagementLayoutBounds, now passed the target window bounds), the renderer panel runtime (applyMaximizeDimensionPreset), the launcher command catalog, and the WindowManagerPanel preset list/icons. The launcher list glyph (command-helpers) is also updated so the two commands render the same window icon as the rest of the Window: set.
1 parent f18bf01 commit 02d726c

5 files changed

Lines changed: 122 additions & 2 deletions

File tree

src/main/commands.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,18 @@ async function discoverAndBuildCommands(): Promise<CommandInfo[]> {
14001400
keywords: ['window', 'management', 'maximize', 'fill', 'fullscreen'],
14011401
category: 'system',
14021402
},
1403+
{
1404+
id: 'system-window-management-maximize-width',
1405+
title: 'Window: Maximize Width',
1406+
keywords: ['window', 'management', 'maximize', 'width', 'horizontal', 'fill', 'stretch'],
1407+
category: 'system',
1408+
},
1409+
{
1410+
id: 'system-window-management-maximize-height',
1411+
title: 'Window: Maximize Height',
1412+
keywords: ['window', 'management', 'maximize', 'height', 'vertical', 'fill', 'stretch'],
1413+
category: 'system',
1414+
},
14031415
{
14041416
id: 'system-window-management-top-left',
14051417
title: 'Window: Top Left',

src/main/main.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,6 +3280,8 @@ const WINDOW_MANAGEMENT_PRESET_COMMAND_IDS = new Set<string>([
32803280
'system-window-management-center',
32813281
'system-window-management-center-80',
32823282
'system-window-management-fill',
3283+
'system-window-management-maximize-width',
3284+
'system-window-management-maximize-height',
32833285
'system-window-management-top-left',
32843286
'system-window-management-top-right',
32853287
'system-window-management-bottom-left',
@@ -3343,6 +3345,8 @@ const WINDOW_MANAGEMENT_LAYOUT_COMMAND_IDS = new Set<string>([
33433345
'system-window-management-center',
33443346
'system-window-management-center-80',
33453347
'system-window-management-fill',
3348+
'system-window-management-maximize-width',
3349+
'system-window-management-maximize-height',
33463350
'system-window-management-top-left',
33473351
'system-window-management-top-right',
33483352
'system-window-management-bottom-left',
@@ -3851,7 +3855,8 @@ function sortNodeWindowsForLayout(windows: NodeWindowInfo[]): NodeWindowInfo[] {
38513855

38523856
function computeWindowManagementLayoutBounds(
38533857
commandId: string,
3854-
area: { x: number; y: number; width: number; height: number }
3858+
area: { x: number; y: number; width: number; height: number },
3859+
windowBounds?: NodeWindowBounds | null
38553860
): NodeWindowBounds | null {
38563861
const normalized = String(commandId || '').trim();
38573862
const halfWidthLeft = Math.max(1, Math.floor(area.width / 2));
@@ -3907,6 +3912,36 @@ function computeWindowManagementLayoutBounds(
39073912
width: area.width,
39083913
height: area.height,
39093914
};
3915+
case 'system-window-management-maximize-width': {
3916+
// Span the full work-area width, keep the current vertical position/height.
3917+
if (!windowBounds) return null;
3918+
const height = clampWindowManagementFineTuneValue(
3919+
Math.round(windowBounds.height),
3920+
WINDOW_MANAGEMENT_FINE_TUNE_MIN_HEIGHT,
3921+
area.height
3922+
);
3923+
const y = clampWindowManagementFineTuneValue(
3924+
Math.round(windowBounds.y),
3925+
area.y,
3926+
areaBottom - height
3927+
);
3928+
return { x: area.x, y, width: area.width, height };
3929+
}
3930+
case 'system-window-management-maximize-height': {
3931+
// Span the full work-area height, keep the current horizontal position/width.
3932+
if (!windowBounds) return null;
3933+
const width = clampWindowManagementFineTuneValue(
3934+
Math.round(windowBounds.width),
3935+
WINDOW_MANAGEMENT_FINE_TUNE_MIN_WIDTH,
3936+
area.width
3937+
);
3938+
const x = clampWindowManagementFineTuneValue(
3939+
Math.round(windowBounds.x),
3940+
area.x,
3941+
areaRight - width
3942+
);
3943+
return { x, y: area.y, width, height: area.height };
3944+
}
39103945
case 'system-window-management-center': {
39113946
const width = clampWindowManagementFineTuneValue(
39123947
Math.round(area.width * 0.6),
@@ -4331,7 +4366,7 @@ async function executeWindowManagementLayoutCommand(
43314366
return await queueWindowMutations(entries);
43324367
}
43334368

4334-
const next = computeWindowManagementLayoutBounds(normalized, area);
4369+
const next = computeWindowManagementLayoutBounds(normalized, area, target.bounds);
43354370
if (!next) return false;
43364371
return await queueWindowMutations([
43374372
{

src/native/window-adjust.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ private enum AdjustAction: String {
1010
case center = "center"
1111
case center80 = "center-80"
1212
case fill = "fill"
13+
case maximizeWidth = "maximize-width"
14+
case maximizeHeight = "maximize-height"
1315
case topLeft = "top-left"
1416
case topRight = "top-right"
1517
case bottomLeft = "bottom-left"
@@ -567,6 +569,26 @@ private func adjustedFrame(_ base: WindowFrame, action: AdjustAction, forcedArea
567569
height: max(minHeight, round(area.height))
568570
)
569571
}
572+
case .maximizeWidth:
573+
if let area {
574+
// Span the full work-area width, keep the current vertical position/height.
575+
next = WindowFrame(
576+
x: area.origin.x,
577+
y: base.y,
578+
width: max(minWidth, round(area.width)),
579+
height: base.height
580+
)
581+
}
582+
case .maximizeHeight:
583+
if let area {
584+
// Span the full work-area height, keep the current horizontal position/width.
585+
next = WindowFrame(
586+
x: base.x,
587+
y: area.origin.y,
588+
width: base.width,
589+
height: max(minHeight, round(area.height))
590+
)
591+
}
570592
case .center:
571593
if let area {
572594
let width = max(minWidth, round(area.width * 0.6))

src/renderer/src/WindowManagerPanel.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ type PresetId =
4949
| 'center'
5050
| 'center-80'
5151
| 'fill'
52+
| 'maximize-width'
53+
| 'maximize-height'
5254
| 'auto-organize'
5355
| 'increase-size-10'
5456
| 'decrease-size-10'
@@ -89,6 +91,8 @@ export const WINDOW_MANAGEMENT_PRESET_COMMANDS: WindowManagementPresetCommand[]
8991
{ commandId: 'system-window-management-center', presetId: 'center' },
9092
{ commandId: 'system-window-management-center-80', presetId: 'center-80' },
9193
{ commandId: 'system-window-management-fill', presetId: 'fill' },
94+
{ commandId: 'system-window-management-maximize-width', presetId: 'maximize-width' },
95+
{ commandId: 'system-window-management-maximize-height', presetId: 'maximize-height' },
9296
{ commandId: 'system-window-management-top-left', presetId: 'top-left' },
9397
{ commandId: 'system-window-management-top-right', presetId: 'top-right' },
9498
{ commandId: 'system-window-management-bottom-left', presetId: 'bottom-left' },
@@ -143,6 +147,8 @@ const PRESETS: Array<{ id: PresetId; label: string; subtitle: string }> = [
143147
{ id: 'center', label: 'Center', subtitle: 'Current window' },
144148
{ id: 'center-80', label: 'Almost Maximize', subtitle: 'Current window' },
145149
{ id: 'fill', label: 'Maximize', subtitle: 'Current window' },
150+
{ id: 'maximize-width', label: 'Maximize Width', subtitle: 'Current window' },
151+
{ id: 'maximize-height', label: 'Maximize Height', subtitle: 'Current window' },
146152
{ id: 'top-left', label: 'Top Left', subtitle: 'Current window' },
147153
{ id: 'top-right', label: 'Top Right', subtitle: 'Current window' },
148154
{ id: 'bottom-right', label: 'Bottom Right', subtitle: 'Current window' },
@@ -183,6 +189,7 @@ const PRESETS: Array<{ id: PresetId; label: string; subtitle: string }> = [
183189
];
184190

185191
const MULTI_WINDOW_PRESETS = new Set<PresetId>(['auto-organize']);
192+
const DIMENSION_MAXIMIZE_PRESETS = new Set<PresetId>(['maximize-width', 'maximize-height']);
186193
const SHIFT_ENTER_ONLY_PRESETS = new Set<PresetId>([
187194
'increase-size-10',
188195
'decrease-size-10',
@@ -302,6 +309,12 @@ function renderPresetIcon(id: PresetId): JSX.Element {
302309
case 'fill':
303310
cells.push({ x: 1, y: 1, w: 18, h: 12 });
304311
break;
312+
case 'maximize-width':
313+
cells.push({ x: 1, y: 4, w: 18, h: 6 });
314+
break;
315+
case 'maximize-height':
316+
cells.push({ x: 7, y: 1, w: 6, h: 12 });
317+
break;
305318
case 'center':
306319
cells.push({ x: 4, y: 3, w: 12, h: 8 });
307320
break;
@@ -381,6 +394,29 @@ function getWindowRect(win: ManagedWindow | null | undefined): Rect | null {
381394
};
382395
}
383396

397+
function applyMaximizeDimensionPreset(presetId: PresetId, target: ManagedWindow, area: ScreenArea): Rect | null {
398+
if (!DIMENSION_MAXIMIZE_PRESETS.has(presetId)) return null;
399+
const base = getWindowRect(target);
400+
if (!base) return null;
401+
402+
const areaRight = area.left + area.width;
403+
const areaBottom = area.top + area.height;
404+
let next: Rect;
405+
if (presetId === 'maximize-width') {
406+
// Span the full work-area width, keep the current vertical position/height.
407+
next = { x: area.left, y: base.y, width: area.width, height: base.height };
408+
} else {
409+
// Span the full work-area height, keep the current horizontal position/width.
410+
next = { x: base.x, y: area.top, width: base.width, height: area.height };
411+
}
412+
413+
next.width = clamp(Math.round(next.width), MIN_WINDOW_WIDTH, area.width);
414+
next.height = clamp(Math.round(next.height), MIN_WINDOW_HEIGHT, area.height);
415+
next.x = clamp(Math.round(next.x), area.left, areaRight - next.width);
416+
next.y = clamp(Math.round(next.y), area.top, areaBottom - next.height);
417+
return next;
418+
}
419+
384420
function applyFineTunePreset(presetId: PresetId, target: ManagedWindow, area: ScreenArea): Rect | null {
385421
if (!isShiftEnterOnlyPreset(presetId)) return null;
386422
const base = getWindowRect(target);
@@ -1312,6 +1348,11 @@ export async function executeWindowManagementPreset(presetId: PresetId): Promise
13121348
} else {
13131349
moves = buildAutoLayout(layoutTargets, layoutArea);
13141350
}
1351+
} else if (DIMENSION_MAXIMIZE_PRESETS.has(presetId) && target) {
1352+
const adjusted = applyMaximizeDimensionPreset(presetId, target, layoutArea);
1353+
if (adjusted) {
1354+
moves = [{ id: target.id, bounds: rectToBounds(adjusted) }];
1355+
}
13151356
} else {
13161357
const region = getPresetRegion(presetId, layoutArea);
13171358
if (region && target) {

src/renderer/src/utils/command-helpers.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@ type WindowManagementGlyphId =
546546
| 'center'
547547
| 'center-80'
548548
| 'fill'
549+
| 'maximize-width'
550+
| 'maximize-height'
549551
| 'top-left'
550552
| 'top-right'
551553
| 'bottom-left'
@@ -595,6 +597,8 @@ const WINDOW_MANAGEMENT_GLYPH_SUFFIXES = new Set<WindowManagementGlyphId>([
595597
'center',
596598
'center-80',
597599
'fill',
600+
'maximize-width',
601+
'maximize-height',
598602
'top-left',
599603
'top-right',
600604
'bottom-left',
@@ -776,6 +780,12 @@ function renderWindowManagementGlyph(glyphId: WindowManagementGlyphId): JSX.Elem
776780
case 'fill':
777781
cells.push({ x: 1, y: 1, w: 18, h: 12 });
778782
break;
783+
case 'maximize-width':
784+
cells.push({ x: 1, y: 4, w: 18, h: 6 });
785+
break;
786+
case 'maximize-height':
787+
cells.push({ x: 7, y: 1, w: 6, h: 12 });
788+
break;
779789
case 'center':
780790
cells.push({ x: 4, y: 3, w: 12, h: 8 });
781791
break;

0 commit comments

Comments
 (0)