Automatizaciones

El objeto automation opcional de un archivo .clinkplugin declara eventos, estado, ajustes, comandos, preajustes y espacios para atajos. Cada aportación tiene un identificador estable, una versión de esquema exacta y títulos localizados. El anfitrión crea nombres con el formato plugin/<plugin-id>/<kind>/<id>. Un plugin no puede usar el espacio de nombres del sistema ni el de otro plugin.

Automatizaciones ›

Cómo funcionan los plugins

Un panel sustituye a las teclas y una acción se ejecuta una vez sobre un texto. Un plugin no tiene pantalla propia en el teclado. Clink llama a sus funciones en momentos concretos, como cuando se abre el teclado o terminas una palabra, y el plugin puede cambiar la barra espaciadora o su propio estado guardado. Sus ajustes se dibujan en la app con los mismos constructores que usan los paneles. WPM Spacebar, más abajo, es un plugin completo.

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 state
WPM Spacebar añade un interruptor en Teclas > Barra espaciadora. Mientras está activado, la barra espaciadora muestra tu velocidad de escritura actual.

Los plugins requieren Clink Pro. Antes de instalar uno desde un repositorio, Clink te pide que permitas el código de ese repositorio, igual que con los paneles y las acciones.

on_key y on_word reciben lo que escribes, que es lo que permite contar palabras. PyMini no tiene acceso a la red ni a archivos, así que un plugin no puede enviar lo que escribes por internet, y lo que guarda se queda en Clink, en tu dispositivo.

Tu primer plugin

La forma más rápida de empezar es el script inicial que la app escribe por ti. Pone un interruptor en Teclas > Barra espaciadora y, mientras está activo, cuenta palabras en la barra. Cinco pasos, sin archivos que descargar.

  1. Abre la pestaña Plugins, activa los plugins arriba y toca + para crear uno nuevo.
  2. El editor se abre con el script inicial. Léelo una vez: initial(), settings(), on_action(), on_open() y on_word() son todo.
  3. Cambia a Vista previa. Activa el interruptor, toca on_open y on_word unas cuantas veces y observa la barra espaciadora simulada y la consola.
  4. Guarda. El plugin queda activado en la lista y su interruptor aparece también en Teclas > Barra espaciadora.
  5. Abre el teclado en cualquier sitio y escribe. La barra espaciadora va contando.
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 state
El script inicial, tal como lo escribe la app.

Hooks

Define las funciones de estas 33 que necesites y omite las demás. Cada una recibe state como último argumento. Las que reaccionan a algo devuelven state, con cambios o sin ellos. Las que responden a una pregunta (elements, draw, effects, los hooks de aspecto, haptics, hitboxes, suggestions y correct) devuelven su respuesta, y aun así pueden cambiar state directamente.

HookCuándo se ejecuta
on_automation(command, args, state)on_automation(command, args, state) ejecuta un comando local aprobado en el entorno aislado existente. Puede actualizar el texto de la barra espaciadora, publicar estado declarado o emitir un evento declarado. Este hook no permite insertar texto, acceder al portapapeles, cambiar ajustes persistentes ni hacer peticiones externas.
initial()Una vez, antes de que haya estado guardado. Devuelve un dict con cualquier cosa que JSON pueda guardar.
settings(state)Cuando se muestran los ajustes del plugin en la app. Devuelve sus controles como un árbol de nodos. Nunca se ejecuta en el teclado.
on_action(action, value, state)Cuando se usa uno de los controles del plugin. on_action(action, state), sin value, también funciona.
on_open(state)Cuando aparece el teclado. Un buen sitio para leer stats() o fijar la barra espaciadora.
on_close(state)Cuando se cierra el teclado. El estado se guarda justo después.
on_key(key, state)Cada vez que una tecla escribe algo. Se ejecuta en cada pulsación, así que hazlo rápido.
on_word(word, state)Cuando se termina una palabra, ya sea con un espacio, una sugerencia o un deslizamiento.
on_backspace(state)Cuando se pulsa la tecla de retroceso. No recibe texto, solo el estado.
on_suggestion(word, state)Cuando se toca una sugerencia. word es la que se tocó.
on_language(code, state)Cuando cambia el idioma de escritura. code es el nuevo, como en o de.
on_field(kind, state)Cuando el teclado se conecta a un campo. kind es default, email, url, number, phone, password o search.
on_tick(state)Una vez por segundo mientras el teclado está en pantalla, escribas o no. El hook para todo lo que debe cambiar solo: un reloj, una cuenta atrás, un ritmo que debe volver a cero cuando paras.
on_swipe(direction, state)Un deslizamiento que empieza en una tecla de letra: "left", "right", "up", "up_left" o "up_right". Solo se lee mientras la escritura por deslizamiento está desactivada. Si el hook no pide nada, el deslizamiento sigue siendo una pulsación normal; si pide algo, primero se retira la letra donde empezó.
elements(state)Lo que este plugin ofrece a un diseño personalizado. Devuelve entradas element(id, name, icon=, width=), o omite el hook.
draw(id, state)La cara de un elemento, como árbol de nodos. Se ejecuta más o menos una vez por segundo mientras el teclado está visible.
effects(state)Efectos de luz que este plugin ofrece a la página Efectos. Devuelve entradas light_effect(...), o omite el hook.
key_styles(state)Estilos de tecla para el editor de temas. Devuelve entradas key_style(...); consulta Aspectos más abajo.
themes(state)Temas completos para la pestaña Plugins del editor de temas. Devuelve entradas theme(...).
popups(state)Estilos de ventana emergente de tecla. Devuelve entradas popup_style(...).
animations(state)Animaciones: cualquier mezcla de entrance(...), press_animation(...), letter_animation(...) y transition(...).
backgrounds(state)Fondos animados. Devuelve entradas background(...) hechas de capas particles(...).
layouts(state)Disposiciones de teclado. Devuelve entradas layout(...).
trails(state)Estelas de deslizamiento que este plugin ofrece al selector de estelas. Devuelve entradas trail(...); ver Aspectos más abajo.
haptics(state)Una vibración para cada tecla, como un dict de nombres de tecla a sensaciones. Se lee al abrirse el teclado y más o menos una vez por segundo.
hitboxes(state)Una zona de toque para cada tecla, como un dict de nombres de tecla a hitbox(...). Se lee al mismo ritmo que haptics.
on_touch(key, x, y, state)Tras cada toque: la tecla que lo recibió y en qué punto de esa tecla cayó el dedo. x e y van de -0.5 a 0.5, con 0 en el centro.
suggestions(word, state)Palabras para la barra de sugerencias mientras se escribe word. Se ejecuta cada vez que la barra se asienta, no en cada tecla.
correct(word, fix, state)El espacio acaba de terminar word. Devuelve una palabra para escribirla en su lugar, False para dejarla como se escribió o None para que valga la corrección del teclado.
bar_items(state)Botones y ruedas que este plugin ofrece a la barra superior. Devuelve entradas bar_button(...) y bar_knob(...), o deja el hook fuera.
key_art(state)Dibujo para pintar en las teclas, como un dict de nombres de tecla a formas. Se relee tras cada evento que el plugin escucha, así que un estado cambiado en un hook se ve en las teclas.
events(state)Los nombres de evento que on_event quiere oír, leídos una vez al cargarse el plugin. Sin este hook, el plugin oye todo menos los eventos frecuentes.
on_event(name, info, state)Un único hook para todo lo que pasa: open, close, word, backspace, suggestion, language, field, shift y plane, más los frecuentes key, key_down, key_up, predictions y tick, que hay que pedir por su nombre en events(state).
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 state
Dos hooks, una lectura y un comando de panel.

