1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* A viewport to each player */
18
19#include <C4Include.h>
20#include <C4Viewport.h>
21
22#include <C4Console.h>
23#include <C4UserMessages.h>
24#include <C4Object.h>
25#include <C4ObjectInfo.h>
26#include <C4Wrappers.h>
27#include <C4FullScreen.h>
28#include <C4Application.h>
29#include <C4ObjectCom.h>
30#include <C4Stat.h>
31#include <C4Gui.h>
32#include <C4Network2Dialogs.h>
33#include <C4GameDialogs.h>
34#include <C4Player.h>
35#include <C4ChatDlg.h>
36#include <C4ObjectMenu.h>
37
38#include <StdGL.h>
39
40#ifdef _WIN32
41#include "StdRegistry.h"
42#include "res/engine_resource.h"
43#elif defined(USE_X11)
44#include <X11/Xlib.h>
45#include <X11/XKBlib.h>
46#ifdef WITH_DEVELOPER_MODE
47#include <gdk/gdkx.h>
48#include <gdk/gdkkeysyms.h>
49#include <gtk/gtk.h>
50#endif
51#endif
52
53#include <format>
54
55namespace
56{
57 const int32_t ViewportScrollSpeed = 10;
58}
59
60#ifdef _WIN32
61
62#include <shellapi.h>
63
64LRESULT APIENTRY C4ViewportWindow::WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
65{
66 // Determine viewport
67 auto *const window = reinterpret_cast<C4ViewportWindow *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
68 if (!window)
69 {
70 return CStdWindow::DefaultWindowProc(hwnd, uMsg, wParam, lParam);
71 }
72
73 C4Viewport *const cvp{window->cvp};
74
75 const auto scale = Application.GetScale();
76
77 // Process message
78 switch (uMsg)
79 {
80 case WM_KEYDOWN:
81 switch (wParam)
82 {
83 case VK_SCROLL:
84 // key bound to this specific viewport. Don't want to pass this through C4Game...
85 cvp->TogglePlayerLock();
86 break;
87
88 default:
89 if (Game.DoKeyboardInput(wParam, KEYEV_Down, !!(lParam & 0x20000000), Application.IsControlDown(), Application.IsShiftDown(), !!(lParam & 0x40000000), nullptr)) return 0;
90 break;
91 }
92 break;
93
94 case WM_KEYUP:
95 if (Game.DoKeyboardInput(wParam, KEYEV_Up, !!(lParam & 0x20000000), Application.IsControlDown(), Application.IsShiftDown(), false, nullptr)) return 0;
96 break;
97
98 case WM_SYSKEYDOWN:
99 if (wParam == 18) break;
100 if (Game.DoKeyboardInput(wParam, KEYEV_Down, !!(lParam & 0x20000000), Application.IsControlDown(), Application.IsShiftDown(), !!(lParam & 0x40000000), nullptr)) return 0;
101 break;
102
103 case WM_CLOSE:
104 cvp->pWindow->Close();
105 break;
106
107 case WM_DROPFILES:
108 cvp->DropFiles((HANDLE)wParam);
109 break;
110
111 case WM_USER_DROPDEF:
112 Game.DropDef(lParam, cvp->ViewX + static_cast<int32_t>(LOWORD(wParam) / scale), cvp->ViewY + static_cast<int32_t>(HIWORD(wParam) / scale));
113 break;
114
115 case WM_SIZE:
116 cvp->UpdateOutputSize();
117 break;
118
119 case WM_PAINT:
120 Game.GraphicsSystem.Execute();
121 break;
122
123 case WM_HSCROLL:
124 switch (LOWORD(wParam))
125 {
126 case SB_THUMBTRACK: case SB_THUMBPOSITION: cvp->ViewX = HIWORD(wParam); break;
127 case SB_LINELEFT: cvp->ViewX -= ViewportScrollSpeed; break;
128 case SB_LINERIGHT: cvp->ViewX += ViewportScrollSpeed; break;
129 case SB_PAGELEFT: cvp->ViewX -= cvp->ViewWdt; break;
130 case SB_PAGERIGHT: cvp->ViewX += cvp->ViewWdt; break;
131 }
132 cvp->Execute();
133 cvp->ScrollBarsByViewPosition();
134 return 0;
135
136 case WM_VSCROLL:
137 switch (LOWORD(wParam))
138 {
139 case SB_THUMBTRACK: case SB_THUMBPOSITION: cvp->ViewY = HIWORD(wParam); break;
140 case SB_LINEUP: cvp->ViewY -= ViewportScrollSpeed; break;
141 case SB_LINEDOWN: cvp->ViewY += ViewportScrollSpeed; break;
142 case SB_PAGEUP: cvp->ViewY -= cvp->ViewWdt; break;
143 case SB_PAGEDOWN: cvp->ViewY += cvp->ViewWdt; break;
144 }
145 cvp->Execute();
146 cvp->ScrollBarsByViewPosition();
147 return 0;
148 }
149
150 // Viewport mouse control
151 if (Game.MouseControl.IsViewport(cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
152 {
153 switch (uMsg)
154 {
155 case WM_LBUTTONDOWN: Game.GraphicsSystem.MouseMove(C4MC_Button_LeftDown, LOWORD(lParam), HIWORD(lParam), wParam, cvp); break;
156 case WM_LBUTTONUP: Game.GraphicsSystem.MouseMove(C4MC_Button_LeftUp, LOWORD(lParam), HIWORD(lParam), wParam, cvp); break;
157 case WM_RBUTTONDOWN: Game.GraphicsSystem.MouseMove(C4MC_Button_RightDown, LOWORD(lParam), HIWORD(lParam), wParam, cvp); break;
158 case WM_RBUTTONUP: Game.GraphicsSystem.MouseMove(C4MC_Button_RightUp, LOWORD(lParam), HIWORD(lParam), wParam, cvp); break;
159 case WM_LBUTTONDBLCLK: Game.GraphicsSystem.MouseMove(C4MC_Button_LeftDouble, LOWORD(lParam), HIWORD(lParam), wParam, cvp); break;
160 case WM_RBUTTONDBLCLK: Game.GraphicsSystem.MouseMove(C4MC_Button_RightDouble, LOWORD(lParam), HIWORD(lParam), wParam, cvp); break;
161
162 case WM_MOUSEMOVE:
163 if (Inside<int32_t>(LOWORD(lParam) - cvp->DrawX, 0, cvp->ViewWdt - 1)
164 && Inside<int32_t>(HIWORD(lParam) - cvp->DrawY, 0, cvp->ViewHgt - 1))
165 SetCursor(nullptr);
166 Game.GraphicsSystem.MouseMove(C4MC_Button_None, LOWORD(lParam), HIWORD(lParam), wParam, cvp);
167 break;
168
169 case WM_MOUSEWHEEL:
170 Game.GraphicsSystem.MouseMove(C4MC_Button_Wheel, LOWORD(lParam), HIWORD(lParam), wParam, cvp);
171 break;
172 }
173 }
174 // Console edit cursor control
175 else
176 {
177 switch (uMsg)
178 {
179 case WM_LBUTTONDOWN:
180 // movement update needed before, so target is always up-to-date
181 Console.EditCursor.Move(cvp->ViewX + static_cast<int32_t>(LOWORD(lParam) / scale), cvp->ViewY + static_cast<int32_t>(HIWORD(lParam) / scale), wParam);
182 Console.EditCursor.LeftButtonDown(wParam & MK_CONTROL); break;
183
184 case WM_LBUTTONUP: Console.EditCursor.LeftButtonUp(); break;
185
186 case WM_RBUTTONDOWN: Console.EditCursor.RightButtonDown(wParam & MK_CONTROL); break;
187
188 case WM_RBUTTONUP: Console.EditCursor.RightButtonUp(); break;
189
190 case WM_MBUTTONUP: Console.EditCursor.MiddleButtonUp(); break;
191
192 case WM_MOUSEMOVE: Console.EditCursor.Move(cvp->ViewX + static_cast<int32_t>(LOWORD(lParam) / scale), cvp->ViewY + static_cast<int32_t>(HIWORD(lParam) / scale), wParam); break;
193 }
194 }
195
196 return CStdWindow::DefaultWindowProc(hwnd, uMsg, wParam, lParam);
197}
198
199WNDCLASSEX C4ViewportWindow::GetWindowClass(const HINSTANCE instance) const
200{
201 return {
202 .cbSize = sizeof(WNDCLASSEX),
203 .style = CS_DBLCLKS | CS_BYTEALIGNCLIENT,
204 .lpfnWndProc = &C4ViewportWindow::WinProc,
205 .cbClsExtra = 0,
206 .cbWndExtra = 0,
207 .hInstance = instance,
208 .hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_01_C4S)),
209 .hCursor = LoadCursor(nullptr, IDC_ARROW),
210 .hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND),
211 .lpszMenuName = nullptr,
212 .lpszClassName = L"C4Viewport",
213 .hIconSm = LoadIcon(instance, MAKEINTRESOURCE(IDI_01_C4S))
214 };
215}
216
217bool C4ViewportWindow::GetPositionData(std::string &id, std::string &subKey, bool &storeSize) const
218{
219 id = std::format("Viewport{}", cvp->Player + 1);
220 subKey = Config.GetSubkeyPath("Console");
221 storeSize = true;
222 return true;
223}
224
225bool C4Viewport::DropFiles(HANDLE hDrop)
226{
227 if (!Console.Editing) { Console.Message(LoadResStr(C4ResStrTableKey::IDS_CNS_NONETEDIT)); return false; }
228
229 int32_t iFileNum = DragQueryFile((HDROP)hDrop, 0xFFFFFFFF, nullptr, 0);
230 POINT pntPoint;
231 char szFilename[500 + 1];
232 for (int32_t cnt = 0; cnt < iFileNum; cnt++)
233 {
234 DragQueryFileA((HDROP)hDrop, cnt, szFilename, 500);
235 DragQueryPoint((HDROP)hDrop, &pntPoint);
236 Game.DropFile(szFilename, ViewX + pntPoint.x, ViewY + pntPoint.y);
237 }
238 DragFinish((HDROP)hDrop);
239 return true;
240}
241
242void UpdateWindowLayout(HWND hwnd)
243{
244 RECT rect;
245 GetWindowRect(hwnd, &rect);
246 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left - 1, rect.bottom - rect.top, TRUE);
247 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
248}
249
250bool C4Viewport::TogglePlayerLock()
251{
252 // Disable player lock
253 if (PlayerLock)
254 {
255 PlayerLock = false;
256 SetWindowLong(pWindow->hWindow, GWL_STYLE, GetWindowLong(pWindow->hWindow, GWL_STYLE) | WS_HSCROLL | WS_VSCROLL);
257 UpdateWindowLayout(pWindow->hWindow);
258 ScrollBarsByViewPosition();
259 }
260 // Enable player lock
261 else if (ValidPlr(Player))
262 {
263 PlayerLock = true;
264 SetWindowLong(pWindow->hWindow, GWL_STYLE, GetWindowLong(pWindow->hWindow, GWL_STYLE) & ~(WS_HSCROLL | WS_VSCROLL));
265 UpdateWindowLayout(pWindow->hWindow);
266 }
267 return true;
268}
269
270bool C4Viewport::ScrollBarsByViewPosition()
271{
272 if (PlayerLock) return false;
273 SCROLLINFO scroll;
274 scroll.cbSize = sizeof(SCROLLINFO);
275 // Vertical
276 scroll.fMask = SIF_ALL;
277 scroll.nMin = 0;
278 scroll.nMax = GBackHgt;
279 scroll.nPage = ViewHgt;
280 scroll.nPos = ViewY;
281 SetScrollInfo(pWindow->hWindow, SB_VERT, &scroll, TRUE);
282 // Horizontal
283 scroll.fMask = SIF_ALL;
284 scroll.nMin = 0;
285 scroll.nMax = GBackWdt;
286 scroll.nPage = ViewWdt;
287 scroll.nPos = ViewX;
288 SetScrollInfo(pWindow->hWindow, SB_HORZ, &scroll, TRUE);
289 return true;
290}
291
292#elif defined(WITH_DEVELOPER_MODE)
293static GtkTargetEntry drag_drop_entries[] =
294{
295 { .target: const_cast<char *>("text/uri-list"), .flags: 0, .info: 0 }
296};
297
298// GTK+ Viewport window implementation
299GtkWidget *C4ViewportWindow::InitGUI()
300{
301 const auto scale = Application.GetScale();
302 gtk_window_set_default_size(GTK_WINDOW(window), width: static_cast<int32_t>(ceilf(x: 640 * scale)), height: static_cast<int32_t>(ceilf(x: 480 * scale)));
303
304 // Cannot just use ScrolledWindow because this would just move
305 // the GdkWindow of the DrawingArea.
306 GtkWidget *table;
307
308 drawing_area = gtk_drawing_area_new();
309 h_scrollbar = gtk_scrollbar_new(orientation: GTK_ORIENTATION_HORIZONTAL, adjustment: nullptr);
310 v_scrollbar = gtk_scrollbar_new(orientation: GTK_ORIENTATION_VERTICAL, adjustment: nullptr);
311 table = gtk_table_new(rows: 2, columns: 2, FALSE);
312
313 GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(h_scrollbar));
314 gtk_adjustment_set_lower(adjustment, lower: 0);
315 gtk_adjustment_set_upper(adjustment, GBackWdt);
316 gtk_adjustment_set_step_increment(adjustment, step_increment: ViewportScrollSpeed);
317
318 g_signal_connect(
319 G_OBJECT(adjustment),
320 "value-changed",
321 G_CALLBACK(OnHScrollStatic),
322 this
323 );
324
325 adjustment = gtk_range_get_adjustment(GTK_RANGE(v_scrollbar));
326 gtk_adjustment_set_lower(adjustment, lower: 0);
327 gtk_adjustment_set_upper(adjustment, GBackHgt);
328 gtk_adjustment_set_step_increment(adjustment, step_increment: ViewportScrollSpeed);
329
330 g_signal_connect(
331 G_OBJECT(adjustment),
332 "value-changed",
333 G_CALLBACK(OnVScrollStatic),
334 this
335 );
336
337 gtk_table_attach(GTK_TABLE(table), child: drawing_area, left_attach: 0, right_attach: 1, top_attach: 0, bottom_attach: 1, xoptions: static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), yoptions: static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), xpadding: 0, ypadding: 0);
338 gtk_table_attach(GTK_TABLE(table), child: v_scrollbar, left_attach: 1, right_attach: 2, top_attach: 0, bottom_attach: 1, xoptions: GTK_SHRINK, yoptions: static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND), xpadding: 0, ypadding: 0);
339 gtk_table_attach(GTK_TABLE(table), child: h_scrollbar, left_attach: 0, right_attach: 1, top_attach: 1, bottom_attach: 2, xoptions: static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), yoptions: GTK_SHRINK, xpadding: 0, ypadding: 0);
340
341 gtk_container_add(GTK_CONTAINER(window), widget: table);
342
343 gtk_widget_add_events(widget: window, events: GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK | GDK_POINTER_MOTION_MASK);
344
345 gtk_drag_dest_set(widget: drawing_area, flags: GTK_DEST_DEFAULT_ALL, targets: drag_drop_entries, n_targets: 1, actions: GDK_ACTION_COPY);
346 g_signal_connect(G_OBJECT(drawing_area), "drag-data-received", G_CALLBACK(OnDragDataReceivedStatic), this);
347 g_signal_connect(G_OBJECT(drawing_area), "expose-event", G_CALLBACK(OnExposeStatic), this);
348
349 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(OnKeyPressStatic), this);
350 g_signal_connect(G_OBJECT(window), "key-release-event", G_CALLBACK(OnKeyReleaseStatic), this);
351 g_signal_connect(G_OBJECT(window), "scroll-event", G_CALLBACK(OnScrollStatic), this);
352 g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(OnButtonPressStatic), this);
353 g_signal_connect(G_OBJECT(window), "button-release-event", G_CALLBACK(OnButtonReleaseStatic), this);
354 g_signal_connect(G_OBJECT(window), "motion-notify-event", G_CALLBACK(OnMotionNotifyStatic), this);
355 g_signal_connect(G_OBJECT(window), "configure-event", G_CALLBACK(OnConfigureStatic), this);
356 g_signal_connect(G_OBJECT(window), "realize", G_CALLBACK(OnRealizeStatic), this);
357
358 g_signal_connect_after(G_OBJECT(drawing_area), "configure-event", G_CALLBACK(OnConfigureDareaStatic), this);
359
360 // do not draw the default background
361 gtk_widget_set_double_buffered(widget: drawing_area, FALSE);
362
363 return drawing_area;
364}
365
366bool C4Viewport::TogglePlayerLock()
367{
368 if (PlayerLock)
369 {
370 PlayerLock = false;
371 gtk_widget_show(widget: pWindow->h_scrollbar);
372 gtk_widget_show(widget: pWindow->v_scrollbar);
373 ScrollBarsByViewPosition();
374 }
375 else
376 {
377 PlayerLock = true;
378 gtk_widget_hide(widget: pWindow->h_scrollbar);
379 gtk_widget_hide(widget: pWindow->v_scrollbar);
380 }
381
382 return true;
383}
384
385bool C4Viewport::ScrollBarsByViewPosition()
386{
387 if (PlayerLock) return false;
388
389 GtkAllocation allocation;
390 gtk_widget_get_allocation(widget: pWindow->drawing_area, allocation: &allocation);
391 GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(pWindow->h_scrollbar));
392 gtk_adjustment_set_page_increment(adjustment, page_increment: allocation.width);
393 gtk_adjustment_set_page_size(adjustment, page_size: allocation.width);
394 gtk_adjustment_set_value(adjustment, value: ViewX);
395
396 adjustment = gtk_range_get_adjustment(GTK_RANGE(pWindow->v_scrollbar));
397 gtk_adjustment_set_page_increment(adjustment, page_increment: allocation.height);
398 gtk_adjustment_set_page_size(adjustment, page_size: allocation.height);
399 gtk_adjustment_set_value(adjustment, value: ViewY);
400
401 return true;
402}
403
404bool C4Viewport::ViewPositionByScrollBars()
405{
406 if (PlayerLock) return false;
407
408 GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(pWindow->h_scrollbar));
409 ViewX = static_cast<int32_t>(gtk_adjustment_get_value(adjustment));
410
411 adjustment = gtk_range_get_adjustment(GTK_RANGE(pWindow->v_scrollbar));
412 ViewY = static_cast<int32_t>(gtk_adjustment_get_value(adjustment));
413
414 return true;
415}
416
417void C4ViewportWindow::OnDragDataReceivedStatic(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, gpointer user_data)
418{
419 const auto scale = Application.GetScale();
420
421 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
422
423 gchar **uris = gtk_selection_data_get_uris(selection_data: data);
424 if (!uris) return;
425
426 for (gchar **uri = uris; *uri != nullptr; ++uri)
427 {
428 gchar *file = g_filename_from_uri(uri: *uri, hostname: nullptr, error: nullptr);
429 if (!file) continue;
430
431 Game.DropFile(szFilename: file, iX: window->cvp->ViewX + static_cast<int32_t>(x / scale), iY: window->cvp->ViewY + static_cast<int32_t>(y / scale));
432 g_free(mem: file);
433 }
434
435 g_strfreev(str_array: uris);
436}
437
438gboolean C4ViewportWindow::OnExposeStatic(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
439{
440 C4Viewport *cvp = static_cast<C4ViewportWindow *>(user_data)->cvp;
441
442 // TODO: Redraw only event->area
443 cvp->Execute();
444 return TRUE;
445}
446
447void C4ViewportWindow::OnRealizeStatic(GtkWidget *widget, gpointer user_data)
448{
449 // Initial PlayerLock
450 if (static_cast<C4ViewportWindow *>(user_data)->cvp->PlayerLock == true)
451 {
452 gtk_widget_hide(widget: static_cast<C4ViewportWindow *>(user_data)->h_scrollbar);
453 gtk_widget_hide(widget: static_cast<C4ViewportWindow *>(user_data)->v_scrollbar);
454 }
455}
456
457gboolean C4ViewportWindow::OnKeyPressStatic(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
458{
459 if (event->keyval == GDK_KEY_Scroll_Lock)
460 static_cast<C4ViewportWindow *>(user_data)->cvp->TogglePlayerLock();
461#ifndef NDEBUG
462 switch (event->keyval)
463 {
464 case GDK_KEY_1: Config.Graphics.BlitOffset -= 0.05; printf("%f\n", Config.Graphics.BlitOffset); break;
465 case GDK_KEY_2: Config.Graphics.BlitOffset += 0.05; printf("%f\n", Config.Graphics.BlitOffset); break;
466 case GDK_KEY_3: Config.Graphics.TexIndent -= 0.05; printf("%f\n", Config.Graphics.TexIndent); break;
467 case GDK_KEY_4: Config.Graphics.TexIndent += 0.05; printf("%f\n", Config.Graphics.TexIndent); break;
468 }
469#endif
470 uint32_t key = XkbKeycodeToKeysym(GDK_WINDOW_XDISPLAY(event->window), event->hardware_keycode, 0, 0);
471 Game.DoKeyboardInput(vk_code: key, eEventType: KEYEV_Down, fAlt: !!(event->state & GDK_MOD1_MASK), fCtrl: !!(event->state & GDK_CONTROL_MASK), fShift: !!(event->state & GDK_SHIFT_MASK), fRepeated: false, pForDialog: nullptr);
472 return TRUE;
473}
474
475gboolean C4ViewportWindow::OnKeyReleaseStatic(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
476{
477 uint32_t key = XkbKeycodeToKeysym(GDK_WINDOW_XDISPLAY(event->window), event->hardware_keycode, 0, 0);
478 Game.DoKeyboardInput(vk_code: key, eEventType: KEYEV_Up, fAlt: !!(event->state & GDK_MOD1_MASK), fCtrl: !!(event->state & GDK_CONTROL_MASK), fShift: !!(event->state & GDK_SHIFT_MASK), fRepeated: false, pForDialog: nullptr);
479 return TRUE;
480}
481
482gboolean C4ViewportWindow::OnScrollStatic(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
483{
484 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
485
486 if (Game.MouseControl.IsViewport(pViewport: window->cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
487 {
488 switch (event->direction)
489 {
490 case GDK_SCROLL_UP:
491 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_Wheel, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state + (short(1) << 16), pVP: window->cvp);
492 break;
493 case GDK_SCROLL_DOWN:
494 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_Wheel, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state + (short(-1) << 16), pVP: window->cvp);
495 break;
496 case GDK_SCROLL_LEFT: case GDK_SCROLL_RIGHT:
497 // no horizontal scrolling implemented so far
498 break;
499 }
500 }
501
502 return TRUE;
503}
504
505gboolean C4ViewportWindow::OnButtonPressStatic(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
506{
507 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
508
509 if (Game.MouseControl.IsViewport(pViewport: window->cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
510 {
511 switch (event->button)
512 {
513 case 1:
514 if (event->type == GDK_BUTTON_PRESS)
515 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_LeftDown, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
516 else if (event->type == GDK_2BUTTON_PRESS)
517 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_LeftDouble, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
518 break;
519 case 2:
520 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_MiddleDown, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
521 break;
522 case 3:
523 if (event->type == GDK_BUTTON_PRESS)
524 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_RightDown, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
525 else if (event->type == GDK_2BUTTON_PRESS)
526 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_RightDouble, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
527 break;
528 }
529 }
530 else
531 {
532 switch (event->button)
533 {
534 case 1:
535 Console.EditCursor.LeftButtonDown(fControl: event->state & MK_CONTROL);
536 break;
537 case 3:
538 Console.EditCursor.RightButtonDown(fControl: event->state & MK_CONTROL);
539 break;
540 }
541 }
542
543 return TRUE;
544}
545
546gboolean C4ViewportWindow::OnButtonReleaseStatic(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
547{
548 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
549
550 if (Game.MouseControl.IsViewport(pViewport: window->cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
551 {
552 switch (event->button)
553 {
554 case 1:
555 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_LeftUp, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
556 break;
557 case 2:
558 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_MiddleUp, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
559 break;
560 case 3:
561 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_RightUp, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
562 break;
563 }
564 }
565 else
566 {
567 switch (event->button)
568 {
569 case 1:
570 Console.EditCursor.LeftButtonUp();
571 break;
572 case 2:
573 Console.EditCursor.MiddleButtonUp();
574 break;
575 case 3:
576 Console.EditCursor.RightButtonUp();
577 break;
578 }
579 }
580
581 return TRUE;
582}
583
584gboolean C4ViewportWindow::OnMotionNotifyStatic(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
585{
586 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
587
588 if (Game.MouseControl.IsViewport(pViewport: window->cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
589 {
590 Game.GraphicsSystem.MouseMove(iButton: C4MC_Button_None, iX: static_cast<int32_t>(event->x), iY: static_cast<int32_t>(event->y), dwKeyParam: event->state, pVP: window->cvp);
591 }
592 else
593 {
594 const auto scale = Application.GetScale();
595
596 Console.EditCursor.Move(iX: window->cvp->ViewX + static_cast<int32_t>(event->x / scale), iY: window->cvp->ViewY + static_cast<int32_t>(event->y / scale), wKeyFlags: event->state);
597 }
598
599 return TRUE;
600}
601
602gboolean C4ViewportWindow::OnConfigureStatic(GtkWidget *widget, GdkEventConfigure *event, gpointer user_data)
603{
604 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
605 C4Viewport *cvp = window->cvp;
606
607 cvp->ScrollBarsByViewPosition();
608
609 return FALSE;
610}
611
612gboolean C4ViewportWindow::OnConfigureDareaStatic(GtkWidget *widget, GdkEventConfigure *event, gpointer user_data)
613{
614 C4ViewportWindow *window = static_cast<C4ViewportWindow *>(user_data);
615 C4Viewport *cvp = window->cvp;
616
617 cvp->UpdateOutputSize();
618
619 return FALSE;
620}
621
622void C4ViewportWindow::OnVScrollStatic(GtkAdjustment *adjustment, gpointer user_data)
623{
624 static_cast<C4ViewportWindow *>(user_data)->cvp->ViewPositionByScrollBars();
625}
626
627void C4ViewportWindow::OnHScrollStatic(GtkAdjustment *adjustment, gpointer user_data)
628{
629 static_cast<C4ViewportWindow *>(user_data)->cvp->ViewPositionByScrollBars();
630}
631
632#else // WITH_DEVELOPER_MODE
633
634bool C4Viewport::TogglePlayerLock() { return false; }
635bool C4Viewport::ScrollBarsByViewPosition() { return false; }
636
637#if defined(USE_X11)
638
639void C4ViewportWindow::HandleMessage(XEvent &e)
640{
641 switch (e.type)
642 {
643 case KeyPress:
644 {
645 // Do not take into account the state of the various modifiers and locks
646 // we don't need that for keyboard control
647 uint32_t key = XkbKeycodeToKeysym(e.xany.display, e.xkey.keycode, 0, 0);
648 Game.DoKeyboardInput(key, KEYEV_Down, Application.IsAltDown(), Application.IsControlDown(), Application.IsShiftDown(), false, nullptr);
649 break;
650 }
651 case KeyRelease:
652 {
653 uint32_t key = XkbKeycodeToKeysym(e.xany.display, e.xkey.keycode, 0, 0);
654 Game.DoKeyboardInput(key, KEYEV_Up, e.xkey.state & Mod1Mask, e.xkey.state & ControlMask, e.xkey.state & ShiftMask, false, nullptr);
655 break;
656 }
657 case ButtonPress:
658 {
659 static int last_left_click, last_right_click;
660 if (Game.MouseControl.IsViewport(cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
661 {
662 switch (e.xbutton.button)
663 {
664 case Button1:
665 if (timeGetTime() - last_left_click < 400)
666 {
667 Game.GraphicsSystem.MouseMove(C4MC_Button_LeftDouble,
668 e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
669 last_left_click = 0;
670 }
671 else
672 {
673 Game.GraphicsSystem.MouseMove(C4MC_Button_LeftDown,
674 e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
675 last_left_click = timeGetTime();
676 }
677 break;
678 case Button2:
679 Game.GraphicsSystem.MouseMove(C4MC_Button_MiddleDown,
680 e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
681 break;
682 case Button3:
683 if (timeGetTime() - last_right_click < 400)
684 {
685 Game.GraphicsSystem.MouseMove(C4MC_Button_RightDouble,
686 e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
687 last_right_click = 0;
688 }
689 else
690 {
691 Game.GraphicsSystem.MouseMove(C4MC_Button_RightDown,
692 e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
693 last_right_click = timeGetTime();
694 }
695 break;
696 case Button4:
697 Game.GraphicsSystem.MouseMove(C4MC_Button_Wheel,
698 e.xbutton.x, e.xbutton.y, e.xbutton.state + (short(1) << 16), cvp);
699 break;
700 case Button5:
701 Game.GraphicsSystem.MouseMove(C4MC_Button_Wheel,
702 e.xbutton.x, e.xbutton.y, e.xbutton.state + (short(-1) << 16), cvp);
703 break;
704 default:
705 break;
706 }
707 }
708 else
709 {
710 switch (e.xbutton.button)
711 {
712 case Button1:
713 Console.EditCursor.LeftButtonDown(e.xbutton.state & MK_CONTROL);
714 break;
715 case Button3:
716 Console.EditCursor.RightButtonDown(e.xbutton.state & MK_CONTROL);
717 break;
718 }
719 }
720 }
721 break;
722 case ButtonRelease:
723 if (Game.MouseControl.IsViewport(cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
724 {
725 switch (e.xbutton.button)
726 {
727 case Button1:
728 Game.GraphicsSystem.MouseMove(C4MC_Button_LeftUp, e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
729 break;
730 case Button2:
731 Game.GraphicsSystem.MouseMove(C4MC_Button_MiddleUp, e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
732 break;
733 case Button3:
734 Game.GraphicsSystem.MouseMove(C4MC_Button_RightUp, e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
735 break;
736 default:
737 break;
738 }
739 }
740 else
741 {
742 switch (e.xbutton.button)
743 {
744 case Button1:
745 Console.EditCursor.LeftButtonUp();
746 break;
747 case Button3:
748 Console.EditCursor.RightButtonUp();
749 break;
750 }
751 }
752 break;
753 case MotionNotify:
754 if (Game.MouseControl.IsViewport(cvp) && (Console.EditCursor.GetMode() == C4CNS_ModePlay))
755 {
756 Game.GraphicsSystem.MouseMove(C4MC_Button_None, e.xbutton.x, e.xbutton.y, e.xbutton.state, cvp);
757 }
758 else
759 {
760 const auto scale = Application.GetScale();
761
762 Console.EditCursor.Move(cvp->ViewX + static_cast<int32_t>(e.xbutton.x / scale), cvp->ViewY + static_cast<int32_t>(e.xbutton.y / scale), e.xbutton.state);
763 }
764 break;
765 case ConfigureNotify:
766 cvp->UpdateOutputSize();
767 break;
768 }
769}
770
771#endif // USE_X11
772
773#endif // WITH_DEVELOPER_MODE/_WIN32
774
775void C4ViewportWindow::Close()
776{
777 Game.GraphicsSystem.CloseViewport(cvp);
778}
779
780bool C4Viewport::UpdateOutputSize()
781{
782 if (!pWindow) return false;
783 // Output size
784 C4Rect rect;
785#ifdef WITH_DEVELOPER_MODE
786 GtkAllocation allocation;
787 gtk_widget_get_allocation(widget: pWindow->drawing_area, allocation: &allocation);
788 // Use only size of drawing area without scrollbars
789 rect.x = allocation.x;
790 rect.y = allocation.y;
791 rect.Wdt = allocation.x + allocation.width;
792 rect.Hgt = allocation.y + allocation.height;
793#else
794 if (!pWindow->GetSize(rect)) return false;
795#endif
796 OutX = rect.x; OutY = rect.y;
797 const auto scale = Application.GetScale();
798 ViewWdt = static_cast<int32_t>(ceilf(x: rect.Wdt / scale)); ViewHgt = static_cast<int32_t>(ceilf(x: rect.Hgt / scale));
799 // Scroll bars
800 ScrollBarsByViewPosition();
801 // Reset menus
802 ResetMenuPositions = true;
803#ifndef USE_CONSOLE
804 // update internal GL size
805 if (pCtx) pCtx->UpdateSize();
806#endif
807 // Done
808 return true;
809}
810
811C4Viewport::C4Viewport()
812{
813 Default();
814}
815
816C4Viewport::~C4Viewport()
817{
818 Clear();
819}
820
821void C4Viewport::Clear()
822{
823#ifndef USE_CONSOLE
824 if (pCtx) { delete pCtx; pCtx = nullptr; }
825#endif
826 if (pWindow) { pWindow->Clear(); delete pWindow; pWindow = nullptr; }
827 Player = NO_OWNER;
828 ViewX = ViewY = ViewWdt = ViewHgt = 0;
829 OutX = OutY = ViewWdt = ViewHgt = 0;
830 DrawX = DrawY = 0;
831 Regions.Clear();
832 dViewX = dViewY = -31337;
833 ViewOffsX = ViewOffsY = 0;
834}
835
836void C4Viewport::DrawOverlay(C4FacetEx &cgo)
837{
838 if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay)
839 {
840 // Player info
841 C4ST_STARTNEW(CInfoStat, "C4Viewport::DrawOverlay: Cursor Info")
842 DrawCursorInfo(cgo);
843 C4ST_STOP(CInfoStat)
844 C4ST_STARTNEW(PInfoStat, "C4Viewport::DrawOverlay: Player Info")
845 DrawPlayerInfo(cgo);
846 C4ST_STOP(PInfoStat)
847 C4ST_STARTNEW(MenuStat, "C4Viewport::DrawOverlay: Menu")
848 DrawMenu(cgo);
849 C4ST_STOP(MenuStat)
850 }
851 // Game messages
852 C4ST_STARTNEW(MsgStat, "C4Viewport::DrawOverlay: Messages")
853 Game.Messages.Draw(cgo, iPlayer: Player);
854 C4ST_STOP(MsgStat)
855
856 // Control overlays (if not film/replay)
857 if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay)
858 {
859 // Mouse control
860 if (Game.MouseControl.IsViewport(pViewport: this))
861 {
862 C4ST_STARTNEW(MouseStat, "C4Viewport::DrawOverlay: Mouse")
863 if (Config.Graphics.ShowCommands) // Now, ShowCommands is respected even for mouse control...
864 DrawMouseButtons(cgo);
865 Game.MouseControl.Draw(cgo);
866 // Draw GUI-mouse in EM if active
867 if (pWindow && Game.pGUI) Game.pGUI->RenderMouse(cgo);
868 C4ST_STOP(MouseStat)
869 }
870 // Keyboard/Gamepad
871 else
872 {
873 // Player menu button
874 if (Config.Graphics.ShowCommands)
875 {
876 int32_t iSymbolSize = C4SymbolSize * 2 / 3;
877 C4Facet ccgo; ccgo.Set(nsfc: cgo.Surface, nx: cgo.X + cgo.Wdt - iSymbolSize, ny: cgo.Y + C4SymbolSize + 2 * C4SymbolBorder, nwdt: iSymbolSize, nhgt: iSymbolSize); ccgo.Y += iSymbolSize;
878 DrawCommandKey(cgo&: ccgo, iCom: COM_PlayerMenu, fPressed: false, szText: PlrControlKeyName(iPlayer: Player, iControl: Com2Control(iCom: COM_PlayerMenu), fShort: true).c_str());
879 }
880 }
881 }
882}
883
884void C4Viewport::DrawCursorInfo(C4FacetEx &cgo)
885{
886 C4Facet ccgo, ccgo2;
887
888 // Get cursor
889 C4Player *pPlr = Game.Players.Get(iPlayer: Player);
890 if (!pPlr)
891 return;
892
893 const auto realCursor = pPlr->Cursor;
894 const auto viewCursor = pPlr->ViewCursor;
895 const auto cursor = viewCursor ? viewCursor : realCursor;
896 if (!cursor)
897 return;
898
899 // Draw info
900 if (Config.Graphics.ShowPlayerHUDAlways)
901 if (cursor->Info)
902 {
903 C4ST_STARTNEW(ObjInfStat, "C4Viewport::DrawCursorInfo: Object info")
904 ccgo.Set(nsfc: cgo.Surface, nx: cgo.X + C4SymbolBorder, ny: cgo.Y + C4SymbolBorder, nwdt: 3 * C4SymbolSize, nhgt: C4SymbolSize);
905 cursor->Info->Draw(cgo&: ccgo,
906 fShowPortrait: Config.Graphics.ShowPortraits,
907 fShowCaptain: (cursor == Game.Players.Get(iPlayer: Player)->Captain), pOfObj: cursor);
908 C4ST_STOP(ObjInfStat)
909 }
910
911 // Draw contents
912 if (!(cursor->Def->HideHUDElements & C4DefCore::HH_Inventory))
913 {
914 C4ST_STARTNEW(ContStat, "C4Viewport::DrawCursorInfo: Contents")
915 ccgo.Set(nsfc: cgo.Surface, nx: cgo.X + C4SymbolBorder, ny: cgo.Y + cgo.Hgt - C4SymbolBorder - C4SymbolSize, nwdt: 7 * C4SymbolSize, nhgt: C4SymbolSize);
916 cursor->Contents.DrawIDList(cgo&: ccgo, iSelection: -1, rDefs&: Game.Defs, dwCategory: C4D_All, pRegions: SetRegions, iRegionCom: COM_Contents, fDrawOneCounts: false);
917 C4ST_STOP(ContStat)
918 }
919
920 // Draw energy levels
921 if (cursor->ViewEnergy || Config.Graphics.ShowPlayerHUDAlways)
922 if (cgo.Hgt > 2 * C4SymbolSize + 2 * C4SymbolBorder)
923 {
924 int32_t cx = C4SymbolBorder;
925 C4ST_STARTNEW(EnStat, "C4Viewport::DrawCursorInfo: Energy")
926 int32_t bar_wdt = Game.GraphicsResource.fctEnergyBars.Wdt;
927 int32_t iYOff = Config.Graphics.ShowPortraits ? 10 : 0;
928 // Energy
929 ccgo.Set(nsfc: cgo.Surface, nx: cgo.X + cx, ny: cgo.Y + C4SymbolSize + 2 * C4SymbolBorder + iYOff, nwdt: bar_wdt, nhgt: cgo.Hgt - 3 * C4SymbolBorder - 2 * C4SymbolSize - iYOff);
930 if (!(cursor->Def->HideHUDBars & C4DefCore::HB_Energy))
931 {
932 cursor->DrawEnergy(cgo&: ccgo); ccgo.X += bar_wdt + 1;
933 }
934 // Magic energy
935 if (cursor->MagicEnergy && !(cursor->Def->HideHUDBars & C4DefCore::HB_MagicEnergy))
936 {
937 cursor->DrawMagicEnergy(cgo&: ccgo); ccgo.X += bar_wdt + 1;
938 }
939 // Breath
940 if (cursor->Breath && (cursor->Breath < cursor->GetPhysical()->Breath) && !(cursor->Def->HideHUDBars & C4DefCore::HB_Breath))
941 {
942 cursor->DrawBreath(cgo&: ccgo); ccgo.X += bar_wdt + 1;
943 }
944 C4ST_STOP(EnStat)
945 }
946
947 // Draw commands
948 if (Config.Graphics.ShowCommands)
949 if (realCursor)
950 if (cgo.Hgt > C4SymbolSize)
951 {
952 C4ST_STARTNEW(CmdStat, "C4Viewport::DrawCursorInfo: Commands")
953 int32_t iSize = 2 * C4SymbolSize / 3;
954 int32_t iSize2 = 2 * iSize;
955 // Primary area (bottom)
956 ccgo.Set(nsfc: cgo.Surface, nx: cgo.X, ny: cgo.Y + cgo.Hgt - iSize, nwdt: cgo.Wdt, nhgt: iSize);
957 // Secondary area (side)
958 ccgo2.Set(nsfc: cgo.Surface, nx: cgo.X + cgo.Wdt - iSize2, ny: cgo.Y, nwdt: iSize2, nhgt: cgo.Hgt - iSize - 5);
959 // Draw commands
960 realCursor->DrawCommands(cgo&: ccgo, cgo2&: ccgo2, pRegions: SetRegions);
961 C4ST_STOP(CmdStat)
962 }
963}
964
965void C4Viewport::DrawMenu(C4FacetEx &cgo)
966{
967 // Get player
968 C4Player *pPlr = Game.Players.Get(iPlayer: Player);
969
970 // Player eliminated
971 if (pPlr && pPlr->Eliminated)
972 {
973 Application.DDraw->TextOut(szText: (pPlr->Surrendered ? LoadResStr(id: C4ResStrTableKey::IDS_PLR_SURRENDERED, args: pPlr->GetName()) : LoadResStr(id: C4ResStrTableKey::IDS_PLR_ELIMINATED, args: pPlr->GetName())).c_str(),
974 rFont&: Game.GraphicsResource.FontRegular, fZoom: 1.0, sfcDest: cgo.Surface, iTx: cgo.X + cgo.Wdt / 2, iTy: cgo.Y + 2 * cgo.Hgt / 3, dwFCol: 0xfaFF0000, byForm: ACenter);
975 return;
976 }
977
978 // for menus, cgo is using GUI-syntax: TargetX/Y marks the drawing offset; x/y/Wdt/Hgt marks the offset rect
979 int32_t iOldTx = cgo.TargetX; int32_t iOldTy = cgo.TargetY;
980 cgo.TargetX = cgo.X; cgo.TargetY = cgo.Y;
981 cgo.X = 0; cgo.Y = 0;
982
983 // Player cursor object menu
984 if (pPlr && pPlr->Cursor && pPlr->Cursor->Menu)
985 {
986 if (ResetMenuPositions) pPlr->Cursor->Menu->ResetLocation();
987 // if mouse is dragging, make it transparent to easy construction site drag+drop
988 bool fDragging = false;
989 if (Game.MouseControl.IsDragging() && Game.MouseControl.IsViewport(pViewport: this))
990 {
991 fDragging = true;
992 lpDDraw->ActivateBlitModulation(dwWithClr: 0xafffffff);
993 }
994 // draw menu
995 pPlr->Cursor->Menu->Draw(cgo);
996 // reset modulation for dragging
997 if (fDragging) lpDDraw->DeactivateBlitModulation();
998 }
999 // Player menu
1000 if (pPlr && pPlr->Menu.IsActive())
1001 {
1002 if (ResetMenuPositions) pPlr->Menu.ResetLocation();
1003 pPlr->Menu.Draw(cgo);
1004 }
1005 // Fullscreen menu
1006 if (FullScreen.pMenu && FullScreen.pMenu->IsActive())
1007 {
1008 if (ResetMenuPositions) FullScreen.pMenu->ResetLocation();
1009 FullScreen.pMenu->Draw(cgo);
1010 }
1011
1012 // Flag done
1013 ResetMenuPositions = false;
1014
1015 // restore cgo
1016 cgo.X = cgo.TargetX; cgo.Y = cgo.TargetY;
1017 cgo.TargetX = iOldTx; cgo.TargetY = iOldTy;
1018}
1019
1020extern int32_t iLastControlSize, iPacketDelay, ScreenRate;
1021extern int32_t ControlQueueSize, ControlQueueDataSize;
1022
1023void C4Viewport::Draw(C4FacetEx &cgo, bool fDrawOverlay)
1024{
1025#ifdef USE_CONSOLE
1026 // No drawing in console mode
1027 return;
1028#endif
1029
1030 if (fDrawOverlay)
1031 {
1032 // Draw landscape borders. Only if overlay, so complete map screenshots don't get messed up
1033 if (BorderLeft) Application.DDraw->BlitSurfaceTile(sfcSurface: Game.GraphicsResource.fctBackground.Surface, sfcTarget: cgo.Surface, iToX: DrawX, iToY: DrawY, iToWdt: BorderLeft, iToHgt: ViewHgt, iOffsetX: -DrawX, iOffsetY: -DrawY);
1034 if (BorderTop) Application.DDraw->BlitSurfaceTile(sfcSurface: Game.GraphicsResource.fctBackground.Surface, sfcTarget: cgo.Surface, iToX: DrawX + BorderLeft, iToY: DrawY, iToWdt: ViewWdt - BorderLeft - BorderRight, iToHgt: BorderTop, iOffsetX: -DrawX - BorderLeft, iOffsetY: -DrawY);
1035 if (BorderRight) Application.DDraw->BlitSurfaceTile(sfcSurface: Game.GraphicsResource.fctBackground.Surface, sfcTarget: cgo.Surface, iToX: DrawX + ViewWdt - BorderRight, iToY: DrawY, iToWdt: BorderRight, iToHgt: ViewHgt, iOffsetX: -DrawX - ViewWdt + BorderRight, iOffsetY: -DrawY);
1036 if (BorderBottom)Application.DDraw->BlitSurfaceTile(sfcSurface: Game.GraphicsResource.fctBackground.Surface, sfcTarget: cgo.Surface, iToX: DrawX + BorderLeft, iToY: DrawY + ViewHgt - BorderBottom, iToWdt: ViewWdt - BorderLeft - BorderRight, iToHgt: BorderBottom, iOffsetX: -DrawX - BorderLeft, iOffsetY: -DrawY - ViewHgt + BorderBottom);
1037
1038 // Set clippers
1039 Application.DDraw->SetPrimaryClipper(iX1: DrawX + BorderLeft, iY1: DrawY + BorderTop, iX2: DrawX + ViewWdt - 1 - BorderRight, iY2: DrawY + ViewHgt - 1 - BorderBottom);
1040 cgo.X += BorderLeft; cgo.Y += BorderTop; cgo.Wdt -= BorderLeft + BorderRight; cgo.Hgt -= BorderTop + BorderBottom;
1041 cgo.TargetX += BorderLeft; cgo.TargetY += BorderTop;
1042 }
1043
1044 // landscape mod by FoW
1045 C4Player *pPlr = Game.Players.Get(iPlayer: Player);
1046 if (pPlr && pPlr->fFogOfWar)
1047 {
1048 ClrModMap.Reset(iResX: Game.C4S.Landscape.FoWRes, iResY: Game.C4S.Landscape.FoWRes, iWdtPx: ViewWdt, iHgtPx: ViewHgt, iOffX: cgo.TargetX, iOffY: cgo.TargetY, dwModClr: 0, dwAddClr: 0, x0: cgo.X, y0: cgo.Y, dwBackClr: Game.FoWColor, backsfc: cgo.Surface);
1049 pPlr->FoW2Map(rMap&: ClrModMap, iOffX: cgo.X - cgo.TargetX, iOffY: cgo.Y - cgo.TargetY);
1050 lpDDraw->SetClrModMap(&ClrModMap);
1051 lpDDraw->SetClrModMapEnabled(true);
1052 }
1053 else
1054 lpDDraw->SetClrModMapEnabled(false);
1055
1056 C4ST_STARTNEW(SkyStat, "C4Viewport::Draw: Sky")
1057 Game.Landscape.Sky.Draw(cgo);
1058 C4ST_STOP(SkyStat)
1059 Game.BackObjects.DrawAll(cgo, iPlayer: Player);
1060
1061 // Draw Landscape
1062 C4ST_STARTNEW(LandStat, "C4Viewport::Draw: Landscape")
1063 Game.Landscape.Draw(cgo, iPlayer: Player);
1064 C4ST_STOP(LandStat)
1065
1066 // draw PXS (unclipped!)
1067 C4ST_STARTNEW(PXSStat, "C4Viewport::Draw: PXS")
1068 Game.PXS.Draw(cgo);
1069 C4ST_STOP(PXSStat)
1070
1071 // draw objects
1072 C4ST_STARTNEW(ObjStat, "C4Viewport::Draw: Objects")
1073 Game.Objects.Draw(cgo, iPlayer: Player);
1074 C4ST_STOP(ObjStat)
1075
1076 // draw global particles
1077 C4ST_STARTNEW(PartStat, "C4Viewport::Draw: Particles")
1078 Game.Particles.GlobalParticles.Draw(cgo, pObj: nullptr);
1079 C4ST_STOP(PartStat)
1080
1081 // draw foreground objects
1082 Game.ForeObjects.DrawIfCategory(cgo, iPlayer: Player, dwCat: C4D_Parallax, fInvert: true);
1083
1084 // Draw PathFinder
1085 if (Game.GraphicsSystem.ShowPathfinder) Game.PathFinder.Draw(cgo);
1086
1087 // Draw overlay
1088 if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay) Game.DrawCursors(cgo, iPlayer: Player);
1089
1090 // FogOfWar-mod off
1091 lpDDraw->SetClrModMapEnabled(false);
1092
1093 // now restore complete cgo range for overlay drawing
1094 if (fDrawOverlay)
1095 {
1096 cgo.X -= BorderLeft; cgo.Y -= BorderTop; cgo.Wdt += BorderLeft + BorderRight; cgo.Hgt += BorderTop + BorderBottom;
1097 cgo.TargetX -= BorderLeft; cgo.TargetY -= BorderTop;
1098 Application.DDraw->SetPrimaryClipper(iX1: DrawX, iY1: DrawY, iX2: DrawX + ViewWdt - 1, iY2: DrawY + ViewHgt - 1);
1099 }
1100
1101 // draw custom GUI objects
1102 Game.ForeObjects.DrawIfCategory(cgo, iPlayer: Player, dwCat: C4D_Parallax, fInvert: false);
1103
1104 // Draw overlay
1105 C4ST_STARTNEW(OvrStat, "C4Viewport::Draw: Overlay")
1106
1107 if (!Application.isFullScreen) Console.EditCursor.Draw(cgo);
1108
1109 if (fDrawOverlay) DrawOverlay(cgo);
1110
1111 // Netstats
1112 if (Game.GraphicsSystem.ShowNetstatus)
1113 Game.Network.DrawStatus(cgo);
1114
1115 C4ST_STOP(OvrStat)
1116
1117 // Remove clippers
1118 if (fDrawOverlay) Application.DDraw->NoPrimaryClipper();
1119}
1120
1121void C4Viewport::BlitOutput()
1122{
1123 if (pWindow) Application.DDraw->PageFlip();
1124}
1125
1126void C4Viewport::Execute()
1127{
1128 // Update regions
1129 static int32_t RegionUpdate = 0;
1130 SetRegions = nullptr;
1131 RegionUpdate++;
1132 if (RegionUpdate >= 5)
1133 {
1134 RegionUpdate = 0;
1135 Regions.Clear();
1136 Regions.SetAdjust(iX: -OutX, iY: -OutY);
1137 SetRegions = &Regions;
1138 }
1139 // Adjust position
1140 AdjustPosition();
1141#ifndef USE_CONSOLE
1142 // select rendering context
1143 if (pCtx) if (!pCtx->Select()) return;
1144#endif
1145 // Current graphics output
1146 C4FacetEx cgo;
1147 cgo.Set(nsfc: Application.DDraw->lpBack, nx: DrawX, ny: DrawY, nwdt: ViewWdt, nhgt: ViewHgt, ntx: ViewX, nty: ViewY);
1148 // Draw
1149 Draw(cgo, fDrawOverlay: true);
1150 // Blit output
1151 BlitOutput();
1152#ifndef USE_CONSOLE
1153 // switch back to original context
1154 if (pCtx) pGL->GetMainCtx().Select();
1155#endif
1156}
1157
1158void C4Viewport::AdjustPosition()
1159{
1160 int32_t ViewportScrollBorder = fIsNoOwnerViewport ? 0 : C4ViewportScrollBorder;
1161 // View position
1162 if (PlayerLock && ValidPlr(plr: Player))
1163 {
1164 C4Player *pPlr = Game.Players.Get(iPlayer: Player);
1165 int32_t iScrollRange = (std::min)(a: ViewWdt / 10, b: ViewHgt / 10);
1166 int32_t iExtraBoundsX = 0, iExtraBoundsY = 0;
1167 if (pPlr->ViewMode == C4PVM_Scrolling)
1168 {
1169 iScrollRange = 0;
1170 iExtraBoundsX = iExtraBoundsY = ViewportScrollBorder;
1171 }
1172 else
1173 {
1174 // if view is close to border, allow scrolling
1175 if (pPlr->ViewX < ViewportScrollBorder) iExtraBoundsX = std::min<int32_t>(a: ViewportScrollBorder - pPlr->ViewX, b: ViewportScrollBorder);
1176 else if (pPlr->ViewX >= GBackWdt - ViewportScrollBorder) iExtraBoundsX = std::min<int32_t>(a: pPlr->ViewX - GBackWdt, b: 0) + ViewportScrollBorder;
1177 if (pPlr->ViewY < ViewportScrollBorder) iExtraBoundsY = std::min<int32_t>(a: ViewportScrollBorder - pPlr->ViewY, b: ViewportScrollBorder);
1178 else if (pPlr->ViewY >= GBackHgt - ViewportScrollBorder) iExtraBoundsY = std::min<int32_t>(a: pPlr->ViewY - GBackHgt, b: 0) + ViewportScrollBorder;
1179 }
1180 iExtraBoundsX = std::max<int32_t>(a: iExtraBoundsX, b: (ViewWdt - GBackWdt) / 2 + 1);
1181 iExtraBoundsY = std::max<int32_t>(a: iExtraBoundsY, b: (ViewHgt - GBackHgt) / 2 + 1);
1182 // calc target view position
1183 int32_t iTargetViewX = pPlr->ViewX - ViewWdt / 2;
1184 int32_t iTargetViewY = pPlr->ViewY - ViewHgt / 2;
1185 // add mouse auto scroll
1186 int32_t iPrefViewX = ViewX - ViewOffsX, iPrefViewY = ViewY - ViewOffsY;
1187 if (pPlr->MouseControl && Game.MouseControl.InitCentered && Config.General.MouseAScroll)
1188 {
1189 int32_t iAutoScrollBorder = (std::min)(a: (std::min)(a: ViewWdt / 10, b: ViewHgt / 10), b: C4SymbolSize);
1190 if (iAutoScrollBorder)
1191 {
1192 iPrefViewX += BoundBy<int32_t>(bval: 0, lbound: Game.MouseControl.VpX - ViewWdt + iAutoScrollBorder, rbound: Game.MouseControl.VpX - iAutoScrollBorder) * iScrollRange * BoundBy<int32_t>(bval: Config.General.MouseAScroll, lbound: 0, rbound: 100) / 100 / iAutoScrollBorder;
1193 iPrefViewY += BoundBy<int32_t>(bval: 0, lbound: Game.MouseControl.VpY - ViewHgt + iAutoScrollBorder, rbound: Game.MouseControl.VpY - iAutoScrollBorder) * iScrollRange * BoundBy<int32_t>(bval: Config.General.MouseAScroll, lbound: 0, rbound: 100) / 100 / iAutoScrollBorder;
1194 }
1195 }
1196 // scroll range
1197 iTargetViewX = BoundBy(bval: iPrefViewX, lbound: iTargetViewX - iScrollRange, rbound: iTargetViewX + iScrollRange);
1198 iTargetViewY = BoundBy(bval: iPrefViewY, lbound: iTargetViewY - iScrollRange, rbound: iTargetViewY + iScrollRange);
1199 // bounds
1200 iTargetViewX = BoundBy<int32_t>(bval: iTargetViewX, lbound: -iExtraBoundsX, GBackWdt - ViewWdt + iExtraBoundsX);
1201 iTargetViewY = BoundBy<int32_t>(bval: iTargetViewY, lbound: -iExtraBoundsY, GBackHgt - ViewHgt + iExtraBoundsY);
1202 // smooth
1203 if (dViewX >= 0 && dViewY >= 0)
1204 {
1205 dViewX += (itofix(x: iTargetViewX) - dViewX) / BoundBy<int32_t>(bval: Config.General.ScrollSmooth, lbound: 1, rbound: 50); ViewX = fixtoi(x: dViewX);
1206 dViewY += (itofix(x: iTargetViewY) - dViewY) / BoundBy<int32_t>(bval: Config.General.ScrollSmooth, lbound: 1, rbound: 50); ViewY = fixtoi(x: dViewY);
1207 }
1208 else
1209 {
1210 dViewX = itofix(x: ViewX = iTargetViewX);
1211 dViewY = itofix(x: ViewY = iTargetViewY);
1212 }
1213 // apply offset
1214 ViewX += ViewOffsX; ViewY += ViewOffsY;
1215 }
1216 // NO_OWNER can't scroll
1217 if (fIsNoOwnerViewport) { ViewOffsX = 0; ViewOffsY = 0; }
1218 // clip at borders, update vars
1219 UpdateViewPosition();
1220}
1221
1222void C4Viewport::CenterPosition()
1223{
1224 // center viewport position on map
1225 // set center position
1226 ViewX = (GBackWdt - ViewWdt) / 2;
1227 ViewY = (GBackHgt - ViewHgt) / 2;
1228 // clips and updates
1229 UpdateViewPosition();
1230}
1231
1232void C4Viewport::UpdateViewPosition()
1233{
1234 // no-owner viewports should not scroll outside viewing area
1235 if (fIsNoOwnerViewport)
1236 {
1237 if (Application.isFullScreen && GBackWdt < ViewWdt)
1238 {
1239 ViewX = (GBackWdt - ViewWdt) / 2;
1240 }
1241 else
1242 {
1243 ViewX = std::min<int32_t>(a: ViewX, GBackWdt - ViewWdt);
1244 ViewX = std::max<int32_t>(a: ViewX, b: 0);
1245 }
1246 if (Application.isFullScreen && GBackHgt < ViewHgt)
1247 {
1248 ViewY = (GBackHgt - ViewHgt) / 2;
1249 }
1250 else
1251 {
1252 ViewY = std::min<int32_t>(a: ViewY, GBackHgt - ViewHgt);
1253 ViewY = std::max<int32_t>(a: ViewY, b: 0);
1254 }
1255 }
1256 // update borders
1257 BorderLeft = std::max<int32_t>(a: -ViewX, b: 0);
1258 BorderTop = std::max<int32_t>(a: -ViewY, b: 0);
1259 BorderRight = std::max<int32_t>(a: ViewWdt - GBackWdt + ViewX, b: 0);
1260 BorderBottom = std::max<int32_t>(a: ViewHgt - GBackHgt + ViewY, b: 0);
1261}
1262
1263void C4Viewport::Default()
1264{
1265 pCtx = nullptr;
1266 pWindow = nullptr;
1267 Player = 0;
1268 ViewX = ViewY = ViewWdt = ViewHgt = 0;
1269 BorderLeft = BorderTop = BorderRight = BorderBottom = 0;
1270 OutX = OutY = ViewWdt = ViewHgt = 0;
1271 DrawX = DrawY = 0;
1272 PlayerLock = true;
1273 ResetMenuPositions = false;
1274 SetRegions = nullptr;
1275 Regions.Default();
1276 dViewX = dViewY = -31337;
1277 ViewOffsX = ViewOffsY = 0;
1278 fIsNoOwnerViewport = false;
1279}
1280
1281void C4Viewport::DrawPlayerInfo(C4FacetEx &cgo)
1282{
1283 C4Facet ccgo;
1284 if (!ValidPlr(plr: Player)) return;
1285
1286 // Wealth
1287 if (Game.Players.Get(iPlayer: Player)->ViewWealth || Config.Graphics.ShowPlayerHUDAlways)
1288 {
1289 int32_t wdt = C4SymbolSize;
1290 int32_t hgt = C4SymbolSize / 2;
1291 ccgo.Set(nsfc: cgo.Surface,
1292 nx: cgo.X + cgo.Wdt - wdt - C4SymbolBorder,
1293 ny: cgo.Y + C4SymbolBorder,
1294 nwdt: wdt, nhgt: hgt);
1295 Game.GraphicsResource.fctWealth.DrawValue(cgo&: ccgo, iValue: Game.Players.Get(iPlayer: Player)->Wealth);
1296 }
1297
1298 // Value gain
1299 if ((Game.C4S.Game.ValueGain && Game.Players.Get(iPlayer: Player)->ViewValue)
1300 || Config.Graphics.ShowPlayerHUDAlways)
1301 {
1302 int32_t wdt = C4SymbolSize;
1303 int32_t hgt = C4SymbolSize / 2;
1304 ccgo.Set(nsfc: cgo.Surface,
1305 nx: cgo.X + cgo.Wdt - 2 * wdt - 2 * C4SymbolBorder,
1306 ny: cgo.Y + C4SymbolBorder,
1307 nwdt: wdt, nhgt: hgt);
1308 Game.GraphicsResource.fctScore.DrawValue(cgo&: ccgo, iValue: Game.Players.Get(iPlayer: Player)->ValueGain);
1309 }
1310
1311 // Crew
1312 if (Config.Graphics.ShowPlayerHUDAlways)
1313 {
1314 int32_t wdt = C4SymbolSize;
1315 int32_t hgt = C4SymbolSize / 2;
1316 ccgo.Set(nsfc: cgo.Surface,
1317 nx: cgo.X + cgo.Wdt - 3 * wdt - 3 * C4SymbolBorder,
1318 ny: cgo.Y + C4SymbolBorder,
1319 nwdt: wdt, nhgt: hgt);
1320 Game.GraphicsResource.fctCrewClr.DrawValue2Clr(cgo&: ccgo, iValue1: Game.Players.Get(iPlayer: Player)->SelectCount, iValue2: Game.Players.Get(iPlayer: Player)->ActiveCrewCount(), dwClr: Game.Players.Get(iPlayer: Player)->ColorDw);
1321 }
1322
1323 // Controls
1324 DrawPlayerControls(cgo);
1325 DrawPlayerStartup(cgo);
1326}
1327
1328bool C4Viewport::Init(int32_t iPlayer, bool fSetTempOnly)
1329{
1330 // Fullscreen viewport initialization
1331 // Set Player
1332 if (!ValidPlr(plr: iPlayer)) iPlayer = NO_OWNER;
1333 Player = iPlayer;
1334 if (!fSetTempOnly) fIsNoOwnerViewport = (iPlayer == NO_OWNER);
1335 // Owned viewport: clear any flash message explaining observer menu
1336 if (ValidPlr(plr: iPlayer)) Game.GraphicsSystem.FlashMessage(szMessage: "");
1337 return true;
1338}
1339
1340bool C4Viewport::Init(CStdWindow *pParent, CStdApp *pApp, int32_t iPlayer)
1341{
1342 // Console viewport initialization
1343 // Set Player
1344 if (!ValidPlr(plr: iPlayer)) iPlayer = NO_OWNER;
1345 Player = iPlayer;
1346 fIsNoOwnerViewport = (Player == NO_OWNER);
1347 // Create window
1348 pWindow = new C4ViewportWindow(this);
1349 const auto scale = Application.GetScale();
1350 const C4Rect bounds{CStdWindow::DefaultBounds.x, CStdWindow::DefaultBounds.y, static_cast<int32_t>(ceilf(x: 400 * scale)), static_cast<int32_t>(ceilf(x: 250 * scale))};
1351 if (!pWindow->Init(app: pApp, title: (Player == NO_OWNER) ? LoadResStr(id: C4ResStrTableKey::IDS_CNS_VIEWPORT) : Game.Players.Get(iPlayer: Player)->GetName(), bounds, parent: pParent))
1352 return false;
1353 // Updates
1354 UpdateOutputSize();
1355 // Disable player lock on unowned viewports
1356 if (!ValidPlr(plr: Player)) TogglePlayerLock();
1357 // create rendering context
1358 if (lpDDraw) pCtx = lpDDraw->CreateContext(pWindow, pApp);
1359 // Success
1360 return true;
1361}
1362
1363std::string PlrControlKeyName(int32_t iPlayer, int32_t iControl, bool fShort)
1364{
1365 // determine player
1366 C4Player *pPlr = Game.Players.Get(iPlayer);
1367 // player control
1368 if (pPlr)
1369 {
1370 if (Inside<int32_t>(ival: pPlr->Control, lbound: C4P_Control_Keyboard1, rbound: C4P_Control_Keyboard4))
1371 return C4KeyCodeEx::KeyCode2String(wCode: Config.Controls.Keyboard[pPlr->Control][iControl], fHumanReadable: true, fShort);
1372 if (Inside<int32_t>(ival: pPlr->Control, lbound: C4P_Control_GamePad1, rbound: C4P_Control_GamePadMax))
1373 return C4KeyCodeEx::KeyCode2String(wCode: Config.Gamepads[pPlr->Control - C4P_Control_GamePad1].Button[iControl], fHumanReadable: true, fShort);
1374 }
1375 // global control
1376 else
1377 {
1378 // look up iControl for a matching mapping in global key map
1379 // and then display the key name - should at least work for
1380 // stuff in KEYSCOPE_FullSMenu...
1381 const char *szKeyID;
1382 switch (iControl)
1383 {
1384 case CON_Throw: szKeyID = "FullscreenMenuOK"; break;
1385 case CON_Dig: szKeyID = "FullscreenMenuCancel"; break;
1386 default: szKeyID = nullptr; break;
1387 }
1388 if (szKeyID) return Game.KeyboardInput.GetKeyCodeNameByKeyName(szKeyName: szKeyID, fShort);
1389 }
1390 // undefined control
1391 return "";
1392}
1393
1394void C4Viewport::DrawPlayerControls(C4FacetEx &cgo)
1395{
1396 if (!ValidPlr(plr: Player)) return;
1397 if (!Game.Players.Get(iPlayer: Player)->ShowControl) return;
1398 int32_t size = (std::min)(a: cgo.Wdt / 3, b: 7 * cgo.Hgt / 24);
1399 int32_t tx;
1400 int32_t ty;
1401 switch (Game.Players.Get(iPlayer: Player)->ShowControlPos)
1402 {
1403 case 1: // Position 1: bottom right corner
1404 tx = cgo.X + cgo.Wdt * 3 / 4 - size / 2;
1405 ty = cgo.Y + cgo.Hgt / 2 - size / 2;
1406 break;
1407 case 2: // Position 2: bottom left corner
1408 tx = cgo.X + cgo.Wdt / 4 - size / 2;
1409 ty = cgo.Y + cgo.Hgt / 2 - size / 2;
1410 break;
1411 case 3: // Position 3: top left corner
1412 tx = cgo.X + cgo.Wdt / 4 - size / 2;
1413 ty = cgo.Y + 15;
1414 break;
1415 case 4: // Position 4: top right corner
1416 tx = cgo.X + cgo.Wdt * 3 / 4 - size / 2;
1417 ty = cgo.Y + 15;
1418 break;
1419 default: // default: Top center
1420 tx = cgo.X + cgo.Wdt / 2 - size / 2;
1421 ty = cgo.Y + 15;
1422 break;
1423 }
1424 int32_t iShowCtrl = Game.Players.Get(iPlayer: Player)->ShowControl;
1425 int32_t iLastCtrl = Com2Control(iCom: Game.Players.Get(iPlayer: Player)->LastCom);
1426 int32_t scwdt = size / 3, schgt = size / 4;
1427 bool showtext;
1428
1429 const int32_t C4MaxShowControl = 10;
1430
1431 for (int32_t iCtrl = 0; iCtrl < C4MaxShowControl; iCtrl++)
1432 if (iShowCtrl & (1 << iCtrl))
1433 {
1434 showtext = iShowCtrl & (1 << (iCtrl + C4MaxShowControl));
1435 if (iShowCtrl & (1 << (iCtrl + 2 * C4MaxShowControl)))
1436 if (Tick35 > 18) showtext = false;
1437 C4Facet ccgo;
1438 ccgo.Set(nsfc: cgo.Surface, nx: tx + scwdt * (iCtrl % 3), ny: ty + schgt * (iCtrl / 3), nwdt: scwdt, nhgt: schgt);
1439 DrawControlKey(cgo&: ccgo, iControl: iCtrl, fPressed: (iLastCtrl == iCtrl) ? 1 : 0,
1440 szText: showtext ? PlrControlKeyName(iPlayer: Player, iControl: iCtrl, fShort: true).c_str() : nullptr);
1441 }
1442}
1443
1444extern int32_t DrawMessageOffset;
1445
1446void C4Viewport::DrawPlayerStartup(C4FacetEx &cgo)
1447{
1448 C4Player *pPlr;
1449 if (!(pPlr = Game.Players.Get(iPlayer: Player))) return;
1450 if (!pPlr->LocalControl || !pPlr->ShowStartup) return;
1451 int32_t iNameHgtOff = 0;
1452
1453 // Control
1454 if (pPlr->MouseControl)
1455 GfxR->fctMouse.Draw(sfcTarget: cgo.Surface,
1456 iX: cgo.X + (cgo.Wdt - GfxR->fctKeyboard.Wdt) / 2 + 55,
1457 iY: cgo.Y + cgo.Hgt * 2 / 3 - 10 + DrawMessageOffset,
1458 iPhaseX: 0, iPhaseY: 0);
1459 if (Inside<int32_t>(ival: pPlr->Control, lbound: C4P_Control_Keyboard1, rbound: C4P_Control_Keyboard4))
1460 {
1461 GfxR->fctKeyboard.Draw(sfcTarget: cgo.Surface,
1462 iX: cgo.X + (cgo.Wdt - GfxR->fctKeyboard.Wdt) / 2,
1463 iY: cgo.Y + cgo.Hgt * 2 / 3 + DrawMessageOffset,
1464 iPhaseX: pPlr->Control - C4P_Control_Keyboard1, iPhaseY: 0);
1465 iNameHgtOff = GfxR->fctKeyboard.Hgt;
1466 }
1467 else if (Inside<int32_t>(ival: pPlr->Control, lbound: C4P_Control_GamePad1, rbound: C4P_Control_GamePad4))
1468 {
1469 GfxR->fctGamepad.Draw(sfcTarget: cgo.Surface,
1470 iX: cgo.X + (cgo.Wdt - GfxR->fctKeyboard.Wdt) / 2,
1471 iY: cgo.Y + cgo.Hgt * 2 / 3 + DrawMessageOffset,
1472 iPhaseX: pPlr->Control - C4P_Control_GamePad1, iPhaseY: 0);
1473 iNameHgtOff = GfxR->fctGamepad.Hgt;
1474 }
1475
1476 // Name
1477 Application.DDraw->TextOut(szText: pPlr->GetName(), rFont&: Game.GraphicsResource.FontRegular, fZoom: 1.0, sfcDest: cgo.Surface,
1478 iTx: cgo.X + cgo.Wdt / 2, iTy: cgo.Y + cgo.Hgt * 2 / 3 + iNameHgtOff + DrawMessageOffset,
1479 dwFCol: pPlr->ColorDw | 0xff000000, byForm: ACenter);
1480}
1481
1482void C4Viewport::SetOutputSize(int32_t iDrawX, int32_t iDrawY, int32_t iOutX, int32_t iOutY, int32_t iOutWdt, int32_t iOutHgt)
1483{
1484 // update view position: Remain centered at previous position
1485 ViewX += (ViewWdt - iOutWdt) / 2;
1486 ViewY += (ViewHgt - iOutHgt) / 2;
1487 // update output parameters
1488 DrawX = iDrawX; DrawY = iDrawY;
1489 OutX = iOutX; OutY = iOutY;
1490 ViewWdt = iOutWdt; ViewHgt = iOutHgt;
1491 UpdateViewPosition();
1492 // Reset menus
1493 ResetMenuPositions = true;
1494 // player uses mouse control? then clip the cursor
1495 C4Player *pPlr;
1496 if (pPlr = Game.Players.Get(iPlayer: Player))
1497 if (pPlr->MouseControl)
1498 {
1499 Game.MouseControl.UpdateClip();
1500 // and inform GUI
1501 if (Game.pGUI)
1502 Game.pGUI->SetPreferredDlgRect(C4Rect(iOutX, iOutY, iOutWdt, iOutHgt));
1503 }
1504}
1505
1506void C4Viewport::ClearPointers(C4Object *pObj)
1507{
1508 Regions.ClearPointers(pObj);
1509}
1510
1511void C4Viewport::DrawMouseButtons(C4FacetEx &cgo)
1512{
1513 C4Facet ccgo;
1514 C4Region rgn;
1515 int32_t iSymbolSize = C4SymbolSize * 2 / 3;
1516 // Help
1517 ccgo.Set(nsfc: cgo.Surface, nx: cgo.X + cgo.Wdt - iSymbolSize, ny: cgo.Y + C4SymbolSize + 2 * C4SymbolBorder, nwdt: iSymbolSize, nhgt: iSymbolSize);
1518 GfxR->fctKey.Draw(cgo&: ccgo);
1519 GfxR->fctOKCancel.Draw(cgo&: ccgo, fAspect: true, iPhaseX: 0, iPhaseY: 1);
1520 if (SetRegions) { rgn.Default(); rgn.Set(fctArea&: ccgo, szCaption: LoadResStr(id: C4ResStrTableKey::IDS_CON_HELP)); rgn.Com = COM_Help; SetRegions->Add(rRegion&: rgn); }
1521 // Player menu
1522 ccgo.Y += iSymbolSize;
1523 DrawCommandKey(cgo&: ccgo, iCom: COM_PlayerMenu, fPressed: false, szText: PlrControlKeyName(iPlayer: Player, iControl: Com2Control(iCom: COM_PlayerMenu), fShort: true).c_str());
1524 if (SetRegions) { rgn.Default(); rgn.Set(fctArea&: ccgo, szCaption: LoadResStr(id: C4ResStrTableKey::IDS_CON_PLAYERMENU)); rgn.Com = COM_PlayerMenu; SetRegions->Add(rRegion&: rgn); }
1525 // Chat
1526 if (C4ChatDlg::IsChatActive())
1527 {
1528 ccgo.Y += iSymbolSize;
1529 GfxR->fctKey.Draw(cgo&: ccgo);
1530 C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Ex_Chat).Draw(cgo&: ccgo, fAspect: true);
1531 if (SetRegions) { rgn.Default(); rgn.Set(fctArea&: ccgo, szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_CHAT)); rgn.Com = COM_Chat; SetRegions->Add(rRegion&: rgn); }
1532 }
1533}
1534
1535void C4Viewport::NextPlayer()
1536{
1537 C4Player *pPlr; int32_t iPlr;
1538 if (!(pPlr = Game.Players.Get(iPlayer: Player)))
1539 {
1540 if (!(pPlr = Game.Players.First)) return;
1541 }
1542 else if (!(pPlr = pPlr->Next))
1543 if (Game.C4S.Head.Film && Game.C4S.Head.Replay)
1544 pPlr = Game.Players.First; // cycle to first in film mode only; in network obs mode allow NO_OWNER-view
1545 if (pPlr) iPlr = pPlr->Number; else iPlr = NO_OWNER;
1546 if (iPlr != Player) Init(iPlayer: iPlr, fSetTempOnly: true);
1547}
1548
1549bool C4Viewport::IsViewportMenu(class C4Menu *pMenu)
1550{
1551 // check all associated menus
1552 // Get player
1553 C4Player *pPlr = Game.Players.Get(iPlayer: Player);
1554 // Player eliminated: No menu
1555 if (pPlr && pPlr->Eliminated) return false;
1556 // Player cursor object menu
1557 if (pPlr && pPlr->Cursor && pPlr->Cursor->Menu == pMenu) return true;
1558 // Player menu
1559 if (pPlr && pPlr->Menu.IsActive() && &(pPlr->Menu) == pMenu) return true;
1560 // Fullscreen menu (if active, only one viewport can exist)
1561 if (FullScreen.pMenu && FullScreen.pMenu->IsActive() && FullScreen.pMenu == pMenu) return true;
1562 // no match
1563 return false;
1564}
1565