[Sugar-devel] [PATCH] refactoring of Homeview layouts to simplify Spiral option

Walter Bender walter at sugarlabs.org
Wed Feb 9 14:45:18 EST 2011


---
 src/jarabe/desktop/favoriteslayout.py |  233 ++++++++++++++++++++-------------
 1 files changed, 145 insertions(+), 88 deletions(-)

diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py
index 360c147..22f729c 100644
--- a/src/jarabe/desktop/favoriteslayout.py
+++ b/src/jarabe/desktop/favoriteslayout.py
@@ -29,17 +29,10 @@ from sugar.graphics import style
 from jarabe.model import bundleregistry
 from jarabe.desktop.grid import Grid
 
-
 _logger = logging.getLogger('FavoritesLayout')
 
 _CELL_SIZE = 4
 _BASE_SCALE = 1000
-_INTERMEDIATE_B = (style.STANDARD_ICON_SIZE + style.SMALL_ICON_SIZE) / 2
-_INTERMEDIATE_A = (style.STANDARD_ICON_SIZE + _INTERMEDIATE_B) / 2
-_INTERMEDIATE_C = (_INTERMEDIATE_B + style.SMALL_ICON_SIZE) / 2
-_ICON_SIZES = [style.MEDIUM_ICON_SIZE, style.STANDARD_ICON_SIZE,
-               _INTERMEDIATE_A, _INTERMEDIATE_B, _INTERMEDIATE_C,
-               style.SMALL_ICON_SIZE]
 
 
 class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
@@ -178,7 +171,7 @@ class RandomLayout(FavoritesLayout):
     def do_allocate(self, x, y, width, height, req_width, req_height,
                     origin_changed):
         for child in self.box.get_layout_children():
-            # We need to always get requests to not confuse hippo
+            # We need to always get requests to not confuse hippo.
             min_w_, child_width = child.get_width_request()
             min_h_, child_height = child.get_height_request(child_width)
 
@@ -197,19 +190,24 @@ _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
-_ICON_SPACING_FACTORS = [1.5, 1.4, 1.3, 1.2, 1.1, 1.0]
-_SPIRAL_SPACING_FACTORS = [1.5, 1.5, 1.5, 1.4, 1.3, 1.2]
-_MIMIMUM_RADIUS_ENCROACHMENT = 0.75
-_INITIAL_ANGLE = math.pi
+_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 RingLayout(FavoritesLayout):
-    """Lay out icons in a ring or spiral around the XO man."""
+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')
@@ -218,7 +216,6 @@ class RingLayout(FavoritesLayout):
     def __init__(self):
         FavoritesLayout.__init__(self)
         self._locked_children = {}
-        self._spiral_mode = False
 
     def append(self, icon, locked=False):
         FavoritesLayout.append(self, icon, locked)
@@ -239,8 +236,8 @@ class RingLayout(FavoritesLayout):
             self._locked_children[child] = (x, y)
 
     def _calculate_radius_and_icon_size(self, children_count):
