mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
1293 lines
33 KiB
C
1293 lines
33 KiB
C
/** @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 <IndustryStandard/AppleDiskLabel.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/BmpSupportLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/MtrrLib.h>
|
|
#include <Library/OcBootManagementLib.h>
|
|
#include <Library/OcCompressionLib.h>
|
|
#include <Library/OcCpuLib.h>
|
|
#include <Library/OcGuardLib.h>
|
|
#include <Library/OcPngLib.h>
|
|
#include <Library/TimerLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
|
|
#include "OpenCanopy.h"
|
|
#include "GuiIo.h"
|
|
#include "GuiApp.h"
|
|
#include "Views/BootPicker.h"
|
|
|
|
typedef struct {
|
|
UINT32 X;
|
|
UINT32 Y;
|
|
UINT32 Width;
|
|
UINT32 Height;
|
|
} GUI_DRAW_REQUEST;
|
|
|
|
//
|
|
// I/O contexts
|
|
//
|
|
STATIC GUI_OUTPUT_CONTEXT *mOutputContext = NULL;
|
|
STATIC GUI_POINTER_CONTEXT *mPointerContext = NULL;
|
|
STATIC GUI_KEY_CONTEXT *mKeyContext = NULL;
|
|
//
|
|
// Screen buffer information
|
|
//
|
|
STATIC EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mScreenBuffer = NULL;
|
|
STATIC UINT32 mScreenBufferDelta = 0;
|
|
//
|
|
// Frame timing information (60 FPS)
|
|
//
|
|
STATIC UINT64 mDeltaTscTarget = 0;
|
|
STATIC UINT64 mStartTsc = 0;
|
|
//
|
|
// Drawing rectangles information
|
|
//
|
|
STATIC UINT8 mNumValidDrawReqs = 0;
|
|
STATIC GUI_DRAW_REQUEST mDrawRequests[4] = { { 0 } };
|
|
|
|
STATIC INT64 mPointerOldBaseX = 0;
|
|
STATIC INT64 mPointerOldBaseY = 0;
|
|
|
|
#define PIXEL_TO_UINT32(Pixel) \
|
|
((UINT32) SIGNATURE_32 ((Pixel)->Blue, (Pixel)->Green, (Pixel)->Red, (Pixel)->Reserved))
|
|
|
|
BOOLEAN
|
|
GuiClipChildBounds (
|
|
IN INT64 ChildOffset,
|
|
IN UINT32 ChildLength,
|
|
IN OUT UINT32 *ReqOffset,
|
|
IN OUT UINT32 *ReqLength
|
|
)
|
|
{
|
|
UINT32 PosChildOffset;
|
|
UINT32 NegChildOffset;
|
|
UINT32 OffsetDelta;
|
|
UINT32 NewOffset;
|
|
UINT32 NewLength;
|
|
|
|
ASSERT (ReqOffset != NULL);
|
|
ASSERT (ReqLength != NULL);
|
|
|
|
if (ChildOffset >= 0) {
|
|
PosChildOffset = (UINT32)ChildOffset;
|
|
NegChildOffset = 0;
|
|
} else {
|
|
if (ChildOffset + ChildLength <= 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
PosChildOffset = 0;
|
|
NegChildOffset = (UINT32) -ChildOffset;
|
|
ChildLength = (UINT32)(ChildOffset + ChildLength);
|
|
}
|
|
|
|
ASSERT (ChildLength > 0);
|
|
|
|
NewOffset = *ReqOffset;
|
|
NewLength = *ReqLength;
|
|
|
|
if (NewOffset >= PosChildOffset) {
|
|
//
|
|
// The requested offset starts within or past the child.
|
|
//
|
|
OffsetDelta = NewOffset - PosChildOffset;
|
|
if (ChildLength <= OffsetDelta) {
|
|
//
|
|
// The requested offset starts past the child.
|
|
//
|
|
return FALSE;
|
|
}
|
|
//
|
|
// The requested offset starts within the child.
|
|
//
|
|
NewOffset -= PosChildOffset;
|
|
NewOffset += NegChildOffset;
|
|
} else {
|
|
//
|
|
// The requested offset ends within or before the child.
|
|
//
|
|
OffsetDelta = PosChildOffset - NewOffset;
|
|
if (NewLength <= OffsetDelta) {
|
|
//
|
|
// The requested offset ends before the child.
|
|
//
|
|
return FALSE;
|
|
}
|
|
//
|
|
// The requested offset ends within the child.
|
|
//
|
|
NewOffset = NegChildOffset;
|
|
NewLength -= OffsetDelta;
|
|
}
|
|
|
|
*ReqOffset = NewOffset;
|
|
*ReqLength = NewLength;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
GuiObjDrawDelegate (
|
|
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
|
|
)
|
|
{
|
|
BOOLEAN Result;
|
|
|
|
LIST_ENTRY *ChildEntry;
|
|
GUI_OBJ_CHILD *Child;
|
|
|
|
UINT32 ChildDrawOffsetX;
|
|
UINT32 ChildDrawOffsetY;
|
|
UINT32 ChildDrawWidth;
|
|
UINT32 ChildDrawHeight;
|
|
|
|
ASSERT (This != NULL);
|
|
ASSERT (This->Width > OffsetX);
|
|
ASSERT (This->Height > OffsetY);
|
|
ASSERT (DrawContext != NULL);
|
|
|
|
Width = MIN (Width, This->Width - OffsetX);
|
|
Height = MIN (Height, This->Height - OffsetY);
|
|
|
|
for (
|
|
ChildEntry = GetPreviousNode (&This->Children, &This->Children);
|
|
!IsNull (&This->Children, ChildEntry);
|
|
ChildEntry = GetPreviousNode (&This->Children, ChildEntry)
|
|
) {
|
|
Child = BASE_CR (ChildEntry, GUI_OBJ_CHILD, Link);
|
|
|
|
ChildDrawOffsetX = OffsetX;
|
|
ChildDrawWidth = Width;
|
|
Result = GuiClipChildBounds (
|
|
Child->Obj.OffsetX,
|
|
Child->Obj.Width,
|
|
&ChildDrawOffsetX,
|
|
&ChildDrawWidth
|
|
);
|
|
if (!Result) {
|
|
continue;
|
|
}
|
|
|
|
ChildDrawOffsetY = OffsetY;
|
|
ChildDrawHeight = Height;
|
|
Result = GuiClipChildBounds (
|
|
Child->Obj.OffsetY,
|
|
Child->Obj.Height,
|
|
&ChildDrawOffsetY,
|
|
&ChildDrawHeight
|
|
);
|
|
if (!Result) {
|
|
continue;
|
|
}
|
|
|
|
ASSERT (ChildDrawWidth > 0);
|
|
ASSERT (ChildDrawHeight > 0);
|
|
ASSERT (Child->Obj.Draw != NULL);
|
|
Child->Obj.Draw (
|
|
&Child->Obj,
|
|
DrawContext,
|
|
Context,
|
|
BaseX + Child->Obj.OffsetX,
|
|
BaseY + Child->Obj.OffsetY,
|
|
ChildDrawOffsetX,
|
|
ChildDrawOffsetY,
|
|
ChildDrawWidth,
|
|
ChildDrawHeight
|
|
);
|
|
}
|
|
}
|
|
|
|
GUI_OBJ *
|
|
GuiObjDelegatePtrEvent (
|
|
IN OUT GUI_OBJ *This,
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN BOOT_PICKER_GUI_CONTEXT *Context,
|
|
IN GUI_PTR_EVENT Event,
|
|
IN INT64 BaseX,
|
|
IN INT64 BaseY,
|
|
IN INT64 OffsetX,
|
|
IN INT64 OffsetY
|
|
)
|
|
{
|
|
GUI_OBJ *Obj;
|
|
LIST_ENTRY *ChildEntry;
|
|
GUI_OBJ_CHILD *Child;
|
|
|
|
ASSERT (This != NULL);
|
|
ASSERT (This->Width > OffsetX);
|
|
ASSERT (This->Height > OffsetY);
|
|
ASSERT (DrawContext != NULL);
|
|
|
|
for (
|
|
ChildEntry = GetFirstNode (&This->Children);
|
|
!IsNull (&This->Children, ChildEntry);
|
|
ChildEntry = GetNextNode (&This->Children, ChildEntry)
|
|
) {
|
|
Child = BASE_CR (ChildEntry, GUI_OBJ_CHILD, Link);
|
|
if (OffsetX < Child->Obj.OffsetX
|
|
|| OffsetX >= Child->Obj.OffsetX + Child->Obj.Width
|
|
|| OffsetY < Child->Obj.OffsetY
|
|
|| OffsetY >= Child->Obj.OffsetY + Child->Obj.Height) {
|
|
continue;
|
|
}
|
|
|
|
ASSERT (Child->Obj.PtrEvent != NULL);
|
|
Obj = Child->Obj.PtrEvent (
|
|
&Child->Obj,
|
|
DrawContext,
|
|
Context,
|
|
Event,
|
|
BaseX + Child->Obj.OffsetX,
|
|
BaseY + Child->Obj.OffsetY,
|
|
OffsetX - Child->Obj.OffsetX,
|
|
OffsetY - Child->Obj.OffsetY
|
|
);
|
|
if (Obj != NULL) {
|
|
return Obj;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID
|
|
GuiDrawToBufferFill (
|
|
IN CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Colour,
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN UINT32 PosX,
|
|
IN UINT32 PosY,
|
|
IN UINT32 Width,
|
|
IN UINT32 Height
|
|
)
|
|
{
|
|
UINT32 RowIndex;
|
|
UINT32 TargetRowOffset;
|
|
|
|
ASSERT (Colour != NULL);
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (DrawContext->Screen != NULL);
|
|
ASSERT (Width > 0);
|
|
ASSERT (Height > 0);
|
|
//
|
|
// Screen cropping happens in GuiRequestDrawCrop().
|
|
//
|
|
ASSERT (DrawContext->Screen->Width >= PosX);
|
|
ASSERT (DrawContext->Screen->Height >= PosY);
|
|
ASSERT (PosX + Width <= DrawContext->Screen->Width);
|
|
ASSERT (PosY + Height <= DrawContext->Screen->Height);
|
|
//
|
|
// Iterate over each row of the request.
|
|
//
|
|
for (
|
|
RowIndex = 0,
|
|
TargetRowOffset = PosY * DrawContext->Screen->Width;
|
|
RowIndex < Height;
|
|
++RowIndex,
|
|
TargetRowOffset += DrawContext->Screen->Width
|
|
) {
|
|
//
|
|
// Populate the row pixel-by-pixel with Source's (0,0).
|
|
//
|
|
SetMem32 (
|
|
&mScreenBuffer[TargetRowOffset + PosX],
|
|
Width * sizeof (UINT32),
|
|
PIXEL_TO_UINT32 (Colour)
|
|
);
|
|
}
|
|
|
|
//
|
|
// TODO: Support opaque fill?
|
|
//
|
|
|
|
#if 0
|
|
//
|
|
// Iterate over each row of the request.
|
|
//
|
|
for (
|
|
RowIndex = 0,
|
|
TargetRowOffset = PosY * DrawContext->Screen->Width;
|
|
RowIndex < Height;
|
|
++RowIndex,
|
|
TargetRowOffset += DrawContext->Screen->Width
|
|
) {
|
|
//
|
|
// Blend the row pixel-by-pixel with Source's (0,0).
|
|
//
|
|
for (
|
|
TargetColumnOffset = PosY;
|
|
TargetColumnOffset < PosY + Width;
|
|
++TargetColumnOffset
|
|
) {
|
|
TargetPixel = &mScreenBuffer[TargetRowOffset + TargetColumnOffset];
|
|
GuiBlendPixel (TargetPixel, &Image->Buffer[0], Opacity);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
VOID
|
|
GuiDrawToBuffer (
|
|
IN CONST GUI_IMAGE *Image,
|
|
IN UINT8 Opacity,
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN INT64 BaseX,
|
|
IN INT64 BaseY,
|
|
IN UINT32 OffsetX,
|
|
IN UINT32 OffsetY,
|
|
IN UINT32 Width,
|
|
IN UINT32 Height
|
|
)
|
|
{
|
|
UINT32 PosX;
|
|
UINT32 PosY;
|
|
|
|
UINT32 RowIndex;
|
|
UINT32 SourceRowOffset;
|
|
UINT32 TargetRowOffset;
|
|
UINT32 SourceColumnOffset;
|
|
UINT32 TargetColumnOffset;
|
|
CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *SourcePixel;
|
|
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *TargetPixel;
|
|
|
|
ASSERT (Image != NULL);
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (DrawContext->Screen != NULL);
|
|
ASSERT (BaseX + OffsetX >= 0);
|
|
ASSERT (BaseY + OffsetY >= 0);
|
|
ASSERT (Width > 0);
|
|
ASSERT (Height > 0);
|
|
ASSERT (BaseX + OffsetX + Width >= 0);
|
|
ASSERT (BaseY + OffsetY + Height >= 0);
|
|
ASSERT (BaseX + OffsetX + Width <= MAX_UINT32);
|
|
ASSERT (BaseY + OffsetY + Height <= MAX_UINT32);
|
|
|
|
PosX = (UINT32) (BaseX + OffsetX);
|
|
PosY = (UINT32) (BaseY + OffsetY);
|
|
//
|
|
// Screen cropping happens in GuiRequestDrawCrop().
|
|
//
|
|
ASSERT (DrawContext->Screen->Width >= PosX);
|
|
ASSERT (DrawContext->Screen->Height >= PosY);
|
|
ASSERT (PosX + Width <= DrawContext->Screen->Width);
|
|
ASSERT (PosY + Height <= DrawContext->Screen->Height);
|
|
|
|
if (Opacity == 0) {
|
|
return;
|
|
}
|
|
|
|
ASSERT (Image->Width > OffsetX);
|
|
ASSERT (Image->Height > OffsetY);
|
|
//
|
|
// Only crop to the image's dimensions when not using fill-drawing.
|
|
//
|
|
Width = MIN (Width, Image->Width - OffsetX);
|
|
Height = MIN (Height, Image->Height - OffsetY);
|
|
if (Width == 0 || Height == 0) {
|
|
return;
|
|
}
|
|
|
|
ASSERT (Image->Buffer != NULL);
|
|
|
|
if (Opacity == 0xFF) {
|
|
//
|
|
// Iterate over each row of the request.
|
|
//
|
|
for (
|
|
RowIndex = 0,
|
|
SourceRowOffset = OffsetY * Image->Width,
|
|
TargetRowOffset = PosY * DrawContext->Screen->Width;
|
|
RowIndex < Height;
|
|
++RowIndex,
|
|
SourceRowOffset += Image->Width,
|
|
TargetRowOffset += DrawContext->Screen->Width
|
|
) {
|
|
//
|
|
// Blend the row pixel-by-pixel.
|
|
//
|
|
for (
|
|
TargetColumnOffset = PosX, SourceColumnOffset = OffsetX;
|
|
TargetColumnOffset < PosX + Width;
|
|
++TargetColumnOffset, ++SourceColumnOffset
|
|
) {
|
|
TargetPixel = &mScreenBuffer[TargetRowOffset + TargetColumnOffset];
|
|
SourcePixel = &Image->Buffer[SourceRowOffset + SourceColumnOffset];
|
|
GuiBlendPixelSolid (TargetPixel, SourcePixel);
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// Iterate over each row of the request.
|
|
//
|
|
for (
|
|
RowIndex = 0,
|
|
SourceRowOffset = OffsetY * Image->Width,
|
|
TargetRowOffset = PosY * DrawContext->Screen->Width;
|
|
RowIndex < Height;
|
|
++RowIndex,
|
|
SourceRowOffset += Image->Width,
|
|
TargetRowOffset += DrawContext->Screen->Width
|
|
) {
|
|
//
|
|
// Blend the row pixel-by-pixel.
|
|
//
|
|
for (
|
|
TargetColumnOffset = PosX, SourceColumnOffset = OffsetX;
|
|
TargetColumnOffset < PosX + Width;
|
|
++TargetColumnOffset, ++SourceColumnOffset
|
|
) {
|
|
TargetPixel = &mScreenBuffer[TargetRowOffset + TargetColumnOffset];
|
|
SourcePixel = &Image->Buffer[SourceRowOffset + SourceColumnOffset];
|
|
GuiBlendPixelOpaque (TargetPixel, SourcePixel, Opacity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
GuiRequestDraw (
|
|
IN UINT32 PosX,
|
|
IN UINT32 PosY,
|
|
IN UINT32 Width,
|
|
IN UINT32 Height
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
UINT32 ThisArea;
|
|
UINT32 ThisMaxX;
|
|
UINT32 ThisMaxY;
|
|
|
|
UINT32 ReqMaxX;
|
|
UINT32 ReqMaxY;
|
|
UINT32 ReqArea;
|
|
|
|
UINT32 CombX;
|
|
UINT32 CombY;
|
|
UINT32 CombWidth;
|
|
UINT32 CombHeight;
|
|
UINT32 CombArea;
|
|
|
|
ThisMaxX = PosX + Width - 1;
|
|
ThisMaxY = PosY + Height - 1;
|
|
|
|
ThisArea = Width * Height;
|
|
|
|
for (Index = 0; Index < mNumValidDrawReqs; ++Index) {
|
|
//
|
|
// Calculate several dimensions to determine whether to merge the two
|
|
// draw requests for improved flushing performance.
|
|
//
|
|
ReqMaxX = mDrawRequests[Index].X + mDrawRequests[Index].Width - 1;
|
|
ReqMaxY = mDrawRequests[Index].Y + mDrawRequests[Index].Height - 1;
|
|
ReqArea = mDrawRequests[Index].Width * mDrawRequests[Index].Height;
|
|
|
|
if (mDrawRequests[Index].X < PosX) {
|
|
CombX = mDrawRequests[Index].X;
|
|
} else {
|
|
CombX = PosX;
|
|
}
|
|
|
|
if (ReqMaxX > ThisMaxX) {
|
|
CombWidth = ReqMaxX - CombX + 1;
|
|
} else {
|
|
CombWidth = ThisMaxX - CombX + 1;
|
|
}
|
|
|
|
if (mDrawRequests[Index].Y < PosY) {
|
|
CombY = mDrawRequests[Index].Y;
|
|
} else {
|
|
CombY = PosY;
|
|
}
|
|
|
|
if (ReqMaxY > ThisMaxY) {
|
|
CombHeight = ReqMaxY - CombY + 1;
|
|
} else {
|
|
CombHeight = ThisMaxY - CombY + 1;
|
|
}
|
|
|
|
CombArea = CombWidth * CombHeight;
|
|
//
|
|
// Two requests are merged when the overarching rectangle is not bigger than
|
|
// the two separate rectangles (not accounting for the overlap, as it would
|
|
// be drawn twice).
|
|
//
|
|
// TODO: Profile a good constant factor?
|
|
//
|
|
if (ThisArea + ReqArea >= CombArea) {
|
|
mDrawRequests[Index].X = CombX;
|
|
mDrawRequests[Index].Y = CombY;
|
|
mDrawRequests[Index].Width = CombWidth;
|
|
mDrawRequests[Index].Height = CombHeight;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mNumValidDrawReqs >= ARRAY_SIZE (mDrawRequests)) {
|
|
ASSERT (FALSE);
|
|
return;
|
|
}
|
|
|
|
mDrawRequests[mNumValidDrawReqs].X = PosX;
|
|
mDrawRequests[mNumValidDrawReqs].Y = PosY;
|
|
mDrawRequests[mNumValidDrawReqs].Width = Width;
|
|
mDrawRequests[mNumValidDrawReqs].Height = Height;
|
|
++mNumValidDrawReqs;
|
|
}
|
|
|
|
VOID
|
|
GuiRequestDrawCrop (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN INT64 X,
|
|
IN INT64 Y,
|
|
IN UINT32 Width,
|
|
IN UINT32 Height
|
|
)
|
|
{
|
|
UINT32 PosX;
|
|
UINT32 PosY;
|
|
INT64 EffWidth;
|
|
INT64 EffHeight;
|
|
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (DrawContext->Screen != NULL);
|
|
|
|
EffWidth = Width;
|
|
EffHeight = Height;
|
|
//
|
|
// Only draw the onscreen parts.
|
|
//
|
|
if (X >= 0) {
|
|
PosX = (UINT32)X;
|
|
} else {
|
|
EffWidth += X;
|
|
PosX = 0;
|
|
}
|
|
|
|
if (Y >= 0) {
|
|
PosY = (UINT32)Y;
|
|
} else {
|
|
EffHeight += Y;
|
|
PosY = 0;
|
|
}
|
|
|
|
EffWidth = MIN (EffWidth, (INT64) DrawContext->Screen->Width - PosX);
|
|
EffHeight = MIN (EffHeight, (INT64) DrawContext->Screen->Height - PosY);
|
|
|
|
if (EffWidth <= 0 || EffHeight <= 0) {
|
|
return;
|
|
}
|
|
|
|
GuiRequestDraw (PosX, PosY, (UINT32) EffWidth, (UINT32) EffHeight);
|
|
}
|
|
|
|
VOID
|
|
GuiOverlayPointer (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext
|
|
)
|
|
{
|
|
CONST GUI_IMAGE *CursorImage;
|
|
UINT32 MaxWidth;
|
|
UINT32 MaxHeight;
|
|
GUI_POINTER_STATE PointerState;
|
|
|
|
INT64 BaseX;
|
|
INT64 BaseY;
|
|
UINT32 ImageOffsetX;
|
|
UINT32 ImageOffsetY;
|
|
UINT32 DrawBaseX;
|
|
UINT32 DrawBaseY;
|
|
|
|
ASSERT (DrawContext != NULL);
|
|
|
|
ASSERT (DrawContext->GetCursorImage != NULL);
|
|
CursorImage = DrawContext->GetCursorImage (DrawContext->GuiContext);
|
|
ASSERT (CursorImage != NULL);
|
|
//
|
|
// Poll the current cursor position late to reduce input lag.
|
|
//
|
|
GuiPointerGetState (mPointerContext, &PointerState);
|
|
|
|
ASSERT (PointerState.X < DrawContext->Screen->Width);
|
|
ASSERT (PointerState.Y < DrawContext->Screen->Height);
|
|
|
|
//
|
|
// Unconditionally draw the cursor to increase frametime consistency and
|
|
// prevent situational hiding.
|
|
//
|
|
// The original area of the cursor is restored at the beginning of the main
|
|
// drawing loop.
|
|
//
|
|
|
|
//
|
|
// Draw the new cursor at the new position.
|
|
//
|
|
|
|
BaseX = (INT64) PointerState.X - BOOT_CURSOR_OFFSET * DrawContext->Scale;
|
|
if (BaseX < 0) {
|
|
ImageOffsetX = (UINT32) -BaseX;
|
|
DrawBaseX = 0;
|
|
} else {
|
|
ImageOffsetX = 0;
|
|
DrawBaseX = (UINT32) BaseX;
|
|
}
|
|
|
|
MaxWidth = MIN (CursorImage->Width, (UINT32) (DrawContext->Screen->Width - BaseX));
|
|
|
|
BaseY = (INT64) PointerState.Y - BOOT_CURSOR_OFFSET * DrawContext->Scale;
|
|
if (BaseY < 0) {
|
|
ImageOffsetY = (UINT32) -BaseY;
|
|
DrawBaseY = 0;
|
|
} else {
|
|
ImageOffsetY = 0;
|
|
DrawBaseY = (UINT32) BaseY;
|
|
}
|
|
|
|
MaxHeight = MIN (CursorImage->Height, (UINT32) (DrawContext->Screen->Height - BaseY));
|
|
|
|
GuiDrawToBuffer (
|
|
CursorImage,
|
|
0xFF,
|
|
DrawContext,
|
|
BaseX,
|
|
BaseY,
|
|
ImageOffsetX,
|
|
ImageOffsetY,
|
|
MaxWidth,
|
|
MaxHeight
|
|
);
|
|
//
|
|
// Queue a draw request for the newly drawn cursor.
|
|
//
|
|
GuiRequestDraw (
|
|
DrawBaseX,
|
|
DrawBaseY,
|
|
MaxWidth,
|
|
MaxHeight
|
|
);
|
|
|
|
mPointerOldBaseX = DrawBaseX;
|
|
mPointerOldBaseY = DrawBaseY;
|
|
}
|
|
|
|
/**
|
|
Stalls the CPU for at least the given number of ticks.
|
|
|
|
Stalls the CPU for at least the given number of ticks. It's invoked by
|
|
MicroSecondDelay() and NanoSecondDelay().
|
|
|
|
@param Delay A period of time to delay in ticks.
|
|
|
|
**/
|
|
STATIC
|
|
UINT64
|
|
InternalCpuDelayTsc (
|
|
IN UINT64 Delay
|
|
)
|
|
{
|
|
UINT64 Ticks;
|
|
UINT64 Tsc;
|
|
|
|
//
|
|
// The target timer count is calculated here
|
|
//
|
|
Ticks = AsmReadTsc () + Delay;
|
|
|
|
//
|
|
// Wait until time out
|
|
// Timer wrap-arounds are NOT handled correctly by this function.
|
|
// Thus, this function must be called within 10 years of reset since
|
|
// Intel guarantees a minimum of 10 years before the TSC wraps.
|
|
//
|
|
while ((Tsc = AsmReadTsc ()) < Ticks) {
|
|
CpuPause ();
|
|
}
|
|
|
|
return Tsc;
|
|
}
|
|
|
|
VOID
|
|
GuiFlushScreen (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext
|
|
)
|
|
{
|
|
EFI_TPL OldTpl;
|
|
|
|
UINTN Index;
|
|
|
|
UINT64 EndTsc;
|
|
UINT64 DeltaTsc;
|
|
|
|
BOOLEAN Interrupts;
|
|
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (DrawContext->Screen != NULL);
|
|
ASSERT (DrawContext->Screen->OffsetX == 0);
|
|
ASSERT (DrawContext->Screen->OffsetY == 0);
|
|
ASSERT (DrawContext->Screen->Draw != NULL);
|
|
for (Index = 0; Index < mNumValidDrawReqs; ++Index) {
|
|
DrawContext->Screen->Draw (
|
|
DrawContext->Screen,
|
|
DrawContext,
|
|
DrawContext->GuiContext,
|
|
0,
|
|
0,
|
|
mDrawRequests[Index].X,
|
|
mDrawRequests[Index].Y,
|
|
mDrawRequests[Index].Width,
|
|
mDrawRequests[Index].Height
|
|
);
|
|
}
|
|
//
|
|
// Raise the TPL to not interrupt timing or flushing.
|
|
//
|
|
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
|
|
Interrupts = SaveAndDisableInterrupts ();
|
|
|
|
EndTsc = AsmReadTsc ();
|
|
DeltaTsc = EndTsc - mStartTsc;
|
|
if (DeltaTsc < mDeltaTscTarget) {
|
|
EndTsc = InternalCpuDelayTsc (mDeltaTscTarget - DeltaTsc);
|
|
}
|
|
|
|
if (mPointerContext != NULL) {
|
|
GuiOverlayPointer (DrawContext);
|
|
}
|
|
|
|
for (Index = 0; Index < mNumValidDrawReqs; ++Index) {
|
|
GuiOutputBlt (
|
|
mOutputContext,
|
|
mScreenBuffer,
|
|
EfiBltBufferToVideo,
|
|
mDrawRequests[Index].X,
|
|
mDrawRequests[Index].Y,
|
|
mDrawRequests[Index].X,
|
|
mDrawRequests[Index].Y,
|
|
mDrawRequests[Index].Width,
|
|
mDrawRequests[Index].Height,
|
|
mScreenBufferDelta
|
|
);
|
|
}
|
|
|
|
if (Interrupts) {
|
|
EnableInterrupts ();
|
|
}
|
|
gBS->RestoreTPL (OldTpl);
|
|
|
|
mNumValidDrawReqs = 0;
|
|
//
|
|
// Explicitly include BLT time in the timing calculation.
|
|
// FIXME: GOP takes inconsistently long depending on dimensions.
|
|
//
|
|
mStartTsc = EndTsc;
|
|
}
|
|
|
|
VOID
|
|
GuiRedrawAndFlushScreen (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext
|
|
)
|
|
{
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (DrawContext->Screen != NULL);
|
|
|
|
mStartTsc = AsmReadTsc ();
|
|
|
|
GuiRequestDraw (0, 0, DrawContext->Screen->Width, DrawContext->Screen->Height);
|
|
GuiFlushScreen (DrawContext);
|
|
}
|
|
|
|
EFI_STATUS
|
|
GuiLibConstruct (
|
|
IN OC_PICKER_CONTEXT *PickerContext,
|
|
IN UINT32 CursorDefaultX,
|
|
IN UINT32 CursorDefaultY
|
|
)
|
|
{
|
|
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
|
|
|
|
mOutputContext = GuiOutputConstruct ();
|
|
if (mOutputContext == NULL) {
|
|
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise output\n"));
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
OutputInfo = GuiOutputGetInfo (mOutputContext);
|
|
ASSERT (OutputInfo != NULL);
|
|
|
|
CursorDefaultX = MIN (CursorDefaultX, OutputInfo->HorizontalResolution - 1);
|
|
CursorDefaultY = MIN (CursorDefaultY, OutputInfo->VerticalResolution - 1);
|
|
|
|
if ((PickerContext->PickerAttributes & OC_ATTR_USE_POINTER_CONTROL) != 0) {
|
|
mPointerContext = GuiPointerConstruct (
|
|
CursorDefaultX,
|
|
CursorDefaultY,
|
|
OutputInfo->HorizontalResolution,
|
|
OutputInfo->VerticalResolution
|
|
);
|
|
if (mPointerContext == NULL) {
|
|
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise pointer\n"));
|
|
}
|
|
}
|
|
|
|
mKeyContext = GuiKeyConstruct (PickerContext);
|
|
if (mKeyContext == NULL) {
|
|
DEBUG ((DEBUG_WARN, "OCUI: Failed to initialise key input\n"));
|
|
}
|
|
|
|
if (mPointerContext == NULL && mKeyContext == NULL) {
|
|
GuiLibDestruct ();
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
mScreenBufferDelta = OutputInfo->HorizontalResolution * sizeof (*mScreenBuffer);
|
|
mScreenBuffer = AllocatePool (OutputInfo->VerticalResolution * mScreenBufferDelta);
|
|
if (mScreenBuffer == NULL) {
|
|
DEBUG ((DEBUG_WARN, "OCUI: GUI alloc failure\n"));
|
|
GuiLibDestruct ();
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
MtrrSetMemoryAttribute (
|
|
(EFI_PHYSICAL_ADDRESS)(UINTN) mScreenBuffer,
|
|
mScreenBufferDelta * OutputInfo->VerticalResolution,
|
|
CacheWriteBack
|
|
);
|
|
|
|
mDeltaTscTarget = DivU64x32 (OcGetTSCFrequency (), 60);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
GuiLibDestruct (
|
|
VOID
|
|
)
|
|
{
|
|
if (mOutputContext != NULL) {
|
|
GuiOutputDestruct (mOutputContext);
|
|
mOutputContext = NULL;
|
|
}
|
|
|
|
if (mPointerContext != NULL) {
|
|
GuiPointerDestruct (mPointerContext);
|
|
mPointerContext = NULL;
|
|
}
|
|
|
|
if (mKeyContext != NULL) {
|
|
GuiKeyDestruct (mKeyContext);
|
|
mKeyContext = NULL;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
GuiViewInitialize (
|
|
OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN OUT GUI_OBJ *Screen,
|
|
IN GUI_CURSOR_GET_IMAGE GetCursorImage,
|
|
IN GUI_EXIT_LOOP ExitLoop,
|
|
IN BOOT_PICKER_GUI_CONTEXT *GuiContext
|
|
)
|
|
{
|
|
CONST EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *OutputInfo;
|
|
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (Screen != NULL);
|
|
ASSERT (GetCursorImage != NULL);
|
|
ASSERT (ExitLoop != NULL);
|
|
|
|
OutputInfo = GuiOutputGetInfo (mOutputContext);
|
|
ASSERT (OutputInfo != NULL);
|
|
|
|
Screen->Width = OutputInfo->HorizontalResolution;
|
|
Screen->Height = OutputInfo->VerticalResolution;
|
|
|
|
DrawContext->Screen = Screen;
|
|
DrawContext->GetCursorImage = GetCursorImage;
|
|
DrawContext->ExitLoop = ExitLoop;
|
|
DrawContext->GuiContext = GuiContext;
|
|
InitializeListHead (&DrawContext->Animations);
|
|
}
|
|
|
|
VOID
|
|
GuiViewDeinitialize (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
OUT BOOT_PICKER_GUI_CONTEXT *GuiContext
|
|
)
|
|
{
|
|
GUI_POINTER_STATE PointerState;
|
|
|
|
if (mPointerContext != NULL) {
|
|
GuiPointerGetState (mPointerContext, &PointerState);
|
|
GuiContext->CursorDefaultX = PointerState.X;
|
|
GuiContext->CursorDefaultY = PointerState.Y;
|
|
}
|
|
|
|
ZeroMem (DrawContext, sizeof (*DrawContext));
|
|
}
|
|
|
|
VOID
|
|
GuiGetBaseCoords (
|
|
IN GUI_OBJ *This,
|
|
IN GUI_DRAWING_CONTEXT *DrawContext,
|
|
OUT INT64 *BaseX,
|
|
OUT INT64 *BaseY
|
|
)
|
|
{
|
|
GUI_OBJ *Obj;
|
|
GUI_OBJ_CHILD *ChildObj;
|
|
INT64 X;
|
|
INT64 Y;
|
|
|
|
ASSERT (This != NULL);
|
|
ASSERT (DrawContext != NULL);
|
|
ASSERT (DrawContext->Screen->OffsetX == 0);
|
|
ASSERT (DrawContext->Screen->OffsetY == 0);
|
|
ASSERT (BaseX != NULL);
|
|
ASSERT (BaseY != NULL);
|
|
|
|
X = 0;
|
|
Y = 0;
|
|
Obj = This;
|
|
while (Obj != DrawContext->Screen) {
|
|
X += Obj->OffsetX;
|
|
Y += Obj->OffsetY;
|
|
|
|
ChildObj = BASE_CR (Obj, GUI_OBJ_CHILD, Obj);
|
|
Obj = ChildObj->Parent;
|
|
ASSERT (Obj != NULL);
|
|
ASSERT (IsNodeInList (&Obj->Children, &ChildObj->Link));
|
|
}
|
|
|
|
*BaseX = X;
|
|
*BaseY = Y;
|
|
}
|
|
|
|
VOID
|
|
GuiDrawLoop (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN UINT32 TimeOutSeconds
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
BOOLEAN Result;
|
|
|
|
INTN InputKey;
|
|
BOOLEAN Modifier;
|
|
GUI_POINTER_STATE PointerState;
|
|
GUI_OBJ *HoldObject;
|
|
INT64 HoldObjBaseX;
|
|
INT64 HoldObjBaseY;
|
|
CONST LIST_ENTRY *AnimEntry;
|
|
CONST GUI_ANIMATION *Animation;
|
|
UINT64 LoopStartTsc;
|
|
UINT64 LastTsc;
|
|
UINT64 NewLastTsc;
|
|
BOOLEAN ObjectHeld;
|
|
|
|
CONST GUI_IMAGE *CursorImage;
|
|
UINT64 FrameTime;
|
|
|
|
ASSERT (DrawContext != NULL);
|
|
|
|
mNumValidDrawReqs = 0;
|
|
FrameTime = 0;
|
|
HoldObject = NULL;
|
|
ObjectHeld = FALSE;
|
|
|
|
//
|
|
// Clear previous inputs.
|
|
//
|
|
if (mPointerContext != NULL) {
|
|
GuiPointerReset (mPointerContext);
|
|
}
|
|
GuiKeyReset (mKeyContext);
|
|
|
|
//
|
|
// Pointer state will be implicitly initialised on the first call in the loop.
|
|
//
|
|
|
|
//
|
|
// Main drawing loop, time and derieve sub-frequencies as required.
|
|
//
|
|
LastTsc = LoopStartTsc = mStartTsc = AsmReadTsc ();
|
|
do {
|
|
if (mPointerContext != NULL) {
|
|
//
|
|
// TODO: Put cursor dimensions in some context?
|
|
//
|
|
ASSERT (DrawContext->GetCursorImage != NULL);
|
|
CursorImage = DrawContext->GetCursorImage (DrawContext->GuiContext);
|
|
ASSERT (CursorImage != NULL);
|
|
//
|
|
// Restore the rectangle previously covered by the cursor.
|
|
// The new cursor is drawn right before flushing the screen.
|
|
//
|
|
GuiRequestDrawCrop (
|
|
DrawContext,
|
|
mPointerOldBaseX,
|
|
mPointerOldBaseY,
|
|
CursorImage->Width,
|
|
CursorImage->Height
|
|
);
|
|
//
|
|
// Process pointer events.
|
|
//
|
|
GuiPointerGetState (mPointerContext, &PointerState);
|
|
|
|
if (PointerState.PrimaryDown) {
|
|
if (!ObjectHeld && HoldObject == NULL) {
|
|
HoldObject = GuiObjDelegatePtrEvent (
|
|
DrawContext->Screen,
|
|
DrawContext,
|
|
DrawContext->GuiContext,
|
|
GuiPointerPrimaryDown,
|
|
0,
|
|
0,
|
|
PointerState.X,
|
|
PointerState.Y
|
|
);
|
|
|
|
}
|
|
|
|
ObjectHeld = TRUE;
|
|
} else {
|
|
ObjectHeld = FALSE;
|
|
}
|
|
|
|
if (HoldObject != NULL) {
|
|
GuiGetBaseCoords (
|
|
HoldObject,
|
|
DrawContext,
|
|
&HoldObjBaseX,
|
|
&HoldObjBaseY
|
|
);
|
|
HoldObject->PtrEvent (
|
|
HoldObject,
|
|
DrawContext,
|
|
DrawContext->GuiContext,
|
|
!PointerState.PrimaryDown ? GuiPointerPrimaryUp : GuiPointerPrimaryHold,
|
|
HoldObjBaseX,
|
|
HoldObjBaseY,
|
|
(INT64)PointerState.X - HoldObjBaseX,
|
|
(INT64)PointerState.Y - HoldObjBaseY
|
|
);
|
|
if (!PointerState.PrimaryDown) {
|
|
HoldObject = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mKeyContext != NULL) {
|
|
//
|
|
// Process key events. Only allow one key at a time for now.
|
|
//
|
|
Status = GuiKeyRead (mKeyContext, &InputKey, &Modifier);
|
|
if (!EFI_ERROR (Status)) {
|
|
ASSERT (DrawContext->Screen->KeyEvent != NULL);
|
|
DrawContext->Screen->KeyEvent (
|
|
DrawContext->Screen,
|
|
DrawContext,
|
|
DrawContext->GuiContext,
|
|
0,
|
|
0,
|
|
InputKey,
|
|
Modifier
|
|
);
|
|
//
|
|
// If detected key press then disable menu timeout
|
|
//
|
|
if (TimeOutSeconds > 0) {
|
|
//
|
|
// Voice only unrelated key press.
|
|
//
|
|
if (!DrawContext->GuiContext->ReadyToBoot
|
|
&& DrawContext->GuiContext->PickerContext->PickerAudioAssist) {
|
|
DrawContext->GuiContext->PickerContext->PlayAudioFile (
|
|
DrawContext->GuiContext->PickerContext,
|
|
OcVoiceOverAudioFileAbortTimeout,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
TimeOutSeconds = 0;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Process queued animations.
|
|
//
|
|
AnimEntry = GetFirstNode (&DrawContext->Animations);
|
|
while (!IsNull (&DrawContext->Animations, AnimEntry)) {
|
|
Animation = BASE_CR (AnimEntry, GUI_ANIMATION, Link);
|
|
Result = Animation->Animate (Animation->Context, DrawContext, FrameTime);
|
|
|
|
AnimEntry = GetNextNode (&DrawContext->Animations, AnimEntry);
|
|
|
|
if (Result) {
|
|
RemoveEntryList (&Animation->Link);
|
|
}
|
|
}
|
|
++FrameTime;
|
|
//
|
|
// Flush the changes performed in this refresh iteration.
|
|
//
|
|
GuiFlushScreen (DrawContext);
|
|
|
|
NewLastTsc = AsmReadTsc ();
|
|
|
|
if (DrawContext->GuiContext->AudioPlaybackTimeout >= 0
|
|
&& DrawContext->GuiContext->PickerContext->PickerAudioAssist) {
|
|
DrawContext->GuiContext->AudioPlaybackTimeout -= (INT32) (DivU64x32 (
|
|
GetTimeInNanoSecond (NewLastTsc - LastTsc),
|
|
1000000
|
|
));
|
|
if (DrawContext->GuiContext->AudioPlaybackTimeout <= 0) {
|
|
DrawContext->GuiContext->PickerContext->PlayAudioFile (
|
|
DrawContext->GuiContext->PickerContext,
|
|
OcVoiceOverAudioFileSelected,
|
|
FALSE
|
|
);
|
|
DrawContext->GuiContext->PickerContext->PlayAudioEntry (
|
|
DrawContext->GuiContext->PickerContext,
|
|
DrawContext->GuiContext->BootEntry
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Exit early if reach timer timeout and timer isn't disabled due to key event
|
|
//
|
|
if (TimeOutSeconds > 0
|
|
&& GetTimeInNanoSecond (NewLastTsc - LoopStartTsc) >= TimeOutSeconds * 1000000000ULL) {
|
|
if (DrawContext->GuiContext->PickerContext->PickerAudioAssist) {
|
|
DrawContext->GuiContext->PickerContext->PlayAudioFile (
|
|
DrawContext->GuiContext->PickerContext,
|
|
OcVoiceOverAudioFileTimeout,
|
|
FALSE
|
|
);
|
|
}
|
|
DrawContext->GuiContext->ReadyToBoot = TRUE;
|
|
break;
|
|
}
|
|
|
|
LastTsc = NewLastTsc;
|
|
} while (!DrawContext->ExitLoop (DrawContext->GuiContext));
|
|
}
|
|
|
|
VOID
|
|
GuiClearScreen (
|
|
IN OUT GUI_DRAWING_CONTEXT *DrawContext,
|
|
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Pixel
|
|
)
|
|
{
|
|
GuiOutputBlt (
|
|
mOutputContext,
|
|
Pixel,
|
|
EfiBltVideoFill,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
DrawContext->Screen->Width,
|
|
DrawContext->Screen->Height,
|
|
0
|
|
);
|
|
}
|
|
|
|
/// A sine approximation via a third-order approx.
|
|
/// @param x Angle (with 2^15 units/circle)
|
|
/// @return Sine value (Q12)
|
|
STATIC
|
|
INT32
|
|
isin_S3 (
|
|
IN INT32 x
|
|
)
|
|
{
|
|
//
|
|
// S(x) = x * ( (3<<p) - (x*x>>r) ) >> s
|
|
// n : Q-pos for quarter circle 13
|
|
// A : Q-pos for output 12
|
|
// p : Q-pos for parentheses intermediate 15
|
|
// r = 2n-p 11
|
|
// s = A-1-p-n 17
|
|
//
|
|
STATIC CONST INT32 n = 13;
|
|
STATIC CONST INT32 p = 15;
|
|
STATIC CONST INT32 r = 11;
|
|
STATIC CONST INT32 s = 17;
|
|
|
|
x = x << (30 - n); // shift to full s32 range (Q13->Q30)
|
|
|
|
if ((x ^ (x << 1)) < 0) // test for quadrant 1 or 2
|
|
x = (1 << 31) - x;
|
|
|
|
x = x >> (30 - n);
|
|
|
|
return x * ((3 << p) - (x * x >> r)) >> s;
|
|
}
|
|
|
|
UINT32
|
|
GuiGetInterpolatedValue (
|
|
IN CONST GUI_INTERPOLATION *Interpol,
|
|
IN UINT64 CurrentTime
|
|
)
|
|
{
|
|
INT32 AnimTime;
|
|
UINT32 DeltaTime;
|
|
|
|
ASSERT (Interpol != NULL);
|
|
ASSERT (Interpol->Duration > 0);
|
|
|
|
STATIC CONST UINT32 InterpolFpTimeFactor = 1U << 12U;
|
|
|
|
if (CurrentTime <= Interpol->StartTime) {
|
|
return Interpol->StartValue;
|
|
}
|
|
|
|
DeltaTime = (UINT32)(CurrentTime - Interpol->StartTime);
|
|
|
|
if (DeltaTime >= Interpol->Duration) {
|
|
return Interpol->EndValue;
|
|
}
|
|
|
|
AnimTime = (INT32) DivU64x64Remainder ((UINT64) InterpolFpTimeFactor * DeltaTime, Interpol->Duration, NULL);
|
|
if (Interpol->Type == GuiInterpolTypeSmooth) {
|
|
//
|
|
// One InterpolFpTimeFactor unit corresponds to 45 degrees in the unit circle. Divide
|
|
// the time by two because the integral of sin from 0 to Pi is equal to 2,
|
|
// i.e. double speed.
|
|
//
|
|
AnimTime = isin_S3 (4 * AnimTime / 2);
|
|
//
|
|
// FP-square to further smoothen the animation.
|
|
//
|
|
AnimTime = (AnimTime * AnimTime) / InterpolFpTimeFactor;
|
|
} else {
|
|
ASSERT (Interpol->Type == GuiInterpolTypeLinear);
|
|
}
|
|
|
|
return (Interpol->EndValue * AnimTime
|
|
+ (Interpol->StartValue * (InterpolFpTimeFactor - AnimTime)))
|
|
/ InterpolFpTimeFactor;
|
|
}
|