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.

Attempting to finish the draggable object class, but it isn't working, will see if there's a better alternative elsewhere

+323 -20
+13
client/scenes/main.tscn
··· 9 9 grow_vertical = 2 10 10 11 11 [node name="ManaGroveNode" type="ManaGroveNode" parent="." unique_id=2018257899] 12 + 13 + [node name="DraggableObject" type="DraggableObject" parent="." unique_id=838482379] 14 + layout_mode = 0 15 + offset_right = 286.0 16 + offset_bottom = 409.0 17 + 18 + [node name="ColorRect" type="ColorRect" parent="DraggableObject" unique_id=2073394659] 19 + layout_mode = 1 20 + anchors_preset = 15 21 + anchor_right = 1.0 22 + anchor_bottom = 1.0 23 + grow_horizontal = 2 24 + grow_vertical = 2
+310 -20
gdext/src/draggable.rs
··· 1 1 use std::collections::HashMap; 2 2 3 - use godot::{classes::{Control, IControl, InputEvent, InputEventMouseButton, Tween, control::MouseFilter}, prelude::*}; 3 + use godot::{classes::{Control, IControl, InputEvent, InputEventMouseButton, Tween, control::MouseFilter}, global::{MouseButton, deg_to_rad}, prelude::*}; 4 4 5 5 /// A draggable object that supports mouse interaction with state-based animation system. 6 6 /// ··· 36 36 /// Base object (Control) 37 37 base: Base<Control>, 38 38 /// The speed at which the object moves. 39 + #[export] 39 40 moving_speed: i32, 40 41 /// Whether the object can be interacted with. 42 + #[export] 41 43 can_be_interacted_with: bool, 42 44 /// The distance the object hovers when interacted with. 45 + #[export] 43 46 hover_distance: i32, 44 47 /// The scale multiplier when hovering. 48 + #[export] 45 49 hover_scale: f32, 46 50 /// The rotation in degrees when hovering. 51 + #[export] 47 52 hover_rotation: f32, 48 53 /// The duration for hover animations. 54 + #[export] 49 55 hover_duration: f32, 50 56 /// State Machine 51 57 current_state: DraggableState, ··· 63 69 original_hover_rotation: f32, 64 70 /// Position and animation tracking. Track position during hover animation 65 71 current_hover_position: Vector2, 72 + target_destination: Vector2, 73 + target_rotation: f32, 66 74 /// Move operation tracking 67 75 original_destination: Vector2, 68 76 /// Move operation tracking ··· 70 78 /// Move operation tracking 71 79 destination_degree: f32, 72 80 /// Tween objects 73 - move_tween: Option<Tween>, 81 + move_tween: Option<Gd<Tween>>, 74 82 /// Tween objects 75 - hover_tween: Option<Tween>, 83 + hover_tween: Option<Gd<Tween>>, 84 + /// Stored Z-Index 85 + stored_z_index: i32, 86 + is_returning_to_original: bool, 87 + is_pressed: bool, 76 88 } 77 89 78 90 fn allowed_transitions(state: DraggableState) -> Vec<DraggableState> { ··· 83 95 DraggableState::Moving => vec![DraggableState::Idle] 84 96 } 85 97 } 98 + 99 + const VISUAL_DRAG_Z_OFFSET: i32 = 0; 86 100 87 101 #[godot_api] 88 102 impl IControl for DraggableObject { ··· 108 122 destination_degree: 0., 109 123 move_tween: None, 110 124 hover_tween: None, 125 + stored_z_index: 0, 126 + is_returning_to_original: false, 127 + target_destination: Vector2::ZERO, 128 + target_rotation: 0., 129 + is_pressed: false 111 130 } 112 131 } 113 132 114 133 fn ready(&mut self) { 115 - let base = self.base.to_init_gd().clone(); 116 - 117 - self.base 118 - .to_init_gd() 134 + self.base_mut() 119 135 .set_mouse_filter(MouseFilter::STOP); 120 136 // connect("mouse_entered", on_mouse_enter) 121 137 self.signals() ··· 130 146 .gui_input() 131 147 .connect_self(Self::on_gui_input); 132 148 133 - self.original_destination = self.base 134 - .to_init_gd() 149 + self.original_destination = self.base() 135 150 .get_global_position(); 136 - self.original_rotation = self.base 137 - .to_init_gd() 151 + self.original_rotation = self.base() 138 152 .get_rotation(); 139 - self.original_position = self.base 140 - .to_init_gd() 153 + self.original_position = self.base() 141 154 .get_position(); 142 - self.original_scale = self.base 143 - .to_init_gd() 155 + self.original_scale = self.base() 144 156 .get_scale(); 145 - self.original_hover_rotation = self.base 146 - .to_init_gd() 157 + self.original_hover_rotation = self.base() 147 158 .get_rotation(); 159 + self.stored_z_index = self.base() 160 + .get_z_index(); 161 + } 162 + 163 + fn process(&mut self, _delta: f64) { 164 + match &self.current_state { 165 + DraggableState::Holding => { 166 + let new_mouse_position = self.base().get_global_mouse_position() - self.current_holding_mouse_position; 167 + 168 + self.base_mut() 169 + .set_global_position(new_mouse_position); 170 + }, 171 + _ => () 172 + } 148 173 } 149 174 } 150 175 ··· 182 207 } 183 208 } 184 209 210 + /// Virtual method to determine if hovering animation can start. 211 + /// Override in subclasses to implement custom hovering conditions. 185 212 fn can_start_hovering(&mut self) -> bool { 186 - false 213 + true 187 214 } 188 215 189 216 fn change_state(&mut self, new_state: DraggableState) -> bool { ··· 204 231 true 205 232 } 206 233 207 - fn handle_mouse_button(&mut self, event: Gd<InputEventMouseButton>) { 234 + fn handle_mouse_button(&mut self, mouse_event: Gd<InputEventMouseButton>) { 235 + if mouse_event.get_button_index() != MouseButton::LEFT { 236 + return; 237 + } 238 + 239 + // Ignore all input during Moving state 240 + if self.current_state == DraggableState::Moving { 241 + return; 242 + } 208 243 244 + if mouse_event.is_pressed() { 245 + self.handle_mouse_pressed(); 246 + } 247 + 248 + if mouse_event.is_released() { 249 + self.handle_mouse_released(); 250 + } 209 251 } 210 252 211 253 fn exit_state(&mut self, state: DraggableState) { 254 + match state { 255 + DraggableState::Hovering => { 256 + let stored_z_index = self.stored_z_index; 212 257 258 + self.base_mut() 259 + .set_z_index(stored_z_index); 260 + self.stop_hover_animation(); 261 + }, 262 + DraggableState::Holding => { 263 + let stored_z_index = self.stored_z_index; 264 + let original_scale = self.original_scale; 265 + let original_hover_rotation = self.original_hover_rotation; 266 + 267 + self.base_mut() 268 + .set_z_index(stored_z_index); 269 + // Reset visual effects but preserve position for return_card() animation 270 + self.base_mut() 271 + .set_scale(original_scale); 272 + self.base_mut() 273 + .set_rotation(original_hover_rotation); 274 + }, 275 + DraggableState::Moving => { 276 + self.base_mut() 277 + .set_mouse_filter(MouseFilter::STOP); 278 + }, 279 + _ => () 280 + } 213 281 } 214 282 215 - fn enter_state(&mut self, new_state: DraggableState, old_state: DraggableState) { 283 + fn enter_state(&mut self, state: DraggableState, from_state: DraggableState) { 284 + match state { 285 + DraggableState::Idle => { 286 + let stored_z_index = self.stored_z_index; 216 287 288 + self.base_mut() 289 + .set_z_index(stored_z_index); 290 + self.base_mut() 291 + .set_mouse_filter(MouseFilter::STOP); 292 + }, 293 + DraggableState::Hovering => { 294 + let stored_z_index = self.stored_z_index; 295 + 296 + self.base_mut() 297 + .set_z_index(stored_z_index + VISUAL_DRAG_Z_OFFSET); 298 + self.start_hover_animation(); 299 + }, 300 + DraggableState::Holding => { 301 + let stored_z_index = self.stored_z_index; 302 + 303 + // Preserve hover position if transitioning from Hovering state 304 + if from_state == DraggableState::Hovering { 305 + self.preserve_hover_position(); 306 + } 307 + 308 + self.current_holding_mouse_position = self.base() 309 + .get_local_mouse_position(); 310 + self.base_mut() 311 + .set_z_index(stored_z_index + VISUAL_DRAG_Z_OFFSET); 312 + self.base_mut() 313 + .set_rotation(0.); 314 + }, 315 + DraggableState::Moving => { 316 + let stored_z_index = self.stored_z_index; 317 + 318 + // Stop hover animations and ignore input during programmatic movement 319 + if let Some(hover_tween) = &mut self.hover_tween { 320 + if hover_tween.is_valid() { 321 + hover_tween.kill(); 322 + self.hover_tween = None; 323 + } 324 + } 325 + 326 + self.base_mut() 327 + .set_z_index(stored_z_index + VISUAL_DRAG_Z_OFFSET); 328 + self.base_mut() 329 + .set_mouse_filter(MouseFilter::IGNORE); 330 + } 331 + } 332 + } 333 + 334 + fn start_hover_animation(&mut self) { 335 + if let Some(hover_tween) = &mut self.hover_tween { 336 + if hover_tween.is_valid() { 337 + hover_tween.kill(); 338 + self.hover_tween = None; 339 + let original_position = self.original_position; // Reset position to original before starting new hover 340 + self.base_mut() 341 + .set_position(original_position); 342 + let original_scale = self.original_scale; 343 + self.base_mut() 344 + .set_scale(original_scale); 345 + let original_hover_rotation = self.original_hover_rotation; 346 + self.base_mut() 347 + .set_rotation(original_hover_rotation); 348 + } 349 + } 350 + 351 + // Update original position to current position (important for correct return) 352 + self.original_position = self.base().get_position(); 353 + self.original_scale = self.base().get_scale(); 354 + self.original_hover_rotation = self.base().get_rotation(); 355 + 356 + // Store current position before animation 357 + self.current_hover_position = self.base().get_position(); 358 + 359 + // Create new hover tween 360 + let mut hover_tween = self.create_tween(); 361 + hover_tween.set_parallel(); // Allow multiple properties to animate simultaneously 362 + 363 + // Animate position (hover up) 364 + let position = self.base().get_position(); 365 + let target_position = Vector2::new(position.x, position.y - (self.hover_distance as f32)); 366 + let obj = Gd::<Object>::from_instance_id(self.to_gd().instance_id()); 367 + hover_tween.tween_property(&obj, "position", &Variant::from(target_position), self.hover_duration.into()); 368 + 369 + // Animate scale 370 + hover_tween.tween_property(&obj, "scale", &Variant::from(self.original_scale * self.hover_scale), self.hover_duration.into()); 371 + 372 + // Animate rotation 373 + hover_tween.tween_property(&obj, "rotation", &Variant::from(deg_to_rad(self.hover_rotation.into())), self.hover_duration.into()); 374 + 375 + // Update current hover position tracking 376 + let func = Callable::from_object_method(&self.to_gd(), "update_hover_position"); 377 + hover_tween.tween_method(&func, &Variant::from(self.base().get_position()), &Variant::from(target_position), self.hover_duration.into()); 378 + } 379 + 380 + /// Preserve current hover position when transitioning to Holding 381 + fn preserve_hover_position(&mut self) { 382 + // Stop hover animation and preserve current position 383 + if let Some(hover_tween) = &mut self.hover_tween { 384 + if hover_tween.is_valid() { 385 + hover_tween.kill(); 386 + self.hover_tween = None; 387 + } 388 + } 389 + 390 + // Explicitly set position to current hover position 391 + // This ensures smooth transition from hover animation to holding 392 + let current_hover_position = self.current_hover_position; 393 + self.base_mut() 394 + .set_position(current_hover_position); 395 + } 396 + 397 + fn stop_hover_animation(&mut self) { 398 + 399 + } 400 + 401 + pub fn finish_move(&mut self) { 402 + // Complete movement processing 403 + self.is_moving_to_destination = false; 404 + let destination_degree = self.destination_degree; 405 + self.base_mut() 406 + .set_rotation(destination_degree); 407 + 408 + // Update original position and rotation only when not returning to original 409 + // Important: Use original target values from move() instead of global_position 410 + if !self.is_returning_to_original { 411 + self.original_destination = self.target_destination; 412 + self.original_rotation = self.target_rotation; 413 + } 414 + 415 + // Reset return flag 416 + self.is_returning_to_original = false; 417 + 418 + // End Moving state, return to Idle 419 + self.change_state(DraggableState::Idle); 420 + 421 + // Call inherited class callback 422 + self.on_move_done() 423 + } 424 + 425 + fn on_move_done(&mut self) { 426 + // This function can be overridden by subclasses to handle when the move is done. 427 + } 428 + 429 + fn create_tween(&mut self) -> Gd<Tween> { 430 + self.base().get_tree().create_tween() 431 + } 432 + 433 + pub fn update_hover_position(&mut self, pos: Vector2) { 434 + self.current_hover_position = pos; 435 + } 436 + 437 + /// Moves the object to target position with optional rotation using smooth animation. 438 + /// Automatically transitions to Moving state and handles animation timing based on distance. 439 + fn move_obj(&mut self, target_destination: Vector2, degree: f32) { 440 + // Skip if current position and rotation match target 441 + if self.base().get_global_position() == target_destination && self.base().get_rotation() == degree { 442 + return; 443 + } 444 + 445 + // Force transition to Moving state (highest priority) 446 + self.change_state(DraggableState::Moving); 447 + 448 + // Stop existing movement 449 + if let Some(move_tween) = &mut self.move_tween { 450 + if move_tween.is_valid() { 451 + move_tween.kill(); 452 + self.move_tween = None; 453 + } 454 + } 455 + 456 + // Store target position and rotation for original value preservation 457 + self.target_destination = target_destination; 458 + self.target_rotation = degree; 459 + 460 + // Initial setup 461 + self.base_mut().set_rotation(0.); 462 + self.destination_degree = degree; 463 + self.is_moving_to_destination = true; 464 + 465 + // Smooth Tween-based movement with dynamic duration based on moving_speed 466 + let distance = self.base().get_global_position().distance_to(target_destination); 467 + let duration = distance / (self.moving_speed as f32); 468 + 469 + let mut move_tween = self.create_tween(); 470 + let obj = Gd::<Object>::from_instance_id(self.to_gd().instance_id()); 471 + move_tween.tween_property(&obj, "global_position", &Variant::from(target_destination), duration.into()); 472 + let func = Callable::from_object_method(&self.to_gd(), "finish_move"); 473 + move_tween.tween_callback(&func); 474 + } 475 + 476 + fn handle_mouse_pressed(&mut self) { 477 + self.is_pressed = true; 478 + 479 + match self.current_state { 480 + DraggableState::Hovering => { 481 + self.change_state(DraggableState::Holding); 482 + }, 483 + DraggableState::Idle => { 484 + if self.is_mouse_inside && self.can_be_interacted_with && self.can_start_hovering() { 485 + self.change_state(DraggableState::Holding); 486 + } 487 + }, 488 + _ => () 489 + } 490 + } 491 + 492 + fn handle_mouse_released(&mut self) { 493 + self.is_pressed = false; 494 + 495 + match self.current_state { 496 + DraggableState::Holding => { 497 + self.change_state(DraggableState::Idle); 498 + }, 499 + _ => () 500 + } 501 + } 502 + 503 + /// Returns the object to its original position with smooth animation. 504 + fn return_to_original(&mut self) { 505 + self.is_returning_to_original = true; 506 + self.move_obj(self.original_destination, self.original_rotation); 217 507 } 218 508 } 219 509