Skip to content

Commit c7594bb

Browse files
authored
Merge pull request #343 from Integration-Automation/feat/window-layout-batch
Add window tiling/layout geometry planner (halves, grids, cascade)
2 parents b15c51d + 9941c44 commit c7594bb

16 files changed

Lines changed: 513 additions & 1 deletion

File tree

README/WHATS_NEW_zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 窗口铺排 / 版面几何规划器
4+
5+
计算应用程序窗口该放在哪里——半边、网格、层叠。完整参考:[`docs/source/Zh/doc/new_features/v133_features_doc.rst`](../docs/source/Zh/doc/new_features/v133_features_doc.rst)
6+
7+
- **`tile_rect` / `grid_rects` / `cascade_rects`**(`AC_tile_rect``AC_grid_rects``AC_cascade_rects`):`save/restore_window_layout` 重播*精确*的已存位置、`snap_window` 移动*一个*窗口——没有任何功能能*计算*出全新的多窗口版面。此纯几何规划器在给定屏幕工作区下,返回半边、四分之一、三分之一、R×C 网格与错位层叠的目标矩形,让脚本能以确定性方式排列窗口。返回 `WindowRect`(`.as_tuple()` / `.to_dict()`);`gap` 内缩铺排间距;跨平台且完全无头可测;可与任何窗口移动后端组合。
8+
39
## 本次更新 (2026-06-23) — 以边缘 / 轮廓定位 UI 元素(免模板)
410

511
在从未见过的画面上找出可点击的方框。完整参考:[`docs/source/Zh/doc/new_features/v132_features_doc.rst`](../docs/source/Zh/doc/new_features/v132_features_doc.rst)

README/WHATS_NEW_zh-TW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 視窗鋪排 / 版面幾何規劃器
4+
5+
計算應用程式視窗該放在哪裡——半邊、網格、層疊。完整參考:[`docs/source/Zh/doc/new_features/v133_features_doc.rst`](../docs/source/Zh/doc/new_features/v133_features_doc.rst)
6+
7+
- **`tile_rect` / `grid_rects` / `cascade_rects`**(`AC_tile_rect``AC_grid_rects``AC_cascade_rects`):`save/restore_window_layout` 重播*精確*的已存位置、`snap_window` 移動*一個*視窗——沒有任何功能能*計算*出全新的多視窗版面。此純幾何規劃器在給定螢幕工作區下,回傳半邊、四分之一、三分之一、R×C 網格與錯位層疊的目標矩形,讓腳本能以決定性方式排列視窗。回傳 `WindowRect`(`.as_tuple()` / `.to_dict()`);`gap` 內縮鋪排間距;跨平台且完全無頭可測;可與任何視窗移動後端組合。
8+
39
## 本次更新 (2026-06-23) — 以邊緣 / 輪廓定位 UI 元素(免模板)
410

511
在從未見過的畫面上找出可點擊的方框。完整參考:[`docs/source/Zh/doc/new_features/v132_features_doc.rst`](../docs/source/Zh/doc/new_features/v132_features_doc.rst)

WHATS_NEW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# What's New — AutoControl
22

3+
## What's new (2026-06-23) — Window Tiling / Layout Geometry Planner
4+
5+
Compute where to place application windows — halves, grids, cascades. Full reference: [`docs/source/Eng/doc/new_features/v133_features_doc.rst`](docs/source/Eng/doc/new_features/v133_features_doc.rst).
6+
7+
- **`tile_rect` / `grid_rects` / `cascade_rects`** (`AC_tile_rect`, `AC_grid_rects`, `AC_cascade_rects`): `save/restore_window_layout` replay *exact* saved positions and `snap_window` moves *one* window — nothing *computes* a fresh multi-window layout. This pure-geometry planner returns the target rectangles for halves, quadrants, thirds, an R×C grid and a staggered cascade given a screen work area, so a script can lay out windows deterministically. Returns `WindowRect` (`.as_tuple()` / `.to_dict()`); `gap` insets tiles; cross-platform and fully headless-testable; composes with any window-move backend.
8+
39
## What's new (2026-06-23) — Locate UI Elements by Edge / Contour (No Template)
410

