From 3bd63f6ff461b96743829d8fe62731000b9b74ed Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Wed, 13 Oct 2010 16:41:39 +0000 Subject: a more succint approach to the spiral morph --- diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py index 85e1b59..8bc0f1f 100644 --- a/src/jarabe/desktop/favoriteslayout.py +++ b/src/jarabe/desktop/favoriteslayout.py @@ -1,4 +1,5 @@ # Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2010 Sugar Labs # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -181,18 +182,29 @@ class RandomLayout(FavoritesLayout): def allow_dnd(self): return True + _MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ style.STANDARD_ICON_SIZE * 2 _MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \ style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING - -class RingLayout(FavoritesLayout): +_INTERMEDIATE_C = (style.STANDARD_ICON_SIZE + style.SMALL_ICON_SIZE) / 2 +_INTERMEDIATE_A = (style.STANDARD_ICON_SIZE * 2 + _INTERMEDIATE_C) / 3 +_INTERMEDIATE_E = (_INTERMEDIATE_C + style.SMALL_ICON_SIZE * 2) / 3 +_INTERMEDIATE_B = (_INTERMEDIATE_A + _INTERMEDIATE_C) / 2 +_INTERMEDIATE_D = (_INTERMEDIATE_C + _INTERMEDIATE_E) / 2 +_ICON_SIZES = [style.MEDIUM_ICON_SIZE, style.STANDARD_ICON_SIZE, + _INTERMEDIATE_A, _INTERMEDIATE_B, _INTERMEDIATE_C, + _INTERMEDIATE_D, _INTERMEDIATE_E, style.SMALL_ICON_SIZE] +_ICON_SPACING_FACTORS = [1.5, 1.4, 1.3, 1.2, 1.15, 1.1, 1.05, 1.0] + + +class BasicRingLayout(FavoritesLayout): """Lay out icons in a ring around the XO man.""" - __gtype_name__ = 'RingLayout' + __gtype_name__ = 'BasicRingLayout' icon_name = 'view-radial' """Name of icon used in home view dropdown palette.""" - key = 'ring-layout' + key = 'basic-ring-layout' """String used in profile to represent this view.""" # TRANS: label for the ring layout in the favorites view palette_name = _('Ring') @@ -221,31 +233,36 @@ class RingLayout(FavoritesLayout): self._locked_children[child] = (x, y) def _calculate_radius_and_icon_size(self, children_count): - # what's the radius required without downscaling? - distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING - icon_size = style.STANDARD_ICON_SIZE - # circumference is 2*pi*r; we want this to be at least - # 'children_count * distance' - radius = children_count * distance / (2 * math.pi) - # limit computed radius to reasonable bounds. - radius = max(radius, _MINIMUM_RADIUS) - radius = min(radius, _MAXIMUM_RADIUS) - # recompute icon size from limited radius - if children_count > 0: - icon_size = (2 * math.pi * radius / children_count) \ - - style.DEFAULT_SPACING - # limit adjusted icon size. - icon_size = max(icon_size, style.SMALL_ICON_SIZE) - icon_size = min(icon_size, style.MEDIUM_ICON_SIZE) + """ Adjust the ring radius and icon size as needed. """ + # Begin by increasing the radius. + distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)] + radius = max(children_count * distance / (2 * math.pi), _MINIMUM_RADIUS) + if radius < _MAXIMUM_RADIUS: + return radius, style.MEDIUM_ICON_SIZE + + # Continue by shrinking the icon size to STANDARD_ICON_SIZE. + radius = _MAXIMUM_RADIUS + distance = radius * (2 * math.pi) / children_count + icon_size = int(distance - style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)]) + if icon_size >= style.STANDARD_ICON_SIZE: + return radius, icon_size + + # Continue by shrinking the icon size to SMALL_ICON_SIZE. + icon_size = max(int(distance - style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index( + style.SMALL_ICON_SIZE)]), style.SMALL_ICON_SIZE) return radius, icon_size - def _calculate_position(self, radius, icon_size, index, children_count, + def _calculate_position(self, radius, icon_size, icon_index, children_count, sin=math.sin, cos=math.cos): + """ Calculate an icon position on a circle. """ width, height = self.box.get_allocation() - angle = index * (2 * math.pi / children_count) - math.pi / 2 + angle = icon_index * (2 * math.pi / children_count) - math.pi / 2 x = radius * cos(angle) + (width - icon_size) / 2 - y = radius * sin(angle) + (height - icon_size - - (style.GRID_CELL_SIZE/2) ) / 2 + y = radius * sin(angle) + (height - icon_size - \ + (style.GRID_CELL_SIZE / 2)) / 2 return x, y def _get_children_in_ring(self): @@ -294,6 +311,104 @@ class RingLayout(FavoritesLayout): else: return 0 + +_MIMIMUM_RADIUS_ENCROACHMENT = 0.75 +_INITIAL_ANGLE = math.pi +_SPIRAL_SPACING_FACTORS = [1.5, 1.5, 1.5, 1.4, 1.35, 1.3, 1.25, 1.2] + + +class RingLayout(BasicRingLayout): + """ Variation of Basic Ring that morphs into a spiral as + the number of icons increases beyond the capacity of the + STANDARD_ICON_SIZE. """ + + __gtype_name__ = 'RingLayout' + icon_name = 'view-radial' + """Name of icon used in home view dropdown palette.""" + key = 'ring-layout' + """String used in profile to represent this view.""" + + def __init__(self): + BasicRingLayout.__init__(self) + self._locked_children = {} + self._spiral_mode = False + + def _calculate_radius_and_icon_size(self, children_count): + """ Adjust the ring or spiral radius and icon size as needed. """ + self._spiral_mode = False + # Begin by increasing the radius. + distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)] + radius = max(children_count * distance / (2 * math.pi), _MINIMUM_RADIUS) + if radius < _MAXIMUM_RADIUS: + return radius, style.MEDIUM_ICON_SIZE + + # Continue by shrinking the icon size. + radius = _MAXIMUM_RADIUS + distance = radius * (2 * math.pi) / children_count + icon_size = int(distance - style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)]) + if icon_size >= style.STANDARD_ICON_SIZE: + return radius, icon_size + + # Finally, switch to a spiral. + self._spiral_mode = True + icon_size = style.STANDARD_ICON_SIZE + angle, radius = self._calculate_angle_and_radius(children_count, + icon_size) + while radius > _MAXIMUM_RADIUS: + i = _ICON_SIZES.index(icon_size) + if i < len(_ICON_SIZES) - 1: + icon_size = _ICON_SIZES[i + 1] + angle, radius = self._calculate_angle_and_radius( + children_count, icon_size) + else: + break + return radius, icon_size + + def _calculate_position(self, radius, icon_size, icon_index, children_count, + sin=math.sin, cos=math.cos): + """ Calculate an icon position on a circle or a spiral. """ + width, height = self.box.get_allocation() + if self._spiral_mode: + min_width_, box_width = self.box.get_width_request() + min_height_, box_height = self.box.get_height_request(box_width) + angle, radius = self._calculate_angle_and_radius(icon_index, + icon_size) + x, y = self._convert_from_polar_to_cartesian(angle, radius, + icon_size, + width, height) + else: + angle = icon_index * (2 * math.pi / children_count) - math.pi / 2 + x = radius * cos(angle) + (width - icon_size) / 2 + y = radius * sin(angle) + (height - icon_size - \ + (style.GRID_CELL_SIZE / 2)) / 2 + return x, y + + def _convert_from_polar_to_cartesian(self, angle, radius, icon_size, width, + height): + """ Convert angle, radius to x, y """ + x = int(math.sin(angle) * radius) + y = int(math.cos(angle) * radius) + x = - x + (width - icon_size) / 2 + y = y + (height - icon_size - (style.GRID_CELL_SIZE / 2)) / 2 + return x, y + + def _calculate_angle_and_radius(self, icon_count, icon_size): + """ Based on icon_count and icon_size, calculate radius and angle. """ + spiral_spacing = _SPIRAL_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] + icon_spacing = icon_size + style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] + angle = _INITIAL_ANGLE + radius = _MINIMUM_RADIUS - (icon_size * _MIMIMUM_RADIUS_ENCROACHMENT) + for i in range(icon_count): + circumference = radius * 2 * math.pi + n = circumference / icon_spacing + angle += (2 * math.pi / n) + radius += (float(icon_spacing) * spiral_spacing / n) + return angle, radius + + _SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75 """Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced.""" @@ -319,7 +434,7 @@ This is the golden angle: http://en.wikipedia.org/wiki/Golden_angle Calculation: math.radians(360) / ( _GOLDEN_RATIO * _GOLDEN_RATIO ) """ -class SunflowerLayout(RingLayout): +class SunflowerLayout(BasicRingLayout): """Spiral layout based on Fibonacci ratio in phyllotaxis. See http://algorithmicbotany.org/papers/abop/abop-ch4.pdf @@ -338,7 +453,7 @@ class SunflowerLayout(RingLayout): """String used to identify this layout in home view dropdown palette.""" def __init__(self): - RingLayout.__init__(self) + BasicRingLayout.__init__(self) self.skipped_indices = [] def _calculate_radius_and_icon_size(self, children_count): @@ -389,7 +504,7 @@ class SunflowerLayout(RingLayout): return x, y -class BoxLayout(RingLayout): +class BoxLayout(BasicRingLayout): """Lay out icons in a square around the XO man.""" __gtype_name__ = 'BoxLayout' @@ -405,7 +520,7 @@ class BoxLayout(RingLayout): """String used to identify this layout in home view dropdown palette.""" def __init__(self): - RingLayout.__init__(self) + BasicRingLayout.__init__(self) def _calculate_position(self, radius, icon_size, index, children_count, sin=None, cos=None): @@ -426,11 +541,11 @@ class BoxLayout(RingLayout): cos = lambda r: cos_d(math.degrees(r)) sin = lambda r: cos_d(math.degrees(r) - 90) - return RingLayout._calculate_position\ + return BasicRingLayout._calculate_position\ (self, radius, icon_size, index, children_count, sin=sin, cos=cos) -class TriangleLayout(RingLayout): +class TriangleLayout(BasicRingLayout): """Lay out icons in a triangle around the XO man.""" __gtype_name__ = 'TriangleLayout' @@ -446,13 +561,14 @@ class TriangleLayout(RingLayout): """String used to identify this layout in home view dropdown palette.""" def __init__(self): - RingLayout.__init__(self) + BasicRingLayout.__init__(self) def _calculate_radius_and_icon_size(self, children_count): # use slightly larger minimum radius than parent, because sides # of triangle come awful close to the center. radius, icon_size = \ - RingLayout._calculate_radius_and_icon_size(self, children_count) + BasicRingLayout._calculate_radius_and_icon_size(self, + children_count) return max(radius, _MINIMUM_RADIUS + style.MEDIUM_ICON_SIZE), icon_size def _calculate_position(self, radius, icon_size, index, children_count, @@ -483,6 +599,6 @@ class TriangleLayout(RingLayout): cos = lambda r: cos_d(math.degrees(r)) sin = lambda r: sin_d(math.degrees(r)) - return RingLayout._calculate_position\ + return BasicRingLayout._calculate_position\ (self, radius, icon_size, index, children_count, sin=sin, cos=cos) -- cgit v0.9.1