Showing with 82 additions and 24 deletions.
  1. +22 −5 src/sugar3/graphics/animator.py
  2. +24 −4 src/sugar3/graphics/palette.py
  3. +35 −15 src/sugar3/graphics/palettewindow.py
  4. +1 −0 src/sugar3/graphics/style.py
@@ -34,12 +34,13 @@ class Animator(GObject.GObject):
'completed': (GObject.SignalFlags.RUN_FIRST, None, ([])),
}

def __init__(self, duration, fps=20, easing=EASE_OUT_EXPO):
def __init__(self, duration, fps=20, easing=EASE_OUT_EXPO, widget=None):
GObject.GObject.__init__(self)
self._animations = []
self._duration = duration
self._interval = 1.0 / fps
self._easing = easing
self._widget = widget
self._timeout_sid = 0
self._start_time = None

@@ -55,16 +56,27 @@ def start(self):
self.stop()

self._start_time = time.time()
self._timeout_sid = GLib.timeout_add(
int(self._interval * 1000), self._next_frame_cb)
if hasattr(self._widget, 'add_tick_callback'):
self._timeout_sid = self._widget.add_tick_callback(
self._next_frame_cb, None)
else:
self._timeout_sid = GLib.timeout_add(
int(self._interval * 1000), self._next_frame_cb)

def stop(self):
if self._timeout_sid:
if self._timeout_sid and \
not hasattr(self._widget, 'add_tick_callback'):
GObject.source_remove(self._timeout_sid)
self._timeout_sid = 0
self.emit('completed')
if self._timeout_sid and hasattr(self._widget, 'add_tick_callback'):
self._widget.remove_tick_callback(self._timeout_sid)
self._timeout_sid = 0
self.emit('completed')
for animation in self._animations:
animation.do_stop()

def _next_frame_cb(self):
def _next_frame_cb(self, *args):
current_time = min(self._duration, time.time() - self._start_time)
current_time = max(current_time, 0.0)

@@ -96,8 +108,13 @@ def do_frame(self, t, duration, easing):
frame = change * (-pow(2, -10 * t / duration) + 1) + start
elif easing == EASE_IN_EXPO:
frame = change * pow(2, 10 * (t / duration - 1)) + start
else:
frame = change * (t / duration) + start

self.next_frame(frame)

def next_frame(self, frame):
pass

