fix(ui): cut-off/clipping — recent-tx collisions, updater note wrap, request-payment URI, console filter, icon-grid scroll
Fixes the cut-off/clipping bugs from the layout audit (all visible at the default
1280/1024 window sizes):
- Receive "Recent Received" rows: the amount collided with the relative-time
("+15.7500 DRGX14 days ago") and the type label touched the address at narrow
widths. Use the shared short time format (formatTimeAgoShort, matching Overview),
chain the amount's right edge off the measured time width, and start the address
after the measured type-label width — so neither pair can collide.
- Daemon & xmrig updater verify-note: drawn unwrapped and clipped at the card's right
edge; wrap it (PushTextWrapPos) within the already-reserved height.
- Request Payment: the three footer buttons shared one fixed width (clipping "Copy
Full Address"); size each to its own label. The Payment URI overflowed a plain
field; render it in a bordered read-only box (bounded, un-chunked).
- Console: the filter input shrank below its own placeholder (gone entirely at 1024);
give it a min width >= the placeholder and drop the "N lines" count when the row
can't fit both.
- Address-label "Choose Icon" grid: had NoScrollbar hiding most of the catalog with
no cue; give it a real scrollbar.
- Overview "Recent Transactions": drop the 4th row at 1024 (it clipped off-screen) by
capping to rows that fully fit the reserved height.
- Sidebar: reserve the unread-badge width in the nav-label centering so History/Chat
labels no longer collide with their badge.
Verified at 1024 and 1280 across full-node + Lite + Windows (ctest green) and an
adversarial diff review (clean). Skipped the legacy settings_window overlay footer
(dead code / removal candidate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -676,17 +676,33 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
ImU32 textCol = selected ? Primary() : (pageNeedsUnlock ? OnSurfaceDisabled() : OnSurfaceMedium());
|
ImU32 textCol = selected ? Primary() : (pageNeedsUnlock ? OnSurfaceDisabled() : OnSurfaceMedium());
|
||||||
|
|
||||||
if (showLabels) {
|
if (showLabels) {
|
||||||
|
// Reserve room for a badge (if this item will draw one) so the
|
||||||
|
// label centers in the space to the left of it instead of
|
||||||
|
// running underneath the badge circle.
|
||||||
|
bool itemHasBadge =
|
||||||
|
(item.page == NavPage::History && status.unconfirmedTxCount > 0) ||
|
||||||
|
(item.page == NavPage::Mining && status.miningActive) ||
|
||||||
|
(item.page == NavPage::Peers && status.peerCount > 0) ||
|
||||||
|
(item.page == NavPage::Chat && status.chatUnreadCount > 0);
|
||||||
|
float badgeReserve = 0.0f;
|
||||||
|
if (itemHasBadge) {
|
||||||
|
bool dotOnlyReserve = (item.page == NavPage::Mining);
|
||||||
|
float badgeRReserve = dotOnlyReserve ? badgeRadiusDot : badgeRadiusNumber;
|
||||||
|
float badgeInsetXReserve = sde("badge-inset-x", 6.0f);
|
||||||
|
badgeReserve = badgeRReserve * 2.0f + badgeInsetXReserve;
|
||||||
|
}
|
||||||
|
|
||||||
ImFont* font = selected ? Type().subtitle2() : Type().body2();
|
ImFont* font = selected ? Type().subtitle2() : Type().body2();
|
||||||
float lblFsz = ScaledFontSize(font);
|
float lblFsz = ScaledFontSize(font);
|
||||||
float btnW = indMax.x - indMin.x;
|
float btnW = indMax.x - indMin.x;
|
||||||
float maxLabelW = btnW - iconS * 2.0f - iconLabelGap - Layout::spacingXs() * 2;
|
float maxLabelW = btnW - iconS * 2.0f - iconLabelGap - Layout::spacingXs() * 2 - badgeReserve;
|
||||||
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
|
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
|
||||||
if (labelSz.x > maxLabelW && maxLabelW > 0) {
|
if (labelSz.x > maxLabelW && maxLabelW > 0) {
|
||||||
lblFsz *= maxLabelW / labelSz.x;
|
lblFsz *= maxLabelW / labelSz.x;
|
||||||
labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
|
labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
|
||||||
}
|
}
|
||||||
float totalW = iconS * 2.0f + iconLabelGap + labelSz.x;
|
float totalW = iconS * 2.0f + iconLabelGap + labelSz.x;
|
||||||
float btnCX = (indMin.x + indMax.x) * 0.5f;
|
float btnCX = (indMin.x + indMax.x - badgeReserve) * 0.5f;
|
||||||
float startX = btnCX - totalW * 0.5f;
|
float startX = btnCX - totalW * 0.5f;
|
||||||
|
|
||||||
DrawNavIcon(dl, item.page, startX + iconS, iconCY, iconS, textCol);
|
DrawNavIcon(dl, item.page, startX + iconS, iconCY, iconS, textCol);
|
||||||
|
|||||||
@@ -138,8 +138,13 @@ public:
|
|||||||
const float controlsTopY = std::max(gridStartY + cellSz * 2.0f, buttonY - preButtonReserve);
|
const float controlsTopY = std::max(gridStartY + cellSz * 2.0f, buttonY - preButtonReserve);
|
||||||
const float gridMaxH = std::max(cellSz * 2.0f, controlsTopY - gridStartY);
|
const float gridMaxH = std::max(cellSz * 2.0f, controlsTopY - gridStartY);
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0));
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 11.0f * dp);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 5.5f * dp);
|
||||||
|
// Scrollbar visible (not NoScrollbar) — the icon set exceeds the fixed-height
|
||||||
|
// grid, so a real scrollbar is the discoverable way to reach the rest.
|
||||||
ImGui::BeginChild("##IconGrid", ImVec2(avail, gridMaxH), ImGuiChildFlags_None,
|
ImGui::BeginChild("##IconGrid", ImVec2(avail, gridMaxH), ImGuiChildFlags_None,
|
||||||
ImGuiWindowFlags_NoScrollbar);
|
ImGuiWindowFlags_NoScrollWithMouse);
|
||||||
|
ApplySmoothScroll();
|
||||||
|
|
||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
@@ -185,6 +190,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleVar(2); // ScrollbarSize + ScrollbarRounding
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
if (ImGui::GetCursorPosY() < controlsTopY) {
|
if (ImGui::GetCursorPosY() < controlsTopY) {
|
||||||
|
|||||||
@@ -805,6 +805,7 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
|
|||||||
const float kRecentTxRowHeight = S.drawElement("tabs.balance", "recent-tx-row-height").sizeOr(22.0f);
|
const float kRecentTxRowHeight = S.drawElement("tabs.balance", "recent-tx-row-height").sizeOr(22.0f);
|
||||||
const auto& state = app->state();
|
const auto& state = app->state();
|
||||||
|
|
||||||
|
float headerStartY = ImGui::GetCursorPosY();
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("recent_transactions"));
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("recent_transactions"));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -812,6 +813,7 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
|
|||||||
app->setCurrentPage(NavPage::History);
|
app->setCurrentPage(NavPage::History);
|
||||||
}
|
}
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
float headerHeight = ImGui::GetCursorPosY() - headerStartY;
|
||||||
|
|
||||||
float scaledRowH = std::max(S.drawElement("tabs.balance", "recent-tx-row-min-height").size, kRecentTxRowHeight * vs);
|
float scaledRowH = std::max(S.drawElement("tabs.balance", "recent-tx-row-min-height").size, kRecentTxRowHeight * vs);
|
||||||
float availableListH = ImGui::GetContentRegionAvail().y;
|
float availableListH = ImGui::GetContentRegionAvail().y;
|
||||||
@@ -820,14 +822,17 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
|
|||||||
ImGuiWindowFlags_NoBackground);
|
ImGuiWindowFlags_NoBackground);
|
||||||
|
|
||||||
const auto& txs = state.transactions;
|
const auto& txs = state.transactions;
|
||||||
int count = std::min(4, (int)txs.size()); // show only the 4 most recent (state.transactions is newest-first)
|
float rowH = std::max(18.0f * dp, kRecentTxRowHeight * vs);
|
||||||
|
// Only draw as many rows as fully fit within the reserved section height (header + rows);
|
||||||
|
// dropping the overflow row is fine since "View All" already links to full History.
|
||||||
|
int maxRows = std::max(1, (int)((recentH - headerHeight) / rowH));
|
||||||
|
int count = std::min({4, maxRows, (int)txs.size()}); // show only the most recent (state.transactions is newest-first)
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions_yet"));
|
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions_yet"));
|
||||||
} else {
|
} else {
|
||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||||
ImFont* capFont = Type().caption();
|
ImFont* capFont = Type().caption();
|
||||||
float rowH = std::max(18.0f * dp, kRecentTxRowHeight * vs);
|
|
||||||
float iconSz = std::max(S.drawElement("tabs.balance", "recent-tx-icon-min-size").size,
|
float iconSz = std::max(S.drawElement("tabs.balance", "recent-tx-icon-min-size").size,
|
||||||
S.drawElement("tabs.balance", "recent-tx-icon-size").size * hs);
|
S.drawElement("tabs.balance", "recent-tx-icon-size").size * hs);
|
||||||
|
|
||||||
|
|||||||
@@ -564,12 +564,27 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec)
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line count
|
// Line count — the least-critical trailing element. When the row is too narrow to fit the
|
||||||
ImGui::TextDisabled(TR("console_line_count"), model_.size());
|
// filter box (at its placeholder-sized minimum) AND its trailing controls, drop the line count
|
||||||
|
// rather than starve/hide the filter (worst at 1024px). Mirror the reservation formula in
|
||||||
|
// drawFilterInput(): trailing = 4 frame-height buttons + the group spacers, and the filter's
|
||||||
|
// hard floor = its placeholder width + frame padding.
|
||||||
|
{
|
||||||
|
char lineCountBuf[64];
|
||||||
|
snprintf(lineCountBuf, sizeof(lineCountBuf), TR("console_line_count"), model_.size());
|
||||||
|
float lineCountW = ImGui::CalcTextSize(lineCountBuf).x + Layout::spacingSm() * 2.0f; // text + its trailing spacer
|
||||||
|
float trailingW = ImGui::GetFrameHeight() * 4.0f + Layout::spacingSm() * 7.0f;
|
||||||
|
float filterMinW = ImGui::CalcTextSize(TR("console_filter_hint")).x
|
||||||
|
+ ImGui::GetStyle().FramePadding.x * 2.0f + 8.0f * Layout::dpiScale();
|
||||||
|
bool showLineCount = ImGui::GetContentRegionAvail().x >= lineCountW + trailingW + filterMinW;
|
||||||
|
|
||||||
ImGui::SameLine();
|
if (showLineCount) {
|
||||||
ImGui::Spacing();
|
ImGui::TextDisabled(TR("console_line_count"), model_.size());
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Output filter input
|
// Output filter input
|
||||||
drawFilterInput();
|
drawFilterInput();
|
||||||
@@ -695,6 +710,13 @@ void ConsoleTab::drawFilterInput()
|
|||||||
float filterAvail = ImGui::GetContentRegionAvail().x - trailingBtnSpace;
|
float filterAvail = ImGui::GetContentRegionAvail().x - trailingBtnSpace;
|
||||||
float filterMaxW = schema::UI().drawElement("tabs.console", "filter-max-width").size * Layout::dpiScale();
|
float filterMaxW = schema::UI().drawElement("tabs.console", "filter-max-width").size * Layout::dpiScale();
|
||||||
float filterW = std::min(filterMaxW, filterAvail * schema::UI().drawElement("tabs.console", "filter-width-ratio").size);
|
float filterW = std::min(filterMaxW, filterAvail * schema::UI().drawElement("tabs.console", "filter-width-ratio").size);
|
||||||
|
// Never shrink below the placeholder — otherwise the hint clips to "Filter outp" (or the box
|
||||||
|
// vanishes) at narrow widths. Floor = placeholder text + frame padding + a little breathing room.
|
||||||
|
// (drawToolbar() drops the "NNN lines" count when even this floor won't fit alongside the row's
|
||||||
|
// trailing controls, so this max() doesn't push the zoom/color buttons off-window.)
|
||||||
|
float filterMinW = ImGui::CalcTextSize(TR("console_filter_hint")).x
|
||||||
|
+ ImGui::GetStyle().FramePadding.x * 2.0f + 8.0f * Layout::dpiScale();
|
||||||
|
filterW = std::max(filterMinW, filterW);
|
||||||
ImGui::SetNextItemWidth(filterW);
|
ImGui::SetNextItemWidth(filterW);
|
||||||
ImGui::InputTextWithHint("##ConsoleFilter", TR("console_filter_hint"), filter_text_, sizeof(filter_text_));
|
ImGui::InputTextWithHint("##ConsoleFilter", TR("console_filter_hint"), filter_text_, sizeof(filter_text_));
|
||||||
if (filter_text_[0] != '\0') {
|
if (filter_text_[0] != '\0') {
|
||||||
|
|||||||
@@ -408,7 +408,9 @@ private:
|
|||||||
|
|
||||||
// ---- Below the info card (outside the surface): verify note + install button ----
|
// ---- Below the info card (outside the surface): verify note + install button ----
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x);
|
||||||
Type().textColored(TypeStyle::Caption, downgrade ? Warning() : OnSurfaceMedium(), noteStr);
|
Type().textColored(TypeStyle::Caption, downgrade ? Warning() : OnSurfaceMedium(), noteStr);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
// Install button sized to its text and centered in the pane.
|
// Install button sized to its text and centered in the pane.
|
||||||
const float bw = ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();
|
const float bw = ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();
|
||||||
|
|||||||
@@ -324,20 +324,9 @@ static void RenderAddressDropdown(App* app, float width) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Helpers: timeAgo / DrawRecvIcon (local copies — originals are static in send_tab)
|
// Helpers: DrawRecvIcon (local copy — original is static in send_tab).
|
||||||
|
// Relative time uses the shared util::formatTimeAgoShort ("14d ago"), matching Overview/Send.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
static std::string recvTimeAgo(int64_t timestamp) {
|
|
||||||
if (timestamp <= 0) return "";
|
|
||||||
int64_t now = (int64_t)std::time(nullptr);
|
|
||||||
int64_t diff = now - timestamp;
|
|
||||||
if (diff < 0) diff = 0;
|
|
||||||
char buf[32];
|
|
||||||
if (diff < 60) { snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff); return buf; }
|
|
||||||
if (diff < 3600) { snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60)); return buf; }
|
|
||||||
if (diff < 86400) { snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600)); return buf; }
|
|
||||||
snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400)); return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DrawRecvIcon(ImDrawList* dl, float cx, float cy, float s, ImU32 col) {
|
static void DrawRecvIcon(ImDrawList* dl, float cx, float cy, float s, ImU32 col) {
|
||||||
dl->AddTriangleFilled(
|
dl->AddTriangleFilled(
|
||||||
ImVec2(cx, cy + s),
|
ImVec2(cx, cy + s),
|
||||||
@@ -404,30 +393,37 @@ static void RenderRecentReceived(const AddressInfo& /* addr */,
|
|||||||
rowDL->AddText(capFont, capFont->LegacySize,
|
rowDL->AddText(capFont, capFont->LegacySize,
|
||||||
ImVec2(txX, rowPos.y + 2.0f * dp), OnSurfaceMedium(), typeText);
|
ImVec2(txX, rowPos.y + 2.0f * dp), OnSurfaceMedium(), typeText);
|
||||||
|
|
||||||
// Address (second line)
|
// Address — start it AFTER the measured type-label width (not a fixed offset that shrinks below
|
||||||
float addrX = txX + S.drawElement("tabs.balance", "recent-tx-addr-offset").sizeOr(65.0f) * hs;
|
// the label at narrow widths), mirroring Overview's recent-tx list. The schema offset is a floor.
|
||||||
|
float typeW = capFont->CalcTextSizeA(capFont->LegacySize, 10000.0f, 0.0f, typeText).x;
|
||||||
|
float addrX = txX + std::max(S.drawElement("tabs.balance", "recent-tx-addr-offset").sizeOr(65.0f) * hs,
|
||||||
|
typeW + Layout::spacingSm());
|
||||||
std::string addrDisplay = util::truncateMiddle(tx.address,
|
std::string addrDisplay = util::truncateMiddle(tx.address,
|
||||||
(int)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
|
(int)S.drawElement("tabs.balance", "recent-tx-addr-trunc").sizeOr(20.0f));
|
||||||
rowDL->AddText(capFont, capFont->LegacySize,
|
rowDL->AddText(capFont, capFont->LegacySize,
|
||||||
ImVec2(addrX, rowPos.y + 2.0f * dp), OnSurfaceDisabled(), addrDisplay.c_str());
|
ImVec2(addrX, rowPos.y + 2.0f * dp), OnSurfaceDisabled(), addrDisplay.c_str());
|
||||||
|
|
||||||
// Amount (right-aligned, first line)
|
// Time ago — short "14d ago" form (shared helper, matches Overview/Send). Measure it
|
||||||
|
// first so the amount can chain its right edge off this width and never overlap.
|
||||||
|
std::string ago = util::formatTimeAgoShort(tx.timestamp);
|
||||||
|
ImVec2 agoSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000.0f, 0.0f, ago.c_str());
|
||||||
|
float rightEdge = rowPos.x + ImGui::GetContentRegionAvail().x;
|
||||||
|
float agoMargin = S.drawElement("tabs.balance", "recent-tx-time-margin").sizeOr(4.0f) * hs;
|
||||||
|
rowDL->AddText(capFont, capFont->LegacySize,
|
||||||
|
ImVec2(rightEdge - agoSz.x - agoMargin, rowPos.y + 2.0f * dp),
|
||||||
|
OnSurfaceDisabled(), ago.c_str());
|
||||||
|
|
||||||
|
// Amount (right-aligned, first line) — anchored to the LEFT of the time-ago text
|
||||||
|
// (measured width + a gap) so the two columns can never collide, whatever the strings.
|
||||||
snprintf(buf, sizeof(buf), "+%.4f %s", std::abs(tx.amount), DRAGONX_TICKER);
|
snprintf(buf, sizeof(buf), "+%.4f %s", std::abs(tx.amount), DRAGONX_TICKER);
|
||||||
ImVec2 amtSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000.0f, 0.0f, buf);
|
ImVec2 amtSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000.0f, 0.0f, buf);
|
||||||
float rightEdge = rowPos.x + ImGui::GetContentRegionAvail().x;
|
float amtGap = std::max(S.drawElement("tabs.balance", "amount-right-min-margin").size,
|
||||||
float amtX = rightEdge - amtSz.x - std::max(S.drawElement("tabs.balance", "amount-right-min-margin").size,
|
S.drawElement("tabs.balance", "amount-right-margin").size * hs);
|
||||||
S.drawElement("tabs.balance", "amount-right-margin").size * hs);
|
float amtRightEdge = rightEdge - agoSz.x - agoMargin - amtGap;
|
||||||
|
float amtX = amtRightEdge - amtSz.x;
|
||||||
rowDL->AddText(capFont, capFont->LegacySize,
|
rowDL->AddText(capFont, capFont->LegacySize,
|
||||||
ImVec2(amtX, rowPos.y + 2.0f * dp), recvCol, buf);
|
ImVec2(amtX, rowPos.y + 2.0f * dp), recvCol, buf);
|
||||||
|
|
||||||
// Time ago
|
|
||||||
std::string ago = recvTimeAgo(tx.timestamp);
|
|
||||||
ImVec2 agoSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000.0f, 0.0f, ago.c_str());
|
|
||||||
rowDL->AddText(capFont, capFont->LegacySize,
|
|
||||||
ImVec2(rightEdge - agoSz.x - S.drawElement("tabs.balance", "recent-tx-time-margin").sizeOr(4.0f) * hs,
|
|
||||||
rowPos.y + 2.0f * dp),
|
|
||||||
OnSurfaceDisabled(), ago.c_str());
|
|
||||||
|
|
||||||
// Clickable row — hover highlight + navigate to History
|
// Clickable row — hover highlight + navigate to History
|
||||||
float rowW = ImGui::GetContentRegionAvail().x;
|
float rowW = ImGui::GetContentRegionAvail().x;
|
||||||
ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH);
|
ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "../notifications.h"
|
#include "../notifications.h"
|
||||||
#include "../schema/ui_schema.h"
|
#include "../schema/ui_schema.h"
|
||||||
#include "../widgets/qr_code.h"
|
#include "../widgets/qr_code.h"
|
||||||
|
#include "../widgets/copy_field.h"
|
||||||
#include "../material/draw_helpers.h"
|
#include "../material/draw_helpers.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
@@ -198,33 +199,58 @@ void RequestPaymentDialog::render(App* app)
|
|||||||
|
|
||||||
// Payment URI display
|
// Payment URI display
|
||||||
if (!s_payment_uri.empty()) {
|
if (!s_payment_uri.empty()) {
|
||||||
// Use a selectable text area for the URI
|
ImGui::Text("%s", TR("request_payment_uri"));
|
||||||
char uri_buf[1024];
|
// Bordered read-only field: bounds the raw drgx: URI within a box (it horizontal-scrolls if
|
||||||
strncpy(uri_buf, s_payment_uri.c_str(), sizeof(uri_buf) - 1);
|
// long) rather than 4-char-chunking it like an address or letting a plain field overflow.
|
||||||
material::LabeledInput(TR("request_payment_uri"), "##URI", uri_buf, sizeof(uri_buf),
|
// The "Copy URI" button below copies the full value.
|
||||||
-1.0f, nullptr, ImGuiInputTextFlags_ReadOnly);
|
{
|
||||||
|
char uriBuf[2048];
|
||||||
|
snprintf(uriBuf, sizeof(uriBuf), "%s", s_payment_uri.c_str());
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
ImGui::InputText("##URI", uriBuf, sizeof(uriBuf), ImGuiInputTextFlags_ReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Copy button
|
// Footer buttons: size each to ITS OWN label (action-button.width is a floor, not the size)
|
||||||
if (material::TactileButton(TR("request_copy_uri"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
// so "Copy Full Address" never clips its final letter — mirrors qr_popup_dialog's per-label
|
||||||
|
// sizing. Font metrics are already DPI-scaled, so don't multiply by dpiScale() here.
|
||||||
|
ImFont* btnFont = S.resolveFont(actionBtn.font);
|
||||||
|
ImGui::PushFont(btnFont);
|
||||||
|
const float btnPad = ImGui::GetStyle().FramePadding.x * 2.0f + 12.0f; // small margin
|
||||||
|
float w_uri = ImGui::CalcTextSize(TR("request_copy_uri")).x + btnPad;
|
||||||
|
float w_addr = ImGui::CalcTextSize(TR("copy_address")).x + btnPad;
|
||||||
|
ImGui::PopFont();
|
||||||
|
if (w_uri < actionBtn.width) w_uri = actionBtn.width;
|
||||||
|
if (w_addr < actionBtn.width) w_addr = actionBtn.width;
|
||||||
|
|
||||||
|
// Copy URI button
|
||||||
|
if (material::TactileButton(TR("request_copy_uri"), ImVec2(w_uri, 0), btnFont)) {
|
||||||
ImGui::SetClipboardText(s_payment_uri.c_str());
|
ImGui::SetClipboardText(s_payment_uri.c_str());
|
||||||
Notifications::instance().success(TR("request_uri_copied"));
|
Notifications::instance().success(TR("request_uri_copied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
if (material::TactileButton(TR("copy_address"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
if (material::TactileButton(TR("copy_address"), ImVec2(w_addr, 0), btnFont)) {
|
||||||
ImGui::SetClipboardText(s_address);
|
ImGui::SetClipboardText(s_address);
|
||||||
Notifications::instance().success(TR("address_copied"));
|
Notifications::instance().success(TR("address_copied"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Close button
|
// Close button — sized to its own label with the same floor.
|
||||||
if (material::TactileButton(TR("close"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
{
|
||||||
s_open = false;
|
ImFont* btnFont = S.resolveFont(actionBtn.font);
|
||||||
|
ImGui::PushFont(btnFont);
|
||||||
|
const float btnPad = ImGui::GetStyle().FramePadding.x * 2.0f + 12.0f;
|
||||||
|
float w_close = ImGui::CalcTextSize(TR("close")).x + btnPad;
|
||||||
|
ImGui::PopFont();
|
||||||
|
if (w_close < actionBtn.width) w_close = actionBtn.width;
|
||||||
|
if (material::TactileButton(TR("close"), ImVec2(w_close, 0), btnFont)) {
|
||||||
|
s_open = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
material::EndOverlayDialog();
|
material::EndOverlayDialog();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,7 +368,9 @@ private:
|
|||||||
|
|
||||||
// ---- Below the info card: verify / stop-mining note + install button (centered, text-fit) ----
|
// ---- Below the info card: verify / stop-mining note + install button (centered, text-fit) ----
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x);
|
||||||
Type().textColored(TypeStyle::Caption, (mining || downgrade) ? Warning() : OnSurfaceMedium(), noteStr);
|
Type().textColored(TypeStyle::Caption, (mining || downgrade) ? Warning() : OnSurfaceMedium(), noteStr);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
const float bw = ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();
|
const float bw = ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, (ImGui::GetContentRegionAvail().x - bw) * 0.5f));
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, (ImGui::GetContentRegionAvail().x - bw) * 0.5f));
|
||||||
|
|||||||
Reference in New Issue
Block a user