wisp-forest / owo-lib

Open ωorthωhile Operations, yes the acronym was "totally accidental"
https://modrinth.com/mod/owo-lib
MIT License
191 stars 37 forks source link

Setting TextBoxComponent to active doesn't set keyboard focus #254

Closed fetimo closed 3 months ago

fetimo commented 3 months ago

Hi!

Firstly thank you for providing such a well-thought out library with personality.

I'm trying to create a textbox which receives keyboard focus when the screen is created to replace the Chat HUD. Here is the XML snippet:

<text-box id="the-text-box">
    <sizing>
        <horizontal method="fill">300</horizontal>
        <vertical method="fill">20</vertical>
    </sizing>
    <active>true</active>
</text-box>

Then in the build method I have the following (you can see my descent into madness).

TextBoxComponent theTextBox = rootComponent.childById(TextBoxComponent.class, "the-text-box");
theTextBox.setFocused(true);
theTextBox.setSelectionStart(0);
theTextBox.setSelectionEnd(0);
theTextBox.setCursorToStart(false);
theTextBox.getNavigationFocus();
theTextBox.onClick(theTextBox.x() + 10, theTextBox.y() + 5);
theTextBox.onClick(30,440);
theTextBox.mouseClicked(30,440, 1);
theTextBox.keyPress();
theTextBox.setFocused(true);
theTextBox.eraseCharacters(0);
theTextBox.getNavigationFocus();
theTextBox.update(0, theTextBox.x() + 10, theTextBox.y() + 5);

I also tried setting various values in the init method and wrapping it in a scheduled task (the ol' setTimeout trick) but the pesky textbox just doesn't want to receive keyboard focus.

I'm new to owo and Java in general so it's likely I'm missing something fundamental but my naive assumption is that between the <active>true</active> and theTextBox.setFocused(true) this should work. Is this a bug or is there another way to do it?

gliscowo commented 3 months ago

In owo-ui, focus is managed by the component tree's FocusHandler (accessible through the .focusHandler()) method on any component in the tree. The .setFocused(...) method on the text box is actually a vanilla method inherited from the the vanilla TextFieldWidget - it is unrelated to owo-ui and has no effect when used outside the vanilla UI system.

As such, to pre-focus the text field, call theTextBox.focusHandler().focus(theTextBox, FocusSource.MOUSE_CLICK). If you have any more questions, feel free to open another issue or join the Wisp Forest Discord server

Cheers

fetimo commented 3 months ago

Thanks @gliscowo!

For some reason the focusHandler() is returning null and shows an error java.lang.NullPointerException: Cannot invoke "io.wispforest.owo.ui.util.FocusHandler.focus(io.wispforest.owo.ui.core.Component, io.wispforest.owo.ui.core.Component$FocusSource)" because the return value of "io.wispforest.owo.ui.component.TextBoxComponent.focusHandler()" is null.

I feel like I must've missed a step somewhere around setting up a UI provider or something. I've tried looking through the source code but can't work it out. I can log out theTextBox and see that definitely exists as io.wispforest.owo.ui.component.TextBoxComponent@3bdf7aff

import io.wispforest.owo.ui.base.BaseUIModelScreen;
import io.wispforest.owo.ui.component.TextBoxComponent;
import io.wispforest.owo.ui.container.FlowLayout;
import io.wispforest.owo.ui.core.Component.FocusSource;
import net.minecraft.util.Identifier;

public class ChatHudInput extends BaseUIModelScreen<FlowLayout> {
    public ChatHudInput() {
        super(FlowLayout.class, DataSource.asset(Identifier.of("siarad", "my_ui_model")));
    }

    @Override
    protected void build(FlowLayout rootComponent) {
        TextBoxComponent theTextBox = rootComponent.childById(TextBoxComponent.class, "the-text-box");
        theTextBox.focusHandler().focus(theTextBox, FocusSource.MOUSE_CLICK);
    }
}

my_ui_model.xml

<owo-ui xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/wisp-forest/owo-lib/1.21/owo-ui.xsd">
    <components>
        <flow-layout direction="vertical">
            <children>
                <text-box id="the-text-box">
                    <sizing>
                        <horizontal method="fill">300</horizontal>
                        <vertical method="fill">20</vertical>
                    </sizing>
                    <active>true</active>
                </text-box>
            </children>
        </flow-layout>
    </components>
</owo-ui>
gliscowo commented 3 months ago

Ah, well, at the point you're calling this function your component tree has not been built yet - this means the text box doesn't yet know about its parent and thus also can't get the focus handler from the root node. Instead, override init() on your screen as well and run your focusing code at the end of that method

fetimo commented 3 months ago

Awesome! In retrospect that should've been obvious to me. Thanks for spelling it out :)

Just for completeness here's what I've ended up with. Thanks for your help @gliscowo

import io.wispforest.owo.ui.base.BaseUIModelScreen;
import io.wispforest.owo.ui.component.TextBoxComponent;
import io.wispforest.owo.ui.container.FlowLayout;
import io.wispforest.owo.ui.core.Component.FocusSource;
import net.minecraft.util.Identifier;

public class ChatHudInput extends BaseUIModelScreen<FlowLayout> {
    TextBoxComponent theTextBox;

    public ChatHudInput() {
        super(FlowLayout.class, DataSource.asset(Identifier.of("siarad", "my_ui_model")));
    }

    @Override
    protected void build(FlowLayout rootComponent) {
        theTextBox = rootComponent.childById(TextBoxComponent.class, "the-text-box");
    }

    @Override
    protected void init() {
        super.init();
        theTextBox.focusHandler().focus(theTextBox, FocusSource.MOUSE_CLICK);
    }
}