Automações

O objeto automation opcional de um ficheiro .clinkplugin declara eventos, estado, definições, comandos, predefinições e espaços para atalhos. Cada contribuição tem um ID estável, uma versão exata do esquema e títulos localizados. O anfitrião cria nomes no formato plugin/<plugin-id>/<kind>/<id>. Um plugin não pode usar o espaço de nomes do sistema nem o de outro plugin.

Automações ›

Como funcionam os plugins

Um painel substitui as teclas, e uma ação corre uma vez sobre um pedaço de texto. Um plugin não tem um ecrã próprio no teclado. O Clink chama as suas funções em momentos definidos, como quando o teclado abre ou quando termina uma palavra, e o plugin pode atualizar a barra de espaço ou o seu próprio estado guardado. As definições do plugin são desenhadas na app com os mesmos construtores que os painéis usam. O WPM Spacebar, mais abaixo, é um 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
O WPM Spacebar acrescenta um interruptor em Teclas > Barra de espaço. Enquanto está ligado, a barra de espaço mostra a sua velocidade de escrita atual.

Os plugins precisam do Clink Pro. Antes de instalar um a partir de um repositório, o Clink pede-lhe que permita código desse repositório, tal como faz para painéis e ações.

on_key e on_word recebem o que escreve, e é assim que funciona um contador de palavras. O PyMini não tem acesso à rede nem a ficheiros, por isso um plugin não pode enviar o que escreve pela internet, e o que guarda fica no Clink, no seu dispositivo.

O seu primeiro plugin

O caminho mais rápido é o script inicial que a app escreve por si. Põe um interruptor em Teclas > Barra de espaço e, enquanto o interruptor está ligado, conta palavras na barra de espaço. Cinco passos, nenhum ficheiro para descarregar.

  1. Abra o separador Plugins, ligue os plugins no topo e toque em + para um novo plugin.
  2. O editor abre no script inicial. Leia-o uma vez: initial(), settings(), on_action(), on_open() e on_word() são tudo.
  3. Mude para Pré-visualização. Ligue o interruptor, toque em on_open e on_word algumas vezes e observe a barra de espaço simulada e a consola.
  4. Guarde. O plugin fica ligado na lista e o seu interruptor passa a estar também em Teclas > Barra de espaço.
  5. Abra o teclado em qualquer lado e escreva. A barra de espaço vai 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
O script inicial, tal como a app o escreve.

Hooks

Defina qualquer uma destas 33 funções e deixe de fora as de que não precisa. Cada uma recebe state como último argumento. As que reagem a algo devolvem state, alterado ou não. As que respondem a uma pergunta (elements, draw, effects, os hooks de aspeto, haptics, hitboxes, suggestions e correct) devolvem a sua resposta, e mesmo assim podem alterar state diretamente.

HookQuando é executado
on_automation(command, args, state)on_automation(command, args, state) executa um comando local aprovado na sandbox existente. Pode atualizar o texto da barra de espaço, publicar estado declarado ou emitir um evento declarado. Este hook não permite inserir texto, aceder à área de transferência, alterar definições persistentes ou fazer pedidos externos.
initial()Uma vez, antes de existir qualquer estado guardado. Devolva um dict com o que o JSON conseguir guardar.
settings(state)Quando as definições do plugin são mostradas na app. Devolva os seus controlos como uma árvore de nós. Nunca corre no teclado.
on_action(action, value, state)Quando um dos controlos do plugin é usado. on_action(action, state), sem value, também funciona.
on_open(state)Quando o teclado aparece. Um bom sítio para ler stats() ou definir a barra de espaço.
on_close(state)Quando o teclado é fechado. O estado é guardado logo a seguir.
on_key(key, state)Sempre que uma tecla escreve algo. Corre a cada toque, por isso mantenha-o rápido.
on_word(word, state)Quando uma palavra termina, seja por um espaço, uma sugestão ou um deslize.
on_backspace(state)Quando a tecla de apagar é premida. Não recebe texto, só o estado.
on_suggestion(word, state)Quando uma sugestão é tocada. word é a que foi tocada.
on_language(code, state)Quando a língua de escrita muda. code é a nova, como en ou de.
on_field(kind, state)Quando o teclado se liga a um campo. kind é default, email, url, number, phone, password ou search.
on_tick(state)Uma vez por segundo enquanto o teclado está no ecrã, esteja a escrever ou não. O hook para tudo o que tem de mudar sozinho: um relógio, uma contagem decrescente, um ritmo que deve voltar a zero quando para.
on_swipe(direction, state)Um gesto que começa numa tecla de letra: "left", "right", "up", "up_left" ou "up_right". Só é lido enquanto a escrita por deslize está desligada. Se o hook não pedir nada, o gesto continua a ser um toque normal; se pedir, a letra onde começou é retirada primeiro.
elements(state)O que este plugin oferece a um esquema personalizado. Devolva entradas element(id, name, icon=, width=), ou deixe o hook de fora.
draw(id, state)O rosto de um elemento, como árvore de nós. Corre cerca de uma vez por segundo enquanto o teclado está no ecrã.
effects(state)Efeitos de luz que este plugin oferece à página Efeitos. Devolva entradas light_effect(...), ou deixe o hook de fora.
key_styles(state)Estilos de tecla para o editor de temas. Devolva entradas key_style(...); veja Aspetos mais abaixo.
themes(state)Temas completos para o separador Plugins do editor de temas. Devolva entradas theme(...).
popups(state)Estilos de pop-up de tecla. Devolva entradas popup_style(...).
animations(state)Animações: qualquer mistura de entrance(...), press_animation(...), letter_animation(...) e transition(...).
backgrounds(state)Fundos animados. Devolva entradas background(...) feitas de camadas particles(...).
layouts(state)Esquemas de teclado. Devolva entradas layout(...).
trails(state)Rastos de deslize que este plugin oferece ao seletor de rastos. Devolva entradas trail(...); ver Aspetos mais abaixo.
haptics(state)Uma vibração para cada tecla, como um dict de nomes de tecla para sensações. Lido quando o teclado abre e mais ou menos uma vez por segundo.
hitboxes(state)Uma área de toque para cada tecla, como um dict de nomes de tecla para hitbox(...). Lido ao mesmo ritmo que haptics.
on_touch(key, x, y, state)Depois de cada toque: a tecla que o recebeu e o ponto dessa tecla onde o dedo pousou. x e y vão de -0.5 a 0.5, com 0 no centro.
suggestions(word, state)Palavras para a barra de sugestões enquanto word está a ser escrita. Corre sempre que a barra assenta, não a cada tecla.
correct(word, fix, state)O espaço acabou de terminar word. Devolva uma palavra para escrever em vez dela, False para a manter como foi escrita ou None para valer a correção do teclado.
bar_items(state)Botões e manípulos que este plugin oferece à barra superior. Devolva entradas bar_button(...) e bar_knob(...), ou deixe o hook de fora.
key_art(state)O desenho a pintar nas teclas, como um dict de nomes de tecla para formas. É relido a seguir a cada evento que o plugin ouve, por isso um estado mudado num hook aparece nas teclas.
events(state)Os nomes de evento que o on_event quer ouvir, lidos uma vez quando o plugin carrega. Sem este hook, o plugin ouve tudo menos os eventos frequentes.
on_event(name, info, state)Um único hook para tudo o que acontece: open, close, word, backspace, suggestion, language, field, shift e plane, mais os frequentes key, key_down, key_up, predictions e tick, que têm de ser pedidos pelo nome em 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
Dois hooks, uma leitura e um comando de painel.

