Ajout du GUI
This commit is contained in:
424
kivy/input/providers/mouse.py
Normal file
424
kivy/input/providers/mouse.py
Normal file
@@ -0,0 +1,424 @@
|
||||
'''
|
||||
Mouse provider implementation
|
||||
=============================
|
||||
|
||||
On linux systems, the mouse provider can be annoying when used with another
|
||||
multitouch provider (hidinput or mtdev). The Mouse can conflict with them: a
|
||||
single touch can generate one event from the mouse provider and another
|
||||
from the multitouch provider.
|
||||
|
||||
To avoid this behavior, you can activate the "disable_on_activity" token in
|
||||
the mouse configuration. Then, if any touches are created by another
|
||||
provider, the mouse event will be discarded. Add this to your configuration::
|
||||
|
||||
[input]
|
||||
mouse = mouse,disable_on_activity
|
||||
|
||||
Using multitouch interaction with the mouse
|
||||
-------------------------------------------
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
|
||||
By default, the middle and right mouse buttons, as well as a combination of
|
||||
ctrl + left mouse button are used for multitouch emulation.
|
||||
If you want to use them for other purposes, you can disable this behavior by
|
||||
activating the "disable_multitouch" token::
|
||||
|
||||
[input]
|
||||
mouse = mouse,disable_multitouch
|
||||
|
||||
.. versionchanged:: 1.9.0
|
||||
|
||||
You can now selectively control whether a click initiated as described above
|
||||
will emulate multi-touch. If the touch has been initiated in the above manner
|
||||
(e.g. right mouse button), a `multitouch_sim` value will be added to the
|
||||
touch's profile, and a `multitouch_sim` property will be added to the touch.
|
||||
By default, `multitouch_sim` is True and multitouch will be emulated for that
|
||||
touch. If, however, `multitouch_on_demand` is added to the config::
|
||||
|
||||
[input]
|
||||
mouse = mouse,multitouch_on_demand
|
||||
|
||||
then `multitouch_sim` defaults to `False`. In that case, if `multitouch_sim`
|
||||
is set to True before the mouse is released (e.g. in on_touch_down/move), the
|
||||
touch will simulate a multi-touch event. For example::
|
||||
|
||||
if 'multitouch_sim' in touch.profile:
|
||||
touch.multitouch_sim = True
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
|
||||
Provider dispatches hover events by listening to properties/events in
|
||||
:class:`~kivy.core.window.Window`. Dispatching can be disabled by setting
|
||||
:attr:`MouseMotionEventProvider.disable_hover` to ``True`` or by adding
|
||||
`disable_hover` in the config::
|
||||
|
||||
[input]
|
||||
mouse = mouse,disable_hover
|
||||
|
||||
It's also possible to enable/disable hover events at runtime with
|
||||
:attr:`MouseMotionEventProvider.disable_hover` property.
|
||||
|
||||
Following is a list of the supported values for the
|
||||
:attr:`~kivy.input.motionevent.MotionEvent.profile` property list.
|
||||
|
||||
================ ==========================================================
|
||||
Profile value Description
|
||||
---------------- ----------------------------------------------------------
|
||||
button Mouse button (one of `left`, `right`, `middle`, `scrollup`
|
||||
or `scrolldown`). Accessed via the 'button' property.
|
||||
pos 2D position. Also reflected in the
|
||||
:attr:`~kivy.input.motionevent.MotionEvent.x`,
|
||||
:attr:`~kivy.input.motionevent.MotionEvent.y`
|
||||
and :attr:`~kivy.input.motionevent.MotionEvent.pos`
|
||||
properties.
|
||||
multitouch_sim Specifies whether multitouch is simulated or not. Accessed
|
||||
via the 'multitouch_sim' property.
|
||||
================ ==========================================================
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('MouseMotionEventProvider', )
|
||||
|
||||
from kivy.base import EventLoop
|
||||
from collections import deque
|
||||
from kivy.logger import Logger
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
|
||||
# late binding
|
||||
Color = Ellipse = None
|
||||
|
||||
|
||||
class MouseMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.multitouch_sim = False
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def depack(self, args):
|
||||
self.sx, self.sy = args[:2]
|
||||
profile = self.profile
|
||||
if self.is_touch:
|
||||
# don't overwrite previous profile
|
||||
if not profile:
|
||||
profile.extend(('pos', 'button'))
|
||||
if len(args) >= 3:
|
||||
self.button = args[2]
|
||||
if len(args) == 4:
|
||||
self.multitouch_sim = args[3]
|
||||
profile.append('multitouch_sim')
|
||||
else:
|
||||
if not profile:
|
||||
profile.append('pos')
|
||||
super().depack(args)
|
||||
|
||||
#
|
||||
# Create automatically touch on the surface.
|
||||
#
|
||||
|
||||
def update_graphics(self, win, create=False):
|
||||
global Color, Ellipse
|
||||
de = self.ud.get('_drawelement', None)
|
||||
if de is None and create:
|
||||
if Color is None:
|
||||
from kivy.graphics import Color, Ellipse
|
||||
with win.canvas.after:
|
||||
de = (
|
||||
Color(.8, .2, .2, .7),
|
||||
Ellipse(size=(20, 20), segments=15))
|
||||
self.ud._drawelement = de
|
||||
if de is not None:
|
||||
self.push()
|
||||
|
||||
# use same logic as WindowBase.on_motion() so we get correct
|
||||
# coordinates when _density != 1
|
||||
w, h = win._get_effective_size()
|
||||
|
||||
self.scale_for_screen(w, h, rotation=win.rotation)
|
||||
|
||||
de[1].pos = self.x - 10, self.y - 10
|
||||
self.pop()
|
||||
|
||||
def clear_graphics(self, win):
|
||||
de = self.ud.pop('_drawelement', None)
|
||||
if de is not None:
|
||||
win.canvas.after.remove(de[0])
|
||||
win.canvas.after.remove(de[1])
|
||||
|
||||
|
||||
class MouseMotionEventProvider(MotionEventProvider):
|
||||
__handlers__ = {}
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(MouseMotionEventProvider, self).__init__(device, args)
|
||||
self.waiting_event = deque()
|
||||
self.touches = {}
|
||||
self.counter = 0
|
||||
self.current_drag = None
|
||||
self.alt_touch = None
|
||||
self.disable_on_activity = False
|
||||
self.disable_multitouch = False
|
||||
self.multitouch_on_demand = False
|
||||
self.hover_event = None
|
||||
self._disable_hover = False
|
||||
self._running = False
|
||||
# split arguments
|
||||
args = args.split(',')
|
||||
for arg in args:
|
||||
arg = arg.strip()
|
||||
if arg == '':
|
||||
continue
|
||||
elif arg == 'disable_on_activity':
|
||||
self.disable_on_activity = True
|
||||
elif arg == 'disable_multitouch':
|
||||
self.disable_multitouch = True
|
||||
elif arg == 'disable_hover':
|
||||
self.disable_hover = True
|
||||
elif arg == 'multitouch_on_demand':
|
||||
self.multitouch_on_demand = True
|
||||
else:
|
||||
Logger.error('Mouse: unknown parameter <%s>' % arg)
|
||||
|
||||
def _get_disable_hover(self):
|
||||
return self._disable_hover
|
||||
|
||||
def _set_disable_hover(self, value):
|
||||
if self._disable_hover != value:
|
||||
if self._running:
|
||||
if value:
|
||||
self._stop_hover_events()
|
||||
else:
|
||||
self._start_hover_events()
|
||||
self._disable_hover = value
|
||||
|
||||
disable_hover = property(_get_disable_hover, _set_disable_hover)
|
||||
'''Disables dispatching of hover events if set to ``True``.
|
||||
|
||||
Hover events are enabled by default (`disable_hover` is ``False``). See
|
||||
module documentation if you want to enable/disable hover events through
|
||||
config file.
|
||||
|
||||
.. versionadded:: 2.1.0
|
||||
'''
|
||||
|
||||
def start(self):
|
||||
'''Start the mouse provider'''
|
||||
if not EventLoop.window:
|
||||
return
|
||||
fbind = EventLoop.window.fbind
|
||||
fbind('on_mouse_down', self.on_mouse_press)
|
||||
fbind('on_mouse_move', self.on_mouse_motion)
|
||||
fbind('on_mouse_up', self.on_mouse_release)
|
||||
fbind('on_rotate', self.update_touch_graphics)
|
||||
fbind('system_size', self.update_touch_graphics)
|
||||
if not self.disable_hover:
|
||||
self._start_hover_events()
|
||||
self._running = True
|
||||
|
||||
def _start_hover_events(self):
|
||||
fbind = EventLoop.window.fbind
|
||||
fbind('mouse_pos', self.begin_or_update_hover_event)
|
||||
fbind('system_size', self.update_hover_event)
|
||||
fbind('on_cursor_enter', self.begin_hover_event)
|
||||
fbind('on_cursor_leave', self.end_hover_event)
|
||||
fbind('on_close', self.end_hover_event)
|
||||
fbind('on_rotate', self.update_hover_event)
|
||||
|
||||
def stop(self):
|
||||
'''Stop the mouse provider'''
|
||||
if not EventLoop.window:
|
||||
return
|
||||
funbind = EventLoop.window.funbind
|
||||
funbind('on_mouse_down', self.on_mouse_press)
|
||||
funbind('on_mouse_move', self.on_mouse_motion)
|
||||
funbind('on_mouse_up', self.on_mouse_release)
|
||||
funbind('on_rotate', self.update_touch_graphics)
|
||||
funbind('system_size', self.update_touch_graphics)
|
||||
if not self.disable_hover:
|
||||
self._stop_hover_events()
|
||||
self._running = False
|
||||
|
||||
def _stop_hover_events(self):
|
||||
funbind = EventLoop.window.funbind
|
||||
funbind('mouse_pos', self.begin_or_update_hover_event)
|
||||
funbind('system_size', self.update_hover_event)
|
||||
funbind('on_cursor_enter', self.begin_hover_event)
|
||||
funbind('on_cursor_leave', self.end_hover_event)
|
||||
funbind('on_close', self.end_hover_event)
|
||||
funbind('on_rotate', self.update_hover_event)
|
||||
|
||||
def test_activity(self):
|
||||
if not self.disable_on_activity:
|
||||
return False
|
||||
# trying to get if we currently have other touch than us
|
||||
# discard touches generated from kinetic
|
||||
for touch in EventLoop.touches:
|
||||
# discard all kinetic touch
|
||||
if touch.__class__.__name__ == 'KineticMotionEvent':
|
||||
continue
|
||||
# not our instance, stop mouse
|
||||
if touch.__class__ != MouseMotionEvent:
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_touch(self, win, x, y):
|
||||
factor = 10. / win.system_size[0]
|
||||
for touch in self.touches.values():
|
||||
if abs(x - touch.sx) < factor and abs(y - touch.sy) < factor:
|
||||
return touch
|
||||
return None
|
||||
|
||||
def create_event_id(self):
|
||||
self.counter += 1
|
||||
return self.device + str(self.counter)
|
||||
|
||||
def create_touch(self, win, nx, ny, is_double_tap, do_graphics, button):
|
||||
event_id = self.create_event_id()
|
||||
args = [nx, ny, button]
|
||||
if do_graphics:
|
||||
args += [not self.multitouch_on_demand]
|
||||
self.current_drag = touch = MouseMotionEvent(
|
||||
self.device, event_id, args,
|
||||
is_touch=True,
|
||||
type_id='touch'
|
||||
)
|
||||
touch.is_double_tap = is_double_tap
|
||||
self.touches[event_id] = touch
|
||||
if do_graphics:
|
||||
# only draw red circle if multitouch is not disabled, and
|
||||
# if the multitouch_on_demand feature is not enable
|
||||
# (because in that case, we wait to see if multitouch_sim
|
||||
# is True or not before doing the multitouch)
|
||||
create_flag = (
|
||||
not self.disable_multitouch
|
||||
and not self.multitouch_on_demand
|
||||
)
|
||||
touch.update_graphics(win, create_flag)
|
||||
self.waiting_event.append(('begin', touch))
|
||||
return touch
|
||||
|
||||
def remove_touch(self, win, touch):
|
||||
if touch.id in self.touches:
|
||||
del self.touches[touch.id]
|
||||
touch.update_time_end()
|
||||
self.waiting_event.append(('end', touch))
|
||||
touch.clear_graphics(win)
|
||||
|
||||
def create_hover(self, win, etype):
|
||||
nx, ny = win.to_normalized_pos(*win.mouse_pos)
|
||||
# Divide by density because it's used by mouse_pos
|
||||
nx /= win._density
|
||||
ny /= win._density
|
||||
args = (nx, ny)
|
||||
hover = self.hover_event
|
||||
if hover:
|
||||
hover.move(args)
|
||||
else:
|
||||
self.hover_event = hover = MouseMotionEvent(
|
||||
self.device,
|
||||
self.create_event_id(),
|
||||
args,
|
||||
type_id='hover'
|
||||
)
|
||||
if etype == 'end':
|
||||
hover.update_time_end()
|
||||
self.hover_event = None
|
||||
self.waiting_event.append((etype, hover))
|
||||
|
||||
def on_mouse_motion(self, win, x, y, modifiers):
|
||||
nx, ny = win.to_normalized_pos(x, y)
|
||||
ny = 1.0 - ny
|
||||
if self.current_drag:
|
||||
touch = self.current_drag
|
||||
touch.move([nx, ny])
|
||||
touch.update_graphics(win)
|
||||
self.waiting_event.append(('update', touch))
|
||||
elif self.alt_touch is not None and 'alt' not in modifiers:
|
||||
# alt just released ?
|
||||
is_double_tap = 'shift' in modifiers
|
||||
self.create_touch(win, nx, ny, is_double_tap, True, [])
|
||||
|
||||
def on_mouse_press(self, win, x, y, button, modifiers):
|
||||
if self.test_activity():
|
||||
return
|
||||
nx, ny = win.to_normalized_pos(x, y)
|
||||
ny = 1.0 - ny
|
||||
found_touch = self.find_touch(win, nx, ny)
|
||||
if found_touch:
|
||||
self.current_drag = found_touch
|
||||
else:
|
||||
is_double_tap = 'shift' in modifiers
|
||||
do_graphics = (
|
||||
not self.disable_multitouch
|
||||
and (button != 'left' or 'ctrl' in modifiers)
|
||||
)
|
||||
touch = self.create_touch(
|
||||
win, nx, ny, is_double_tap, do_graphics, button
|
||||
)
|
||||
if 'alt' in modifiers:
|
||||
self.alt_touch = touch
|
||||
self.current_drag = None
|
||||
|
||||
def on_mouse_release(self, win, x, y, button, modifiers):
|
||||
if button == 'all':
|
||||
# Special case, if button is all,
|
||||
# then remove all the current touches.
|
||||
for touch in list(self.touches.values()):
|
||||
self.remove_touch(win, touch)
|
||||
self.current_drag = None
|
||||
touch = self.current_drag
|
||||
if touch:
|
||||
not_right = button in (
|
||||
'left',
|
||||
'scrollup', 'scrolldown',
|
||||
'scrollleft', 'scrollright'
|
||||
)
|
||||
not_ctrl = 'ctrl' not in modifiers
|
||||
not_multi = (
|
||||
self.disable_multitouch
|
||||
or 'multitouch_sim' not in touch.profile
|
||||
or not touch.multitouch_sim
|
||||
)
|
||||
if not_right and not_ctrl or not_multi:
|
||||
self.remove_touch(win, touch)
|
||||
self.current_drag = None
|
||||
else:
|
||||
touch.update_graphics(win, True)
|
||||
if self.alt_touch:
|
||||
self.remove_touch(win, self.alt_touch)
|
||||
self.alt_touch = None
|
||||
|
||||
def update_touch_graphics(self, win, *args):
|
||||
for touch in self.touches.values():
|
||||
touch.update_graphics(win)
|
||||
|
||||
def begin_or_update_hover_event(self, win, *args):
|
||||
etype = 'update' if self.hover_event else 'begin'
|
||||
self.create_hover(win, etype)
|
||||
|
||||
def begin_hover_event(self, win, *args):
|
||||
if not self.hover_event:
|
||||
self.create_hover(win, 'begin')
|
||||
|
||||
def update_hover_event(self, win, *args):
|
||||
if self.hover_event:
|
||||
self.create_hover(win, 'update')
|
||||
|
||||
def end_hover_event(self, win, *args):
|
||||
if self.hover_event:
|
||||
self.create_hover(win, 'end')
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
'''Update the mouse provider (pop event from the queue)'''
|
||||
try:
|
||||
while True:
|
||||
event = self.waiting_event.popleft()
|
||||
dispatch_fn(*event)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
|
||||
# registers
|
||||
MotionEventFactory.register('mouse', MouseMotionEventProvider)
|
||||
Reference in New Issue
Block a user