github.com/jmigpin/editor@v1.6.0/util/uiutil/mousefilter/clickfilter.go (about)

     1  package mousefilter
     2  
     3  import (
     4  	"image"
     5  	"time"
     6  
     7  	"github.com/jmigpin/editor/util/uiutil/event"
     8  )
     9  
    10  // produce click/doubleclick/tripleclick events
    11  type ClickFilter struct {
    12  	m        map[event.MouseButton]*MultipleClick
    13  	emitEvFn func(interface{}, image.Point)
    14  }
    15  
    16  func NewClickFilter(emitEvFn func(interface{}, image.Point)) *ClickFilter {
    17  	return &ClickFilter{
    18  		m:        map[event.MouseButton]*MultipleClick{},
    19  		emitEvFn: emitEvFn,
    20  	}
    21  }
    22  
    23  func (clickf *ClickFilter) Filter(ev interface{}) {
    24  	switch t := ev.(type) {
    25  	case *event.MouseDown:
    26  		clickf.down(t)
    27  	case *event.MouseUp:
    28  		clickf.up(t)
    29  	case *event.MouseMove:
    30  		clickf.move(t)
    31  	}
    32  }
    33  
    34  func (clickf *ClickFilter) down(ev *event.MouseDown) {
    35  	// initialize on demand
    36  	mc, ok := clickf.m[ev.Button]
    37  	if !ok {
    38  		mc = &MultipleClick{}
    39  		clickf.m[ev.Button] = mc
    40  	}
    41  
    42  	mc.prevDownPoint = mc.downPoint
    43  	mc.downPoint = ev.Point
    44  }
    45  
    46  func (clickf *ClickFilter) up(ev *event.MouseUp) {
    47  	mc, ok := clickf.m[ev.Button]
    48  	if !ok {
    49  		return
    50  	}
    51  
    52  	// update time
    53  	upTime0 := mc.upTime
    54  	mc.upTime = time.Now()
    55  
    56  	// must be clicked within a margin
    57  	if DetectMove(mc.downPoint, ev.Point) {
    58  		mc.action = MClickActionSingle // reset action
    59  		return
    60  	}
    61  
    62  	// if it takes too much time, it gets back to single click
    63  	d := mc.upTime.Sub(upTime0)
    64  	if d > 400*time.Millisecond {
    65  		mc.action = MClickActionSingle
    66  	} else {
    67  		if DetectMove(mc.prevDownPoint, ev.Point) {
    68  			mc.action = MClickActionSingle // reset action
    69  		} else {
    70  			// single, double, triple
    71  			mc.action = (mc.action + 1) % 3
    72  		}
    73  	}
    74  
    75  	// always run a click
    76  	ev2 := &event.MouseClick{ev.Point, ev.Button, ev.Buttons, ev.Mods}
    77  	clickf.emitEv(ev2, ev.Point)
    78  
    79  	switch mc.action {
    80  	case MClickActionDouble:
    81  		ev2 := &event.MouseDoubleClick{ev.Point, ev.Button, ev.Buttons, ev.Mods}
    82  		clickf.emitEv(ev2, ev.Point)
    83  	case MClickActionTriple:
    84  		ev2 := &event.MouseTripleClick{ev.Point, ev.Button, ev.Buttons, ev.Mods}
    85  		clickf.emitEv(ev2, ev.Point)
    86  	}
    87  }
    88  
    89  func (clickf *ClickFilter) move(ev *event.MouseMove) {
    90  	for b, mc := range clickf.m {
    91  		// clear if moved outside move detection margins
    92  		if DetectMove(mc.downPoint, ev.Point) {
    93  			delete(clickf.m, b)
    94  		}
    95  	}
    96  }
    97  
    98  //----------
    99  
   100  func (clickf *ClickFilter) emitEv(ev interface{}, p image.Point) {
   101  	clickf.emitEvFn(ev, p)
   102  }
   103  
   104  //----------
   105  
   106  type MultipleClick struct {
   107  	upTime        time.Time
   108  	downPoint     image.Point
   109  	prevDownPoint image.Point
   110  	action        MClickAction
   111  }
   112  
   113  type MClickAction int
   114  
   115  const (
   116  	MClickActionSingle MClickAction = iota
   117  	MClickActionDouble
   118  	MClickActionTriple
   119  )