/** @file This file is part of OpenCanopy, OpenCore GUI. Copyright (c) 2018-2019, Download-Fritz. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause **/ #include #include #include #include #include #include #include #include #include #include #include #include #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, &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 = { INITIALIZE_LIST_HEAD_VARIABLE (mBootPickerLabelAnimation.Link), 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; if (mBootPicker.Hdr.Obj.NumChildren == 1) { 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 (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 // if (GuiContext->HideAuxiliary) { GuiContext->HideAuxiliary = FALSE; GuiContext->Refresh = TRUE; DrawContext->GuiContext->PickerContext->PlayAudioFile ( DrawContext->GuiContext->PickerContext, OcVoiceOverAudioFileShowAuxiliary, FALSE ); } } else if (KeyEvent->OcKeyCode == OC_INPUT_ABORTED) { GuiContext->Refresh = TRUE; DrawContext->GuiContext->PickerContext->PlayAudioFile ( DrawContext->GuiContext->PickerContext, OcVoiceOverAudioFileReloading, FALSE ); } } STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL mBootPickerLabelLeftShadowBuffer[2 * BOOT_LABEL_SHADOW_WIDTH * 2 * BOOT_LABEL_SHADOW_HEIGHT]; STATIC GUI_IMAGE mBootPickerLabelLeftShadowImage; STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL mBootPickerLabelRightShadowBuffer[2 * BOOT_LABEL_SHADOW_WIDTH * 2 * BOOT_LABEL_SHADOW_HEIGHT]; STATIC GUI_IMAGE mBootPickerLabelRightShadowImage; VOID InternalInitialiseLabelShadows ( IN CONST GUI_DRAWING_CONTEXT *DrawContext, IN BOOT_PICKER_GUI_CONTEXT *GuiContext ) { UINT32 RowOffset; UINT32 ColumnOffset; UINT32 MaxOffset; UINT8 Opacity; EFI_GRAPHICS_OUTPUT_BLT_PIXEL LeftPixel; EFI_GRAPHICS_OUTPUT_BLT_PIXEL RightPixel; ASSERT (DrawContext->Scale <= 2); mBootPickerLabelLeftShadowImage.Width = BOOT_LABEL_SHADOW_WIDTH * DrawContext->Scale; mBootPickerLabelLeftShadowImage.Height = BOOT_LABEL_SHADOW_HEIGHT * DrawContext->Scale; mBootPickerLabelLeftShadowImage.Buffer = mBootPickerLabelLeftShadowBuffer; mBootPickerLabelRightShadowImage.Width = BOOT_LABEL_SHADOW_WIDTH * DrawContext->Scale; mBootPickerLabelRightShadowImage.Height = BOOT_LABEL_SHADOW_HEIGHT * DrawContext->Scale; mBootPickerLabelRightShadowImage.Buffer = mBootPickerLabelRightShadowBuffer; MaxOffset = mBootPickerLabelLeftShadowImage.Width * mBootPickerLabelLeftShadowImage.Height; for ( ColumnOffset = 0; ColumnOffset < mBootPickerLabelLeftShadowImage.Width; ++ColumnOffset ) { Opacity = (UINT8) (((ColumnOffset + 1) * 0xFF) / (mBootPickerLabelLeftShadowImage.Width + 1)); LeftPixel.Blue = RGB_APPLY_OPACITY ( GuiContext->BackgroundColor.Pixel.Blue, 0xFF - Opacity ); LeftPixel.Green = RGB_APPLY_OPACITY ( GuiContext->BackgroundColor.Pixel.Green, 0xFF - Opacity ); LeftPixel.Red = RGB_APPLY_OPACITY ( GuiContext->BackgroundColor.Pixel.Red, 0xFF - Opacity ); LeftPixel.Reserved = 0xFF - Opacity; RightPixel.Blue = RGB_APPLY_OPACITY ( GuiContext->BackgroundColor.Pixel.Blue, Opacity ); RightPixel.Green = RGB_APPLY_OPACITY ( GuiContext->BackgroundColor.Pixel.Green, Opacity ); RightPixel.Red = RGB_APPLY_OPACITY ( GuiContext->BackgroundColor.Pixel.Red, Opacity ); RightPixel.Reserved = Opacity; for ( RowOffset = 0; RowOffset < MaxOffset; RowOffset += mBootPickerLabelLeftShadowImage.Width ) { mBootPickerLabelLeftShadowBuffer[RowOffset + ColumnOffset].Blue = LeftPixel.Blue; mBootPickerLabelLeftShadowBuffer[RowOffset + ColumnOffset].Green = LeftPixel.Green; mBootPickerLabelLeftShadowBuffer[RowOffset + ColumnOffset].Red = LeftPixel.Red; mBootPickerLabelLeftShadowBuffer[RowOffset + ColumnOffset].Reserved = LeftPixel.Reserved; mBootPickerLabelRightShadowBuffer[RowOffset + ColumnOffset].Blue = RightPixel.Blue; mBootPickerLabelRightShadowBuffer[RowOffset + ColumnOffset].Green = RightPixel.Green; mBootPickerLabelRightShadowBuffer[RowOffset + ColumnOffset].Red = RightPixel.Red; mBootPickerLabelRightShadowBuffer[RowOffset + ColumnOffset].Reserved = Opacity; } } } 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 ) { 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 ); } // // Draw the label horizontally centered. // // // FIXME: Apple allows the label to be up to 340px wide, // but OpenCanopy can't display it now (it would overlap adjacent entries) // //ASSERT (Label->Width <= BOOT_ENTRY_DIMENSION * DrawContext->Scale); ASSERT (Label->Height <= BOOT_ENTRY_LABEL_HEIGHT * DrawContext->Scale); GuiDrawChildImage ( Label, Opacity, DrawContext, BaseX, BaseY, Entry->LabelOffset, This->Height - Label->Height, OffsetX, OffsetY, Width, Height ); // // If the label needs scrolling, draw a second one to get a wraparound effect. // if (Entry->Label.Width > This->Width) { GuiDrawChildImage ( Label, Opacity, DrawContext, BaseX, BaseY, (INT64) Entry->LabelOffset + Entry->Label.Width + BOOT_LABEL_WRAPAROUND_PADDING * DrawContext->Scale, This->Height - Label->Height, OffsetX, OffsetY, Width, Height ); if (Entry->ShowLeftShadow) { GuiDrawChildImage ( &mBootPickerLabelLeftShadowImage, Opacity, DrawContext, BaseX, BaseY, 0, This->Height - mBootPickerLabelLeftShadowImage.Height, OffsetX, OffsetY, Width, Height ); } GuiDrawChildImage ( &mBootPickerLabelRightShadowImage, Opacity, DrawContext, BaseX, BaseY, This->Width - mBootPickerLabelRightShadowImage.Width, This->Height - mBootPickerLabelRightShadowImage.Height, OffsetX, OffsetY, Width, 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 ); } } 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, 0 ); 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 *mBootPickerSelectorContainerChilds[] = { &mBootPickerSelectorBackground, &mBootPickerSelectorButton.Hdr }; GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD mBootPickerSelectorContainer = { { 0, 0, 0, 0, 0xFF, GuiObjDrawDelegate, NULL, GuiObjDelegatePtrEvent, NULL, ARRAY_SIZE (mBootPickerSelectorContainerChilds), mBootPickerSelectorContainerChilds }, &mBootPickerContainer.Obj }; STATIC GUI_OBJ_CHILD *mBootPickerContainerChilds[] = { &mBootPickerSelectorContainer, &mBootPicker.Hdr }; GLOBAL_REMOVE_IF_UNREFERENCED GUI_OBJ_CHILD mBootPickerContainer = { { 0, 0, 0, 0, 0xFF, GuiObjDrawDelegate, NULL, GuiObjDelegatePtrEvent, NULL, ARRAY_SIZE (mBootPickerContainerChilds), mBootPickerContainerChilds }, 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 }; STATIC GUI_OBJ_CHILD *mBootPickerViewChilds[] = { &mBootPickerContainer, &mCommonActionButtonsContainer, &mBootPickerLeftScroll.Hdr, &mBootPickerRightScroll.Hdr, &mBootPickerVersionLabel }; STATIC GUI_OBJ_CHILD *mBootPickerViewChildsMinimal[] = { &mBootPickerContainer, &mBootPickerLeftScroll.Hdr, &mBootPickerRightScroll.Hdr, &mBootPickerVersionLabel }; GLOBAL_REMOVE_IF_UNREFERENCED GUI_VIEW_CONTEXT mBootPickerViewContext = { InternalCommonViewDraw, InternalCommonViewPtrEvent, ARRAY_SIZE (mBootPickerViewChilds), mBootPickerViewChilds, InternalBootPickerViewKeyEvent, InternalGetCursorImage, InternalBootPickerExitLoop, mBootPickerFocusList, ARRAY_SIZE (mBootPickerFocusList) }; GLOBAL_REMOVE_IF_UNREFERENCED GUI_VIEW_CONTEXT mBootPickerViewContextMinimal = { InternalCommonViewDraw, InternalCommonViewPtrEvent, ARRAY_SIZE (mBootPickerViewChildsMinimal), mBootPickerViewChildsMinimal, InternalBootPickerViewKeyEvent, InternalGetCursorImage, InternalBootPickerExitLoop, mBootPickerFocusListMinimal, ARRAY_SIZE (mBootPickerFocusListMinimal) }; STATIC EFI_STATUS CopyLabel ( OUT GUI_IMAGE *Destination, IN CONST GUI_IMAGE *Source ) { 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; case OC_BOOT_RESET_NVRAM: Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_RESET_NVRAM]); break; case OC_BOOT_TOGGLE_SIP: ASSERT ( StrCmp (Entry->Name, OC_MENU_SIP_IS_DISABLED) == 0 || StrCmp (Entry->Name, OC_MENU_SIP_IS_ENABLED) == 0 ); if (StrCmp (Entry->Name, OC_MENU_SIP_IS_DISABLED) == 0) { Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_SIP_IS_DISABLED]); } else { Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_SIP_IS_ENABLED]); } break; 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_UEFI_SHELL) != NULL) { Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_SHELL]); } else { Status = CopyLabel (&VolumeEntry->Label, &GuiContext->Labels[LABEL_TOOL]); } 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)); return EFI_UNSUPPORTED; } } if (EFI_ERROR (Status)) { EntryName = Entry->Name; EntryNameLength = StrLen (Entry->Name); if (Entry->IsFolder) { EntryName = AllocatePool (EntryNameLength * sizeof (CHAR16) + L_STR_SIZE (L" (dmg)")); if (EntryName != NULL) { CopyMem (EntryName, Entry->Name, EntryNameLength * sizeof (CHAR16)); CopyMem (&EntryName[EntryNameLength], L" (dmg)", L_STR_SIZE (L" (dmg)")); EntryNameLength += L_STR_LEN (L" (dmg)"); } 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 || (Entry->Type & 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 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; } BOOLEAN InternalBootPickerAnimateIntro ( IN BOOT_PICKER_GUI_CONTEXT *Context, IN OUT GUI_DRAWING_CONTEXT *DrawContext, IN UINT64 CurrentTime ) { STATIC UINT32 PrevSine = 0; 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 - PrevSine; mBootPickerContainer.Obj.OffsetX -= DeltaSine; PrevSine = 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 = { INITIALIZE_LIST_HEAD_VARIABLE (mBootPickerIntroAnimation.Link), 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 ? &mBootPickerViewContext : &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 - ((3 * mBootPickerVersionLabel.Obj.Width ) / 2); mBootPickerVersionLabel.Obj.OffsetY = DrawContext->Screen.Height - ((5 * mBootPickerVersionLabel.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: // if (!GuiContext->DoneIntroAnimation) { 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; GuiContext->DoneIntroAnimation = TRUE; } 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 (DrawContext->TimeOutSeconds > 0) { STATIC GUI_ANIMATION PickerAnim2; PickerAnim2.Context = NULL; PickerAnim2.Animate = InternalBootPickerAnimateTimeout; InsertHeadList (&DrawContext->Animations, &PickerAnim2.Link); } InternalInitialiseLabelShadows (DrawContext, GuiContext); /* 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); }