188 | | |
189 | | class RingLayout(FavoritesLayout): |
| 190 | _INTERMEDIATE_C = (style.STANDARD_ICON_SIZE + style.SMALL_ICON_SIZE) / 2 |
| 191 | _INTERMEDIATE_A = (style.STANDARD_ICON_SIZE * 2 + _INTERMEDIATE_C) / 3 |
| 192 | _INTERMEDIATE_E = (_INTERMEDIATE_C + style.SMALL_ICON_SIZE * 2) / 3 |
| 193 | _INTERMEDIATE_B = (_INTERMEDIATE_A + _INTERMEDIATE_C) / 2 |
| 194 | _INTERMEDIATE_D = (_INTERMEDIATE_C + _INTERMEDIATE_E) / 2 |
| 195 | _ICON_SIZES = [style.MEDIUM_ICON_SIZE, style.STANDARD_ICON_SIZE, |
| 196 | _INTERMEDIATE_A, _INTERMEDIATE_B, _INTERMEDIATE_C, |
| 197 | _INTERMEDIATE_D, _INTERMEDIATE_E, style.SMALL_ICON_SIZE] |
| 198 | _ICON_SPACING_FACTORS = [1.5, 1.4, 1.3, 1.2, 1.15, 1.1, 1.05, 1.0] |
| 199 | |
| 200 | |
| 201 | class BasicRingLayout(FavoritesLayout): |
224 | | # what's the radius required without downscaling? |
225 | | distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING |
226 | | icon_size = style.STANDARD_ICON_SIZE |
227 | | # circumference is 2*pi*r; we want this to be at least |
228 | | # 'children_count * distance' |
229 | | radius = children_count * distance / (2 * math.pi) |
230 | | # limit computed radius to reasonable bounds. |
231 | | radius = max(radius, _MINIMUM_RADIUS) |
232 | | radius = min(radius, _MAXIMUM_RADIUS) |
233 | | # recompute icon size from limited radius |
234 | | if children_count > 0: |
235 | | icon_size = (2 * math.pi * radius / children_count) \ |
236 | | - style.DEFAULT_SPACING |
237 | | # limit adjusted icon size. |
238 | | icon_size = max(icon_size, style.SMALL_ICON_SIZE) |
239 | | icon_size = min(icon_size, style.MEDIUM_ICON_SIZE) |
| 236 | """ Adjust the ring radius and icon size as needed. """ |
| 237 | # Begin by increasing the radius. |
| 238 | distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \ |
| 239 | _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)] |
| 240 | radius = max(children_count * distance / (2 * math.pi), _MINIMUM_RADIUS) |
| 241 | if radius < _MAXIMUM_RADIUS: |
| 242 | return radius, style.MEDIUM_ICON_SIZE |
| 243 | |
| 244 | # Continue by shrinking the icon size to STANDARD_ICON_SIZE. |
| 245 | radius = _MAXIMUM_RADIUS |
| 246 | distance = radius * (2 * math.pi) / children_count |
| 247 | icon_size = int(distance - style.DEFAULT_SPACING * \ |
| 248 | _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)]) |
| 249 | if icon_size >= style.STANDARD_ICON_SIZE: |
| 250 | return radius, icon_size |
| 251 | |
| 252 | # Continue by shrinking the icon size to SMALL_ICON_SIZE. |
| 253 | icon_size = max(int(distance - style.DEFAULT_SPACING * \ |
| 254 | _ICON_SPACING_FACTORS[_ICON_SIZES.index( |
| 255 | style.SMALL_ICON_SIZE)]), style.SMALL_ICON_SIZE) |
| 314 | |
| 315 | _MIMIMUM_RADIUS_ENCROACHMENT = 0.75 |
| 316 | _INITIAL_ANGLE = math.pi |
| 317 | _SPIRAL_SPACING_FACTORS = [1.5, 1.5, 1.5, 1.4, 1.35, 1.3, 1.25, 1.2] |
| 318 | |
| 319 | |
| 320 | class RingLayout(BasicRingLayout): |
| 321 | """ Variation of Basic Ring that morphs into a spiral as |
| 322 | the number of icons increases beyond the capacity of the |
| 323 | STANDARD_ICON_SIZE. """ |
| 324 | |
| 325 | __gtype_name__ = 'RingLayout' |
| 326 | icon_name = 'view-radial' |
| 327 | """Name of icon used in home view dropdown palette.""" |
| 328 | key = 'ring-layout' |
| 329 | """String used in profile to represent this view.""" |
| 330 | |
| 331 | def __init__(self): |
| 332 | BasicRingLayout.__init__(self) |
| 333 | self._locked_children = {} |
| 334 | self._spiral_mode = False |
| 335 | |
| 336 | def _calculate_radius_and_icon_size(self, children_count): |
| 337 | """ Adjust the ring or spiral radius and icon size as needed. """ |
| 338 | self._spiral_mode = False |
| 339 | # Begin by increasing the radius. |
| 340 | distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \ |
| 341 | _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)] |
| 342 | radius = max(children_count * distance / (2 * math.pi), _MINIMUM_RADIUS) |
| 343 | if radius < _MAXIMUM_RADIUS: |
| 344 | return radius, style.MEDIUM_ICON_SIZE |
| 345 | |
| 346 | # Continue by shrinking the icon size. |
| 347 | radius = _MAXIMUM_RADIUS |
| 348 | distance = radius * (2 * math.pi) / children_count |
| 349 | icon_size = int(distance - style.DEFAULT_SPACING * \ |
| 350 | _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)]) |
| 351 | if icon_size >= style.STANDARD_ICON_SIZE: |
| 352 | return radius, icon_size |
| 353 | |
| 354 | # Finally, switch to a spiral. |
| 355 | self._spiral_mode = True |
| 356 | icon_size = style.STANDARD_ICON_SIZE |
| 357 | angle, radius = self._calculate_angle_and_radius(children_count, |
| 358 | icon_size) |
| 359 | while radius > _MAXIMUM_RADIUS: |
| 360 | i = _ICON_SIZES.index(icon_size) |
| 361 | if i < len(_ICON_SIZES) - 1: |
| 362 | icon_size = _ICON_SIZES[i + 1] |
| 363 | angle, radius = self._calculate_angle_and_radius( |
| 364 | children_count, icon_size) |
| 365 | else: |
| 366 | break |
| 367 | return radius, icon_size |
| 368 | |
| 369 | def _calculate_position(self, radius, icon_size, icon_index, children_count, |
| 370 | sin=math.sin, cos=math.cos): |
| 371 | """ Calculate an icon position on a circle or a spiral. """ |
| 372 | width, height = self.box.get_allocation() |
| 373 | if self._spiral_mode: |
| 374 | min_width_, box_width = self.box.get_width_request() |
| 375 | min_height_, box_height = self.box.get_height_request(box_width) |
| 376 | angle, radius = self._calculate_angle_and_radius(icon_index, |
| 377 | icon_size) |
| 378 | x, y = self._convert_from_polar_to_cartesian(angle, radius, |
| 379 | icon_size, |
| 380 | width, height) |
| 381 | else: |
| 382 | angle = icon_index * (2 * math.pi / children_count) - math.pi / 2 |
| 383 | x = radius * cos(angle) + (width - icon_size) / 2 |
| 384 | y = radius * sin(angle) + (height - icon_size - \ |
| 385 | (style.GRID_CELL_SIZE / 2)) / 2 |
| 386 | return x, y |
| 387 | |
| 388 | def _convert_from_polar_to_cartesian(self, angle, radius, icon_size, width, |
| 389 | height): |
| 390 | """ Convert angle, radius to x, y """ |
| 391 | x = int(math.sin(angle) * radius) |
| 392 | y = int(math.cos(angle) * radius) |
| 393 | x = - x + (width - icon_size) / 2 |
| 394 | y = y + (height - icon_size - (style.GRID_CELL_SIZE / 2)) / 2 |
| 395 | return x, y |
| 396 | |
| 397 | def _calculate_angle_and_radius(self, icon_count, icon_size): |
| 398 | """ Based on icon_count and icon_size, calculate radius and angle. """ |
| 399 | spiral_spacing = _SPIRAL_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] |
| 400 | icon_spacing = icon_size + style.DEFAULT_SPACING * \ |
| 401 | _ICON_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] |
| 402 | angle = _INITIAL_ANGLE |
| 403 | radius = _MINIMUM_RADIUS - (icon_size * _MIMIMUM_RADIUS_ENCROACHMENT) |
| 404 | for i in range(icon_count): |
| 405 | circumference = radius * 2 * math.pi |
| 406 | n = circumference / icon_spacing |
| 407 | angle += (2 * math.pi / n) |
| 408 | radius += (float(icon_spacing) * spiral_spacing / n) |
| 409 | return angle, radius |
| 410 | |
| 411 | |