Merge pull request #625 from kivy/core-dpi

Handle DPI in NumericProperty
This commit is contained in:
Mathieu Virbel 2012-09-06 17:52:30 -07:00
commit 642abedbf6
11 changed files with 185 additions and 31 deletions

View File

@ -32,6 +32,11 @@ 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
------------
@ -95,3 +100,4 @@ KIVY_CLIPBOARD
Implementation to use for clipboard management
Values: pygame, dummy

View File

@ -171,6 +171,8 @@ def kivy_usage():
Save current Kivy configuration.
--size=640x480
Size of window geometry.
--dpi=96
Manually overload the Window DPI (for testing only.)
'''
print kivy_usage.__doc__ % (basename(sys.argv[0]))
@ -284,7 +286,8 @@ if not environ.get('KIVY_DOC_INCLUDE'):
opts, args = getopt(sys_argv[1:], 'hp:fkawFem:sr:dc:',
['help', 'fullscreen', 'windowed', 'fps', 'event',
'module=', 'save', 'fake-fullscreen', 'auto-fullscreen',
'display=', 'size=', 'rotate=', 'config=', 'debug'])
'display=', 'size=', 'rotate=', 'config=', 'debug',
'dpi='])
except GetoptError, err:
Logger.error('Core: %s' % str(err))
@ -353,6 +356,8 @@ if not environ.get('KIVY_DOC_INCLUDE'):
elif opt in ('-d', '--debug'):
level = LOG_LEVELS.get('debug')
Logger.setLevel(level=level)
elif opt == '--dpi':
environ['KIVY_DPI'] = arg
if need_save and 'KIVY_NO_CONFIG' not in environ:
try:

View File

@ -15,10 +15,12 @@ __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
@ -103,6 +105,32 @@ 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

@ -20,7 +20,7 @@ class LabelPIL(LabelBase):
_cache = {}
def _select_font(self):
fontsize = int(self.options['font_size'] * 1.333)
fontsize = int(self.options['font_size'])
fontname = self.options['font_name_r']
id = '%s.%s' % (unicode(fontname), unicode(fontsize))
if not id in self._cache:

View File

@ -35,7 +35,7 @@ class LabelPygame(LabelBase):
if ext.lower() == 'ttf':
# fontobject
fontobject = pygame.font.Font(fontname,
int(self.options['font_size'] * 1.333))
int(self.options['font_size']))
# fallback to search a system font
if fontobject is None:
@ -47,7 +47,7 @@ class LabelPygame(LabelBase):
# fontobject
fontobject = pygame.font.Font(font,
int(self.options['font_size'] * 1.333))
int(self.options['font_size']))
pygame_cache[fontid] = fontobject
pygame_cache_order.append(fontid)

View File

@ -21,7 +21,7 @@ from kivy.modules import Modules
from kivy.event import EventDispatcher
from kivy.properties import ListProperty, ObjectProperty, AliasProperty, \
NumericProperty, OptionProperty, StringProperty
from kivy.utils import platform
from kivy.utils import platform, reify
# late import
VKeyboard = None
@ -804,6 +804,18 @@ class WindowBase(EventDispatcher):
'''
pass
@reify
def dpi(self):
'''Return the DPI of the screen. If the implementation doesn't support
any DPI lookup, it will just return 96.
.. warning::
This value is not cross-platform. Use
:data:`kivy.base.EventLoop.dpi` instead.
'''
return 96.
def configure_keyboards(self):
# Configure how to provide keyboards (virtual or not)

View File

@ -2,7 +2,7 @@ cdef class Property:
cdef str _name
cdef int allownone
cdef object defaultvalue
cdef init_storage(self, dict storage)
cdef init_storage(self, object obj, dict storage)
cpdef link(self, object obj, str name)
cpdef link_deps(self, object obj, str name)
cpdef bind(self, obj, observer)
@ -15,7 +15,8 @@ cdef class Property:
cpdef dispatch(self, obj)
cdef class NumericProperty(Property):
pass
cdef float parse_str(self, object obj, value)
cdef float parse_list(self, object obj, value, bytes ext)
cdef class StringProperty(Property):
pass

View File

