responsive desktop: shrink icons on cramped viewports so they never collide; clamp windows to the viewport
This commit is contained in:
parent
9695452974
commit
194501e467
4 changed files with 136 additions and 9 deletions
|
|
@ -149,11 +149,15 @@ export default function WindowsDesktop() {
|
|||
const id = idRef.current++;
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight - TASKBAR_HEIGHT;
|
||||
// Fit the window to the viewport so it never opens off-screen or under the
|
||||
// panel on a narrow/short monitor.
|
||||
const w = Math.min(app.width, Math.max(260, vw - 20));
|
||||
const h = Math.min(app.height, Math.max(150, vh - 20));
|
||||
const cascade = Math.min(wins.length, 8) * 30;
|
||||
let x = Math.round((vw - app.width) / 2) + cascade;
|
||||
let y = Math.round((vh - app.height) / 2) + cascade;
|
||||
x = Math.max(10, Math.min(x, vw - app.width - 10));
|
||||
y = Math.max(10, Math.min(y, vh - app.height - 10));
|
||||
let x = Math.round((vw - w) / 2) + cascade;
|
||||
let y = Math.round((vh - h) / 2) + cascade;
|
||||
x = Math.max(10, Math.min(x, vw - w - 10));
|
||||
y = Math.max(10, Math.min(y, vh - h - 10));
|
||||
setWins((prev) => [
|
||||
...prev,
|
||||
{
|
||||
|
|
@ -161,8 +165,8 @@ export default function WindowsDesktop() {
|
|||
appId: app.id,
|
||||
x,
|
||||
y,
|
||||
w: app.width,
|
||||
h: app.height,
|
||||
w,
|
||||
h,
|
||||
z: ++zRef.current,
|
||||
minimized: false,
|
||||
maximized: false,
|
||||
|
|
@ -252,11 +256,43 @@ export default function WindowsDesktop() {
|
|||
);
|
||||
};
|
||||
const resizeWin = (id: number, x: number, y: number, w: number, h: number) => {
|
||||
const vw = typeof window !== "undefined" ? window.innerWidth : 1280;
|
||||
const vh = (typeof window !== "undefined" ? window.innerHeight : 800) - TASKBAR_HEIGHT;
|
||||
const nw = Math.min(w, Math.max(260, vw - 20));
|
||||
const nh = Math.min(h, Math.max(150, vh - 20));
|
||||
setWins((prev) =>
|
||||
prev.map((win) => (win.id === id ? { ...win, centered: false, x, y, w, h } : win))
|
||||
prev.map((win) =>
|
||||
win.id === id ? { ...win, centered: false, x, y, w: nw, h: nh } : win
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Keep windows on-screen when the viewport shrinks (narrow monitor, or the
|
||||
// user resizing the browser): clamp size to the viewport and pull anything
|
||||
// that would fall off the edge back into view.
|
||||
useEffect(() => {
|
||||
const clampToViewport = () => {
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight - TASKBAR_HEIGHT;
|
||||
setWins((prev) =>
|
||||
prev.map((w) => {
|
||||
if (w.maximized) return w;
|
||||
const nw = Math.min(w.w, Math.max(260, vw - 20));
|
||||
const nh = Math.min(w.h, Math.max(150, vh - 20));
|
||||
if (w.centered) {
|
||||
return nw === w.w && nh === w.h ? w : { ...w, w: nw, h: nh };
|
||||
}
|
||||
const nx = Math.max(10, Math.min(w.x, vw - nw - 10));
|
||||
const ny = Math.max(10, Math.min(w.y, vh - nh - 10));
|
||||
if (nw === w.w && nh === w.h && nx === w.x && ny === w.y) return w;
|
||||
return { ...w, x: nx, y: ny, w: nw, h: nh };
|
||||
})
|
||||
);
|
||||
};
|
||||
window.addEventListener("resize", clampToViewport);
|
||||
return () => window.removeEventListener("resize", clampToViewport);
|
||||
}, []);
|
||||
|
||||
const taskbarClick = (w: WinState) => {
|
||||
if (w.minimized) {
|
||||
setWins((prev) =>
|
||||
|
|
|
|||
|
|
@ -163,6 +163,75 @@ a.win-icon {
|
|||
opacity: 0.92;
|
||||
}
|
||||
|
||||
/* Cramped desktops: shrink the icons so the grid always has a cell for every
|
||||
icon (so they can never collide) and the launcher stacks stay on screen. The
|
||||
grid is measured from the rendered icons, so it adapts automatically. */
|
||||
@media (max-width: 900px), (max-height: 600px) {
|
||||
.win-icon {
|
||||
width: 80px;
|
||||
padding: 5px 4px 7px;
|
||||
gap: 6px;
|
||||
}
|
||||
.win-icon-glyph {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 32px;
|
||||
}
|
||||
.win-icon-label {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.win-icon-img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 720px), (max-height: 440px) {
|
||||
.win-icon {
|
||||
width: 66px;
|
||||
padding: 4px 3px 5px;
|
||||
gap: 4px;
|
||||
}
|
||||
.win-icon-glyph {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 26px;
|
||||
}
|
||||
.win-icon-label {
|
||||
font-size: 0.6rem;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.win-icon-img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
}
|
||||
@media (max-height: 380px) {
|
||||
.win-icon {
|
||||
width: 56px;
|
||||
padding: 3px 3px 4px;
|
||||
gap: 3px;
|
||||
}
|
||||
.win-icon-glyph {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.win-icon-label {
|
||||
font-size: 0.54rem;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.win-icon-img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
/* The three default stacks are laid out once so the grid columns can be read
|
||||
off them. They stay in the DOM (keeping the same metrics) but must never be
|
||||
seen or clicked - the visible icons all come from .win-icon-layer. */
|
||||
|
|
@ -246,6 +315,16 @@ a.win-icon {
|
|||
top: 0;
|
||||
bottom: 54px;
|
||||
margin: auto;
|
||||
/* Never let a centred window (e.g. Welcome) overflow a small viewport. */
|
||||
max-width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 74px);
|
||||
}
|
||||
/* On very short viewports the default min-height would win over max-height and
|
||||
push the window off-screen; let it shrink further so it still fits. */
|
||||
@media (max-height: 320px) {
|
||||
.win-window {
|
||||
min-height: 110px;
|
||||
}
|
||||
}
|
||||
.win-window.closing {
|
||||
animation: winClose 0.14s ease-in forwards;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue