Merge branch 'master' of ssh://github.com/kivy/kivy into ios-support

This commit is contained in:
Mathieu Virbel 2012-11-02 20:04:29 +01:00
commit 596cbab408
41 changed files with 833 additions and 291 deletions

View File

@ -23,6 +23,7 @@ import kivy
# force loading of kivy modules
import kivy.app
import kivy.metrics
import kivy.atlas
import kivy.core.audio
import kivy.core.camera
@ -43,6 +44,7 @@ import kivy.modules.monitor
import kivy.modules.touchring
import kivy.modules.inspector
import kivy.modules.recorder
import kivy.modules.screen
import kivy.network.urlrequest
import kivy.support
import kivy.input.recorder

View File

@ -32,11 +32,6 @@ KIVY_NO_FILELOG
KIVY_NO_CONSOLELOG
If set, logs will be not print on the console
KIVY_DPI
If set, the value will be used instead of the value returned by the window.
.. versionadded:: 1.4.0
Path control
------------
@ -101,3 +96,22 @@ KIVY_CLIPBOARD
Values: pygame, dummy
Metrics
-------
KIVY_DPI
If set, the value will be used for :data:`Metrics.dpi`.
.. versionadded:: 1.4.0
KIVY_METRICS_DENSITY
If set, the value will be used for :data:`Metrics.density`.
.. versionadded:: 1.5.0
KIVY_METRICS_FONTSCALE
If set, the value will be used for :data:`Metrics.fontscale`.
.. versionadded:: 1.5.0

View File

@ -81,6 +81,8 @@ You can customize the build in many ways:
#. Go to the settings panel > build, search for "strip" options, and
triple-check that they are all set to NO. Stripping is not working with
Python dynamic modules, and will strip needed symbols.
#. Indicate a launch image in portrait/landscape for ipad with and without
retina display.
.. _Known issues:
@ -90,12 +92,6 @@ Known issues
Currently, the project have few issues as (we'll fixes them during the
development):
- Loading time: Apple provide a way to reduce the feeling of a slow application
loading by showing an image when the application is initialize. But, due to
the SDL approach, IOS remove the launch image before we have started. So if
you are using a launch image, the user will see: The launch image -> black
screen -> your app. Remove the launch image for now.
- Application configuration not writing: we are learning how IOS manage its
filesystem.

View File

@ -137,11 +137,11 @@
TextInput:
id:info_lbl
readonly: True
font_size: 11
font_size: '11sp'
background_color: (0, 0, 0, 1)
foreground_color: (1, 1, 1, 1)
opacity:0
size_hint: 1, None
text_size: self.size
height: '150pt'
top: 0
top: 0

View File

@ -250,16 +250,16 @@ class ShowcaseApp(App):
return col
def show_popup(self):
btnclose = Button(text='Close this popup', size_hint_y=None, height=50)
btnclose = Button(text='Close this popup', size_hint_y=None, height='50sp')
content = BoxLayout(orientation='vertical')
content.add_widget(Label(text='Hello world'))
content.add_widget(btnclose)
popup = Popup(content=content, title='Modal popup example',
size_hint=(None, None), size=(300, 300),
size_hint=(None, None), size=('300dp', '300dp'),
auto_dismiss=False)
btnclose.bind(on_release=popup.dismiss)
button = Button(text='Open popup', size_hint=(None, None),
size=(150, 70))
size=('150sp', '70dp'))
button.bind(on_release=popup.open)
popup.open()
col = AnchorLayout()

View File

@ -14,17 +14,18 @@
orientation: 'vertical'
BoxLayout:
padding: 10
spacing: 10
padding: '10dp'
spacing: '10dp'
size_hint: 1, None
pos_hint: {'top': 1}
height: 44
height: '44dp'
Image:
size_hint: None, None
size: 24, 24
source: 'data/logo/kivy-icon-24.png'
size: '24dp', '24dp'
source: 'data/logo/kivy-icon-64.png'
mipmap: True
Label:
height: 24
height: '24dp'
text_size: self.width, None
color: (1, 1, 1, .8)
text: 'Kivy %s - Showcase' % kivy.__version__
@ -55,7 +56,7 @@
[Title@Label]
pos_hint: {'center_x': .5, 'y': .3}
text: ctx.text
font_size: 22
font_size: '29dp'
<AnchorLayoutShowcase>
Title:
@ -153,9 +154,9 @@
[HSeparator@Label]:
size_hint_y: None
height: 45
height: max(dp(45), self.texture_size[1] + dp(10))
text: ctx.text if 'text' in ctx else ''
text_size: self.size
text_size: self.width, None
valign: 'middle'
halign: 'center'
canvas.before:
@ -257,18 +258,18 @@
TextInput:
text: 'Monoline textinput'
size_hint_y: None
height: 30
height: sp(30)
TextInput:
text: 'This is a password'
size_hint_y: None
height: 30
height: sp(30)
password: True
TextInput:
text: 'Readonly textinput'
size_hint_y: None
height: 30
height: sp(30)
readonly: True
TextInput:
@ -313,6 +314,8 @@
title: 'Panel 1'
Label:
text: 'This is a label fit to the content view'
halign: 'center'
text_size: self.width, None
AccordionItem:
title: 'Panel 2'
@ -323,6 +326,8 @@
title: 'Panel 3'
Label:
text: 'This is a label fit to the content view'
halign: 'center'
text_size: self.width, None
Accordion:
@ -333,6 +338,7 @@
title: 'Panel 1'
Label:
text: 'This is a label fit to the content view'
text_size: self.width, None
AccordionItem:
title: 'Panel 2'
@ -343,6 +349,7 @@
title: 'Panel 3'
Label:
text: 'This is a label fit to the content view'
text_size: self.width, None
VSeparator

View File

@ -10,17 +10,18 @@
size: self.size
BoxLayout:
padding: 10
spacing: 10
padding: '10dp'
spacing: '10dp'
size_hint: 1, None
pos_hint: {'top': 1}
height: 44
height: '44dp'
Image:
size_hint: None, None
size: 24, 24
source: 'data/logo/kivy-icon-24.png'
size: '24dp', '24dp'
source: 'data/logo/kivy-icon-64.png'
mipmap: True
Label:
height: 24
height: '24dp'
text_size: self.width, None
color: (1, 1, 1, .8)
text: 'Kivy %s - Touchtracer' % kivy.__version__

View File

@ -371,6 +371,10 @@ if not environ.get('KIVY_DOC_INCLUDE'):
Logger.info('Core: Kivy configuration saved.')
sys.exit(0)
# configure all activated modules
from kivy.modules import Modules
Modules.configure()
# android hooks: force fullscreen and add android touch input provider
if platform() in ('android', 'ios'):
from kivy.config import Config

View File

@ -381,6 +381,7 @@ class App(EventDispatcher):
if clsname.endswith('App'):
clsname = clsname[:-3]
filename = join(kv_directory, '%s.kv' % clsname.lower())
Logger.debug('App: Loading kv <{0}>'.format(filename))
if not exists(filename):
Logger.debug('App: kv <%s> not found' % filename)
return False
@ -472,6 +473,7 @@ class App(EventDispatcher):
filename = self.get_application_config()
if filename is None:
return config
Logger.debug('App: Loading configuration <{0}>'.format(filename))
if exists(filename):
try:
config.read(filename)
@ -481,6 +483,8 @@ class App(EventDispatcher):
self.build_config(config)
pass
else:
Logger.debug('App: First configuration, create <{0}>'.format(
filename))
config.filename = filename
config.write()
return config

View File

