@ -9,6 +9,7 @@
# include "gui/PageView.h"
# include "gui/XournalView.h"
# include "gui/XournalppCursor.h"
# include "gui/widgets/XournalWidget.h"
# include "model/Document.h"
# include "model/Element.h"
# include "model/Layer.h"
@ -37,6 +38,9 @@ constexpr size_t MINPIXSIZE = 5;
constexpr int DELETE_PADDING = 20 ;
constexpr int ROTATE_PADDING = 8 ;
/// Number of times to trigger edge pan timer per second
constexpr unsigned int PAN_TIMER_RATE = 30 ;
EditSelection : : EditSelection ( UndoRedoHandler * undo , const PageRef & page , XojPageView * view ) :
snappingHandler ( view - > getXournal ( ) - > getControl ( ) - > getSettings ( ) ) ,
x ( 0 ) ,
@ -162,6 +166,11 @@ EditSelection::~EditSelection() {
this - > view = nullptr ;
this - > undo = nullptr ;
if ( this - > edgePanHandler ) {
g_source_destroy ( this - > edgePanHandler ) ;
g_source_unref ( this - > edgePanHandler ) ;
}
}
/**
@ -408,7 +417,13 @@ void EditSelection::mouseUp() {
this - > view , this - > undo , this - > mouseDownType ) ;
this - > mouseDownType = CURSOR_SELECTION_NONE ;
const bool wasEdgePanning = this - > isEdgePanning ( ) ;
this - > setEdgePan ( false ) ;
updateMatrix ( ) ;
if ( wasEdgePanning ) {
this - > ensureWithinVisibleArea ( ) ;
}
}
/**
@ -469,7 +484,12 @@ void EditSelection::mouseMove(double mouseX, double mouseY, bool alt) {
p = snappingHandler . snapToGrid ( p , alt ) ;
// move
moveSelection ( p . x - cx , p . y - cy ) ;
if ( ! this - > edgePanInhibitNext ) {
moveSelection ( p . x - cx , p . y - cy ) ;
this - > setEdgePan ( true ) ;
} else {
this - > edgePanInhibitNext = false ;
}
} else if ( this - > mouseDownType = = CURSOR_SELECTION_ROTATE & & supportRotation ) { // catch rotation here
double rdx = mouseX / zoom - this - > snappedBounds . x - this - > snappedBounds . width / 2 ;
double rdy = mouseY / zoom - this - > snappedBounds . y - this - > snappedBounds . height / 2 ;
@ -692,13 +712,103 @@ void EditSelection::moveSelection(double dx, double dy) {
this - > snappedBounds . x + = dx ;
this - > snappedBounds . y + = dy ;
ensureWithinVisibleArea ( ) ;
updateMatrix ( ) ;
this - > view - > getXournal ( ) - > repaintSelection ( ) ;
}
void EditSelection : : setEdgePan ( bool pan ) {
if ( pan & & ! this - > edgePanHandler ) {
this - > edgePanHandler = g_timeout_source_new ( 1000 / PAN_TIMER_RATE ) ;
g_source_set_callback ( this - > edgePanHandler , reinterpret_cast < GSourceFunc > ( EditSelection : : handleEdgePan ) , this ,
nullptr ) ;
g_source_attach ( this - > edgePanHandler , nullptr ) ;
} else if ( ! pan & & this - > edgePanHandler ) {
g_source_unref ( this - > edgePanHandler ) ;
this - > edgePanHandler = nullptr ;
this - > edgePanInhibitNext = false ;
}
}
bool EditSelection : : isEdgePanning ( ) const { return this - > edgePanHandler ; }
bool EditSelection : : handleEdgePan ( EditSelection * self ) {
if ( self - > view - > getXournal ( ) - > getControl ( ) - > getZoomControl ( ) - > isZoomPresentationMode ( ) ) {
self - > setEdgePan ( false ) ;
return false ;
}
Layout * layout = gtk_xournal_get_layout ( self - > view - > getXournal ( ) - > getWidget ( ) ) ;
const double zoom = self - > view - > getXournal ( ) - > getZoom ( ) ;
// Helper function to compute scroll amount for a single dimension, based on visible region and selection bbox
const auto computeScrollAmt = [ & ] ( double visMin , double visLen , double bboxMin , double bboxLen ,
double layoutSize ) - > double {
const bool belowMin = bboxMin < visMin ;
const bool aboveMax = bboxMin + bboxLen > visMin + visLen ;
const double visMax = visMin + visLen ;
const double bboxMax = bboxMin + bboxLen ;
// Scroll amount multiplier
double mult = 0.0 ;
// Calculate bonus scroll amount due to proportion of selection out of view.
const double maxMult = 5.0 ;
int panDir = 0 ;
if ( aboveMax ) {
panDir = 1 ;
mult = maxMult * std : : min ( bboxLen , bboxMax - visMax ) / bboxLen ;
} else if ( belowMin ) {
panDir = - 1 ;
mult = maxMult * std : : min ( bboxLen , visMin - bboxMin ) / bboxLen ;
}
// Base amount to translate selection (in document coordinates) per timer tick
const double panSpeed = 20.0 ;
const double translateAmt = visLen * panSpeed / ( 100.0 * PAN_TIMER_RATE ) ;
// Amount to scroll the visible area by (in layout coordinates), accounting for multiplier
double layoutScroll = zoom * panDir * ( translateAmt * mult ) ;
// If scrolling past layout boundaries, clamp scroll amount to boundary
if ( visMin + layoutScroll < 0 ) {
layoutScroll = - visMin ;
} else if ( visMax + layoutScroll > layoutSize ) {
layoutScroll = std : : max ( 0.0 , layoutSize - visMax ) ;
}
return layoutScroll ;
} ;
// Compute scroll (for layout) and translation (for selection) for x and y
const int layoutWidth = layout - > getMinimalWidth ( ) ;
const int layoutHeight = layout - > getMinimalHeight ( ) ;
const auto visRect = layout - > getVisibleRect ( ) ;
const auto bbox = self - > getBoundingBoxInView ( ) ;
const auto layoutScrollX = computeScrollAmt ( visRect . x , visRect . width , bbox . x , bbox . width , layoutWidth ) ;
const auto layoutScrollY = computeScrollAmt ( visRect . y , visRect . height , bbox . y , bbox . height , layoutHeight ) ;
const auto translateX = layoutScrollX / zoom ;
const auto translateY = layoutScrollY / zoom ;
// Perform the scrolling
bool edgePanned = false ;
if ( self - > isMoving ( ) & & ( layoutScrollX ! = 0.0 | | layoutScrollY ! = 0.0 ) ) {
layout - > scrollRelative ( layoutScrollX , layoutScrollY ) ;
self - > moveSelection ( translateX , translateY ) ;
edgePanned = true ;
// To prevent the selection from jumping and to reduce jitter, block the selection movement triggered by user
// input
self - > edgePanInhibitNext = true ;
} else {
// No panning, so disable the timer.
self - > setEdgePan ( false ) ;
}
return edgePanned ;
}
auto EditSelection : : getBoundingBoxInView ( ) const - > Rectangle < double > {
int viewx = this - > view - > getX ( ) ;
int viewy = this - > view - > getY ( ) ;