La app y el teclado comparten una sola copia del estado. Si activas un interruptor en la app, estará activado la próxima vez que se abra el teclado. Lo que cuente el teclado estará ahí la próxima vez que abras los ajustes del plugin. El teclado guarda el estado al cerrarse, no después de cada tecla.

Sugerencia actual en la barra espaciadora

Lee la sugerencia principal que se muestra con context()["suggestion"]. Suscríbete al evento predictions para recibir los cambios en info["suggestion"]. Ambos requieren acceso a los datos de escritura. on_suggestion se ejecuta al aceptar una sugerencia, no al cambiar las predicciones. Usa space_text(value or None) para cambiar la etiqueta o restaurarla cuando no haya sugerencias. La acción de la barra espaciadora no cambia.

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 state

Estado

El estado es un solo dict. initial() lo construye la primera vez; después, cada hook recibe el mismo dict, lo cambia y lo devuelve. Guarda todo lo que puede guardar JSON: números, cadenas, listas, dicts anidados. La app lo guarda después de cada control que usas, el teclado al cerrarse, y ambos leen el mismo archivo, así que nunca discrepan mucho tiempo.

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 state
Un recuento por sesión que on_open reinicia y un total que se conserva.

Devolver el estado es el hábito recomendable, pero no es estrictamente obligatorio: el dict es una referencia, así que cambiarlo en el sitio también funciona. Devuelve otro dict, como initial() arriba, y ese pasa a ser el estado.

Ajustes y secciones

settings(state) devuelve un árbol de nodos construido con los constructores de panel: text, toggle, slider, stepper, segmented, button, row, field y los diseños. Aparece en la página del plugin, en la pestaña Plugins. Envuelve una parte en section(anchor, children, title) y esa parte también aparece en una de las pantallas de ajustes de Clink, junto al ajuste con el que tiene que ver.

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"),
    ])
El interruptor y el stepper aparecen en Teclas > Barra espaciadora. El texto explicativo solo aparece en la página del plugin.

Los controles que un plugin necesita más a menudo. Cada uno escribe su nuevo valor en el estado bajo key, o indica una action para on_action, o ambas cosas. La lista completa de constructores, incluidos los diseños, está en la página de paneles.

ConstructorQué dibuja
toggle(label, on=False, key="", action="")Un interruptor. key escribe True o False en esa clave del estado.
slider(value, min=0, max=100, step=1, label="", key="", action="")Un deslizador entre min y max. key escribe la posición en esa clave del estado.
stepper(value, min=0, max=100, step=1, label="", key="", action="")Un valor con − y + al lado, entre min y max.
segmented(options, value=None, key="", action="")Una opción de una lista. key escribe la elegida en esa clave del estado.
field(key, placeholder="", action="", submit="")Un cuadro de texto ligado a state[key]. Al tocarlo, las teclas escriben en esa clave; submit nombra el manejador que ejecuta Intro.
button(label, action="", value=None, insert="", set=None, style="plain", icon="", enabled=True)Un botón. insert escribe su texto en lo que estés redactando, set fusiona claves en el estado y action nombra un manejador para on_action. style admite plain, primary, tinted, quiet o destructive.
row(title, subtitle="", detail="", icon="", action="", value=None, insert="")Una fila que se puede tocar: un título, una segunda línea, un icono y un detalle a la derecha.
text(s, size=17, weight="regular", color="", align="leading", lines=0, mono=False)Una línea de texto. weight admite regular, medium, semibold, bold, heavy, light o thin; align admite leading, center o trailing; lines limita a cuántas líneas se parte; color admite un nombre de color o #RRGGBB.
Paneles ›

Dónde van las secciones

Un ancla es el ID de una tarjeta en una de las pantallas de ajustes de Clink. Indícala en section() y los controles del plugin se dibujan justo debajo de esa tarjeta, con el nombre del plugin encima. La forma más fácil de encontrar una: en la app, abre Más > Desarrollador y activa Mostrar IDs. Cada tarjeta recibe una pequeña insignia de información que muestra su ID y lo copia al tocarla; las páginas muestran el suyo en la barra de título.

Todas las anclas que puede indicar una sección

  • analytics.heatmap
  • analytics.privacy
  • analytics.trends
  • analytics.typing-test
  • automations.rules
  • gestures.accents
  • gestures.cursor
  • gestures.delete
  • gestures.general
  • gestures.suggestions
  • gestures.swipe
  • haptics.feel
  • keys.adaptive
  • keys.faces
  • keys.hitboxes
  • keys.hitmap
  • keys.long-press
  • keys.numberrow
  • keys.onehanded
  • keys.roundness
  • keys.size
  • keys.spacebar
  • keys.spacing
  • keys.split
  • languages.app
  • languages.custom
  • languages.manage
  • languages.packs
  • languages.switch
  • languages.typing
  • layout.arrange
  • layout.arrangement
  • layout.build
  • layout.longpress
  • layout.presets
  • layout.topbar
  • motion.delete
  • motion.entrance
  • motion.glow
  • motion.key-press
  • motion.key-response
  • motion.letters
  • motion.space-response
  • motion.transition
  • popups.style
  • sound.keysounds
  • text.automation
  • text.content
  • text.corrections
  • text.history
  • text.punctuation
  • text.speed
  • text.suggestions
  • text.symbols
  • themes.background
  • themes.canvas
  • themes.theme

keys.spacebar tiene un lugar propio, en la propia pantalla de la barra espaciadora. Una sección con un ancla que no esté en esta lista sigue apareciendo en la página del plugin, así que una errata te cuesta una tarjeta, no el plugin.

Reclamaciones

Cuando un plugin maneja uno de los ajustes de Clink, la persona debe verlo, y los dos no deben pelearse. claim(control) declara que el plugin lo posee: la app muestra el nombre del plugin en la tarjeta de ese ajuste, y el campo de texto de la barra espaciadora se bloquea mientras lo tiene. release(control) lo devuelve. Reclama en el on_action que activa tu función, libera en el que la desactiva, y devuelve el valor a None o a su valor anterior al mismo tiempo. Un plugin desactivado en la lista libera todo lo que tenía.

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 state
La pareja que tiene todo plugin que reclama: tomar al entrar, devolver al salir.

