Skip to content

Commit a1589b8

Browse files
committed
Merge branch 'fixes_issue_2697_clickthrublocker' into develop
2 parents 0aa6465 + 02cc9cd commit a1589b8

14 files changed

+191
-40
lines changed

CONTRIBUTING.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,23 @@ Setting Up Your Environment
213213
* Click on the resulting link to go to that class's documentation page.
214214
* The Unity documentation for that class will start with some faint grey text at the top of the page that says, "Implemented in:", which tells you which DLL you need to reference to get code using that class to compile properly.
215215

216-
3. If you do not have a copy of KSP locally, you may
216+
3. Make sure your installation of KSP has LinuxGuruGamer's ClivkThroughBlocker
217+
mod installed. kOS now needs it in order to compile. After it is
218+
installed, copy its DLL file into Resources/ from your GameData folder.
219+
220+
Copy This file:
221+
222+
* $KSP/GameData/00_ClickThroughBlocker/Plugins/ClickThroughBlocker.dll
223+
224+
To here:
225+
226+
* Resources/
227+
228+
Then *make a Reference to Resoruces/ClickThroughBlocker.dll in your kOS project
229+
file*. This is needed for it to let you compile parts of kOS that use classes
230+
from ClickThroughBlocker.
231+
232+
4. If you do not have a copy of KSP locally, you may
217233
download dummy assemblies at https://github.com/KSP-KOS/KSP_LIB
218234

219235
3. Make sure you are targeting this version of .Net: ".Net 4.0 Framework".

src/kOS/Module/kOSCustomParameters.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using KSP.IO;
22
using System;
33
using System.Reflection;
4+
using System.Linq;
45

56
namespace kOS.Module
67
{
@@ -46,6 +47,9 @@ public static kOSCustomParameters Instance
4647
[GameParameters.CustomIntParameterUI("")]
4748
public int version = 0;
4849

50+
[GameParameters.CustomParameterUI("")]
51+
public bool passedClickThroughCheck = false;
52+
4953
// these values constrain and back the InstructionsPerUpdate property so that it is clamped both in the
5054
// user interface and when set from within a script.
5155
private const int ipuMin = 50;
@@ -202,6 +206,95 @@ public void CheckMigrateSettings()
202206
}
203207
}
204208

209+
public void CheckClickThroughBlockerExists()
210+
{
211+
if (passedClickThroughCheck)
212+
return;
213+
bool clickThroughExists = false;
214+
215+
var loadedCTBAssembly = AssemblyLoader.loadedAssemblies.FirstOrDefault(a => a.dllName.Equals("ClickThroughBlocker"));
216+
if (loadedCTBAssembly != null)
217+
{
218+
// Must be at least version 0.10 of ClickThroughBlocker:
219+
if (loadedCTBAssembly.versionMajor > 0 || loadedCTBAssembly.versionMinor >= 10)
220+
{
221+
Type ctbType = loadedCTBAssembly.assembly.GetType("ClickThroughFix.CTB", false);
222+
if (ctbType != null)
223+
{
224+
if (ctbType.GetField("focusFollowsclick") != null)
225+
{
226+
clickThroughExists = true;
227+
}
228+
}
229+
}
230+
}
231+
232+
string popupText =
233+
"=======================================\n" +
234+
"<b><color=#ffffff>kOS is Checking for ClickThroughBlocker</color></b>\n" +
235+
"=======================================\n\n" +
236+
"Starting with kOS v1.3, kOS has become dependent on the existence of the ClickThroughBlocker mod. " +
237+
"(And it must be at least version 0.10 of ClickThroughBlocker.)\n\n";
238+
239+
if (clickThroughExists)
240+
{
241+
popupText +=
242+
" <b><color=#ddffdd><<<< CHECK SUCCEEDED >>>>></color></b>\n\n" +
243+
"kOS has found ClickThroughBlocker installed, and it appears to be a version that will work with kOS.\n" +
244+
"\n" +
245+
"Please note that while in the past the kOS terminal has always been click-to-focus, from now " +
246+
"on it will behave however ClickThroughBlocker is set to act, which may be focus-follows-mouse.\n" +
247+
"You can use ClickThroughBlocker's settings to change this behvior like this:\n\n" +
248+
"[Hit Escape] Settings ->\n" +
249+
" Difficulty Options ->\n" +
250+
" ClickThroughBlocker ->\n" +
251+
" [x] Focus Follows Click\n\n";
252+
}
253+
else
254+
{
255+
popupText +=
256+
" <b><color=#ffff88>!!! CHECK FAILED !!!</color></b>\n\n" +
257+
"kOS couldn't find a version of ClickThroughBlocker that works with kOS. This could be " +
258+
"because ClickThroughBlocker is not installed at all, or it could be because its version is too old " +
259+
"(or too new, if ClickThroughBlocker ever renames some things that kOS is using).\n" +
260+
"\n\n" +
261+
"To use kOS v1.3 or higher you'll need to quit Kerbal Space Program and install a version of ClickThroughBlocker that it supports.\n";
262+
}
263+
264+
string buttonText;
265+
global::Callback clickThroughAck;
266+
if (clickThroughExists)
267+
{
268+
clickThroughAck = AcceptClickThrough;
269+
buttonText = "Acknowledged.";
270+
}
271+
else
272+
{
273+
clickThroughAck = FailedClickThrough;
274+
buttonText = "Acknowledged. I'll have to quit and change my mods.";
275+
}
276+
277+
kOSSettingsChecker.QueueDialog(
278+
0.75f, 0.6f,
279+
new MultiOptionDialog(
280+
"ClickThroughBlockerCheck",
281+
popupText,
282+
"kOS ClickThroughBlocker Check",
283+
HighLogic.UISkin,
284+
new DialogGUIButton(buttonText, clickThroughAck, true)
285+
));
286+
}
287+
288+
public void AcceptClickThrough()
289+
{
290+
passedClickThroughCheck = true;
291+
}
292+
293+
public void FailedClickThrough()
294+
{
295+
passedClickThroughCheck = false;
296+
}
297+
205298
public void MigrateSettingsNormal()
206299
{
207300
MigrateSettings(false);

src/kOS/Module/kOSSettingsChecker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ private void CheckSettings()
2222
SafeHouse.Logger.SuperVerbose("kOSSettingsChecker.CheckSettings()");
2323
HighLogic.CurrentGame.Parameters.CustomParams<kOSCustomParameters>().CheckMigrateSettings();
2424
HighLogic.CurrentGame.Parameters.CustomParams<kOSConnectivityParameters>().CheckNewManagers();
25+
HighLogic.CurrentGame.Parameters.CustomParams<kOSCustomParameters>().CheckClickThroughBlockerExists();
2526
}
2627

