1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Mouse input */
19 
20 #include "C4Include.h"
21 #include "gui/C4MouseControl.h"
22 
23 #include "control/C4GameControl.h"
24 #include "game/C4Application.h"
25 #include "game/C4FullScreen.h"
26 #include "game/C4Viewport.h"
27 #include "graphics/C4Draw.h"
28 #include "graphics/C4GraphicsResource.h"
29 #include "gui/C4ChatDlg.h"
30 #include "gui/C4Gui.h"
31 #include "gui/C4ScriptGuiWindow.h"
32 #include "landscape/C4Landscape.h"
33 #include "lib/StdMesh.h"
34 #include "object/C4Def.h"
35 #include "object/C4Object.h"
36 #include "player/C4Player.h"
37 #include "player/C4PlayerList.h"
38 
39 const int32_t C4MC_Drag_None            = 0,
40               C4MC_Drag_Script          = 6,
41               C4MC_Drag_Unhandled       = 7;
42 
43 const int32_t C4MC_Tooltip_Delay = 20;
44 
C4MouseControl()45 C4MouseControl::C4MouseControl()
46 {
47 	Default();
48 }
49 
~C4MouseControl()50 C4MouseControl::~C4MouseControl()
51 {
52 	Clear();
53 }
54 
Default()55 void C4MouseControl::Default()
56 {
57 	Active=false;
58 	Player=NO_OWNER;
59 	pPlayer=nullptr;
60 	Viewport=nullptr;
61 	Cursor=0;
62 	Caption.Clear();
63 	CaptionBottomY=0;
64 	VpX=VpY=0;
65 	DownX=DownY=0;
66 	ViewX=ViewY=0;
67 	GuiX=GuiY=GameX=GameY=0;
68 	LeftButtonDown=RightButtonDown=false;
69 	LeftDoubleIgnoreUp=false;
70 	ButtonDownOnSelection=false;
71 	Visible=true;
72 	InitCentered=false;
73 	FogOfWar=false;
74 	DragID=C4ID::None;
75 	DragObject=nullptr;
76 	KeepCaption=0;
77 	Drag=C4MC_Drag_None;
78 	Selection.Default();
79 	TargetObject=DownTarget=nullptr;
80 	ControlDown=false;
81 	ShiftDown=false;
82 	AltDown=false;
83 	Scrolling=false;
84 	ScrollSpeed=10;
85 	DragImageDef=nullptr;
86 	DragImageObject=nullptr;
87 	fMouseOwned = true; // default mouse owned
88 	fctViewport.Default();
89 }
90 
Clear()91 void C4MouseControl::Clear()
92 {
93 	Active = false;
94 	Selection.Clear();
95 	UpdateClip(); // reset mouse clipping!
96 }
97 
Execute()98 void C4MouseControl::Execute()
99 {
100 
101 	if (!Active || !fMouseOwned) return;
102 
103 	// Scrolling/continuous update
104 	if (Scrolling || !::Game.iTick5)
105 	{
106 		WORD wKeyState=0;
107 		if (ControlDown) wKeyState|=MK_CONTROL;
108 		if (ShiftDown) wKeyState|=MK_SHIFT;
109 		if (AltDown) wKeyState|=MK_ALT;
110 		Move(C4MC_Button_None, VpX, VpY, wKeyState);
111 	}
112 }
113 
Init(int32_t iPlayer)114 bool C4MouseControl::Init(int32_t iPlayer)
115 {
116 	Clear();
117 	Default();
118 	Active = true;
119 	Player = iPlayer;
120 	InitCentered = false;
121 	UpdateClip();
122 	return true;
123 }
124 
ClearPointers(C4Object * pObj)125 void C4MouseControl::ClearPointers(C4Object *pObj)
126 {
127 	if (TargetObject==pObj) TargetObject=nullptr;
128 	if (DownTarget==pObj) DownTarget=nullptr;
129 	if (DragObject==pObj)
130 	{
131 		DragObject=nullptr;
132 		Drag=C4MC_Drag_None;
133 		DragImageDef=nullptr;
134 		DragImageObject=nullptr;
135 	}
136 	Selection.ClearPointers(pObj);
137 }
138 
IsViewport(C4Viewport * pViewport)139 bool C4MouseControl::IsViewport(C4Viewport *pViewport)
140 {
141 	return (Viewport==pViewport);
142 }
143 
UpdateClip()144 void C4MouseControl::UpdateClip()
145 {
146 #ifdef _DEBUG
147 	// never in debug
148 	return;
149 #endif
150 #ifdef USE_WIN32_WINDOWS
151 	// fullscreen only
152 	if (Application.isEditor) return;
153 	// application or mouse control not active? remove any clips
154 	if (!Active || !Application.Active || ::pGUI->HasMouseFocus()) { ClipCursor(nullptr); return; }
155 	// get controlled viewport
156 	C4Viewport *pVP=::Viewports.GetViewport(Player);
157 	if (!pVP) { ClipCursor(nullptr); return; }
158 	// adjust size by viewport size
159 	RECT vpRct;
160 	vpRct.left=pVP->OutX; vpRct.top=pVP->OutY; vpRct.right=pVP->OutX+pVP->ViewWdt; vpRct.bottom=pVP->OutY+pVP->ViewHgt;
161 	// adjust by window pos
162 	RECT rtWindow;
163 	if (GetWindowRect(FullScreen.hWindow, &rtWindow))
164 	{
165 		vpRct.left += rtWindow.left; vpRct.top += rtWindow.top;
166 		vpRct.right += rtWindow.left; vpRct.bottom+= rtWindow.top;
167 	}
168 	ClipCursor(&vpRct);
169 	// and inform GUI
170 	::pGUI->SetPreferredDlgRect(C4Rect(pVP->OutX, pVP->OutY, pVP->ViewWdt, pVP->ViewHgt));
171 #endif
172 	//StdWindow manages this.
173 }
174 
Move(int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyFlags,bool fCenter)175 void C4MouseControl::Move(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyFlags, bool fCenter)
176 {
177 	// Control state
178 	ControlDown=false; if (dwKeyFlags & MK_CONTROL) ControlDown=true;
179 	ShiftDown=false; if (dwKeyFlags & MK_SHIFT) ShiftDown=true;
180 	AltDown=false; if(dwKeyFlags & MK_ALT) AltDown=true;
181 	// Active
182 	if (!Active || !fMouseOwned) return;
183 	// Execute caption
184 	if (KeepCaption) KeepCaption--; else { Caption.Clear(); CaptionBottomY=0; }
185 	// Check player
186 	if (Player>NO_OWNER)
187 	{
188 		pPlayer=::Players.Get(Player);
189 		if (!pPlayer) { Active=false; return; }
190 	}
191 	else
192 		pPlayer = nullptr;
193 	// Check viewport
194 	if (!(Viewport=::Viewports.GetViewport(Player))) return;
195 	// get view position
196 	C4Rect rcViewport = Viewport->GetOutputRect();
197 	fctViewport.Set(nullptr, rcViewport.x, rcViewport.y, rcViewport.Wdt, rcViewport.Hgt);
198 	ViewX=Viewport->GetViewX(); ViewY=Viewport->GetViewY();
199 	fctViewportGame = Viewport->last_game_draw_cgo;
200 	fctViewportGUI = Viewport->last_gui_draw_cgo;
201 	// First time viewport attachment: center mouse
202 #ifdef USE_WIN32_WINDOWS
203 	if (!InitCentered || fCenter)
204 	{
205 		iX = Viewport->ViewWdt/2;
206 		iY = Viewport->ViewHgt/2;
207 		if (!Application.isEditor)
208 		{
209 			int32_t iMidX = Viewport->OutX + iX;
210 			int32_t iMidY = Viewport->OutY + iY;
211 			RECT rtWindow;
212 			if (GetWindowRect(Application.pWindow->hWindow, &rtWindow))
213 			{
214 				iMidX += rtWindow.left; iMidY += rtWindow.top;
215 			}
216 			SetCursorPos(iMidX, iMidY);
217 		}
218 		InitCentered = true;
219 	}
220 #else
221 	if (!InitCentered || fCenter)
222 	{
223 		iX = Viewport->ViewWdt/2;
224 		iY = Viewport->ViewHgt/2;
225 		InitCentered = true;
226 	}
227 #endif
228 	// passive mode: scrolling and player buttons only
229 	if (IsPassive())
230 	{
231 		if (iButton != C4MC_Button_Wheel)
232 		{
233 			VpX=iX; VpY=iY;
234 			GameX=ViewX+VpX/Viewport->Zoom; GameY=ViewY+VpY/Viewport->Zoom;
235 			GuiX=float(VpX)/Viewport->GetGUIZoom(); GuiY=float(VpY)/Viewport->GetGUIZoom();
236 		}
237 		UpdateScrolling();
238 		if (iButton == C4MC_Button_LeftDown) LeftDown();
239 		else if (iButton == C4MC_Button_LeftUp) LeftUp();
240 		else UpdateCursorTarget();
241 		return;
242 	}
243 
244 	if (iButton != C4MC_Button_Wheel)
245 	{
246 		// Position
247 		VpX=iX; VpY=iY;
248 		GameX=ViewX+VpX/Viewport->Zoom; GameY=ViewY+VpY/Viewport->Zoom;
249 		GuiX=float(VpX)/Viewport->GetGUIZoom(); GuiY=float(VpY)/Viewport->GetGUIZoom();
250 		// Scrolling
251 		UpdateScrolling();
252 		// Fog of war
253 		UpdateFogOfWar();
254 
255 		// Blocked by fog of war: evaluate button up, dragging and region controls only
256 		if (FogOfWar && Drag == C4MC_Drag_None)
257 		{
258 			// Left button up
259 			if (iButton==C4MC_Button_LeftUp)
260 			{
261 				LeftButtonDown=false;
262 				// End any drag
263 				Drag=C4MC_Drag_None;
264 			}
265 			// Right button up
266 			if (iButton==C4MC_Button_RightUp)
267 			{
268 				RightButtonDown=false;
269 			}
270 		}
271 	}
272 
273 	// Move execution by button/drag status
274 	switch (iButton)
275 	{
276 		//------------------------------------------------------------------------------------------
277 	case C4MC_Button_None:
278 		switch (Drag)
279 		{
280 		case C4MC_Drag_Unhandled: break; // nothing to do
281 		case C4MC_Drag_None: DragNone(); break;
282 		case C4MC_Drag_Script: DragScript(); break;
283 		}
284 		break;
285 		//------------------------------------------------------------------------------------------
286 	case C4MC_Button_LeftDown: LeftDown(); break;
287 		//------------------------------------------------------------------------------------------
288 	case C4MC_Button_LeftUp: LeftUp(); break;
289 		//------------------------------------------------------------------------------------------
290 	case C4MC_Button_LeftDouble: LeftDouble(); break;
291 		//------------------------------------------------------------------------------------------
292 	case C4MC_Button_RightDown: RightDown(); break;
293 		//------------------------------------------------------------------------------------------
294 	case C4MC_Button_RightUp: RightUp(); break;
295 		//------------------------------------------------------------------------------------------
296 	case C4MC_Button_Wheel: Wheel(dwKeyFlags); break;
297 	}
298 
299 	// are custom menus active?
300 	bool menuProcessed = false;
301 	if (pPlayer)
302 		// adjust by viewport X/Y because the GUI windows calculate their positions (and thus check input) based on that
303 		menuProcessed = ::Game.ScriptGuiRoot->MouseInput(iButton, iX, iY, dwKeyFlags);
304 
305 	if (menuProcessed)
306 		Cursor = C4MC_Cursor_Select;
307 
308 	// if not caught by a menu
309 	if (!menuProcessed)
310 		// script handling of mouse control for everything but regular movement (which is sent at control frame intervals only)
311 		if (iButton != C4MC_Button_None)
312 			// not if blocked by selection object
313 			if (!TargetObject)
314 				// safety (can't really happen in !IsPassive, but w/e
315 				if (pPlayer && pPlayer->ControlSet)
316 					if (!menuProcessed && pPlayer->ControlSet->IsMouseControlAssigned(iButton))
317 						pPlayer->Control.DoMouseInput(0 /* only 1 mouse supported so far */, iButton, GameX, GameY, GuiX, GuiY, dwKeyFlags);
318 }
319 
DoMoveInput()320 void C4MouseControl::DoMoveInput()
321 {
322 	// current mouse move to input queue
323 	// do sanity checks
324 	if (!Active || !fMouseOwned) return;
325 	if (!(pPlayer=::Players.Get(Player))) return;
326 	if (!pPlayer->ControlSet) return;
327 	if (!pPlayer->ControlSet->IsMouseControlAssigned(C4MC_Button_None)) return;
328 	pPlayer->Control.DoMouseInput(0 /* only 1 mouse supported so far */, C4MC_Button_None, GameX, GameY, GuiX, GuiY, (ControlDown && MK_CONTROL) | (ShiftDown && MK_SHIFT) | (AltDown && MK_ALT));
329 }
330 
Draw(C4TargetFacet & cgo,const ZoomData & GameZoom)331 void C4MouseControl::Draw(C4TargetFacet &cgo, const ZoomData &GameZoom)
332 {
333 	int32_t iOffsetX,iOffsetY;
334 	float wdt = GfxR->fctMouseCursor.Wdt, hgt = GfxR->fctMouseCursor.Hgt;
335 	// Cursor size relative to height - does not matter with current square graphics.
336 	float zoom = Config.Graphics.MouseCursorSize / hgt;
337 	hgt *= zoom;
338 	wdt *= zoom;
339 
340 	ZoomData GuiZoom;
341 	pDraw->GetZoom(&GuiZoom);
342 
343 	// Hidden
344 	if (!Visible || !fMouseOwned) return;
345 
346 	// Draw selection
347 	if (!IsPassive())
348 	{
349 		Selection.DrawSelectMark(cgo);
350 	}
351 
352 	// Draw control
353 	switch (Drag)
354 	{
355 		//------------------------------------------------------------------------------------------
356 	case C4MC_Drag_None: case C4MC_Drag_Script: case C4MC_Drag_Unhandled:
357 		// Hotspot offset: Usually, hotspot is in center
358 		iOffsetX = wdt/2;
359 		iOffsetY = hgt/2;
360 		// calculate the hotspot for the scrolling cursors
361 		switch (Cursor)
362 		{
363 		case C4MC_Cursor_Up: iOffsetY += -hgt/2; break;
364 		case C4MC_Cursor_Down:iOffsetY += +hgt/2; break;
365 		case C4MC_Cursor_Left: iOffsetX += -wdt/2; break;
366 		case C4MC_Cursor_Right: iOffsetX += +wdt/2; break;
367 		case C4MC_Cursor_UpLeft: iOffsetX += -wdt/2; iOffsetY += -hgt/2; break;
368 		case C4MC_Cursor_UpRight: iOffsetX += +wdt/2; iOffsetY += -hgt/2; break;
369 		case C4MC_Cursor_DownLeft: iOffsetX += -wdt/2; iOffsetY += +hgt/2; break;
370 		case C4MC_Cursor_DownRight: iOffsetX += +wdt/2; iOffsetY += +hgt/2; break;
371 		}
372 		// Drag image
373 		if (DragImageObject || DragImageDef)
374 		{
375 			C4DefGraphics* pGfx;
376 			if(DragImageObject)
377 				pGfx = DragImageObject->GetGraphics();
378 			else
379 				pGfx = &DragImageDef->Graphics;
380 
381 			// Determine image boundaries
382 			float ImageWdt;
383 			float ImageHgt;
384 			if (pGfx->Type == C4DefGraphics::TYPE_Bitmap)
385 			{
386 				C4Def* Def = (DragImageObject ? DragImageObject->Def : DragImageDef);
387 				ImageWdt = Def->PictureRect.Wdt;
388 				ImageHgt = Def->PictureRect.Hgt;
389 			}
390 			else if (pGfx->Type == C4DefGraphics::TYPE_Mesh)
391 			{
392 				// Note bounding box is in OGRE coordinate system
393 				ImageWdt = pGfx->Mesh->GetBoundingBox().y2 - pGfx->Mesh->GetBoundingBox().y1;
394 				ImageHgt = pGfx->Mesh->GetBoundingBox().z2 - pGfx->Mesh->GetBoundingBox().z1;
395 			}
396 			else
397 			{
398 				ImageWdt = ImageHgt = 1.0f;
399 			}
400 
401 			// zoom mode: Drag in GUI or Game depending on source object
402 			bool fIsGameZoom = true;
403 			if (Drag == C4MC_Drag_Script && DragObject && (DragObject->Category & C4D_Foreground))
404 				fIsGameZoom = false;
405 			// drag image in game zoom
406 			float XDraw, YDraw, ZoomDraw;
407 			if (fIsGameZoom)
408 			{
409 				pDraw->SetZoom(GameZoom);
410 				XDraw = GameX; YDraw = GameY;
411 				ZoomDraw = 1.0f;
412 			}
413 			else
414 			{
415 				ZoomDraw = std::min(64.0f / ImageWdt, 64.0f / ImageHgt);
416 				XDraw = GuiX; YDraw = GuiY;
417 			}
418 
419 			iOffsetX=int(ZoomDraw*ImageWdt/2);
420 			iOffsetY=int(ZoomDraw*ImageHgt/2);
421 
422 			C4TargetFacet ccgo;
423 			ccgo.Set(cgo.Surface, XDraw + cgo.X - iOffsetX, YDraw + cgo.Y - iOffsetY, float(ImageWdt)*ZoomDraw, float(ImageHgt)*ZoomDraw);
424 
425 			if (DragImageObject)
426 			{
427 				uint32_t ColorMod = DragImageObject->ColorMod;
428 				uint32_t BlitMode = DragImageObject->BlitMode;
429 				DragImageObject->ColorMod = (Drag == C4MC_Drag_Script) ? 0x7fffffff : (/*DragImagePhase*/false ? 0x8f7f0000 : 0x1f007f00);
430 				DragImageObject->BlitMode = C4GFXBLIT_MOD2;
431 
432 				DragImageObject->DrawPicture(ccgo, false, nullptr);
433 
434 				DragImageObject->ColorMod = ColorMod;
435 				DragImageObject->BlitMode = BlitMode;
436 			}
437 			else
438 			{
439 				// draw in special modulation mode
440 				pDraw->SetBlitMode(C4GFXBLIT_MOD2);
441 				// draw DragImage in red or green, according to the phase to be used
442 				pDraw->ActivateBlitModulation((Drag == C4MC_Drag_Script) ? 0x7fffffff : (/*DragImagePhase*/false ? 0x8f7f0000 : 0x1f007f00));
443 
444 				DragImageDef->Draw(ccgo, false, pPlayer ? pPlayer->ColorDw : 0xff0000ff, nullptr, 0, 0, nullptr);
445 
446 				// reset color
447 				pDraw->DeactivateBlitModulation();
448 				pDraw->SetBlitMode(0);
449 			}
450 
451 			if (fIsGameZoom) pDraw->SetZoom(GuiZoom);
452 			// reset cursor hotspot offset for script drawing
453 			iOffsetX = wdt/2;
454 			iOffsetY = hgt/2;
455 		}
456 		// Cursor
457 		if ( (!DragImageDef && !DragImageObject) || (Drag == C4MC_Drag_Script))
458 		{
459 			GfxR->fctMouseCursor.DrawX(cgo.Surface, cgo.X+GuiX-iOffsetX, cgo.Y+GuiY-iOffsetY, wdt, hgt,  Cursor);
460 		}
461 		break;
462 		//------------------------------------------------------------------------------------------
463 	}
464 
465 	// Draw caption
466 	if (Caption && ::pGUI)
467 	{
468 		C4TargetFacet cgoTip;
469 		cgoTip = static_cast<const C4Facet &>(cgo);
470 		C4GUI::Screen::DrawToolTip(Caption.getData(), cgoTip, cgo.X+GuiX, cgo.Y+GuiY);
471 	}
472 
473 }
474 
UpdateCursorTarget()475 void C4MouseControl::UpdateCursorTarget()
476 {
477 	C4Object* OldTargetObject = TargetObject;
478 
479 	if (Scrolling)
480 	{
481 		// Scrolling: no other target
482 		TargetObject=nullptr;
483 	}
484 	else
485 	{
486 		// Target object
487 		TargetObject=GetTargetObject();
488 		if (TargetObject && FogOfWar && !(TargetObject->Category & C4D_IgnoreFoW)) TargetObject = nullptr;
489 
490 		// Movement
491 		if (!FogOfWar && !IsPassive()) Cursor=C4MC_Cursor_Crosshair;
492 
493 		// Target action
494 		if (TargetObject && !IsPassive())
495 		{
496 			// default cursor for object; also set if not in FoW
497 			Cursor=C4MC_Cursor_Crosshair;
498 
499 			// select custom region. Can select an object if it does not have the MD_NoClick
500 			// flag set. If we are currently dragging then selection depends on it being a drop target.
501 			bool CanSelect;
502 			if(Drag == C4MC_Drag_Script)
503 				CanSelect = (TargetObject->GetPropertyInt(P_MouseDrag) & C4MC_MD_DropTarget) != 0;
504 			else
505 				CanSelect = (TargetObject->GetPropertyInt(P_MouseDrag) & C4MC_MD_NoClick) == 0;
506 
507 			if ( (TargetObject->Category & C4D_MouseSelect) && CanSelect)
508 				Cursor=C4MC_Cursor_Select;
509 			else
510 				TargetObject = nullptr;
511 		}
512 
513 		// passive cursor
514 		if (IsPassive())
515 			Cursor=C4MC_Cursor_Passive;
516 
517 		// update tooltip information
518 		if (OldTargetObject != TargetObject)
519 		{
520 			C4String *newTooltip = nullptr;
521 			if (TargetObject && (Cursor == C4MC_Cursor_Select) && (TargetObject->Category & C4D_MouseSelect) && (newTooltip = TargetObject->GetPropertyStr(P_Tooltip)))
522 			{
523 				float objX, objY;
524 				TargetObject->GetViewPos(objX, objY, -fctViewportGUI.X, -fctViewportGUI.Y, fctViewportGUI);
525 				objX += TargetObject->Shape.x;
526 				objY += TargetObject->Shape.y - TargetObject->addtop();
527 				SetTooltipRectangle(C4Rect(objX, objY, TargetObject->Shape.Wdt, TargetObject->Shape.Hgt + TargetObject->addtop()));
528 				SetTooltipText(StdStrBuf(newTooltip->GetCStr()));
529 			}
530 			else
531 			{
532 				SetTooltipRectangle(C4Rect(0, 0, 0, 0));
533 			}
534 		}
535 
536 		if (!KeepCaption
537 			&& ToolTipRectangle.Wdt != 0
538 			&& Inside(GuiX, ToolTipRectangle.x, ToolTipRectangle.x + ToolTipRectangle.Wdt)
539 			&& Inside(GuiY, ToolTipRectangle.y, ToolTipRectangle.y + ToolTipRectangle.Hgt))
540 		{
541 			++TimeInTooltipRectangle;
542 
543 			if (TimeInTooltipRectangle >= C4MC_Tooltip_Delay)
544 			{
545 				Caption = TooltipText;
546 			}
547 		}
548 		else
549 		{
550 			// disable tooltip pop-up; whatever set it in the first place will set it again on the next mouse-enter
551 			TimeInTooltipRectangle = 0;
552 			ToolTipRectangle.Wdt = 0;
553 		}
554 	}
555 
556 	// Make a script callback if the object being hovered changes
557 	if(!IsPassive() && OldTargetObject != TargetObject)
558 	{
559 		// TODO: This might put a heavy load on the network, depending on the number of
560 		// selectable objects around. If it turns out to be a problem we might want to
561 		// deduce these hover callbacks client-side instead.
562 		// Or, make sure to send this at most once per control frame.
563 		Game.Input.Add(CID_PlrMouseMove, C4ControlPlayerMouse::Hover(::Players.Get(Player), TargetObject, OldTargetObject, DragObject));
564 	}
565 }
566 
UpdateSingleSelection()567 int32_t C4MouseControl::UpdateSingleSelection()
568 {
569 	// Set single selection if cursor on selection object (clear prior object selection)
570 	if (TargetObject && (Cursor==C4MC_Cursor_Select))
571 		{ Selection.Clear(); Selection.Add(TargetObject, C4ObjectList::stNone); }
572 
573 	// Cursor has moved off single object (or target object) selection: clear selection
574 	else if (Selection.GetObject())
575 		if (::Players.Get(Player)->ObjectInCrew(Selection.GetObject())
576 		    || (Selection.GetObject()->Category & C4D_MouseSelect))
577 			Selection.Clear();
578 
579 	return Selection.ObjectCount();
580 }
581 
UpdateScrolling()582 void C4MouseControl::UpdateScrolling()
583 {
584 	// Assume no scrolling
585 	Scrolling=false;
586 	// No scrolling if disabled by player
587 	if (pPlayer) if (pPlayer->IsViewLocked()) return;
588 	// Scrolling on border
589 	if (VpX==0)
590 		{ Cursor=C4MC_Cursor_Left; ScrollView(-ScrollSpeed/Viewport->Zoom,0,Viewport->ViewWdt/Viewport->Zoom,Viewport->ViewHgt/Viewport->Zoom); Scrolling=true; }
591 	if (VpY==0)
592 		{ Cursor=C4MC_Cursor_Up; ScrollView(0,-ScrollSpeed/Viewport->Zoom,Viewport->ViewWdt/Viewport->Zoom,Viewport->ViewHgt/Viewport->Zoom); Scrolling=true; }
593 	if (VpX==Viewport->ViewWdt-1)
594 		{ Cursor=C4MC_Cursor_Right; ScrollView(+ScrollSpeed/Viewport->Zoom,0,Viewport->ViewWdt/Viewport->Zoom,Viewport->ViewHgt/Viewport->Zoom); Scrolling=true; }
595 	if (VpY==Viewport->ViewHgt-1)
596 		{ Cursor=C4MC_Cursor_Down; ScrollView(0,+ScrollSpeed/Viewport->Zoom,Viewport->ViewWdt/Viewport->Zoom,Viewport->ViewHgt/Viewport->Zoom); Scrolling=true; }
597 	// Set correct cursor
598 	if ((VpX==0) && (VpY==0)) Cursor=C4MC_Cursor_UpLeft;
599 	if ((VpX==Viewport->ViewWdt-1) && (VpY==0)) Cursor=C4MC_Cursor_UpRight;
600 	if ((VpX==0) && (VpY==Viewport->ViewHgt-1)) Cursor=C4MC_Cursor_DownLeft;
601 	if ((VpX==Viewport->ViewWdt-1) && (VpY==Viewport->ViewHgt-1)) Cursor=C4MC_Cursor_DownRight;
602 }
603 
LeftDown()604 void C4MouseControl::LeftDown()
605 {
606 	// Set flag
607 	LeftButtonDown=true;
608 	// Store down values (same MoveRightDown -> use StoreDown)
609 	DownX=GameX; DownY=GameY;
610 	DownTarget=TargetObject;
611 }
612 
LeftUp()613 void C4MouseControl::LeftUp()
614 {
615 	// Ignore left up after double click
616 	if (LeftDoubleIgnoreUp)
617 	{
618 		LeftDoubleIgnoreUp=false;
619 	}
620 	else
621 	{
622 		// Evaluate by drag status
623 		switch (Drag)
624 		{
625 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
626 		case C4MC_Drag_Unhandled: // nobreak
627 		case C4MC_Drag_None: LeftUpDragNone(); break;
628 			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
629 		case C4MC_Drag_Script: ButtonUpDragScript(); break;
630 			// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
631 		}
632 	}
633 	// Update status flag
634 	LeftButtonDown=false;
635 	if(!RightButtonDown) DownTarget = nullptr;
636 }
637 
DragNone()638 void C4MouseControl::DragNone()
639 {
640 	// Cursor movement
641 	UpdateCursorTarget();
642 	// Update selection
643 	UpdateSingleSelection();
644 
645 	// Button down: begin drag
646 	if ( (LeftButtonDown || RightButtonDown)
647 	     && ((Abs(GameX-DownX)>C4MC_DragSensitivity) || (Abs(GameY-DownY)>C4MC_DragSensitivity)) )
648 	{
649 		bool fAllowDrag = true;
650 		// check if target object allows scripted dragging
651 		if (fAllowDrag && DownTarget && (!FogOfWar || (DownTarget->Category & C4D_IgnoreFoW)))
652 		{
653 			C4Object *drag_image_obj; C4Def * drag_image_def;
654 
655 			// Drag only if MD_SOURCE is set and drag image is present
656 			if ( (DownTarget->GetPropertyInt(P_MouseDrag) & C4MC_MD_DragSource) &&
657 			      DownTarget->GetDragImage(&drag_image_obj, &drag_image_def))
658 			{
659 				Drag=C4MC_Drag_Script;
660 
661 				if(drag_image_obj) DragImageObject = drag_image_obj;
662 				else DragImageDef = drag_image_def;
663 
664 				DragObject = DownTarget;
665 			}
666 		}
667 
668 		// dragging somewhere unhandled - mark drag process so moving over a draggable object won't start a drag
669 		if (Drag == C4MC_Drag_None)
670 		{
671 			Drag=C4MC_Drag_Unhandled;
672 		}
673 	}
674 }
675 
LeftDouble()676 void C4MouseControl::LeftDouble()
677 {
678 	// Update status flag
679 	LeftButtonDown=false;
680 	// Set ignore flag for next left up
681 	LeftDoubleIgnoreUp=true;
682 	// Evaluate left double by drag status (can only be C4MC_Drag_None really)
683 	switch (Drag)
684 	{
685 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
686 	case C4MC_Drag_None:
687 		// Double left click (might be on a target)
688 		break;
689 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
690 	}
691 }
692 
RightDown()693 void C4MouseControl::RightDown()
694 {
695 	// Update status flag
696 	RightButtonDown=true;
697 	// Store down values (same MoveLeftDown -> use StoreDown)
698 	DownX=GameX; DownY=GameY;
699 	DownTarget=TargetObject;
700 }
701 
RightUp()702 void C4MouseControl::RightUp()
703 {
704 	// Evaluate by drag status
705 	switch (Drag)
706 	{
707 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
708 	case C4MC_Drag_Unhandled: // nobreak
709 	case C4MC_Drag_None: RightUpDragNone(); break;
710 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
711 	case C4MC_Drag_Script: ButtonUpDragScript(); break;
712 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
713 	}
714 	// Update status flag
715 	RightButtonDown=false;
716 	if(!LeftButtonDown) DownTarget = nullptr;
717 }
718 
Wheel(DWORD dwFlags)719 void C4MouseControl::Wheel(DWORD dwFlags)
720 {
721 }
722 
DragScript()723 void C4MouseControl::DragScript()
724 {
725 	// script drag should update target and selection so selection highlight on drop target is visible
726 	// Cursor movement
727 	UpdateCursorTarget();
728 	// Update selection
729 	UpdateSingleSelection();
730 	Cursor=C4MC_Cursor_DragDrop;
731 }
732 
LeftUpDragNone()733 void C4MouseControl::LeftUpDragNone()
734 {
735 	// might be in Drag_Unknown
736 	Drag = C4MC_Drag_None;
737 	// Single left click (might be on a target)
738 	switch (Cursor)
739 	{
740 	case C4MC_Cursor_Select:
741 		// Object selection to control queue
742 		if (!IsPassive() && Selection.GetObject() == DownTarget)
743 			Game.Input.Add(CID_PlrSelect, new C4ControlPlayerSelect(Player,Selection,false));
744 		break;
745 	default:
746 		// done in script
747 		break;
748 	}
749 	// Clear selection
750 	Selection.Clear();
751 }
752 
ButtonUpDragScript()753 void C4MouseControl::ButtonUpDragScript()
754 {
755 	// Determine drag+drop targets
756 	UpdateCursorTarget();
757 	// Finish drag
758 	Drag=C4MC_Drag_None;
759 	DragID=C4ID::None;
760 	DragImageObject = nullptr;
761 	DragImageDef = nullptr;
762 	C4Object *DragObject = this->DragObject;
763 	this->DragObject = nullptr;
764 	C4Object *DropObject = TargetObject;
765 	// drag object must exist; drop object is optional
766 	if (!DragObject) return;
767 	if (DropObject && (~DropObject->GetPropertyInt(P_MouseDrag) & C4MC_MD_DropTarget))
768 		DropObject = nullptr;
769 	// no commands if player is eliminated or doesn't exist any more
770 	C4Player *pPlr = ::Players.Get(Player);
771 	if (!pPlr || pPlr->Eliminated) return;
772 	// todo: Perform drag/drop validity check
773 	// now drag/drop is handled by script
774 	Game.Input.Add(CID_PlrMouseMove, C4ControlPlayerMouse::DragDrop(::Players.Get(Player), DropObject, DragObject));
775 }
776 
RightUpDragNone()777 void C4MouseControl::RightUpDragNone()
778 {
779 
780 	// might be in Drag_Unknown
781 	Drag = C4MC_Drag_None;
782 
783 	// Alternative object selection
784 	if (Cursor==C4MC_Cursor_Select && !IsPassive() && Selection.GetObject() == DownTarget)
785 		{ Game.Input.Add(CID_PlrSelect, new C4ControlPlayerSelect(Player,Selection,true)); }
786 
787 	// TODO: Evaluate right click
788 
789 }
790 
UpdateFogOfWar()791 void C4MouseControl::UpdateFogOfWar()
792 {
793 	// Assume no fog of war
794 	FogOfWar=false;
795 	// Check for fog of war
796 	// TODO: Check C4FoWRegion... should maybe be passed as a parameter?
797 	// pDraw->GetFoW() might not be current at this time.
798 	if (/*(pPlayer->fFogOfWar && !pPlayer->FoWIsVisible(int32_t(GameX),int32_t(GameY))) || */GameX<0 || GameY<0 || int32_t(GameX)>=::Landscape.GetWidth() || int32_t(GameY)>=::Landscape.GetHeight())
799 	{
800 		FogOfWar=true;
801 		// allow dragging, scrolling, region selection and manipulations of objects not affected by FoW
802 		if (!Scrolling && (!TargetObject || !(TargetObject->Category & C4D_IgnoreFoW)))
803 		{
804 			Cursor=C4MC_Cursor_Passive;
805 		}
806 	}
807 }
808 
ShowCursor()809 void C4MouseControl::ShowCursor()
810 {
811 	Visible=true;
812 }
813 
HideCursor()814 void C4MouseControl::HideCursor()
815 {
816 	Visible=false;
817 }
818 
GetCaption()819 const char *C4MouseControl::GetCaption()
820 {
821 	return Caption.getData();
822 }
823 
SetTooltipRectangle(const C4Rect & rectangle)824 void C4MouseControl::SetTooltipRectangle(const C4Rect &rectangle)
825 {
826 	// Set the tooltip rectangle slightly larger than originally requested.
827 	// The tooltip will be removed when the cursor leaves the rectangle, so make sure that the tooltip is not already disabled when the cursor moves to the border-pixel of the GUI item
828 	// in case the GUI item uses a different check for bounds (< vs <=) than the tooltip rectangle.
829 	ToolTipRectangle = C4Rect(rectangle.x - 2, rectangle.y - 2, rectangle.Wdt + 4, rectangle.Hgt + 4);
830 	TimeInTooltipRectangle = 0;
831 }
832 
SetTooltipText(const StdStrBuf & text)833 void C4MouseControl::SetTooltipText(const StdStrBuf &text)
834 {
835 	TooltipText = text;
836 }
837 
GetTargetObject()838 C4Object *C4MouseControl::GetTargetObject()
839 {
840 	// find object
841 	// gui object position currently wrong...will fall apart once GUIZoom is activated
842 	C4Object *pObj = Game.FindVisObject(ViewX, ViewY, Player, fctViewportGame, fctViewportGUI, GameX,GameY, C4D_MouseSelect, GuiX-fctViewportGUI.X, GuiY-fctViewportGUI.Y);
843 	if (!pObj) return nullptr;
844 	return pObj;
845 }
846 
IsPassive()847 bool C4MouseControl::IsPassive()
848 {
849 	return ::Control.isReplay() || Player<=NO_OWNER;
850 }
851 
ScrollView(float iX,float iY,float ViewWdt,float ViewHgt)852 void C4MouseControl::ScrollView(float iX, float iY, float ViewWdt, float ViewHgt)
853 {
854 	// player assigned: scroll player view
855 	if (pPlayer)
856 		pPlayer->ScrollView(iX, iY, ViewWdt, ViewHgt);
857 	else if (Viewport)
858 	{
859 		// no player: Scroll fullscreen viewport
860 		Viewport->ScrollView(iX, iY);
861 	}
862 
863 }
864 
IsDragging()865 bool C4MouseControl::IsDragging()
866 {
867 	return Active && Drag == C4MC_Drag_Script;
868 }
869 
GetLastCursorPos(int32_t * x_out_gui,int32_t * y_out_gui,int32_t * x_out_game,int32_t * y_out_game) const870 bool C4MouseControl::GetLastCursorPos(int32_t *x_out_gui, int32_t *y_out_gui, int32_t *x_out_game, int32_t *y_out_game) const
871 {
872 	// safety
873 	if (!Active || !fMouseOwned) return false;
874 	// OK; assign last known pos
875 	*x_out_gui = GuiX; *y_out_gui = GuiY;
876 	*x_out_game = GameX; *y_out_game = GameY;
877 	return true;
878 }
879