Jump to content

Is it possible to edit the right-click action menu?


Recommended Posts

As the title says, I was wondering whether it is possible to edit the right-click action menu of parts when in the VAB and in-flight.

Thanks

EDIT: To clarify, I want to edit one of the built-in options in the right-click menu.

Edited by Zarpar
To clarify question
Link to comment
Share on other sites

Take a look at https://github.com/taraniselsu/TacExamples/blob/master/03-PartRightClicking/Source/PartRightClick.cs ; that's an example of a partmodule that defines two events for the part's action menu, where each event deactivates itself and activates the other event.

I'm new at this myself so I don't know if there's a good way to activate and deactivate events belonging to other partmodules, so if that's what you're hoping to do, hopefully someone more experienced than me will come tell us about it.

Link to comment
Share on other sites

Take a look at https://github.com/taraniselsu/TacExamples/blob/master/03-PartRightClicking/Source/PartRightClick.cs ; that's an example of a partmodule that defines two events for the part's action menu, where each event deactivates itself and activates the other event.

I'm new at this myself so I don't know if there's a good way to activate and deactivate events belonging to other partmodules, so if that's what you're hoping to do, hopefully someone more experienced than me will come tell us about it.

This examples appears to add something to the right-click menu, whereas I want to edit a built-in option. I'll clarify it in the OP.

Link to comment
Share on other sites

Edit in what way? Basically yes (the prefabs are exposed), but working with EzGUI by hand is tedious. If you had some idea of what you wanted to do it would be helpful

I was essentially trying to research whether this was possible.

Link to comment
Share on other sites

I was essentially trying to research whether this was possible.

It's a good suggestion. I spent about 20 minutes poking at it and came up with this:

[KSPAddon(KSPAddon.Startup.EditorAny, true)]
public class CtrlClickEditNumber : MonoBehaviour
{
private const string ControlLockId = "InputValueDialogLock";
private UIPartActionFloatRange paFloatRange;

private void Start()
{
DontDestroyOnLoad(this);

paFloatRange =
UIPartActionController.Instance.fieldPrefabs.FirstOrDefault(
fi => fi.GetType() == typeof(UIPartActionFloatRange)) as UIPartActionFloatRange;

if (paFloatRange == null)
{
Debug.LogError("Failed to find a UI prefab. Post a bug report");
return;
}

paFloatRange.gameObject.PrintComponents(new DebugLog("FloatRange"));
paFloatRange.gameObject.AddComponent<ControlClickEntersEditMode>();
}
}


