]> Shamusworld >> Repos - apple2/blob - src/gui/menu.cpp
7401837dd6e9c611e700158883f82ab8c58a99c4
[apple2] / src / gui / menu.cpp
1 //
2 // MENU.CPP
3 //
4 // Graphical User Interface menu support
5 // by James Hammons
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  02/09/2006  Created this file
12 // JLH  02/13/2006  Added rendering support
13 //
14
15 #include "menu.h"
16 #include "guimisc.h"
17
18 //#define DEBUG_MENU
19
20 #ifdef DEBUG_MENU
21 #include "log.h"
22 #endif
23
24 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
25 #define MASK_R 0xFF000000
26 #define MASK_G 0x00FF0000
27 #define MASK_B 0x0000FF00
28 #define MASK_A 0x000000FF
29 #else
30 #define MASK_R 0x000000FF
31 #define MASK_G 0x0000FF00
32 #define MASK_B 0x00FF0000
33 #define MASK_A 0xFF000000
34 #endif
35
36 //
37 // MenuItems class implementation
38 //
39
40 MenuItems::MenuItems(): charLength(0), popupBackstore(NULL)
41 {
42 }
43
44 bool MenuItems::Inside(uint32_t x, uint32_t y)
45 {
46         return (x >= (uint32_t)extents.x && x < (uint32_t)(extents.x + extents.w)
47                 && y >= (uint32_t)extents.y && y < (uint32_t)(extents.y + extents.h) ? true : false);
48 }
49
50 //
51 // Menu class implementation
52 //
53
54 Menu::Menu(uint32_t x/*= 0*/, uint32_t y/*= 0*/, uint32_t w/*= 0*/, uint32_t h/*= 0*/,
55         uint8_t fgcR/*= 0x00*/, uint8_t fgcG/*= 0x00*/, uint8_t fgcB/*= 0x7F*/, uint8_t fgcA/*= 0xFF*/,
56         uint8_t bgcR/*= 0x3F*/, uint8_t bgcG/*= 0x3F*/, uint8_t bgcB/*= 0xFF*/, uint8_t bgcA/*= 0xFF*/,
57         uint8_t fgchR/*= 0x3F*/, uint8_t fgchG/*= 0x3F*/, uint8_t fgchB/*= 0xFF*/, uint8_t fgchA/*= 0xFF*/,
58         uint8_t bgchR/*= 0x87*/, uint8_t bgchG/*= 0x87*/, uint8_t bgchB/*= 0xFF*/, uint8_t bgchA/*= 0xFF*/):
59         Element(x, y, w, GetFontHeight(), fgcR, fgcG, fgcB, fgcA, bgcR, bgcG, bgcB, bgcA),
60         activated(false), clicked(false),
61         inside(0), insidePopup(0), menuChosen(-1), menuItemChosen(-1),
62         activatedSave(false), clickedSave(false),
63         insideSave(0), insidePopupSave(0), menuChosenSave(-1), menuItemChosenSave(-1)
64 {
65 #if 0
66         // This *should* allow us to store our colors in an endian safe way... :-/
67         // Nope. Only on SW surfaces. With HW, all bets are off. :-(
68         uint8_t * c = (uint8_t *)&fgColorHL;
69         c[0] = fgchR, c[1] = fgchG, c[2] = fgchB, c[3] = fgchA;
70         c = (uint8_t *)&bgColorHL;
71         c[0] = bgchR, c[1] = bgchG, c[2] = bgchB, c[3] = bgchA;
72 #else
73         fgColorHL = SDL_MapRGBA(screen->format, fgchR, fgchG, fgchB, fgchA);
74         bgColorHL = SDL_MapRGBA(screen->format, bgchR, bgchG, bgchB, bgchA);
75 #endif
76 }
77
78 Menu::~Menu()
79 {
80         for(uint32_t i=0; i<itemList.size(); i++)
81         {
82                 if (itemList[i].popupBackstore)
83                         SDL_FreeSurface(itemList[i].popupBackstore);
84         }
85 }
86
87 void Menu::HandleKey(SDL_Scancode key)
88 {
89         SaveStateVariables();
90
91         for(uint32_t i=0; i<itemList.size(); i++)
92         {
93                 for(uint32_t j=0; j<itemList[i].item.size(); j++)
94                 {
95                         if (itemList[i].item[j].hotKey == key)
96                         {
97                                 SDL_Event event;
98                                 event.type = SDL_USEREVENT;
99                                 event.user.code = MENU_ITEM_CHOSEN;
100                                 event.user.data1 = (void *)itemList[i].item[j].action;
101                         SDL_PushEvent(&event);
102
103                                 clicked = false, menuChosen = menuItemChosen = -1;
104                                 break;
105                         }
106                 }
107         }
108
109         CheckStateAndRedrawIfNeeded();
110 }
111
112 void Menu::HandleMouseMove(uint32_t x, uint32_t y)
113 {
114 #ifdef DEBUG_MENU
115 WriteLog("--> Inside Menu::HandleMouseMove()...\n");
116 #endif
117         SaveStateVariables();
118
119         inside = insidePopup = 0;
120
121         if (Inside(x, y))
122         {
123                 // Find out *where* we are inside the menu bar
124                 uint32_t xpos = extents.x;
125
126                 for(uint32_t i=0; i<itemList.size(); i++)
127                 {
128                         uint32_t width = (itemList[i].title.length() + 2) * GetFontWidth();
129
130                         if (x >= xpos && x < xpos + width)
131                         {
132                                 inside = i + 1;
133                                 menuChosen = i;
134                                 break;
135                         }
136
137                         xpos += width;
138                 }
139         }
140
141         if (!Inside(x, y) && !clicked)
142         {
143                 menuChosen = -1;
144         }
145
146         if (itemList[menuChosen].Inside(x, y) && clicked)
147         {
148                 insidePopup = ((y - itemList[menuChosen].extents.y) / GetFontHeight()) + 1;
149                 menuItemChosen = insidePopup - 1;
150         }
151
152         CheckStateAndRedrawIfNeeded();
153 }
154
155 void Menu::HandleMouseButton(uint32_t x, uint32_t y, bool mouseDown)
156 {
157 #ifdef DEBUG_MENU
158 WriteLog("--> Inside Menu::HandleMouseButton()...\n");
159 #endif
160         SaveStateVariables();
161
162         if (!clicked)
163         {
164                 if (mouseDown)
165                 {
166                         if (inside)
167                                 clicked = true;
168                         else
169                                 menuChosen = -1;                                // clicked is already false...!
170                 }
171         }
172         else                                                                            // clicked == true
173         {
174                 if (insidePopup && !mouseDown)                  // I.e., mouse-button-up
175                 {
176                         activated = true;
177
178                         if (itemList[menuChosen].item[menuItemChosen].action != NULL)
179                         {
180                                 SDL_Event event;
181                                 event.type = SDL_USEREVENT;
182                                 event.user.code = MENU_ITEM_CHOSEN;
183                                 event.user.data1 = (void *)itemList[menuChosen].item[menuItemChosen].action;
184                             SDL_PushEvent(&event);
185                         }
186
187                         clicked = false, menuChosen = menuItemChosen = -1;
188                 }
189
190                 if (!inside && !insidePopup && mouseDown)
191                         clicked = false, menuChosen = menuItemChosen = -1;
192         }
193
194         CheckStateAndRedrawIfNeeded();
195 }
196
197 void Menu::Draw(void)
198 {
199 #ifdef DEBUG_MENU
200 WriteLog("--> Inside Menu::Draw()...\n");
201 #endif
202         char separator[] = "--------------------------------------------------------";
203
204         uint32_t xpos = extents.x;
205
206         for(uint32_t i=0; i<itemList.size(); i++)
207         {
208                 uint32_t color1 = fgColor, color2 = bgColor;
209
210                 if (inside == (i + 1) || (menuChosen != -1 && (uint32_t)menuChosen == i))
211                         color1 = fgColorHL, color2 = bgColorHL;
212
213                 DrawStringOpaque(screen, xpos, extents.y, color1, color2,
214                         " %s ", itemList[i].title.c_str());
215                 xpos += (itemList[i].title.length() + 2) * GetFontWidth();
216         }
217
218         // Prime the backstore if we're about to draw a popup...
219         if (!clickedSave && clicked)                            // If we transitioned from no popup to popup
220 #ifdef DEBUG_MENU
221         {
222 WriteLog("--> Attempting to prime pubs...\n    pubs x/y/w/h = %u/%u/%u/%u\n    surface = %08X\n",
223         itemList[menuChosen].extents.x,
224         itemList[menuChosen].extents.y,
225         itemList[menuChosen].extents.w,
226         itemList[menuChosen].extents.h,
227         itemList[menuChosen].popupBackstore);
228 #endif
229                 SDL_BlitSurface(screen, &itemList[menuChosen].extents, itemList[menuChosen].popupBackstore, NULL);
230 #ifdef DEBUG_MENU
231         }
232 #endif
233
234         // Draw sub menu (but only if active)
235         if (clicked)
236         {
237                 uint32_t ypos = extents.y + GetFontHeight() + 1;
238
239                 for(uint32_t i=0; i<itemList[menuChosen].item.size(); i++)
240                 {
241                         uint32_t color1 = fgColor, color2 = bgColor;
242
243                         if (insidePopup == i + 1)
244                                 color1 = fgColorHL, color2 = bgColorHL, menuItemChosen = i;
245
246                         if (itemList[menuChosen].item[i].name.length() > 0)
247                                 DrawStringOpaque(screen, itemList[menuChosen].extents.x, ypos,
248                                         color1, color2, " %-*.*s ", itemList[menuChosen].charLength,
249                                         itemList[menuChosen].charLength, itemList[menuChosen].item[i].name.c_str());
250                         else
251                                 DrawStringOpaque(screen, itemList[menuChosen].extents.x, ypos,
252                                         fgColor, bgColor, "%.*s", itemList[menuChosen].charLength + 2, separator);
253
254                         ypos += GetFontHeight();
255                 }
256         }
257
258         // Do cleanup if we're done with the popup menu
259         if (clickedSave && !clicked)                            // If we transitioned from popup to no popup
260         {
261                 SDL_Rect r;
262
263                 r.x = itemList[menuChosenSave].extents.x;
264                 r.y = itemList[menuChosenSave].extents.y;
265                 SDL_BlitSurface(itemList[menuChosenSave].popupBackstore, NULL, screen, &r);
266         }
267
268         needToRefreshScreen = true;
269 }
270
271 void Menu::Notify(Element *)
272 {
273 }
274
275 void Menu::Add(MenuItems mi)
276 {
277         for(uint32_t i=0; i<mi.item.size(); i++)
278                 if (mi.item[i].name.length() > mi.charLength)
279                         mi.charLength = mi.item[i].name.length();
280
281         // Set extents here as well...
282         mi.extents.x = extents.x + extents.w;
283         mi.extents.y = extents.y + GetFontHeight() + 1;
284         mi.extents.w = (mi.charLength + 2) * GetFontWidth();
285         mi.extents.h = mi.item.size() * GetFontHeight();
286
287         mi.popupBackstore = SDL_CreateRGBSurface(SDL_SWSURFACE, mi.extents.w, mi.extents.h, 32,
288                 MASK_R, MASK_G, MASK_B, 0x00);
289
290         itemList.push_back(mi);
291         extents.w += (mi.title.length() + 2) * GetFontWidth();
292
293 //This is incorrect--this should be sampled just *before* we draw the popup! !!! FIX !!! [DONE]
294 //      SDL_BlitSurface(screen, &mi.extents, mi.popupBackstore, NULL);
295 #ifdef DEBUG_MENU
296 WriteLog("--> Added menu item...\n    pubs x/y/w/h = %u/%u/%u/%u\n    surface = %08X\n",
297         mi.extents.x,
298         mi.extents.y,
299         mi.popupBackstore->w,
300         mi.popupBackstore->h,
301         mi.popupBackstore);
302 #endif
303 }
304
305 void Menu::SaveStateVariables(void)
306 {
307         activatedSave = activated;
308         clickedSave = clicked;
309         insideSave = inside;
310         insidePopupSave = insidePopup;
311         menuChosenSave = menuChosen;
312         menuItemChosenSave = menuItemChosen;
313 }
314
315 void Menu::CheckStateAndRedrawIfNeeded(void)
316 {
317         // Check to see if any of our state variables changed since we last saved them...
318         if (activated != activatedSave || clicked != clickedSave
319                 || inside != insideSave || insidePopup != insidePopupSave
320                 || menuChosen != menuChosenSave || menuItemChosen != menuItemChosenSave)
321                 Draw();
322 }