@ -15,12 +15,10 @@ __all__ = (
'stopTouchApp',
)
from os import environ
from kivy.config import Config
from kivy.logger import Logger
from kivy.clock import Clock
from kivy.event import EventDispatcher
from kivy.utils import platform, reify
# private vars
EventLoop = None
@ -106,32 +104,6 @@ class EventLoopBase(EventDispatcher):
'''
return self.me_list
@reify
def dpi(self):
'''Return the DPI of the screen. Depending of the platform, the DPI can
be taken from the Window provider (Desktop mainly), or from
platform-specific module (like android/ios).
On desktop, you can overload the value returned by the Window object
(96 by default), by setting the environ KIVY_DPI::
KIVY_DPI=200 python main.py
.. versionadded:: 1.4.0
'''
custom_dpi = environ.get('KIVY_DPI')
if custom_dpi:
return float(custom_dpi)
plat = platform()
if plat == 'android':
import android
return android.get_dpi()
# for all other platforms..
self.ensure_window()
return self.window.dpi
def ensure_window(self):
'''Ensure that we have an window
'''

View File

@ -138,6 +138,16 @@ Available configuration tokens
property in :class:`~kivy.uix.scrollview.Scrollview` widget.
Check the widget documentation for more information.
`scroll_stoptime`: int
Default value of :data:`~kivy.uix.scrollview.Scrollview.scroll_stoptime`
property in :class:`~kivy.uix.scrollview.Scrollview` widget.
Check the widget documentation for more information.
`scroll_moves`: int
Default value of :data:`~kivy.uix.scrollview.Scrollview.scroll_moves`
property in :class:`~kivy.uix.scrollview.Scrollview` widget.
Check the widget documentation for more information.
:modules:
You can activate modules with this syntax::
@ -174,7 +184,7 @@ from kivy.logger import Logger, logger_config_update
from kivy.utils import OrderedDict
# Version number of current configuration format
KIVY_CONFIG_VERSION = 6
KIVY_CONFIG_VERSION = 7
#: Kivy configuration object
Config = None
@ -388,7 +398,7 @@ if not environ.get('KIVY_DOC_INCLUDE'):
elif version == 3:
# add token for scrollview
Config.setdefault('widgets', 'scroll_timeout', '250')
Config.setdefault('widgets', 'scroll_timeout', '55')
Config.setdefault('widgets', 'scroll_distance', '20')
Config.setdefault('widgets', 'scroll_friction', '1.')
@ -408,6 +418,13 @@ if not environ.get('KIVY_DOC_INCLUDE'):
elif version == 5:
Config.setdefault('graphics', 'resizable', '1')
elif version == 6:
# if the timeout is still the default value, change it
if Config.getint('widgets', 'scroll_timeout') == 250:
Config.set('widgets', 'scroll_timeout', '55')
Config.setdefault('widgets', 'scroll_stoptime', '300')
Config.setdefault('widgets', 'scroll_moves', '5')
#elif version == 1:
# # add here the command for upgrading from configuration 0 to 1
#

View File

@ -8,10 +8,16 @@ core provider can select an OpenGL ES or a 'classic' desktop OpenGL library.
'''
from os import environ
from sys import platform, exit
MIN_REQUIRED_GL_VERSION = (2, 0)
def msgbox(message):
if platform == 'win32':
import win32ui
win32ui.MessageBox(message, 'Kivy Fatal Error')
exit(1)
if 'KIVY_DOC' not in environ:
@ -22,16 +28,32 @@ if 'KIVY_DOC' not in environ:
def init_gl():
gl_init_symbols()
gl_init_resources()
print_gl_version()
gl_init_resources()
def print_gl_version():
version = str(glGetString(GL_VERSION))
vendor = str(glGetString(GL_VENDOR))
renderer = str(glGetString(GL_RENDERER))
Logger.info('GL: OpenGL version <%s>' % version)
Logger.info('GL: OpenGL vendor <%s>' % str(
glGetString(GL_VENDOR)))
Logger.info('GL: OpenGL renderer <%s>' % str(
glGetString(GL_RENDERER)))
Logger.info('GL: OpenGL vendor <%s>' % vendor)
Logger.info('GL: OpenGL renderer <%s>' % renderer)
# Let the user know if his graphics hardware/drivers are too old
major, minor = gl_get_version()
Logger.info('GL: OpenGL parsed version: %d, %d' % (major, minor))
if (major, minor) < MIN_REQUIRED_GL_VERSION:
msg = (
'GL: Minimum required OpenGL version (2.0) NOT found!\n\n'
'OpenGL version detected: {0}.{1}\n\n'
'Version: {2}\nVendor: {3}\nRenderer: {4}\n\n'
'Try upgrading your graphics drivers and/or your '
'graphics hardware in case of problems.\n\n'
'The application will leave now.').format(
major, minor, version, vendor, renderer)
Logger.critical(msg)
msgbox(msg)
Logger.info('GL: Shading version <%s>' % str(
glGetString(GL_SHADING_LANGUAGE_VERSION)))
Logger.info('GL: Texture max size <%s>' % str(
@ -39,15 +61,6 @@ if 'KIVY_DOC' not in environ:
Logger.info('GL: Texture max units <%s>' % str(
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS)[0]))
# Let the user know if his graphics hardware/drivers are too old
major, minor = gl_get_version()
Logger.info('GL: OpenGL parsed version: %d, %d' % (major, minor))
if (major, minor) < MIN_REQUIRED_GL_VERSION:
msg = 'GL: Minimum required OpenGL version (2.0) NOT found! ' \
'Try upgrading your graphics drivers and/or your ' \
'graphics hardware in case of problems.'
Logger.critical(msg)
# To be able to use our GL provider, we must have a window
# Automaticly import window auto to ensure the default window creation
import kivy.core.window

View File

@ -38,7 +38,7 @@ If you need to escape the markup from the current text, use
__all__ = ('MarkupLabel', )
from kivy.graphics.texture import Texture
from kivy.utils import platform
from kivy.properties import dpi2px
from kivy.parser import parse_color
from kivy.logger import Logger
import re
@ -138,9 +138,14 @@ class MarkupLabel(MarkupLabelBase):
spop('italic')
self.resolve_font_name()
elif item[:6] == '[size=':
item = item[6:-1]
try:
size = int(item[6:-1])
if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm', 'dp', 'sp'):
size = dpi2px(item[:-2], item[-2:])
else:
size = int(item)
except ValueError:
raise
size = options['font_size']
spush('font_size')
options['font_size'] = size

View File

@ -109,7 +109,7 @@
background_normal: 'atlas://data/images/defaulttheme/tab_btn'
background_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'
border: (8, 8, 8, 8)
font_size: 11
font_size: '15sp'
<TextInput>:
@ -167,7 +167,7 @@
<TreeViewLabel>:
width: self.texture_size[0]
height: max(self.texture_size[1], 24)
height: max(self.texture_size[1] + dp(10), dp(24))
text_size: self.width, None
@ -252,7 +252,7 @@
orientation: 'horizontal'
size_hint_y: None
height: 24
height: '24sp'
# Don't allow expansion of the ../ node
is_leaf: not ctx.isdir or ctx.name.endswith('..' + ctx.sep) or self.locked
on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
@ -260,12 +260,13 @@
BoxLayout:
pos: root.pos
Label:
text_size: self.size
id: filename
text_size: self.width, None
halign: 'left'
shorten: True
text: unicode(ctx.name)
Label:
text_size: self.size
text_size: self.width, None
size_hint_x: None
halign: 'right'
text: unicode(ctx.get_nice_size())
@ -291,8 +292,8 @@
width: scrollview.width
size_hint_y: None
height: self.minimum_height
spacing: 10
padding: 10
spacing: '10dp'
padding: '10dp'
[FileIconEntry@Widget]:
locked: False
@ -302,6 +303,7 @@
on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])
size: '100dp', '100dp'
canvas:
Color:
@ -313,22 +315,22 @@
source: 'atlas://data/images/defaulttheme/filechooser_selected'
Image:
size: 48, 48
size: '48dp', '48dp'
source: 'atlas://data/images/defaulttheme/filechooser_%s' % ('folder' if ctx.isdir else 'file')
pos: root.x + 24, root.y + 40
pos: root.x + dp(24), root.y + dp(40)
Label:
text: unicode(ctx.name)
text_size: (root.width, self.height)
halign: 'center'
shorten: True
size: 100, 16
pos: root.x, root.y + 16
size: '100dp', '16dp'
pos: root.x, root.y + dp(16)
Label:
text: unicode(ctx.get_nice_size())
font_size: 8
font_size: '11sp'
color: .8, .8, .8, 1
size: 100, 16
size: '100dp', '16sp'
pos: root.pos
halign: 'center'
@ -358,7 +360,7 @@
size_hint_y: None
height: self.texture_size[1]
y: pb.center_y - self.height - 8
font_size: 10
font_size: '13sp'
color: (.8, .8, .8, .8)
AnchorLayout:
@ -499,7 +501,8 @@
size: self.width, 1
<SettingItem>:
size_hint_x: .25
size_hint: .25, None
height: labellayout.texture_size[1] + dp(10)
content: content
canvas:
Color:
@ -516,29 +519,17 @@
BoxLayout:
pos: root.pos
FloatLayout:
Label:
size_hint_x: .66
id: labellayout
orientation: 'vertical'
size_hint_x: .5
Label:
pos_hint: {'y': .5, 'x': 0}
text: root.title
text_size: self.width - 32, None
size_hint_y: None
height: self.texture_size[1]
Label:
pos_hint: {'top': .5, 'x': 0}
text: root.desc if root.desc else ''
text_size: self.width - 32, None
size_hint_y: None
height: self.texture_size[1]
font_size: 10
color: (.7, .7, .7, 1) if root.selected_alpha < 0.5 else (.3, .3, .3, 1)
markup: True
text: '{0}\n[size=13sp][color=999999]{1}[/color][/size]{2}'.format(root.title or '', root.desc or '', self.font_size)
font_size: '15sp'
text_size: self.width - 32, None
BoxLayout:
id: content
size_hint_x: .5
size_hint_x: .33
<SettingBoolean>:
@ -552,20 +543,26 @@
Label:
text: str(root.value)
pos: root.pos
font_size: '15sp'
<SettingPath>:
Label:
text: str(root.value)
pos: root.pos
font_size: '15sp'
<SettingOptions>:
Label:
text: str(root.value)
pos: root.pos
font_size: '15sp'
<SettingTitle>:
text_size: self.width - 32, None
size_hint_y: None
height: max(dp(20), self.texture_size[1] + dp(20))
color: (.9, .9, .9, 1)
font_size: '15sp'
canvas:
Color:
rgba: .15, .15, .15, .5
@ -581,7 +578,8 @@
<SettingSidebarLabel>:
size_hint: 1, None
text_size: self.width - 32, None
height: max(48, self.texture_size[1] + 8)
height: self.texture_size[1] + dp(20)
font_size: '15sp'
canvas.before:
Color:
rgba: 47 / 255., 167 / 255., 212 / 255., int(self.selected)
@ -592,15 +590,16 @@
<SettingsPanel>:
spacing: 5
padding: 5
row_default_height: 48
row_force_default: True
size_hint_y: None
height: self.minimum_height
Label:
size_hint_y: None
text: root.title
text_size: self.width - 32, None
height: max(50, self.texture_size[1] + 20)
color: (.5, .5, .5, 1)
font_size: '15sp'
canvas.after:
Color:
@ -622,7 +621,7 @@
FloatLayout:
size_hint_x: None
width: 200
width: '200dp'
GridLayout:
pos: root.pos
cols: 1
@ -640,10 +639,11 @@
Button:
text: 'Close'
size_hint: None, None
width: 180
height: 50
pos: root.x + 10, root.y + 10
width: '180dp'
height: max(50, self.texture_size[1] + dp(20))
pos: root.x + dp(10), root.y + dp(10)
on_release: root.dispatch('on_close')
font_size: '15sp'
ScrollView:
do_scroll_x: False
@ -766,7 +766,7 @@
do_translation: False
do_rotation: False
do_scale: False
auto_bring_to_front: False
auto_bring_to_front: False
# =============================================================================
@ -774,16 +774,16 @@
# =============================================================================
<ScreenManager>:
canvas.before:
StencilPush
Rectangle:
pos: self.pos
size: self.size
StencilUse
canvas.after:
StencilUnUse
Rectangle:
pos: self.pos
size: self.size
StencilPop
canvas.before:
StencilPush
Rectangle:
pos: self.pos
size: self.size
StencilUse
canvas.after:
StencilUnUse
Rectangle:
pos: self.pos
size: self.size
StencilPop