def do_stop(self):
pass
@@ -23,6 +23,7 @@
STABLE.
"""
import textwrap
import logging

from gi.repository import GLib
from gi.repository import Gtk
@@ -113,6 +114,7 @@ def __init__(self, label=None, accel_path=None,
self._icon = None
self._icon_visible = True
self._palette_state = self.PRIMARY
self._secondary_anim = None

self._primary_event_box = Gtk.EventBox()
self._primary_event_box.show()
@@ -155,9 +157,6 @@ def __init__(self, label=None, accel_path=None,
self._separator = Gtk.HSeparator()
self._secondary_box.pack_start(self._separator, True, True, 0)

self._secondary_anim = animator.Animator(2.0, 10)
self._secondary_anim.add(_SecondaryAnimation(self))

# we init after initializing all of our containers
PaletteWindow.__init__(self, **kwargs)

@@ -186,6 +185,9 @@ def _setup_widget(self):
self._widget.connect('destroy', self.__destroy_cb)
self._widget.connect('map', self.__map_cb)

self._secondary_anim = animator.Animator(2.0, widget=self._widget, easing=None)
self._secondary_anim.add(_SecondaryAnimation(self, self._widget))

def __map_cb(self, *args):
# Fixes #4463
self._widget.present()
@@ -488,10 +490,28 @@ def add_action(self, label, icon_name=None):

class _SecondaryAnimation(animator.Animation):

def __init__(self, palette):

def __init__(self, palette, widget):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
self._widget = widget
self._aim = None

def next_frame(self, current):
if current < 0.75:
return

if self._aim is None:
self._aim, _ = self._palette._secondary_box.get_preferred_height()
self._aim = style.PALATTE_HEADER_HEIGHT * 2 # TODO!

current_width, current_height = self._widget.get_size()
current = (current - 0.75) * 4 # Animation starts at 1.5
h = style.PALATTE_HEADER_HEIGHT + current * self._aim
logging.error('now <%r %rx%r>, aim %r, going to %r', current, current_width, current_height, self._aim, h)
self._widget.resize(
current_width, h)

if current == 1.0:
self._aim = None # Reset if label changes next time
self._palette.set_palette_state(Palette.SECONDARY)
@@ -279,7 +279,6 @@ def __init__(self, palette=None):

self._palette = palette
self.set_decorated(False)
self.set_resizable(False)
self.set_position(Gtk.WindowPosition.NONE)

context = self.get_style_context()
@@ -317,7 +316,7 @@ def do_get_preferred_width(self):
label_width = self._palette.get_label_width()
size = max(natural, label_width + 2 * self.get_border_width(),
style.GRID_CELL_SIZE * 3)
return size, size
return -1, size

def do_size_allocate(self, allocation):
Gtk.Window.do_size_allocate(self, allocation)
@@ -505,12 +504,8 @@ def __init__(self, **kwargs):
self._up = False
self._palette_state = None
self._widget = None

self._popup_anim = animator.Animator(.5, 10)
self._popup_anim.add(_PopupAnimation(self))

self._popdown_anim = animator.Animator(0.6, 10)
self._popdown_anim.add(_PopdownAnimation(self))
self._popup_anim = None
self._popdown_anim = None

GObject.GObject.__init__(self, **kwargs)

@@ -531,6 +526,13 @@ def _setup_widget(self):
self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
self._mouse_detector.parent = self._widget

self._popup_anim = animator.Animator(
0.5, widget=self._widget, easing=None)
self._popup_anim.add(_PopupAnimation(self, self._widget))

self._popdown_anim = animator.Animator(0.6, widget=self._widget)
self._popdown_anim.add(_PopdownAnimation(self, self._widget))

def _teardown_widget(self):
self._widget.disconnect_by_func(self.__show_cb)
self._widget.disconnect_by_func(self.__hide_cb)
@@ -657,14 +659,15 @@ def popup(self, immediate=False):

self._popdown_anim.stop()

self._widget.popup(self._invoker)
self._widget.set_opacity(1.0)
# we have to invoke update_position() twice
# since WM could ignore first move() request
self.update_position()
if not immediate:
self._popup_anim.start()
else:
self._popup_anim.stop()
self._widget.popup(self._invoker)
# we have to invoke update_position() twice
# since WM could ignore first move() request
self.update_position()

def popdown(self, immediate=False):
self._popup_anim.stop()
@@ -760,25 +763,42 @@ def set_palette_state(self, state):

class _PopupAnimation(animator.Animation):

def __init__(self, palette):
def __init__(self, palette, widget):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
self._widget = widget
self._aim = None

def next_frame(self, current):
if current == 1.0:
self._palette.popup(immediate=True)
self._aim = None # Reset if label changes next time
self._widget.get_child().show()
else:
self._widget.get_child().hide()

current_width, current_height = self._widget.get_size()
if self._aim is None:
self._aim = current_width

self._widget.resize(self._aim * current, current_height)
self._widget.queue_resize()


class _PopdownAnimation(animator.Animation):

def __init__(self, palette):
def __init__(self, palette, widget):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
self._widget = widget

def next_frame(self, current):
self._widget.set_opacity(1.0 - current)
if current == 1.0:
self._palette.popdown(immediate=True)

def do_stop(self):
self._widget.set_opacity(1.0)


class Invoker(GObject.GObject):

@@ -144,6 +144,7 @@ def zoom(units):
COLOR_HIGHLIGHT = Color('#E7E7E7')

PALETTE_CURSOR_DISTANCE = zoom(10)
PALATTE_HEADER_HEIGHT = zoom(75)

TOOLBAR_ARROW_SIZE = zoom(24)