import json from flask import render_template, request, abort, g import auth import db import screens def render_scene_edit(slug, screen_id, game): """Vue de l'éditeur de scène 2D — appelée par routes/screens/ screen_edit.py (le dispatcher, selon screen["kind"]), PAS une route Flask elle-même : même URL/endpoint "screen_edit" pour les deux types d'écran. Réutilise TEL QUEL tout ce qui est déjà générique côté logique de flow/animation/variables/événements (voir screens/screens_repo/ ensure_schema.py — le type d'écran est une propriété PAR ÉCRAN) — seuls les objets de scène et la palette de nœuds proposée diffèrent de l'éditeur document.""" screen = screens.get_screen(slug, screen_id) if not screen: abort(404) objects = screens.list_scene_objects(slug, screen_id) for o in objects: o["rendered_html"] = screens.render_scene_object(o) # "Monde" (voir screens.resolve_scene_world_size, réutilisé aussi par # la miniature de "Mes écrans", routes/games/game_dashboard.py) : un # "fond" posé à sa taille RÉELLE (souvent plus grande que la scène, # voir add_scene_object.py) rend visible/joignable, dans CET éditeur, # tout ce qui dépasse la scène nominale plutôt que de le rogner (le # glisser-déposer le permet déjà depuis le fix précédent — sans # agrandir le canevas ici, cette partie restait invisible/injoignable # à la souris). Le rectangle "🎥 Champ de la caméra" (voir # templates/scene_edit.html) marque ensuite où s'arrête la scène # nominale À L'INTÉRIEUR de ce monde plus grand. world_width, world_height = screens.resolve_scene_world_size(screen, objects) selected_id = request.args.get("selected", type=int) personnage_data = None personnage_commands = None personnage_role = None collision_settings = None dialogue_box_style = None selected_obj = next((o for o in objects if o["id"] == selected_id), None) if selected_id else None _UI_WIDGET_KINDS = ("dialogue_box", "quiz_box", "score_widget") if selected_obj: if selected_obj["kind"] not in _UI_WIDGET_KINDS: collision_settings = screens.resolve_collision_settings(selected_obj) if selected_obj["kind"] == "personnage": personnage_data = screens.resolve_personnage_data(selected_obj) personnage_commands = screens.resolve_personnage_commands(selected_obj) personnage_role = screens.resolve_personnage_role(selected_obj) elif selected_obj["kind"] in _UI_WIDGET_KINDS: dialogue_box_style = screens.resolve_dialogue_box_style(selected_obj) _DEFAULT_OBJECT_LABELS = {"personnage": "Personnage", "fond": "Fond"} object_labels = { o["id"]: (o.get("name") or _DEFAULT_OBJECT_LABELS.get(o["kind"], "Décor")) for o in objects } element_animations_map = { o["id"]: screens.resolve_personnage_animations(o) for o in objects if o["kind"] == "personnage" } flow_blocks = screens.list_flow_blocks(slug, screen_id) flow_nodes = screens.list_flow_nodes(slug, screen_id) flow_edges = screens.list_flow_edges(slug, screen_id) animation_clips = screens.list_animation_clips(slug, screen_id) global_variables = db.list_global_variables(slug) custom_events = db.list_custom_events(slug) custom_event_usages = screens.list_custom_event_usages(slug) custom_events_map = {e["id"]: {"name": e["name"]} for e in custom_events} all_screens = screens.list_screens(slug) screens_map = {s["id"]: s["name"] for s in all_screens} blocks_view = screens.blocks_view(flow_blocks, flow_nodes, object_labels) # Sprites animaux CraftPix (screens.ADMIN_SPRITE_LIBRARY) : réservés au # rôle admin (licence CraftPix, voir core/sprite_gate.py et le plan # "Bibliothèque de sprites animaux CraftPix"). sprite_library = screens.SPRITE_LIBRARY if g.current_user["role"] == "admin" else screens.PUBLIC_SPRITE_LIBRARY sprite_library_families = screens.sprite_gallery_families(sprite_library) # Images de fond CraftPix (screens.BACKGROUND_LIBRARY) : même règle # d'accès que les sprites animaux — réservées au rôle admin. background_library = screens.BACKGROUND_LIBRARY if g.current_user["role"] == "admin" else {} # "Mes assets" (voir auth/user_assets_dir.py) : par COMPTE, jamais # filtré par rôle contrairement à sprite_library/background_library # ci-dessus (rien à voir avec le catalogue Forge réservé à l'admin). user_assets = auth.list_user_assets(g.current_user["id"]) return render_template( "scene_edit.html", game=game, screen=screen, all_screens=all_screens, world_width=world_width, world_height=world_height, objects=objects, selected_id=selected_id, personnage_data=personnage_data, personnage_commands=personnage_commands or screens.DEFAULT_PERSONNAGE_COMMANDS, personnage_role=personnage_role or screens.DEFAULT_PERSONNAGE_ROLE, personnage_role_choices=screens.PERSONNAGE_ROLES, personnage_role_labels=screens.PERSONNAGE_ROLE_LABELS, collision_settings=collision_settings or screens.DEFAULT_COLLISION_SETTINGS, dialogue_box_style=dialogue_box_style or screens.DEFAULT_DIALOGUE_BOX_STYLE, dialogue_box_font_weight_choices=screens.FONT_WEIGHT_CHOICES, sprite_library=sprite_library, sprite_library_families=sprite_library_families, sprite_library_json=json.dumps(sprite_library), background_library=background_library, user_assets=user_assets, element_animations_map_json=json.dumps(element_animations_map), element_orientation_values=screens.ELEMENT_ORIENTATION_VALUES, element_orientation_labels_json=json.dumps(screens.ELEMENT_ORIENTATION_LABELS), element_visibility_values=screens.ELEMENT_VISIBILITY_VALUES, element_visibility_labels_json=json.dumps(screens.ELEMENT_VISIBILITY_LABELS), trigger_events=screens.TRIGGER_EVENTS_2D, action_type_labels=screens.ACTION_TYPE_LABELS_2D, object_action_properties=screens.OBJECT_ACTION_PROPERTIES, element_action_property_labels_json=json.dumps(dict(screens.OBJECT_ACTION_PROPERTIES)), condition_operators=screens.CONDITION_OPERATORS, data_operations=screens.DATA_OPERATIONS, data_operation_labels=screens.DATA_OPERATION_LABELS, score_status_choices=db.SCORE_STATUS_CHOICES, score_status_labels_json=json.dumps(db.SCORE_STATUS_LABELS), data_operation_labels_json=json.dumps(screens.DATA_OPERATION_LABELS), condition_operator_labels_json=json.dumps(screens.CONDITION_OPERATOR_LABELS), action_type_labels_json=json.dumps(screens.ACTION_TYPE_LABELS_2D), elements_labels_json=json.dumps(object_labels), screens_map_json=json.dumps(screens_map), flow_nodes=flow_nodes, flow_edges=flow_edges, flow_nodes_json=json.dumps(flow_nodes), flow_edges_json=json.dumps(flow_edges), flow_blocks_json=json.dumps(flow_blocks), blocks_view=blocks_view, animation_clips=animation_clips, animation_clips_json=json.dumps(animation_clips), animate_css_catalog=screens.ANIMATE_CSS_CATALOG, animate_css_labels_json=json.dumps(screens.ANIMATE_CSS_LABELS), global_variables=global_variables, global_variable_types=db.GLOBAL_VARIABLE_TYPES, custom_events=custom_events, custom_event_usages=custom_event_usages, custom_events_map_json=json.dumps(custom_events_map), definitions=db.list_definitions(slug), definitions_json=json.dumps({str(d["id"]): screens.data_definition_options(slug, d["id"]) for d in db.list_definitions(slug)}), definitions_names_json=json.dumps({d["id"]: d["name"] for d in db.list_definitions(slug)}), # "qui parle" dans un dialogue de déclencheur (voir "ℹ️ # Informations", static/js/triggers/trigger-editor.js) : les noms # donnés à N'IMPORTE QUEL objet de scène (personnage, décor, # fond), toutes scènes du jeu confondues. scene_object_names_json=json.dumps(screens.list_named_scene_object_names(slug)), )