A app e o teclado partilham uma única cópia do estado. Ligue um interruptor na app e ele estará ligado da próxima vez que o teclado abrir. Tudo o que o teclado contar estará lá da próxima vez que abrir as definições do plugin. O teclado guarda o estado quando fecha, não depois de cada tecla.

Sugestão atual na barra de espaço

Leia a principal sugestão apresentada com context()["suggestion"]. Subscreva o evento predictions para receber as alterações em info["suggestion"]. Ambos exigem acesso aos dados de escrita. on_suggestion é executado depois de uma sugestão ser aceite, não quando as previsões mudam. Use space_text(value or None) para definir o texto da tecla ou repô-lo quando não houver sugestões. A ação da barra de espaço não muda.

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

O estado é um único dict. initial() constrói-o da primeira vez; depois cada hook recebe o mesmo dict, altera-o e devolve-o. Guarda tudo o que o JSON pode guardar: números, cadeias, listas, dicts aninhados. A app guarda-o depois de cada controlo que usa, o teclado ao fechar, e ambos leem o mesmo ficheiro, por isso nunca discordam por muito tempo.

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
Uma contagem por sessão que on_open repõe, e um total que se mantém.

Devolver o estado é o hábito a manter, mas não é estritamente obrigatório: o dict é uma referência, por isso alterá-lo no lugar também funciona. Devolva um dict diferente, como initial() acima, e esse passa a ser o estado.

Definições e secções

settings(state) devolve uma árvore de nós feita com os construtores de painel: text, toggle, slider, stepper, segmented, button, row, field e os layouts. Aparece na página do plugin, no separador Plugins. Envolva uma parte em section(anchor, children, title) e essa parte aparece também num dos ecrãs de definições do próprio Clink, junto à definição a que diz respeito.

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"),
    ])
O interruptor e o stepper aparecem em Teclas > Barra de espaço. A legenda só aparece na página do próprio plugin.

Os controlos de que um plugin mais precisa. Cada um escreve o seu novo valor no estado sob key, ou indica uma action para on_action, ou ambos. A lista completa de construtores, incluindo os layouts, está na página dos painéis.

ConstrutorO que desenha
toggle(label, on=False, key="", action="")Um interruptor. key escreve True ou False nessa chave do estado.
slider(value, min=0, max=100, step=1, label="", key="", action="")Um cursor entre min e max. key escreve a posição nessa chave do estado.
stepper(value, min=0, max=100, step=1, label="", key="", action="")Um valor com − e + ao lado, entre min e max.
segmented(options, value=None, key="", action="")Uma escolha de uma lista. key escreve a opção escolhida nessa chave do estado.
field(key, placeholder="", action="", submit="")Uma caixa de texto ligada a state[key]. Ao tocá-la, as teclas escrevem nessa chave; submit indica o tratador que o Return executa.
button(label, action="", value=None, insert="", set=None, style="plain", icon="", enabled=True)Um botão. insert escreve o seu texto naquilo que está a escrever, set funde chaves no estado e action indica um tratador para on_action. style aceita plain, primary, tinted, quiet ou destructive.
row(title, subtitle="", detail="", icon="", action="", value=None, insert="")Uma linha tocável: um título, uma segunda linha, um ícone e um detalhe à direita.
text(s, size=17, weight="regular", color="", align="leading", lines=0, mono=False)Uma linha de texto. weight aceita regular, medium, semibold, bold, heavy, light ou thin; align aceita leading, center ou trailing; lines limita quantas linhas ocupa; color aceita um nome de cor ou #RRGGBB.
Painéis ›

Para onde vão as secções

