]> code.delx.au - refind/blob - refind/menu.c
Added max_tags option; new shell filenames; new rEFIt icon
[refind] / refind / menu.c
1 /*
2 * refit/menu.c
3 * Menu functions
4 *
5 * Copyright (c) 2006 Christoph Pfisterer
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * Neither the name of Christoph Pfisterer nor the names of the
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36 /*
37 * Modifications copyright (c) 2012 Roderick W. Smith
38 *
39 * Modifications distributed under the terms of the GNU General Public
40 * License (GPL) version 3 (GPLv3), a copy of which must be distributed
41 * with this source code or binaries made from it.
42 *
43 */
44
45 #include "global.h"
46 #include "screen.h"
47 #include "lib.h"
48 #include "menu.h"
49 #include "config.h"
50 #include "libeg.h"
51 #include "refit_call_wrapper.h"
52
53 #include "egemb_back_selected_small.h"
54 #include "egemb_arrow_left.h"
55 #include "egemb_arrow_right.h"
56
57 // other menu definitions
58
59 #define MENU_FUNCTION_INIT (0)
60 #define MENU_FUNCTION_CLEANUP (1)
61 #define MENU_FUNCTION_PAINT_ALL (2)
62 #define MENU_FUNCTION_PAINT_SELECTION (3)
63 #define MENU_FUNCTION_PAINT_TIMEOUT (4)
64
65 typedef VOID (*MENU_STYLE_FUNC)(IN REFIT_MENU_SCREEN *Screen, IN SCROLL_STATE *State, IN UINTN Function, IN CHAR16 *ParamText);
66
67 static CHAR16 ArrowUp[2] = { ARROW_UP, 0 };
68 static CHAR16 ArrowDown[2] = { ARROW_DOWN, 0 };
69
70 #define TEXT_YMARGIN (2)
71 #define TEXT_XMARGIN (8)
72 #define TEXT_LINE_HEIGHT (FONT_CELL_HEIGHT + TEXT_YMARGIN * 2)
73 #define TITLEICON_SPACING (16)
74
75 #define ROW0_TILESIZE (144)
76 #define ROW1_TILESIZE (64)
77 #define TILE_XSPACING (8)
78 #define TILE_YSPACING (16)
79
80 // Alignment values for PaintIcon()
81 #define ALIGN_RIGHT 1
82 #define ALIGN_LEFT 0
83
84 static EG_IMAGE *SelectionImages[4] = { NULL, NULL, NULL, NULL };
85 static EG_PIXEL SelectionBackgroundPixel = { 0xff, 0xff, 0xff, 0 };
86 static EG_IMAGE *TextBuffer = NULL;
87
88 // Used in MainMenuStyle(), but must be persistent....
89 UINTN row0PosX = 0, row0PosXRunning = 0, row1PosY = 0, row0Loaders = 0;
90
91 //
92 // Graphics helper functions
93 //
94
95 static VOID InitSelection(VOID)
96 {
97 UINTN x, y, src_x, src_y;
98 EG_PIXEL *DestPtr, *SrcPtr;
99
100 if (!AllowGraphicsMode)
101 return;
102 if (SelectionImages[0] != NULL)
103 return;
104
105 // load small selection image
106 if (GlobalConfig.SelectionSmallFileName != NULL) {
107 SelectionImages[2] = egLoadImage(SelfDir, GlobalConfig.SelectionSmallFileName, FALSE);
108 }
109 if (SelectionImages[2] == NULL)
110 SelectionImages[2] = egPrepareEmbeddedImage(&egemb_back_selected_small, FALSE);
111 SelectionImages[2] = egEnsureImageSize(SelectionImages[2],
112 ROW1_TILESIZE, ROW1_TILESIZE, &MenuBackgroundPixel);
113 if (SelectionImages[2] == NULL)
114 return;
115
116 // load big selection image
117 if (GlobalConfig.SelectionBigFileName != NULL) {
118 SelectionImages[0] = egLoadImage(SelfDir, GlobalConfig.SelectionBigFileName, FALSE);
119 SelectionImages[0] = egEnsureImageSize(SelectionImages[0],
120 ROW0_TILESIZE, ROW0_TILESIZE, &MenuBackgroundPixel);
121 }
122 if (SelectionImages[0] == NULL) {
123 // calculate big selection image from small one
124
125 SelectionImages[0] = egCreateImage(ROW0_TILESIZE, ROW0_TILESIZE, FALSE);
126 if (SelectionImages[0] == NULL) {
127 egFreeImage(SelectionImages[2]);
128 SelectionImages[2] = NULL;
129 return;
130 }
131
132 DestPtr = SelectionImages[0]->PixelData;
133 SrcPtr = SelectionImages[2]->PixelData;
134 for (y = 0; y < ROW0_TILESIZE; y++) {
135 if (y < (ROW1_TILESIZE >> 1))
136 src_y = y;
137 else if (y < (ROW0_TILESIZE - (ROW1_TILESIZE >> 1)))
138 src_y = (ROW1_TILESIZE >> 1);
139 else
140 src_y = y - (ROW0_TILESIZE - ROW1_TILESIZE);
141
142 for (x = 0; x < ROW0_TILESIZE; x++) {
143 if (x < (ROW1_TILESIZE >> 1))
144 src_x = x;
145 else if (x < (ROW0_TILESIZE - (ROW1_TILESIZE >> 1)))
146 src_x = (ROW1_TILESIZE >> 1);
147 else
148 src_x = x - (ROW0_TILESIZE - ROW1_TILESIZE);
149
150 *DestPtr++ = SrcPtr[src_y * ROW1_TILESIZE + src_x];
151 }
152 }
153 }
154
155 // non-selected background images
156 SelectionImages[1] = egCreateFilledImage(ROW0_TILESIZE, ROW0_TILESIZE, FALSE, &MenuBackgroundPixel);
157 SelectionImages[3] = egCreateFilledImage(ROW1_TILESIZE, ROW1_TILESIZE, FALSE, &MenuBackgroundPixel);
158 }
159
160 //
161 // Scrolling functions
162 //
163
164 static VOID InitScroll(OUT SCROLL_STATE *State, IN UINTN ItemCount, IN UINTN VisibleSpace)
165 {
166 State->LastSelection = State->CurrentSelection = 0;
167 State->MaxIndex = (INTN)ItemCount - 1;
168 State->FirstVisible = 0;
169 State->MaxVisible = UGAWidth / (ROW0_TILESIZE + TILE_XSPACING) - 1;
170 if ((VisibleSpace > 0) && (VisibleSpace < State->MaxVisible))
171 State->MaxVisible = (INTN)VisibleSpace;
172 State->PaintAll = TRUE;
173 State->PaintSelection = FALSE;
174
175 State->LastVisible = State->FirstVisible + State->MaxVisible - 1;
176 }
177
178 static VOID UpdateScroll(IN OUT SCROLL_STATE *State, IN UINTN Movement)
179 {
180 State->LastSelection = State->CurrentSelection;
181
182 switch (Movement) {
183 case SCROLL_LINE_UP:
184 if (State->CurrentSelection > 0) {
185 State->CurrentSelection --;
186 }
187 break;
188
189 case SCROLL_LINE_DOWN:
190 if (State->CurrentSelection < State->MaxIndex) {
191 State->CurrentSelection ++;
192 }
193 break;
194
195 // TODO: Better handling of SCROLL_PAGE_UP & SCROLL_PAGE_DOWN
196 case SCROLL_PAGE_UP:
197 case SCROLL_FIRST:
198 if (State->CurrentSelection > 0) {
199 State->PaintAll = TRUE;
200 State->CurrentSelection = 0;
201 }
202 break;
203
204 case SCROLL_PAGE_DOWN:
205 case SCROLL_LAST:
206 if (State->CurrentSelection < State->MaxIndex) {
207 State->PaintAll = TRUE;
208 State->CurrentSelection = State->MaxIndex;
209 }
210 break;
211
212 case SCROLL_NONE:
213 break;
214
215 }
216
217 if (!State->PaintAll && State->CurrentSelection != State->LastSelection)
218 State->PaintSelection = TRUE;
219 State->LastVisible = State->FirstVisible + State->MaxVisible - 1;
220 }
221
222 //
223 // menu helper functions
224 //
225
226 VOID AddMenuInfoLine(IN REFIT_MENU_SCREEN *Screen, IN CHAR16 *InfoLine)
227 {
228 AddListElement((VOID ***) &(Screen->InfoLines), &(Screen->InfoLineCount), InfoLine);
229 }
230
231 VOID AddMenuEntry(IN REFIT_MENU_SCREEN *Screen, IN REFIT_MENU_ENTRY *Entry)
232 {
233 AddListElement((VOID ***) &(Screen->Entries), &(Screen->EntryCount), Entry);
234 }
235
236 VOID FreeMenu(IN REFIT_MENU_SCREEN *Screen)
237 {
238 if (Screen->Entries)
239 FreePool(Screen->Entries);
240 }
241
242 static INTN FindMenuShortcutEntry(IN REFIT_MENU_SCREEN *Screen, IN CHAR16 *Shortcut)
243 {
244 UINTN i;
245
246 if (Shortcut == NULL)
247 return (-1);
248
249 if (StrLen(Shortcut) == 1) {
250 if (Shortcut[0] >= 'a' && Shortcut[0] <= 'z')
251 Shortcut[0] -= ('a' - 'A');
252 if (Shortcut[0]) {
253 for (i = 0; i < Screen->EntryCount; i++) {
254 if (Screen->Entries[i]->ShortcutDigit == Shortcut[0] ||
255 Screen->Entries[i]->ShortcutLetter == Shortcut[0]) {
256 return i;
257 } // if
258 } // for
259 } // if
260 } else if (StrLen(Shortcut) > 1) {
261 for (i = 0; i < Screen->EntryCount; i++) {
262 if (StriSubCmp(Shortcut, Screen->Entries[i]->Title))
263 return i;
264 } // for
265 }
266 return -1;
267 }
268
269 //
270 // generic menu function
271 //
272
273 static UINTN RunGenericMenu(IN REFIT_MENU_SCREEN *Screen, IN MENU_STYLE_FUNC StyleFunc, IN INTN DefaultEntryIndex, OUT REFIT_MENU_ENTRY **ChosenEntry)
274 {
275 SCROLL_STATE State;
276 EFI_STATUS Status;
277 EFI_INPUT_KEY key;
278 UINTN index;
279 INTN ShortcutEntry;
280 BOOLEAN HaveTimeout = FALSE;
281 UINTN TimeoutCountdown = 0;
282 CHAR16 *TimeoutMessage;
283 CHAR16 KeyAsString[2];
284 UINTN MenuExit;
285
286 if (Screen->TimeoutSeconds > 0) {
287 HaveTimeout = TRUE;
288 TimeoutCountdown = Screen->TimeoutSeconds * 10;
289 }
290 MenuExit = 0;
291
292 StyleFunc(Screen, &State, MENU_FUNCTION_INIT, NULL);
293 // override the starting selection with the default index, if any
294 if (DefaultEntryIndex >= 0 && DefaultEntryIndex <= State.MaxIndex) {
295 State.CurrentSelection = DefaultEntryIndex;
296 UpdateScroll(&State, SCROLL_NONE);
297 }
298
299 while (!MenuExit) {
300 // update the screen
301 if (State.PaintAll) {
302 StyleFunc(Screen, &State, MENU_FUNCTION_PAINT_ALL, NULL);
303 State.PaintAll = FALSE;
304 } else if (State.PaintSelection) {
305 StyleFunc(Screen, &State, MENU_FUNCTION_PAINT_SELECTION, NULL);
306 State.PaintSelection = FALSE;
307 }
308
309 if (HaveTimeout) {
310 TimeoutMessage = PoolPrint(L"%s in %d seconds", Screen->TimeoutText, (TimeoutCountdown + 5) / 10);
311 StyleFunc(Screen, &State, MENU_FUNCTION_PAINT_TIMEOUT, TimeoutMessage);
312 FreePool(TimeoutMessage);
313 }
314
315 // read key press (and wait for it if applicable)
316 Status = refit_call2_wrapper(ST->ConIn->ReadKeyStroke, ST->ConIn, &key);
317 if (Status == EFI_NOT_READY) {
318 if (HaveTimeout && TimeoutCountdown == 0) {
319 // timeout expired
320 MenuExit = MENU_EXIT_TIMEOUT;
321 break;
322 } else if (HaveTimeout) {
323 refit_call1_wrapper(BS->Stall, 100000);
324 TimeoutCountdown--;
325 } else
326 refit_call3_wrapper(BS->WaitForEvent, 1, &ST->ConIn->WaitForKey, &index);
327 continue;
328 }
329 if (HaveTimeout) {
330 // the user pressed a key, cancel the timeout
331 StyleFunc(Screen, &State, MENU_FUNCTION_PAINT_TIMEOUT, L"");
332 HaveTimeout = FALSE;
333 }
334
335 // react to key press
336 switch (key.ScanCode) {
337 case SCAN_UP:
338 case SCAN_LEFT:
339 UpdateScroll(&State, SCROLL_LINE_UP);
340 break;
341 case SCAN_DOWN:
342 case SCAN_RIGHT:
343 UpdateScroll(&State, SCROLL_LINE_DOWN);
344 break;
345 case SCAN_HOME:
346 UpdateScroll(&State, SCROLL_FIRST);
347 break;
348 case SCAN_END:
349 UpdateScroll(&State, SCROLL_LAST);
350 break;
351 case SCAN_PAGE_UP:
352 UpdateScroll(&State, SCROLL_PAGE_UP);
353 break;
354 case SCAN_PAGE_DOWN:
355 UpdateScroll(&State, SCROLL_PAGE_DOWN);
356 break;
357 case SCAN_ESC:
358 MenuExit = MENU_EXIT_ESCAPE;
359 break;
360 case SCAN_INSERT:
361 case SCAN_F2:
362 MenuExit = MENU_EXIT_DETAILS;
363 break;
364 case SCAN_F10:
365 egScreenShot();
366 break;
367 }
368 switch (key.UnicodeChar) {
369 case CHAR_LINEFEED:
370 case CHAR_CARRIAGE_RETURN:
371 case ' ':
372 MenuExit = MENU_EXIT_ENTER;
373 break;
374 case '+':
375 MenuExit = MENU_EXIT_DETAILS;
376 break;
377 default:
378 KeyAsString[0] = key.UnicodeChar;
379 KeyAsString[1] = 0;
380 ShortcutEntry = FindMenuShortcutEntry(Screen, KeyAsString);
381 if (ShortcutEntry >= 0) {
382 State.CurrentSelection = ShortcutEntry;
383 MenuExit = MENU_EXIT_ENTER;
384 }
385 break;
386 }
387 }
388
389 StyleFunc(Screen, &State, MENU_FUNCTION_CLEANUP, NULL);
390
391 if (ChosenEntry)
392 *ChosenEntry = Screen->Entries[State.CurrentSelection];
393 return MenuExit;
394 } /* static UINTN RunGenericMenu( */
395
396 //
397 // text-mode generic style
398 //
399
400 static VOID TextMenuStyle(IN REFIT_MENU_SCREEN *Screen, IN SCROLL_STATE *State, IN UINTN Function, IN CHAR16 *ParamText)
401 {
402 INTN i;
403 UINTN MenuWidth, ItemWidth, MenuHeight;
404 static UINTN MenuPosY;
405 static CHAR16 **DisplayStrings;
406 CHAR16 *TimeoutMessage;
407
408 switch (Function) {
409
410 case MENU_FUNCTION_INIT:
411 // vertical layout
412 MenuPosY = 4;
413 if (Screen->InfoLineCount > 0)
414 MenuPosY += Screen->InfoLineCount + 1;
415 MenuHeight = ConHeight - MenuPosY;
416 if (Screen->TimeoutSeconds > 0)
417 MenuHeight -= 2;
418 InitScroll(State, Screen->EntryCount, MenuHeight);
419
420 // determine width of the menu
421 MenuWidth = 20; // minimum
422 for (i = 0; i <= State->MaxIndex; i++) {
423 ItemWidth = StrLen(Screen->Entries[i]->Title);
424 if (MenuWidth < ItemWidth)
425 MenuWidth = ItemWidth;
426 }
427 if (MenuWidth > ConWidth - 6)
428 MenuWidth = ConWidth - 6;
429
430 // prepare strings for display
431 DisplayStrings = AllocatePool(sizeof(CHAR16 *) * Screen->EntryCount);
432 for (i = 0; i <= State->MaxIndex; i++)
433 DisplayStrings[i] = PoolPrint(L" %-.*s ", MenuWidth, Screen->Entries[i]->Title);
434 // TODO: shorten strings that are too long (PoolPrint doesn't do that...)
435 // TODO: use more elaborate techniques for shortening too long strings (ellipses in the middle)
436 // TODO: account for double-width characters
437
438 // initial painting
439 BeginTextScreen(Screen->Title);
440 if (Screen->InfoLineCount > 0) {
441 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
442 for (i = 0; i < (INTN)Screen->InfoLineCount; i++) {
443 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 3, 4 + i);
444 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, Screen->InfoLines[i]);
445 }
446 }
447
448 break;
449
450 case MENU_FUNCTION_CLEANUP:
451 // release temporary memory
452 for (i = 0; i <= State->MaxIndex; i++)
453 FreePool(DisplayStrings[i]);
454 FreePool(DisplayStrings);
455 break;
456
457 case MENU_FUNCTION_PAINT_ALL:
458 // paint the whole screen (initially and after scrolling)
459 for (i = 0; i <= State->MaxIndex; i++) {
460 if (i >= State->FirstVisible && i <= State->LastVisible) {
461 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 2, MenuPosY + (i - State->FirstVisible));
462 if (i == State->CurrentSelection)
463 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_CHOICE_CURRENT);
464 else
465 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_CHOICE_BASIC);
466 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, DisplayStrings[i]);
467 }
468 }
469 // scrolling indicators
470 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_SCROLLARROW);
471 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 0, MenuPosY);
472 if (State->FirstVisible > 0)
473 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, ArrowUp);
474 else
475 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, L" ");
476 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 0, MenuPosY + State->MaxVisible);
477 if (State->LastVisible < State->MaxIndex)
478 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, ArrowDown);
479 else
480 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, L" ");
481 break;
482
483 case MENU_FUNCTION_PAINT_SELECTION:
484 // redraw selection cursor
485 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 2, MenuPosY + (State->LastSelection - State->FirstVisible));
486 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_CHOICE_BASIC);
487 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, DisplayStrings[State->LastSelection]);
488 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 2, MenuPosY + (State->CurrentSelection - State->FirstVisible));
489 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_CHOICE_CURRENT);
490 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, DisplayStrings[State->CurrentSelection]);
491 break;
492
493 case MENU_FUNCTION_PAINT_TIMEOUT:
494 if (ParamText[0] == 0) {
495 // clear message
496 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
497 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 0, ConHeight - 1);
498 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, BlankLine + 1);
499 } else {
500 // paint or update message
501 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_ERROR);
502 refit_call3_wrapper(ST->ConOut->SetCursorPosition, ST->ConOut, 3, ConHeight - 1);
503 TimeoutMessage = PoolPrint(L"%s ", ParamText);
504 refit_call2_wrapper(ST->ConOut->OutputString, ST->ConOut, TimeoutMessage);
505 FreePool(TimeoutMessage);
506 }
507 break;
508
509 }
510 }
511
512 //
513 // graphical generic style
514 //
515
516
517 static VOID DrawMenuText(IN CHAR16 *Text, IN UINTN SelectedWidth, IN UINTN XPos, IN UINTN YPos)
518 {
519 // Print(L"Entering DrawMenuText(); Text is '%s', SelectedWidth is %d, XPos is %d, YPos is %d\n",
520 // Text, SelectedWidth, XPos, YPos);
521 if (TextBuffer == NULL)
522 TextBuffer = egCreateImage(LAYOUT_TEXT_WIDTH, TEXT_LINE_HEIGHT, FALSE);
523
524 egFillImage(TextBuffer, &MenuBackgroundPixel);
525 if (SelectedWidth > 0) {
526 // draw selection bar background
527 egFillImageArea(TextBuffer, 0, 0, SelectedWidth, TextBuffer->Height,
528 &SelectionBackgroundPixel);
529 }
530
531 // render the text
532 egRenderText(Text, TextBuffer, TEXT_XMARGIN, TEXT_YMARGIN);
533 BltImage(TextBuffer, XPos, YPos);
534 }
535
536 // Displays sub-menus
537 static VOID GraphicsMenuStyle(IN REFIT_MENU_SCREEN *Screen, IN SCROLL_STATE *State, IN UINTN Function, IN CHAR16 *ParamText)
538 {
539 INTN i;
540 UINTN ItemWidth;
541 static UINTN MenuWidth, EntriesPosX, EntriesPosY, TimeoutPosY;
542
543 switch (Function) {
544
545 case MENU_FUNCTION_INIT:
546 InitScroll(State, Screen->EntryCount, 0);
547
548 // determine width of the menu
549 MenuWidth = 20; // minimum
550 for (i = 0; i < (INTN)Screen->InfoLineCount; i++) {
551 ItemWidth = StrLen(Screen->InfoLines[i]);
552 if (MenuWidth < ItemWidth)
553 MenuWidth = ItemWidth;
554 }
555 for (i = 0; i <= State->MaxIndex; i++) {
556 ItemWidth = StrLen(Screen->Entries[i]->Title);
557 if (MenuWidth < ItemWidth)
558 MenuWidth = ItemWidth;
559 }
560 MenuWidth = TEXT_XMARGIN * 2 + MenuWidth * FONT_CELL_WIDTH;
561 if (MenuWidth > LAYOUT_TEXT_WIDTH)
562 MenuWidth = LAYOUT_TEXT_WIDTH;
563
564 if (Screen->TitleImage)
565 EntriesPosX = (UGAWidth + (Screen->TitleImage->Width + TITLEICON_SPACING) - MenuWidth) >> 1;
566 else
567 EntriesPosX = (UGAWidth - MenuWidth) >> 1;
568 EntriesPosY = ((UGAHeight - LAYOUT_TOTAL_HEIGHT) >> 1) + LAYOUT_BANNER_YOFFSET + TEXT_LINE_HEIGHT * 2;
569 TimeoutPosY = EntriesPosY + (Screen->EntryCount + 1) * TEXT_LINE_HEIGHT;
570
571 // initial painting
572 SwitchToGraphicsAndClear();
573 egMeasureText(Screen->Title, &ItemWidth, NULL);
574 DrawMenuText(Screen->Title, 0, ((UGAWidth - ItemWidth) >> 1) - TEXT_XMARGIN, EntriesPosY - TEXT_LINE_HEIGHT * 2);
575 if (Screen->TitleImage)
576 BltImageAlpha(Screen->TitleImage,
577 EntriesPosX - (Screen->TitleImage->Width + TITLEICON_SPACING), EntriesPosY,
578 &MenuBackgroundPixel);
579 if (Screen->InfoLineCount > 0) {
580 for (i = 0; i < (INTN)Screen->InfoLineCount; i++) {
581 DrawMenuText(Screen->InfoLines[i], 0, EntriesPosX, EntriesPosY);
582 EntriesPosY += TEXT_LINE_HEIGHT;
583 }
584 EntriesPosY += TEXT_LINE_HEIGHT; // also add a blank line
585 }
586
587 break;
588
589 case MENU_FUNCTION_CLEANUP:
590 // nothing to do
591 break;
592
593 case MENU_FUNCTION_PAINT_ALL:
594 for (i = 0; i <= State->MaxIndex; i++) {
595 DrawMenuText(Screen->Entries[i]->Title, (i == State->CurrentSelection) ? MenuWidth : 0,
596 EntriesPosX, EntriesPosY + i * TEXT_LINE_HEIGHT);
597 }
598 break;
599
600 case MENU_FUNCTION_PAINT_SELECTION:
601 // redraw selection cursor
602 DrawMenuText(Screen->Entries[State->LastSelection]->Title, 0,
603 EntriesPosX, EntriesPosY + State->LastSelection * TEXT_LINE_HEIGHT);
604 DrawMenuText(Screen->Entries[State->CurrentSelection]->Title, MenuWidth,
605 EntriesPosX, EntriesPosY + State->CurrentSelection * TEXT_LINE_HEIGHT);
606 break;
607
608 case MENU_FUNCTION_PAINT_TIMEOUT:
609 DrawMenuText(ParamText, 0, EntriesPosX, TimeoutPosY);
610 break;
611
612 }
613 }
614
615 //
616 // graphical main menu style
617 //
618
619 static VOID DrawMainMenuEntry(REFIT_MENU_ENTRY *Entry, BOOLEAN selected, UINTN XPos, UINTN YPos)
620 {
621 UINTN ImageNum;
622
623 ImageNum = ((Entry->Row == 0) ? 0 : 2) + (selected ? 0 : 1);
624 if (SelectionImages != NULL)
625 BltImageCompositeBadge(SelectionImages[ImageNum],
626 Entry->Image, Entry->BadgeImage, XPos, YPos);
627 }
628
629 static VOID DrawMainMenuText(IN CHAR16 *Text, IN UINTN XPos, IN UINTN YPos)
630 {
631 UINTN TextWidth, TextPosX;
632
633 if (TextBuffer == NULL)
634 TextBuffer = egCreateImage(LAYOUT_TEXT_WIDTH, TEXT_LINE_HEIGHT, FALSE);
635
636 egFillImage(TextBuffer, &MenuBackgroundPixel);
637
638 // render the text
639 egMeasureText(Text, &TextWidth, NULL);
640 if (TextWidth > TextBuffer->Width)
641 TextPosX = 0;
642 else
643 TextPosX = (TextBuffer->Width - TextWidth) / 2;
644 egRenderText(Text, TextBuffer, TextPosX, 0);
645 // egRenderText(Text, TextBuffer, (TextBuffer->Width - TextWidth) >> 1, 0);
646 BltImage(TextBuffer, XPos, YPos);
647 }
648
649 static VOID PaintAll(IN REFIT_MENU_SCREEN *Screen, IN SCROLL_STATE *State, UINTN *itemPosX,
650 UINTN row0PosY, UINTN row1PosY, UINTN textPosY) {
651 INTN i;
652
653 if (Screen->Entries[State->CurrentSelection]->Row == 0) {
654 if (State->CurrentSelection > State->LastVisible) {
655 State->LastVisible = State->CurrentSelection;
656 State->FirstVisible = 1 + State->CurrentSelection - State->MaxVisible;
657 if (State->FirstVisible < 0) // shouldn't happen, but just in case....
658 State->FirstVisible = 0;
659 } // Scroll forward
660 if (State->CurrentSelection < State->FirstVisible) {
661 State->FirstVisible = State->CurrentSelection;
662 State->LastVisible = State->CurrentSelection + State->MaxVisible - 1;
663 } // Scroll backward
664 }
665 for (i = State->FirstVisible; i <= State->MaxIndex; i++) {
666 if (Screen->Entries[i]->Row == 0) {
667 if (i <= State->LastVisible) {
668 DrawMainMenuEntry(Screen->Entries[i], (i == State->CurrentSelection) ? TRUE : FALSE,
669 itemPosX[i - State->FirstVisible], row0PosY);
670 } // if
671 } else {
672 DrawMainMenuEntry(Screen->Entries[i], (i == State->CurrentSelection) ? TRUE : FALSE,
673 itemPosX[i], row1PosY);
674 }
675 }
676 if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_LABEL))
677 DrawMainMenuText(Screen->Entries[State->CurrentSelection]->Title,
678 (UGAWidth - LAYOUT_TEXT_WIDTH) >> 1, textPosY);
679 } // static VOID PaintAll()
680
681 // Move the selection to State->CurrentSelection, adjusting icon row if necessary...
682 static VOID PaintSelection(IN REFIT_MENU_SCREEN *Screen, IN SCROLL_STATE *State, UINTN *itemPosX,
683 UINTN row0PosY, UINTN row1PosY, UINTN textPosY) {
684 if ((State->CurrentSelection < State->LastVisible) && (State->CurrentSelection >= State->FirstVisible)) {
685 DrawMainMenuEntry(Screen->Entries[State->LastSelection], FALSE,
686 itemPosX[State->LastSelection - State->FirstVisible],
687 (Screen->Entries[State->LastSelection]->Row == 0) ? row0PosY : row1PosY);
688 DrawMainMenuEntry(Screen->Entries[State->CurrentSelection], TRUE,
689 itemPosX[State->CurrentSelection - State->FirstVisible],
690 (Screen->Entries[State->CurrentSelection]->Row == 0) ? row0PosY : row1PosY);
691 if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_LABEL))
692 DrawMainMenuText(Screen->Entries[State->CurrentSelection]->Title,
693 (UGAWidth - LAYOUT_TEXT_WIDTH) >> 1, textPosY);
694 } else {
695 MainMenuStyle(Screen, State, MENU_FUNCTION_PAINT_ALL, NULL);
696 }
697 } // static VOID MoveSelection(VOID)
698
699 // Display an icon at the specified location. Uses the image specified by
700 // ExternalFilename if it's available, or BuiltInImage if it's not. The
701 // Y position is specified as the center value, and so is adjusted by half
702 // the icon's height. The X position is set along the icon's left
703 // edge if Alignment == ALIGN_LEFT, and along the right edge if
704 // Alignment == ALIGN_RIGHT
705 static VOID PaintIcon(IN EG_EMBEDDED_IMAGE *BuiltInIcon, IN CHAR16 *ExternalFilename, UINTN PosX, UINTN PosY, UINTN Alignment) {
706 EG_IMAGE *Icon = NULL;
707
708 if (FileExists(SelfDir, ExternalFilename))
709 Icon = egLoadIcon(SelfDir, ExternalFilename, 48);
710 if (Icon == NULL)
711 Icon = egPrepareEmbeddedImage(BuiltInIcon, TRUE);
712 if (Icon != NULL) {
713 if (Alignment == ALIGN_RIGHT)
714 PosX -= Icon->Width;
715 BltImageAlpha(Icon, PosX, PosY - (Icon->Height / 2), &MenuBackgroundPixel);
716 }
717 } // static VOID PaintArrow()
718
719 // Display main menu in graphics mode
720 VOID MainMenuStyle(IN REFIT_MENU_SCREEN *Screen, IN SCROLL_STATE *State, IN UINTN Function, IN CHAR16 *ParamText)
721 {
722 INTN i;
723 extern UINTN row0PosX, row0PosXRunning, row1PosY, row0Loaders;
724 UINTN row0Count, row1Count, row1PosX, row1PosXRunning;
725 static UINTN *itemPosX;
726 static UINTN row0PosY, textPosY;
727
728 switch (Function) {
729
730 case MENU_FUNCTION_INIT:
731 InitScroll(State, Screen->EntryCount, GlobalConfig.MaxTags);
732
733 // layout
734 row0Count = 0;
735 row1Count = 0;
736 row0Loaders = 0;
737 for (i = 0; i <= State->MaxIndex; i++) {
738 if (Screen->Entries[i]->Row == 1) {
739 row1Count++;
740 } else {
741 row0Loaders++;
742 if (row0Count < State->MaxVisible)
743 row0Count++;
744 }
745 }
746 row0PosX = (UGAWidth + TILE_XSPACING - (ROW0_TILESIZE + TILE_XSPACING) * row0Count) >> 1;
747 row0PosY = ((UGAHeight - LAYOUT_TOTAL_HEIGHT) >> 1) + LAYOUT_BANNER_YOFFSET;
748 row1PosX = (UGAWidth + TILE_XSPACING - (ROW1_TILESIZE + TILE_XSPACING) * row1Count) >> 1;
749 row1PosY = row0PosY + ROW0_TILESIZE + TILE_YSPACING;
750 if (row1Count > 0)
751 textPosY = row1PosY + ROW1_TILESIZE + TILE_YSPACING;
752 else
753 textPosY = row1PosY;
754
755 itemPosX = AllocatePool(sizeof(UINTN) * Screen->EntryCount);
756 row0PosXRunning = row0PosX;
757 row1PosXRunning = row1PosX;
758 for (i = 0; i <= State->MaxIndex; i++) {
759 if (Screen->Entries[i]->Row == 0) {
760 itemPosX[i] = row0PosXRunning;
761 row0PosXRunning += ROW0_TILESIZE + TILE_XSPACING;
762 } else {
763 itemPosX[i] = row1PosXRunning;
764 row1PosXRunning += ROW1_TILESIZE + TILE_XSPACING;
765 }
766 }
767 // initial painting
768 InitSelection();
769 SwitchToGraphicsAndClear();
770 break;
771
772 case MENU_FUNCTION_CLEANUP:
773 FreePool(itemPosX);
774 break;
775
776 case MENU_FUNCTION_PAINT_ALL:
777 BltClearScreen(TRUE);
778 PaintAll(Screen, State, itemPosX, row0PosY, row1PosY, textPosY);
779 // For PaintIcon() calls, the starting Y position is moved to the midpoint
780 // of the surrounding row; PaintIcon() adjusts this back up by half the
781 // icon's height to properly center it.
782 if (State->FirstVisible > 0)
783 PaintIcon(&egemb_arrow_left, L"icons\\arrow_left.icns", row0PosX - TILE_XSPACING,
784 row0PosY + (ROW0_TILESIZE / 2), ALIGN_RIGHT);
785 if (State->LastVisible < (row0Loaders - 1))
786 PaintIcon(&egemb_arrow_right, L"icons\\arrow_right.icns",
787 (UGAWidth + (ROW0_TILESIZE + TILE_XSPACING) * State->MaxVisible) / 2 + TILE_XSPACING,
788 row0PosY + (ROW0_TILESIZE / 2), ALIGN_LEFT);
789 break;
790
791 case MENU_FUNCTION_PAINT_SELECTION:
792 PaintSelection(Screen, State, itemPosX, row0PosY, row1PosY, textPosY);
793 break;
794
795 case MENU_FUNCTION_PAINT_TIMEOUT:
796 if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_LABEL))
797 DrawMainMenuText(ParamText, (UGAWidth - LAYOUT_TEXT_WIDTH) >> 1, textPosY + TEXT_LINE_HEIGHT);
798 break;
799
800 }
801 }
802
803 //
804 // user-callable dispatcher functions
805 //
806
807 UINTN RunMenu(IN REFIT_MENU_SCREEN *Screen, OUT REFIT_MENU_ENTRY **ChosenEntry)
808 {
809 MENU_STYLE_FUNC Style = TextMenuStyle;
810
811 if (AllowGraphicsMode)
812 Style = GraphicsMenuStyle;
813
814 return RunGenericMenu(Screen, Style, -1, ChosenEntry);
815 }
816
817 UINTN RunMainMenu(IN REFIT_MENU_SCREEN *Screen, IN CHAR16* DefaultSelection, OUT REFIT_MENU_ENTRY **ChosenEntry)
818 {
819 MENU_STYLE_FUNC Style = TextMenuStyle;
820 MENU_STYLE_FUNC MainStyle = TextMenuStyle;
821 REFIT_MENU_ENTRY *TempChosenEntry;
822 UINTN MenuExit = 0;
823 UINTN DefaultEntryIndex = -1;
824
825 if (DefaultSelection != NULL) {
826 // Find a menu entry whose shortcut is the first character of DefaultSelection, or
827 // whose
828 DefaultEntryIndex = FindMenuShortcutEntry(Screen, DefaultSelection);
829 // If that didn't work, should we scan more characters? For now, no.
830 }
831
832 if (AllowGraphicsMode) {
833 Style = GraphicsMenuStyle;
834 MainStyle = MainMenuStyle;
835 }
836
837 while (!MenuExit) {
838 MenuExit = RunGenericMenu(Screen, MainStyle, DefaultEntryIndex, &TempChosenEntry);
839 Screen->TimeoutSeconds = 0;
840
841 if (MenuExit == MENU_EXIT_DETAILS && TempChosenEntry->SubScreen != NULL) {
842 MenuExit = RunGenericMenu(TempChosenEntry->SubScreen, Style, -1, &TempChosenEntry);
843 if (MenuExit == MENU_EXIT_ESCAPE || TempChosenEntry->Tag == TAG_RETURN)
844 MenuExit = 0;
845 }
846 }
847
848 if (ChosenEntry)
849 *ChosenEntry = TempChosenEntry;
850 return MenuExit;
851 } /* UINTN RunMainMenu() */