class ControlClickEntersEditMode : MonoBehaviour
{
private const string ControlLockId = "FloatSliderEditModeLock";
private const float ColorChangePerSecond = 2f;

private static Color FlashColor = new Color(255f, 255f, 255f);
private static char[] ValidCharacters = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', ',', '.'};

private Material _backgroundMaterial;
private Color _originalBackgroundColor;
private Gradient _gradient;
private UIPartActionFieldItem _fieldItem;
private UIProgressSlider _slider;
private UIButton _button;
private SpriteText _ourSpriteText;
private SpriteText _originalSpriteText;
private bool _editMode = false;
private string _inputString = string.Empty;
private float _initialValue = 0f;

private void Start()
{
print("ControlClickEntersEditMode instantiated");

if (!LookupComponents())
{
print("ControlClickEntersEditMode: failed to find a dependency");
Destroy(this);
return;
}

// the game sets the original sprite text every frame, overwriting any changes. Instead of
// playing LateUpdate shenanigans and fighting it, clone that component and then we can
// activate/deactive our custom text as needed
var clone = Instantiate(_originalSpriteText.gameObject, _originalSpriteText.transform.position, _originalSpriteText.transform.rotation) as GameObject;

_ourSpriteText = clone.GetComponent<SpriteText>();
_ourSpriteText.Text = "<not set>";

clone.SetActive(false);
clone.transform.parent = _originalSpriteText.transform.parent;
clone.layer = _originalSpriteText.gameObject.layer;


_gradient = new Gradient();
_gradient.SetKeys(
new[]
{
new GradientColorKey(_originalBackgroundColor, 0f),
new GradientColorKey(FlashColor, 1f)
},
new[] {new GradientAlphaKey(.5f, 0f), new GradientAlphaKey(.9f, 1f)});

_button.AddValueChangedDelegate(OnSliderClick);
_initialValue = _slider.Value;
}


private bool LookupComponents()
{
_fieldItem = gameObject.GetComponent<UIPartActionFieldItem>();
if (_fieldItem == null)
{
print("ERROR: Couldn't find UIPartActionFieldItem on " + gameObject.name);
return false;
}

_slider = GetComponentsInChildren<UIProgressSlider>(true).FirstOrDefault();
if (_slider == null)
{
print("ERROR: Couldn't find UIProgressSlider");
return false;
}

_slider.AddValueChangedDelegate(OnSliderClick);

_button = GetComponentsInChildren<UIButton>(true).FirstOrDefault();
if (_button == null)
{
print("ERROR: Couldn't find UIButton");
return false;
}

var background = transform.Find("Background");
if (background == null)
{
print("ERROR: Couldn't find Background transform");
return false;
}
_backgroundMaterial = background.renderer.material; // this causes the material to be clone
_originalBackgroundColor = _backgroundMaterial.color;

// the game seems to set the text value of this every frame. Instead of fighting with
// it or playing LateUpdate shenanigans, we'll just clone it and hide the original
// when needed
_originalSpriteText = GetComponentsInChildren<SpriteText>(true).FirstOrDefault(st => st.name == "amnt");
if (_originalSpriteText == null)
{
print("ERROR: couldn't find SpriteText");
return false;
}


return true;
}


private void OnDestroy()
{
print("ControlClickEntersEditMode destroying");

Destroy(_backgroundMaterial);
_slider.RemoveValueChangedDelegate(OnSliderClick);
_button.RemoveValueChangedDelegate(OnSliderClick);
}



private void OnSliderClick(IUIObject obj)
{
if (Input.GetKey(KeyCode.LeftControl))
SetEditMode(true);
}


private void SetFieldValue(float value)
{
_fieldItem.Field.SetValue(value, _fieldItem.Field.host);
_ourSpriteText.Text = value.ToString(new NumberFormatInfo());
}


private void Update()
{
if (!_editMode) return;

if (Input.GetKeyDown(KeyCode.Escape))
{
_inputString = _initialValue.ToString("F2");
SetEditMode(false);

}
else if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
{
if (!IsNumeric(_inputString))
{
Debug.LogError("Couldn't set slider value because '" + _inputString + "' is nonnumeric");
SetFieldValue(_initialValue);
}
else SetFieldValue(float.Parse(_inputString, NumberStyles.Float));

SetEditMode(false);
} else if (Input.anyKeyDown)
{
if (ValidCharacters.Contains(Event.current.character))
{
_inputString += Event.current.character;
} else if (Input.GetKeyDown(KeyCode.Backspace))
{
if (_inputString.Length > 0) _inputString = _inputString.Substring(0, _inputString.Length - 1);
}

_ourSpriteText.Text = _inputString;
}
}


private bool IsNumeric(string str)
{
float result;

return float.TryParse(str, NumberStyles.Float, new NumberFormatInfo(), out result);
}


private void SetEditMode(bool tf)
{
if (tf)
{
if (!_editMode)
{
_editMode = true;
_originalSpriteText.gameObject.SetActive(false);
_ourSpriteText.gameObject.SetActive(true);
_inputString = _fieldItem.Field.GetValue<float>(_fieldItem.Field.host).ToString("F2");

InputLockManager.SetControlLock(ControlTypes.All, ControlLockId);
StartCoroutine("FlashySlider");
}
}
else
{
_editMode = false;
_originalSpriteText.gameObject.SetActive(true);
_ourSpriteText.gameObject.SetActive(false);
InputLockManager.RemoveControlLock(ControlLockId);
}
}


private IEnumerator FlashySlider()
{
float delta = 0f;

while (_editMode)
{
delta = UtilMath.WrapAround(delta + Time.deltaTime*ColorChangePerSecond, 0f, 2f);

_backgroundMaterial.color = _gradient.Evaluate(delta < 1f ? delta : 2f - delta);

yield return 0;
}

_backgroundMaterial.color = _originalBackgroundColor;
}
}

It needs polish and support for the other slider types but it's a working prototype to get you started (Ctrl+click on a slider to enter edit mode, escape cancels, enter confirms). I tested it on the engine thrust limit slider

Link to comment
Share on other sites

Wow this is detailed, thanks. You ok with me building on this for a full mod, with credit to you for the prototype?

EDIT: Also, what is a UIPartActionFloatRange?

Link to comment
Share on other sites

Wow this is detailed, thanks. You ok with me building on this for a full mod, with credit to you for the prototype?

Yes absolutely.

UIPartActionFloatRange is a component that contains the logic for the slider used for KSPFields marked with the UI_FloatRange attribute. When a UIPartActionWindow is created, each PartModule field is checked for attributes that determine whether or not and how that field gets displayed in the part popup menu; in this case, we're modifying the prefab that will be used for PartModule fields with the UI_FloatRange attribute.

Edited by xEvilReeperx
Link to comment
Share on other sites

Yes absolutely.

UIPartActionFloatRange is a component that contains the logic for the slider used for KSPFields marked with the UI_FloatRange attribute. When a UIPartActionWindow is created, each PartModule field is checked for attributes that determine whether or not and how that field gets displayed in the part popup menu; in this case, we're modifying the prefab that will be used for PartModule fields with the UI_FloatRange attribute.

I can't appear to get this code to work. I've got all the right imports added and compiled it to a dll placed in GameData/PluginName but keep getting the error

AssemblyLoader: Exception loading 'PluginName': System.Reflection.ReflectionTypeLoadException: The classes in the module cannot be loaded.

EDIT: Fixed now, I was compiling for .NET 4.0 instead of 3.5.

Edited by Zarpar
Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...