mirror of https://github.com/kivy/kivy.git
Merge pull request #625 from kivy/core-dpi
Handle DPI in NumericProperty
This commit is contained in:
commit
642abedbf6
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
28
kivy/base.py
28
kivy/base.py
|
@ -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
|
||||
'''
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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('&', '&')
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in New Issue