I am trying to use Unity3D's UIToolkit with Visual Scripting Graph...
Typically, I can Query for the proper element, and then add the necessary function to the callback... However, as I am new to Visual Scripting, I am not sure how to get to this point. I've got the 'clickable' object of the button isolated, but I'm not sure how to assign the follow-up execution in the graph.
Usually in the code, I would do something like
clickable.clicked += ExampleFunction();
What I am messing up in the graph, is how to access the '.clicked' part. I can get the proper button element, and I can isolate it's clickable property, but I can't figure out how to assign some kind of functionality to react to the button getting clicked.
I could use a custom node to make this work, but I am looking for a way to do this with built-in nodes, if possible.
Alright... I had to write a custom node, but I figured this out. Here is the graph for the solution.
You have to grab the UIDocument from whichever GameObject it is attached to... You then need to get the Root Visual Element, do NOT clone or instantiate it. You then need to Query for the desired button, using the name you gave it in the UI Builder. It is easier if you use the U Query Extensions nodes... After that, I just made a custom node to subscribe the functionality. I am not familiar of any nodes that do this.
Here is the 'Subscribe Start Result' node code:
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
public class SubscribeStartResult : Unit
{
[DoNotSerialize]
[PortLabelHidden]
public ControlInput inputTrigger;
[DoNotSerialize]
[PortLabelHidden]
public ValueInput element;
protected override void Definition()
{
element = ValueInput<Button>("element");
inputTrigger = ControlInput("inputTrigger", (flow) =>
{
flow.GetValue<Button>(element).clicked += () =>
{
Debug.Log("Button clicked");
};
return null;
});
}
}
With this setup, clicking the 'Start Button' in play-mode will log "Button clicked" in the Console.
The 'return null;' line is an artifact of the lambda. It is required to continue the control flow in the event this node has a follow-up... Otherwise, this combination of nodes and code allow you to assign callbacks for the UI Builder elements, using the Visual Scripting Graph.
Related
The repro is a small example based on the maui template.
I created a button called MyButtonView and changed the MainPage to consume that control.
The button is created and shows correctly on the page, but when I try to create just the control as in
var b = new MyButtonView(); the handler is not created and I cant figure out how to get this created.
Notice in the source I have implemented the clicked event to show how the handler is not created. I am sure I am missing something but could someone lead me in the right direction?
Github repro
So it seems that if the control once created has a null handler, you will need to call MyButtonView.ToHandler(mauiContext); sounds simple, but getting the mauiContext is a bit of a pain.
The only way i was able to do this was to do the following in the MauiProgram.cs. This works for windows, have yet to try it with iOS
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<DtNavigationView, DtNavigationViewHandler>();
handlers.AddHandler<DtWindowTabView, DtWindowTabViewHandler>();
handlers.AddHandler<DtWindowTabItem, DtWindowTabItemHandler>();
});
builder.UseMauiEmbedding<Application>();
var mauiapp = builder.Build();
mauiContext = new MauiContext(mauiapp.Services);
return mauiapp;
Now you can use the static context to get the object to a handler, by using
MyButtonView.ToHandler(MauiProgram.mauiContext);
Dont think this is the best way to do this but its all i can come up with for now.
Update - this is not the answer to the issue. Storing the MauiContext at this point will result in other issues such as not being able to have the base navigation framework setup.
So the only work around i have found so far that will work for me is to capture the MauiContext was to save it off in the handler when
public override void SetMauiContext(IMauiContext mauiContext) { DtMauiContext.mauiContext = mauiContext; base.SetMauiContext(mauiContext); }
The DtMauiContext is a static i can use it in the view level.
In the Maui source they have Application.Current.FindMauiContext(), it would have been so easy of they just exposed this.
I am working on a VsCode extension in that I want to provide custom snippets for code completion.
I know about the option of using snippet json files directly, however those have the limitation of not being able to utilize the CompletionItemKind property that determines the icon next to the completion suggestion in the pop-up.
My issue:
If I implement a simple CompletionItemProvider like this:
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
{scheme:"file",language:"MyLang"},
{
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
let item = new vscode.CompletionItem('test');
item.documentation = 'my test function';
item.kind = vscode.CompletionItemKind.Function;
return [item];
}
}
)
)
then the original VsCode IntelliSense text suggestions are not shown anymore, only my own. Should I just return a kind of an empty response, like
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
return [null|[]|undefined];
}
the suggestions appear again as they should. It seems to me that instead of merging the results of the built-in IntelliSense and my own provider, the built-in ones get simply overridden.
Question:
How can I keep the built-in IntelliSense suggestions while applying my own CompletionItems?
VsCode Version: v1.68.1 Ubuntu
I seem to have found the answer for my problem, so I will answer my question.
Multiple providers can be registered for a language. In that case providers are sorted
by their {#link languages.match score} and groups of equal score are sequentially asked for
completion items. The process stops when one or many providers of a group return a
result.
My provider seems to provide results that are just higher scored than those of IntelliSense.
Since I didn't provide any trigger characters, my CompletionItems were comteping directly with the words found by the built-in system by every single pressed key and won.My solution is to simply parse and register the words in my TextDocument myself and extend my provider results by them. I could probably just as well create and register a new CompletionItemProvider for them if I wanted to, however I decided to have a different structure for my project.
I've been working on this solidly for hours and can't seem to get anywhere, so your help will be much appreciated.
I have created a few TextMeshPro UI InputFields in Unity and can't seem to access the text after a user has inputted something. I can do it when it's a normal InputField but not with the TM Pro version. I've created a Serializefield of type TMP_InputField which I have hooked up in the inspector (also including using TMPro namespace).
Could somebody please outline the steps required to get the text into a variable because I am stumped! The script is currently sitting on a different GameObject. I have an array of type TMP_InputField called theFields and when I debug theFields[0].name that seems to work fine (using FindObjectsOfType<>) but when I try to just access a specificField.text it throws a null reference objection when trying to edit the field during the game. Any tutorials/support would be very welcome!
Did you try with UnityEvents fields on the Inspector of TMP_InputField?
I make a simple script and it combines with TMP_InputField
using UnityEngine;
public class InputfieldListener : MonoBehaviour
{
public void OnChangedInputField(string input)
{
Debug.Log("[OnChangedInputField] " + input);
}
public void OnEndedInputField(string input)
{
Debug.Log("[OnEndedInputField] " + input);
}
public void OnSelectedInputField(string input)
{
Debug.Log("[OnSelectedInputField] " + input);
}
public void OnDeslectedInputField(string input)
{
Debug.Log("[OnDeslectedInputField] " + input);
}
}
Then you can find the log of each event.
FYI, the creator has a youtube channel and there is a related video tutorial.
using TMPro;
public TMP_InputField TMP_IF;
TMP_IF.interactable = true;
TMP_IF.text = "whatever you want";
For those interested in year 2020 :)
Seems that property interactable is instantly disabled, solution was to enable it so it text could be updated...
I just code it up and then in the editor drag the TMP_InputField object into the corresponding slots in the script.
So, code-wise, I would have script like Milorad Zivanovic shows.
If you want multiple ones you might have:
public TMP_InputField [] myInputs;
In the editor (as described above in my first sentence) you just hit the plus button and drag them in. Or just highlight them all and drag in all at once. If you lock your object in the editor to do this (little lock icon up top right) don't forget to unlock it.
To access in your code you either loop or use an index. Like to get the first one you would do this:
{//... in some method
myInputs[0].text = "something";
}
PS: I did not have to explicitly set the interactable field.
PPS: The answer that HyoJin KIM gave is great and best if you like using Unity's Event. I just gave this answer so you'd have an additional way of doing it that might match your original attempt(s).
Be aware that if you do choose this latter approach it is very easy to make the mistake of choosing the static version of the same method (they are listed twice!) Many times I make this mistake. Choose the top one (non-static, aka Dynamic) for most cases!
How does one reliably find out if a given Component is actually present in the DOM?
Until now I used Component.getUI().isPresent() which is supposed to able to determine if the Component is attached to a UI.
It also might be that I ran into an edge case as the Components in question are encapsulated in a ComponentRenderer which is managed by a Grid.
I need to access these Componets via JavaScript like this:
void setValue(Component comp, Value value){
Runnable callJs = comp.getUI().ifPresent(ui -> ui.getPage().executeJs("someCall($0)", value));
if(comp.isAttached()){
// execute it right away
callJs.run();
} else {
// execute onAttach
comp.addAttachListener(evt -> callJs.run());
}
}
After some digging I stumbled across the StateNode API:
comp.getElement().getNode().isAttached()
I have a problem use editor script to remove a statemachinebehaviour,like this:
I use AnimatorState.AddStateMachineBehaviour add the behaviour
The Documentation said use Object.Destroy,I use this api,but it appears:
I want to know how to use Editor Script to implement the function as "remove"
thanks for any idea!!!
In order to remove the state machine behaviour you have to grab the state machine behaviour array, remove the behaviour that you're after, and then reassign the array.
Something along these lines should do it:
using UnityEditor;
using UnityEditor.Animations;
//how you invoke this is up to you
[MenuItem("CONTEXT/StateMachineBehaviour/Remove Test")]
public static void RemoveBehaviour(MenuCommand command)
{
Object selection = Selection.activeObject;
AnimatorState state = selection as AnimatorState;
if(state != null)
{
StateMachineBehaviour behaviour = command.context as StateMachineBehaviour;
StateMachineBehaviour[] theBehaviours = state.behaviours;
ArrayUtility.Remove(ref theBehaviours, behaviour);
Undo.RegisterCompleteObjectUndo(state, "Removed behaviour");
Undo.DestroyObjectImmediate(behaviour);
state.behaviours = theBehaviours;
}
}
This will remove the behaviour using the state machine dropdown menu, with added undo and redo support, an optional bonus.
Depending on how you want to handle the remove, this approach will change but in terms of removing the behaviour, this should be what you're after.
Also, for animator state machines the approach is exactly the same, you just cast the selection object to an AnimatorStateMachine instead.
Hopefully this helps.