A card game engine for TCGs, primarily Magic: The Gathering but with support for others
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Beginning to implement draggable stuff

+241
+231
gdext/src/draggable.rs
··· 1 + use std::collections::HashMap; 2 + 3 + use godot::{classes::{Control, IControl, InputEvent, InputEventMouseButton, Tween, control::MouseFilter}, prelude::*}; 4 + 5 + /// A draggable object that supports mouse interaction with state-based animation system. 6 + /// 7 + /// This class provides a robust state machine for handling mouse interactions including 8 + /// hover effects, drag operations, and programmatic movement using Tween animations. 9 + /// All interactive cards and objects extend this base class to inherit consistent 10 + /// drag-and-drop behavior. 11 + /// 12 + /// Key Features: 13 + /// - State machine with safe transitions (IDLE → HOVERING → HOLDING → MOVING) 14 + /// - Tween-based animations for smooth hover effects and movement 15 + /// - Mouse interaction handling with proper event management 16 + /// - Z-index management for visual layering during interactions 17 + /// - Extensible design with virtual methods for customization 18 + /// 19 + /// State Transitions: 20 + /// - IDLE: Default state, ready for interaction 21 + /// - HOVERING: Mouse over with visual feedback (scale, rotation, position) 22 + /// - HOLDING: Active drag state following mouse movement 23 + /// - MOVING: Programmatic movement ignoring user input 24 + /// 25 + /// Usage: 26 + /// ``` 27 + /// class_name MyDraggable 28 + /// extends DraggableObject 29 + /// 30 + /// func _can_start_hovering() -> bool: 31 + /// return my_custom_condition 32 + /// ``` 33 + #[derive(GodotClass)] 34 + #[class(base=Control)] 35 + pub struct DraggableObject { 36 + /// Base object (Control) 37 + base: Base<Control>, 38 + /// The speed at which the object moves. 39 + moving_speed: i32, 40 + /// Whether the object can be interacted with. 41 + can_be_interacted_with: bool, 42 + /// The distance the object hovers when interacted with. 43 + hover_distance: i32, 44 + /// The scale multiplier when hovering. 45 + hover_scale: f32, 46 + /// The rotation in degrees when hovering. 47 + hover_rotation: f32, 48 + /// The duration for hover animations. 49 + hover_duration: f32, 50 + /// State Machine 51 + current_state: DraggableState, 52 + /// Mouse tracking 53 + is_mouse_inside: bool, 54 + /// Movement state tracking 55 + is_moving_to_destination: bool, 56 + /// Movement state tracking 57 + current_holding_mouse_position: Vector2, 58 + /// Position and animation tracking 59 + original_position: Vector2, 60 + /// Position and animation tracking 61 + original_scale: Vector2, 62 + /// Position and animation tracking 63 + original_hover_rotation: f32, 64 + /// Position and animation tracking. Track position during hover animation 65 + current_hover_position: Vector2, 66 + /// Move operation tracking 67 + original_destination: Vector2, 68 + /// Move operation tracking 69 + original_rotation: f32, 70 + /// Move operation tracking 71 + destination_degree: f32, 72 + /// Tween objects 73 + move_tween: Option<Tween>, 74 + /// Tween objects 75 + hover_tween: Option<Tween>, 76 + } 77 + 78 + fn allowed_transitions(state: DraggableState) -> Vec<DraggableState> { 79 + match state { 80 + DraggableState::Idle => vec![DraggableState::Hovering, DraggableState::Holding, DraggableState::Moving], 81 + DraggableState::Hovering => vec![DraggableState::Idle, DraggableState::Holding, DraggableState::Moving], 82 + DraggableState::Holding => vec![DraggableState::Idle, DraggableState::Moving], 83 + DraggableState::Moving => vec![DraggableState::Idle] 84 + } 85 + } 86 + 87 + #[godot_api] 88 + impl IControl for DraggableObject { 89 + fn init(base: Base<Control>) -> Self { 90 + Self { 91 + base, 92 + moving_speed: 0, 93 + can_be_interacted_with: true, 94 + hover_distance: 0, 95 + hover_scale: 0., 96 + hover_rotation: 0., 97 + hover_duration: 0., 98 + current_state: DraggableState::Idle, 99 + is_mouse_inside: false, 100 + is_moving_to_destination: false, 101 + current_holding_mouse_position: Vector2::ZERO, 102 + original_position: Vector2::ZERO, 103 + original_scale: Vector2::ZERO, 104 + original_hover_rotation: 0., 105 + current_hover_position: Vector2::ZERO, 106 + original_destination: Vector2::ZERO, 107 + original_rotation: 0., 108 + destination_degree: 0., 109 + move_tween: None, 110 + hover_tween: None, 111 + } 112 + } 113 + 114 + fn ready(&mut self) { 115 + let base = self.base.to_init_gd().clone(); 116 + 117 + self.base 118 + .to_init_gd() 119 + .set_mouse_filter(MouseFilter::STOP); 120 + // connect("mouse_entered", on_mouse_enter) 121 + self.signals() 122 + .mouse_entered() 123 + .connect_self(Self::on_mouse_enter); 124 + // connect("mouse_exited", on_mouse_exit) 125 + self.signals() 126 + .mouse_exited() 127 + .connect_self(Self::on_mouse_exit); 128 + // connect("gui_input", on_gui_input) 129 + self.signals() 130 + .gui_input() 131 + .connect_self(Self::on_gui_input); 132 + 133 + self.original_destination = self.base 134 + .to_init_gd() 135 + .get_global_position(); 136 + self.original_rotation = self.base 137 + .to_init_gd() 138 + .get_rotation(); 139 + self.original_position = self.base 140 + .to_init_gd() 141 + .get_position(); 142 + self.original_scale = self.base 143 + .to_init_gd() 144 + .get_scale(); 145 + self.original_hover_rotation = self.base 146 + .to_init_gd() 147 + .get_rotation(); 148 + } 149 + } 150 + 151 + #[godot_api] 152 + impl DraggableObject { 153 + #[signal] 154 + fn dummy_signal(); 155 + } 156 + 157 + impl DraggableObject { 158 + fn on_mouse_enter(&mut self) { 159 + self.is_mouse_inside = true; 160 + 161 + if self.can_be_interacted_with && self.can_start_hovering() { 162 + self.change_state(DraggableState::Hovering); 163 + } 164 + } 165 + 166 + fn on_mouse_exit(&mut self) { 167 + self.is_mouse_inside = false; 168 + 169 + match self.current_state { 170 + DraggableState::Hovering => self.change_state(DraggableState::Idle), 171 + _ => true 172 + }; 173 + } 174 + 175 + fn on_gui_input(&mut self, event: Gd<InputEvent>) { 176 + if !self.can_be_interacted_with { 177 + return; 178 + } 179 + 180 + if event.is_class("InputEventMouseButton") { 181 + self.handle_mouse_button(event.try_cast::<InputEventMouseButton>().unwrap()); 182 + } 183 + } 184 + 185 + fn can_start_hovering(&mut self) -> bool { 186 + false 187 + } 188 + 189 + fn change_state(&mut self, new_state: DraggableState) -> bool { 190 + if new_state == self.current_state { 191 + return true; 192 + } 193 + 194 + if !allowed_transitions(self.current_state).contains(&new_state) { 195 + return false; 196 + } 197 + 198 + self.exit_state(self.current_state); 199 + 200 + let old_state = self.current_state; 201 + self.current_state = new_state; 202 + 203 + self.enter_state(new_state, old_state); 204 + true 205 + } 206 + 207 + fn handle_mouse_button(&mut self, event: Gd<InputEventMouseButton>) { 208 + 209 + } 210 + 211 + fn exit_state(&mut self, state: DraggableState) { 212 + 213 + } 214 + 215 + fn enter_state(&mut self, new_state: DraggableState, old_state: DraggableState) { 216 + 217 + } 218 + } 219 + 220 + /// Enumeration of possible interaction states for the draggable object. 221 + #[derive(PartialEq, Copy, Clone)] 222 + pub enum DraggableState { 223 + /// Default state - no interaction 224 + Idle, 225 + /// Mouse over state - visual feedback 226 + Hovering, 227 + /// Dragging state - follows mouse 228 + Holding, 229 + /// Programmatic move state - ignores input 230 + Moving 231 + }
+10
gdext/src/lib.rs
··· 1 1 use godot::prelude::*; 2 2 use managrove_core::Game; 3 3 4 + mod draggable; 5 + 4 6 #[derive(GodotClass)] 5 7 #[class(base=Node)] 6 8 struct ManaGroveNode { ··· 20 22 base, 21 23 data: Game::new() 22 24 } 25 + } 26 + 27 + fn ready(&mut self) { 28 + godot_print!("Loaded ManaGrove game"); 29 + } 30 + 31 + fn process(&mut self, delta: f64) { 32 + 23 33 } 24 34 }