extends Node2D class_name GridInput @onready var grid: Grid = $/root/Root/Grid var highlight_color: Color = Color(0.075, 0.536, 0.75, 0.38) var build_color: Color = Color(0.087, 0.62, 0.087, 0.514) var deletion_color: Color = Color(0.76, 0.237, 0.106, 0.4) var current_color = highlight_color var cursor_pool: CursorPool func _ready(): cursor_pool = Util.find_child_type(self, CursorPool) enum AreaType { Rectangle, Line, None, } var current_marquee_type: AreaType = AreaType.Rectangle # === RAW MOUSE EVENTS === # first we process inputs raw and separate them out into something digestable # This uses some state data! var _left_dragging = false var _left_mouse_down = false var _left_mouse_down_start_pos: Vector2 = Vector2.ZERO func __is_mouse_pos_significant() -> bool: return (get_local_mouse_position() - _left_mouse_down_start_pos).length_squared() > 50 func _input(event: InputEvent): if event is InputEventMouseMotion: if _left_mouse_down: if not _left_dragging and __is_mouse_pos_significant(): _left_dragging = true _begin_drag(get_local_mouse_position()) elif _left_dragging: _update_drag(get_local_mouse_position()) else: _update_hover(get_local_mouse_position()) elif event is InputEventMouseButton: if event.button_index == 1: _left_mouse_down = event.pressed if event.pressed: _left_mouse_down_start_pos = get_local_mouse_position() else: if _left_dragging: _left_dragging = false _end_drag(get_local_mouse_position()) else: _left_click(get_local_mouse_position()) elif event.button_index == 2 && event.pressed: _right_click(get_local_mouse_position()) # for now, lets pretend these are simple :) func _left_click(pos: Vector2): if !grid.contains_world_pos(pos): return var tile_pos = grid.world2tile(pos) _grid_clicked(tile_pos) func _right_click(pos: Vector2): _cancel_operation() cursor_pool.explode() if !mouse_in_grid: return cursor_pool.new_cursor(current_color) _update_cursor() var mouse_in_grid: bool = false # TODO init this correctly lol func __mouse_may_have_left_or_entered_grid(pos: Vector2): var new_mouse_in_grid = grid.contains_world_pos(pos) var mouse_entered_grid = new_mouse_in_grid && !mouse_in_grid var mouse_left_grid = !new_mouse_in_grid && mouse_in_grid mouse_in_grid = new_mouse_in_grid if mouse_entered_grid: _mouse_enter_grid() elif mouse_left_grid: _mouse_leave_grid() func _update_hover(pos: Vector2): __mouse_may_have_left_or_entered_grid(pos) if !mouse_in_grid: return _mouse_hover_grid(pos) # while dragging will attempt to begin marquee selection var in_marquee: bool = false var marquee_start_tile: Vector2i = Vector2i.ZERO func __get_current_marquee_area(pos: Vector2i): var marquee_end_tile = grid.world2tile(pos) match current_marquee_type: AreaType.Rectangle: return Rect2i(marquee_start_tile, Vector2i.ONE).merge(Rect2i(marquee_end_tile, Vector2i.ONE)) AreaType.Line: var vertical = Rect2i(marquee_start_tile, Vector2i.ONE).merge(Rect2i(Vector2i(marquee_start_tile.x, marquee_end_tile.y), Vector2i.ONE)) var horizontal = Rect2i(marquee_start_tile, Vector2i.ONE).merge(Rect2i(Vector2i(marquee_end_tile.x, marquee_start_tile.y), Vector2i.ONE)) return vertical if vertical.get_area() > horizontal.get_area() else horizontal func _begin_drag(pos: Vector2): __mouse_may_have_left_or_entered_grid(pos) if !mouse_in_grid: return in_marquee = true marquee_start_tile = grid.world2tile(pos) _begin_marquee(marquee_start_tile) func _update_drag(pos: Vector2): __mouse_may_have_left_or_entered_grid(pos) if !mouse_in_grid: return if !in_marquee: return var new_area = __get_current_marquee_area(pos) _update_marquee(new_area) func _end_drag(pos: Vector2): if !in_marquee: cursor_pool.new_cursor(current_color) _update_cursor() return var final_area = __get_current_marquee_area(pos) if !mouse_in_grid: _cancel_marquee() else: _end_marquee(final_area) in_marquee = false # === PROCESSED GAME INTERACTION EVENTS === func _mouse_enter_grid(): if !in_marquee && !_left_dragging: cursor_pool.new_cursor(current_color) if in_marquee: cursor_pool.new_cursor(current_color) cursor_pool.current_cursor.animation = \ cursor_pool.AnimationState.Area func _mouse_leave_grid(): cursor_pool.fade_out() func _mouse_hover_grid(world_pos: Vector2): _update_cursor() func _cancel_operation(): clear_request() func _begin_marquee(tile_pos: Vector2i): cursor_pool.current_cursor.desired_box = \ grid.tile2world_rect(Rect2i(tile_pos, Vector2i.ONE)) cursor_pool.current_cursor.animation = CursorPool.AnimationState.Area func _update_marquee(area: Rect2i): cursor_pool.current_cursor.desired_box = \ grid.tile2world_rect(area) func _end_marquee(area: Rect2i): _area_selected(area) cursor_pool.explode() cursor_pool.new_cursor(current_color) _update_cursor() func _cancel_marquee(): cursor_pool.explode() if !mouse_in_grid: return cursor_pool.new_cursor(current_color) func _grid_clicked(tile_pos: Vector2i): _area_selected(Rect2i(tile_pos, Vector2i.ONE)) cursor_pool.explode() cursor_pool.new_cursor(current_color) _update_cursor() pass func _update_cursor(world_pos = get_local_mouse_position()): cursor_pool.current_cursor.desired_box = \ grid.quantize_on_objects(world_pos) enum Style { Highlight, Build, Delete } class Request: var context: Variant var confirmed var cancelled var request: Request = null var one_shot: bool = false @export var selection_manager: SelectionManager func _area_selected(area: Rect2i): if request != null: request.confirmed.call(request.context, area) if one_shot: _reset() return if area.size == Vector2i.ONE: var item = grid._get_building_at(area.position) if item != null: selection_manager.select_array([item]) return if item == null: selection_manager.clear() func clear_request(): if request == null: return request.cancelled.call(request.context) request = null _reset() func request_area(ctx, confirm, cancel, style: Style, area_type: AreaType, one_shot: bool): request = Request.new() request.confirmed = confirm request.cancelled = cancel request.context = ctx current_marquee_type = area_type _set_style(style) func _reset(): _set_style(Style.Highlight) current_marquee_type = AreaType.Rectangle func _set_style(style: Style): match style: Style.Highlight: current_color = highlight_color Style.Build: current_color = build_color Style.Delete: current_color = deletion_color