Pebble: How to autoscroll large text

Last time i described how to load random string from resource. If you recall the code ended up with the line

text_layer_set_text(s_textlayer_quote, (char *)quote);

to display loaded text on the screen. But what if text is too large to fit on the screen? If you’re building a watchface, there’s no scrolllayer with user interaction available. But what we can do is automatically scroll the text for user convenience to gradually reveal entire content.

The trick is to create text layer larger than it’s container. In my case I am displaying text full screen on Pebble window which is 144×168 pixels, but I will create text layer with the height of 2000:

#define WINDOW_HEIGHT 168
#define WINDOW_WIDTH 144  
#define TEXTBOX_HEIGHT 2000
...
s_textlayer_quote = text_layer_create(GRect(0, 0, WINDOW_WIDTH, TEXTBOX_HEIGHT));

If the loaded text is larger then container window we now can dynamically detect the difference:

text_layer_set_text(s_textlayer_quote, (char *)quote);
  
// if height of quote > height of window, initiate animation to scroll
GSize text_size = text_layer_get_content_size(s_textlayer_quote);
int number_of_pixels = WINDOW_HEIGHT - text_size.h;
if (number_of_pixels < 0) {
    animate_quote(number_of_pixels);
}

Here we get dynamic size of loaded text content and if the difference between window height and actual text height is negative – call animation function, passing number of pixel we need to shift by.

Now to animation function. The easiest way to animate something on Pebble is PropertyAnimation. You set up start coordinates, end coordinates, a few properties and kick it off to move pretty much anything around the screen. This is how it’s achieved in “Blue Sun Quotes”:

PropertyAnimation *s_box_animation;

void animate_quote(int pixels_to_scroll_by) {
  GRect start_frame = GRect(0, (pixels_to_scroll_by < 0? 0 : -pixels_to_scroll_by), WINDOW_WIDTH, TEXTBOX_HEIGH);
  GRect finish_frame =  GRect(0, (pixels_to_scroll_by < 0? pixels_to_scroll_by : 0), WINDOW_WIDTH, TEXTBOX_HEIGH);
  
  s_box_animation = property_animation_create_layer_frame(text_layer_get_layer(s_textlayer_quote), &start_frame, &finish_frame);
  animation_set_handlers((Animation*)s_box_animation, (AnimationHandlers) {
    .stopped = anim_stopped_handler
  }, NULL);
  animation_set_duration((Animation*)s_box_animation, abs(pixels_to_scroll_by) * 35); // delay is proportional to text size
  animation_set_curve((Animation*)s_box_animation, AnimationCurveLinear);  // setting equal speed animation
  animation_set_delay((Animation*)s_box_animation, 3000); //initial delay of 3 seconds to let user start reading quote

  animation_schedule((Animation*)s_box_animation);
}

Once we call animate_quote function, Lines 4-5 create start and end frame for our large textlayer location (depending on direction we either move from Y=0 location to Y= -size difference location or vice versa). Lines 7-10 create property animation and set callback function that is called once animation stopped. Line 11 sets total duration time for animation, as u can see it is proportional to the size of the text that needed scrolling (I found that coefficient x35 creates smooth slow text moving, enough to read the text comfortably). Line 12 sets Animation Curve, meaning how fast animation will go at the beginning, in the middle and the end. AnimationCurveLinear meaning the speed will remain the same throughout the animation. And finally Lines 13-15 set initial delay for the animation (3 seconds so user can begin reading) and kick off the animation.

Once the animation stopped we need to reverse it. And this is done in the callback function:

void anim_stopped_handler(Animation *animation, bool finished, void *context) {
  // Free the animation
  property_animation_destroy(s_box_animation);

  // Schedule the reverse animation, unless the app is exiting
  if (finished) {
    number_of_pixels = -number_of_pixels;
    animate_quote(number_of_pixels);
  }
}

Line 3 destroys existing animation, Line 6-9 check if animation indeed finished and not stopped, say, because user changed watchfaces. If animation finished – we reverse the direction and call it again.

Some useful links:

2 replies on “Pebble: How to autoscroll large text”

  1. Not able to get this technique to work using SDK 3. The text displays fine initially, but clips as soon as the animation starts.

    I’ve tried layer_set_clips() to false for each layer, but it still clips.

  2. @Drew Gulino Haven’t tried to port it to SDK 3 yet. Original PBW still runs fine under firmware 3.x, when I upgrade it to SDK 3 I will post update here.

Leave a Reply

Your email address will not be published. Required fields are marked *