2728
// Because rapidly showing dialogs can prevent some from being shown, we can just queue up

src/kOS/Properties/AssemblyInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,8 @@
3434
[assembly: AssemblyFileVersion("1.2.1.0")]
3535
[assembly: AssemblyVersion("1.2.1.0")]
3636
[assembly: KSPAssembly("kOS", 1, 7)]
37+
38+
// No longer a hard dependancy, because we want to tell the user why kOS isn't working
39+
// if ClickThroughBlocker is not there, rather than just have it silently refuse to
40+
// load kOS with no explanation as would happen if this line was enabled:
41+
// [assembly: KSPAssemblyDependency("ClickThroughBlocker", 1, 0)]

src/kOS/Screen/DelegateDialog.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
2+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
23
using UnityEngine;
34

45
namespace kOS.Screen
@@ -38,7 +39,7 @@ public void OnGUI()
3839
if (invoked)
3940
{
4041
float guessWidth = GUI.skin.label.CalcSize( new GUIContent(message) ).x;
41-
GUILayout.Window( parent.GetUniqueId()+1, new Rect( parent.GetRect().xMin+200,
42+
ClickThruBlocker.GUILayoutWindow( parent.GetUniqueId()+1, new Rect( parent.GetRect().xMin+200,
4243
parent.GetRect().yMin+10,
4344
guessWidth,
4445
0) , DrawConfirm, "Confirm", GUILayout.ExpandWidth(true) );

src/kOS/Screen/GUIWindow.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using kOS.Utilities;
1515
using kOS.Communication;
1616
using kOS.Safe;
17+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
1718

1819
namespace kOS.Screen
1920
{
@@ -116,15 +117,15 @@ void OnGUI()
116117

117118
GUI.skin = HighLogic.Skin;
118119

119-
WindowRect = GUILayout.Window(UniqueId, WindowRect, WidgetGui, TitleText, style);
120+
WindowRect = ClickThruBlocker.GUILayoutWindow(UniqueId, WindowRect, WidgetGui, TitleText, style);
120121

121122
if (currentPopup != null) {
122123
var r = RectExtensions.EnsureCompletelyVisible(currentPopup.popupRect);
123124
if (Event.current.type == EventType.MouseDown && !r.Contains(Event.current.mousePosition)) {
124125
currentPopup.PopDown();
125126
} else {
126127
GUI.BringWindowToFront(UniqueId + 1);
127-
currentPopup.popupRect = GUILayout.Window(UniqueId + 1, r, PopupGui, "", style);
128+
currentPopup.popupRect = ClickThruBlocker.GUILayoutWindow(UniqueId + 1, r, PopupGui, "", style);
128129
}
129130
}
130131
}

src/kOS/Screen/KOSManagedWindow.cs

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
using System.Collections.Generic;
22
using UnityEngine;
3+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
34

45
namespace kOS.Screen
56
{
67
/// <summary>
78
/// kOSManagedWindow is for any Unity Monobehavior that you'd like to
8-
/// have contain a GUI.Window, and you need kOS to keep track of the
9+
/// have contain an IMGUI Window, and you need kOS to keep track of the
910
/// window stacking for which click is on top of which other click.
1011
/// Unity's built in systems for this don't work well at all, so
1112
/// we had to make up our own.
13+
///
14+
/// Issue #2697 - In addition to KOS's own system to handle this,
15+
/// This also is now layered on top of Linuxgurugamer's ClickThroughBlocker
16+
/// mod, so it uses its wrappers around GUI.Window. That was needed because
17+
/// if SOME mods use ClickThruBlocker windows, then those windows will get first
18+
/// dibs on events before kOS gets to, making kOS helpless to intercept events it's
19+
/// trying to protect other windows from seeing. ClickThruBlocker is a mod that
20+
/// once some mods use it, then all the other mods have to as well.
1221
/// </summary>
1322
public abstract class KOSManagedWindow : MonoBehaviour
1423
{
@@ -222,14 +231,13 @@ public bool IsOpen
222231
}
223232

224233
/// <summary>
225-
/// Pass in the absolute GUI screen location of a mouse click to decide whether
226-
/// or not this widget gets keyboard focus because of that click.
227-
/// (Clicking outside the window takes focus away. Clicking inside
228-
/// the window gives focus to the window and brings it to the front.)
229-
/// </summary>
234+
/// Pass in the absolute GUI screen location of the mouse to decide whether
235+
/// or not this window gets keyboard focus because of that position.
236+
/// (If you want focus-follows-mouse logic, call this every Update(). If you
237+
/// want click-to-focus logic, only call this in Update()s where a click just happened.)
230238
/// <param name="absMousePos">Absolute position of mouse on whole screen</param>
231239
/// <returns>True if the window got focused, false if it didn't.</returns>
232-
public bool FocusClickLocationCheck(Vector2 absMousePos)
240+
public bool FocusMouseLocationCheck(Vector2 absMousePos)
233241
{
234242
bool wasInside = false;
235243
if (IsInsideMyExposedPortion(absMousePos))
@@ -286,42 +294,55 @@ public bool IsInsideMyExposedPortion(Vector2 posAbsolute)
286294

287295
/// <summary>
288296
/// When you subclass KOSManagedWindow, make sure that you call this
289-
/// from inside your Update. It does not use OnGUI because of the fact
290-
/// that the OnGUI event handler is broken - it only sends MouseDown
291-
/// and MouseUp events when the mouse is OUTSIDE the window, which is
292-
/// utterly backward, and it's hard to work out how to fix this,
293-
/// given how badly documented the Unity GUI API is. If anyone who
294-
/// actually understands the Unity GUI system wants to fix this,
295-
/// please feel free to do so.
297+
/// from inside your Update() to check for focus change on the window.
298+
/// Calling this will maybe call GetFocus() or LoseFocus() depending on
299+
/// what the mouse is doing.
300+
/// Note, you call this during *Update()*, NOT the OnGUI() call.
301+
/// It does not use OnGUI() because the raw mousebutton state it
302+
/// needs to see can get consumed and wiped by Unity's IMGUI widgets
303+
/// before application code like this can see it.
296304
/// </summary>
297-
/// <returns>True if there was a mouseclick within this window.</returns>
298-
public bool UpdateLogic()
305+
public void UpdateLogic()
299306
{
300-
if (!IsOpen) return false;
307+
if (!IsOpen) return;
301308

302309
// Input.mousePosition, unlike Event.current.MousePosition, puts the origin at the
303310
// lower-left instead of upper-left of the screen, thus the subtraction in the y coord below:
304311
mousePosAbsolute = new Vector2( Input.mousePosition.x, UnityEngine.Screen.height - Input.mousePosition.y);
305312

306313
// Mouse coord within the window, rather than within the screen.
307314
mousePosRelative = new Vector2( mousePosAbsolute.x - windowRect.xMin, mousePosAbsolute.y - windowRect.yMin);
308-
309-
bool clickUp = false;
315+
316+
// Could maybe cache the CustomParams call once up front to get a reference to the CTB instance, then only
317+
// repeat the ".focusFollowsclick" part each update. The reason that's not being done here is that I
318+
// noticed ClickThroughBlocker's OWN code always does it like this, and for all I know there might be
319+
// an important reason. It always gets this value by using the fully qualified long chain you see
320+
// here, starting from HighLogic, each update. :
321+
bool clickToFocus = HighLogic.CurrentGame.Parameters.CustomParams<ClickThroughFix.CTB>().focusFollowsclick;
322+
310323
if (Input.GetMouseButtonDown(0))
311324
{
312325
mouseButtonDownPosAbsolute = mousePosAbsolute;
313326
mouseButtonDownPosRelative = mousePosRelative;
314327
}
315-
316-
if (Input.GetMouseButtonUp(0))
328+
329+
bool mousePositionCanSetFocus = !(clickToFocus); // Always true in focus-follows-mouse mode
330+
331+
if (clickToFocus)
317332
{
318-
clickUp = true;
319-
if (Vector2.Distance(mousePosAbsolute,mouseButtonDownPosAbsolute) <= dragTolerance)
333+
if (Input.GetMouseButtonUp(0))
320334
{
321-
FocusClickLocationCheck(mousePosAbsolute);
335+
if (Vector2.Distance(mousePosAbsolute, mouseButtonDownPosAbsolute) <= dragTolerance)
336+
{
337+
mousePositionCanSetFocus = true;
338+
}
322339
}
323340
}
324-
return IsInsideMyExposedPortion(mousePosAbsolute) && clickUp;
341+
342+
if (mousePositionCanSetFocus)
343+
{
344+
FocusMouseLocationCheck(mousePosAbsolute);
345+
}
325346
}
326347
}
327348
}

src/kOS/Screen/KOSNameTagWindow.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using kOS.Utilities;
1+
using kOS.Utilities;
22
using UnityEngine;
33
using kOS.Module;
4+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
45
using System;
56

67
namespace kOS.Screen
@@ -124,7 +125,7 @@ public void OnGUI()
124125
EditorLogic.fetch.Lock(false, false, false, "KOSNameTagLock");
125126

126127
GUI.skin = HighLogic.Skin;
127-
GUILayout.Window(myWindowId, windowRect, DrawWindow,"KOS nametag");
128+
ClickThruBlocker.GUILayoutWindow(myWindowId, windowRect, DrawWindow,"KOS nametag");
128129

129130
// Ensure that the first time the window is made, it gets keybaord focus,
130131
// but allow the focus to leave the window after that:

src/kOS/Screen/KOSTextEditPopup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using kOS.Safe.Exceptions;
77
using kOS.Safe.Utilities;
88
using kOS.Module;
9+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
910

1011
namespace kOS.Screen
1112
{
@@ -170,7 +171,7 @@ public void OnGUI()
170171
CalcOuterCoords(); // force windowRect to lock to bottom edge of the parents
171172
CalcInnerCoords();
172173

173-
WindowRect = GUI.Window(UniqueId, WindowRect, ProcessWindow, "");
174+
WindowRect = ClickThruBlocker.GUIWindow(UniqueId, WindowRect, ProcessWindow, "");
174175
// Some mouse global state data used by several of the checks:
175176

176177
if (consumeEvent)

src/kOS/Screen/KOSToolbarWindow.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
using System.Collections.Generic;
1111
using System.Linq;
1212
using UnityEngine;
13+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
14+
1315

1416
namespace kOS.Screen
1517
{
@@ -443,7 +445,7 @@ public void OnGUI()
443445

444446
GUI.skin = HighLogic.Skin;
445447

446-
windowRect = GUILayout.Window(UNIQUE_ID, windowRect, DrawWindow, "kOS " + versionString);
448+
windowRect = ClickThruBlocker.GUILayoutWindow(UNIQUE_ID, windowRect, DrawWindow, "kOS " + versionString);
447449
windowRect = RectExtensions.ClampToRectAngle(windowRect, rectToFit);
448450
}
449451

src/kOS/Screen/ListPickerDialog.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using UnityEngine;
3+
using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
4+
35

46
namespace kOS.Screen
57
{
@@ -110,7 +112,7 @@ public void OnGUI()
110112

111113
// Make sure it shifts enough to the left to fit the biggest string:
112114
outerWindowRect.x = Mathf.Min(outerWindowRect.x, UnityEngine.Screen.width - outerWindowRect.width - 60);
113-
outerWindowRect = GUILayout.Window(
115+
outerWindowRect = ClickThruBlocker.GUILayoutWindow(
114116
title.GetHashCode(),
115117
outerWindowRect,
116118
DrawInnards,

0 commit comments

Comments
 (0)