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