/* Theme Name: melawy-plymouth-theme-neon-recline (neon-recline) Version: 3.0 Description: Plymouth boot splash theme for Melawy OS Author: Valeria Fadeeva https://github.com/Valeria-Fadeeva https://github.com/Melawy https://gitlab.com/melawy/ Date: 2026.07.24 License: GNU AFFERO GENERAL PUBLIC LICENSE, see */ // ============================================================ // УТИЛИТЫ // ============================================================ Window.GetMaxWidth = fun() { i = 0; width = 0; while (Window.GetWidth(i)) { width = Math.Max(width, Window.GetWidth(i)); i++; } return width; }; Window.GetMaxHeight = fun() { i = 0; height = 0; while (Window.GetHeight(i)) { height = Math.Max(height, Window.GetHeight(i)); i++; } return height; }; Percent = fun(perc, pixels) { return Math.Int(Math.Abs(Math.Int(pixels)) / 100 * Math.Abs(Math.Int(perc))); }; Limit = fun(width, height, perc) { local.this; this.width = width; this.height = height; this.perc = perc; this.GetWidth = fun() { return Percent(this.perc, this.width); }; this.GetHeight = fun() { return Percent(this.perc, this.height); }; return this; }; ScaleImage = fun(image1, image2) { local.img1_w = image1.GetWidth(); local.img1_h = image1.GetHeight(); local.img1_ratio = img1_w / img1_h; local.img2_w = image2.GetWidth(); local.img2_h = image2.GetHeight(); local.img2_ratio = img2_w / img2_h; // Если изображение уже маленькое или квадратное — масштабируем напрямую if (img1_ratio == 1 || (img1_w < 200 && img1_h < 200)) { return image1.Scale(img2_w, img2_h); } // Размеры совпадают — ничего не делаем if (img2_w == img1_w && img2_h == img1_h) { return image1; } // Сохраняем пропорции local.factor; if (img2_ratio < img1_ratio) { factor = img2_h / img1_h; } else { factor = img2_w / img1_w; } return image1.Scale(Math.Int(img1_w * factor), Math.Int(img1_h * factor)); }; FitIntoDimensions = fun(image1, image2) { local.img1_w = image1.GetWidth(); local.img1_h = image1.GetHeight(); local.img2_w = image2.GetWidth(); local.img2_h = image2.GetHeight(); local.ma = Math.Max(img1_w, img1_h); local.mi = Math.Min(img2_w, img2_h); if (mi == 0) return image1; local.factor = ma / mi; return image1.Scale(Math.Int(img1_w / factor), Math.Int(img1_h / factor)); }; SetSpriteImage = fun(asset, x, y, z) { local.sprite = Sprite(); local.img = Image(asset); sprite.SetImage(img); sprite.SetPosition(x, y, z); return sprite; }; // Улучшенный blur: больше слоёв с меньшим смещением для более плавного эффекта Blur = fun(background_src, foreground_src, x, y, z, width, height, scale_width, scale_height) { local.result = []; // --- Фон (размытие через многослойное наложение) --- local.bg_image = Image(background_src); bg_image = ScaleImage(bg_image, Limit(scale_width, scale_height, 100)); bg_image = bg_image.Crop(x, y, width, height); bg_image = ScaleImage(bg_image, Limit(Percent(120, width), Percent(120, height), 100)); bg_image = bg_image.Crop(x, y, width, height); // Создаём 9 слоёв для более качественного размытия (3x3 сетка со смещением ±1) local.blur_layers = [ [-1, -1], [0, -1], [1, -1], [-1, 0], [0, 0], [1, 0], [-1, 1], [0, 1], [1, 1] ]; local.layer_opacity = 1.0 / 9.0; local.i; for (i = 0; i < 9; i++) { result[i].sprite = Sprite(bg_image); result[i].sprite.SetOpacity(layer_opacity); result[i].sprite.SetPosition( x + blur_layers[i][0], y + blur_layers[i][1], z + i ); } // --- Передний план --- local.fg_image = Image(foreground_src); fg_image = fg_image.Scale(width, height); result[9].sprite = Sprite(fg_image); result[9].sprite.SetOpacity(0.75); result[9].sprite.SetPosition(x, y, z + 9); return result; }; // ============================================================ // ШРИФТЫ И ТЕКСТ (масштабируются от высоты экрана) // ============================================================ screen.width = Window.GetMaxWidth(); screen.height = Window.GetMaxHeight(); aspect_ratio = screen.width / screen.height; // Базовый размер шрифта зависит от высоты экрана base_font_size = Math.Max(Math.Int(screen.height / 110), 10); if (base_font_size > 32) base_font_size = 32; common_font = "Noto Sans Mono Regular " + base_font_size; progress_font = "Noto Sans Mono Regular " + Math.Int(base_font_size * 1.2); link_font = "Noto Sans Italic " + Math.Int(base_font_size * 1.2); common_symbol.image = Image.Text("M", 0.5, 0.5, 0.5, 1, common_font); common_symbol.height = common_symbol.image.GetHeight(); common_symbol.width = common_symbol.image.GetWidth(); progress_symbol.image = Image.Text("M", 0.5, 0.5, 0.5, 1, progress_font); progress_symbol.height = progress_symbol.image.GetHeight(); progress_symbol.width = progress_symbol.image.GetWidth(); link_symbol.image = Image.Text("M", 0.5, 0.5, 0.5, 1, link_font); link_symbol.height = link_symbol.image.GetHeight(); link_symbol.width = link_symbol.image.GetWidth(); starting_text = "Starting up…"; bye_text = "System is shutting down"; progress_t = 0; x0 = 0; y0 = 0; z0 = 0; Window.SetBackgroundTopColor(0, 0, 0); Window.SetBackgroundBottomColor(0, 0, 0); // ============================================================ // ФОН // ============================================================ if (aspect_ratio == 3440 / 1440) { background.image = Image("0EkLvkiE_4g_3440x1440.png"); } else if (aspect_ratio == 1920 / 1080) { background.image = Image("0EkLvkiE_4g_2564x1440.png"); } else { background.image = Image("BACKGROUND.png"); } background.image = ScaleImage(background.image, Limit(screen.width, screen.height, 100)); background.width = background.image.GetWidth(); background.height = background.image.GetHeight(); background.x = x0 + screen.width / 2 - background.width / 2; background.y = y0 + screen.height / 2 - background.height / 2; background.sprite = Sprite(); background.sprite.SetImage(background.image); background.sprite.SetOpacity(1); background.sprite.SetPosition(background.x, background.y, 0); // ============================================================ // БОКОВАЯ ПАНЕЛЬ (BLUR) // ============================================================ side_panel.background_src = "BACKGROUND.png"; side_panel.foreground_src = "SIDE_BACKGROUND2.png"; side_panel.x = x0; side_panel.y = y0; side_panel.z = 1; if (aspect_ratio == 3440 / 1440) { side_panel.width = Percent(25, screen.width); } else if (aspect_ratio == 1920 / 1080) { side_panel.width = Percent(33, screen.width); } else { side_panel.width = Percent(33, screen.width); } side_panel.height = Percent(100, screen.height); blur_result = Blur( side_panel.background_src, side_panel.foreground_src, side_panel.x, side_panel.y, side_panel.z, side_panel.width, side_panel.height, screen.width, screen.height ); // ============================================================ // БЛОК СООБЩЕНИЙ // ============================================================ side_block.width = 0; side_block.height = 0; messages_box.image = Image("MESSAGES_BOX.png"); messages_box.image = messages_box.image.Scale( Percent(80, side_panel.width), Percent(50, side_panel.height) ); messages_box.width = messages_box.image.GetWidth(); messages_box.height = messages_box.image.GetHeight(); messages_box.x = Percent(50, side_panel.width) - Percent(50, messages_box.width); // messages_box.y = Percent(aspect_ratio, side_panel.height); messages_box.y = 0; messages_box.sprite = Sprite(messages_box.image); messages_box.sprite.SetOpacity(1); messages_box.sprite.SetPosition(messages_box.x, messages_box.y, 12); side_block.width = messages_box.width; side_block.height = messages_box.height; // ============================================================ // ФОРМА ПРОГРЕССА // ============================================================ progress_form.image = Image("PROGRESS_FORM.png"); progress_form.image = progress_form.image.Scale( Percent(80, side_panel.width), Percent(15, side_panel.height) ); progress_form.width = progress_form.image.GetWidth(); progress_form.height = progress_form.image.GetHeight(); progress_form.x = Percent(50, side_panel.width) - Percent(50, progress_form.width); progress_form.y = messages_box.y + messages_box.height + Percent(aspect_ratio, side_panel.height); progress_form.sprite = Sprite(progress_form.image); progress_form.sprite.SetOpacity(1); progress_form.sprite.SetPosition(progress_form.x, progress_form.y, 12); side_block.height = side_block.height + Percent(aspect_ratio, side_panel.height) + progress_form.height; // ============================================================ // СПИННЕР // ============================================================ spinner.image = Image("SPINNER.png"); spinner.image = spinner.image.Scale( Percent(1, screen.width), Percent(1, screen.width) ); spinner.width = spinner.image.GetWidth(); spinner.height = spinner.image.GetHeight(); spinner.x = progress_form.x + progress_form.width - Percent(10, progress_form.width); spinner.y = progress_form.y + Percent(10, progress_form.height); spinner.sprite = Sprite(spinner.image); spinner.sprite.SetPosition(spinner.x, spinner.y, 13); // ============================================================ // ТЕКСТ ПРОГРЕССА (%) // ============================================================ progress_text.image = Image.Text(progress_t + "%", 0.5, 0.5, 0.5, 1, progress_font); progress_text.width = progress_text.image.GetWidth(); progress_text.height = progress_text.image.GetHeight(); progress_text.x = progress_form.x + Percent(5, progress_form.width); progress_text.y = progress_form.y + Percent(10, progress_form.height); progress_text.sprite = Sprite(); progress_text.sprite.SetImage(progress_text.image); progress_text.sprite.SetPosition(progress_text.x, progress_text.y, 13); // ============================================================ // БЛОК ПРОГРЕССА (РАМКА) // ============================================================ progress_box.image = Image("PROGRESS_BOX.png"); progress_box.image = progress_box.image.Scale( Percent(90, progress_form.width), Percent(15, progress_form.height) ); progress_box.width = progress_box.image.GetWidth(); progress_box.height = progress_box.image.GetHeight(); progress_box.x = progress_form.x + (progress_form.width - progress_box.width) / 2; progress_box.y = progress_form.y + (progress_form.height - progress_box.height) / 2; progress_box.sprite = Sprite(progress_box.image); progress_box.sprite.SetPosition(progress_box.x, progress_box.y, 13); // ============================================================ // ПОЛОСКА ПРОГРЕССА // ============================================================ progress_bar.original_image = Image("PROGRESS_BAR.png"); progress_bar.original_image = progress_bar.original_image.Scale( Percent(90, progress_form.width), Percent(15, progress_form.height) ); progress_bar.width = progress_bar.original_image.GetWidth(); progress_bar.height = progress_bar.original_image.GetHeight(); progress_bar.x = progress_form.x + (progress_form.width - progress_bar.original_image.GetWidth()) / 2; progress_bar.y = progress_form.y + (progress_form.height - progress_bar.original_image.GetHeight()) / 2; progress_bar.sprite = Sprite(); progress_bar.sprite.SetPosition(progress_bar.x, progress_bar.y, 14); // ============================================================ // FADE ЭФФЕКТ НА ПРОГРЕССЕ // ============================================================ progress_fade.image = Image("PROGRESS_FADE.png"); progress_fade.image = progress_fade.image.Scale( Percent(10, progress_form.width), Percent(15, progress_form.height) ); progress_fade.width = progress_fade.image.GetWidth(); progress_fade.height = progress_fade.image.GetHeight(); progress_fade.sprite = Sprite(progress_fade.image); max_counter = progress_box.width - progress_fade.width; // Глобальные переменные анимации fade counter = 0; fade_dir = 0; // 0 = вправо, 1 = влево // ============================================================ // CALLBACK: ПРОГРЕСС ЗАГРУЗКИ // ============================================================ fun progress_callback(duration, progress) { local.new_width = Math.Int(progress_bar.original_image.GetWidth() * progress); if (progress_bar.image.GetWidth() != new_width) { progress_bar.image = progress_bar.original_image.Scale( new_width, progress_bar.original_image.GetHeight() ); progress_bar.sprite.SetImage(progress_bar.image); local.f = Math.Int(progress * 100); progress_t.image = Image.Text(f + "%", 0.5, 0.5, 0.5, 1, progress_font); progress_text.sprite.SetImage(progress_t.image); } } Plymouth.SetBootProgressFunction(progress_callback); // ============================================================ // DIALOG: ЗАПРОС ПАРОЛЯ // ============================================================ status = "normal"; box_form.image = Image("BOX.png"); box_form.image = box_form.image.Scale( Percent(80, side_panel.width), Percent(20, side_panel.height) ); box_form.width = box_form.image.GetWidth(); box_form.height = box_form.image.GetHeight(); box_form.x = Percent(50, side_panel.width) - Percent(50, box_form.width); box_form.y = progress_form.y + progress_form.height + Percent(aspect_ratio, side_panel.height); box_form.z = 10000; box_form.sprite = Sprite(box_form.image); box_form.sprite.SetPosition(box_form.x, box_form.y, box_form.z); box_form.sprite.SetOpacity(0); fun dialog_setup() { local.box_form = global.box_form; local.lock; local.entry; local.hint; local.bullet_i; box_form.sprite.SetOpacity(1); entry.image = Image("ENTRY.png"); entry.image = entry.image.Scale( Percent(90, box_form.width), Percent(20, box_form.height) ); entry.width = entry.image.GetWidth(); entry.height = entry.image.GetHeight(); entry.x = box_form.x + box_form.width / 2 - entry.width / 2; entry.y = box_form.y + (box_form.height / 3 * 2) - entry.height / 2; entry.z = box_form.z + 1; entry.sprite = Sprite(entry.image); entry.sprite.SetPosition(entry.x, entry.y, entry.z); lock.image = Image("LOCK.png"); lock.image = lock.image.Scale( Percent(95, entry.height), Percent(95, entry.height) ); lock.width = lock.image.GetWidth(); lock.height = lock.image.GetHeight(); lock.x = entry.x + entry.width - lock.width - (entry.height - lock.height); lock.y = entry.y + entry.height / 2 - lock.height / 2; lock.z = box_form.z + 2; lock.sprite = Sprite(lock.image); lock.sprite.SetPosition(lock.x, lock.y, lock.z); hint.sprite = Sprite(); hint.x = entry.x; hint.y = box_form.y + Percent(5, box_form.height); hint.z = box_form.z + 1; hint.sprite.SetPosition(hint.x, hint.y, hint.z); bullet_i.image = Image("BULLET.png"); bullet_i.image = bullet_i.image.Scale( Percent(50, entry.height), Percent(50, entry.height) ); bullet_i.width = bullet_i.image.GetWidth(); bullet_i.height = bullet_i.image.GetHeight(); global.dialog.box_form = box_form; global.dialog.lock = lock; global.dialog.entry = entry; global.dialog.hint = hint; global.dialog.bullet_i = bullet_i; dialog_opacity(1); } fun dialog_opacity(opacity) { if (opacity == 0) { dialog.box_form.sprite.SetOpacity(opacity); dialog.lock.sprite.SetOpacity(opacity); dialog.entry.sprite.SetOpacity(opacity); } else { box_form.sprite.SetOpacity(opacity); dialog.lock.sprite.SetOpacity(opacity); dialog.entry.sprite.SetOpacity(opacity); } dialog.hint.sprite.SetOpacity(opacity); local.index; for (index = 0; dialog.bullet[index]; index++) { dialog.bullet[index].sprite.SetOpacity(opacity); } } // ============================================================ // Изменение позиции интерфейса исходя из его общей высоты // ============================================================ side_block.height = side_block.height + Percent(aspect_ratio, side_panel.height) + box_form.height; yyy = (side_panel.height - side_block.height) / 2; messages_box.y = messages_box.y + yyy; progress_form.y = progress_form.y + yyy; spinner.y = spinner.y + yyy; progress_text.y = progress_text.y + yyy; progress_box.y = progress_box.y + yyy; progress_bar.y = progress_bar.y + yyy; box_form.y = box_form.y + yyy; messages_box.sprite.SetPosition(messages_box.x, messages_box.y, 12); progress_form.sprite.SetPosition(progress_form.x, progress_form.y, 12); spinner.sprite.SetPosition(spinner.x, spinner.y, 13); progress_text.sprite.SetPosition(progress_text.x, progress_text.y, 13); progress_box.sprite.SetPosition(progress_box.x, progress_box.y, 13); progress_bar.sprite.SetPosition(progress_bar.x, progress_bar.y, 14); box_form.sprite.SetPosition(box_form.x, box_form.y, box_form.z); // ============================================================ fun display_normal_callback() { global.status = "normal"; if (global.dialog) dialog_opacity(0); } Plymouth.SetDisplayNormalFunction(display_normal_callback); fun display_password_callback(prompt_text, bullets) { global.status = "password"; if (!global.dialog) dialog_setup(); else dialog_opacity(1); local.padding_top_bottom = Percent(10, dialog.box_form.height); local.padding_left_right = Percent(5, dialog.box_form.width); local.LINE_WIDTH = 100; while ((LINE_WIDTH * common_symbol.width) > (dialog.box_form.width - (padding_left_right * 2))) { LINE_WIDTH = LINE_WIDTH - 1; } local.str_len = StringLength(prompt_text); local.line_count = 0; local.i; for (i = 1; LINE_WIDTH * i <= str_len; i++) { line_count = i; } if (str_len > LINE_WIDTH) { if (str_len % LINE_WIDTH > 0) { line_count = line_count + 1; } } i = 1; local.start = 0; local.end = LINE_WIDTH; local.text = ""; for (i = 1; i <= line_count; i++) { text += prompt_text.SubString(start, end); text += "\n"; start = end; if (LINE_WIDTH * i < str_len) { end = end + LINE_WIDTH; } else { end = str_len; } } prompt_text = text; dialog.hint.image = Image.Text(prompt_text, 0.5, 0.5, 0.5, 1, common_font); dialog.hint.sprite.SetImage(dialog.hint.image); // Важно: обновляем позицию после смены изображения dialog.hint.sprite.SetPosition(dialog.hint.x, dialog.hint.y, dialog.hint.z); local.index; for (index = 0; dialog.bullet[index] || index < bullets; index++) { if (!dialog.bullet[index]) { dialog.bullet[index].sprite = Sprite(dialog.bullet_i.image); dialog.bullet[index].x = dialog.entry.x + index * dialog.bullet_i.width / 0.9 - dialog.entry.height * -0.2; dialog.bullet[index].y = dialog.entry.y + (dialog.entry.height - dialog.bullet_i.height) / 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.SetDisplayPasswordFunction(display_password_callback); // ============================================================ // ГЛАВНЫЙ ЛОГОТИП // ============================================================ logo.image = Image("MAIN_LOGO.png"); logo.image = FitIntoDimensions(logo.image, Limit(screen.width, screen.height, 15)); logo.width = logo.image.GetWidth(); logo.height = logo.image.GetHeight(); logo.sprite = Sprite(logo.image); logo.x = screen.width - logo.image.GetWidth() - messages_box.x; logo.y = yyy; logo.sprite.SetOpacity(1); logo.sprite.SetPosition(logo.x, logo.y, 12); // ============================================================ // ССЫЛКА НА ПРОЕКТ // ============================================================ project_link.image = Image.Text("https://Melawy.ru", 0.5, 0.5, 0.5, 1, link_font); project_link.width = project_link.image.GetWidth(); project_link.height = project_link.image.GetHeight(); project_link.sprite = Sprite(project_link.image); project_link.x = logo.x + logo.width / 2 - project_link.width / 2; project_link.y = logo.y + logo.height + Percent(aspect_ratio, screen.height); project_link.sprite.SetOpacity(1); project_link.sprite.SetPosition(project_link.x, project_link.y, 15); // ============================================================ // REFRESH: ОСНОВНОЙ ЦИКЛ АНИМАЦИИ // ============================================================ time = 1; // FSCK переменные (для совместимости с Plymouth API) fsck_running = 0; fsck_done_fading = 0; fsck_fade_in_counter = 0; fsck_progress_meter_sprite = Sprite(); fsck_progress_fade_sprite = Sprite(); fun refreshHandler() { // Показываем/скрываем элементы в зависимости от режима if (global.status == "normal" && Plymouth.GetMode() == "boot") { progress_fade.sprite.SetOpacity(0); progress_bar.sprite.SetOpacity(1); local.text_img = Image.Text(starting_text, 0.5, 0.5, 0.5, 1, common_font); local.text_spr = Sprite(text_img); text_spr.SetPosition( progress_box.x, progress_box.y - progress_text.image.GetHeight() * 2, 13 ); } else { progress_fade.sprite.SetOpacity(1); progress_bar.sprite.SetOpacity(0); local.text_end_img = Image.Text(bye_text, 0.5, 0.5, 0.5, 1, common_font); local.text_end_spr = Sprite(text_end_img); text_end_spr.SetPosition( progress_box.x, progress_box.y - progress_text.image.GetHeight() * 2, 13 ); } // Анимация fade-полоски progress_fade.sprite.SetPosition( counter + progress_bar.x, progress_bar.y, 14 ); if (fade_dir == 0) { counter++; if (counter >= max_counter) { fade_dir = 1; } } else { counter--; if (counter <= 0) { fade_dir = 0; } } // Скрываем спиннер при fsck или запросе пароля if (fsck_running == 1 || (global.dialog && dialog.hint.sprite.GetOpacity() == 1)) { spinner.sprite.SetOpacity(0); } else { spinner.sprite.SetOpacity(1); time++; local.theta = time / (Math.Pi * 2); spinner.sprite.SetImage(spinner.image.Rotate(theta)); } // FSCK анимация плавного появления if ((fsck_running == 1) && (fsck_done_fading == 0)) { fsck_progress_meter_sprite.SetOpacity(fsck_fade_in_counter); fsck_progress_fade_sprite.SetOpacity(fsck_fade_in_counter); if (fsck_fade_in_counter < 1) { fsck_fade_in_counter += 0.025; } else { fsck_done_fading = 1; } } } Plymouth.SetRefreshFunction(refreshHandler); // ============================================================ // CALLBACK: ОДИНОЧНОЕ СООБЩЕНИЕ // ============================================================ message_status.sprite = Sprite(); fun message_callback(prompt_text) { message_status.image = Image.Text(prompt_text, 0.5, 0.5, 0.5, 1, common_font); message_status.sprite.SetImage(message_status.image); message_status.height = message_status.image.GetHeight(); message_status.x = messages_box.x + Percent(5, messages_box.width); message_status.y = messages_box.y + messages_box.height - Percent(7, messages_box.height); message_status.sprite.SetOpacity(1); message_status.sprite.SetPosition(message_status.x, message_status.y, 13); } Plymouth.SetMessageFunction(message_callback); // ============================================================ // СКРОЛЛЯЩИЙСЯ ЛОГ // ============================================================ local.padding_top_bottom = Percent(10, messages_box.height); local.padding_left_right = Percent(5, messages_box.width); message_sprite_.x = messages_box.x + padding_left_right; message_sprite_.y = messages_box.y + padding_top_bottom; NUM_SCROLL_LINES = 100; LINE_WIDTH_LOG = 100; LINE_HEIGHT = common_symbol.height * 1.5; // Подстраиваем количество строк под высоту блока сообщений local.index; for (index = NUM_SCROLL_LINES; index > 0; index--) { if ((NUM_SCROLL_LINES * LINE_HEIGHT) > (messages_box.height - (padding_top_bottom * 2))) { NUM_SCROLL_LINES = NUM_SCROLL_LINES - 1; } else { break; } } // Подстраиваем ширину строки под ширину блока сообщений for (index = LINE_WIDTH_LOG; index > 0; index--) { if ((LINE_WIDTH_LOG * common_symbol.width) > (messages_box.width - (padding_left_right * 2))) { LINE_WIDTH_LOG = LINE_WIDTH_LOG - 1; } else { break; } } // Инициализация спрайтов для каждой строки лога local.i; for (i = 0; i < NUM_SCROLL_LINES; i++) { lines[i] = Image.Text("", 0.5, 0.5, 0.5, 1, common_font); message_sprite[i] = Sprite(); message_sprite[i].SetPosition( message_sprite_.x, message_sprite_.y + (i * LINE_HEIGHT), 13 ); } fun StringLength(string) { local.index = 0; local.str = String(string); while (str.CharAt(index)) index++; return index; } fun scroll_message_callback(text) { // Обрезаем слишком длинные сообщения if (StringLength(text) > LINE_WIDTH_LOG) { text = text.SubString(0, LINE_WIDTH_LOG - 1); text += "…"; } // Сдвигаем все сообщения вверх на одну строку local.i; for (i = 0; i < NUM_SCROLL_LINES - 1; i++) { lines[i] = lines[i + 1]; } // Создаём изображение для последнего сообщения lines[i] = Image.Text(text, 0.5, 0.5, 0.5, 1, common_font); // Обновляем все спрайты for (i = 0; i < NUM_SCROLL_LINES; i++) { message_sprite[i].SetImage(lines[i]); } } Plymouth.SetUpdateStatusFunction(scroll_message_callback); // ============================================================ // CALLBACK: ЗАВЕРШЕНИЕ // ============================================================ fun quit_callback() { progress_fade.sprite.SetOpacity(1); progress_bar.sprite.SetOpacity(0); if (Plymouth.GetMode() == "shutdown") { logo.sprite.SetOpacity(0); } } Plymouth.SetQuitFunction(quit_callback);