循环

面板要定义 view(state),另外可以定义 initial()、on_open(state) 和 on_action(action, state)。Clink 调用 initial() 一次取得起始状态,每次面板出现时调用 on_open,然后调用 view(state) 搭出界面。控件要么把文字插到你正在写的地方,要么设置新状态,要么指定一个交给 on_action 处理的动作,之后界面重新搭一次。

def initial():
    return {"count": 0, "step": 1}

def view(state):
    return vstack([
        text(f"{state['count']}", size=34, weight="bold", align="center"),
        segmented(["1", "5", "10"], value=str(state["step"]), action="step"),
        hstack([
            button("−", "bump", value=-1),
            button("+", "bump", value=1, style="primary"),
        ]),
        button("Type it", "type", icon="keyboard"),
    ])

def on_action(action, value, state):
    if action == "bump":
        state["count"] = state["count"] + value * state["step"]
    elif action == "step":
        state["step"] = int(value)
    elif action == "type":
        insert(str(state["count"]))
        close()
    return state
起步面板,正是编辑器里“新建面板”按钮写出来的那份。

计算器、代码片段板、选择器:你的面板会出现在键盘面板按钮后面。

构建函数

view(state) 返回由这 19 个函数创建的节点。可以随意嵌套:堆叠和网格接收一组子元素。

构建函数绘制内容
vstack(children, spacing=6, align="leading")把子元素自上而下堆叠。
hstack(children, spacing=6, align="center")把子元素横向排开。
grid(children, columns=4, spacing=6)把子元素铺进等宽的列里。
wrap(children, spacing=6)把子元素横向排开,放不下就换到下一行。
scroll(children, spacing=6)独立的滚动区域,用来放比面板更长的列表。
card(children, padding=10, color="")把子元素放在一块底面上,四周留出内边距。
spacer(size=0)把后面的内容推到尽头。
divider()行与行之间的细线。
text(s, size=17, weight="regular", color="", align="leading", lines=0, mono=False)一行文字。weight 可取 regular、medium、semibold、bold、heavy、light、thin;align 可取 leading、center、trailing;lines 限制最多折成几行;color 接受颜色名或 #RRGGBB。
icon(name, size=20, color="")按名称放一个 SF Symbol 图标。
badge(s, color="")在着色的胶囊里放一个短词。
progress(value, total=1, label="")一条进度条,在 total 中填到 value。
button(label, action="", value=None, insert="", set=None, style="plain", icon="", enabled=True)一个按钮。insert 会把它的文字打进你正在写的地方,set 把键合并进 state,action 则给 on_action 指定一个名字。style 可取 plain、primary、tinted、quiet、destructive。
row(title, subtitle="", detail="", icon="", action="", value=None, insert="")可点按的一行:标题、第二行、图标,右侧还有一个附注。
toggle(label, on=False, key="", action="")一个开关。key 会把 True 或 False 写进那个状态键。
slider(value, min=0, max=100, step=1, label="", key="", action="")在 min 与 max 之间的滑块。key 会把当前位置写进那个状态键。
stepper(value, min=0, max=100, step=1, label="", key="", action="")一个带 − 和 + 的数值,取值在 min 与 max 之间。
segmented(options, value=None, key="", action="")从一组里选一个。key 会把选中的项写进那个状态键。
field(key, placeholder="", action="", submit="")绑定到 state[key] 的文本框。点一下,按键就写进那个键;submit 指定回车要运行的处理函数名。

普通字符串在任何能放节点的位置都成立:它会按默认字号显示为文本。

响应点按

需要算点什么的控件不再携带写死的 set,而是指定一个 action。Clink 随后会调用 on_action(action, state);想拿到控件携带的值时,就写成 on_action(action, value, state)。state 是普通字典,既可以就地改动、什么都不返回,也可以返回一个新的。

def initial():
    return {"items": [], "draft": ""}

def view(state):
    return vstack([
        field("draft", placeholder="Add one…", submit="add"),
        scroll([row(item, action="use", value=item) for item in state["items"]]),
    ])

def on_action(action, value, state):
    if action == "add" and value:
        state["items"].append(value)
        state["draft"] = ""
    elif action == "use":
        insert(value)
        close()
    return state
一个可以往里添加、点一下就插入的列表。

on_open(state) 在每次面板出现时运行,面板就是在这里从你正在写的内容取初值的。

请键盘代劳

在 on_action 和 on_open 里,面板可以请键盘做一些返回值表达不了的事。每次调用都会先记下来,等你的函数返回之后再执行,所以面板绝不会在自己界面还在构建时去改输入框。

命令

  • insert()
  • backspace()
  • delete_word()
  • replace()
  • move_cursor()
  • copy()
  • close()
  • haptic()
  • toast()
def on_action(action, state):
    if action == "quote":
        word = context()["word"]
        replace(len(word), "“" + word + "”")
        haptic("light")
    return state
把光标处的词换成加了引号的同一个词。

context() 读取光标周围的内容:before、selected、after、word、clipboard、locale、language、full_access 和 time。它是调用开始时拍下的快照,不会在渲染途中被改掉。

状态

state 就是一个普通字典,脚本能造出来的东西它都放得下:数字、字符串、布尔值、None,以及由它们组成的列表和字典。每次调用都会把它交还给你,所以上一次点按往列表里加的东西,下一次还在。

def initial():
    return {"name": ""}

def view(state):
    return vstack([
        field("name", placeholder="Who?"),
        button("Greet", insert="Hi " + state["name"] + "!", style="primary"),
    ])
输入框会直接写进你指定的状态键。

打开“记住状态”后,面板里的内容在重新打开、甚至重新启动键盘之后都还在。计算器可以关着,列表建议打开。

只要键盘还在屏幕上,state 就还在;面板设为记住时留得更久。面板可以用 context() 读到光标周围的文字,除此之外读不到关于你的任何东西。

限制

键盘拿到的内存只是应用的一小部分,所以面板靠边界约束,而不是靠信任。每次绘制有 2,000,000 步解释器额度,失控的循环会在屏幕上报错收场,而不是让键盘不再响应。

以文件形式或从仓库来的面板,在保存前会先检查:不超过 48,000 字节和 1,200 行,必须定义 def view(,每一处 import 都必须指向沙箱内的命名空间之一。下面这些片段一律拒收:

共享面板中被拒绝

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

共享面板中可导入的命名空间

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

你在 app 里自己写的面板不受这份名单约束。这时能拦住失控循环的只剩步数预算,所以记得给它留个出口。

位置

给每个面板在选择器里单独一个按钮,不再全塞进一个“面板”按钮里。单个面板可以在自己的编辑器里覆盖这个设置。

“默认”跟随那个开关,“独立”始终占据自己的按钮,“分组”始终嵌在里面。

动手写一个

  1. 在 Clink 里打开自定义面板,点按“新建面板”。
  2. 写脚本。旁边的预览会运行真正的面板,错误随打随现。
  3. 保存、打开这个面板,然后在键盘上点开面板按钮。

“共享面板”会写出 .clinkpanel 文件,也就是 JSON。在 Clink 里打开它时会以新的 id 保存,所以同一个面板导入两次也不会覆盖第一个。

创作器 ›
在 App Store 下载