Let the page fling work anywhere, with a setting to turn it off
A fling toward a free side of the active page now offers a page at any zoom or pan position, not only once that edge is on screen. If the side's plus is visible it pulses as before; otherwise an "Add page" button appears at that edge of the screen, tapped through the same palm-checked gesture as the plus. Settings → Pen & touch gets "Swipe to add pages" (on by default). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
This commit is contained in:
parent
2df91b561d
commit
5fb11e4928
5 changed files with 71 additions and 11 deletions
|
|
@ -119,7 +119,8 @@
|
|||
|
||||
const placeSlots = $derived(tools.placing ? freeSlots(doc.tree) : []);
|
||||
|
||||
// A finger fling past the active page's free edge highlights the plus on that side.
|
||||
// A finger fling toward a free side of the active page offers a new page there: the plus
|
||||
// pulses if it's on screen, otherwise a button appears at that edge of the screen.
|
||||
let offer = $state<Slot | null>(null);
|
||||
let offerTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
|
|
@ -130,18 +131,21 @@
|
|||
}
|
||||
|
||||
function offerAfterFling(samples: Sample[]) {
|
||||
if (!settings.data.flingToAdd) return;
|
||||
const dir = flingDir(samples);
|
||||
const id = doc.activeId;
|
||||
const r = id && doc.rects.get(id);
|
||||
const slot = dir && ghostSlots.find((s) => s.anchor === id && s.dir === dir);
|
||||
if (!r || !slot) return;
|
||||
// Only once that edge has come into view, i.e. the fling went past the end.
|
||||
if (slot) setOffer(slot);
|
||||
}
|
||||
|
||||
/** The offered plus is off screen, so the offer shows as an edge-of-screen button instead. */
|
||||
const offerOffscreen = $derived.by(() => {
|
||||
const r = offer && plusRect(offer);
|
||||
if (!r) return false;
|
||||
const a = viewport.toScreen(r.x, r.y);
|
||||
const b = viewport.toScreen(r.x + r.width, r.y + r.height);
|
||||
const edge = { left: a.x, right: b.x, up: a.y, down: b.y }[slot.dir];
|
||||
const size = slot.dir === 'left' || slot.dir === 'right' ? viewport.width : viewport.height;
|
||||
if (edge >= 0 && edge <= size) setOffer(slot);
|
||||
}
|
||||
return b.x < 0 || b.y < 0 || a.x > viewport.width || a.y > viewport.height;
|
||||
});
|
||||
|
||||
// Show every possible spot when an imported PDF is waiting to be placed.
|
||||
$effect(() => {
|
||||
|
|
@ -338,7 +342,7 @@
|
|||
const fingerInks = tools.inking && settings.data.fingerDraw && !palm.penSeen;
|
||||
// Taps on an add-page button are palm-checked like navigation: still rejected for
|
||||
// pen-down, palm-sized contacts and right after pen use, but not for the 5 s pen session.
|
||||
const plus = target.closest<HTMLElement>('.ghost');
|
||||
const plus = target.closest<HTMLElement>('.ghost, .offer-pill');
|
||||
if (e.pointerType === 'touch') {
|
||||
const decision = palm.evaluate(e, touchIntent(fingerInks, !!plus));
|
||||
if (!decision.accept) return; // palm: ignore entirely
|
||||
|
|
@ -707,6 +711,20 @@
|
|||
</button>
|
||||
{/if}
|
||||
|
||||
{#if offer && offerOffscreen}
|
||||
<!-- Like the plus buttons, taps go through the palm-checked gesture. -->
|
||||
<button
|
||||
class="offer-pill"
|
||||
data-anchor={offer.anchor}
|
||||
data-dir={offer.dir}
|
||||
data-side={offer.dir}
|
||||
onclick={(e) => e.detail === 0 && offer && addPage(offer.anchor, offer.dir)}
|
||||
>
|
||||
<CirclePlusIcon size={18} strokeWidth={1.75} />
|
||||
Add page
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if eraserAt && tools.tool === 'eraser'}
|
||||
<div class="eraser-ring" style="left:{eraserAt.x}px; top:{eraserAt.y}px; width:{tools.eraserRadius * 2}px; height:{tools.eraserRadius * 2}px"></div>
|
||||
{/if}
|
||||
|
|
@ -834,6 +852,41 @@
|
|||
transform: scale(1.3);
|
||||
}
|
||||
}
|
||||
.offer-pill {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 16px 10px 12px;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 999px;
|
||||
background: var(--bg);
|
||||
color: var(--accent);
|
||||
box-shadow: var(--shadow);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.offer-pill[data-side='right'] {
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.offer-pill[data-side='left'] {
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.offer-pill[data-side='up'] {
|
||||
top: 12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.offer-pill[data-side='down'] {
|
||||
/* Clear of the toolbar. */
|
||||
bottom: 72px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ghost.offer .plus {
|
||||
animation: none;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue