/** @file
  This file is part of OpenCanopy, OpenCore GUI.

  Copyright (c) 2018-2019, Download-Fritz. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-3-Clause
**/

#include <Uefi.h>

#include <IndustryStandard/AppleIcon.h>

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OcBootManagementLib.h>
#include <Library/OcPngLib.h>
#include <Library/OcStorageLib.h>
#include <Library/OcMiscLib.h>

#include "../OpenCanopy.h"
#include "../BmfLib.h"
#include "../GuiApp.h"
#include "../GuiIo.h"
#include "BootPicker.h"

#include "Common.h"
#include "../Blending.h"

#define BOOT_LABEL_WRAPAROUND_PADDING   30U
#define BOOT_LABEL_SCROLLING_HOLD_TIME  180U

#define BOOT_LABEL_SHADOW_WIDTH   8U
#define BOOT_LABEL_SHADOW_HEIGHT  BOOT_ENTRY_LABEL_HEIGHT

extern GUI_KEY_CONTEXT  *mKeyContext;

extern GUI_VOLUME_PICKER  mBootPicker;
extern GUI_OBJ_CHILD      mBootPickerContainer;
extern GUI_OBJ_CHILD      mBootPickerSelectorContainer;
extern GUI_OBJ_CHILD      mBootPickerSelectorBackground;
extern GUI_OBJ_CLICKABLE  mBootPickerSelectorButton;
extern GUI_OBJ_CLICKABLE  mBootPickerRightScroll;
extern GUI_OBJ_CLICKABLE  mBootPickerLeftScroll;
extern CONST GUI_IMAGE    mBackgroundImage;

// STATIC UINT8 mBootPickerImageIndex = 0;

extern INT64  mBackgroundImageOffsetX;
extern INT64  mBackgroundImageOffsetY;

STATIC UINT32  mBootPickerLabelScrollHoldTime = 0;

STATIC GUI_OBJ  *mBootPickerFocusList[] = {
  &mBootPicker.Hdr.Obj,
  &mCommonShutDown.Hdr.Obj,
  &mCommonRestart.Hdr.Obj
};

STATIC GUI_OBJ  *mBootPickerFocusListReversed[] = {
  &mBootPicker.Hdr.Obj,
  &mCommonRestart.Hdr.Obj,
  &mCommonShutDown.Hdr.Obj
};

STATIC GUI_OBJ  *mBootPickerFocusListMinimal[] = {
  &mBootPicker.Hdr.Obj
};

STATIC
GUI_VOLUME_ENTRY *
InternalGetVolumeEntry (
  IN UINT32  Index
  )
{
  ASSERT (Index < mBootPicker.Hdr.Obj.NumChildren);
  return (GUI_VOLUME_ENTRY *)(
                              mBootPicker.Hdr.Obj.Children[Index]
                              );
}

STATIC
VOID
InternalRedrawVolumeLabel (
  IN OUT GUI_DRAWING_CONTEXT     *DrawContext,
  IN     CONST GUI_VOLUME_ENTRY  *Entry
  )
{
  GuiRequestDrawCrop (
    DrawContext,
    mBootPickerContainer.Obj.OffsetX + mBootPicker.Hdr.Obj.OffsetX + Entry->Hdr.Obj.OffsetX,
    (mBootPickerContainer.Obj.OffsetY + mBootPicker.Hdr.Obj.OffsetY + Entry->Hdr.Obj.OffsetY + Entry->Hdr.Obj.Height - Entry->Label.Height),
    Entry->Hdr.Obj.Width,
    Entry->Label.Height
    );
}

BOOLEAN
InternalBootPickerAnimateLabel (
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     UINT64                   CurrentTime
  )
{
  GUI_VOLUME_ENTRY  *Entry;

  ASSERT (DrawContext != NULL);

  if (mBootPickerLabelScrollHoldTime < BOOT_LABEL_SCROLLING_HOLD_TIME) {
    ++mBootPickerLabelScrollHoldTime;
    return FALSE;
  }

  Entry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
  if (Entry->Label.Width <= Entry->Hdr.Obj.Width) {
    return FALSE;
  }

  Entry->ShowLeftShadow = TRUE;
  Entry->LabelOffset   -= DrawContext->Scale;

  if (Entry->LabelOffset <= -(INT16)Entry->Label.Width) {
    //
    // Hide the left shadow once the first label is hidden.
    //
    Entry->ShowLeftShadow = FALSE;
    //
    // If the second drawn label reaches the front, switch back to the first.
    //
    if (Entry->LabelOffset <= -(INT16)(Entry->Label.Width + BOOT_LABEL_WRAPAROUND_PADDING * DrawContext->Scale)) {
      Entry->LabelOffset             = 0;
      mBootPickerLabelScrollHoldTime = 0;
    }
  }

  InternalRedrawVolumeLabel (DrawContext, Entry);

  return FALSE;
}

STATIC GUI_ANIMATION  mBootPickerLabelAnimation = {
  { NULL, NULL },
  NULL,
  InternalBootPickerAnimateLabel
};

VOID
InternalStartAnimateLabel (
  IN OUT GUI_DRAWING_CONTEXT     *DrawContext,
  IN     CONST GUI_VOLUME_ENTRY  *Entry
  )
{
  ASSERT (!IsNodeInList (&DrawContext->Animations, &mBootPickerLabelAnimation.Link));
  //
  // Reset the boot entry label scrolling timer.
  //
  mBootPickerLabelScrollHoldTime = 0;

  if (Entry->Label.Width > Entry->Hdr.Obj.Width) {
    //
    // Add the animation if the next entry needs scrolling.
    //
    InsertHeadList (&DrawContext->Animations, &mBootPickerLabelAnimation.Link);
  }
}

VOID
InternalStopAnimateLabel (
  IN OUT GUI_DRAWING_CONTEXT  *DrawContext,
  IN OUT GUI_VOLUME_ENTRY     *Entry
  )
{
  if (Entry->Label.Width > Entry->Hdr.Obj.Width) {
    //
    // Reset the label of the old entry back to its default position.
    //
    if (Entry->LabelOffset != 0) {
      Entry->ShowLeftShadow = FALSE;
      Entry->LabelOffset    = 0;
      InternalRedrawVolumeLabel (DrawContext, Entry);
    }

    RemoveEntryList (&mBootPickerLabelAnimation.Link);
    InitializeListHead (&mBootPickerLabelAnimation.Link);
  }
}

VOID
InternalBootPickerSelectEntry (
  IN OUT GUI_VOLUME_PICKER    *This,
  IN OUT GUI_DRAWING_CONTEXT  *DrawContext,
  IN     UINT32               NewIndex
  )
{
  GUI_VOLUME_ENTRY        *OldEntry;
  CONST GUI_VOLUME_ENTRY  *NewEntry;

  ASSERT (This != NULL);
  ASSERT (NewIndex < mBootPicker.Hdr.Obj.NumChildren);

  OldEntry            = InternalGetVolumeEntry (This->SelectedIndex);
  This->SelectedIndex = NewIndex;
  NewEntry            = InternalGetVolumeEntry (NewIndex);

  ASSERT (NewEntry->Hdr.Obj.Width <= mBootPickerSelectorContainer.Obj.Width);
  ASSERT_EQUALS (This->Hdr.Obj.Height, mBootPickerSelectorContainer.Obj.OffsetY + mBootPickerSelectorContainer.Obj.Height);

  mBootPickerSelectorContainer.Obj.OffsetX = mBootPicker.Hdr.Obj.OffsetX + NewEntry->Hdr.Obj.OffsetX - (mBootPickerSelectorContainer.Obj.Width - NewEntry->Hdr.Obj.Width) / 2;

  if (DrawContext != NULL) {
    if (OldEntry->Label.Width > OldEntry->Hdr.Obj.Width) {
      ASSERT (IsNodeInList (&DrawContext->Animations, &mBootPickerLabelAnimation.Link));
    }

    InternalStopAnimateLabel (DrawContext, OldEntry);
    InternalStartAnimateLabel (DrawContext, NewEntry);
    //
    // Set voice timeout to N frames from now.
    //
    DrawContext->GuiContext->AudioPlaybackTimeout = OC_VOICE_OVER_IDLE_TIMEOUT_MS;
    DrawContext->GuiContext->VoAction             = CanopyVoSelectedEntry;
    DrawContext->GuiContext->BootEntry            = NewEntry->Context;
  }
}

INT64
InternelBootPickerScrollSelected (
  VOID
  )
{
  CONST GUI_VOLUME_ENTRY  *SelectedEntry;
  INT64                   EntryOffsetX;
  UINT32                  EntryWidth;

  ASSERT (mBootPicker.SelectedIndex < mBootPicker.Hdr.Obj.NumChildren);

  //
  // No scroll required if nothing off screen
  //
  if (mBootPicker.Hdr.Obj.Width <= mBootPickerContainer.Obj.Width) {
    return 0;
  }

  //
  // If the selected entry is outside of the view, scroll it accordingly.
  //
  SelectedEntry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
  EntryOffsetX  = mBootPicker.Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.OffsetX - (mBootPickerSelectorContainer.Obj.Width - SelectedEntry->Hdr.Obj.Width) / 2;
  EntryWidth    = SelectedEntry->Hdr.Obj.Width + (mBootPickerSelectorContainer.Obj.Width - SelectedEntry->Hdr.Obj.Width);

  if (EntryOffsetX < 0) {
    return -EntryOffsetX;
  }

  if (EntryOffsetX + EntryWidth > mBootPickerContainer.Obj.Width) {
    return -((EntryOffsetX + EntryWidth) - mBootPickerContainer.Obj.Width);
  }

  return 0;
}

VOID
InternalUpdateScrollButtons (
  VOID
  )
{
  if (mBootPicker.Hdr.Obj.OffsetX < 0) {
    mBootPickerLeftScroll.Hdr.Obj.Opacity = 0xFF;
  } else {
    mBootPickerLeftScroll.Hdr.Obj.Opacity = 0;
  }

  if (mBootPicker.Hdr.Obj.OffsetX + mBootPicker.Hdr.Obj.Width > mBootPickerContainer.Obj.Width) {
    mBootPickerRightScroll.Hdr.Obj.Opacity = 0xFF;
  } else {
    mBootPickerRightScroll.Hdr.Obj.Opacity = 0;
  }
}

VOID
InternalBootPickerScroll (
  IN OUT GUI_VOLUME_PICKER    *This,
  IN OUT GUI_DRAWING_CONTEXT  *DrawContext,
  IN     INT64                BaseX,
  IN     INT64                BaseY,
  IN     INT64                ScrollOffset
  )
{
  INT64  ScrollX;
  INT64  ScrollY;

  mBootPicker.Hdr.Obj.OffsetX              += ScrollOffset;
  mBootPickerSelectorContainer.Obj.OffsetX += ScrollOffset;
  InternalUpdateScrollButtons ();
  //
  // The entry list has been scrolled, the entire horizontal space to also cover
  // the scroll buttons.
  //
  DEBUG_CODE_BEGIN ();
  GuiGetBaseCoords (
    &mBootPickerLeftScroll.Hdr.Obj,
    DrawContext,
    &ScrollX,
    &ScrollY
    );
  ASSERT (ScrollY >= BaseY);
  ASSERT (ScrollY + mBootPickerLeftScroll.Hdr.Obj.Height <= BaseY + This->Hdr.Obj.Height);

  GuiGetBaseCoords (
    &mBootPickerRightScroll.Hdr.Obj,
    DrawContext,
    &ScrollX,
    &ScrollY
    );
  ASSERT (ScrollY >= BaseY);
  ASSERT (ScrollY + mBootPickerRightScroll.Hdr.Obj.Height <= BaseY + This->Hdr.Obj.Height);
  DEBUG_CODE_END ();
  //
  // The container is constructed such that it is always fully visible.
  //
  ASSERT (This->Hdr.Obj.Height <= mBootPickerContainer.Obj.Height);
  ASSERT (BaseY + This->Hdr.Obj.Height <= DrawContext->Screen.Height);

  GuiRequestDraw (
    0,
    (UINT32)BaseY,
    DrawContext->Screen.Width,
    This->Hdr.Obj.Height
    );
}

VOID
InternalBootPickerChangeEntry (
  IN OUT GUI_VOLUME_PICKER    *This,
  IN OUT GUI_DRAWING_CONTEXT  *DrawContext,
  IN     INT64                BaseX,
  IN     INT64                BaseY,
  IN     UINT32               NewIndex
  )
{
  INT64  ScrollOffset;
  INT64  OldSelectorOffsetX;

  ASSERT (This != NULL);
  ASSERT (DrawContext != NULL);
  ASSERT (NewIndex < This->Hdr.Obj.NumChildren);
  //
  // The caller must guarantee the entry is actually new for performance
  // reasons.
  //
  ASSERT (This->SelectedIndex != NewIndex);

  OldSelectorOffsetX = mBootPickerSelectorContainer.Obj.OffsetX;
  InternalBootPickerSelectEntry (This, DrawContext, NewIndex);

  ScrollOffset = InternelBootPickerScrollSelected ();
  if (ScrollOffset == 0) {
    GuiRequestDrawCrop (
      DrawContext,
      mBootPickerContainer.Obj.OffsetX + OldSelectorOffsetX,
      mBootPickerContainer.Obj.OffsetY + mBootPickerSelectorContainer.Obj.OffsetY,
      mBootPickerSelectorContainer.Obj.Width,
      mBootPickerSelectorContainer.Obj.Height
      );

    GuiRequestDrawCrop (
      DrawContext,
      mBootPickerContainer.Obj.OffsetX + mBootPickerSelectorContainer.Obj.OffsetX,
      mBootPickerContainer.Obj.OffsetY + mBootPickerSelectorContainer.Obj.OffsetY,
      mBootPickerSelectorContainer.Obj.Width,
      mBootPickerSelectorContainer.Obj.Height
      );
  } else {
    InternalBootPickerScroll (
      This,
      DrawContext,
      BaseX,
      BaseY,
      ScrollOffset
      );
  }
}

VOID
InternalBootPickerKeyEvent (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *GuiContext,
  IN     CONST GUI_KEY_EVENT      *KeyEvent
  )
{
  GUI_VOLUME_PICKER       *Picker;
  INT64                   BaseX;
  INT64                   BaseY;
  CONST GUI_VOLUME_ENTRY  *SelectedEntry;
  UINT8                   ImageId;

  ASSERT (This != NULL);
  ASSERT (GuiContext != NULL);
  ASSERT (DrawContext != NULL);

  if ((KeyEvent->OcModifiers & OC_MODIFIERS_SET_DEFAULT) == 0) {
    ImageId = ICON_SELECTOR;
  } else {
    ImageId = ICON_SET_DEFAULT;
  }

  if (mBootPickerSelectorButton.ImageId != ImageId) {
    mBootPickerSelectorButton.ImageId = ImageId;
    GuiRequestDraw (
      (UINT32)(mBootPickerContainer.Obj.OffsetX + mBootPickerSelectorContainer.Obj.OffsetX + mBootPickerSelectorButton.Hdr.Obj.OffsetX),
      (UINT32)(mBootPickerContainer.Obj.OffsetY + mBootPickerSelectorContainer.Obj.OffsetY + mBootPickerSelectorButton.Hdr.Obj.OffsetY),
      mBootPickerSelectorButton.Hdr.Obj.Width,
      mBootPickerSelectorButton.Hdr.Obj.Height
      );
  }

  Picker = BASE_CR (This, GUI_VOLUME_PICKER, Hdr.Obj);

  BaseX = mBootPickerContainer.Obj.OffsetX + mBootPicker.Hdr.Obj.OffsetX;
  BaseY = mBootPickerContainer.Obj.OffsetY + mBootPicker.Hdr.Obj.OffsetY;

  if (KeyEvent->OcKeyCode == OC_INPUT_RIGHT) {
    if (mBootPicker.Hdr.Obj.NumChildren > 1) {
      InternalBootPickerChangeEntry (
        Picker,
        DrawContext,
        BaseX,
        BaseY,
        mBootPicker.SelectedIndex + 1 < mBootPicker.Hdr.Obj.NumChildren
          ? mBootPicker.SelectedIndex + 1
          : 0
        );
    }
  } else if (KeyEvent->OcKeyCode == OC_INPUT_LEFT) {
    ASSERT (mBootPicker.Hdr.Obj.NumChildren > 0);
    if (mBootPicker.Hdr.Obj.NumChildren > 1) {
      InternalBootPickerChangeEntry (
        Picker,
        DrawContext,
        BaseX,
        BaseY,
        mBootPicker.SelectedIndex > 0
          ? mBootPicker.SelectedIndex - 1
          : mBootPicker.Hdr.Obj.NumChildren - 1
        );
    }
  } else if (KeyEvent->OcKeyCode == OC_INPUT_CONTINUE) {
    if (mBootPicker.Hdr.Obj.NumChildren > 0) {
      SelectedEntry                      = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
      SelectedEntry->Context->SetDefault = (KeyEvent->OcModifiers & OC_MODIFIERS_SET_DEFAULT) != 0;
      GuiContext->ReadyToBoot            = TRUE;
      ASSERT (GuiContext->BootEntry == SelectedEntry->Context);
    }
  } else if (  GuiContext->PickerContext->PickerAudioAssist
            && (KeyEvent->OcKeyCode >= 0)
            && ((UINTN)KeyEvent->OcKeyCode < mBootPicker.Hdr.Obj.NumChildren)
               )
  {
    InternalBootPickerChangeEntry (
      Picker,
      DrawContext,
      BaseX,
      BaseY,
      (UINT32)KeyEvent->OcKeyCode
      );
    SelectedEntry                      = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
    SelectedEntry->Context->SetDefault = (KeyEvent->OcModifiers & OC_MODIFIERS_SET_DEFAULT) != 0;
    GuiContext->ReadyToBoot            = TRUE;
    ASSERT (GuiContext->BootEntry == SelectedEntry->Context);
  } else if (mBootPickerContainer.Obj.Opacity != 0xFF) {
    //
    // FIXME: Other keys are not allowed when boot picker is partially transparent.
    //
    return;
  }

  if (KeyEvent->OcKeyCode == OC_INPUT_MORE) {
    //
    // Match Builtin picker logic here: only refresh if the keypress makes a change
    //
    GuiContext->HideAuxiliary = !GuiContext->HideAuxiliary;
    GuiContext->Refresh = TRUE;
    if (!GuiContext->HideAuxiliary) {
      GuiContext->Refresh = TRUE;
      DrawContext->GuiContext->PickerContext->PlayAudioFile (
                                                DrawContext->GuiContext->PickerContext,
                                                OC_VOICE_OVER_AUDIO_FILE_SHOW_AUXILIARY,
                                                OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
                                                FALSE
                                                );
    }
  } else if (KeyEvent->OcKeyCode == OC_INPUT_ABORTED) {
    GuiContext->Refresh = TRUE;
    DrawContext->GuiContext->PickerContext->PlayAudioFile (
                                              DrawContext->GuiContext->PickerContext,
                                              OC_VOICE_OVER_AUDIO_FILE_RELOADING,
                                              OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
                                              FALSE
                                              );
  }
  if (KeyEvent->OcKeyCode == OC_INPUT_FUNCTIONAL(9)) {//按F9隐藏版本和作者信息
    if (GuiContext->PickerContext->TitleSuffix != NULL) {
          GuiContext->PickerContext->WelcomeSuffix = NULL;
          GuiContext->PickerContext->TitleSuffix = NULL;
          GuiContext->Refresh = TRUE;
					DrawContext->GuiContext->PickerContext->PlayAudioFile (
																										DrawContext->GuiContext->PickerContext,
																										OC_VOICE_OVER_AUDIO_FILE_RELOADING,
																										OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
																										FALSE
																										);
    }
    else if (GuiContext->PickerContext->TitleSuffix == NULL){
          GuiContext->PickerContext->TitleSuffix = GuiContext->PickerContext->TempTitleSuffix;
          GuiContext->PickerContext->WelcomeSuffix =  GuiContext->PickerContext->TempWelcomSuffix;
          GuiContext->Refresh = TRUE;
					DrawContext->GuiContext->PickerContext->PlayAudioFile (
																										DrawContext->GuiContext->PickerContext,
																										OC_VOICE_OVER_AUDIO_FILE_RELOADING,
																										OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE,
																										FALSE
																										);
    }
  }
  if (KeyEvent->OcKeyCode == OC_INPUT_INTERNAL) {
    if (mBootPicker.Hdr.Obj.NumChildren > 0) {
      SelectedEntry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
      SelectedEntry->Context->SetDefault = (KeyEvent->OcModifiers & OC_MODIFIERS_SET_DEFAULT) != 0;
      GuiContext->ReadyToBoot = TRUE;
      ASSERT (GuiContext->BootEntry == SelectedEntry->Context);
    }
  }
}

STATIC
VOID
InternalBootPickerEntryDraw (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     UINT32                   OffsetX,
  IN     UINT32                   OffsetY,
  IN     UINT32                   Width,
  IN     UINT32                   Height,
  IN     UINT8                    Opacity
  )
{
  UINT32  ShadowWidth;
  UINT32  ShadowStart;
  UINT32  ShadowEnd;
  UINT32  LeftShadowSize;
  UINT32  RightShadowSize1;
  UINT32  RightShadowSize2;
  UINT32  LabelWidth;
  UINT32  ColumnOffset;
  UINT8   ShadowOpacity;
  INT64   LabelOffset;

  CONST GUI_VOLUME_ENTRY  *Entry;
  CONST GUI_IMAGE         *EntryIcon;
  CONST GUI_IMAGE         *Label;

  ASSERT (This != NULL);
  ASSERT (DrawContext != NULL);
  ASSERT (Context != NULL);

  Entry = BASE_CR (This, GUI_VOLUME_ENTRY, Hdr.Obj);

  /*if (mBootPickerImageIndex < 5) {
    EntryIcon = &((BOOT_PICKER_GUI_CONTEXT *) DrawContext->GuiContext)->Poof[mBootPickerImageIndex];
  } else */{
    EntryIcon = &Entry->EntryIcon;
  }
  Label = &Entry->Label;
  //
  // Draw the icon horizontally centered.
  //
  ASSERT (EntryIcon != NULL);
  ASSERT_EQUALS (EntryIcon->Width, This->Width);

  if (OffsetY < EntryIcon->Height) {
    GuiDrawToBuffer (
      EntryIcon,
      Opacity,
      DrawContext,
      BaseX,
      BaseY,
      OffsetX,
      OffsetY,
      Width,
      Height
      );
  }

  ASSERT (Label->Height <= BOOT_ENTRY_LABEL_HEIGHT * DrawContext->Scale);

  if (Entry->Label.Width <= This->Width) {
    //
    // Draw the label horizontally centered.
    //
    GuiDrawChildImage (
      Label,
      Opacity,
      DrawContext,
      BaseX,
      BaseY,
      Entry->LabelOffset,
      This->Height - Label->Height,
      OffsetX,
      OffsetY,
      Width,
      Height
      );
  } else {
    //
    // Draw scrolling label with fade-in and fade-out.
    //
    ShadowWidth = BOOT_LABEL_SHADOW_WIDTH * DrawContext->Scale;

    if (!Entry->ShowLeftShadow) {
      LeftShadowSize = 0;
    } else {
      //
      // Draw left shadow one column at a time.
      //
      ShadowStart = OffsetX; // <- No left clip, unsigned

      if (OffsetX + Width > ShadowWidth) {
        ShadowEnd = ShadowWidth;
      } else {
        ShadowEnd = OffsetX + Width;
      }

      if (ShadowEnd <= ShadowStart) {
        LeftShadowSize = 0;
      } else {
        LeftShadowSize = ShadowEnd - ShadowStart;

        for (ColumnOffset = ShadowStart; ColumnOffset < ShadowEnd; ++ColumnOffset) {
          ShadowOpacity = (UINT8)(((ColumnOffset + 1) * 0xFF) / (ShadowWidth + 1));

          ShadowOpacity = RGB_APPLY_OPACITY (
                            Opacity,
                            ShadowOpacity
                            );

          GuiDrawChildImage (
            Label,
            ShadowOpacity,
            DrawContext,
            BaseX,
            BaseY,
            Entry->LabelOffset,
            This->Height - Label->Height,
            ColumnOffset,
            OffsetY,
            1,
            Height
            );
        }
      }
    }

    //
    // If the label needs scrolling, draw a second one to get a wraparound effect.
    //

    //
    // Draw right shadow one column at a time.
    //
    if (OffsetX < This->Width - ShadowWidth) {
      ShadowStart = This->Width - ShadowWidth;
    } else {
      ShadowStart = OffsetX;
    }

    if (OffsetX + Width > This->Width) {
      ShadowEnd = This->Width;
    } else {
      ShadowEnd = OffsetX + Width;
    }

    if (ShadowEnd <= ShadowStart) {
      RightShadowSize1 = 0;
      RightShadowSize2 = 0;
    } else {
      //
      // Right shadow needs to be drawn on first or second label depending on
      // position of scroll, but never on both because of the padding between them.
      //
      STATIC_ASSERT (BOOT_LABEL_WRAPAROUND_PADDING >= BOOT_LABEL_SHADOW_WIDTH, "additional code required for wraparound padding < shadow width");

      LabelOffset = Entry->LabelOffset + Entry->Label.Width + BOOT_LABEL_WRAPAROUND_PADDING * DrawContext->Scale;
      if (LabelOffset > This->Width) {
        LabelOffset      = Entry->LabelOffset;
        RightShadowSize1 = ShadowEnd - ShadowStart;
        RightShadowSize2 = 0;
      } else {
        RightShadowSize1 = 0;
        RightShadowSize2 = ShadowEnd - ShadowStart;
      }

      for (ColumnOffset = ShadowStart; ColumnOffset < ShadowEnd; ++ColumnOffset) {
        ShadowOpacity = (UINT8)(((ColumnOffset - (This->Width - ShadowWidth) + 1) * 0xFF) / (ShadowWidth + 1));

        ShadowOpacity = RGB_APPLY_OPACITY (
                          Opacity,
                          0xFF - ShadowOpacity
                          );

        GuiDrawChildImage (
          Label,
          ShadowOpacity,
          DrawContext,
          BaseX,
          BaseY,
          LabelOffset,
          This->Height - Label->Height,
          ColumnOffset,
          OffsetY,
          1,
          Height
          );
      }
    }

    //
    // Draw non-shaded part of first label.
    //
    LabelWidth = Width - MIN (Width, LeftShadowSize);
    LabelWidth = LabelWidth - MIN (LabelWidth, RightShadowSize1);
    if (LabelWidth > 0) {
      GuiDrawChildImage (
        Label,
        Opacity,
        DrawContext,
        BaseX,
        BaseY,
        Entry->LabelOffset,
        This->Height - Label->Height,
        OffsetX + LeftShadowSize,
        OffsetY,
        LabelWidth,
        Height
        );
    }

    //
    // Draw non-shaded part of second label.
    //
    LabelWidth = Width - MIN (Width, RightShadowSize2);
    if (LabelWidth > 0) {
      GuiDrawChildImage (
        Label,
        Opacity,
        DrawContext,
        BaseX,
        BaseY,
        (INT64)Entry->LabelOffset + Entry->Label.Width + BOOT_LABEL_WRAPAROUND_PADDING * DrawContext->Scale,
        This->Height - Label->Height,
        OffsetX,
        OffsetY,
        LabelWidth,
        Height
        );
    }
  }

  //
  // There should be no children.
  //
  ASSERT (This->NumChildren == 0);
}

STATIC
GUI_OBJ *
InternalBootPickerEntryPtrEvent (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     CONST GUI_PTR_EVENT      *Event
  )
{
  GUI_VOLUME_ENTRY  *Entry;
  BOOLEAN           IsHit;
  UINT32            OffsetX;
  UINT32            OffsetY;

  OffsetX = (UINT32)(Event->Pos.Pos.X - BaseX);
  OffsetY = (UINT32)(Event->Pos.Pos.Y - BaseY);

  ASSERT (
    Event->Type == GuiPointerPrimaryDown
         || Event->Type == GuiPointerPrimaryUp
         || Event->Type == GuiPointerPrimaryDoubleClick
    );

  Entry = BASE_CR (This, GUI_VOLUME_ENTRY, Hdr.Obj);

  IsHit = GuiClickableIsHit (
            &Entry->EntryIcon,
            OffsetX,
            OffsetY
            );
  if (!IsHit) {
    return This;
  }

  if (Event->Type == GuiPointerPrimaryDown) {
    if (mBootPicker.SelectedIndex != Entry->Index) {
      ASSERT (Entry->Hdr.Parent == &mBootPicker.Hdr.Obj);
      InternalBootPickerChangeEntry (
        &mBootPicker,
        DrawContext,
        BaseX - This->OffsetX,
        BaseY - This->OffsetY,
        Entry->Index
        );
    }
  } else if (Event->Type == GuiPointerPrimaryDoubleClick) {
    //
    // This must be ensured because the UI directs Move/Up events to the object
    // Down had been sent to.
    //
    ASSERT (mBootPicker.SelectedIndex == Entry->Index);
    Context->ReadyToBoot = TRUE;
  }

  //
  // There should be no children.
  //
  ASSERT (This->NumChildren == 0);
  return This;
}

VOID
InternalBootPickerSelectorBackgroundDraw (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     UINT32                   OffsetX,
  IN     UINT32                   OffsetY,
  IN     UINT32                   Width,
  IN     UINT32                   Height,
  IN     UINT8                    Opacity
  )
{
  CONST GUI_IMAGE  *BackgroundImage;

  ASSERT (This != NULL);
  ASSERT (DrawContext != NULL);
  ASSERT (Context != NULL);

  BackgroundImage = &Context->Icons[ICON_SELECTED][ICON_TYPE_BASE];

  ASSERT_EQUALS (This->Width, BackgroundImage->Width);
  ASSERT_EQUALS (This->Height, BackgroundImage->Height);

  GuiDrawToBuffer (
    BackgroundImage,
    Opacity,
    DrawContext,
    BaseX,
    BaseY,
    OffsetX,
    OffsetY,
    Width,
    Height
    );
  //
  // There should be no children.
  //
  ASSERT (This->NumChildren == 0);
}

GUI_OBJ *
InternalBootPickerSelectorPtrEvent (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     CONST GUI_PTR_EVENT      *Event
  )
{
  UINT8  Result;

  Result = InternalCommonSimpleButtonPtrEvent (
             This,
             DrawContext,
             Context,
             BaseX,
             BaseY,
             Event
             );
  switch (Result) {
    case CommonPtrNotHit:
    {
      return NULL;
    }

    case CommonPtrAction:
    {
      Context->ReadyToBoot = TRUE;
      //
      // Falthrough to 'hit' case.
      //
    }

    case CommonPtrHit:
    {
      break;
    }
  }

  return This;
}

GUI_OBJ *
InternalBootPickerLeftScrollPtrEvent (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     CONST GUI_PTR_EVENT      *Event
  )
{
  UINT8  Result;

  INT64                   BootPickerX;
  INT64                   BootPickerY;
  CONST GUI_VOLUME_ENTRY  *SelectedEntry;

  if (This->Opacity == 0) {
    return NULL;
  }

  Result = InternalCommonSimpleButtonPtrEvent (
             This,
             DrawContext,
             Context,
             BaseX,
             BaseY,
             Event
             );
  switch (Result) {
    case CommonPtrNotHit:
    {
      return NULL;
    }

    case CommonPtrAction:
    {
      //
      // The view can only be scrolled when there are off-screen entries.
      //
      GuiGetBaseCoords (
        &mBootPicker.Hdr.Obj,
        DrawContext,
        &BootPickerX,
        &BootPickerY
        );
      //
      // Scroll the boot entry view by one spot.
      //
      InternalBootPickerScroll (
        &mBootPicker,
        DrawContext,
        BootPickerX,
        BootPickerY,
        (BOOT_ENTRY_WIDTH + BOOT_ENTRY_SPACE) * Context->Scale
        );
      //
      // If the selected entry is pushed off-screen by scrolling, select the
      // appropriate neighbour entry.
      //
      SelectedEntry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
      if (mBootPicker.Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.Width > mBootPickerContainer.Obj.Width) {
        //
        // The internal design ensures a selected entry cannot be off-screen,
        // scrolling offsets it by at most one spot.
        //
        InternalBootPickerSelectEntry (
          &mBootPicker,
          DrawContext,
          mBootPicker.SelectedIndex > 0
            ? mBootPicker.SelectedIndex - 1
            : mBootPicker.Hdr.Obj.NumChildren - 1
          );

        SelectedEntry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
        ASSERT (!(mBootPicker.Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.Width > mBootPickerContainer.Obj.Width));
      }

      //
      // Falthrough to 'hit' case.
      //
    }

    case CommonPtrHit:
    {
      break;
    }
  }

  return This;
}

GUI_OBJ *
InternalBootPickerRightScrollPtrEvent (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     CONST GUI_PTR_EVENT      *Event
  )
{
  UINT8  Result;

  INT64             BootPickerX;
  INT64             BootPickerY;
  GUI_VOLUME_ENTRY  *SelectedEntry;

  if (This->Opacity == 0) {
    return NULL;
  }

  Result = InternalCommonSimpleButtonPtrEvent (
             This,
             DrawContext,
             Context,
             BaseX,
             BaseY,
             Event
             );
  switch (Result) {
    case CommonPtrNotHit:
    {
      return NULL;
    }

    case CommonPtrAction:
    {
      //
      // The view can only be scrolled when there are off-screen entries.
      //
      GuiGetBaseCoords (
        &mBootPicker.Hdr.Obj,
        DrawContext,
        &BootPickerX,
        &BootPickerY
        );
      //
      // Scroll the boot entry view by one spot.
      //
      InternalBootPickerScroll (
        &mBootPicker,
        DrawContext,
        BootPickerX,
        BootPickerY,
        -(INT64)(BOOT_ENTRY_WIDTH + BOOT_ENTRY_SPACE) * Context->Scale
        );
      //
      // If the selected entry is pushed off-screen by scrolling, select the
      // appropriate neighbour entry.
      //
      SelectedEntry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
      if (mBootPicker.Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.OffsetX < 0) {
        //
        // The internal design ensures a selected entry cannot be off-screen,
        // scrolling offsets it by at most one spot.
        //
        InternalBootPickerSelectEntry (
          &mBootPicker,
          DrawContext,
          mBootPicker.SelectedIndex + 1 < mBootPicker.Hdr.Obj.NumChildren
            ? mBootPicker.SelectedIndex + 1
            : 0
          );

        SelectedEntry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);
        ASSERT (!(mBootPicker.Hdr.Obj.OffsetX + SelectedEntry->Hdr.Obj.OffsetX < 0));
      }

      //
      // Falthrough to 'hit' case.
      //
    }

    case CommonPtrHit:
    {
      break;
    }
  }

  return This;
}

STATIC GUI_IMAGE  mVersionLabelImage;

VOID
InternalVersionLabelDraw (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     INT64                    BaseX,
  IN     INT64                    BaseY,
  IN     UINT32                   OffsetX,
  IN     UINT32                   OffsetY,
  IN     UINT32                   Width,
  IN     UINT32                   Height,
  IN     UINT8                    Opacity
  )
{
  if (mVersionLabelImage.Buffer != NULL) {
    GuiDrawToBuffer (
      &mVersionLabelImage,
      Opacity,
      DrawContext,
      BaseX,
      BaseY,
      OffsetX,
      OffsetY,
      Width,
      Height
      );
  }
}

//自定义一个欢迎字符串
STATIC GUI_IMAGE mWelcomeLabelImage;

VOID
InternalWelcomeLabelDraw (
  IN OUT GUI_OBJ                 *This,
  IN OUT GUI_DRAWING_CONTEXT     *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT *Context,
  IN     INT64                   BaseX,
  IN     INT64                   BaseY,
  IN     UINT32                  OffsetX,
  IN     UINT32                  OffsetY,
  IN     UINT32                  Width,
  IN     UINT32                  Height,
  IN     UINT8                   Opacity
  )
{
  if (mWelcomeLabelImage.Buffer != NULL) {
    GuiDrawToBuffer (
      &mWelcomeLabelImage,
      Opacity,
      DrawContext,
      BaseX,
      BaseY,
      OffsetX,
      OffsetY,
      Width,
      Height
      );
  }
}
//自定义结束

VOID
InternalBootPickerViewKeyEvent (
  IN OUT GUI_OBJ                  *This,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN     CONST GUI_KEY_EVENT      *KeyEvent
  )
{
  GUI_OBJ           *FocusChangedObj;
  GUI_VOLUME_ENTRY  *Entry;

  ASSERT (This != NULL);
  ASSERT (DrawContext != NULL);

  if (KeyEvent->OcKeyCode == OC_INPUT_VOICE_OVER) {
    DrawContext->GuiContext->PickerContext->ToggleVoiceOver (
                                              DrawContext->GuiContext->PickerContext,
                                              NULL,
                                              NULL
                                              );
    return;
  }

  Entry = InternalGetVolumeEntry (mBootPicker.SelectedIndex);

  FocusChangedObj = InternalFocusKeyHandler (
                      DrawContext,
                      Context,
                      KeyEvent
                      );
  if (FocusChangedObj == &mBootPicker.Hdr.Obj) {
    InternalStartAnimateLabel (DrawContext, Entry);
  } else if (FocusChangedObj != NULL) {
    if (!IsListEmpty (&mBootPickerLabelAnimation.Link)) {
      InternalStopAnimateLabel (DrawContext, Entry);
    }
  }
}

VOID
InternalBootPickerFocus (
  IN     CONST GUI_OBJ        *This,
  IN OUT GUI_DRAWING_CONTEXT  *DrawContext,
  IN     BOOLEAN              Focus
  )
{
  if (!Focus) {
    mBootPickerSelectorContainer.Obj.Opacity = 0;
  } else {
    mBootPickerSelectorContainer.Obj.Opacity = 0xFF;

    DrawContext->GuiContext->AudioPlaybackTimeout = 0;
    DrawContext->GuiContext->VoAction             = CanopyVoSelectedEntry;
  }

  GuiRequestDraw (
    (UINT32)(mBootPickerContainer.Obj.OffsetX + mBootPickerSelectorContainer.Obj.OffsetX),
    (UINT32)(mBootPickerContainer.Obj.OffsetY + mBootPickerSelectorContainer.Obj.OffsetY),
    mBootPickerSelectorContainer.Obj.Width,
    mBootPickerSelectorContainer.Obj.Height
    );
}

BOOLEAN
InternalBootPickerExitLoop (
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *Context
  )
{
  ASSERT (Context != NULL);

  if (Context->ReadyToBoot) {
    ASSERT (Context->BootEntry == InternalGetVolumeEntry (mBootPicker.SelectedIndex)->Context);
  }

  return Context->ReadyToBoot || Context->Refresh;
}

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD  mBootPickerSelectorBackground = {
  {
    0, 0, 0, 0, 0xFF,
    InternalBootPickerSelectorBackgroundDraw,
    NULL,
    GuiObjDelegatePtrEvent,
    NULL,
    0,
    NULL
  },
  &mBootPickerSelectorContainer.Obj
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CLICKABLE  mBootPickerSelectorButton = {
  {
    {
      0, 0, 0, 0, 0xFF,
      InternalCommonSimpleButtonDraw,
      NULL,
      InternalBootPickerSelectorPtrEvent,
      NULL,
      0,
      NULL
    },
    &mBootPickerSelectorContainer.Obj
  },
  0,
  0
};

STATIC GUI_OBJ_CHILD  *mBootPickerSelectorContainerChildren[] = {
  &mBootPickerSelectorBackground,
  &mBootPickerSelectorButton.Hdr
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD  mBootPickerSelectorContainer = {
  {
    0, 0, 0, 0, 0xFF,
    GuiObjDrawDelegate,
    NULL,
    GuiObjDelegatePtrEvent,
    NULL,
    ARRAY_SIZE (mBootPickerSelectorContainerChildren),
    mBootPickerSelectorContainerChildren
  },
  &mBootPickerContainer.Obj
};

STATIC GUI_OBJ_CHILD  *mBootPickerContainerChildren[] = {
  &mBootPickerSelectorContainer,
  &mBootPicker.Hdr
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD  mBootPickerContainer = {
  {
    0, 0, 0, 0, 0xFF,
    GuiObjDrawDelegate,
    NULL,
    GuiObjDelegatePtrEvent,
    NULL,
    ARRAY_SIZE (mBootPickerContainerChildren),
    mBootPickerContainerChildren
  },
  NULL
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_VOLUME_PICKER  mBootPicker = {
  {
    {
      0, 0, 0, 0, 0xFF,
      GuiObjDrawDelegate,
      InternalBootPickerKeyEvent,
      GuiObjDelegatePtrEvent,
      InternalBootPickerFocus,
      0,
      NULL
    },
    &mBootPickerContainer.Obj
  },
  1
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CLICKABLE  mBootPickerLeftScroll = {
  {
    {
      0, 0, 0, 0, 0xFF,
      InternalCommonSimpleButtonDraw,
      NULL,
      InternalBootPickerLeftScrollPtrEvent,
      NULL,
      0,
      NULL
    },
    NULL
  },
  0,
  0
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CLICKABLE  mBootPickerRightScroll = {
  {
    {
      0, 0, 0, 0, 0xFF,
      InternalCommonSimpleButtonDraw,
      NULL,
      InternalBootPickerRightScrollPtrEvent,
      NULL,
      0,
      NULL
    },
    NULL
  },
  0,
  0
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD  mBootPickerVersionLabel = {
  {
    0, 0, 0, 0, 0xFF,
    InternalVersionLabelDraw,
    NULL,
    GuiObjDelegatePtrEvent,
    NULL,
    0,
    NULL
  },
  NULL
};

//仿造版本标签
GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD mBootWelcomeLabel = {
  {
    0, 0, 0, 0, 0xFF,
    InternalWelcomeLabelDraw,
    NULL,
    GuiObjDelegatePtrEvent,
    NULL,
    0,
    NULL
  },
  NULL
};
//结束


STATIC GUI_OBJ_CHILD *mBootPickerViewChildren[] = {
  &mBootPickerContainer,
  &mCommonActionButtonsContainer,
  &mBootPickerLeftScroll.Hdr,
  &mBootPickerRightScroll.Hdr,
  &mBootPickerVersionLabel,
  &mBootWelcomeLabel //我的添加
};

STATIC GUI_OBJ_CHILD  *mBootPickerViewChildrenMinimal[] = {
  &mBootPickerContainer,
  &mBootPickerLeftScroll.Hdr,
  &mBootPickerRightScroll.Hdr,
  &mBootPickerVersionLabel,
  &mBootWelcomeLabel //我的添加
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_VIEW_CONTEXT  mBootPickerViewContext = {
  InternalCommonViewDraw,
  InternalCommonViewPtrEvent,
  ARRAY_SIZE (mBootPickerViewChildren),
  mBootPickerViewChildren,
  InternalBootPickerViewKeyEvent,
  InternalGetCursorImage,
  InternalBootPickerExitLoop,
  mBootPickerFocusList,
  ARRAY_SIZE (mBootPickerFocusList)
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_VIEW_CONTEXT  mBootPickerViewContextReversed = {
  InternalCommonViewDraw,
  InternalCommonViewPtrEvent,
  ARRAY_SIZE (mBootPickerViewChildren),
  mBootPickerViewChildren,
  InternalBootPickerViewKeyEvent,
  InternalGetCursorImage,
  InternalBootPickerExitLoop,
  mBootPickerFocusListReversed,
  ARRAY_SIZE (mBootPickerFocusListReversed)
};

GLOBAL_REMOVE_IF_UNREFERENCED GUI_VIEW_CONTEXT  mBootPickerViewContextMinimal = {
  InternalCommonViewDraw,
  InternalCommonViewPtrEvent,
  ARRAY_SIZE (mBootPickerViewChildrenMinimal),
  mBootPickerViewChildrenMinimal,
  InternalBootPickerViewKeyEvent,
  InternalGetCursorImage,
  InternalBootPickerExitLoop,
  mBootPickerFocusListMinimal,
  ARRAY_SIZE (mBootPickerFocusListMinimal)
};

STATIC
EFI_STATUS
CopyLabel (
  OUT GUI_IMAGE        *Destination,
  IN  CONST GUI_IMAGE  *Source
  )
{
  if (Source->Buffer == NULL) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  Destination->Width  = Source->Width;
  Destination->Height = Source->Height;
  Destination->Buffer = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)AllocateCopyPool (
                                                           sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) * Source->Width * Source->Height,
                                                           Source->Buffer
                                                           );

  if (Destination->Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
BootPickerEntriesSet (
  IN OC_PICKER_CONTEXT        *Context,
  IN BOOT_PICKER_GUI_CONTEXT  *GuiContext,
  IN OC_BOOT_ENTRY            *Entry,
  IN UINT8                    EntryIndex
  )
{
  EFI_STATUS              Status;
  GUI_VOLUME_ENTRY        *VolumeEntry;
  CONST GUI_IMAGE         *SuggestedIcon;
  CONST GUI_VOLUME_ENTRY  *PrevEntry;
  UINT32                  IconFileSize;
  UINT32                  IconTypeIndex;
  VOID                    *IconFileData;
  BOOLEAN                 UseVolumeIcon;
  BOOLEAN                 UseFlavourIcon;
  BOOLEAN                 UseDiskLabel;
  BOOLEAN                 UseGenericLabel;
  BOOLEAN                 Result;
  CHAR16                  *EntryName;
  UINTN                   EntryNameLength;
  CHAR8                   *FlavourNameStart;
  CHAR8                   *FlavourNameEnd;

  ASSERT (GuiContext != NULL);
  ASSERT (Entry != NULL);
  ASSERT (EntryIndex < mBootPicker.Hdr.Obj.NumChildren);

  DEBUG ((DEBUG_INFO, "OCUI: Console attributes: %d\n", Context->ConsoleAttributes));

  UseVolumeIcon   = (Context->PickerAttributes & OC_ATTR_USE_VOLUME_ICON) != 0;
  UseFlavourIcon  = (Context->PickerAttributes & OC_ATTR_USE_FLAVOUR_ICON) != 0;
  UseDiskLabel    = (Context->PickerAttributes & OC_ATTR_USE_DISK_LABEL_FILE) != 0;
  UseGenericLabel = (Context->PickerAttributes & OC_ATTR_USE_GENERIC_LABEL_IMAGE) != 0;

  DEBUG ((DEBUG_INFO, "OCUI: UseDiskLabel: %d, UseGenericLabel: %d\n", UseDiskLabel, UseGenericLabel));

  VolumeEntry = AllocateZeroPool (sizeof (*VolumeEntry));
  if (VolumeEntry == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (UseDiskLabel) {
    Status = Context->GetEntryLabelImage (
                        Context,
                        Entry,
                        GuiContext->Scale,
                        &IconFileData,
                        &IconFileSize
                        );
    if (!EFI_ERROR (Status)) {
      Status = GuiLabelToImage (
                 &VolumeEntry->Label,
                 IconFileData,
                 IconFileSize,
                 GuiContext->Scale,
                 GuiContext->LightBackground
                 );
    }
  } else {
    Status = EFI_UNSUPPORTED;
  }

  if (EFI_ERROR (Status) && UseGenericLabel) {
    switch (Entry->Type) {
      case OC_BOOT_APPLE_OS:
        Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_APPLE]);
        break;
      case OC_BOOT_APPLE_FW_UPDATE:
      case OC_BOOT_APPLE_RECOVERY:
        Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_APPLE_RECOVERY]);
        break;
      case OC_BOOT_APPLE_TIME_MACHINE:
        Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_APPLE_TIME_MACHINE]);
        break;
      case OC_BOOT_WINDOWS:
        Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_WINDOWS]);
        break;
      case OC_BOOT_EXTERNAL_OS:
        Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_OTHER]);
        break;
      //
      // Use flavour-based labels for system entries (e.g. from boot entry protocol).
      //
      case OC_BOOT_SYSTEM:
      case OC_BOOT_EXTERNAL_TOOL:
        if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_RESET_NVRAM) != NULL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_RESET_NVRAM]);
        } else if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_TOGGLE_SIP_ENABLED) != NULL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_SIP_IS_ENABLED]);
        } else if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_TOGGLE_SIP_DISABLED) != NULL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_SIP_IS_DISABLED]);
        } else if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_UEFI_SHELL) != NULL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_SHELL]);
        } else if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_ID_FIRMWARE_SETTINGS) != NULL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_FIRMWARE_SETTINGS]);
        } else if (Entry->Type == OC_BOOT_EXTERNAL_TOOL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_TOOL]);
        } else {
          DEBUG ((DEBUG_WARN, "OCUI: System entry flavour %a unsupported for label\n", Entry->Flavour));
          Status = EFI_UNSUPPORTED;
        }

        break;
      case OC_BOOT_EXTERNAL_SYSTEM:
        if (OcAsciiStriStr (Entry->Flavour, OC_FLAVOUR_WINDOWS) != NULL) {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_WINDOWS]);
        } else {
          Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_OTHER]);
        }

        break;
      case OC_BOOT_UNKNOWN:
        Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_GENERIC_HDD]);
        break;
      default:
        DEBUG ((DEBUG_WARN, "OCUI: Entry kind %d unsupported for label\n", Entry->Type));
        Status = EFI_UNSUPPORTED;
    }
  }

  if (EFI_ERROR (Status)) {
    EntryName       = Entry->Name;
    EntryNameLength = StrLen (Entry->Name);
    if (Entry->IsFolder) {
      EntryName = AllocatePool (EntryNameLength * sizeof (CHAR16) + L_STR_SIZE (OC_MENU_DISK_IMAGE));
      if (EntryName != NULL) {
        CopyMem (EntryName, Entry->Name, EntryNameLength * sizeof (CHAR16));
        CopyMem (&EntryName[EntryNameLength], OC_MENU_DISK_IMAGE, L_STR_SIZE (OC_MENU_DISK_IMAGE));
        EntryNameLength += L_STR_LEN (OC_MENU_DISK_IMAGE);
      } else {
        EntryName = Entry->Name;
      }
    }

    ASSERT (StrLen (EntryName) == EntryNameLength);

    Result = GuiGetLabel (
               &VolumeEntry->Label,
               &GuiContext->FontContext,
               EntryName,
               EntryNameLength,
               GuiContext->LightBackground
               );

    if (EntryName != Entry->Name) {
      FreePool (EntryName);
    }

    if (!Result) {
      DEBUG ((DEBUG_WARN, "OCUI: label failed\n"));
      return EFI_UNSUPPORTED;
    }
  }

  VolumeEntry->Context = Entry;

  //
  // Load volume icons when allowed.
  // Do not load volume icons for Time Machine entries unless explicitly enabled.
  // This works around Time Machine icon style incompatibilities.
  //
  if (  UseVolumeIcon
     && (  (Entry->Type != OC_BOOT_APPLE_TIME_MACHINE)
        || ((Context->PickerAttributes & OC_ATTR_HIDE_THEMED_ICONS) == 0)))
  {
    Status = Context->GetEntryIcon (Context, Entry, &IconFileData, &IconFileSize);

    if (!EFI_ERROR (Status)) {
      Status = GuiIcnsToImageIcon (
                 &VolumeEntry->EntryIcon,
                 IconFileData,
                 IconFileSize,
                 GuiContext->Scale,
                 BOOT_ENTRY_ICON_DIMENSION,
                 BOOT_ENTRY_ICON_DIMENSION,
                 FALSE
                 );
      FreePool (IconFileData);
      if (!EFI_ERROR (Status)) {
        VolumeEntry->CustomIcon = TRUE;
      } else {
        DEBUG ((DEBUG_INFO, "OCUI: Failed to convert icon - %r\n", Status));
      }
    }
  } else {
    Status = EFI_UNSUPPORTED;
  }

  //
  // Flavour system is used internally for icon priorities even when
  // user-specified flavours from .contentFlavour are not being read
  //
  if (EFI_ERROR (Status)) {
    ASSERT (Entry->Flavour != NULL);

    IconTypeIndex = Entry->IsExternal ? ICON_TYPE_EXTERNAL : ICON_TYPE_BASE;

    FlavourNameEnd = Entry->Flavour - 1;
    do {
      for (FlavourNameStart = ++FlavourNameEnd; *FlavourNameEnd != '\0' && *FlavourNameEnd != ':'; ++FlavourNameEnd) {
      }

      Status = InternalGetFlavourIcon (
                 GuiContext,
                 Context->StorageContext,
                 FlavourNameStart,
                 FlavourNameEnd - FlavourNameStart,
                 IconTypeIndex,
                 UseFlavourIcon,
                 &VolumeEntry->EntryIcon,
                 &VolumeEntry->CustomIcon
                 );
    } while (EFI_ERROR (Status) && *FlavourNameEnd != '\0');

    if (EFI_ERROR (Status)) {
      SuggestedIcon = NULL;

      if (Entry->Type == OC_BOOT_EXTERNAL_OS) {
        SuggestedIcon = &GuiContext->Icons[ICON_OTHER][IconTypeIndex];
      } else if ((Entry->Type & (OC_BOOT_EXTERNAL_TOOL | OC_BOOT_SYSTEM)) != 0) {
        SuggestedIcon = &GuiContext->Icons[ICON_TOOL][IconTypeIndex];
      }

      if ((SuggestedIcon == NULL) || (SuggestedIcon->Buffer == NULL)) {
        SuggestedIcon = &GuiContext->Icons[ICON_GENERIC_HDD][IconTypeIndex];
      }

      CopyMem (&VolumeEntry->EntryIcon, SuggestedIcon, sizeof (VolumeEntry->EntryIcon));
    } else {
      DEBUG ((DEBUG_INFO, "OCUI: Using flavour icon, custom: %u\n", VolumeEntry->CustomIcon));
    }
  }

  VolumeEntry->Hdr.Parent          = &mBootPicker.Hdr.Obj;
  VolumeEntry->Hdr.Obj.Width       = BOOT_ENTRY_ICON_DIMENSION * GuiContext->Scale;
  VolumeEntry->Hdr.Obj.Height      = (BOOT_ENTRY_HEIGHT - BOOT_ENTRY_ICON_SPACE) * GuiContext->Scale;
  VolumeEntry->Hdr.Obj.Opacity     = 0xFF;
  VolumeEntry->Hdr.Obj.Draw        = InternalBootPickerEntryDraw;
  VolumeEntry->Hdr.Obj.PtrEvent    = InternalBootPickerEntryPtrEvent;
  VolumeEntry->Hdr.Obj.NumChildren = 0;
  VolumeEntry->Hdr.Obj.Children    = NULL;
  if (VolumeEntry->Hdr.Obj.Width > VolumeEntry->Label.Width) {
    VolumeEntry->LabelOffset = (INT16)((VolumeEntry->Hdr.Obj.Width - VolumeEntry->Label.Width) / 2);
  }

  if (EntryIndex > 0) {
    PrevEntry                    = InternalGetVolumeEntry (EntryIndex - 1);
    VolumeEntry->Hdr.Obj.OffsetX = PrevEntry->Hdr.Obj.OffsetX + (BOOT_ENTRY_DIMENSION + BOOT_ENTRY_SPACE) * GuiContext->Scale;
  } else {
    //
    // Account for the selector background.
    //
    VolumeEntry->Hdr.Obj.OffsetX = BOOT_ENTRY_ICON_SPACE * GuiContext->Scale;
  }

  VolumeEntry->Hdr.Obj.OffsetY = BOOT_ENTRY_ICON_SPACE * GuiContext->Scale;

  mBootPicker.Hdr.Obj.Children[EntryIndex] = &VolumeEntry->Hdr;
  VolumeEntry->Index                       = EntryIndex;
  mBootPicker.Hdr.Obj.Width               += (BOOT_ENTRY_WIDTH + BOOT_ENTRY_SPACE) * GuiContext->Scale;
  mBootPicker.Hdr.Obj.OffsetX             -= (BOOT_ENTRY_WIDTH + BOOT_ENTRY_SPACE) * GuiContext->Scale / 2;

  return EFI_SUCCESS;
}

VOID
InternalBootPickerEntryDestruct (
  IN GUI_VOLUME_ENTRY  *Entry
  )
{
  ASSERT (Entry != NULL);
  ASSERT (Entry->Label.Buffer != NULL);

  if (Entry->CustomIcon) {
    FreePool (Entry->EntryIcon.Buffer);
  }

  FreePool (Entry->Label.Buffer);
  FreePool (Entry);
}

STATIC GUI_INTERPOLATION  mBpAnimInfoImageList;

VOID
InitBpAnimImageList (
  IN GUI_INTERPOL_TYPE  Type,
  IN UINT64             StartTime,
  IN UINT64             Duration
  )
{
  mBpAnimInfoImageList.Type       = Type;
  mBpAnimInfoImageList.StartTime  = StartTime;
  mBpAnimInfoImageList.Duration   = Duration;
  mBpAnimInfoImageList.StartValue = 0;
  mBpAnimInfoImageList.EndValue   = 5;
}

BOOLEAN
InternalBootPickerAnimateImageList (
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     UINT64                   CurrentTime
  )
{
 #if 0
  GUI_VOLUME_ENTRY  *Entry;
  CONST GUI_IMAGE   *EntryIcon;

  Entry     = BASE_CR (&mBootPicker.Hdr.Obj, GUI_VOLUME_ENTRY, Hdr.Obj);
  EntryIcon = &Entry->EntryIcon;

  mBootPickerImageIndex++;
  mBootPickerImageIndex = (UINT8)GuiGetInterpolatedValue (&mBpAnimInfoImageList, CurrentTime);
  Entry->EntryIcon      = &((GUI_IMAGE *)Context)[mBootPickerImageIndex];
  GuiRedrawObject (
    &mBootPicker.Hdr.Obj,
    DrawContext,
    mBootPicker.Hdr.Obj.OffsetX,
    mBootPicker.Hdr.Obj.OffsetY
    );

  if (mBootPickerImageIndex == mBpAnimInfoImageList.EndValue) {
    return TRUE;
  }

 #endif
  return FALSE;
}

STATIC UINT32  mPrevSine;

STATIC GUI_INTERPOLATION  mBpAnimInfoSinMove = {
  GuiInterpolTypeSmooth,
  0,
  25,
  0,
  0,
  0
};

VOID
InitBpAnimIntro (
  IN CONST GUI_DRAWING_CONTEXT  *DrawContext
  )
{
  mBpAnimInfoSinMove.EndValue = 35 * DrawContext->Scale;
  //
  // FIXME: This assumes that only relative changes of X are performed on
  //        mBootPickerContainer between animation initialisation and start.
  //
  mBootPickerContainer.Obj.OffsetX += 35 * DrawContext->Scale;

  mPrevSine = 0;
}

