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 stateOs 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.
- Abra o separador Plugins, ligue os plugins no topo e toque em + para um novo plugin.
- O editor abre no script inicial. Leia-o uma vez: initial(), settings(), on_action(), on_open() e on_word() são tudo.
- 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.
- Guarde. O plugin fica ligado na lista e o seu interruptor passa a estar também em Teclas > Barra de espaço.
- 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 stateHooks
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.
| Hook | Quando é 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 stateA 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 stateEstado
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 stateDevolver 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"),
])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.
| Construtor | O 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. |
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.heatmapanalytics.privacyanalytics.trendsanalytics.typing-testautomations.rulesgestures.accentsgestures.cursorgestures.deletegestures.generalgestures.suggestionsgestures.swipehaptics.feelkeys.adaptivekeys.faceskeys.hitboxeskeys.hitmapkeys.long-presskeys.numberrowkeys.onehandedkeys.roundnesskeys.sizekeys.spacebarkeys.spacingkeys.splitlanguages.applanguages.customlanguages.managelanguages.packslanguages.switchlanguages.typinglayout.arrangelayout.arrangementlayout.buildlayout.longpresslayout.presetslayout.topbarmotion.deletemotion.entrancemotion.glowmotion.key-pressmotion.key-responsemotion.lettersmotion.space-responsemotion.transitionpopups.stylesound.keysoundstext.automationtext.contenttext.correctionstext.historytext.punctuationtext.speedtext.suggestionstext.symbolsthemes.backgroundthemes.canvasthemes.theme
keys.spacebar 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 stateDois 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.
| Chamada | O 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. |
| Chave | O 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
analyticsemoji.skin_toneemoji.trailing_spacegestures.cursorgestures.cursor_stylegestures.highlight_shiftgestures.plane_slidegestures.predictive_flickgestures.quick_accentgestures.swipegestures.swipe_deletegestures.swipe_multi_wordgestures.swipe_space_commitgestures.swipe_two_thumbgestures.trailgestures.trail_stylegestures.trail_widthhaptics.enabledhaptics.intensityhaptics.sharpnesskeys.accentskeys.glyph_scalekeys.heightkeys.long_press_hintkeys.popup_stylekeys.popupskeys.radiuskeys.row_spacingkeys.spacingkeys.uppercasekeys.widthlanguagelanguage.bar_keylanguage.bar_key_stylelanguage.modelayoutlayout.dismiss_shortcutlayout.number_rowlayout.number_row_scalelayout.one_handedlayout.one_handed_shortcutlayout.one_handed_sidelayout.one_handed_widthlayout.splitlayout.split_gaplayout.split_number_rowlayout.split_shortcutlayout.split_space_barpet.cornerpet.enabledpet.speciessettings.accentHoldDelaysettings.accentMoveCancelsettings.activateWithIconsettings.adaptiveGrowsettings.adaptiveHitboxessettings.adaptivePredictAtWordStartsettings.adaptivePredictionWeightsettings.adaptiveShrinksettings.adaptiveSpacesettings.aiAutocorrectsettings.aiCompletionssettings.aiExtensionEnabledsettings.aiSearchsettings.aiToolsColorOverridessettings.aiToolsDiffStylesettings.aiToolsDisabledsettings.aiToolsLayoutStylesettings.aiToolsOrdersettings.aiToolsPromptOverridessettings.aiTranslatesettings.alternatingSplitsettings.arabicIndicNumeralssettings.backgroundEffectOverridesettings.clipboardCloseOnPastesettings.clipboardDeleteOnPastesettings.clipboardIgnoreImagessettings.clipboardIgnorePinsOnDeletesettings.clipboardStylesettings.cursorActivationHapticsettings.cursorLineStridesettings.cursorStepHapticsettings.customPanelsStandalonesettings.customPetIDsettings.deleteWordSwipeEngagesettings.deleteWordSwipeStridesettings.dictationAssistsettings.dictationAssistCustomsettings.dictationAssistLevelsettings.dictationColorSchemesettings.dictationGlassCapsulesettings.dictationSmartActionssettings.dictationStylesettings.dictationVisualStylesettings.dragUpThresholdsettings.emojiCategoryOrdersettings.emojiCellSpacingsettings.emojiColumnCountsettings.emojiCrossAxisSwitchesTabsettings.emojiCustomSetssettings.emojiGlyphScalesettings.emojiHiddenCategoriessettings.emojiHiddenFromPanelssettings.emojiRecentsCapsettings.emojiRecentsSortsettings.emojiRememberCategorysettings.emojiRowCountsettings.emojiScrollDirectionsettings.emojiSearchSlotsettings.emojiShowABCKeysettings.emojiShowBackspaceKeysettings.emojiStartCategoryIDsettings.emojiTabBarSlotsettings.emojiTabIconStylesettings.emojiToneHoldDelaysettings.extensionOrdersettings.extraTopBarssettings.formFreeCornerssettings.formLayoutEnabledsettings.gifShareAsLinksettings.glassPerRowMergesettings.glassReleaseResponsesettings.gridSwitchAnimationsettings.gridSwitchDurationsettings.handwritingInkColorsettings.handwritingInkGlowsettings.handwritingInkStylesettings.handwritingInkWidthsettings.hitboxScalesettings.iconPickerStylesettings.keyBloomScalesettings.keyLightingsettings.keyPressGlowsettings.keyPressInstantsettings.keyPressLingersettings.keySpringDampingsettings.keySpringResponsesettings.keyboardBottomPaddingsettings.keyboardLanguagessettings.keyboardTopPaddingsettings.longPressGlyphScalesettings.minPressVisiblesettings.notepadBrowseStylesettings.notepadModesettings.numberRowFontSizesettings.numberRowLeadingKeyssettings.numberRowTrailingKeyssettings.oneHandedCustomKeyssettings.oneHandedExtraKeyssettings.panelButtonHitboxScalesettings.persistentLeadingKeyssettings.persistentTrailingKeyssettings.petIdleMotionsettings.petScalesettings.pinyinFuzzyEnabledsettings.pluginLookssettings.popupSpringDampingsettings.popupSpringResponsesettings.predictiveFlickSuggestionPositionsettings.predictiveFlickSuggestionsSeparatesettings.reduceEffectsOnLowPowersettings.repeatAccelStepsettings.repeatHoldDelaysettings.repeatInitialIntervalsettings.repeatMinIntervalsettings.replacementsLayoutsettings.rowInsetssettings.secondaryNeuralModelsEnabledsettings.separateActivationsettings.separateLanguageLayoutssettings.showCaptureOverlaysettings.showCorrectionFieldsettings.showHitboxOverlaysettings.showIconsBeforeTypingsettings.showRecentEmojisettings.showTouchHeatmapsettings.showTouchSurfaceBoundssettings.showTouchTelemetryOverlaysettings.slideUpPickerStylesettings.solidPopupOpacitysettings.soundVoicesettings.spaceBloomScalesettings.spaceCursorActivationDelaysettings.spaceCursorDragScalesettings.spaceCursorStridesettings.spaceLeanMultipliersettings.spaceSpringDampingsettings.spaceSpringResponsesettings.spatialBiasEnabledsettings.spatialBiasGainsettings.splitIndicessettings.splitOtherPlanessettings.stickerGridSnapsettings.stickerPlacementssettings.suggestionDebounceDelaysettings.suggestionHitboxScalesettings.suggestionSegmentLiquidGlasssettings.suggestionSegmentStylesettings.suggestionSegmentsFollowThemesettings.suggestionSeparatorColorsettings.suggestionSeparatorStylesettings.suggestionTopPaddingsettings.swipeKeyMorphsettings.swipeMorphRadiussettings.swipeMorphStrengthsettings.swipeTrailEndWidthsettings.swipeTrailMaxLengthsettings.swipeTrailStartWidthsettings.swipeTrailTrimsettings.toolsButtonStylesettings.toolsHiddenFromPanelssettings.topBarItemssettings.topBarOrdersettings.translateLanguageOrdersettings.translateLanguagesDisabledsettings.translateStylesettings.translateTonesettings.vietnameseInputMethodsound.enabledsound.packsound.volumespacebar.cornerspacebar.language_codespacebar.sizespacebar.textstickers.enabledtext.arithmetictext.auto_capitalizetext.auto_punctuationtext.autocorrecttext.autocorrect_everywheretext.contactstext.conversionstext.double_space_periodtext.learningtext.punctuation_spacingtext.return_to_letterstext.revert_on_deletetext.smart_quotestext.suggestionstext.suggestions_animationtext.suggestions_heighttext.suggestions_scrolltext.suggestions_stylethemetheme.backgroundtheme.darktheme.delete_glyphtheme.entrancetheme.glyph_presstheme.lighttheme.match_systemtheme.press_styletheme.reactive_backgroundtools.calculatortools.clingtools.clipboardtools.conversiontools.dictationtools.dictionarytools.emojitools.giftools.handwritingtools.layout_switchertools.notepadtools.plugin_switchertools.profilestools.replacementstools.textfxtools.theme_switchertools.translate
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 stateSHORTCUTS = {"omw": "on my way", "brb": "be right back", "ty": "thank you"}
def on_key(key, state):
if key != " ":
return state
words = context()["before"].split()
if words and words[-1] in SHORTCUTS:
# the space is already in the text, so it goes too and comes back after
replace(len(words[-1]) + 1, SHORTCUTS[words[-1]] + " ")
return statedef initial():
return {"goal": 200, "count": 0}
def settings(state):
return vstack([
stepper(state["goal"], min=50, max=2000, step=50, label="Words per session", key="goal"),
progress(state["count"], total=state["goal"], label=f"{state['count']} of {state['goal']}"),
])
def on_open(state):
state["count"] = 0
return state
def on_word(word, state):
state["count"] += 1
if state["count"] == state["goal"]:
haptic("medium")
banner("Goal reached")
return statedef initial():
return {"muted": False, "was_on": True}
def on_field(kind, state):
# a password or a number field is not a place for key sounds
quiet = kind in ("password", "number", "phone")
if quiet and not state["muted"]:
state["was_on"] = setting("sound.enabled")
state["muted"] = True
set_setting("sound.enabled", False)
elif not quiet and state["muted"]:
state["muted"] = False
if state["was_on"]:
set_setting("sound.enabled", True)
return stateNAMES = ["sam", "ana", "team"]
def on_key(key, state):
typing = context()["before"].split(" ")[-1]
if typing.startswith("@"):
start = typing[1:].lower()
suggest([n for n in NAMES if n.startswith(start)])
state["showing"] = True
elif state.get("showing"):
suggest([]) # the mention is over, give the bar back
state["showing"] = False
return statedef on_swipe(direction, state):
if direction == "left":
delete_word()
elif direction == "right":
press("space") # the space bar's own path, so it autocorrects
elif direction == "up":
pick_suggestion("center")
elif direction == "up_left":
pick_suggestion("left")
elif direction == "up_right":
pick_suggestion("right")
return stateElementos 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.
| Chamada | O 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")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
sparklinetextbadgeiconprogresshstackvstackspacerdivider
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")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)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.
- Instale o plugin e ligue-o. Os elementos dele aparecem nesse instante.
- Abra Esquema, crie ou bifurque um esquema personalizado e vá a Organizar.
- 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.
| Chamada | O 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 stateAmbos 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.
- Instale o plugin e ligue-o. Os seus itens de barra aparecem logo.
- Abra Esquema > Barra superior.
- 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.
| Chamada | O 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"),
]),
]Padrões
solidpulsewavegradienttwinklesweeprainflickerchecker
| Chave | O 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=1 | De 0 a 4. Com 0 o padrão fica parado. |
size=1 | De 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=1 | O 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=0 | Quanto 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),
]),
]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),
]),
]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.
| Chamada | O 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),
]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),
]),
]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)])]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.
| Chamada | O 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)}Formas
circlerectcapsulelinepathtexticon
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.
| Chamada | O 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),
}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 boxessuggestions(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 NoneNenhum 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.
- Use os controlos na pré-visualização. Cada um corre on_action e redesenha.
- 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.
- Leia a consola. Os comandos aparecem tal como os escreveu, as linhas de print() por baixo, e um erro indica a sua linha.
- 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
jsonmathrandomresystime
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..."
}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.
| Sintoma | O que verificar |
|---|---|
| Nada acontece | Os 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á desativado | Um 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 aparece | A â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á vazia | O 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 muda | O 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 nada | Um 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 zero | O 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 lento | Algo 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 plugins | Plugins 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 é usado | Um 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. |