Uma âncora é o ID de um cartão num dos ecrãs de definições do próprio Clink. Indique-a em section() e os controlos do plugin são desenhados mesmo por baixo desse cartão, com o nome do plugin por cima. A forma mais fácil de encontrar uma: na app, abra Mais > Programador e ligue Mostrar IDs. Cada cartão recebe um pequeno distintivo de informação que indica o seu ID e o copia ao toque; as páginas mostram o seu na barra de título.

Todas as âncoras que uma secção pode indicar

  • 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 tem um lugar próprio, no próprio ecrã da barra de espaço. Uma secção com uma âncora que não esteja nesta lista continua a aparecer na página do plugin, por isso uma gralha custa-lhe um cartão, não o plugin.

Assunções

Quando um plugin conduz uma das definições do próprio Clink, a pessoa deve vê-lo, e os dois não devem lutar. claim(control) diz que o plugin é dono dela: a app indica o plugin no cartão dessa definição, e o campo de texto da barra de espaço fica bloqueado enquanto o plugin a detém. release(control) devolve-a. Assuma no on_action que liga a sua funcionalidade, liberte no que a desliga, e reponha o valor a None ou ao valor anterior ao mesmo tempo. Um plugin desligado na lista liberta tudo o que tinha.

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
O par que todo o plugin que assume tem: tomar ao ligar, devolver ao desligar.

Dois plugins podem assumir o mesmo controlo; a app indica o que assumiu por último. As assunções são registadas pela app, por isso assuma a partir de on_action, que corre lá. Uma assunção feita dentro do teclado não é lembrada.

Comandos e leituras

Os plugins podem usar todos os comandos de painel, mais seis comandos e duas leituras só seus. Os comandos ficam em fila e são aplicados depois de a sua função devolver, por isso nada muda no teclado a meio de uma chamada. As leituras devolvem os valores tal como estavam imediatamente antes da chamada.

ChamadaO que faz
space_text(text)Mostra uma legenda na barra de espaço, até 32 caracteres. Passe None para voltar ao texto que o utilizador definiu para a barra de espaço.
space_language_text(text)Substitui o emblema de idioma ao canto da barra de espaço, por si só e mesmo com o emblema nativo desligado. Uma linha, até 12 caracteres; "" esconde-o e None devolve o texto nativo.
space_language_flag(language)Põe uma bandeira nesse emblema para um id de idioma como en_GB, a partir da arte de bandeiras incluída. None devolve o comportamento nativo.
space_language_emoji(language)O mesmo emblema como emoji da bandeira da região. Os três comandos de emblema partilham um único lugar, por isso ligar um plugin de emblema desliga os outros.
set_setting(name, value)Altera uma das definições do Clink pelo nome, para um valor do tipo certo: um booleano, um número dentro do intervalo, uma das suas opções ou texto. Um nome desconhecido lança KeyError. Um valor do tipo errado ou fora do intervalo não é aplicado, e a consola do editor indica o que a definição espera.
claim(control)Assume uma das definições do próprio Clink. O cartão dela na app indica o plugin que a detém.
release(control)Devolve a definição. Desligar um plugin liberta tudo o que ele tinha assumido.
suggest(words)Coloca até dez palavras suas no início da barra de sugestões. Ficam até uma ser tocada, apagar ser premido ou o campo mudar, e suggest([]) limpa-as mais cedo. Tocar numa escreve-a.
banner(text)Mostra uma mensagem curta no teclado por um momento. Para um toque, não para uma conversa.
press(key)Executa uma das teclas do próprio teclado como se tivesse sido tocada: "space" ou "delete". Qualquer outro nome lança ValueError.
pick_suggestion(slot)Escolhe a sugestão na parte "left", "center" ou "right" da barra, tal como um toque nela. Numa barra deslocada conta o que está no ecrã. Dentro de on_swipe escolhe da barra tal como estava quando o dedo pousou.
stats()Devolve um dict com wpm, peak_wpm, keystrokes, words e streak. wpm atualiza-se em direto enquanto os plugins estão ligados. Os totais vêm das Estatísticas e deixam de ser atualizados se as Estatísticas estiverem desligadas.
setting(name)Lê pelo nome uma das definições listadas abaixo. Qualquer outro nome lança KeyError.
context()O mesmo instantâneo que um painel lê, mais suggestion, shift (off, on ou locked) e plane num plugin. É o acesso a dados de escrita que enche as chaves do documento; shift continua legível sem ele.
ChaveO que contém
stats()["wpm"]Palavras por minuto nos últimos segundos, contadas a partir dos caracteres que chegam ao campo. O número aparece um ou dois segundos depois de começar, desce enquanto faz uma pausa e chega a 0 quando para. Apagar nunca lhe acrescenta nada.
stats()["peak_wpm"]A melhor velocidade alguma vez registada pelas Estatísticas.
stats()["keystrokes"]Teclas premidas, no total, tal como as Estatísticas as contam.
stats()["words"]Palavras confirmadas, no total.
stats()["streak"]Dias seguidos com escrita, a terminar hoje.

setting(name) e set_setting(name, value) partilham uma lista de nomes, e claim(control) também aceita qualquer um deles. Os booleanos leem-se como True ou False, os números como números, as opções pelo seu id. A lista é longa de propósito: um plugin pode reagir a, ou conduzir, quase tudo o que uma pessoa pode definir na app.

Nomes que setting() aceita

  • 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()

Tal como o view() de um painel, settings() deve apenas descrever controlos. Os comandos chamados a partir daí são ignorados e registados na consola.

Exemplos