Dos plugins pueden reclamar el mismo control; la app nombra al que lo reclamó por último. Las reclamaciones las registra la app, así que reclama desde on_action, que se ejecuta ahí. Una reclamación hecha dentro del teclado no se recuerda.

Comandos y lecturas

Los plugins pueden usar todos los comandos de panel, además de seis comandos y dos lecturas propios. Los comandos se ponen en cola y se aplican cuando tu función termina, así que nada cambia en el teclado a mitad de una llamada. Las lecturas devuelven los valores tal como estaban justo antes de la llamada.

LlamadaQué hace
space_text(text)Muestra un texto en la barra espaciadora, de hasta 32 caracteres. Pasa None para volver al texto que la persona tenga puesto en la barra espaciadora.
space_language_text(text)Sustituye la insignia de idioma en la esquina de la barra espaciadora, por su cuenta e incluso con la insignia nativa apagada. Una línea, hasta 12 caracteres; "" la oculta y None devuelve el texto nativo.
space_language_flag(language)Pone una bandera en esa insignia para un id de idioma como en_GB, tomada del dibujo de banderas incluido. None devuelve el comportamiento nativo.
space_language_emoji(language)La misma insignia como emoji de bandera de la región. Los tres comandos de insignia comparten una única plaza, así que encender un plugin de insignia apaga los otros.
set_setting(name, value)Cambia uno de los ajustes de Clink por su nombre, a un valor del tipo correcto: un booleano, un número dentro de su rango, una de sus opciones o texto. Un nombre desconocido lanza KeyError. Un valor del tipo equivocado o fuera de rango no se aplica, y la consola del editor indica qué espera el ajuste.
claim(control)Toma uno de los ajustes propios de Clink. Su tarjeta en la app muestra qué plugin lo tiene.
release(control)Devuelve el ajuste. Al desactivar un plugin se libera todo lo que había tomado.
suggest(words)Pone hasta diez palabras tuyas al principio de la barra de sugerencias. Se quedan hasta que se toca una, se pulsa borrar o cambia el campo, y suggest([]) las quita antes. Tocar una la escribe.
banner(text)Muestra un mensaje breve en el teclado durante un momento. Para un aviso, no para una conversación.
press(key)Ejecuta una de las teclas del propio teclado como si se hubiera pulsado: "space" o "delete". Cualquier otro nombre lanza ValueError.
pick_suggestion(slot)Elige la sugerencia que está en la parte "left", "center" o "right" de la barra, igual que al tocarla. En una barra desplazada cuenta lo que se ve en pantalla. Dentro de on_swipe elige de la barra tal como estaba cuando el dedo se apoyó.
stats()Devuelve un dict con wpm, peak_wpm, keystrokes, words y streak. wpm se actualiza en vivo mientras los plugins están activados. Los totales vienen de Analíticas y dejan de actualizarse si Analíticas está desactivado.
setting(name)Lee por nombre uno de los ajustes que aparecen abajo. Cualquier otro nombre lanza un KeyError.
context()La misma instantánea que lee un panel, más suggestion, shift (off, on o locked) y plane en un plugin. El acceso a datos de escritura es lo que llena las claves del documento; shift se puede leer sin él.
ClaveQué contiene
stats()["wpm"]Palabras por minuto de los últimos segundos, contadas a partir de los caracteres que llegan al campo. Aparece uno o dos segundos después de empezar, baja cuando haces una pausa y llega a 0 cuando paras. Borrar nunca suma.
stats()["peak_wpm"]La mejor velocidad registrada por Estadísticas.
stats()["keystrokes"]Teclas pulsadas en total, tal como las cuenta Estadísticas.
stats()["words"]Palabras confirmadas en total.
stats()["streak"]Días seguidos escribiendo, hasta hoy.

setting(name) y set_setting(name, value) comparten una lista de nombres, y claim(control) también acepta cualquiera de ellos. Los booleanos se leen como True o False, los números como números, las opciones como su id. La lista es larga a propósito: un plugin puede reaccionar a casi todo lo que una persona puede ajustar en la app, o manejarlo.

