Merge lp:~mterry/unity-greeter/scrolling-update into lp:unity-greeter

Proposed by Michael Terry
Status: Merged
Merged at revision: 241
Proposed branch: lp:~mterry/unity-greeter/scrolling-update
Merge into: lp:unity-greeter
Diff against target: 621 lines (+249/-139)
3 files modified
src/Makefile.am (+1/-0)
src/animate-timer.vala (+112/-0)
src/user-list.vala (+136/-139)
To merge this branch: bzr merge lp:~mterry/unity-greeter/scrolling-update
Reviewer Review Type Date Requested Status
Unity Greeter Development Team Pending
Review via email: mp+86288@code.launchpad.net

Description of the change

This branch updates the scrolling animation a bit to more closely match the video in bug 844050.

Specifically, the following bits are done when switching users:
1) The current password box disappears (no animation)
2) The current username and gear scroll up or down and off the highlight box
3) The next username and gear scroll up or down into the highlight box to rest where they should
4) the new password box appears (no animation)

There are bits in the bug that aren't done in this branch. But this was a good checkpoint, so I thought no harm in having this reviewed/merged.

Specifically, the things that aren't done:
5) When first appearing, the greeter scrolls from the top of the list to the default user
6) Final speed and tweening adjustments

There are some slight rough edges because (6) isn't done. Notably, if your are scrolling past multiple users (like you clicked at the top of the list, it looks a little odd because each name that scrolls by changes speed as it passes through the box). This isn't worse than the current code really, it just isn't perfect yet.

Notable things that this branch does:
 * Make more sections of code aware of when we are scrolling
 * Consolidate some entry drawing code and have smaller code branches for in-box drawing
 * Slowed speed a tad from 5.0 to 4.0. 5.0 made things move too fast on the screen I thought.

To post a comment you must log in.
241. By Michael Terry

make animation match Unity speed/easing

242. By Michael Terry

fix race condition when starting and showing old background

243. By Michael Terry

scroll to first user after we are realized

244. By Michael Terry

remove some unused code

Revision history for this message
Michael Terry (mterry) wrote :

Just pushed an update that fixes (5) and (6).

245. By Michael Terry

merge from trunk

246. By Michael Terry

