From 3a8c1547397f0d4e1c6b8bb2f967fbc02e5df3e0 Mon Sep 17 00:00:00 2001 From: Fini Date: Sun, 9 Aug 2026 19:53:09 +0800 Subject: [PATCH] fix(renderer): keep transparent decoration layers out of the way of clicks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two carousel templates stack a full-bleed transparent shell (washi tape, punch holes, film sprockets) in front of their content. Every click over the board landed on that shell instead of the text behind it. The engine half is the jian bump: a container that paints nothing no longer claims its own body in the hit-test. The template half marks those shells `locked`, so the shell is unselectable as a layer while the marks inside it stay individually selectable — dragging a piece of tape no longer drags the whole layer, and a marquee no longer nets the board. Both templates are regenerated output, so they also carry the row wrap their generators gained in the same pass. Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x --- .../cityguide-film-carousel.op | 30 ++++++++++++++----- .../tutorial-journal-carousel.op | 24 +++++++++++---- .../_generators/tpl_cityguide_carousel.py | 24 +++++++++++---- .../_generators/tpl_tutorial_carousel.py | 22 +++++++++++--- vendor/jian | 2 +- 5 files changed, 78 insertions(+), 24 deletions(-) diff --git a/crates/op-editor-core/assets/scene_templates/cityguide-film-carousel.op b/crates/op-editor-core/assets/scene_templates/cityguide-film-carousel.op index 755ebfffb..3b397e96e 100644 --- a/crates/op-editor-core/assets/scene_templates/cityguide-film-carousel.op +++ b/crates/op-editor-core/assets/scene_templates/cityguide-film-carousel.op @@ -60,6 +60,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -1844,6 +1845,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -1929,6 +1931,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -3726,6 +3729,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -3811,6 +3815,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -5935,6 +5940,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -6020,6 +6026,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -7817,6 +7824,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -7875,8 +7883,8 @@ ] } ], - "x": 3600, - "y": 0 + "x": 0, + "y": 1800 }, { "type": "frame", @@ -7902,6 +7910,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -9881,6 +9890,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -9939,8 +9949,8 @@ ] } ], - "x": 4800, - "y": 0 + "x": 1200, + "y": 1800 }, { "type": "frame", @@ -9966,6 +9976,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -11626,6 +11637,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -11684,8 +11696,8 @@ ] } ], - "x": 6000, - "y": 0 + "x": 2400, + "y": 1800 }, { "type": "frame", @@ -11711,6 +11723,7 @@ "height": 1440, "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -13612,6 +13625,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "rectangle", @@ -13670,8 +13684,8 @@ ] } ], - "x": 7200, - "y": 0 + "x": 0, + "y": 3600 } ] } diff --git a/crates/op-editor-core/assets/scene_templates/tutorial-journal-carousel.op b/crates/op-editor-core/assets/scene_templates/tutorial-journal-carousel.op index 1dd1100bf..119d5b2e9 100644 --- a/crates/op-editor-core/assets/scene_templates/tutorial-journal-carousel.op +++ b/crates/op-editor-core/assets/scene_templates/tutorial-journal-carousel.op @@ -59,6 +59,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "path", @@ -480,6 +481,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "ellipse", @@ -654,6 +656,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "path", @@ -1310,6 +1313,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "ellipse", @@ -1484,6 +1488,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "path", @@ -2019,6 +2024,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "ellipse", @@ -2193,6 +2199,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "path", @@ -2874,6 +2881,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "ellipse", @@ -3022,8 +3030,8 @@ ] } ], - "x": 3600, - "y": 0 + "x": 0, + "y": 1800 }, { "type": "frame", @@ -3048,6 +3056,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "path", @@ -3474,6 +3483,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "ellipse", @@ -3622,8 +3632,8 @@ ] } ], - "x": 4800, - "y": 0 + "x": 1200, + "y": 1800 }, { "type": "frame", @@ -3648,6 +3658,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "path", @@ -4258,6 +4269,7 @@ "height": "fill_container", "layout": "none", "fill": [], + "locked": true, "children": [ { "type": "ellipse", @@ -4406,8 +4418,8 @@ ] } ], - "x": 6000, - "y": 0 + "x": 2400, + "y": 1800 } ] } diff --git a/templates/step0/_generators/tpl_cityguide_carousel.py b/templates/step0/_generators/tpl_cityguide_carousel.py index 11c476ae2..ef2c7d18e 100644 --- a/templates/step0/_generators/tpl_cityguide_carousel.py +++ b/templates/step0/_generators/tpl_cityguide_carousel.py @@ -111,6 +111,13 @@ CJK = "Noto Sans SC" NUM = "DM Mono" W, H, GAP = 1080, 1440, 120 + +# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上 +# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以 +# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素, +# 第二行的帧名会压到上一行的板上。 +BOARDS_PER_ROW = 3 +ROW_GAP = GAP + 240 BORDER = 24 # 相纸白边 EDGE = 80 - BORDER # 片窗内的左右内边距,合计仍是 80 TOP, BOT = 96 - BORDER, 128 - BORDER @@ -241,7 +248,13 @@ def footer(edge_code): def board(page, name, main, edge_code): - """一板。外壳是相纸白,白边之内才是片基 —— 白边是本套的第一识别信号。""" + """一板。外壳是相纸白,白边之内才是片基 —— 白边是本套的第一识别信号。 + + 齿孔层与色偏层都上 `locked`:两者都是铺满整板的壳子,自己一寸不画(墨 + 在里面那 24 个孔 / 两条色带上)。齿孔层还压在正文之上,不锁的话它自身 + 就是一块盖住全板的透明板。locked 让壳子不可选而壳内的孔照常可选 + (hit_test_walk 的 locked 判定在子节点遍历之后)。 + """ content = frame(ids, f"{name} · 内容", width="fill_container", height="fill_container", layout="vertical", padding=[TOP, EDGE, BOT, EDGE], gap=0, @@ -253,19 +266,20 @@ def board(page, name, main, edge_code): fill=solid("$c-bg"), clipContent=True) # 装饰(色偏)最后 —— jian 里 index 0 最上,写反了会盖住正文。 cast = frame(ids, f"{name} · 色偏", width="fill_container", - height="fill_container", layout="none", fill=[]) + height="fill_container", layout="none", fill=[], + locked=True) cast["children"] = colour_cast() gate["children"] = [content, cast] shell = frame(ids, f"{page:02d} {name}", width=W, height=H, layout="none", fill=solid("$c-print"), padding=BORDER, clipContent=True) holes = frame(ids, f"{name} · 齿孔", width=W, height=H, layout="none", - fill=[]) + fill=[], locked=True) holes["children"] = sprockets() holes["x"], holes["y"] = -BORDER, -BORDER shell["children"] = [holes, gate] - shell["x"] = (page - 1) * (W + GAP) - shell["y"] = 0 + shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP) + shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP) return shell diff --git a/templates/step0/_generators/tpl_tutorial_carousel.py b/templates/step0/_generators/tpl_tutorial_carousel.py index edaf21a23..312f50d82 100644 --- a/templates/step0/_generators/tpl_tutorial_carousel.py +++ b/templates/step0/_generators/tpl_tutorial_carousel.py @@ -102,6 +102,13 @@ CJK = "Noto Sans SC" NUM = "Inter" W, H, GAP = 1080, 1440, 120 + +# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上 +# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以 +# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素, +# 第二行的帧名会压到上一行的板上。 +BOARDS_PER_ROW = 3 +ROW_GAP = GAP + 240 EDGE = 80 RING_GUTTER = 56 # 活页孔占掉的左侧宽度 TEXT_EDGE = EDGE + RING_GUTTER @@ -265,6 +272,11 @@ def board(page, name, main, tapes): jian 的兄弟顺序是 index 0 最上(canvas_viewport_paint_mask 反着遍历)。 胶带层用 group:group 不是图片投放目标(image_drop.rs 不匹配 Group), 所以它盖在整板上也不会截走用户拖进来的图。 + + 两个装饰层都上 `locked`:它们是 fill_container 的满幅壳子,压在正文之 + 上,但自己一寸也不画(真正的墨在里面那几条胶带 / 六个孔上)。locked 让 + 壳子自身不可选,壳内的胶带和孔照常可选(hit_test_walk 的 locked 判定在 + 子节点遍历之后),于是拖动装饰不会拽走整层,框选也不会一网兜住整板。 """ content = frame(ids, f"{name} · 内容", width="fill_container", height="fill_container", layout="vertical", @@ -273,18 +285,20 @@ def board(page, name, main, tapes): content["children"] = [header(page), main, footer()] tape_layer = group(ids, f"{name} · 胶带", width="fill_container", - height="fill_container", layout="none", fill=[]) + height="fill_container", layout="none", fill=[], + locked=True) tape_layer["children"] = tapes holes = frame(ids, f"{name} · 活页孔", width="fill_container", - height="fill_container", layout="none", fill=[]) + height="fill_container", layout="none", fill=[], + locked=True) holes["children"] = ring_holes() shell = frame(ids, f"{page:02d} {name}", width=W, height=H, layout="none", fill=solid("$c-bg"), clipContent=True) shell["children"] = [tape_layer, content, holes] - shell["x"] = (page - 1) * (W + GAP) - shell["y"] = 0 + shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP) + shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP) return shell diff --git a/vendor/jian b/vendor/jian index 453bd70b6..18691c421 160000 --- a/vendor/jian +++ b/vendor/jian @@ -1 +1 @@ -Subproject commit 453bd70b646417e58f0d5c9a51594e27d399d849 +Subproject commit 18691c42169f59374d58d13065fc606fa6c39c24