fix desktop icon grid gap at right edge: stretch column pitch so right-anchored stacks sit flush
This commit is contained in:
parent
74a442d9fd
commit
eecf8cf339
2 changed files with 56 additions and 20 deletions
15
opencode.md
15
opencode.md
|
|
@ -121,9 +121,13 @@ with icons, it's almost certainly here.
|
|||
`.win-icons-tr` / `.win-icons-br`). They are rendered **hidden** with class
|
||||
`win-measure` purely so the grid can be measured from them.
|
||||
- On mount (a `useLayoutEffect`), `measure()` reads the real rendered boxes of
|
||||
those stacks: cell size = icon width/height **+ the CSS flex gap** (`GRID_GAP`,
|
||||
currently 4px), and origin = the left stack's offset. From that it computes
|
||||
`cols` / `rows` for the whole desktop.
|
||||
those stacks: cell size = icon width/height, and origin = the left stack's
|
||||
offset. The row pitch is cell height **+ the CSS flex gap** (`GRID_GAP`,
|
||||
currently 4px). The column pitch starts at cell width + gap but is **stretched
|
||||
a hair** so the grid spans from the left margin to the right margin — a fixed
|
||||
pitch left up to a full cell of slack on the right, which pulled the
|
||||
right-anchored services/friends stacks away from the border. From that it
|
||||
computes `cols` / `rows` for the whole desktop.
|
||||
- `defaultCells()` assigns each icon the cell its stack already occupies. So
|
||||
**the grid you see on load is the grid dragging uses** — no re-layout jump.
|
||||
- Icons are then rendered absolutely positioned in `.win-icon-layer`, driven by
|
||||
|
|
@ -183,6 +187,11 @@ list reordering, not free placement. Don't confuse the two.
|
|||
6. **Friend icons couldn't be dragged.** Native image drag on the
|
||||
`next/image` glyph fired `pointercancel`. Fixed with `draggable={false}` +
|
||||
`-webkit-user-drag: none`.
|
||||
7. **Right-edge gap.** Columns tiled from the left with a fixed pitch, so the
|
||||
right-anchored stacks (services/friends) stopped up to a full cell short of
|
||||
the right border (e.g. ~90px on a 1920px screen), and any remount/resize
|
||||
re-snapped them. Fixed by stretching the column pitch so the grid spans both
|
||||
margins; the row pitch is unchanged.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,20 @@ export function useDesktopIcons(
|
|||
const [positions, setPositions] = useState<Record<string, IconPosition>>({});
|
||||
const [dragId, setDragId] = useState<string | null>(null);
|
||||
const cellRef = useRef({ w: DEFAULT_CELL_WIDTH, h: DEFAULT_CELL_HEIGHT });
|
||||
const gridRef = useRef<{ cols: number; rows: number; originX: number; originY: number }>({
|
||||
const gridRef = useRef<{
|
||||
cols: number;
|
||||
rows: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
pitchX: number;
|
||||
pitchY: number;
|
||||
}>({
|
||||
cols: 1,
|
||||
rows: 1,
|
||||
originX: 10,
|
||||
originY: 12,
|
||||
pitchX: DEFAULT_CELL_WIDTH + GRID_GAP,
|
||||
pitchY: DEFAULT_CELL_HEIGHT + GRID_GAP,
|
||||
});
|
||||
const suppressRef = useRef(false);
|
||||
|
||||
|
|
@ -71,8 +80,10 @@ export function useDesktopIcons(
|
|||
|
||||
/**
|
||||
* Work out the grid from where the launcher stacks actually sit. The icon
|
||||
* width/height (plus the flex gap) is the cell pitch, and the left/top stack
|
||||
* offsets give column 0 / row 0. Falls back to sensible values before mount.
|
||||
* width/height is the cell size and the left/top stack offsets give column 0 /
|
||||
* row 0; the column pitch is stretched a touch so the right-hand stacks stay
|
||||
* flush with the right margin instead of stopping up to a cell short. Falls
|
||||
* back to sensible values before mount.
|
||||
*/
|
||||
const measure = useCallback(() => {
|
||||
const root = rootRef.current;
|
||||
|
|
@ -86,11 +97,13 @@ export function useDesktopIcons(
|
|||
|
||||
// Each stack renders from its own top offset; the left stack defines row 0,
|
||||
// the top-right stack shares it and the bottom-right stack defines the last
|
||||
// row band. Use the smallest top as the grid origin.
|
||||
// row band. Use the smallest top as the grid origin. The right-anchored
|
||||
// stacks also give the true right margin.
|
||||
let cellW = 0;
|
||||
let cellH = 0;
|
||||
let originX = Infinity;
|
||||
let originY = Infinity;
|
||||
let rightMargin = Infinity;
|
||||
|
||||
stacks.forEach((stack) => {
|
||||
const stackRect = stack.getBoundingClientRect();
|
||||
|
|
@ -109,45 +122,60 @@ export function useDesktopIcons(
|
|||
const r = el.getBoundingClientRect();
|
||||
cellW = Math.max(cellW, r.width);
|
||||
cellH = Math.max(cellH, r.height);
|
||||
rightMargin = Math.min(rightMargin, rootRect.right - r.right);
|
||||
});
|
||||
});
|
||||
|
||||
if (!Number.isFinite(originX)) originX = 10;
|
||||
if (!Number.isFinite(originY)) originY = 12;
|
||||
if (!Number.isFinite(rightMargin)) rightMargin = 10;
|
||||
if (cellW <= 0 || cellH <= 0) return;
|
||||
|
||||
const pitchX = cellW + GRID_GAP;
|
||||
const pitchY = cellH + GRID_GAP;
|
||||
const width = root.clientWidth || window.innerWidth;
|
||||
const height = (root.clientHeight || window.innerHeight) - TASKBAR_HEIGHT;
|
||||
|
||||
// Columns tile from the left, but the services/friends stacks are anchored
|
||||
// to the right edge. A fixed pitch leaves up to a full cell of slack on the
|
||||
// right (the remainder of width / pitch), which pulled those icons away from
|
||||
// the border. Stretch the column pitch a hair so the first and last columns
|
||||
// land flush on both margins; the extra gap is a few px and stays uniform.
|
||||
const minPitchX = cellW + GRID_GAP;
|
||||
const span = width - originX - rightMargin;
|
||||
const cols = Math.max(1, Math.floor((span + GRID_GAP) / minPitchX));
|
||||
let pitchX = minPitchX;
|
||||
if (cols > 1) {
|
||||
const stretched = (span - cellW) / (cols - 1);
|
||||
if (stretched > pitchX) pitchX = stretched;
|
||||
}
|
||||
|
||||
cellRef.current = { w: cellW, h: cellH };
|
||||
gridRef.current = {
|
||||
cols: Math.max(1, Math.floor((width - originX) / pitchX)),
|
||||
cols,
|
||||
rows: Math.max(1, Math.floor((height - originY) / pitchY)),
|
||||
originX,
|
||||
originY,
|
||||
pitchX,
|
||||
pitchY,
|
||||
};
|
||||
}, [rootRef]);
|
||||
|
||||
const posToCell = useCallback(
|
||||
(p: IconPosition): Cell => {
|
||||
const { w: cellW, h: cellH } = cellRef.current;
|
||||
const g = gridRef.current;
|
||||
return clampCell(
|
||||
Math.round((p.x - g.originX) / (cellW + GRID_GAP)),
|
||||
Math.round((p.y - g.originY) / (cellH + GRID_GAP))
|
||||
Math.round((p.x - g.originX) / g.pitchX),
|
||||
Math.round((p.y - g.originY) / g.pitchY)
|
||||
);
|
||||
},
|
||||
[clampCell]
|
||||
);
|
||||
|
||||
const cellToPos = useCallback((c: Cell): IconPosition => {
|
||||
const { w: cellW, h: cellH } = cellRef.current;
|
||||
const g = gridRef.current;
|
||||
return {
|
||||
x: g.originX + c.col * (cellW + GRID_GAP),
|
||||
y: g.originY + c.row * (cellH + GRID_GAP),
|
||||
x: g.originX + c.col * g.pitchX,
|
||||
y: g.originY + c.row * g.pitchY,
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -217,7 +245,6 @@ export function useDesktopIcons(
|
|||
const defaultCells = useCallback(
|
||||
(keys: string[]): Record<string, IconPosition> => {
|
||||
const root = rootRef.current;
|
||||
const { w: cellW, h: cellH } = cellRef.current;
|
||||
const g = gridRef.current;
|
||||
const out: Record<string, IconPosition> = {};
|
||||
if (!root) return out;
|
||||
|
|
@ -225,8 +252,8 @@ export function useDesktopIcons(
|
|||
const stacks = Array.from(
|
||||
root.querySelectorAll<HTMLElement>(".win-icons")
|
||||
).filter((el) => el.querySelector("[data-drag-id]"));
|
||||
const pitchX = cellW + GRID_GAP;
|
||||
const pitchY = cellH + GRID_GAP;
|
||||
const pitchX = g.pitchX;
|
||||
const pitchY = g.pitchY;
|
||||
const rootRect = root.getBoundingClientRect();
|
||||
|
||||
// Natural cell for each icon: the one its stack actually renders it in.
|
||||
|
|
@ -267,8 +294,8 @@ export function useDesktopIcons(
|
|||
const { w: cellW, h: cellH } = cellRef.current;
|
||||
const g = gridRef.current;
|
||||
return clampCell(
|
||||
Math.floor((x - g.originX + GRID_GAP / 2) / (cellW + GRID_GAP)),
|
||||
Math.floor((y - g.originY + GRID_GAP / 2) / (cellH + GRID_GAP))
|
||||
Math.floor((x - g.originX + (g.pitchX - cellW) / 2) / g.pitchX),
|
||||
Math.floor((y - g.originY + (g.pitchY - cellH) / 2) / g.pitchY)
|
||||
);
|
||||
},
|
||||
[clampCell]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue