@@ -2062,6 +2062,40 @@ def set_handle_props(self, **handle_props):
20622062 self .update ()
20632063 self ._handle_props .update (handle_props )
20642064
2065+ @property
2066+ def default_state (self ):
2067+ """
2068+ Default state of the selector, which affect the widget's behavior. See
2069+ the `state_modifier_keys` parameters for details.
2070+ """
2071+ return tuple (self ._default_state )
2072+
2073+ def add_default_state (self , value ):
2074+ """
2075+ Add a default state to define the widget's behavior. See the
2076+ `state_modifier_keys` parameters for details.
2077+
2078+ Parameters
2079+ ----------
2080+ value : str
2081+ Must be a supported state of the selector. See the
2082+ `state_modifier_keys` parameters for details.
2083+
2084+ Raises
2085+ ------
2086+ ValueError
2087+ When the value is not supported by the selector.
2088+
2089+ """
2090+ supported_default_state = [
2091+ key for key , value in self .state_modifier_keys .items ()
2092+ if key != 'clear' and value != 'not-applicable'
2093+ ]
2094+ if value not in supported_default_state :
2095+ keys = ', ' .join (supported_default_state )
2096+ raise ValueError ('Setting default state must be one of the '
2097+ f'following: { keys } .' )
2098+ self ._default_state .add (value )
20652099
20662100class SpanSelector (_SelectorWidget ):
20672101 """
@@ -2129,6 +2163,12 @@ def on_select(min: float, max: float) -> Any
21292163 Distance in pixels within which the interactive tool handles can be
21302164 activated.
21312165
2166+ state_modifier_keys : dict, optional
2167+ Keyboard modifiers which affect the widget's behavior. Values
2168+ amend the defaults.
2169+
2170+ - "clear": Clear the current shape, default: "escape".
2171+
21322172 drag_from_anywhere : bool, default: False
21332173 If `True`, the widget can be moved by clicking anywhere within
21342174 its bounds.
@@ -2157,9 +2197,15 @@ def on_select(min: float, max: float) -> Any
21572197 def __init__ (self , ax , onselect , direction , minspan = 0 , useblit = False ,
21582198 props = None , onmove_callback = None , interactive = False ,
21592199 button = None , handle_props = None , grab_range = 10 ,
2160- drag_from_anywhere = False , ignore_event_outside = False ):
2200+ state_modifier_keys = None , drag_from_anywhere = False ,
2201+ ignore_event_outside = False ):
21612202
2162- super ().__init__ (ax , onselect , useblit = useblit , button = button )
2203+ if state_modifier_keys is None :
2204+ state_modifier_keys = dict (clear = 'escape' ,
2205+ square = 'not-applicable' ,
2206+ center = 'not-applicable' )
2207+ super ().__init__ (ax , onselect , useblit = useblit , button = button ,
2208+ state_modifier_keys = state_modifier_keys )
21632209
21642210 if props is None :
21652211 props = dict (facecolor = 'red' , alpha = 0.5 )
@@ -2446,7 +2492,7 @@ def _set_active_handle(self, event):
24462492
24472493 # Prioritise center handle over other handles
24482494 # Use 'C' to match the notation used in the RectangleSelector
2449- if 'move' in self ._state :
2495+ if 'move' in self ._state | self . _default_state :
24502496 self ._active_handle = 'C'
24512497 elif e_dist > self .grab_range :
24522498 # Not close to any handles
@@ -2730,8 +2776,7 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent)
27302776 - "move": Move the existing shape, default: no modifier.
27312777 - "clear": Clear the current shape, default: "escape".
27322778 - "square": Make the shape square, default: "shift".
2733- - "center": Make the initial point the center of the shape,
2734- default: "ctrl".
2779+ - "center": change the shape around its center, default: "ctrl".
27352780
27362781 "square" and "center" can be combined.
27372782
@@ -2893,7 +2938,6 @@ def _press(self, event):
28932938 # button, ...
28942939 if self ._interactive and self ._selection_artist .get_visible ():
28952940 self ._set_active_handle (event )
2896- self ._extents_on_press = self .extents
28972941 else :
28982942 self ._active_handle = None
28992943
@@ -2910,6 +2954,8 @@ def _press(self, event):
29102954 else :
29112955 self .set_visible (True )
29122956
2957+ self ._extents_on_press = self .extents
2958+
29132959 return False
29142960
29152961 def _release (self , event ):
@@ -3027,9 +3073,7 @@ def _onmove(self, event):
30273073 y1 = event .ydata
30283074
30293075 # move existing shape
3030- elif (self ._active_handle == 'C' or
3031- (self .drag_from_anywhere and self ._contains (event )) and
3032- self ._extents_on_press is not None ):
3076+ elif self ._active_handle == 'C' :
30333077 x0 , x1 , y0 , y1 = self ._extents_on_press
30343078 dx = event .xdata - self ._eventpress .xdata
30353079 dy = event .ydata - self ._eventpress .ydata
@@ -3164,14 +3208,13 @@ def _set_active_handle(self, event):
31643208 e_idx , e_dist = self ._edge_handles .closest (event .x , event .y )
31653209 m_idx , m_dist = self ._center_handle .closest (event .x , event .y )
31663210
3167- if 'move' in self ._state :
3211+ if 'move' in self ._state | self . _default_state :
31683212 self ._active_handle = 'C'
31693213 # Set active handle as closest handle, if mouse click is close enough.
31703214 elif m_dist < self .grab_range * 2 :
31713215 # Prioritise center handle over other handles
31723216 self ._active_handle = 'C'
3173- elif (c_dist > self .grab_range and
3174- e_dist > self .grab_range ):
3217+ elif c_dist > self .grab_range and e_dist > self .grab_range :
31753218 # Not close to any handles
31763219 if self .drag_from_anywhere and self ._contains (event ):
31773220 # Check if we've clicked inside the region
0 commit comments