cudag 0.3.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cudag/__init__.py +334 -0
- cudag/annotation/__init__.py +77 -0
- cudag/annotation/codegen.py +648 -0
- cudag/annotation/config.py +545 -0
- cudag/annotation/loader.py +342 -0
- cudag/annotation/scaffold.py +121 -0
- cudag/annotation/transcription.py +296 -0
- cudag/cli/__init__.py +5 -0
- cudag/cli/main.py +315 -0
- cudag/cli/new.py +873 -0
- cudag/core/__init__.py +364 -0
- cudag/core/button.py +137 -0
- cudag/core/canvas.py +222 -0
- cudag/core/config.py +70 -0
- cudag/core/coords.py +233 -0
- cudag/core/data_grid.py +804 -0
- cudag/core/dataset.py +678 -0
- cudag/core/distribution.py +136 -0
- cudag/core/drawing.py +75 -0
- cudag/core/fonts.py +156 -0
- cudag/core/generator.py +163 -0
- cudag/core/grid.py +367 -0
- cudag/core/grounding_task.py +247 -0
- cudag/core/icon.py +207 -0
- cudag/core/iconlist_task.py +301 -0
- cudag/core/models.py +1251 -0
- cudag/core/random.py +130 -0
- cudag/core/renderer.py +190 -0
- cudag/core/screen.py +402 -0
- cudag/core/scroll_task.py +254 -0
- cudag/core/scrollable_grid.py +447 -0
- cudag/core/state.py +110 -0
- cudag/core/task.py +293 -0
- cudag/core/taskbar.py +350 -0
- cudag/core/text.py +212 -0
- cudag/core/utils.py +82 -0
- cudag/data/surnames.txt +5000 -0
- cudag/modal_apps/__init__.py +4 -0
- cudag/modal_apps/archive.py +103 -0
- cudag/modal_apps/extract.py +138 -0
- cudag/modal_apps/preprocess.py +529 -0
- cudag/modal_apps/upload.py +317 -0
- cudag/prompts/SYSTEM_PROMPT.txt +104 -0
- cudag/prompts/__init__.py +33 -0
- cudag/prompts/system.py +43 -0
- cudag/prompts/tools.py +382 -0
- cudag/py.typed +0 -0
- cudag/schemas/filesystem.json +90 -0
- cudag/schemas/test_record.schema.json +113 -0
- cudag/schemas/train_record.schema.json +90 -0
- cudag/server/__init__.py +21 -0
- cudag/server/app.py +232 -0
- cudag/server/services/__init__.py +9 -0
- cudag/server/services/generator.py +128 -0
- cudag/templates/scripts/archive.sh +35 -0
- cudag/templates/scripts/build.sh +13 -0
- cudag/templates/scripts/extract.sh +54 -0
- cudag/templates/scripts/generate.sh +116 -0
- cudag/templates/scripts/pre-commit.sh +44 -0
- cudag/templates/scripts/preprocess.sh +46 -0
- cudag/templates/scripts/upload.sh +63 -0
- cudag/templates/scripts/verify.py +428 -0
- cudag/validation/__init__.py +35 -0
- cudag/validation/validate.py +508 -0
- cudag-0.3.10.dist-info/METADATA +570 -0
- cudag-0.3.10.dist-info/RECORD +69 -0
- cudag-0.3.10.dist-info/WHEEL +4 -0
- cudag-0.3.10.dist-info/entry_points.txt +2 -0
- cudag-0.3.10.dist-info/licenses/LICENSE +66 -0
cudag/core/__init__.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Copyright (c) 2025 Tylt LLC. All rights reserved.
|
|
2
|
+
# CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
|
|
3
|
+
# is strictly prohibited. For licensing inquiries: hello@claimhawk.app
|
|
4
|
+
|
|
5
|
+
"""Core framework classes and DSL functions."""
|
|
6
|
+
|
|
7
|
+
from cudag.core.coords import (
|
|
8
|
+
RU_MAX,
|
|
9
|
+
bounds_to_tolerance,
|
|
10
|
+
calculate_tolerance_ru,
|
|
11
|
+
clamp_coord,
|
|
12
|
+
coord_distance,
|
|
13
|
+
coord_within_tolerance,
|
|
14
|
+
get_normalized_bounds,
|
|
15
|
+
normalize_coord,
|
|
16
|
+
pixel_from_normalized,
|
|
17
|
+
tolerance_to_ru,
|
|
18
|
+
)
|
|
19
|
+
from cudag.core.button import (
|
|
20
|
+
DIALOG_CANCEL,
|
|
21
|
+
DIALOG_OK,
|
|
22
|
+
LARGE_RECT,
|
|
23
|
+
LARGE_SQUARE,
|
|
24
|
+
MEDIUM_RECT,
|
|
25
|
+
MEDIUM_SQUARE,
|
|
26
|
+
NAV_BUTTON,
|
|
27
|
+
SMALL_RECT,
|
|
28
|
+
SMALL_SQUARE,
|
|
29
|
+
TOOLBAR_BUTTON,
|
|
30
|
+
ButtonPlacement,
|
|
31
|
+
ButtonShape,
|
|
32
|
+
ButtonSpec,
|
|
33
|
+
)
|
|
34
|
+
from cudag.core.canvas import CanvasConfig, RegionConfig
|
|
35
|
+
from cudag.core.grid import Grid, GridCell, GridGeometry
|
|
36
|
+
from cudag.core.scrollable_grid import (
|
|
37
|
+
ColumnDef,
|
|
38
|
+
RowLayout,
|
|
39
|
+
ScrollableGrid,
|
|
40
|
+
ScrollableGridGeometry,
|
|
41
|
+
ScrollState as GridScrollState,
|
|
42
|
+
)
|
|
43
|
+
from cudag.core.data_grid import (
|
|
44
|
+
ColumnDef as DataColumnDef,
|
|
45
|
+
Grid as DataGrid,
|
|
46
|
+
GridGeometry as DataGridGeometry,
|
|
47
|
+
RowLayout as DataRowLayout,
|
|
48
|
+
ScrollableGrid as DataScrollableGrid,
|
|
49
|
+
ScrollState as DataScrollState,
|
|
50
|
+
SelectableRowGrid,
|
|
51
|
+
SelectionState,
|
|
52
|
+
wrap_text as grid_wrap_text,
|
|
53
|
+
)
|
|
54
|
+
from cudag.core.icon import (
|
|
55
|
+
APP_ICON_LARGE,
|
|
56
|
+
APP_ICON_SMALL,
|
|
57
|
+
DESKTOP_ICON,
|
|
58
|
+
TASKBAR_ICON,
|
|
59
|
+
TOOLBAR_ICON,
|
|
60
|
+
IconLayout,
|
|
61
|
+
IconPlacement,
|
|
62
|
+
IconSpec,
|
|
63
|
+
)
|
|
64
|
+
from cudag.core.taskbar import TaskbarRenderer, TaskbarState
|
|
65
|
+
from cudag.core.dataset import DatasetBuilder, DatasetConfig
|
|
66
|
+
from cudag.core.distribution import DistributionSampler
|
|
67
|
+
from cudag.core.scroll_task import ScrollTaskBase, ScrollTaskConfig
|
|
68
|
+
from cudag.core.iconlist_task import IconListTaskBase, make_tool_call
|
|
69
|
+
from cudag.core.grounding_task import GroundingTaskBase, bbox_to_ru, scale_bbox
|
|
70
|
+
from cudag.core.models import (
|
|
71
|
+
# Classes
|
|
72
|
+
Attachment,
|
|
73
|
+
BelongsToRel,
|
|
74
|
+
BoolField,
|
|
75
|
+
ChoiceField,
|
|
76
|
+
Claim,
|
|
77
|
+
ComputedField,
|
|
78
|
+
DateField,
|
|
79
|
+
Field,
|
|
80
|
+
FloatField,
|
|
81
|
+
HasManyRel,
|
|
82
|
+
HasOneRel,
|
|
83
|
+
IntField,
|
|
84
|
+
ListField,
|
|
85
|
+
Model,
|
|
86
|
+
ModelGenerator,
|
|
87
|
+
MoneyField,
|
|
88
|
+
Patient,
|
|
89
|
+
Procedure,
|
|
90
|
+
Provider,
|
|
91
|
+
Relationship,
|
|
92
|
+
StringField,
|
|
93
|
+
TimeField,
|
|
94
|
+
# DSL functions
|
|
95
|
+
attribute,
|
|
96
|
+
belongs_to,
|
|
97
|
+
boolean,
|
|
98
|
+
choice,
|
|
99
|
+
computed,
|
|
100
|
+
date_field,
|
|
101
|
+
decimal,
|
|
102
|
+
get_first_name,
|
|
103
|
+
get_last_name,
|
|
104
|
+
has_many,
|
|
105
|
+
has_one,
|
|
106
|
+
integer,
|
|
107
|
+
list_of,
|
|
108
|
+
money,
|
|
109
|
+
string,
|
|
110
|
+
time_field,
|
|
111
|
+
years_since,
|
|
112
|
+
# Semantic field types
|
|
113
|
+
City,
|
|
114
|
+
ClaimNumber,
|
|
115
|
+
ClaimStatus,
|
|
116
|
+
DOB,
|
|
117
|
+
Email,
|
|
118
|
+
Fee,
|
|
119
|
+
FirstName,
|
|
120
|
+
FullName,
|
|
121
|
+
LastName,
|
|
122
|
+
LicenseNumber,
|
|
123
|
+
MemberID,
|
|
124
|
+
NPI,
|
|
125
|
+
Phone,
|
|
126
|
+
ProcedureCode,
|
|
127
|
+
SSN,
|
|
128
|
+
Specialty,
|
|
129
|
+
State,
|
|
130
|
+
Street,
|
|
131
|
+
ZipCode,
|
|
132
|
+
)
|
|
133
|
+
from cudag.core.renderer import BaseRenderer
|
|
134
|
+
from cudag.core.screen import (
|
|
135
|
+
# Classes
|
|
136
|
+
Bounds,
|
|
137
|
+
ButtonRegion,
|
|
138
|
+
ClickRegion,
|
|
139
|
+
DropdownRegion,
|
|
140
|
+
GridRegion,
|
|
141
|
+
Region,
|
|
142
|
+
Screen,
|
|
143
|
+
ScreenBase,
|
|
144
|
+
ScreenMeta,
|
|
145
|
+
ScrollRegion,
|
|
146
|
+
# DSL functions
|
|
147
|
+
button,
|
|
148
|
+
dropdown,
|
|
149
|
+
grid,
|
|
150
|
+
region,
|
|
151
|
+
scrollable,
|
|
152
|
+
)
|
|
153
|
+
from cudag.core.config import get_config_path, load_yaml_config
|
|
154
|
+
from cudag.core.drawing import render_scrollbar
|
|
155
|
+
from cudag.core.fonts import SYSTEM_FONTS, load_font, load_font_family
|
|
156
|
+
from cudag.core.generator import run_generator
|
|
157
|
+
from cudag.core.random import amount, choose, date_in_range, weighted_choice
|
|
158
|
+
from cudag.core.state import BaseState, ScrollState
|
|
159
|
+
from cudag.core.task import BaseTask, TaskContext, TaskSample, TestCase
|
|
160
|
+
from cudag.core.text import (
|
|
161
|
+
center_text_position,
|
|
162
|
+
draw_centered_text,
|
|
163
|
+
measure_text,
|
|
164
|
+
ordinal_suffix,
|
|
165
|
+
truncate_text,
|
|
166
|
+
wrap_text,
|
|
167
|
+
)
|
|
168
|
+
from cudag.core.utils import check_script_invocation, get_researcher_name
|
|
169
|
+
|
|
170
|
+
__all__ = [
|
|
171
|
+
# Coordinates
|
|
172
|
+
"RU_MAX",
|
|
173
|
+
"normalize_coord",
|
|
174
|
+
"pixel_from_normalized",
|
|
175
|
+
"get_normalized_bounds",
|
|
176
|
+
"clamp_coord",
|
|
177
|
+
"coord_distance",
|
|
178
|
+
"coord_within_tolerance",
|
|
179
|
+
"tolerance_to_ru",
|
|
180
|
+
"bounds_to_tolerance",
|
|
181
|
+
"calculate_tolerance_ru",
|
|
182
|
+
# Screen DSL - classes
|
|
183
|
+
"Screen",
|
|
184
|
+
"ScreenBase",
|
|
185
|
+
"ScreenMeta",
|
|
186
|
+
"Region",
|
|
187
|
+
"Bounds",
|
|
188
|
+
"ClickRegion",
|
|
189
|
+
"ButtonRegion",
|
|
190
|
+
"GridRegion",
|
|
191
|
+
"ScrollRegion",
|
|
192
|
+
"DropdownRegion",
|
|
193
|
+
# Screen DSL - functions
|
|
194
|
+
"region",
|
|
195
|
+
"button",
|
|
196
|
+
"grid",
|
|
197
|
+
"scrollable",
|
|
198
|
+
"dropdown",
|
|
199
|
+
# Canvas/Region
|
|
200
|
+
"CanvasConfig",
|
|
201
|
+
"RegionConfig",
|
|
202
|
+
# Grid
|
|
203
|
+
"Grid",
|
|
204
|
+
"GridCell",
|
|
205
|
+
"GridGeometry",
|
|
206
|
+
# Scrollable Grid (legacy)
|
|
207
|
+
"ScrollableGrid",
|
|
208
|
+
"ScrollableGridGeometry",
|
|
209
|
+
"ColumnDef",
|
|
210
|
+
"RowLayout",
|
|
211
|
+
"GridScrollState",
|
|
212
|
+
# Data Grid (composable)
|
|
213
|
+
"DataGrid",
|
|
214
|
+
"DataGridGeometry",
|
|
215
|
+
"DataColumnDef",
|
|
216
|
+
"DataRowLayout",
|
|
217
|
+
"DataScrollableGrid",
|
|
218
|
+
"DataScrollState",
|
|
219
|
+
"SelectableRowGrid",
|
|
220
|
+
"SelectionState",
|
|
221
|
+
"grid_wrap_text",
|
|
222
|
+
# Icons
|
|
223
|
+
"IconSpec",
|
|
224
|
+
"IconPlacement",
|
|
225
|
+
"IconLayout",
|
|
226
|
+
"DESKTOP_ICON",
|
|
227
|
+
"TASKBAR_ICON",
|
|
228
|
+
"TOOLBAR_ICON",
|
|
229
|
+
"APP_ICON_LARGE",
|
|
230
|
+
"APP_ICON_SMALL",
|
|
231
|
+
# Taskbar
|
|
232
|
+
"TaskbarState",
|
|
233
|
+
"TaskbarRenderer",
|
|
234
|
+
# Buttons
|
|
235
|
+
"ButtonSpec",
|
|
236
|
+
"ButtonPlacement",
|
|
237
|
+
"ButtonShape",
|
|
238
|
+
"SMALL_SQUARE",
|
|
239
|
+
"MEDIUM_SQUARE",
|
|
240
|
+
"LARGE_SQUARE",
|
|
241
|
+
"SMALL_RECT",
|
|
242
|
+
"MEDIUM_RECT",
|
|
243
|
+
"LARGE_RECT",
|
|
244
|
+
"NAV_BUTTON",
|
|
245
|
+
"TOOLBAR_BUTTON",
|
|
246
|
+
"DIALOG_OK",
|
|
247
|
+
"DIALOG_CANCEL",
|
|
248
|
+
# State
|
|
249
|
+
"BaseState",
|
|
250
|
+
"ScrollState",
|
|
251
|
+
# Renderer
|
|
252
|
+
"BaseRenderer",
|
|
253
|
+
# Task
|
|
254
|
+
"BaseTask",
|
|
255
|
+
"TaskSample",
|
|
256
|
+
"TaskContext",
|
|
257
|
+
"TestCase",
|
|
258
|
+
# Dataset
|
|
259
|
+
"DatasetBuilder",
|
|
260
|
+
"DatasetConfig",
|
|
261
|
+
# Distribution
|
|
262
|
+
"DistributionSampler",
|
|
263
|
+
# Scroll Tasks
|
|
264
|
+
"ScrollTaskBase",
|
|
265
|
+
"ScrollTaskConfig",
|
|
266
|
+
# IconList Tasks
|
|
267
|
+
"IconListTaskBase",
|
|
268
|
+
"make_tool_call",
|
|
269
|
+
# Grounding Tasks
|
|
270
|
+
"GroundingTaskBase",
|
|
271
|
+
"bbox_to_ru",
|
|
272
|
+
"scale_bbox",
|
|
273
|
+
# Model DSL - classes
|
|
274
|
+
"Model",
|
|
275
|
+
"ModelGenerator",
|
|
276
|
+
"Field",
|
|
277
|
+
"StringField",
|
|
278
|
+
"IntField",
|
|
279
|
+
"FloatField",
|
|
280
|
+
"BoolField",
|
|
281
|
+
"DateField",
|
|
282
|
+
"TimeField",
|
|
283
|
+
"ChoiceField",
|
|
284
|
+
"ListField",
|
|
285
|
+
"MoneyField",
|
|
286
|
+
"ComputedField",
|
|
287
|
+
# Model DSL - functions
|
|
288
|
+
"attribute",
|
|
289
|
+
"string",
|
|
290
|
+
"integer",
|
|
291
|
+
"decimal",
|
|
292
|
+
"money",
|
|
293
|
+
"date_field",
|
|
294
|
+
"time_field",
|
|
295
|
+
"boolean",
|
|
296
|
+
"choice",
|
|
297
|
+
"list_of",
|
|
298
|
+
"computed",
|
|
299
|
+
"years_since",
|
|
300
|
+
# Name generation functions
|
|
301
|
+
"get_first_name",
|
|
302
|
+
"get_last_name",
|
|
303
|
+
# Relationship DSL - classes
|
|
304
|
+
"Relationship",
|
|
305
|
+
"HasManyRel",
|
|
306
|
+
"BelongsToRel",
|
|
307
|
+
"HasOneRel",
|
|
308
|
+
# Relationship DSL - functions
|
|
309
|
+
"has_many",
|
|
310
|
+
"belongs_to",
|
|
311
|
+
"has_one",
|
|
312
|
+
# Common healthcare models
|
|
313
|
+
"Patient",
|
|
314
|
+
"Provider",
|
|
315
|
+
"Procedure",
|
|
316
|
+
"Claim",
|
|
317
|
+
"Attachment",
|
|
318
|
+
# Semantic field types
|
|
319
|
+
"FirstName",
|
|
320
|
+
"LastName",
|
|
321
|
+
"FullName",
|
|
322
|
+
"DOB",
|
|
323
|
+
"NPI",
|
|
324
|
+
"SSN",
|
|
325
|
+
"Phone",
|
|
326
|
+
"Email",
|
|
327
|
+
"Street",
|
|
328
|
+
"City",
|
|
329
|
+
"State",
|
|
330
|
+
"ZipCode",
|
|
331
|
+
"MemberID",
|
|
332
|
+
"ClaimNumber",
|
|
333
|
+
"ProcedureCode",
|
|
334
|
+
"LicenseNumber",
|
|
335
|
+
"Specialty",
|
|
336
|
+
"ClaimStatus",
|
|
337
|
+
"Fee",
|
|
338
|
+
# Utils
|
|
339
|
+
"check_script_invocation",
|
|
340
|
+
"get_researcher_name",
|
|
341
|
+
# Generator
|
|
342
|
+
"run_generator",
|
|
343
|
+
# Fonts
|
|
344
|
+
"load_font",
|
|
345
|
+
"load_font_family",
|
|
346
|
+
"SYSTEM_FONTS",
|
|
347
|
+
# Random utilities
|
|
348
|
+
"choose",
|
|
349
|
+
"date_in_range",
|
|
350
|
+
"amount",
|
|
351
|
+
"weighted_choice",
|
|
352
|
+
# Text utilities
|
|
353
|
+
"measure_text",
|
|
354
|
+
"center_text_position",
|
|
355
|
+
"draw_centered_text",
|
|
356
|
+
"wrap_text",
|
|
357
|
+
"truncate_text",
|
|
358
|
+
"ordinal_suffix",
|
|
359
|
+
# Drawing utilities
|
|
360
|
+
"render_scrollbar",
|
|
361
|
+
# Config utilities
|
|
362
|
+
"load_yaml_config",
|
|
363
|
+
"get_config_path",
|
|
364
|
+
]
|
cudag/core/button.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Copyright (c) 2025 Tylt LLC. All rights reserved.
|
|
2
|
+
# CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
|
|
3
|
+
# is strictly prohibited. For licensing inquiries: hello@claimhawk.app
|
|
4
|
+
|
|
5
|
+
"""Button abstractions for UI elements.
|
|
6
|
+
|
|
7
|
+
Provides reusable button definitions for different UI button types:
|
|
8
|
+
- Square buttons (equal width/height)
|
|
9
|
+
- Rectangular buttons (different width/height)
|
|
10
|
+
- Text buttons (with label)
|
|
11
|
+
- Icon buttons (with icon)
|
|
12
|
+
|
|
13
|
+
Each button type includes natural tolerance calculation (70% of dimensions).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from enum import Enum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ButtonShape(Enum):
|
|
23
|
+
"""Button shape types."""
|
|
24
|
+
|
|
25
|
+
SQUARE = "square"
|
|
26
|
+
RECT = "rect"
|
|
27
|
+
PILL = "pill" # Rounded ends
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ButtonSpec:
|
|
32
|
+
"""Specification for a button type.
|
|
33
|
+
|
|
34
|
+
Defines dimensions and characteristics for buttons in UIs.
|
|
35
|
+
Buttons can be square (equal dimensions) or rectangular.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
width: Button width in pixels.
|
|
39
|
+
height: Button height in pixels.
|
|
40
|
+
shape: Button shape (square, rect, pill).
|
|
41
|
+
has_label: Whether button displays text label.
|
|
42
|
+
has_icon: Whether button displays an icon.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
width: int
|
|
46
|
+
height: int
|
|
47
|
+
shape: ButtonShape = ButtonShape.RECT
|
|
48
|
+
has_label: bool = False
|
|
49
|
+
has_icon: bool = False
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def is_square(self) -> bool:
|
|
53
|
+
"""Check if button is square."""
|
|
54
|
+
return self.width == self.height
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def tolerance_pixels(self) -> tuple[int, int]:
|
|
58
|
+
"""Calculate natural click tolerance in pixels.
|
|
59
|
+
|
|
60
|
+
Returns 70% of dimensions (15% padding on each side).
|
|
61
|
+
"""
|
|
62
|
+
return (int(self.width * 0.7), int(self.height * 0.7))
|
|
63
|
+
|
|
64
|
+
def tolerance_ru(self, image_size: tuple[int, int]) -> tuple[int, int]:
|
|
65
|
+
"""Calculate natural tolerance in RU (Resolution Units).
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
image_size: (width, height) of the full image.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Tuple of (x_tolerance_ru, y_tolerance_ru) in 0-1000 range.
|
|
72
|
+
"""
|
|
73
|
+
tol_pixels = self.tolerance_pixels
|
|
74
|
+
x_ru = (tol_pixels[0] / image_size[0]) * 1000
|
|
75
|
+
y_ru = (tol_pixels[1] / image_size[1]) * 1000
|
|
76
|
+
return (int(x_ru), int(y_ru))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ButtonPlacement:
|
|
81
|
+
"""A button placed at a specific location.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
spec: The button specification.
|
|
85
|
+
x: X position (left edge) in pixels.
|
|
86
|
+
y: Y position (top edge) in pixels.
|
|
87
|
+
label: Optional text label for the button.
|
|
88
|
+
description: Human-readable description of button action.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
spec: ButtonSpec
|
|
92
|
+
x: int
|
|
93
|
+
y: int
|
|
94
|
+
label: str = ""
|
|
95
|
+
description: str = ""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def bounds(self) -> tuple[int, int, int, int]:
|
|
99
|
+
"""Get button bounds as (x, y, width, height)."""
|
|
100
|
+
return (self.x, self.y, self.spec.width, self.spec.height)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def center(self) -> tuple[int, int]:
|
|
104
|
+
"""Get button center coordinates."""
|
|
105
|
+
return (
|
|
106
|
+
self.x + self.spec.width // 2,
|
|
107
|
+
self.y + self.spec.height // 2,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def tolerance_pixels(self) -> tuple[int, int]:
|
|
112
|
+
"""Get natural tolerance in pixels."""
|
|
113
|
+
return self.spec.tolerance_pixels
|
|
114
|
+
|
|
115
|
+
def tolerance_ru(self, image_size: tuple[int, int]) -> tuple[int, int]:
|
|
116
|
+
"""Get natural tolerance in RU units."""
|
|
117
|
+
return self.spec.tolerance_ru(image_size)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Common button presets
|
|
121
|
+
SMALL_SQUARE = ButtonSpec(width=16, height=16, shape=ButtonShape.SQUARE, has_icon=True)
|
|
122
|
+
MEDIUM_SQUARE = ButtonSpec(width=24, height=24, shape=ButtonShape.SQUARE, has_icon=True)
|
|
123
|
+
LARGE_SQUARE = ButtonSpec(width=32, height=32, shape=ButtonShape.SQUARE, has_icon=True)
|
|
124
|
+
|
|
125
|
+
SMALL_RECT = ButtonSpec(width=60, height=24, shape=ButtonShape.RECT, has_label=True)
|
|
126
|
+
MEDIUM_RECT = ButtonSpec(width=80, height=28, shape=ButtonShape.RECT, has_label=True)
|
|
127
|
+
LARGE_RECT = ButtonSpec(width=120, height=32, shape=ButtonShape.RECT, has_label=True)
|
|
128
|
+
|
|
129
|
+
# Navigation buttons (like calendar back/forward)
|
|
130
|
+
NAV_BUTTON = ButtonSpec(width=20, height=12, shape=ButtonShape.RECT, has_icon=True)
|
|
131
|
+
|
|
132
|
+
# Toolbar buttons
|
|
133
|
+
TOOLBAR_BUTTON = ButtonSpec(width=24, height=24, shape=ButtonShape.SQUARE, has_icon=True)
|
|
134
|
+
|
|
135
|
+
# Dialog buttons
|
|
136
|
+
DIALOG_OK = ButtonSpec(width=75, height=23, shape=ButtonShape.RECT, has_label=True)
|
|
137
|
+
DIALOG_CANCEL = ButtonSpec(width=75, height=23, shape=ButtonShape.RECT, has_label=True)
|
cudag/core/canvas.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Copyright (c) 2025 Tylt LLC. All rights reserved.
|
|
2
|
+
# CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
|
|
3
|
+
# is strictly prohibited. For licensing inquiries: hello@claimhawk.app
|
|
4
|
+
|
|
5
|
+
"""Canvas and Region abstractions for screen composition.
|
|
6
|
+
|
|
7
|
+
Provides a declarative way to define screenshot layouts:
|
|
8
|
+
- Canvas: full screenshot dimensions with base blank image
|
|
9
|
+
- Region: subsection of canvas with optional overlay and generator
|
|
10
|
+
|
|
11
|
+
Regions can have associated generators for:
|
|
12
|
+
- Grids (calendars, data tables)
|
|
13
|
+
- Icons (desktop icons, toolbars)
|
|
14
|
+
- Content (text, form fields)
|
|
15
|
+
|
|
16
|
+
Supports loading from YAML/JSON configuration.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
import yaml
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class RegionConfig:
|
|
30
|
+
"""Configuration for a region within a canvas."""
|
|
31
|
+
|
|
32
|
+
name: str
|
|
33
|
+
"""Unique region identifier."""
|
|
34
|
+
|
|
35
|
+
bounds: tuple[int, int, int, int]
|
|
36
|
+
"""Region bounds as (x, y, width, height)."""
|
|
37
|
+
|
|
38
|
+
z_index: int = 0
|
|
39
|
+
"""Layer order (higher = on top)."""
|
|
40
|
+
|
|
41
|
+
blank_image: str | Path = ""
|
|
42
|
+
"""Optional overlay/blank image for this region."""
|
|
43
|
+
|
|
44
|
+
generator_type: str = ""
|
|
45
|
+
"""Generator type: 'grid', 'icons', 'content', etc."""
|
|
46
|
+
|
|
47
|
+
generator_config: dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
"""Configuration for the generator."""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def x(self) -> int:
|
|
52
|
+
return self.bounds[0]
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def y(self) -> int:
|
|
56
|
+
return self.bounds[1]
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def width(self) -> int:
|
|
60
|
+
return self.bounds[2]
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def height(self) -> int:
|
|
64
|
+
return self.bounds[3]
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def center(self) -> tuple[int, int]:
|
|
68
|
+
return (self.x + self.width // 2, self.y + self.height // 2)
|
|
69
|
+
|
|
70
|
+
def tolerance_ru(self, image_size: tuple[int, int]) -> tuple[int, int]:
|
|
71
|
+
"""Get natural tolerance in RU units based on region size."""
|
|
72
|
+
x_ru = (self.width * 0.7 / image_size[0]) * 1000
|
|
73
|
+
y_ru = (self.height * 0.7 / image_size[1]) * 1000
|
|
74
|
+
return (int(x_ru), int(y_ru))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class CanvasConfig:
|
|
79
|
+
"""Configuration for a full screenshot canvas."""
|
|
80
|
+
|
|
81
|
+
name: str
|
|
82
|
+
"""Canvas identifier."""
|
|
83
|
+
|
|
84
|
+
size: tuple[int, int]
|
|
85
|
+
"""Canvas dimensions as (width, height)."""
|
|
86
|
+
|
|
87
|
+
blank_image: str | Path
|
|
88
|
+
"""Base blank image for the canvas."""
|
|
89
|
+
|
|
90
|
+
regions: list[RegionConfig] = field(default_factory=list)
|
|
91
|
+
"""Regions on this canvas."""
|
|
92
|
+
|
|
93
|
+
task_types: list[str] = field(default_factory=list)
|
|
94
|
+
"""Supported task types for this canvas."""
|
|
95
|
+
|
|
96
|
+
def get_region(self, name: str) -> RegionConfig | None:
|
|
97
|
+
"""Get a region by name."""
|
|
98
|
+
for region in self.regions:
|
|
99
|
+
if region.name == name:
|
|
100
|
+
return region
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def regions_by_z(self) -> list[RegionConfig]:
|
|
104
|
+
"""Get regions sorted by z-index (bottom to top)."""
|
|
105
|
+
return sorted(self.regions, key=lambda r: r.z_index)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_yaml(cls, path: Path) -> CanvasConfig:
|
|
109
|
+
"""Load canvas configuration from YAML file."""
|
|
110
|
+
with open(path) as f:
|
|
111
|
+
data = yaml.safe_load(f)
|
|
112
|
+
return cls.from_dict(data)
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def from_dict(cls, data: dict[str, Any]) -> CanvasConfig:
|
|
116
|
+
"""Create from dictionary."""
|
|
117
|
+
regions = []
|
|
118
|
+
for r in data.get("regions", []):
|
|
119
|
+
regions.append(
|
|
120
|
+
RegionConfig(
|
|
121
|
+
name=r["name"],
|
|
122
|
+
bounds=tuple(r["bounds"]),
|
|
123
|
+
z_index=r.get("z_index", 0),
|
|
124
|
+
blank_image=r.get("blank_image", ""),
|
|
125
|
+
generator_type=r.get("generator", ""),
|
|
126
|
+
generator_config=r.get("config", {}),
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return cls(
|
|
131
|
+
name=data["name"],
|
|
132
|
+
size=tuple(data["size"]),
|
|
133
|
+
blank_image=data["blank_image"],
|
|
134
|
+
regions=regions,
|
|
135
|
+
task_types=data.get("task_types", []),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def to_dict(self) -> dict[str, Any]:
|
|
139
|
+
"""Convert to dictionary for serialization."""
|
|
140
|
+
return {
|
|
141
|
+
"name": self.name,
|
|
142
|
+
"size": list(self.size),
|
|
143
|
+
"blank_image": str(self.blank_image),
|
|
144
|
+
"task_types": self.task_types,
|
|
145
|
+
"regions": [
|
|
146
|
+
{
|
|
147
|
+
"name": r.name,
|
|
148
|
+
"bounds": list(r.bounds),
|
|
149
|
+
"z_index": r.z_index,
|
|
150
|
+
"blank_image": str(r.blank_image) if r.blank_image else "",
|
|
151
|
+
"generator": r.generator_type,
|
|
152
|
+
"config": r.generator_config,
|
|
153
|
+
}
|
|
154
|
+
for r in self.regions
|
|
155
|
+
],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Example YAML format:
|
|
160
|
+
"""
|
|
161
|
+
# canvas.yaml
|
|
162
|
+
name: desktop
|
|
163
|
+
size: [1920, 1080]
|
|
164
|
+
blank_image: assets/blanks/desktop-blank.png
|
|
165
|
+
task_types:
|
|
166
|
+
- click-desktop-icon
|
|
167
|
+
- click-taskbar-icon
|
|
168
|
+
|
|
169
|
+
regions:
|
|
170
|
+
- name: desktop_area
|
|
171
|
+
bounds: [0, 0, 1920, 1032]
|
|
172
|
+
z_index: 0
|
|
173
|
+
generator: icons
|
|
174
|
+
config:
|
|
175
|
+
icon_type: desktop
|
|
176
|
+
layout: grid
|
|
177
|
+
cols: 1
|
|
178
|
+
padding: 20
|
|
179
|
+
|
|
180
|
+
- name: taskbar
|
|
181
|
+
bounds: [0, 1032, 1920, 48]
|
|
182
|
+
z_index: 1
|
|
183
|
+
blank_image: assets/blanks/taskbar.png
|
|
184
|
+
generator: icons
|
|
185
|
+
config:
|
|
186
|
+
icon_type: taskbar
|
|
187
|
+
layout: horizontal
|
|
188
|
+
start_x: 946
|
|
189
|
+
padding: 8
|
|
190
|
+
|
|
191
|
+
# calendar.yaml
|
|
192
|
+
name: calendar
|
|
193
|
+
size: [224, 208]
|
|
194
|
+
blank_image: assets/blanks/calendar-blank.png
|
|
195
|
+
task_types:
|
|
196
|
+
- click-day
|
|
197
|
+
- click-back-month
|
|
198
|
+
- click-forward-month
|
|
199
|
+
|
|
200
|
+
regions:
|
|
201
|
+
- name: day_grid
|
|
202
|
+
bounds: [2, 72, 219, 90]
|
|
203
|
+
generator: grid
|
|
204
|
+
config:
|
|
205
|
+
rows: 6
|
|
206
|
+
cols: 7
|
|
207
|
+
cell_width: 24
|
|
208
|
+
cell_height: 15
|
|
209
|
+
col_gap: 8
|
|
210
|
+
|
|
211
|
+
- name: back_button
|
|
212
|
+
bounds: [7, 192, 20, 12]
|
|
213
|
+
generator: button
|
|
214
|
+
config:
|
|
215
|
+
label: "Back Month"
|
|
216
|
+
|
|
217
|
+
- name: forward_button
|
|
218
|
+
bounds: [197, 192, 20, 12]
|
|
219
|
+
generator: button
|
|
220
|
+
config:
|
|
221
|
+
label: "Forward Month"
|
|
222
|
+
"""
|