From 3f075922cbdd7b80b9609cb0640bc2591ad3240b Mon Sep 17 00:00:00 2001
From: dayu <506228475@qq.com>
Date: Thu, 22 Oct 2026 00:13:43 +0800
Subject: [PATCH] Publish preview on every slider step and prevent stale number
resubmission; prepare build12
---
README.md | 2 +-
RECOVERY.md | 2 +-
Templates/大鱼人像美化/VALIDATION.json | 12 ++--
beatygril/beatygril/Plugin/Info.plist | 2 +-
beatygril/beatygril/Plugin/PGInspectorView.m | 22 ++++++--
.../beatygril/Wrapper Application/Info.plist | 2 +-
docs/analysis/short-jaw-report.json | 3 +-
docs/analysis/面板交互连接修复.md | 8 +++
docs/verification/v1.1.2/automated-tests.json | 9 +--
.../v1.1.2/continuous-preview-before.txt | 4 ++
docs/verification/v1.1.2/inspector-asan.txt | 2 +-
docs/verification/v1.1.2/interim-build11.json | 44 +++++++++++++++
docs/验证记录.md | 2 +-
tests/inspector_tests.m | 56 ++++++++++++++++++-
14 files changed, 146 insertions(+), 24 deletions(-)
create mode 100644 docs/verification/v1.1.2/continuous-preview-before.txt
create mode 100644 docs/verification/v1.1.2/interim-build11.json
diff --git a/README.md b/README.md
index 689be5d..213886e 100644
--- a/README.md
+++ b/README.md
@@ -49,4 +49,4 @@ xcodebuild -project beatygril/beatygril.xcodeproj -scheme 'Wrapper Application'
面板改为先在真实视图的宿主 action 内取得读写接口,修复控件一直禁用的问题。短脸改用76点下半脸闭合区域和鼻梁位移轴,下颌骨改为六组相邻轮廓半径的局部位移,移除旧脸框近似场;详见[分析](docs/analysis/短脸与下颌骨分析.md)。其余五项脸型仍保留近似实现,不能将局部算法恢复视为全部效果逐像素一致。
-1535项回归通过,含335项宿主适配、98项OSC和141项面板ASan检查;28种控制/原点组合均核验实际解剖位置,改变原点标签时逐字节一致。额外真实帧ASan专项886项通过,含重复基础用例。合并版签名公证及Motion/FCP原生验收进行中。
+1549项回归通过,含335项宿主适配、98项OSC和155项面板ASan检查;28种控制/原点组合均核验实际解剖位置,改变原点标签时逐字节一致。额外真实帧ASan专项886项通过,含重复基础用例。合并版签名公证及Motion/FCP原生验收进行中。
diff --git a/RECOVERY.md b/RECOVERY.md
index 99d24ab..bc873cd 100644
--- a/RECOVERY.md
+++ b/RECOVERY.md
@@ -77,4 +77,4 @@ FCP模板从 `Templates/大鱼人像美化/` 恢复,将 `.moef` 和 `Drop Zone
## 1.1.2 合并修复准备
-版本1.1.2(11)合并面板action上下文修复和76点短脸/下颌骨局部场。构建/恢复依赖、正式路径、签名公证入口保持不变;自动检查1535项及额外886项真实帧ASan专项通过。原生验收使用build/native-112独立Motion副本,用户素材、截图和完整AX数据不随源码上传。用户已明确授权接手Motion/FCP进行测试;先前不操作界面的偏好已被本次授权更新。
+版本1.1.2(12)合并面板action上下文修复和76点短脸/下颌骨局部场。构建/恢复依赖、正式路径、签名公证入口保持不变;自动检查1549项及额外886项真实帧ASan专项通过。原生验收使用build/native-112独立Motion副本,用户素材、截图和完整AX数据不随源码上传。用户已明确授权接手Motion/FCP进行测试;先前不操作界面的偏好已被本次授权更新。
diff --git a/Templates/大鱼人像美化/VALIDATION.json b/Templates/大鱼人像美化/VALIDATION.json
index 8ee5cbd..1eb3b98 100644
--- a/Templates/大鱼人像美化/VALIDATION.json
+++ b/Templates/大鱼人像美化/VALIDATION.json
@@ -156,14 +156,14 @@
},
"automatedInspectorValidation": {
"scope": "offscreen AppKit inspector, real production controller and mocked host callbacks; no Motion/FCP UI",
- "fullChecks": 1535,
+ "fullChecks": 1549,
"counts": {
"beauty": 49,
"face": 116,
"thinChinJawShort": 796,
"hostAdapterASan": 335,
"oscASan": 98,
- "inspectorASan": 141
+ "inspectorASan": 155
},
"nativeVisualQA": "pending user",
"manualAnalysisUI": "removed; legacy IDs hidden and buttons disabled",
@@ -189,21 +189,21 @@
},
"installation": {
"version": "1.1.2",
- "build": "11",
+ "build": "12",
"status": "pending",
"previousInstalledVersion": "1.1.1 (interim)"
},
"shortJawValidation": {
"version": "1.1.2",
- "build": "11",
- "total": 1535,
+ "build": "12",
+ "total": 1549,
"counts": {
"beauty": 49,
"face": 116,
"thinChinJawShort": 796,
"hostAdapterASan": 335,
"oscASan": 98,
- "inspectorASan": 141
+ "inspectorASan": 155
},
"allPassed": true,
"extraRealFramesASan": {
diff --git a/beatygril/beatygril/Plugin/Info.plist b/beatygril/beatygril/Plugin/Info.plist
index ff185ef..199ccb2 100644
--- a/beatygril/beatygril/Plugin/Info.plist
+++ b/beatygril/beatygril/Plugin/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
1.1.2
CFBundleVersion
- 11
+ 12
NSHumanReadableCopyright
PlugInKit
diff --git a/beatygril/beatygril/Plugin/PGInspectorView.m b/beatygril/beatygril/Plugin/PGInspectorView.m
index 4e3be21..7a43a82 100644
--- a/beatygril/beatygril/Plugin/PGInspectorView.m
+++ b/beatygril/beatygril/Plugin/PGInspectorView.m
@@ -155,6 +155,7 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
@property (nonatomic, strong) NSColor *accent;
@property (nonatomic, strong) NSTextField *label;
@property (nonatomic, strong) NSTextField *valueField;
+@property (nonatomic) BOOL numberHasUnsubmittedEdit;
@property (nonatomic, strong) NSTextField *hint;
@property (nonatomic, strong) PGInspectorSlider *slider;
@property (nonatomic, strong) PGInspectorGlyphButton *eye;
@@ -244,7 +245,11 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
- (void)toggleEnabled:(id)sender { (void)sender;[_inspector.controller toggleEnabledForParameter:[_descriptor[@"id"]unsignedIntValue]];[_inspector reloadControls]; }
- (void)resetValue:(id)sender { (void)sender;[_inspector.controller resetParameter:[_descriptor[@"id"]unsignedIntValue]];[_inspector reloadControls]; }
- (void)numberCommitted:(id)sender {
- (void)sender;NSString *text=[_valueField.stringValue stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
+ // The explicit Return action and the subsequent end-editing notification
+ // describe the same submission. Clear the dirty flag before host callbacks
+ // so focus loss cannot write stale text over a reset or a host undo.
+ _numberHasUnsubmittedEdit=NO;
+ NSString *text=[_valueField.stringValue stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
if([text hasSuffix:@"%"]||[text hasSuffix:@"°"])text=[text substringToIndex:text.length-1];
NSNumber *number=nil;
for(NSLocale *locale in @[NSLocale.currentLocale,[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]){
@@ -259,8 +264,14 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
[_inspector writeValue:value parameter:[_descriptor[@"id"]unsignedIntValue]];
}else NSBeep();
_valueField.stringValue=PGFormattedValue(_descriptor,[_inspector.controller.state valueForParameter:[_descriptor[@"id"]unsignedIntValue]]);
+ if(sender==_valueField && _valueField.currentEditor)[_valueField.window makeFirstResponder:nil];
+}
+- (void)controlTextDidChange:(NSNotification *)notification { (void)notification;_numberHasUnsubmittedEdit=YES; }
+- (void)controlTextDidEndEditing:(NSNotification *)notification {
+ (void)notification;
+ if(_numberHasUnsubmittedEdit)[self numberCommitted:nil];
+ [_inspector refreshFromHost];
}
-- (void)controlTextDidEndEditing:(NSNotification *)notification { (void)notification;[self numberCommitted:_valueField]; }
@end
@implementation PGInspectorCard
@@ -336,8 +347,11 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
- (void)refreshFromHost { if(_interactionDepth || [self hasActiveTextEditor])return;[_controller refresh];[self reloadControls]; }
- (void)hostParameterChanged { if(_interactionDepth || [self hasActiveTextEditor])return;[_controller documentDidChange];[self reloadControls]; }
- (void)writeValue:(double)value parameter:(UInt32)identifier { [_controller setValue:value forParameter:identifier];[self reloadControls]; }
-- (void)beginControlInteraction { if(_interactionDepth++==0)[_controller beginInteraction]; }
-- (void)endControlInteraction { if(!_interactionDepth)return;if(--_interactionDepth==0){[_controller endInteraction];[self refreshFromHost];} }
+// Keep refreshes out of the tracking loop, but let writeValue own a complete
+// FxPlug action for each continuous step. Holding the action until mouse-up
+// prevents hosts that publish at endAction from previewing intermediate values.
+- (void)beginControlInteraction { _interactionDepth++; }
+- (void)endControlInteraction { if(!_interactionDepth)return;if(--_interactionDepth==0)[self refreshFromHost]; }
- (void)masterChanged:(NSButton *)sender { [_controller setValue:sender.state==NSControlStateValueOn?1:0 forParameter:(UInt32)sender.tag];[self reloadControls]; }
- (void)moreChanged:(NSButton *)sender { _more.expanded=sender.state==NSControlStateValueOn;sender.accessibilityLabel=_more.expanded?@"收起更多设置":@"展开更多设置";[_more setNeedsDisplay:YES];[self setFrameSize:self.frame.size]; }
- (void)viewDidMoveToWindow {
diff --git a/beatygril/beatygril/Wrapper Application/Info.plist b/beatygril/beatygril/Wrapper Application/Info.plist
index 1c849cb..cc0f471 100644
--- a/beatygril/beatygril/Wrapper Application/Info.plist
+++ b/beatygril/beatygril/Wrapper Application/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
1.1.2
CFBundleVersion
- 11
+ 12
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSHumanReadableCopyright
diff --git a/docs/analysis/short-jaw-report.json b/docs/analysis/short-jaw-report.json
index 645f81e..9b7980d 100644
--- a/docs/analysis/short-jaw-report.json
+++ b/docs/analysis/short-jaw-report.json
@@ -172,6 +172,7 @@
},
"air_excerpts": {
"path": "docs/analysis/short-jaw-air.txt",
- "sha256": "45ef87253c5d2591d78531b59b1c8672e10b84615457faabd4dc7f0e9dcdbcac"
+ "sha256": "f4ade84fdb2303515dab7b4d7eca8a9c2241039b9e6428cd51ceffc32b3f6b9b",
+ "formatNote": "Trailing whitespace removed from numbered excerpt; original AIR source SHA remains unchanged."
}
}
diff --git a/docs/analysis/面板交互连接修复.md b/docs/analysis/面板交互连接修复.md
index 74b767c..8b38172 100644
--- a/docs/analysis/面板交互连接修复.md
+++ b/docs/analysis/面板交互连接修复.md
@@ -13,3 +13,11 @@
Mouse/keyboard自动化尝试中有焦点落回时间线及参数在非本次操作时改变的情况,不能据此断言点击坐标一定错位。另一次FCP高CPU与AX超时,调用栈落在大量效果浏览器的辅助功能遍历;插件主线程大多空闲。收起效果浏览器后AX读取从约10秒降到约1秒。这不是已证明的插件死锁。原始截图、用户测试片段和全部窗口树保留本机build/native-111,不上传远程。
原版UI的只读对照显示isFlipped同为YES,原生滑块同样使用super mouseDown;root鼠标以locationInWindow经convertPoint:fromView:nil转换。名字含WidthFix的方法只处理容器高度不足,没有鼠标Y翻转或backing像素重映射。没有证据需要盲改本产品的isFlipped。1.1.2最终Motion/FCP数值、重置、保存重开及画面响应将另行记录。
+
+## 数值失焦与拖动连续预览
+
+原生build11验收发现数值Return后仍保留字段编辑器,失焦通知无条件再次提交旧文本,可能把宿主刚撤销或重置的状态写回。新增独立回归确认旧delegate在失焦时复活旧值;现追踪尚未提交的真实文本改动,Return只提交一次并释放AppKit字段编辑器,失焦仅提交新改动。离屏真实NSWindow/field editor用例已覆盖。
+
+用户另报滑杆只在松手后更新:旧view层在mouseDown之前beginInteraction,整个NSSlider tracking loop结束才endInteraction,虽然continuous为YES,宿主可直到endAction才发布预览。现在gesture depth只阻止定时刷新覆盖拖动显示,每个有效slider action由controller单独开启/结束宿主上下文,仍先在真实NSView sender的action内取得API,完整自定义状态和隐藏镜像一起提交。没有新增手动分析或强制刷新按钮。
+
+在仅于endAction发布预览的模拟宿主中,三个中间步进旧版3项失败,新版155项检查器ASan全部通过,含无变化去重、action配对、每步可读的完整渲染设置。宿主是否合并快速更新以及实际显示帧率由其调度和设备性能决定;不保证每一个鼠标事件均在屏幕独立显示一帧。新版原生复验另记。
diff --git a/docs/verification/v1.1.2/automated-tests.json b/docs/verification/v1.1.2/automated-tests.json
index a661aed..5ce0221 100644
--- a/docs/verification/v1.1.2/automated-tests.json
+++ b/docs/verification/v1.1.2/automated-tests.json
@@ -1,14 +1,14 @@
{
"version": "1.1.2",
- "build": "11",
- "total": 1535,
+ "build": "12",
+ "total": 1549,
"counts": {
"beauty": 49,
"face": 116,
"thinChinJawShort": 796,
"hostAdapterASan": 335,
"oscASan": 98,
- "inspectorASan": 141
+ "inspectorASan": 155
},
"allPassed": true,
"extraRealFramesASan": {
@@ -20,5 +20,6 @@
"originTagsExactlyByteIdentical": true,
"maxByteDifferenceVsIndependentUprightReference": 2,
"meanByteDifferenceLimit": 0.005,
- "nativeCombinedBuildQA": "pending"
+ "nativeCombinedBuildQA": "pending",
+ "continuousPreview": "each changed step completes its own host action before mouse-up; mock publishes at endAction"
}
diff --git a/docs/verification/v1.1.2/continuous-preview-before.txt b/docs/verification/v1.1.2/continuous-preview-before.txt
new file mode 100644
index 0000000..6461e3f
--- /dev/null
+++ b/docs/verification/v1.1.2/continuous-preview-before.txt
@@ -0,0 +1,4 @@
+FAIL: every drag step publishes a completed host snapshot before mouse-up
+FAIL: every drag step publishes a completed host snapshot before mouse-up
+FAIL: every drag step publishes a completed host snapshot before mouse-up
+Inspector: 155 checks, 3 failures
diff --git a/docs/verification/v1.1.2/inspector-asan.txt b/docs/verification/v1.1.2/inspector-asan.txt
index 080b3ca..3820763 100644
--- a/docs/verification/v1.1.2/inspector-asan.txt
+++ b/docs/verification/v1.1.2/inspector-asan.txt
@@ -1 +1 @@
-Inspector: 141 checks, 0 failures
+Inspector: 155 checks, 0 failures
diff --git a/docs/verification/v1.1.2/interim-build11.json b/docs/verification/v1.1.2/interim-build11.json
new file mode 100644
index 0000000..04529d9
--- /dev/null
+++ b/docs/verification/v1.1.2/interim-build11.json
@@ -0,0 +1,44 @@
+{
+ "status": "failed",
+ "bundleIdentifier": "com.dayu.beatygril",
+ "installationPath": "/Library/Plug-Ins/FxPlug/beatygril.app",
+ "options": {
+ "waitForHosts": true,
+ "userTests": false
+ },
+ "nativeEffectQA": {
+ "status": "pending_operator",
+ "performedByScript": false
+ },
+ "source": {
+ "commit": "183bf0e4b2227477c4d38cb201ce2c0279eeb740",
+ "trackedWorktreeChanges": []
+ },
+ "updatedAt": "2026-10-21T16:06:51.497292+00:00",
+ "version": "1.1.2",
+ "build": "11",
+ "stagingSignature": "Executable=/private/tmp/paintfcp-build-20261021/Build/Products/Release/beatygril.app/Contents/MacOS/beatygril\nIdentifier=com.dayu.beatygril\nFormat=app bundle with Mach-O universal (x86_64 arm64)\nCodeDirectory v=20500 size=478 flags=0x10000(runtime) hashes=4+7 location=embedded\nHash type=sha256 size=32\nCandidateCDHash sha256=8e50a6a2b99f93880d8a5a8b5978d120a227a6b7\nCandidateCDHashFull sha256=8e50a6a2b99f93880d8a5a8b5978d120a227a6b72d1b6480bef338f2c8f1fbd4\nHash choices=sha256\nCMSDigest=8e50a6a2b99f93880d8a5a8b5978d120a227a6b72d1b6480bef338f2c8f1fbd4\nCMSDigestType=2\nCDHash=8e50a6a2b99f93880d8a5a8b5978d120a227a6b7\nSignature size=8969\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 21, 2026 at 23:57:21\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=4\nInternal requirements count=1 size=180\nTotal signatures=1\nChosen signature=1\n",
+ "notarization": {
+ "message": "Processing complete",
+ "id": "a9b454c3-5d30-47bd-b266-f19447f3db7e",
+ "status": "Accepted"
+ },
+ "signingVerified": true,
+ "stapleValidated": true,
+ "stagingPluginUnregistered": true,
+ "runningHosts": [],
+ "installedSignature": "Executable=/Library/Plug-Ins/FxPlug/beatygril.app/Contents/MacOS/beatygril\nIdentifier=com.dayu.beatygril\nFormat=app bundle with Mach-O universal (x86_64 arm64)\nCodeDirectory v=20500 size=478 flags=0x10000(runtime) hashes=4+7 location=embedded\nHash type=sha256 size=32\nCandidateCDHash sha256=8e50a6a2b99f93880d8a5a8b5978d120a227a6b7\nCandidateCDHashFull sha256=8e50a6a2b99f93880d8a5a8b5978d120a227a6b72d1b6480bef338f2c8f1fbd4\nHash choices=sha256\nCMSDigest=8e50a6a2b99f93880d8a5a8b5978d120a227a6b72d1b6480bef338f2c8f1fbd4\nCMSDigestType=2\nCDHash=8e50a6a2b99f93880d8a5a8b5978d120a227a6b7\nSignature size=8969\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 21, 2026 at 23:57:21\nNotarization Ticket=stapled\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=4\nInternal requirements count=1 size=180\nTotal signatures=1\nChosen signature=1\n",
+ "installedSignatureVerified": true,
+ "installedStapleValidated": true,
+ "gatekeeperAccepted": true,
+ "pluginDiscovery": {
+ "bundleIdentifier": "com.dayu.beatygril.fxplug",
+ "formalPath": "/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit",
+ "output": " com.dayu.beatygril.fxplug(1.1)\tEFA7AB61-D5AA-44BB-B096-FF6BF12E7D02\t2026-10-21 15:57:43 +0000\t/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit\n (1 plug-in)\n"
+ },
+ "failure": {
+ "type": "KeyboardInterrupt",
+ "message": ""
+ },
+ "interimReason": "Native testing found number field focus-loss resubmission; combined build11 not accepted as final."
+}
diff --git a/docs/验证记录.md b/docs/验证记录.md
index c4fb5d7..28e548f 100644
--- a/docs/验证记录.md
+++ b/docs/验证记录.md
@@ -116,4 +116,4 @@ Motion:独立普通视频项目,源PTS0、1920×1080/25fps/10秒,媒体通
替换短脸和下颌骨的旧脸框近似,恢复目标76点的11边下半脸区域与六组局部采样,沿用单次图像重采样和图像/四张蒙版共用坐标场。四项已恢复控制缺完整76点时安全直通,其余五项和全链组合仍有近似边界。分析报告、CPU证据和AIR摘要见analysis/短脸与下颌骨分析.md。
-1535项全部通过:49美化、116人脸、796瘦脸/下巴/短脸/下颌骨、335宿主适配ASan、98 OSC ASan、141检查器ASan。宿主独立物理行序用例扩展到短脸正负与下颌骨,28种控制/原点组合响应在预期下半脸,瞳孔区差异为0,坐标标签切换逐字节一致;独立upright参考最大差2/255,均差<0.005/255。额外真实正面/侧脸ASan专项886项通过,含重复基础用例,不相加。已核对生产源码与冻结摘要一致。合并版尚待正式构建和原生验收。
+1549项全部通过:49美化、116人脸、796瘦脸/下巴/短脸/下颌骨、335宿主适配ASan、98 OSC ASan、155检查器ASan。宿主独立物理行序用例扩展到短脸正负与下颌骨,28种控制/原点组合响应在预期下半脸,瞳孔区差异为0,坐标标签切换逐字节一致;独立upright参考最大差2/255,均差<0.005/255。额外真实正面/侧脸ASan专项886项通过,含重复基础用例,不相加。已核对生产源码与冻结摘要一致。合并版尚待正式构建和原生验收。
diff --git a/tests/inspector_tests.m b/tests/inspector_tests.m
index d60577e..3f7cc26 100644
--- a/tests/inspector_tests.m
+++ b/tests/inspector_tests.m
@@ -13,6 +13,8 @@
@property BOOL actionRequiresView;
@property NSUInteger invalidActionSenders;
@property (weak) id lastActionSender;
+@property NSUInteger previewPublications;
+@property (strong) NSDictionary *previewValues;
@end
@implementation PGInspectorTestAPI
- (id)apiForProtocol:(Protocol *)protocol {
@@ -27,7 +29,12 @@
if(_actionRequiresView && ![sender isKindOfClass:NSView.class]){_invalidActionSenders++;return;}
if(!self.actionDepth)_actionBefore=[self.values copy];[super startAction:sender];
}
-- (void)endAction:(id)sender {if(self.actionDepth==1 && ![self.values isEqual:_actionBefore])_undoValues=_actionBefore;[super endAction:sender];}
+- (void)endAction:(id)sender {
+ if(self.actionDepth==1 && ![self.values isEqual:_actionBefore]) {
+ _undoValues=_actionBefore;_previewValues=[self.values copy];_previewPublications++;
+ }
+ [super endAction:sender];
+}
- (CMTime)currentTime {[self guard];return [super currentTime];}
- (BOOL)getCustomParameterValue:(NSObject **)v fromParameter:(UInt32)i atTime:(CMTime)t {[self guard];return [super getCustomParameterValue:v fromParameter:i atTime:t];}
- (BOOL)getFloatValue:(double *)v fromParameter:(UInt32)i atTime:(CMTime)t {[self guard];return [super getFloatValue:v fromParameter:i atTime:t];}
@@ -96,7 +103,7 @@ static void ControllerTests(void) {
Check(c.available && Near([c.state valueForParameter:PGMidtone],.5) && !c.state.hasEdits,@"initial UI reads the current legacy/template values instead of replacing them");
NSUInteger starts=api.actionStarts,writes=api.customWrites;
[c beginInteraction];[c setValue:.1 forParameter:PGChin];[c setValue:.3 forParameter:PGChin];[c setValue:.5 forParameter:PGChin];[c endInteraction];
- Check(api.actionStarts==starts+1 && api.actionDepth==0 && api.customWrites==writes+3,@"a full continuous drag uses one balanced host action");
+ Check(api.actionStarts==starts+1 && api.actionDepth==0 && api.customWrites==writes+3,@"an explicitly grouped controller transaction uses one balanced host action");
Check(api.scalarWrites==3,@"continuous drags mirror only changed values rather than rewriting every hidden parameter");
PGInspectorState *saved=api.values[@(PGInspectorPanel)];Check(saved.hasEdits && Near([saved valueForParameter:PGChin],.5) && Near([saved valueForParameter:PGMidtone],.5),@"published state contains the edited control and all untouched document values");
NSUInteger before=api.customWrites;[c setValue:.5 forParameter:PGChin];Check(api.customWrites==before,@"an unchanged value does not create duplicate undo writes");
@@ -150,6 +157,30 @@ static void ViewInteractionTests(void) {
api.values[@(PGInspectorPanel)]=[PGInspectorState stateWithValues:changed disabledParameters:s.disabledParameters hasEdits:YES];
number.stringValue=@"0.40";Fire(number);s=api.values[@(PGInspectorPanel)];
Check(Near([s valueForParameter:PGChin],.4),@"explicit input matching old UI cache still overrides a newer host value");
+ // Return has already submitted .40. Losing focus after an external undo
+ // must not send that old field text again and resurrect the undone value.
+ changed=[s.values mutableCopy];changed[@(PGChin)]=@.8;
+ api.values[@(PGInspectorPanel)]=[PGInspectorState stateWithValues:changed disabledParameters:s.disabledParameters hasEdits:YES];
+ [number.delegate controlTextDidEndEditing:[NSNotification notificationWithName:NSControlTextDidEndEditingNotification object:number]];
+ s=api.values[@(PGInspectorPanel)];
+ Check(Near([s valueForParameter:PGChin],.8),@"focus loss after a submitted number does not resurrect an externally undone value");
+ number.stringValue=@"0.65";
+ [number.delegate controlTextDidChange:[NSNotification notificationWithName:NSControlTextDidChangeNotification object:number]];
+ [number.delegate controlTextDidEndEditing:[NSNotification notificationWithName:NSControlTextDidEndEditingNotification object:number]];
+ s=api.values[@(PGInspectorPanel)];
+ Check(Near([s valueForParameter:PGChin],.65),@"genuinely edited number still commits when focus leaves without Return");
+ changed=[s.values mutableCopy];changed[@(PGChin)]=@.2;
+ api.values[@(PGInspectorPanel)]=[PGInspectorState stateWithValues:changed disabledParameters:s.disabledParameters hasEdits:YES];
+ [number.delegate controlTextDidEndEditing:[NSNotification notificationWithName:NSControlTextDidEndEditingNotification object:number]];
+ s=api.values[@(PGInspectorPanel)];
+ Check(Near([s valueForParameter:PGChin],.2),@"duplicate end-editing notifications do not create another parameter transaction");
+ NSWindow *window=[[NSWindow alloc]initWithContentRect:NSMakeRect(0,0,380,900) styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
+ window.releasedWhenClosed=NO;window.contentView=view;
+ [number selectText:nil];
+ Check(number.currentEditor!=nil,@"offscreen AppKit window supplies a real field editor for Return handling");
+ number.stringValue=@"0.25";Fire(number);
+ Check(number.currentEditor==nil && Near([(PGInspectorState *)api.values[@(PGInspectorPanel)] valueForParameter:PGChin],.25),@"Return commits once and releases the actual AppKit editor before host commands");
+ [window close];
NSButton *preview=(NSButton *)FindControl(view,NSButton.class,@"人脸框预览");preview.state=NSControlStateValueOn;Fire(preview);s=api.values[@(PGInspectorPanel)];Check([s valueForParameter:PGTrackBoxes]==1,@"preview switch events change the automatic face box setting");
for(NSNumber *width in @[@280,@320,@380,@520]) {
[view setFrameSize:NSMakeSize(width.doubleValue,20)];[view layoutSubtreeIfNeeded];
@@ -174,9 +205,28 @@ static void HostHandshakeTests(void) {
Check(view.controller.available && brightness.enabled,@"a later host connection restores controls after a transient API failure");
Check(api.accessViolations==0 && api.actionDepth==0 && api.actionStarts==api.actionEnds,@"action-scoped connection, edits, failure and reconnection remain balanced");
}
+static void ContinuousPreviewTests(void) {
+ PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
+ api.parameterAPIsRequireAction=YES;api.actionRequiresView=YES;
+ PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
+ NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,@"短脸");
+ void (^began)(void)=[slider valueForKey:@"interactionBegan"], (^ended)(void)=[slider valueForKey:@"interactionEnded"];
+ Check(slider.continuous && began && ended,@"native slider emits continuous events and exposes balanced gesture callbacks");
+ began();
+ for(NSNumber *value in @[@.2,@.5,@1]) {
+ NSUInteger publications=api.previewPublications;slider.doubleValue=value.doubleValue;Fire(slider);
+ PGInspectorState *visible=api.previewValues[@(PGInspectorPanel)];
+ Check(api.actionDepth==0 && api.previewPublications==publications+1 && Near([visible valueForParameter:PGShort],value.doubleValue),@"every drag step publishes a completed host snapshot before mouse-up");
+ Check(visible && Near(PGSettingsWithInspectorState(PGBeautyDefaults(),visible).faceShort,value.doubleValue),@"published frame settings contain each intermediate short-face value while gesture is still open");
+ }
+ NSUInteger publications=api.previewPublications;Fire(slider);
+ Check(api.previewPublications==publications,@"unchanged drag events do not enqueue redundant previews");
+ ended();
+ Check(api.actionDepth==0 && api.actionStarts==api.actionEnds && api.accessViolations==0 && api.invalidActionSenders==0,@"step-wise publication preserves action-scoped APIs and the actual view sender");
+}
int main(int argc,const char **argv) {@autoreleasepool {
[NSApplication sharedApplication];[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
- RegistrationTests();ModelTests();ControllerTests();ViewInteractionTests();HostHandshakeTests();
+ RegistrationTests();ModelTests();ControllerTests();ViewInteractionTests();HostHandshakeTests();ContinuousPreviewTests();
if(argc>2) {
NSData *inner=[NSData dataWithContentsOfFile:@(argv[1])],*outer=[NSData dataWithContentsOfFile:@(argv[2])];NSError *error=nil;
NSKeyedUnarchiver *reader=[[NSKeyedUnarchiver alloc]initForReadingFromData:inner error:&error];reader.requiresSecureCoding=YES;