BOOLEAN
InternalBootPickerAnimateIntro (
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     UINT64                   CurrentTime
  )
{
  UINT8   Opacity;
  UINT32  InterpolVal;
  UINT32  DeltaSine;

  ASSERT (DrawContext != NULL);

  Opacity = (UINT8)GuiGetInterpolatedValue (
                     &mCommonIntroOpacityInterpol,
                     CurrentTime
                     );
  mBootPickerContainer.Obj.Opacity = Opacity;
  //
  // Animate the scroll buttons based on their active state.
  //
  if (mBootPicker.Hdr.Obj.OffsetX < 0) {
    mBootPickerLeftScroll.Hdr.Obj.Opacity = Opacity;
  }

  if (mBootPicker.Hdr.Obj.OffsetX + mBootPicker.Hdr.Obj.Width > mBootPickerContainer.Obj.Width) {
    mBootPickerRightScroll.Hdr.Obj.Opacity = Opacity;
  }

  //
  // If PasswordView already faded-in the action buttons, skip them.
  //
  if (mCommonActionButtonsContainer.Obj.Opacity != 0xFF) {
    mCommonActionButtonsContainer.Obj.Opacity = Opacity;
    //
    // The view is constructed such that the action buttons are always fully
    // visible.
    //
    ASSERT (mCommonActionButtonsContainer.Obj.OffsetX >= 0);
    ASSERT (mCommonActionButtonsContainer.Obj.OffsetY >= 0);
    ASSERT (mCommonActionButtonsContainer.Obj.OffsetX + mCommonActionButtonsContainer.Obj.Width <= DrawContext->Screen.Width);
    ASSERT (mCommonActionButtonsContainer.Obj.OffsetY + mCommonActionButtonsContainer.Obj.Height <= DrawContext->Screen.Height);
    GuiRequestDraw (
      (UINT32)mCommonActionButtonsContainer.Obj.OffsetX,
      (UINT32)mCommonActionButtonsContainer.Obj.OffsetY,
      mCommonActionButtonsContainer.Obj.Width,
      mCommonActionButtonsContainer.Obj.Height
      );
  }

  InterpolVal                       = GuiGetInterpolatedValue (&mBpAnimInfoSinMove, CurrentTime);
  DeltaSine                         = InterpolVal - mPrevSine;
  mBootPickerContainer.Obj.OffsetX -= DeltaSine;
  mPrevSine                         = InterpolVal;
  //
  // Draw the full dimension of the inner container to implicitly cover the
  // scroll buttons with the off-screen entries.
  //
  GuiRequestDrawCrop (
    DrawContext,
    mBootPickerContainer.Obj.OffsetX + mBootPicker.Hdr.Obj.OffsetX,
    mBootPickerContainer.Obj.OffsetY + mBootPicker.Hdr.Obj.OffsetY,
    mBootPicker.Hdr.Obj.Width + DeltaSine,
    mBootPicker.Hdr.Obj.Height
    );

  ASSERT (mBpAnimInfoSinMove.Duration == mCommonIntroOpacityInterpol.Duration);
  return CurrentTime - mBpAnimInfoSinMove.StartTime >= mBpAnimInfoSinMove.Duration;
}

STATIC GUI_INTERPOLATION  mBootPickerTimeoutOpacityInterpolDown = {
  GuiInterpolTypeLinear,
  0,
  125,
  0xFF,
  0x50,
  0
};

STATIC GUI_INTERPOLATION  mBootPickerTimeoutOpacityInterpolUp = {
  GuiInterpolTypeLinear,
  0,
  125,
  0x50,
  0xFF,
  20
};

BOOLEAN
InternalBootPickerAnimateTimeout (
  IN     BOOT_PICKER_GUI_CONTEXT  *Context,
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     UINT64                   CurrentTime
  )
{
  STATIC GUI_INTERPOLATION  *CurInterpol  = &mBootPickerTimeoutOpacityInterpolDown;
  STATIC GUI_INTERPOLATION  *NextInterpol = &mBootPickerTimeoutOpacityInterpolUp;

  GUI_INTERPOLATION  *Temp;

  if (DrawContext->TimeOutSeconds > 0) {
    mBootPickerSelectorButton.Hdr.Obj.Opacity = (UINT8)GuiGetInterpolatedValue (
                                                         CurInterpol,
                                                         CurrentTime
                                                         );
  } else {
    mBootPickerSelectorButton.Hdr.Obj.Opacity = 0xFF;
  }

  //
  // The view is constructed such that the action buttons are always fully
  // visible.
  //
  GuiRequestDraw (
    (UINT32)(mBootPickerContainer.Obj.OffsetX + mBootPickerSelectorContainer.Obj.OffsetX + mBootPickerSelectorButton.Hdr.Obj.OffsetX),
    (UINT32)(mBootPickerContainer.Obj.OffsetY + mBootPickerSelectorContainer.Obj.OffsetY + mBootPickerSelectorButton.Hdr.Obj.OffsetY),
    mBootPickerSelectorButton.Hdr.Obj.Width,
    mBootPickerSelectorButton.Hdr.Obj.Height
    );

  if (DrawContext->TimeOutSeconds == 0) {
    return TRUE;
  }

  if (CurrentTime - CurInterpol->StartTime >= CurInterpol->Duration + CurInterpol->HoldTime) {
    Temp                   = CurInterpol;
    CurInterpol            = NextInterpol;
    NextInterpol           = Temp;
    CurInterpol->StartTime = CurrentTime + 1;
  }

  return FALSE;
}

STATIC GUI_ANIMATION  mBootPickerIntroAnimation = {
  { NULL, NULL },
  NULL,
  InternalBootPickerAnimateIntro
};