Cinco plugins pequenos, cada um completo. Cole um num novo plugin, guarde, e corre.

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
Distintivo de língua: a língua de escrita na barra de espaço, atualizada no momento em que muda.
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
Atalhos: escreva omw e um espaço, obtenha on my way. on_key vê o espaço depois de ter sido escrito, por isso a substituição passa por cima dele.
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 palavras: um stepper na página do plugin, uma barra de progresso, e uma vibração com um banner quando a sessão a atinge.
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: os sons das teclas desligam-se num campo de palavra-passe ou numérico e voltam depois, com on_field, setting() e 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
Menções: escrever @ põe os seus próprios nomes na barra de sugestões.
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
Gestos ao estilo Fleksy, o núcleo do plugin Flick Gestures: para a esquerda apaga uma palavra, para a direita escreve um espaço, para cima escolhe uma sugestão. Só chegam enquanto a escrita por deslize está desligada.

Elementos num esquema

Um esquema personalizado é feito de teclas e elementos: uma faixa de emojis recentes, uma fila de dígitos, um painel de cursor. Um plugin pode acrescentar os seus. elements(state) diz o que oferece e draw(id, state) pinta um deles, por isso uma tecla pode mostrar tudo o que o plugin souber calcular: um gráfico de velocidade, um relógio, uma contagem decrescente, uma bateria à sua maneira.

Dois hooks constroem um. elements(state) lista o que oferece, uma entrada por elemento, e a app lê isso para encher a paleta do editor de esquemas. draw(id, state) recebe um desses ids e devolve o que pintar. Um plugin pode oferecer vários; o draw é chamado para cada um pelo nome.

ChamadaO que faz
element(id, name, icon="", width=2)Um elemento oferecido. width conta-se em larguras de tecla, como num esquema, e icon é o SF Symbol que o editor mostra na sua paleta.
sparkline(values, min=None, max=None, fill=False)Uma linha através de uma série de números, a preencher o espaço que lhe dão. Sem min e max, ajusta-se aos seus valores. Só um plugin a pode desenhar, e foi feita para uma 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")
O plugin WPM Sparkline, completo: oferece um elemento e desenha nele os últimos trinta segundos do seu ritmo.

Um rosto é uma árvore de nós, como uma página de definições, mas uma tecla não é uma página de definições: só os nós que desenham são usados e tudo o que se toca é ignorado. A tecla já pertence ao esquema, por isso não há lugar para um botão lá dentro.

O QUE UM ROSTO PODE DESENHAR

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

Dimensione-o para uma tecla. width conta-se em larguras de tecla: 1 é uma letra e 3 é cerca de um terço de uma fila, e a pessoa pode redimensionar depois. O texto tem uma linha e encolhe para caber; as cores seguem por omissão a cor do texto da tecla, por isso um elemento combina com as teclas à volta a menos que peça outra. Uma sparkline guarda os seus últimos 120 pontos, mais do que uma tecla consegue mostrar.

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")
Um relógio numa tecla: sem estado, sem hooks, apenas a hora sempre que é pedida. Os elementos voltam a desenhar-se sozinhos cerca de uma vez por segundo.
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)
Uma meta de palavras por sessão, contada pelo on_word e desenhada como barra. Os hooks guardam o estado; o draw apenas o lê.

Os elementos vivem em esquemas personalizados, por isso há primeiro um esquema para construir. As predefinições não os aceitam: bifurcar uma predefinição para o seu próprio esquema é a primeira coisa que o editor oferece.

  1. Instale o plugin e ligue-o. Os elementos dele aparecem nesse instante.
  2. Abra Esquema, crie ou bifurque um esquema personalizado e vá a Organizar.
  3. Toque em Elemento, escolha o elemento do plugin na faixa e defina a largura.

A pré-visualização do editor desenha cada elemento que o script oferece, mais ou menos do tamanho da tecla onde vai ficar, por baixo da maqueta da barra de espaço. Guarde e coloque uma vez, e o teclado desenha o mesmo.

Um elemento mostra apenas o que o plugin desenha: tocar nele não faz nada, porque a tecla já pertence ao esquema. O esquema mantém o elemento mesmo com o plugin desligado ou desinstalado, e a tecla volta a encher quando o plugin regressa.

Botões e manípulos na barra superior

A faixa por cima das teclas monta-se em Esquema > Barra superior, e um plugin pode oferecer-lhe os seus próprios controlos. bar_items(state) devolve-os: bar_button(...) para algo que se toca, bar_knob(...) para algo que se roda. A lista é lida quando o teclado abre e outra vez depois de cada toque, por isso um item pode aparecer e desaparecer com o estado do plugin.

ChamadaO que faz
bar_items(state)Tudo o que este plugin oferece à barra, em lista. Um id só precisa de ser único dentro do plugin; a barra guarda-o ao lado do id do próprio plugin. Um id repetido fica com o primeiro, e um item que o hook deixa de devolver já não é desenhado.
bar_button(id, name, icon="", title="")Um botão. name é o que o editor lista e o que o VoiceOver lê. icon é um SF Symbol e title são até 12 caracteres de etiqueta ao lado: com ícone e sem title fica só o ícone, sem ícone fica só o texto. Um botão não tem valor nem intervalo. Um toque chama on_action(id, None, state), e tudo o que ele faz acontece aí.
bar_knob(id, name, icon="", min=0, max=1, step=0, value=0, setting=None, art=None, rotor=None)Um manípulo. min e max são o intervalo, step faz com que encaixe em passos quando é maior do que zero, e value é onde começa. Em vez disso, setting= liga-o a um dos controlos numéricos da própria app, que passa então a dar o intervalo e a posição. art= e rotor= desenham-no em 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
Um botão e dois manípulos, um deles ligado ao volume das teclas

