# Spacefun Plymouth theme script # Copyright 2010 Aurélien COUDERC # # Inspired by the great blog series on Plymouth by Charlie Brej. # # The script uses the material made by Valessio Brito for his SpaceFun # theme for Debian 6.0 Squeeze. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . #------------------------------- Constants ----------------------------------------- NUM_STARS = 40; MAX_STAR_SIZE = Math.Min(Window.GetWidth(), Window.GetHeight()) * 0.045; MAX_MSG_DISPLAYED = 5; #------------------------------- Background ---------------------------------------- bg_image = Image("background.png"); # Compute screen/image ratio and scale the background accordingly window_max_width = Window.GetX() * 2 + Window.GetWidth(); window_max_height = Window.GetY() * 2 + Window.GetHeight(); screen_ratio = window_max_width / window_max_height; bg_image_ratio = bg_image.GetWidth() / bg_image.GetHeight(); if (screen_ratio > bg_image_ratio) bg_scale_factor = window_max_width / bg_image.GetWidth(); else bg_scale_factor = window_max_height / bg_image.GetHeight(); scaled_bg_image = bg_image.Scale(bg_image.GetWidth() * bg_scale_factor, bg_image.GetHeight() * bg_scale_factor); # Display background bg_sprite = Sprite(scaled_bg_image); bg_sprite.SetPosition(Window.GetX() + Window.GetWidth() / 2 - scaled_bg_image.GetWidth() / 2, Window.GetY() + Window.GetHeight() / 2 - scaled_bg_image.GetHeight() / 2, -10000); #------------------------------- Earth --------------------------------------------- earth_height = Math.Min(Window.GetWidth(), Window.GetHeight()) * 0.12; for (i = 0; i < 5; i++) { earth_image = Image("earth" + i + ".png"); earth_scale_factor = earth_height / earth_image.GetHeight(); earth_images[i] = earth_image.Scale(earth_image.GetWidth() * earth_scale_factor, earth_image.GetHeight() * earth_scale_factor); } earth_glow_index = 0; earth_sprite = Sprite(earth_images[earth_glow_index]); earth_to_edge = Math.Min(Window.GetWidth(), Window.GetHeight()) * 0.07; earth_x = Window.GetX() + earth_to_edge; earth_y = Window.GetY() + Window.GetHeight() - earth_to_edge - earth_images[0].GetHeight(); earth_sprite.SetPosition(earth_x, earth_y, -10); #------------------------------- Planet -------------------------------------------- planet_image = Image("planet.png"); planet_height = Math.Min(Window.GetWidth(), Window.GetHeight()) * 0.07; planet_scale_factor = planet_height / planet_image.GetHeight(); planet_image = planet_image.Scale(planet_image.GetWidth() * planet_scale_factor, planet_image.GetHeight() * planet_scale_factor); planet_sprite = Sprite(planet_image); planet_to_edge_y = Window.GetHeight() * 0.14; planet_to_edge_x = Window.GetWidth() * 0.08; planet_x = Window.GetX() + Window.GetWidth() - planet_to_edge_x - planet_image.GetWidth(); planet_y = Window.GetY() + planet_to_edge_y; planet_sprite.SetPosition(planet_x, planet_y, -10); #------------------------------- Logo ---------------------------------------------- logo_image = Image("logo.png"); logo_height = Math.Min(Window.GetWidth(), Window.GetHeight()) * 0.1; logo_scale_factor = logo_height / logo_image.GetHeight(); logo_image = logo_image.Scale(logo_image.GetWidth() * logo_scale_factor, logo_image.GetHeight() * logo_scale_factor); logo_sprite = Sprite(logo_image); logo_to_edge = Window.GetHeight() * 0.1; logo_sprite.SetPosition(Window.GetX() + Window.GetWidth() - logo_to_edge - logo_image.GetWidth(), Window.GetY() + Window.GetHeight() - logo_to_edge - logo_image.GetHeight(), -10); #------------------------------- Swirl galaxies ------------------------------------ swirlaxy_image = Image("swirlaxy.png"); if (!(Plymouth.GetMode() == "boot" || Plymouth.GetMode == "resume")) { swirls[0].x = 0.1; # Percentage of screen from left when positive, or right otherwise swirls[0].y = 0.1; # Percentage of screen from top when positive, or bottom otherwise swirls[0].height_p = 0.21; # Percent of screen height swirls[0].angle = Math.Pi; swirls[1].x = 0.54; swirls[1].y = 0.14; swirls[1].height_p = 0.11; swirls[1].angle = Math.Pi; swirls[2].x = -0.16; swirls[2].y = -0.16; swirls[2].height_p = 0.31; swirls[2].angle = 0; } else { swirls[0].x = 0.1; swirls[0].y = 0.12; swirls[0].height_p = 0.38; swirls[0].angle = 0; swirls[1].x = 0.3; swirls[1].y = -0.24; swirls[1].height_p = 0.065; swirls[1].angle = Math.Pi; swirls[2].x = -0.05; swirls[2].y = -0.3; swirls[2].height_p = 0.21; swirls[2].angle = Math.Pi; } for (i = 0; i < 3; i++) { swirl_height = Math.Min(Window.GetWidth(), Window.GetHeight()) * swirls[i].height_p; swirl_width = swirl_height * swirlaxy_image.GetWidth() / swirlaxy_image.GetHeight(); swirl_image = swirlaxy_image.Scale(swirl_width, swirl_height); swirl_image = swirl_image.Rotate(swirls[i].angle); swirl_sprites[i] = Sprite(); if (swirls[i].x >= 0) swirl_sprites[i].SetX(Window.GetX() + Window.GetWidth() * swirls[i].x); else swirl_sprites[i].SetX(Window.GetX() + Window.GetWidth() * (1 + swirls[i].x) - swirl_image.GetWidth()); if (swirls[i].y >= 0) swirl_sprites[i].SetY(Window.GetY() + Window.GetHeight() * swirls[i].y); else swirl_sprites[i].SetY(Window.GetY() + Window.GetHeight() * (1 + swirls[i].y) - swirl_image.GetHeight()); swirl_sprites[i].SetImage(swirl_image); } #------------------------------- Stars --------------------------------------------- # Load 3 star images star_white_image = Image("star-white.png"); star_fuzzy_image = Image("star-fuzzy.png"); star_red_image = Image("star-red.png"); star_white_small_image = Image("star-white-small.png"); star_fuzzy_small_image = Image("star-fuzzy-small.png"); star_red_small_image = Image("star-red-small.png"); # Initialize the random seed depending on the resolution pixels = Window.GetWidth() * Window.GetHeight(); while (pixels % 10 == 0) { pixels = pixels / 10; Math.Random(); } for (i = 0; i < pixels % 10; i++) Math.Random(); # Create all star sprites for (i = 0; i < NUM_STARS; i++) { # Randomly choose star image; white/fuzzy/red have 2/1/1 weights star_type = Math.Random() * 4; if (star_type < 2) { selected_image.big = star_white_image; selected_image.small = star_white_small_image; } else if (star_type < 3) { selected_image.big = star_fuzzy_image; selected_image.small = star_fuzzy_small_image; } else { selected_image.big = star_red_image; selected_image.small = star_red_small_image; } # Choose random size star_scaled_size = MAX_STAR_SIZE * Math.Random(); # Adapt the source image depending on the scale, smaller scales on big images # don't render so nicely. if (star_scaled_size > selected_image.small.GetHeight() || star_scaled_size > selected_image.small.GetWidth()) star_image = selected_image.big; else star_image = selected_image.small; transformed_image = star_image.Scale(star_scaled_size, star_scaled_size); # Random rotation of a 5 branch stars, we only need to rotate 2 * Pi / 5 at maximum. transformed_image = transformed_image.Rotate(2 * Math.Pi / 5 * Math.Random()); stars[i] = Sprite(transformed_image); # Randomize position, we accept that stars may be half out of the viewport # in each direction star_x = Window.GetX() + Math.Random() * (Window.GetWidth() + transformed_image.GetWidth()) - transformed_image.GetWidth() / 2; star_y = Window.GetY() + Math.Random() * (Window.GetHeight() + transformed_image.GetHeight()) - transformed_image.GetHeight() / 2; stars[i].SetPosition(star_x, star_y, -20); # Stars go behind other elements. } #------------------------------- Rocket -------------------------------------------- # Load all rocket images for animation rocket_height = Math.Min(Window.GetWidth(), Window.GetHeight()) * 0.145; for (i = 0; i < 4; i++) { rocket_image = Image("rocket" + i + ".png"); rocket_scale_factor = rocket_height / rocket_image.GetHeight(); rocket_images[i] = rocket_image.Scale(rocket_image.GetWidth() * rocket_scale_factor, rocket_image.GetHeight() * rocket_scale_factor); } rocket_flame_index = 0; rocket_sprite = Sprite(); # Rocket trajectory # Take a point somewhere in the middle to compute the parabola middle_x = Window.GetX() + Window.GetWidth() * 0.42; middle_y = Window.GetY() + Window.GetHeight() * 0.42; # Parabole coeffs so that y = a.x^2 + b.x + c for the earth, planet and middle points # Use the earth and planet centers instead of top left corner earth_cx = earth_x + earth_images[0].GetWidth()/2; earth_cy = earth_y + earth_images[0].GetHeight()/2; planet_cx = planet_x + planet_image.GetWidth()/2; planet_cy = planet_y + planet_image.GetHeight()/2; a = ((earth_cy-middle_y)/(earth_cx-middle_x)-(earth_cy-planet_cy)/(earth_cx-planet_cx))/(middle_x-planet_cx); b = (middle_y-planet_cy)/(middle_x-planet_cx)-a*(middle_x+planet_cx); c = earth_cy-a*earth_cx*earth_cx-b*earth_cx; rocket_x_start = earth_cx + Window.GetWidth() * 0.1; # Don't start right on the earth rocket_x_end = planet_cx - Window.GetWidth() * 0.1; # Don't reach the planet # Set initial position rocket_x = rocket_x_start; rocket_y = a*rocket_x*rocket_x+b*rocket_x+c; alpha = 2*a*rocket_x + b; # Set flame status and rocket direction. has_rocket = 0; rocket_comes_back = 0; if (Plymouth.GetMode() == "boot") { has_rocket = 1; rocket_comes_back = 0; } if (Plymouth.GetMode() == "shutdown" || Plymouth.GetMode() == "suspend") { has_rocket = 1; rocket_comes_back = 1; } if (Plymouth.GetMode() == "resume") { has_rocket = 1; rocket_comes_back = 0; } #------------------------------- Animation ----------------------------------------- progress = 0; fun refresh_callback () { progress++; # Rocket, update 50/3 times per second if (progress % 3 == 0) { # 6 states for the rocket flame: 1, 2, 3, 4, 3, 2 rocket_flame_index = (rocket_flame_index + 1) % 6; rocket_image = rocket_images[3 - Math.Abs(rocket_flame_index - 3)]; rotated_rocket = rocket_image.Rotate(alpha); rocket_sprite.SetImage(rotated_rocket); rocket_sprite.SetPosition(rocket_x - rotated_rocket.GetWidth()/2, rocket_y - rotated_rocket.GetHeight()/2, -10); } # Earth glow, update 10 times per second if (earth_glow_index != 0 && progress % 5 == 0) { earth_glow_anim = Math.Int(3 * Math.Random()) - 1; earth_glow_index = Math.Clamp(earth_glow_index + earth_glow_anim, 1, 4); earth_sprite.SetImage(earth_images[earth_glow_index]); } # Stars dim, update 5 times per second if (progress % 10 == 0) { for (i = 0; i < NUM_STARS; i++) { # Reset star to visible by 0.05 steps star_opacity = stars[i].GetOpacity(); if (star_opacity < 1) stars[i].SetOpacity(star_opacity + 0.05); # 10% chance to dim the star if (Math.Random() < 0.1) stars[i].SetOpacity(0.8); } } } Plymouth.SetRefreshFunction (refresh_callback); #------------------------------- Dialogue ------------------------------------------ status = "normal"; fun dialog_setup() { local.box; local.lock; local.entry; box.image = Image("box.png"); lock.image = Image("lock.png"); entry.image = Image("entry.png"); box.sprite = Sprite(box.image); box.x = Window.GetX() + Window.GetWidth() / 2 - box.image.GetWidth ()/2; box.y = Window.GetY() + Window.GetHeight() / 2 - box.image.GetHeight()/2; box.z = 10000; box.sprite.SetPosition(box.x, box.y, box.z); lock.sprite = Sprite(lock.image); lock.x = box.x + box.image.GetWidth()/2 - (lock.image.GetWidth() + entry.image.GetWidth()) / 2; lock.y = box.y + box.image.GetHeight()/2 - lock.image.GetHeight()/2; lock.z = box.z + 1; lock.sprite.SetPosition(lock.x, lock.y, lock.z); entry.sprite = Sprite(entry.image); entry.x = lock.x + lock.image.GetWidth(); entry.y = box.y + box.image.GetHeight()/2 - entry.image.GetHeight()/2; entry.z = box.z + 1; entry.sprite.SetPosition(entry.x, entry.y, entry.z); global.dialog.box = box; global.dialog.lock = lock; global.dialog.entry = entry; global.dialog.bullet_image = Image("bullet.png"); dialog_opacity (1); } fun dialog_opacity(opacity) { dialog.box.sprite.SetOpacity (opacity); dialog.lock.sprite.SetOpacity (opacity); dialog.entry.sprite.SetOpacity (opacity); for (index = 0; dialog.bullet[index]; index++) { dialog.bullet[index].sprite.SetOpacity(opacity); } } fun display_normal_callback () { global.status = "normal"; if (global.dialog) dialog_opacity (0); } fun display_password_callback (prompt, bullets) { global.status = "password"; if (!global.dialog) dialog_setup(); else dialog_opacity(1); for (index = 0; dialog.bullet[index] || index < bullets; index++) { if (!dialog.bullet[index]) { dialog.bullet[index].sprite = Sprite(dialog.bullet_image); dialog.bullet[index].x = dialog.entry.x + index * dialog.bullet_image.GetWidth(); dialog.bullet[index].y = dialog.entry.y + dialog.entry.image.GetHeight() / 2 - dialog.bullet_image.GetHeight() / 2; dialog.bullet[index].z = dialog.entry.z + 1; dialog.bullet[index].sprite.SetPosition(dialog.bullet[index].x, dialog.bullet[index].y, dialog.bullet[index].z); } if (index < bullets) dialog.bullet[index].sprite.SetOpacity(1); else dialog.bullet[index].sprite.SetOpacity(0); } } Plymouth.SetDisplayNormalFunction(display_normal_callback); Plymouth.SetDisplayPasswordFunction(display_password_callback); #------------------------------- Progress Bar ------------------------------------------ # No "progress bar" per se, we use the rocket position fun progress_callback (duration, progress) { if (rocket_comes_back) { # Rocket position rocket_x = rocket_x_end - progress * (rocket_x_end - rocket_x_start); rocket_y = a*rocket_x*rocket_x + b*rocket_x + c; # Rocket orientation alpha = Math.ATan2(2*a*rocket_x + b, 1) + Math.Pi; } else { # Rocket position rocket_x = rocket_x_start + progress * (rocket_x_end - rocket_x_start); rocket_y = a*rocket_x*rocket_x + b*rocket_x + c; # Rocket orientation alpha = Math.ATan2(2*a*rocket_x + b, 1); } } Plymouth.SetBootProgressFunction(progress_callback); #------------------------------- Root filesystem mount --------------------------------- fun root_mounted_callback () { # On boot show earth glow starting when root filesystem is mounted earth_glow_index = 1; } Plymouth.SetRootMountedFunction(root_mounted_callback); #------------------------------- Quit -------------------------------------------------- fun quit_callback () { } Plymouth.SetQuitFunction(quit_callback); #------------------------------- Message ----------------------------------------------- msg_sprites; # Declare global variable num_displayed_msg = 0; # Current number of message being displayed next_msg_idx = 0; # Index of the message to be displayed next in the msg_sprites table next_msg_y; # Y of next message to display fun message_callback (text) { if (num_displayed_msg < MAX_MSG_DISPLAYED) { # We've not reached max number of messages # Compute next y next_msg_y = 10; for (i = 0; i < num_displayed_msg; i++) next_msg_y += msg_sprites[i].GetImage().GetHeight() + 1; # 1px between lines num_displayed_msg++; } else { # We've reached max number of messages # Move all existing messages one line up move_y_by = -1 - msg_sprites[(next_msg_idx + 1) % MAX_MSG_DISPLAYED].GetImage().GetHeight(); for (i = 0; i < MAX_MSG_DISPLAYED; i++) { msg = msg_sprites[(next_msg_idx + 1 + i) % MAX_MSG_DISPLAYED]; msg.SetY(msg.GetY() + move_y_by); } } next_msg_image = Image.Text(text, 1, 1, 1, 1); msg_sprites[next_msg_idx] = Sprite(); msg_sprites[next_msg_idx].SetPosition(Window.GetX() + 10, Window.GetY() + next_msg_y, 10000); msg_sprites[next_msg_idx].SetImage(next_msg_image); next_msg_idx = (next_msg_idx + 1) % MAX_MSG_DISPLAYED; } Plymouth.SetMessageFunction(message_callback);