View File

@ -14,7 +14,7 @@ cdef class Color(ContextInstruction):
cdef class BindTexture(ContextInstruction):
cdef int _index
cdef bytes _source
cdef object _source
cdef Texture _texture
cdef void apply(self)

View File

@ -41,7 +41,7 @@ cdef object get_default_texture():
# register Image cache
Cache.register('kv.texture', limit=1000, timeout=60)
Cache.register('kv.shader', limit=1000, timeout=60)
Cache.register('kv.shader', limit=1000, timeout=3600)
# ensure that our resources are cleaned
def gl_init_resources():
@ -285,9 +285,9 @@ cdef class BindTexture(ContextInstruction):
'''
def __get__(self):
return self._source
def __set__(self, bytes filename):
def __set__(self, filename):
Logger.trace('BindTexture: setting source: <%s>' % filename)
self._source = <bytes>resource_find(filename)
self._source = resource_find(filename)
if self._source:
tex = Cache.get('kv.texture', filename)
if not tex:

View File

@ -75,6 +75,7 @@ void glew_dynamic_binding() {
*/
if (glGenFramebuffers == NULL) {
printf("GL: glGenFramebuffers is NULL, try to detect an extension\n");
printf("GL: available extensions: %s\n", gl_extensions);
if (strstr(gl_extensions, "ARB_framebuffer_object")) {
printf("GL: ARB_framebuffer_object is supported\n");

View File

@ -69,6 +69,8 @@ cdef class Instruction:
self.set_parent(ig)
cdef void rremove(self, InstructionGroup ig):
if self.parent is None:
return
ig.children.remove(self)
self.set_parent(None)

View File

@ -1570,9 +1570,18 @@ def glViewport(GLint x, GLint y, GLsizei width, GLsizei height):
IF USE_GLEW:
cdef extern from "gl_redirect.h":
int glewInit()
int GLEW_OK
char *glewGetErrorString(int)
void glew_dynamic_binding()
def gl_init_symbols():
glewInit()
cdef int result
cdef bytes error
result = glewInit()
if result != GLEW_OK:
error = glewGetErrorString(result)
print 'GLEW initialization error:', error
else:
print 'GLEW initialization succeeded'
glew_dynamic_binding()
ELSE:

View File

@ -68,8 +68,8 @@ class AndroidMotionEventProvider(MotionEventProvider):
pressed = joy.get_button(0)
x = joy.get_axis(0) * 32768. / w
y = 1. - (joy.get_axis(1) * 32768. / h)
pressure = joy.get_axis(2)
radius = joy.get_axis(3)
pressure = joy.get_axis(2) / 1000. # python for android do * 1000.
radius = joy.get_axis(3) / 1000. # python for android do * 1000.
# new touche ?
if pressed and jid not in touches:

View File

@ -54,11 +54,11 @@ else:
h = property(lambda self: self.bottom - self.top)
win_rect = RECT()
if hasattr(windll.user32, 'SetWindowLongPtrW'):
try:
windll.user32.SetWindowLongPtrW.restype = WNDPROC
windll.user32.SetWindowLongPtrW.argtypes = [HANDLE, c_int, WNDPROC]
SetWindowLong_wrapper = windll.user32.SetWindowLongPtrW
else:
except AttributeError:
windll.user32.SetWindowLongW.restype = WNDPROC
windll.user32.SetWindowLongW.argtypes = [HANDLE, c_int, WNDPROC]
SetWindowLong_wrapper = windll.user32.SetWindowLongW

View File

@ -98,11 +98,11 @@ else:
w = property(lambda self: self.right - self.left)
h = property(lambda self: self.bottom - self.top)
if hasattr(windll.user32, 'SetWindowLongPtrW'):
try:
windll.user32.SetWindowLongPtrW.restype = WNDPROC
windll.user32.SetWindowLongPtrW.argtypes = [HANDLE, c_int, WNDPROC]
SetWindowLong_wrapper = windll.user32.SetWindowLongPtrW
else:
except AttributeError:
windll.user32.SetWindowLongW.restype = WNDPROC
windll.user32.SetWindowLongW.argtypes = [HANDLE, c_int, WNDPROC]
SetWindowLong_wrapper = windll.user32.SetWindowLongW

View File

@ -458,6 +458,7 @@ from kivy.utils import OrderedDict, QueryDict
from kivy.cache import Cache
from kivy import kivy_data_dir, require
from kivy.lib.debug import make_traceback
import kivy.metrics as metrics
trace = Logger.trace
@ -517,7 +518,14 @@ class ProxyApp(object):
object.__getattribute__(self, '_ensure_app')()
return repr(object.__getattribute__(self, '_obj'))
global_idmap['app'] = ProxyApp()
global_idmap['pt'] = metrics.pt
global_idmap['inch'] = metrics.inch
global_idmap['cm'] = metrics.cm
global_idmap['mm'] = metrics.mm
global_idmap['dp'] = metrics.dp
global_idmap['sp'] = metrics.sp
class ParserException(Exception):

214
kivy/metrics.py Normal file
View File

@ -0,0 +1,214 @@
'''
Metrics
=======
.. versionadded:: 1.5.0
A screen is defined by its actual physical size, density, resolution. These
factors are essential for creating UI with correct size everywhere.
In Kivy, all our graphics pipeline is working with pixels. But using pixels as a
measurement unit is wrong, because the size will changes according to the
screen.
Dimensions
----------
As you design your UI for different screen sizes, you'll need new measurement
unit to work with.
:Units:
`pt`
Points - 1/72 of an inch based on the physical size of the screen.
Prefer to use sp instead of pt.
`mm`
Millimeters - Based on the physical size of the screen
`cm`
Centimeters - Based on the physical size of the screen
`in`
Inches - Based on the physical size of the screen
`dp`
Density-independent Pixels - An abstract unit that is based on the
physical density of the screen. With a :data:`Metrics.density` of 1, 1dp
is equal to 1px. When running on a higher density screen, the number of
pixels used to draw 1dp is scaled up by the factor of appropriate
screen's dpi, and the inverse for lower dpi.
The ratio dp-to-pixes will change with the screen density, but not
necessarily in direct proportions. Using dp unit is a simple solution to
making the view dimensions in your layout resize properly for different
screen densities. In others words, it provides consistency for the
real-world size of your UI across different devices.
`sp`
Scale-independent Pixels - This is like the dp unit, but it is also
scaled by the user's font size preference. It is recommended to use this
unit when specifying font sizes, so they will be adjusted for both the
screen density and the user's preference.
Examples
--------
An example of creating a label with a sp font_size, and set a manual height with
a 10dp margin::
#:kivy 1.5.0
<MyWidget>:
Label:
text: 'Hello world'
font_size: '15sp'
size_hint_y: None
height: self.texture_size[1] + dp(10)
Manual control of metrics
-------------------------
The metrics cannot be changed in runtime. Once a value has been converted to
pixels, you don't have the original value anymore. It's not like you'll change
the DPI or density in runtime.
We provide new environment variables to control the metrics:
- `KIVY_METRICS_DENSITY`: if set, this value will be used for
:data:`Metrics.density` instead of the system one. On android, the value
varies between 0.75, 1, 1.5. 2.
- `KIVY_METRICS_FONTSCALE`: if set, this value will be used for
:data:`Metrics.fontscale` instead of the system one. On android, the value
varies between 0.8-1.2.
- `KIVY_DPI`: if set, this value will be used for :data:`Metrics.dpi`. Please
note that settings the DPI will not impact dp/sp notation, because thoses are
based on the density.
For example, if you want to simulate an high-density screen (like HTC One X)::
KIVY_DPI=320 KIVY_METRICS_DENSITY=2 python main.py --size 1280x720
Or a medium-density (like Motorola Droid 2)::
KIVY_DPI=240 KIVY_METRICS_DENSITY=1.5 python main.py --size 854x480
You can also simulate an alternative user preference for fontscale, like::
KIVY_METRICS_FONTSCALE=1.2 python main.py
'''
__all__ = ('metrics', 'Metrics', 'pt', 'inch', 'cm', 'mm', 'dp', 'sp')
from os import environ
from kivy.utils import reify, platform
from kivy.properties import dpi2px
def pt(value):
'''Convert from points to pixels
'''
return dpi2px(value, 'pt')
def inch(value):
'''Convert from inches to pixels
'''
return dpi2px(value, 'in')
def cm(value):
'''Convert from centimeters to pixels
'''
return dpi2px(value, 'cm')
def mm(value):
'''Convert from millimeters to pixels
'''
return dpi2px(value, 'mm')
def dp(value):
'''Convert from density-independent pixels to pixels
'''
return dpi2px(value, 'dp')
def sp(value):
'''Convert from scale-independent pixels to pixels
'''
return dpi2px(value, 'sp')
class Metrics(object):
'''Class that contain the default attribute for metrics. Don't use the class
directly, but use the `metrics` instance.
'''
@reify
def dpi(self):
'''Return the DPI of the screen. Depending of the platform, the DPI can
be taken from the Window provider (Desktop mainly), or from
platform-specific module (like android/ios).
'''
custom_dpi = environ.get('KIVY_DPI')
if custom_dpi:
return float(custom_dpi)
if platform() == 'android':
import android
return android.get_dpi()
# for all other platforms..
from kivy.base import EventLoop
EventLoop.ensure_window()
return EventLoop.window.dpi
@reify
def dpi_rounded(self):
'''Return the dpi of the screen, rounded to the nearest of 120, 160,
240, 320.
'''
dpi = self.dpi
if dpi < 140:
return 120
elif dpi < 200:
return 160
elif dpi < 280:
return 240
return 320
@reify
def density(self):
'''Return the density of the screen. This value is 1 by default
on desktop, and varies on android depending the screen.
'''
custom_density = environ.get('KIVY_METRICS_DENSITY')
if custom_density:
return float(custom_density)
if platform() == 'android':
import jnius
Hardware = jnius.autoclass('org.renpy.android.Hardware')
return Hardware.metrics.scaledDensity
return 1.0
@reify
def fontscale(self):
'''Return the fontscale user preference. This value is 1 by default, and
can varies between 0.8-1.2.
'''
custom_fontscale = environ.get('KIVY_METRICS_FONTSCALE')
if custom_fontscale:
return float(custom_fontscale)
if platform() == 'android':
import jnius
PythonActivity = jnius.autoclass('org.renpy.android.PythonActivity')
config = PythonActivity.mActivity.getResources().getConfiguration()
return config.fontScale
return 1.0
#: default instance of :class:`Metrics`, used everywhere in the code
metrics = Metrics()

View File

@ -113,7 +113,9 @@ class ModuleBase:
def import_module(self, name):
try:
module = __import__(name=name, fromlist='.')
modname = 'kivy.modules.{0}'.format(name)
module = __import__(name=modname)
module = sys.modules[modname]
except ImportError:
Logger.exception('Modules: unable to import <%s>' % name)
raise
@ -130,38 +132,17 @@ class ModuleBase:
def activate_module(self, name, win):
'''Activate a module on a window'''
if not name in self.mods:
if name not in self.mods:
Logger.warning('Modules: Module <%s> not found' % name)
return
if not 'module' in self.mods[name]:
try:
self.import_module(name)
except ImportError:
return
module = self.mods[name]['module']
if not self.mods[name]['activated']:
# 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]
msg = 'Modules: Start <%s> with config %s' % (name, str(config))
context = self.mods[name]['context']
msg = 'Modules: Start <{0}> with config {1}'.format(
name, context)
Logger.debug(msg)
self.mods[name]['context'].config = config
module.start(win, self.mods[name]['context'])
module.start(win, context)
def deactivate_module(self, name, win):
'''Deactivate a module from a window'''
@ -196,6 +177,45 @@ class ModuleBase:
for name in modules_to_activate:
self.activate_module(name, win)
def configure(self):
'''(internal) Configure all the modules before using it.
'''
modules_to_configure = map(lambda x: x[0], 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
print 'Available modules'

100
kivy/modules/screen.py Normal file
View File

@ -0,0 +1,100 @@
'''
Screen
======
This module change some environement and configuration to match the density /
dpi / screensize of a specific devices.
To see a list of the available screenid, just run::
python main.py -m screen
Simulate a medium-density screen as Motolora Droid 2::
python main.py -m screen,droid2
Simulate a high-density screen as HTC One X, in portrait::
python main.py -m screen,onex,portrait
Simulate the iPad 2 screen::
python main.py -m screen,ipad
'''
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),
's3': ('Galaxy SIII', 1280, 720, 306, 2),
'droid2': ('Motolora Droid 2', 854, 480, 240, 1.5),
'xoom': ('Motolora Xoom', 1280, 800, 149, 1),
'ipad': ('iPad (1 and 2)', 1024, 768, 132, 1),
'ipad3': ('iPad 3', 2048, 1536, 264, 2),
'iphone4': ('iPhone 4', 640, 960, 326, 2),
'iphone5': ('iPhone 5', 640, 1136, 326, 2),
}
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))
environ['KIVY_METRICS_DENSITY'] = str(density)
environ['KIVY_DPI'] = str(dpi)
Config.set('graphics', 'width', str(width))
Config.set('graphics', 'height', str(height))
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 unknow.',
device)
print '\nModule usage: python main.py -m screen,deviceid[,orientation]\n'
print 'Availables 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.iteritems():
print '{0:12} {1:<22} {2:<8} {3:<8} {4:<5} {5:<8}'.format(
device, *info)
print '\n'
print 'Simulate a medium-density screen as Motolora Droid 2:\n'
print ' python main.py -m screen,droid2\n'
print 'Simulate a high-density screen 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'
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 = ctx.keys()[0]
if device not in devices:
return usage('')
apply_device(device, scale, orientation)

View File

@ -173,7 +173,33 @@ __all__ = ('Property',
from weakref import ref
EventLoop = None
cdef float g_dpi = -1
cdef float g_density = -1
cdef float g_fontscale = -1
cpdef float dpi2px(value, ext):
# 1in = 2.54cm = 25.4mm = 72pt = 12pc
global g_dpi, g_density, g_fontscale
if g_dpi == -1:
from kivy.metrics import metrics
g_dpi = metrics.dpi
g_density = metrics.density
g_fontscale = metrics.fontscale
cdef float rv = float(value)
if ext == 'in':
return rv * g_dpi
elif ext == 'px':
return rv
elif ext == 'dp':
return rv * g_density
elif ext == 'sp':
return rv * g_density * g_fontscale
elif ext == 'pt':
return rv * g_dpi / 72.
elif ext == 'cm':
return rv * g_dpi / 2.54
elif ext == 'mm':
return rv * g_dpi / 25.4
cdef class Property:
@ -439,23 +465,8 @@ cdef class NumericProperty(Property):
return self.parse_list(obj, value[:-2], <str>value[-2:])
cdef float parse_list(self, EventDispatcher obj, value, str ext):
# 1in = 2.54cm = 25.4mm = 72pt = 12pc
global EventLoop
if EventLoop is None:
from kivy.base import EventLoop
cdef float rv = float(value)
cdef float dpi = EventLoop.dpi
obj.__storage[self.name]['format'] = ext
if ext == 'in':
return rv * dpi
elif ext == 'px':
return rv
elif ext == 'pt':
return rv * dpi / 72.
elif ext == 'cm':
return rv * dpi / 2.54
elif ext == 'mm':
return rv * dpi / 25.4
return dpi2px(value, ext)
def get_format(self, EventDispatcher obj):
'''