Nombres que acepta setting()

  • analytics
  • emoji.skin_tone
  • emoji.trailing_space
  • gestures.cursor
  • gestures.cursor_style
  • gestures.highlight_shift
  • gestures.plane_slide
  • gestures.predictive_flick
  • gestures.quick_accent
  • gestures.swipe
  • gestures.swipe_delete
  • gestures.swipe_multi_word
  • gestures.swipe_space_commit
  • gestures.swipe_two_thumb
  • gestures.trail
  • gestures.trail_style
  • gestures.trail_width
  • haptics.enabled
  • haptics.intensity
  • haptics.sharpness
  • keys.accents
  • keys.glyph_scale
  • keys.height
  • keys.long_press_hint
  • keys.popup_style
  • keys.popups
  • keys.radius
  • keys.row_spacing
  • keys.spacing
  • keys.uppercase
  • keys.width
  • language
  • language.bar_key
  • language.bar_key_style
  • language.mode
  • layout
  • layout.dismiss_shortcut
  • layout.number_row
  • layout.number_row_scale
  • layout.one_handed
  • layout.one_handed_shortcut
  • layout.one_handed_side
  • layout.one_handed_width
  • layout.split
  • layout.split_gap
  • layout.split_number_row
  • layout.split_shortcut
  • layout.split_space_bar
  • pet.corner
  • pet.enabled
  • pet.species
  • settings.accentHoldDelay
  • settings.accentMoveCancel
  • settings.activateWithIcon
  • settings.adaptiveGrow
  • settings.adaptiveHitboxes
  • settings.adaptivePredictAtWordStart
  • settings.adaptivePredictionWeight
  • settings.adaptiveShrink
  • settings.adaptiveSpace
  • settings.aiAutocorrect
  • settings.aiCompletions
  • settings.aiExtensionEnabled
  • settings.aiSearch
  • settings.aiToolsColorOverrides
  • settings.aiToolsDiffStyle
  • settings.aiToolsDisabled
  • settings.aiToolsLayoutStyle
  • settings.aiToolsOrder
  • settings.aiToolsPromptOverrides
  • settings.aiTranslate
  • settings.alternatingSplit
  • settings.arabicIndicNumerals
  • settings.backgroundEffectOverride
  • settings.clipboardCloseOnPaste
  • settings.clipboardDeleteOnPaste
  • settings.clipboardIgnoreImages
  • settings.clipboardIgnorePinsOnDelete
  • settings.clipboardStyle
  • settings.cursorActivationHaptic
  • settings.cursorLineStride
  • settings.cursorStepHaptic
  • settings.customPanelsStandalone
  • settings.customPetID
  • settings.deleteWordSwipeEngage
  • settings.deleteWordSwipeStride
  • settings.dictationAssist
  • settings.dictationAssistCustom
  • settings.dictationAssistLevel
  • settings.dictationColorScheme
  • settings.dictationGlassCapsule
  • settings.dictationSmartActions
  • settings.dictationStyle
  • settings.dictationVisualStyle
  • settings.dragUpThreshold
  • settings.emojiCategoryOrder
  • settings.emojiCellSpacing
  • settings.emojiColumnCount
  • settings.emojiCrossAxisSwitchesTab
  • settings.emojiCustomSets
  • settings.emojiGlyphScale
  • settings.emojiHiddenCategories
  • settings.emojiHiddenFromPanels
  • settings.emojiRecentsCap
  • settings.emojiRecentsSort
  • settings.emojiRememberCategory
  • settings.emojiRowCount
  • settings.emojiScrollDirection
  • settings.emojiSearchSlot
  • settings.emojiShowABCKey
  • settings.emojiShowBackspaceKey
  • settings.emojiStartCategoryID
  • settings.emojiTabBarSlot
  • settings.emojiTabIconStyle
  • settings.emojiToneHoldDelay
  • settings.extensionOrder
  • settings.extraTopBars
  • settings.formFreeCorners
  • settings.formLayoutEnabled
  • settings.gifShareAsLink
  • settings.glassPerRowMerge
  • settings.glassReleaseResponse
  • settings.gridSwitchAnimation
  • settings.gridSwitchDuration
  • settings.handwritingInkColor
  • settings.handwritingInkGlow
  • settings.handwritingInkStyle
  • settings.handwritingInkWidth
  • settings.hitboxScale
  • settings.iconPickerStyle
  • settings.keyBloomScale
  • settings.keyLighting
  • settings.keyPressGlow
  • settings.keyPressInstant
  • settings.keyPressLinger
  • settings.keySpringDamping
  • settings.keySpringResponse
  • settings.keyboardBottomPadding
  • settings.keyboardLanguages
  • settings.keyboardTopPadding
  • settings.longPressGlyphScale
  • settings.minPressVisible
  • settings.notepadBrowseStyle
  • settings.notepadMode
  • settings.numberRowFontSize
  • settings.numberRowLeadingKeys
  • settings.numberRowTrailingKeys
  • settings.oneHandedCustomKeys
  • settings.oneHandedExtraKeys
  • settings.panelButtonHitboxScale
  • settings.persistentLeadingKeys
  • settings.persistentTrailingKeys
  • settings.petIdleMotion
  • settings.petScale
  • settings.pinyinFuzzyEnabled
  • settings.pluginLooks
  • settings.popupSpringDamping
  • settings.popupSpringResponse
  • settings.predictiveFlickSuggestionPosition
  • settings.predictiveFlickSuggestionsSeparate
  • settings.reduceEffectsOnLowPower
  • settings.repeatAccelStep
  • settings.repeatHoldDelay
  • settings.repeatInitialInterval
  • settings.repeatMinInterval
  • settings.replacementsLayout
  • settings.rowInsets
  • settings.secondaryNeuralModelsEnabled
  • settings.separateActivation
  • settings.separateLanguageLayouts
  • settings.showCaptureOverlay
  • settings.showCorrectionField
  • settings.showHitboxOverlay
  • settings.showIconsBeforeTyping
  • settings.showRecentEmoji
  • settings.showTouchHeatmap
  • settings.showTouchSurfaceBounds
  • settings.showTouchTelemetryOverlay
  • settings.slideUpPickerStyle
  • settings.solidPopupOpacity
  • settings.soundVoice
  • settings.spaceBloomScale
  • settings.spaceCursorActivationDelay
  • settings.spaceCursorDragScale
  • settings.spaceCursorStride
  • settings.spaceLeanMultiplier
  • settings.spaceSpringDamping
  • settings.spaceSpringResponse
  • settings.spatialBiasEnabled
  • settings.spatialBiasGain
  • settings.splitIndices
  • settings.splitOtherPlanes
  • settings.stickerGridSnap
  • settings.stickerPlacements
  • settings.suggestionDebounceDelay
  • settings.suggestionHitboxScale
  • settings.suggestionSegmentLiquidGlass
  • settings.suggestionSegmentStyle
  • settings.suggestionSegmentsFollowTheme
  • settings.suggestionSeparatorColor
  • settings.suggestionSeparatorStyle
  • settings.suggestionTopPadding
  • settings.swipeKeyMorph
  • settings.swipeMorphRadius
  • settings.swipeMorphStrength
  • settings.swipeTrailEndWidth
  • settings.swipeTrailMaxLength
  • settings.swipeTrailStartWidth
  • settings.swipeTrailTrim
  • settings.toolsButtonStyle
  • settings.toolsHiddenFromPanels
  • settings.topBarItems
  • settings.topBarOrder
  • settings.translateLanguageOrder
  • settings.translateLanguagesDisabled
  • settings.translateStyle
  • settings.translateTone
  • settings.vietnameseInputMethod
  • sound.enabled
  • sound.pack
  • sound.volume
  • spacebar.corner
  • spacebar.language_code
  • spacebar.size
  • spacebar.text
  • stickers.enabled
  • text.arithmetic
  • text.auto_capitalize
  • text.auto_punctuation
  • text.autocorrect
  • text.autocorrect_everywhere
  • text.contacts
  • text.conversions
  • text.double_space_period
  • text.learning
  • text.punctuation_spacing
  • text.return_to_letters
  • text.revert_on_delete
  • text.smart_quotes
  • text.suggestions
  • text.suggestions_animation
  • text.suggestions_height
  • text.suggestions_scroll
  • text.suggestions_style
  • theme
  • theme.background
  • theme.dark
  • theme.delete_glyph
  • theme.entrance
  • theme.glyph_press
  • theme.light
  • theme.match_system
  • theme.press_style
  • theme.reactive_background
  • tools.calculator
  • tools.cling
  • tools.clipboard
  • tools.conversion
  • tools.dictation
  • tools.dictionary
  • tools.emoji
  • tools.gif
  • tools.handwriting
  • tools.layout_switcher
  • tools.notepad
  • tools.plugin_switcher
  • tools.profiles
  • tools.replacements
  • tools.textfx
  • tools.theme_switcher
  • tools.translate

Comandos

  • insert()
  • backspace()
  • delete_word()
  • replace()
  • move_cursor()
  • copy()
  • close()
  • haptic()
  • toast()

Igual que el view() de un panel, settings() solo debe describir controles. Los comandos que se llamen desde ahí se ignoran y se registran en la consola.

Ejemplos

