Automationen
Das optionale automation-Objekt in einer .clinkplugin-Datei deklariert Ereignisse, Zustand, Einstellungen, Befehle, Vorlagen und Kurzbefehlplätze. Jeder Beitrag hat eine stabile ID, eine genau passende Schemaversion und lokalisierte Titel. Der Host erstellt Namen nach plugin/<plugin-id>/<kind>/<id>. Ein Plugin kann keinen System-Namensraum und keinen Namensraum eines anderen Plugins beanspruchen.
Automationen ›Wie Plugins funktionieren
Ein Panel ersetzt die Tasten, eine Aktion läuft einmal über einen Text. Ein Plugin hat auf der Tastatur keine eigene Fläche. Clink ruft seine Funktionen zu festen Zeitpunkten auf, etwa wenn die Tastatur öffnet oder du ein Wort beendest, und das Plugin kann die Leertaste oder seinen eigenen gespeicherten Zustand ändern. Seine Einstellungen werden in der App mit denselben Bausteinen gezeichnet, die auch Panels verwenden. WPM Spacebar, unten, ist ein vollständiges Plugin.
def initial():
return {"on": False}
def settings(state):
return section("keys.spacebar", [
toggle("Show typing speed", state["on"], action="toggle"),
], title="WPM Spacebar")
def on_action(action, value, state):
state["on"] = value
if value:
claim("spacebar.text")
else:
release("spacebar.text")
space_text(None)
return state
def on_word(word, state):
if state["on"]:
space_text(f"{stats()['wpm']} wpm")
return statePlugins brauchen Clink Pro. Bevor Clink eines aus einem Repository installiert, fragt es dich, ob du Code aus diesem Repository erlaubst, genau wie bei Panels und Aktionen.
on_key und on_word bekommen, was du tippst, denn so funktioniert ein Wortzähler. PyMini hat keinen Netzwerk- oder Dateizugriff, ein Plugin kann deine Eingaben also nicht über das Internet senden, und was es speichert, bleibt bei Clink auf deinem Gerät.
Dein erstes Plugin
Am schnellsten geht es mit dem Starter-Skript, das die App für dich schreibt. Es setzt einen Schalter unter Tasten > Leertaste und zählt, solange der Schalter an ist, Wörter auf der Leertaste. Fünf Schritte, keine Datei zum Herunterladen.
- Öffne den Tab Plugins, schalte Plugins oben ein und tippe auf + für ein neues Plugin.
- Der Editor öffnet sich mit dem Starter-Skript. Lies es einmal durch: initial(), settings(), on_action(), on_open() und on_word() sind das Ganze.
- Wechsle zur Vorschau. Lege den Schalter um, tippe dann ein paar Mal auf on_open und on_word und beobachte die Leertasten-Attrappe und die Konsole.
- Speichern. Das Plugin ist in der Liste eingeschaltet, und sein Schalter sitzt jetzt auch unter Tasten > Leertaste.
- Öffne die Tastatur irgendwo und tippe. Die Leertaste zählt mit.
def initial():
return {"on": False, "words": 0}
def settings(state):
return section("keys.spacebar", [
toggle("Count words on the space bar", state["on"], action="toggle"),
], title="Word count")
def on_action(action, value, state):
if action == "toggle":
state["on"] = value
if value:
claim("spacebar.text")
else:
release("spacebar.text")
space_text(None)
return state
def on_open(state):
state["words"] = 0
if state["on"]:
space_text("0 words")
return state
def on_word(word, state):
state["words"] += 1
if state["on"]:
space_text(f"{state['words']} words")
return stateHooks
Definiere beliebige dieser 33 Funktionen und lass die weg, die du nicht brauchst. Jede bekommt state als letztes Argument. Die, die auf etwas reagieren, geben state zurück, verändert oder nicht. Die, die eine Frage beantworten (elements, draw, effects, die Hooks für Look-Vorlagen, haptics, hitboxes, suggestions und correct), geben ihre Antwort zurück und können state trotzdem direkt ändern.
| Hook | Wann er läuft |
|---|---|
on_automation(command, args, state) | on_automation(command, args, state) führt einen zugelassenen lokalen Befehl in der bestehenden Sandbox aus. Der Hook darf die Beschriftung der Leertaste aktualisieren, deklarierten Zustand veröffentlichen oder deklarierte Ereignisse auslösen. Texteingabe, Zwischenablagezugriff, dauerhafte Einstellungsänderungen und externe Anfragen sind hier nicht erlaubt. |
initial() | Einmal, bevor es einen gespeicherten Zustand gibt. Gib ein Dict mit allem zurück, was JSON speichern kann. |
settings(state) | Wenn die Einstellungen des Plugins in der App angezeigt werden. Gib seine Bedienelemente als Knotenbaum zurück. Läuft nie in der Tastatur. |
on_action(action, value, state) | Wenn eines der Bedienelemente des Plugins benutzt wird. on_action(action, state), ohne value, geht auch. |
on_open(state) | Wenn die Tastatur erscheint. Ein guter Ort, um stats() zu lesen oder die Leertaste zu setzen. |
on_close(state) | Wenn die Tastatur geschlossen wird. Direkt danach wird der Zustand gespeichert. |
on_key(key, state) | Jedes Mal, wenn eine Taste etwas schreibt. Das läuft bei jedem Tastendruck, also halte es schnell. |
on_word(word, state) | Wenn ein Wort abgeschlossen ist, ob durch ein Leerzeichen, einen Vorschlag oder ein Wischen. |
on_backspace(state) | Wenn die Rücktaste gedrückt wird. Bekommt keinen Text, nur den Zustand. |
on_suggestion(word, state) | Wenn ein Vorschlag angetippt wird. word ist der angetippte Vorschlag. |
on_language(code, state) | Wenn die Tippsprache wechselt. code ist die neue, etwa en oder de. |
on_field(kind, state) | Wenn sich die Tastatur mit einem Feld verbindet. kind ist default, email, url, number, phone, password oder search. |
on_tick(state) | Einmal pro Sekunde, solange die Tastatur zu sehen ist, ob getippt wird oder nicht. Der Hook für alles, was sich von selbst ändern muss: eine Uhr, ein Countdown, eine Rate, die auf null zurückgehen soll, wenn du aufhörst. |
on_swipe(direction, state) | Ein Wischen, das auf einer Buchstabentaste beginnt: "left", "right", "up", "up_left" oder "up_right". Gewischt wird nur, solange Wischtippen aus ist. Fordert der Hook nichts an, bleibt das Wischen ein normaler Tastendruck; fordert er etwas an, wird zuerst der Buchstabe zurückgenommen, auf dem es begann. |
elements(state) | Was dieses Plugin einem eigenen Layout anbietet. Gib element(id, name, icon=, width=)-Einträge zurück oder lass den Hook weg. |
draw(id, state) | Das Gesicht eines Elements als Knotenbaum. Läuft etwa einmal pro Sekunde, solange die Tastatur zu sehen ist. |
effects(state) | Lichteffekte, die dieses Plugin der Effekte-Seite anbietet. Gib light_effect(...)-Einträge zurück oder lass den Hook weg. |
key_styles(state) | Tastenstile für den Theme-Editor. Gib key_style(...)-Einträge zurück; siehe Look-Vorlagen weiter unten. |
themes(state) | Komplette Themes für den Tab Plugins im Theme-Editor. Gib theme(...)-Einträge zurück. |
popups(state) | Stile für das Tasten-Popup. Gib popup_style(...)-Einträge zurück. |
animations(state) | Animationen: eine beliebige Mischung aus entrance(...), press_animation(...), letter_animation(...) und transition(...). |
backgrounds(state) | Animierte Hintergründe. Gib background(...)-Einträge aus particles(...)-Ebenen zurück. |
layouts(state) | Tastaturlayouts. Gib layout(...)-Einträge zurück. |
trails(state) | Wischspuren, die dieses Plugin der Spurauswahl anbietet. Gib trail(...)-Einträge zurück; siehe Look-Vorlagen weiter unten. |
haptics(state) | Eine Haptik pro Taste, als Dict von Tastennamen zu Feels. Wird beim Öffnen der Tastatur und etwa einmal pro Sekunde gelesen. |
hitboxes(state) | Eine Trefferfläche pro Taste, als Dict von Tastennamen zu hitbox(...). Wird im selben Takt wie haptics gelesen. |
on_touch(key, x, y, state) | Nach jedem Tipp: die Taste, an die er ging, und wo auf dieser Taste der Finger landete. x und y laufen von -0.5 bis 0.5, mit 0 in der Mitte. |
suggestions(word, state) | Wörter für die Vorschlagsleiste, während word getippt wird. Läuft jedes Mal, wenn sich die Leiste beruhigt, nicht bei jeder Taste. |
correct(word, fix, state) | Die Leertaste hat word gerade beendet. Gib ein Wort zurück, das stattdessen eingefügt wird, False, um es wie getippt zu lassen, oder None, damit die Korrektur der Tastatur gilt. |
bar_items(state) | Schaltflächen und Regler, die dieses Plugin der oberen Leiste anbietet. Gib bar_button(...)- und bar_knob(...)-Einträge zurück oder lass den Hook weg. |
key_art(state) | Grafik für die Tasten, als Dict von Tastennamen zu Formen. Wird nach jedem Ereignis neu gelesen, das das Plugin hört, sodass ein in einem Hook geänderter Zustand auf den Tasten erscheint. |
events(state) | Die Ereignisnamen, die on_event hören will, einmal beim Laden des Plugins gelesen. Ohne den Hook hört das Plugin alles außer den häufigen Ereignissen. |
on_event(name, info, state) | Ein Hook für alles, was passiert: open, close, word, backspace, suggestion, language, field, shift und plane, dazu die häufigen key, key_down, key_up, predictions und tick, die in events(state) namentlich angefordert werden müssen. |
def on_key(key, state):
if key == " " and setting("language") == "en":
state["spaces"] = state.get("spaces", 0) + 1
return state
def on_close(state):
haptic("light")
return stateDie App und die Tastatur teilen sich eine Kopie des Zustands. Schaltest du in der App einen Schalter ein, ist er beim nächsten Öffnen der Tastatur an. Was die Tastatur zählt, ist da, wenn du die Einstellungen des Plugins das nächste Mal öffnest. Die Tastatur speichert den Zustand beim Schließen, nicht nach jeder Taste.
Aktueller Vorschlag auf der Leertaste
Mit context()["suggestion"] liest du den wichtigsten angezeigten Vorschlag. Abonniere das Ereignis predictions, um Änderungen über info["suggestion"] zu erhalten. Beides erfordert Zugriff auf Eingabedaten. on_suggestion wird erst beim Übernehmen eines Vorschlags aufgerufen. Mit space_text(value or None) setzt du die Beschriftung oder stellst sie wieder her, wenn keine Vorschläge mehr angezeigt werden. Die Funktion der Leertaste bleibt gleich.
def initial():
return {}
def events(state):
return ["predictions"]
def on_open(state):
space_text(context()["suggestion"] or None)
return state
def on_event(name, info, state):
if name == "predictions":
space_text(info["suggestion"] or None)
return state
def on_close(state):
space_text(None)
return stateZustand
Der Zustand ist ein einzelnes Dict. initial() baut es beim ersten Mal; danach bekommt jeder Hook dasselbe Dict, ändert es und gibt es zurück. Es fasst alles, was JSON fassen kann: Zahlen, Strings, Listen, verschachtelte Dicts. Die App speichert es nach jedem Bedienelement, das du benutzt, die Tastatur beim Schließen, und beide lesen dieselbe Datei, sodass sie nie lange auseinanderliegen.
def initial():
return {"session": 0, "total": 0, "longest": ""}
def on_open(state):
state["session"] = 0 # starts over each time the keyboard opens
return state
def on_word(word, state):
state["session"] += 1
state["total"] += 1 # survives, it is saved when the keyboard closes
if len(word) > len(state["longest"]):
state["longest"] = word
return state
def settings(state):
return vstack([
text(f"{state['total']:,} words so far"),
text(f"Longest: {state['longest'] or '...'}", size=13, color="gray"),
button("Start over", "reset", style="destructive"),
])
def on_action(action, value, state):
if action == "reset":
return initial()
return stateDen Zustand zurückzugeben ist die Gewohnheit, die man beibehalten sollte, aber nicht zwingend nötig: Das Dict ist eine Referenz, also funktioniert auch das Ändern an Ort und Stelle. Gib ein anderes Dict zurück, wie oben initial(), und das wird zum Zustand.
Einstellungen und Abschnitte
settings(state) gibt einen Knotenbaum zurück, gebaut mit den Panel-Bausteinen: text, toggle, slider, stepper, segmented, button, row, field und die Layouts. Er erscheint auf der Seite des Plugins im Tab Plugins. Umschließe einen Teil davon mit section(anchor, children, title), dann erscheint dieser Teil auch auf einem von Clinks eigenen Einstellungsbildschirmen, neben der Einstellung, zu der er gehört.
def settings(state):
return vstack([
text("Counts words as you type.", size=13),
section("keys.spacebar", [
toggle("Count on the space bar", state["on"], action="toggle"),
stepper(state["goal"], min=10, max=500, step=10, label="Goal", key="goal"),
], title="Word count"),
])Die Bedienelemente, die ein Plugin am häufigsten braucht. Jedes schreibt entweder seinen neuen Wert unter key in den Zustand oder nennt eine action für on_action, oder beides. Die vollständige Liste der Bausteine, samt Layouts, steht auf der Panel-Seite.
| Baustein | Was er zeichnet |
|---|---|
toggle(label, on=False, key="", action="") | Ein Schalter. key schreibt True oder False in diesen Zustandsschlüssel. |
slider(value, min=0, max=100, step=1, label="", key="", action="") | Ein Regler zwischen min und max. key schreibt die Position in diesen Zustandsschlüssel. |
stepper(value, min=0, max=100, step=1, label="", key="", action="") | Ein Wert mit − und + daneben, zwischen min und max. |
segmented(options, value=None, key="", action="") | Eine Auswahl aus einer Liste. key schreibt die gewählte Option in diesen Zustandsschlüssel. |
field(key, placeholder="", action="", submit="") | Ein Textfeld, gebunden an state[key]. Ein Tippen richtet die Tasten auf diesen Schlüssel; submit benennt den Handler, den die Eingabetaste auslöst. |
button(label, action="", value=None, insert="", set=None, style="plain", icon="", enabled=True) | Eine Schaltfläche. insert tippt ihren Text in das, was du gerade schreibst, set führt Schlüssel in den Zustand ein, und action benennt einen Handler für on_action. style nimmt plain, primary, tinted, quiet oder destructive. |
row(title, subtitle="", detail="", icon="", action="", value=None, insert="") | Eine antippbare Zeile: Titel, zweite Zeile, Symbol und ein Detail rechts. |
text(s, size=17, weight="regular", color="", align="leading", lines=0, mono=False) | Eine Textzeile. weight nimmt regular, medium, semibold, bold, heavy, light oder thin; align nimmt leading, center oder trailing; lines begrenzt die Zeilen beim Umbruch; color nimmt einen Farbnamen oder #RRGGBB. |
Wo Abschnitte landen
Ein Anker ist die ID einer Karte auf einem der eigenen Einstellungsbildschirme von Clink. Nenne ihn in section(), und die Bedienelemente des Plugins werden direkt unter dieser Karte gezeichnet, mit dem Namen des Plugins darüber. Am einfachsten findest du einen so: Öffne in der App Mehr > Entwickler und schalte IDs anzeigen ein. Jede Karte bekommt ein kleines Info-Symbol, das ihre ID nennt und beim Tippen kopiert; Seiten zeigen ihre in der Titelleiste.
Jeder Anker, den ein Abschnitt nennen kann
analytics.heatmapanalytics.privacyanalytics.trendsanalytics.typing-testautomations.rulesgestures.accentsgestures.cursorgestures.deletegestures.generalgestures.suggestionsgestures.swipehaptics.feelkeys.adaptivekeys.faceskeys.hitboxeskeys.hitmapkeys.long-presskeys.numberrowkeys.onehandedkeys.roundnesskeys.sizekeys.spacebarkeys.spacingkeys.splitlanguages.applanguages.customlanguages.managelanguages.packslanguages.switchlanguages.typinglayout.arrangelayout.arrangementlayout.buildlayout.longpresslayout.presetslayout.topbarmotion.deletemotion.entrancemotion.glowmotion.key-pressmotion.key-responsemotion.lettersmotion.space-responsemotion.transitionpopups.stylesound.keysoundstext.automationtext.contenttext.correctionstext.historytext.punctuationtext.speedtext.suggestionstext.symbolsthemes.backgroundthemes.canvasthemes.theme
keys.spacebar hat einen eigenen Platz, auf dem Leertasten-Bildschirm selbst. Ein Abschnitt mit einem Anker, der nicht in dieser Liste steht, erscheint trotzdem auf der Seite des Plugins, ein Tippfehler kostet dich also eine Karte, nicht das Plugin.
Übernahmen
Wenn ein Plugin eine der eigenen Einstellungen von Clink steuert, sollte die Person das sehen, und die beiden sollten sich nicht streiten. claim(control) sagt, dass das Plugin sie besitzt: Die App nennt das Plugin auf der Karte dieser Einstellung, und das Feld für den Leertastentext ist gesperrt, solange es gehalten wird. release(control) gibt sie zurück. Übernimm in der on_action, die dein Feature einschaltet, gib in derjenigen frei, die es ausschaltet, und setze den Wert gleichzeitig auf None oder seinen alten Wert zurück. Ein in der Liste ausgeschaltetes Plugin gibt alles frei, was es hielt.
def on_action(action, value, state):
if action == "toggle":
state["on"] = value
if value:
claim("spacebar.text")
space_text("...")
else:
release("spacebar.text")
space_text(None) # hand the person's own text back
return stateZwei Plugins können dasselbe Bedienelement übernehmen; die App nennt das, das zuletzt übernommen hat. Übernahmen werden von der App aufgezeichnet, also übernimm in on_action, das dort läuft. Eine Übernahme aus der Tastatur heraus wird nicht gemerkt.
Befehle und Lesezugriffe
Plugins können jeden Panel-Befehl verwenden, dazu sechs eigene Befehle und zwei eigene Lesezugriffe. Befehle werden gesammelt und erst angewendet, nachdem deine Funktion zurückgekehrt ist, damit sich auf der Tastatur nichts mitten in einem Aufruf ändert. Lesezugriffe liefern die Werte, wie sie direkt vor dem Aufruf waren.
| Aufruf | Was er tut |
|---|---|
space_text(text) | Zeigt eine Beschriftung auf der Leertaste, bis zu 32 Zeichen. Übergib None, um zum eigenen Leertastentext der Person zurückzukehren. |
space_language_text(text) | Überschreibt das Sprachabzeichen in der Ecke der Leertaste, für sich allein und auch dann, wenn das native Abzeichen aus ist. Eine Zeile, bis zu 12 Zeichen; "" blendet es aus, None gibt den nativen Text zurück. |
space_language_flag(language) | Setzt eine Flagge auf dieses Abzeichen, für eine Sprach-id wie en_GB, aus der mitgelieferten Flaggengrafik. None gibt das native Verhalten zurück. |
space_language_emoji(language) | Dasselbe Abzeichen als Flaggen-Emoji der Region. Die drei Abzeichenbefehle teilen sich einen Platz, ein eingeschaltetes Abzeichen-Plugin schaltet die anderen also aus. |
set_setting(name, value) | Ändert eine von Clinks Einstellungen über ihren Namen auf einen Wert der richtigen Art: einen Wahrheitswert, eine Zahl im erlaubten Bereich, eine ihrer Optionen oder Text. Ein unbekannter Name löst KeyError aus. Ein Wert der falschen Art oder außerhalb des Bereichs wird nicht übernommen, und die Konsole des Editors zeigt, was die Einstellung erwartet. |
claim(control) | Übernimmt eine von Clinks eigenen Einstellungen. Ihre Karte in der App nennt das Plugin, das sie hält. |
release(control) | Gibt die Einstellung zurück. Wird ein Plugin ausgeschaltet, gibt es alles frei, was es übernommen hat. |
suggest(words) | Setzt bis zu zehn eigene Wörter an den Anfang der Vorschlagsleiste. Sie bleiben, bis eines angetippt, Löschen gedrückt wird oder das Feld wechselt; suggest([]) entfernt sie früher. Ein Tipp tippt das Wort. |
banner(text) | Zeigt kurz eine Nachricht auf der Tastatur. Für einen Hinweis, nicht für ein Gespräch. |
press(key) | Löst eine der Tasten der Tastatur aus, als wäre sie getippt worden: "space" oder "delete". Jeder andere Name löst ValueError aus. |
pick_suggestion(slot) | Übernimmt den Vorschlag im Teil "left", "center" oder "right" der Leiste, genau wie ein Tippen darauf. Bei einer gescrollten Leiste zählt, was zu sehen ist. In on_swipe wird aus der Leiste gewählt, wie sie beim Aufsetzen des Fingers war. |
stats() | Gibt ein Dict mit wpm, peak_wpm, keystrokes, words und streak zurück. wpm wird live aktualisiert, solange Plugins an sind. Die Summen kommen aus Analyse und werden nicht mehr aktualisiert, wenn Analyse aus ist. |
setting(name) | Liest eine der unten aufgeführten Einstellungen per Name. Jeder andere Name löst einen KeyError aus. |
context() | Dieselbe Momentaufnahme, die ein Panel liest, für ein Plugin zusätzlich um suggestion, shift (off, on oder locked) und plane erweitert. Die Dokumentschlüssel füllt der Zugriff auf Tippdaten; shift bleibt auch ohne ihn lesbar. |
| Schlüssel | Was drin steckt |
|---|---|
stats()["wpm"] | Wörter pro Minute über die letzten Sekunden, gezählt aus den Zeichen, die im Feld ankommen. Die Zahl erscheint ein bis zwei Sekunden nach dem Beginn, fällt bei einer Pause und erreicht 0, wenn du aufhörst. Löschen zählt nie dazu. |
stats()["peak_wpm"] | Die beste jemals von der Statistik erfasste Rate. |
stats()["keystrokes"] | Gedrückte Tasten insgesamt, wie die Statistik sie zählt. |
stats()["words"] | Abgeschlossene Wörter insgesamt. |
stats()["streak"] | Tage in Folge mit Tippen, bis heute. |
setting(name) und set_setting(name, value) teilen sich eine Liste von Namen, und claim(control) nimmt ebenfalls jeden davon. Booleans lesen sich als True oder False, Zahlen als Zahlen, Auswahlen als ihre ID. Die Liste ist absichtlich lang: Ein Plugin kann auf fast alles reagieren oder es steuern, was eine Person in der App einstellen kann.
Namen, die setting() akzeptiert
analyticsemoji.skin_toneemoji.trailing_spacegestures.cursorgestures.cursor_stylegestures.highlight_shiftgestures.plane_slidegestures.predictive_flickgestures.quick_accentgestures.swipegestures.swipe_deletegestures.swipe_multi_wordgestures.swipe_space_commitgestures.swipe_two_thumbgestures.trailgestures.trail_stylegestures.trail_widthhaptics.enabledhaptics.intensityhaptics.sharpnesskeys.accentskeys.glyph_scalekeys.heightkeys.long_press_hintkeys.popup_stylekeys.popupskeys.radiuskeys.row_spacingkeys.spacingkeys.uppercasekeys.widthlanguagelanguage.bar_keylanguage.bar_key_stylelanguage.modelayoutlayout.dismiss_shortcutlayout.number_rowlayout.number_row_scalelayout.one_handedlayout.one_handed_shortcutlayout.one_handed_sidelayout.one_handed_widthlayout.splitlayout.split_gaplayout.split_number_rowlayout.split_shortcutlayout.split_space_barpet.cornerpet.enabledpet.speciessettings.accentHoldDelaysettings.accentMoveCancelsettings.activateWithIconsettings.adaptiveGrowsettings.adaptiveHitboxessettings.adaptivePredictAtWordStartsettings.adaptivePredictionWeightsettings.adaptiveShrinksettings.adaptiveSpacesettings.aiAutocorrectsettings.aiCompletionssettings.aiExtensionEnabledsettings.aiSearchsettings.aiToolsColorOverridessettings.aiToolsDiffStylesettings.aiToolsDisabledsettings.aiToolsLayoutStylesettings.aiToolsOrdersettings.aiToolsPromptOverridessettings.aiTranslatesettings.alternatingSplitsettings.arabicIndicNumeralssettings.backgroundEffectOverridesettings.clipboardCloseOnPastesettings.clipboardDeleteOnPastesettings.clipboardIgnoreImagessettings.clipboardIgnorePinsOnDeletesettings.clipboardStylesettings.cursorActivationHapticsettings.cursorLineStridesettings.cursorStepHapticsettings.customPanelsStandalonesettings.customPetIDsettings.deleteWordSwipeEngagesettings.deleteWordSwipeStridesettings.dictationAssistsettings.dictationAssistCustomsettings.dictationAssistLevelsettings.dictationColorSchemesettings.dictationGlassCapsulesettings.dictationSmartActionssettings.dictationStylesettings.dictationVisualStylesettings.dragUpThresholdsettings.emojiCategoryOrdersettings.emojiCellSpacingsettings.emojiColumnCountsettings.emojiCrossAxisSwitchesTabsettings.emojiCustomSetssettings.emojiGlyphScalesettings.emojiHiddenCategoriessettings.emojiHiddenFromPanelssettings.emojiRecentsCapsettings.emojiRecentsSortsettings.emojiRememberCategorysettings.emojiRowCountsettings.emojiScrollDirectionsettings.emojiSearchSlotsettings.emojiShowABCKeysettings.emojiShowBackspaceKeysettings.emojiStartCategoryIDsettings.emojiTabBarSlotsettings.emojiTabIconStylesettings.emojiToneHoldDelaysettings.extensionOrdersettings.extraTopBarssettings.formFreeCornerssettings.formLayoutEnabledsettings.gifShareAsLinksettings.glassPerRowMergesettings.glassReleaseResponsesettings.gridSwitchAnimationsettings.gridSwitchDurationsettings.handwritingInkColorsettings.handwritingInkGlowsettings.handwritingInkStylesettings.handwritingInkWidthsettings.hitboxScalesettings.iconPickerStylesettings.keyBloomScalesettings.keyLightingsettings.keyPressGlowsettings.keyPressInstantsettings.keyPressLingersettings.keySpringDampingsettings.keySpringResponsesettings.keyboardBottomPaddingsettings.keyboardLanguagessettings.keyboardTopPaddingsettings.longPressGlyphScalesettings.minPressVisiblesettings.notepadBrowseStylesettings.notepadModesettings.numberRowFontSizesettings.numberRowLeadingKeyssettings.numberRowTrailingKeyssettings.oneHandedCustomKeyssettings.oneHandedExtraKeyssettings.panelButtonHitboxScalesettings.persistentLeadingKeyssettings.persistentTrailingKeyssettings.petIdleMotionsettings.petScalesettings.pinyinFuzzyEnabledsettings.pluginLookssettings.popupSpringDampingsettings.popupSpringResponsesettings.predictiveFlickSuggestionPositionsettings.predictiveFlickSuggestionsSeparatesettings.reduceEffectsOnLowPowersettings.repeatAccelStepsettings.repeatHoldDelaysettings.repeatInitialIntervalsettings.repeatMinIntervalsettings.replacementsLayoutsettings.rowInsetssettings.secondaryNeuralModelsEnabledsettings.separateActivationsettings.separateLanguageLayoutssettings.showCaptureOverlaysettings.showCorrectionFieldsettings.showHitboxOverlaysettings.showIconsBeforeTypingsettings.showRecentEmojisettings.showTouchHeatmapsettings.showTouchSurfaceBoundssettings.showTouchTelemetryOverlaysettings.slideUpPickerStylesettings.solidPopupOpacitysettings.soundVoicesettings.spaceBloomScalesettings.spaceCursorActivationDelaysettings.spaceCursorDragScalesettings.spaceCursorStridesettings.spaceLeanMultipliersettings.spaceSpringDampingsettings.spaceSpringResponsesettings.spatialBiasEnabledsettings.spatialBiasGainsettings.splitIndicessettings.splitOtherPlanessettings.stickerGridSnapsettings.stickerPlacementssettings.suggestionDebounceDelaysettings.suggestionHitboxScalesettings.suggestionSegmentLiquidGlasssettings.suggestionSegmentStylesettings.suggestionSegmentsFollowThemesettings.suggestionSeparatorColorsettings.suggestionSeparatorStylesettings.suggestionTopPaddingsettings.swipeKeyMorphsettings.swipeMorphRadiussettings.swipeMorphStrengthsettings.swipeTrailEndWidthsettings.swipeTrailMaxLengthsettings.swipeTrailStartWidthsettings.swipeTrailTrimsettings.toolsButtonStylesettings.toolsHiddenFromPanelssettings.topBarItemssettings.topBarOrdersettings.translateLanguageOrdersettings.translateLanguagesDisabledsettings.translateStylesettings.translateTonesettings.vietnameseInputMethodsound.enabledsound.packsound.volumespacebar.cornerspacebar.language_codespacebar.sizespacebar.textstickers.enabledtext.arithmetictext.auto_capitalizetext.auto_punctuationtext.autocorrecttext.autocorrect_everywheretext.contactstext.conversionstext.double_space_periodtext.learningtext.punctuation_spacingtext.return_to_letterstext.revert_on_deletetext.smart_quotestext.suggestionstext.suggestions_animationtext.suggestions_heighttext.suggestions_scrolltext.suggestions_stylethemetheme.backgroundtheme.darktheme.delete_glyphtheme.entrancetheme.glyph_presstheme.lighttheme.match_systemtheme.press_styletheme.reactive_backgroundtools.calculatortools.clingtools.clipboardtools.conversiontools.dictationtools.dictionarytools.emojitools.giftools.handwritingtools.layout_switchertools.notepadtools.plugin_switchertools.profilestools.replacementstools.textfxtools.theme_switchertools.translate
Befehle
insert()backspace()delete_word()replace()move_cursor()copy()close()haptic()toast()
Wie das view() eines Panels sollte settings() nur Bedienelemente beschreiben. Befehle, die dort aufgerufen werden, werden ignoriert und in der Konsole protokolliert.
Beispiele
Fünf kleine Plugins, jedes vollständig. Füge eines in ein neues Plugin ein, speichere, und es läuft.
def initial():
return {"on": True}
def settings(state):
return section("keys.spacebar", [
toggle("Language on the space bar", state["on"], action="toggle"),
], title="Language badge")
def on_action(action, value, state):
if action != "toggle":
return state
state["on"] = value
if value:
claim("spacebar.text")
space_text(setting("language").upper())
else:
release("spacebar.text")
space_text(None)
return state
def on_open(state):
if state["on"]:
space_text(setting("language").upper())
return state
def on_language(code, state):
if state["on"]:
space_text(code.upper())
return stateSHORTCUTS = {"omw": "on my way", "brb": "be right back", "ty": "thank you"}
def on_key(key, state):
if key != " ":
return state
words = context()["before"].split()
if words and words[-1] in SHORTCUTS:
# the space is already in the text, so it goes too and comes back after
replace(len(words[-1]) + 1, SHORTCUTS[words[-1]] + " ")
return statedef initial():
return {"goal": 200, "count": 0}
def settings(state):
return vstack([
stepper(state["goal"], min=50, max=2000, step=50, label="Words per session", key="goal"),
progress(state["count"], total=state["goal"], label=f"{state['count']} of {state['goal']}"),
])
def on_open(state):
state["count"] = 0
return state
def on_word(word, state):
state["count"] += 1
if state["count"] == state["goal"]:
haptic("medium")
banner("Goal reached")
return statedef initial():
return {"muted": False, "was_on": True}
def on_field(kind, state):
# a password or a number field is not a place for key sounds
quiet = kind in ("password", "number", "phone")
if quiet and not state["muted"]:
state["was_on"] = setting("sound.enabled")
state["muted"] = True
set_setting("sound.enabled", False)
elif not quiet and state["muted"]:
state["muted"] = False
if state["was_on"]:
set_setting("sound.enabled", True)
return stateNAMES = ["sam", "ana", "team"]
def on_key(key, state):
typing = context()["before"].split(" ")[-1]
if typing.startswith("@"):
start = typing[1:].lower()
suggest([n for n in NAMES if n.startswith(start)])
state["showing"] = True
elif state.get("showing"):
suggest([]) # the mention is over, give the bar back
state["showing"] = False
return statedef on_swipe(direction, state):
if direction == "left":
delete_word()
elif direction == "right":
press("space") # the space bar's own path, so it autocorrects
elif direction == "up":
pick_suggestion("center")
elif direction == "up_left":
pick_suggestion("left")
elif direction == "up_right":
pick_suggestion("right")
return stateElemente in einem Layout
Ein eigenes Layout besteht aus Tasten und Elementen: eine Leiste zuletzt benutzter Emoji, eine Ziffernreihe, ein Cursor-Pad. Ein Plugin kann eigene hinzufügen. elements(state) sagt, was es anbietet, und draw(id, state) zeichnet eines davon. So kann eine Taste alles zeigen, was das Plugin berechnen kann: eine Tempokurve, eine Uhr, einen Countdown, eine eigene Batterieanzeige.
Zwei Hooks bauen eines. elements(state) listet auf, was du anbietest, einen Eintrag pro Element, und die App liest das für die Palette des Layout-Editors. draw(id, state) bekommt eine dieser IDs und gibt zurück, was gezeichnet werden soll. Ein Plugin darf mehrere anbieten; draw wird für jedes einzeln gefragt.
| Aufruf | Was er tut |
|---|---|
element(id, name, icon="", width=2) | Ein angebotenes Element. width zählt in Tastenbreiten, wie ein Layout sie zählt, und icon ist das SF-Symbol, das der Editor in seiner Palette zeigt. |
sparkline(values, min=None, max=None, fill=False) | Eine Linie durch eine Zahlenreihe, die den verfügbaren Platz füllt. Lässt du min und max weg, passt sie sich ihren Werten an. Nur ein Plugin kann sie zeichnen, und sie ist für eine Taste gemacht. |
POINTS = 30
def initial():
return {"history": []}
def elements(state):
return [element("sparkline", "WPM sparkline", icon="waveform.path.ecg", width=3)]
def on_tick(state):
history = state["history"]
history.append(stats()["wpm"])
state["history"] = history[-POINTS:]
return state
def draw(id, state):
rate = state["history"][-1] if state["history"] else 0
return hstack([
sparkline(state["history"], min=0, max=max(60, max(state["history"] or [0])), fill=True),
text(f"{rate}", size=12, weight="semibold"),
], spacing=5, align="center")Ein Gesicht ist ein Knotenbaum wie eine Einstellungsseite, aber eine Taste ist keine Einstellungsseite: nur die zeichnenden Knoten werden verwendet, alles Antippbare wird ignoriert. Die Taste gehört bereits dem Layout, also ist darin kein Platz für eine Schaltfläche.
WAS EIN GESICHT ZEICHNEN KANN
sparklinetextbadgeiconprogresshstackvstackspacerdivider
Bemiss es für eine Taste. width zählt in Tastenbreiten: 1 ist ein Buchstabe, 3 etwa ein Drittel einer Reihe, und die Person kann es danach ändern. Text bekommt eine Zeile und schrumpft, damit er passt; Farben folgen standardmäßig der Textfarbe der Taste, sodass ein Element zu den Tasten ringsum passt, wenn du nichts anderes verlangst. Eine Sparkline behält ihre letzten 120 Punkte, mehr als eine Taste zeigen kann.
import time
def elements(state):
return [element("clock", "Clock", icon="clock", width=2)]
def draw(id, state):
return text(time.format("HH:mm"), size=15, weight="semibold")GOAL = 200
def initial():
return {"words": 0}
def elements(state):
return [element("goal", "Word goal", icon="target", width=2)]
def on_open(state):
state["words"] = 0
return state
def on_word(word, state):
state["words"] += 1
return state
def draw(id, state):
return vstack([
text(f"{state['words']}/{GOAL}", size=11),
progress(state["words"], total=GOAL),
], spacing=2)Elemente leben in eigenen Layouts, also gibt es zuerst ein Layout zu bauen. Vorlagen können keines aufnehmen: Eine Vorlage in ein eigenes Layout zu kopieren, ist das Erste, was der Editor anbietet.
- Installiere das Plugin und schalte es ein. Seine Elemente erscheinen im selben Moment.
- Öffne Layout, erstelle oder kopiere ein eigenes Layout und geh zu Anordnen.
- Tippe auf Element, wähle das Element des Plugins aus der Leiste und stelle seine Breite ein.
Die Vorschau im Editor zeichnet jedes Element, das das Skript anbietet, etwa so groß wie die Taste, auf der es sitzen wird, unter der Leertasten-Attrappe. Einmal speichern und platzieren, und die Tastatur zeichnet dasselbe.
Ein Element zeigt nur, was das Plugin zeichnet: Ein Tipp darauf macht nichts, weil die Taste schon zum Layout gehört. Ein Layout behält das Element auch dann, wenn sein Plugin aus oder deinstalliert ist, und die Taste füllt sich wieder, sobald es zurück ist.
Schaltflächen und Regler in der oberen Leiste
Die Leiste über den Tasten wird unter Layout > Obere Leiste zusammengestellt, und ein Plugin kann eigene Bedienelemente dafür anbieten. bar_items(state) gibt sie zurück: bar_button(...) zum Antippen, bar_knob(...) zum Drehen. Die Liste wird beim Öffnen der Tastatur und nach jedem Tippen erneut gelesen, ein Element kann also mit dem Zustand des Plugins kommen und gehen.
| Aufruf | Was er tut |
|---|---|
bar_items(state) | Alles, was dieses Plugin der Leiste anbietet, als Liste. Eine id muss nur innerhalb des Plugins eindeutig sein; die Leiste merkt sie sich neben der id des Plugins. Bei doppelter id gilt die erste, und ein Element, das der Hook nicht mehr zurückgibt, wird nicht mehr gezeichnet. |
bar_button(id, name, icon="", title="") | Eine Schaltfläche. name steht im Editor und wird von VoiceOver vorgelesen. icon ist ein SF Symbol, title bis zu 12 Zeichen Beschriftung daneben: mit Icon und ohne title steht das Icon allein, ohne Icon steht der Text allein. Eine Schaltfläche hat weder Wert noch Bereich. Ein Tippen ruft on_action(id, None, state) auf, und alles, was sie tut, passiert dort. |
bar_knob(id, name, icon="", min=0, max=1, step=0, value=0, setting=None, art=None, rotor=None) | Ein Regler. min und max sind sein Bereich, step rastet ihn ein, sobald es über null liegt, und value ist der Startwert. setting= bindet ihn stattdessen an einen Zahlenregler der App, der dann Bereich und Stand vorgibt. art= und rotor= zeichnen ihn in Python. |
def initial():
return {"width": 2.0}
def bar_items(state):
return [
bar_button("sig", "Sign off", icon="signature", title="Sign off"),
bar_knob("volume", "Key volume", icon="speaker.wave.2", setting="sound.volume"),
bar_knob("width", "Trail width", icon="scribble", min=1, max=6, step=0.5, value=state["width"]),
]
def on_action(action, value, state):
if action == "sig":
insert(" sent from Clink")
elif action == "width":
state["width"] = value
return stateBeides kommt über on_action(action, value, state) zurück, adressiert über die id des Elements, sodass Leistenelemente und Einstellungen eines Plugins denselben Hook teilen. Ein Tippen schickt None. Ein Regler schickt seinen Wert bei jeder Raste, solange der Finger unten ist, und noch einmal beim Loslassen, ein Plugin kann die Bewegung also mitlesen oder auf den letzten Wert warten.
Ein Regler mit setting= dreht an einem Wert, den die App schon hat, benannt aus der Liste unter Befehle und Lesezugriffe weiter oben, etwa sound.volume oder haptics.intensity. Ein Name, den die App nicht kennt, ist ein KeyError in der Konsole. Bereich und Startstand kommen von diesem Bedienelement, die Kopie der Tastatur ändert sich schon beim Ziehen, sodass der nächste Tastendruck bereits auf dem neuen Stand ist, und gespeichert wird beim Loslassen. Ein Lautstärke- oder Haptikregler, der aus dem Nichts aufgedreht wird, schaltet Ton oder Haptik für die Bewegung wieder ein, damit die Rasten auf dem Weg nach oben zu hören und zu spüren sind; diesen Schalter speichert das Plugin selbst mit set_setting in on_action.
Ein Regler lässt sich in Python zeichnen. art= ist der Teil, der stehen bleibt, Fassung oder Körper, und rotor= ist die Scheibe, die sich dreht, von -135 Grad am Minimum bis +135 Grad am Maximum. Beide nehmen dieselben Formen, Farben, Verläufe und Schatten wie Tastengrafik, je bis zu sechzehn Formen, und unit="key" skaliert die Koordinaten auf das quadratische Zifferblatt, wo ein Zeiger bei negativem y nach oben zeigt. Die Grafik bekommt die Textfarbe der Leiste als "text" und die Akzentfarbe des Themes als "accent". Lässt du beides weg, ist der Regler ein Ring mit dem Icon darin; im Editor lässt sich einem Regler in der Leiste auch jede eingebaute Optik geben.
Von allein landet nichts in der Leiste. Ein Element wird von Hand platziert, genau wie das Menü und die Vorschläge.
- Installiere das Plugin und schalte es ein. Seine Leistenelemente erscheinen sofort.
- Öffne Layout > Obere Leiste.
- Füge die Schaltfläche oder den Regler des Plugins hinzu und zieh sie an ihren Platz. Für einen Regler lässt sich außerdem eine Optik wählen.
Die Builder übergehen ein Schlüsselwort, das sie nicht kennen, ohne Fehler. setting=, min=, max=, step=, value=, art= und rotor= gehören allein zu bar_knob; ein bar_button, dem man eines mitgibt, wird trotzdem als schlichte Schaltfläche gebaut, ohne einen Hinweis: Die Arbeit einer Schaltfläche gehört in on_action. Umgekehrt gehört title= zur Schaltfläche, und ein Regler übergeht es.
Lichteffekte
Ein Plugin kann eigene Tastenbeleuchtung beisteuern. effects(state) gibt light_effect(...)-Einträge zurück, und jeder erscheint in der App unter Effekte > Aus Plugins, neben den eingebauten Stilen und den Effekten, die Leute selbst bauen. Ein Effekt ist Daten, keine Zeichnung: ein Stapel Ebenen, den die Tastatur selbst animiert. Im Skript läuft also nichts pro Bild, und Tastenflächen, Buchstaben, Glühen, das Aufleuchten beim Anschlag und der Ruhemodus funktionieren wie bei den eingebauten Stilen.
| Aufruf | Was er tut |
|---|---|
light_effect(id, name, layers=[...], colors=[], icon="") | Ein Effekt. id muss nur innerhalb des Plugins eindeutig sein. layers ist eine Liste von light_layer(...), angewendet von oben nach unten. colors sind bis zu acht "#rrggbb"-Strings, durch die die Farbe läuft; ohne sie folgt der Effekt der Farbeinstellung der Person. |
light_layer(pattern, ...) | Eine Ebene: ein Muster aus der Liste unten. Jedes Schlüsselwort ist optional. |
COLORS = ["#00e5ff", "#7c4dff", "#ff4081"]
def effects(state):
return [
light_effect("tide", "Tide", colors=COLORS, layers=[
light_layer("solid", low=0.2, high=0.2),
light_layer("wave", moves="both", speed=0.8, size=1.5,
direction="up"),
]),
]Muster
solidpulsewavegradienttwinklesweeprainflickerchecker
| Schlüssel | Was er tut |
|---|---|
moves="light" | Was das Muster verändert: "light" für die Helligkeit, "color" oder "both". |
mix="add" | Wie seine Helligkeit auf die oberen Ebenen trifft: "add", "max" behält das Hellere, "multiply" dunkelt sie wie eine Maske ab. |
shape="smooth" | Das Auf und Ab von pulse, wave und gradient: "smooth", "ramp", "step" oder "spike". |
direction="right" | In welche Richtung wave, gradient, sweep und rain laufen: "right", "left", "down", "up" oder "out" von der Mitte aus. |
speed=1 | Von 0 bis 4. Bei 0 steht das Muster still. |
size=1 | Von 0,25 bis 4: wie oft sich das Muster über die Tastatur wiederholt. Bei twinkle bestimmt es, wie viele Tasten leuchten, bei sweep die Länge des Schweifs. |
low=0, high=1 | Die Helligkeit, zwischen der das Muster läuft, jeweils von 0 bis 1. Liegt low über high, kehrt es sich um. |
color_span=1, color_offset=0 | Wie weit das Muster durch die Farben wandert und wo es beginnt, jeweils von 0 bis 1. |
def effects(state):
return [
light_effect("scanner", "Scanner", layers=[
light_layer("wave", moves="color", speed=0.3),
light_layer("sweep", mix="multiply", speed=1.2, size=1.5),
]),
]Wer einen Effekt auswählt, kopiert ihn in die eigenen Einstellungen, also läuft er auch mit ausgeschaltetem Plugin weiter. Solange das Plugin an ist und einer seiner Effekte läuft, liest die Tastatur effects(state) beim Öffnen und danach etwa einmal pro Sekunde neu und übernimmt, was sich geändert hat. So folgt ein Effekt dem Zustand, der Uhrzeit oder stats().
def effects(state):
# Rounded, so the effect only changes when the pace really does.
tempo = round(0.3 + min(stats()["wpm"], 120) / 40, 1)
return [
light_effect("tempo", "Tempo", colors=["#ff3d7f", "#ffb000"], layers=[
light_layer("wave", moves="both", speed=tempo, low=0.15),
]),
]Der Button effects im Editor listet, was das Skript anbietet. Um einen Effekt in Bewegung zu sehen, sichere das Plugin, wähle den Effekt unter Effekte > Aus Plugins und schau auf die Tastatur oben auf dieser Seite.
Runde alles, was zappelt, etwa eine Tipprate. Ein Effekt, der jede Sekunde anders zurückkommt, wird jede Sekunde für nichts ausgetauscht. Ein unbekanntes Muster oder Schlüsselwort ist ein ValueError in der Editor-Konsole, und ein Effekt ohne Ebenen wird weggelassen.
Look-Vorlagen
Sieben Hooks bieten der App etwas zur Auswahl an: key_styles, themes, popups, animations, backgrounds, trails und layouts. Jeder gibt Einträge zurück, die mit den Aufrufen unten gebaut werden, und sie erscheinen unter Aus Plugins neben den eingebauten Optionen. Nur Themes haben im Theme-Editor einen eigenen Tab Plugins. Eine Look-Vorlage besteht aus Zahlen und Wörtern, nicht aus Zeichencode, also läuft im Skript nichts pro Frame. Wer einen auswählt, kopiert ihn in die eigenen Einstellungen, also läuft er auch mit ausgeschaltetem Plugin weiter, und wer danach eine eingebaute Option wählt, legt ihn beiseite.
| Aufruf | Was er tut |
|---|---|
key_style(id, name, material=, variant=, shape=, shadow=, cap=cap(...)) | Ein Tastenstil, angewendet auf das Theme, das gerade im Theme-Editor offen ist, über dessen Stil-Karte. Nur die genannten Felder ändern sich: material, variant, shape, glass, fan, die mechanischen inner_radius, face_inset, edges, raised und light_angle, shadow (0 ist flach) und outline. Die Farben bleiben die des Themes. |
cap(outline="round", corner=None, travel=2, layers=[cap_layer(...)]) | Eine gemalte Tastenkappe für einen Tastenstil, gestapelt aus cap_layer(...)-Einträgen. outline ist round oder rect, corner überschreibt den Radius, und travel sagt, wie tief die Kappe beim Drücken einsinkt. |
cap_layer(kind, paint, inset=0, x=0, y=0, blur=0, fade=None, when=[...]) | Eine Ebene einer gemalten Kappe. kind ist fill, stroke oder inner, paint nimmt die Kappenfarben-Grammatik, ein color(...) oder ein gradient(...), und when beschränkt die Ebene auf pale, dark, pressed, resting und highlighted. Mit moves=False bleibt die Ebene stehen, während die Kappe einsinkt. |
theme(id, name, background=, keys=, key_text=, style=key_style(...), ...) | Ein komplettes Theme, im Tab Plugins des Theme-Editors. background, keys und key_text sind Pflicht, als "#rrggbb"-Farben; special, special_text, accent, background_bottom (ein Verlauf nach unten), dark, font und weight sind optional. style=key_style(...) legt die Oberfläche fest. Wer es auswählt, installiert es als eigenes Theme. |
popup_style(id, name, shape="tile", width=48, height=56, lift=30, ...) | Die Blase über einer gedrückten Taste, unter Aussehen > Popups. shape ist "tile", "round" oder "balloon"; width, height, lift und font_size sind in Punkten, und response und damping bestimmen die Feder. |
entrance(id, name, opacity=0, x=0, y=0, scale=1, tilt=0, spin=0, ...) | Wie die Tastatur erscheint, unter Aussehen > Auftritt. opacity, x, y, scale, tilt und spin sind der Startpunkt; mit response und damping federt sie in die Ruhelage. |
press_animation(id, name, scale=, x=0, y=0, rotation=0, ...) | Die Form einer gedrückten Taste, unter Reaktionen > Geometrie: scale (oder scale_x und scale_y), x, y und rotation bei vollem Druck. |
letter_animation(id, name, scale=, x=0, y=0, rotation=0, anchor="center") | Der kurze Effekt, den ein Buchstabe bei jedem Tipp abspielt, unter Reaktionen > Buchstaben: dieselben Zahlen am Höhepunkt, dazu anchor. |
transition(id, name, x=0, y=0, scale=1, tilt=0, fade=True, duration=None) | Der Wechsel zwischen Buchstaben, 123 und #+=, unter Aussehen > Übergang. x und y sind, wie weit die alten Tasten wandern, als Anteil der Tastatur, und die neuen kommen gespiegelt herein. Außerdem nimmt er scale, tilt, fade und duration. |
background(id, name, layers=[...], colors=[]) | Ein animierter Hintergrund, unter Aussehen > Hintergrund: bis zu vier particles(...)-Ebenen und bis zu acht "#rrggbb"-Farben. |
particles(shape="glow", count=40, size=4, speed=20, direction="none", ...) | Eine Ebene eines Hintergrunds. shape ist "dot", "glow", "streak", "ring" oder "square"; count, size, speed, direction, spread, gravity, wobble, life, twinkle und opacity formen die Bewegung, und burst schleudert Partikel aus jeder gedrückten Taste. |
layout(id, name, rows=[...], left=[], right=[]) | Ein Layout, unter Layout > Anordnung. Jede Reihe ist eine Liste von Tasten: ein String ist ein Buchstabe, layout_key(...) alles andere. left und right setzen bis zu drei Tasten neben die Leertaste. Beim Auswählen wird ein ganz normales eigenes Layout installiert. |
layout_key(glyph, action="insert", width=1) | Eine Taste, die kein einfacher Buchstabe ist. action ist eines von insert, spacer, shift, delete, space, return, numbers, emoji, globe, tab, left, right, undo, redo oder dismiss, und width ist in Tasten. |
trail(id, name, layers=[...], colors=[]) | Eine Wischspur. layers sind Einträge aus trail_line, trail_stamps und trail_head, der Reihe nach gezeichnet, und colors ist die Palette, die sie ansprechen. |
trail_line(width=1, tail_width=1, color=-1, glow=0, dash=0, gap=0, band=0, flow=0) | Der Strich entlang des Wischens. tail_width verjüngt das alte Ende, color=-1 blendet die ganze Palette über die Linie, und dash, gap, band und flow brechen sie auf oder bringen sie in Bewegung. |
trail_stamps(shape="dot", size=1, spacing=14, scatter=0, spin=0, twinkle=0, color=-1) | Formen, die entlang des Wischens fallen: dot, ring, square, diamond, star, spark oder heart. spacing ist ihr Abstand in Punkten, scatter wirft sie von der Linie, spin dreht sie und twinkle lässt sie auf- und abblenden. |
trail_head(shape="dot", size=1.5, pulse=0, opacity=1, color=-1, glow=0) | Das Zeichen an der Fingerspitze. pulse lässt es atmen, glow streut Licht darum herum. |
def animations(state):
return [
entrance("swoop", "Swoop", y=90, scale=0.94, tilt=-20,
response=0.5, damping=0.72),
press_animation("dip", "Dip", scale=0.93, y=2),
letter_animation("bounce", "Bounce", y=-7, scale=1.12,
anchor="bottom"),
transition("glide", "Glide", x=0.3, scale=0.96),
]def backgrounds(state):
return [
background("snowfall", "Snowfall", colors=["#ffffff", "#cfe8ff"], layers=[
particles(shape="dot", count=70, size=2.2, speed=28,
direction="down", spread=12, wobble=10, life=9),
particles(shape="glow", count=0, size=3, life=1,
burst=8, burst_speed=90),
]),
]Gib in trails(state) trail-Knoten zurück. Jede Spur hat eine colors-Palette und Ebenen wie trail_line. Mit color=-1 gehen die Farben entlang der Wischspur ineinander über. Aktiviere das Plugin und wähle dann seine Spur unter Aus Plugins in der Spurauswahl. Zeichendaten im Zustand allein erzeugen keine Spur.
def trails(state):
return [trail("rainbow", "Rainbow", colors=["#ff0000", "#ff8800", "#ffff00", "#00cc44", "#0066ff", "#9900ff"],
layers=[trail_line(width=1.3, tail_width=0.2, color=-1)])]Ein Look wird beim Auswählen kopiert, also ändert ein späteres Ändern des Skripts keinen Look, den jemand schon benutzt; man wählt ihn neu aus. Ein falsch geschriebenes Schlüsselwort, ein Wort außerhalb seiner Liste oder Text, wo eine Zahl hingehört, ist ein Fehler in der Editor-Konsole, und Zahlen bleiben in den Bereichen, die auch die Regler der App nutzen.
Grafik auf den Tasten
key_art(state) gibt ein Dict von Tastennamen zu Grafik zurück, und die Tastatur malt sie in die Tastenkappe. Die Tastennamen sind die von haptics, mit den Rückfällen letters und keys. Grafik ist eine Liste von shape(...), eine einzelne Form, eine Ebene art([...]) oder eine Liste von Ebenen, und jede Ebene blendet nach ihrer eigenen Uhr in ihr nächstes Bild über, sodass eine Taste zwei verschieden schnelle Dinge tragen kann.
| Aufruf | Was er tut |
|---|---|
art(shapes, animate=0, curve="ease_out") | Eine animierende Ebene. Übergib ein neues Bild, und die Ebene blendet über animate Sekunden entlang curve hinüber: ease_out, linear, ease_in, ease_in_out oder spring. Mehrere Ebenen auf einer Taste animieren jede für sich. |
shape(kind, anchor="center", unit="pt", x=0, y=0, size=, width=, height=, fill=, stroke=, ...) | Ein gezeichnetes Ding: circle, rect, capsule, line, path, text oder icon. Es sitzt bei x und y von einem anchor auf der Taste aus, in Punkten oder mit unit="key" in Anteilen der Taste. fill und stroke nehmen eine Hex-Farbe, "text", "accent", ein color(...) oder ein gradient(...), dazu gibt es line_width, corner, trim_from, trim_to, rotation, opacity, blur und bis zu drei Schatten. |
graph(values, min=None, max=None, width=0.8, height=0.25, stroke=, fill=) | Eine Zahlenreihe, aufgefaltet in gewöhnliche Formen, zum Anhängen an eine Formenliste oder allein. Die neuesten 60 endlichen Werte bleiben erhalten, die Grenzen kommen sonst aus der Reihe selbst, und fill legt einen geschlossenen Pfad zur Grundlinie unter die Linie. |
color(value, opacity=1) | Eine Farbe: ein Hex-Wert oder "text" beziehungsweise "accent", aufgelöst gegen die Taste, auf der sie landet, mit opacity. |
gradient(kind, colors, stops=[], start=, end=, center=, radius=0.5) | Ein linearer oder radialer Verlauf durch eine Farbliste. stops setzt die Farben, start und end richten einen linearen aus, center und radius setzen einen radialen. |
shadow(color, radius=4, x=0, y=0) | Ein Schatten unter einer Form, bis zu drei davon. Ein Schein, der die Taste füllt, folgt den Ecken der Kappe, statt sie abzuschneiden; ein Glühen tritt über die Kappe hinaus. |
Die Grafik wird nach jedem Ereignis, das das Plugin hört, neu gelesen, und das ist die ganze Schleife: Zustand in on_event(name, info, state) ändern, in key_art(state) daraus zeichnen. events(state) nennt die gewünschten Ereignisse und wird einmal beim Laden des Plugins gelesen. Ohne den Hook hört ein Plugin alles außer key, key_down, key_up, predictions und tick, die ein Skript pro Anschlag oder pro Sekunde laufen lassen und deshalb angefordert werden müssen. Änderungen an Shift und Ebene zeichnen die Grafik neu, ob jemand zuhört oder nicht, und sie erreichen auch context().
def initial():
return {"shift": "off"}
def events(state):
return ["shift"]
def on_event(name, info, state):
state["shift"] = info["state"]
return state
def key_art(state):
if state["shift"] != "locked":
return {}
lamp = shape("circle", anchor="top_right", x=-7, y=7, size=5,
fill="accent", shadow=shadow("accent", radius=4))
return {"shift": art([lamp], animate=0.12)}Formen
circlerectcapsulelinepathtexticon
Sechzehn Formen pro Taste, über alle Ebenen darauf zusammengezählt, und 64 Punkte pro Pfad; alles darüber fällt weg. Eine Form wird in einem Kasten ihrer eigenen width und height gezeichnet, und ein Kasten ohne Dicke malt nichts. Eine waagerechte Linie braucht darum eine kleine eigene height mit ihren Punkten in deren Mitte (height=0.03, points=[[0, 0.5], [1, 0.5]]), sonst erscheint sie stillschweigend nicht.
Haptik, Trefferflächen und Korrekturen
haptics(state) und hitboxes(state) geben Dicts zurück, deren Schlüssel Tastennamen sind: der Buchstabe selbst, "space", "delete", "return", "shift" oder "globe", dann "letters" für jeden nicht genannten Buchstaben und "keys" für alles andere. Tasten, die ein Plugin auslässt, behalten die eigenen Einstellungen der Person. Beide Tabellen werden beim Öffnen der Tastatur und etwa einmal pro Sekunde gelesen, also läuft für sie nichts pro Tastendruck.
| Aufruf | Was er tut |
|---|---|
feel(style=None, intensity=None, sharpness=None) | Eine Haptik: style ist "soft", "light", "medium", "heavy", "rigid" oder "off", und intensity und sharpness (0 bis 1) passen sie an. Ein Stilwort allein geht auch. |
hitbox(scale=1, x=0, y=0) | Eine Trefferfläche: x und y verschieben das Ziel der Taste um diesen Anteil ihrer Größe, höchstens eine halbe Taste, und scale vergrößert oder verkleinert es. Eine bloße Zahl ist ein scale. |
def haptics(state):
return {
"space": "heavy",
"return": "rigid",
"delete": feel(intensity=0.45, sharpness=0.9),
}on_touch(key, x, y, state) läuft nach jedem Tipp, sobald er verarbeitet ist, mit der Stelle auf der Taste, an der der Finger aufkam. Gemessen wird an der gezeichneten Taste, nicht an ihrem verschobenen Ziel, also kann ein Plugin jede Taste dorthin schieben, wo sie wirklich getroffen wird, ohne der eigenen Verschiebung hinterherzulaufen.
def initial():
return {"keys": {}}
def on_touch(key, x, y, state):
if len(key) != 1:
return state
n, ax, ay = state["keys"].get(key, [0, 0.0, 0.0])
n = min(n + 1, 50)
ax += (x - ax) / n
ay += (y - ay) / n
state["keys"][key] = [n, ax, ay]
return state
def hitboxes(state):
boxes = {}
for key in state["keys"]:
n, x, y = state["keys"][key]
if n >= 12:
boxes[key] = hitbox(x=round(x * 0.6, 2), y=round(y * 0.6, 2))
return boxessuggestions(word, state) setzt Wörter an den Anfang der Leiste, während ein Wort getippt wird. correct(word, fix, state) läuft einmal pro Wort, wenn die Leertaste es beendet, mit der Korrektur der Tastatur oder None, auch bei ausgeschalteter Autokorrektur. Gib ein Wort zurück, um es einzufügen, False, um das Wort wie getippt zu lassen, oder None, um es der Tastatur zu überlassen. Das erste Plugin mit einer Antwort gewinnt, und Löschen direkt danach macht es rückgängig wie jede Autokorrektur.
SHORT = {"brb": "be right back", "omw": "on my way", "idk": "I don't know"}
def suggestions(word, state):
long = SHORT.get(word.lower())
return [long] if long else []
def correct(word, fix, state):
if len(word) > 1 and word.isupper():
return False
return NoneKeiner der beiden Hooks läuft in Passwortfeldern. Der Korrektur-Chip in der Leiste zeigt die Korrektur der Tastatur, nicht das, was correct zurückgäbe, und eine in on_touch geänderte Tabelle erreicht die Tastatur beim nächsten Takt.
Testen im Editor
Die Vorschau des Editors ist eine Ersatztastatur. Sie zeichnet settings(state) live, löst jeden Hook auf Tipp mit einem Beispielwort, einer Taste oder einer Feldart aus, zeigt die Leertaste, wie das Plugin sie hinterlassen hat, und listet alles auf, was das Plugin von der Tastatur verlangt hat. stats() liefert dort erfundene Zahlen, sodass eine Rate ohne Tippen erscheint.
- Nutze die Bedienelemente in der Vorschau. Jedes führt on_action aus und zeichnet neu.
- Tippe die Hook-Schaltflächen in der Reihenfolge, in der die Tastatur sie aufrufen würde: on_open, dann ein paar Mal on_word oder on_key, dann on_close.
- Lies die Konsole. Befehle erscheinen so, wie du sie geschrieben hast, print()-Zeilen darunter, und ein Fehler nennt seine Zeile.
- Lade neu, um den Zustand zurückzusetzen. Das Bearbeiten des Skripts setzt ihn nicht von allein zurück.
Die Vorschau hat kein Dokument, deshalb protokollieren insert() und replace() nur. Um sie auszuprobieren, speichere und tippe bei geöffneter Tastatur in irgendein Feld.
Grenzen
Wenn ein Plugin als Datei oder aus einem Repository kommt, prüft Clink es vor dem Speichern. Es muss unter 64.000 Bytes und 1.600 Zeilen bleiben, mindestens einen Hook definieren, nur die erlaubten Module importieren und darf nichts hiervon enthalten:
In geteilten Plugins nicht erlaubt
__exec(eval(open(compile(
Module, die ein geteiltes Plugin importieren kann
jsonmathrandomresystime
Jeder Hook-Aufruf bekommt 2.000.000 Interpreterschritte, genauso viele wie das Rendern eines Panels. on_key läuft aber bei jedem Tastendruck, deshalb bremst aufwendige Arbeit dort das Tippen, lange bevor sie diese Grenze erreicht.
Die Datei
Ein Plugin ist ein einzelnes JSON-Dokument. id bleibt über Updates stabil, version ist Freitext, der in der Liste erscheint, icon ist ein SF-Symbol-Name, und source ist das Skript mit maskierten Zeilenumbrüchen. Teile es aus dem Inspektor des Editors, oder importiere eines mit der Pfeiltaste im Tab Plugins.
{
"id": "word-count",
"name": "Word count",
"icon": "text.word.spacing",
"summary": "Counts words on the space bar",
"version": "1.0",
"author": "You",
"enabled": true,
"source": "def initial():\n return {\"on\": False}\n..."
}Plugins werden als .clinkplugin-Dateien geteilt, einem JSON-Dokument mit dem Skript darin. Sie werden wie Panels über ein Repository veröffentlicht, mit einem Ordner voller Dateien, einem Manifest und einem Release. Das offizielle Repository ist anti-ltd/clink-plugins.
Repositories ›Wenn etwas nicht passiert
Meistens ist es eines von diesen.
| Symptom | Was zu prüfen ist |
|---|---|
| Nichts passiert | Plugins sind oben im Tab Plugins ausgeschaltet, das Plugin ist in der Liste aus, oder es gibt keine Clink-Pro-Mitgliedschaft. In allen drei Fällen laufen auf der Tastatur gar keine Plugins. |
| Der Leertastentext ist ausgegraut | Ein Plugin hat sie übernommen. Die Zeile unter dem Feld nennt welches; schalte den Schalter dieses Plugins aus, oder das Plugin selbst, um das Feld zurückzubekommen. |
| Der Abschnitt fehlt | Der Anker ist falsch geschrieben. Vergleiche ihn mit IDs anzeigen in der App und denke daran, dass der Abschnitt trotzdem auf der eigenen Seite des Plugins erscheint, wo du zuerst nachsehen solltest. |
| Eine Elementtaste ist leer | Das Plugin dahinter ist aus, deinstalliert, oder sein draw(id, state) hat einen Fehler ausgelöst. Das Layout behält die Taste so oder so, sie füllt sich also wieder, sobald das Plugin an ist. Sieh in der Konsole des Editors nach dem Fehler. |
| Ein Element ändert sich nie | draw wird weiter gefragt, also bewegt sich der Zustand dahinter nicht. Was das Gesicht speist, muss aus einem Hook kommen: on_tick für etwas, das sich von selbst ändert, on_word oder on_key für etwas, das dem Tippen folgt. |
| set_setting() hat nichts bewirkt | Ein Name, der nicht in der Liste oben steht, löst KeyError aus. Ein Wert der falschen Art oder außerhalb des Bereichs wird übersprungen, und die Konsole des Editors zeigt, was die Einstellung erwartet. Eine gesperrte Einstellung springt ohne Mitgliedschaft außerdem zurück. |
| Der Zähler wurde zurückgesetzt | Die Tastatur speichert den Zustand beim Schließen, nicht bei jeder Taste. Eine Tastatur, die das System mitten in der Sitzung beendet hat, verliert, was ihre Plugins seit dem Öffnen gezählt haben. Halte Summen in den App-seitigen Hooks, wenn das wichtig ist. |
| Das Tippen fühlt sich langsam an | In on_key läuft etwas Schweres. Verschiebe es nach on_word, mach weniger davon oder speichere das Berechnete im Zustand. |
| Ein Effekt fehlt unter Aus Plugins | Plugins muss an und das Plugin aktiviert sein, und effects(state) muss ein light_effect mit mindestens einer Ebene zurückgeben. Tippe im Editor auf effects, um zu sehen, was zurückkam, und such in der Konsole nach einem ValueError. |
| Ein Element der oberen Leiste tut nichts | Ein Tippen und ein losgelassener Regler landen beide in on_action(action, value, state), zugeordnet über die id des Elements, nicht über seinen Namen. Ein Regler schreibt nur dann eine Einstellung, wenn bar_knob eine in setting= nennt; sonst liegt der Wert beim Plugin, und eine Schaltfläche schreibt nie eine Einstellung. |