Controls and properties¶
Every node in a layout is a Control: a control type, an id, a set of properties, its live values, its messages, and its children.
Creating¶
There is a factory per control type, and each applies that type's defaults:
import py2tosc
py2tosc.box() py2tosc.button() py2tosc.label()
py2tosc.text() py2tosc.fader() py2tosc.xy()
py2tosc.radial() py2tosc.encoder() py2tosc.radar()
py2tosc.radio() py2tosc.group() py2tosc.pager()
py2tosc.grid()
Properties can be passed straight in:
For a type chosen at runtime, construct a Control directly:
control = py2tosc.Control("FADER", name="cutoff")
control = py2tosc.Control(py2tosc.ControlType.FADER, name="cutoff")
Naming¶
TouchOSC property keys are camelCase, because that is what the file stores. Python names are snake_case. py2tosc translates between them, so you never write camelCase yourself:
| Python | Stored in the file |
|---|---|
corner_radius |
cornerRadius |
text_size |
textSize |
grid_steps_x |
gridStepsX |
outline_style |
outlineStyle |
Both spellings work everywhere, so pasting a key out of Hexler's documentation also works:
The raw dict is available when you need it, and is always keyed the way the file is:
fader.properties["gridSteps"].type #> <PropertyType.INTEGER: 'i'>
sorted(fader.properties) # every key on this control
Reading¶
Attribute access raises AttributeError for a property that is not set, rather than returning None and letting the mistake travel:
fader.name #> 'cutoff'
fader.script #> AttributeError: FADER control has no property 'script'
fader.get("script") #> None
fader.get("script", "") #> ''
fader.has("script") #> False
Types¶
Property types are inferred from the value, with known TouchOSC keys using their documented type. You rarely have to think about it:
fader.visible = 1 # stored as type 'b', because visible is a boolean
fader.corner_radius = 1 # stored as type 'f', because cornerRadius is a float
fader.text_size = 14 # stored as type 'i'
Frames and colours are both four-item tuples, so those keys are resolved by name. Custom keys fall back to the Python type:
Force a type when you need to:
Frames and colours¶
Both come back as named tuples that still compare and unpack as plain tuples:
fader.frame #> Frame(x=0, y=0, w=50, h=200)
fader.frame.w #> 50
x, y, w, h = fader.frame
fader.frame == (0, 0, 50, 200) #> True
Colours accept whichever notation is convenient:
fader.color = (1.0, 0.0, 0.0, 1.0) # normalised floats
fader.color = (255, 0, 0) # 0-255, alpha defaults to opaque
fader.color = "#e76f51" # hex, with or without the #
fader.color = "#e76f51ff" # hex with alpha
Named numbers¶
A dozen properties hold a number that stands for a name. shape 2 is a circle, orientation 1 faces east, buttonType 0 is momentary. The enumerations name them, and because they are IntEnum the two spellings are one value:
button.shape = py2tosc.Shape.HEXAGON
button.shape = 6 # identical, and still what the file stores
button.shape == py2tosc.Shape.HEXAGON #> True
py2tosc.Shape(button.shape).name #> 'HEXAGON'
Nothing requires you to use them. A layout written before they existed loads and compares against them unchanged, which is the point of their being integers underneath.
| Property | Enumeration |
|---|---|
shape |
Shape |
text_align_h |
AlignH |
text_align_v |
AlignV |
orientation |
Orientation |
button_type |
ButtonType |
outline_style |
OutlineStyle |
cursor_display, bar_display, lines_display |
CursorDisplay |
font |
Font |
response |
Response |
radio_type |
RadioType |
pointer_priority |
PointerPriority |
The reason to prefer the names is that the numbering is not uniform. shape, text_align_h and text_align_v count from 1; every other property here counts from 0. Hexler's manual lists the names in order without numbers, so reading it and counting from zero gets three of the twelve wrong -- Shape.RECTANGLE is 1, not 0.
GamepadInput names the twenty-one buttons and axes a gamepad binding can use, and is a string rather than a number:
Values¶
A control's values are its live state. default is what it starts at.
fader.values #> [Value(key='x', ...), Value(key='touch', ...)]
fader.value("x").default = 0.5
fader.value("x").locked = True
Children¶
panel = py2tosc.group(name="panel")
panel.add(py2tosc.fader(name="a"), py2tosc.fader(name="b"))
len(panel) #> 2
panel[0] #> <FADER 'a'>
list(panel) # direct children
list(panel.walk()) # panel and everything beneath it
panel.remove(panel[0])
find and find_all search the whole subtree, never including the control they are called on:
Copying¶
copy duplicates a control and its subtree with fresh ids, which is what you want almost every time -- TouchOSC expects ids to be unique.
strip = doc.find("channel1")
for channel in range(2, 9):
doc.add(strip.copy(name=f"channel{channel}"))
Pass new_ids=False only if you are deliberately writing duplicates.