View File

@ -212,7 +212,7 @@ class AccordionItem(FloatLayout):
'''Link to the :attr:`Accordion.orientation` property.
'''
min_space = NumericProperty(44)
min_space = NumericProperty('44dp')
'''Link to the :attr:`Accordion.min_space` property.
'''
@ -321,7 +321,7 @@ class Accordion(Widget):
easing function.
'''
min_space = NumericProperty(44)
min_space = NumericProperty('44dp')
'''Minimum space to use for title of each item. This value is automatically
set on each children, each time the layout happens.

View File

@ -312,11 +312,14 @@ class Carousel(StencilView):
if self.collide_point(*touch.pos):
return super(Carousel, self).on_touch_move(touch)
def add_widget(self, *args, **kwargs):
def add_widget(self, widget, index=0):
slide = RelativeLayout(size=self.size, x=self.x - self.width, y=self.y)
slide.add_widget(*args, **kwargs)
super(Carousel, self).add_widget(slide)
self.slides.append(slide)
slide.add_widget(widget)
super(Carousel, self).add_widget(slide, index)
if index != 0:
self.slides.insert(index, slide)
else:
self.slides.append(slide)
def remove_widget(self, widget, *args, **kwargs):
# XXX be careful, the widget.parent.parent refer to the RelativeLayout

