From 0984938f95cd66b0cb7c198c346a5f80da68f106 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Mon, 12 Mar 2007 12:39:29 +0100 Subject: [PATCH] New animation API. Start rewriting the frame slidein slideout logic. (Use the keys for now, mouse is not working) --- shell/view/Shell.py | 1 - shell/view/frame/frame.py | 199 +++++++++++++++---------------------- sugar/graphics/Makefile.am | 1 + sugar/graphics/animator.py | 70 +++++++++++++ 4 files changed, 151 insertions(+), 120 deletions(-) create mode 100644 sugar/graphics/animator.py diff --git a/shell/view/Shell.py b/shell/view/Shell.py index 99c940f5..9a2772f5 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -53,7 +53,6 @@ class Shell(gobject.GObject): self._popup_context = PopupContext() self._frame = Frame(self) - self._frame.show_and_hide(3) #self.start_activity('org.laptop.JournalActivity') diff --git a/shell/view/frame/frame.py b/shell/view/frame/frame.py index 6586cd33..c1f65471 100644 --- a/shell/view/frame/frame.py +++ b/shell/view/frame/frame.py @@ -27,33 +27,43 @@ from view.frame.PanelWindow import PanelWindow from view.frame.clipboardpanelwindow import ClipboardPanelWindow from view.frame.framepopupcontext import FramePopupContext from model.ShellModel import ShellModel -from sugar.graphics.timeline import Timeline +from sugar.graphics import animator from sugar.graphics import units -_ANIMATION = True +class _Animation(animator.Animation): + def __init__(self, frame, end): + start = frame.get_current_position() + animator.Animation.__init__(self, start, end) + self._frame = frame -class Frame: - INACTIVE = 0 - TEMPORARY = 1 - STICKY = 2 - HIDE_ON_LEAVE = 3 - AUTOMATIC = 4 + def next_frame(self, current): + self._frame.move(current) +class _KeyListener(object): + def __init__(self, frame): + self._frame = frame + self._frame_active = False + + def key_press(self): + if self._frame_active: + self._frame.hide() + self._frame_active = False + else: + self._frame.show() + self._frame_active = True + + def key_release(self): + pass + +class Frame(object): def __init__(self, shell): self._left_panel = None self._right_panel = None self._top_panel = None self._bottom_panel = None - self._hover_frame = False self._shell = shell - self._mode = Frame.INACTIVE - self._current_position = 0 - - self._timeline = Timeline(self) - self._timeline.add_tag('slide_in', 18, 24) - self._timeline.add_tag('before_slide_out', 48, 48) - self._timeline.add_tag('slide_out', 49, 54) + self._current_position = 0.0 self._event_frame = EventFrame() self._event_frame.connect('enter-edge', self._enter_edge_cb) @@ -78,6 +88,21 @@ class Frame: screen = gtk.gdk.screen_get_default() screen.connect('size-changed', self._size_changed_cb) + self._key_listener = _KeyListener(self) + + def is_visible(self): + return self._top_panel.props.visible + + def get_popup_context(self): + return self._popup_context + + def get_current_position(self): + return self._current_position + + def move(self, pos): + self._current_position = pos + self._update_position() + def _create_top_panel(self): panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL) root = panel.get_root() @@ -141,75 +166,6 @@ class Frame: panel.connect('enter-notify-event', self._enter_notify_cb) panel.connect('leave-notify-event', self._leave_notify_cb) - def _popup_context_activated_cb(self, popup_context): - self._timeline.goto('slide_in', True) - - def _popup_context_deactivated_cb(self, popup_context): - if self._mode != Frame.STICKY and not self._hover_frame: - self._timeline.play('before_slide_out', 'slide_out') - - def _enter_notify_cb(self, window, event): - self._enter_notify() - - def _drag_motion_cb(self, window, context, x, y, time): - self._enter_notify() - return True - - def _drag_leave_cb(self, window, drag_context, timestamp): - self._leave_notify(window) - - def _leave_notify_cb(self, window, event): - # FIXME for some reason every click cause also a leave-notify - if event.state == gtk.gdk.BUTTON1_MASK: - return - - self._leave_notify(window) - - def _enter_notify(self): - self._hover_frame = True - if not self._timeline.on_tag('slide_in'): - self._timeline.goto('slide_in', True) - - def _leave_notify(self, panel): - self._hover_frame = False - if not self._popup_context.is_active() and \ - (self._mode == Frame.HIDE_ON_LEAVE or \ - self._mode == Frame.AUTOMATIC): - self._timeline.play('before_slide_out', 'slide_out') - - def _enter_edge_cb(self, event_frame): - self._mode = Frame.HIDE_ON_LEAVE - self._timeline.play(None, 'slide_in') - - def _enter_corner_cb(self, event_frame): - self._mode = Frame.HIDE_ON_LEAVE - self._timeline.play('slide_in', 'slide_in') - - def _event_frame_leave_cb(self, event_frame): - if self._mode != Frame.STICKY: - self._timeline.goto('slide_out', True) - - def show_and_hide(self, seconds): - self._mode = Frame.AUTOMATIC - self._timeline.play() - - def notify_key_press(self): - if self._timeline.on_tag('slide_in'): - self._timeline.play('before_slide_out', 'slide_out') - elif self._timeline.on_tag('before_slide_out'): - self._mode = Frame.TEMPORARY - else: - self._mode = Frame.STICKY - self._timeline.play('slide_in', 'slide_in') - - def notify_key_release(self): - if self._mode == Frame.TEMPORARY: - self._timeline.play('before_slide_out', 'slide_out') - - def _move(self, pos): - self._current_position = pos - self._update_position() - def _update_position(self): screen_h = gtk.gdk.screen_height() screen_w = gtk.gdk.screen_width() @@ -230,43 +186,48 @@ class Frame: screen_w, 0, screen_w - units.grid_to_pixels(1), 0) - def do_slide_in(self, current=0, n_frames=0): - if _ANIMATION: - if current + 1 == n_frames: - # hardcode last frame to avoid precision errors in division - self._move(1) - else: - # each frame, move half the remaining distance - pos = 0.0 - for i in range(0, current + 1): - pos += float((1.0 - float(pos)) / 2.0) - self._move(pos) - elif current == 0: - self._move(1) - if self._event_frame.is_visible(): - self._event_frame.hide() + def hide(self): + anim = animator.Animator(0.5, 30, animator.EASE_OUT_EXPO) + anim.add(_Animation(self, 0.0)) + anim.start() - def do_slide_out(self, current=0, n_frames=0): - if _ANIMATION: - if current + 1 == n_frames: - # hardcode last frame to avoid precision errors in division - self._move(0) - else: - # each frame, move half the remaining distance - pos = 0.0 - for i in range(0, current + 1): - pos += float((1.0 - float(pos)) / 2.0) - self._move(1.0 - float(pos)) - elif current == 0: - self._move(0) - if not self._event_frame.is_visible(): - self._event_frame.show() + def show(self): + anim = animator.Animator(0.5, 30, animator.EASE_OUT_EXPO) + anim.add(_Animation(self, 1.0)) + anim.start() def _size_changed_cb(self, screen): self._update_position() - def is_visible(self): - return self._top_panel.props.visible + def _popup_context_activated_cb(self, popup_context): + pass - def get_popup_context(self): - return self._popup_context + def _popup_context_deactivated_cb(self, popup_context): + pass + + def _enter_notify_cb(self, window, event): + pass + + def _leave_notify_cb(self, window, event): + pass + + def _drag_motion_cb(self, window, context, x, y, time): + pass + + def _drag_leave_cb(self, window, drag_context, timestamp): + pass + + def _enter_edge_cb(self, event_frame): + pass + + def _enter_corner_cb(self, event_frame): + pass + + def _event_frame_leave_cb(self, event_frame): + pass + + def notify_key_press(self): + self._key_listener.key_press() + + def notify_key_release(self): + self._key_listener.key_release() diff --git a/sugar/graphics/Makefile.am b/sugar/graphics/Makefile.am index 9479ea23..d2ccbe71 100644 --- a/sugar/graphics/Makefile.am +++ b/sugar/graphics/Makefile.am @@ -1,6 +1,7 @@ sugardir = $(pythondir)/sugar/graphics sugar_PYTHON = \ __init__.py \ + animator.py \ bubble.py \ button.py \ iconbutton.py \ diff --git a/sugar/graphics/animator.py b/sugar/graphics/animator.py new file mode 100644 index 00000000..def823cc --- /dev/null +++ b/sugar/graphics/animator.py @@ -0,0 +1,70 @@ +# Copyright (C) 2007, Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import time + +import gobject + +EASE_OUT_EXPO = 1 + +class Animator(object): + def __init__(self, time, fps, easing=EASE_OUT_EXPO): + self._animations = [] + self._time = time + self._interval = 1.0 / fps + self._easing = easing + self._timeout_sid = 0 + + def add(self, animation): + self._animations.append(animation) + + def start(self): + if self._timeout_sid: + self.stop() + + self._start_time = time.time() + self._timeout_sid = gobject.timeout_add( + int(self._interval * 1000), self._next_frame_cb) + + def stop(self): + if self._timeout_sid: + gobject.source_remove(self._timeout_sid) + self._timeout_sid = 0 + + def _next_frame_cb(self): + current_time = min (self._time, time.time() - self._start_time) + for animation in self._animations: + animation.do_frame(current_time, self._time, self._easing) + + return (current_time != self._time) + +class Animation(object): + def __init__(self, start, end): + self.start = start + self.end = end + + def do_frame(self, time, duration, easing): + start = self.start + change = self.end - self.start + + if easing == EASE_OUT_EXPO: + frame = change * pow(2, 10 * (time / duration - 1)) + start; + + self.next_frame(frame) + + def next_frame(self, frame): + pass