Files
zfxaction25/cardrow.md
2026-04-19 00:43:27 +02:00

10 KiB

Card Row UX (target design)

This document describes the intended behavior for the next CardRow/CardControl UX iteration (presorted hand, external-drag box select, unified program row).

Sources

  • DonkeysAndDroids.Godot/CardRow.cs
  • DonkeysAndDroids.Godot/CardControl.cs
  • DonkeysAndDroids.Godot/card_row.tscn
  • DonkeysAndDroids.Godot/program.tscn
  • DonkeysAndDroids.Godot/ProgramScreen.cs
  • DonkeysAndDroids.Godot/improve.tscn
  • DonkeysAndDroids.Godot/Improve.cs
  • DonkeysAndDroids.Godot/draw_glitch.tscn
  • DonkeysAndDroids.Godot/DrawGlitch.cs
  • DonkeysAndDroids.Godot/gamble.tscn
  • DonkeysAndDroids.Godot/Gamble.cs
  • DonkeysAndDroids.Godot/buffer_overflow.tscn
  • DonkeysAndDroids.Godot/BufferOverflow.cs

Component model

  • CardRow is the container that owns CardControls, maintains OrderedCards, layout, and drag/drop reordering. It exposes signals SelectionChanged and OrderChanged.
  • CardControl is the per-card UI. It handles hover/selection/drag visuals and emits CardDroppedOn and CardSelectionChanged signals.

Card states and visuals

State triggers and visuals (applies when not Disabled unless noted):

  • Idle: default; scale (1,1), pivot center, modulate (1,1,1), z-index 0.
  • Hovered: mouse enters card while no GUI drag is active and (CanSelect or CanDrag) is true; scale (1.25,1.25), modulate (1.25,1.25,1.25), z-index 1.
  • Selected: Selected == true; pivot Y moves to bottom edge, scale (1.25,1.25). Tooltip panel is updated via Tooltip.Instance.Describe.
  • Drop target (before/after): during a valid drag over this card, m_DropBefore set based on cursor X; pivot X snaps to left or right edge, scale (1.25,1.25), alpha set to 0.5.
  • Dragging (source card): after drag starts, card is hidden by setting scale X to 0; hover and drop indicators do not apply.

State precedence (in UpdateState):

  1. Dragging overrides all other visuals.
  2. Drop target overrides pivot X and alpha (pivot Y from Selected may remain).
  3. Hover overrides scale/modulate.
  4. Selected sets pivot Y and scale.

All pivot/scale/modulate changes are tweened with short spring transitions.

Selection behavior

  • Select toggle: left mouse press + release on a card toggles Selected if CanSelect and not Disabled.
  • External-drag box select: if the left mouse button is pressed outside any CardRow (not on a card/row) and the mouse is dragged, any hovered card in the first CardRow entered is force-selected (no toggle off) until release. This is additive unless SupportsMultiSelect is false (in which case the most recently hovered card remains selected).
  • Clicking a selected card again deselects it (subject to capacity rules when deselecting; see Program screen).
  • SupportsMultiSelect:
    • If false, selecting a card automatically deselects any other selected cards in that row.
    • If true, multiple cards can be toggled independently.
  • SelectionContext is global: selecting a card in any row clears selection in the previously active row. Multi-row selection is not possible.
  • SelectionChanged emits an array of selected CardControls from the row. The array order is not guaranteed (dictionary order).
  • Tooltip:
    • Hover uses Godot TooltipText set to card name + description.
    • Selection additionally calls Tooltip.Instance.Describe(card).

Drag and drop behavior

Drag start

  • Valid only when CanDrag is true and the card is not Disabled.
  • Drag payload is the original CardControl; drag preview is a duplicated CardControl configured with the same card and row.
  • The source card becomes visually hidden (scale X = 0) until drag ends.

Multi-drag selection

  • SupportsMultiDrag must be true.
  • If multiple cards are selected and the dragged card is among them, all selected cards are dragged together.
  • If multiple cards are selected but the dragged card is not selected, only the dragged card moves.
  • If only one card is selected, only that card moves.
  • Dragged card order is preserved as the order they appear in OrderedCards.

Drop targets

  • Row background (CardRow._DropData) inserts at an index computed from cursor X relative to existing card positions.
  • Card target (CardControl._DropData) inserts before/after the target card based on cursor X < card width/2.

Valid drop checks

A drop is valid only if all are true:

  • Target row is not Disabled.
  • Target row CardsCanDrag == true.
  • Drag payload is a CardControl currently in a CardRow.
  • If source row != target row, capacity check passes: targetRow.OccupiedSpace + sum(dragged.OccupiedSpace) <= targetRow.MaxCards.

Additional invalid cases:

  • Dropping a card onto itself (source == target) is ignored.
  • Dropping onto a card that is part of the dragged set is ignored.
  • If the dragged card cannot be resolved in its source row (no card id match), the drop is ignored.

Move results

  • Reorder within the same row:
    • Cards are removed and reinserted at the adjusted index (accounts for removed cards before the insert point).
    • Selection state is preserved because existing CardControls are reused.
    • OrderChanged is emitted on that row.
  • Move across rows:
    • Cards are removed from the source list and inserted into the target list.
    • New CardControls are created in the target row; selection state resets for moved cards.
    • OrderChanged is emitted on the target row only.
  • All moves animate via tweened layout. During drag-triggered moves, new cards start at their target positions (no center-entry animation).

