<div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000">
                                        
                                        
                                            
                                        
                                        
                                        Hmm... so you want to move any delta aggregation into the VM but keep delta-to-event mapping inside the image. In a perfect world, this would be the way to go. But the use of "Sensor peekEvent" is really, really, really unfortunate! :-) I would try to remove it the next time I stumble over it. :-D Sorry.<div><br></div><div>Considering cross-platform VM issues in the past, I would rather not remove the delta aggregation (or delta mapping) from the image. <span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">MouseWheelState took inspiration from MouseClickState and MouseOverHandler.</span><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px"><br></span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">So, I vote for:</span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px"><br></span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">- make delta aggregation (or mapping) optional in the image via a preference</span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">- add that time-out to MouseWheelState</span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">- no calls to "Sensor peekEvent" in HandMorph under any circumstances ... please :-)</span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px"><br></span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">Best,</span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px">Marcel</span></div><div><span style="font-family: Arial, Helvetica, sans-serif;font-size: 13px"><br></span></div></div><div class="mb_sig"></div>
                                        
                                        <blockquote class="history_container" type="cite" style="border-left-style: solid;border-width: 1px;margin-top: 20px;margin-left: 0px;padding-left: 10px;min-width: 500px">
                        <p style="color: #AAAAAA; margin-top: 10px;">Am 27.12.2019 22:58:16 schrieb commits@source.squeak.org <commits@source.squeak.org>:</p><div style="font-family:Arial,Helvetica,sans-serif">Nicolas Cellier uploaded a new version of Morphic to project The Inbox:<br>http://source.squeak.org/inbox/Morphic-nice.1613.mcz<br><br>==================== Summary ====================<br><br>Name: Morphic-nice.1613<br>Author: nice<br>Time: 27 December 2019, 10:57:36.813907 pm<br>UUID: 07043442-aa16-4af4-9123-601cd503346a<br>Ancestors: Morphic-mt.1612<br><br>Provide smoother scrolling in response to mouse wheel events<br><br>Instead of delivering the events when delta reaches 120, make this threshold a Preference (minimumWheelDelta)<br><br>Reminder: 120 represents a single notch for traditional mouse wheel with notches, but trackpads can deliver much smaller deltas<br><br>Rather than accumulating the deltas into MouseWheelState, do it when we #generateMouseWheelEvent:<br>Indeed, small deltas will come in packets of successive events, and it's more efficient to regroup then, a bit like we do with mouse trails...<br><br>Also MouseWheelState did ignore time outs (long delays between deltas) and other state changes (buttons/modifiers), which was not ideal.<br><br>Directly get those states from the raw eventBuffer, like any other mouse event. This requires integration of tose 2 PR:<br>https://github.com/OpenSmalltalk/opensmalltalk-vm/pull/461 for Windows<br>https://github.com/OpenSmalltalk/opensmalltalk-vm/pull/462 for OSX and linux<br><br>Honour larger deltas too in ScrollPane>>#mouseWheel: event handling<br>Honour horizontal mouse wheels too in ScrollPane<br><br>With patched VM, and following settings, I get a reasonnable scrolling experience on OSX:<br><br>     HandMorph minimumWheelDelta: 20.<br>      Smalltalk sendMouseWheelEvents: true.<br><br>=============== Diff against Morphic-mt.1612 ===============<br><br>Item was changed:<br>  Morph subclass: #HandMorph<br>      instanceVariableNames: 'mouseFocus keyboardFocus eventListeners mouseListeners keyboardListeners eventCaptureFilters mouseCaptureFilters keyboardCaptureFilters mouseClickState mouseOverHandler mouseWheelState lastMouseEvent targetOffset damageRecorder cacheCanvas cachedCanvasHasHoles temporaryCursor temporaryCursorOffset hardwareCursor hasChanged savedPatch userInitials lastEventBuffer genieGestureProcessor keyboardInterpreter'<br>+      classVariableNames: 'CompositionWindowManager DoubleClickTime DragThreshold EventStats MinimalWheelDelta NewEventRules NormalCursor PasteBuffer SendMouseWheelToKeyboardFocus ShowEvents SynthesizeMouseWheelEvents'<br>-         classVariableNames: 'CompositionWindowManager DoubleClickTime DragThreshold EventStats NewEventRules NormalCursor PasteBuffer SendMouseWheelToKeyboardFocus ShowEvents SynthesizeMouseWheelEvents'<br>    poolDictionaries: 'EventSensorConstants'<br>      category: 'Morphic-Kernel'!<br>  <br>  !HandMorph commentStamp: '<historical>' prior: 0!<br>  The cursor may be thought of as the HandMorph.  The hand's submorphs hold anything being carried by dragging.  <br>  <br>  There is some minimal support for multiple hands in the same world.!<br><br>Item was added:<br>+ ----- Method: HandMorph class>>minimumWheelDelta (in category 'preferences') -----<br>+ minimumWheelDelta<br>+        <preference: 'minimal="" mouse="" wheel="" scroll=""></preference:><br>+              categoryList: #(Morphic mouse)<br>+               description: 'Answer the minimal scroll increment taken into account<br>+ Defaults to 120, corresponding to a single mouse wheel notch.<br>+ Use a lower value (20) if wanting smoother scrolling with trackpads.'<br>+               type: #Number><br>+    ^MinimalWheelDelta ifNil: [120].!<br><br>Item was added:<br>+ ----- Method: HandMorph class>>minimumWheelDelta: (in category 'preferences') -----<br>+ minimumWheelDelta: anInteger<br>+    MinimalWheelDelta := anInteger ifNotNil: [anInteger clampLow: 20 high: 120]!<br><br>Item was changed:<br>  ----- Method: HandMorph>>generateMouseWheelEvent: (in category 'private events') -----<br>  generateMouseWheelEvent: evtBuf<br>          "Generate the appropriate mouse wheel event for the given raw event buffer"<br>  <br>+    | buttons modifiers deltaX deltaY stamp nextEvent |<br>-  | buttons modifiers deltaX deltaY stamp |<br>     stamp := evtBuf second.<br>       stamp = 0 ifTrue: [stamp := Time eventMillisecondClock].<br>      deltaX := evtBuf third.<br>       deltaY := evtBuf fourth.<br>+     buttons := evtBuf fifth.<br>+     modifiers := evtBuf sixth.<br>+   [(deltaX abs + deltaY abs < self="" class=""><br>+                      and: [(nextEvent := Sensor peekEvent) notNil<br>+                         and: [nextEvent first = evtBuf first<br>+                         and: [nextEvent fifth = evtBuf fifth <br>+                        and: [nextEvent sixth = evtBuf sixth]<br>+                        and: [nextEvent third isZero = evtBuf third isZero "both horizontal or vertical"]]]]]<br>+              whileTrue:<br>+                   ["nextEvent is similar.  Remove it from the queue, and check the next."<br>+                    nextEvent := Sensor nextEvent.<br>+                       deltaX := deltaX + nextEvent third.<br>+                  deltaY := deltaY + nextEvent fourth].<br>-        modifiers := evtBuf fifth.<br>-   buttons := (modifiers bitShift: 3) bitOr: (lastMouseEvent buttons bitAnd: 7).<br>         ^ MouseWheelEvent new<br>                 setType: #mouseWheel<br>                  position: self position<br>               delta: deltaX@deltaY<br>-                 direction: 2r0000<br>             buttons: buttons        <br>              hand: self<br>            stamp: stamp!<br><br>Item was changed:<br>  ----- Method: HandMorph>>handleEvent: (in category 'events-processing') -----<br>  handleEvent: unfilteredEvent<br>  <br>         | filteredEvent |<br>     owner ifNil: [^ unfilteredEvent  "not necessary but good style -- see Morph >> #handleEvent:"].<br>       <br>      self logEvent: unfilteredEvent.<br>  <br>   "Mouse-over events occur really, really, really often. They are kind of the heart beat of the Morphic UI process."<br>          unfilteredEvent isMouseOver ifTrue: [^ self sendMouseEvent: unfilteredEvent].<br>  <br>     self showEvent: unfilteredEvent.<br>      self sendListenEvents: unfilteredEvent.<br>       <br>      filteredEvent := self sendFilterEventCapture: unfilteredEvent for: nil.<br>       "filteredEvent := unfilteredEvent" " <-- use="" this="" to="" disable="" global="" capture=""><!------><br>        <br>      filteredEvent wasIgnored ifTrue: [<br>            self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>     <br>      filteredEvent isWindowEvent ifTrue: [<br>                 self sendEvent: filteredEvent focus: nil.<br>             self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>  <br>         filteredEvent isKeyboard ifTrue:[<br>             self sendKeyboardEvent: filteredEvent.<br>                self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>                     <br>      filteredEvent isDropEvent ifTrue:[<br>            self sendEvent: filteredEvent focus: nil.<br>             self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>  <br>         filteredEvent isMouse ifFalse: [<br>              self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>  <br>         " ********** MOUSE EVENT *********** "<br>  <br>          lastMouseEvent := filteredEvent.<br>  <br>          "Check for pending drag or double click operations."<br>        mouseClickState ifNotNil:[<br>            (mouseClickState handleEvent: filteredEvent from: self) ifFalse:[<br>                     "Possibly dispatched #click: or something and will not re-establish otherwise"<br>                      self mouseOverHandler processMouseOver: lastMouseEvent.<br>                       ^ filteredEvent]].<br>  <br>        filteredEvent isMouseWheel ifTrue: [<br>+                 self class sendMouseWheelToKeyboardFocus<br>+                     ifFalse: [self sendMouseEvent: filteredEvent]<br>+                        ifTrue: [self sendEvent: filteredEvent focus: self keyboardFocus clear: [self keyboardFocus: nil]].<br>-          mouseWheelState ifNil: [mouseWheelState := MouseWheelState new].<br>-             mouseWheelState handleEvent: filteredEvent from: self.<br>                self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>  <br>         filteredEvent isMove ifTrue:[<br>                 self position: filteredEvent position.<br>                self sendMouseEvent: filteredEvent.<br>           self mouseOverHandler processMouseOver: lastMouseEvent.<br>               ^ filteredEvent].<br>  <br>         "Issue a synthetic move event if we're not at the position of the event"<br>    filteredEvent position = self position<br>                ifFalse: [self moveToEvent: filteredEvent].<br>   <br>      "Drop submorphs on button events"<br>   self hasSubmorphs<br>             ifTrue:[self dropMorphs: filteredEvent]<br>               ifFalse:[self sendMouseEvent: filteredEvent].<br>  <br>     self mouseOverHandler processMouseOver: lastMouseEvent.<br>       ^ filteredEvent "not necessary but good style -- see Morph >> #handleEvent:"    !<br><br>Item was added:<br>+ ----- Method: MouseWheelEvent>>setDirection (in category 'initialization') -----<br>+ setDirection<br>+       delta x > 0 ifTrue: [self setWheelRight].<br>+         delta x < 0="" iftrue:="" [self=""><br>+ <br>+  delta y > 0 ifTrue: [self setWheelUp].<br>+    delta y < 0="" iftrue:="" [self=""><br><br>Item was added:<br>+ ----- Method: MouseWheelEvent>>setType:position:delta:buttons:hand:stamp: (in category 'private') -----<br>+ setType: evtType position: evtPos delta: evtDelta buttons: evtButtons hand: evtHand stamp: stamp<br>+      type := evtType.<br>+     position := evtPos.<br>+  buttons := evtButtons.<br>+       source := evtHand.<br>+   wasHandled := false.<br>+         direction := 2r0000.<br>+         delta := evtDelta.<br>+   timeStamp := stamp.<br>+  self setDirection!<br><br>Item was removed:<br>- Object subclass: #MouseWheelState<br>-         instanceVariableNames: 'currentDelta'<br>-        classVariableNames: ''<br>-       poolDictionaries: ''<br>-         category: 'Morphic-Events'!<br><br>Item was removed:<br>- ----- Method: MouseWheelState>>handleEvent:from: (in category 'event processing') -----<br>- handleEvent: aMouseWheelEvent from: aHand<br>-       "Every 120 units, raise the wheel flags for convenient mouse wheel programming. We choose not to send multiple mouse-wheel events for multiples of 120 because applications can always react to the actual delta values if they want to do more scrolling or zooming."<br>-     <br>-     | sign |<br>-     currentDelta := currentDelta + aMouseWheelEvent wheelDelta.<br>- <br>-      sign := currentDelta sign.<br>-   currentDelta := currentDelta abs.<br>- <br>-        (currentDelta x // 120) > 0 ifTrue: [<br>-             sign x = 1<br>-                   ifTrue: [aMouseWheelEvent setWheelRight]<br>-                     ifFalse: [aMouseWheelEvent setWheelLeft]].<br>- <br>-       (currentDelta y // 120) > 0 ifTrue: [<br>-             sign y = 1<br>-                   ifTrue: [aMouseWheelEvent setWheelUp]<br>-                        ifFalse: [aMouseWheelEvent setWheelDown]].<br>-           <br>-     currentDelta := currentDelta \\ 120.<br>-         currentDelta := currentDelta * sign.<br>- <br>-     "Finally, send the event."<br>-         HandMorph sendMouseWheelToKeyboardFocus<br>-              ifFalse: [aHand sendMouseEvent: aMouseWheelEvent]<br>-            ifTrue: [aHand sendEvent: aMouseWheelEvent focus: aHand keyboardFocus clear: [aHand keyboardFocus: nil]].<br>- !<br><br>Item was removed:<br>- ----- Method: MouseWheelState>>initialize (in category 'initialize-release') -----<br>- initialize<br>- <br>-    super initialize.<br>-    currentDelta := 0@0.!<br><br>Item was changed:<br>  ----- Method: ScrollPane>>mouseWheel: (in category 'event handling') -----<br>  mouseWheel: evt<br>  <br>+        evt isWheelUp ifTrue: [scrollBar scrollUp: (3 * evt wheelDelta y abs // 120 max: 1)].<br>+        evt isWheelDown ifTrue: [scrollBar scrollDown: (3 * evt wheelDelta y abs // 120 max: 1)].<br>+    evt isWheelLeft ifTrue: [hScrollBar scrollUp: (3 * evt wheelDelta x abs // 120 max: 1)].<br>+     evt isWheelRight ifTrue: [hScrollBar scrollDown: (3 * evt wheelDelta x abs // 120 max: 1)].!<br>-         evt isWheelUp ifTrue: [scrollBar scrollUp: 3].<br>-       evt isWheelDown ifTrue: [scrollBar scrollDown: 3].!<br><br><br></historical></div></blockquote></div>