feat: Add StageDialogStyler for enhanced dialog management
- Introduced StageDialogStyler class to render child-facing dialogs as shared in-stage cards. - Preserved native AcceptDialog/ConfirmationDialog signals for stage workflows. - Implemented methods for applying styles, managing visibility, and handling button actions. - Added support for dynamic theming and responsive design based on viewport size. - Created utility functions for styling buttons and managing dialog content.
This commit is contained in:
@@ -8,6 +8,7 @@ addons/
|
||||
*.import
|
||||
# Local scratch captures, logs, and temporary browser profiles
|
||||
/tmp/
|
||||
/work/
|
||||
content/review/artist/
|
||||
content/examples/
|
||||
/tests/godot_test_user_data/
|
||||
|
||||
@@ -0,0 +1,857 @@
|
||||
{
|
||||
"version": "1.7",
|
||||
"scene_name": "Plain Background",
|
||||
"sky_id": "sky_starry_night",
|
||||
"background_kind": "sky",
|
||||
"plain_color": "#F5EFE0",
|
||||
"camera": {
|
||||
"position": {
|
||||
"x": -43.8016738891602,
|
||||
"y": -691.851379394531
|
||||
},
|
||||
"zoom": 0.605000019073486
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"file_id": "background_ground_1",
|
||||
"type": "background_ground",
|
||||
"position": {
|
||||
"x": 96.0,
|
||||
"y": 672.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"size": {
|
||||
"x": 6592.0,
|
||||
"y": 192.0
|
||||
},
|
||||
"finish_id": "finish_background_grass",
|
||||
"color": "#72a84cff"
|
||||
},
|
||||
{
|
||||
"file_id": "scenery_artwork_1",
|
||||
"type": "scenery_artwork",
|
||||
"position": {
|
||||
"x": -1472.0,
|
||||
"y": -320.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "res://content/props/art_storefront-v1.prop",
|
||||
"display_name": "Sunny Storefront",
|
||||
"depth": "near",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"last_known_local_bounds": {
|
||||
"position": {
|
||||
"x": -871.896057128906,
|
||||
"y": -826.405822753906
|
||||
},
|
||||
"size": {
|
||||
"x": 1743.79211425781,
|
||||
"y": 1652.81164550781
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_id": "scenery_artwork_2",
|
||||
"type": "scenery_artwork",
|
||||
"position": {
|
||||
"x": -321.925048828125,
|
||||
"y": -864.47802734375
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "res://content/props/art_town_house-v1.prop",
|
||||
"display_name": "Town House",
|
||||
"depth": "near",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"last_known_local_bounds": {
|
||||
"position": {
|
||||
"x": -958.074951171875,
|
||||
"y": -1375.52197265625
|
||||
},
|
||||
"size": {
|
||||
"x": 1916.14990234375,
|
||||
"y": 2751.0439453125
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_id": "scenery_artwork_3",
|
||||
"type": "scenery_artwork",
|
||||
"position": {
|
||||
"x": 1408.0,
|
||||
"y": -320.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "res://content/props/art_storefront-v1.prop",
|
||||
"display_name": "Sunny Storefront",
|
||||
"depth": "near",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"last_known_local_bounds": {
|
||||
"position": {
|
||||
"x": -871.896057128906,
|
||||
"y": -826.405822753906
|
||||
},
|
||||
"size": {
|
||||
"x": 1743.79211425781,
|
||||
"y": 1652.81164550781
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_id": "scenery_artwork_4",
|
||||
"type": "scenery_artwork",
|
||||
"position": {
|
||||
"x": -2368.0,
|
||||
"y": -256.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "res://content/props/art_city_skyline-v1.prop",
|
||||
"display_name": "City Skyline",
|
||||
"depth": "far",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"last_known_local_bounds": {
|
||||
"position": {
|
||||
"x": -939.5,
|
||||
"y": -289.301696777344
|
||||
},
|
||||
"size": {
|
||||
"x": 1879.0,
|
||||
"y": 578.603393554688
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_id": "scenery_artwork_5",
|
||||
"type": "scenery_artwork",
|
||||
"position": {
|
||||
"x": 704.0,
|
||||
"y": -256.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "res://content/props/art_city_skyline-v1.prop",
|
||||
"display_name": "City Skyline",
|
||||
"depth": "far",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"last_known_local_bounds": {
|
||||
"position": {
|
||||
"x": -939.5,
|
||||
"y": -289.301696777344
|
||||
},
|
||||
"size": {
|
||||
"x": 1879.0,
|
||||
"y": 578.603393554688
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_id": "scenery_artwork_6",
|
||||
"type": "scenery_artwork",
|
||||
"position": {
|
||||
"x": 2304.0,
|
||||
"y": -256.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "res://content/props/art_city_skyline-v1.prop",
|
||||
"display_name": "City Skyline",
|
||||
"depth": "far",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"last_known_local_bounds": {
|
||||
"position": {
|
||||
"x": -939.5,
|
||||
"y": -289.301696777344
|
||||
},
|
||||
"size": {
|
||||
"x": 1879.0,
|
||||
"y": 578.603393554688
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_id": "background_ground_2",
|
||||
"type": "background_ground",
|
||||
"position": {
|
||||
"x": 96.0,
|
||||
"y": 352.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"size": {
|
||||
"x": 6848.0,
|
||||
"y": 832.0
|
||||
},
|
||||
"finish_id": "finish_background_grass",
|
||||
"color": "#72a84cff"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_1",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -3264.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_2",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -3072.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_3",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -2880.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_4",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -2688.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_5",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -2496.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_6",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -2304.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_7",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -2112.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_8",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -1920.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_9",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -1728.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_10",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -1536.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_11",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -1344.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_12",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -1152.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_13",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -960.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_14",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -768.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_15",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -576.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_16",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -384.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_17",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": -192.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_18",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 0.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_19",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 192.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_20",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 384.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_21",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 576.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_22",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 768.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_23",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 960.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_24",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 1152.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_25",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 1344.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_26",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 1536.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_27",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 1728.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_28",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 1920.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_29",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 2112.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_30",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 2304.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_31",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 2496.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_32",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 2688.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_33",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 2880.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_34",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 3072.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_35",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 3264.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "terrain_36",
|
||||
"type": "terrain",
|
||||
"position": {
|
||||
"x": 3456.0,
|
||||
"y": 768.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"asset_id": "ground",
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"finish_id": "finish_ground_sidewalk"
|
||||
},
|
||||
{
|
||||
"file_id": "background_ground_3",
|
||||
"type": "background_ground",
|
||||
"position": {
|
||||
"x": 160.0,
|
||||
"y": 864.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"size": {
|
||||
"x": 6912.0,
|
||||
"y": 192.0
|
||||
},
|
||||
"finish_id": "finish_background_concrete",
|
||||
"color": "#a6aaa3ff"
|
||||
},
|
||||
{
|
||||
"file_id": "background_ground_4",
|
||||
"type": "background_ground",
|
||||
"position": {
|
||||
"x": -2896.0,
|
||||
"y": 848.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"size": {
|
||||
"x": 800.0,
|
||||
"y": 160.0
|
||||
},
|
||||
"finish_id": "finish_background_concrete",
|
||||
"color": "#a6aaa3ff"
|
||||
},
|
||||
{
|
||||
"file_id": "background_ground_5",
|
||||
"type": "background_ground",
|
||||
"position": {
|
||||
"x": -3264.0,
|
||||
"y": 864.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"size": {
|
||||
"x": 320.0,
|
||||
"y": 192.0
|
||||
},
|
||||
"finish_id": "finish_background_concrete",
|
||||
"color": "#a6aaa3ff"
|
||||
},
|
||||
{
|
||||
"file_id": "stickman_1",
|
||||
"type": "stickman",
|
||||
"position": {
|
||||
"x": -2784.0,
|
||||
"y": 351.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"stk_path": "res://content/stickmen/break.stk",
|
||||
"facing_profile": "FORWARD",
|
||||
"action_queue": [
|
||||
{
|
||||
"type": "move_to",
|
||||
"target": {
|
||||
"x": 2027.27270507813,
|
||||
"y": 683.355224609375
|
||||
},
|
||||
"style": "walk"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file_id": "stickman_2",
|
||||
"type": "stickman",
|
||||
"position": {
|
||||
"x": -672.0,
|
||||
"y": 352.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"stk_path": "res://content/stickmen/break.stk",
|
||||
"facing_profile": "FORWARD",
|
||||
"action_queue": [
|
||||
{
|
||||
"type": "move_to",
|
||||
"target": {
|
||||
"x": -3161.15698242188,
|
||||
"y": 701.537109375
|
||||
},
|
||||
"style": "walk"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file_id": "stickman_3",
|
||||
"type": "stickman",
|
||||
"position": {
|
||||
"x": 3392.0,
|
||||
"y": 351.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"stk_path": "res://content/stickmen/break.stk",
|
||||
"facing_profile": "FORWARD",
|
||||
"action_queue": [
|
||||
{
|
||||
"type": "move_to",
|
||||
"target": {
|
||||
"x": -3333.0576171875,
|
||||
"y": 726.330444335938
|
||||
},
|
||||
"style": "run"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"event_rules": [],
|
||||
"metadata": {
|
||||
"created_unix": 1792022400,
|
||||
"modified_unix": 1792732500.154,
|
||||
"autosave": false,
|
||||
"source_path": ""
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ Use this guide when creating or revising child-facing dialogs, popups, and short
|
||||
## Theme and implementation
|
||||
|
||||
- Put reusable colors, sizes, spacing, radii, and border widths in `SandboxTheme` or `PropTheme` tokens. Apply local stylebox overrides instead of mutating a shared theme resource.
|
||||
- For Sandbox Stage modal windows, use `StageDialogStyler` in `scripts/shared/stage_ui/stage_dialog_styler.gd` for the shared shell, typography, and primary, secondary, or destructive button roles. Custom Control-based dialogs can reuse its `panel_style()` and `style_button()` helpers.
|
||||
- Render the shared modal surface as a Control in the stage UI CanvasLayer. Keep the native `AcceptDialog` or `ConfirmationDialog` hidden as the signal source; a full-screen native Window can replace the stage with a blank surface on some render paths.
|
||||
- Keep larger text from clipping: use containers that can expand or wrap, and size the dialog after its visible content has been laid out.
|
||||
- Preserve clear hover, pressed, selected, focused, and disabled states. Do not use color as the only indication of a state.
|
||||
- Adapt the arrangement to each workflow while keeping this shared visual language. The Choose Scenery dialog in `scripts/shared/ui/asset_selector.gd` and its `popups.asset_selector` theme tokens are the current implementation reference.
|
||||
|
||||
+18
-11
@@ -101,9 +101,9 @@
|
||||
"queue_size": [680, 520],
|
||||
"rule_size": [720, 560],
|
||||
"action_size": [560, 390],
|
||||
"confirm_size": [620, 300],
|
||||
"confirm_size": [560, 300],
|
||||
"font_size": 22,
|
||||
"message_font_size": 26
|
||||
"message_font_size": 20
|
||||
}
|
||||
},
|
||||
"popups": {
|
||||
@@ -122,24 +122,31 @@
|
||||
},
|
||||
"confirmation": {
|
||||
"scenery_erase_size": [520, 208],
|
||||
"padding": 18,
|
||||
"body_gap": 10,
|
||||
"padding": 22,
|
||||
"body_gap": 12,
|
||||
"footer_gap": 12,
|
||||
"corner_radius": 16,
|
||||
"corner_radius": 18,
|
||||
"border_width": 2,
|
||||
"title_font_size": 26,
|
||||
"title_font_size": 28,
|
||||
"message_font_size": 20,
|
||||
"button_font_size": 20,
|
||||
"button_height": 48,
|
||||
"button_height": 52,
|
||||
"button_min_width": 112,
|
||||
"card_width": 560,
|
||||
"button_radius": 12,
|
||||
"button_border_width": 2,
|
||||
"remove_button_width": 170,
|
||||
"keep_button_width": 132,
|
||||
"keep_button_width": 160,
|
||||
"surface_color": "#202633FA",
|
||||
"border_color": "#22C6FF",
|
||||
"accent_color": "#22C6FF",
|
||||
"border_color": "#667386",
|
||||
"secondary_surface": "#293242",
|
||||
"secondary_border": "#718096",
|
||||
"focus_color": "#8BD9FF",
|
||||
"backdrop_color": "#080D15B8",
|
||||
"accent_color": "#8BD9FF",
|
||||
"action_color": "#087DBA",
|
||||
"danger_color": "#FF626B",
|
||||
"danger_color": "#E66B75",
|
||||
"danger_surface": "#44262D",
|
||||
"danger_hover_surface": "#3B252C",
|
||||
"danger_pressed_surface": "#4A252D"
|
||||
},
|
||||
|
||||
@@ -47,6 +47,7 @@ const STAGE_RUN_SESSION_SCRIPT := preload("res://scripts/editors/stage/stage_run
|
||||
const STAGE_IMPACT_RESOLVER_SCRIPT := preload("res://scripts/stage/stage_impact_resolver.gd")
|
||||
const STAGE_ASSET_BROWSER_SCRIPT := preload("res://scripts/editors/stage/stage_asset_browser.gd")
|
||||
const STAGE_MODAL_VIEWS_SCRIPT := preload("res://scripts/editors/stage/stage_modal_views.gd")
|
||||
const STAGE_DIALOG_STYLER := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
|
||||
const STAGE_THEME_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/stage_theme_controller.gd")
|
||||
const STAGE_TARGET_CAPTURE_SCRIPT := preload("res://scripts/editors/stage/stage_target_capture_session.gd")
|
||||
const STAGE_SCENERY_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/stage_scenery_controller.gd")
|
||||
@@ -1543,11 +1544,28 @@ func _place_speech_bubble_at(viewport_position: Vector2) -> void:
|
||||
_speech_session.place_bubble_at(viewport_position)
|
||||
|
||||
|
||||
func _active_styled_dialog() -> Window:
|
||||
if _chrome == null:
|
||||
return null
|
||||
var ui_layer: CanvasLayer = _chrome.get_canvas_layer()
|
||||
for node: Node in ui_layer.find_children("*", "Window", true, false):
|
||||
var dialog: Window = node as Window
|
||||
if dialog != null and STAGE_DIALOG_STYLER.is_styled(dialog) and STAGE_DIALOG_STYLER.is_open(dialog):
|
||||
return dialog
|
||||
return null
|
||||
|
||||
|
||||
func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if _hosted_prop_editor != null:
|
||||
return
|
||||
if not (event is InputEventKey) or not event.pressed or event.echo:
|
||||
return
|
||||
var active_dialog: Window = _active_styled_dialog()
|
||||
if active_dialog != null:
|
||||
if event.keycode == KEY_ESCAPE:
|
||||
STAGE_DIALOG_STYLER.dismiss_on_escape(active_dialog)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if _scenery_erase_piece_dialog != null and _scenery_erase_piece_dialog.visible:
|
||||
if event.keycode == KEY_ESCAPE:
|
||||
_dismiss_scenery_erase_piece_dialog()
|
||||
@@ -1670,7 +1688,7 @@ func _space_should_pass_to_gui() -> bool:
|
||||
if focus is LineEdit or focus is TextEdit or focus is SpinBox:
|
||||
return true
|
||||
for window: Window in [_browse_dialog, _speak_dialog, _wait_dialog, _scene_open_dialog, _scene_save_dialog, _speech_save_gate_dialog, _group_preview_save_dialog, _scenery_repair_confirm_dialog, _missing_scenery_dialog, _rename_scene_dialog, _unsaved_changes_dialog, _missing_assets_dialog, _cli_error_dialog, _confirm_dialog]:
|
||||
if window != null and window.visible:
|
||||
if window != null and STAGE_DIALOG_STYLER.is_open(window):
|
||||
return true
|
||||
if _selector_open or (_queue_panel != null and _queue_panel.visible) or (_rule_panel != null and _rule_panel.visible) or (_action_editor != null and _action_editor.visible) or (_rule_editor != null and _rule_editor.visible):
|
||||
return true
|
||||
@@ -2074,7 +2092,7 @@ func _pause_toggle_blocked() -> bool:
|
||||
if focus is LineEdit or focus is TextEdit or focus is SpinBox:
|
||||
return true
|
||||
for dialog: Window in [_browse_dialog, _speak_dialog, _wait_dialog, _scene_open_dialog, _scene_save_dialog, _speech_save_gate_dialog, _group_preview_save_dialog, _scenery_repair_confirm_dialog, _missing_scenery_dialog, _rename_scene_dialog, _unsaved_changes_dialog, _missing_assets_dialog, _cli_error_dialog, _confirm_dialog]:
|
||||
if dialog != null and dialog.visible:
|
||||
if dialog != null and STAGE_DIALOG_STYLER.is_open(dialog):
|
||||
return true
|
||||
return (_queue_panel != null and _queue_panel.visible) or (_action_editor != null and _action_editor.visible) or (_rule_editor != null and _rule_editor.visible)
|
||||
|
||||
@@ -2630,8 +2648,8 @@ func _save_scene_to_path(path: String, is_autosave: bool = false) -> bool:
|
||||
if is_autosave:
|
||||
return false
|
||||
_pending_group_save_path = path
|
||||
if _group_preview_save_dialog != null and not _group_preview_save_dialog.visible:
|
||||
_group_preview_save_dialog.popup_centered()
|
||||
if _group_preview_save_dialog != null and not STAGE_DIALOG_STYLER.is_open(_group_preview_save_dialog):
|
||||
STAGE_DIALOG_STYLER.popup(_group_preview_save_dialog)
|
||||
return false
|
||||
if _speech_session != null and _speech_session.is_save_blocked():
|
||||
if is_autosave:
|
||||
@@ -2639,8 +2657,8 @@ func _save_scene_to_path(path: String, is_autosave: bool = false) -> bool:
|
||||
_speech_pending_scene_save_path = path
|
||||
_speech_pending_scene_save_as = false
|
||||
_speech_scene_save_pending = true
|
||||
if _speech_save_gate_dialog != null and not _speech_save_gate_dialog.visible:
|
||||
_speech_save_gate_dialog.popup_centered()
|
||||
if _speech_save_gate_dialog != null and not STAGE_DIALOG_STYLER.is_open(_speech_save_gate_dialog):
|
||||
STAGE_DIALOG_STYLER.popup(_speech_save_gate_dialog)
|
||||
return false
|
||||
if _rule_edit_uncommitted:
|
||||
if not is_autosave:
|
||||
@@ -2699,7 +2717,7 @@ func _reset_scene_content() -> void:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
_clear_erase_scenery_targets()
|
||||
if _scenery_repair_confirm_dialog != null:
|
||||
_scenery_repair_confirm_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_scenery_repair_confirm_dialog)
|
||||
_cancel_scenery_repair_preview()
|
||||
_scenery_repair_target = null
|
||||
_sky_id = &"sky_neutral"
|
||||
@@ -2806,36 +2824,42 @@ func _apply_scene_data(data: Dictionary, source_path: String, is_recovery: bool)
|
||||
|
||||
func _show_scene_load_warnings(warnings: Array[String]) -> void:
|
||||
var dialog: AcceptDialog = AcceptDialog.new()
|
||||
dialog.title = "Some saved actions need a look"
|
||||
dialog.title = "⚠️ Some saved actions need a look"
|
||||
dialog.ok_button_text = "Got it"
|
||||
var lines: PackedStringArray = PackedStringArray()
|
||||
for index: int in mini(warnings.size(), 12):
|
||||
for index: int in mini(warnings.size(), 5):
|
||||
lines.append("• " + warnings[index])
|
||||
if warnings.size() > 12:
|
||||
lines.append("• And %d more warning(s)." % (warnings.size() - 12))
|
||||
if warnings.size() > 5:
|
||||
lines.append("• And %d more warning(s)." % (warnings.size() - 5))
|
||||
dialog.dialog_text = "These items could not be restored:\n\n%s" % "\n".join(lines)
|
||||
add_child(dialog)
|
||||
_chrome.get_canvas_layer().add_child(dialog)
|
||||
_apply_dialog_theme(dialog)
|
||||
dialog.confirmed.connect(dialog.queue_free)
|
||||
dialog.close_requested.connect(dialog.queue_free)
|
||||
dialog.popup_centered(Vector2i(620, 420))
|
||||
STAGE_DIALOG_STYLER.popup(dialog)
|
||||
|
||||
|
||||
func _show_missing_assets_dialog(paths: Array[String]) -> void:
|
||||
if _missing_assets_dialog == null:
|
||||
return
|
||||
var asset_lines: Array[String] = []
|
||||
for path: String in paths:
|
||||
var asset_name := path.get_file()
|
||||
if asset_name.is_empty(): asset_name = "Unnamed .stk asset"
|
||||
asset_lines.append("• %s\n %s" % [asset_name, path if not path.is_empty() else "(no asset path was saved)"])
|
||||
_missing_assets_dialog.dialog_text = "%d stickman asset(s) could not be loaded:\n\n%s\n\nA red marker was placed where each missing asset belongs." % [paths.size(), "\n\n".join(asset_lines)]
|
||||
_missing_assets_dialog.popup_centered()
|
||||
for index: int in mini(paths.size(), 5):
|
||||
var asset_name: String = paths[index].get_file()
|
||||
if asset_name.is_empty():
|
||||
asset_name = "Unnamed stickman"
|
||||
asset_lines.append("• %s" % asset_name)
|
||||
if paths.size() > 5:
|
||||
asset_lines.append("• And %d more" % (paths.size() - 5))
|
||||
_missing_assets_dialog.title = "⚠️ Some scene assets are missing"
|
||||
_missing_assets_dialog.dialog_text = "Red markers show where the missing stickmen belong.\n\n%s" % "\n".join(asset_lines)
|
||||
STAGE_DIALOG_STYLER.popup(_missing_assets_dialog)
|
||||
|
||||
|
||||
func _show_missing_scenery_dialog(count: int) -> void:
|
||||
if _missing_scenery_dialog == null:
|
||||
return
|
||||
_missing_scenery_dialog.dialog_text = "%d scenery picture%s could not be found. The scene still has a marker in each spot, so nothing was lost." % [count, "" if count == 1 else "s"]
|
||||
_missing_scenery_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_missing_scenery_dialog)
|
||||
|
||||
|
||||
func _add_recent_scene(path: String) -> void:
|
||||
@@ -2884,7 +2908,12 @@ func _check_autosave_recovery(show_starter_chooser_after_discard: bool = false)
|
||||
return
|
||||
var autosave_data: Dictionary = inspection.get("data", {}) as Dictionary
|
||||
var dialog := ConfirmationDialog.new()
|
||||
dialog.name = "AutosaveRecoveryDialog"; dialog.title = "Recover Sandbox Scene?"; dialog.dialog_text = "A previous Play session was not closed cleanly. Recover it?"
|
||||
dialog.name = "AutosaveRecoveryDialog"
|
||||
dialog.title = "🔄 Scene recovery found"
|
||||
dialog.dialog_text = "A recovery copy of this scene is available. Would you like to restore it?\n\nℹ️ Restoring opens the recovered scene for review before you save it."
|
||||
dialog.ok_button_text = "Restore scene"
|
||||
dialog.cancel_button_text = "Discard recovery"
|
||||
dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
dialog.confirmed.connect(func() -> void: _apply_scene_data(autosave_data, AUTOSAVE_PATH, true); dialog.queue_free())
|
||||
dialog.canceled.connect(func() -> void:
|
||||
_document.remove_autosave()
|
||||
@@ -2892,7 +2921,9 @@ func _check_autosave_recovery(show_starter_chooser_after_discard: bool = false)
|
||||
if show_starter_chooser_after_discard:
|
||||
_queue_startup_starter_chooser()
|
||||
)
|
||||
get_node(NodePath("UI")).add_child(dialog); _apply_dialog_theme(dialog); dialog.popup_centered()
|
||||
get_node(NodePath("UI")).add_child(dialog)
|
||||
_apply_dialog_theme(dialog, dialog.get_ok_button(), [dialog.get_cancel_button()])
|
||||
STAGE_DIALOG_STYLER.popup(dialog)
|
||||
|
||||
|
||||
func _queue_startup_starter_chooser() -> void:
|
||||
@@ -2924,7 +2955,7 @@ func _startup_modal_is_visible() -> bool:
|
||||
if ui_layer != null:
|
||||
for child: Node in ui_layer.get_children():
|
||||
var window := child as Window
|
||||
if window != null and window.visible:
|
||||
if (window != null and STAGE_DIALOG_STYLER.is_open(window)) or (child is Control and child.name == "StageDialogOverlay" and (child as Control).visible):
|
||||
return true
|
||||
for dialog: Window in [
|
||||
_cli_error_dialog,
|
||||
@@ -2940,7 +2971,7 @@ func _startup_modal_is_visible() -> bool:
|
||||
_group_preview_save_dialog,
|
||||
_confirm_dialog,
|
||||
]:
|
||||
if dialog != null and is_instance_valid(dialog) and dialog.visible:
|
||||
if dialog != null and is_instance_valid(dialog) and STAGE_DIALOG_STYLER.is_open(dialog):
|
||||
return true
|
||||
return false
|
||||
|
||||
@@ -2978,11 +3009,11 @@ func _load_cli_scene(path: String) -> Dictionary:
|
||||
return {"ok": true, "error": ""}
|
||||
|
||||
|
||||
func _show_cli_error_dialog(message: String) -> void:
|
||||
func _show_cli_error_dialog(_message: String) -> void:
|
||||
if _cli_error_dialog == null:
|
||||
return
|
||||
_cli_error_dialog.dialog_text = message
|
||||
_cli_error_dialog.popup_centered()
|
||||
_cli_error_dialog.dialog_text = ""
|
||||
STAGE_DIALOG_STYLER.popup(_cli_error_dialog)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
@@ -3085,9 +3116,9 @@ func _theme_dialog_size(key: String, fallback: Vector2i) -> Vector2i:
|
||||
|
||||
## Dialogs are separate native/embedded windows, so they do not reliably
|
||||
## inherit the CanvasLayer's visual defaults. Apply only local overrides.
|
||||
func _apply_dialog_theme(dialog: Window) -> void:
|
||||
func _apply_dialog_theme(dialog: Window, primary_button: Button = null, destructive_buttons: Array = []) -> void:
|
||||
if _modal_views != null:
|
||||
_modal_views.apply_dialog_theme(dialog)
|
||||
_modal_views.apply_dialog_theme(dialog, primary_button, destructive_buttons)
|
||||
|
||||
|
||||
func _apply_dialog_font_to_children(node: Node, size: int) -> void:
|
||||
@@ -3446,7 +3477,7 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_missing_scenery_dialog.dialog_text = "This scene has artwork that could not be found. You can still explore the scene and fix it later."
|
||||
_missing_scenery_dialog.ok_button_text = "Show Scenery"
|
||||
_missing_scenery_dialog.cancel_button_text = "Later"
|
||||
_missing_scenery_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
_missing_scenery_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
_missing_scenery_dialog.confirmed.connect(_enter_scenery_edit_mode)
|
||||
ui.add_child(_missing_scenery_dialog)
|
||||
_apply_dialog_theme(_missing_scenery_dialog)
|
||||
@@ -3455,7 +3486,7 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_scenery_repair_confirm_dialog.title = "Use this artwork here?"
|
||||
_scenery_repair_confirm_dialog.ok_button_text = "Use artwork"
|
||||
_scenery_repair_confirm_dialog.cancel_button_text = "Keep for now"
|
||||
_scenery_repair_confirm_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
_scenery_repair_confirm_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
_scenery_repair_confirm_dialog.confirmed.connect(_confirm_scenery_repair)
|
||||
_scenery_repair_confirm_dialog.canceled.connect(_cancel_scenery_repair_preview)
|
||||
ui.add_child(_scenery_repair_confirm_dialog)
|
||||
@@ -3556,7 +3587,7 @@ func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
var backdrop: ColorRect = ColorRect.new()
|
||||
backdrop.name = "DialogBackdrop"
|
||||
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
backdrop.color = _confirmation_dialog_color("backdrop_color", Color("#10151D66"))
|
||||
backdrop.color = _confirmation_dialog_color("backdrop_color", Color("#080D15B8"))
|
||||
backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_scenery_erase_piece_dialog.add_child(backdrop)
|
||||
|
||||
@@ -3572,7 +3603,7 @@ func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
|
||||
var margin: MarginContainer = MarginContainer.new()
|
||||
margin.name = "DialogMargin"
|
||||
var padding: int = _confirmation_dialog_integer("padding", 18, 12, 32)
|
||||
var padding: int = _confirmation_dialog_integer("padding", 22, 12, 36)
|
||||
margin.add_theme_constant_override("margin_left", padding)
|
||||
margin.add_theme_constant_override("margin_right", padding)
|
||||
margin.add_theme_constant_override("margin_top", padding)
|
||||
@@ -3583,14 +3614,14 @@ func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
layout.name = "DialogLayout"
|
||||
layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
layout.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
layout.add_theme_constant_override("separation", _confirmation_dialog_integer("body_gap", 10, 4, 24))
|
||||
layout.add_theme_constant_override("separation", _confirmation_dialog_integer("body_gap", 12, 4, 24))
|
||||
margin.add_child(layout)
|
||||
|
||||
_scenery_erase_piece_dialog_title = Label.new()
|
||||
_scenery_erase_piece_dialog_title.name = "DialogTitle"
|
||||
_scenery_erase_piece_dialog_title.text = "Remove this scenery?"
|
||||
_scenery_erase_piece_dialog_title.text = "🧱 Remove this scenery piece?"
|
||||
_scenery_erase_piece_dialog_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_erase_piece_dialog_title.add_theme_font_size_override("font_size", _confirmation_dialog_integer("title_font_size", 26, 20, 40))
|
||||
_scenery_erase_piece_dialog_title.add_theme_font_size_override("font_size", _confirmation_dialog_integer("title_font_size", 28, 20, 40))
|
||||
_scenery_erase_piece_dialog_title.add_theme_color_override("font_color", _stage_panel_color("type.text_color", Color("#F5F7FA")))
|
||||
_apply_ui_font(_scenery_erase_piece_dialog_title)
|
||||
var title_font: Font = _ui_font
|
||||
@@ -3603,7 +3634,7 @@ func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
|
||||
_scenery_erase_piece_dialog_message = Label.new()
|
||||
_scenery_erase_piece_dialog_message.name = "DialogMessage"
|
||||
_scenery_erase_piece_dialog_message.text = "Remove this whole piece? You can’t undo this."
|
||||
_scenery_erase_piece_dialog_message.text = "This removes the selected scenery piece from the scene."
|
||||
_scenery_erase_piece_dialog_message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_scenery_erase_piece_dialog_message.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_erase_piece_dialog_message.add_theme_font_size_override("font_size", _confirmation_dialog_integer("message_font_size", 20, 16, 32))
|
||||
@@ -3623,14 +3654,14 @@ func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
|
||||
_scenery_erase_piece_keep_button = Button.new()
|
||||
_scenery_erase_piece_keep_button.name = "KeepSceneryButton"
|
||||
_scenery_erase_piece_keep_button.text = "Keep it"
|
||||
_scenery_erase_piece_keep_button.text = "Keep this piece"
|
||||
_scenery_erase_piece_keep_button.focus_mode = Control.FOCUS_ALL
|
||||
_scenery_erase_piece_keep_button.pressed.connect(_dismiss_scenery_erase_piece_dialog)
|
||||
_scenery_erase_piece_dialog_actions.add_child(_scenery_erase_piece_keep_button)
|
||||
|
||||
_scenery_erase_piece_remove_button = Button.new()
|
||||
_scenery_erase_piece_remove_button.name = "RemoveSceneryButton"
|
||||
_scenery_erase_piece_remove_button.text = "Remove scenery"
|
||||
_scenery_erase_piece_remove_button.text = "Remove piece"
|
||||
_scenery_erase_piece_remove_button.focus_mode = Control.FOCUS_ALL
|
||||
_scenery_erase_piece_remove_button.pressed.connect(_confirm_erase_scenery_from_dialog)
|
||||
_scenery_erase_piece_dialog_actions.add_child(_scenery_erase_piece_remove_button)
|
||||
@@ -3642,66 +3673,18 @@ func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
func _apply_scenery_erase_piece_dialog_style() -> void:
|
||||
if _scenery_erase_piece_dialog == null:
|
||||
return
|
||||
var shell: StyleBoxFlat = StyleBoxFlat.new()
|
||||
shell.bg_color = _confirmation_dialog_color("surface_color", Color("#202633FA"))
|
||||
shell.border_color = _confirmation_dialog_color("border_color", Color("#AAB2C066"))
|
||||
shell.set_border_width_all(_confirmation_dialog_integer("border_width", 1, 1, 4))
|
||||
shell.set_corner_radius_all(_confirmation_dialog_integer("corner_radius", 16, 10, 28))
|
||||
var shell: StyleBoxFlat = STAGE_DIALOG_STYLER.panel_style(_sandbox_theme, false)
|
||||
_scenery_erase_piece_dialog_panel.add_theme_stylebox_override("panel", shell)
|
||||
_apply_scenery_erase_piece_action_style(_scenery_erase_piece_keep_button, true)
|
||||
_apply_scenery_erase_piece_action_style(_scenery_erase_piece_remove_button, false)
|
||||
|
||||
|
||||
func _apply_scenery_erase_piece_action_style(button: Button, primary: bool) -> void:
|
||||
if button == null:
|
||||
return
|
||||
var accent: Color = _confirmation_dialog_color("accent_color", Color("#22C6FF"))
|
||||
var blue: Color = _confirmation_dialog_color("action_color", Color("#087DBA"))
|
||||
var danger: Color = _confirmation_dialog_color("danger_color", Color("#FF626B"))
|
||||
var neutral: Color = _stage_panel_color("action.surface_color", Color("#171B24"))
|
||||
var hover_neutral: Color = _confirmation_dialog_color("danger_hover_surface", Color("#3B252C"))
|
||||
var pressed_neutral: Color = _confirmation_dialog_color("danger_pressed_surface", Color("#4A252D"))
|
||||
var border_width: int = _confirmation_dialog_integer("button_border_width", 2, 1, 5)
|
||||
var radius: int = _confirmation_dialog_integer("button_radius", 12, 8, 24)
|
||||
var normal_color: Color = blue if primary else neutral
|
||||
var hover_color: Color = blue.lightened(0.12) if primary else hover_neutral
|
||||
var pressed_color: Color = blue.darkened(0.12) if primary else pressed_neutral
|
||||
var edge_color: Color = accent if primary else danger
|
||||
button.add_theme_stylebox_override("normal", _make_scenery_erase_button_style(normal_color, edge_color, border_width, radius))
|
||||
button.add_theme_stylebox_override("hover", _make_scenery_erase_button_style(hover_color, edge_color.lightened(0.12), border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("pressed", _make_scenery_erase_button_style(pressed_color, edge_color, border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("hover_pressed", _make_scenery_erase_button_style(pressed_color.lightened(0.05), edge_color, border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("focus", _make_scenery_erase_button_style(normal_color, accent, border_width + 1, radius))
|
||||
var label_color: Color = Color.WHITE if primary else danger
|
||||
button.add_theme_color_override("font_color", label_color)
|
||||
button.add_theme_color_override("font_hover_color", label_color)
|
||||
button.add_theme_color_override("font_pressed_color", label_color)
|
||||
button.add_theme_font_size_override("font_size", _confirmation_dialog_integer("button_font_size", 20, 16, 32))
|
||||
button.custom_minimum_size = Vector2(
|
||||
float(_confirmation_dialog_integer("remove_button_width", 170, 128, 240)) if not primary else float(_confirmation_dialog_integer("keep_button_width", 132, 112, 220)),
|
||||
float(_confirmation_dialog_integer("button_height", 48, 44, 80))
|
||||
STAGE_DIALOG_STYLER.style_button(_scenery_erase_piece_keep_button, &"primary", _sandbox_theme, _ui_font)
|
||||
STAGE_DIALOG_STYLER.style_button(_scenery_erase_piece_remove_button, &"danger", _sandbox_theme, _ui_font)
|
||||
_scenery_erase_piece_keep_button.custom_minimum_size = Vector2(
|
||||
float(_confirmation_dialog_integer("keep_button_width", 144, 112, 240)),
|
||||
float(_confirmation_dialog_integer("button_height", 52, 44, 80))
|
||||
)
|
||||
_scenery_erase_piece_remove_button.custom_minimum_size = Vector2(
|
||||
float(_confirmation_dialog_integer("remove_button_width", 170, 128, 240)),
|
||||
float(_confirmation_dialog_integer("button_height", 52, 44, 80))
|
||||
)
|
||||
var font: Font = _ui_font
|
||||
if primary and font != null:
|
||||
var strong_font: FontVariation = FontVariation.new()
|
||||
strong_font.base_font = font
|
||||
strong_font.variation_embolden = 0.55
|
||||
button.add_theme_font_override("font", strong_font)
|
||||
else:
|
||||
_apply_ui_font(button)
|
||||
|
||||
|
||||
func _make_scenery_erase_button_style(background: Color, border: Color, width: int, radius: int) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
style.set_border_width_all(width)
|
||||
style.set_corner_radius_all(radius)
|
||||
style.content_margin_left = 12.0
|
||||
style.content_margin_right = 12.0
|
||||
style.content_margin_top = 8.0
|
||||
style.content_margin_bottom = 8.0
|
||||
return style
|
||||
|
||||
|
||||
func _confirmation_dialog_integer(token: String, fallback: int, minimum: int, maximum: int) -> int:
|
||||
@@ -3961,53 +3944,14 @@ func _build_use_spot_overlay(ui: CanvasLayer) -> void:
|
||||
ui.add_child(_use_choice_popup)
|
||||
_spot_erase_dialog = ConfirmationDialog.new()
|
||||
_spot_erase_dialog.name = "EraseUseSpotDialog"
|
||||
_spot_erase_dialog.title = "⚠️ Erase Use spot?"
|
||||
_spot_erase_dialog.title = "📍 Erase this Use spot?"
|
||||
_spot_erase_dialog.ok_button_text = "Erase spot"
|
||||
_spot_erase_dialog.cancel_button_text = "Keep spot"
|
||||
_spot_erase_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
_spot_erase_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
_spot_erase_dialog.theme = _sandbox_theme.control_theme() if _sandbox_theme != null else null
|
||||
_spot_erase_dialog.confirmed.connect(_on_spot_erase_confirmed)
|
||||
ui.add_child(_spot_erase_dialog)
|
||||
_apply_dialog_theme(_spot_erase_dialog)
|
||||
_style_spot_erase_dialog_buttons()
|
||||
|
||||
|
||||
func _style_spot_erase_dialog_buttons() -> void:
|
||||
if _spot_erase_dialog == null:
|
||||
return
|
||||
var danger: Color = _sandbox_theme.color("overlays.use_preview.invalid_color", Color("#F06464")) if _sandbox_theme != null else Color("#F06464")
|
||||
var safe: Color = _sandbox_theme.color("mode_colors.edit", Color("#22C6FF")) if _sandbox_theme != null else Color("#22C6FF")
|
||||
_style_spot_erase_titlebar(danger.darkened(0.58))
|
||||
_style_spot_erase_action_button(_spot_erase_dialog.get_ok_button(), danger.darkened(0.55), danger)
|
||||
_style_spot_erase_action_button(_spot_erase_dialog.get_cancel_button(), safe.darkened(0.72), safe)
|
||||
|
||||
|
||||
func _style_spot_erase_titlebar(color: Color) -> void:
|
||||
for style_name: String in ["embedded_border", "embedded_unfocused_border"]:
|
||||
var original: StyleBoxFlat = _spot_erase_dialog.get_theme_stylebox(style_name) as StyleBoxFlat
|
||||
if original == null:
|
||||
continue
|
||||
var tinted: StyleBoxFlat = original.duplicate() as StyleBoxFlat
|
||||
tinted.bg_color = color
|
||||
_spot_erase_dialog.add_theme_stylebox_override(style_name, tinted)
|
||||
_spot_erase_dialog.add_theme_color_override("title_color", Color("#FFFFFF"))
|
||||
|
||||
|
||||
func _style_spot_erase_action_button(button: Button, surface: Color, accent: Color) -> void:
|
||||
if button == null:
|
||||
return
|
||||
button.custom_minimum_size = Vector2(156.0, 56.0)
|
||||
button.add_theme_color_override("font_color", Color("#FFFFFF"))
|
||||
button.add_theme_color_override("font_hover_color", Color("#FFFFFF"))
|
||||
button.add_theme_color_override("font_pressed_color", Color("#FFFFFF"))
|
||||
for state: String in ["normal", "hover", "pressed", "hover_pressed"]:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = surface.lightened(0.10) if state == "hover" or state == "hover_pressed" else surface
|
||||
style.border_color = accent.lightened(0.18) if state == "hover" or state == "pressed" or state == "hover_pressed" else accent
|
||||
style.set_border_width_all(2 if state == "hover" or state == "pressed" or state == "hover_pressed" else 1)
|
||||
style.set_corner_radius_all(10)
|
||||
style.set_content_margin_all(12.0)
|
||||
button.add_theme_stylebox_override(state, style)
|
||||
_apply_dialog_theme(_spot_erase_dialog, _spot_erase_dialog.get_cancel_button(), [_spot_erase_dialog.get_ok_button()])
|
||||
|
||||
|
||||
func _stage_panel_integer(key: String, fallback: int, minimum: int, maximum: int) -> int:
|
||||
@@ -4153,8 +4097,9 @@ func _on_spot_erase_requested(spot_id: String) -> void:
|
||||
_spot_erase_prop_id = prop.stage_object_id
|
||||
_spot_erase_id = spot_id
|
||||
var warning: String = "\nThis spot is used in your story. Those steps will need a new spot." if _use_spot_is_referenced(prop.stage_object_id, spot_id) else ""
|
||||
_spot_erase_dialog.dialog_text = "Erase %s (spot %d)?%s" % [String(spot.get("name", "Spot")), int(spot.get("number", 0)), warning]
|
||||
_spot_erase_dialog.popup_centered(_spot_erase_dialog.min_size)
|
||||
_spot_erase_dialog.dialog_text = "The character will no longer be able to use this spot.%s" % warning
|
||||
STAGE_DIALOG_STYLER.popup(_spot_erase_dialog)
|
||||
STAGE_DIALOG_STYLER.visual_button(_spot_erase_dialog.get_cancel_button()).grab_focus()
|
||||
|
||||
|
||||
func _on_spot_erase_confirmed() -> void:
|
||||
@@ -5600,14 +5545,14 @@ func _start_scenery_repair_preview(entry: Dictionary) -> void:
|
||||
_scenery_repair_candidate = candidate
|
||||
_scenery_repair_path = path
|
||||
if _scenery_repair_confirm_dialog != null:
|
||||
_scenery_repair_confirm_dialog.dialog_text = "Use %s in this spot? Only this scenery piece will change." % candidate.display_name
|
||||
_scenery_repair_confirm_dialog.dialog_text = "Use %s in the selected spot?\n\nℹ️ Only this scenery piece will change." % candidate.display_name
|
||||
call_deferred("_popup_scenery_repair_confirm_after_frame")
|
||||
|
||||
|
||||
func _popup_scenery_repair_confirm_after_frame() -> void:
|
||||
await get_tree().process_frame
|
||||
if _scenery_repair_confirm_dialog != null and _scenery_repair_candidate != null and is_instance_valid(_scenery_repair_candidate):
|
||||
_scenery_repair_confirm_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_scenery_repair_confirm_dialog)
|
||||
|
||||
|
||||
func _confirm_scenery_repair() -> void:
|
||||
@@ -5667,7 +5612,7 @@ func _exit_scenery_edit_mode() -> void:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
_clear_erase_scenery_targets()
|
||||
if _scenery_repair_confirm_dialog != null:
|
||||
_scenery_repair_confirm_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_scenery_repair_confirm_dialog)
|
||||
_cancel_scenery_repair_preview()
|
||||
_scenery_repair_target = null
|
||||
if _scenery_inspector != null:
|
||||
@@ -6147,8 +6092,8 @@ func _request_scene_save(path: String = "", force_save_as: bool = false) -> void
|
||||
_speech_pending_scene_save_path = path
|
||||
_speech_pending_scene_save_as = force_save_as
|
||||
_speech_scene_save_pending = true
|
||||
if _speech_save_gate_dialog != null and not _speech_save_gate_dialog.visible:
|
||||
_speech_save_gate_dialog.popup_centered()
|
||||
if _speech_save_gate_dialog != null and not STAGE_DIALOG_STYLER.is_open(_speech_save_gate_dialog):
|
||||
STAGE_DIALOG_STYLER.popup(_speech_save_gate_dialog)
|
||||
return
|
||||
if _rule_edit_uncommitted:
|
||||
_show_toast("Finish the rule before saving the scene.")
|
||||
@@ -6207,12 +6152,12 @@ func _cancel_deferred_speech_scene_save(reason: String) -> void:
|
||||
|
||||
func _rename_scene_prompt() -> void:
|
||||
_scene_name_edit.text = _scene_name
|
||||
_rename_scene_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_rename_scene_dialog)
|
||||
call_deferred("_focus_scene_name_edit")
|
||||
|
||||
|
||||
func _focus_scene_name_edit() -> void:
|
||||
if _scene_name_edit == null or not _scene_name_edit.visible:
|
||||
if _scene_name_edit == null or not _scene_name_edit.visible or not _scene_name_edit.is_inside_tree():
|
||||
return
|
||||
_scene_name_edit.grab_focus()
|
||||
_scene_name_edit.select_all()
|
||||
@@ -6227,14 +6172,14 @@ func _request_pending_file_action(action: PendingFileAction, path: String = "")
|
||||
return
|
||||
_pending_file_action = action; _pending_open_path = path
|
||||
if _dirty:
|
||||
_unsaved_changes_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_unsaved_changes_dialog)
|
||||
else:
|
||||
_continue_pending_file_action()
|
||||
|
||||
|
||||
func _continue_pending_file_action() -> void:
|
||||
if _unsaved_changes_dialog != null:
|
||||
_unsaved_changes_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_unsaved_changes_dialog)
|
||||
var action := _pending_file_action; var path := _pending_open_path
|
||||
_pending_file_action = PendingFileAction.NONE; _pending_open_path = ""
|
||||
match action:
|
||||
@@ -6248,12 +6193,12 @@ func _continue_pending_file_action() -> void:
|
||||
|
||||
|
||||
func _on_unsaved_save() -> void:
|
||||
_unsaved_changes_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_unsaved_changes_dialog)
|
||||
_request_scene_save()
|
||||
|
||||
|
||||
func _on_unsaved_discard() -> void:
|
||||
_unsaved_changes_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_unsaved_changes_dialog)
|
||||
_continue_pending_file_action()
|
||||
|
||||
|
||||
@@ -6361,7 +6306,7 @@ func _on_action_popup_id_pressed(id: int) -> void:
|
||||
_open_speech_composer(_context_rig, {"type": "speak", "text": "", "duration": 2.0}, false, "direct")
|
||||
ACT_WAIT:
|
||||
_wait_spin.value = 1.0
|
||||
_wait_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_wait_dialog)
|
||||
ACT_RAGDOLL:
|
||||
_context_rig.queue_action({ "type": "ragdoll" })
|
||||
ACT_RECOVER:
|
||||
@@ -6657,7 +6602,7 @@ func _handle_action_target_click(world_pos: Vector2) -> void:
|
||||
"wait":
|
||||
_rule_step = RuleStep.PARAMS
|
||||
_wait_spin.value = 1.0
|
||||
_wait_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_wait_dialog)
|
||||
"pick_up_prop":
|
||||
_begin_capture(CaptureKind.PROP, "🫴 Click the prop to pick up (Esc or RMB to cancel)", _cb_rule_action_prop_target, _cb_rule_action_capture_cancel)
|
||||
"throw_prop":
|
||||
@@ -7521,9 +7466,9 @@ func _open_speech_composer(actor: StickmanRig, initial: Dictionary, reactive_opt
|
||||
var queue: Array[Dictionary] = actor.get_queue()
|
||||
if queue_index >= 0 and queue_index < queue.size() and String(queue[queue_index].get("type", "")) == "speak":
|
||||
speech_initial = queue[queue_index].duplicate(true)
|
||||
if _speech_recovery_matches_loaded_scene() and not _speech_recovery_dialog.visible and not _speech_recovery_in_progress:
|
||||
if _speech_recovery_matches_loaded_scene() and not STAGE_DIALOG_STYLER.is_open(_speech_recovery_dialog) and not _speech_recovery_in_progress:
|
||||
_speech_pending_open = {"actor_id": actor.get_instance_id(), "initial": speech_initial, "reactive": reactive_options, "context": context}
|
||||
_speech_recovery_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_speech_recovery_dialog)
|
||||
return
|
||||
_speech_actor = actor
|
||||
_speech_session_kind = context
|
||||
@@ -7547,7 +7492,7 @@ func _update_speech_composition() -> void:
|
||||
func _on_speech_committed(action: Dictionary) -> void:
|
||||
if _speech_session_kind == "recovered_unbound":
|
||||
_speech_pending_commit_action = action.duplicate(true)
|
||||
_speech_destination_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_speech_destination_dialog)
|
||||
return
|
||||
var speech_updates_uncommitted_rule: bool = _speech_session_kind == "rule_builder" or (
|
||||
_speech_session_kind == "action_editor" and _action_editor_kind in ["rule_add", "rule_edit"]
|
||||
@@ -7595,9 +7540,9 @@ func _finish_speech_composition(clear_recovery: bool = false) -> void:
|
||||
if _speech_session != null:
|
||||
_speech_session.close(clear_recovery, preserve_draft)
|
||||
if _speech_destination_dialog != null:
|
||||
_speech_destination_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_speech_destination_dialog)
|
||||
if _speech_save_gate_dialog != null:
|
||||
_speech_save_gate_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_speech_save_gate_dialog)
|
||||
_speech_restored_unbound = false
|
||||
|
||||
|
||||
@@ -7654,7 +7599,7 @@ func _canonicalize_speech_origin(origin: Dictionary) -> Dictionary:
|
||||
|
||||
func _check_speech_recovery() -> void:
|
||||
if _speech_recovery_dialog != null and _speech_recovery_matches_loaded_scene():
|
||||
_speech_recovery_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_speech_recovery_dialog)
|
||||
|
||||
|
||||
func _speech_recovery_matches_loaded_scene() -> bool:
|
||||
@@ -7983,9 +7928,21 @@ func _restore_queue_panel() -> void:
|
||||
|
||||
func _ask_confirm(title: String, message: String, callback: Callable) -> void:
|
||||
_confirm_callback = callback
|
||||
_confirm_dialog.title = title
|
||||
var normalized_title: String = title.to_lower()
|
||||
var destructive: bool = normalized_title.begins_with("delete") or normalized_title.begins_with("clear") or normalized_title.begins_with("remove")
|
||||
_confirm_dialog.title = ("🗑️ " if destructive else "❔ ") + title
|
||||
_confirm_dialog.dialog_text = message
|
||||
_confirm_dialog.popup_centered()
|
||||
if destructive:
|
||||
_confirm_dialog.ok_button_text = "Delete" if normalized_title.begins_with("delete") else ("Remove" if normalized_title.begins_with("remove") else "Clear")
|
||||
_confirm_dialog.cancel_button_text = "Keep"
|
||||
_apply_dialog_theme(_confirm_dialog, _confirm_dialog.get_cancel_button(), [_confirm_dialog.get_ok_button()])
|
||||
else:
|
||||
_confirm_dialog.ok_button_text = "Continue"
|
||||
_confirm_dialog.cancel_button_text = "Cancel"
|
||||
_apply_dialog_theme(_confirm_dialog)
|
||||
STAGE_DIALOG_STYLER.popup(_confirm_dialog)
|
||||
if destructive:
|
||||
STAGE_DIALOG_STYLER.visual_button(_confirm_dialog.get_cancel_button()).grab_focus()
|
||||
|
||||
|
||||
func _on_confirm_confirmed() -> void:
|
||||
|
||||
@@ -4,6 +4,7 @@ extends Control
|
||||
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech/speech_payload.gd")
|
||||
const SANDBOX_THEME := preload("res://scripts/stage/sandbox_theme.gd")
|
||||
const STAGE_DIALOG_STYLER := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
|
||||
const SPEECH_BUBBLE := preload("res://scripts/speech/stickman_speech_bubble.gd")
|
||||
const FIVE_BUBBLE_HINT: String = "Long story? One at a time can be easier to follow."
|
||||
const MAX_BUBBLE_HINT: String = "You can add up to eight bubbles."
|
||||
@@ -510,7 +511,7 @@ func preview_elapsed() -> float:
|
||||
|
||||
|
||||
func contains_viewport_point(point: Vector2) -> bool:
|
||||
if _cancel_confirm_dialog != null and _cancel_confirm_dialog.visible:
|
||||
if _cancel_confirm_dialog != null and STAGE_DIALOG_STYLER.is_open(_cancel_confirm_dialog):
|
||||
return true
|
||||
if is_message_dialog_open():
|
||||
return true
|
||||
@@ -608,7 +609,7 @@ func close_without_commit() -> void:
|
||||
if _message_dialog != null:
|
||||
_message_dialog.hide()
|
||||
if _cancel_confirm_dialog != null:
|
||||
_cancel_confirm_dialog.hide()
|
||||
STAGE_DIALOG_STYLER.hide(_cancel_confirm_dialog)
|
||||
visible = false
|
||||
_replay_running = false
|
||||
_actor = null
|
||||
@@ -623,7 +624,8 @@ func set_cancel_requires_confirmation(required: bool) -> void:
|
||||
func request_cancel_speech() -> void:
|
||||
var changed: bool = _cancel_requires_confirmation or get_draft() != _initial_action
|
||||
if changed and _cancel_confirm_dialog != null:
|
||||
_cancel_confirm_dialog.popup_centered()
|
||||
STAGE_DIALOG_STYLER.popup(_cancel_confirm_dialog)
|
||||
STAGE_DIALOG_STYLER.visual_button(_cancel_confirm_dialog.get_cancel_button()).grab_focus()
|
||||
return
|
||||
cancelled.emit()
|
||||
|
||||
@@ -710,13 +712,21 @@ func _build_ui() -> void:
|
||||
_active_editor.text_changed.connect(_on_active_text_changed)
|
||||
_active_editor.caret_changed.connect(_remember_active_editor_state)
|
||||
_cancel_confirm_dialog = ConfirmationDialog.new()
|
||||
_cancel_confirm_dialog.title = "Discard speech changes?"
|
||||
_cancel_confirm_dialog.title = "🗣️ Discard speech changes?"
|
||||
_cancel_confirm_dialog.dialog_text = "Your speech edits will be lost. Do you want to discard them?"
|
||||
_cancel_confirm_dialog.get_ok_button().text = "Discard changes"
|
||||
_cancel_confirm_dialog.get_cancel_button().text = "Keep editing"
|
||||
_cancel_confirm_dialog.min_size = Vector2i(560, 300)
|
||||
_cancel_confirm_dialog.confirmed.connect(func() -> void: cancelled.emit())
|
||||
_cancel_confirm_dialog.dialog_hide_on_ok = true
|
||||
add_child(_cancel_confirm_dialog)
|
||||
STAGE_DIALOG_STYLER.apply(
|
||||
_cancel_confirm_dialog,
|
||||
_theme,
|
||||
_theme.font(),
|
||||
_cancel_confirm_dialog.get_cancel_button(),
|
||||
[_cancel_confirm_dialog.get_ok_button()]
|
||||
)
|
||||
_build_message_dialog()
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ const SPEECH_SHOW_TOOLBAR_SCRIPT := preload("res://scripts/speech/speech_show_to
|
||||
const WAYPOINT_CONTEXT_SCRIPT := preload("res://scripts/stage/waypoint_context.gd")
|
||||
const PROP_LIBRARY_SCRIPT := preload("res://scripts/props/prop_library.gd")
|
||||
const PROP_STAGE_ROLES := preload("res://scripts/props/prop_stage_roles.gd")
|
||||
const DIALOG_STYLER := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
|
||||
|
||||
signal object_selected(node: Node2D)
|
||||
|
||||
@@ -28,6 +29,7 @@ var scene_name_edit: LineEdit
|
||||
var missing_assets_dialog: AcceptDialog
|
||||
var cli_error_dialog: AcceptDialog
|
||||
var unsaved_changes_dialog: ConfirmationDialog
|
||||
var unsaved_discard_button: Button
|
||||
var rig_context_popup: PopupMenu
|
||||
var waypoint_context: PopupMenu
|
||||
var confirm_dialog: ConfirmationDialog
|
||||
@@ -51,7 +53,7 @@ func configure(sandbox_theme: SandboxTheme, ui_font: Font) -> void:
|
||||
|
||||
func build_action_dialogs(parent: Node) -> void:
|
||||
speak_dialog = AcceptDialog.new()
|
||||
speak_dialog.title = "Speak"
|
||||
speak_dialog.title = "🗣️ Speak"
|
||||
_speak_box = VBoxContainer.new()
|
||||
speak_dialog.add_child(_speak_box)
|
||||
speak_edit = LineEdit.new()
|
||||
@@ -67,7 +69,7 @@ func build_action_dialogs(parent: Node) -> void:
|
||||
apply_dialog_theme(speak_dialog)
|
||||
|
||||
wait_dialog = AcceptDialog.new()
|
||||
wait_dialog.title = "Wait"
|
||||
wait_dialog.title = "⏱️ Wait"
|
||||
wait_spin = SpinBox.new()
|
||||
wait_spin.min_value = 0.1
|
||||
wait_spin.max_value = 10.0
|
||||
@@ -97,7 +99,7 @@ func build_file_dialogs(parent: Node) -> void:
|
||||
|
||||
speech_save_gate_dialog = ConfirmationDialog.new()
|
||||
speech_save_gate_dialog.name = "FinishSpeechBeforeSaveDialog"
|
||||
speech_save_gate_dialog.title = "Finish speech before saving"
|
||||
speech_save_gate_dialog.title = "✏️ Finish speech before saving"
|
||||
speech_save_gate_dialog.dialog_text = "Choose Done speech to finish editing before the scene is saved."
|
||||
speech_save_gate_dialog.ok_button_text = "Done speech"
|
||||
speech_save_gate_dialog.get_cancel_button().text = "Keep editing"
|
||||
@@ -106,50 +108,64 @@ func build_file_dialogs(parent: Node) -> void:
|
||||
|
||||
group_preview_save_dialog = ConfirmationDialog.new()
|
||||
group_preview_save_dialog.name = "GroupPreviewSaveDialog"
|
||||
group_preview_save_dialog.title = "Finish scenery preview?"
|
||||
group_preview_save_dialog.title = "🧱 Finish scenery preview?"
|
||||
group_preview_save_dialog.dialog_text = "Keep the scenery changes before saving, or cancel this save."
|
||||
group_preview_save_dialog.ok_button_text = "Keep changes"
|
||||
group_preview_save_dialog.cancel_button_text = "Cancel save"
|
||||
group_preview_save_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
group_preview_save_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
parent.add_child(group_preview_save_dialog)
|
||||
apply_dialog_theme(group_preview_save_dialog)
|
||||
|
||||
rename_scene_dialog = ConfirmationDialog.new()
|
||||
rename_scene_dialog.name = "RenameSceneDialog"
|
||||
rename_scene_dialog.title = "Rename Scene"
|
||||
rename_scene_dialog.title = "📝 Rename Scene"
|
||||
rename_scene_dialog.ok_button_text = "Save name"
|
||||
rename_scene_dialog.cancel_button_text = "Cancel"
|
||||
rename_scene_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
rename_scene_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
var scene_name_hint: Label = Label.new()
|
||||
scene_name_hint.name = "SceneNameHint"
|
||||
scene_name_hint.text = "Choose a short name that helps you find this scene later."
|
||||
scene_name_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
scene_name_hint.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var scene_name_label: Label = Label.new()
|
||||
scene_name_label.name = "SceneNameLabel"
|
||||
scene_name_label.text = "Scene name"
|
||||
scene_name_edit = LineEdit.new()
|
||||
scene_name_edit.name = "SceneNameEdit"
|
||||
scene_name_edit.placeholder_text = "Scene name"
|
||||
scene_name_edit.custom_minimum_size = Vector2(460.0, 0.0)
|
||||
scene_name_edit.custom_minimum_size = Vector2(460.0, 48.0)
|
||||
rename_scene_dialog.add_child(scene_name_hint)
|
||||
rename_scene_dialog.add_child(scene_name_label)
|
||||
rename_scene_dialog.add_child(scene_name_edit)
|
||||
parent.add_child(rename_scene_dialog)
|
||||
apply_dialog_theme(rename_scene_dialog)
|
||||
|
||||
missing_assets_dialog = AcceptDialog.new()
|
||||
missing_assets_dialog.name = "MissingAssetsDialog"
|
||||
missing_assets_dialog.title = "Missing Scene Assets"
|
||||
missing_assets_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
missing_assets_dialog.title = "⚠️ Some scene assets are missing"
|
||||
missing_assets_dialog.ok_button_text = "Got it"
|
||||
missing_assets_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
parent.add_child(missing_assets_dialog)
|
||||
apply_dialog_theme(missing_assets_dialog)
|
||||
|
||||
cli_error_dialog = AcceptDialog.new()
|
||||
cli_error_dialog.name = "CliErrorDialog"
|
||||
cli_error_dialog.title = "Could Not Load Scene"
|
||||
cli_error_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
cli_error_dialog.title = "⚠️ Could not load scene"
|
||||
cli_error_dialog.ok_button_text = "Got it"
|
||||
cli_error_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
parent.add_child(cli_error_dialog)
|
||||
apply_dialog_theme(cli_error_dialog)
|
||||
|
||||
unsaved_changes_dialog = ConfirmationDialog.new()
|
||||
unsaved_changes_dialog.name = "UnsavedChangesDialog"
|
||||
unsaved_changes_dialog.title = "⚠️ Unsaved Changes"
|
||||
unsaved_changes_dialog.title = "💾 Unsaved Changes"
|
||||
unsaved_changes_dialog.dialog_text = "Would you like to save your scene before continuing?"
|
||||
unsaved_changes_dialog.ok_button_text = "💾 Save"
|
||||
unsaved_changes_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
unsaved_changes_dialog.add_button("🗑️ Discard", false, "discard")
|
||||
unsaved_changes_dialog.ok_button_text = "Save scene"
|
||||
unsaved_changes_dialog.cancel_button_text = "Cancel"
|
||||
unsaved_changes_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
unsaved_discard_button = unsaved_changes_dialog.add_button("Discard", false, "discard")
|
||||
parent.add_child(unsaved_changes_dialog)
|
||||
apply_dialog_theme(unsaved_changes_dialog)
|
||||
apply_dialog_theme(unsaved_changes_dialog, unsaved_changes_dialog.get_ok_button(), [unsaved_discard_button])
|
||||
|
||||
|
||||
func build_context_and_editor_views(
|
||||
@@ -169,7 +185,11 @@ func build_context_and_editor_views(
|
||||
parent.add_child(waypoint_context)
|
||||
|
||||
confirm_dialog = ConfirmationDialog.new()
|
||||
confirm_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(620, 300))
|
||||
confirm_dialog.title = "❔ Confirm this change"
|
||||
confirm_dialog.dialog_text = "This action updates the current scene. Do you want to continue?"
|
||||
confirm_dialog.ok_button_text = "Continue"
|
||||
confirm_dialog.cancel_button_text = "Cancel"
|
||||
confirm_dialog.min_size = theme_dialog_size("confirm_size", Vector2i(560, 300))
|
||||
parent.add_child(confirm_dialog)
|
||||
apply_dialog_theme(confirm_dialog)
|
||||
|
||||
@@ -196,18 +216,20 @@ func build_context_and_editor_views(
|
||||
parent.add_child(speech_show_toolbar)
|
||||
|
||||
speech_recovery_dialog = ConfirmationDialog.new()
|
||||
speech_recovery_dialog.title = "Speech draft found"
|
||||
speech_recovery_dialog.dialog_text = "Restore your unsaved speech draft?"
|
||||
speech_recovery_dialog.get_ok_button().text = "Restore"
|
||||
speech_recovery_dialog.get_cancel_button().text = "Discard"
|
||||
speech_recovery_dialog.title = "🗣️ Speech draft found"
|
||||
speech_recovery_dialog.dialog_text = "There is an unsaved speech draft. Would you like to restore it?\n\nℹ️ Restore brings back the draft so you can continue editing."
|
||||
speech_recovery_dialog.get_ok_button().text = "Restore draft"
|
||||
speech_recovery_dialog.get_cancel_button().text = "Discard draft"
|
||||
parent.add_child(speech_recovery_dialog)
|
||||
apply_dialog_theme(speech_recovery_dialog, speech_recovery_dialog.get_ok_button(), [speech_recovery_dialog.get_cancel_button()])
|
||||
|
||||
speech_destination_dialog = ConfirmationDialog.new()
|
||||
speech_destination_dialog.title = "Save recovered speech"
|
||||
speech_destination_dialog.title = "🎭 Save recovered speech"
|
||||
speech_destination_dialog.dialog_text = "Add this speech to the chosen character’s action queue?"
|
||||
speech_destination_dialog.get_ok_button().text = "Add to queue"
|
||||
speech_destination_dialog.get_cancel_button().text = "Keep editing"
|
||||
parent.add_child(speech_destination_dialog)
|
||||
apply_dialog_theme(speech_destination_dialog)
|
||||
|
||||
|
||||
func show_objects_here(
|
||||
@@ -310,35 +332,11 @@ func theme_dialog_size(key: String, fallback: Vector2i) -> Vector2i:
|
||||
return fallback
|
||||
|
||||
|
||||
## Dialogs are separate native/embedded windows, so apply local visual overrides.
|
||||
func apply_dialog_theme(dialog: Window) -> void:
|
||||
## Keep the native dialog objects as signal sources and draw their shared surface in the stage CanvasLayer.
|
||||
func apply_dialog_theme(dialog: Window, primary_button: Button = null, destructive_buttons: Array = []) -> void:
|
||||
if dialog == null:
|
||||
return
|
||||
var size: int = _sandbox_theme.integer("chrome.dialogs.font_size", 22, 12, 48) if _sandbox_theme != null else 22
|
||||
var message_size: int = _sandbox_theme.integer("chrome.dialogs.message_font_size", 26, 12, 64) if _sandbox_theme != null else 26
|
||||
dialog.add_theme_font_size_override("font_size", size)
|
||||
if _ui_font != null:
|
||||
dialog.add_theme_font_override("font", _ui_font)
|
||||
if dialog is AcceptDialog:
|
||||
var message: Label = (dialog as AcceptDialog).get_label()
|
||||
if message != null:
|
||||
message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
message.add_theme_font_size_override("font_size", message_size)
|
||||
if _ui_font != null:
|
||||
message.add_theme_font_override("font", _ui_font)
|
||||
var ok: Button = (dialog as AcceptDialog).get_ok_button()
|
||||
if ok != null:
|
||||
ok.add_theme_font_size_override("font_size", size)
|
||||
if _ui_font != null:
|
||||
ok.add_theme_font_override("font", _ui_font)
|
||||
if dialog is ConfirmationDialog:
|
||||
var cancel: Button = (dialog as ConfirmationDialog).get_cancel_button()
|
||||
if cancel != null:
|
||||
cancel.add_theme_font_size_override("font_size", size)
|
||||
if _ui_font != null:
|
||||
cancel.add_theme_font_override("font", _ui_font)
|
||||
_apply_dialog_font_to_children(dialog, size)
|
||||
DIALOG_STYLER.apply(dialog, _sandbox_theme, _ui_font, primary_button, destructive_buttons)
|
||||
|
||||
|
||||
func apply_dialog_font_to_children(node: Node, size: int) -> void:
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
class_name StageDialogStyler
|
||||
extends RefCounted
|
||||
## Renders child-facing dialogs as a shared in-stage card while preserving the
|
||||
## native AcceptDialog/ConfirmationDialog signals used by the stage workflows.
|
||||
|
||||
const THEME_DEFAULTS := preload("res://scripts/stage/sandbox_theme.gd")
|
||||
const OVERLAY_META := &"stage_dialog_overlay"
|
||||
const BODY_META := &"stage_dialog_body"
|
||||
const BODY_SCROLL_META := &"stage_dialog_body_scroll"
|
||||
const NOTE_META := &"stage_dialog_note"
|
||||
const DETAIL_META := &"stage_dialog_detail"
|
||||
const ACTIONS_META := &"stage_dialog_actions"
|
||||
const EXTRAS_META := &"stage_dialog_extra_controls"
|
||||
const CARD_META := &"stage_dialog_card"
|
||||
const TITLE_META := &"stage_dialog_title"
|
||||
const ICON_META := &"stage_dialog_icon"
|
||||
const ICON_LABEL_META := &"stage_dialog_icon_label"
|
||||
const ICON_BADGE_META := &"stage_dialog_icon_badge"
|
||||
const PRIMARY_META := &"stage_dialog_primary_button"
|
||||
const DESTRUCTIVE_META := &"stage_dialog_destructive_buttons"
|
||||
const OPEN_META := &"stage_dialog_open"
|
||||
|
||||
|
||||
static func apply(
|
||||
dialog: Window,
|
||||
stage_theme: SandboxTheme,
|
||||
ui_font: Font,
|
||||
primary_button: Button = null,
|
||||
destructive_buttons: Array = []
|
||||
) -> void:
|
||||
if dialog == null:
|
||||
return
|
||||
var active_theme: SandboxTheme = stage_theme if stage_theme != null else THEME_DEFAULTS.new()
|
||||
if not dialog.has_meta(OVERLAY_META):
|
||||
_install_dialog_surface(dialog, active_theme, ui_font)
|
||||
var ok_button: Button = (dialog as AcceptDialog).get_ok_button() if dialog is AcceptDialog else null
|
||||
var primary: Button = primary_button if primary_button != null else ok_button
|
||||
dialog.set_meta(PRIMARY_META, primary)
|
||||
dialog.set_meta(DESTRUCTIVE_META, destructive_buttons.duplicate())
|
||||
_refresh_dialog_surface(dialog, active_theme, ui_font, primary, destructive_buttons)
|
||||
|
||||
|
||||
static func popup(dialog: Window) -> void:
|
||||
if dialog == null:
|
||||
return
|
||||
var active_theme: SandboxTheme = _theme_for(dialog)
|
||||
var font: Font = dialog.get_meta(&"stage_dialog_font") as Font if dialog.has_meta(&"stage_dialog_font") else null
|
||||
var primary: Button = dialog.get_meta(PRIMARY_META, null) as Button
|
||||
var destructive_buttons: Array = dialog.get_meta(DESTRUCTIVE_META, []) as Array
|
||||
_refresh_dialog_surface(dialog, active_theme, font, primary, destructive_buttons)
|
||||
var overlay: Control = dialog.get_meta(OVERLAY_META) as Control
|
||||
if overlay == null:
|
||||
return
|
||||
dialog.hide()
|
||||
dialog.set_meta(OPEN_META, true)
|
||||
overlay.show()
|
||||
var host_viewport: Viewport = overlay.get_viewport()
|
||||
if host_viewport != null:
|
||||
var viewport_size: Vector2i = Vector2i(host_viewport.get_visible_rect().size)
|
||||
var card: PanelContainer = dialog.get_meta(CARD_META) as PanelContainer
|
||||
if card != null:
|
||||
var card_width: int = active_theme.integer("popups.confirmation.card_width", 560, 320, 720)
|
||||
card.custom_minimum_size.x = minf(float(card_width), maxf(280.0, float(viewport_size.x - 36)))
|
||||
_fit_body_scroll(dialog, viewport_size, active_theme)
|
||||
var tree: SceneTree = dialog.get_tree()
|
||||
if tree != null:
|
||||
var fit_after_final_layout := func() -> void:
|
||||
if not is_instance_valid(dialog) or not dialog.is_inside_tree() or not is_open(dialog):
|
||||
return
|
||||
var viewport_size: Vector2i = Vector2i(host_viewport.get_visible_rect().size) if host_viewport != null else Vector2i.ZERO
|
||||
_fit_body_scroll(dialog, viewport_size, active_theme)
|
||||
var fit_after_layout := func() -> void:
|
||||
if is_instance_valid(tree):
|
||||
tree.process_frame.connect(fit_after_final_layout, Object.CONNECT_ONE_SHOT)
|
||||
var defer_fit_one_frame := func() -> void:
|
||||
if is_instance_valid(tree):
|
||||
tree.process_frame.connect(fit_after_layout, Object.CONNECT_ONE_SHOT)
|
||||
tree.process_frame.connect(defer_fit_one_frame, Object.CONNECT_ONE_SHOT)
|
||||
var focus_primary: Button = dialog.get_meta(PRIMARY_META, null) as Button
|
||||
var focus_target: Button = visual_button(focus_primary)
|
||||
if focus_target != null and focus_target.visible:
|
||||
focus_target.call_deferred("grab_focus")
|
||||
|
||||
|
||||
static func is_open(dialog: Window) -> bool:
|
||||
if dialog == null or not is_instance_valid(dialog):
|
||||
return false
|
||||
if dialog.has_meta(OVERLAY_META):
|
||||
var overlay: Control = dialog.get_meta(OVERLAY_META) as Control
|
||||
return overlay != null and is_instance_valid(overlay) and overlay.visible
|
||||
return dialog.visible
|
||||
|
||||
|
||||
static func is_styled(dialog: Window) -> bool:
|
||||
return dialog != null and is_instance_valid(dialog) and dialog.has_meta(OVERLAY_META)
|
||||
|
||||
|
||||
static func hide(dialog: Window) -> void:
|
||||
if dialog == null or not is_instance_valid(dialog):
|
||||
return
|
||||
dialog.set_meta(OPEN_META, false)
|
||||
var overlay: Control = dialog.get_meta(OVERLAY_META) as Control if dialog.has_meta(OVERLAY_META) else null
|
||||
if overlay != null and is_instance_valid(overlay):
|
||||
overlay.hide()
|
||||
if dialog.visible:
|
||||
dialog.hide()
|
||||
|
||||
|
||||
static func dismiss_on_escape(dialog: Window) -> void:
|
||||
if dialog == null or not is_instance_valid(dialog):
|
||||
return
|
||||
if dialog is ConfirmationDialog:
|
||||
var cancel_button: Button = (dialog as ConfirmationDialog).get_cancel_button()
|
||||
var visible_cancel: Button = visual_button(cancel_button)
|
||||
if visible_cancel != null and visible_cancel.visible:
|
||||
visible_cancel.pressed.emit()
|
||||
return
|
||||
if dialog.has_signal("close_requested"):
|
||||
dialog.emit_signal("close_requested")
|
||||
hide(dialog)
|
||||
|
||||
|
||||
static func visual_button(original_button: Button) -> Button:
|
||||
if original_button == null:
|
||||
return null
|
||||
var visual: Variant = original_button.get_meta(&"stage_dialog_visual_button", null)
|
||||
return visual as Button if visual is Button else original_button
|
||||
|
||||
|
||||
static func panel_style(stage_theme: SandboxTheme, include_content_padding: bool = true) -> StyleBoxFlat:
|
||||
var active_theme: SandboxTheme = stage_theme if stage_theme != null else THEME_DEFAULTS.new()
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = active_theme.color("popups.confirmation.surface_color", Color("#202633FA"))
|
||||
style.border_color = active_theme.color("popups.confirmation.border_color", Color("#667386"))
|
||||
style.set_border_width_all(active_theme.integer("popups.confirmation.border_width", 2, 1, 4))
|
||||
style.set_corner_radius_all(active_theme.integer("popups.confirmation.corner_radius", 18, 10, 28))
|
||||
if include_content_padding:
|
||||
var padding: int = active_theme.integer("popups.confirmation.padding", 22, 12, 36)
|
||||
style.content_margin_left = padding
|
||||
style.content_margin_right = padding
|
||||
style.content_margin_top = padding
|
||||
style.content_margin_bottom = padding
|
||||
return style
|
||||
|
||||
|
||||
static func style_button(button: Button, role: StringName, stage_theme: SandboxTheme, ui_font: Font = null) -> void:
|
||||
if button == null:
|
||||
return
|
||||
var active_theme: SandboxTheme = stage_theme if stage_theme != null else THEME_DEFAULTS.new()
|
||||
var action: Color = active_theme.color("popups.confirmation.action_color", Color("#087DBA"))
|
||||
var accent: Color = active_theme.color("popups.confirmation.accent_color", Color("#8BD9FF"))
|
||||
var danger: Color = active_theme.color("popups.confirmation.danger_color", Color("#D94A56"))
|
||||
var secondary: Color = active_theme.color("popups.confirmation.secondary_surface", Color("#293242"))
|
||||
var secondary_border: Color = active_theme.color("popups.confirmation.secondary_border", Color("#718096"))
|
||||
var normal: Color = action
|
||||
var hover: Color = action.lightened(0.12)
|
||||
var pressed: Color = action.darkened(0.12)
|
||||
var border: Color = accent
|
||||
var label_color: Color = Color("#F5F7FA")
|
||||
match role:
|
||||
&"primary":
|
||||
normal = action
|
||||
hover = active_theme.color("stage_panels.action.primary_hover_color", action.lightened(0.12))
|
||||
pressed = active_theme.color("stage_panels.action.primary_pressed_color", action.darkened(0.12))
|
||||
border = accent
|
||||
&"danger":
|
||||
normal = active_theme.color("popups.confirmation.danger_surface", Color("#44262D"))
|
||||
hover = active_theme.color("popups.confirmation.danger_hover_surface", Color("#3B252C"))
|
||||
pressed = active_theme.color("popups.confirmation.danger_pressed_surface", Color("#4A252D"))
|
||||
border = danger
|
||||
label_color = Color("#FFE6E8")
|
||||
_:
|
||||
normal = secondary
|
||||
hover = active_theme.color("stage_panels.action.hover_surface", Color("#303A4B"))
|
||||
pressed = active_theme.color("stage_panels.action.selected_surface", Color("#12394A"))
|
||||
border = secondary_border
|
||||
var border_width: int = active_theme.integer("popups.confirmation.button_border_width", 2, 1, 5)
|
||||
var radius: int = active_theme.integer("popups.confirmation.button_radius", 12, 8, 24)
|
||||
button.add_theme_stylebox_override("normal", _button_box(normal, border, border_width, radius))
|
||||
button.add_theme_stylebox_override("hover", _button_box(hover, border.lightened(0.12), border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("pressed", _button_box(pressed, border, border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("hover_pressed", _button_box(pressed.lightened(0.05), border, border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("disabled", _button_box(secondary.darkened(0.18), secondary_border.darkened(0.35), 1, radius))
|
||||
button.add_theme_stylebox_override("focus", _button_box(normal, active_theme.color("popups.confirmation.focus_color", Color("#8BD9FF")), border_width + 1, radius))
|
||||
button.add_theme_color_override("font_color", label_color)
|
||||
button.add_theme_color_override("font_hover_color", label_color)
|
||||
button.add_theme_color_override("font_pressed_color", label_color)
|
||||
button.add_theme_color_override("font_disabled_color", Color("#AAB2C0"))
|
||||
button.add_theme_font_size_override("font_size", active_theme.integer("popups.confirmation.button_font_size", 20, 16, 32))
|
||||
var min_height: int = active_theme.integer("popups.confirmation.button_height", 52, 44, 80)
|
||||
var min_width: int = active_theme.integer("popups.confirmation.button_min_width", 112, 88, 240)
|
||||
button.custom_minimum_size = Vector2(maxf(button.custom_minimum_size.x, float(min_width)), maxf(button.custom_minimum_size.y, float(min_height)))
|
||||
if ui_font != null:
|
||||
button.add_theme_font_override("font", _strong_font(ui_font) if role == &"primary" else ui_font)
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
|
||||
|
||||
static func _install_dialog_surface(dialog: Window, stage_theme: SandboxTheme, ui_font: Font) -> void:
|
||||
dialog.set_meta(&"stage_dialog_theme", stage_theme)
|
||||
if ui_font != null:
|
||||
dialog.set_meta(&"stage_dialog_font", ui_font)
|
||||
dialog.theme = stage_theme.control_theme()
|
||||
dialog.borderless = true
|
||||
dialog.transparent = true
|
||||
dialog.transparent_bg = true
|
||||
dialog.unresizable = true
|
||||
dialog.wrap_controls = false
|
||||
dialog.add_theme_color_override("title_color", Color.TRANSPARENT)
|
||||
dialog.add_theme_font_size_override("title_font_size", 1)
|
||||
if ui_font != null:
|
||||
dialog.add_theme_font_override("font", ui_font)
|
||||
|
||||
var overlay_host: Node = _find_overlay_host(dialog)
|
||||
if overlay_host == null:
|
||||
return
|
||||
var overlay := Control.new()
|
||||
overlay.name = "StageDialogOverlay"
|
||||
overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
overlay.z_index = 100
|
||||
overlay.z_as_relative = false
|
||||
overlay.visible = false
|
||||
overlay.gui_input.connect(_on_overlay_input.bind(dialog))
|
||||
overlay_host.add_child(overlay)
|
||||
dialog.set_meta(OVERLAY_META, overlay)
|
||||
dialog.set_meta(OPEN_META, false)
|
||||
dialog.set_meta(&"stage_dialog_overlay_host", overlay_host)
|
||||
dialog.tree_exiting.connect(_on_dialog_tree_exiting.bind(dialog, overlay), CONNECT_ONE_SHOT)
|
||||
if dialog.has_signal("confirmed"):
|
||||
dialog.connect("confirmed", _on_native_dialog_closed.bind(dialog))
|
||||
if dialog.has_signal("canceled"):
|
||||
dialog.connect("canceled", _on_native_dialog_closed.bind(dialog))
|
||||
if dialog.has_signal("custom_action"):
|
||||
dialog.connect("custom_action", _on_native_dialog_custom_action.bind(dialog))
|
||||
if dialog.has_signal("close_requested"):
|
||||
dialog.connect("close_requested", _on_native_dialog_closed.bind(dialog))
|
||||
|
||||
var backdrop := ColorRect.new()
|
||||
backdrop.name = "Backdrop"
|
||||
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
backdrop.color = stage_theme.color("popups.confirmation.backdrop_color", Color("#080D15B8"))
|
||||
backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
overlay.add_child(backdrop)
|
||||
|
||||
var center := CenterContainer.new()
|
||||
center.name = "CardCenter"
|
||||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
overlay.add_child(center)
|
||||
|
||||
var card := PanelContainer.new()
|
||||
card.name = "DialogCard"
|
||||
card.custom_minimum_size = Vector2(560.0, 0.0)
|
||||
card.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
card.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
card.add_theme_stylebox_override("panel", panel_style(stage_theme))
|
||||
center.add_child(card)
|
||||
dialog.set_meta(CARD_META, card)
|
||||
|
||||
var content := VBoxContainer.new()
|
||||
content.name = "DialogContent"
|
||||
content.add_theme_constant_override("separation", stage_theme.integer("popups.confirmation.body_gap", 12, 4, 24))
|
||||
card.add_child(content)
|
||||
|
||||
var header := HBoxContainer.new()
|
||||
header.name = "DialogHeader"
|
||||
header.add_theme_constant_override("separation", 12)
|
||||
content.add_child(header)
|
||||
|
||||
var icon_badge := PanelContainer.new()
|
||||
icon_badge.name = "IconBadge"
|
||||
icon_badge.custom_minimum_size = Vector2(40.0, 40.0)
|
||||
icon_badge.add_theme_stylebox_override("panel", _icon_box(Color("#173A4B"), Color("#4DB9FF")))
|
||||
header.add_child(icon_badge)
|
||||
dialog.set_meta(ICON_BADGE_META, icon_badge)
|
||||
|
||||
var icon_label := Label.new()
|
||||
icon_label.name = "DialogIcon"
|
||||
icon_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
icon_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
icon_label.add_theme_font_size_override("font_size", 22)
|
||||
icon_badge.add_child(icon_label)
|
||||
dialog.set_meta(ICON_LABEL_META, icon_label)
|
||||
|
||||
var title_label := Label.new()
|
||||
title_label.name = "DialogTitle"
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
title_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
title_label.add_theme_font_size_override("font_size", stage_theme.integer("popups.confirmation.title_font_size", 28, 20, 40))
|
||||
if ui_font != null:
|
||||
title_label.add_theme_font_override("font", _strong_font(ui_font))
|
||||
header.add_child(title_label)
|
||||
dialog.set_meta(TITLE_META, title_label)
|
||||
|
||||
var body := VBoxContainer.new()
|
||||
body.name = "DialogBody"
|
||||
body.add_theme_constant_override("separation", 10)
|
||||
body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var body_scroll := ScrollContainer.new()
|
||||
body_scroll.name = "DialogBodyScroll"
|
||||
body_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
body_scroll.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
body_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
content.add_child(body_scroll)
|
||||
body_scroll.add_child(body)
|
||||
dialog.set_meta(BODY_META, body)
|
||||
dialog.set_meta(BODY_SCROLL_META, body_scroll)
|
||||
|
||||
var body_label := Label.new()
|
||||
body_label.name = "DialogMessage"
|
||||
body_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
body_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
body_label.add_theme_font_size_override("font_size", stage_theme.integer("popups.confirmation.message_font_size", 20, 16, 32))
|
||||
body.add_child(body_label)
|
||||
dialog.set_meta(&"stage_dialog_message", body_label)
|
||||
|
||||
var detail_box := PanelContainer.new()
|
||||
detail_box.name = "DialogDetails"
|
||||
detail_box.add_theme_stylebox_override("panel", _detail_box(stage_theme))
|
||||
var detail_label := Label.new()
|
||||
detail_label.name = "DetailsText"
|
||||
detail_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
detail_label.add_theme_font_size_override("font_size", stage_theme.integer("popups.confirmation.message_font_size", 20, 16, 32))
|
||||
detail_box.add_child(detail_label)
|
||||
body.add_child(detail_box)
|
||||
dialog.set_meta(DETAIL_META, detail_box)
|
||||
|
||||
var note := Label.new()
|
||||
note.name = "DialogNote"
|
||||
note.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
note.add_theme_color_override("font_color", Color("#B8C7D8"))
|
||||
note.add_theme_font_size_override("font_size", 17)
|
||||
body.add_child(note)
|
||||
dialog.set_meta(NOTE_META, note)
|
||||
|
||||
var action_row := HBoxContainer.new()
|
||||
action_row.name = "DialogActions"
|
||||
action_row.alignment = BoxContainer.ALIGNMENT_END
|
||||
action_row.add_theme_constant_override("separation", stage_theme.integer("popups.confirmation.footer_gap", 12, 8, 24))
|
||||
content.add_child(action_row)
|
||||
dialog.set_meta(ACTIONS_META, action_row)
|
||||
|
||||
var extras: Array[Control] = []
|
||||
var ok_button: Button = (dialog as AcceptDialog).get_ok_button() if dialog is AcceptDialog else null
|
||||
var cancel_button: Button = (dialog as ConfirmationDialog).get_cancel_button() if dialog is ConfirmationDialog else null
|
||||
var default_label: Label = (dialog as AcceptDialog).get_label() if dialog is AcceptDialog else null
|
||||
if default_label != null:
|
||||
default_label.visible = false
|
||||
if ok_button != null:
|
||||
ok_button.visible = false
|
||||
if cancel_button != null:
|
||||
cancel_button.visible = false
|
||||
for child: Node in dialog.get_children():
|
||||
if child is Control and not child is Button and child != default_label and child != ok_button and child != cancel_button:
|
||||
var control: Control = child as Control
|
||||
control.reparent(body)
|
||||
extras.append(control)
|
||||
dialog.set_meta(EXTRAS_META, extras)
|
||||
dialog.hide()
|
||||
|
||||
|
||||
static func _refresh_dialog_surface(
|
||||
dialog: Window,
|
||||
stage_theme: SandboxTheme,
|
||||
ui_font: Font,
|
||||
primary_button: Button = null,
|
||||
destructive_buttons: Array = []
|
||||
) -> void:
|
||||
if not dialog.has_meta(OVERLAY_META):
|
||||
return
|
||||
dialog.set_meta(&"stage_dialog_theme", stage_theme)
|
||||
if ui_font != null:
|
||||
dialog.set_meta(&"stage_dialog_font", ui_font)
|
||||
var ok_button: Button = (dialog as AcceptDialog).get_ok_button() if dialog is AcceptDialog else null
|
||||
var cancel_button: Button = (dialog as ConfirmationDialog).get_cancel_button() if dialog is ConfirmationDialog else null
|
||||
var original_message: String = String((dialog as AcceptDialog).dialog_text) if dialog is AcceptDialog else ""
|
||||
var body_label: Label = dialog.get_meta(&"stage_dialog_message") as Label
|
||||
var detail_box: PanelContainer = dialog.get_meta(DETAIL_META) as PanelContainer
|
||||
var detail_label: Label = detail_box.get_child(0) as Label if detail_box != null and detail_box.get_child_count() > 0 else null
|
||||
var note_label: Label = dialog.get_meta(NOTE_META) as Label
|
||||
var body_text: String = original_message
|
||||
var note_text: String = ""
|
||||
var note_marker: int = body_text.find("\n\nℹ️")
|
||||
if note_marker >= 0:
|
||||
note_text = body_text.substr(note_marker + 2)
|
||||
body_text = body_text.substr(0, note_marker)
|
||||
var detail_marker: int = body_text.find("\n\n•")
|
||||
var detail_text: String = ""
|
||||
if detail_marker >= 0:
|
||||
detail_text = body_text.substr(detail_marker + 2)
|
||||
body_text = body_text.substr(0, detail_marker)
|
||||
if body_label != null:
|
||||
body_label.text = body_text
|
||||
body_label.visible = not body_text.is_empty()
|
||||
if detail_label != null and detail_box != null:
|
||||
detail_label.text = detail_text
|
||||
detail_box.visible = not detail_text.is_empty()
|
||||
if note_label != null:
|
||||
note_label.text = note_text
|
||||
note_label.visible = not note_text.is_empty()
|
||||
var heading: Dictionary = _split_heading(dialog.title, String(dialog.get_meta(ICON_META, _icon_for_dialog(dialog))))
|
||||
var title_label: Label = dialog.get_meta(TITLE_META) as Label
|
||||
var icon_label: Label = dialog.get_meta(ICON_LABEL_META) as Label
|
||||
if title_label != null:
|
||||
title_label.text = String(heading.get("title", dialog.title))
|
||||
var font_size: int = stage_theme.integer("popups.confirmation.title_font_size", 28, 20, 40)
|
||||
title_label.add_theme_font_size_override("font_size", font_size)
|
||||
title_label.add_theme_color_override("font_color", stage_theme.color("global.text_color", Color("#F5F7FA")))
|
||||
if ui_font != null:
|
||||
title_label.add_theme_font_override("font", _strong_font(ui_font))
|
||||
if icon_label != null:
|
||||
icon_label.text = String(heading.get("icon", "❔"))
|
||||
var icon_badge: PanelContainer = dialog.get_meta(ICON_BADGE_META) as PanelContainer
|
||||
if icon_badge != null:
|
||||
var heading_title: String = String(heading.get("title", "")).to_lower()
|
||||
var badge_background: Color = Color("#173A4B")
|
||||
var badge_border: Color = Color("#4DB9FF")
|
||||
if String(heading.get("icon", "")).contains("⚠"):
|
||||
badge_background = Color("#493A13")
|
||||
badge_border = Color("#D7AE28")
|
||||
elif String(heading.get("icon", "")).contains("🗑") or "erase" in heading_title or "discard" in heading_title:
|
||||
badge_background = Color("#48262D")
|
||||
badge_border = Color("#E66B75")
|
||||
icon_badge.add_theme_stylebox_override("panel", _icon_box(badge_background, badge_border))
|
||||
var text_color: Color = stage_theme.color("global.text_color", Color("#F5F7FA"))
|
||||
if body_label != null:
|
||||
body_label.add_theme_color_override("font_color", text_color)
|
||||
if detail_label != null:
|
||||
detail_label.add_theme_color_override("font_color", text_color)
|
||||
var extras: Array = dialog.get_meta(EXTRAS_META, []) as Array
|
||||
for extra: Variant in extras:
|
||||
if extra is Control:
|
||||
_apply_control_font(extra as Control, ui_font, stage_theme.integer("popups.confirmation.message_font_size", 20, 16, 32))
|
||||
|
||||
var action_row: HBoxContainer = dialog.get_meta(ACTIONS_META) as HBoxContainer
|
||||
if action_row != null:
|
||||
for old_button: Node in action_row.get_children():
|
||||
action_row.remove_child(old_button)
|
||||
old_button.queue_free()
|
||||
var primary: Button = primary_button if primary_button != null else ok_button
|
||||
if ok_button != null:
|
||||
ok_button.visible = false
|
||||
if cancel_button != null:
|
||||
cancel_button.visible = false
|
||||
for raw_button: Variant in destructive_buttons:
|
||||
if raw_button is Button:
|
||||
(raw_button as Button).visible = false
|
||||
var ordered: Array[Button] = []
|
||||
_append_unique_button(ordered, primary)
|
||||
for raw_destructive: Variant in destructive_buttons:
|
||||
if raw_destructive is Button:
|
||||
_append_unique_button(ordered, raw_destructive as Button)
|
||||
_append_unique_button(ordered, cancel_button)
|
||||
_append_unique_button(ordered, ok_button)
|
||||
for original: Button in ordered:
|
||||
var visual: Button = Button.new()
|
||||
visual.text = original.text
|
||||
visual.name = "Action_%s" % String(original.name)
|
||||
visual.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||
var role: StringName = &"secondary"
|
||||
if original == primary:
|
||||
role = &"primary"
|
||||
elif _contains_button(destructive_buttons, original):
|
||||
role = &"danger"
|
||||
style_button(visual, role, stage_theme, ui_font)
|
||||
visual.pressed.connect(func() -> void: original.pressed.emit())
|
||||
visual.set_meta(&"stage_dialog_original_button", original)
|
||||
original.set_meta(&"stage_dialog_visual_button", visual)
|
||||
if action_row != null:
|
||||
action_row.add_child(visual)
|
||||
|
||||
|
||||
static func _on_overlay_input(event: InputEvent, dialog: Window) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_ESCAPE:
|
||||
dismiss_on_escape(dialog)
|
||||
var overlay: Control = dialog.get_meta(OVERLAY_META) as Control
|
||||
if overlay != null and overlay.get_viewport() != null:
|
||||
overlay.get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
static func _fit_body_scroll(dialog: Window, viewport_size: Vector2i, stage_theme: SandboxTheme) -> void:
|
||||
var body_scroll: ScrollContainer = dialog.get_meta(BODY_SCROLL_META) as ScrollContainer
|
||||
var body: Control = dialog.get_meta(BODY_META) as Control
|
||||
var overlay: Control = dialog.get_meta(OVERLAY_META) as Control
|
||||
var header: Control = overlay.get_node_or_null("CardCenter/DialogCard/DialogContent/DialogHeader") as Control if overlay != null else null
|
||||
var actions: Control = dialog.get_meta(ACTIONS_META) as Control
|
||||
var card: Control = dialog.get_meta(CARD_META) as Control
|
||||
if body_scroll == null or body == null or header == null or actions == null or card == null:
|
||||
return
|
||||
var card_width: float = card.custom_minimum_size.x
|
||||
var card_padding: float = float(stage_theme.integer("popups.confirmation.padding", 22, 12, 36))
|
||||
var content_gap: float = float(stage_theme.integer("popups.confirmation.body_gap", 12, 4, 24))
|
||||
var body_width: float = maxf(0.0, card_width - card_padding * 2.0)
|
||||
var preferred_body_height: float = body.get_combined_minimum_size().y
|
||||
var fixed_height: float = header.get_combined_minimum_size().y + actions.get_combined_minimum_size().y + card_padding * 2.0 + content_gap * 2.0
|
||||
var max_body_height: float = maxf(48.0, float(viewport_size.y) - 36.0 - fixed_height)
|
||||
if preferred_body_height > max_body_height:
|
||||
var vertical_bar: VScrollBar = body_scroll.get_v_scroll_bar()
|
||||
if vertical_bar != null:
|
||||
body_width = maxf(0.0, body_width - vertical_bar.get_combined_minimum_size().x)
|
||||
body.custom_minimum_size.x = body_width
|
||||
body.size.x = body_width
|
||||
preferred_body_height = body.get_combined_minimum_size().y
|
||||
body_scroll.custom_minimum_size = Vector2(0.0, minf(preferred_body_height, max_body_height))
|
||||
|
||||
|
||||
static func _find_overlay_host(dialog: Window) -> Node:
|
||||
var ancestor: Node = dialog.get_parent()
|
||||
while ancestor != null:
|
||||
if ancestor is CanvasLayer:
|
||||
return ancestor
|
||||
ancestor = ancestor.get_parent()
|
||||
var tree: SceneTree = dialog.get_tree()
|
||||
var scene: Node = tree.current_scene if tree != null else null
|
||||
if scene != null:
|
||||
var ui_layer: Node = scene.get_node_or_null("UI")
|
||||
if ui_layer is CanvasLayer:
|
||||
return ui_layer
|
||||
var existing: Node = scene.get_node_or_null("StageDialogLayer")
|
||||
if existing is CanvasLayer:
|
||||
return existing
|
||||
var layer := CanvasLayer.new()
|
||||
layer.name = "StageDialogLayer"
|
||||
layer.layer = 100
|
||||
scene.add_child(layer)
|
||||
return layer
|
||||
return null
|
||||
|
||||
|
||||
static func _on_native_dialog_closed(dialog: Window) -> void:
|
||||
hide(dialog)
|
||||
|
||||
|
||||
static func _on_native_dialog_custom_action(_action: String, dialog: Window) -> void:
|
||||
hide(dialog)
|
||||
|
||||
|
||||
static func _on_dialog_tree_exiting(dialog: Window, overlay: Control) -> void:
|
||||
if dialog != null and is_instance_valid(dialog):
|
||||
dialog.set_meta(OPEN_META, false)
|
||||
if overlay != null and is_instance_valid(overlay):
|
||||
overlay.queue_free()
|
||||
|
||||
|
||||
static func _append_unique_button(buttons: Array[Button], button: Button) -> void:
|
||||
if button != null and not buttons.has(button):
|
||||
buttons.append(button)
|
||||
|
||||
|
||||
static func _contains_button(buttons: Array, button: Button) -> bool:
|
||||
for item: Variant in buttons:
|
||||
if item is Button and item == button:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func _split_heading(raw_title: String, fallback_icon: String) -> Dictionary:
|
||||
var icon: String = fallback_icon
|
||||
var title: String = raw_title
|
||||
var space: int = raw_title.find(" ")
|
||||
if space > 0 and space < 5:
|
||||
var possible_icon: String = raw_title.substr(0, space)
|
||||
if possible_icon.length() <= 4 and not possible_icon.is_empty():
|
||||
icon = possible_icon
|
||||
title = raw_title.substr(space + 1).strip_edges()
|
||||
if icon.is_empty():
|
||||
icon = "❔"
|
||||
return {"icon": icon, "title": title}
|
||||
|
||||
|
||||
static func _icon_for_dialog(dialog: Window) -> String:
|
||||
var dialog_name: String = dialog.name.to_lower()
|
||||
if "artwork" in dialog_name or "scenery" in dialog_name:
|
||||
return "🖼️"
|
||||
if "asset" in dialog_name or "error" in dialog_name or "warning" in dialog_name:
|
||||
return "⚠️"
|
||||
if "speech" in dialog_name or "speak" in dialog_name:
|
||||
return "🗣️"
|
||||
if "rename" in dialog_name:
|
||||
return "📝"
|
||||
return "❔"
|
||||
|
||||
|
||||
static func _theme_for(dialog: Window) -> SandboxTheme:
|
||||
var raw: Variant = dialog.get_meta(&"stage_dialog_theme", null)
|
||||
return raw as SandboxTheme if raw is SandboxTheme else THEME_DEFAULTS.new()
|
||||
|
||||
|
||||
static func _apply_control_font(control: Control, ui_font: Font, size: int) -> void:
|
||||
control.add_theme_font_size_override("font_size", size)
|
||||
if ui_font != null:
|
||||
control.add_theme_font_override("font", ui_font)
|
||||
for child: Node in control.get_children():
|
||||
if child is Control:
|
||||
_apply_control_font(child as Control, ui_font, size)
|
||||
|
||||
|
||||
static func _icon_box(background: Color, border: Color) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(9)
|
||||
return style
|
||||
|
||||
|
||||
static func _detail_box(stage_theme: SandboxTheme) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color("#151C28")
|
||||
style.border_color = Color("#39465B")
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(9)
|
||||
style.content_margin_left = 12.0
|
||||
style.content_margin_right = 12.0
|
||||
style.content_margin_top = 8.0
|
||||
style.content_margin_bottom = 8.0
|
||||
return style
|
||||
|
||||
|
||||
static func _button_box(background: Color, border: Color, width: int, radius: int) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
style.set_border_width_all(width)
|
||||
style.set_corner_radius_all(radius)
|
||||
style.content_margin_left = 12.0
|
||||
style.content_margin_right = 12.0
|
||||
style.content_margin_top = 8.0
|
||||
style.content_margin_bottom = 8.0
|
||||
return style
|
||||
|
||||
|
||||
static func _strong_font(base_font: Font) -> Font:
|
||||
if base_font == null:
|
||||
return ThemeDB.fallback_font
|
||||
var strong_font := FontVariation.new()
|
||||
strong_font.base_font = base_font
|
||||
strong_font.variation_embolden = 0.55
|
||||
return strong_font
|
||||
@@ -0,0 +1 @@
|
||||
uid://6iflwn2v1lkg
|
||||
@@ -6,24 +6,31 @@ const SCHEMA_VERSION: String = "2.0"
|
||||
const DEFAULT_PATH: String = "res://sandbox_theme.json"
|
||||
const CONFIRMATION_DEFAULTS: Dictionary = {
|
||||
"scenery_erase_size": [520, 208],
|
||||
"padding": 18,
|
||||
"body_gap": 10,
|
||||
"padding": 22,
|
||||
"body_gap": 12,
|
||||
"footer_gap": 12,
|
||||
"corner_radius": 16,
|
||||
"corner_radius": 18,
|
||||
"border_width": 2,
|
||||
"title_font_size": 26,
|
||||
"title_font_size": 28,
|
||||
"message_font_size": 20,
|
||||
"button_font_size": 20,
|
||||
"button_height": 48,
|
||||
"button_height": 52,
|
||||
"button_min_width": 112,
|
||||
"card_width": 560,
|
||||
"button_radius": 12,
|
||||
"button_border_width": 2,
|
||||
"remove_button_width": 170,
|
||||
"keep_button_width": 132,
|
||||
"keep_button_width": 160,
|
||||
"surface_color": "#202633FA",
|
||||
"border_color": "#22C6FF",
|
||||
"accent_color": "#22C6FF",
|
||||
"border_color": "#667386",
|
||||
"secondary_surface": "#293242",
|
||||
"secondary_border": "#718096",
|
||||
"focus_color": "#8BD9FF",
|
||||
"backdrop_color": "#080D15B8",
|
||||
"accent_color": "#8BD9FF",
|
||||
"action_color": "#087DBA",
|
||||
"danger_color": "#FF626B",
|
||||
"danger_color": "#E66B75",
|
||||
"danger_surface": "#44262D",
|
||||
"danger_hover_surface": "#3B252C",
|
||||
"danger_pressed_surface": "#4A252D"
|
||||
}
|
||||
@@ -35,7 +42,7 @@ const DEFAULT_CONFIG: Dictionary = {
|
||||
"mode_colors": {"edit": "#22C6FF", "direct": "#FFB300", "play": "#33DD77", "guide": "#22C6FF"},
|
||||
"effects": {"impact_wobble": {"duration": 0.48, "maximum_angle_degrees": 7.0, "sideways_offset": 4.0}},
|
||||
"grid": {"snap_size": 15.0},
|
||||
"chrome": {"style_defaults": {}, "top_bar": {"height": 64}, "status_bar": {"height": 38}, "mode_switcher": {"segment_min_width": 124}, "mode_frame": {"border_width": 3}, "mode_badge": {"corner_radius": 12, "padding": 10}, "timestamp_badge": {"corner_radius": 10, "padding": 8, "bg_color": "#171B24F2"}, "tooltip": {"bg_color": "#101010E6", "border_width": 1}, "dialogs": {"queue_size":[680,520], "rule_size":[720,560], "action_size":[560,390], "confirm_size":[620,300], "font_size":22, "message_font_size":26}},
|
||||
"chrome": {"style_defaults": {}, "top_bar": {"height": 64}, "status_bar": {"height": 38}, "mode_switcher": {"segment_min_width": 124}, "mode_frame": {"border_width": 3}, "mode_badge": {"corner_radius": 12, "padding": 10}, "timestamp_badge": {"corner_radius": 10, "padding": 8, "bg_color": "#171B24F2"}, "tooltip": {"bg_color": "#101010E6", "border_width": 1}, "dialogs": {"queue_size":[680,520], "rule_size":[720,560], "action_size":[560,390], "confirm_size":[560,300], "font_size":22, "message_font_size":20}},
|
||||
"popups": {"style_defaults": {"bg_color": "#202633FA", "border_color": "#FFFFFF33", "corner_radius": 12, "padding": 12}, "action_menu": {"font_size": 24}, "dialog": {"surface_color_size":[560,640], "surface_color_preview_size":[420,140], "surface_color_picker_min_size":[320,340], "action_min_target_size":48}, "asset_selector": {"backdrop_alpha": 0.55, "cell_size":[190,220], "thumbnail_size":[156,156], "surface_preview_size":[420,132], "look_cue_size":[80,42], "setup_gap":8, "setup_title_size":30, "setup_heading_size":26, "setup_choice_font_size":22, "setup_choice_height":68, "setup_choice_corner_radius":22, "setup_choice_border_width":2, "tab_font_size":22, "tab_height":60, "filter_chip_font_size":20, "filter_chip_height":48, "filter_chip_gap":8, "setup_button_font_size":20, "button_border_width":2, "footer_gap":16, "surface_corner_radius":14, "look_corner_radius":8, "corner_radius":16, "compact_swatch_size":42, "color_group_gap":8, "action_color":"#087DBA", "separator_color":"#AAB2C0A0"}},
|
||||
"palette": {"style_defaults": {}, "dock_width": 116, "card_size": [96, 94], "icon_size": 30, "drag_threshold": 8, "selected_border_width": 3},
|
||||
"stage_panels": {
|
||||
@@ -136,7 +143,9 @@ func _apply_phase19_defaults() -> void:
|
||||
|
||||
func _apply_confirmation_defaults() -> void:
|
||||
var popups: Dictionary = _config.get("popups", {}).duplicate(true)
|
||||
popups["confirmation"] = CONFIRMATION_DEFAULTS.duplicate(true)
|
||||
var configured: Variant = popups.get("confirmation", {})
|
||||
var overrides: Dictionary = configured as Dictionary if configured is Dictionary else {}
|
||||
popups["confirmation"] = _deep_merge(CONFIRMATION_DEFAULTS, overrides)
|
||||
_config["popups"] = popups
|
||||
|
||||
func raw_config() -> Dictionary:
|
||||
|
||||
Reference in New Issue
Block a user