From: Carl Worth Date: Sun, 8 Mar 2026 13:20:25 +0000 (-0400) Subject: Add support for dragging tiles while claiming X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=6125475ff20abb3751b51b2960f31627463002d6;p=lmno.games Add support for dragging tiles while claiming This can be either to drag tiles to the rack (to claim them) or to just rearrange the tiles (as can already be done outside of claiming). A 5-pixel threshold of movement will trigger a drag operation. Movement less than that will be considered a click and will claim the letter. --- diff --git a/anagrams/anagrams.jsx b/anagrams/anagrams.jsx index 0d2397d..14b4d05 100644 --- a/anagrams/anagrams.jsx +++ b/anagrams/anagrams.jsx @@ -596,23 +596,43 @@ class Game extends React.Component { * Center tile dragging (client-side rearrangement) * *****************************************************/ + _drop_on_rack(clientX, clientY, tile) { + if (!this.state.claim_active) return false; + const rack = document.querySelector(".claim-rack"); + if (!rack) return false; + const r = rack.getBoundingClientRect(); + if (clientX >= r.left && clientX <= r.right + && clientY >= r.top && clientY <= r.bottom) { + this.take_letter(tile); + return true; + } + return false; + } + on_center_mouse_down(e, tile) { - /* Only allow rearranging when not in claim mode. */ - if (this.state.claim_active) return; if (e.button !== 0) return; const pool = e.target.closest(".center-pool"); if (!pool) return; const rect = pool.getBoundingClientRect(); + const startX = e.clientX; + const startY = e.clientY; this._drag_offset = { x: e.clientX - (this.state.tile_positions[tile.id].x / 100 * rect.width), y: e.clientY - (this.state.tile_positions[tile.id].y / 100 * rect.height) }; this._drag_pool_rect = rect; - this.setState({ dragging_center_tile: tile.id }); + let dragging = false; const onMove = (me) => { + const dx = me.clientX - startX; + const dy = me.clientY - startY; + if (!dragging) { + if (dx * dx + dy * dy < 25) return; + dragging = true; + this.setState({ dragging_center_tile: tile.id }); + } const x = ((me.clientX - this._drag_offset.x) / rect.width) * 100; const y = ((me.clientY - this._drag_offset.y) / rect.height) * 100; this.setState(prev => ({ @@ -626,10 +646,15 @@ class Game extends React.Component { })); }; - const onUp = () => { + const onUp = (me) => { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); - this.setState({ dragging_center_tile: null }); + if (dragging) { + this.setState({ dragging_center_tile: null }); + this._drop_on_rack(me.clientX, me.clientY, tile); + } else if (this.state.claim_active && !this.state.revealing[tile.id]) { + this.take_letter(tile); + } }; document.addEventListener("mousemove", onMove); @@ -638,7 +663,6 @@ class Game extends React.Component { } on_center_touch_start(e, tile) { - if (this.state.claim_active) return; if (e.touches.length !== 1) return; const pool = e.target.closest(".center-pool"); @@ -646,29 +670,38 @@ class Game extends React.Component { const touch = e.touches[0]; const rect = pool.getBoundingClientRect(); + this._drag_start = { x: touch.clientX, y: touch.clientY }; this._drag_offset = { x: touch.clientX - (this.state.tile_positions[tile.id].x / 100 * rect.width), y: touch.clientY - (this.state.tile_positions[tile.id].y / 100 * rect.height) }; this._drag_pool_rect = rect; - this._touch_moved = false; - this.setState({ dragging_center_tile: tile.id }); + this._drag_dragging = false; + this._drag_tile = tile; e.preventDefault(); } on_center_touch_move(e) { - if (this.state.dragging_center_tile === null) return; + if (!this._drag_tile) return; const touch = e.touches[0]; + this._drag_last_touch = { x: touch.clientX, y: touch.clientY }; const rect = this._drag_pool_rect; - this._touch_moved = true; + + if (!this._drag_dragging) { + const dx = touch.clientX - this._drag_start.x; + const dy = touch.clientY - this._drag_start.y; + if (dx * dx + dy * dy < 25) return; + this._drag_dragging = true; + this.setState({ dragging_center_tile: this._drag_tile.id }); + } const x = ((touch.clientX - this._drag_offset.x) / rect.width) * 100; const y = ((touch.clientY - this._drag_offset.y) / rect.height) * 100; this.setState(prev => ({ tile_positions: { ...prev.tile_positions, - [this.state.dragging_center_tile]: { + [this._drag_tile.id]: { x: Math.max(0, Math.min(90, x)), y: Math.max(0, Math.min(85, y)) } @@ -678,8 +711,21 @@ class Game extends React.Component { } on_center_touch_end(e) { - if (this.state.dragging_center_tile === null) return; - this.setState({ dragging_center_tile: null }); + const tile = this._drag_tile; + if (!tile) return; + + if (this._drag_dragging) { + this.setState({ dragging_center_tile: null }); + /* Use last known touch position for drop detection. */ + if (this._drag_last_touch) { + this._drop_on_rack(this._drag_last_touch.x, this._drag_last_touch.y, tile); + } + } else if (this.state.claim_active && !this.state.revealing[tile.id]) { + this.take_letter(tile); + } + + this._drag_tile = null; + this._drag_dragging = false; } /***************************************************** @@ -791,8 +837,7 @@ class Game extends React.Component { left: pos.x + "%", top: pos.y + "%", zIndex: is_dragging ? 10 : 1, - cursor: claim_active && !is_revealing ? "pointer" : - !claim_active && !is_revealing ? "grab" : "default" + cursor: claim_active ? "pointer" : "grab" }; let className = ""; @@ -804,12 +849,8 @@ class Game extends React.Component {
this.on_center_mouse_down(e, tile) : null} - onTouchStart={!claim_active && !is_revealing - ? (e) => this.on_center_touch_start(e, tile) : null} - onClick={claim_active && !is_revealing - ? () => this.take_letter(tile) : null}> + onMouseDown={(e) => this.on_center_mouse_down(e, tile)} + onTouchStart={(e) => this.on_center_touch_start(e, tile)}> {is_revealing ? {rev.seconds} : tile.letter}