Cinco plugins pequeños, cada uno completo. Pega uno en un plugin nuevo, guarda y funciona.

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 state
Insignia de idioma: el idioma de escritura en la barra espaciadora, actualizado en cuanto cambia.
SHORTCUTS = {"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 state
Atajos: escribe omw y un espacio y obtienes on my way. on_key ve el espacio después de que se ha escrito, así que el reemplazo lo salta.
def 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 state
Meta de palabras: un stepper en la página del plugin, una barra de progreso y una vibración con un banner cuando la sesión la alcanza.
def 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 state
Campos silenciosos: los sonidos de tecla se apagan en un campo de contraseña o numérico y vuelven después, usando on_field, setting() y set_setting().
NAMES = ["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 state
Menciones: escribir @ pone tus propios nombres en la barra de sugerencias.
def 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 state
Deslizamientos al estilo Fleksy, el núcleo del plugin Flick Gestures: a la izquierda borra una palabra, a la derecha escribe un espacio, hacia arriba elige una sugerencia. Solo llegan mientras la escritura por deslizamiento está desactivada.

Elementos en un diseño

Un diseño personalizado se construye con teclas y elementos: una tira de emojis recientes, una fila de dígitos, un panel de cursor. Un plugin puede añadir los suyos. elements(state) dice qué ofrece y draw(id, state) pinta uno, así que una tecla puede mostrar cualquier cosa que el plugin sepa calcular: un gráfico de velocidad, un reloj, una cuenta atrás, tu propia batería.

Dos hooks construyen uno. elements(state) enumera lo que ofreces, una entrada por elemento, y la app lo lee para llenar la paleta del editor de diseños. draw(id, state) recibe uno de esos ids y devuelve qué pintar. Un plugin puede ofrecer varios; a draw se le pregunta por cada uno por su nombre.

LlamadaQué hace
element(id, name, icon="", width=2)Un elemento en oferta. width se mide en anchos de tecla, como los cuenta un diseño, e icon es el SF Symbol que el editor muestra en su paleta.
sparkline(values, min=None, max=None, fill=False)Una línea a través de una serie de números, que llena el espacio disponible. Si omites min y max, se ajusta a sus valores. Solo un plugin puede dibujarla, y está pensada para una tecla.
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")
El plugin WPM Sparkline, completo: ofrece un elemento y dibuja en él los últimos treinta segundos de tu ritmo.

Una cara es un árbol de nodos, como una página de ajustes, pero una tecla no es una página de ajustes: solo se usan los nodos que dibujan y se ignora todo lo que se pueda tocar. La tecla ya pertenece al diseño, así que dentro no hay sitio para un botón.

LO QUE PUEDE DIBUJAR UNA CARA

  • sparkline
  • text
  • badge
  • icon
  • progress
  • hstack
  • vstack
  • spacer
  • divider

Dimensiónalo para una tecla. width se mide en anchos de tecla: 1 es una letra y 3 es como un tercio de fila, y la persona puede cambiarlo después. El texto tiene una línea y se encoge para caber; los colores siguen por defecto al color de texto de la tecla, así que un elemento combina con las teclas de alrededor salvo que pidas otro. Una sparkline guarda sus últimos 120 puntos, más de los que cabe mostrar en una tecla.

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")
Un reloj en una tecla: sin estado, sin hooks, solo la hora cada vez que se pide. Los elementos se redibujan solos una vez por segundo aproximadamente.
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)
Una meta de palabras por sesión, contada por on_word y dibujada como una barra. Los hooks llevan el estado; draw solo lo lee.

Los elementos viven en diseños personalizados, así que primero hay que construir uno. Los preajustes no pueden llevarlos: bifurcar un preajuste en tu propio diseño es lo primero que ofrece el editor.

  1. Instala el plugin y actívalo. Sus elementos aparecen en cuanto lo haces.
  2. Abre Diseño, crea o bifurca un diseño personalizado y ve a Organizar.
  3. Toca Elemento, elige el elemento del plugin en la tira y ajusta su ancho.

La vista previa del editor dibuja cada elemento que ofrece el script, más o menos del tamaño de la tecla donde irá, bajo la maqueta de la barra espaciadora. Guarda y colócalo una vez, y el teclado dibuja lo mismo.

Un elemento solo muestra lo que el plugin dibuja: tocarlo no hace nada, porque la tecla ya es del diseño. El diseño conserva el elemento aunque su plugin esté apagado o desinstalado, y la tecla se vuelve a llenar cuando el plugin regresa.

Botones y ruedas en la barra superior

La franja sobre las teclas se organiza en Diseño > Barra superior, y un plugin puede ofrecer sus propios controles para ella. bar_items(state) los devuelve: bar_button(...) para algo que se toca, bar_knob(...) para algo que se gira. La lista se lee al abrirse el teclado y otra vez tras cada toque, así que un elemento puede aparecer y desaparecer con el estado del plugin.

LlamadaQué hace
bar_items(state)Todo lo que este plugin ofrece a la barra, como lista. Un id solo tiene que ser único dentro del plugin; la barra lo guarda junto al id del propio plugin. Un id repetido se queda con el primero, y un elemento que el hook deja de devolver ya no se dibuja.
bar_button(id, name, icon="", title="")Un botón. name es lo que lista el editor y lo que lee VoiceOver. icon es un SF Symbol y title son hasta 12 caracteres de etiqueta al lado: con icono y sin title queda solo el icono, sin icono queda solo el texto. Un botón no tiene valor ni rango. Un toque llama a on_action(id, None, state), y todo lo que hace ocurre ahí.
bar_knob(id, name, icon="", min=0, max=1, step=0, value=0, setting=None, art=None, rotor=None)Una rueda. min y max son su rango, step la hace saltar por pasos cuando es mayor que cero, y value es dónde empieza. En su lugar, setting= la ata a uno de los controles numéricos de la propia app, que entonces aporta tanto el rango como la posición. art= y rotor= la dibujan en 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 state
Un botón y dos ruedas, una de ellas atada al volumen de las teclas

Ambos vuelven por on_action(action, value, state), identificados por el id del elemento, así que los elementos de barra de un plugin y sus controles de ajustes comparten un mismo hook. Un toque envía None. Una rueda envía su valor en cada muesca mientras el dedo sigue abajo y una vez más al soltarla, así que un plugin puede seguir el arrastre o esperar al último valor.

Una rueda con setting= gira un ajuste que la app ya tiene, tomado de la lista que hay más arriba en Comandos y lecturas, como sound.volume o haptics.intensity. Un nombre que la app no conoce es un KeyError en la consola. El rango y la posición inicial vienen de ese control, la copia del propio teclado cambia mientras arrastras, así que la siguiente tecla ya suena al nuevo nivel, y el valor se guarda al soltar. Una rueda de volumen o de vibración subida desde cero también vuelve a encender el sonido o la vibración durante el arrastre, para que las muescas se oigan y se sientan al subir; guardar ese interruptor es cosa del propio set_setting del plugin en on_action.

Una rueda se puede dibujar en Python. art= es la parte que se queda quieta, el aro o el cuerpo, y rotor= es la cara que gira, de -135 grados en el mínimo a +135 grados en el máximo. Las dos aceptan las mismas formas, pinturas, degradados y sombras que el arte de tecla, hasta dieciséis formas cada una, y unit="key" escala las coordenadas al dial cuadrado, donde una aguja con y negativa apunta hacia arriba. Al dibujo se le pasa el color de texto de la barra como "text" y el acento del tema como "accent". Si dejas las dos fuera, la rueda es un anillo con el icono dentro; en el editor también se le puede dar a una rueda ya puesta cualquiera de los acabados incluidos.

Nada llega a la barra por su cuenta. Un elemento se coloca a mano, igual que el menú y las sugerencias.

  1. Instala el plugin y actívalo. Sus elementos de barra aparecen enseguida.
  2. Abre Diseño > Barra superior.
  3. Añade el botón o la rueda del plugin y arrástralo hasta su sitio. A una rueda además se le elige un acabado.

Los builders pasan por alto una palabra clave que no conocen, sin dar error. setting=, min=, max=, step=, value=, art= y rotor= son solo de bar_knob, así que un bar_button al que se le pasa una se construye igual como botón simple y no se dice nada: el trabajo de un botón va en on_action. Al revés, title= es del botón, y una rueda lo pasa por alto.

Efectos de luz

Un plugin puede añadir su propia iluminación de teclas. effects(state) devuelve entradas light_effect(...), y cada una aparece en la app en Efectos > De plugins, junto a los estilos de serie y los efectos que crea la gente. Un efecto son datos, no un dibujo: una pila de capas que el teclado anima por su cuenta, así que en el script no se ejecuta nada por fotograma, y las caras, las letras, el brillo, el destello al pulsar y el reposo funcionan igual que con los estilos de serie.

LlamadaQué hace
light_effect(id, name, layers=[...], colors=[], icon="")Un efecto. id solo tiene que ser único dentro del plugin. layers es una lista de light_layer(...), aplicada de arriba abajo. colors son hasta ocho cadenas "#rrggbb" por las que pasa el color en bucle; si la omites, el efecto sigue el ajuste de Color de la persona.
light_layer(pattern, ...)Una capa: un patrón de la lista de abajo. Todas las palabras clave son opcionales.
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"),
        ]),
    ]
