Commit 1b6c3c43 authored by Jim Nelson's avatar Jim Nelson

#1081: Slideshow transitions, including Fade, Shift, and Tear down. Courtesy Maxim Kartashev.

parent d0a1fdf2
......@@ -15,7 +15,7 @@ guillaumev <guillaume@viguierjust.com>
Mikko Huhtala <mikko.t.huhtala@gmail.com>
David Jeske <davidj@gmail.com>
Matt Jones <mattjones@workhorsy.org>
Maxim <perezk@mail.ru>
Maxim Kartashev <perezk@mail.ru>
mcben <mcbennn@gmail.com>
Till Kamppeter <till.kamppeter@gmail.com>
Richard B. Kreckel <kreckel@ginac.de>
......
......@@ -85,6 +85,11 @@ public class FullscreenWindow : PageWindow {
toolbar_window.realize.connect(on_toolbar_realized);
add(page);
// call to set_default_size() saves one repaint caused by changing
// size from default to full screen. In slideshow mode, this change
// also causes pixbuf cache updates, so it really saves some work.
set_default_size(get_screen().get_width(), get_screen().get_height());
// need to create a Gdk.Window to set masks
fullscreen();
......
......@@ -14,6 +14,9 @@ public class Config {
public const double SLIDESHOW_DELAY_MAX = 30.0;
public const double SLIDESHOW_DELAY_MIN = 1.0;
public const double SLIDESHOW_DELAY_DEFAULT = 3.0;
public const double SLIDESHOW_TRANSITION_DELAY_MAX = 1.0;
public const double SLIDESHOW_TRANSITION_DELAY_MIN = 0.1;
public const double SLIDESHOW_TRANSITION_DELAY_DEFAULT = 0.3;
public const int WIDTH_DEFAULT = 1024;
public const int HEIGHT_DEFAULT = 768;
public const int SIDEBAR_MIN_POSITION = 180;
......@@ -423,7 +426,24 @@ public class Config {
return get_double("/apps/shotwell/preferences/slideshow/delay", SLIDESHOW_DELAY_DEFAULT).clamp(
SLIDESHOW_DELAY_MIN, SLIDESHOW_DELAY_MAX);
}
public bool set_slideshow_transition_delay(double delay) {
return set_double("/apps/shotwell/preferences/slideshow_transition/delay", delay);
}
public double get_slideshow_transition_delay() {
return get_double("/apps/shotwell/preferences/slideshow_transition/delay", SLIDESHOW_TRANSITION_DELAY_DEFAULT).clamp(
SLIDESHOW_TRANSITION_DELAY_MIN, SLIDESHOW_TRANSITION_DELAY_MAX);
}
public bool set_slideshow_transition_effect(string name) {
return set_string("/apps/shotwell/preferences/slideshow_transition/name", name);
}
public string get_slideshow_transition_effect() {
return get_string("/apps/shotwell/preferences/slideshow_transition/name", TransitionEffectsManager.NULL_TRANSITION_NAME);
}
public RatingFilter get_photo_rating_filter() {
return (RatingFilter)(get_int("/apps/shotwell/preferences/ui/photo_rating_filter",
RatingFilter.UNRATED_OR_HIGHER));
......
......@@ -1789,22 +1789,26 @@ public abstract class SinglePhotoPage : Page {
protected Gdk.GC text_gc = null;
private bool scale_up_to_viewport;
private TransitionEffect transition_effect;
private Gdk.Pixmap pixmap = null;
private Dimensions pixmap_dim = Dimensions();
private Gdk.Pixbuf unscaled = null;
private Dimensions max_dim = Dimensions();
private Gdk.Pixbuf scaled = null;
private Gdk.Pixbuf old_scaled = null; // previous scaled image
private Gdk.Rectangle scaled_pos = Gdk.Rectangle();
private ZoomState static_zoom_state;
private bool zoom_high_quality = true;
private ZoomState saved_zoom_state;
private bool has_saved_zoom_state = false;
public SinglePhotoPage(string page_name, bool scale_up_to_viewport) {
base(page_name);
this.scale_up_to_viewport = scale_up_to_viewport;
transition_effect = TransitionEffectsManager.get_instance().get_null_instance();
// With the current code automatically resizing the image to the viewport, scrollbars
// should never be shown, but this may change if/when zooming is supported
set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
......@@ -1829,7 +1833,24 @@ public abstract class SinglePhotoPage : Page {
set_event_source(canvas);
}
public bool is_transition_in_progress() {
return transition_effect.state.is_in_progress();
}
public void cancel_transition() {
if (transition_effect.state.is_in_progress())
transition_effect.state.cancel();
}
public void set_transition_effect(TransitionEffect transition_effect) {
// cancel current transition
cancel_transition();
// replace with new one
this.transition_effect = transition_effect;
}
private void render_zoomed_to_pixmap(ZoomState zoom_state) {
assert(is_zoom_supported());
......@@ -1945,19 +1966,23 @@ public abstract class SinglePhotoPage : Page {
// the caller capable of producing larger ones depending on the viewport size). max_dim
// is used when scale_up_to_viewport is set to true. Pass a Dimensions with no area if
// max_dim should be ignored (i.e. scale_up_to_viewport is false).
public void set_pixbuf(Gdk.Pixbuf unscaled, Dimensions max_dim) {
public void set_pixbuf(Gdk.Pixbuf unscaled, Dimensions max_dim, bool do_transition = false,
Direction transition_direction = Direction.FORWARD) {
static_zoom_state = ZoomState(max_dim, pixmap_dim,
static_zoom_state.get_interpolation_factor(),
static_zoom_state.get_viewport_center());
cancel_transition();
this.unscaled = unscaled;
this.max_dim = max_dim;
this.old_scaled = scaled;
scaled = null;
// need to make sure this has happened
canvas.realize();
repaint();
repaint(do_transition, transition_direction);
}
public void blank_display() {
......@@ -2054,17 +2079,25 @@ public abstract class SinglePhotoPage : Page {
pixmap.draw_rectangle(canvas.style.black_gc, true, 0, 0, -1, -1);
render_zoomed_to_pixmap(static_zoom_state);
canvas.window.draw_drawable(canvas_gc, pixmap, 0, 0, 0, 0, -1, -1);
} else if (transition_effect.state.is_in_progress()) {
transition_effect.paint(drawable);
} else {
drawable.draw_rectangle(canvas.style.black_gc, true, 0, 0, -1, -1);
drawable.draw_pixbuf(gc, scaled, 0, 0, scaled_pos.x, scaled_pos.y, -1, -1,
Gdk.RgbDither.NORMAL, 0, 0);
}
}
public void repaint() {
internal_repaint(false);
public void simple_repaint() {
repaint();
}
public void repaint(bool do_transition = false, Direction transition_direction = Direction.FORWARD) {
internal_repaint(false, do_transition, transition_direction);
}
private void internal_repaint(bool fast) {
private void internal_repaint(bool fast, bool do_transition = false,
Direction transition_direction = Direction.FORWARD) {
// if not in view, assume a full repaint needed in future but do nothing more
if (!is_in_view()) {
pixmap = null;
......@@ -2087,6 +2120,7 @@ public abstract class SinglePhotoPage : Page {
// save if reporting an image being rescaled
Dimensions old_scaled_dim = Dimensions.for_rectangle(scaled_pos);
Gdk.Rectangle old_scaled_pos = scaled_pos;
// attempt to reuse pixmap
if (pixmap_dim.width != width || pixmap_dim.height != height)
......@@ -2143,8 +2177,15 @@ public abstract class SinglePhotoPage : Page {
}
zoom_high_quality = !fast;
paint(canvas_gc, pixmap);
if (do_transition && transition_effect.enabled) {
assert(!transition_effect.state.is_in_progress());
transition_effect.start(old_scaled, old_scaled_pos, scaled, scaled_pos,
transition_direction);
} else {
paint(canvas_gc, pixmap);
}
// invalidate everything
invalidate_all();
}
......
......@@ -8,11 +8,6 @@ class SlideshowPage : SinglePhotoPage {
private const int READAHEAD_COUNT = 5;
private const int CHECK_ADVANCE_MSEC = 250;
private enum Direction {
FORWARD,
BACKWARD
}
private SourceCollection sources;
private ViewCollection controller;
private Photo current;
......@@ -30,44 +25,162 @@ class SlideshowPage : SinglePhotoPage {
private class SettingsDialog : Gtk.Dialog {
Gtk.SpinButton delay_entry;
Gtk.HScale hscale;
Gtk.ComboBox transition_effect_selector;
Gtk.HScale transition_effect_hscale;
Gtk.SpinButton transition_effect_entry;
Gtk.Adjustment transition_effect_adjustment;
public SettingsDialog() {
double delay = Config.get_instance().get_slideshow_delay();
set_modal(true);
set_transient_for(AppWindow.get_fullscreen());
add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK);
Gtk.STOCK_OK, Gtk.ResponseType.OK);
set_title(_("Settings"));
Gtk.Label delay_label = new Gtk.Label(_("Delay:"));
Gtk.Label delay_label = new Gtk.Label.with_mnemonic(_("_Delay:"));
delay_label.xalign = (float) 1.0;
Gtk.Label units_label = new Gtk.Label(_("seconds"));
units_label.xalign = (float) 0.0;
Gtk.Label units_label1 = new Gtk.Label(_("seconds"));
units_label1.xalign = (float) 0.0;
Gtk.Adjustment adjustment = new Gtk.Adjustment(delay, Config.SLIDESHOW_DELAY_MIN, Config.SLIDESHOW_DELAY_MAX, 0.1, 1, 0);
hscale = new Gtk.HScale(adjustment);
hscale.set_draw_value(false);
hscale.set_size_request(150,-1);
delay_label.set_mnemonic_widget(hscale);
delay_entry = new Gtk.SpinButton(adjustment, 0.1, 1);
delay_entry = new Gtk.SpinButton(adjustment, 0.1, 1);
delay_entry.set_value(delay);
delay_entry.set_numeric(true);
delay_entry.set_activates_default(true);
Gtk.HBox query = new Gtk.HBox(false, 0);
query.pack_start(delay_label, false, false, 3);
query.pack_start(hscale, true, true, 3);
query.pack_start(delay_entry, false, false, 3);
query.pack_start(units_label, false, false, 3);
transition_effect_selector = new Gtk.ComboBox.text();
Gtk.Label transition_effect_selector_label = new Gtk.Label.with_mnemonic(
_("_Transition effect:"));
transition_effect_selector_label.xalign = (float) 1.0;
transition_effect_selector_label.set_mnemonic_widget(transition_effect_selector);
// get last effect name
string effect_name = Config.get_instance().get_slideshow_transition_effect();
// null effect first, always, and set active in case no other one is found
string null_display_name = TransitionEffectsManager.get_instance().get_display_name(
TransitionEffectsManager.NULL_TRANSITION_NAME);
transition_effect_selector.append_text(null_display_name);
transition_effect_selector.set_active(0);
int i = 1;
foreach (string display_name in
TransitionEffectsManager.get_instance().get_display_names(utf8_ci_compare)) {
if (display_name == null_display_name)
continue;
transition_effect_selector.append_text(display_name);
if (effect_name == TransitionEffectsManager.get_instance().get_name_for_display_name(display_name))
transition_effect_selector.set_active(i);
++i;
}
transition_effect_selector.changed.connect(on_transition_changed);
Gtk.Label transition_delay_label = new Gtk.Label.with_mnemonic(_("Transition d_elay:"));
transition_delay_label.xalign = (float) 1.0;
double transition_delay = Config.get_instance().get_slideshow_transition_delay();
transition_effect_adjustment = new Gtk.Adjustment(transition_delay,
Config.SLIDESHOW_TRANSITION_DELAY_MIN, Config.SLIDESHOW_TRANSITION_DELAY_MAX,
0.1, 1, 0);
transition_effect_hscale = new Gtk.HScale(transition_effect_adjustment);
transition_effect_hscale.set_draw_value(false);
transition_effect_hscale.set_size_request(150, -1);
transition_effect_entry = new Gtk.SpinButton(transition_effect_adjustment, 0.1, 1);
transition_effect_entry.set_value(transition_delay);
transition_effect_entry.set_numeric(true);
transition_effect_entry.set_activates_default(true);
transition_delay_label.set_mnemonic_widget(transition_effect_hscale);
set_default_response(Gtk.ResponseType.OK);
vbox.pack_start(query, true, false, 6);
Gtk.Table tbl = new Gtk.Table(3, 4, false);
tbl.attach(delay_label, 0, 1, 0, 1,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(hscale, 1, 2, 0, 1,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(delay_entry, 2, 3, 0, 1,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(units_label, 3, 4, 0, 1,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(transition_effect_selector_label, 0, 1, 1, 2,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 6);
tbl.attach(transition_effect_selector, 1, 2, 1, 2,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 6);
tbl.attach(transition_delay_label, 0, 1, 2, 3,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(transition_effect_hscale, 1, 2, 2, 3,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(transition_effect_entry, 2, 3, 2, 3,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
tbl.attach(units_label1, 3, 4, 2, 3,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
3, 0);
vbox.pack_start(tbl, true, false, 6);
on_transition_changed();
}
private void on_transition_changed() {
string selected = transition_effect_selector.get_active_text();
bool sensitive = selected != null
&& selected != TransitionEffectsManager.NULL_TRANSITION_NAME;
transition_effect_hscale.sensitive = sensitive;
transition_effect_entry.sensitive = sensitive;
}
public double get_delay() {
return delay_entry.get_value();
}
public double get_transition_delay() {
return transition_effect_entry.get_value();
}
public string get_transition_effect() {
string? active = transition_effect_selector.get_active_text();
if (active == null)
return TransitionEffectsManager.NULL_TRANSITION_NAME;
string? name = TransitionEffectsManager.get_instance().get_name_for_display_name(active);
return (name != null) ? name : TransitionEffectsManager.NULL_TRANSITION_NAME;
}
}
public SlideshowPage(SourceCollection sources, ViewCollection controller, Photo start) {
......@@ -77,6 +190,8 @@ class SlideshowPage : SinglePhotoPage {
this.controller = controller;
current = start;
update_transition_effect();
// Set up toolbar
Gtk.Toolbar toolbar = get_toolbar();
......@@ -122,7 +237,7 @@ class SlideshowPage : SinglePhotoPage {
Gdk.Pixbuf pixbuf;
if (get_next_photo(current, Direction.FORWARD, out current, out pixbuf))
set_pixbuf(pixbuf, current.get_dimensions());
set_pixbuf(pixbuf, current.get_dimensions(), true, Direction.FORWARD);
// start the auto-advance timer
Timeout.add(CHECK_ADVANCE_MSEC, auto_advance);
......@@ -138,7 +253,7 @@ class SlideshowPage : SinglePhotoPage {
exiting = true;
}
private bool get_next_photo(Photo start, Direction direction, out Photo next,
private bool get_next_photo(Photo start, Direction direction, out Photo next,
out Gdk.Pixbuf next_pixbuf) {
next = start;
......@@ -208,7 +323,7 @@ class SlideshowPage : SinglePhotoPage {
DataView view = controller.get_view_for_source(current);
Photo? prev_photo = null;
DataView? start_view = controller.get_previous(view);
DataView? start_view = controller.get_previous(view);
DataView? prev_view = start_view;
while (prev_view != null) {
......@@ -258,7 +373,7 @@ class SlideshowPage : SinglePhotoPage {
// set pixbuf
Gdk.Pixbuf next_pixbuf;
if (get_next_photo(current, direction, out current, out next_pixbuf))
set_pixbuf(next_pixbuf, current.get_dimensions());
set_pixbuf(next_pixbuf, current.get_dimensions(), true, direction);
// reset the advance timer
timer.start();
......@@ -318,11 +433,27 @@ class SlideshowPage : SinglePhotoPage {
if (response == Gtk.ResponseType.OK) {
// sync with the config setting so it will persist
Config.get_instance().set_slideshow_delay(settings_dialog.get_delay());
Config.get_instance().set_slideshow_transition_delay(settings_dialog.get_transition_delay());
Config.get_instance().set_slideshow_transition_effect(settings_dialog.get_transition_effect());
update_transition_effect();
}
settings_dialog.destroy();
playing = slideshow_playing;
timer.start();
}
private void update_transition_effect() {
string effect_name = Config.get_instance().get_slideshow_transition_effect();
double effect_delay = Config.get_instance().get_slideshow_transition_delay();
TransitionEffect transition_effect = TransitionEffectsManager.get_instance().create_effect(
effect_name, simple_repaint, canvas.style.black);
transition_effect.duration = (int) (effect_delay * 1000.0);
set_transition_effect(transition_effect);
}
}
......@@ -92,6 +92,9 @@ private void search_for_plugins(File dir) throws Error {
if (info == null)
break;
if (info.get_is_hidden())
continue;
File file = dir.get_child(info.get_name());
switch (info.get_file_type()) {
......
......@@ -18,7 +18,8 @@ UNIT_FILES := \
# be listed here using its Vala namespace.
#
# NOTE: All units are assumed to rely upon the unit-unit. Do not include that here.
UNIT_USES :=
UNIT_USES := \
Util
# List of plugin interfaces. These form the plugins VAPI.
PLUGIN_INTERFACES := \
......
/* Copyright 2011 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
namespace Slideshow {
public void init() throws Error {
}
public void terminate() {
}
}
/* Copyright 2010 Maxim Kartashev
*
* This software is licensed under the GNU LGPL (version 2.1 or later).
* See the COPYING file in this distribution.
*/
public class TransitionEffectsManager {
public const string NULL_TRANSITION_NAME = TransitionEffectsImpl.NullTransitionEffectDescriptor.NAME;
private static TransitionEffectsManager? instance = null;
// effects are stored by name
private Gee.Map<string, TransitionEffectDescriptor> effects = new Gee.HashMap<
string, TransitionEffectDescriptor>();
private TransitionEffectsManager() {
add_effect(new TransitionEffectsImpl.NullTransitionEffectDescriptor());
add_effect(new TransitionEffectsImpl.ShiftTransitionEffectDescriptor());
add_effect(new TransitionEffectsImpl.FadeTransitionEffectDescriptor());
add_effect(new TransitionEffectsImpl.TearTransitionEffectDescriptor());
}
public static TransitionEffectsManager get_instance() {
if (instance == null)
instance = new TransitionEffectsManager();
return instance;
}
public TransitionEffect get_null_instance() {
return new TransitionEffectsImpl.NullTransitionEffect.empty();
}
private void add_effect(TransitionEffectDescriptor desc) {
effects.set(desc.get_name(), desc);
}
public Gee.Collection<string> get_names() {
return effects.keys;
}
public Gee.Collection<string> get_display_names(CompareFunc? comparator = null) {
Gee.Collection<string> display_names = new Gee.TreeSet<string>(comparator);
foreach (TransitionEffectDescriptor desc in effects.values)
display_names.add(desc.get_display_name());
return display_names;
}
public string? get_name_for_display_name(string display_name) {
foreach (TransitionEffectDescriptor desc in effects.values) {
if (desc.get_display_name() == display_name)
return desc.get_name();
}
return null;
}
public string get_display_name(string name) {
TransitionEffectDescriptor? desc = effects.get(name);
return (desc != null) ? desc.get_display_name() : _("(no name)");
}
public TransitionEffect? create_effect(string name, TransitionEffect.RepaintCallback repaint_callback,
Gdk.Color bg_color) {
TransitionEffectDescriptor? desc = effects.get(name);
return (desc != null) ? desc.create(repaint_callback, bg_color) : null;
}
}
public abstract class TransitionState {
private Cancellable cancellable = new Cancellable();
// Returns true if transition still has frames to draw
public bool is_in_progress() {
return !is_cancelled() && check_in_progress();
}
// Override this in order to inform that your child transition
// is still in progress.
protected abstract bool check_in_progress();
// Go to next state of transition between photos
public abstract void next();
public bool is_cancelled() {
return cancellable.is_cancelled();
}
public void cancel() {
cancellable.cancel();
}
}
public abstract class TransitionEffect {
public delegate void RepaintCallback();
// Describes current state of transition
public TransitionState state { get; protected set; default = null; }
// Desired duration of transition in milliseconds
public int duration { get; set; default = 300; }
// Desired number of frames-per-second for this transition effects
public int fps { get; set; default = 30; }
// Hard minimum for frames-per-second; 0 if don't care
// Transition will get cancelled if current system can't provide this
// amount of FPS
public int min_fps { get; protected set; default = 0; }
public bool enabled { get; set; default = true; }
// Color of background when drawing transition
protected Gdk.Color bg_color;
// Current transition data:
// "from" image and "to" image and their respective locations
protected Gdk.Pixbuf from_pixbuf = null;
protected Gdk.Rectangle from_pos;
protected Gdk.Pixbuf to_pixbuf = null;
protected Gdk.Rectangle to_pos;
// Direction of current transition
protected Direction direction;
// Bookkeeping data: time current transition has started and ...
protected ulong time_started;
// ... how many frames has been drawn so far
protected int frames_drawn;
private RepaintCallback repaint_callback;
// ID of the timer that is used to initiate repaint
protected uint timer_id;
public TransitionEffect(RepaintCallback repaint_callback, Gdk.Color bg_color) {
this.repaint_callback = repaint_callback;
this.bg_color = bg_color;
state = get_initial_state();
}
// Initiate transition from from_pixbuf to to_pixbuf by periodically
// calling repaint_callback() (see above) and this.paint()
public virtual void start(Gdk.Pixbuf? from_pixbuf, Gdk.Rectangle from_pos,
Gdk.Pixbuf? to_pixbuf, Gdk.Rectangle to_pos, Direction direction) {
assert(enabled);
assert(fps > 0 && fps >= min_fps);
assert(duration > 0);
// Both pixbufs cannot be null
assert(from_pixbuf != null || to_pixbuf != null);
assert(!state.is_in_progress());
assert(timer_id == 0);
this.from_pixbuf = from_pixbuf;
this.from_pos = from_pos;
this.to_pixbuf = to_pixbuf;
this.to_pos = to_pos;
this.direction = direction;
state = get_initial_state();
time_started = now_ms();
frames_drawn = 0;
image_transition_tick(); // initiate first repaint now
timer_id = Timeout.add((uint) (1000.0 / (double) fps), image_transition_tick);
}
// Calculate current FPS rate and returns true if it's above minimum
protected bool is_fps_ok() {
assert(time_started > 0);
if (frames_drawn <= 2)
return true; // don't bother measuring if statistical data are too small
int elapsed = (int) (now_ms() - time_started);
int cur_fps = (int) (frames_drawn * 1000.0 / elapsed);
if (cur_fps < min_fps)
debug("Transition rate of %dfps below minimum of %dfps", cur_fps, min_fps);
return (cur_fps >= min_fps);
}
// Cancels current transition.
public void cancel() {
state.cancel();
if (timer_id != 0) {
Source.remove(timer_id);
timer_id = 0;
}
repaint_callback(); // repaint without transition this time
}
public abstract TransitionState get_initial_state();
public virtual void paint(Gdk.Drawable drawable) {
assert(state.is_in_progress());
frames_drawn++;
if (is_fps_ok()) {
child_paint(drawable);
} else {
debug("TransitionEffect: Cancelling: below minimum fps");
cancel();
enabled = false;
}
}
public abstract void child_paint(Gdk.Drawable drawable);
private bool image_transition_tick() {
if (!state.is_in_progress()) {
// cancels timer
timer_id = 0;
return false;
}
repaint_callback();
state.next();
if (!state.is_in_progress()) {
// cancels timer
timer_id = 0;
return false;
}
return true;
}