Row-to-row interaction rules (valid and invalid)

  • Selecting a card in Row B always clears selection in Row A (global SelectionContext).
  • External-drag box selection locks onto the first row entered; hovered cards in other rows are ignored until mouse release.
  • Dragging from Row A to Row B is only possible if both rows have CardsCanDrag = true and are not Disabled.
  • Dropping on a row with CardsCanDrag = false is invalid (no acceptance, no drop indicator).
  • Dropping into a row that would exceed MaxCards is invalid (drop rejected).
  • Dragging within a row is always allowed when the row is draggable, regardless of capacity.
  • Dragging while any GUI drag is active disables hover visuals on other cards (no hover scaling).

Layout and ordering

  • Card positions are computed from CardSize, CardSpacing, and the row Size.
  • Non-wrap rows center the line of cards; spacing shrinks (can be negative) when there is insufficient width, causing overlap.
  • Wrap rows (WrapCards = true) break onto additional rows when the next card would overflow width and set CustomMinimumSize to fit.
  • GetSortedCards sorts by:
    1. Rarity
    2. Card Id (ECard enum)
    3. Modifier count
    4. CardId (unique Guid)

Use cases by screen

Program row (Program screen, unified hand/tape)

Files: DonkeysAndDroids.Godot/program.tscn, DonkeysAndDroids.Godot/ProgramScreen.cs. Row node: single CardRow replacing the split %Hand/%Tape. Configuration:

  • CardSize 80x100 (from card_row.tscn).
  • CardSpacing 2.
  • AnimationDuration 0.5.
  • CardsCanDrag true, CardsCanSelect true.
  • SupportsMultiSelect true, SupportsMultiDrag true.
  • MaxCards is not used to enforce tape/hand capacity; capacity is enforced by selection rules.

Behavior:

  • The row contains all cards from CoreLoop.Hand and CoreLoop.Tape.
  • Initial order on entry: CoreLoop.Tape (in tape order) followed by GetSortedCards(CoreLoop.Hand) (hand pre-sorted).
  • Initial selection: cards from CoreLoop.Tape are Selected; cards from CoreLoop.Hand are Deselected.
  • Selection defines location:
    • Selected cards count as Tape.
    • Deselected cards count as Hand.
  • Tape order is the order of selected cards in the row; hand order is the order of deselected cards in the row.
  • Program row order is authoritative; results must not reorder existing cards. HandResult/TapeResult are treated as membership sets (selection + add/remove only).
  • New cards not already in the row are appended after existing cards (tape cards in result order, then hand cards in result order).
  • Dragging reorders the row only (no row-to-row drag). Multi-drag still reorders within the row.
  • Selecting a card is only allowed if the resulting tape OccupiedSpace is <= TapeLength.
  • Deselecting a card is only allowed if the resulting hand OccupiedSpace is <= HandSize.
  • Discard operates on the selected set (selected cards can be discarded).
  • Sort button re-sorts only the deselected (hand) cards and keeps selected (tape) cards fixed in place.

Shop (Improve screen)

Files: DonkeysAndDroids.Godot/improve.tscn, DonkeysAndDroids.Godot/Improve.cs. Row node: %Shop (CardRow instance). Configuration:

  • CardSize 90x112.
  • CardSpacing 5.
  • CardsCanDrag false.
  • CardsCanSelect true.
  • SupportsMultiSelect true.
  • SupportsMultiDrag false (default).

Behavior:

  • Multiple cards can be selected for purchase; Buy button cost is the sum of selected cards.
  • Selection is the only interaction; drag and drop are disabled.
  • Selection persists within the row until cleared by clicking again or selecting another row.

Invalid interactions:

  • Dragging from or dropping onto the shop row is not possible (CardsCanDrag false).
  • Selecting cards in the deck preview clears shop selection.

Deck preview (Improve + DrawGlitch screens)

Files: DonkeysAndDroids.Godot/improve.tscn, DonkeysAndDroids.Godot/draw_glitch.tscn, DonkeysAndDroids.Godot/Improve.cs, DonkeysAndDroids.Godot/DrawGlitch.cs. Row nodes: %Deck in both scenes.

Configuration (Improve deck):

  • CardsCanDrag false.
  • WrapCards true.
  • CardSpacing 15.
  • CardsCanSelect true (default), SupportsMultiSelect false.

Configuration (DrawGlitch deck):

  • CardsCanDrag false.
  • WrapCards true.
  • CardSpacing 10.
  • CardsCanSelect true (default), SupportsMultiSelect false.

Behavior:

  • Deck order is always the sorted order from GetSortedCards.
  • Cards can be hovered and selected (single selection only), primarily for tooltip/inspect.
  • No drag/drop or reordering.

Invalid interactions:

  • Dragging from or dropping onto deck preview rows is not possible.
  • Selecting a card here clears any selection in other rows.

Variants using the same CardRow mechanics (for completeness)

  • Gamble (DonkeysAndDroids.Godot/gamble.tscn): %Patches row uses CardsCanDrag false and single selection to pick exactly one card to add.
  • BufferOverflow (DonkeysAndDroids.Godot/buffer_overflow.tscn): %Hand row uses CardsCanDrag false and single selection to pick exactly one card to remove.