Ajout du GUI
This commit is contained in:
300
kivy/modules/__init__.py
Normal file
300
kivy/modules/__init__.py
Normal file
@@ -0,0 +1,300 @@
|
||||
'''
|
||||
Modules
|
||||
=======
|
||||
|
||||
Modules are classes that can be loaded when a Kivy application is starting. The
|
||||
loading of modules is managed by the config file. Currently, we include:
|
||||
|
||||
* :class:`~kivy.modules.touchring`: Draw a circle around each touch.
|
||||
* :class:`~kivy.modules.monitor`: Add a red topbar that indicates the FPS
|
||||
and a small graph indicating input activity.
|
||||
* :class:`~kivy.modules.keybinding`: Bind some keys to actions, such as a
|
||||
screenshot.
|
||||
* :class:`~kivy.modules.recorder`: Record and playback a sequence of
|
||||
events.
|
||||
* :class:`~kivy.modules.screen`: Emulate the characteristics (dpi/density/
|
||||
resolution) of different screens.
|
||||
* :class:`~kivy.modules.inspector`: Examines your widget hierarchy and
|
||||
widget properties.
|
||||
* :class:`~kivy.modules.webdebugger`: Realtime examination of your app
|
||||
internals via a web browser.
|
||||
* :class:`~kivy.modules.joycursor`: Navigate in your app with a joystick.
|
||||
* :class:`~kivy.modules.showborder`: Show widget's border.
|
||||
|
||||
Modules are automatically loaded from the Kivy path and User path:
|
||||
|
||||
* `PATH_TO_KIVY/kivy/modules`
|
||||
* `HOME/.kivy/mods`
|
||||
|
||||
Activating a module
|
||||
-------------------
|
||||
|
||||
There are various ways in which you can activate a kivy module.
|
||||
|
||||
Activate a module in the config
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To activate a module this way, you can edit your configuration file (in your
|
||||
`HOME/.kivy/config.ini`)::
|
||||
|
||||
[modules]
|
||||
# uncomment to activate
|
||||
touchring =
|
||||
# monitor =
|
||||
# keybinding =
|
||||
|
||||
Only the name of the module followed by "=" is sufficient to activate the
|
||||
module.
|
||||
|
||||
Activate a module in Python
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Before starting your application, preferably at the start of your import, you
|
||||
can do something like this::
|
||||
|
||||
import kivy
|
||||
kivy.require('1.0.8')
|
||||
|
||||
# Activate the touchring module
|
||||
from kivy.config import Config
|
||||
Config.set('modules', 'touchring', '')
|
||||
|
||||
Activate a module via the commandline
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When starting your application from the commandline, you can add a
|
||||
*-m <modulename>* to the arguments. For example::
|
||||
|
||||
python main.py -m webdebugger
|
||||
|
||||
.. note::
|
||||
Some modules, such as the screen, may require additional parameters. They
|
||||
will, however, print these parameters to the console when launched without
|
||||
them.
|
||||
|
||||
|
||||
Create your own module
|
||||
----------------------
|
||||
|
||||
Create a file in your `HOME/.kivy/mods`, and create 2 functions::
|
||||
|
||||
def start(win, ctx):
|
||||
pass
|
||||
|
||||
def stop(win, ctx):
|
||||
pass
|
||||
|
||||
Start/stop are functions that will be called for every window opened in
|
||||
Kivy. When you are starting a module, you can use these to store and
|
||||
manage the module state. Use the `ctx` variable as a dictionary. This
|
||||
context is unique for each instance/start() call of the module, and will
|
||||
be passed to stop() too.
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('Modules', )
|
||||
|
||||
from kivy.config import Config
|
||||
from kivy.logger import Logger
|
||||
import kivy
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
class ModuleContext:
|
||||
'''Context of a module
|
||||
|
||||
You can access to the config with self.config.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.config = {}
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.config)
|
||||
|
||||
|
||||
class ModuleBase:
|
||||
'''Handle Kivy modules. It will automatically load and instantiate the
|
||||
module for the general window.'''
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.mods = {}
|
||||
self.wins = []
|
||||
|
||||
def add_path(self, path):
|
||||
'''Add a path to search for modules in'''
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
if path not in sys.path:
|
||||
sys.path.append(path)
|
||||
dirs = os.listdir(path)
|
||||
for module in dirs:
|
||||
name, ext = os.path.splitext(module)
|
||||
# accept only python extensions
|
||||
if ext not in ('.py', '.pyo', '.pyc') or name == '__init__':
|
||||
continue
|
||||
self.mods[name] = {
|
||||
'name': name,
|
||||
'activated': False,
|
||||
'context': ModuleContext()}
|
||||
|
||||
def list(self):
|
||||
'''Return the list of available modules'''
|
||||
return self.mods
|
||||
|
||||
def import_module(self, name):
|
||||
try:
|
||||
modname = 'kivy.modules.{0}'.format(name)
|
||||
module = importlib.__import__(name=modname)
|
||||
module = sys.modules[modname]
|
||||
except ImportError:
|
||||
try:
|
||||
module = importlib.__import__(name=name)
|
||||
module = sys.modules[name]
|
||||
except ImportError:
|
||||
Logger.exception('Modules: unable to import <%s>' % name)
|
||||
# protect against missing module dependency crash
|
||||
self.mods[name]['module'] = None
|
||||
return
|
||||
# basic check on module
|
||||
if not hasattr(module, 'start'):
|
||||
Logger.warning('Modules: Module <%s> missing start() function' %
|
||||
name)
|
||||
return
|
||||
if not hasattr(module, 'stop'):
|
||||
err = 'Modules: Module <%s> missing stop() function' % name
|
||||
Logger.warning(err)
|
||||
return
|
||||
self.mods[name]['module'] = module
|
||||
|
||||
def activate_module(self, name, win):
|
||||
'''Activate a module on a window'''
|
||||
if name not in self.mods:
|
||||
Logger.warning('Modules: Module <%s> not found' % name)
|
||||
return
|
||||
|
||||
mod = self.mods[name]
|
||||
|
||||
# ensure the module has been configured
|
||||
if 'module' not in mod:
|
||||
self._configure_module(name)
|
||||
|
||||
pymod = mod['module']
|
||||
if not mod['activated']:
|
||||
context = mod['context']
|
||||
msg = 'Modules: Start <{0}> with config {1}'.format(
|
||||
name, context)
|
||||
Logger.debug(msg)
|
||||
pymod.start(win, context)
|
||||
mod['activated'] = True
|
||||
|
||||
def deactivate_module(self, name, win):
|
||||
'''Deactivate a module from a window'''
|
||||
if name not in self.mods:
|
||||
Logger.warning('Modules: Module <%s> not found' % name)
|
||||
return
|
||||
if 'module' not in self.mods[name]:
|
||||
return
|
||||
|
||||
module = self.mods[name]['module']
|
||||
if self.mods[name]['activated']:
|
||||
module.stop(win, self.mods[name]['context'])
|
||||
self.mods[name]['activated'] = False
|
||||
|
||||
def register_window(self, win):
|
||||
'''Add the window to the window list'''
|
||||
if win not in self.wins:
|
||||
self.wins.append(win)
|
||||
self.update()
|
||||
|
||||
def unregister_window(self, win):
|
||||
'''Remove the window from the window list'''
|
||||
if win in self.wins:
|
||||
self.wins.remove(win)
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
'''Update the status of the module for each window'''
|
||||
modules_to_activate = [x[0] for x in Config.items('modules')]
|
||||
for win in self.wins:
|
||||
for name in self.mods:
|
||||
if name not in modules_to_activate:
|
||||
self.deactivate_module(name, win)
|
||||
for name in modules_to_activate:
|
||||
try:
|
||||
self.activate_module(name, win)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def configure(self):
|
||||
'''(internal) Configure all the modules before using them.
|
||||
'''
|
||||
modules_to_configure = [x[0] for x in Config.items('modules')]
|
||||
for name in modules_to_configure:
|
||||
if name not in self.mods:
|
||||
Logger.warning('Modules: Module <%s> not found' % name)
|
||||
continue
|
||||
self._configure_module(name)
|
||||
|
||||
def _configure_module(self, name):
|
||||
if 'module' not in self.mods[name]:
|
||||
try:
|
||||
self.import_module(name)
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
# convert configuration like:
|
||||
# -m mjpegserver:port=8080,fps=8
|
||||
# and pass it in context.config token
|
||||
config = dict()
|
||||
|
||||
args = Config.get('modules', name)
|
||||
if args != '':
|
||||
values = Config.get('modules', name).split(',')
|
||||
for value in values:
|
||||
x = value.split('=', 1)
|
||||
if len(x) == 1:
|
||||
config[x[0]] = True
|
||||
else:
|
||||
config[x[0]] = x[1]
|
||||
|
||||
self.mods[name]['context'].config = config
|
||||
|
||||
# call configure if module have one
|
||||
if hasattr(self.mods[name]['module'], 'configure'):
|
||||
self.mods[name]['module'].configure(config)
|
||||
|
||||
def usage_list(self):
|
||||
print('Available modules')
|
||||
print('=================')
|
||||
for module in sorted(self.list()):
|
||||
if 'module' not in self.mods[module]:
|
||||
self.import_module(module)
|
||||
|
||||
# ignore modules without docstring
|
||||
if not self.mods[module]['module'].__doc__:
|
||||
continue
|
||||
|
||||
text = self.mods[module]['module'].__doc__.strip("\n ")
|
||||
text = text.split('\n')
|
||||
# make sure we don't get IndexError along the way
|
||||
# then pretty format the header
|
||||
if len(text) > 2:
|
||||
if text[1].startswith('='):
|
||||
# '\n%-12s: %s' -> 12 spaces + ": "
|
||||
text[1] = '=' * (14 + len(text[1]))
|
||||
text = '\n'.join(text)
|
||||
print('\n%-12s: %s' % (module, text))
|
||||
|
||||
|
||||
Modules = ModuleBase()
|
||||
Modules.add_path(kivy.kivy_modules_dir)
|
||||
if 'KIVY_DOC' not in os.environ:
|
||||
Modules.add_path(kivy.kivy_usermodules_dir)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(Modules.list())
|
||||
BIN
kivy/modules/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/_webdebugger.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/_webdebugger.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/console.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/console.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/cursor.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/cursor.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/inspector.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/inspector.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/joycursor.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/joycursor.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/keybinding.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/keybinding.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/monitor.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/monitor.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/recorder.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/recorder.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/screen.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/screen.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/showborder.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/showborder.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/touchring.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/touchring.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/modules/__pycache__/webdebugger.cpython-310.pyc
Normal file
BIN
kivy/modules/__pycache__/webdebugger.cpython-310.pyc
Normal file
Binary file not shown.
200
kivy/modules/_webdebugger.py
Normal file
200
kivy/modules/_webdebugger.py
Normal file
File diff suppressed because one or more lines are too long
1059
kivy/modules/console.py
Normal file
1059
kivy/modules/console.py
Normal file
File diff suppressed because it is too large
Load Diff
75
kivy/modules/cursor.py
Normal file
75
kivy/modules/cursor.py
Normal file
@@ -0,0 +1,75 @@
|
||||
'''
|
||||
Cursor
|
||||
======
|
||||
|
||||
Shows a cursor following mouse motion events, useful on systems with no
|
||||
visible native mouse cursor.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
:Parameters:
|
||||
`texture`: str, defaults to
|
||||
'data/images/cursor.png' Image used to represent the cursor if
|
||||
displayed
|
||||
`size`: tuple, defaults to (40, 40)
|
||||
Apparent size of the mouse cursor, if displayed, (None,None) value
|
||||
will keep its real size.
|
||||
`offset`: tuple, defaults to (None, None)
|
||||
Offset of the texture image. The default value will align the
|
||||
top-left corner of the image to the mouse pos.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
In your configuration (`~/.kivy/config.ini`), you can add something like
|
||||
this::
|
||||
|
||||
[modules]
|
||||
cursor = texture=mypointer.png,size=20x20,offset=20x20
|
||||
|
||||
.. versionadded:: 1.10.0
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
from kivy.core.image import Image
|
||||
from kivy.graphics import Color, Rectangle
|
||||
from kivy import kivy_data_dir
|
||||
from kivy.compat import string_types
|
||||
from os.path import join
|
||||
from functools import partial
|
||||
|
||||
|
||||
def _mouse_move(texture, size, offset, win, pos, *args):
|
||||
if hasattr(win, '_cursor'):
|
||||
c = win._cursor
|
||||
else:
|
||||
with win.canvas.after:
|
||||
Color(1, 1, 1, 1, mode='rgba')
|
||||
win._cursor = c = Rectangle(texture=texture, size=size)
|
||||
|
||||
c.pos = pos[0] + offset[0], pos[1] - size[1] + offset[1]
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
cursor_texture = Image(
|
||||
ctx.config.get('texture', join(kivy_data_dir, 'images', 'cursor.png'))
|
||||
).texture
|
||||
cursor_size = ctx.config.get('size')
|
||||
if isinstance(cursor_size, string_types):
|
||||
cursor_size = [int(x) for x in cursor_size.split('x')]
|
||||
elif not cursor_size:
|
||||
cursor_size = cursor_texture.size
|
||||
|
||||
cursor_offset = ctx.config.get('offset', (0, 0))
|
||||
if isinstance(cursor_offset, string_types):
|
||||
cursor_offset = [int(x) for x in cursor_offset.split('x')]
|
||||
|
||||
win.bind(
|
||||
mouse_pos=partial(
|
||||
_mouse_move, cursor_texture, cursor_size, cursor_offset))
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
win.unbind(mouse_pos=_mouse_move)
|
||||
755
kivy/modules/inspector.py
Normal file
755
kivy/modules/inspector.py
Normal file
@@ -0,0 +1,755 @@
|
||||
'''
|
||||
Inspector
|
||||
=========
|
||||
|
||||
.. versionadded:: 1.0.9
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is highly experimental, use it with care.
|
||||
|
||||
The Inspector is a tool for finding a widget in the widget tree by clicking or
|
||||
tapping on it.
|
||||
Some keyboard shortcuts are activated:
|
||||
|
||||
* "Ctrl + e": activate / deactivate the inspector view
|
||||
* "Escape": cancel widget lookup first, then hide the inspector view
|
||||
|
||||
Available inspector interactions:
|
||||
|
||||
* tap once on a widget to select it without leaving inspect mode
|
||||
* double tap on a widget to select and leave inspect mode (then you can
|
||||
manipulate the widget again)
|
||||
|
||||
Some properties can be edited live. However, due to the delayed usage of
|
||||
some properties, it might crash if you don't handle all the cases.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For normal module usage, please see the :mod:`~kivy.modules` documentation.
|
||||
|
||||
The Inspector, however, can also be imported and used just like a normal
|
||||
python module. This has the added advantage of being able to activate and
|
||||
deactivate the module programmatically::
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.app import App
|
||||
from kivy.uix.button import Button
|
||||
from kivy.modules import inspector
|
||||
|
||||
class Demo(App):
|
||||
def build(self):
|
||||
button = Button(text="Test")
|
||||
inspector.create_inspector(Window, button)
|
||||
return button
|
||||
|
||||
Demo().run()
|
||||
|
||||
To remove the Inspector, you can do the following::
|
||||
|
||||
inspector.stop(Window, button)
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop', 'create_inspector')
|
||||
|
||||
import weakref
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
|
||||
from kivy.animation import Animation
|
||||
from kivy.logger import Logger
|
||||
from kivy.graphics.transformation import Matrix
|
||||
from kivy.clock import Clock
|
||||
from kivy.lang import Builder
|
||||
from kivy.factory import Factory
|
||||
from kivy.weakproxy import WeakProxy
|
||||
from kivy.properties import (
|
||||
ObjectProperty, BooleanProperty, ListProperty,
|
||||
NumericProperty, StringProperty, OptionProperty,
|
||||
ReferenceListProperty, AliasProperty, VariableListProperty
|
||||
)
|
||||
|
||||
Builder.load_string('''
|
||||
<Inspector>:
|
||||
layout: layout
|
||||
widgettree: widgettree
|
||||
treeview: treeview
|
||||
content: content
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
id: layout
|
||||
size_hint_y: None
|
||||
height: 250
|
||||
padding: 5
|
||||
spacing: 5
|
||||
top: 0
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgb: .4, .4, .4
|
||||
Rectangle:
|
||||
pos: self.x, self.top
|
||||
size: self.width, 1
|
||||
Color:
|
||||
rgba: .185, .18, .18, .95
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
|
||||
# Top Bar
|
||||
BoxLayout:
|
||||
size_hint_y: None
|
||||
height: 50
|
||||
spacing: 5
|
||||
Button:
|
||||
text: 'Move to Top'
|
||||
on_release: root.toggle_position(args[0])
|
||||
size_hint_x: None
|
||||
width: 120
|
||||
|
||||
ToggleButton:
|
||||
text: 'Inspect'
|
||||
on_state: root.inspect_enabled = args[1] == 'down'
|
||||
size_hint_x: None
|
||||
state: 'down' if root.inspect_enabled else 'normal'
|
||||
width: 80
|
||||
|
||||
Button:
|
||||
text: 'Parent'
|
||||
on_release:
|
||||
root.highlight_widget(root.widget.parent) if root.widget \
|
||||
else None
|
||||
size_hint_x: None
|
||||
width: 80
|
||||
|
||||
Button:
|
||||
text: '%r' % root.widget
|
||||
on_release: root.show_widget_info()
|
||||
|
||||
Button:
|
||||
text: 'X'
|
||||
size_hint_x: None
|
||||
width: 50
|
||||
on_release: root.activated = False
|
||||
|
||||
# Bottom Bar
|
||||
BoxLayout:
|
||||
ScrollView:
|
||||
scroll_type: ['bars', 'content']
|
||||
bar_width: 10
|
||||
size_hint_x: 0.0001
|
||||
|
||||
WidgetTree:
|
||||
id: widgettree
|
||||
hide_root: True
|
||||
size_hint: None, None
|
||||
height: self.minimum_height
|
||||
width: max(self.parent.width, self.minimum_width)
|
||||
selected_widget: root.widget
|
||||
on_select_widget: root.highlight_widget(args[1])
|
||||
|
||||
Splitter:
|
||||
sizeable_from: 'left'
|
||||
min_size: self.parent.width / 2
|
||||
max_size: self.parent.width
|
||||
|
||||
BoxLayout:
|
||||
ScrollView:
|
||||
scroll_type: ['bars', 'content']
|
||||
bar_width: 10
|
||||
TreeView:
|
||||
id: treeview
|
||||
size_hint_y: None
|
||||
hide_root: True
|
||||
height: self.minimum_height
|
||||
|
||||
Splitter:
|
||||
sizeable_from: 'left'
|
||||
keep_within_parent: True
|
||||
rescale_with_parent: True
|
||||
max_size: self.parent.width / 2
|
||||
min_size: 0
|
||||
|
||||
ScrollView:
|
||||
id: content
|
||||
|
||||
<TreeViewProperty>:
|
||||
height: max(lkey.texture_size[1], ltext.texture_size[1])
|
||||
Label:
|
||||
id: lkey
|
||||
text: root.key
|
||||
text_size: (self.width, None)
|
||||
width: 150
|
||||
size_hint_x: None
|
||||
Label:
|
||||
id: ltext
|
||||
text: [repr(getattr(root.widget, root.key, '')), root.refresh][0]\
|
||||
if root.widget else ''
|
||||
text_size: (self.width, None)
|
||||
|
||||
<-TreeViewWidget>:
|
||||
height: self.texture_size[1] + sp(4)
|
||||
size_hint_x: None
|
||||
width: self.texture_size[0] + sp(4)
|
||||
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: self.color_selected if self.is_selected else (0, 0, 0, 0)
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
Color:
|
||||
rgba: 1, 1, 1, int(not self.is_leaf)
|
||||
Rectangle:
|
||||
source:
|
||||
('atlas://data/images/defaulttheme/tree_%s' %
|
||||
('opened' if self.is_open else 'closed'))
|
||||
size: 16, 16
|
||||
pos: self.x - 20, self.center_y - 8
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba:
|
||||
(self.disabled_color if self.disabled else
|
||||
(self.color if not self.markup else (1, 1, 1, 1)))
|
||||
Rectangle:
|
||||
texture: self.texture
|
||||
size: self.texture_size
|
||||
pos:
|
||||
(int(self.center_x - self.texture_size[0] / 2.),
|
||||
int(self.center_y - self.texture_size[1] / 2.))
|
||||
''')
|
||||
|
||||
|
||||
class TreeViewProperty(Factory.BoxLayout, Factory.TreeViewNode):
|
||||
|
||||
widget_ref = ObjectProperty(None, allownone=True)
|
||||
|
||||
def _get_widget(self):
|
||||
wr = self.widget_ref
|
||||
if wr is None:
|
||||
return
|
||||
wr = wr()
|
||||
if wr is None:
|
||||
self.widget_ref = None
|
||||
return
|
||||
return wr
|
||||
widget = AliasProperty(_get_widget, None, bind=('widget_ref', ))
|
||||
|
||||
key = ObjectProperty(None, allownone=True)
|
||||
|
||||
inspector = ObjectProperty(None)
|
||||
|
||||
refresh = BooleanProperty(False)
|
||||
|
||||
|
||||
class TreeViewWidget(Factory.Label, Factory.TreeViewNode):
|
||||
widget = ObjectProperty(None)
|
||||
|
||||
|
||||
class WidgetTree(Factory.TreeView):
|
||||
selected_widget = ObjectProperty(None, allownone=True)
|
||||
|
||||
__events__ = ('on_select_widget',)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(WidgetTree, self).__init__(**kwargs)
|
||||
self.update_scroll = Clock.create_trigger(self._update_scroll)
|
||||
|
||||
def find_node_by_widget(self, widget):
|
||||
for node in self.iterate_all_nodes():
|
||||
if not node.parent_node:
|
||||
continue
|
||||
try:
|
||||
if node.widget == widget:
|
||||
return node
|
||||
except ReferenceError:
|
||||
pass
|
||||
return
|
||||
|
||||
def update_selected_widget(self, widget):
|
||||
if widget:
|
||||
node = self.find_node_by_widget(widget)
|
||||
if node:
|
||||
self.select_node(node, False)
|
||||
while node and isinstance(node, TreeViewWidget):
|
||||
if not node.is_open:
|
||||
self.toggle_node(node)
|
||||
node = node.parent_node
|
||||
|
||||
def on_selected_widget(self, inst, widget):
|
||||
if widget:
|
||||
self.update_selected_widget(widget)
|
||||
self.update_scroll()
|
||||
|
||||
def select_node(self, node, select_widget=True):
|
||||
super(WidgetTree, self).select_node(node)
|
||||
if select_widget:
|
||||
try:
|
||||
self.dispatch('on_select_widget', node.widget.__self__)
|
||||
except ReferenceError:
|
||||
pass
|
||||
|
||||
def on_select_widget(self, widget):
|
||||
pass
|
||||
|
||||
def _update_scroll(self, *args):
|
||||
node = self._selected_node
|
||||
if not node:
|
||||
return
|
||||
|
||||
self.parent.scroll_to(node)
|
||||
|
||||
|
||||
class Inspector(Factory.FloatLayout):
|
||||
|
||||
widget = ObjectProperty(None, allownone=True)
|
||||
|
||||
layout = ObjectProperty(None)
|
||||
|
||||
widgettree = ObjectProperty(None)
|
||||
|
||||
treeview = ObjectProperty(None)
|
||||
|
||||
inspect_enabled = BooleanProperty(False)
|
||||
|
||||
activated = BooleanProperty(False)
|
||||
|
||||
widget_info = BooleanProperty(False)
|
||||
|
||||
content = ObjectProperty(None)
|
||||
|
||||
at_bottom = BooleanProperty(True)
|
||||
|
||||
_update_widget_tree_ev = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.win = kwargs.pop('win', None)
|
||||
super(Inspector, self).__init__(**kwargs)
|
||||
self.avoid_bring_to_top = False
|
||||
with self.canvas.before:
|
||||
self.gcolor = Factory.Color(1, 0, 0, .25)
|
||||
Factory.PushMatrix()
|
||||
self.gtransform = Factory.Transform(Matrix())
|
||||
self.grect = Factory.Rectangle(size=(0, 0))
|
||||
Factory.PopMatrix()
|
||||
Clock.schedule_interval(self.update_widget_graphics, 0)
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
ret = super(Inspector, self).on_touch_down(touch)
|
||||
if (('button' not in touch.profile or touch.button == 'left') and
|
||||
not ret and self.inspect_enabled):
|
||||
self.highlight_at(*touch.pos)
|
||||
if touch.is_double_tap:
|
||||
self.inspect_enabled = False
|
||||
self.show_widget_info()
|
||||
ret = True
|
||||
return ret
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
ret = super(Inspector, self).on_touch_move(touch)
|
||||
if not ret and self.inspect_enabled:
|
||||
self.highlight_at(*touch.pos)
|
||||
ret = True
|
||||
return ret
|
||||
|
||||
def on_touch_up(self, touch):
|
||||
ret = super(Inspector, self).on_touch_up(touch)
|
||||
if not ret and self.inspect_enabled:
|
||||
ret = True
|
||||
return ret
|
||||
|
||||
def on_window_children(self, win, children):
|
||||
if self.avoid_bring_to_top or not self.activated:
|
||||
return
|
||||
self.avoid_bring_to_top = True
|
||||
win.remove_widget(self)
|
||||
win.add_widget(self)
|
||||
self.avoid_bring_to_top = False
|
||||
|
||||
def highlight_at(self, x, y):
|
||||
widget = None
|
||||
# reverse the loop - look at children on top first and
|
||||
# modalviews before others
|
||||
win_children = self.win.children
|
||||
children = chain(
|
||||
(c for c in win_children if isinstance(c, Factory.ModalView)),
|
||||
(
|
||||
c for c in reversed(win_children)
|
||||
if not isinstance(c, Factory.ModalView)
|
||||
)
|
||||
)
|
||||
for child in children:
|
||||
if child is self:
|
||||
continue
|
||||
widget = self.pick(child, x, y)
|
||||
if widget:
|
||||
break
|
||||
self.highlight_widget(widget)
|
||||
|
||||
def highlight_widget(self, widget, info=True, *largs):
|
||||
# no widget to highlight, reduce rectangle to 0, 0
|
||||
self.widget = widget
|
||||
if not widget:
|
||||
self.grect.size = 0, 0
|
||||
if self.widget_info and info:
|
||||
self.show_widget_info()
|
||||
|
||||
def update_widget_graphics(self, *largs):
|
||||
if not self.activated:
|
||||
return
|
||||
if self.widget is None:
|
||||
self.grect.size = 0, 0
|
||||
return
|
||||
self.grect.size = self.widget.size
|
||||
matrix = self.widget.get_window_matrix()
|
||||
if self.gtransform.matrix.get() != matrix.get():
|
||||
self.gtransform.matrix = matrix
|
||||
|
||||
def toggle_position(self, button):
|
||||
to_bottom = button.text == 'Move to Bottom'
|
||||
|
||||
if to_bottom:
|
||||
button.text = 'Move to Top'
|
||||
if self.widget_info:
|
||||
Animation(top=250, t='out_quad', d=.3).start(self.layout)
|
||||
else:
|
||||
Animation(top=60, t='out_quad', d=.3).start(self.layout)
|
||||
|
||||
bottom_bar = self.layout.children[1]
|
||||
self.layout.remove_widget(bottom_bar)
|
||||
self.layout.add_widget(bottom_bar)
|
||||
else:
|
||||
button.text = 'Move to Bottom'
|
||||
if self.widget_info:
|
||||
Animation(top=self.height, t='out_quad', d=.3).start(
|
||||
self.layout)
|
||||
else:
|
||||
Animation(y=self.height - 60, t='out_quad', d=.3).start(
|
||||
self.layout)
|
||||
|
||||
bottom_bar = self.layout.children[1]
|
||||
self.layout.remove_widget(bottom_bar)
|
||||
self.layout.add_widget(bottom_bar)
|
||||
self.at_bottom = to_bottom
|
||||
|
||||
def pick(self, widget, x, y):
|
||||
ret = None
|
||||
# try to filter widgets that are not visible (invalid inspect target)
|
||||
if (hasattr(widget, 'visible') and not widget.visible):
|
||||
return ret
|
||||
if widget.collide_point(x, y):
|
||||
ret = widget
|
||||
x2, y2 = widget.to_local(x, y)
|
||||
# reverse the loop - look at children on top first
|
||||
for child in reversed(widget.children):
|
||||
ret = self.pick(child, x2, y2) or ret
|
||||
return ret
|
||||
|
||||
def on_activated(self, instance, activated):
|
||||
if not activated:
|
||||
self.grect.size = 0, 0
|
||||
if self.at_bottom:
|
||||
anim = Animation(top=0, t='out_quad', d=.3)
|
||||
else:
|
||||
anim = Animation(y=self.height, t='out_quad', d=.3)
|
||||
anim.bind(on_complete=self.animation_close)
|
||||
anim.start(self.layout)
|
||||
self.widget = None
|
||||
self.widget_info = False
|
||||
else:
|
||||
self.win.add_widget(self)
|
||||
Logger.info('Inspector: inspector activated')
|
||||
if self.at_bottom:
|
||||
Animation(top=60, t='out_quad', d=.3).start(self.layout)
|
||||
else:
|
||||
Animation(y=self.height - 60, t='out_quad', d=.3).start(
|
||||
self.layout)
|
||||
ev = self._update_widget_tree_ev
|
||||
if ev is None:
|
||||
ev = self._update_widget_tree_ev = Clock.schedule_interval(
|
||||
self.update_widget_tree, 1)
|
||||
else:
|
||||
ev()
|
||||
self.update_widget_tree()
|
||||
|
||||
def animation_close(self, instance, value):
|
||||
if not self.activated:
|
||||
self.inspect_enabled = False
|
||||
self.win.remove_widget(self)
|
||||
self.content.clear_widgets()
|
||||
treeview = self.treeview
|
||||
for node in list(treeview.iterate_all_nodes()):
|
||||
node.widget_ref = None
|
||||
treeview.remove_node(node)
|
||||
|
||||
self._window_node = None
|
||||
if self._update_widget_tree_ev is not None:
|
||||
self._update_widget_tree_ev.cancel()
|
||||
|
||||
widgettree = self.widgettree
|
||||
for node in list(widgettree.iterate_all_nodes()):
|
||||
widgettree.remove_node(node)
|
||||
Logger.info('Inspector: inspector deactivated')
|
||||
|
||||
def show_widget_info(self):
|
||||
self.content.clear_widgets()
|
||||
widget = self.widget
|
||||
treeview = self.treeview
|
||||
for node in list(treeview.iterate_all_nodes())[:]:
|
||||
node.widget_ref = None
|
||||
treeview.remove_node(node)
|
||||
if not widget:
|
||||
if self.at_bottom:
|
||||
Animation(top=60, t='out_quad', d=.3).start(self.layout)
|
||||
else:
|
||||
Animation(y=self.height - 60, t='out_quad', d=.3).start(
|
||||
self.layout)
|
||||
self.widget_info = False
|
||||
return
|
||||
self.widget_info = True
|
||||
if self.at_bottom:
|
||||
Animation(top=250, t='out_quad', d=.3).start(self.layout)
|
||||
else:
|
||||
Animation(top=self.height, t='out_quad', d=.3).start(self.layout)
|
||||
for node in list(treeview.iterate_all_nodes())[:]:
|
||||
treeview.remove_node(node)
|
||||
|
||||
keys = list(widget.properties().keys())
|
||||
keys.sort()
|
||||
node = None
|
||||
if type(widget) is WeakProxy:
|
||||
wk_widget = widget.__ref__
|
||||
else:
|
||||
wk_widget = weakref.ref(widget)
|
||||
for key in keys:
|
||||
node = TreeViewProperty(key=key, widget_ref=wk_widget)
|
||||
node.bind(is_selected=self.show_property)
|
||||
try:
|
||||
widget.bind(**{key: partial(
|
||||
self.update_node_content, weakref.ref(node))})
|
||||
except:
|
||||
pass
|
||||
treeview.add_node(node)
|
||||
|
||||
def update_node_content(self, node, *largs):
|
||||
node = node()
|
||||
if node is None:
|
||||
return
|
||||
node.refresh = True
|
||||
node.refresh = False
|
||||
|
||||
def keyboard_shortcut(self, win, scancode, *largs):
|
||||
modifiers = largs[-1]
|
||||
if scancode == 101 and set(modifiers) & {'ctrl'} and not set(
|
||||
modifiers) & {'shift', 'alt', 'meta'}:
|
||||
self.activated = not self.activated
|
||||
if self.activated:
|
||||
self.inspect_enabled = True
|
||||
return True
|
||||
elif scancode == 27:
|
||||
if self.inspect_enabled:
|
||||
self.inspect_enabled = False
|
||||
return True
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
return True
|
||||
|
||||
def show_property(self, instance, value, key=None, index=-1, *largs):
|
||||
# normal call: (tree node, focus, )
|
||||
# nested call: (widget, prop value, prop key, index in dict/list)
|
||||
if value is False:
|
||||
return
|
||||
|
||||
content = None
|
||||
if key is None:
|
||||
# normal call
|
||||
nested = False
|
||||
widget = instance.widget
|
||||
key = instance.key
|
||||
prop = widget.property(key)
|
||||
value = getattr(widget, key)
|
||||
else:
|
||||
# nested call, we might edit subvalue
|
||||
nested = True
|
||||
widget = instance
|
||||
prop = None
|
||||
|
||||
dtype = None
|
||||
|
||||
if isinstance(prop, AliasProperty) or nested:
|
||||
# trying to resolve type dynamically
|
||||
if type(value) in (str, str):
|
||||
dtype = 'string'
|
||||
elif type(value) in (int, float):
|
||||
dtype = 'numeric'
|
||||
elif type(value) in (tuple, list):
|
||||
dtype = 'list'
|
||||
|
||||
if isinstance(prop, NumericProperty) or dtype == 'numeric':
|
||||
content = Factory.TextInput(text=str(value) or '', multiline=False)
|
||||
content.bind(text=partial(
|
||||
self.save_property_numeric, widget, key, index))
|
||||
elif isinstance(prop, StringProperty) or dtype == 'string':
|
||||
content = Factory.TextInput(text=value or '', multiline=True)
|
||||
content.bind(text=partial(
|
||||
self.save_property_text, widget, key, index))
|
||||
elif (isinstance(prop, ListProperty) or
|
||||
isinstance(prop, ReferenceListProperty) or
|
||||
isinstance(prop, VariableListProperty) or
|
||||
dtype == 'list'):
|
||||
content = Factory.GridLayout(cols=1, size_hint_y=None)
|
||||
content.bind(minimum_height=content.setter('height'))
|
||||
for i, item in enumerate(value):
|
||||
button = Factory.Button(
|
||||
text=repr(item),
|
||||
size_hint_y=None,
|
||||
height=44
|
||||
)
|
||||
if isinstance(item, Factory.Widget):
|
||||
button.bind(on_release=partial(self.highlight_widget, item,
|
||||
False))
|
||||
else:
|
||||
button.bind(on_release=partial(self.show_property, widget,
|
||||
item, key, i))
|
||||
content.add_widget(button)
|
||||
elif isinstance(prop, OptionProperty):
|
||||
content = Factory.GridLayout(cols=1, size_hint_y=None)
|
||||
content.bind(minimum_height=content.setter('height'))
|
||||
for option in prop.options:
|
||||
button = Factory.ToggleButton(
|
||||
text=option,
|
||||
state='down' if option == value else 'normal',
|
||||
group=repr(content.uid), size_hint_y=None,
|
||||
height=44)
|
||||
button.bind(on_press=partial(
|
||||
self.save_property_option, widget, key))
|
||||
content.add_widget(button)
|
||||
elif isinstance(prop, ObjectProperty):
|
||||
if isinstance(value, Factory.Widget):
|
||||
content = Factory.Button(text=repr(value))
|
||||
content.bind(on_release=partial(self.highlight_widget, value))
|
||||
elif isinstance(value, Factory.Texture):
|
||||
content = Factory.Image(texture=value)
|
||||
else:
|
||||
content = Factory.Label(text=repr(value))
|
||||
|
||||
elif isinstance(prop, BooleanProperty):
|
||||
state = 'down' if value else 'normal'
|
||||
content = Factory.ToggleButton(text=key, state=state)
|
||||
content.bind(on_release=partial(self.save_property_boolean, widget,
|
||||
key, index))
|
||||
|
||||
self.content.clear_widgets()
|
||||
if content:
|
||||
self.content.add_widget(content)
|
||||
|
||||
def save_property_numeric(self, widget, key, index, instance, value):
|
||||
try:
|
||||
if index >= 0:
|
||||
getattr(widget, key)[index] = float(instance.text)
|
||||
else:
|
||||
setattr(widget, key, float(instance.text))
|
||||
except:
|
||||
pass
|
||||
|
||||
def save_property_text(self, widget, key, index, instance, value):
|
||||
try:
|
||||
if index >= 0:
|
||||
getattr(widget, key)[index] = instance.text
|
||||
else:
|
||||
setattr(widget, key, instance.text)
|
||||
except:
|
||||
pass
|
||||
|
||||
def save_property_boolean(self, widget, key, index, instance, ):
|
||||
try:
|
||||
value = instance.state == 'down'
|
||||
if index >= 0:
|
||||
getattr(widget, key)[index] = value
|
||||
else:
|
||||
setattr(widget, key, value)
|
||||
except:
|
||||
pass
|
||||
|
||||
def save_property_option(self, widget, key, instance, *largs):
|
||||
try:
|
||||
setattr(widget, key, instance.text)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _update_widget_tree_node(self, node, widget, is_open=False):
|
||||
tree = self.widgettree
|
||||
update_nodes = []
|
||||
nodes = {}
|
||||
for cnode in node.nodes[:]:
|
||||
try:
|
||||
nodes[cnode.widget] = cnode
|
||||
except ReferenceError:
|
||||
# widget no longer exists, just remove it
|
||||
pass
|
||||
tree.remove_node(cnode)
|
||||
for child in widget.children:
|
||||
if child is self:
|
||||
continue
|
||||
if child in nodes:
|
||||
cnode = tree.add_node(nodes[child], node)
|
||||
else:
|
||||
cnode = tree.add_node(TreeViewWidget(
|
||||
text=child.__class__.__name__, widget=child.proxy_ref,
|
||||
is_open=is_open), node)
|
||||
update_nodes.append((cnode, child))
|
||||
return update_nodes
|
||||
|
||||
def update_widget_tree(self, *args):
|
||||
if not hasattr(self, '_window_node') or not self._window_node:
|
||||
self._window_node = self.widgettree.add_node(
|
||||
TreeViewWidget(text='Window', widget=self.win, is_open=True))
|
||||
|
||||
nodes = self._update_widget_tree_node(self._window_node, self.win,
|
||||
is_open=True)
|
||||
while nodes:
|
||||
ntmp = nodes[:]
|
||||
nodes = []
|
||||
for node in ntmp:
|
||||
nodes += self._update_widget_tree_node(*node)
|
||||
|
||||
self.widgettree.update_selected_widget(self.widget)
|
||||
|
||||
|
||||
def create_inspector(win, ctx, *largs):
|
||||
'''Create an Inspector instance attached to the *ctx* and bound to the
|
||||
Window's :meth:`~kivy.core.window.WindowBase.on_keyboard` event for
|
||||
capturing the keyboard shortcut.
|
||||
|
||||
:Parameters:
|
||||
`win`: A :class:`Window <kivy.core.window.WindowBase>`
|
||||
The application Window to bind to.
|
||||
`ctx`: A :class:`~kivy.uix.widget.Widget` or subclass
|
||||
The Widget to be inspected.
|
||||
|
||||
'''
|
||||
# Dunno why, but if we are creating inspector within the start(), no lang
|
||||
# rules are applied.
|
||||
ctx.inspector = Inspector(win=win)
|
||||
win.bind(children=ctx.inspector.on_window_children,
|
||||
on_keyboard=ctx.inspector.keyboard_shortcut)
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
ctx.ev_late_create = Clock.schedule_once(
|
||||
partial(create_inspector, win, ctx))
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
'''Stop and unload any active Inspectors for the given *ctx*.'''
|
||||
if hasattr(ctx, 'ev_late_create'):
|
||||
ctx.ev_late_create.cancel()
|
||||
del ctx.ev_late_create
|
||||
if hasattr(ctx, 'inspector'):
|
||||
win.unbind(children=ctx.inspector.on_window_children,
|
||||
on_keyboard=ctx.inspector.keyboard_shortcut)
|
||||
win.remove_widget(ctx.inspector)
|
||||
del ctx.inspector
|
||||
299
kivy/modules/joycursor.py
Normal file
299
kivy/modules/joycursor.py
Normal file
@@ -0,0 +1,299 @@
|
||||
'''
|
||||
JoyCursor
|
||||
=========
|
||||
|
||||
.. versionadded:: 1.10.0
|
||||
|
||||
The JoyCursor is a tool for navigating with a joystick as if using a mouse
|
||||
or touch. Most of the actions that are possible for a mouse user are available
|
||||
in this module.
|
||||
|
||||
For example:
|
||||
|
||||
* left click
|
||||
* right click
|
||||
* double click (two clicks)
|
||||
* moving the cursor
|
||||
* holding the button (+ moving at the same time)
|
||||
* selecting
|
||||
* scrolling
|
||||
|
||||
There are some properties that can be edited live, such as intensity of the
|
||||
JoyCursor movement and toggling mouse button holding.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For normal module usage, please see the :mod:`~kivy.modules` documentation
|
||||
and these bindings:
|
||||
|
||||
+------------------+--------------------+
|
||||
| Event | Joystick |
|
||||
+==================+====================+
|
||||
| cursor move | Axis 3, Axis 4 |
|
||||
+------------------+--------------------+
|
||||
| cursor intensity | Button 0, Button 1 |
|
||||
+------------------+--------------------+
|
||||
| left click | Button 2 |
|
||||
+------------------+--------------------+
|
||||
| right click | Button 3 |
|
||||
+------------------+--------------------+
|
||||
| scroll up | Button 4 |
|
||||
+------------------+--------------------+
|
||||
| scroll down | Button 5 |
|
||||
+------------------+--------------------+
|
||||
| hold button | Button 6 |
|
||||
+------------------+--------------------+
|
||||
| joycursor on/off | Button 7 |
|
||||
+------------------+--------------------+
|
||||
|
||||
The JoyCursor, like Inspector, can also be imported and used as a normal
|
||||
python module. This has the added advantage of being able to activate and
|
||||
deactivate the module programmatically::
|
||||
|
||||
from kivy.lang import Builder
|
||||
from kivy.base import runTouchApp
|
||||
runTouchApp(Builder.load_string("""
|
||||
#:import jc kivy.modules.joycursor
|
||||
BoxLayout:
|
||||
Button:
|
||||
text: 'Press & activate with Ctrl+E or Button 7'
|
||||
on_release: jc.create_joycursor(root.parent, root)
|
||||
Button:
|
||||
text: 'Disable'
|
||||
on_release: jc.stop(root.parent, root)
|
||||
"""))
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop', 'create_joycursor')
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.logger import Logger
|
||||
from kivy.uix.widget import Widget
|
||||
from kivy.graphics import Color, Line
|
||||
from kivy.properties import (
|
||||
ObjectProperty,
|
||||
NumericProperty,
|
||||
BooleanProperty
|
||||
)
|
||||
|
||||
|
||||
class JoyCursor(Widget):
|
||||
win = ObjectProperty()
|
||||
activated = BooleanProperty(False)
|
||||
cursor_width = NumericProperty(1.1)
|
||||
cursor_hold = BooleanProperty(False)
|
||||
intensity = NumericProperty(4)
|
||||
dead_zone = NumericProperty(10000)
|
||||
offset_x = NumericProperty(0)
|
||||
offset_y = NumericProperty(0)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(JoyCursor, self).__init__(**kwargs)
|
||||
self.avoid_bring_to_top = False
|
||||
self.size_hint = (None, None)
|
||||
self.size = (21, 21)
|
||||
self.set_cursor()
|
||||
|
||||
# draw cursor
|
||||
with self.canvas:
|
||||
Color(rgba=(0.19, 0.64, 0.81, 0.5))
|
||||
self.cursor_ox = Line(
|
||||
points=self.cursor_pts[:4],
|
||||
width=self.cursor_width + 0.1
|
||||
)
|
||||
self.cursor_oy = Line(
|
||||
points=self.cursor_pts[4:],
|
||||
width=self.cursor_width + 0.1
|
||||
)
|
||||
Color(rgba=(1, 1, 1, 0.5))
|
||||
self.cursor_x = Line(
|
||||
points=self.cursor_pts[:4],
|
||||
width=self.cursor_width
|
||||
)
|
||||
self.cursor_y = Line(
|
||||
points=self.cursor_pts[4:],
|
||||
width=self.cursor_width
|
||||
)
|
||||
self.pos = [-i for i in self.size]
|
||||
|
||||
def on_window_children(self, win, *args):
|
||||
# pull JoyCursor to the front when added
|
||||
# as a child directly to the window.
|
||||
if self.avoid_bring_to_top or not self.activated:
|
||||
return
|
||||
self.avoid_bring_to_top = True
|
||||
win.remove_widget(self)
|
||||
win.add_widget(self)
|
||||
self.avoid_bring_to_top = False
|
||||
|
||||
def on_activated(self, instance, activated):
|
||||
# bind/unbind when JoyCursor's state is changed
|
||||
if activated:
|
||||
self.win.add_widget(self)
|
||||
self.move = Clock.schedule_interval(self.move_cursor, 0)
|
||||
self.win.fbind('on_joy_axis', self.check_cursor)
|
||||
self.win.fbind('on_joy_button_down', self.set_intensity)
|
||||
self.win.fbind('on_joy_button_down', self.check_dispatch)
|
||||
self.win.fbind('mouse_pos', self.stop_cursor)
|
||||
mouse_pos = self.win.mouse_pos
|
||||
self.pos = (
|
||||
mouse_pos[0] - self.size[0] / 2.0,
|
||||
mouse_pos[1] - self.size[1] / 2.0
|
||||
)
|
||||
Logger.info('JoyCursor: joycursor activated')
|
||||
else:
|
||||
self.pos = [-i for i in self.size]
|
||||
Clock.unschedule(self.move)
|
||||
self.win.funbind('on_joy_axis', self.check_cursor)
|
||||
self.win.funbind('on_joy_button_down', self.set_intensity)
|
||||
self.win.funbind('on_joy_button_down', self.check_dispatch)
|
||||
self.win.funbind('mouse_pos', self.stop_cursor)
|
||||
self.win.remove_widget(self)
|
||||
Logger.info('JoyCursor: joycursor deactivated')
|
||||
|
||||
def set_cursor(self, *args):
|
||||
# create cursor points
|
||||
px, py = self.pos
|
||||
sx, sy = self.size
|
||||
self.cursor_pts = [
|
||||
px, py + round(sy / 2.0), px + sx, py + round(sy / 2.0),
|
||||
px + round(sx / 2.0), py, px + round(sx / 2.0), py + sy
|
||||
]
|
||||
|
||||
def check_cursor(self, win, stickid, axisid, value):
|
||||
# check axes and set offset if a movement is registered
|
||||
intensity = self.intensity
|
||||
dead = self.dead_zone
|
||||
|
||||
if axisid == 3:
|
||||
if value < -dead:
|
||||
self.offset_x = -intensity
|
||||
elif value > dead:
|
||||
self.offset_x = intensity
|
||||
else:
|
||||
self.offset_x = 0
|
||||
elif axisid == 4:
|
||||
# invert Y axis to behave like mouse
|
||||
if value < -dead:
|
||||
self.offset_y = intensity
|
||||
elif value > dead:
|
||||
self.offset_y = -intensity
|
||||
else:
|
||||
self.offset_y = 0
|
||||
else:
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
|
||||
def set_intensity(self, win, stickid, buttonid):
|
||||
# set intensity of joycursor with joystick buttons
|
||||
intensity = self.intensity
|
||||
if buttonid == 0 and intensity > 2:
|
||||
intensity -= 1
|
||||
elif buttonid == 1:
|
||||
intensity += 1
|
||||
self.intensity = intensity
|
||||
|
||||
def check_dispatch(self, win, stickid, buttonid):
|
||||
if buttonid == 6:
|
||||
self.cursor_hold = not self.cursor_hold
|
||||
if buttonid not in (2, 3, 4, 5, 6):
|
||||
return
|
||||
|
||||
x, y = self.center
|
||||
# window event, correction necessary
|
||||
y = self.win.system_size[1] - y
|
||||
modifiers = []
|
||||
actions = {
|
||||
2: 'left',
|
||||
3: 'right',
|
||||
4: 'scrollup',
|
||||
5: 'scrolldown',
|
||||
6: 'left'
|
||||
}
|
||||
button = actions[buttonid]
|
||||
|
||||
self.win.dispatch('on_mouse_down', x, y, button, modifiers)
|
||||
if not self.cursor_hold:
|
||||
self.win.dispatch('on_mouse_up', x, y, button, modifiers)
|
||||
|
||||
def move_cursor(self, *args):
|
||||
# move joycursor as a mouse
|
||||
self.pos[0] += self.offset_x
|
||||
self.pos[1] += self.offset_y
|
||||
modifiers = []
|
||||
if self.cursor_hold:
|
||||
self.win.dispatch(
|
||||
'on_mouse_move',
|
||||
self.center[0],
|
||||
self.win.system_size[1] - self.center[1],
|
||||
modifiers
|
||||
)
|
||||
|
||||
def stop_cursor(self, instance, mouse_pos):
|
||||
# pin the cursor to the mouse pos
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
self.pos = (
|
||||
mouse_pos[0] - self.size[0] / 2.0,
|
||||
mouse_pos[1] - self.size[1] / 2.0
|
||||
)
|
||||
|
||||
def on_pos(self, instance, new_pos):
|
||||
self.set_cursor()
|
||||
self.cursor_x.points = self.cursor_pts[:4]
|
||||
self.cursor_y.points = self.cursor_pts[4:]
|
||||
self.cursor_ox.points = self.cursor_pts[:4]
|
||||
self.cursor_oy.points = self.cursor_pts[4:]
|
||||
|
||||
def keyboard_shortcuts(self, win, scancode, *args):
|
||||
modifiers = args[-1]
|
||||
if scancode == 101 and modifiers == ['ctrl']:
|
||||
self.activated = not self.activated
|
||||
return True
|
||||
elif scancode == 27:
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
return True
|
||||
|
||||
def joystick_shortcuts(self, win, stickid, buttonid):
|
||||
if buttonid == 7:
|
||||
self.activated = not self.activated
|
||||
if self.activated:
|
||||
self.pos = [round(i / 2.0) for i in win.size]
|
||||
|
||||
|
||||
def create_joycursor(win, ctx, *args):
|
||||
'''Create a JoyCursor instance attached to the *ctx* and bound to the
|
||||
Window's :meth:`~kivy.core.window.WindowBase.on_keyboard` event for
|
||||
capturing the keyboard shortcuts.
|
||||
|
||||
:Parameters:
|
||||
`win`: A :class:`Window <kivy.core.window.WindowBase>`
|
||||
The application Window to bind to.
|
||||
`ctx`: A :class:`~kivy.uix.widget.Widget` or subclass
|
||||
The Widget for JoyCursor to attach to.
|
||||
|
||||
'''
|
||||
ctx.joycursor = JoyCursor(win=win)
|
||||
win.bind(children=ctx.joycursor.on_window_children,
|
||||
on_keyboard=ctx.joycursor.keyboard_shortcuts)
|
||||
# always listen for joystick input to open the module
|
||||
# (like a keyboard listener)
|
||||
win.fbind('on_joy_button_down', ctx.joycursor.joystick_shortcuts)
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
Clock.schedule_once(lambda *t: create_joycursor(win, ctx))
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
'''Stop and unload any active JoyCursors for the given *ctx*.
|
||||
'''
|
||||
if hasattr(ctx, 'joycursor'):
|
||||
ctx.joycursor.activated = False
|
||||
win.unbind(children=ctx.joycursor.on_window_children,
|
||||
on_keyboard=ctx.joycursor.keyboard_shortcuts)
|
||||
win.funbind('on_joy_button_down', ctx.joycursor.joystick_shortcuts)
|
||||
win.remove_widget(ctx.joycursor)
|
||||
del ctx.joycursor
|
||||
64
kivy/modules/keybinding.py
Normal file
64
kivy/modules/keybinding.py
Normal file
@@ -0,0 +1,64 @@
|
||||
'''Keybinding
|
||||
==========
|
||||
|
||||
This module forces the mapping of some keys to functions:
|
||||
|
||||
* F11: Rotate the Window through 0, 90, 180 and 270 degrees
|
||||
* Shift + F11: Switches between portrait and landscape on desktops
|
||||
* F12: Take a screenshot
|
||||
|
||||
Note: this doesn't work if the application requests the keyboard beforehand.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For normal module usage, please see the :mod:`~kivy.modules` documentation.
|
||||
|
||||
The Keybinding module, however, can also be imported and used just
|
||||
like a normal python module. This has the added advantage of being
|
||||
able to activate and deactivate the module programmatically::
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.uix.button import Button
|
||||
from kivy.modules import keybinding
|
||||
from kivy.core.window import Window
|
||||
|
||||
class Demo(App):
|
||||
|
||||
def build(self):
|
||||
button = Button(text="Hello")
|
||||
keybinding.start(Window, button)
|
||||
return button
|
||||
|
||||
Demo().run()
|
||||
|
||||
To remove the Keybinding, you can do the following::
|
||||
|
||||
Keybinding.stop(Window, button)
|
||||
|
||||
'''
|
||||
|
||||
from kivy.utils import platform
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
|
||||
def _on_keyboard_handler(instance, key, scancode, codepoint, modifiers):
|
||||
if key == 293 and modifiers == []: # F12
|
||||
instance.screenshot()
|
||||
elif key == 292 and modifiers == []: # F11
|
||||
instance.rotation += 90
|
||||
elif key == 292 and modifiers == ['shift']: # Shift + F11
|
||||
if platform in ('win', 'linux', 'macosx'):
|
||||
instance.rotation = 0
|
||||
w, h = instance.size
|
||||
w, h = h, w
|
||||
instance.size = (w, h)
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
win.bind(on_keyboard=_on_keyboard_handler)
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
win.unbind(on_keyboard=_on_keyboard_handler)
|
||||
88
kivy/modules/monitor.py
Normal file
88
kivy/modules/monitor.py
Normal file
@@ -0,0 +1,88 @@
|
||||
'''
|
||||
Monitor module
|
||||
==============
|
||||
|
||||
The Monitor module is a toolbar that shows the activity of your current
|
||||
application :
|
||||
|
||||
* FPS
|
||||
* Graph of input events
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For normal module usage, please see the :mod:`~kivy.modules` documentation.
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
from kivy.uix.label import Label
|
||||
from kivy.graphics import Rectangle, Color
|
||||
from kivy.clock import Clock
|
||||
from functools import partial
|
||||
|
||||
_statsinput = 0
|
||||
_maxinput = -1
|
||||
|
||||
|
||||
def update_fps(ctx, *largs):
|
||||
ctx.label.text = 'FPS: %f' % Clock.get_fps()
|
||||
ctx.rectangle.texture = ctx.label.texture
|
||||
ctx.rectangle.size = ctx.label.texture_size
|
||||
|
||||
|
||||
def update_stats(win, ctx, *largs):
|
||||
global _statsinput
|
||||
ctx.stats = ctx.stats[1:] + [_statsinput]
|
||||
_statsinput = 0
|
||||
m = max(1., _maxinput)
|
||||
for i, x in enumerate(ctx.stats):
|
||||
ctx.statsr[i].size = (4, ctx.stats[i] / m * 20)
|
||||
ctx.statsr[i].pos = (win.width - 64 * 4 + i * 4, win.height - 25)
|
||||
|
||||
|
||||
def _update_monitor_canvas(win, ctx, *largs):
|
||||
with win.canvas.after:
|
||||
ctx.overlay.pos = (0, win.height - 25)
|
||||
ctx.overlay.size = (win.width, 25)
|
||||
ctx.rectangle.pos = (5, win.height - 20)
|
||||
|
||||
|
||||
class StatsInput(object):
|
||||
def process(self, events):
|
||||
global _statsinput, _maxinput
|
||||
_statsinput += len(events)
|
||||
if _statsinput > _maxinput:
|
||||
_maxinput = float(_statsinput)
|
||||
return events
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
# late import to avoid breaking module loading
|
||||
from kivy.input.postproc import kivy_postproc_modules
|
||||
kivy_postproc_modules['fps'] = StatsInput()
|
||||
global _ctx
|
||||
ctx.label = Label(text='FPS: 0.0')
|
||||
ctx.inputstats = 0
|
||||
ctx.stats = []
|
||||
ctx.statsr = []
|
||||
with win.canvas.after:
|
||||
ctx.color = Color(1, 0, 0, .5)
|
||||
ctx.overlay = Rectangle(pos=(0, win.height - 25),
|
||||
size=(win.width, 25))
|
||||
ctx.color = Color(1, 1, 1)
|
||||
ctx.rectangle = Rectangle(pos=(5, win.height - 20))
|
||||
ctx.color = Color(1, 1, 1, .5)
|
||||
for i in range(64):
|
||||
ctx.stats.append(0)
|
||||
ctx.statsr.append(
|
||||
Rectangle(pos=(win.width - 64 * 4 + i * 4, win.height - 25),
|
||||
size=(4, 0)))
|
||||
win.bind(size=partial(_update_monitor_canvas, win, ctx))
|
||||
Clock.schedule_interval(partial(update_fps, ctx), .5)
|
||||
Clock.schedule_interval(partial(update_stats, win, ctx), 1 / 60.)
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
win.canvas.remove(ctx.label)
|
||||
94
kivy/modules/recorder.py
Normal file
94
kivy/modules/recorder.py
Normal file
@@ -0,0 +1,94 @@
|
||||
'''
|
||||
Recorder module
|
||||
===============
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Create an instance of :class:`~kivy.input.recorder.Recorder`, attach to the
|
||||
class, and bind some keys to record / play sequences:
|
||||
|
||||
- F6: play the last record in a loop
|
||||
- F7: read the latest recording
|
||||
- F8: record input events
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. |attrs| replace:: :attr:`~kivy.input.recorder.Recorder.record_attrs`
|
||||
.. |profile_mask| replace::
|
||||
:attr:`~kivy.input.recorder.Recorder.record_profile_mask`
|
||||
|
||||
:Parameters:
|
||||
`attrs`: str, defaults to |attrs| value.
|
||||
Attributes to record from the motion event
|
||||
`profile_mask`: str, defaults to |profile_mask| value.
|
||||
Mask for motion event profile. Used to filter which profile will appear
|
||||
in the fake motion event when replayed.
|
||||
`filename`: str, defaults to 'recorder.kvi'
|
||||
Name of the file to record / play with
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
For normal module usage, please see the :mod:`~kivy.modules` documentation.
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
from kivy.logger import Logger
|
||||
from functools import partial
|
||||
|
||||
|
||||
def replay(recorder, *args):
|
||||
if recorder.play:
|
||||
return
|
||||
else:
|
||||
recorder.play = True
|
||||
|
||||
|
||||
def on_recorder_key(recorder, window, key, *largs):
|
||||
if key == 289: # F8
|
||||
if recorder.play:
|
||||
Logger.error('Recorder: Cannot start recording while playing.')
|
||||
return
|
||||
recorder.record = not recorder.record
|
||||
elif key == 288: # F7
|
||||
if recorder.record:
|
||||
Logger.error('Recorder: Cannot start playing while recording.')
|
||||
return
|
||||
recorder.play = not recorder.play
|
||||
elif key == 287: # F6
|
||||
if recorder.play:
|
||||
recorder.unbind(play=replay)
|
||||
else:
|
||||
recorder.bind(play=replay)
|
||||
recorder.play = True
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
keys = {}
|
||||
|
||||
# attributes
|
||||
value = ctx.config.get('attrs', None)
|
||||
if value is not None:
|
||||
keys['record_attrs'] = value.split(':')
|
||||
|
||||
# profile mask
|
||||
value = ctx.config.get('profile_mask', None)
|
||||
if value is not None:
|
||||
keys['record_profile_mask'] = value.split(':')
|
||||
|
||||
# filename
|
||||
value = ctx.config.get('filename', None)
|
||||
if value is not None:
|
||||
keys['filename'] = value
|
||||
|
||||
from kivy.input.recorder import Recorder
|
||||
ctx.recorder = Recorder(window=win, **keys)
|
||||
win.bind(on_key_down=partial(on_recorder_key, ctx.recorder))
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
if hasattr(ctx, 'recorder'):
|
||||
ctx.recorder.release()
|
||||
183
kivy/modules/screen.py
Normal file
183
kivy/modules/screen.py
Normal file
@@ -0,0 +1,183 @@
|
||||
'''Screen
|
||||
======
|
||||
|
||||
This module changes some environment and configuration variables
|
||||
to match the density / dpi / screensize of a specific device.
|
||||
|
||||
To see a list of the available screenid's, just run::
|
||||
|
||||
python main.py -m screen
|
||||
|
||||
To simulate a medium-density screen such as the Motorola Droid 2::
|
||||
|
||||
python main.py -m screen:droid2
|
||||
|
||||
To simulate a high-density screen such as HTC One X, in portrait::
|
||||
|
||||
python main.py -m screen:onex,portrait
|
||||
|
||||
To simulate the iPad 2 screen::
|
||||
|
||||
python main.py -m screen:ipad
|
||||
|
||||
If the generated window is too large, you can specify a scale::
|
||||
|
||||
python main.py -m screen:note2,portrait,scale=.75
|
||||
|
||||
Note that to display your contents correctly on a scaled window you
|
||||
must consistently use units 'dp' and 'sp' throughout your app. See
|
||||
:mod:`~kiv.metrics` for more details.
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
from os import environ
|
||||
from kivy.config import Config
|
||||
from kivy.logger import Logger
|
||||
|
||||
# taken from http://en.wikipedia.org/wiki/List_of_displays_by_pixel_density
|
||||
devices = {
|
||||
# device: (name, width, height, dpi, density)
|
||||
'onex': ('HTC One X', 1280, 720, 312, 2),
|
||||
'one': ('HTC One', 1920, 1080, 468, 3),
|
||||
'onesv': ('HTC One SV', 800, 480, 216, 1.5),
|
||||
's3': ('Galaxy SIII', 1280, 720, 306, 2),
|
||||
'note2': ('Galaxy Note II', 1280, 720, 267, 2),
|
||||
'droid2': ('Motorola Droid 2', 854, 480, 240, 1.5),
|
||||
'xoom': ('Motorola Xoom', 1280, 800, 149, 1),
|
||||
'ipad': ('iPad (1 and 2)', 1024, 768, 132, 1),
|
||||
'ipad3': ('iPad 3', 2048, 1536, 264, 2),
|
||||
'iphone4': ('iPhone 4', 960, 640, 326, 2),
|
||||
'iphone5': ('iPhone 5', 1136, 640, 326, 2),
|
||||
'xperiae': ('Xperia E', 480, 320, 166, 1),
|
||||
'nexus4': ('Nexus 4', 1280, 768, 320, 2),
|
||||
'nexus7': ('Nexus 7 (2012 version)', 1280, 800, 216, 1.325),
|
||||
'nexus7.2': ('Nexus 7 (2013 version)', 1920, 1200, 323, 2),
|
||||
|
||||
# taken from design.google.com/devices
|
||||
# please consider using another data instead of
|
||||
# a dict for autocompletion to work
|
||||
# these are all in landscape
|
||||
'phone_android_one': ('Android One', 854, 480, 218, 1.5),
|
||||
'phone_htc_one_m8': ('HTC One M8', 1920, 1080, 432, 3.0),
|
||||
'phone_htc_one_m9': ('HTC One M9', 1920, 1080, 432, 3.0),
|
||||
'phone_iphone': ('iPhone', 480, 320, 168, 1.0),
|
||||
'phone_iphone_4': ('iPhone 4', 960, 640, 320, 2.0),
|
||||
'phone_iphone_5': ('iPhone 5', 1136, 640, 320, 2.0),
|
||||
'phone_iphone_6': ('iPhone 6', 1334, 750, 326, 2.0),
|
||||
'phone_iphone_6_plus': ('iPhone 6 Plus', 1920, 1080, 400, 3.0),
|
||||
'phone_lg_g2': ('LG G2', 1920, 1080, 432, 3.0),
|
||||
'phone_lg_g3': ('LG G3', 2560, 1440, 533, 3.0),
|
||||
'phone_moto_g': ('Moto G', 1280, 720, 327, 2.0),
|
||||
'phone_moto_x': ('Moto X', 1280, 720, 313, 2.0),
|
||||
'phone_moto_x_2nd_gen': ('Moto X 2nd Gen', 1920, 1080, 432, 3.0),
|
||||
'phone_nexus_4': ('Nexus 4', 1280, 768, 240, 2.0),
|
||||
'phone_nexus_5': ('Nexus 5', 1920, 1080, 450, 3.0),
|
||||
'phone_nexus_5x': ('Nexus 5X', 1920, 1080, 432, 2.6),
|
||||
'phone_nexus_6': ('Nexus 6', 2560, 1440, 496, 3.5),
|
||||
'phone_nexus_6p': ('Nexus 6P', 2560, 1440, 514, 3.5),
|
||||
'phone_oneplus_3t': ('OnePlus 3t', 1863, 1080, 380, 2.375),
|
||||
'phone_oneplus_6t': ('OnePlus 6t', 2340, 1080, 420, 2.625),
|
||||
'phone_samsung_galaxy_note_4': ('Samsung Galaxy Note 4',
|
||||
2560, 1440, 514, 3.0),
|
||||
'phone_samsung_galaxy_s5': ('Samsung Galaxy S5', 1920, 1080, 372, 3.0),
|
||||
'phone_samsung_galaxy_s6': ('Samsung Galaxy S6', 2560, 1440, 576, 4.0),
|
||||
'phone_sony_xperia_c4': ('Sony Xperia C4', 1920, 1080, 400, 2.0),
|
||||
'phone_sony_xperia_z_ultra': ('Sony Xperia Z Ultra', 1920, 1080, 348, 2.0),
|
||||
'phone_sony_xperia_z1_compact': ('Sony Xperia Z1 Compact',
|
||||
1280, 720, 342, 2.0),
|
||||
'phone_sony_xperia_z2z3': ('Sony Xperia Z2/Z3', 1920, 1080, 432, 3.0),
|
||||
'phone_sony_xperia_z3_compact': ('Sony Xperia Z3 Compact',
|
||||
1280, 720, 313, 2.0),
|
||||
'tablet_dell_venue_8': ('Dell Venue 8', 2560, 1600, 355, 2.0),
|
||||
'tablet_ipad': ('iPad', 1024, 768, 132, 1.0),
|
||||
'tablet_ipad_mini': ('iPad Mini', 1024, 768, 163, 1.0),
|
||||
'tablet_ipad_mini_retina': ('iPad Mini Retina', 2048, 1536, 326, 2.0),
|
||||
'tablet_ipad_pro': ('iPad Pro', 2732, 2048, 265, 2.0),
|
||||
'tablet_ipad_retina': ('iPad Retina', 2048, 1536, 264, 2.0),
|
||||
'tablet_nexus_10': ('Nexus 10', 2560, 1600, 297, 2.0),
|
||||
'tablet_nexus_7_12': ('Nexus 7 12', 1280, 800, 216, 1.3),
|
||||
'tablet_nexus_7_13': ('Nexus 7 13', 1920, 1200, 324, 2.0),
|
||||
'tablet_nexus_9': ('Nexus 9', 2048, 1536, 288, 2.0),
|
||||
'tablet_samsung_galaxy_tab_10': ('Samsung Galaxy Tab 10',
|
||||
1280, 800, 148, 1.0),
|
||||
'tablet_sony_xperia_z3_tablet': ('Sony Xperia Z3 Tablet',
|
||||
1920, 1200, 282, 2.0),
|
||||
'tablet_sony_xperia_z4_tablet': ('Sony Xperia Z4 Tablet',
|
||||
2560, 1600, 297, 2.0),
|
||||
'tablet_huawei_mediapad_m3_lite_10': ('HUAWEI MediaPad M3 Lite 10',
|
||||
1920, 1200, 320, 2.25)
|
||||
|
||||
}
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
pass
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
pass
|
||||
|
||||
|
||||
def apply_device(device, scale, orientation):
|
||||
name, width, height, dpi, density = devices[device]
|
||||
if orientation == 'portrait':
|
||||
width, height = height, width
|
||||
Logger.info('Screen: Apply screen settings for {0}'.format(name))
|
||||
Logger.info('Screen: size={0}x{1} dpi={2} density={3} '
|
||||
'orientation={4}'.format(width, height, dpi, density,
|
||||
orientation))
|
||||
try:
|
||||
scale = float(scale)
|
||||
except:
|
||||
scale = 1
|
||||
environ['KIVY_METRICS_DENSITY'] = str(density * scale)
|
||||
environ['KIVY_DPI'] = str(dpi * scale)
|
||||
Config.set('graphics', 'width', str(int(width * scale)))
|
||||
# simulate with the android bar
|
||||
# FIXME should be configurable
|
||||
Config.set('graphics', 'height', str(int(height * scale - 25 * density)))
|
||||
Config.set('graphics', 'fullscreen', '0')
|
||||
Config.set('graphics', 'show_mousecursor', '1')
|
||||
|
||||
|
||||
def usage(device=None):
|
||||
if device:
|
||||
Logger.error('Screen: The specified device ({0}) is unknown.',
|
||||
device)
|
||||
print('\nModule usage: python main.py -m screen:deviceid[,orientation]\n')
|
||||
print('Available devices:\n')
|
||||
print('{0:12} {1:<22} {2:<8} {3:<8} {4:<5} {5:<8}'.format(
|
||||
'Device ID', 'Name', 'Width', 'Height', 'DPI', 'Density'))
|
||||
for device, info in devices.items():
|
||||
print('{0:12} {1:<22} {2:<8} {3:<8} {4:<5} {5:<8}'.format(
|
||||
device, *info))
|
||||
print('\n')
|
||||
print('Simulate a medium-density screen such as Motorola Droid 2:\n')
|
||||
print(' python main.py -m screen:droid2\n')
|
||||
print('Simulate a high-density screen such as HTC One X, in portrait:\n')
|
||||
print(' python main.py -m screen:onex,portrait\n')
|
||||
print('Simulate the iPad 2 screen\n')
|
||||
print(' python main.py -m screen:ipad\n')
|
||||
print('If the generated window is too large, you can specify a scale:\n')
|
||||
print(' python main.py -m screen:note2,portrait,scale=.75\n')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def configure(ctx):
|
||||
scale = ctx.pop('scale', None)
|
||||
orientation = 'landscape'
|
||||
ctx.pop('landscape', None)
|
||||
if ctx.pop('portrait', None):
|
||||
orientation = 'portrait'
|
||||
if not ctx:
|
||||
return usage(None)
|
||||
device = list(ctx.keys())[0]
|
||||
if device not in devices:
|
||||
return usage('')
|
||||
apply_device(device, scale, orientation)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for n in devices.values():
|
||||
assert n[1] > n[2]
|
||||
33
kivy/modules/showborder.py
Normal file
33
kivy/modules/showborder.py
Normal file
@@ -0,0 +1,33 @@
|
||||
'''
|
||||
Show border
|
||||
===========
|
||||
|
||||
Shows widget's border.
|
||||
|
||||
The idea was taken from
|
||||
http://robertour.com/2013/10/02/easy-way-debugging-kivy-interfaces/
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
|
||||
KV_CODE = '''
|
||||
<Widget>:
|
||||
canvas.after:
|
||||
Color:
|
||||
rgba: 1, 1, 1, 1
|
||||
Line:
|
||||
rectangle: self.x + 1, self.y + 1, self.width - 1, self.height - 1
|
||||
dash_offset: 5
|
||||
dash_length: 3
|
||||
'''
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
Builder.load_string(KV_CODE, filename=__file__)
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
Builder.unload_file(__file__)
|
||||
97
kivy/modules/touchring.py
Normal file
97
kivy/modules/touchring.py
Normal file
@@ -0,0 +1,97 @@
|
||||
'''
|
||||
Touchring
|
||||
=========
|
||||
|
||||
Shows rings around every touch on the surface / screen. You can use this module
|
||||
to check that you don't have any calibration issues with touches.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
:Parameters:
|
||||
`image`: str, defaults to '<kivy>/data/images/ring.png'
|
||||
Filename of the image to use.
|
||||
`scale`: float, defaults to 1.
|
||||
Scale of the image.
|
||||
`alpha`: float, defaults to 1.
|
||||
Opacity of the image.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
In your configuration (`~/.kivy/config.ini`), you can add something like
|
||||
this::
|
||||
|
||||
[modules]
|
||||
touchring = image=mypointer.png,scale=.3,alpha=.7
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
from kivy.core.image import Image
|
||||
from kivy.graphics import Color, Rectangle
|
||||
from kivy import kivy_data_dir
|
||||
from os.path import join
|
||||
|
||||
pointer_image = None
|
||||
pointer_scale = 1.0
|
||||
pointer_alpha = 0.7
|
||||
|
||||
|
||||
def _touch_down(win, touch):
|
||||
ud = touch.ud
|
||||
with win.canvas.after:
|
||||
ud['tr.color'] = Color(1, 1, 1, pointer_alpha)
|
||||
iw, ih = pointer_image.size
|
||||
ud['tr.rect'] = Rectangle(
|
||||
pos=(
|
||||
touch.x - (pointer_image.width / 2. * pointer_scale),
|
||||
touch.y - (pointer_image.height / 2. * pointer_scale)),
|
||||
size=(iw * pointer_scale, ih * pointer_scale),
|
||||
texture=pointer_image.texture)
|
||||
|
||||
if not ud.get('tr.grab', False):
|
||||
ud['tr.grab'] = True
|
||||
touch.grab(win)
|
||||
|
||||
|
||||
def _touch_move(win, touch):
|
||||
ud = touch.ud
|
||||
if not ud.get('tr.rect', False):
|
||||
_touch_down(win, touch)
|
||||
ud['tr.rect'].pos = (
|
||||
touch.x - (pointer_image.width / 2. * pointer_scale),
|
||||
touch.y - (pointer_image.height / 2. * pointer_scale))
|
||||
|
||||
|
||||
def _touch_up(win, touch):
|
||||
if touch.grab_current is win:
|
||||
ud = touch.ud
|
||||
win.canvas.after.remove(ud['tr.color'])
|
||||
win.canvas.after.remove(ud['tr.rect'])
|
||||
|
||||
if ud.get('tr.grab') is True:
|
||||
touch.ungrab(win)
|
||||
ud['tr.grab'] = False
|
||||
|
||||
|
||||
def start(win, ctx):
|
||||
# XXX use ctx !
|
||||
global pointer_image, pointer_scale, pointer_alpha
|
||||
|
||||
pointer_fn = ctx.config.get('image',
|
||||
'atlas://data/images/defaulttheme/ring')
|
||||
pointer_scale = float(ctx.config.get('scale', 1.0))
|
||||
pointer_alpha = float(ctx.config.get('alpha', 1.0))
|
||||
pointer_image = Image(pointer_fn)
|
||||
|
||||
win.bind(on_touch_down=_touch_down,
|
||||
on_touch_move=_touch_move,
|
||||
on_touch_up=_touch_up)
|
||||
|
||||
|
||||
def stop(win, ctx):
|
||||
win.unbind(on_touch_down=_touch_down,
|
||||
on_touch_move=_touch_move,
|
||||
on_touch_up=_touch_up)
|
||||
30
kivy/modules/webdebugger.py
Normal file
30
kivy/modules/webdebugger.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Web Debugger
|
||||
============
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is highly experimental, use it with care.
|
||||
|
||||
This module will start a webserver and run in the background. You can
|
||||
see how your application evolves during runtime, examine the internal
|
||||
cache etc.
|
||||
|
||||
Run with::
|
||||
|
||||
python main.py -m webdebugger
|
||||
|
||||
Then open your webbrowser on http://localhost:5000/
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('start', 'stop')
|
||||
|
||||
import os
|
||||
if 'KIVY_DOC' not in os.environ:
|
||||
from kivy.modules._webdebugger import start, stop
|
||||
else:
|
||||
start = stop = lambda *x: True
|
||||
Reference in New Issue
Block a user