Ambos voltam por on_action(action, value, state), identificados pelo id do item, por isso os itens de barra de um plugin e os seus controlos de definições partilham o mesmo hook. Um toque envia None. Um manípulo envia o valor em cada encaixe enquanto o dedo está pousado e mais uma vez quando o largam, por isso um plugin pode seguir o arrasto ou esperar pelo último valor.

Um manípulo com setting= roda um valor que a app já tem, escolhido da lista Comandos e leituras acima, como sound.volume ou haptics.intensity. Um nome que a app não conhece dá um KeyError na consola. O intervalo e a posição inicial vêm desse controlo, a cópia do próprio teclado muda enquanto arrasta, por isso a tecla seguinte já sai no novo nível, e o valor é guardado quando larga. Um manípulo de volume ou de vibração subido a partir do zero volta também a ligar o som ou a vibração durante o arrasto, para que os encaixes se ouçam e se sintam a subir; guardar esse interruptor é o set_setting do próprio plugin dentro de on_action.

Um manípulo pode ser desenhado em Python. art= é a parte que fica parada, o aro ou o corpo, e rotor= é a face que roda, de -135 graus no mínimo a +135 graus no máximo. Ambos aceitam as mesmas formas, tintas, gradientes e sombras da arte de tecla, até dezasseis formas cada, e unit="key" escala as coordenadas para o mostrador quadrado, onde um ponteiro com y negativo aponta para cima. Ao desenho chegam a cor de texto da barra como "text" e o acento do tema como "accent". Deixe os dois de fora e o manípulo fica um anel com o ícone lá dentro; no editor também se pode dar a um manípulo já na barra qualquer um dos acabamentos incluídos.

Nada chega à barra sozinho. Um item coloca-se à mão, tal como o menu e as sugestões.

  1. Instale o plugin e ligue-o. Os seus itens de barra aparecem logo.
  2. Abra Esquema > Barra superior.
  3. Adicione o botão ou o manípulo do plugin e arraste-o para o sítio certo. Um manípulo tem ainda um acabamento a escolher.

Os construtores saltam sem erro uma palavra-chave que não conhecem. setting=, min=, max=, step=, value=, art= e rotor= são só do bar_knob, por isso um bar_button a quem se passe uma continua a ser construído como botão simples e nada é dito: o trabalho de um botão vai no on_action. Ao contrário, title= é do botão, e um manípulo salta-o.

Efeitos de luz

Um plugin pode acrescentar a sua própria iluminação das teclas. effects(state) devolve entradas light_effect(...), e cada uma aparece na app em Efeitos > De plugins, ao lado dos estilos de origem e dos efeitos que as pessoas criam. Um efeito são dados, não um desenho: uma pilha de camadas que o próprio teclado anima. Nada no script corre por fotograma, e as faces, as letras, o brilho, o brilho ao premir e o repouso funcionam como nos estilos de origem.

ChamadaO que faz
light_effect(id, name, layers=[...], colors=[], icon="")Um efeito. id só tem de ser único dentro do plugin. layers é uma lista de light_layer(...), aplicada de cima para baixo. colors são até oito strings "#rrggbb" que a cor percorre em ciclo; sem elas, o efeito segue a definição de Cor da pessoa.
light_layer(pattern, ...)Uma camada: um padrão da lista abaixo. Todas as palavras-chave são opcionais.
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"),
        ]),
    ]
Uma base estável com uma onda lenta por três cores por cima. A onda soma a sua luz à base e move a cor à passagem.

Padrões

  • solid
  • pulse
  • wave
  • gradient
  • twinkle
  • sweep
  • rain
  • flicker
  • checker
ChaveO que faz
moves="light"O que o padrão muda: "light" para o brilho, "color", ou "both".
mix="add"Como o seu brilho se combina com as camadas de cima: "add", "max" para ficar com o mais brilhante, ou "multiply" para as escurecer como uma máscara.
shape="smooth"A subida e descida de pulse, wave e gradient: "smooth", "ramp", "step" ou "spike".
direction="right"Para onde avançam wave, gradient, sweep e rain: "right", "left", "down", "up", ou "out" a partir do centro.
speed=1De 0 a 4. Com 0 o padrão fica parado.
size=1De 0,25 a 4: quantas vezes o padrão se repete ao longo do teclado. Em twinkle define quantas teclas acendem, em sweep o comprimento da cauda.
low=0, high=1O brilho entre o qual o padrão se move, cada um de 0 a 1. Um low acima do high inverte-o.
color_span=1, color_offset=0Quanto o padrão avança pelas cores e onde começa, cada um 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),
        ]),
    ]
Um sweep usado como máscara. A primeira camada só move a cor, e o sweep decide que teclas acendem. Sem colors segue a definição de Cor, por isso com Espetro fica um arco-íris.

Escolher um efeito copia-o para as definições da pessoa, por isso continua a funcionar com o plugin desligado. Enquanto o plugin está ligado e um dos seus efeitos está em uso, o teclado volta a ler effects(state) ao abrir e depois cerca de uma vez por segundo, e troca o que mudou. É assim que um efeito segue o estado, a hora ou 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, do plugin Light Show: uma onda que acelera quanto mais depressa escreve. Uma camada pode mudar de velocidade sem que o padrão salte.

O botão effects no editor lista o que o script oferece. Para ver um em movimento, guarde o plugin, escolha o efeito em Efeitos > De plugins e observe o teclado no topo desse ecrã.

