From 42b1ae1d6813e679a8ed38b703a8d6e3e481c98f Mon Sep 17 00:00:00 2001 From: Lee Clagett Date: Mon, 19 May 2025 18:15:50 -0400 Subject: [PATCH] Update UI (mouse) handling --- src/components/table.cpp | 80 ++++++++++++++++++++++++++++++++++++---- src/events.h | 7 ++++ src/views/accounts.cpp | 18 +++++---- src/views/history.cpp | 27 +++----------- src/views/send.cpp | 4 +- src/views/wallet.cpp | 2 +- 6 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/components/table.cpp b/src/components/table.cpp index afdd910..fb37e07 100644 --- a/src/components/table.cpp +++ b/src/components/table.cpp @@ -43,8 +43,12 @@ namespace lwcli { namespace component const table_generator generator_; const table_on_key key_; const std::vector> title_; + std::vector> boxes_; + ftxui::Box box_; std::ptrdiff_t rows_; std::ptrdiff_t selected_; + std::ptrdiff_t highlighted_; + const std::size_t columns_; static constexpr std::ptrdiff_t min_row() noexcept { return 0; }; bool Focusable() const override final { return true; } @@ -67,8 +71,12 @@ namespace lwcli { namespace component generator_(std::move(generator)), key_(std::move(key)), title_(get_title_bar(std::move(title))), + boxes_(), + box_(), rows_(0), - selected_(-1) + selected_(-1), + highlighted_(-1), + columns_(title_.empty() ? 0 : title_.at(0).size()) { set_size(generator_().size()); } @@ -88,12 +96,43 @@ namespace lwcli { namespace component bool OnEvent(ftxui::Event event) override final { - if (event.is_mouse()) + const auto original = selected_; + if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) { if (event.mouse().button == ftxui::Mouse::WheelDown && can_increment()) + { ++selected_; + if (!Focused()) + TakeFocus(); + } else if (event.mouse().button == ftxui::Mouse::WheelUp && can_decrement()) + { --selected_;// = std::max(min_row(), selected_ - 1); + if (!Focused()) + TakeFocus(); + } + else + { + highlighted_ = -1; + const auto x = event.mouse().x; + const auto y = event.mouse().y; + + const auto match = std::lower_bound(boxes_.begin(), boxes_.end(), y, [] (const auto& lhs, const auto rhs) { + return std::get<0>(lhs).y_min < rhs; + }); + + if (match != boxes_.end() && ftxui::Box::Union(std::get<0>(*match), std::get<1>(*match)).Contain(x, y)) + { + const std::size_t i = match - boxes_.begin(); + highlighted_ = i; + if (key_(event, i)) + { + selected_ = i; + TakeFocus(); + } + return true; + } + } } else if (event == ftxui::Event::ArrowDown && can_increment()) ++selected_; @@ -106,6 +145,8 @@ namespace lwcli { namespace component else if (min_row() <= selected_ && selected_ < rows_) return key_(std::move(event), std::size_t(selected_)); + if (original != selected_) + highlighted_ = -1; return min_row() <= selected_ && selected_ - min_row() < rows_; } @@ -123,7 +164,22 @@ namespace lwcli { namespace component auto title = table.SelectRow(0); title.Decorate(ftxui::bold); title.SeparatorVertical(ftxui::LIGHT); - //title.Border(ftxui::LIGHT); + } + + if (columns_) + { + const std::size_t offset = title_.size(); + boxes_.resize(rows_); + for (std::size_t i = offset; i < rows_ + offset; ++i) + { + auto& boxes = boxes_.at(i - offset); + + auto cell1 = table.SelectCell(0, i); + cell1.DecorateCells(ftxui::reflect(std::get<0>(boxes))); + + auto cell2 = table.SelectCell(columns_ - 1, i); + cell2.DecorateCells(ftxui::reflect(std::get<1>(boxes))); + } } if (Focused()) @@ -132,16 +188,24 @@ namespace lwcli { namespace component selected_ = min_row(); else if (rows_ <= selected_ - min_row()) selected_ = rows_ - 1 + min_row(); - } - if (min_row() <= selected_ && selected_ < rows_) + if (min_row() <= selected_ && selected_ < rows_) + { + auto row = table.SelectRow(std::size_t(selected_) + title_.size()); + row.Decorate(ftxui::inverted); + row.Decorate(ftxui::focus); + } + } + else + selected_ = -1; + + if (selected_ != highlighted_ && min_row() <= highlighted_ && highlighted_ < rows_) { - auto row = table.SelectRow(std::size_t(selected_) + title_.size()); + auto row = table.SelectRow(std::size_t(highlighted_) + title_.size()); row.Decorate(ftxui::inverted); - row.Decorate(ftxui::focus); } - return table.Render(); + return table.Render() | ftxui::reflect(box_); } }; } // anonymous diff --git a/src/events.h b/src/events.h index 30a7e45..8fe7d59 100644 --- a/src/events.h +++ b/src/events.h @@ -47,4 +47,11 @@ namespace lwcli { namespace event extern const ftxui::Event lock_wallet; extern const ftxui::Event refresh_wallet; extern const ftxui::Event send_async; + + inline bool is_left_click(ftxui::Event& e) noexcept + { return e.is_mouse() && e.mouse().button == ftxui::Mouse::Left && e.mouse().motion == ftxui::Mouse::Pressed; } + + inline bool is_right_click(ftxui::Event& e) noexcept + { return e.is_mouse() && e.mouse().button == ftxui::Mouse::Right && e.mouse().motion == ftxui::Mouse::Pressed; } + }} // lwcli // event diff --git a/src/views/accounts.cpp b/src/views/accounts.cpp index 7827e67..c80c06d 100644 --- a/src/views/accounts.cpp +++ b/src/views/accounts.cpp @@ -221,10 +221,10 @@ namespace lwcli { namespace view Add(ui_); } - bool display_details(const ftxui::Event& e, const std::size_t index) + bool display_details(ftxui::Event& e, const std::size_t index) { - if (e != ftxui::Event::Return) - return true; + if (details_ || (e != ftxui::Event::Return && !event::is_left_click(e))) + return false; const std::size_t minor = row_map_.at(index); if (std::numeric_limits::max() < minor) @@ -355,18 +355,22 @@ namespace lwcli { namespace view Add(ui_); } - bool display_details(const ftxui::Event& e, const std::size_t index) + bool display_details(ftxui::Event& e, const std::size_t index) { - if (e == ftxui::Event::Return) + if (!details_ && (e == ftxui::Event::Return || event::is_left_click(e))) { if (details_) details_->Detach(); details_ = std::make_shared(wal_, wal_->subaddress(), row_map_.at(index)); Add(details_); + return true; } - else if (e == ftxui::Event::l || e == ftxui::Event::L) + else if (e == ftxui::Event::l || e == ftxui::Event::L || event::is_right_click(e)) + { *account_ = row_map_.at(index); - return true; + return true; + } + return false; } bool OnEvent(ftxui::Event event) override final diff --git a/src/views/history.cpp b/src/views/history.cpp index bece3cd..8d968d3 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -199,7 +199,6 @@ namespace lwcli { namespace view ftxui::Element title_; ftxui::Element table_cached_; //!< Does not redraw when child is enabled std::unordered_map row_map_; - std::uint64_t balance_; const std::uint32_t account_; bool Focusable() const override final { return true; } @@ -241,16 +240,15 @@ namespace lwcli { namespace view Add(table_); } - bool add_overlay(const ftxui::Event& e, const std::size_t i) + bool add_overlay(ftxui::Event& e, const std::size_t i) { - if (e == ftxui::Event::Return) + if (!overlay_ && (e == ftxui::Event::Return || event::is_left_click(e))) { - if (overlay_) - overlay_->Detach(); overlay_ = std::make_shared(wallet_->history(), row_map_.at(i)); Add(overlay_); + return true; } - return true; + return false; } bool OnEvent(ftxui::Event event) override final @@ -298,7 +296,6 @@ namespace lwcli { namespace view ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx, i); } - balance_ = 0; std::vector> rows; rows.reserve(ordered.size() + 2); @@ -312,19 +309,7 @@ namespace lwcli { namespace view const std::uint64_t amount = tx->amount(); const int direction = tx->direction(); const std::uint64_t fee = tx->fee(); - - - if (!tx->isFailed()) - { - if (direction == Monero::TransactionInfo::Direction_Out) - { - balance_ -= amount; - balance_ -= fee; - } - else - balance_ += amount; - } - + std::tm expanded{}; const std::time_t timestamp = tx->timestamp(); if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded))) @@ -376,7 +361,7 @@ namespace lwcli { namespace view auto table = table_->Render(); // compute balance first in callback table_cached_ = ftxui::vbox({ title_, - ftxui::text(_("Balance: ") + lwsf::displayAmount(balance_)), + ftxui::text(_("Balance: ") + lwsf::displayAmount(wallet_->balance(account_))), std::move(table) | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex }); return table_cached_; diff --git a/src/views/send.cpp b/src/views/send.cpp index 17d0787..4f3e765 100644 --- a/src/views/send.cpp +++ b/src/views/send.cpp @@ -233,6 +233,7 @@ namespace lwcli { namespace view const ftxui::Element title_; std::vector> dests_; std::vector dests_ui_; + const ftxui::Decorator min_amount_size; ftxui::Component overlay_; ftxui::Component buttons_; ftxui::Element error_; @@ -260,6 +261,7 @@ namespace lwcli { namespace view title_(ftxui::text(_("Send from account #") + std::to_string(account) + " (" + lwsf::displayAmount(wal_->unlockedBalance(account)) + " XMR available)")), dests_(), dests_ui_(), + min_amount_size(ftxui::size(ftxui::WIDTH, ftxui::GREATER_THAN, 5)), overlay_(), buttons_(), error_(), @@ -498,7 +500,7 @@ namespace lwcli { namespace view { ftxui::Elements row; row.reserve(6); - row.push_back(std::get<0>(e)->Render()); + row.push_back(std::get<0>(e)->Render() | min_amount_size); row.push_back(ftxui::text(" XMR to ")); row.push_back(std::get<1>(e)->Render()); if (!animate) diff --git a/src/views/wallet.cpp b/src/views/wallet.cpp index 803020e..f935784 100644 --- a/src/views/wallet.cpp +++ b/src/views/wallet.cpp @@ -155,7 +155,7 @@ namespace lwcli { namespace view else if (state_.overlay) state_.overlay->OnEvent(std::move(event)); else if (event == ftxui::Event::CtrlQ) - throw event::close{}; + return history_->OnEvent(std::move(event)); else if (!ui_->OnEvent(event)) { if (event == ftxui::Event::s || event == ftxui::Event::S)