Skip to main content

Create Your Own Components

Instead of using sprite-based components, you might want to create components that are based on shapes provided by the UI Module. Some advantages to this approach are better control of the colors, as well as ending up with components that are easily resizable.

You can use the basic building blocks provided in the Kontakt Controls Package to create your own components. Below is an example of how you could go about creating a Knob. You can similarly implement a Slider, Switch, and ToggleButton based on the same approach.

Here's how to use the same building blocks that make up the Knob offered in the package to make your own Knob using the Arc component from the UI Module.

To start off, let's make an instrument with the Knob available out-of-the-box, for comparison purposes.

main.kscript
import * from ui
import * from kontakt_controls

component Main {
ZStack {
// Background
Rectangle(color: Color(0xFF2A2A2A))

// Controls
HStack(spacing: 50) {
Knob(
control_id: "my_knob",
label: "My Knob",
)
}
}
}

export var main: Component = Main()
main.txt
on init
declare ui_knob $my_knob (0, 1000, 1)
make_persistent($my_knob)
read_persistent_var($my_knob)

expose_controls
end on

And this is the starting state of the instrument:

To start building the custom Knob component you can reuse the same base that the current Knob uses. Namely the Draggable modifier, which already implements the drag gesture behavior needed for a knob. So what's left to implement is the connection to the KSP Control and the appearance of the knob.

Knob Appearance

Create a new file which contains the custom knob component, next to the main.kscript file. Let's call it my_knob.kscript, and add to it code that implements the knob appearance, with some hard-coded values. Note that the component is exported, so that you can use it in your instrument:

my_knob.kscript
import * from ui

export component MyKnob {
Arc(
color: Color(0xFFC780FF),
angle: Angle(degrees: 180),
start_angle: Angle(degrees:90),
) with {
Frame (width: 60, height: 60)
Overlay {
Text("180", color: Color(0xFFC780FF))
}
}
}

To test it out, you can add it to the instrument along the existing knob:

main.kscript
import { MyKnob } from my_knob

component Main {
ZStack {
// Background
Rectangle(color: Color(0xFF2A2A2A))

// Controls
HStack(spacing: 50) {
MyKnob()
Knob(
control_id: "my_knob",
label: "My Knob",
)
}
}
}

KSP Control Connection

The next step is connect the Knob to the KSP Control, so that the data can come from KSP instead of being hard-coded. The id of the control will be passed as a property to the component, and a ksp_control computed state provides access to all properties of the KSPKnob. You can use these properties to set values for the angle of the knob, and for the displayed text.

my_knob.kscript
import * from ui
import { KSPKnob } from kontakt

export component MyKnob {
@property control_id: String

ksp_control: KSPKnob { return KSPKnob(id: self.control_id) }

Arc(
color: Color(0xFFC780FF),
angle: Angle(degrees: self.ksp_control.normalized_value*360),
start_angle: Angle(degrees: 90),
) with {
Frame (width: 60, height: 60)
Overlay {
Text("\{self.ksp_control.value}", color: Color(0xFFC780FF))
}
}
}

Now pass the same control id used by the Knob from the Kontakt Controls Package to the new component:

main.kscript
component Main {
ZStack {
// Background
Rectangle(color: Color(0xFF2A2A2A))

// Controls
HStack(spacing: 50) {
MyKnob(
control_id: "my_knob"
)
Knob(
control_id: "my_knob",
label: "My Knob",
)
}
}
}

Interactive Behavior

All there is left to do is to add the interactive behavior of the knob. This comes from applying to the Knob the existing Draggable modifier from the package. Draggable is suitable for both knobs and sliders implementations, and contains the handling of the drag gesture that controls the value updates of the component, as well as the reset trigger implementation(control/command + tap).

For the complete functioning of the modifier, you also need to implement two callbacks:

  • on_editing_changed called when the user starts or ends interacting with the control,
  • and on_reset, called when the user triggers a reset (control/command + tap).
my_knob.kscript
import * from ui
import { KSPKnob } from kontakt
import { EdgeInsets, Draggable, DraggableAxis } from kontakt_controls

export component MyKnob {
@property control_id: String
@property disabled: Bool = false

ksp_control: KSPKnob { return KSPKnob(id: self.control_id) }

noop: Float {
get { return 0 }
set(new_value) {}
}

axis: DraggableAxis {
return DraggableAxis(
step_count: self.ksp_control.max - self.ksp_control.min,
fine_step_count: 2,
sensitivity: 0.5,
)
}

Arc(
color: Color(0xFFC780FF),
angle: Angle(degrees: self.ksp_control.normalized_value*360),
start_angle: Angle(degrees: 90),
) with {
Frame (width: 60, height: 60)
Overlay {
Text("\{self.ksp_control.value}", color: Color(0xFFC780FF))
}
Draggable(
x_value: self.$noop,
y_value: self.ksp_control.$normalized_value,
y_axis: self.axis,
disabled: self.disabled,
on_editing_changed: fun (editing: Bool) {
self.ksp_control.automate = editing
},
on_reset: fun () {
self.ksp_control.value = self.ksp_control.default_value
},
inset: EdgeInsets(0)
)
}
}

Notice that most of the property values you set for Draggable come from the KSP Control. In addition, the code above:

  • passes a binding to a 0 value for the horizontal axis, since the knob only updates when dragging on the vertical axis, not the horizontal one
  • sets a vertical axis for interacting with the knob
  • passes a disabled property from above
  • and sets an inset value

You can further refine and customize this knob based on your design needs, but with a minimal implementation you already got a rough equivalent of the knob from the package, using vector instead or raster graphics. The two knobs in the instrument are now in sync, both connected to the same KSP control.