Una base fija con una onda lenta por tres colores encima. La onda suma su luz a la base y mueve el color a su paso.

Patrones

  • solid
  • pulse
  • wave
  • gradient
  • twinkle
  • sweep
  • rain
  • flicker
  • checker
ClaveQué hace
moves="light"Lo que cambia el patrón: "light" para el brillo, "color" o "both".
mix="add"Cómo se combina su brillo con las capas de arriba: "add", "max" para quedarse con el más brillante, o "multiply" para atenuarlas como una máscara.
shape="smooth"La subida y bajada de pulse, wave y gradient: "smooth", "ramp", "step" o "spike".
direction="right"Hacia dónde avanzan wave, gradient, sweep y rain: "right", "left", "down", "up" o "out" desde el centro.
speed=1De 0 a 4. Con 0 el patrón se queda quieto.
size=1De 0,25 a 4: cuántas veces se repite el patrón a lo ancho del teclado. En twinkle fija cuántas teclas se encienden, y en sweep la longitud de la estela.
low=0, high=1El brillo entre el que se mueve el patrón, cada uno de 0 a 1. Si low queda por encima de high, se invierte.
color_span=1, color_offset=0Cuánto avanza el patrón por los colores y dónde empieza, cada uno de 0 a 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),
        ]),
    ]
Un sweep usado como máscara. La primera capa solo mueve el color, y el sweep decide qué teclas se encienden. Sin colors sigue el ajuste de Color, así que con Espectro se vuelve un arcoíris.

Al elegir un efecto, se copia en los ajustes de la persona, así que sigue funcionando con el plugin apagado. Mientras el plugin está activo y uno de sus efectos está en marcha, el teclado vuelve a leer effects(state) al abrirse y después más o menos una vez por segundo, y cambia lo que haya cambiado. Así un efecto sigue al estado, a la hora o a 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),
        ]),
    ]
Tempo, del plugin Light Show: una onda que acelera cuanto más rápido escribes. Una capa puede cambiar de velocidad sin que su patrón dé saltos.

El botón effects del editor muestra lo que ofrece el script. Para verlo en movimiento, guarda el plugin, elige el efecto en Efectos > De plugins y mira el teclado de la parte superior de esa pantalla.

Redondea lo que tiemble, como una velocidad de escritura. Un efecto que vuelve distinto cada segundo se sustituye cada segundo para nada. Un patrón o una palabra clave desconocidos son un ValueError en la consola del editor, y un efecto sin capas se descarta.

Aspectos

Siete hooks ofrecen a la app cosas para elegir: key_styles, themes, popups, animations, backgrounds, trails y layouts. Cada uno devuelve entradas creadas con las llamadas de abajo, y aparecen en De plugins junto a las opciones incluidas. Los temas son la excepción: tienen su propia pestaña Plugins en el editor de temas. Un aspecto son números y palabras, no código de dibujo, así que nada del script se ejecuta en cada fotograma. Al elegir uno se copia en los ajustes de la persona, así que sigue funcionando con el plugin desactivado, y elegir después una opción incluida lo aparta.

