Merge pull request #2629 from storytold/fix/2585-scriptui-dock-size

ScriptUI: a docked panel gets its dock's size and onResize (#2585)
This commit is contained in:
BFlat
2026-10-30 08:12:01 +07:00
committed by GitHub
5 changed files with 75 additions and 0 deletions
+9
View File
@@ -581,6 +581,15 @@ pub fn specs() -> Vec<CommandSpec> {
always,
scriptui::mouse
),
cmd!(
"scriptui.resize",
"Resize Script Window",
[],
None,
"{window?, size: [width, height] (pixels)} → the window takes the size, lays itself out again and its onResizing / onResize run (a docked ScriptUI panel gets its dock's size)",
always,
scriptui::resize
),
cmd!(
"scriptui.close",
"Close Script Window",
+11
View File
@@ -769,6 +769,17 @@ pub(crate) fn mouse(s: &mut Session, p: &Value) -> Result<Value> {
dispatch(s, ScriptUiEvent { window, widget, kind: (*ty).to_string(), value })
}
/// `scriptui.resize`: the host gave the window a new size (a docked panel's dock, #2585): the
/// script's window takes it, lays itself out again and gets `onResizing` / `onResize`.
pub(crate) fn resize(s: &mut Session, p: &Value) -> Result<Value> {
let c = "scriptui.resize";
let window = window_p(s, p, c)?;
let size = p.get("size").and_then(Value::as_array).ok_or_else(|| crate::commands::bad(c, "missing `size` [width, height]"))?;
let side = |i: usize| size.get(i).and_then(Value::as_f64).filter(|v| v.is_finite()).map(|v| v.round().clamp(1.0, 16384.0));
let (Some(w), Some(h)) = (side(0), side(1)) else { return Err(crate::commands::bad(c, "`size` must be [width, height] in pixels")) };
dispatch(s, ScriptUiEvent { window, widget: 0, kind: "resize".into(), value: json!([w, h]) })
}
/// `scriptui.close`: close a window (a dialog's `show()` returns `result`, default 2 = Cancel).
pub(crate) fn close(s: &mut Session, p: &Value) -> Result<Value> {
let window = window_p(s, p, "scriptui.close")?;
+12
View File
@@ -912,6 +912,18 @@ function __uiDispatch(winId, id, kind, valueJson) {
w.close(v === null ? 2 : v);
return;
}
// The host gave the window a new size (a docked panel's dock): take it, lay out, tell the script.
if (kind === "resize") {
var nw = Number(v && v[0]), nh = Number(v && v[1]);
var b = w.__ub || w.__lb;
if (nw > 0 && nh > 0 && (b[2] - b[0] !== nw || b[3] - b[1] !== nh)) {
w.__ub = [b[0], b[1], b[0] + nw, b[1] + nh];
__uiLayout(w);
w.__fire("onResizing");
w.__fire("onResize");
}
return;
}
var c = __uiFind(w, id);
if (!c || c.enabled === false) return;
var radio = function () {
@@ -26,6 +26,8 @@ enum Act {
Close(u32),
/// A mouse event (`scriptui.mouse` parameters without window and widget).
Mouse(u32, u32, Value),
/// A docked panel's new size (`scriptui.resize`).
Resize(u32, [f32; 2]),
}
fn rect_of(origin: egui::Pos2, b: [f64; 4]) -> Rect {
@@ -500,6 +502,7 @@ fn perform(app: &mut EffectcraftApp, ctx: &egui::Context, acts: Vec<Act>) {
Act::Set(w, id, v) => ("scriptui.set", json!({"window": w, "widget": id, "value": v})),
Act::Changing(w, id, v) => ("scriptui.set", json!({"window": w, "widget": id, "value": v, "changing": true})),
Act::Close(w) => ("scriptui.close", json!({"window": w})),
Act::Resize(w, [x, y]) => ("scriptui.resize", json!({"window": w, "size": [x, y]})),
Act::Mouse(w, id, mut v) => {
if let Some(o) = v.as_object_mut() {
o.insert("window".into(), json!(w));
@@ -582,6 +585,14 @@ pub fn panel(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, id: u32) {
// A modal script dialog blocks the panels too.
let modal = app.session.script_ui.windows.iter().any(|w| w.modal);
let mut acts = vec![];
// The script learns its dock's size when it changes (#2585), as After Effects tells a
// docked panel: its window takes the size and onResize runs.
let dock = [rect.width().round(), rect.height().round()];
let told = egui::Id::unique(("scriptui-dock-size", id));
if ui.data(|d| d.get_temp::<[f32; 2]>(told)) != Some(dock) && dock[0] >= 1.0 && dock[1] >= 1.0 {
ui.data_mut(|d| d.insert_temp(told, dock));
acts.push(Act::Resize(id, dock));
}
let content = rect;
egui::ScrollArea::both().id_salt(("scriptui-panel", id)).max_width(content.width()).max_height(content.height()).show(ui, |ui| {
let (r, _) = ui.allocate_exact_size(vec2(w.root.bounds[2] as f32, w.root.bounds[3] as f32), Sense::hover());
+32
View File
@@ -341,6 +341,38 @@ fn a_drag_keeps_its_control_past_the_window_edge() {
h.state_mut().session.execute("scriptui.close", json!({})).unwrap();
}
/// #2585: a window the host resizes takes the size, lays out again and runs onResize; a docked
/// ScriptUI panel gets its dock's size that way.
#[test]
fn script_windows_learn_the_size_they_are_given() {
let mut h = harness();
let code = r#"
var w = new Window("palette", "Sized");
var log = w.add("statictext", undefined, "", { name: "log" });
log.preferredSize = [200, 20];
w.onResize = function () { log.text = "resized " + w.size.width + "x" + w.size.height; };
w.show();
"#;
let r = h.state_mut().session.execute("script.run", json!({"code": code, "name": "sized.jsx"})).unwrap();
assert_eq!(r["ok"], true, "{r}");
h.run_steps(2);
h.state_mut().session.execute("scriptui.resize", json!({"window": "Sized", "size": [300, 180]})).unwrap();
let win = h.state().session.script_ui.windows[0].clone();
assert_eq!(win.root.lookup(&json!("log")).unwrap().text, "resized 300x180");
let list = h.state_mut().session.execute("scriptui.list", json!({})).unwrap();
assert_eq!(list[0]["size"], json!([300.0, 180.0]), "{list}");
assert!(h.state_mut().session.execute("scriptui.resize", json!({"window": "Sized", "size": ["wide", 1]})).is_err());
h.state_mut().session.execute("scriptui.close", json!({})).unwrap();
// A docked panel: its window is its dock's size, not its preferred size.
h.state_mut().session.execute("window.scriptPanel", json!({"name": "Layer Tools.jsx"})).unwrap();
h.run_steps(4);
let panel = h.state().session.script_ui.windows.iter().find(|w| w.script == "Layer Tools.jsx").unwrap().clone();
let dock = rect(&h, &format!("scriptui.{}", panel.id));
assert_eq!([panel.root.bounds[2].round(), panel.root.bounds[3].round()], [dock.width().round() as f64, dock.height().round() as f64]);
h.state_mut().close_panel(PanelKind::ScriptPanel(panel.id));
h.run_steps(2);
}
fn click_at(h: &mut Harness<'_, EffectcraftApp>, c: egui::Pos2) {
h.input_mut().events.push(Event::PointerMoved(c));
h.run_steps(1);