#!/usr/bin/env python3 """Drag and Drop (Grid) Demo - Drag entities between grid cells Interactive controls: Left click + drag: Move entity to new cell ESC: Return to menu This demonstrates: - Grid entity dragging with on_click and on_cell_enter - ColorLayer for cell highlighting - Collision detection (can't drop on occupied cells) - Visual feedback during drag """ import mcrfpy import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Item data for sprites ITEMS = [ (103, "Shortsword"), # +1 atk (104, "Longsword"), # +2 atk (117, "Hammer"), # +2 atk (119, "Axe"), # +3 atk (101, "Buckler"), # +1 def (102, "Shield"), # +2 def (115, "Health Pot"), (116, "Mana Pot"), (129, "Wand"), # +1 atk, +4 int (114, "Str Potion"), ] class GridDragDropDemo: """Demo showing entity drag and drop on a grid.""" def __init__(self): self.scene = mcrfpy.Scene("demo_drag_drop_grid") self.ui = self.scene.children self.grid = None self.tile_layer = None self.color_layer = None self.dragging_entity = None self.drag_start_cell = None self.occupied_cells = set() # Track which cells have entities self.setup() def setup(self): """Build the demo UI.""" # Background bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=(20, 25, 30)) self.ui.append(bg) # Title title = mcrfpy.Caption( text="Grid Drag & Drop", pos=(512, 30), font_size=28, fill_color=(255, 255, 255) ) title.outline = 2 title.outline_color = (0, 0, 0) self.ui.append(title) # Status caption self.status = mcrfpy.Caption( text="Click and drag items to rearrange", pos=(512, 70), font_size=16, fill_color=(180, 180, 180) ) self.ui.append(self.status) # Create grid - zoom in constructor for proper centering grid_size = (10, 8) cell_size = 48 grid_pixel_size = (grid_size[0] * cell_size, grid_size[1] * cell_size) grid_pos = ((1024 - grid_pixel_size[0]) // 2, 150) self.grid = mcrfpy.Grid( pos=grid_pos, size=grid_pixel_size, grid_size=grid_size, texture=mcrfpy.default_texture, zoom=3.0 # Each cell is 16px * 3 = 48px ) # Get tile layer and fill with floor tiles self.tile_layer = self.grid.layers[0] self.tile_layer.fill(46) # Floor tile # Add color layer for highlighting (above tiles, below entities) self.color_layer = self.grid.add_layer('color', z_index=-1) # Add event handlers self.grid.on_click = self._on_grid_click self.grid.on_cell_enter = self._on_cell_enter self.ui.append(self.grid) # Add some entities to the grid self._populate_grid() # Instructions instr = mcrfpy.Caption( text="Click to pick up, drag to move, release to drop | Red = occupied | ESC to exit", pos=(512, 700), font_size=14, fill_color=(150, 150, 150) ) self.ui.append(instr) def _populate_grid(self): """Add entities to the grid in a scattered pattern.""" # Place items at various positions positions = [ (1, 1), (3, 1), (5, 2), (7, 1), (2, 4), (4, 3), (6, 5), (8, 4), (1, 6), (5, 6) ] for i, (x, y) in enumerate(positions): if i >= len(ITEMS): break sprite_idx, name = ITEMS[i] entity = mcrfpy.Entity() self.grid.entities.append(entity) entity.grid_pos = (x, y) # Use grid_pos for tile coordinates entity.sprite_index = sprite_idx self.occupied_cells.add((x, y)) def _get_entity_at(self, x, y): """Get entity at grid position, or None.""" for entity in self.grid.entities: gp = entity.grid_pos ex, ey = int(gp[0]), int(gp[1]) if ex == x and ey == y: return entity return None def _on_grid_click(self, pos, button, action): """Handle grid click for drag start/end.""" if button != "left": return # Convert screen pos to grid cell grid_x = int((pos[0] - self.grid.x) / (16 * self.grid.zoom)) grid_y = int((pos[1] - self.grid.y) / (16 * self.grid.zoom)) # Bounds check grid_w, grid_h = self.grid.grid_size if not (0 <= grid_x < grid_w and 0 <= grid_y < grid_h): return if action == "start": # Start drag if there's an entity here entity = self._get_entity_at(grid_x, grid_y) if entity: self.dragging_entity = entity self.drag_start_cell = (grid_x, grid_y) self.status.text = f"Dragging from ({grid_x}, {grid_y})" self.status.fill_color = (100, 200, 255) # Highlight start cell yellow self.color_layer.set((grid_x, grid_y), (255, 255, 100, 200)) elif action == "end": if self.dragging_entity: # Drop the entity target_cell = (grid_x, grid_y) if target_cell == self.drag_start_cell: # Dropped in same cell - no change self.status.text = "Cancelled - same cell" elif target_cell in self.occupied_cells: # Can't drop on occupied cell self.status.text = f"Can't drop on occupied cell ({grid_x}, {grid_y})" self.status.fill_color = (255, 100, 100) else: # Valid drop - move entity self.occupied_cells.discard(self.drag_start_cell) self.occupied_cells.add(target_cell) self.dragging_entity.grid_pos = target_cell self.status.text = f"Moved to ({grid_x}, {grid_y})" self.status.fill_color = (100, 255, 100) # Clear all highlights self._clear_highlights() self.dragging_entity = None self.drag_start_cell = None def _on_cell_enter(self, cell_pos): """Handle cell hover during drag.""" if not self.dragging_entity: return x, y = int(cell_pos[0]), int(cell_pos[1]) # Clear previous highlights (except start cell) self._clear_highlights() # Re-highlight start cell if self.drag_start_cell: self.color_layer.set(self.drag_start_cell, (255, 255, 100, 200)) # Highlight current cell if (x, y) != self.drag_start_cell: if (x, y) in self.occupied_cells: self.color_layer.set((x, y), (255, 100, 100, 200)) # Red - can't drop else: self.color_layer.set((x, y), (100, 255, 100, 200)) # Green - can drop # Move entity preview self.dragging_entity.grid_pos = (x, y) def _clear_highlights(self): """Clear all cell color highlights.""" grid_w, grid_h = self.grid.grid_size for y in range(grid_h): for x in range(grid_w): self.color_layer.set((x, y), (0, 0, 0, 0)) def on_key(self, key, state): """Handle keyboard input.""" if state != "start": return if key == "Escape": # Cancel any drag in progress if self.dragging_entity and self.drag_start_cell: self.dragging_entity.grid_pos = self.drag_start_cell self._clear_highlights() self.dragging_entity = None self.drag_start_cell = None self.status.text = "Drag cancelled" return # Return to cookbook menu or exit try: from cookbook_main import main main() except: sys.exit(0) def activate(self): """Activate the demo scene.""" self.scene.on_key = self.on_key mcrfpy.current_scene = self.scene def main(): """Run the demo.""" demo = GridDragDropDemo() demo.activate() # Headless screenshot try: if mcrfpy.headless_mode(): from mcrfpy import automation mcrfpy.Timer("screenshot", lambda rt: ( automation.screenshot("screenshots/primitives/drag_drop_grid.png"), sys.exit(0) ), 100) except AttributeError: pass if __name__ == "__main__": main()