LlamadaQué hace
key_style(id, name, material=, variant=, shape=, shadow=, cap=cap(...))Un estilo de tecla, aplicado al tema que esté abierto en el editor de temas, desde su tarjeta Estilo. Solo cambian los campos que nombra: material, variant, shape, glass, fan, los inner_radius, face_inset, edges, raised y light_angle mecánicos, shadow (0 es plano) y outline. Los colores siguen siendo los del tema.
cap(outline="round", corner=None, travel=2, layers=[cap_layer(...)])Una tecla pintada para un estilo de tecla, apilada a partir de entradas cap_layer(...). outline es round o rect, corner cambia el radio, y travel es cuánto se hunde la tecla al pulsarla.
cap_layer(kind, paint, inset=0, x=0, y=0, blur=0, fade=None, when=[...])Una capa de una tecla pintada. kind es fill, stroke o inner, paint acepta la gramática de color de tecla, un color(...) o un gradient(...), y when limita la capa a pale, dark, pressed, resting y highlighted. Con moves=False la capa se queda quieta mientras la tecla se hunde.
theme(id, name, background=, keys=, key_text=, style=key_style(...), ...)Un tema completo, en la pestaña Plugins del editor de temas. background, keys y key_text son obligatorios, como colores "#rrggbb"; special, special_text, accent, background_bottom (un degradado hacia abajo), dark, font y weight son opcionales. style=key_style(...) define su acabado. Al elegirlo se instala como tema personalizado.
popup_style(id, name, shape="tile", width=48, height=56, lift=30, ...)La burbuja sobre una tecla pulsada, en Aspecto > Ventanas emergentes. shape es "tile", "round" o "balloon"; width, height, lift y font_size van en puntos, y response y damping ajustan su muelle.
entrance(id, name, opacity=0, x=0, y=0, scale=1, tilt=0, spin=0, ...)Cómo aparece el teclado, en Aspecto > Entrada. opacity, x, y, scale, tilt y spin son su punto de partida; vuelve al reposo con un muelle según response y damping.
press_animation(id, name, scale=, x=0, y=0, rotation=0, ...)La forma de una tecla mantenida, en Reacciones > Geometría: scale (o scale_x y scale_y), x, y y rotation con la pulsación completa.
letter_animation(id, name, scale=, x=0, y=0, rotation=0, anchor="center")El efecto breve que hace una letra en cada toque, en Reacciones > Letras: los mismos números en el punto máximo, más anchor.
transition(id, name, x=0, y=0, scale=1, tilt=0, fade=True, duration=None)El cambio entre letras, 123 y #+=, en Aspecto > Transición. x e y indican cuánto se desplazan las teclas viejas, como fracción del teclado, y las nuevas llegan desde el lado opuesto. También admite scale, tilt, fade y duration.
background(id, name, layers=[...], colors=[])Un fondo animado, en Aspecto > Fondo: hasta cuatro capas particles(...) y hasta ocho colores "#rrggbb".
particles(shape="glow", count=40, size=4, speed=20, direction="none", ...)Una capa de un fondo. shape es "dot", "glow", "streak", "ring" o "square"; count, size, speed, direction, spread, gravity, wobble, life, twinkle y opacity dan forma al movimiento, y burst lanza partículas desde cada tecla pulsada.
layout(id, name, rows=[...], left=[], right=[])Una disposición, en Disposición > Disposición. Cada fila es una lista de teclas: una cadena es una letra y layout_key(...) es cualquier otra cosa. left y right ponen hasta tres teclas junto a la barra espaciadora. Al elegirla se instala una disposición personalizada normal.
layout_key(glyph, action="insert", width=1)Una tecla que no es una simple letra. action es insert, spacer, shift, delete, space, return, numbers, emoji, globe, tab, left, right, undo, redo o dismiss, y width se mide en teclas.
trail(id, name, layers=[...], colors=[])Una estela de deslizamiento. layers son entradas de trail_line, trail_stamps y trail_head, dibujadas en orden, y colors es la paleta a la que apuntan.
trail_line(width=1, tail_width=1, color=-1, glow=0, dash=0, gap=0, band=0, flow=0)El trazo a lo largo del deslizamiento. tail_width adelgaza el extremo viejo, color=-1 funde toda la paleta a lo largo de la línea, y dash, gap, band y flow la rompen o la ponen en movimiento.
trail_stamps(shape="dot", size=1, spacing=14, scatter=0, spin=0, twinkle=0, color=-1)Formas que van cayendo por el deslizamiento: dot, ring, square, diamond, star, spark o heart. spacing es la separación en puntos, scatter las aparta de la línea, spin las gira y twinkle las hace aparecer y desaparecer.
trail_head(shape="dot", size=1.5, pulse=0, opacity=1, color=-1, glow=0)La marca en la punta del dedo. pulse la hace respirar y glow reparte luz a su alrededor.
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),
    ]
Una animación de cada tipo. Cada una aparece en De plugins en su propia tarjeta de Aspecto.
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),
        ]),
    ]
Snowfall: copos que caen balanceándose y un soplo de luz desde cada tecla pulsada.

Devuelve nodos trail desde trails(state). Cada estela tiene una paleta colors y capas como trail_line. Con color=-1, la línea mezcla los colores a lo largo del deslizamiento. Activa el plugin y elige su estela en De plugins, dentro del selector de estelas. Guardar gráficos en el estado no dibuja una estela.

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)])]
Estela de arcoíris

Un aspecto se copia al elegirlo, así que cambiar el script después no cambia un aspecto que alguien ya usa; hay que elegirlo otra vez. Una palabra clave mal escrita, una palabra fuera de su lista o texto donde va un número es un error en la consola del editor, y los números se mantienen en los rangos que usan los controles de la propia app.

Dibujo en las teclas

key_art(state) devuelve un dict de nombres de tecla a dibujo, y el teclado lo pinta dentro de la tecla. Los nombres de tecla son los de haptics, con los recursos letters y keys. El dibujo es una lista de shape(...), una sola forma, una capa art([...]) o una lista de capas, y cada capa pasa a su siguiente dibujo con su propio reloj, así que una tecla puede llevar dos cosas moviéndose a velocidades distintas.

LlamadaQué hace
art(shapes, animate=0, curve="ease_out")Una capa que anima. Pásale un dibujo nuevo y la capa hace la transición durante animate segundos siguiendo curve: ease_out, linear, ease_in, ease_in_out o spring. Varias capas en una misma tecla animan cada una por su cuenta.
shape(kind, anchor="center", unit="pt", x=0, y=0, size=, width=, height=, fill=, stroke=, ...)Una cosa dibujada: circle, rect, capsule, line, path, text o icon. Se sitúa en x e y desde un anchor de la tecla, en puntos o, con unit="key", en fracciones de la tecla. fill y stroke aceptan un color hex, "text", "accent", un color(...) o un gradient(...), y además hay line_width, corner, trim_from, trim_to, rotation, opacity, blur y hasta tres sombras.
graph(values, min=None, max=None, width=0.8, height=0.25, stroke=, fill=)Una serie de números desplegada en formas corrientes, para unir a una lista de formas o usar sola. Se guardan las 60 muestras finitas más recientes, los límites salen de la propia serie si no se dan, y fill añade un trazado cerrado hasta la base bajo la línea.
color(value, opacity=1)Una pintura: un color hex, o "text" o "accent" resuelto contra la tecla en la que cae, con opacity.
gradient(kind, colors, stops=[], start=, end=, center=, radius=0.5)Un degradado lineal o radial por una lista de colores. stops los coloca, start y end orientan uno lineal, center y radius sitúan uno radial.
shadow(color, radius=4, x=0, y=0)Una sombra bajo una forma, hasta tres. Un velo que llena la tecla sigue las esquinas de la tecla en vez de cuadrarlas; un resplandor se escapa de ella.

El dibujo se vuelve a leer tras cada evento que el plugin escucha, y ese es todo el ciclo: cambia el estado en on_event(name, info, state) y dibuja a partir de él en key_art(state). events(state) nombra los eventos que quieres y se lee una vez al cargarse el plugin. Sin él, un plugin oye todo menos key, key_down, key_up, predictions y tick, que hacen correr un script por pulsación o por segundo y hay que pedirlos. Los cambios de mayúsculas y de plano redibujan el arte aunque nadie escuche, y también llegan a 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)}
Un piloto de bloqueo de mayúsculas, dibujado por el plugin en vez de venir en el teclado

Formas

  • circle
  • rect
  • capsule
  • line
  • path
  • text
  • icon

Dieciséis formas por tecla, contando todas sus capas, y 64 puntos por trazado; lo que pase de ahí se descarta. Una forma se dibuja dentro de una caja de su propio width y height, y una caja sin grosor no pinta nada, así que una línea horizontal necesita un height pequeño propio con sus puntos por el medio (height=0.03, points=[[0, 0.5], [1, 0.5]]) o no aparece y no avisa.

Vibración, zonas de toque y correcciones

haptics(state) y hitboxes(state) devuelven dicts cuyas claves son nombres de tecla: la propia letra, "space", "delete", "return", "shift" o "globe", luego "letters" para cualquier letra no nombrada y "keys" para todo lo demás. Las teclas que un plugin no nombra conservan los ajustes de la persona. Ambas tablas se leen al abrirse el teclado y más o menos una vez por segundo, así que nada se ejecuta en cada pulsación por ellas.

