7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-02 00:26:45 +00:00

Pcbnew: fix snapping view update after hover delay

Te update didn't refresh, so the snap extensions sometimes only
appeared after the _next_ mouse move, which was annoying when the
mouse was very still (e.g. when deliberately hovering over an item).

Use a wxTimer to put all the callbacks in the UI thread which is also
much simpler. Also the update function needs to call a canvas refresh
as well as the item update.
This commit is contained in:
John Beard 2025-01-28 07:51:56 +08:00
parent 28a4ca2d80
commit d90862b444
2 changed files with 52 additions and 80 deletions

View File

@ -24,19 +24,21 @@
#include "tool/construction_manager.h"
#include <chrono>
#include <condition_variable>
#include <thread>
#include <wx/timer.h>
#include <advanced_config.h>
#include <hash.h>
/**
* A helper class to manage the activation of a "proposal" after a timeout.
*
* When a proposal is made, a timer starts. If no new proposal is made and the proposal
* is not canceled before the timer expires, the proposal is "accepted" via a callback.
*
* Propos
* Proposals are "tagged" with a hash - this is used to avoid reproposing the same thing
* multiple times.
*
* @tparam T The type of the proposal, which will be passed to the callback (by value)
*/
@ -47,29 +49,15 @@ public:
using ACTIVATION_CALLBACK = std::function<void( T&& )>;
ACTIVATION_HELPER( std::chrono::milliseconds aTimeout, ACTIVATION_CALLBACK aCallback ) :
m_timeout( aTimeout ), m_callback( std::move( aCallback ) ), m_stop( false ),
m_thread( &ACTIVATION_HELPER::ProposalCheckFunction, this )
m_timeout( aTimeout ),
m_callback( std::move( aCallback ) )
{
}
~ACTIVATION_HELPER()
{
// Stop the delay thread and wait for it
{
std::lock_guard<std::mutex> lock( m_mutex );
m_stop = true;
m_cv.notify_all();
}
if( m_thread.joinable() )
{
m_thread.join();
}
m_timer.Bind( wxEVT_TIMER, &ACTIVATION_HELPER::onTimerExpiry, this );
}
void ProposeActivation( T&& aProposal, std::size_t aProposalTag, bool aAcceptImmediately )
{
std::lock_guard<std::mutex> lock( m_mutex );
std::unique_lock<std::mutex> lock( m_mutex );
if( m_lastAcceptedProposalTag.has_value() && aProposalTag == *m_lastAcceptedProposalTag )
{
@ -86,75 +74,59 @@ public:
m_pendingProposalTag = aProposalTag;
m_lastProposal = std::move( aProposal );
m_proposalDeadline = std::chrono::steady_clock::now();
if( !aAcceptImmediately )
m_proposalDeadline += m_timeout;
m_cv.notify_all();
if( aAcceptImmediately )
{
// Synchonously accept the proposal
lock.unlock();
acceptPendingProposal();
}
else
{
m_timer.Start( m_timeout.count(), wxTIMER_ONE_SHOT );
}
}
void CancelProposal()
{
std::lock_guard<std::mutex> lock( m_mutex );
m_pendingProposalTag.reset();
m_cv.notify_all();
}
void ProposalCheckFunction()
{
while( !m_stop )
{
std::unique_lock<std::mutex> lock( m_mutex );
if( !m_stop && !m_pendingProposalTag.has_value() )
{
// No active proposal - wait for one (unlocks while waiting)
m_cv.wait( lock );
}
if( !m_stop && m_pendingProposalTag.has_value() )
{
// Active proposal - wait for timeout
auto now = std::chrono::steady_clock::now();
if( m_cv.wait_for( lock, m_proposalDeadline - now ) == std::cv_status::timeout )
{
// See if the timeout was extended for a new proposal
now = std::chrono::steady_clock::now();
if( now < m_proposalDeadline )
{
// Extended - wait for the new deadline
continue;
}
// See if there is still a proposal to accept
// (could have been canceled in the meantime)
if( m_pendingProposalTag )
{
m_lastAcceptedProposalTag = m_pendingProposalTag;
m_pendingProposalTag.reset();
T proposalToAccept = std::move( m_lastProposal );
lock.unlock();
// Call the callback (outside the lock)
m_callback( std::move( proposalToAccept ) );
}
}
}
}
m_timer.Stop();
}
private:
/**
* Timer expiry callback in the UI thread.
*/
void onTimerExpiry( wxTimerEvent& aEvent )
{
acceptPendingProposal();
}
void acceptPendingProposal()
{
std::unique_lock<std::mutex> lock( m_mutex );
if( m_pendingProposalTag )
{
m_lastAcceptedProposalTag = m_pendingProposalTag;
m_pendingProposalTag.reset();
// Move out from the locked variable
T proposalToAccept = std::move( m_lastProposal );
lock.unlock();
// Call the callback (outside the lock)
// This is all in the UI thread now, so it won't be concurrent
m_callback( std::move( proposalToAccept ) );
}
}
mutable std::mutex m_mutex;
/// Activation timeout in milliseconds.
std::chrono::milliseconds m_timeout;
std::chrono::time_point<std::chrono::steady_clock> m_proposalDeadline;
/// The last proposal tag that was made.
std::optional<std::size_t> m_pendingProposalTag;
@ -165,12 +137,9 @@ private:
T m_lastProposal;
/// Callback to call when the proposal is accepted.
ACTIVATION_CALLBACK m_callback;
std::condition_variable m_cv;
std::atomic<bool> m_stop;
ACTIVATION_CALLBACK m_callback;
/// The thread must be constructed last, as it starts running immediately.
std::thread m_thread;
wxTimer m_timer;
};

View File

@ -32,6 +32,7 @@
#include <math/vector2d.h>
#include <render_settings.h>
#include <tool/tool_manager.h>
#include <tool/tools_holder.h>
#include <view/view.h>
#include <settings/app_settings.h>
@ -68,6 +69,8 @@ GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
{
view->SetVisible( &m_constructionGeomPreview, aAnythingShown );
}
m_toolMgr->GetToolHolder()->RefreshCanvas();
} );
}