511
Find the clickable boxes on a screen you have never seen. Full reference: [`docs/source/Eng/doc/new_features/v132_features_doc.rst`](docs/source/Eng/doc/new_features/v132_features_doc.rst).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Window Tiling / Layout Geometry Planner
2+
=======================================
3+
4+
``save_window_layout`` / ``restore_window_layout`` capture and replay the *exact*
5+
positions a user already arranged, and ``snap_window`` moves *one* window to a half
6+
or quarter. Nothing *computes* a fresh multi-window layout. ``tile_rect``,
7+
``grid_rects`` and ``cascade_rects`` are a pure-geometry planner: given a screen
8+
work area they return the target rectangles for the common tiling layouts — halves,
9+
quadrants, thirds, an R×C grid, a staggered cascade — so a script can lay out
10+
application windows deterministically.
11+
12+
The planner is cross-platform and has no device dependency, so it is fully
13+
unit-testable; the rectangles it returns compose with any window-move backend.
14+
Imports no ``PySide6``.
15+
16+
Headless API
17+
------------
18+
19+
.. code-block:: python
20+
21+
from je_auto_control import tile_rect, grid_rects, cascade_rects
22+
23+
left = tile_rect((0, 0, 1920, 1080), "left_third", gap=8)
24+
print(left.as_tuple()) # (8, 8, 624, 1064)
25+
26+
for cell in grid_rects((0, 0, 1920, 1080), rows=2, cols=3):
27+
window_move("Editor", *cell.as_tuple()) # 6-up grid
28+
29+
plan = cascade_rects((0, 0, 1920, 1080), count=4, offset=40)
30+
31+
``tile_rect`` returns a ``WindowRect`` (``x, y, width, height`` with ``.as_tuple()``
32+
and ``.to_dict()``) for a named ``slot`` — see :func:`available_slots`
33+
(``left``, ``top_right``, ``center``, ``left_third`` …); ``gap`` insets all sides
34+
for a margin between tiles. ``grid_rects`` returns one rectangle per cell of an
35+
``rows`` × ``cols`` grid, row-major. ``cascade_rects`` returns ``count`` staggered,
36+
overlapping rectangles clamped to the screen (``size`` defaults to 60% of the work
37+
area). Unknown slots / non-positive grid dimensions raise ``ValueError``.
38+
39+
Executor commands
40+
-----------------
41+
42+
``AC_tile_rect`` (``slot`` / ``screen`` / ``gap`` → ``{rect}``), ``AC_grid_rects``
43+
(``rows`` / ``cols`` / ``screen`` / ``gap`` → ``{count, rects}``) and
44+
``AC_cascade_rects`` (``count`` / ``screen`` / ``offset`` / ``size`` →
45+
``{count, rects}``). ``screen`` defaults to the live primary screen work area. They
46+
are exposed as the MCP tools ``ac_tile_rect`` / ``ac_grid_rects`` /
47+
``ac_cascade_rects`` and as Script Builder commands under **Window**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Comprehensive guides for all AutoControl features.
155155
doc/new_features/v130_features_doc
156156
doc/new_features/v131_features_doc
157157
doc/new_features/v132_features_doc
158+
doc/new_features/v133_features_doc
158159
doc/ocr_backends/ocr_backends_doc
159160
doc/observability/observability_doc
160161
doc/operations_layer/operations_layer_doc
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
視窗鋪排 / 版面幾何規劃器
2+
==========================
3+
4+
``save_window_layout`` / ``restore_window_layout``擷取並重播使用者已經排好的*精確*位置,``snap_window`` 把*一個*
5+
視窗移到一半或四分之一。沒有任何功能能*計算*出全新的多視窗版面。``tile_rect``、``grid_rects`` 與 ``cascade_rects``
6+
是純幾何規劃器:給定螢幕工作區,回傳常見鋪排版面的目標矩形——左右半、四分之一、三分之一、R×C 網格、錯位層疊——
7+
讓腳本能以決定性方式排列應用程式視窗。
8+
9+
此規劃器跨平台且無裝置相依,因此完全可單元測試;它回傳的矩形可與任何視窗移動後端組合。不匯入 ``PySide6``。
10+
11+
無頭 API
12+
--------
13+
14+
.. code-block:: python
15+
16+
from je_auto_control import tile_rect, grid_rects, cascade_rects
17+
18+
left = tile_rect((0, 0, 1920, 1080), "left_third", gap=8)
19+
print(left.as_tuple()) # (8, 8, 624, 1064)
20+
21+
for cell in grid_rects((0, 0, 1920, 1080), rows=2, cols=3):
22+
window_move("Editor", *cell.as_tuple()) # 6 格網格
23+
24+
plan = cascade_rects((0, 0, 1920, 1080), count=4, offset=40)
25+
26+
``tile_rect`` 為具名 ``slot`` 回傳 ``WindowRect``(``x, y, width, height``,含 ``.as_tuple()`` 與 ``.to_dict()``)
27+
——見 :func:`available_slots`(``left``、``top_right``、``center``、``left_third`` …);``gap`` 內縮各邊作為鋪排間距。
28+
``grid_rects`` 為 ``rows`` × ``cols`` 網格的每格(列優先)回傳一個矩形。``cascade_rects`` 回傳 ``count`` 個錯位、
29+
重疊且被夾在螢幕內的矩形(``size`` 預設為工作區的 60%)。未知 slot / 非正網格維度會丟出 ``ValueError``。
30+
31+
執行器命令
32+
----------
33+
34+
``AC_tile_rect``(``slot`` / ``screen`` / ``gap`` → ``{rect}``)、``AC_grid_rects``(``rows`` / ``cols`` / ``screen``
35+
/ ``gap`` → ``{count, rects}``)與 ``AC_cascade_rects``(``count`` / ``screen`` / ``offset`` / ``size`` →
36+
``{count, rects}``)。``screen`` 預設為實際主螢幕工作區。它們以 MCP 工具 ``ac_tile_rect`` / ``ac_grid_rects`` /
37+
``ac_cascade_rects`` 以及 Script Builder 中 **Window** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ AutoControl 所有功能的完整使用指南。
155155
doc/new_features/v130_features_doc
156156
doc/new_features/v131_features_doc
157157
doc/new_features/v132_features_doc
158+
doc/new_features/v133_features_doc
158159
doc/ocr_backends/ocr_backends_doc
159160
doc/observability/observability_doc
160161
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@
294294
from je_auto_control.utils.shape_locator import (
295295
find_rectangles, find_shapes,
296296
)
297+
# Window tiling/layout geometry planner (halves, quadrants, grids, cascade)
298+
from je_auto_control.utils.window_layout import (
299+
WindowRect, available_slots, cascade_rects, grid_rects, tile_rect,
300+
)
297301
# CI workflow annotations (GitHub Actions)
298302
from je_auto_control.utils.ci_annotations import (
299303
emit_annotations, format_annotation,
@@ -1097,6 +1101,11 @@ def start_autocontrol_gui(*args, **kwargs):
10971101
"FeatureMatch",
10981102
"find_shapes",
10991103
"find_rectangles",
1104+
"WindowRect",
1105+
"available_slots",
1106+
"tile_rect",
1107+
"grid_rects",
1108+
"cascade_rects",
11001109
"emit_annotations", "format_annotation",
11011110
"ClipboardHistory", "default_clipboard_history",
11021111
"analyze_heal_log", "heal_stats", "scan_secrets",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,43 @@ def _add_window_specs(specs: List[CommandSpec]) -> None:
552552
fields=(FieldSpec("layout", FieldType.FILE_PATH),),
553553
description="Move windows back to a saved layout file.",
554554
))
555+
specs.append(CommandSpec(
556+
"AC_tile_rect", "Window", "Tile Rect (compute)",
557+
fields=(
558+
FieldSpec("slot", FieldType.ENUM,
559+
choices=("full", "left", "right", "top", "bottom",
560+
"top_left", "top_right", "bottom_left",
561+
"bottom_right", "center", "left_third",
562+
"center_third", "right_third"), default="left"),
563+
FieldSpec("screen", FieldType.STRING, optional=True,
564+
placeholder="[x, y, width, height]"),
565+
FieldSpec("gap", FieldType.INT, optional=True, default=0),
566+
),
567+
description="Compute the rectangle for a tiling slot of the screen.",
568+
))
569+
specs.append(CommandSpec(
570+
"AC_grid_rects", "Window", "Grid Rects (compute)",
571+
fields=(
572+
FieldSpec("rows", FieldType.INT, default=2),
573+
FieldSpec("cols", FieldType.INT, default=2),
574+
FieldSpec("screen", FieldType.STRING, optional=True,
575+
placeholder="[x, y, width, height]"),
576+
FieldSpec("gap", FieldType.INT, optional=True, default=0),
577+
),
578+
description="Compute the cell rectangles of an R×C screen grid.",
579+
))
580+
specs.append(CommandSpec(
581+
"AC_cascade_rects", "Window", "Cascade Rects (compute)",
582+
fields=(
583+
FieldSpec("count", FieldType.INT, default=3),
584+
FieldSpec("screen", FieldType.STRING, optional=True,
585+
placeholder="[x, y, width, height]"),
586+
FieldSpec("offset", FieldType.INT, optional=True, default=30),
587+
FieldSpec("size", FieldType.STRING, optional=True,
588+
placeholder="[width, height]"),
589+
),
590+
description="Compute staggered, overlapping window rectangles.",
591+
))
555592