Arredonde tudo o que oscila, como uma velocidade de escrita. Um efeito que volta diferente a cada segundo é trocado a cada segundo sem proveito. Um padrão ou palavra-chave desconhecidos dão um ValueError na consola do editor, e um efeito sem camadas fica de fora.

Aspetos

Sete hooks oferecem à app coisas para escolher: key_styles, themes, popups, animations, backgrounds, trails e layouts. Cada um devolve entradas criadas com as chamadas abaixo, e aparecem em De plugins ao lado das opções incluídas. Os temas são a exceção: têm o seu próprio separador Plugins no editor de temas. Um aspeto são números e palavras, não código de desenho, por isso nada no script corre a cada fotograma. Escolher um copia-o para as definições da pessoa, por isso continua a funcionar com o plugin desligado, e escolher depois uma opção incluída põe-no de lado.

ChamadaO que faz
key_style(id, name, material=, variant=, shape=, shadow=, cap=cap(...))Um estilo de tecla, aplicado ao tema aberto no editor de temas, a partir do cartão Estilo. Só mudam os campos que indica: material, variant, shape, glass, fan, os inner_radius, face_inset, edges, raised e light_angle mecânicos, shadow (0 é plano) e outline. As cores continuam a ser as do tema.
cap(outline="round", corner=None, travel=2, layers=[cap_layer(...)])Uma face de tecla pintada para um estilo de tecla, empilhada a partir de entradas cap_layer(...). outline é round ou rect, corner substitui o raio, e travel é o quanto a tecla afunda quando é premida.
cap_layer(kind, paint, inset=0, x=0, y=0, blur=0, fade=None, when=[...])Uma camada de uma face de tecla pintada. kind é fill, stroke ou inner, paint aceita a gramática de cor de tecla, um color(...) ou um gradient(...), e when limita a camada a pale, dark, pressed, resting e highlighted. Com moves=False a camada fica quieta enquanto a tecla afunda.
theme(id, name, background=, keys=, key_text=, style=key_style(...), ...)Um tema completo, no separador Plugins do editor de temas. background, keys e key_text são obrigatórios, como cores "#rrggbb"; special, special_text, accent, background_bottom (um degradê para baixo), dark, font e weight são opcionais. style=key_style(...) define o acabamento. Escolhê-lo instala-o como tema personalizado.
popup_style(id, name, shape="tile", width=48, height=56, lift=30, ...)A bolha sobre uma tecla premida, em Aspeto > Pop-ups. shape é "tile", "round" ou "balloon"; width, height, lift e font_size estão em pontos, e response e damping regulam a mola.
entrance(id, name, opacity=0, x=0, y=0, scale=1, tilt=0, spin=0, ...)Como o teclado chega, em Aspeto > Entrada. opacity, x, y, scale, tilt e spin são o ponto de partida; volta ao repouso com uma mola segundo response e damping.
press_animation(id, name, scale=, x=0, y=0, rotation=0, ...)A forma de uma tecla mantida premida, em Reações > Geometria: scale (ou scale_x e scale_y), x, y e rotation com a pressão completa.
letter_animation(id, name, scale=, x=0, y=0, rotation=0, anchor="center")O efeito curto que uma letra faz a cada toque, em Reações > Letras: os mesmos números no pico, mais anchor.
transition(id, name, x=0, y=0, scale=1, tilt=0, fade=True, duration=None)A passagem entre letras, 123 e #+=, em Aspeto > Transição. x e y dizem quanto se deslocam as teclas antigas, como fração do teclado, e as novas chegam do lado oposto. Também aceita scale, tilt, fade e duration.
background(id, name, layers=[...], colors=[])Um fundo animado, em Aspeto > Fundo: até quatro camadas particles(...) e até oito cores "#rrggbb".
particles(shape="glow", count=40, size=4, speed=20, direction="none", ...)Uma camada de um fundo. shape é "dot", "glow", "streak", "ring" ou "square"; count, size, speed, direction, spread, gravity, wobble, life, twinkle e opacity dão forma ao movimento, e burst lança partículas de cada tecla premida.
layout(id, name, rows=[...], left=[], right=[])Um esquema, em Esquema > Disposição. Cada linha é uma lista de teclas: uma string é uma letra, layout_key(...) é qualquer outra coisa. left e right põem até três teclas ao lado da barra de espaço. Escolhê-lo instala um esquema personalizado normal.
layout_key(glyph, action="insert", width=1)Uma tecla que não é uma simples letra. action é uma de insert, spacer, shift, delete, space, return, numbers, emoji, globe, tab, left, right, undo, redo ou dismiss, e width conta-se em teclas.
trail(id, name, layers=[...], colors=[])Um rasto de deslize. layers são entradas trail_line, trail_stamps e trail_head, desenhadas por ordem, e colors é a paleta que elas indexam.
trail_line(width=1, tail_width=1, color=-1, glow=0, dash=0, gap=0, band=0, flow=0)O traço ao longo do deslize. tail_width afina a ponta antiga, color=-1 funde toda a paleta ao longo da linha, e dash, gap, band e flow partem-na ou põem-na em movimento.
trail_stamps(shape="dot", size=1, spacing=14, scatter=0, spin=0, twinkle=0, color=-1)Formas largadas ao longo do deslize: dot, ring, square, diamond, star, spark ou heart. spacing é o intervalo entre elas em pontos, scatter atira-as para fora da linha, spin roda-as e twinkle fá-las aparecer e desaparecer.
trail_head(shape="dot", size=1.5, pulse=0, opacity=1, color=-1, glow=0)A marca na ponta do dedo. pulse fá-la respirar e glow espalha luz à sua volta.
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),
    ]