EFI_STATUS
BootPickerViewInitialize (
  OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN  BOOT_PICKER_GUI_CONTEXT  *GuiContext,
  IN  GUI_CURSOR_GET_IMAGE     GetCursorImage,
  IN  UINT8                    NumBootEntries
  )
{
  CONST GUI_IMAGE  *SelectorBackgroundImage;
  CONST GUI_IMAGE  *SelectorButtonImage;
  UINT32           ContainerMaxWidth;
  UINT32           ContainerWidthDelta;
  UINTN            DestLen;
  CHAR16           *UString;
  BOOLEAN          Result;

  ASSERT (DrawContext != NULL);
  ASSERT (GuiContext != NULL);
  ASSERT (GetCursorImage != NULL);

  mKeyContext->KeyFilter = OC_PICKER_KEYS_FOR_PICKER;

  CommonViewInitialize (
    DrawContext,
    GuiContext,
    (GuiContext->PickerContext->PickerAttributes & OC_ATTR_USE_MINIMAL_UI) == 0
      ? ((GuiContext->PickerContext->PickerAttributes & OC_ATTR_USE_REVERSED_UI) == 0
        ? &mBootPickerViewContext
        : &mBootPickerViewContextReversed
         )
      : &mBootPickerViewContextMinimal
    );

  mBackgroundImageOffsetX = DivS64x64Remainder (
                              (INT64)DrawContext->Screen.Width - DrawContext->GuiContext->Background.Width,
                              2,
                              NULL
                              );
  mBackgroundImageOffsetY = DivS64x64Remainder (
                              (INT64)DrawContext->Screen.Height - DrawContext->GuiContext->Background.Height,
                              2,
                              NULL
                              );

  SelectorBackgroundImage = &GuiContext->Icons[ICON_SELECTED][ICON_TYPE_BASE];
  SelectorButtonImage     = &GuiContext->Icons[ICON_SELECTOR][ICON_TYPE_BASE];

  mBootPickerSelectorBackground.Obj.OffsetX = 0;
  mBootPickerSelectorBackground.Obj.OffsetY = 0;
  mBootPickerSelectorBackground.Obj.Width   = SelectorBackgroundImage->Width;
  mBootPickerSelectorBackground.Obj.Height  = SelectorBackgroundImage->Height;

  ASSERT (SelectorBackgroundImage->Width >= SelectorButtonImage->Width);
  mBootPickerSelectorButton.Hdr.Obj.OffsetX = (SelectorBackgroundImage->Width - SelectorButtonImage->Width) / 2;
  mBootPickerSelectorButton.Hdr.Obj.OffsetY = SelectorBackgroundImage->Height + BOOT_SELECTOR_BUTTON_SPACE * DrawContext->Scale;
  mBootPickerSelectorButton.Hdr.Obj.Width   = SelectorButtonImage->Width;
  mBootPickerSelectorButton.Hdr.Obj.Height  = SelectorButtonImage->Height;
  mBootPickerSelectorButton.ImageId         = ICON_SELECTOR;
  mBootPickerSelectorButton.ImageState      = ICON_TYPE_BASE;

  mBootPickerSelectorContainer.Obj.OffsetX = 0;
  mBootPickerSelectorContainer.Obj.OffsetY = 0;
  mBootPickerSelectorContainer.Obj.Width   = SelectorBackgroundImage->Width;
  mBootPickerSelectorContainer.Obj.Height  = (UINT32)(mBootPickerSelectorButton.Hdr.Obj.OffsetY + mBootPickerSelectorButton.Hdr.Obj.Height);

  mBootPickerLeftScroll.Hdr.Obj.Height  = BOOT_SCROLL_BUTTON_DIMENSION * GuiContext->Scale;
  mBootPickerLeftScroll.Hdr.Obj.Width   = BOOT_SCROLL_BUTTON_DIMENSION * GuiContext->Scale;
  mBootPickerLeftScroll.Hdr.Obj.OffsetX = BOOT_SCROLL_BUTTON_SPACE;
  mBootPickerLeftScroll.Hdr.Obj.OffsetY = (DrawContext->Screen.Height - mBootPickerLeftScroll.Hdr.Obj.Height) / 2;
  mBootPickerLeftScroll.ImageId         = ICON_LEFT;
  mBootPickerLeftScroll.ImageState      = ICON_TYPE_BASE;

  mBootPickerRightScroll.Hdr.Obj.Height  = BOOT_SCROLL_BUTTON_DIMENSION * GuiContext->Scale;
  mBootPickerRightScroll.Hdr.Obj.Width   = BOOT_SCROLL_BUTTON_DIMENSION * GuiContext->Scale;
  mBootPickerRightScroll.Hdr.Obj.OffsetX = DrawContext->Screen.Width - mBootPickerRightScroll.Hdr.Obj.Width - BOOT_SCROLL_BUTTON_SPACE;
  mBootPickerRightScroll.Hdr.Obj.OffsetY = (DrawContext->Screen.Height - mBootPickerRightScroll.Hdr.Obj.Height) / 2;
  mBootPickerRightScroll.ImageId         = ICON_RIGHT;
  mBootPickerRightScroll.ImageState      = ICON_TYPE_BASE;
  //
  // The boot entry container must precisely show a set of boot entries, i.e.
  // there may not be partial entries or extra padding.
  //
  ContainerMaxWidth   = DrawContext->Screen.Width - mBootPickerLeftScroll.Hdr.Obj.Width - 2 * BOOT_SCROLL_BUTTON_SPACE - mBootPickerRightScroll.Hdr.Obj.Width - 2 * BOOT_SCROLL_BUTTON_SPACE;
  ContainerWidthDelta = (ContainerMaxWidth + BOOT_ENTRY_SPACE * GuiContext->Scale) % ((BOOT_ENTRY_WIDTH + BOOT_ENTRY_SPACE) * GuiContext->Scale);

  mBootPickerContainer.Obj.Height  = BOOT_SELECTOR_HEIGHT * GuiContext->Scale;
  mBootPickerContainer.Obj.Width   = ContainerMaxWidth - ContainerWidthDelta;
  mBootPickerContainer.Obj.OffsetX = (DrawContext->Screen.Width - mBootPickerContainer.Obj.Width) / 2;
  //
  // Center the icons and labels excluding the selector images vertically.
  //
  ASSERT ((DrawContext->Screen.Height - (BOOT_ENTRY_HEIGHT - BOOT_ENTRY_ICON_SPACE) * GuiContext->Scale) / 2 - (BOOT_ENTRY_ICON_SPACE * GuiContext->Scale) == (DrawContext->Screen.Height - (BOOT_ENTRY_HEIGHT + BOOT_ENTRY_ICON_SPACE) * GuiContext->Scale) / 2);
  mBootPickerContainer.Obj.OffsetY = (DrawContext->Screen.Height - (BOOT_ENTRY_HEIGHT + BOOT_ENTRY_ICON_SPACE) * GuiContext->Scale) / 2;

  mBootPicker.Hdr.Obj.Height = BOOT_SELECTOR_HEIGHT * GuiContext->Scale;
  //
  // Adding an entry will wrap around Width such that the first entry has no
  // padding.
  //
  mBootPicker.Hdr.Obj.Width = 0U - (UINT32)(BOOT_ENTRY_SPACE * GuiContext->Scale);
  //
  // Adding an entry will also shift OffsetX considering the added boot entry
  // space. This is not needed for the first, so initialise accordingly.
  //
  mBootPicker.Hdr.Obj.OffsetX = mBootPickerContainer.Obj.Width / 2 + (UINT32)(BOOT_ENTRY_SPACE * GuiContext->Scale) / 2;
  mBootPicker.Hdr.Obj.OffsetY = 0;

  mBootPicker.SelectedIndex = 0;

  mBootPicker.Hdr.Obj.Children = AllocateZeroPool (NumBootEntries * sizeof (*mBootPicker.Hdr.Obj.Children));
  if (mBootPicker.Hdr.Obj.Children == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  mBootPicker.Hdr.Obj.NumChildren = NumBootEntries;

  if (GuiContext->PickerContext->TitleSuffix == NULL) {
    mVersionLabelImage.Buffer = NULL;

    mBootPickerVersionLabel.Obj.Width   = 0;
    mBootPickerVersionLabel.Obj.Height  = 0;
    mBootPickerVersionLabel.Obj.OffsetX = 0;
    mBootPickerVersionLabel.Obj.OffsetY = 0;
  } else {
    DestLen = AsciiStrLen (GuiContext->PickerContext->TitleSuffix);
    UString = AsciiStrCopyToUnicode (GuiContext->PickerContext->TitleSuffix, DestLen);

    Result = GuiGetLabel (
               &mVersionLabelImage,
               &GuiContext->FontContext,
               UString,
               DestLen,
               GuiContext->LightBackground
               );

    FreePool (UString);

    if (!Result) {
      DEBUG ((DEBUG_WARN, "OCUI: version label failed\n"));
      return Result;
    }

    mBootPickerVersionLabel.Obj.Width   = mVersionLabelImage.Width;
    mBootPickerVersionLabel.Obj.Height  = mVersionLabelImage.Height;
    mBootPickerVersionLabel.Obj.OffsetX = DrawContext->Screen.Width  - mBootPickerVersionLabel.Obj.Width;
    mBootPickerVersionLabel.Obj.OffsetY = DrawContext->Screen.Height - ((5 * mBootPickerVersionLabel.Obj.Height) / 2);
  }


//构建一个欢迎字符串,在版本信息上一行显示
  if (GuiContext->PickerContext->WelcomeSuffix == NULL) {
    mWelcomeLabelImage.Buffer = NULL;

    mBootWelcomeLabel.Obj.Width   = 0;
    mBootWelcomeLabel.Obj.Height  = 0;
    mBootWelcomeLabel.Obj.OffsetX = 0;
    mBootWelcomeLabel.Obj.OffsetY = 0;
  } else {
    DestLen = AsciiStrLen (GuiContext->PickerContext->WelcomeSuffix);
    UString = AsciiStrCopyToUnicode (GuiContext->PickerContext->WelcomeSuffix, DestLen);

    Result = GuiGetLabel (
      &mWelcomeLabelImage,
      &GuiContext->FontContext,
      UString,
      DestLen,
      GuiContext->LightBackground
      );

    FreePool (UString);

    if (!Result) {
      DEBUG ((DEBUG_WARN, "OCUI: welcome label failed\n"));
      return Result;
    }

    mBootWelcomeLabel.Obj.Width   = mWelcomeLabelImage.Width;
    mBootWelcomeLabel.Obj.Height  = mWelcomeLabelImage.Height;
    mBootWelcomeLabel.Obj.OffsetX = 0;
    mBootWelcomeLabel.Obj.OffsetY = DrawContext->Screen.Height - ((5 * mBootWelcomeLabel.Obj.Height) / 2);
  }

//构建结束

  // TODO: animations should be tied to UI objects, not global
  // Each object has its own list of animations.
  // How to animate addition of one or more boot entries?
  // 1. MOVE:
  //    - Calculate the delta by which each entry moves to the left or to the right.
  //      ∆i = (N(added after) - N(added before))
  // Conditions for delta function:
  //

  InitializeListHead (&mBootPickerLabelAnimation.Link);

  if (GuiContext->UseMenuEaseIn) {
    InitBpAnimIntro (DrawContext);
    InsertHeadList (&DrawContext->Animations, &mBootPickerIntroAnimation.Link);
    //
    // Fade-in picker, scroll buttons, and action buttons.
    //
    mBootPickerContainer.Obj.Opacity       = 0;
    mBootPickerLeftScroll.Hdr.Obj.Opacity  = 0;
    mBootPickerRightScroll.Hdr.Obj.Opacity = 0;
  } else {
    //
    // The late code assumes the scroll buttons are visible by default.
    //
    mBootPickerLeftScroll.Hdr.Obj.Opacity  = 0xFF;
    mBootPickerRightScroll.Hdr.Obj.Opacity = 0xFF;
    //
    // Unhide action buttons immediately if they are not animated.
    //
    mCommonActionButtonsContainer.Obj.Opacity = 0xFF;
  }

  if (  !GuiContext->PickerContext->PickerAudioAssist
     && (DrawContext->TimeOutSeconds > 0))
  {
    STATIC GUI_ANIMATION  PickerAnim2;
    PickerAnim2.Context = NULL;
    PickerAnim2.Animate = InternalBootPickerAnimateTimeout;
    InsertHeadList (&DrawContext->Animations, &PickerAnim2.Link);
  }

  /*
  InitBpAnimImageList(GuiInterpolTypeLinear, 25, 25);
  STATIC GUI_ANIMATION PoofAnim;
  PoofAnim.Context = GuiContext->Poof;
  PoofAnim.Animate = InternalBootPickerAnimateImageList;
  InsertHeadList(&DrawContext->Animations, &PoofAnim.Link);
  */

  return EFI_SUCCESS;
}

VOID
BootPickerViewLateInitialize (
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN     BOOT_PICKER_GUI_CONTEXT  *GuiContext,
  IN     UINT8                    DefaultIndex
  )
{
  CONST GUI_VOLUME_ENTRY  *BootEntry;

  ASSERT (DefaultIndex < mBootPicker.Hdr.Obj.NumChildren);

  mBootPicker.SelectedIndex = DefaultIndex;

  //
  // If more entries than screen width, firstly align left-most entry
  // then scroll from there as needed to bring default entry on screen
  //
  if (mBootPicker.Hdr.Obj.OffsetX < 0) {
    mBootPicker.Hdr.Obj.OffsetX = 0;
    mBootPicker.Hdr.Obj.OffsetX = InternelBootPickerScrollSelected ();
  }

  //
  // If the scroll buttons are hidden, the intro animation will update them
  // implicitly.
  //
  if (mBootPickerLeftScroll.Hdr.Obj.Opacity == 0xFF) {
    InternalUpdateScrollButtons ();
  }

  InternalBootPickerSelectEntry (&mBootPicker, NULL, DefaultIndex);
  BootEntry = InternalGetVolumeEntry (DefaultIndex);
  InternalStartAnimateLabel (DrawContext, BootEntry);
  GuiContext->BootEntry = BootEntry->Context;
}

VOID
BootPickerViewDeinitialize (
  IN OUT GUI_DRAWING_CONTEXT      *DrawContext,
  IN OUT BOOT_PICKER_GUI_CONTEXT  *GuiContext
  )
{
  UINT32  Index;

  if (mVersionLabelImage.Buffer != NULL) {
    FreePool (mVersionLabelImage.Buffer);
    mVersionLabelImage.Buffer = NULL;
  }

  for (Index = 0; Index < mBootPicker.Hdr.Obj.NumChildren; ++Index) {
    InternalBootPickerEntryDestruct (InternalGetVolumeEntry (Index));
  }

  if (mBootPicker.Hdr.Obj.Children != NULL) {
    FreePool (mBootPicker.Hdr.Obj.Children);
  }

  mBootPicker.Hdr.Obj.NumChildren = 0;

  GuiViewDeinitialize (DrawContext, GuiContext);
}