556593

557594
def _add_flow_specs(specs: List[CommandSpec]) -> None:

je_auto_control/utils/executor/action_executor.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3345,6 +3345,45 @@ def _find_rectangles(region: Any = None, min_area: Any = 400, max_area: Any = No
33453345
return {"count": len(rects), "rectangles": rects}
33463346

33473347

3348+
def _resolve_screen(screen: Any) -> list:
3349+
"""Parse a JSON screen rect, or default to the live primary screen work area."""
3350+
import json
3351+
if isinstance(screen, str):
3352+
screen = json.loads(screen) if screen.strip() else None
3353+
if screen:
3354+
return list(screen)
3355+
from je_auto_control.wrapper.auto_control_screen import screen_size
3356+
width, height = screen_size()
3357+
return [0, 0, int(width), int(height)]
3358+
3359+
3360+
def _tile_rect(slot: str, screen: Any = None, gap: Any = 0) -> Dict[str, Any]:
3361+
"""Adapter: rectangle for a named tiling slot of the screen work area."""
3362+
from je_auto_control.utils.window_layout import tile_rect
3363+
rect = tile_rect(_resolve_screen(screen), str(slot), gap=int(gap))
3364+
return {"rect": rect.to_dict()}
3365+
3366+
3367+
def _grid_rects(rows: Any, cols: Any, screen: Any = None,
3368+
gap: Any = 0) -> Dict[str, Any]:
3369+
"""Adapter: one rectangle per cell of an rows x cols grid over the screen."""
3370+
from je_auto_control.utils.window_layout import grid_rects
3371+
rects = grid_rects(_resolve_screen(screen), int(rows), int(cols), gap=int(gap))
3372+
return {"count": len(rects), "rects": [rect.to_dict() for rect in rects]}
3373+
3374+
3375+
def _cascade_rects(count: Any, screen: Any = None, offset: Any = 30,
3376+
size: Any = None) -> Dict[str, Any]:
3377+
"""Adapter: count staggered, overlapping window rectangles (a cascade)."""
3378+
import json
3379+
from je_auto_control.utils.window_layout import cascade_rects
3380+
if isinstance(size, str):
3381+
size = json.loads(size) if size.strip() else None
3382+
rects = cascade_rects(_resolve_screen(screen), int(count), offset=int(offset),
3383+
size=tuple(size) if size else None)
3384+
return {"count": len(rects), "rects": [rect.to_dict() for rect in rects]}
3385+
3386+
33483387
def _with_modifiers(modifiers: Any, actions: Any) -> Dict[str, Any]:
33493388
"""Adapter: run nested actions while modifier keys are held down."""
33503389
import json
@@ -5071,6 +5110,9 @@ def __init__(self):
50715110
"AC_feature_match": _feature_match,
50725111
"AC_find_shapes": _find_shapes,
50735112
"AC_find_rectangles": _find_rectangles,
5113+
"AC_tile_rect": _tile_rect,
5114+
"AC_grid_rects": _grid_rects,
5115+
"AC_cascade_rects": _cascade_rects,
50745116
"AC_find_color_region": _find_color_region,
50755117
"AC_detect_drift": _detect_drift,
50765118
"AC_categorical_drift": _categorical_drift,

0 commit comments

Comments
 (0)