Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/jarabe/desktop/favoriteslayout.py182
1 files changed, 149 insertions, 33 deletions
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)