-        """ Adjust the ring or spiral radius and icon size as needed. """
-        self._spiral_mode = False
+        """ 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),
@@ -248,13 +245,118 @@ class RingLayout(FavoritesLayout):
         if radius < _MAXIMUM_RADIUS:
             return radius, style.MEDIUM_ICON_SIZE
 
-        distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING * \
-            _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_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, icon_index,
+                            children_count, sin=math.sin, cos=math.cos):
+        """ Calculate an icon position on a circle. """
+        width, height = self.box.get_allocation()
+        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 _get_children_in_ring(self):
+        children_in_ring = [child for child in self.box.get_layout_children() \
+                if child not in self._locked_children]
+        return children_in_ring
+
+    def do_allocate(self, x, y, width, height, req_width, req_height,
+                    origin_changed):
+        children_in_ring = self._get_children_in_ring()
+        if children_in_ring:
+            radius, icon_size = \
+                    self._calculate_radius_and_icon_size(len(children_in_ring))
+
+            for n in range(len(children_in_ring)):
+                child = children_in_ring[n]
+
+                x, y = self._calculate_position(radius, icon_size, n,
+                                                len(children_in_ring))
+
+                # We need to always get requests to not confuse hippo.
+                min_w_, child_width = child.get_width_request()
+                min_h_, child_height = child.get_height_request(child_width)
+
+                child.allocate(int(x), int(y), child_width, child_height,
+                               origin_changed)
+                child.item.props.size = icon_size
+
+        for child in self._locked_children.keys():
+            x, y = self._locked_children[child]
+
+            # We need to always get requests to not confuse hippo.
+            min_w_, child_width = child.get_width_request()
+            min_h_, child_height = child.get_height_request(child_width)
+
+            if child_width <= 0 or child_height <= 0:
+                return
+
+            child.allocate(int(x), int(y), child_width, child_height,
+                            origin_changed)
+
+    def compare_activities(self, icon_a, icon_b):
+        if hasattr(icon_a, 'installation_time') and \
+                hasattr(icon_b, 'installation_time'):
+            return icon_b.installation_time - icon_a.installation_time
+        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.STANDARD_ICON_SIZE
+            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,
@@ -311,52 +413,6 @@ class RingLayout(FavoritesLayout):
             radius += (float(icon_spacing) * spiral_spacing / n)
         return angle, radius
 
-    def _get_children_in_ring(self):
-        children_in_ring = [child for child in self.box.get_layout_children() \
-                if child not in self._locked_children]
-        return children_in_ring
-
-    def do_allocate(self, x, y, width, height, req_width, req_height,
-                    origin_changed):
-        children_in_ring = self._get_children_in_ring()
-        if children_in_ring:
-            radius, icon_size = \
-                    self._calculate_radius_and_icon_size(len(children_in_ring))
-
-            for n in range(len(children_in_ring)):
-                child = children_in_ring[n]
-
-                x, y = self._calculate_position(radius, icon_size, n,
-                                                len(children_in_ring))
-
-                # We need to always get requests to not confuse hippo
-                min_w_, child_width = child.get_width_request()
-                min_h_, child_height = child.get_height_request(child_width)
-
-                child.allocate(int(x), int(y), child_width, child_height,
-                               origin_changed)
-                child.item.props.size = icon_size
-
-        for child in self._locked_children.keys():
-            x, y = self._locked_children[child]
-
-            # We need to always get requests to not confuse hippo
-            min_w_, child_width = child.get_width_request()
-            min_h_, child_height = child.get_height_request(child_width)
-
-            if child_width <= 0 or child_height <= 0:
-                return
-
-            child.allocate(int(x), int(y), child_width, child_height,
-                            origin_changed)
-
-    def compare_activities(self, icon_a, icon_b):
-        if hasattr(icon_a, 'installation_time') and \
-                hasattr(icon_b, 'installation_time'):
-            return icon_b.installation_time - icon_a.installation_time
-        else:
-            return 0
-
 
 _SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75
 """Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced."""
@@ -384,7 +440,7 @@ 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
@@ -403,7 +459,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):
@@ -431,11 +487,11 @@ class SunflowerLayout(RingLayout):
 
             index = self.adjust_index(oindex)
 
-            # tweak phi to get a nice gap lined up where the "active activity"
+            # Tweak phi to get a nice gap lined up where the "active activity"
             # icon is, below the central XO man.
             phi = index * _SUNFLOWER_ANGLE + math.radians(-130)
 
-            # we offset index when computing r to make space for the XO man.
+            # We offset index when computing r to make space for the XO man.
             r = _SUNFLOWER_CONSTANT * math.sqrt(index + _SUNFLOWER_OFFSET)
 
             # x,y are the top-left corner of the icon, so remove icon_size
@@ -445,8 +501,8 @@ class SunflowerLayout(RingLayout):
             y = r * sin(phi) + (height - icon_size - \
                                 (style.GRID_CELL_SIZE / 2)) / 2
 
-            # skip allocations outside the allocation box.
-            # give up once we can't fit
+            # Skip allocations outside the allocation box.
+            # Give up once we can't fit.
             if r < math.hypot(width / 2, height / 2):
                 if y < 0 or y > (height - icon_size) or \
                        x < 0 or x > (width - icon_size):
@@ -457,7 +513,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'
@@ -473,12 +529,12 @@ 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):
 
-        # use "orthogonal" versions of cos and sin in order to square the
+        # Use "orthogonal" versions of cos and sin in order to square the
         # circle and turn the 'ring view' into a 'box view'
         def cos_d(d):
             while d < 0:
@@ -495,12 +551,12 @@ class BoxLayout(RingLayout):
         cos = lambda r: cos_d(math.degrees(r))
         sin = lambda r: cos_d(math.degrees(r) - 90)
 
-        return RingLayout._calculate_position(self, radius, icon_size, index,
-                                              children_count, sin=sin,
-                                              cos=cos)
+        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'
@@ -516,18 +572,19 @@ 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
+        # 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,
                             sin=math.sin, cos=math.cos):
-        # tweak cos and sin in order to make the 'ring' into an equilateral
+        # Tweak cos and sin in order to make the 'ring' into an equilateral
         # triangle.
 
         def cos_d(d):
@@ -555,6 +612,6 @@ class TriangleLayout(RingLayout):
         cos = lambda r: cos_d(math.degrees(r))
         sin = lambda r: sin_d(math.degrees(r))
 
-        return RingLayout._calculate_position(self, radius, icon_size, index,
-                                              children_count, sin=sin,
-                                              cos=cos)
+        return BasicRingLayout._calculate_position(self, radius, icon_size,
+                                                   index, children_count,
+                                                   sin=sin, cos=cos)
-- 
1.7.4



More information about the Sugar-devel mailing list