@tool class_name RoomLayoutItem extends PanelContainer signal selected( item_id: StringName ) signal moved( item_id: StringName, old_position: Vector2, new_position: Vector2 ) signal moving( item_id: StringName, new_position: Vector2 ) var item_id: StringName = &"" var is_dragging: bool = false var drag_offset: Vector2 = Vector2.ZERO var drag_start_position: Vector2 = Vector2.ZERO var base_title: String = "" var base_subtitle: String = "" var is_selected: bool = false var is_active: bool = false var is_cabinet: bool = false var is_device: bool = false var editor_texture: Texture2D = null var editor_texture_scale: float = 1.0 var footprint_size_pixels: Vector2 = Vector2( 80.0, 75.0 ) @onready var title_label: Label = ( $Margin/VBox/TitleLabel ) @onready var subtitle_label: Label = ( $Margin/VBox/SubtitleLabel ) func get_physical_center_offset() -> Vector2: if ( is_cabinet or is_device ): return ( footprint_size_pixels * 0.5 ) return ( size * 0.5 ) func configure( new_item_id: StringName, title: String, subtitle: String ) -> void: item_id = new_item_id base_title = title base_subtitle = subtitle title_label.text = title subtitle_label.text = subtitle func configure_editor_texture( texture: Texture2D, texture_scale: float = 1.0 ) -> void: editor_texture = texture editor_texture_scale = clampf( texture_scale, 0.1, 1.0 ) queue_redraw() func _draw_editor_texture( physical_rect: Rect2 ) -> void: if editor_texture == null: return var texture_size := ( editor_texture.get_size() ) if ( texture_size.x <= 0.0 or texture_size.y <= 0.0 ): return var available_size := ( physical_rect.size ) if ( available_size.x <= 0.0 or available_size.y <= 0.0 ): return # # Fit the image inside the physical footprint # while preserving its original aspect ratio. # var fit_scale := minf( available_size.x / texture_size.x, available_size.y / texture_size.y ) fit_scale *= ( editor_texture_scale ) var draw_size := ( texture_size * fit_scale ) var draw_position := ( physical_rect.position + ( physical_rect.size - draw_size ) * 0.5 ) var texture_rect := Rect2( draw_position, draw_size ) draw_texture_rect( editor_texture, texture_rect, false ) func _draw() -> void: if is_cabinet: var rect := Rect2( Vector2.ZERO, footprint_size_pixels ) # # Cabinet footprint background. # draw_rect( rect, Color( 0.25, 0.55, 0.95, 0.18 ), true ) # # Optional cabinet artwork. # _draw_editor_texture( rect ) # # Cabinet footprint outline. # draw_rect( rect, Color( 0.45, 0.75, 1.0, 1.0 ), false, 3.0 ) # # Front/player-facing edge. # var front_y := ( footprint_size_pixels.y ) draw_line( Vector2( 0.0, front_y ), Vector2( footprint_size_pixels.x, front_y ), Color( 1.0, 0.85, 0.2, 1.0 ), 6.0 ) # # Center marker on front edge. # var center_x := ( footprint_size_pixels.x * 0.5 ) draw_line( Vector2( center_x, front_y ), Vector2( center_x, front_y - 14.0 ), Color( 1.0, 0.85, 0.2, 1.0 ), 4.0 ) return if is_device: var rect := Rect2( Vector2.ZERO, footprint_size_pixels ) var fill_color := Color( 0.25, 0.75, 0.45, 0.18 ) var outline_color := Color( 0.4, 0.9, 0.6, 1.0 ) if is_active: fill_color = Color( 1.0, 0.8, 0.2, 0.18 ) outline_color = Color( 1.0, 0.85, 0.25, 1.0 ) # # Physical footprint background. # draw_rect( rect, fill_color, true ) # # Optional device artwork. # _draw_editor_texture( rect ) # # Physical footprint outline. # draw_rect( rect, outline_color, false, 3.0 ) func set_selected( value: bool ) -> void: is_selected = value if is_selected: title_label.text = ( "▶ " + base_title ) else: title_label.text = base_title func set_active( active: bool ) -> void: is_active = active if active: subtitle_label.text = ( "● ACTIVE" ) else: subtitle_label.text = ( base_subtitle ) queue_redraw() func _gui_input( event: InputEvent ) -> void: if event is InputEventMouseButton: if ( event.button_index == MOUSE_BUTTON_LEFT ): if event.pressed: selected.emit( item_id ) is_dragging = true drag_start_position = ( position ) drag_offset = ( get_local_mouse_position() ) else: if is_dragging: is_dragging = false moved.emit( item_id, drag_start_position, position ) elif event is InputEventMouseMotion: if is_dragging: position += ( event.relative ) moving.emit( item_id, position ) func configure_cabinet_footprint( width_pixels: float, depth_pixels: float ) -> void: is_cabinet = true footprint_size_pixels = Vector2( maxf(width_pixels, 10.0), maxf(depth_pixels, 10.0) ) custom_minimum_size = ( footprint_size_pixels ) size = ( footprint_size_pixels ) pivot_offset = ( get_physical_center_offset() ) queue_redraw() func configure_device_footprint( width_pixels: float, height_pixels: float ) -> void: is_device = true is_cabinet = false footprint_size_pixels = Vector2( maxf( width_pixels, 6.0 ), maxf( height_pixels, 6.0 ) ) custom_minimum_size = ( footprint_size_pixels ) size = ( footprint_size_pixels ) pivot_offset = ( get_physical_center_offset() ) print( "[DEVICE FOOTPRINT] ", item_id, " pixels=", footprint_size_pixels, " size=", size ) queue_redraw()