Uma animação de cada tipo. Cada uma aparece em De plugins no seu próprio cartão em Aspeto.
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: flocos que caem a balançar e um sopro de luz a partir de cada tecla premida.

Devolve nós trail a partir de trails(state). Cada rasto tem uma paleta colors e camadas como trail_line. Com color=-1, a linha mistura as cores ao longo do deslize. Ativa o plugin e escolhe o seu rasto em De plugins, no seletor de rastos. Guardar elementos gráficos no estado não desenha um rasto.

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

Um aspeto é copiado quando é escolhido, por isso alterar o script mais tarde não altera um aspeto que alguém já usa; tem de ser escolhido outra vez. Uma palavra-chave mal escrita, uma palavra fora da sua lista ou texto onde vai um número é um erro na consola do editor, e os números ficam nos intervalos que os controlos da própria app usam.

Desenho nas teclas

key_art(state) devolve um dict de nomes de tecla para desenho, e o teclado pinta-o dentro da tecla. Os nomes de tecla são os de haptics, com os recursos letters e keys. O desenho é uma lista de shape(...), uma forma única, uma camada art([...]) ou uma lista de camadas, e cada camada passa para o desenho seguinte no seu próprio relógio, por isso uma tecla pode levar duas coisas a moverem-se a velocidades diferentes.

ChamadaO que faz
art(shapes, animate=0, curve="ease_out")Uma camada animada. Passe-lhe um desenho novo e a camada transita para ele ao longo de animate segundos segundo curve: ease_out, linear, ease_in, ease_in_out ou spring. Várias camadas na mesma tecla animam cada uma por si.
shape(kind, anchor="center", unit="pt", x=0, y=0, size=, width=, height=, fill=, stroke=, ...)Uma coisa desenhada: circle, rect, capsule, line, path, text ou icon. Fica em x e y a partir de um anchor da tecla, em pontos ou, com unit="key", em frações da tecla. fill e stroke aceitam uma cor hexadecimal, "text", "accent", um color(...) ou um gradient(...), e há ainda line_width, corner, trim_from, trim_to, rotation, opacity, blur e até três sombras.
graph(values, min=None, max=None, width=0.8, height=0.25, stroke=, fill=)Uma série de números aberta em formas vulgares, para juntar a uma lista de formas ou usar sozinha. Guardam-se as 60 amostras finitas mais recentes, os limites vêm da própria série quando não são dados, e fill acrescenta um traçado fechado até à base por baixo da linha.
color(value, opacity=1)Uma tinta: uma cor hexadecimal, ou "text" ou "accent" resolvido contra a tecla onde assenta, com opacity.
gradient(kind, colors, stops=[], start=, end=, center=, radius=0.5)Um gradiente linear ou radial por uma lista de cores. stops coloca-as, start e end orientam o linear, center e radius situam o radial.
shadow(color, radius=4, x=0, y=0)Uma sombra sob uma forma, até três. Um véu que enche a tecla acompanha os cantos da tecla em vez de os esquadrar; um brilho escapa-se para fora dela.

O desenho é lido outra vez a seguir a cada evento que o plugin ouve, e é esse o ciclo todo: mudar o estado em on_event(name, info, state) e desenhar a partir dele em key_art(state). events(state) nomeia os eventos pretendidos e é lido uma vez quando o plugin carrega. Sem ele, um plugin ouve tudo menos key, key_down, key_up, predictions e tick, que põem um script a correr a cada toque ou a cada segundo e têm de ser pedidos. As mudanças de maiúsculas e de plano voltam a desenhar o traço quer alguém esteja a ouvir quer não, e chegam também ao 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)}
Uma luz de caps lock, desenhada pelo plugin em vez de vir no teclado

Formas

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

Dezasseis formas por tecla, contadas em todas as suas camadas, e 64 pontos por traçado; o que passar disso é deitado fora. Uma forma é desenhada dentro de uma caixa do seu próprio width e height, e uma caixa sem espessura não pinta nada, por isso uma linha horizontal precisa de um height pequeno seu, com os pontos a meio dele (height=0.03, points=[[0, 0.5], [1, 0.5]]), ou não aparece e nada é dito.

Vibração, áreas de toque e correções

haptics(state) e hitboxes(state) devolvem dicts com nomes de tecla como chaves: a própria letra, "space", "delete", "return", "shift" ou "globe", depois "letters" para qualquer letra não indicada e "keys" para tudo o resto. As teclas que um plugin deixa de fora mantêm as definições da pessoa. As duas tabelas são lidas quando o teclado abre e mais ou menos uma vez por segundo, por isso nada corre a cada tecla por causa delas.

ChamadaO que faz
feel(style=None, intensity=None, sharpness=None)Uma vibração: style é "soft", "light", "medium", "heavy", "rigid" ou "off", e intensity e sharpness (de 0 a 1) ajustam-na. Uma palavra de estilo sozinha também serve.
hitbox(scale=1, x=0, y=0)Uma área de toque: x e y deslocam o alvo da tecla essa fração do seu tamanho, meia tecla no máximo, e scale aumenta-o ou reduz-o. Um número sozinho é um scale.
def haptics(state):
    return {
        "space": "heavy",
        "return": "rigid",
        "delete": feel(intensity=0.45, sharpness=0.9),
    }
Heavy Space: uma barra de espaço mais pesada, um retorno nítido e um apagar leve.

on_touch(key, x, y, state) corre depois de cada toque, já tratado, com o ponto da tecla onde o dedo pousou. A posição é medida em relação à tecla desenhada, não ao seu alvo deslocado, por isso um plugin pode mover cada tecla para onde é realmente tocada sem perseguir o seu próprio desvio.

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 onde cada tecla é realmente tocada e move o seu alvo parte do caminho até lá.

