> For the complete documentation index, see [llms.txt](https://wiki.redmodding.org/scripting-cyberpunk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.redmodding.org/scripting-cyberpunk/scripting/game-systems/ui-scripting/ink-controllers.md).

# Ink Controllers

The UI elements you see on screen are usually a part of some controller, i.e. the code. These controllers are responsible for defining and setting up the behavior of their respective UI components.

## Types of Controllers

There are two types of ink controllers:

### [`inkGameController`](https://nativedb.red4ext.com/c/7301684378316427)

This controller connects the [root widget library item](/scripting-cyberpunk/scripting/game-systems/ui-scripting/.inkwidget-files.md#library-items) to the ink layer/window, it provides access to the game instance, library animations, sounds (which can be attached to a library item), and (possibly) more.

### [`inkLogicController`](https://nativedb.red4ext.com/c/5170750406542702)

This controller is a lightweight controller that can be attached to any widget, these controllers don't have access to game or resource functions (but can be given these manually). Generally in a UI tree, the game will may delegate the behavior of a widget (and its children if its a container) to a `inkLogicController`.

## Finding An Ink Controller

[Ink Inspector](/scripting-cyberpunk/scripting/game-systems/ui-scripting.md#ink-inspector) can help greatly for finding the respective controller for a widget. In Ink Inspector, select your desired ink widget (either by manually finding it in the tree or by using the "pick" option) and you'll see important information regarding it in the [Widget](/scripting-cyberpunk/scripting/game-systems/ui-scripting.md#widget-tab) and [Context](/scripting-cyberpunk/scripting/game-systems/ui-scripting.md#context-tab) tab.

### Logic Controller

Logic controllers can be attached to any widget, though they usually aren't. Game controllers (the main controller for a widget library item) will usually delegate a widget in the tree (and all of its children if it's a container) to a logic controller, so when you find a widget, you'll have to traverse up the tree and see if there's a respective controller.

In Ink Inspector, if a widget has an assigned logic controller, you'll find it in the property `logicController` and `secondaryControllers` in the "Widget" tab at the very bottom.

### Game Controller

For game controllers, open the [Context](/scripting-cyberpunk/scripting/game-systems/ui-scripting.md#context-tab) tab and take a look at the "Library Path" property, which will tell you the specific [`.inkWidget`](https://wiki.redmodding.org/scripting-cyberpunk/scripting/game-systems/ui-scripting/pages/zip39N2azOyeCDEYWKwd#finding-a-.inkwidget-file) that the widget is from. Open the file in [WolvenKit](https://wiki.redmodding.org/wolvenkit/) and you can find the respective game controller by going to `libraryItems[0]/package/inkWidgetLibraryItemInstance/gameController`.

For example, here's how the `gameController` is set for the inventory screen, the main `inkGameController` for the inventory screen is [`gameuiInventoryGameController`](https://codeberg.org/adamsmasher/cyberpunk/src/branch/master/cyberpunk/UI/inventory/inventoryGameController.swift#L2).

<figure><img src="https://1927068511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Ffwsaoju1TBAUvMpI6NIw%2Fuploads%2F4U07G7YNbG3eIJVrWoVd%2Fimage.png?alt=media&amp;token=72a02703-aee3-4959-9cd7-1a15a2c0f2ac" alt=""><figcaption><p>An example of the root widget for the inventory screen <code>.inkWidget</code></p></figcaption></figure>

### NativeDB

Once you found the controller that is responsible for your respective widget, you can then plug it into [NativeDB](https://nativedb.red4ext.com/) and see all of the methods and properties for the controller.

## Events

### Lifecycle Events

Ink controllers in general have the following lifecycle method to handle the respective process when the controller is attached (initialized) and detached (uninitialized) from the UI.

```swift
// Called when the controller is attached to the UI
protected cb func OnInitialize() -> Bool

// Called when the controller is detached from the UI
protected cb func OnUninitialize() -> Bool
```

If you want to modify the UI for some context, [finding the respective controller](#finding-an-ink-controller) for the widget (or root library item) and inject your code into these lifecycle method(s) is the recommended first step to do so.

{% hint style="info" %}
If your controller doesn't have these lifecycle methods defined, you can use Redscript to [create a method](/scripting-cyberpunk/redscript/language-reference/hook-annotations.md#addmethod-class) with the **exact** signature to handle the specific event.

If you do, check whether if the controller has a valid parent controller and whether if the parent controller has defined a respective method to handle the lifecycle event. If so, be sure to call the parent's method in addition to your code within the method (with `super.OnInitialize();` by example).
{% endhint %}

### Custom Events

You can use custom (or vanilla) [`Events`](https://nativedb.red4ext.com/Event) to handle specific events that occur during UI stuff. The important aspect is that for ink controllers, events are broadcasted throughout the UI layer so any attached controller may handle that event.

For more information, look here: [Custom redEvents](/scripting-cyberpunk/scripting/game-systems/events-and-callbacks/custom-redevents.md#widgets)

## Root Widget

Once you have your logic controller, you can always do `inkController#GetRootCompoundWidget` to get a reference to the controller's respective root widget. With this reference, you can then use [`inkCompoundWidget#GetWidgetByPathName`](https://nativedb.red4ext.com/inkCompoundWidget#GetWidgetByPathName) to specify the path of the widget you want.

## Property References

Ink controllers may have properties [that store a reference to a widget somewhere along the UI tree](/scripting-cyberpunk/scripting/game-systems/ui-scripting/ink-widgets.md#ink-references). Instead of manually getting a reference by specifying the path or traversing the tree, the game (and you) can use these properties instead.

These references are assigned in the respective [`.inkWidget`](https://wiki.redmodding.org/scripting-cyberpunk/scripting/game-systems/ui-scripting/pages/zip39N2azOyeCDEYWKwd#finding-a-.inkwidget-file) file. Find the respective library component that contains your widget, and then traverse the tree to find your respective controller, either `inkGameController` which is in the root library item, or `inkLogicController` which is assigned to some widget within the tree (or possibly root widget).

Once you found your controller, expand it to see the various widget references it has, for example:

<figure><img src="https://1927068511-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Ffwsaoju1TBAUvMpI6NIw%2Fuploads%2Fe4g3pjaZcUH2OIGRhPYD%2Fimage.png?alt=media&amp;token=9f29c9bb-cbdb-4d8d-9863-22c191881007" alt=""><figcaption><p>A preview of the game controller for the map menu's <code>.inkWidget</code> file</p></figcaption></figure>

For the widget references you see, you can expand them to see where they are within the tree (and also try to find them within the UI tree starting from the root widget).

In code, the property is stored within the controller under `m_[propertyName]`, for example, in the preview above, the reference `districtNameText` has the following signature under the [`WorldMapMenuGameController`](https://nativedb.red4ext.com/c/6190446718930808) class.

```swift
private edit let m_districtNameText: inkTextRef;
```

## Injecting Custom Ink Controllers

{% hint style="info" %}
Injecting custom logic controllers requires [Codeware](/scripting-cyberpunk/introduction/tools-and-frameworks/codeware.md).
{% endhint %}

If you have [Codeware](/scripting-cyberpunk/introduction/tools-and-frameworks/codeware.md) installed, you can use [Redscript](/scripting-cyberpunk/redscript/what-is-redscript.md) to create your own custom [logic controller](#inklogiccontroller) and attach it to the widget with `widget.AttachController(controller, secondary)`.

```swift
public native func AttachController(controller: ref<inkLogicController>, opt secondary: Bool);
```

### Defining The Custom Controller

{% hint style="info" %}
You must define your logic controller in Redscript, but you're able to inject with Redscript or Lua.
{% endhint %}

```swift
module MyMod

public class MyLogicController extends inkLogicController {
  protected cb func OnInitialize() -> Bool {
    FTLog(s"\(this.GetClassName())#OnInitialize");
  }

  protected cb func OnUninitialize() -> Bool {
    FTLog(s"\(this.GetClassName())#OnUninitialize");
  }

  public static final func Create() -> ref<MyLogicController> {
    return new MyLogicController();
  }
}
```

### Injecting It

In the `OnInitialize` [lifecycle event](#lifecycle) of your controller, you can then inject your custom logic controller into the root widget the source controller represents.

For example, this injects the logic controller into the inventory screen.

{% tabs %}
{% tab title="REDScript" %}

```swift
module MyMod

@wrapMethod(gameuiInventoryGameController)
protected cb func OnInitialize() -> Bool {
  FTLog(s"\(this.GetClassName())#OnInitialize");

  let root: ref<inkWidget> = this.GetRootWidget();

  if IsDefined(root) {
    root.AttachController(MyLogicController.Create(), true);
  }

  wrappedMethod();
}

@wrapMethod(gameuiInventoryGameController)
protected cb func OnUninitialize() -> Bool {
  wrappedMethod();
  FTLog(s"\(this.GetClassName())#OnUninitialize");
}
```

{% endtab %}

{% tab title="Lua" %}

```lua
registerForEvent('onInit', function()
  ---@param this gameuiInventoryGameController
  Observe("gameuiInventoryGameController", "OnInitialize", function(this)
    print(NameToString(this:GetClassName()) .. "#OnInitialize")

    local root = this:GetRootWidget()

    if IsDefined(root) then
      root:AttachController(MyMod_MyLogicController.Create(), true)
    end
  end)

  ---@param this gameuiInventoryGameController
  Observe("gameuiInventoryGameController", "OnUninitialize", function(this)
    print(NameToString(this:GetClassName()) .. "#OnUninitialize")
  end)
end)
```

{% endtab %}
{% endtabs %}

Under the hood, Codeware auto-magically handles your logic controller so it's properly attached to  the source controller's root widget, it makes your controller fully functional as if it were spawned from the [`.inkWidget`](/scripting-cyberpunk/scripting/game-systems/ui-scripting/.inkwidget-files.md) file.

You now have a custom logic controller where you can go and modify the UI as you wish, additionally in Ink Inspector, if you go the root widget, you should see the class name for your controller under the `secondaryControllers` property in the [Widget](/scripting-cyberpunk/scripting/game-systems/ui-scripting.md#widget-tab) tab.
