From f42f05f743c7df0115e6267297abc8e682ce6e92 Mon Sep 17 00:00:00 2001 From: milanboers Date: Mon, 22 Oct 2012 19:20:48 +0200 Subject: [PATCH 01/40] Made scrolling more intuitive. Tapping/clicking is now immediately (within 55ms by default) being passed to the children if not scrolling instead of a delay, except for when there was a scroll move within this time. Also takes into account more than one move for a better and more intuitive experience on touch devices. Tested on desktop and phone. --- kivy/config.py | 4 +- kivy/uix/scrollview.py | 148 ++++++++++++++++++++++++++++++----------- 2 files changed, 114 insertions(+), 38 deletions(-) diff --git a/kivy/config.py b/kivy/config.py index 7be5a88e8..c3f1fb98d 100644 --- a/kivy/config.py +++ b/kivy/config.py @@ -388,9 +388,11 @@ 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.') + Config.setdefualt('widgets', 'scroll_stoptime', '300') + Config.setdefault('widgets', 'scroll_moves', '5') # remove old list_* token Config.remove_option('widgets', 'list_friction') diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py index 8293bde23..0c6c9ba9c 100644 --- a/kivy/uix/scrollview.py +++ b/kivy/uix/scrollview.py @@ -82,23 +82,47 @@ 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_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,10 +230,12 @@ class ScrollView(StencilView): return uid = self._get_uid() touch = self._touch - mode = touch.ud[uid]['mode'] - if mode == 'unknown': - touch.ungrab(self) - self._touch = None + 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 # scrollview touch.push() @@ -231,31 +257,40 @@ 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['user_stopped'] and \ + abs(avgdy) < self.scroll_distance and \ + abs(avgdx) < self.scroll_distance: + return + 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,6 +303,7 @@ 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 @@ -284,8 +320,18 @@ 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 + 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 @@ -299,12 +345,10 @@ class ScrollView(StencilView): vp = self._viewport 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 touch.button == 'scrollup': - syd = self._scroll_y_mouse - d + syd = self._scroll_y_mouse elif touch.button == 'scrolldown': - syd = self._scroll_y_mouse + d + syd = self._scroll_y_mouse self._scroll_y_mouse = scroll_y = min(max(syd, 0), 1) Animation.stop_all(self, 'scroll_y') Animation(scroll_y=scroll_y, d=.3, t='out_quart').start(self) @@ -319,9 +363,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,23 +384,22 @@ 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 ! if mode == 'unknown': distance = abs(touch.ox - touch.x) - if distance > self.scroll_distance: - if not self.do_scroll_x: - self._change_touch_mode() - return - mode = 'scroll' + if not self.do_scroll_x: + self._change_touch_mode() + return + mode = 'scroll' distance = abs(touch.oy - touch.y) - if distance > self.scroll_distance: - if not self.do_scroll_y: - self._change_touch_mode() - return - mode = 'scroll' + if not self.do_scroll_y: + self._change_touch_mode() + return + mode = 'scroll' if mode == 'scroll': ud['mode'] = mode @@ -374,7 +422,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 +433,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 +483,35 @@ 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. + ''' + + 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) + ''' + 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,11 +520,11 @@ 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. ''' @@ -601,4 +676,3 @@ class ScrollView(StencilView): if value: value.bind(size=self._set_viewport_size) self._viewport_size = value.size - From c7d5e98632c6f06a6a90bfa759f3d90a45981bad Mon Sep 17 00:00:00 2001 From: milanboers Date: Mon, 22 Oct 2012 19:22:23 +0200 Subject: [PATCH 02/40] Config documentation update. --- kivy/config.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kivy/config.py b/kivy/config.py index c3f1fb98d..50100f42b 100644 --- a/kivy/config.py +++ b/kivy/config.py @@ -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:: From b42ddc4222c4035e742fabb6e21f558bb7c952ad Mon Sep 17 00:00:00 2001 From: milanboers Date: Tue, 23 Oct 2012 07:18:17 +0200 Subject: [PATCH 03/40] Fixed typo. --- kivy/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy/config.py b/kivy/config.py index 50100f42b..c198f0376 100644 --- a/kivy/config.py +++ b/kivy/config.py @@ -401,7 +401,7 @@ if not environ.get('KIVY_DOC_INCLUDE'): Config.setdefault('widgets', 'scroll_timeout', '55') Config.setdefault('widgets', 'scroll_distance', '20') Config.setdefault('widgets', 'scroll_friction', '1.') - Config.setdefualt('widgets', 'scroll_stoptime', '300') + Config.setdefault('widgets', 'scroll_stoptime', '300') Config.setdefault('widgets', 'scroll_moves', '5') # remove old list_* token From 7b3b209f4332e31a0cdad18c2fab5e678b5bc01b Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Sun, 28 Oct 2012 16:10:07 +0100 Subject: [PATCH 04/40] vkeyboard: fix missing clear() on background canvas + speedup the background loop. closes #770 --- kivy/uix/vkeyboard.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kivy/uix/vkeyboard.py b/kivy/uix/vkeyboard.py index a70ff0cfc..ef55e22d1 100644 --- a/kivy/uix/vkeyboard.py +++ b/kivy/uix/vkeyboard.py @@ -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 From 2516ccc2f8f7b07caa156cb61057aa276a7714b9 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Sun, 28 Oct 2012 17:30:21 +0100 Subject: [PATCH 05/40] widget: enhance the documentation of Widget to include more information / disclamer about the defaults of the Widget --- kivy/uix/widget.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/kivy/uix/widget.py b/kivy/uix/widget.py index 7739922bf..d38cbbfbc 100644 --- a/kivy/uix/widget.py +++ b/kivy/uix/widget.py @@ -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') From 11f55027a049299ce257582cb22f63a60d00cef9 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Sun, 28 Oct 2012 17:54:38 +0100 Subject: [PATCH 06/40] ios/packaging: update documentation about launch image --- doc/sources/guide/packaging-ios.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/doc/sources/guide/packaging-ios.rst b/doc/sources/guide/packaging-ios.rst index f22ff4569..84cc54928 100644 --- a/doc/sources/guide/packaging-ios.rst +++ b/doc/sources/guide/packaging-ios.rst @@ -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. From 8b5c0e4a1fe4919e31813c239c59468c9cca8431 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 24 Oct 2012 12:27:29 +0200 Subject: [PATCH 07/40] fix auto configuration upgrade with betterscroll --- kivy/config.py | 11 ++++++++--- kivy/uix/scrollview.py | 9 ++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/kivy/config.py b/kivy/config.py index c198f0376..6530ee358 100644 --- a/kivy/config.py +++ b/kivy/config.py @@ -184,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 @@ -401,8 +401,6 @@ if not environ.get('KIVY_DOC_INCLUDE'): Config.setdefault('widgets', 'scroll_timeout', '55') Config.setdefault('widgets', 'scroll_distance', '20') Config.setdefault('widgets', 'scroll_friction', '1.') - Config.setdefault('widgets', 'scroll_stoptime', '300') - Config.setdefault('widgets', 'scroll_moves', '5') # remove old list_* token Config.remove_option('widgets', 'list_friction') @@ -420,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 # diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py index 0c6c9ba9c..c6194fb4f 100644 --- a/kivy/uix/scrollview.py +++ b/kivy/uix/scrollview.py @@ -493,6 +493,8 @@ class ScrollView(StencilView): :data:`scroll_moves` is a :class:`~kivy.properties.NumericProperty`, default to 5. + + .. versionadded:: 1.5.0 ''' scroll_stoptime = NumericProperty(_scroll_stoptime) @@ -501,9 +503,11 @@ class ScrollView(StencilView): 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) @@ -526,6 +530,9 @@ class ScrollView(StencilView): :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`, 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.) From 22927bef96b20ef7f8e188b3d578376c8413b459 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 01:21:50 +0100 Subject: [PATCH 08/40] wm_touch/pen: try to see if SetWindowLongPtrW exist with AttributeError exception, as referenced in the issue. closes #755 --- kivy/input/providers/wm_pen.py | 4 ++-- kivy/input/providers/wm_touch.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kivy/input/providers/wm_pen.py b/kivy/input/providers/wm_pen.py index 2abdc7ab4..d6dd3170c 100644 --- a/kivy/input/providers/wm_pen.py +++ b/kivy/input/providers/wm_pen.py @@ -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 diff --git a/kivy/input/providers/wm_touch.py b/kivy/input/providers/wm_touch.py index 8f96269cf..c1bb66877 100644 --- a/kivy/input/providers/wm_touch.py +++ b/kivy/input/providers/wm_touch.py @@ -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 From eb947fc8b8bb4657313c6e5cbdeeb793e08f6be7 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 01:33:16 +0100 Subject: [PATCH 09/40] screenmanager: fix transition bug due to flipped texture coordinates. closes #752 --- kivy/uix/screenmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy/uix/screenmanager.py b/kivy/uix/screenmanager.py index 724ddb79a..5ffea3cdd 100644 --- a/kivy/uix/screenmanager.py +++ b/kivy/uix/screenmanager.py @@ -367,7 +367,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 From fc9e819b249357529b43ecd50eb763b46c50e9e3 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 01:44:53 +0100 Subject: [PATCH 10/40] carousel: honor index in add_widget(). closes #747 --- kivy/uix/carousel.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kivy/uix/carousel.py b/kivy/uix/carousel.py index 114e5c97a..3246d42fa 100644 --- a/kivy/uix/carousel.py +++ b/kivy/uix/carousel.py @@ -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 From fb12e1579ca8259e98d5b38aacfeb8905dea91a0 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 01:56:26 +0100 Subject: [PATCH 11/40] widget: fix opacity not working when passed in a constructor. closes #734 --- kivy/uix/widget.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kivy/uix/widget.py b/kivy/uix/widget.py index d38cbbfbc..0ce503f42 100644 --- a/kivy/uix/widget.py +++ b/kivy/uix/widget.py @@ -141,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: @@ -586,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. From fb3ce12c9045118306605c2cab63c8e50717cb1b Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 02:01:10 +0100 Subject: [PATCH 12/40] app: add more debug information. refs #714 --- kivy/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kivy/app.py b/kivy/app.py index 1b47d764c..b05ccd9a5 100644 --- a/kivy/app.py +++ b/kivy/app.py @@ -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 From b40d6b7fe2e7638650146745869a2eb887c30bd9 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 02:20:43 +0100 Subject: [PATCH 13/40] bindtexture: fix usage of unicode filename in source attribute. closes #671 --- kivy/graphics/context_instructions.pxd | 2 +- kivy/graphics/context_instructions.pyx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kivy/graphics/context_instructions.pxd b/kivy/graphics/context_instructions.pxd index 189cd334f..2e3a0b3a9 100644 --- a/kivy/graphics/context_instructions.pxd +++ b/kivy/graphics/context_instructions.pxd @@ -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) diff --git a/kivy/graphics/context_instructions.pyx b/kivy/graphics/context_instructions.pyx index 7b3482d82..0e758fc0f 100644 --- a/kivy/graphics/context_instructions.pyx +++ b/kivy/graphics/context_instructions.pyx @@ -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 = resource_find(filename) + self._source = resource_find(filename) if self._source: tex = Cache.get('kv.texture', filename) if not tex: From f31acf830d1a384b8e3c0052925131c5b40ec835 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 02:29:58 +0100 Subject: [PATCH 14/40] kv.shader: increase the default timeout for shadersource cache. closes #673 --- kivy/graphics/context_instructions.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy/graphics/context_instructions.pyx b/kivy/graphics/context_instructions.pyx index 0e758fc0f..c8b626821 100644 --- a/kivy/graphics/context_instructions.pyx +++ b/kivy/graphics/context_instructions.pyx @@ -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(): From 65a9ea458648de1427939f5d453946893653b538 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 03:15:23 +0100 Subject: [PATCH 15/40] graphics: avoid error in case of multiple rremove: same behavior as remove_widget(). closes #481 --- kivy/graphics/instructions.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kivy/graphics/instructions.pyx b/kivy/graphics/instructions.pyx index 10e8ca3ba..12a3579d3 100644 --- a/kivy/graphics/instructions.pyx +++ b/kivy/graphics/instructions.pyx @@ -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) From 784f956e0847756c513799be5b05fe35ec97ab20 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 03:30:29 +0100 Subject: [PATCH 16/40] fix documentation --- kivy/uix/scrollview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py index c6194fb4f..3dd1da859 100644 --- a/kivy/uix/scrollview.py +++ b/kivy/uix/scrollview.py @@ -93,7 +93,8 @@ from kivy.properties import NumericProperty, BooleanProperty, AliasProperty, \ # When we are generating documentation, Config doesn't exist -_scroll_timeout = _scroll_stoptime = _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') From be50dc37f45a4befa0a2c319401a82863c1cf686 Mon Sep 17 00:00:00 2001 From: bibile Date: Mon, 29 Oct 2012 11:46:36 +0100 Subject: [PATCH 17/40] add app -> sample works --- kivy/uix/screenmanager.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/kivy/uix/screenmanager.py b/kivy/uix/screenmanager.py index 5ffea3cdd..7701c46fe 100644 --- a/kivy/uix/screenmanager.py +++ b/kivy/uix/screenmanager.py @@ -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' - + : 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 From c949a5984c43d35df7858c0825faf7f523e5e4c2 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 15:17:06 +0100 Subject: [PATCH 18/40] glew: add more information about the GLEW initialization. refs #610 --- kivy/graphics/gl_redirect.h | 1 + kivy/graphics/opengl.pyx | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/kivy/graphics/gl_redirect.h b/kivy/graphics/gl_redirect.h index 35db27dc5..62a7abf64 100644 --- a/kivy/graphics/gl_redirect.h +++ b/kivy/graphics/gl_redirect.h @@ -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"); diff --git a/kivy/graphics/opengl.pyx b/kivy/graphics/opengl.pyx index 65526296c..883ba46c1 100644 --- a/kivy/graphics/opengl.pyx +++ b/kivy/graphics/opengl.pyx @@ -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: From ca4dfea0af6c34de399a26d334c24bbaddc80f75 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 29 Oct 2012 16:14:28 +0100 Subject: [PATCH 19/40] settings: add Settings.create_json_panel(), and use it in Settings.add_json_panel() --- kivy/uix/settings.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/kivy/uix/settings.py b/kivy/uix/settings.py index ee7a0febb..c342f837d 100644 --- a/kivy/uix/settings.py +++ b/kivy/uix/settings.py @@ -654,12 +654,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 +671,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 +695,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 From 754eb0e1ed9770e7ad7e74c69df269361684f3b9 Mon Sep 17 00:00:00 2001 From: Qua-non Date: Tue, 30 Oct 2012 01:29:17 +0530 Subject: [PATCH 20/40] UIX:CodeInput: fix handeling of `foreground_color` wasn't being honoured --- kivy/uix/codeinput.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/kivy/uix/codeinput.py b/kivy/uix/codeinput.py index 67f227ce4..53ef2c977 100644 --- a/kivy/uix/codeinput.py +++ b/kivy/uix/codeinput.py @@ -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 From 97ec21ccc781e2126fda2fb8e9b7f0ef3323662c Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 31 Oct 2012 00:33:59 +0100 Subject: [PATCH 21/40] scrollview: fix multiples issues due to the betterscroll PR. mousewheel is fixed, avoid touch-up event are fixed, and tweaked the algo to always to scrolling if possible. Now it's working really better :) --- kivy/uix/scrollview.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py index 3dd1da859..1df5a4c4d 100644 --- a/kivy/uix/scrollview.py +++ b/kivy/uix/scrollview.py @@ -235,8 +235,8 @@ class ScrollView(StencilView): if ud['mode'] == 'unknown' and \ not ud['user_stopped'] and \ touch.dx + touch.dy == 0: - #touch.ungrab(self) - #self._touch = None + touch.ungrab(self) + self._touch = None # correctly calculate the position of the touch inside the # scrollview touch.push() @@ -264,10 +264,6 @@ class ScrollView(StencilView): dt = touch.time_end - ud['time'] 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['user_stopped'] and \ - abs(avgdy) < self.scroll_distance and \ - abs(avgdx) < self.scroll_distance: - return if ud['same'] > self.scroll_stoptime / 1000.: return dt = ud['dt'] @@ -308,10 +304,6 @@ class ScrollView(StencilView): 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 @@ -326,6 +318,8 @@ class ScrollView(StencilView): 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: @@ -346,10 +340,13 @@ class ScrollView(StencilView): vp = self._viewport if vp.height > self.height: # let's say we want to move over 40 pixels each scroll + d = (vp.height - self.height) + if d != 0: + d = self.scroll_distance / float(d) if touch.button == 'scrollup': - syd = self._scroll_y_mouse + syd = self._scroll_y_mouse - d elif touch.button == 'scrolldown': - syd = self._scroll_y_mouse + syd = self._scroll_y_mouse + d self._scroll_y_mouse = scroll_y = min(max(syd, 0), 1) Animation.stop_all(self, 'scroll_y') Animation(scroll_y=scroll_y, d=.3, t='out_quart').start(self) @@ -391,16 +388,18 @@ class ScrollView(StencilView): # if a distance is reach, but on the inverse axis, stop scroll mode ! if mode == 'unknown': distance = abs(touch.ox - touch.x) - if not self.do_scroll_x: - self._change_touch_mode() - return - mode = 'scroll' + if distance > self.scroll_distance: + if not self.do_scroll_x: + self._change_touch_mode() + return + mode = 'scroll' distance = abs(touch.oy - touch.y) - if not self.do_scroll_y: - self._change_touch_mode() - return - mode = 'scroll' + if distance > self.scroll_distance: + if not self.do_scroll_y: + self._change_touch_mode() + return + mode = 'scroll' if mode == 'scroll': ud['mode'] = mode @@ -413,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 From 141656c892ca3f4c61818c854c6d0379fec8c139 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Tue, 30 Oct 2012 22:33:39 +0100 Subject: [PATCH 22/40] settings: remove static row height, and make the height flexible --- kivy/core/text/markup.py | 9 ++++-- kivy/data/style.kv | 61 ++++++++++++++++++---------------------- kivy/properties.pyx | 35 ++++++++++++----------- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/kivy/core/text/markup.py b/kivy/core/text/markup.py index fabdc922d..3e9cc2199 100644 --- a/kivy/core/text/markup.py +++ b/kivy/core/text/markup.py @@ -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'): + size = dpi2px(item[:-2], item[-2:]) + else: + size = int(item) except ValueError: + raise size = options['font_size'] spush('font_size') options['font_size'] = size diff --git a/kivy/data/style.kv b/kivy/data/style.kv index 2d4d773b4..4364aa9d8 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -499,7 +499,8 @@ size: self.width, 1 : - size_hint_x: .25 + size_hint: .25, None + height: max(50, labellayout.height) content: content canvas: Color: @@ -516,25 +517,13 @@ BoxLayout: pos: root.pos - FloatLayout: + Label: + size_hint: .5, None 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=10pt]{1}[/size]{2}'.format(root.title or '', root.desc or '', self.height) + text_size: self.width - 32, None + height: self.texture_size[1] + 10 BoxLayout: id: content @@ -565,6 +554,8 @@ : text_size: self.width - 32, None + size_hint_y: None + height: max(50, self.texture_size[1] + 20) color: (.9, .9, .9, 1) canvas: Color: @@ -592,14 +583,16 @@ : spacing: 5 padding: 5 - row_default_height: 48 - row_force_default: True + #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) canvas.after: @@ -766,7 +759,7 @@ do_translation: False do_rotation: False do_scale: False - auto_bring_to_front: False + auto_bring_to_front: False # ============================================================================= @@ -774,16 +767,16 @@ # ============================================================================= : - 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 diff --git a/kivy/properties.pyx b/kivy/properties.pyx index 43df1ab5b..bc36916b8 100644 --- a/kivy/properties.pyx +++ b/kivy/properties.pyx @@ -175,6 +175,24 @@ from weakref import ref EventLoop = None +cpdef float dpi2px(value, 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 + 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 + cdef class Property: '''Base class for building more complex properties. @@ -439,23 +457,8 @@ cdef class NumericProperty(Property): return self.parse_list(obj, value[:-2], 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): ''' From ec80be21b058cfdca13780bc8448c4de6208d0e3 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 31 Oct 2012 01:14:46 +0100 Subject: [PATCH 23/40] settings: fixes for DPI compatibility --- kivy/data/style.kv | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/kivy/data/style.kv b/kivy/data/style.kv index 4364aa9d8..b705bc70a 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -500,7 +500,7 @@ : size_hint: .25, None - height: max(50, labellayout.height) + height: max(50, labellayout.height) content: content canvas: Color: @@ -518,16 +518,17 @@ pos: root.pos Label: - size_hint: .5, None + size_hint: .66, None id: labellayout markup: True - text: '{0}\n[size=10pt]{1}[/size]{2}'.format(root.title or '', root.desc or '', self.height) + text: '{0}\n[size=10pt][color=999999]{1}[/color][/size]'.format(root.title or '', root.desc or '') + font_size: '11.5pt' text_size: self.width - 32, None - height: self.texture_size[1] + 10 + height: self.texture_size[1] + 10 BoxLayout: id: content - size_hint_x: .5 + size_hint_x: .33 : @@ -541,22 +542,26 @@ Label: text: str(root.value) pos: root.pos + font_size: '11.5pt' : Label: text: str(root.value) pos: root.pos + font_size: '11.5pt' : Label: text: str(root.value) pos: root.pos + font_size: '11.5pt' : text_size: self.width - 32, None - size_hint_y: None - height: max(50, self.texture_size[1] + 20) + size_hint_y: None + height: max(50, self.texture_size[1] + 20) color: (.9, .9, .9, 1) + font_size: '11.5pt' canvas: Color: rgba: .15, .15, .15, .5 @@ -572,7 +577,8 @@ : size_hint: 1, None text_size: self.width - 32, None - height: max(48, self.texture_size[1] + 8) + height: max(50, self.texture_size[1] + 10) + font_size: '11.5pt' canvas.before: Color: rgba: 47 / 255., 167 / 255., 212 / 255., int(self.selected) @@ -583,17 +589,16 @@ : 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 + size_hint_y: None text: root.title text_size: self.width - 32, None - height: max(50, self.texture_size[1] + 20) + height: max(50, self.texture_size[1] + 20) color: (.5, .5, .5, 1) + font_size: '11.5pt' canvas.after: Color: @@ -634,9 +639,10 @@ text: 'Close' size_hint: None, None width: 180 - height: 50 + height: max(50, self.texture_size[1] + 10) pos: root.x + 10, root.y + 10 on_release: root.dispatch('on_close') + font_size: '11.5pt' ScrollView: do_scroll_x: False From 132468acfd70aca05c7f0d124aecfe55b965274d Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 04:03:47 +0100 Subject: [PATCH 24/40] dpi: add dpi_rounded and dpi_density in EventLoop. Both need proper renaming and documentation. + add dp calculation (px = dp * density) --- kivy/base.py | 24 ++++++++++++++++++++++++ kivy/properties.pyx | 22 ++++++++++++++-------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/kivy/base.py b/kivy/base.py index d6a25620c..afd86f46e 100644 --- a/kivy/base.py +++ b/kivy/base.py @@ -132,6 +132,30 @@ class EventLoopBase(EventDispatcher): self.ensure_window() return self.window.dpi + @reify + def dpi_rounded(self): + '''Return the dpi of the screen, rounded to the nearest of 120, 160, + 240, 320. + + .. versionadded:: 1.5.0 + ''' + dpi = self.dpi + if dpi < 140: + return 120 + elif dpi < 200: + return 160 + elif dpi < 280: + return 240 + return 320 + + @reify + def dpi_density(self): + if platform() == 'android': + import jnius + Hardware = jnius.autoclass('org.renpy.android.Hardware') + return Hardware.metrics.scaledDensity + return float(environ.get('KIVY_DPI_DENSITY', '1.0')) + def ensure_window(self): '''Ensure that we have an window ''' diff --git a/kivy/properties.pyx b/kivy/properties.pyx index bc36916b8..7f8747acf 100644 --- a/kivy/properties.pyx +++ b/kivy/properties.pyx @@ -173,25 +173,31 @@ __all__ = ('Property', from weakref import ref -EventLoop = None +cdef float g_dpi = -1 +cdef float g_dpi_rounded = -1 +cdef float g_dpi_density = -1 cpdef float dpi2px(value, ext): # 1in = 2.54cm = 25.4mm = 72pt = 12pc - global EventLoop - if EventLoop is None: + global g_dpi, g_dpi_rounded, g_dpi_density + if g_dpi == -1: from kivy.base import EventLoop + g_dpi = EventLoop.dpi + g_dpi_rounded = EventLoop.dpi_rounded + g_dpi_density = EventLoop.dpi_density cdef float rv = float(value) - cdef float dpi = EventLoop.dpi if ext == 'in': - return rv * dpi + return rv * g_dpi elif ext == 'px': return rv + elif ext == 'dp': + return rv * g_dpi_density elif ext == 'pt': - return rv * dpi / 72. + return rv * g_dpi / 72. elif ext == 'cm': - return rv * dpi / 2.54 + return rv * g_dpi / 2.54 elif ext == 'mm': - return rv * dpi / 25.4 + return rv * g_dpi / 25.4 cdef class Property: From 0059e9c2b3f062f4b4a961228dc644dd0c01baf2 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 04:04:28 +0100 Subject: [PATCH 25/40] lang: introduce dp/inches/mm/cm/pt functions into kivy.metrics + add access from the lang global map --- kivy/lang.py | 8 ++++++++ kivy/metrics.py | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 kivy/metrics.py diff --git a/kivy/lang.py b/kivy/lang.py index 60765f672..383606716 100644 --- a/kivy/lang.py +++ b/kivy/lang.py @@ -458,6 +458,8 @@ 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 +from kivy.properties import dpi2px +import kivy.metrics as metrics trace = Logger.trace @@ -517,7 +519,13 @@ 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 class ParserException(Exception): diff --git a/kivy/metrics.py b/kivy/metrics.py new file mode 100644 index 000000000..7d62a450f --- /dev/null +++ b/kivy/metrics.py @@ -0,0 +1,27 @@ +''' +Display Metrics +=============== + +This module give you access to multiple display values, and some conversion +functions. +''' + +__all__ = ('pt', 'inch', 'cm', 'mm', 'dp') + +from kivy.properties import dpi2px + +def pt(value): + return dpi2px(value, 'pt') + +def inch(value): + return dpi2px(value, 'in') + +def cm(value): + return dpi2px(value, 'cm') + +def mm(value): + return dpi2px(value, 'mm') + +def dp(value): + return dpi2px(value, 'dp') + From 36e45c198a09de843889b01a65e6ffe2fc9f286f Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 04:05:25 +0100 Subject: [PATCH 26/40] label: removed px hack for font_size + updated the font_size to 13dp (instead of 10). --- kivy/core/text/markup.py | 2 +- kivy/uix/label.py | 14 ++++---------- kivy/uix/textinput.py | 10 ++-------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/kivy/core/text/markup.py b/kivy/core/text/markup.py index 3e9cc2199..6424ff947 100644 --- a/kivy/core/text/markup.py +++ b/kivy/core/text/markup.py @@ -140,7 +140,7 @@ class MarkupLabel(MarkupLabelBase): elif item[:6] == '[size=': item = item[6:-1] try: - if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm'): + if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm', 'dp'): size = dpi2px(item[:-2], item[-2:]) else: size = int(item) diff --git a/kivy/uix/label.py b/kivy/uix/label.py index 64c2aac52..3836592f8 100644 --- a/kivy/uix/label.py +++ b/kivy/uix/label.py @@ -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='20dp') 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('14dp') '''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 = '50dp' # l.texture is not updated yet l.update_texture() # l.texture is good now. diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index ce1c8b7be..cd01e941d 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -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('13dp') '''Font size of the text, in pixels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to From cf8ccfa505014e25c1a0ed1d9d67ee41dcd1cbea Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 04:05:45 +0100 Subject: [PATCH 27/40] settings: update settings to use dp everywhere. --- kivy/uix/settings.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/kivy/uix/settings.py b/kivy/uix/settings.py index c342f837d..389c08519 100644 --- a/kivy/uix/settings.py +++ b/kivy/uix/settings.py @@ -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) From 7c4d4b8cbaecd5cdf0bbd91efa0a3d2a6dc01e9e Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 04:06:19 +0100 Subject: [PATCH 28/40] style: started to update from px to dp, in settings, treeview etc. --- kivy/data/style.kv | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/kivy/data/style.kv b/kivy/data/style.kv index b705bc70a..95b626b9d 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -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: '14dp' : @@ -167,7 +167,7 @@ : 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 @@ -326,7 +326,7 @@ Label: text: unicode(ctx.get_nice_size()) - font_size: 8 + font_size: '11dp' color: .8, .8, .8, 1 size: 100, 16 pos: root.pos @@ -358,7 +358,7 @@ size_hint_y: None height: self.texture_size[1] y: pb.center_y - self.height - 8 - font_size: 10 + font_size: '13dp' color: (.8, .8, .8, .8) AnchorLayout: @@ -500,7 +500,7 @@ : size_hint: .25, None - height: max(50, labellayout.height) + height: labellayout.texture_size[1] + dp(10) content: content canvas: Color: @@ -518,13 +518,12 @@ pos: root.pos Label: - size_hint: .66, None + size_hint_x: .66 id: labellayout markup: True - text: '{0}\n[size=10pt][color=999999]{1}[/color][/size]'.format(root.title or '', root.desc or '') - font_size: '11.5pt' + text: '{0}\n[size=13dp][color=999999]{1}[/color][/size]{2}'.format(root.title or '', root.desc or '', self.font_size) + font_size: '15dp' text_size: self.width - 32, None - height: self.texture_size[1] + 10 BoxLayout: id: content @@ -542,26 +541,26 @@ Label: text: str(root.value) pos: root.pos - font_size: '11.5pt' + font_size: '15dp' : Label: text: str(root.value) pos: root.pos - font_size: '11.5pt' + font_size: '15dp' : Label: text: str(root.value) pos: root.pos - font_size: '11.5pt' + font_size: '15dp' : text_size: self.width - 32, None size_hint_y: None - height: max(50, self.texture_size[1] + 20) + height: max(dp(20), self.texture_size[1] + dp(20)) color: (.9, .9, .9, 1) - font_size: '11.5pt' + font_size: '15dp' canvas: Color: rgba: .15, .15, .15, .5 @@ -577,8 +576,8 @@ : size_hint: 1, None text_size: self.width - 32, None - height: max(50, self.texture_size[1] + 10) - font_size: '11.5pt' + height: self.texture_size[1] + dp(20) + font_size: '15dp' canvas.before: Color: rgba: 47 / 255., 167 / 255., 212 / 255., int(self.selected) @@ -598,7 +597,7 @@ text_size: self.width - 32, None height: max(50, self.texture_size[1] + 20) color: (.5, .5, .5, 1) - font_size: '11.5pt' + font_size: '15dp' canvas.after: Color: @@ -620,7 +619,7 @@ FloatLayout: size_hint_x: None - width: 200 + width: '200dp' GridLayout: pos: root.pos cols: 1 @@ -638,11 +637,11 @@ Button: text: 'Close' size_hint: None, None - width: 180 - height: max(50, self.texture_size[1] + 10) - 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: '11.5pt' + font_size: '15dp' ScrollView: do_scroll_x: False From 9d7779d0d2438a97e8d4729f2c84622363d0fd34 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 04:06:27 +0100 Subject: [PATCH 29/40] showcase: fixed dpi --- examples/demo/showcase/main.py | 6 +++--- examples/demo/showcase/showcase.kv | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/demo/showcase/main.py b/examples/demo/showcase/main.py index 0b50230dc..2cf1ce540 100644 --- a/examples/demo/showcase/main.py +++ b/examples/demo/showcase/main.py @@ -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='50dp') 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=('150dp', '70dp')) button.bind(on_release=popup.open) popup.open() col = AnchorLayout() diff --git a/examples/demo/showcase/showcase.kv b/examples/demo/showcase/showcase.kv index 41f0efcc8..5fad80a20 100644 --- a/examples/demo/showcase/showcase.kv +++ b/examples/demo/showcase/showcase.kv @@ -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' Title: From f1cfe335dce4c57778d28f0cbf0be48447b60805 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 12:18:49 +0100 Subject: [PATCH 30/40] androidjoystick: normalize pressure and radius, according to python-for-android / API doc --- kivy/input/providers/androidjoystick.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kivy/input/providers/androidjoystick.py b/kivy/input/providers/androidjoystick.py index 74a073bfa..0e0d1c816 100644 --- a/kivy/input/providers/androidjoystick.py +++ b/kivy/input/providers/androidjoystick.py @@ -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: From 2dbc1cf3cc2d35c834b8787415cf3b5b3cc00127 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 13:52:42 +0100 Subject: [PATCH 31/40] dpi: implement sp(), and add Metrics class in kivy.metrics: expose dpi, dpi_rounded, fontscale, density --- kivy/base.py | 50 ------------------------ kivy/core/text/markup.py | 2 +- kivy/data/style.kv | 24 ++++++------ kivy/metrics.py | 82 +++++++++++++++++++++++++++++++++++++++- kivy/properties.pyx | 18 +++++---- kivy/uix/label.py | 6 +-- kivy/uix/textinput.py | 2 +- 7 files changed, 108 insertions(+), 76 deletions(-) diff --git a/kivy/base.py b/kivy/base.py index afd86f46e..def229aa1 100644 --- a/kivy/base.py +++ b/kivy/base.py @@ -106,56 +106,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 - - @reify - def dpi_rounded(self): - '''Return the dpi of the screen, rounded to the nearest of 120, 160, - 240, 320. - - .. versionadded:: 1.5.0 - ''' - dpi = self.dpi - if dpi < 140: - return 120 - elif dpi < 200: - return 160 - elif dpi < 280: - return 240 - return 320 - - @reify - def dpi_density(self): - if platform() == 'android': - import jnius - Hardware = jnius.autoclass('org.renpy.android.Hardware') - return Hardware.metrics.scaledDensity - return float(environ.get('KIVY_DPI_DENSITY', '1.0')) - def ensure_window(self): '''Ensure that we have an window ''' diff --git a/kivy/core/text/markup.py b/kivy/core/text/markup.py index 6424ff947..abd41384f 100644 --- a/kivy/core/text/markup.py +++ b/kivy/core/text/markup.py @@ -140,7 +140,7 @@ class MarkupLabel(MarkupLabelBase): elif item[:6] == '[size=': item = item[6:-1] try: - if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm', 'dp'): + if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm', 'dp', 'sp'): size = dpi2px(item[:-2], item[-2:]) else: size = int(item) diff --git a/kivy/data/style.kv b/kivy/data/style.kv index 95b626b9d..5354860c8 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -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: '14dp' + font_size: '14sp' : @@ -326,7 +326,7 @@ Label: text: unicode(ctx.get_nice_size()) - font_size: '11dp' + font_size: '11sp' color: .8, .8, .8, 1 size: 100, 16 pos: root.pos @@ -358,7 +358,7 @@ size_hint_y: None height: self.texture_size[1] y: pb.center_y - self.height - 8 - font_size: '13dp' + font_size: '13sp' color: (.8, .8, .8, .8) AnchorLayout: @@ -521,8 +521,8 @@ size_hint_x: .66 id: labellayout markup: True - text: '{0}\n[size=13dp][color=999999]{1}[/color][/size]{2}'.format(root.title or '', root.desc or '', self.font_size) - font_size: '15dp' + 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: @@ -541,26 +541,26 @@ Label: text: str(root.value) pos: root.pos - font_size: '15dp' + font_size: '15sp' : Label: text: str(root.value) pos: root.pos - font_size: '15dp' + font_size: '15sp' : Label: text: str(root.value) pos: root.pos - font_size: '15dp' + font_size: '15sp' : 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: '15dp' + font_size: '15sp' canvas: Color: rgba: .15, .15, .15, .5 @@ -577,7 +577,7 @@ size_hint: 1, None text_size: self.width - 32, None height: self.texture_size[1] + dp(20) - font_size: '15dp' + font_size: '15sp' canvas.before: Color: rgba: 47 / 255., 167 / 255., 212 / 255., int(self.selected) @@ -597,7 +597,7 @@ text_size: self.width - 32, None height: max(50, self.texture_size[1] + 20) color: (.5, .5, .5, 1) - font_size: '15dp' + font_size: '15sp' canvas.after: Color: @@ -641,7 +641,7 @@ 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: '15dp' + font_size: '15sp' ScrollView: do_scroll_x: False diff --git a/kivy/metrics.py b/kivy/metrics.py index 7d62a450f..f5435fcbf 100644 --- a/kivy/metrics.py +++ b/kivy/metrics.py @@ -6,8 +6,10 @@ This module give you access to multiple display values, and some conversion functions. ''' -__all__ = ('pt', 'inch', 'cm', 'mm', 'dp') +__all__ = ('metrics', 'Metrics', 'pt', 'inch', 'cm', 'mm', 'dp') +from os import environ +from kivy.utils import reify, platform from kivy.properties import dpi2px def pt(value): @@ -25,3 +27,81 @@ def mm(value): def dp(value): return dpi2px(value, 'dp') +def sp(value): + return dpi2px(value, 'sp') + + +class Metrics(object): + + @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) + + 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. + + .. versionadded:: 1.5.0 + ''' + dpi = self.dpi + if dpi < 140: + return 120 + elif dpi < 200: + return 160 + elif dpi < 280: + return 240 + return 320 + + @reify + def density(self): + 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): + 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() diff --git a/kivy/properties.pyx b/kivy/properties.pyx index 7f8747acf..35196d4c9 100644 --- a/kivy/properties.pyx +++ b/kivy/properties.pyx @@ -174,24 +174,26 @@ __all__ = ('Property', from weakref import ref cdef float g_dpi = -1 -cdef float g_dpi_rounded = -1 -cdef float g_dpi_density = -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_dpi_rounded, g_dpi_density + global g_dpi, g_density, g_fontscale if g_dpi == -1: - from kivy.base import EventLoop - g_dpi = EventLoop.dpi - g_dpi_rounded = EventLoop.dpi_rounded - g_dpi_density = EventLoop.dpi_density + 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_dpi_density + return rv * g_density + elif ext == 'sp': + return rv * g_density * g_fontscale elif ext == 'pt': return rv * g_dpi / 72. elif ext == 'cm': diff --git a/kivy/uix/label.py b/kivy/uix/label.py index 3836592f8..daccc48f4 100644 --- a/kivy/uix/label.py +++ b/kivy/uix/label.py @@ -15,7 +15,7 @@ strings:: l = Label(text='Multi\\nLine') # size - l = Label(text='Hello world', font_size='20dp') + l = Label(text='Hello world', font_size='20sp') Markup text ----------- @@ -268,7 +268,7 @@ class Label(Widget): 'DroidSans'. ''' - font_size = NumericProperty('14dp') + font_size = NumericProperty('14sp') '''Font size of the text, in pixels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to @@ -373,7 +373,7 @@ class Label(Widget): l = Label(text='Hello world') # l.texture is good - l.font_size = '50dp' + l.font_size = '50sp' # l.texture is not updated yet l.update_texture() # l.texture is good now. diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index cd01e941d..bd402b719 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -1708,7 +1708,7 @@ class TextInput(Widget): 'DroidSans'. ''' - font_size = NumericProperty('13dp') + font_size = NumericProperty('13sp') '''Font size of the text, in pixels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to From 75d1ff0b5367907b6809a7f4e7518e4ec78e7c6c Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 14:25:30 +0100 Subject: [PATCH 32/40] lang: add sp() --- kivy/lang.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy/lang.py b/kivy/lang.py index 383606716..44fd488f5 100644 --- a/kivy/lang.py +++ b/kivy/lang.py @@ -458,7 +458,6 @@ 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 -from kivy.properties import dpi2px import kivy.metrics as metrics @@ -526,6 +525,7 @@ 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): From 086023dc0c8103062f45bdf51b36a77002318699 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 14:25:51 +0100 Subject: [PATCH 33/40] style: wip conversion to dp/sp --- examples/demo/showcase/main.py | 4 ++-- examples/demo/showcase/showcase.kv | 10 +++++----- kivy/data/style.kv | 22 ++++++++++++---------- kivy/uix/accordion.py | 4 ++-- kivy/uix/scrollview.py | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/examples/demo/showcase/main.py b/examples/demo/showcase/main.py index 2cf1ce540..db966fcc4 100644 --- a/examples/demo/showcase/main.py +++ b/examples/demo/showcase/main.py @@ -250,7 +250,7 @@ class ShowcaseApp(App): return col def show_popup(self): - btnclose = Button(text='Close this popup', size_hint_y=None, height='50dp') + 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) @@ -259,7 +259,7 @@ class ShowcaseApp(App): auto_dismiss=False) btnclose.bind(on_release=popup.dismiss) button = Button(text='Open popup', size_hint=(None, None), - size=('150dp', '70dp')) + size=('150sp', '70dp')) button.bind(on_release=popup.open) popup.open() col = AnchorLayout() diff --git a/examples/demo/showcase/showcase.kv b/examples/demo/showcase/showcase.kv index 5fad80a20..a81cab496 100644 --- a/examples/demo/showcase/showcase.kv +++ b/examples/demo/showcase/showcase.kv @@ -154,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: @@ -258,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: diff --git a/kivy/data/style.kv b/kivy/data/style.kv index 5354860c8..430d93503 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -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: '11sp' color: .8, .8, .8, 1 - size: 100, 16 + size: '100dp', '16sp' pos: root.pos halign: 'center' diff --git a/kivy/uix/accordion.py b/kivy/uix/accordion.py index d2a003f4d..6f2c7f875 100644 --- a/kivy/uix/accordion.py +++ b/kivy/uix/accordion.py @@ -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. diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py index 1df5a4c4d..30da4d5c7 100644 --- a/kivy/uix/scrollview.py +++ b/kivy/uix/scrollview.py @@ -651,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. From 1ac5d8b928bcd93f3673a71c196dbd9b4ad1d6ea Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 1 Nov 2012 22:26:24 +0100 Subject: [PATCH 34/40] win32/opengl: after initialization, test first the opengl version before going further. If we don't meet the miminum requirements, we show a msgbox. closes #778 --- kivy/core/gl/__init__.py | 41 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/kivy/core/gl/__init__.py b/kivy/core/gl/__init__.py index 502b88c31..cc56d1660 100644 --- a/kivy/core/gl/__init__.py +++ b/kivy/core/gl/__init__.py @@ -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 From 222829ecc50527f905eebfe517893739f861ff33 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 2 Nov 2012 04:14:56 +0100 Subject: [PATCH 35/40] dpi: wip style --- examples/demo/touchtracer/touchtracer.kv | 13 +++++++------ kivy/data/style.kv | 2 +- kivy/uix/label.py | 2 +- kivy/uix/popup.py | 4 ++-- kivy/uix/rst.py | 16 ++++++++-------- kivy/uix/tabbedpanel.py | 4 ++-- kivy/uix/textinput.py | 2 +- kivy/uix/treeview.py | 4 ++-- kivy/uix/vkeyboard.py | 2 +- 9 files changed, 25 insertions(+), 24 deletions(-) diff --git a/examples/demo/touchtracer/touchtracer.kv b/examples/demo/touchtracer/touchtracer.kv index b6326865b..259bb5329 100644 --- a/examples/demo/touchtracer/touchtracer.kv +++ b/examples/demo/touchtracer/touchtracer.kv @@ -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__ diff --git a/kivy/data/style.kv b/kivy/data/style.kv index 430d93503..1fe843cb9 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -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: '14sp' + font_size: '15sp' : diff --git a/kivy/uix/label.py b/kivy/uix/label.py index daccc48f4..100e5ef0f 100644 --- a/kivy/uix/label.py +++ b/kivy/uix/label.py @@ -268,7 +268,7 @@ class Label(Widget): 'DroidSans'. ''' - font_size = NumericProperty('14sp') + font_size = NumericProperty('15sp') '''Font size of the text, in pixels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to diff --git a/kivy/uix/popup.py b/kivy/uix/popup.py index 9fcd5dd59..61fbca346 100644 --- a/kivy/uix/popup.py +++ b/kivy/uix/popup.py @@ -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. diff --git a/kivy/uix/rst.py b/kivy/uix/rst.py index 1d8937cd9..a3df4c80b 100644 --- a/kivy/uix/rst.py +++ b/kivy/uix/rst.py @@ -147,9 +147,9 @@ Builder.load_string(''' : 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) : 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(''' : 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) : 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) : 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) : size_hint: 0.01, 0.01 diff --git a/kivy/uix/tabbedpanel.py b/kivy/uix/tabbedpanel.py index c45b7aadf..d46a44c6d 100644 --- a/kivy/uix/tabbedpanel.py +++ b/kivy/uix/tabbedpanel.py @@ -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`, diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py index bd402b719..c9f31f43d 100644 --- a/kivy/uix/textinput.py +++ b/kivy/uix/textinput.py @@ -1708,7 +1708,7 @@ class TextInput(Widget): 'DroidSans'. ''' - font_size = NumericProperty('13sp') + font_size = NumericProperty('15sp') '''Font size of the text, in pixels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, default to diff --git a/kivy/uix/treeview.py b/kivy/uix/treeview.py index 2c57f8bfb..1319b6eb1 100644 --- a/kivy/uix/treeview.py +++ b/kivy/uix/treeview.py @@ -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 diff --git a/kivy/uix/vkeyboard.py b/kivy/uix/vkeyboard.py index ef55e22d1..fea7d861b 100644 --- a/kivy/uix/vkeyboard.py +++ b/kivy/uix/vkeyboard.py @@ -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): From c196edd42dacfa43832710b501e69298f7303cca Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 2 Nov 2012 13:38:39 +0100 Subject: [PATCH 36/40] metrics: add documentation --- doc/autobuild.py | 1 + doc/sources/guide/environment.rst | 24 ++++-- kivy/metrics.py | 135 ++++++++++++++++++++++++++---- 3 files changed, 141 insertions(+), 19 deletions(-) diff --git a/doc/autobuild.py b/doc/autobuild.py index 3f1851211..5db980515 100644 --- a/doc/autobuild.py +++ b/doc/autobuild.py @@ -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 diff --git a/doc/sources/guide/environment.rst b/doc/sources/guide/environment.rst index b98f4bb2b..db7fe7ec3 100644 --- a/doc/sources/guide/environment.rst +++ b/doc/sources/guide/environment.rst @@ -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 + diff --git a/kivy/metrics.py b/kivy/metrics.py index f5435fcbf..2b2aff71c 100644 --- a/kivy/metrics.py +++ b/kivy/metrics.py @@ -1,50 +1,153 @@ ''' -Display Metrics -=============== +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 + : + 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 -This module give you access to multiple display values, and some conversion -functions. ''' -__all__ = ('metrics', 'Metrics', 'pt', 'inch', 'cm', 'mm', 'dp') + +__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). - - 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: @@ -63,8 +166,6 @@ class Metrics(object): def dpi_rounded(self): '''Return the dpi of the screen, rounded to the nearest of 120, 160, 240, 320. - - .. versionadded:: 1.5.0 ''' dpi = self.dpi if dpi < 140: @@ -77,6 +178,9 @@ class Metrics(object): @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) @@ -90,6 +194,9 @@ class Metrics(object): @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) From 1d41016bd6f0fddf3d57057ffdb9a47bfd7d5c49 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 2 Nov 2012 16:35:25 +0100 Subject: [PATCH 37/40] base: remove unused imports --- examples/demo/kivycatalog/kivycatalog.kv | 4 ++-- kivy/base.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/demo/kivycatalog/kivycatalog.kv b/examples/demo/kivycatalog/kivycatalog.kv index 7b292bd11..c2784fdc5 100644 --- a/examples/demo/kivycatalog/kivycatalog.kv +++ b/examples/demo/kivycatalog/kivycatalog.kv @@ -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 \ No newline at end of file + top: 0 diff --git a/kivy/base.py b/kivy/base.py index def229aa1..7e618b157 100644 --- a/kivy/base.py +++ b/kivy/base.py @@ -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 From a4f1ffde7af7a197d57f63d105d520393e51c5dd Mon Sep 17 00:00:00 2001 From: tshirtman Date: Fri, 2 Nov 2012 16:49:06 +0100 Subject: [PATCH 38/40] makes the "this label fits to the content" really fit to the content --- examples/demo/showcase/showcase.kv | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/demo/showcase/showcase.kv b/examples/demo/showcase/showcase.kv index 41f0efcc8..28837ce5c 100644 --- a/examples/demo/showcase/showcase.kv +++ b/examples/demo/showcase/showcase.kv @@ -313,6 +313,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 +325,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 +337,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 +348,7 @@ title: 'Panel 3' Label: text: 'This is a label fit to the content view' + text_size: self.width, None VSeparator From 7e3f193d60e50d813ebf52a9709f6c0e1fc27d7d Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 2 Nov 2012 18:06:35 +0100 Subject: [PATCH 39/40] screen: add new screen module, that can be used for simulating different screen sizes/density/dpi. --- kivy/__init__.py | 4 ++ kivy/modules/__init__.py | 74 ++++++++++++++++++----------- kivy/modules/screen.py | 100 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 kivy/modules/screen.py diff --git a/kivy/__init__.py b/kivy/__init__.py index e70e3e316..14f989c39 100644 --- a/kivy/__init__.py +++ b/kivy/__init__.py @@ -369,6 +369,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() == 'android': from kivy.config import Config diff --git a/kivy/modules/__init__.py b/kivy/modules/__init__.py index 864228dbb..1143c1f94 100644 --- a/kivy/modules/__init__.py +++ b/kivy/modules/__init__.py @@ -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' diff --git a/kivy/modules/screen.py b/kivy/modules/screen.py new file mode 100644 index 000000000..71ba23d48 --- /dev/null +++ b/kivy/modules/screen.py @@ -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) + From 0530cdfd7c5f2e1eeb7a4cffb155c475ce751a01 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 2 Nov 2012 18:27:32 +0100 Subject: [PATCH 40/40] screen: add module into the documentation --- doc/autobuild.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/autobuild.py b/doc/autobuild.py index 5db980515..2a93d6d82 100644 --- a/doc/autobuild.py +++ b/doc/autobuild.py @@ -44,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