@ -153,6 +153,9 @@ __all__ = ('Property',
from weakref import ref
EventLoop = None
cdef class Property:
'''Base class for building more complex properties.
@ -197,8 +200,8 @@ cdef class Property:
def __get__(self):
return self._name
cdef init_storage(self, dict storage):
storage['value'] = self.defaultvalue
cdef init_storage(self, object obj, dict storage):
storage['value'] = self.convert(obj, self.defaultvalue)
storage['allownone'] = self.allownone
storage['observers'] = []
@ -223,8 +226,8 @@ cdef class Property:
'''
d = dict()
self._name = name
self.init_storage(d)
obj.__storage[name] = d
self.init_storage(obj, d)
cpdef link_deps(self, object obj, str name):
pass
@ -327,27 +330,87 @@ cdef class NumericProperty(Property):
The NumericProperty accepts only int or float.
>>> Widget.x = 42
>>> print Widget.x
>>> wid = Widget()
>>> wid.x = 42
>>> print wid.x
42
>>> Widget.x = "plop"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "properties.pyx", line 93, in kivy.properties.Property.__set__
File "properties.pyx", line 111, in kivy.properties.Property.set
File "properties.pyx", line 159, in kivy.properties.NumericProperty.check
>>> wid.x = "plop"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "properties.pyx", line 93, in kivy.properties.Property.__set__
File "properties.pyx", line 111, in kivy.properties.Property.set
File "properties.pyx", line 159, in kivy.properties.NumericProperty.check
ValueError: NumericProperty accept only int/float
.. versionchanged:: 1.4.1
NumericProperty can now accept custom text and tuple value to indicate a
type, like "in", "pt", "px", "cm", "mm", in the format: '10pt' or (10,
'pt').
'''
def __init__(self, defaultvalue=0, **kw):
super(NumericProperty, self).__init__(defaultvalue, **kw)
cdef init_storage(self, object obj, dict storage):
storage['format'] = 'px'
Property.init_storage(self, obj, storage)
cdef check(self, obj, value):
if Property.check(self, obj, value):
return True
if type(value) not in (int, float):
raise ValueError('%s.%s accept only int/float' % (
raise ValueError('%s.%s accept only int/float (got %r)' % (
obj.__class__.__name__,
self.name))
self.name, value))
cdef convert(self, obj, x):
if x is None:
return x
tp = type(x)
if tp is int or tp is float:
return x
if tp is tuple or tp is list:
if len(x) != 2:
raise ValueError('%s.%s must have 2 components (got %r)' % (
obj.__class__.__name__,
self.name, x))
return self.parse_list(obj, x[0], <bytes>x[1])
elif tp is str:
return self.parse_str(obj, x)
else:
raise ValueError('%s.%s have an invalid format (got %r)' % (
obj.__class__.__name__,
self.name, x))
cdef float parse_str(self, object obj, value):
return self.parse_list(obj, value[:-2], <bytes>value[-2:])
cdef float parse_list(self, object obj, value, bytes 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
def get_format(self, obj):
'''
Return the format used for Numeric calculation. Default is px (mean
the value have not been changed at all). Otherwise, it can be one of
'in', 'pt', 'cm', 'mm'.
'''
return obj.__storage[self.name]['format']
cdef class StringProperty(Property):
@ -601,8 +664,8 @@ cdef class BoundedNumericProperty(Property):
self.max = value
Property.__init__(self, *largs, **kw)
cdef init_storage(self, dict storage):
Property.init_storage(self, storage)
cdef init_storage(self, object obj, dict storage):
Property.init_storage(self, obj, storage)
storage['min'] = self.min
storage['max'] = self.max
storage['use_min'] = self.use_min
@ -727,8 +790,8 @@ cdef class OptionProperty(Property):
self.options = <list>(kw.get('options', []))
super(OptionProperty, self).__init__(*largs, **kw)
cdef init_storage(self, dict storage):
Property.init_storage(self, storage)
cdef init_storage(self, object obj, dict storage):
Property.init_storage(self, obj, storage)
storage['options'] = self.options[:]
cdef check(self, obj, value):
@ -769,8 +832,8 @@ cdef class ReferenceListProperty(Property):
self.properties.append(prop)
Property.__init__(self, largs, **kw)
cdef init_storage(self, dict storage):
Property.init_storage(self, storage)
cdef init_storage(self, object obj, dict storage):
Property.init_storage(self, obj, storage)
storage['properties'] = self.properties
storage['stop_event'] = 0
@ -869,8 +932,8 @@ cdef class AliasProperty(Property):
self.bind_objects = list(v) if v is not None else []
self.use_cache = 1 if kwargs.get('cache') else 0
cdef init_storage(self, dict storage):
Property.init_storage(self, storage)
cdef init_storage(self, object obj, dict storage):
Property.init_storage(self, obj, storage)
storage['getter'] = self.getter
storage['setter'] = self.setter
storage['initial'] = True

View File

@ -148,6 +148,9 @@ 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:
@ -162,6 +165,11 @@ class Label(Widget):
self._label.text = value
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
self._trigger_texture()
@ -266,7 +274,7 @@ class Label(Widget):
'DroidSans'.
'''
font_size = NumericProperty(12)
font_size = NumericProperty('12px')
'''Font size of the text, in pixels.
:data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to

View File

@ -1074,9 +1074,15 @@ 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,
'font_size': self.font_size * factor,
'font_name': self.font_name,
'anchor_x': 'left',
'anchor_y': 'top',

View File

@ -10,7 +10,7 @@ __all__ = ('intersection', 'difference', 'strtotuple',
'is_color_transparent', 'boundary',
'deprecated', 'SafeList',
'interpolate', 'OrderedDict', 'QueryDict',
'platform', 'escape_markup')
'platform', 'escape_markup', 'reify')
from sys import platform as _sys_platform
from re import match, split
@ -370,3 +370,28 @@ def escape_markup(text):
'''
return text.replace('[', '&bl;').replace(']', '&br;').replace('&', '&amp;')
class reify(object):
'''
Put the result of a method which uses this (non-data) descriptor decorator
in the instance dict after the first call, effectively replacing the
decorator with an instance variable.
It acts like @property, except that the function is only ever called once;
after that, the value is cached as a regular attribute. This gives you lazy
attribute creation on objects that are meant to be immutable.
Taken from Pyramid project.
'''
def __init__(self, func):
self.func = func
self.__doc__ = func.__doc__
def __get__(self, inst, cls):
if inst is None:
return self
retval = self.func(inst)
setattr(inst, self.func.__name__, retval)
return retval