start scrolling before we try to show the prompt so that the entry box hides during scrolling again

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Makefile.am'
2--- src/Makefile.am 2012-01-10 09:31:10 +0000
3+++ src/Makefile.am 2012-01-11 13:58:24 +0000
4@@ -6,6 +6,7 @@
5 config.vapi \
6 fixes.vapi \
7 indicator.vapi \
8+ animate-timer.vala \
9 settings-daemon.vala \
10 unity-greeter.vala \
11 user-list.vala
12
13=== added file 'src/animate-timer.vala'
14--- src/animate-timer.vala 1970-01-01 00:00:00 +0000
15+++ src/animate-timer.vala 2012-01-11 13:58:24 +0000
16@@ -0,0 +1,112 @@
17+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
18+ *
19+ * Copyright (C) 2011,2012 Canonical Ltd
20+ *
21+ * This program is free software: you can redistribute it and/or modify
22+ * it under the terms of the GNU General Public License version 3 as
23+ * published by the Free Software Foundation.
24+ *
25+ * This program is distributed in the hope that it will be useful,
26+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
27+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28+ * GNU General Public License for more details.
29+ *
30+ * You should have received a copy of the GNU General Public License
31+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
32+ *
33+ * Authors: Robert Ancell <robert.ancell@canonical.com>
34+ * Michael Terry <michael.terry@canonical.com>
35+ */
36+
37+private class AnimateTimer : Object
38+{
39+ public enum Speed
40+ {
41+ INSTANT, /* Good for animations that don't convey any information */
42+ FAST, /* Good for animations that convey duplicated information (e.g. scrolling) */
43+ NORMAL,
44+ SLOW, /* Good for animations that convey information that is only presented in the animation */
45+ }
46+
47+ public Speed speed {get; construct;}
48+ public bool is_running { get { return timeout != 0; } }
49+
50+ /* progress is from 0.0 to 1.0 */
51+ public signal void animate (double progress);
52+
53+ public AnimateTimer (Speed speed)
54+ {
55+ Object (speed: speed);
56+ }
57+
58+ public void reset ()
59+ {
60+ stop ();
61+ timeout = Timeout.add (16, animate_cb);
62+
63+ start_time = GLib.get_monotonic_time ();
64+
65+ /* The following are the same intervals that Unity uses */
66+ switch (speed)
67+ {
68+ default:
69+ case Speed.INSTANT:
70+ length = 150 * TimeSpan.MILLISECOND;
71+ break;
72+ case Speed.FAST:
73+ length = 250 * TimeSpan.MILLISECOND;
74+ break;
75+ case Speed.NORMAL:
76+ length = 500 * TimeSpan.MILLISECOND;
77+ break;
78+ case Speed.SLOW:
79+ length = 1000 * TimeSpan.MILLISECOND;
80+ break;
81+ }
82+ }
83+
84+ public void stop ()
85+ {
86+ if (timeout != 0)
87+ Source.remove (timeout);
88+ timeout = 0;
89+ }
90+
91+ private uint timeout = 0;
92+ private TimeSpan start_time = 0;
93+ private TimeSpan length = 0;
94+
95+ private bool animate_cb ()
96+ {
97+ var time = normalize_time ();
98+ var progress = normalize_progress (time);
99+
100+ animate (progress);
101+
102+ if (progress >= 1.0)
103+ {
104+ timeout = 0;
105+ return false;
106+ }
107+ else
108+ return true;
109+ }
110+
111+ /* Returns 0.0 to 1.0 where 1.0 is at or past end_time */
112+ private double normalize_time ()
113+ {
114+ var now = GLib.get_monotonic_time ();
115+ return (((double)(now - start_time)) / length).clamp (0.0, 1.0);
116+ }
117+
118+ /* Returns 0.0 to 1.0 where 1.0 is done.
119+ time is normalized time from 0.0 to 1.0. */
120+ private double normalize_progress (double time)
121+ {
122+ /* Use a sine wave function similar to what Unity uses. They call it
123+ 'easing' and is designed to make the animation start and end slower
124+ than in the middle. */
125+ return ((-1 * Math.cos (Math.PI * time) + 1) / 2).clamp (0.0, 1.0);
126+ }
127+}
128+
129
130=== modified file 'src/user-list.vala'
131--- src/user-list.vala 2012-01-10 09:31:10 +0000
132+++ src/user-list.vala 2012-01-11 13:58:24 +0000
133@@ -25,43 +25,6 @@
134 return (int) (size % grid_size) / 2;
135 }
136
137-private class AnimateTimer
138-{
139- private Timer timer;
140- private uint timeout = 0;
141- private double last_timestep;
142-
143- public signal void animate (double timestep);
144-
145- private bool animate_cb ()
146- {
147- var t = timer.elapsed ();
148- var timestep = t - last_timestep;
149- last_timestep = t;
150-
151- animate (timestep);
152-
153- return true;
154- }
155-
156- public bool is_running { get { return timeout != 0; } }
157-
158- public void reset ()
159- {
160- stop ();
161- last_timestep = 0.0;
162- timer = new Timer ();
163- timeout = Timeout.add (10, animate_cb);
164- }
165-
166- public void stop ()
167- {
168- if (timeout != 0)
169- Source.remove (timeout);
170- timeout = 0;
171- }
172-}
173-
174 private class UserEntry
175 {
176 /* Unique name for this entry */
177@@ -298,6 +261,7 @@
178 private UserEntry? selected_entry = null;
179
180 private double scroll_target_location;
181+ private double scroll_start_location;
182 private double scroll_location;
183 private double scroll_direction;
184
185@@ -324,8 +288,10 @@
186
187 private Gtk.Entry prompt_entry;
188 private Gtk.Button login_button;
189+ private Gtk.Widget prompt_widget_to_show;
190 private Gtk.Button options_button;
191 private Gtk.Menu options_menu;
192+ private Gdk.Pixbuf options_pixbuf;
193 unowned GLib.SList<SessionMenuItem> session_group = null;
194
195 private bool complete = false;
196@@ -432,14 +398,22 @@
197 login_button.clicked.connect (login_button_clicked_cb);
198 add (login_button);
199
200+ try {
201+ options_pixbuf = new Gdk.Pixbuf.from_file (Path.build_filename (Config.PKGDATADIR, "cog.png", null));
202+ }
203+ catch (Error e) {
204+ debug ("Error loading cog image: %s", e.message);
205+ }
206+
207 options_button = new Gtk.Button ();
208 options_button.focus_on_click = false;
209 options_button.get_accessible ().set_name (_("Session Options"));
210- var image = new Gtk.Image.from_file (Path.build_filename (Config.PKGDATADIR, "cog.png", null));
211+ var image = new Gtk.Image.from_pixbuf (options_pixbuf);
212 image.show ();
213 options_button.relief = Gtk.ReliefStyle.NONE;
214 options_button.add (image);
215 options_button.clicked.connect (options_button_clicked_cb);
216+ options_button.show ();
217 add (options_button);
218
219 options_menu = new Gtk.Menu ();
220@@ -449,9 +423,9 @@
221
222 backgrounds = new HashTable<string?, Background> (str_hash, str_equal);
223
224- scroll_timer = new AnimateTimer ();
225+ scroll_timer = new AnimateTimer (AnimateTimer.Speed.FAST);
226 scroll_timer.animate.connect (scroll_animate_cb);
227- background_timer = new AnimateTimer ();
228+ background_timer = new AnimateTimer (AnimateTimer.Speed.INSTANT);
229 background_timer.animate.connect (background_animate_cb);
230
231 setup_indicators ();
232@@ -662,11 +636,15 @@
233 public void show_prompt (string text, bool secret = false)
234 {
235 login_button.hide ();
236+ prompt_entry.hide ();
237 message = text;
238 prompt_entry.text = "";
239 prompt_entry.sensitive = true;
240- prompt_entry.show ();
241 prompt_entry.visibility = !secret;
242+ if (scroll_timer.is_running)
243+ prompt_widget_to_show = prompt_entry;
244+ else
245+ prompt_entry.show ();
246 var accessible = prompt_entry.get_accessible ();
247 if (selected_entry.name != null)
248 accessible.set_name (_("Enter password for %s").printf (selected_entry.layout.get_text ()));
249@@ -684,8 +662,12 @@
250 public void show_authenticated ()
251 {
252 prompt_entry.hide ();
253+ login_button.hide ();
254 message = "";
255- login_button.show ();
256+ if (scroll_timer.is_running)
257+ prompt_widget_to_show = login_button;
258+ else
259+ login_button.show();
260 var accessible = login_button.get_accessible ();
261 accessible.set_name (_("Login as %s").printf (selected_entry.layout.get_text ()));
262 login_button.grab_focus ();
263@@ -879,61 +861,43 @@
264 options_menu.popup (null, null, options_menu_position_cb, 0, Gtk.get_current_event_time ());
265 }
266
267- private void scroll_animate_cb (double timestep)
268+ private void scroll_animate_cb (double progress)
269 {
270- if (scroll_location != scroll_target_location)
271- {
272- var speed = 5.0;
273-
274- var delta = timestep * speed;
275-
276- /* Total height of list */
277- var h = (double) entries.length ();
278-
279- var distance = scroll_target_location - scroll_location;
280- if (scroll_direction < 0.0)
281- distance = -distance;
282- if (distance < 0)
283- distance += h;
284-
285- /* If close enough finish moving */
286- if (distance <= delta)
287- scroll_location = scroll_target_location;
288- else
289- {
290- scroll_location += delta * scroll_direction;
291-
292- /* Wrap around */
293- if (scroll_location > h)
294- scroll_location -= h;
295- if (scroll_location < 0)
296- scroll_location += h;
297- }
298-
299- redraw_user_list ();
300- }
301-
302- /* Stop when we get there */
303- if (scroll_location == scroll_target_location)
304- scroll_timer.stop ();
305+ /* Total height of list */
306+ var h = entries.length ();
307+
308+ /* How far we have to go in total, either up or down with wrapping */
309+ var distance = scroll_target_location - scroll_start_location;
310+ if (scroll_direction * distance < 0)
311+ distance += scroll_direction * h;
312+
313+ /* How far we've gone so far */
314+ distance *= progress;
315+
316+ /* Go that far and wrap around */
317+ scroll_location = scroll_start_location + distance;
318+ if (scroll_location > h)
319+ scroll_location -= h;
320+ if (scroll_location < 0)
321+ scroll_location += h;
322+
323+ /* And finally, redraw */
324+ redraw_user_list ();
325+
326+ /* Stop when we get there */
327+ if (progress >= 1.0)
328+ finished_animating ();
329 }
330
331- private void background_animate_cb (double timestep)
332+ private void background_animate_cb (double progress)
333 {
334- var speed = 5.0;
335-
336- background_alpha += timestep * speed;
337- if (background_alpha > 1.0)
338- background_alpha = 1.0;
339+ background_alpha = progress;
340
341 redraw_background ();
342
343 /* Stop when we get there */
344- if (background_alpha == 1.0)
345- {
346+ if (background_alpha >= 1.0)
347 old_background = background;
348- background_timer.stop ();
349- }
350 }
351
352 private bool change_background_timeout_cb ()
353@@ -970,14 +934,33 @@
354 change_background_timeout_cb ();
355 }
356
357+ private void finished_animating ()
358+ {
359+ if (prompt_widget_to_show != null) {
360+ prompt_widget_to_show.show ();
361+ prompt_widget_to_show = null;
362+ }
363+ options_button.show ();
364+ }
365+
366 private void select_entry (UserEntry entry, double direction)
367 {
368+ if (get_realized ())
369+ {
370+ scroll_target_location = entries.index (entry);
371+ scroll_start_location = scroll_location;
372+ scroll_direction = direction;
373+ if (scroll_location != scroll_target_location)
374+ scroll_timer.reset ();
375+ }
376+
377 if (selected_entry != entry)
378 {
379 selected_entry = entry;
380
381 prompt_entry.hide ();
382 login_button.hide ();
383+ options_button.hide ();
384
385 if (get_realized ())
386 {
387@@ -985,15 +968,6 @@
388 user_selected (selected_entry.name);
389 }
390 }
391-
392- scroll_target_location = entries.index (selected_entry);
393- scroll_direction = direction;
394- /* Move straight there if not drawn yet */
395- if (frame_count == 0)
396- scroll_location = scroll_target_location;
397-
398- if (scroll_location != scroll_target_location && !scroll_timer.is_running)
399- scroll_timer.reset ();
400 }
401
402 public override void add (Gtk.Widget widget)
403@@ -1023,6 +997,8 @@
404
405 set_realized (true);
406
407+ select_entry (selected_entry, 1);
408+
409 Gtk.Allocation allocation;
410 get_allocation (out allocation);
411
412@@ -1093,7 +1069,6 @@
413 child_allocation.width = grid_size;
414 child_allocation.height = grid_size;
415 options_button.size_allocate (child_allocation);
416- options_button.show ();
417
418 /* Regenerate backgrounds */
419 if (resized)
420@@ -1104,11 +1079,11 @@
421 }
422 }
423
424- private void draw_entry (Cairo.Context c, UserEntry entry, double alpha = 0.5)
425+ private void draw_entry (Cairo.Context c, UserEntry entry, double alpha = 0.5, bool in_box = false)
426 {
427 c.save ();
428
429- if (high_contrast_item.active)
430+ if (high_contrast_item.active || in_box)
431 alpha = 1.0;
432
433 if (entry.is_active)
434@@ -1124,11 +1099,17 @@
435 int w, h;
436 entry.layout.get_pixel_size (out w, out h);
437
438- var bw = (box_width - 0.5) * grid_size;
439+ var bw = (box_width - (in_box ? 1.1 : 0.5)) * grid_size;
440 if (w > bw)
441 {
442 var mask = new Cairo.Pattern.linear (0, 0, bw, 0);
443- mask.add_color_stop_rgba (1.0 - 64.0 / bw, 1.0, 1.0, 1.0, alpha);
444+ if (in_box)
445+ {
446+ mask.add_color_stop_rgba (1.0 - 27.0 / bw, 1.0, 1.0, 1.0, 1.0);
447+ mask.add_color_stop_rgba (1.0 - 21.6 / bw, 1.0, 1.0, 1.0, 0.5);
448+ }
449+ else
450+ mask.add_color_stop_rgba (1.0 - 64.0 / bw, 1.0, 1.0, 1.0, alpha);
451 mask.add_color_stop_rgba (1.0, 1.0, 1.0, 1.0, 0.0);
452 c.set_source (mask);
453 }
454@@ -1140,6 +1121,17 @@
455 Pango.cairo_show_layout (c, entry.layout);
456
457 c.restore ();
458+
459+ /* Now draw options button if we're animating in the box */
460+ if (in_box && scroll_timer.is_running && options_pixbuf != null) {
461+ c.save ();
462+ var xpadding = (grid_size - options_pixbuf.width) / 2;
463+ var ypadding = (grid_size - options_pixbuf.height) / 2;
464+ c.translate (box_width * grid_size - grid_size - grid_size / 4 + xpadding, grid_size / 4 - ypadding);
465+ Gdk.cairo_set_source_pixbuf (c, options_pixbuf, 0, 0);
466+ c.paint ();
467+ c.restore ();
468+ }
469 }
470
471 private void background_loaded_cb (Background b)
472@@ -1188,12 +1180,15 @@
473 background_timer.reset ();
474
475 c.set_source_rgb (0x2C, 0x00, 0x1E);
476+ var old_not_loaded = false;
477
478 /* Draw old background */
479 if (background_alpha < 1.0)
480 {
481 if (old_background.load ())
482 c.set_source (old_background.pattern);
483+ else
484+ old_not_loaded = true;
485 c.paint ();
486 }
487
488@@ -1201,11 +1196,12 @@
489 if (background.load () && background_alpha > 0.0)
490 {
491 c.set_source (background.pattern);
492- c.paint_with_alpha (background_alpha);
493+ c.paint_with_alpha (old_not_loaded ? 1.0 : background_alpha);
494 }
495 }
496
497- private void draw_entry_at_position (Cairo.Context c, UserEntry entry, double position)
498+ private void draw_entry_at_position (Cairo.Context c, UserEntry entry,
499+ double position, bool in_box = false)
500 {
501 c.save ();
502 c.translate (0, position * grid_size);
503@@ -1214,7 +1210,7 @@
504 alpha = 1.0 + position / (n_above + 1);
505 else
506 alpha = 1.0 - (position - 2) / (n_below + 1);
507- draw_entry (c, entry, alpha);
508+ draw_entry (c, entry, alpha, in_box);
509 c.restore ();
510 }
511
512@@ -1231,26 +1227,45 @@
513
514 c.translate (box_x, box_y);
515
516+ var border = 4;
517+
518 var index = 0;
519 foreach (var entry in entries)
520 {
521+ var position = index - scroll_location;
522+
523 /* Draw entries above the box */
524 var h_above = (double) (n_above + 1) * grid_size;
525 c.save ();
526
527 c.rectangle (0, -h_above, box_width * grid_size, h_above);
528 c.clip ();
529- draw_entry_at_position (c, entry, index - scroll_location);
530+ draw_entry_at_position (c, entry, position);
531
532 c.restore ();
533
534+ /* Draw entries in the box */
535+ if (position > -1 && position < 1 && scroll_timer.is_running) {
536+ c.save ();
537+ c.translate (0, border);
538+ c.rectangle (0, border * 2, box_width * grid_size, box_height * grid_size - border * 5);
539+ c.clip ();
540+
541+ if (position <= 0) /* top of box, normal pace */
542+ draw_entry_at_position (c, entry, position, true);
543+ else /* bottom of box; pace must put across bottom halfway through animation */
544+ draw_entry_at_position (c, entry, position * box_height * 2, true);
545+
546+ c.restore ();
547+ }
548+
549 /* Draw entries below the box */
550 var h_below = (double) (n_below + 1) * grid_size;
551 c.save ();
552
553 c.rectangle (0, box_height * grid_size, box_width * grid_size, h_below);
554 c.clip ();
555- draw_entry_at_position (c, entry, index - scroll_location + 2);
556+ draw_entry_at_position (c, entry, position + box_height - 1);
557
558 c.restore ();
559
560@@ -1259,47 +1274,29 @@
561
562 /* Draw box */
563 c.save ();
564- var border = 4;
565 c.translate (-border, -border);
566 c.set_source (box_pattern);
567 c.paint ();
568 c.restore ();
569
570 /* Selected item */
571- if (selected_entry != null)
572+ if (selected_entry != null && !scroll_timer.is_running)
573 {
574- int w, h;
575- selected_entry.layout.get_pixel_size (out w, out h);
576- var text_y = grid_size - (grid_size - border - h) / 4 - h;
577-
578- if (selected_entry.is_active)
579- {
580- c.move_to (8, text_y + h / 2 + 0.5 - 4);
581- c.rel_line_to (5, 4);
582- c.rel_line_to (-5, 4);
583- c.close_path ();
584- c.set_source_rgb (1.0, 1.0, 1.0);
585- c.fill ();
586- }
587-
588- c.move_to (grid_size / 2, text_y);
589-
590- var bw = (box_width - 1.1) * grid_size;
591- if (w > bw)
592- {
593- var mask = new Cairo.Pattern.linear (0, 0, bw, 0);
594- mask.add_color_stop_rgba (1.0 - 27.0 / bw, 1.0, 1.0, 1.0, 1.0);
595- mask.add_color_stop_rgba (1.0 - 21.6 / bw, 1.0, 1.0, 1.0, 0.5);
596- mask.add_color_stop_rgba (1.0, 1.0, 1.0, 1.0, 0.0);
597- c.set_source (mask);
598- }
599- else
600- c.set_source_rgba (1.0, 1.0, 1.0, 1.0);
601-
602- Pango.cairo_show_layout (c, selected_entry.layout);
603+ var text_y = border;
604+
605+ c.save ();
606+ c.rectangle (border, border, box_width * grid_size - border * 2, box_height * grid_size - border * 2);
607+ c.clip ();
608+
609+ c.save ();
610+ c.translate (0, text_y);
611+ draw_entry (c, selected_entry, 1.0);
612+ c.restore ();
613+
614+ c.restore();
615 }
616
617- if (error != null || message != null)
618+ if ((error != null || message != null) && !scroll_timer.is_running)
619 {
620 string text;
621 if (error == null)

Subscribers

People subscribed via source and target branches