View File

@ -66,22 +66,34 @@ class CodeInput(TextInput):
def __init__(self, **kwargs):
self.formatter = BBCodeFormatter()
self.lexer = lexers.PythonLexer()
self.text_color = (0, 0, 0, 1)
self.text_color = '#000000'
self._label_cached = Label()
self.use_text_color = True
super(CodeInput, self).__init__(**kwargs)
self._line_options = kw = self._get_line_options()
self._label_cached = Label(**kw)
#use text_color as foreground color
# use text_color as foreground color
text_color = kwargs.get('foreground_color')
if text_color:
self.text_color = (text_color[0], text_color[1], text_color[2],
text_color[3])
get_hex_clr = self.get_hex_clr
self.text_color = ''.join(('#',
get_hex_clr(text_color[0]),
get_hex_clr(text_color[1]),
get_hex_clr(text_color[2]),
get_hex_clr(text_color[3])))
# set foreground to white to allow text colors to show
# use text_color as the default color in bbcodes
self.foreground_color = [1, 1, 1, 1]
self.use_text_color = False
self.foreground_color = [1, 1, 1, .999]
if not kwargs.get('background_color'):
self.background_color = [.9, .92, .92, 1]
def get_hex_clr(self, color):
clr = hex(int(color * 255))[2:]
if len(str(clr)) < 2:
clr = ''.join(('0', str(clr)))
return clr
def _create_line_label(self, text):
# Create a label from a text, using line options
ntext = text.replace('\n', '').replace('\t', ' ' * self.tab_width)
@ -104,7 +116,7 @@ class CodeInput(TextInput):
try:
label.refresh()
except ValueError:
pass
return
# ok, we found it.
texture = label.texture
@ -116,6 +128,7 @@ class CodeInput(TextInput):
kw = super(CodeInput, self)._get_line_options()
kw['markup'] = True
kw['valign'] = 'top'
kw['codeinput'] = True
return kw
def _get_bbcode(self, ntext):
@ -130,7 +143,7 @@ class CodeInput(TextInput):
ntext = highlight(ntext, self.lexer, self.formatter)
ntext = ntext.replace(u'⣿;', '&bl;').replace(u'⣾;', '&br;')
# replace special chars with &bl; and &br;
ntext = ''.join(('[color=rgba', str(self.text_color), ']',
ntext = ''.join(('[color=', str(self.text_color), ']',
ntext, '[/color]'))
ntext = ntext.replace('\n', '')
return ntext
@ -154,6 +167,21 @@ class CodeInput(TextInput):
def on_lexer(self, instance, value):
self._trigger_refresh_text()
def on_foreground_color(self, instance, text_color):
if not self.use_text_color:
self.use_text_color = True
return
get_hex_clr = self.get_hex_clr
self.text_color = ''.join((
get_hex_clr(text_color[0]),
get_hex_clr(text_color[1]),
get_hex_clr(text_color[2]),
get_hex_clr(text_color[3])))
self.use_text_color = False
self.foreground_color = (1, 1, 1, .999)
self._trigger_refresh_text()
if __name__ == '__main__':
from kivy.extras.highlight import KivyLexer
from kivy.app import App

View File

@ -15,7 +15,7 @@ strings::
l = Label(text='Multi\\nLine')
# size
l = Label(text='Hello world', font_size=20)
l = Label(text='Hello world', font_size='20sp')
Markup text
-----------
@ -148,9 +148,6 @@ class Label(Widget):
# markup have change, we need to change our rendering method.
d = Label._font_properties
dkw = dict(zip(d, [getattr(self, x) for x in d]))
# XXX font_size core provider compatibility
if Label.font_size.get_format(self) == 'px':
dkw['font_size'] *= 1.333
if markup:
self._label = CoreMarkupLabel(**dkw)
else:
@ -166,9 +163,6 @@ class Label(Widget):
elif name == 'text_size':
self._label.usersize = value
elif name == 'font_size':
# XXX font_size core provider compatibility
if Label.font_size.get_format(self) == 'px':
value *= 1.333
self._label.options[name] = value
else:
self._label.options[name] = value
@ -274,11 +268,11 @@ class Label(Widget):
'DroidSans'.
'''
font_size = NumericProperty('12px')
font_size = NumericProperty('15sp')
'''Font size of the text, in pixels.
:data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to
12.
12dp.
'''
bold = BooleanProperty(False)
@ -379,7 +373,7 @@ class Label(Widget):
l = Label(text='Hello world')
# l.texture is good
l.font_size = 50
l.font_size = '50sp'
# l.texture is not updated yet
l.update_texture()
# l.texture is good now.

View File

@ -119,13 +119,13 @@ class Popup(ModalView):
default to [47 / 255., 167 / 255., 212 / 255., 1.]
'''
separator_height = NumericProperty(2)
separator_height = NumericProperty('2dp')
'''Height of the separator.
.. versionadded:: 1.1.0
:data:`separator_height` is a :class:`~kivy.properties.NumericProperty`,
default to 2.
default to 2dp.
'''
# Internals properties used for graphical representation.

View File

@ -147,9 +147,9 @@ Builder.load_string('''
<RstTitle>:
markup: True
valign: 'top'
font_size: 24 - self.section * 2
font_size: sp(31 - self.section * 2)
size_hint_y: None
height: self.texture_size[1] + 20
height: self.texture_size[1] + dp(20)
text_size: self.width, None
bold: True
@ -178,7 +178,7 @@ Builder.load_string('''
markup: True
valign: 'top'
size_hint: None, None
size: self.texture_size[0] + 10, self.texture_size[1] + 10
size: self.texture_size[0] + dp(10), self.texture_size[1] + dp(10)
<RstBlockQuote>:
cols: 2
@ -198,7 +198,7 @@ Builder.load_string('''
cols: 1
content: content
size_hint_y: None
height: content.texture_size[1] + 20
height: content.texture_size[1] + dp(20)
canvas:
Color:
rgb: parse_color('#cccccc')
@ -277,11 +277,11 @@ Builder.load_string('''
<RstImage>:
size_hint: None, None
size: self.texture_size[0], self.texture_size[1] + 10
size: self.texture_size[0], self.texture_size[1] + dp(10)
<RstAsyncImage>:
size_hint: None, None
size: self.texture_size[0], self.texture_size[1] + 10
size: self.texture_size[0], self.texture_size[1] + dp(10)
<RstDefinitionList>:
cols: 1
@ -350,8 +350,8 @@ Builder.load_string('''
markup: True
valign: 'top'
size_hint_x: None
width: self.texture_size[0] + 10
text_size: None, self.height - 10
width: self.texture_size[0] + dp(10)
text_size: None, self.height - dp(10)
<RstEmptySpace>:
size_hint: 0.01, 0.01

View File

@ -47,9 +47,10 @@ a root widget for your own screen. Best way is to subclass.
Here is an example with a 'Menu Screen', and a 'Setting Screen'::
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# Create both screen. Please note the root.manager.current: this is how you
# can control the ScreenManager from kv. Each screen have by default a
# property manager that give you the instance of the ScreenManager used.
@ -61,7 +62,7 @@ Here is an example with a 'Menu Screen', and a 'Setting Screen'::
on_press: root.manager.current = 'settings'
Button:
text: 'Quit'
<SettingsScreen>:
BoxLayout:
Button:
@ -70,18 +71,26 @@ Here is an example with a 'Menu Screen', and a 'Setting Screen'::
text: 'Back to menu'
on_press: root.manager.current = 'menu'
""")
# Declare both screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
Changing transition
@ -367,7 +376,7 @@ class ShaderTransition(TransitionBase):
with self.render_ctx:
BindTexture(texture=self.fbo_out.texture, index=1)
BindTexture(texture=self.fbo_in.texture, index=2)
Rectangle(size=(1, 1))
Rectangle(size=(1, -1), pos=(0, 1))
self.render_ctx['projection_mat'] = Matrix().\
view_clip(0, 1, 0, 1, 0, 1, 0)
self.render_ctx['tex_out'] = 1

View File

@ -82,23 +82,48 @@ If you want to reduce the default timeout, you can set::
__all__ = ('ScrollView', )
from copy import copy
from functools import partial
from kivy.animation import Animation
from kivy.config import Config
from kivy.clock import Clock
from kivy.uix.stencilview import StencilView
from kivy.properties import NumericProperty, BooleanProperty, AliasProperty, \
ObjectProperty, ListProperty
ObjectProperty, ListProperty
# When we are generating documentation, Config doesn't exist
_scroll_timeout = _scroll_distance = _scroll_friction = 0
_scroll_moves = _scroll_timeout = _scroll_stoptime = \
_scroll_distance = _scroll_friction = 0
if Config:
_scroll_timeout = Config.getint('widgets', 'scroll_timeout')
_scroll_stoptime = Config.getint('widgets', 'scroll_stoptime')
_scroll_distance = Config.getint('widgets', 'scroll_distance')
_scroll_moves = Config.getint('widgets', 'scroll_moves')
_scroll_friction = Config.getfloat('widgets', 'scroll_friction')
class FixedList(list):
'''A list. In addition, you can specify the maximum length.
This will save memory.
'''
def __init__(self, maxlength=0, *args, **kwargs):
super(FixedList, self).__init__(*args, **kwargs)
self.maxlength = maxlength
def append(self, x):
super(FixedList, self).append(x)
self._cut()
def extend(self, L):
super(FixedList, self).append(L)
self._cut()
def _cut(self):
while len(self) > self.maxlength:
self.pop(0)
class ScrollView(StencilView):
'''ScrollView class. See module documentation for more information.
'''
@ -206,8 +231,10 @@ class ScrollView(StencilView):
return
uid = self._get_uid()
touch = self._touch
mode = touch.ud[uid]['mode']
if mode == 'unknown':
ud = touch.ud[uid]
if ud['mode'] == 'unknown' and \
not ud['user_stopped'] and \
touch.dx + touch.dy == 0:
touch.ungrab(self)
self._touch = None
# correctly calculate the position of the touch inside the
@ -231,31 +258,36 @@ class ScrollView(StencilView):
super(ScrollView, self).on_touch_up(touch)
touch.grab_current = None
def _do_animation(self, touch):
def _do_animation(self, touch, *largs):
uid = self._get_uid()
ud = touch.ud[uid]
dt = touch.time_end - ud['time']
if dt > self.scroll_timeout / 1000.:
self._tdx = self._tdy = self._ts = 0
avgdx = sum([move.dx for move in ud['moves']]) / len(ud['moves'])
avgdy = sum([move.dy for move in ud['moves']]) / len(ud['moves'])
if ud['same'] > self.scroll_stoptime / 1000.:
return
dt = ud['dt']
if dt == 0:
self._tdx = self._tdy = self._ts = 0
return
dx = touch.dx
dy = touch.dy
dx = avgdx
dy = avgdy
self._sx = ud['sx']
self._sy = ud['sy']
self._tdx = dx = dx / dt
self._tdy = dy = dy / dt
if abs(dx) < 10 and abs(dy) < 10:
return
self._ts = self._tsn = touch.time_update
Clock.unschedule(self._update_animation)
Clock.schedule_interval(self._update_animation, 0)
def _update_animation(self, dt):
if self._touch is not None or self._ts == 0:
touch = self._touch
uid = self._get_uid()
ud = touch.ud[uid]
# scrolling stopped by user input
ud['user_stopped'] = True
return False
self._tsn += dt
global_dt = self._tsn - self._ts
@ -268,13 +300,10 @@ class ScrollView(StencilView):
(self.do_scroll_y and not self.do_scroll_x and test_dy) or\
(self.do_scroll_x and self.do_scroll_y and test_dx and test_dy):
self._ts = 0
# scrolling stopped by friction
return False
dx *= dt
dy *= dt
'''
print 'move by %.3f %.3f | dt=%.3f, divider=%.3f, tdXY=(%.3f, %.3f)' % (
dx, dy, global_dt, divider, self._tdx, self._tdy)
'''
sx, sy = self.convert_distance_to_scroll(dx, dy)
ssx = self.scroll_x
ssy = self.scroll_y
@ -284,8 +313,20 @@ class ScrollView(StencilView):
self.scroll_y -= sy
self._scroll_y_mouse = self.scroll_y
if ssx == self.scroll_x and ssy == self.scroll_y:
# scrolling stopped by end of box
return False
def _update_delta(self, dt):
touch = self._touch
if not touch:
return False
uid = self._get_uid()
ud = touch.ud[uid]
if touch.dx + touch.dy != 0:
ud['same'] += dt
else:
ud['same'] = 0
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
touch.ud[self._get_uid('svavoid')] = True
@ -300,7 +341,8 @@ class ScrollView(StencilView):
if vp.height > self.height:
# let's say we want to move over 40 pixels each scroll
d = (vp.height - self.height)
d = self.scroll_distance / float(d)
if d != 0:
d = self.scroll_distance / float(d)
if touch.button == 'scrollup':
syd = self._scroll_y_mouse - d
elif touch.button == 'scrolldown':
@ -319,9 +361,14 @@ class ScrollView(StencilView):
'sx': self.scroll_x,
'sy': self.scroll_y,
'dt': None,
'time': touch.time_start}
'time': touch.time_start,
'user_stopped': False,
'same': 0,
'moves': FixedList(self.scroll_moves)}
Clock.schedule_interval(self._update_delta, 0)
Clock.schedule_once(self._change_touch_mode,
self.scroll_timeout / 1000.)
self.scroll_timeout / 1000.)
return True
def on_touch_move(self, touch):
@ -335,6 +382,7 @@ class ScrollView(StencilView):
uid = self._get_uid()
ud = touch.ud[uid]
mode = ud['mode']
ud['moves'].append(copy(touch))
# seperate the distance to both X and Y axis.
# if a distance is reach, but on the inverse axis, stop scroll mode !
@ -364,6 +412,7 @@ class ScrollView(StencilView):
self.scroll_y = ud['sy'] + sy
ud['dt'] = touch.time_update - ud['time']
ud['time'] = touch.time_update
ud['user_stopped'] = True
return True
@ -374,7 +423,7 @@ class ScrollView(StencilView):
# never ungrabed, cause their on_touch_up will be never called.
# base.py: the me.grab_list[:] => it's a copy, and we are already
# iterate on it.
Clock.unschedule(self._update_delta)
if self._get_uid('svavoid') in touch.ud:
return
@ -385,10 +434,12 @@ class ScrollView(StencilView):
touch.ungrab(self)
self._touch = None
uid = self._get_uid()
mode = touch.ud[uid]['mode']
if mode == 'unknown':
ud = touch.ud[uid]
if ud['mode'] == 'unknown':
# we must do the click at least..
super(ScrollView, self).on_touch_down(touch)
# only send the click if it was not a click to stop autoscrolling
if not ud['user_stopped']:
super(ScrollView, self).on_touch_down(touch)
Clock.schedule_once(partial(self._do_touch_up, touch), .1)
elif self.auto_scroll:
self._do_animation(touch)
@ -433,10 +484,39 @@ class ScrollView(StencilView):
default to 1, according to the default value in user configuration.
'''
scroll_moves = NumericProperty(_scroll_moves)
'''The speed of automatic scrolling is based on previous touch moves. This
is to prevent accidental slowing down by the user at the end of the swipe
to slow down the automatic scrolling.
The moves property specifies the amount of previous scrollmoves that
should be taken into consideration when calculating the automatic scrolling
speed.
:data:`scroll_moves` is a :class:`~kivy.properties.NumericProperty`,
default to 5.
.. versionadded:: 1.5.0
'''
scroll_stoptime = NumericProperty(_scroll_stoptime)
'''Time after which user input not moving will disable autoscroll for that
move. If the user has not moved within the stoptime, autoscroll will not
start.
This is to prevent autoscroll to trigger while the user has slowed down
on purpose to prevent this.
:data:`scroll_stoptime` is a :class:`~kivy.properties.NumericProperty`,
default to 300 (milliseconds)
.. versionadded:: 1.5.0
'''
scroll_distance = NumericProperty(_scroll_distance)
'''Distance to move before scrolling the :class:`ScrollView`, in pixels. As
soon as the distance has been traveled, the :class:`ScrollView` will start
to scroll, and no touch event will go to children.
It is advisable that you base this value on the dpi of your target device's
screen.
:data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`,
default to 20 (pixels), according to the default value in user
@ -445,12 +525,15 @@ class ScrollView(StencilView):
scroll_timeout = NumericProperty(_scroll_timeout)
'''Timeout allowed to trigger the :data:`scroll_distance`, in milliseconds.
If the timeout is reached, the scrolling will be disabled, and the touch
event will go to the children.
If the user has not moved :data:`scroll_distance` within the timeout,
the scrolling will be disabled, and the touch event will go to the children.
:data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`,
default to 250 (milliseconds), according to the default value in user
default to 55 (milliseconds), according to the default value in user
configuration.
.. versionchanged:: 1.5.0
Default value changed from 250 to 55.
'''
scroll_x = NumericProperty(0.)
@ -568,7 +651,7 @@ class ScrollView(StencilView):
[.7, .7, .7, .9].
'''
bar_width = NumericProperty(2)
bar_width = NumericProperty('2dp')
'''Width of the horizontal / vertical scroll bar. The width is interpreted
as a height for the horizontal bar.
@ -601,4 +684,3 @@ class ScrollView(StencilView):
if value:
value.bind(size=self._set_viewport_size)
self._viewport_size = value.size

View File

@ -108,6 +108,7 @@ __all__ = ('Settings', 'SettingsPanel', 'SettingItem', 'SettingString',
import json
import os
from kivy.metrics import dp
from kivy.config import ConfigParser
from kivy.animation import Animation
from kivy.uix.boxlayout import BoxLayout
@ -339,13 +340,13 @@ class SettingString(SettingItem):
def _create_popup(self, instance):
# create popup layout
content = BoxLayout(orientation='vertical', spacing=5)
content = BoxLayout(orientation='vertical', spacing='5dp')
self.popup = popup = Popup(title=self.title,
content=content, size_hint=(None, None), size=(400, 250))
content=content, size_hint=(None, None), size=('400dp', '250dp'))
# create the textinput used for numeric input
self.textinput = textinput = TextInput(text=str(self.value),
font_size=24, multiline=False, size_hint_y=None, height=50)
font_size=24, multiline=False, size_hint_y=None, height='50dp')
textinput.bind(on_text_validate=self._validate)
self.textinput = textinput
@ -356,7 +357,7 @@ class SettingString(SettingItem):
content.add_widget(SettingSpacer())
# 2 buttons are created for accept or cancel the current value
btnlayout = BoxLayout(size_hint_y=None, height=50, spacing=5)
btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp')
btn = Button(text='Ok')
btn.bind(on_release=self._validate)
btnlayout.add_widget(btn)
@ -433,7 +434,7 @@ class SettingPath(SettingItem):
content.add_widget(SettingSpacer())
# 2 buttons are created for accept or cancel the current value
btnlayout = BoxLayout(size_hint_y=None, height=50, spacing=5)
btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp')
btn = Button(text='Ok')
btn.bind(on_release=self._validate)
btnlayout.add_widget(btn)
@ -500,10 +501,10 @@ class SettingOptions(SettingItem):
def _create_popup(self, instance):
# create the popup
content = BoxLayout(orientation='vertical', spacing=5)
content = BoxLayout(orientation='vertical', spacing='5dp')
self.popup = popup = Popup(content=content,
title=self.title, size_hint=(None, None), size=(400, 400))
popup.height = len(self.options) * 55 + 150
title=self.title, size_hint=(None, None), size=('400dp', '400dp'))
popup.height = len(self.options) * dp(55) + dp(150)
# add all the options
content.add_widget(Widget(size_hint_y=None, height=1))
@ -516,7 +517,7 @@ class SettingOptions(SettingItem):
# finally, add a cancel button to return on the previous panel
content.add_widget(SettingSpacer())
btn = Button(text='Cancel', size_hint_y=None, height=50)
btn = Button(text='Cancel', size_hint_y=None, height=dp(50))
btn.bind(on_release=popup.dismiss)
content.add_widget(btn)
@ -654,12 +655,12 @@ class Settings(BoxLayout):
'''
self._types[tp] = cls
def add_json_panel(self, title, config, filename=None, data=None):
'''Create and add a new :class:`SettingsPanel` using the configuration
`config`, with the JSON definition `filename`.
def create_json_panel(self, title, config, filename=None, data=None):
'''Create new :class:`SettingsPanel`.
Check the :ref:`settings_json` section in the documentation for more
information about JSON format, and the usage of this function.
.. versionadded:: 1.5.0
Check the documentation of :meth:`add_json_panel` for more information.
'''
if filename is None and data is None:
raise Exception('You must specify either the filename or data')
@ -671,7 +672,6 @@ class Settings(BoxLayout):
if type(data) != list:
raise ValueError('The first element must be a list')
panel = SettingsPanel(title=title, settings=self, config=config)
self.add_widget(panel)
for setting in data:
# determine the type and the class to use
@ -696,6 +696,16 @@ class Settings(BoxLayout):
return panel
def add_json_panel(self, title, config, filename=None, data=None):
'''Create and add a new :class:`SettingsPanel` using the configuration
`config`, with the JSON definition `filename`.
Check the :ref:`settings_json` section in the documentation for more
information about JSON format, and the usage of this function.
'''
panel = self.create_json_panel(title, config, filename, data)
self.add_widget(panel)
def add_kivy_panel(self):
'''Add a panel for configuring Kivy. This panel acts directly on the
kivy configuration. Feel free to include or exclude it in your

View File

@ -234,14 +234,14 @@ class TabbedPanel(GridLayout):
default to 'bottom_mid'.
'''
tab_height = NumericProperty(40)
tab_height = NumericProperty('40dp')
'''Specifies the height of the tab header.
:data:`tab_height` is a :class:`~kivy.properties.NumericProperty`,
default to 40.
'''
tab_width = NumericProperty(100, allownone=True)
tab_width = NumericProperty('100dp', allownone=True)
'''Specifies the width of the tab header.
:data:`tab_width` is a :class:`~kivy.properties.NumericProperty`,

View File

@ -1171,15 +1171,9 @@ class TextInput(Widget):
def _get_line_options(self):
# Get or create line options, to be used for Label creation
# XXX font_size core label compatibility
factor = 1.
if TextInput.font_size.get_format(self) == 'px':
factor = 1.333
if self._line_options is None:
self._line_options = kw = {
'font_size': self.font_size * factor,
'font_size': self.font_size,
'font_name': self.font_name,
'anchor_x': 'left',
'anchor_y': 'top',
@ -1714,7 +1708,7 @@ class TextInput(Widget):
'DroidSans'.
'''
font_size = NumericProperty(10)
font_size = NumericProperty('15sp')
'''Font size of the text, in pixels.
:data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to

View File

@ -509,7 +509,7 @@ class TreeView(Widget):
of (:data:`minimum_width`, :data:`minimum_height`) properties.
'''
indent_level = NumericProperty(16)
indent_level = NumericProperty('16dp')
'''Width used for identation of each level, except the first level.
Computation of spacing for eaching level of tree is::
@ -520,7 +520,7 @@ class TreeView(Widget):
defaults to 16.
'''
indent_start = NumericProperty(24)
indent_start = NumericProperty('24dp')
'''Indentation width of the level 0 / root node. This is mostly the initial
size to accommodate a tree icon (collapsed / expanded). See
:data:`indent_level` for more information about the computation of level

View File

@ -276,7 +276,7 @@ class VKeyboard(Scatter):
have_capslock = BooleanProperty(False)
have_shift = BooleanProperty(False)
active_keys = DictProperty({})
font_size = NumericProperty(15)
font_size = NumericProperty('20dp')
font_name = StringProperty('data/fonts/DejaVuSans.ttf')
def __init__(self, **kwargs):
@ -561,6 +561,7 @@ class VKeyboard(Scatter):
background = resource_find(self.background)
texture = Image(background, mipmap=True).texture
self.background_key_layer.clear()
with self.background_key_layer:
Color(*self.background_color)
BorderImage(texture=texture, size=self.size,
@ -572,12 +573,11 @@ class VKeyboard(Scatter):
# first draw keys without the font
key_normal = resource_find(self.key_background_normal)
texture = Image(key_normal, mipmap=True).texture
for line_nb in xrange(1, layout_rows + 1):
for pos, size in layout_geometry['LINE_%d' % line_nb]:
with self.background_key_layer:
Color(self.key_background_color)
BorderImage(texture=texture, pos=pos, size=size,
border=self.key_border)
with self.background_key_layer:
for line_nb in xrange(1, layout_rows + 1):
for pos, size in layout_geometry['LINE_%d' % line_nb]:
BorderImage(texture=texture, pos=pos, size=size,
border=self.key_border)
# then draw the text
# calculate font_size

View File

@ -33,6 +33,24 @@ Our widget class is designed with a couple of principles in mind:
You can also check if a widget collides with another widget with
:meth:`Widget.collide_widget`.
We also have some defaults that you should be aware of:
* A :class:`Widget` is not a :class:`Layout`: it will not change the position
nor the size of its children. If you want a better positionning / sizing, use
a :class:`Layout`.
* The default size is (100, 100), if the parent is not a :class:`Layout`. For
example, adding a widget inside a :class:`Button`, :class:`Label`, will not
inherit from the parent size or pos.
* The default size_hint is (1, 1). If the parent is a :class:`Layout`, then the
widget size will be the parent/layout size.
* All the :meth:`Widget.on_touch_down`, :meth:`Widget.on_touch_move`,
:meth:`Widget.on_touch_up` doesn't do any sort of collisions. If you want to
know if the touch is inside your widget, use :meth:`Widget.collide_point`.
Using Properties
----------------
@ -54,6 +72,8 @@ widget moves, you can bind your own callback function like this::
wid = Widget()
wid.bind(pos=callback_pos)
Read more about the :doc:`/api-kivy.properties`.
'''
__all__ = ('Widget', 'WidgetException')
@ -121,7 +141,7 @@ class Widget(EventDispatcher):
# Create the default canvas if not exist
if self.canvas is None:
self.canvas = Canvas()
self.canvas = Canvas(opacity=self.opacity)
# Apply all the styles
if '__no_builder' not in kwargs:
@ -566,7 +586,9 @@ class Widget(EventDispatcher):
'''
def on_opacity(self, instance, value):
self.canvas.opacity = value
canvas = self.canvas
if canvas is not None:
canvas.opacity = value
canvas = None
'''Canvas of the widget.