LlamadaQué hace
feel(style=None, intensity=None, sharpness=None)Una vibración: style es "soft", "light", "medium", "heavy", "rigid" u "off", e intensity y sharpness (de 0 a 1) la ajustan. Una palabra de estilo sola también sirve.
hitbox(scale=1, x=0, y=0)Una zona de toque: x e y mueven el objetivo de la tecla esa fracción de su tamaño, media tecla como mucho, y scale lo agranda o lo encoge. Un número solo es un scale.
def haptics(state):
    return {
        "space": "heavy",
        "return": "rigid",
        "delete": feel(intensity=0.45, sharpness=0.9),
    }
Heavy Space: una barra espaciadora más pesada, un retorno nítido y un borrado suave.

on_touch(key, x, y, state) se ejecuta tras cada toque, una vez atendido, con el punto de la tecla donde cayó el dedo. La posición se mide respecto a la tecla dibujada, no a su objetivo desplazado, así que un plugin puede mover cada tecla hacia donde de verdad se toca sin perseguir su propio desplazamiento.

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 boxes
Adaptive Hitbox: aprende dónde se toca de verdad cada tecla y mueve su objetivo parte del camino hacia allí.

suggestions(word, state) pone palabras al principio de la barra mientras se escribe una palabra. correct(word, fix, state) se ejecuta una vez por palabra cuando el espacio la termina, con la corrección del teclado o None, incluso con la autocorrección desactivada. Devuelve una palabra para escribirla, False para dejar la palabra como se escribió o None para dejarlo en manos del teclado. Gana el primer plugin que responde, y borrar justo después lo deshace como cualquier autocorrección.

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 None
Shorthand: ofrece «be right back» mientras se escribe brb y mantiene la autocorrección lejos de las palabras en mayúsculas.

Ninguno de los dos hooks se ejecuta en campos de contraseña. La sugerencia de corrección de la barra muestra la corrección del teclado, no lo que devolvería correct, y una tabla cambiada en on_touch llega al teclado en el siguiente ciclo.

Probar en el editor

La Vista previa del editor es un teclado sustituto. Dibuja settings(state) en vivo, dispara cualquier hook con un toque usando una palabra, tecla o tipo de campo de ejemplo, muestra la barra espaciadora tal como la dejó el plugin y enumera todo lo que el plugin le pidió al teclado. Allí stats() devuelve números inventados, así que aparece una velocidad sin escribir.

  1. Usa los controles de la vista previa. Cada uno ejecuta on_action y vuelve a dibujar.
  2. Toca los botones de hook en el orden en que lo haría el teclado: on_open, luego on_word u on_key unas cuantas veces, luego on_close.
  3. Lee la consola. Los comandos aparecen tal como los escribiste, las líneas de print() debajo, y un error indica su línea.
  4. Recarga para reiniciar el estado. Editar el script no lo reinicia por sí solo.

La vista previa no tiene documento, así que insert() y replace() solo se registran. Para probarlos, guarda y escribe en cualquier campo con el teclado abierto.

Límites

Cuando un plugin llega como archivo o desde un repositorio, Clink lo comprueba antes de guardarlo. Tiene que ocupar menos de 64.000 bytes y 1600 líneas, definir al menos un hook, importar solo los módulos permitidos y no contener nada de lo siguiente:

No permitido en plugins compartidos

  • __
  • exec(
  • eval(
  • open(
  • compile(

Módulos que puede importar un plugin compartido

  • json
  • math
  • random
  • re
  • sys
  • time

Cada llamada a un hook dispone de 2.000.000 pasos de intérprete, los mismos que el renderizado de un panel. Pero on_key se ejecuta en cada pulsación, así que un trabajo pesado ahí ralentizará la escritura mucho antes de llegar a ese límite.

El archivo

Un plugin es un solo documento JSON. id se mantiene estable entre actualizaciones, version es texto libre que se muestra en la lista, icon es el nombre de un SF Symbol y source es el script con los saltos de línea escapados. Compártelo desde el inspector del editor o importa uno con el botón de flecha de la pestaña 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..."
}
Un .clinkplugin, abreviado.

Los plugins se comparten como archivos .clinkplugin, un documento JSON con el script dentro. Se publican a través de un repositorio igual que los paneles, con una carpeta de archivos, un manifiesto y una versión. El repositorio oficial es anti-ltd/clink-plugins.

Repositorios ›

Cuando algo no pasa

Casi siempre es una de estas cosas.

SíntomaQué comprobar
No pasa nadaLos plugins están desactivados arriba en la pestaña Plugins, el plugin está apagado en la lista o no hay membresía de Clink Pro. Con cualquiera de las tres, el teclado no ejecuta ningún plugin.
El texto de la barra espaciadora está atenuadoUn plugin lo ha reclamado. La línea bajo el campo indica cuál; apaga el interruptor de ese plugin, o el plugin en sí, para recuperar el campo.
Falta la secciónEl ancla está mal escrita. Compárala con Mostrar IDs en la app y recuerda que la sección sigue apareciendo en la página del plugin, que es donde hay que mirar primero.
Una tecla de elemento está en blancoEl plugin que hay detrás está apagado, desinstalado, o su draw(id, state) lanzó un error. El diseño conserva la tecla igualmente, así que se vuelve a llenar en cuanto el plugin esté activo. Mira el error en la consola del editor.
Un elemento no cambia nuncaA draw se le sigue preguntando, así que lo que no se mueve es el estado. Lo que alimenta la cara tiene que actualizarse desde un hook: on_tick para algo que cambia solo, on_word o on_key para algo que sigue a la escritura.
set_setting() no hizo nadaUn nombre que no está en la lista de arriba lanza KeyError. Un valor del tipo equivocado o fuera de rango se omite, y la consola del editor indica qué espera el ajuste. Un ajuste restringido también vuelve atrás sin membresía.
El recuento se reinicióEl teclado guarda el estado al cerrarse, no en cada tecla. Un teclado que el sistema mata a mitad de sesión pierde lo que sus plugins contaron desde que se abrió. Lleva los totales en los hooks del lado de la app cuando eso importe.
Escribir va lentoAlgo pesado se ejecuta en on_key. Muévelo a on_word, haz menos, o guarda en el estado lo que calcula.
Falta un efecto en De pluginsPlugins tiene que estar activado y el plugin habilitado, y effects(state) tiene que devolver un light_effect con al menos una capa. Toca effects en el editor para ver qué ha vuelto, y busca un ValueError en la consola.
Un elemento de la barra superior no hace nada al usarloUn toque y una rueda soltada llegan los dos a on_action(action, value, state), emparejados por el id del elemento y no por su nombre. Una rueda solo escribe un ajuste cuando bar_knob nombra uno en setting=; sin eso el valor queda en manos del plugin, y un botón nunca escribe un ajuste.
PyMini ›
Descargar en el App Store