fix(balance): disambiguate address drag — edge to reorder, centre to transfer
The address list supported two drag gestures that collided: dragging a row onto another transferred funds, dragging into a gap reordered. Since rows are contiguous, a reorder-drag was almost always over another row, so it triggered a fund transfer instead of reordering. Disambiguate by WHERE on the target row the drag is released (user's suggestion): the top/bottom ~30% edge bands = reorder (an insertion line is shown), the centre = transfer (the row highlights). A zero-balance row or an off-row drop always reorders. Tooltip and i18n hint updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -139,6 +139,9 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
static float s_dragStartY = 0.0f; // mouse Y at drag start
|
||||
static bool s_dragActive = false; // drag distance threshold passed
|
||||
static int s_dropTargetIdx = -1; // row hovered during drag
|
||||
static int s_dropMode = 0; // 0 = transfer (released over a row's CENTER), 1 = reorder
|
||||
// (released over a row's top/bottom EDGE). Disambiguates the
|
||||
// two drag gestures by WHERE on the target row you release.
|
||||
|
||||
// Copy feedback
|
||||
static int s_copiedRow = -1;
|
||||
@@ -314,38 +317,45 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
s_copiedRow = s_dragIdx;
|
||||
s_copiedTime = (float)ImGui::GetTime();
|
||||
}
|
||||
// Drop
|
||||
if (s_dragActive && s_dropTargetIdx >= 0 && s_dropTargetIdx != s_dragIdx) {
|
||||
const auto& srcRow = rows[s_dragIdx];
|
||||
const auto& dstRow = rows[s_dropTargetIdx];
|
||||
// Transfer: if dropped on another row and drag was active
|
||||
if (srcRow.info->balance > 1e-9) {
|
||||
AddressTransferDialog::TransferInfo ti;
|
||||
ti.fromAddr = srcRow.info->address;
|
||||
ti.toAddr = dstRow.info->address;
|
||||
ti.fromBalance = srcRow.info->balance;
|
||||
ti.toBalance = dstRow.info->balance;
|
||||
ti.fromIsZ = srcRow.isZ;
|
||||
ti.toIsZ = dstRow.isZ;
|
||||
AddressTransferDialog::show(app, ti);
|
||||
// Drop. Released over a row's CENTER (transfer mode) and that row has funds -> transfer;
|
||||
// released over an EDGE, in a gap, or on a zero-balance row -> reorder.
|
||||
if (s_dragActive) {
|
||||
bool didTransfer = false;
|
||||
if (s_dropMode == 0 && s_dropTargetIdx >= 0 && s_dropTargetIdx != s_dragIdx &&
|
||||
s_dragIdx < (int)rows.size()) {
|
||||
const auto& srcRow = rows[s_dragIdx];
|
||||
const auto& dstRow = rows[s_dropTargetIdx];
|
||||
if (srcRow.info->balance > 1e-9) {
|
||||
AddressTransferDialog::TransferInfo ti;
|
||||
ti.fromAddr = srcRow.info->address;
|
||||
ti.toAddr = dstRow.info->address;
|
||||
ti.fromBalance = srcRow.info->balance;
|
||||
ti.toBalance = dstRow.info->balance;
|
||||
ti.fromIsZ = srcRow.isZ;
|
||||
ti.toIsZ = dstRow.isZ;
|
||||
AddressTransferDialog::show(app, ti);
|
||||
didTransfer = true;
|
||||
}
|
||||
}
|
||||
} else if (s_dragActive && s_dropTargetIdx < 0) {
|
||||
// Reorder: dropped in gap — compute insert position from mouseY
|
||||
int insertIdx = 0;
|
||||
for (int i = 0; i < (int)rows.size(); ++i) {
|
||||
if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1;
|
||||
}
|
||||
if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1) {
|
||||
int targetIdx = (insertIdx > s_dragIdx) ? insertIdx - 1 : insertIdx;
|
||||
if (targetIdx >= 0 && targetIdx < (int)rows.size()) {
|
||||
app->swapAddressOrder(rows[s_dragIdx].info->address,
|
||||
rows[targetIdx].info->address);
|
||||
if (!didTransfer) {
|
||||
// Reorder — compute insert position from mouseY.
|
||||
int insertIdx = 0;
|
||||
for (int i = 0; i < (int)rows.size(); ++i) {
|
||||
if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1;
|
||||
}
|
||||
if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1) {
|
||||
int targetIdx = (insertIdx > s_dragIdx) ? insertIdx - 1 : insertIdx;
|
||||
if (targetIdx >= 0 && targetIdx < (int)rows.size() && s_dragIdx < (int)rows.size()) {
|
||||
app->swapAddressOrder(rows[s_dragIdx].info->address,
|
||||
rows[targetIdx].info->address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s_dragIdx = -1;
|
||||
s_dragActive = false;
|
||||
s_dropTargetIdx = -1;
|
||||
s_dropMode = 0;
|
||||
}
|
||||
|
||||
// ---- PASS 2: Render rows ----
|
||||
@@ -371,8 +381,9 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
ImVec2(rowEnd.x + 2*dp, rowEnd.y + 2*dp),
|
||||
IM_COL32(0, 0, 0, 30), 6.0f * dp);
|
||||
dl->AddRectFilled(rowPos, rowEnd, IM_COL32(30, 30, 40, 120), 4.0f * dp);
|
||||
// Tooltip following cursor — show transfer intent if over a target row
|
||||
if (s_dropTargetIdx >= 0 && s_dropTargetIdx < (int)rows.size()) {
|
||||
// Tooltip following cursor — reflect the gesture: centre of a row = transfer to it,
|
||||
// edge of a row = reorder (move here).
|
||||
if (s_dropTargetIdx >= 0 && s_dropTargetIdx < (int)rows.size() && s_dropMode == 0) {
|
||||
const auto& target = rows[s_dropTargetIdx];
|
||||
ImGui::SetTooltip("%s\n%s\n\n%s %s",
|
||||
truncateAddress(addr.address, 32).c_str(),
|
||||
@@ -380,9 +391,10 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
TR("transfer_to"),
|
||||
truncateAddress(target.info->address, 32).c_str());
|
||||
} else {
|
||||
ImGui::SetTooltip("%s\n%s",
|
||||
ImGui::SetTooltip("%s\n%s\n\n%s",
|
||||
truncateAddress(addr.address, 32).c_str(),
|
||||
row.isZ ? TR("shielded") : TR("transparent"));
|
||||
row.isZ ? TR("shielded") : TR("transparent"),
|
||||
TR("address_reorder_hint"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,12 +419,24 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
// Hover effects
|
||||
bool hovered = !isDragged && material::IsRectHovered(rowPos, rowEnd);
|
||||
|
||||
// Drop target highlight when dragging another row over this one
|
||||
// Drop target feedback when dragging another row over this one. The cursor's vertical
|
||||
// position WITHIN the row picks the gesture: top/bottom edge = reorder (show an insertion
|
||||
// line), centre = transfer (highlight the whole row).
|
||||
if (s_dragActive && s_dragIdx != row_idx && material::IsRectHovered(rowPos, rowEnd)) {
|
||||
s_dropTargetIdx = row_idx;
|
||||
ImU32 dropCol = WithAlpha(Primary(), 40);
|
||||
dl->AddRectFilled(rowPos, rowEnd, dropCol, 4.0f * dp);
|
||||
dl->AddRect(rowPos, rowEnd, WithAlpha(Primary(), 140), 4.0f * dp, 0, 2.0f * dp);
|
||||
const float relY = rowH > 0.0f ? (mousePos.y - rowPos.y) / rowH : 0.5f;
|
||||
s_dropMode = (relY < 0.30f || relY > 0.70f) ? 1 : 0;
|
||||
if (s_dropMode == 0) {
|
||||
// Transfer: highlight the whole target row.
|
||||
dl->AddRectFilled(rowPos, rowEnd, WithAlpha(Primary(), 40), 4.0f * dp);
|
||||
dl->AddRect(rowPos, rowEnd, WithAlpha(Primary(), 140), 4.0f * dp, 0, 2.0f * dp);
|
||||
} else {
|
||||
// Reorder: draw an insertion line at the near (top or bottom) edge.
|
||||
const float lineY = (relY < 0.5f) ? rowPos.y : rowEnd.y;
|
||||
dl->AddLine(ImVec2(rowPos.x, lineY), ImVec2(rowEnd.x, lineY),
|
||||
WithAlpha(Primary(), 230), 2.5f * dp);
|
||||
dl->AddCircleFilled(ImVec2(rowPos.x, lineY), 3.0f * dp, WithAlpha(Primary(), 230));
|
||||
}
|
||||
}
|
||||
|
||||
if (hovered && selected_row != row_idx && !s_dragActive) {
|
||||
|
||||
@@ -484,6 +484,7 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["no_icons_found"] = "No icons match your search.";
|
||||
strings_["transfer_funds"] = "Transfer Funds";
|
||||
strings_["transfer_to"] = "Transfer to:";
|
||||
strings_["address_reorder_hint"] = "Drop on a row's edge to reorder, or its centre to transfer";
|
||||
strings_["deshielding_warning"] = "Warning: This will de-shield funds from a private (Z) address to a transparent (T) address.";
|
||||
strings_["shielding_notice"] = "Note: This will shield funds from a transparent (T) address to a private (Z) address.";
|
||||
strings_["result_preview"] = "Result Preview";
|
||||
|
||||
Reference in New Issue
Block a user