Ticket #2349: #2349.3.diff

File #2349.3.diff, 89.6 KB (added by Stan, 7 years ago)

Add back the default.cfg changes.

Line 
1Index: binaries/data/config/default.cfg
2===================================================================
3--- binaries/data/config/default.cfg (revision 19922)
4+++ binaries/data/config/default.cfg (working copy)
5@@ -56,6 +56,9 @@ yres = 0
6 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth)
7 bpp = 0
8
9+; Render at high-dpi resolution if screen supports it. Will be much slower.
10+high_dpi = false
11+
12 ; Preferred display (for multidisplay setups, only works with SDL 2.0)
13 display = 0
14Index: source/gui/CGUI.cpp
15===================================================================
16--- source/gui/CGUI.cpp (revision 19922)
17+++ source/gui/CGUI.cpp (working copy)
18@@ -1,1735 +1,1735 @@
19 /* Copyright (C) 2017 Wildfire Games.
20 * This file is part of 0 A.D.
21 *
22 * 0 A.D. is free software: you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation, either version 2 of the License, or
25 * (at your option) any later version.
26 *
27 * 0 A.D. is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
34 */
35
36 #include "precompiled.h"
37
38 #include <stdarg.h>
39 #include <string>
40
41 #include "GUI.h"
42
43 // Types - when including them into the engine.
44 #include "CButton.h"
45 #include "CChart.h"
46 #include "CCheckBox.h"
47 #include "CDropDown.h"
48 #include "CImage.h"
49 #include "CInput.h"
50 #include "CList.h"
51 #include "COList.h"
52 #include "CProgressBar.h"
53 #include "CRadioButton.h"
54 #include "CSlider.h"
55 #include "CText.h"
56 #include "CTooltip.h"
57 #include "MiniMap.h"
58
59 #include "graphics/FontMetrics.h"
60 #include "graphics/ShaderManager.h"
61 #include "graphics/TextRenderer.h"
62 #include "i18n/L10n.h"
63 #include "lib/bits.h"
64 #include "lib/input.h"
65 #include "lib/sysdep/sysdep.h"
66 #include "lib/timer.h"
67 #include "lib/utf8.h"
68 #include "ps/CLogger.h"
69 #include "ps/Filesystem.h"
70 #include "ps/GameSetup/Config.h"
71 #include "ps/Globals.h"
72 #include "ps/Hotkey.h"
73 #include "ps/Profile.h"
74 #include "ps/Pyrogenesis.h"
75 #include "ps/XML/Xeromyces.h"
76 #include "renderer/Renderer.h"
77 #include "scripting/ScriptFunctions.h"
78 #include "scriptinterface/ScriptInterface.h"
79
80 extern int g_yres;
81
82 const double SELECT_DBLCLICK_RATE = 0.5;
83 const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion
84
85 InReaction CGUI::HandleEvent(const SDL_Event_* ev)
86 {
87 InReaction ret = IN_PASS;
88
89 if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP)
90 {
91 const char* hotkey = static_cast<const char*>(ev->ev.user.data1);
92
93 std::map<CStr, std::vector<IGUIObject*> >::iterator it = m_HotkeyObjects.find(hotkey);
94 if (it != m_HotkeyObjects.end())
95 for (IGUIObject* const& obj : it->second)
96 {
97 // Update hotkey status before sending the event,
98 // else the status will be outdated when processing the GUI event.
99 HotkeyInputHandler(ev);
100 ret = IN_HANDLED;
101
102 if (ev->ev.type == SDL_HOTKEYDOWN)
103 obj->SendEvent(GUIM_PRESSED, "press");
104 else
105 obj->SendEvent(GUIM_RELEASED, "release");
106 }
107 }
108
109 else if (ev->ev.type == SDL_MOUSEMOTION)
110 {
111 // Yes the mouse position is stored as float to avoid
112- // constant conversions when operating in a
113- // float-based environment.
114- m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale);
115+ // constant conversions when operating in a
116+ // float-based environment.
117+ m_MousePos = CPos(static_cast<float>(ev->ev.motion.x) / g_GuiScale * g_DpiScale, static_cast<float>(ev->ev.motion.y) / g_GuiScale * g_DpiScale);
118
119 SGUIMessage msg(GUIM_MOUSE_MOTION);
120 GUI<SGUIMessage>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
121 &IGUIObject::HandleMessage,
122 msg);
123 }
124
125 // Update m_MouseButtons. (BUTTONUP is handled later.)
126 else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
127 {
128 switch (ev->ev.button.button)
129 {
130 case SDL_BUTTON_LEFT:
131 case SDL_BUTTON_RIGHT:
132 case SDL_BUTTON_MIDDLE:
133 m_MouseButtons |= Bit<unsigned int>(ev->ev.button.button);
134 break;
135 default:
136 break;
137 }
138 }
139
140 // Update m_MousePos (for delayed mouse button events)
141 CPos oldMousePos = m_MousePos;
142 if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
143 {
144- m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
145+ m_MousePos = CPos(static_cast<float>(ev->ev.button.x) / g_GuiScale * g_DpiScale, static_cast<float>(ev->ev.button.y) / g_GuiScale * g_DpiScale);
146 }
147
148 // Only one object can be hovered
149 IGUIObject* pNearest = NULL;
150
151 // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
152 try
153 {
154 PROFILE("mouse events");
155 // TODO Gee: Optimizations needed!
156 // these two recursive function are quite overhead heavy.
157
158 // pNearest will after this point at the hovered object, possibly NULL
159 pNearest = FindObjectUnderMouse();
160
161 // Now we'll call UpdateMouseOver on *all* objects,
162 // we'll input the one hovered, and they will each
163 // update their own data and send messages accordingly
164 GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST,
165 m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest);
166
167 if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
168 {
169 switch (ev->ev.button.button)
170 {
171 case SDL_BUTTON_LEFT:
172 // Focus the clicked object (or focus none if nothing clicked on)
173 SetFocusedObject(pNearest);
174
175 if (pNearest)
176 ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress");
177 break;
178
179 case SDL_BUTTON_RIGHT:
180 if (pNearest)
181 ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress");
182 break;
183
184 default:
185 break;
186 }
187 }
188 else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest)
189 {
190 if (ev->ev.wheel.y < 0)
191 ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown");
192 else if (ev->ev.wheel.y > 0)
193 ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup");
194 }
195 else if (ev->ev.type == SDL_MOUSEBUTTONUP)
196 {
197 switch (ev->ev.button.button)
198 {
199 case SDL_BUTTON_LEFT:
200 if (pNearest)
201 {
202 double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT];
203 pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time();
204
205 if (timeElapsed < SELECT_DBLCLICK_RATE)
206 ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick");
207 else
208 ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease");
209 }
210 break;
211 case SDL_BUTTON_RIGHT:
212 if (pNearest)
213 {
214 double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT];
215 pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time();
216
217 if (timeElapsed < SELECT_DBLCLICK_RATE)
218 ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick");
219 else
220 ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease");
221 }
222 break;
223 }
224
225 // Reset all states on all visible objects
226 GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject,
227 &IGUIObject::ResetStates);
228
229 // Since the hover state will have been reset, we reload it.
230 GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST,
231 m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest);
232 }
233 }
234 catch (PSERROR_GUI& e)
235 {
236 UNUSED2(e);
237 debug_warn(L"CGUI::HandleEvent error");
238 // TODO Gee: Handle
239 }
240
241 // BUTTONUP's effect on m_MouseButtons is handled after
242 // everything else, so that e.g. 'press' handlers (activated
243 // on button up) see which mouse button had been pressed.
244 if (ev->ev.type == SDL_MOUSEBUTTONUP)
245 {
246 switch (ev->ev.button.button)
247 {
248 case SDL_BUTTON_LEFT:
249 case SDL_BUTTON_RIGHT:
250 case SDL_BUTTON_MIDDLE:
251 m_MouseButtons &= ~Bit<unsigned int>(ev->ev.button.button);
252 break;
253 default:
254 break;
255 }
256 }
257
258 // Restore m_MousePos (for delayed mouse button events)
259 if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
260 m_MousePos = oldMousePos;
261
262 // Handle keys for input boxes
263 if (GetFocusedObject())
264 {
265 if ((ev->ev.type == SDL_KEYDOWN &&
266 ev->ev.key.keysym.sym != SDLK_ESCAPE &&
267 !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
268 !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) ||
269 ev->ev.type == SDL_HOTKEYDOWN ||
270 ev->ev.type == SDL_TEXTINPUT ||
271 ev->ev.type == SDL_TEXTEDITING)
272 {
273 ret = GetFocusedObject()->ManuallyHandleEvent(ev);
274 }
275 // else will return IN_PASS because we never used the button.
276 }
277
278 return ret;
279 }
280
281 void CGUI::TickObjects()
282 {
283 CStr action = "tick";
284 GUI<CStr>::RecurseObject(0, m_BaseObject,
285 &IGUIObject::ScriptEvent, action);
286
287 m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this);
288 }
289
290 void CGUI::SendEventToAll(const CStr& EventName)
291 {
292 // janwas 2006-03-03: spoke with Ykkrosh about EventName case.
293 // when registering, case is converted to lower - this avoids surprise
294 // if someone were to get the case wrong and then not notice their
295 // handler is never called. however, until now, the other end
296 // (sending events here) wasn't converting to lower case,
297 // leading to a similar problem.
298 // now fixed; case is irrelevant since all are converted to lower.
299 GUI<CStr>::RecurseObject(0, m_BaseObject,
300 &IGUIObject::ScriptEvent, EventName.LowerCase());
301 }
302
303 CGUI::CGUI(const shared_ptr<ScriptRuntime>& runtime)
304 : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0)
305 {
306 m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime));
307 GuiScriptingInit(*m_ScriptInterface);
308 m_ScriptInterface->LoadGlobalScripts();
309 m_BaseObject = new CGUIDummyObject;
310 m_BaseObject->SetGUI(this);
311 }
312
313 CGUI::~CGUI()
314 {
315 Destroy();
316
317 if (m_BaseObject)
318 delete m_BaseObject;
319 }
320
321 IGUIObject* CGUI::ConstructObject(const CStr& str)
322 {
323 if (m_ObjectTypes.count(str) > 0)
324 return (*m_ObjectTypes[str])();
325 else
326 {
327 // Error reporting will be handled with the NULL return.
328 return NULL;
329 }
330 }
331
332 void CGUI::Initialize()
333 {
334 // Add base types!
335 // You can also add types outside the GUI to extend the flexibility of the GUI.
336 // Pyrogenesis though will have all the object types inserted from here.
337 AddObjectType("empty", &CGUIDummyObject::ConstructObject);
338 AddObjectType("button", &CButton::ConstructObject);
339 AddObjectType("image", &CImage::ConstructObject);
340 AddObjectType("text", &CText::ConstructObject);
341 AddObjectType("checkbox", &CCheckBox::ConstructObject);
342 AddObjectType("radiobutton", &CRadioButton::ConstructObject);
343 AddObjectType("progressbar", &CProgressBar::ConstructObject);
344 AddObjectType("minimap", &CMiniMap::ConstructObject);
345 AddObjectType("input", &CInput::ConstructObject);
346 AddObjectType("list", &CList::ConstructObject);
347 AddObjectType("olist", &COList::ConstructObject);
348 AddObjectType("dropdown", &CDropDown::ConstructObject);
349 AddObjectType("tooltip", &CTooltip::ConstructObject);
350 AddObjectType("chart", &CChart::ConstructObject);
351 AddObjectType("slider", &CSlider::ConstructObject);
352 }
353
354 void CGUI::Draw()
355 {
356 // Clear the depth buffer, so the GUI is
357 // drawn on top of everything else
358 glClear(GL_DEPTH_BUFFER_BIT);
359
360 try
361 {
362 // Recurse IGUIObject::Draw() with restriction: hidden
363 // meaning all hidden objects won't call Draw (nor will it recurse its children)
364 GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw);
365 }
366 catch (PSERROR_GUI& e)
367 {
368 LOGERROR("GUI draw error: %s", e.what());
369 }
370 }
371
372 void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping))
373 {
374 // If the sprite doesn't exist (name == ""), don't bother drawing anything
375 if (Sprite.IsEmpty())
376 return;
377
378 // TODO: Clipping?
379
380 Sprite.Draw(Rect, CellID, m_Sprites, Z);
381 }
382
383 void CGUI::Destroy()
384 {
385 // We can use the map to delete all
386 // now we don't want to cancel all if one Destroy fails
387 for (const std::pair<CStr, IGUIObject*>& p : m_pAllObjects)
388 {
389 try
390 {
391 p.second->Destroy();
392 }
393 catch (PSERROR_GUI& e)
394 {
395 UNUSED2(e);
396 debug_warn(L"CGUI::Destroy error");
397 // TODO Gee: Handle
398 }
399
400 delete p.second;
401 }
402 m_pAllObjects.clear();
403
404 for (const std::pair<CStr, CGUISprite*>& p : m_Sprites)
405 delete p.second;
406 m_Sprites.clear();
407 m_Icons.clear();
408 }
409
410 void CGUI::UpdateResolution()
411 {
412 // Update ALL cached
413 GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize);
414 }
415
416 void CGUI::AddObject(IGUIObject* pObject)
417 {
418 try
419 {
420 // Add CGUI pointer
421 GUI<CGUI*>::RecurseObject(0, pObject, &IGUIObject::SetGUI, this);
422
423 m_BaseObject->AddChild(pObject);
424
425 // Cache tree
426 GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
427
428 SGUIMessage msg(GUIM_LOAD);
429 GUI<SGUIMessage>::RecurseObject(0, pObject, &IGUIObject::HandleMessage, msg);
430 }
431 catch (PSERROR_GUI&)
432 {
433 throw;
434 }
435 }
436
437 void CGUI::UpdateObjects()
438 {
439 // We'll fill a temporary map until we know everything succeeded
440 map_pObjects AllObjects;
441
442 try
443 {
444 // Fill freshly
445 GUI<map_pObjects>::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects);
446 }
447 catch (PSERROR_GUI&)
448 {
449 throw;
450 }
451
452 // Else actually update the real one
453 m_pAllObjects.swap(AllObjects);
454 }
455
456 bool CGUI::ObjectExists(const CStr& Name) const
457 {
458 return m_pAllObjects.count(Name) != 0;
459 }
460
461 IGUIObject* CGUI::FindObjectByName(const CStr& Name) const
462 {
463 map_pObjects::const_iterator it = m_pAllObjects.find(Name);
464 if (it == m_pAllObjects.end())
465 return NULL;
466 else
467 return it->second;
468 }
469
470 IGUIObject* CGUI::FindObjectUnderMouse() const
471 {
472 IGUIObject* pNearest = NULL;
473
474 GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
475 &IGUIObject::ChooseMouseOverAndClosest, pNearest);
476
477 return pNearest;
478 }
479
480 void CGUI::SetFocusedObject(IGUIObject* pObject)
481 {
482 if (pObject == m_FocusedObject)
483 return;
484
485 if (m_FocusedObject)
486 {
487 SGUIMessage msg(GUIM_LOST_FOCUS);
488 m_FocusedObject->HandleMessage(msg);
489 }
490
491 m_FocusedObject = pObject;
492
493 if (m_FocusedObject)
494 {
495 SGUIMessage msg(GUIM_GOT_FOCUS);
496 m_FocusedObject->HandleMessage(msg);
497 }
498 }
499
500 // private struct used only in GenerateText(...)
501 struct SGenerateTextImage
502 {
503 float m_YFrom, // The image's starting location in Y
504 m_YTo, // The image's end location in Y
505 m_Indentation; // The image width in other words
506
507 // Some help functions
508 // TODO Gee: CRect => CPoint ?
509 void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall& SpriteCall,
510 const float width, const float y,
511 const CSize& Size, const CStr& TextureName,
512 const float BufferZone, const int CellID)
513 {
514 // TODO Gee: Temp hardcoded values
515 SpriteCall.m_Area.top = y+BufferZone;
516 SpriteCall.m_Area.bottom = y+BufferZone + Size.cy;
517
518 if (Left)
519 {
520 SpriteCall.m_Area.left = BufferZone;
521 SpriteCall.m_Area.right = Size.cx+BufferZone;
522 }
523 else
524 {
525 SpriteCall.m_Area.left = width-BufferZone - Size.cx;
526 SpriteCall.m_Area.right = width-BufferZone;
527 }
528
529 SpriteCall.m_CellID = CellID;
530 SpriteCall.m_Sprite = TextureName;
531
532 m_YFrom = SpriteCall.m_Area.top-BufferZone;
533 m_YTo = SpriteCall.m_Area.bottom+BufferZone;
534 m_Indentation = Size.cx+BufferZone*2;
535 }
536 };
537
538 SGUIText CGUI::GenerateText(const CGUIString& string, const CStrW& FontW, const float& Width, const float& BufferZone, const IGUIObject* pObject)
539 {
540 SGUIText Text;
541
542 CStrIntern Font(FontW.ToUTF8());
543
544 if (string.m_Words.empty())
545 return Text;
546
547 float x = BufferZone, y = BufferZone; // drawing pointer
548 int from = 0;
549 bool done = false;
550
551 bool FirstLine = true; // Necessary because text in the first line is shorter
552 // (it doesn't count the line spacing)
553
554 // Images on the left or the right side.
555 std::vector<SGenerateTextImage> Images[2];
556 int pos_last_img = -1; // Position in the string where last img (either left or right) were encountered.
557 // in order to avoid duplicate processing.
558
559 // Easier to read.
560 bool WordWrapping = (Width != 0);
561
562 // get the alignment type for the control we are computing the text for since
563 // we are computing the horizontal alignment in this method in order to not have
564 // to run through the TextCalls a second time in the CalculateTextPosition method again
565 EAlign align;
566 GUI<EAlign>::GetSetting(pObject, "text_align", align);
567
568 // Go through string word by word
569 for (int i = 0; i < (int)string.m_Words.size()-1 && !done; ++i)
570 {
571 // Pre-process each line one time, so we know which floating images
572 // will be added for that line.
573
574 // Generated stuff is stored in Feedback.
575 CGUIString::SFeedback Feedback;
576
577 // Preliminary line_height, used for word-wrapping with floating images.
578 float prelim_line_height = 0.f;
579
580 // Width and height of all text calls generated.
581 string.GenerateTextCall(this, Feedback, Font,
582 string.m_Words[i], string.m_Words[i+1],
583 FirstLine);
584
585 // Loop through our images queues, to see if images has been added.
586
587 // Check if this has already been processed.
588 // Also, floating images are only applicable if Word-Wrapping is on
589 if (WordWrapping && i > pos_last_img)
590 {
591 // Loop left/right
592 for (int j = 0; j < 2; ++j)
593 {
594 for (const CStr& imgname : Feedback.m_Images[j])
595 {
596 SGUIText::SSpriteCall SpriteCall;
597 SGenerateTextImage Image;
598
599 // Y is if no other floating images is above, y. Else it is placed
600 // after the last image, like a stack downwards.
601 float _y;
602 if (!Images[j].empty())
603 _y = std::max(y, Images[j].back().m_YTo);
604 else
605 _y = y;
606
607 // Get Size from Icon database
608 SGUIIcon icon = GetIcon(imgname);
609
610 CSize size = icon.m_Size;
611 Image.SetupSpriteCall((j == CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID);
612
613 // Check if image is the lowest thing.
614 Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo);
615
616 Images[j].push_back(Image);
617 Text.m_SpriteCalls.push_back(SpriteCall);
618 }
619 }
620 }
621
622 pos_last_img = std::max(pos_last_img, i);
623
624 x += Feedback.m_Size.cx;
625 prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.cy);
626
627 // If Width is 0, then there's no word-wrapping, disable NewLine.
628 if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2)
629 {
630 // Change 'from' to 'i', but first keep a copy of its value.
631 int temp_from = from;
632 from = i;
633
634 static const int From = 0, To = 1;
635 //int width_from=0, width_to=width;
636 float width_range[2];
637 width_range[From] = BufferZone;
638 width_range[To] = Width - BufferZone;
639
640 // Floating images are only applicable if word-wrapping is enabled.
641 if (WordWrapping)
642 {
643 // Decide width of the line. We need to iterate our floating images.
644 // this won't be exact because we're assuming the line_height
645 // will be as our preliminary calculation said. But that may change,
646 // although we'd have to add a couple of more loops to try straightening
647 // this problem out, and it is very unlikely to happen noticeably if one
648 // structures his text in a stylistically pure fashion. Even if not, it
649 // is still quite unlikely it will happen.
650 // Loop through left and right side, from and to.
651 for (int j = 0; j < 2; ++j)
652 {
653 for (const SGenerateTextImage& img : Images[j])
654 {
655 // We're working with two intervals here, the image's and the line height's.
656 // let's find the union of these two.
657 float union_from, union_to;
658
659 union_from = std::max(y, img.m_YFrom);
660 union_to = std::min(y+prelim_line_height, img.m_YTo);
661
662 // The union is not empty
663 if (union_to > union_from)
664 {
665 if (j == From)
666 width_range[From] = std::max(width_range[From], img.m_Indentation);
667 else
668 width_range[To] = std::min(width_range[To], Width - img.m_Indentation);
669 }
670 }
671 }
672 }
673
674 // Reset X for the next loop
675 x = width_range[From];
676
677 // Now we'll do another loop to figure out the height and width of
678 // the line (the height of the largest character and the width is
679 // the sum of all of the individual widths). This
680 // couldn't be determined in the first loop (main loop)
681 // because it didn't regard images, so we don't know
682 // if all characters processed, will actually be involved
683 // in that line.
684 float line_height = 0.f;
685 float line_width = 0.f;
686 for (int j = temp_from; j <= i; ++j)
687 {
688 // We don't want to use Feedback now, so we'll have to use
689 // another.
690 CGUIString::SFeedback Feedback2;
691
692 // Don't attach object, it'll suppress the errors
693 // we want them to be reported in the final GenerateTextCall()
694 // so that we don't get duplicates.
695 string.GenerateTextCall(this, Feedback2, Font,
696 string.m_Words[j], string.m_Words[j+1],
697 FirstLine);
698
699 // Append X value.
700 x += Feedback2.m_Size.cx;
701
702 if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine)
703 break;
704
705 // Let line_height be the maximum m_Height we encounter.
706 line_height = std::max(line_height, Feedback2.m_Size.cy);
707
708 line_width += Feedback2.m_Size.cx;
709
710 if (WordWrapping && Feedback2.m_NewLine)
711 break;
712 }
713
714 float dx = 0.f;
715 // compute offset based on what kind of alignment
716 switch (align)
717 {
718 case EAlign_Left:
719 // don't add an offset
720 dx = 0.f;
721 break;
722
723 case EAlign_Center:
724 dx = ((width_range[To] - width_range[From]) - line_width) / 2;
725 break;
726
727 case EAlign_Right:
728 dx = width_range[To] - line_width;
729 break;
730
731 default:
732 debug_warn(L"Broken EAlign in CGUI::GenerateText()");
733 break;
734 }
735 // Reset x once more
736 x = width_range[From];
737 // Move down, because font drawing starts from the baseline
738 y += line_height;
739
740 // Do the real processing now
741 for (int j = temp_from; j <= i; ++j)
742 {
743 // We don't want to use Feedback now, so we'll have to use
744 // another one.
745 CGUIString::SFeedback Feedback2;
746
747 // Defaults
748 string.GenerateTextCall(this, Feedback2, Font,
749 string.m_Words[j], string.m_Words[j+1],
750 FirstLine, pObject);
751
752 // Iterate all and set X/Y values
753 // Since X values are not set, we need to make an internal
754 // iteration with an increment that will append the internal
755 // x, that is what x_pointer is for.
756 float x_pointer = 0.f;
757
758 for (SGUIText::STextCall& tc : Feedback2.m_TextCalls)
759 {
760 tc.m_Pos = CPos(dx + x + x_pointer, y);
761
762 x_pointer += tc.m_Size.cx;
763
764 if (tc.m_pSpriteCall)
765 tc.m_pSpriteCall->m_Area += tc.m_Pos - CSize(0, tc.m_pSpriteCall->m_Area.GetHeight());
766 }
767
768 // Append X value.
769 x += Feedback2.m_Size.cx;
770
771 Text.m_Size.cx = std::max(Text.m_Size.cx, x+BufferZone);
772
773 // The first word overrides the width limit, what we
774 // do, in those cases, are just drawing that word even
775 // though it'll extend the object.
776 if (WordWrapping) // only if word-wrapping is applicable
777 {
778 if (Feedback2.m_NewLine)
779 {
780 from = j+1;
781
782 // Sprite call can exist within only a newline segment,
783 // therefore we need this.
784 Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
785 break;
786 }
787 else if (x > width_range[To] && j == temp_from)
788 {
789 from = j+1;
790 // do not break, since we want it to be added to m_TextCalls
791 }
792 else if (x > width_range[To])
793 {
794 from = j;
795 break;
796 }
797 }
798
799 // Add the whole Feedback2.m_TextCalls to our m_TextCalls.
800 Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end());
801 Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
802
803 if (j == (int)string.m_Words.size()-2)
804 done = true;
805 }
806
807 // Reset X
808 x = 0.f;
809
810 // Update height of all
811 Text.m_Size.cy = std::max(Text.m_Size.cy, y+BufferZone);
812
813 FirstLine = false;
814
815 // Now if we entered as from = i, then we want
816 // i being one minus that, so that it will become
817 // the same i in the next loop. The difference is that
818 // we're on a new line now.
819 i = from-1;
820 }
821 }
822
823 return Text;
824 }
825
826 void CGUI::DrawText(SGUIText& Text, const CColor& DefaultColor, const CPos& pos, const float& z, const CRect& clipping)
827 {
828 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
829
830 tech->BeginPass();
831
832 bool isClipped = (clipping != CRect());
833 if (isClipped)
834 {
835 glEnable(GL_SCISSOR_TEST);
836 glScissor(
837 clipping.left * g_GuiScale,
838 g_yres - clipping.bottom * g_GuiScale,
839 clipping.GetWidth() * g_GuiScale,
840 clipping.GetHeight() * g_GuiScale);
841 }
842
843 CTextRenderer textRenderer(tech->GetShader());
844 textRenderer.SetClippingRect(clipping);
845 textRenderer.Translate(0.0f, 0.0f, z);
846
847 for (const SGUIText::STextCall& tc : Text.m_TextCalls)
848 {
849 // If this is just a placeholder for a sprite call, continue
850 if (tc.m_pSpriteCall)
851 continue;
852
853 CColor color = tc.m_UseCustomColor ? tc.m_Color : DefaultColor;
854
855 textRenderer.Color(color);
856 textRenderer.Font(tc.m_Font);
857 textRenderer.Put((float)(int)(pos.x + tc.m_Pos.x), (float)(int)(pos.y + tc.m_Pos.y), &tc.m_String);
858 }
859
860 textRenderer.Render();
861
862 for (const SGUIText::SSpriteCall& sc : Text.m_SpriteCalls)
863 DrawSprite(sc.m_Sprite, sc.m_CellID, z, sc.m_Area + pos);
864
865 if (isClipped)
866 glDisable(GL_SCISSOR_TEST);
867
868 tech->EndPass();
869 }
870
871 bool CGUI::GetPreDefinedColor(const CStr& name, CColor& Output) const
872 {
873 std::map<CStr, CColor>::const_iterator cit = m_PreDefinedColors.find(name);
874 if (cit == m_PreDefinedColors.end())
875 return false;
876
877 Output = cit->second;
878 return true;
879 }
880
881 /**
882 * @callgraph
883 */
884 void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set<VfsPath>& Paths)
885 {
886 Paths.insert(Filename);
887
888 CXeromyces XeroFile;
889 if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK)
890 return;
891
892 XMBElement node = XeroFile.GetRoot();
893
894 CStr root_name(XeroFile.GetElementString(node.GetNodeName()));
895 try
896 {
897 if (root_name == "objects")
898 {
899 Xeromyces_ReadRootObjects(node, &XeroFile, Paths);
900
901 // Re-cache all values so these gets cached too.
902 //UpdateResolution();
903 }
904 else if (root_name == "sprites")
905 Xeromyces_ReadRootSprites(node, &XeroFile);
906 else if (root_name == "styles")
907 Xeromyces_ReadRootStyles(node, &XeroFile);
908 else if (root_name == "setup")
909 Xeromyces_ReadRootSetup(node, &XeroFile);
910 else
911 debug_warn(L"CGUI::LoadXmlFile error");
912 }
913 catch (PSERROR_GUI& e)
914 {
915 LOGERROR("Errors loading GUI file %s (%u)", Filename.string8(), e.getCode());
916 return;
917 }
918 }
919
920 //===================================================================
921 // XML Reading Xeromyces Specific Sub-Routines
922 //===================================================================
923
924 void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set<VfsPath>& Paths)
925 {
926 int el_script = pFile->GetElementID("script");
927
928 std::vector<std::pair<CStr, CStr> > subst;
929
930 // Iterate main children
931 // they should all be <object> or <script> elements
932 for (XMBElement child : Element.GetChildNodes())
933 {
934 if (child.GetNodeName() == el_script)
935 // Execute the inline script
936 Xeromyces_ReadScript(child, pFile, Paths);
937 else
938 // Read in this whole object into the GUI
939 Xeromyces_ReadObject(child, pFile, m_BaseObject, subst, Paths, 0);
940 }
941 }
942
943 void CGUI::Xeromyces_ReadRootSprites(XMBElement Element, CXeromyces* pFile)
944 {
945 for (XMBElement child : Element.GetChildNodes())
946 Xeromyces_ReadSprite(child, pFile);
947 }
948
949 void CGUI::Xeromyces_ReadRootStyles(XMBElement Element, CXeromyces* pFile)
950 {
951 for (XMBElement child : Element.GetChildNodes())
952 Xeromyces_ReadStyle(child, pFile);
953 }
954
955 void CGUI::Xeromyces_ReadRootSetup(XMBElement Element, CXeromyces* pFile)
956 {
957 for (XMBElement child : Element.GetChildNodes())
958 {
959 CStr name(pFile->GetElementString(child.GetNodeName()));
960
961 if (name == "scrollbar")
962 Xeromyces_ReadScrollBarStyle(child, pFile);
963 else if (name == "icon")
964 Xeromyces_ReadIcon(child, pFile);
965 else if (name == "tooltip")
966 Xeromyces_ReadTooltip(child, pFile);
967 else if (name == "color")
968 Xeromyces_ReadColor(child, pFile);
969 else
970 debug_warn(L"Invalid data - DTD shouldn't allow this");
971 }
972 }
973
974 void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector<std::pair<CStr, CStr> >& NameSubst, boost::unordered_set<VfsPath>& Paths, u32 nesting_depth)
975 {
976 ENSURE(pParent);
977
978 XMBAttributeList attributes = Element.GetAttributes();
979
980 CStr type(attributes.GetNamedItem(pFile->GetAttributeID("type")));
981 if (type.empty())
982 type = "empty";
983
984 // Construct object from specified type
985 // henceforth, we need to do a rollback before aborting.
986 // i.e. releasing this object
987 IGUIObject* object = ConstructObject(type);
988
989 if (!object)
990 {
991 LOGERROR("GUI: Unrecognized object type \"%s\"", type.c_str());
992 return;
993 }
994
995 // Cache some IDs for element attribute names, to avoid string comparisons
996 #define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
997 #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
998 ELMT(object);
999 ELMT(action);
1000 ELMT(repeat);
1001 ELMT(translatableAttribute);
1002 ELMT(translate);
1003 ELMT(attribute);
1004 ELMT(keep);
1005 ELMT(include);
1006 ATTR(style);
1007 ATTR(type);
1008 ATTR(name);
1009 ATTR(hotkey);
1010 ATTR(z);
1011 ATTR(on);
1012 ATTR(file);
1013 ATTR(directory);
1014 ATTR(id);
1015 ATTR(context);
1016
1017 //
1018 // Read Style and set defaults
1019 //
1020 // If the setting "style" is set, try loading that setting.
1021 //
1022 // Always load default (if it's available) first!
1023 //
1024 CStr argStyle(attributes.GetNamedItem(attr_style));
1025
1026 if (m_Styles.count("default") == 1)
1027 object->LoadStyle(*this, "default");
1028
1029 if (!argStyle.empty())
1030 {
1031 if (m_Styles.count(argStyle) == 0)
1032 LOGERROR("GUI: Trying to use style '%s' that doesn't exist.", argStyle.c_str());
1033 else
1034 object->LoadStyle(*this, argStyle);
1035 }
1036
1037 bool NameSet = false;
1038 bool ManuallySetZ = false;
1039
1040 CStrW inclusionPath;
1041 CStr hotkeyTag;
1042
1043 for (XMBAttribute attr : attributes)
1044 {
1045 // If value is "null", then it is equivalent as never being entered
1046 if (CStr(attr.Value) == "null")
1047 continue;
1048
1049 // Ignore "type" and "style", we've already checked it
1050 if (attr.Name == attr_type || attr.Name == attr_style)
1051 continue;
1052
1053 if (attr.Name == attr_name)
1054 {
1055 CStr name(attr.Value);
1056
1057 for (const std::pair<CStr, CStr>& sub : NameSubst)
1058 name.Replace(sub.first, sub.second);
1059
1060 object->SetName(name);
1061 NameSet = true;
1062 continue;
1063 }
1064
1065 if (attr.Name == attr_hotkey)
1066 hotkeyTag = attr.Value;
1067
1068 if (attr.Name == attr_z)
1069 ManuallySetZ = true;
1070
1071 if (object->SetSetting(pFile->GetAttributeString(attr.Name), attr.Value.FromUTF8(), true) != PSRETURN_OK)
1072 LOGERROR("GUI: (object: %s) Can't set \"%s\" to \"%s\"", object->GetPresentableName(), pFile->GetAttributeString(attr.Name), attr.Value);
1073 }
1074
1075 // Check if name isn't set, generate an internal name in that case.
1076 if (!NameSet)
1077 {
1078 object->SetName("__internal(" + CStr::FromInt(m_InternalNameNumber) + ")");
1079 ++m_InternalNameNumber;
1080 }
1081
1082 if (!hotkeyTag.empty())
1083 m_HotkeyObjects[hotkeyTag].push_back(object);
1084
1085 CStrW caption(Element.GetText().FromUTF8());
1086 if (!caption.empty())
1087 object->SetSetting("caption", caption, true);
1088
1089 for (XMBElement child : Element.GetChildNodes())
1090 {
1091 // Check what name the elements got
1092 int element_name = child.GetNodeName();
1093
1094 if (element_name == elmt_object)
1095 {
1096 // Call this function on the child
1097 Xeromyces_ReadObject(child, pFile, object, NameSubst, Paths, nesting_depth);
1098 }
1099 else if (element_name == elmt_action)
1100 {
1101 // Scripted <action> element
1102
1103 // Check for a 'file' parameter
1104 CStrW filename(child.GetAttributes().GetNamedItem(attr_file).FromUTF8());
1105
1106 CStr code;
1107
1108 // If there is a file, open it and use it as the code
1109 if (!filename.empty())
1110 {
1111 Paths.insert(filename);
1112 CVFSFile scriptfile;
1113 if (scriptfile.Load(g_VFS, filename) != PSRETURN_OK)
1114 {
1115 LOGERROR("Error opening GUI script action file '%s'", utf8_from_wstring(filename));
1116 throw PSERROR_GUI_JSOpenFailed();
1117 }
1118
1119 code = scriptfile.DecodeUTF8(); // assume it's UTF-8
1120 }
1121
1122 XMBElementList grandchildren = child.GetChildNodes();
1123 if (!grandchildren.empty()) // The <action> element contains <keep> and <translate> tags.
1124 for (XMBElement grandchild : grandchildren)
1125 {
1126 if (grandchild.GetNodeName() == elmt_translate)
1127 code += g_L10n.Translate(grandchild.GetText());
1128 else if (grandchild.GetNodeName() == elmt_keep)
1129 code += grandchild.GetText();
1130 }
1131 else // It???s pure JavaScript code.
1132 // Read the inline code (concatenating to the file code, if both are specified)
1133 code += CStr(child.GetText());
1134
1135 CStr action = CStr(child.GetAttributes().GetNamedItem(attr_on));
1136
1137 // We need to set the GUI this object belongs to because RegisterScriptHandler requires an associated GUI.
1138 object->SetGUI(this);
1139 object->RegisterScriptHandler(action.LowerCase(), code, this);
1140 }
1141 else if (element_name == elmt_repeat)
1142 {
1143 Xeromyces_ReadRepeat(child, pFile, object, NameSubst, Paths, nesting_depth);
1144 }
1145 else if (element_name == elmt_translatableAttribute)
1146 {
1147 // This is an element in the form ???<translatableAttribute id="attributeName">attributeValue</translatableAttribute>???.
1148 CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
1149 if (attributeName.empty())
1150 {
1151 LOGERROR("GUI: ???translatableAttribute??? XML element with empty ???id??? XML attribute found. (object: %s)", object->GetPresentableName().c_str());
1152 continue;
1153 }
1154
1155 CStr value(child.GetText());
1156 if (value.empty())
1157 continue;
1158
1159 CStr context(child.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
1160 if (!context.empty())
1161 {
1162 CStr translatedValue(g_L10n.TranslateWithContext(context, value));
1163 object->SetSetting(attributeName, translatedValue.FromUTF8(), true);
1164 }
1165 else
1166 {
1167 CStr translatedValue(g_L10n.Translate(value));
1168 object->SetSetting(attributeName, translatedValue.FromUTF8(), true);
1169 }
1170 }
1171 else if (element_name == elmt_attribute)
1172 {
1173 // This is an element in the form ???<attribute id="attributeName"><keep>Don???t translate this part
1174 // </keep><translate>but translate this one.</translate></attribute>???.
1175 CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
1176 if (attributeName.empty())
1177 {
1178 LOGERROR("GUI: ???attribute??? XML element with empty ???id??? XML attribute found. (object: %s)", object->GetPresentableName().c_str());
1179 continue;
1180 }
1181
1182 CStr translatedValue;
1183
1184 for (XMBElement grandchild : child.GetChildNodes())
1185 {
1186 if (grandchild.GetNodeName() == elmt_translate)
1187 translatedValue += g_L10n.Translate(grandchild.GetText());
1188 else if (grandchild.GetNodeName() == elmt_keep)
1189 translatedValue += grandchild.GetText();
1190 }
1191 object->SetSetting(attributeName, translatedValue.FromUTF8(), true);
1192 }
1193 else if (element_name == elmt_include)
1194 {
1195 CStrW filename(child.GetAttributes().GetNamedItem(attr_file).FromUTF8());
1196 CStrW directory(child.GetAttributes().GetNamedItem(attr_directory).FromUTF8());
1197 if (!filename.empty())
1198 {
1199 if (!directory.empty())
1200 LOGWARNING("GUI: Include element found with file name (%s) and directory name (%s). Only the file will be processed.", utf8_from_wstring(filename), utf8_from_wstring(directory));
1201
1202 Paths.insert(filename);
1203
1204 CXeromyces XeroIncluded;
1205 if (XeroIncluded.Load(g_VFS, filename, "gui") != PSRETURN_OK)
1206 {
1207 LOGERROR("GUI: Error reading included XML: '%s'", utf8_from_wstring(filename));
1208 continue;
1209 }
1210
1211 XMBElement node = XeroIncluded.GetRoot();
1212 if (node.GetNodeName() != XeroIncluded.GetElementID("object"))
1213 {
1214 LOGERROR("GUI: Error reading included XML: '%s', root element must have be of type 'object'.", utf8_from_wstring(filename));
1215 continue;
1216 }
1217
1218 if (nesting_depth+1 >= MAX_OBJECT_DEPTH)
1219 {
1220 LOGERROR("GUI: Too many nested GUI includes. Probably caused by a recursive include attribute. Abort rendering '%s'.", utf8_from_wstring(filename));
1221 continue;
1222 }
1223
1224 Xeromyces_ReadObject(node, &XeroIncluded, object, NameSubst, Paths, nesting_depth+1);
1225 }
1226 else if (!directory.empty())
1227 {
1228 if (nesting_depth+1 >= MAX_OBJECT_DEPTH)
1229 {
1230 LOGERROR("GUI: Too many nested GUI includes. Probably caused by a recursive include attribute. Abort rendering '%s'.", utf8_from_wstring(directory));
1231 continue;
1232 }
1233
1234 VfsPaths pathnames;
1235 vfs::GetPathnames(g_VFS, directory, L"*.xml", pathnames);
1236 for (const VfsPath& path : pathnames)
1237 {
1238 // as opposed to loading scripts, don't care if it's loaded before
1239 // one might use the same parts of the GUI in different situations
1240 Paths.insert(path);
1241 CXeromyces XeroIncluded;
1242 if (XeroIncluded.Load(g_VFS, path, "gui") != PSRETURN_OK)
1243 {
1244 LOGERROR("GUI: Error reading included XML: '%s'", path.string8());
1245 continue;
1246 }
1247
1248 XMBElement node = XeroIncluded.GetRoot();
1249 if (node.GetNodeName() != XeroIncluded.GetElementID("object"))
1250 {
1251 LOGERROR("GUI: Error reading included XML: '%s', root element must have be of type 'object'.", path.string8());
1252 continue;
1253 }
1254 Xeromyces_ReadObject(node, &XeroIncluded, object, NameSubst, Paths, nesting_depth+1);
1255 }
1256
1257 }
1258 else
1259 LOGERROR("GUI: 'include' XML element must have valid 'file' or 'directory' attribute found. (object %s)", object->GetPresentableName().c_str());
1260 }
1261 else
1262 {
1263 // Try making the object read the tag.
1264 if (!object->HandleAdditionalChildren(child, pFile))
1265 LOGERROR("GUI: (object: %s) Reading unknown children for its type", object->GetPresentableName().c_str());
1266 }
1267 }
1268
1269 if (!ManuallySetZ)
1270 {
1271 // Set it automatically to 10 plus its parents
1272 bool absolute;
1273 GUI<bool>::GetSetting(object, "absolute", absolute);
1274
1275 if (absolute)
1276 // If the object is absolute, we'll have to get the parent's Z buffered,
1277 // and add to that!
1278 GUI<float>::SetSetting(object, "z", pParent->GetBufferedZ() + 10.f, true);
1279 else
1280 // If the object is relative, then we'll just store Z as "10"
1281 GUI<float>::SetSetting(object, "z", 10.f, true);
1282 }
1283
1284 try
1285 {
1286 if (pParent == m_BaseObject)
1287 AddObject(object);
1288 else
1289 pParent->AddChild(object);
1290 }
1291 catch (PSERROR_GUI& e)
1292 {
1293 LOGERROR("GUI error: %s", e.what());
1294 }
1295 }
1296
1297 void CGUI::Xeromyces_ReadRepeat(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector<std::pair<CStr, CStr> >& NameSubst, boost::unordered_set<VfsPath>& Paths, u32 nesting_depth)
1298 {
1299 #define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
1300 #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
1301 ELMT(object);
1302 ATTR(count);
1303 ATTR(var);
1304
1305 XMBAttributeList attributes = Element.GetAttributes();
1306
1307 int count = CStr(attributes.GetNamedItem(attr_count)).ToInt();
1308 CStr var("["+attributes.GetNamedItem(attr_var)+"]");
1309 if (var.size() < 3)
1310 var = "[n]";
1311
1312 for (int n = 0; n < count; ++n)
1313 {
1314 NameSubst.emplace_back(var, "[" + CStr::FromInt(n) + "]");
1315
1316 XERO_ITER_EL(Element, child)
1317 {
1318 if (child.GetNodeName() == elmt_object)
1319 Xeromyces_ReadObject(child, pFile, pParent, NameSubst, Paths, nesting_depth);
1320 }
1321 NameSubst.pop_back();
1322 }
1323 }
1324
1325 void CGUI::Xeromyces_ReadScript(XMBElement Element, CXeromyces* pFile, boost::unordered_set<VfsPath>& Paths)
1326 {
1327 // Check for a 'file' parameter
1328 CStrW file(Element.GetAttributes().GetNamedItem(pFile->GetAttributeID("file")).FromUTF8());
1329
1330 // If there is a file specified, open and execute it
1331 if (!file.empty())
1332 {
1333 Paths.insert(file);
1334 try
1335 {
1336 m_ScriptInterface->LoadGlobalScriptFile(file);
1337 }
1338 catch (PSERROR_Scripting& e)
1339 {
1340 LOGERROR("GUI: Error executing script %s: %s", utf8_from_wstring(file), e.what());
1341 }
1342 }
1343
1344 // If it has a directory attribute, read all JS files in that directory
1345 CStrW directory(Element.GetAttributes().GetNamedItem(pFile->GetAttributeID("directory")).FromUTF8());
1346 if (!directory.empty())
1347 {
1348 VfsPaths pathnames;
1349 vfs::GetPathnames(g_VFS, directory, L"*.js", pathnames);
1350 for (const VfsPath& path : pathnames)
1351 {
1352 // Only load new files (so when the insert succeeds)
1353 if (Paths.insert(path).second)
1354 try
1355 {
1356 m_ScriptInterface->LoadGlobalScriptFile(path);
1357 }
1358 catch (PSERROR_Scripting& e)
1359 {
1360 LOGERROR("GUI: Error executing script %s: %s", path.string8(), e.what());
1361 }
1362 }
1363 }
1364
1365 // Execute inline scripts
1366 try
1367 {
1368 CStr code(Element.GetText());
1369 if (!code.empty())
1370 m_ScriptInterface->LoadGlobalScript(L"Some XML file", code.FromUTF8());
1371 }
1372 catch (PSERROR_Scripting& e)
1373 {
1374 LOGERROR("GUI: Error executing inline script: %s", e.what());
1375 }
1376 }
1377
1378 void CGUI::Xeromyces_ReadSprite(XMBElement Element, CXeromyces* pFile)
1379 {
1380 CGUISprite* Sprite = new CGUISprite;
1381
1382 // Get name, we know it exists because of DTD requirements
1383 CStr name = Element.GetAttributes().GetNamedItem(pFile->GetAttributeID("name"));
1384
1385 if (m_Sprites.find(name) != m_Sprites.end())
1386 LOGWARNING("GUI sprite name '%s' used more than once; first definition will be discarded", name.c_str());
1387
1388 SGUIImageEffects* effects = NULL;
1389
1390 for (XMBElement child : Element.GetChildNodes())
1391 {
1392 CStr ElementName(pFile->GetElementString(child.GetNodeName()));
1393
1394 if (ElementName == "image")
1395 Xeromyces_ReadImage(child, pFile, *Sprite);
1396 else if (ElementName == "effect")
1397 {
1398 if (effects)
1399 LOGERROR("GUI <sprite> must not have more than one <effect>");
1400 else
1401 {
1402 effects = new SGUIImageEffects;
1403 Xeromyces_ReadEffects(child, pFile, *effects);
1404 }
1405 }
1406 else
1407 debug_warn(L"Invalid data - DTD shouldn't allow this");
1408 }
1409
1410 // Apply the effects to every image (unless the image overrides it with
1411 // different effects)
1412 if (effects)
1413 for (SGUIImage* const& img : Sprite->m_Images)
1414 if (!img->m_Effects)
1415 img->m_Effects = new SGUIImageEffects(*effects); // do a copy just so it can be deleted correctly later
1416
1417 delete effects;
1418
1419 m_Sprites[name] = Sprite;
1420 }
1421
1422 void CGUI::Xeromyces_ReadImage(XMBElement Element, CXeromyces* pFile, CGUISprite& parent)
1423 {
1424 SGUIImage* Image = new SGUIImage;
1425
1426 Image->m_TextureSize = CClientArea(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
1427 Image->m_Size = CClientArea(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
1428
1429 // TODO Gee: Setup defaults here (or maybe they are in the SGUIImage ctor)
1430
1431 for (XMBAttribute attr : Element.GetAttributes())
1432 {
1433 CStr attr_name(pFile->GetAttributeString(attr.Name));
1434 CStrW attr_value(attr.Value.FromUTF8());
1435
1436 if (attr_name == "texture")
1437 {
1438 Image->m_TextureName = VfsPath("art/textures/ui") / attr_value;
1439 }
1440 else if (attr_name == "size")
1441 {
1442 CClientArea ca;
1443 if (!GUI<CClientArea>::ParseString(attr_value, ca))
1444 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1445 else
1446 Image->m_Size = ca;
1447 }
1448 else if (attr_name == "texture_size")
1449 {
1450 CClientArea ca;
1451 if (!GUI<CClientArea>::ParseString(attr_value, ca))
1452 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1453 else
1454 Image->m_TextureSize = ca;
1455 }
1456 else if (attr_name == "real_texture_placement")
1457 {
1458 CRect rect;
1459 if (!GUI<CRect>::ParseString(attr_value, rect))
1460 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1461 else
1462 Image->m_TexturePlacementInFile = rect;
1463 }
1464 else if (attr_name == "cell_size")
1465 {
1466 CSize size;
1467 if (!GUI<CSize>::ParseString(attr_value, size))
1468 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1469 else
1470 Image->m_CellSize = size;
1471 }
1472 else if (attr_name == "fixed_h_aspect_ratio")
1473 {
1474 float val;
1475 if (!GUI<float>::ParseString(attr_value, val))
1476 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1477 else
1478 Image->m_FixedHAspectRatio = val;
1479 }
1480 else if (attr_name == "round_coordinates")
1481 {
1482 bool b;
1483 if (!GUI<bool>::ParseString(attr_value, b))
1484 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1485 else
1486 Image->m_RoundCoordinates = b;
1487 }
1488 else if (attr_name == "wrap_mode")
1489 {
1490 if (attr_value == L"repeat")
1491 Image->m_WrapMode = GL_REPEAT;
1492 else if (attr_value == L"mirrored_repeat")
1493 Image->m_WrapMode = GL_MIRRORED_REPEAT;
1494 else if (attr_value == L"clamp_to_edge")
1495 Image->m_WrapMode = GL_CLAMP_TO_EDGE;
1496 else
1497 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1498 }
1499 else if (attr_name == "z_level")
1500 {
1501 float z_level;
1502 if (!GUI<float>::ParseString(attr_value, z_level))
1503 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1504 else
1505 Image->m_DeltaZ = z_level/100.f;
1506 }
1507 else if (attr_name == "backcolor")
1508 {
1509 CColor color;
1510 if (!GUI<CColor>::ParseString(attr_value, color))
1511 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1512 else
1513 Image->m_BackColor = color;
1514 }
1515 else if (attr_name == "bordercolor")
1516 {
1517 CColor color;
1518 if (!GUI<CColor>::ParseString(attr_value, color))
1519 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1520 else
1521 Image->m_BorderColor = color;
1522 }
1523 else if (attr_name == "border")
1524 {
1525 bool b;
1526 if (!GUI<bool>::ParseString(attr_value, b))
1527 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1528 else
1529 Image->m_Border = b;
1530 }
1531 else
1532 debug_warn(L"Invalid data - DTD shouldn't allow this");
1533 }
1534
1535 // Look for effects
1536 for (XMBElement child : Element.GetChildNodes())
1537 {
1538 CStr ElementName(pFile->GetElementString(child.GetNodeName()));
1539 if (ElementName == "effect")
1540 {
1541 if (Image->m_Effects)
1542 LOGERROR("GUI <image> must not have more than one <effect>");
1543 else
1544 {
1545 Image->m_Effects = new SGUIImageEffects;
1546 Xeromyces_ReadEffects(child, pFile, *Image->m_Effects);
1547 }
1548 }
1549 else
1550 debug_warn(L"Invalid data - DTD shouldn't allow this");
1551 }
1552
1553 parent.AddImage(Image);
1554 }
1555
1556 void CGUI::Xeromyces_ReadEffects(XMBElement Element, CXeromyces* pFile, SGUIImageEffects& effects)
1557 {
1558 for (XMBAttribute attr : Element.GetAttributes())
1559 {
1560 CStr attr_name(pFile->GetAttributeString(attr.Name));
1561 CStrW attr_value(attr.Value.FromUTF8());
1562
1563 if (attr_name == "add_color")
1564 {
1565 CColor color;
1566 if (!GUI<int>::ParseColor(attr_value, color, 0))
1567 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
1568 else effects.m_AddColor = color;
1569 }
1570 else if (attr_name == "grayscale")
1571 effects.m_Greyscale = true;
1572 else
1573 debug_warn(L"Invalid data - DTD shouldn't allow this");
1574 }
1575 }
1576
1577 void CGUI::Xeromyces_ReadStyle(XMBElement Element, CXeromyces* pFile)
1578 {
1579 SGUIStyle style;
1580 CStr name;
1581
1582 for (XMBAttribute attr : Element.GetAttributes())
1583 {
1584 CStr attr_name(pFile->GetAttributeString(attr.Name));
1585
1586 // The "name" setting is actually the name of the style
1587 // and not a new default
1588 if (attr_name == "name")
1589 name = attr.Value;
1590 else
1591 style.m_SettingsDefaults[attr_name] = attr.Value.FromUTF8();
1592 }
1593
1594 m_Styles[name] = style;
1595 }
1596
1597 void CGUI::Xeromyces_ReadScrollBarStyle(XMBElement Element, CXeromyces* pFile)
1598 {
1599 SGUIScrollBarStyle scrollbar;
1600 CStr name;
1601
1602 // Setup some defaults.
1603 scrollbar.m_MinimumBarSize = 0.f;
1604 // Using 1.0e10 as a substitute for infinity
1605 scrollbar.m_MaximumBarSize = 1.0e10;
1606 scrollbar.m_UseEdgeButtons = false;
1607
1608 for (XMBAttribute attr : Element.GetAttributes())
1609 {
1610 CStr attr_name = pFile->GetAttributeString(attr.Name);
1611 CStr attr_value(attr.Value);
1612
1613 if (attr_value == "null")
1614 continue;
1615
1616 if (attr_name == "name")
1617 name = attr_value;
1618 else if (attr_name == "show_edge_buttons")
1619 {
1620 bool b;
1621 if (!GUI<bool>::ParseString(attr_value.FromUTF8(), b))
1622 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
1623 else
1624 scrollbar.m_UseEdgeButtons = b;
1625 }
1626 else if (attr_name == "width")
1627 {
1628 float f;
1629 if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
1630 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
1631 else
1632 scrollbar.m_Width = f;
1633 }
1634 else if (attr_name == "minimum_bar_size")
1635 {
1636 float f;
1637 if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
1638 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
1639 else
1640 scrollbar.m_MinimumBarSize = f;
1641 }
1642 else if (attr_name == "maximum_bar_size")
1643 {
1644 float f;
1645 if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
1646 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
1647 else
1648 scrollbar.m_MaximumBarSize = f;
1649 }
1650 else if (attr_name == "sprite_button_top")
1651 scrollbar.m_SpriteButtonTop = attr_value;
1652 else if (attr_name == "sprite_button_top_pressed")
1653 scrollbar.m_SpriteButtonTopPressed = attr_value;
1654 else if (attr_name == "sprite_button_top_disabled")
1655 scrollbar.m_SpriteButtonTopDisabled = attr_value;
1656 else if (attr_name == "sprite_button_top_over")
1657 scrollbar.m_SpriteButtonTopOver = attr_value;
1658 else if (attr_name == "sprite_button_bottom")
1659 scrollbar.m_SpriteButtonBottom = attr_value;
1660 else if (attr_name == "sprite_button_bottom_pressed")
1661 scrollbar.m_SpriteButtonBottomPressed = attr_value;
1662 else if (attr_name == "sprite_button_bottom_disabled")
1663 scrollbar.m_SpriteButtonBottomDisabled = attr_value;
1664 else if (attr_name == "sprite_button_bottom_over")
1665 scrollbar.m_SpriteButtonBottomOver = attr_value;
1666 else if (attr_name == "sprite_back_vertical")
1667 scrollbar.m_SpriteBackVertical = attr_value;
1668 else if (attr_name == "sprite_bar_vertical")
1669 scrollbar.m_SpriteBarVertical = attr_value;
1670 else if (attr_name == "sprite_bar_vertical_over")
1671 scrollbar.m_SpriteBarVerticalOver = attr_value;
1672 else if (attr_name == "sprite_bar_vertical_pressed")
1673 scrollbar.m_SpriteBarVerticalPressed = attr_value;
1674 }
1675
1676 m_ScrollBarStyles[name] = scrollbar;
1677 }
1678
1679 void CGUI::Xeromyces_ReadIcon(XMBElement Element, CXeromyces* pFile)
1680 {
1681 SGUIIcon icon;
1682 CStr name;
1683
1684 for (XMBAttribute attr : Element.GetAttributes())
1685 {
1686 CStr attr_name(pFile->GetAttributeString(attr.Name));
1687 CStr attr_value(attr.Value);
1688
1689 if (attr_value == "null")
1690 continue;
1691
1692 if (attr_name == "name")
1693 name = attr_value;
1694 else if (attr_name == "sprite")
1695 icon.m_SpriteName = attr_value;
1696 else if (attr_name == "size")
1697 {
1698 CSize size;
1699 if (!GUI<CSize>::ParseString(attr_value.FromUTF8(), size))
1700 LOGERROR("Error parsing '%s' (\"%s\") inside <icon>.", attr_name, attr_value);
1701 else
1702 icon.m_Size = size;
1703 }
1704 else if (attr_name == "cell_id")
1705 {
1706 int cell_id;
1707 if (!GUI<int>::ParseString(attr_value.FromUTF8(), cell_id))
1708 LOGERROR("GUI: Error parsing '%s' (\"%s\") inside <icon>.", attr_name, attr_value);
1709 else
1710 icon.m_CellID = cell_id;
1711 }
1712 else
1713 debug_warn(L"Invalid data - DTD shouldn't allow this");
1714 }
1715
1716 m_Icons[name] = icon;
1717 }
1718
1719 void CGUI::Xeromyces_ReadTooltip(XMBElement Element, CXeromyces* pFile)
1720 {
1721 IGUIObject* object = new CTooltip;
1722
1723 for (XMBAttribute attr : Element.GetAttributes())
1724 {
1725 CStr attr_name(pFile->GetAttributeString(attr.Name));
1726 CStr attr_value(attr.Value);
1727
1728 if (attr_name == "name")
1729 object->SetName("__tooltip_" + attr_value);
1730 else
1731 object->SetSetting(attr_name, attr_value.FromUTF8());
1732 }
1733
1734 AddObject(object);
1735 }
1736
1737 void CGUI::Xeromyces_ReadColor(XMBElement Element, CXeromyces* pFile)
1738 {
1739 XMBAttributeList attributes = Element.GetAttributes();
1740
1741 CColor color;
1742 CStr name = attributes.GetNamedItem(pFile->GetAttributeID("name"));
1743
1744 // Try parsing value
1745 CStr value(Element.GetText());
1746 if (value.empty())
1747 return;
1748
1749 // Try setting color to value
1750 if (!color.ParseString(value))
1751 {
1752 LOGERROR("GUI: Unable to create custom color '%s'. Invalid color syntax.", name.c_str());
1753 return;
1754 }
1755
1756 m_PreDefinedColors[name] = color;
1757 }
1758Index: source/gui/scripting/GuiScriptConversions.cpp
1759===================================================================
1760--- source/gui/scripting/GuiScriptConversions.cpp (revision 19922)
1761+++ source/gui/scripting/GuiScriptConversions.cpp (working copy)
1762@@ -1,145 +1,147 @@
1763 /* Copyright (C) 2017 Wildfire Games.
1764 * This file is part of 0 A.D.
1765 *
1766 * 0 A.D. is free software: you can redistribute it and/or modify
1767 * it under the terms of the GNU General Public License as published by
1768 * the Free Software Foundation, either version 2 of the License, or
1769 * (at your option) any later version.
1770 *
1771 * 0 A.D. is distributed in the hope that it will be useful,
1772 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1773 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1774 * GNU General Public License for more details.
1775 *
1776 * You should have received a copy of the GNU General Public License
1777 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
1778 */
1779
1780 #include "precompiled.h"
1781
1782 #include "scriptinterface/ScriptConversions.h"
1783
1784 #include "gui/IGUIObject.h"
1785 #include "lib/external_libraries/libsdl.h"
1786 #include "ps/Hotkey.h"
1787 #include "maths/Vector2D.h"
1788
1789 #define SET(obj, name, value) STMT(JS::RootedValue v_(cx); AssignOrToJSVal(cx, &v_, (value)); JS_SetProperty(cx, obj, (name), v_))
1790 // ignore JS_SetProperty return value, because errors should be impossible
1791 // and we can't do anything useful in the case of errors anyway
1792
1793+extern float g_DpiScale;
1794+
1795 template<> void ScriptInterface::ToJSVal<SDL_Event_>(JSContext* cx, JS::MutableHandleValue ret, SDL_Event_ const& val)
1796 {
1797 JSAutoRequest rq(cx);
1798 const char* typeName;
1799
1800 switch (val.ev.type)
1801 {
1802 case SDL_WINDOWEVENT: typeName = "windowevent"; break;
1803 case SDL_KEYDOWN: typeName = "keydown"; break;
1804 case SDL_KEYUP: typeName = "keyup"; break;
1805 case SDL_MOUSEMOTION: typeName = "mousemotion"; break;
1806 case SDL_MOUSEBUTTONDOWN: typeName = "mousebuttondown"; break;
1807 case SDL_MOUSEBUTTONUP: typeName = "mousebuttonup"; break;
1808 case SDL_QUIT: typeName = "quit"; break;
1809 case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break;
1810 case SDL_HOTKEYUP: typeName = "hotkeyup"; break;
1811 default: typeName = "(unknown)"; break;
1812 }
1813
1814 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
1815 if (!obj)
1816 {
1817 ret.setUndefined();
1818 return;
1819 }
1820
1821 SET(obj, "type", typeName);
1822
1823 switch (val.ev.type)
1824 {
1825 case SDL_KEYDOWN:
1826 case SDL_KEYUP:
1827 {
1828 // SET(obj, "which", (int)val.ev.key.which); // (not in wsdl.h)
1829 // SET(obj, "state", (int)val.ev.key.state); // (not in wsdl.h)
1830
1831 JS::RootedObject keysym(cx, JS_NewPlainObject(cx));
1832 if (!keysym)
1833 {
1834 ret.setUndefined();
1835 return;
1836 }
1837 JS::RootedValue keysymVal(cx, JS::ObjectValue(*keysym));
1838 JS_SetProperty(cx, obj, "keysym", keysymVal);
1839
1840 // SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h)
1841 SET(keysym, "sym", (int)val.ev.key.keysym.sym);
1842 // SET(keysym, "mod", (int)val.ev.key.keysym.mod); // (not in wsdl.h)
1843 {
1844 SET(keysym, "unicode", JS::UndefinedHandleValue);
1845 }
1846 // TODO: scripts have no idea what all the key/mod enum values are;
1847 // we should probably expose them as constants if we expect scripts to use them
1848
1849 break;
1850 }
1851 case SDL_MOUSEMOTION:
1852 {
1853 // SET(obj, "which", (int)val.ev.motion.which); // (not in wsdl.h)
1854 // SET(obj, "state", (int)val.ev.motion.state); // (not in wsdl.h)
1855- SET(obj, "x", (int)val.ev.motion.x);
1856- SET(obj, "y", (int)val.ev.motion.y);
1857+ SET(obj, "x", static_cast<int>(val.ev.motion.x * g_DpiScale));
1858+ SET(obj, "y", static_cast<int>(val.ev.motion.y * g_DpiScale));
1859 // SET(obj, "xrel", (int)val.ev.motion.xrel); // (not in wsdl.h)
1860 // SET(obj, "yrel", (int)val.ev.motion.yrel); // (not in wsdl.h)
1861 break;
1862 }
1863 case SDL_MOUSEBUTTONDOWN:
1864 case SDL_MOUSEBUTTONUP:
1865 {
1866 // SET(obj, "which", (int)val.ev.button.which); // (not in wsdl.h)
1867 SET(obj, "button", (int)val.ev.button.button);
1868 SET(obj, "state", (int)val.ev.button.state);
1869- SET(obj, "x", (int)val.ev.button.x);
1870- SET(obj, "y", (int)val.ev.button.y);
1871+ SET(obj, "x", static_cast<int>(val.ev.button.x * g_DpiScale));
1872+ SET(obj, "y", static_cast<int>(val.ev.button.y * g_DpiScale));
1873 SET(obj, "clicks", (int)val.ev.button.clicks);
1874 break;
1875 }
1876 case SDL_HOTKEYDOWN:
1877 case SDL_HOTKEYUP:
1878 {
1879 SET(obj, "hotkey", static_cast<const char*>(val.ev.user.data1));
1880 break;
1881 }
1882 }
1883
1884 ret.setObject(*obj);
1885 }
1886
1887 template<> void ScriptInterface::ToJSVal<IGUIObject*>(JSContext* UNUSED(cx), JS::MutableHandleValue ret, IGUIObject* const& val)
1888 {
1889 if (val == NULL)
1890 ret.setNull();
1891 else
1892 ret.setObject(*val->GetJSObject());
1893 }
1894
1895 template<> void ScriptInterface::ToJSVal<CGUIString>(JSContext* cx, JS::MutableHandleValue ret, const CGUIString& val)
1896 {
1897 ScriptInterface::ToJSVal(cx, ret, val.GetOriginalString());
1898 }
1899
1900 template<> bool ScriptInterface::FromJSVal<CGUIString>(JSContext* cx, JS::HandleValue v, CGUIString& out)
1901 {
1902 std::wstring val;
1903 if (!ScriptInterface::FromJSVal(cx, v, val))
1904 return false;
1905 out.SetValue(val);
1906 return true;
1907 }
1908
1909 // define some vectors
1910 JSVAL_VECTOR(CVector2D)
1911 JSVAL_VECTOR(std::vector<CVector2D>)
1912 JSVAL_VECTOR(CGUIString)
1913
1914Index: source/ps/GameSetup/Config.cpp
1915===================================================================
1916--- source/ps/GameSetup/Config.cpp (revision 19922)
1917+++ source/ps/GameSetup/Config.cpp (working copy)
1918@@ -1,211 +1,212 @@
1919 /* Copyright (C) 2016 Wildfire Games.
1920 * This file is part of 0 A.D.
1921 *
1922 * 0 A.D. is free software: you can redistribute it and/or modify
1923 * it under the terms of the GNU General Public License as published by
1924 * the Free Software Foundation, either version 2 of the License, or
1925 * (at your option) any later version.
1926 *
1927 * 0 A.D. is distributed in the hope that it will be useful,
1928 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1929 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1930 * GNU General Public License for more details.
1931 *
1932 * You should have received a copy of the GNU General Public License
1933 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
1934 */
1935
1936 #include "precompiled.h"
1937
1938 #include "Config.h"
1939
1940 #include "lib/timer.h"
1941 #include "ps/CConsole.h"
1942 #include "ps/CLogger.h"
1943 #include "ps/ConfigDB.h"
1944 #include "ps/GameSetup/CmdLineArgs.h"
1945
1946 // (these variables are documented in the header.)
1947
1948 const wchar_t g_DefaultCursor[] = L"default-arrow";
1949
1950 CStrW g_CursorName = g_DefaultCursor;
1951
1952 bool g_NoGLS3TC = false;
1953 bool g_NoGLAutoMipmap = false;
1954 bool g_NoGLVBO = false;
1955
1956 bool g_PauseOnFocusLoss = false;
1957
1958 bool g_RenderActors = true;
1959
1960 bool g_Shadows = false;
1961 bool g_ShadowPCF = false;
1962
1963 bool g_WaterUgly = false;
1964 bool g_WaterFancyEffects = false;
1965 bool g_WaterRealDepth = false;
1966 bool g_WaterRefraction = false;
1967 bool g_WaterReflection = false;
1968 bool g_WaterShadows = false;
1969
1970 bool g_Particles = false;
1971 bool g_Silhouettes = false;
1972 bool g_ShowSky = false;
1973
1974 bool g_PreferGLSL = false;
1975 bool g_PostProc = false;
1976 bool g_SmoothLOS = false;
1977
1978 float g_Gamma = 1.0f;
1979
1980 CStr g_RenderPath = "default";
1981
1982 int g_xres, g_yres;
1983 float g_GuiScale = 1.0f;
1984+float g_DpiScale = 1.0f;
1985 bool g_VSync = false;
1986
1987 bool g_Quickstart = false;
1988 bool g_DisableAudio = false;
1989
1990 // flag to switch on drawing terrain overlays
1991 bool g_ShowPathfindingOverlay = false;
1992
1993 // flag to switch on triangulation pathfinding
1994 bool g_TriPathfind = false;
1995
1996
1997 // If non-empty, specified map will be automatically loaded
1998 CStr g_AutostartMap = "";
1999
2000
2001 //----------------------------------------------------------------------------
2002 // config
2003 //----------------------------------------------------------------------------
2004
2005 // Fill in the globals from the config files.
2006 static void LoadGlobals()
2007 {
2008 CFG_GET_VAL("vsync", g_VSync);
2009
2010 CFG_GET_VAL("nos3tc", g_NoGLS3TC);
2011 CFG_GET_VAL("noautomipmap", g_NoGLAutoMipmap);
2012 CFG_GET_VAL("novbo", g_NoGLVBO);
2013 CFG_GET_VAL("pauseonfocusloss", g_PauseOnFocusLoss);
2014 CFG_GET_VAL("renderactors", g_RenderActors);
2015 CFG_GET_VAL("shadows", g_Shadows);
2016 CFG_GET_VAL("shadowpcf", g_ShadowPCF);
2017
2018 CFG_GET_VAL("waterugly", g_WaterUgly);
2019 CFG_GET_VAL("waterfancyeffects", g_WaterFancyEffects);
2020 CFG_GET_VAL("waterrealdepth", g_WaterRealDepth);
2021 CFG_GET_VAL("waterrefraction", g_WaterRefraction);
2022 CFG_GET_VAL("waterreflection", g_WaterReflection);
2023 CFG_GET_VAL("watershadows", g_WaterShadows);
2024
2025 CFG_GET_VAL("renderpath", g_RenderPath);
2026 CFG_GET_VAL("particles", g_Particles);
2027 CFG_GET_VAL("silhouettes", g_Silhouettes);
2028 CFG_GET_VAL("showsky", g_ShowSky);
2029 CFG_GET_VAL("preferglsl", g_PreferGLSL);
2030 CFG_GET_VAL("postproc", g_PostProc);
2031 CFG_GET_VAL("smoothlos", g_SmoothLOS);
2032 CFG_GET_VAL("gui.scale", g_GuiScale);
2033 }
2034
2035
2036 static void ProcessCommandLineArgs(const CmdLineArgs& args)
2037 {
2038 // TODO: all these options (and the ones processed elsewhere) should
2039 // be documented somewhere for users.
2040
2041 // Handle "-conf=key:value" (potentially multiple times)
2042 std::vector<CStr> conf = args.GetMultiple("conf");
2043 for (size_t i = 0; i < conf.size(); ++i)
2044 {
2045 CStr name_value = conf[i];
2046 if (name_value.Find(':') != -1)
2047 {
2048 CStr name = name_value.BeforeFirst(":");
2049 CStr value = name_value.AfterFirst(":");
2050 g_ConfigDB.SetValueString(CFG_COMMAND, name, value);
2051 }
2052 }
2053
2054 if (args.Has("g"))
2055 {
2056 g_Gamma = args.Get("g").ToFloat();
2057 if (g_Gamma == 0.0f)
2058 g_Gamma = 1.0f;
2059 }
2060
2061 // if (args.Has("listfiles"))
2062 // trace_enable(true);
2063
2064 if (args.Has("profile"))
2065 g_ConfigDB.SetValueString(CFG_COMMAND, "profile", args.Get("profile"));
2066
2067 if (args.Has("quickstart"))
2068 {
2069 g_Quickstart = true;
2070 g_DisableAudio = true; // do this for backward-compatibility with user expectations
2071 }
2072
2073 if (args.Has("nosound"))
2074 g_DisableAudio = true;
2075
2076 if (args.Has("shadows"))
2077 g_ConfigDB.SetValueString(CFG_COMMAND, "shadows", "true");
2078
2079 if (args.Has("xres"))
2080 g_ConfigDB.SetValueString(CFG_COMMAND, "xres", args.Get("xres"));
2081
2082 if (args.Has("yres"))
2083 g_ConfigDB.SetValueString(CFG_COMMAND, "yres", args.Get("yres"));
2084
2085 if (args.Has("vsync"))
2086 g_ConfigDB.SetValueString(CFG_COMMAND, "vsync", "true");
2087
2088 if (args.Has("ooslog"))
2089 g_ConfigDB.SetValueString(CFG_COMMAND, "ooslog", "true");
2090
2091 if (args.Has("serializationtest"))
2092 g_ConfigDB.SetValueString(CFG_COMMAND, "serializationtest", "true");
2093
2094 if (args.Has("rejointest"))
2095 g_ConfigDB.SetValueString(CFG_COMMAND, "rejointest", args.Get("rejointest"));
2096 }
2097
2098
2099 void CONFIG_Init(const CmdLineArgs& args)
2100 {
2101 TIMER(L"CONFIG_Init");
2102
2103 new CConfigDB;
2104
2105 // Load the global, default config file
2106 g_ConfigDB.SetConfigFile(CFG_DEFAULT, L"config/default.cfg");
2107 g_ConfigDB.Reload(CFG_DEFAULT); // 216ms
2108 // Try loading the local system config file (which doesn't exist by
2109 // default) - this is designed as a way of letting developers edit the
2110 // system config without accidentally committing their changes back to SVN.
2111 g_ConfigDB.SetConfigFile(CFG_SYSTEM, L"config/local.cfg");
2112 g_ConfigDB.Reload(CFG_SYSTEM);
2113
2114 g_ConfigDB.SetConfigFile(CFG_USER, L"config/user.cfg");
2115 g_ConfigDB.Reload(CFG_USER);
2116
2117 g_ConfigDB.SetConfigFile(CFG_MOD, L"config/mod.cfg");
2118 // No point in reloading mod.cfg here - we haven't mounted mods yet
2119
2120 ProcessCommandLineArgs(args);
2121
2122 // Initialise console history file
2123 int max_history_lines = 200;
2124 CFG_GET_VAL("console.history.size", max_history_lines);
2125 g_Console->UseHistoryFile(L"config/console.txt", max_history_lines);
2126
2127 // Collect information from system.cfg, the profile file,
2128 // and any command-line overrides to fill in the globals.
2129 LoadGlobals(); // 64ms
2130 }
2131Index: source/ps/GameSetup/Config.h
2132===================================================================
2133--- source/ps/GameSetup/Config.h (revision 19922)
2134+++ source/ps/GameSetup/Config.h (working copy)
2135@@ -1,100 +1,101 @@
2136 /* Copyright (C) 2016 Wildfire Games.
2137 * This file is part of 0 A.D.
2138 *
2139 * 0 A.D. is free software: you can redistribute it and/or modify
2140 * it under the terms of the GNU General Public License as published by
2141 * the Free Software Foundation, either version 2 of the License, or
2142 * (at your option) any later version.
2143 *
2144 * 0 A.D. is distributed in the hope that it will be useful,
2145 * but WITHOUT ANY WARRANTY; without even the implied warranty of
2146 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2147 * GNU General Public License for more details.
2148 *
2149 * You should have received a copy of the GNU General Public License
2150 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
2151 */
2152
2153 #ifndef INCLUDED_PS_GAMESETUP_CONFIG
2154 #define INCLUDED_PS_GAMESETUP_CONFIG
2155
2156 #include "ps/CStr.h"
2157
2158
2159 //-----------------------------------------------------------------------------
2160 // prevent various OpenGL features from being used. this allows working
2161 // around issues like buggy drivers.
2162
2163 // when loading S3TC-compressed texture files, do not pass them directly to
2164 // OpenGL; instead, decompress them via software to regular textures.
2165 // (necessary on JW's S3 laptop graphics card -- oh, the irony)
2166 extern bool g_NoGLS3TC;
2167
2168 // do not ask OpenGL to create mipmaps; instead, generate them in software
2169 // and upload them all manually. (potentially helpful for PT's system, where
2170 // Mesa falsely reports full S3TC support but isn't able to generate mipmaps
2171 // for them)
2172 extern bool g_NoGLAutoMipmap;
2173
2174 // don't use VBOs. (RC: that was necessary on laptop Radeon cards)
2175 extern bool g_NoGLVBO;
2176
2177 //-----------------------------------------------------------------------------
2178
2179 // flag to pause the game on window focus loss
2180 extern bool g_PauseOnFocusLoss;
2181
2182 // flag to switch on actor rendering
2183 extern bool g_RenderActors;
2184
2185 // flag to switch on shadows
2186 extern bool g_Shadows;
2187
2188 // Force the use of the fixed function for rendering water.
2189 extern bool g_WaterUgly;
2190 // Add foam and waves near the shores, trails following ships, and other HQ things.
2191 extern bool g_WaterFancyEffects;
2192 // Use real depth for water rendering.
2193 extern bool g_WaterRealDepth;
2194 // Use a real refraction map and not transparency.
2195 extern bool g_WaterRefraction;
2196 // Use a real reflection map and not a skybox texture.
2197 extern bool g_WaterReflection;
2198 // Enable on-water shadows.
2199 extern bool g_WaterShadows;
2200
2201 // flag to switch on shadow PCF
2202 extern bool g_ShadowPCF;
2203 // flag to switch on particles rendering
2204 extern bool g_Particles;
2205 // flag to switch on unit silhouettes
2206 extern bool g_Silhouettes;
2207 // flag to switch on sky rendering
2208 extern bool g_ShowSky;
2209
2210 // Prefer GLSL shaders over ARB shaders
2211 extern bool g_PreferGLSL;
2212 // Use screen-space postprocessing filters (HDR, bloom, DOF, etc)
2213 extern bool g_PostProc;
2214 // Use smooth LOS interpolation
2215 extern bool g_SmoothLOS;
2216
2217 extern float g_Gamma;
2218 // name of configured render path (depending on OpenGL extensions, this may not be
2219 // the render path that is actually in use right now)
2220 extern CStr g_RenderPath;
2221
2222 extern int g_xres, g_yres;
2223 extern float g_GuiScale;
2224+extern float g_DpiScale;
2225 extern bool g_VSync;
2226
2227 extern bool g_Quickstart;
2228 extern bool g_DisableAudio;
2229
2230 extern CStrW g_CursorName;
2231 extern const wchar_t g_DefaultCursor[];
2232
2233 class CmdLineArgs;
2234 extern void CONFIG_Init(const CmdLineArgs& args);
2235
2236 #endif // INCLUDED_PS_GAMESETUP_CONFIG
2237Index: source/ps/Globals.cpp
2238===================================================================
2239--- source/ps/Globals.cpp (revision 19922)
2240+++ source/ps/Globals.cpp (working copy)
2241@@ -1,96 +1,96 @@
2242 /* Copyright (C) 2017 Wildfire Games.
2243 * This file is part of 0 A.D.
2244 *
2245 * 0 A.D. is free software: you can redistribute it and/or modify
2246 * it under the terms of the GNU General Public License as published by
2247 * the Free Software Foundation, either version 2 of the License, or
2248 * (at your option) any later version.
2249 *
2250 * 0 A.D. is distributed in the hope that it will be useful,
2251 * but WITHOUT ANY WARRANTY; without even the implied warranty of
2252 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2253 * GNU General Public License for more details.
2254 *
2255 * You should have received a copy of the GNU General Public License
2256 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
2257 */
2258
2259 #include "precompiled.h"
2260 #include "Globals.h"
2261
2262 #include "network/NetClient.h"
2263 #include "ps/GameSetup/Config.h"
2264 #include "soundmanager/ISoundManager.h"
2265
2266 bool g_app_minimized = false;
2267 bool g_app_has_focus = true;
2268
2269 std::map<int32_t, bool> g_keys;
2270 int g_mouse_x = 50, g_mouse_y = 50;
2271 bool g_mouse_active = true;
2272
2273 // g_mouse_buttons[0] is unused. The order of entries is as in KeyName.h for MOUSE_*
2274 bool g_mouse_buttons[MOUSE_LAST - MOUSE_BASE] = {0};
2275
2276 PIFrequencyFilter g_frequencyFilter;
2277
2278 // updates the state of the above; never swallows messages.
2279 InReaction GlobalsInputHandler(const SDL_Event_* ev)
2280 {
2281 size_t c;
2282
2283 switch(ev->ev.type)
2284 {
2285 case SDL_WINDOWEVENT:
2286 switch(ev->ev.window.event)
2287 {
2288 case SDL_WINDOWEVENT_MINIMIZED:
2289 g_app_minimized = true;
2290 break;
2291 case SDL_WINDOWEVENT_EXPOSED:
2292 case SDL_WINDOWEVENT_RESTORED:
2293 g_app_minimized = false;
2294 break;
2295 case SDL_WINDOWEVENT_FOCUS_GAINED:
2296 g_app_has_focus = true;
2297 break;
2298 case SDL_WINDOWEVENT_FOCUS_LOST:
2299 g_app_has_focus = false;
2300 break;
2301 case SDL_WINDOWEVENT_ENTER:
2302 g_mouse_active = true;
2303 break;
2304 case SDL_WINDOWEVENT_LEAVE:
2305 g_mouse_active = false;
2306 break;
2307 }
2308 return IN_PASS;
2309
2310 case SDL_MOUSEMOTION:
2311- g_mouse_x = ev->ev.motion.x;
2312- g_mouse_y = ev->ev.motion.y;
2313+ g_mouse_x = static_cast<int>(ev->ev.motion.x * g_DpiScale);
2314+ g_mouse_y = static_cast<int>(ev->ev.motion.y * g_DpiScale);
2315 return IN_PASS;
2316
2317 case SDL_MOUSEBUTTONDOWN:
2318 case SDL_MOUSEBUTTONUP:
2319 c = ev->ev.button.button;
2320 if(c < ARRAY_SIZE(g_mouse_buttons))
2321 g_mouse_buttons[c] = (ev->ev.type == SDL_MOUSEBUTTONDOWN);
2322 else
2323 {
2324 // don't complain: just ignore people with too many mouse buttons
2325 //debug_warn(L"invalid mouse button");
2326 }
2327 return IN_PASS;
2328
2329 case SDL_KEYDOWN:
2330 case SDL_KEYUP:
2331 g_keys[ev->ev.key.keysym.sym] = (ev->ev.type == SDL_KEYDOWN);
2332 return IN_PASS;
2333
2334 default:
2335 return IN_PASS;
2336 }
2337
2338 UNREACHABLE;
2339 }
2340Index: source/ps/VideoMode.cpp
2341===================================================================
2342--- source/ps/VideoMode.cpp (revision 19922)
2343+++ source/ps/VideoMode.cpp (working copy)
2344@@ -1,490 +1,502 @@
2345 /* Copyright (C) 2015 Wildfire Games.
2346 * This file is part of 0 A.D.
2347 *
2348 * 0 A.D. is free software: you can redistribute it and/or modify
2349 * it under the terms of the GNU General Public License as published by
2350 * the Free Software Foundation, either version 2 of the License, or
2351 * (at your option) any later version.
2352 *
2353 * 0 A.D. is distributed in the hope that it will be useful,
2354 * but WITHOUT ANY WARRANTY; without even the implied warranty of
2355 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2356 * GNU General Public License for more details.
2357 *
2358 * You should have received a copy of the GNU General Public License
2359 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
2360 */
2361
2362 #include "precompiled.h"
2363
2364 #include "VideoMode.h"
2365
2366 #include "graphics/Camera.h"
2367 #include "graphics/GameView.h"
2368 #include "gui/GUIManager.h"
2369 #include "lib/config2.h"
2370 #include "lib/ogl.h"
2371 #include "lib/external_libraries/libsdl.h"
2372 #include "lib/sysdep/gfx.h"
2373 #include "ps/CConsole.h"
2374 #include "ps/CLogger.h"
2375 #include "ps/ConfigDB.h"
2376 #include "ps/Game.h"
2377 #include "ps/GameSetup/Config.h"
2378 #include "renderer/Renderer.h"
2379
2380 #if OS_MACOSX
2381 # include "lib/sysdep/os/osx/osx_sys_version.h"
2382 #endif
2383
2384
2385 static int DEFAULT_WINDOW_W = 1024;
2386 static int DEFAULT_WINDOW_H = 768;
2387
2388 static int DEFAULT_FULLSCREEN_W = 1024;
2389 static int DEFAULT_FULLSCREEN_H = 768;
2390
2391 CVideoMode g_VideoMode;
2392
2393 CVideoMode::CVideoMode() :
2394 m_IsFullscreen(false), m_IsInitialised(false), m_Window(NULL),
2395 m_PreferredW(0), m_PreferredH(0), m_PreferredBPP(0), m_PreferredFreq(0),
2396- m_ConfigW(0), m_ConfigH(0), m_ConfigBPP(0), m_ConfigFullscreen(false), m_ConfigForceS3TCEnable(true),
2397+ m_ConfigW(0), m_ConfigH(0), m_ConfigBPP(0), m_ConfigHighDPI(false), m_ConfigFullscreen(false), m_ConfigForceS3TCEnable(true),
2398 m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
2399 {
2400 // (m_ConfigFullscreen defaults to false, so users don't get stuck if
2401 // e.g. half the filesystem is missing and the config files aren't loaded)
2402 }
2403
2404 void CVideoMode::ReadConfig()
2405 {
2406 bool windowed = !m_ConfigFullscreen;
2407 CFG_GET_VAL("windowed", windowed);
2408 m_ConfigFullscreen = !windowed;
2409
2410 CFG_GET_VAL("xres", m_ConfigW);
2411 CFG_GET_VAL("yres", m_ConfigH);
2412+ CFG_GET_VAL("high_dpi", m_ConfigHighDPI);
2413 CFG_GET_VAL("bpp", m_ConfigBPP);
2414 CFG_GET_VAL("display", m_ConfigDisplay);
2415 CFG_GET_VAL("force_s3tc_enable", m_ConfigForceS3TCEnable);
2416 }
2417
2418 bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
2419 {
2420 Uint32 flags = 0;
2421 if (fullscreen)
2422 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
2423
2424 if (!m_Window)
2425 {
2426 // Note: these flags only take affect in SDL_CreateWindow
2427 flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
2428+ if (m_ConfigHighDPI)
2429+ flags |= SDL_WINDOW_ALLOW_HIGHDPI;
2430 m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
2431
2432 m_Window = SDL_CreateWindow("0 A.D.", m_WindowedX, m_WindowedY, w, h, flags);
2433 if (!m_Window)
2434 {
2435 // If fullscreen fails, try windowed mode
2436 if (fullscreen)
2437 {
2438 LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
2439 "%dx%d:%d (\"%hs\"), falling back to windowed mode",
2440 w, h, bpp, SDL_GetError());
2441 // Using default size for the window for now, as the attempted setting
2442 // could be as large, or larger than the screen size.
2443 return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
2444 }
2445 else
2446 {
2447 LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
2448 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
2449 return false;
2450 }
2451 }
2452
2453 if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
2454 {
2455 LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
2456 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
2457 return false;
2458 }
2459
2460 SDL_GLContext context = SDL_GL_CreateContext(m_Window);
2461 if (!context)
2462 {
2463 LOGERROR("SetVideoMode failed in SDL_GL_CreateContext: %dx%d:%d %d (\"%s\")",
2464 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
2465 return false;
2466 }
2467 }
2468 else
2469 {
2470 if (m_IsFullscreen != fullscreen)
2471 {
2472 if (!fullscreen)
2473 {
2474 // For some reason, when switching from fullscreen to windowed mode,
2475 // we have to set the window size and position before and after switching
2476 SDL_SetWindowSize(m_Window, w, h);
2477 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
2478 }
2479
2480 if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
2481 {
2482 LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
2483 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
2484 return false;
2485 }
2486 }
2487
2488 if (!fullscreen)
2489 {
2490 SDL_SetWindowSize(m_Window, w, h);
2491 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
2492 }
2493 }
2494
2495 // Grab the current video settings
2496 SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
2497 m_CurrentBPP = bpp;
2498+ UpdateRenderer();
2499
2500 if (fullscreen)
2501 SDL_SetWindowGrab(m_Window, SDL_TRUE);
2502 else
2503 SDL_SetWindowGrab(m_Window, SDL_FALSE);
2504
2505 m_IsFullscreen = fullscreen;
2506
2507- g_xres = m_CurrentW;
2508- g_yres = m_CurrentH;
2509-
2510 return true;
2511 }
2512
2513 bool CVideoMode::InitSDL()
2514 {
2515 ENSURE(!m_IsInitialised);
2516
2517 ReadConfig();
2518
2519 EnableS3TC();
2520
2521 // preferred video mode = current desktop settings
2522 // (command line params may override these)
2523 gfx::GetVideoMode(&m_PreferredW, &m_PreferredH, &m_PreferredBPP, &m_PreferredFreq);
2524
2525 int w = m_ConfigW;
2526 int h = m_ConfigH;
2527
2528 if (m_ConfigFullscreen)
2529 {
2530 // If fullscreen and no explicit size set, default to the desktop resolution
2531 if (w == 0 || h == 0)
2532 {
2533 w = m_PreferredW;
2534 h = m_PreferredH;
2535 }
2536 }
2537
2538 // If no size determined, default to something sensible
2539 if (w == 0 || h == 0)
2540 {
2541 w = DEFAULT_WINDOW_W;
2542 h = DEFAULT_WINDOW_H;
2543 }
2544
2545 if (!m_ConfigFullscreen)
2546 {
2547 // Limit the window to the screen size (if known)
2548 if (m_PreferredW)
2549 w = std::min(w, m_PreferredW);
2550 if (m_PreferredH)
2551 h = std::min(h, m_PreferredH);
2552 }
2553
2554 int bpp = GetBestBPP();
2555
2556 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
2557 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
2558 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
2559
2560 #if CONFIG2_GLES
2561 // Require GLES 2.0
2562 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
2563 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
2564 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
2565 #endif
2566
2567 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
2568 {
2569 // Fall back to a smaller depth buffer
2570 // (The rendering may be ugly but this helps when running in VMware)
2571 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
2572
2573 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
2574 return false;
2575 }
2576
2577 SDL_GL_SetSwapInterval(g_VSync ? 1 : 0);
2578
2579 // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
2580 // The driver appears to register its own atexit hook on context creation.
2581 // If this atexit hook is called before SDL_Quit destroys the OpenGL context,
2582 // some kind of double-free problem causes a crash and lockup in the driver.
2583 // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
2584 // by destroying the context *before* the driver's atexit hook is called.
2585 // (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
2586 atexit(SDL_Quit);
2587 // End work around.
2588
2589 ogl_Init(); // required after each mode change
2590 // (TODO: does that mean we need to call this when toggling fullscreen later?)
2591
2592 #if !OS_ANDROID
2593 u16 ramp[256];
2594 SDL_CalculateGammaRamp(g_Gamma, ramp);
2595 if (SDL_SetWindowGammaRamp(m_Window, ramp, ramp, ramp) < 0)
2596 LOGWARNING("SDL_SetWindowGammaRamp failed");
2597 #endif
2598
2599 m_IsInitialised = true;
2600
2601 if (!m_ConfigFullscreen)
2602 {
2603 m_WindowedW = w;
2604 m_WindowedH = h;
2605 }
2606
2607 return true;
2608 }
2609
2610 bool CVideoMode::InitNonSDL()
2611 {
2612 ENSURE(!m_IsInitialised);
2613
2614 ReadConfig();
2615
2616 EnableS3TC();
2617
2618 m_IsInitialised = true;
2619
2620 return true;
2621 }
2622
2623 void CVideoMode::Shutdown()
2624 {
2625 ENSURE(m_IsInitialised);
2626
2627 m_IsFullscreen = false;
2628 m_IsInitialised = false;
2629 if (m_Window)
2630 {
2631 SDL_DestroyWindow(m_Window);
2632 m_Window = NULL;
2633 }
2634 }
2635
2636 void CVideoMode::EnableS3TC()
2637 {
2638 // On Linux we have to try hard to get S3TC compressed texture support.
2639 // If the extension is already provided by default, that's fine.
2640 // Otherwise we should enable the 'force_s3tc_enable' environment variable
2641 // and (re)initialise the video system, so that Mesa provides the extension
2642 // (if the driver at least supports decompression).
2643 // (This overrides the force_s3tc_enable specified via driconf files.)
2644 // Otherwise we should complain to the user, and stop using compressed textures.
2645 //
2646 // Setting the environment variable causes Mesa to print an ugly message to stderr
2647 // ("ATTENTION: default value of option force_s3tc_enable overridden by environment."),
2648 // so it'd be nicer to skip that if S3TC will be supported by default,
2649 // but reinitialising video is a pain (and it might do weird things when fullscreen)
2650 // so we just unconditionally set it (unless our config file explicitly disables it).
2651
2652 #if !(OS_WIN || OS_MACOSX) // (assume Mesa is used for all non-Windows non-Mac platforms)
2653 if (m_ConfigForceS3TCEnable)
2654 setenv("force_s3tc_enable", "true", 0);
2655 #endif
2656 }
2657
2658 bool CVideoMode::ResizeWindow(int w, int h)
2659 {
2660 ENSURE(m_IsInitialised);
2661
2662 // Ignore if not windowed
2663 if (m_IsFullscreen)
2664 return true;
2665
2666 // Ignore if the size hasn't changed
2667 if (w == m_WindowedW && h == m_WindowedH)
2668 return true;
2669
2670 int bpp = GetBestBPP();
2671
2672 if (!SetVideoMode(w, h, bpp, false))
2673 return false;
2674
2675 m_WindowedW = w;
2676 m_WindowedH = h;
2677
2678- UpdateRenderer(w, h);
2679-
2680 return true;
2681 }
2682
2683 bool CVideoMode::SetFullscreen(bool fullscreen)
2684 {
2685 // This might get called before initialisation by psDisplayError;
2686 // if so then silently fail
2687 if (!m_IsInitialised)
2688 return false;
2689
2690 // Check whether this is actually a change
2691 if (fullscreen == m_IsFullscreen)
2692 return true;
2693
2694 if (!m_IsFullscreen)
2695 {
2696 // Windowed -> fullscreen:
2697
2698 int w = 0, h = 0;
2699
2700 // If a fullscreen size was configured, use that; else use the desktop size; else use a default
2701 if (m_ConfigFullscreen)
2702 {
2703 w = m_ConfigW;
2704 h = m_ConfigH;
2705 }
2706 if (w == 0 || h == 0)
2707 {
2708 w = m_PreferredW;
2709 h = m_PreferredH;
2710 }
2711 if (w == 0 || h == 0)
2712 {
2713 w = DEFAULT_FULLSCREEN_W;
2714 h = DEFAULT_FULLSCREEN_H;
2715 }
2716
2717 int bpp = GetBestBPP();
2718
2719 if (!SetVideoMode(w, h, bpp, fullscreen))
2720 return false;
2721
2722- UpdateRenderer(m_CurrentW, m_CurrentH);
2723-
2724 return true;
2725 }
2726 else
2727 {
2728 // Fullscreen -> windowed:
2729
2730 // Go back to whatever the previous window size was
2731 int w = m_WindowedW, h = m_WindowedH;
2732
2733 int bpp = GetBestBPP();
2734
2735 if (!SetVideoMode(w, h, bpp, fullscreen))
2736 return false;
2737
2738- UpdateRenderer(w, h);
2739-
2740 return true;
2741 }
2742 }
2743
2744 bool CVideoMode::ToggleFullscreen()
2745 {
2746 return SetFullscreen(!m_IsFullscreen);
2747 }
2748
2749 void CVideoMode::UpdatePosition(int x, int y)
2750 {
2751 if (!m_IsFullscreen)
2752 {
2753 m_WindowedX = x;
2754 m_WindowedY = y;
2755 }
2756 }
2757
2758+void CVideoMode::UpdateRenderer() {
2759+ int drawingWidth = 0;
2760+ int drawingHeight = 0;
2761+
2762+ SDL_GL_GetDrawableSize(m_Window, &drawingWidth, &drawingHeight);
2763+ UpdateRenderer(drawingWidth, drawingHeight);
2764+ g_xres = drawingWidth;
2765+ g_yres = drawingHeight;
2766+
2767+ if (!m_ConfigHighDPI)
2768+ return;
2769+
2770+ // In HDPI Mode the gui scale is equal to the dpi scale.
2771+ g_DpiScale = static_cast<float>(drawingWidth) / static_cast<float>(m_CurrentW);
2772+ g_GuiScale = g_DpiScale;
2773+}
2774+
2775 void CVideoMode::UpdateRenderer(int w, int h)
2776 {
2777 if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
2778 if (h < 2) h = 2;
2779
2780 g_xres = w;
2781 g_yres = h;
2782
2783 SViewPort vp = { 0, 0, w, h };
2784
2785 if (CRenderer::IsInitialised())
2786 {
2787 g_Renderer.SetViewport(vp);
2788 g_Renderer.Resize(w, h);
2789 }
2790
2791 if (g_GUI)
2792 g_GUI->UpdateResolution();
2793
2794 if (g_Console)
2795 g_Console->UpdateScreenSize(w, h);
2796
2797 if (g_Game)
2798 g_Game->GetView()->SetViewport(vp);
2799 }
2800
2801 int CVideoMode::GetBestBPP()
2802 {
2803 if (m_ConfigBPP)
2804 return m_ConfigBPP;
2805 if (m_PreferredBPP)
2806 return m_PreferredBPP;
2807 return 32;
2808 }
2809
2810 int CVideoMode::GetXRes()
2811 {
2812 ENSURE(m_IsInitialised);
2813 return m_CurrentW;
2814 }
2815
2816 int CVideoMode::GetYRes()
2817 {
2818 ENSURE(m_IsInitialised);
2819 return m_CurrentH;
2820 }
2821
2822 int CVideoMode::GetBPP()
2823 {
2824 ENSURE(m_IsInitialised);
2825 return m_CurrentBPP;
2826 }
2827
2828 int CVideoMode::GetDesktopXRes()
2829 {
2830 ENSURE(m_IsInitialised);
2831 return m_PreferredW;
2832 }
2833
2834 int CVideoMode::GetDesktopYRes()
2835 {
2836 ENSURE(m_IsInitialised);
2837 return m_PreferredH;
2838 }
2839
2840 int CVideoMode::GetDesktopBPP()
2841 {
2842 ENSURE(m_IsInitialised);
2843 return m_PreferredBPP;
2844 }
2845
2846 int CVideoMode::GetDesktopFreq()
2847 {
2848 ENSURE(m_IsInitialised);
2849 return m_PreferredFreq;
2850 }
2851
2852 SDL_Window* CVideoMode::GetWindow()
2853 {
2854 ENSURE(m_IsInitialised);
2855 return m_Window;
2856 }
2857Index: source/ps/VideoMode.h
2858===================================================================
2859--- source/ps/VideoMode.h (revision 19922)
2860+++ source/ps/VideoMode.h (working copy)
2861@@ -1,130 +1,137 @@
2862 /* Copyright (C) 2014 Wildfire Games.
2863 * This file is part of 0 A.D.
2864 *
2865 * 0 A.D. is free software: you can redistribute it and/or modify
2866 * it under the terms of the GNU General Public License as published by
2867 * the Free Software Foundation, either version 2 of the License, or
2868 * (at your option) any later version.
2869 *
2870 * 0 A.D. is distributed in the hope that it will be useful,
2871 * but WITHOUT ANY WARRANTY; without even the implied warranty of
2872 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2873 * GNU General Public License for more details.
2874 *
2875 * You should have received a copy of the GNU General Public License
2876 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
2877 */
2878
2879 #ifndef INCLUDED_VIDEOMODE
2880 #define INCLUDED_VIDEOMODE
2881
2882 typedef struct SDL_Window SDL_Window;
2883
2884 class CVideoMode
2885 {
2886 public:
2887 CVideoMode();
2888
2889 /**
2890 * Initialise the video mode, for use in an SDL-using application.
2891 */
2892 bool InitSDL();
2893
2894 /**
2895 * Initialise parts of the video mode, for use in Atlas (which uses
2896 * wxWidgets instead of SDL for GL).
2897 * Currently this just tries to enable S3TC.
2898 */
2899 bool InitNonSDL();
2900
2901 /**
2902 * Shut down after InitSDL/InitNonSDL, so that they can be used again.
2903 */
2904 void Shutdown();
2905
2906 /**
2907 * Resize the SDL window and associated graphics stuff to the new size.
2908 */
2909 bool ResizeWindow(int w, int h);
2910
2911 /**
2912 * Switch to fullscreen or windowed mode.
2913 */
2914 bool SetFullscreen(bool fullscreen);
2915
2916 /**
2917 * Switch between fullscreen and windowed mode.
2918 */
2919 bool ToggleFullscreen();
2920
2921 /**
2922 * Update window position, to restore later if necessary (SDL2 only).
2923 */
2924 void UpdatePosition(int x, int y);
2925
2926+ /**
2927+ * Update the graphics code to start drawing to the new size set for the SDL context.
2928+ * This should be called after the GL context has been resized.
2929+ */
2930+ void UpdateRenderer();
2931+
2932 /**
2933- * Update the graphics code to start drawing to the new size.
2934+ * Update the graphics code to start drawing to the specified size.
2935 * This should be called after the GL context has been resized.
2936 * This can also be used when the GL context is managed externally, not via SDL.
2937 */
2938 static void UpdateRenderer(int w, int h);
2939
2940 int GetXRes();
2941 int GetYRes();
2942 int GetBPP();
2943
2944 int GetDesktopXRes();
2945 int GetDesktopYRes();
2946 int GetDesktopBPP();
2947 int GetDesktopFreq();
2948
2949 SDL_Window* GetWindow();
2950
2951 private:
2952 void ReadConfig();
2953 int GetBestBPP();
2954 bool SetVideoMode(int w, int h, int bpp, bool fullscreen);
2955 void EnableS3TC();
2956
2957 /**
2958 * Remember whether Init has been called. (This isn't used for anything
2959 * important, just for verifying that the callers call our methods in
2960 * the right order.)
2961 */
2962 bool m_IsInitialised;
2963
2964 SDL_Window* m_Window;
2965
2966 // Initial desktop settings
2967 int m_PreferredW;
2968 int m_PreferredH;
2969 int m_PreferredBPP;
2970 int m_PreferredFreq;
2971
2972 // Config file settings (0 if unspecified)
2973 int m_ConfigW;
2974 int m_ConfigH;
2975 int m_ConfigBPP;
2976+ bool m_ConfigHighDPI;
2977 int m_ConfigDisplay;
2978 bool m_ConfigFullscreen;
2979 bool m_ConfigForceS3TCEnable;
2980
2981 // If we're fullscreen, size/position of window when we were last windowed (or the default window
2982 // size/position if we started fullscreen), to support switching back to the old window size/position
2983 int m_WindowedW;
2984 int m_WindowedH;
2985 int m_WindowedX;
2986 int m_WindowedY;
2987
2988 // Whether we're currently being displayed fullscreen
2989 bool m_IsFullscreen;
2990
2991 // The last mode selected
2992 int m_CurrentW;
2993 int m_CurrentH;
2994 int m_CurrentBPP;
2995 };
2996
2997 extern CVideoMode g_VideoMode;
2998
2999 #endif // INCLUDED_VIDEOMODE