suggestions(word, state) põe palavras no início da barra enquanto uma palavra é escrita. correct(word, fix, state) corre uma vez por palavra quando o espaço a termina, com a correção do teclado ou None, mesmo com a correção automática desligada. Devolva uma palavra para a escrever, False para manter a palavra como foi escrita ou None para deixar o teclado decidir. Ganha o primeiro plugin que responde, e apagar logo a seguir desfaz como qualquer correção automática.

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: sugere «be right back» enquanto se escreve brb e mantém a correção automática longe das palavras em maiúsculas.

Nenhum dos dois hooks corre em campos de palavra-passe. A sugestão de correção na barra mostra a correção do teclado, não o que correct devolveria, e uma tabela alterada em on_touch chega ao teclado no ciclo seguinte.

Testar no editor

A Pré-visualização do editor é um teclado substituto. Desenha settings(state) em direto, dispara qualquer hook com um toque usando uma palavra, tecla ou tipo de campo de exemplo, mostra a barra de espaço tal como o plugin a deixou e lista tudo o que o plugin pediu ao teclado. Ali, stats() devolve números inventados, por isso aparece uma velocidade sem escrever.

  1. Use os controlos na pré-visualização. Cada um corre on_action e redesenha.
  2. Toque nos botões dos hooks pela ordem em que o teclado o faria: on_open, depois on_word ou on_key algumas vezes, depois on_close.
  3. Leia a consola. Os comandos aparecem tal como os escreveu, as linhas de print() por baixo, e um erro indica a sua linha.
  4. Recarregue para começar o estado de novo. Editar o script não o repõe sozinho.

A pré-visualização não tem documento, por isso insert() e replace() só registam. Para os experimentar, guarde e escreva em qualquer campo com o teclado aberto.

Limites

Quando um plugin chega como ficheiro ou de um repositório, o Clink verifica-o antes de o guardar. Tem de ter menos de 64.000 bytes e 1.600 linhas, definir pelo menos um hook, importar apenas os módulos permitidos e não conter nenhum destes elementos:

Não permitido em plugins partilhados

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

Módulos que um plugin partilhado pode importar

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

Cada chamada a um hook recebe 2.000.000 passos do interpretador, tal como um desenho de painel. Mas on_key corre a cada toque, por isso trabalho pesado ali torna a escrita lenta muito antes de chegar a esse limite.

O ficheiro

Um plugin é um único documento JSON. id mantém-se estável entre atualizações, version é texto livre mostrado na lista, icon é o nome de um SF Symbol e source é o script com as quebras de linha escapadas. Partilhe-o a partir do inspetor do editor, ou importe um com o botão de seta no separador 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..."
}
Um .clinkplugin, abreviado.

Os plugins são partilhados como ficheiros .clinkplugin, um documento JSON com o script lá dentro. São publicados através de um repositório tal como os painéis, com uma pasta de ficheiros, um manifesto e uma versão. O repositório oficial é anti-ltd/clink-plugins.

Repositórios ›

Quando algo não acontece

Na maior parte das vezes é uma destas.

SintomaO que verificar
Nada aconteceOs plugins estão desligados no topo do separador Plugins, o plugin está desligado na lista, ou não há subscrição Clink Pro. Qualquer das três deixa o teclado sem correr plugin nenhum.
O texto da barra de espaço está desativadoUm plugin assumiu-o. A linha por baixo do campo indica qual; desligue o interruptor desse plugin, ou o próprio plugin, para recuperar o campo.
A secção não apareceA âncora está mal escrita. Compare-a com Mostrar IDs na app, e lembre-se de que a secção continua a aparecer na página do plugin, que é onde deve olhar primeiro.
Uma tecla de elemento está vaziaO plugin por trás está desligado, desinstalado, ou o draw(id, state) dele lançou um erro. O esquema mantém a tecla de qualquer forma, por isso volta a encher assim que o plugin estiver ligado. Veja o erro na consola do editor.
Um elemento nunca mudaO draw continua a ser chamado, por isso o que não se mexe é o estado. O que alimenta o rosto tem de ser atualizado a partir de um hook: on_tick para algo que muda sozinho, on_word ou on_key para algo que segue a escrita.
set_setting() não fez nadaUm nome que não está na lista acima lança KeyError. Um valor do tipo errado ou fora do intervalo é ignorado, e a consola do editor indica o que a definição espera. Uma definição reservada também volta atrás sem subscrição.
A contagem voltou a zeroO teclado guarda o estado ao fechar, não a cada tecla. Um teclado que o sistema mata a meio da sessão perde o que os seus plugins contaram desde que abriu. Mantenha os totais nos hooks do lado da app quando isso importar.
Escrever parece lentoAlgo pesado corre em on_key. Passe-o para on_word, faça menos, ou guarde em cache no estado o que ele calcula.
Falta um efeito em De pluginsPlugins tem de estar ligado e o plugin ativado, e effects(state) tem de devolver um light_effect com pelo menos uma camada. Toque em effects no editor para ver o que voltou, e procure um ValueError na consola.
Um item da barra superior não faz nada quando é usadoUm toque e um manípulo largado chegam ambos a on_action(action, value, state), ligados pelo id do item e não pelo nome. Um manípulo só escreve uma definição quando o bar_knob nomeia uma em setting=; sem isso o valor fica a cargo do plugin, e um botão nunca escreve uma definição.
PyMini ›
Descarregar na App Store