1 //=============================================================================
2 //
3 // Adventure Game Studio (AGS)
4 //
5 // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
6 // The full list of copyright holders can be found in the Copyright.txt
7 // file, which is part of this source code distribution.
8 //
9 // The AGS source code is provided under the Artistic License 2.0.
10 // A copy of this license can be found in the file License.txt and at
11 // http://www.opensource.org/licenses/artistic-license-2.0.php
12 //
13 //=============================================================================
14 
15 #include <algorithm>
16 #include "gui/guimain.h"
17 #include "ac/common.h"	// quit()
18 #include "ac/gamesetupstruct.h"
19 #include "gui/guibutton.h"
20 #include "gui/guilabel.h"
21 #include "gui/guislider.h"
22 #include "gui/guiinv.h"
23 #include "gui/guitextbox.h"
24 #include "gui/guilistbox.h"
25 #include "font/fonts.h"
26 #include "ac/spritecache.h"
27 #include "util/stream.h"
28 #include "gfx/bitmap.h"
29 #include "gfx/gfx_def.h"
30 #include "debug/out.h"
31 #include "util/math.h"
32 #include "util/string_utils.h"
33 
34 using namespace AGS::Common;
35 
36 #define MOVER_MOUSEDOWNLOCKED -4000
37 
38 int guis_need_update = 1;
39 int all_buttons_disabled = 0, gui_inv_pic = -1;
40 int gui_disabled_style = 0;
41 
42 namespace AGS
43 {
44 namespace Common
45 {
46 
FixupGUIName(const String & name)47 /* static */ String GUIMain::FixupGUIName(const String &name)
48 {
49     if (name.GetLength() > 0 && name[0u] != 'g')
50         return String::FromFormat("g%c%s", name[0u], name.Mid(1).Lower().GetCStr());
51     return name;
52 }
53 
GUIMain()54 GUIMain::GUIMain()
55 {
56     Init();
57 }
58 
Init()59 void GUIMain::Init()
60 {
61     Id            = 0;
62     Name.Empty();
63     Flags         = 0;
64 
65     X             = 0;
66     Y             = 0;
67     Width         = 0;
68     Height        = 0;
69     BgColor       = 8;
70     BgImage       = 0;
71     FgColor       = 1;
72     Padding       = TEXTWINDOW_PADDING_DEFAULT;
73     PopupStyle    = kGUIPopupNone;
74     PopupAtMouseY = -1;
75     Transparency  = 0;
76     ZOrder        = -1;
77 
78     _visibility   = kGUIVisibility_On;
79     FocusCtrl     = 0;
80     HighlightCtrl = -1;
81     MouseOverCtrl = -1;
82     MouseDownCtrl = -1;
83     MouseWasAt.X  = -1;
84     MouseWasAt.Y  = -1;
85 
86     OnClickHandler.Empty();
87 
88     ControlCount  = 0;
89 }
90 
FindControlUnderMouse(int leeway,bool must_be_clickable) const91 int GUIMain::FindControlUnderMouse(int leeway, bool must_be_clickable) const
92 {
93     if (loaded_game_file_version <= kGameVersion_262)
94     {
95         // Ignore draw order On 2.6.2 and lower
96         for (int i = 0; i < ControlCount; ++i)
97         {
98             if (!Controls[i]->IsVisible())
99                 continue;
100             if (!Controls[i]->IsClickable() && must_be_clickable)
101                 continue;
102             if (Controls[i]->IsOverControl(mousex, mousey, leeway))
103                 return i;
104         }
105     }
106     else
107     {
108         for (int i = ControlCount - 1; i >= 0; --i)
109         {
110             const int ctrl_index = CtrlDrawOrder[i];
111             if (!Controls[ctrl_index]->IsVisible())
112                 continue;
113             if (!Controls[ctrl_index]->IsClickable() && must_be_clickable)
114                 continue;
115             if (Controls[ctrl_index]->IsOverControl(mousex, mousey, leeway))
116                 return ctrl_index;
117         }
118     }
119     return -1;
120 }
121 
FindControlUnderMouse() const122 int GUIMain::FindControlUnderMouse() const
123 {
124     return FindControlUnderMouse(0, true);
125 }
126 
FindControlUnderMouse(int leeway) const127 int GUIMain::FindControlUnderMouse(int leeway) const
128 {
129     return FindControlUnderMouse(leeway, true);
130 }
131 
GetControlType(int index) const132 GUIControlType GUIMain::GetControlType(int index) const
133 {
134     if (index < 0 || index >= ControlCount)
135         return kGUIControlUndefined;
136     return (GUIControlType)((CtrlRefs[index] >> 16) & 0x0000ffff);
137 }
138 
IsInteractableAt(int x,int y) const139 bool GUIMain::IsInteractableAt(int x, int y) const
140 {
141     if (!IsVisible())
142         return false;
143     if (Flags & kGUIMain_NoClick)
144         return false;
145     if ((x >= X) & (y >= Y) & (x < X + Width) & (y < Y + Height))
146         return true;
147     return false;
148 }
149 
IsTextWindow() const150 bool GUIMain::IsTextWindow() const
151 {
152     return (Flags & kGUIMain_TextWindow) != 0;
153 }
154 
BringControlToFront(int index)155 bool GUIMain::BringControlToFront(int index)
156 {
157     return SetControlZOrder(index, ControlCount - 1);
158 }
159 
Draw(Common::Bitmap * ds)160 void GUIMain::Draw(Common::Bitmap *ds)
161 {
162     DrawAt(ds, X, Y);
163 }
164 
DrawAt(Common::Bitmap * ds,int x,int y)165 void GUIMain::DrawAt(Common::Bitmap *ds, int x, int y)
166 {
167     SET_EIP(375)
168 
169     if ((Width < 1) || (Height < 1))
170         return;
171 
172     Bitmap subbmp;
173     subbmp.CreateSubBitmap(ds, RectWH(x, y, Width, Height));
174 
175     SET_EIP(376)
176     // stop border being transparent, if the whole GUI isn't
177     if ((FgColor == 0) && (BgColor != 0))
178         FgColor = 16;
179 
180     if (BgColor != 0)
181         subbmp.Fill(subbmp.GetCompatibleColor(BgColor));
182 
183     SET_EIP(377)
184 
185     color_t draw_color;
186     if (FgColor != BgColor)
187     {
188         draw_color = subbmp.GetCompatibleColor(FgColor);
189         subbmp.DrawRect(Rect(0, 0, subbmp.GetWidth() - 1, subbmp.GetHeight() - 1), draw_color);
190         if (get_fixed_pixel_size(1) > 1)
191             subbmp.DrawRect(Rect(1, 1, subbmp.GetWidth() - 2, subbmp.GetHeight() - 2), draw_color);
192     }
193 
194     SET_EIP(378)
195 
196     if (BgImage > 0 && spriteset[BgImage] != NULL)
197         draw_gui_sprite(&subbmp, BgImage, 0, 0, false);
198 
199     SET_EIP(379)
200 
201     for (int ctrl_index = 0; ctrl_index < ControlCount; ++ctrl_index)
202     {
203         set_eip_guiobj(CtrlDrawOrder[ctrl_index]);
204 
205         GUIObject *objToDraw = Controls[CtrlDrawOrder[ctrl_index]];
206 
207         if (objToDraw->IsDisabled() && gui_disabled_style == GUIDIS_BLACKOUT)
208             continue;
209         if (!objToDraw->IsVisible())
210             continue;
211 
212         objToDraw->Draw(&subbmp);
213 
214         int selectedColour = 14;
215 
216         if (HighlightCtrl == CtrlDrawOrder[ctrl_index])
217         {
218             if (outlineGuiObjects)
219                 selectedColour = 13;
220             draw_color = subbmp.GetCompatibleColor(selectedColour);
221             DrawBlob(&subbmp, objToDraw->x + objToDraw->wid - get_fixed_pixel_size(1) - 1, objToDraw->y, draw_color);
222             DrawBlob(&subbmp, objToDraw->x, objToDraw->y + objToDraw->hit - get_fixed_pixel_size(1) - 1, draw_color);
223             DrawBlob(&subbmp, objToDraw->x, objToDraw->y, draw_color);
224             DrawBlob(&subbmp, objToDraw->x + objToDraw->wid - get_fixed_pixel_size(1) - 1,
225                     objToDraw->y + objToDraw->hit - get_fixed_pixel_size(1) - 1, draw_color);
226         }
227         if (outlineGuiObjects)
228         {
229             // draw a dotted outline round all objects
230             draw_color = subbmp.GetCompatibleColor(selectedColour);
231             for (int i = 0; i < objToDraw->wid; i += 2)
232             {
233                 subbmp.PutPixel(i + objToDraw->x, objToDraw->y, draw_color);
234                 subbmp.PutPixel(i + objToDraw->x, objToDraw->y + objToDraw->hit - 1, draw_color);
235             }
236             for (int i = 0; i < objToDraw->hit; i += 2)
237             {
238                 subbmp.PutPixel(objToDraw->x, i + objToDraw->y, draw_color);
239                 subbmp.PutPixel(objToDraw->x + objToDraw->wid - 1, i + objToDraw->y, draw_color);
240             }
241         }
242     }
243 
244     SET_EIP(380)
245 }
246 
DrawBlob(Common::Bitmap * ds,int x,int y,color_t draw_color)247 void GUIMain::DrawBlob(Common::Bitmap *ds, int x, int y, color_t draw_color)
248 {
249     ds->FillRect(Rect(x, y, x + get_fixed_pixel_size(1), y + get_fixed_pixel_size(1)), draw_color);
250 }
251 
Poll()252 void GUIMain::Poll()
253 {
254     int mxwas = mousex, mywas = mousey;
255 
256     mousex -= X;
257     mousey -= Y;
258     if (mousex != MouseWasAt.X || mousey != MouseWasAt.Y)
259     {
260         int ctrl_index = FindControlUnderMouse();
261 
262         if (MouseOverCtrl == MOVER_MOUSEDOWNLOCKED)
263             Controls[MouseDownCtrl]->MouseMove(mousex, mousey);
264         else if (ctrl_index != MouseOverCtrl)
265         {
266             if (MouseOverCtrl >= 0)
267                 Controls[MouseOverCtrl]->MouseLeave();
268 
269             if (ctrl_index >= 0 && Controls[ctrl_index]->IsDisabled())
270                 // the control is disabled - ignore it
271                 MouseOverCtrl = -1;
272             else if (ctrl_index >= 0 && !Controls[ctrl_index]->IsClickable())
273                 // the control is not clickable - ignore it
274                 MouseOverCtrl = -1;
275             else
276             {
277                 // over a different control
278                 MouseOverCtrl = ctrl_index;
279                 if (MouseOverCtrl >= 0)
280                 {
281                     Controls[MouseOverCtrl]->MouseOver();
282                     Controls[MouseOverCtrl]->MouseMove(mousex, mousey);
283                 }
284             }
285             guis_need_update = 1;
286         }
287         else if (MouseOverCtrl >= 0)
288             Controls[MouseOverCtrl]->MouseMove(mousex, mousey);
289     }
290 
291     MouseWasAt.X = mousex;
292     MouseWasAt.Y = mousey;
293     mousex = mxwas;
294     mousey = mywas;
295 }
296 
RebuildArray()297 void GUIMain::RebuildArray()
298 {
299     int thistype, thisnum;
300 
301     Controls.resize(ControlCount);
302     for (int i = 0; i < ControlCount; ++i)
303     {
304         thistype = (CtrlRefs[i] >> 16) & 0x000ffff;
305         thisnum = CtrlRefs[i] & 0x0000ffff;
306 
307         if (thisnum < 0 || thisnum >= 2000)
308             quit("GUIMain: rebuild array failed (invalid object index)");
309 
310         if (thistype == kGUIButton)
311             Controls[i] = &guibuts[thisnum];
312         else if (thistype == kGUILabel)
313             Controls[i] = &guilabels[thisnum];
314         else if (thistype == kGUIInvWindow)
315             Controls[i] = &guiinv[thisnum];
316         else if (thistype == kGUISlider)
317             Controls[i] = &guislider[thisnum];
318         else if (thistype == kGUITextBox)
319             Controls[i] = &guitext[thisnum];
320         else if (thistype == kGUIListBox)
321             Controls[i] = &guilist[thisnum];
322         else
323             quit("guimain: unknown control type found On gui");
324 
325         Controls[i]->guin = Id;
326         Controls[i]->objn = i;
327     }
328 
329     ResortZOrder();
330 }
331 
GUIControlZOrder(const GUIObject * e1,const GUIObject * e2)332 bool GUIControlZOrder(const GUIObject *e1, const GUIObject *e2)
333 {
334     return e1->zorder < e2->zorder;
335 }
336 
ResortZOrder()337 void GUIMain::ResortZOrder()
338 {
339     std::vector<GUIObject*> ctrl_sort = Controls;
340     std::sort(ctrl_sort.begin(), ctrl_sort.end(), GUIControlZOrder);
341 
342     CtrlDrawOrder.resize(ctrl_sort.size());
343     for (int i = 0; i < ControlCount; ++i)
344         CtrlDrawOrder[i] = ctrl_sort[i]->objn;
345 }
346 
SendControlToBack(int index)347 bool GUIMain::SendControlToBack(int index)
348 {
349     return SetControlZOrder(index, 0);
350 }
351 
SetControlZOrder(int index,int zorder)352 bool GUIMain::SetControlZOrder(int index, int zorder)
353 {
354     if (index < 0 || index >= ControlCount)
355         return false; // no such control
356 
357     zorder = Math::Clamp(0, ControlCount - 1, zorder);
358     const int old_zorder = Controls[index]->zorder;
359     if (old_zorder == zorder)
360         return false; // no change
361 
362     const bool move_back = zorder < old_zorder; // back is at zero index
363     const int  left      = move_back ? zorder : old_zorder;
364     const int  right     = move_back ? old_zorder : zorder;
365     for (int i = 0; i < ControlCount; ++i)
366     {
367         const int i_zorder = Controls[i]->zorder;
368         if (i_zorder == old_zorder)
369             Controls[i]->zorder = zorder; // the control we are moving
370         else if (i_zorder >= left && i_zorder <= right)
371         {
372             // controls in between old and new positions shift towards free place
373             if (move_back)
374                 Controls[i]->zorder++; // move to front
375             else
376                 Controls[i]->zorder--; // move to back
377         }
378     }
379     ResortZOrder();
380     OnControlPositionChanged();
381     return true;
382 }
383 
SetTransparencyAsPercentage(int percent)384 void GUIMain::SetTransparencyAsPercentage(int percent)
385 {
386     Transparency = GfxDef::Trans100ToLegacyTrans255(percent);
387 }
388 
SetVisibility(GUIVisibilityState visibility)389 void GUIMain::SetVisibility(GUIVisibilityState visibility)
390 {
391     _visibility = visibility;
392 }
393 
OnControlPositionChanged()394 void GUIMain::OnControlPositionChanged()
395 {
396     // force it to re-check for which control is under the mouse
397     MouseWasAt.X = -1;
398     MouseWasAt.Y = -1;
399 }
400 
OnMouseButtonDown()401 void GUIMain::OnMouseButtonDown()
402 {
403     if (MouseOverCtrl < 0)
404         return;
405 
406     // don't activate disabled buttons
407     if (Controls[MouseOverCtrl]->IsDisabled() || !Controls[MouseOverCtrl]->IsVisible() ||
408         !Controls[MouseOverCtrl]->IsClickable())
409     return;
410 
411     MouseDownCtrl = MouseOverCtrl;
412     if (Controls[MouseOverCtrl]->MouseDown())
413         MouseOverCtrl = MOVER_MOUSEDOWNLOCKED;
414     Controls[MouseDownCtrl]->MouseMove(mousex - X, mousey - Y);
415     guis_need_update = 1;
416 }
417 
OnMouseButtonUp()418 void GUIMain::OnMouseButtonUp()
419 {
420     // FocusCtrl was locked - reset it back to normal, but On the
421     // locked object so that a MouseLeave gets fired if necessary
422     if (MouseOverCtrl == MOVER_MOUSEDOWNLOCKED)
423     {
424         MouseOverCtrl = MouseDownCtrl;
425         MouseWasAt.X = -1;  // force update
426     }
427 
428     if (MouseDownCtrl < 0)
429         return;
430 
431     Controls[MouseDownCtrl]->MouseUp();
432     MouseDownCtrl = -1;
433     guis_need_update = 1;
434 }
435 
ReadFromFile(Stream * in,GuiVersion gui_version)436 void GUIMain::ReadFromFile(Stream *in, GuiVersion gui_version)
437 {
438     char tw_flags[GUIMAIN_LEGACY_TW_FLAGS_SIZE];
439     in->Read(tw_flags, sizeof(tw_flags));
440     if (gui_version < kGuiVersion_340)
441     {
442         Name.ReadCount(in, GUIMAIN_LEGACY_NAME_LENGTH);
443         OnClickHandler.ReadCount(in, GUIMAIN_LEGACY_EVENTHANDLER_LENGTH);
444     }
445     else
446     {
447         Name = StrUtil::ReadString(in);
448         OnClickHandler = StrUtil::ReadString(in);
449     }
450     X             = in->ReadInt32();
451     Y             = in->ReadInt32();
452     Width         = in->ReadInt32();
453     Height        = in->ReadInt32();
454     FocusCtrl     = in->ReadInt32();
455     ControlCount  = in->ReadInt32();
456     PopupStyle    = (GUIPopupStyle)in->ReadInt32();
457     PopupAtMouseY = in->ReadInt32();
458     BgColor       = in->ReadInt32();
459     BgImage       = in->ReadInt32();
460     FgColor       = in->ReadInt32();
461     MouseOverCtrl = in->ReadInt32();
462     MouseWasAt.X  = in->ReadInt32();
463     MouseWasAt.Y  = in->ReadInt32();
464     MouseDownCtrl = in->ReadInt32();
465     HighlightCtrl = in->ReadInt32();
466     Flags         = in->ReadInt32();
467     if (tw_flags[0] == kGUIMain_LegacyTextWindow)
468     {
469         Flags |= kGUIMain_TextWindow;
470     }
471     Transparency  = in->ReadInt32();
472     ZOrder        = in->ReadInt32();
473     Id            = in->ReadInt32();
474     Padding       = in->ReadInt32();
475     in->Seek(sizeof(int32_t) * GUIMAIN_RESERVED_INTS);
476     _visibility = (GUIVisibilityState)in->ReadInt32();
477 
478     if (gui_version < kGuiVersion_340)
479     {
480         CtrlRefs.resize(LEGACY_MAX_OBJS_ON_GUI);
481         // array of 32-bit pointers; these values are unused
482         in->Seek(LEGACY_MAX_OBJS_ON_GUI * sizeof(int32_t));
483         in->ReadArrayOfInt32(&CtrlRefs.front(), LEGACY_MAX_OBJS_ON_GUI);
484     }
485     else
486     {
487         CtrlRefs.resize(ControlCount);
488         if (ControlCount > 0)
489             in->ReadArrayOfInt32(&CtrlRefs.front(), ControlCount);
490     }
491 }
492 
WriteToFile(Stream * out,GuiVersion gui_version) const493 void GUIMain::WriteToFile(Stream *out, GuiVersion gui_version) const
494 {
495     char tw_flags[GUIMAIN_LEGACY_TW_FLAGS_SIZE] = {0};
496     if (Flags & kGUIMain_TextWindow)
497         tw_flags[0] = kGUIMain_LegacyTextWindow;
498     out->Write(tw_flags, sizeof(tw_flags));
499     if (gui_version < kGuiVersion_340)
500     {
501         Name.WriteCount(out, GUIMAIN_LEGACY_NAME_LENGTH);
502         OnClickHandler.WriteCount(out, GUIMAIN_LEGACY_EVENTHANDLER_LENGTH);
503     }
504     else
505     {
506         StrUtil::WriteString(Name, out);
507         StrUtil::WriteString(OnClickHandler, out);
508     }
509     out->WriteInt32(X);
510     out->WriteInt32(Y);
511     out->WriteInt32(Width);
512     out->WriteInt32(Height);
513     out->WriteInt32(FocusCtrl);
514     out->WriteInt32(ControlCount);
515     out->WriteInt32(PopupStyle);
516     out->WriteInt32(PopupAtMouseY);
517     out->WriteInt32(BgColor);
518     out->WriteInt32(BgImage);
519     out->WriteInt32(FgColor);
520     out->WriteInt32(MouseOverCtrl);
521     out->WriteInt32(MouseWasAt.X);
522     out->WriteInt32(MouseWasAt.Y);
523     out->WriteInt32(MouseDownCtrl);
524     out->WriteInt32(HighlightCtrl);
525     out->WriteInt32(Flags);
526     out->WriteInt32(Transparency);
527     out->WriteInt32(ZOrder);
528     out->WriteInt32(Id);
529     out->WriteInt32(Padding);
530     int32_t reserved_ints[GUIMAIN_RESERVED_INTS] = {0};
531     out->WriteArrayOfInt32(reserved_ints, GUIMAIN_RESERVED_INTS);
532     out->WriteInt32(_visibility);
533 
534     if (gui_version < kGuiVersion_340)
535     {
536         // array of dummy 32-bit pointers
537         int32_t dummy_arr[LEGACY_MAX_OBJS_ON_GUI] = {0};
538         out->WriteArrayOfInt32(dummy_arr, LEGACY_MAX_OBJS_ON_GUI);
539         out->WriteArrayOfInt32(&CtrlRefs.front(), LEGACY_MAX_OBJS_ON_GUI);
540     }
541     else if (ControlCount > 0)
542     {
543         out->WriteArrayOfInt32(&CtrlRefs.front(), ControlCount);
544     }
545 }
546 
547 } // namespace Common
548 } // namespace AGS
549 
550 GuiVersion GameGuiVersion = kGuiVersion_Initial;
read_gui(Stream * in,std::vector<GUIMain> & guiread,GameSetupStruct * gss)551 void read_gui(Stream *in, std::vector<GUIMain> &guiread, GameSetupStruct * gss)
552 {
553   int ee;
554 
555   if (in->ReadInt32() != (int)GUIMAGIC)
556     quit("read_gui: file is corrupt");
557 
558   GameGuiVersion = (GuiVersion)in->ReadInt32();
559   Debug::Printf(kDbgMsg_Init, "Game GUI version: %d", GameGuiVersion);
560   if (GameGuiVersion < kGuiVersion_214) {
561     gss->numgui = (int)GameGuiVersion;
562     GameGuiVersion = kGuiVersion_Initial;
563   }
564   else if (GameGuiVersion > kGuiVersion_Current)
565     quit("read_gui: this game requires a newer version of AGS");
566   else
567     gss->numgui = in->ReadInt32();
568 
569   if ((gss->numgui < 0) || (gss->numgui > 1000))
570     quit("read_gui: invalid number of GUIs, file corrupt?");
571 
572   guiread.resize(gss->numgui);
573 
574   // import the main GUI elements
575   for (int iteratorCount = 0; iteratorCount < gss->numgui; ++iteratorCount)
576   {
577     guiread[iteratorCount].Init();
578     guiread[iteratorCount].ReadFromFile(in, GameGuiVersion);
579   }
580 
581   for (ee = 0; ee < gss->numgui; ee++) {
582     if (guiread[ee].Height < 2)
583       guiread[ee].Height = 2;
584 
585     if (GameGuiVersion < kGuiVersion_unkn_103)
586       guiread[ee].Name.Format("GUI%d", ee);
587     if (GameGuiVersion < kGuiVersion_260)
588       guiread[ee].ZOrder = ee;
589     if (GameGuiVersion < kGuiVersion_331)
590       guiread[ee].Padding = TEXTWINDOW_PADDING_DEFAULT;
591 
592     if (loaded_game_file_version <= kGameVersion_272) // Fix names for 2.x: "GUI" -> "gGui"
593         guiread[ee].Name = GUIMain::FixupGUIName(guiread[ee].Name);
594 
595     guiread[ee].Id = ee;
596   }
597 
598   // import the buttons
599   numguibuts = in->ReadInt32();
600   guibuts.resize(numguibuts);
601 
602   for (ee = 0; ee < numguibuts; ee++)
603     guibuts[ee].ReadFromFile(in, GameGuiVersion);
604 
605   // labels
606   numguilabels = in->ReadInt32();
607   guilabels.resize(numguilabels);
608 
609   for (ee = 0; ee < numguilabels; ee++)
610     guilabels[ee].ReadFromFile(in, GameGuiVersion);
611 
612   // inv controls
613   numguiinv = in->ReadInt32();
614   guiinv.resize(numguiinv);
615 
616   for (ee = 0; ee < numguiinv; ee++)
617     guiinv[ee].ReadFromFile(in, GameGuiVersion);
618 
619   if (GameGuiVersion >= kGuiVersion_214) {
620     // sliders
621     numguislider = in->ReadInt32();
622     guislider.resize(numguislider);
623 
624     for (ee = 0; ee < numguislider; ee++)
625       guislider[ee].ReadFromFile(in, GameGuiVersion);
626   }
627 
628   if (GameGuiVersion >= kGuiVersion_222) {
629     // text boxes
630     numguitext = in->ReadInt32();
631     guitext.resize(numguitext);
632 
633     for (ee = 0; ee < numguitext; ee++)
634       guitext[ee].ReadFromFile(in, GameGuiVersion);
635   }
636 
637   if (GameGuiVersion >= kGuiVersion_230) {
638     // list boxes
639     numguilist = in->ReadInt32();
640     guilist.resize(numguilist);
641 
642     for (ee = 0; ee < numguilist; ee++)
643       guilist[ee].ReadFromFile(in, GameGuiVersion);
644   }
645 
646   // set up the reverse-lookup array
647   for (ee = 0; ee < gss->numgui; ee++) {
648     guiread[ee].RebuildArray();
649 
650     if (GameGuiVersion < kGuiVersion_270)
651       guiread[ee].OnClickHandler.Empty();
652 
653     for (int ff = 0; ff < guiread[ee].ControlCount; ff++) {
654       guiread[ee].Controls[ff]->guin = ee;
655       guiread[ee].Controls[ff]->objn = ff;
656 
657       if (GameGuiVersion < kGuiVersion_272e)
658         guiread[ee].Controls[ff]->zorder = ff;
659     }
660 
661     guiread[ee].ResortZOrder();
662   }
663 
664   guis_need_update = 1;
665 }
666 
write_gui(Stream * out,const std::vector<GUIMain> & guiwrite,GameSetupStruct * gss,bool savedgame)667 void write_gui(Stream *out, const std::vector<GUIMain> &guiwrite, GameSetupStruct * gss, bool savedgame)
668 {
669   int ee;
670 
671   out->WriteInt32(GUIMAGIC);
672 
673   GuiVersion write_version;
674   if (savedgame)
675     write_version = GameGuiVersion > kGuiVersion_ForwardCompatible ? GameGuiVersion : kGuiVersion_ForwardCompatible;
676   else
677     write_version = kGuiVersion_Current;
678 
679   out->WriteInt32(write_version);
680   out->WriteInt32(gss->numgui);
681 
682   for (int iteratorCount = 0; iteratorCount < gss->numgui; ++iteratorCount)
683   {
684     guiwrite[iteratorCount].WriteToFile(out, write_version);
685   }
686 
687   out->WriteInt32(numguibuts);
688   for (ee = 0; ee < numguibuts; ee++)
689     guibuts[ee].WriteToFile(out);
690 
691   out->WriteInt32(numguilabels);
692   for (ee = 0; ee < numguilabels; ee++)
693     guilabels[ee].WriteToFile(out);
694 
695   out->WriteInt32(numguiinv);
696   for (ee = 0; ee < numguiinv; ee++)
697     guiinv[ee].WriteToFile(out);
698 
699   out->WriteInt32(numguislider);
700   for (ee = 0; ee < numguislider; ee++)
701     guislider[ee].WriteToFile(out);
702 
703   out->WriteInt32(numguitext);
704   for (ee = 0; ee < numguitext; ee++)
705     guitext[ee].WriteToFile(out);
706 
707   out->WriteInt32(numguilist);
708   for (ee = 0; ee < numguilist; ee++)
709     guilist[ee].WriteToFile(out);
710 }
711