diff --git a/assets/cards/club.png b/assets/cards/club.png deleted file mode 100644 index 08884bc..0000000 Binary files a/assets/cards/club.png and /dev/null differ diff --git a/assets/cards/diamond.png b/assets/cards/diamond.png deleted file mode 100644 index c603300..0000000 Binary files a/assets/cards/diamond.png and /dev/null differ diff --git a/assets/cards/glow_blue.png b/assets/cards/glow_blue.png new file mode 100644 index 0000000..1c6f84e Binary files /dev/null and b/assets/cards/glow_blue.png differ diff --git a/assets/cards/glow_green.png b/assets/cards/glow_green.png new file mode 100644 index 0000000..8272afe Binary files /dev/null and b/assets/cards/glow_green.png differ diff --git a/assets/cards/glow_red.png b/assets/cards/glow_red.png new file mode 100644 index 0000000..e59e43c Binary files /dev/null and b/assets/cards/glow_red.png differ diff --git a/assets/cards/glow_yellow.png b/assets/cards/glow_yellow.png new file mode 100644 index 0000000..47eb716 Binary files /dev/null and b/assets/cards/glow_yellow.png differ diff --git a/assets/cards/heart.png b/assets/cards/heart.png deleted file mode 100644 index ce41f6d..0000000 Binary files a/assets/cards/heart.png and /dev/null differ diff --git a/assets/cards/spade.png b/assets/cards/spade.png deleted file mode 100644 index 3fb6371..0000000 Binary files a/assets/cards/spade.png and /dev/null differ diff --git a/assets/cards/variations/blue_1.png b/assets/cards/variations/blue_1.png new file mode 100644 index 0000000..3e780bc Binary files /dev/null and b/assets/cards/variations/blue_1.png differ diff --git a/assets/cards/variations/blue_10.png b/assets/cards/variations/blue_10.png new file mode 100644 index 0000000..979eb74 Binary files /dev/null and b/assets/cards/variations/blue_10.png differ diff --git a/assets/cards/variations/blue_11.png b/assets/cards/variations/blue_11.png new file mode 100644 index 0000000..bff87ea Binary files /dev/null and b/assets/cards/variations/blue_11.png differ diff --git a/assets/cards/variations/blue_12.png b/assets/cards/variations/blue_12.png new file mode 100644 index 0000000..a9ba627 Binary files /dev/null and b/assets/cards/variations/blue_12.png differ diff --git a/assets/cards/variations/blue_13.png b/assets/cards/variations/blue_13.png new file mode 100644 index 0000000..e87a1ec Binary files /dev/null and b/assets/cards/variations/blue_13.png differ diff --git a/assets/cards/variations/blue_2.png b/assets/cards/variations/blue_2.png new file mode 100644 index 0000000..1b94f36 Binary files /dev/null and b/assets/cards/variations/blue_2.png differ diff --git a/assets/cards/variations/blue_3.png b/assets/cards/variations/blue_3.png new file mode 100644 index 0000000..fc3734d Binary files /dev/null and b/assets/cards/variations/blue_3.png differ diff --git a/assets/cards/variations/blue_4.png b/assets/cards/variations/blue_4.png new file mode 100644 index 0000000..435abc2 Binary files /dev/null and b/assets/cards/variations/blue_4.png differ diff --git a/assets/cards/variations/blue_5.png b/assets/cards/variations/blue_5.png new file mode 100644 index 0000000..97b5598 Binary files /dev/null and b/assets/cards/variations/blue_5.png differ diff --git a/assets/cards/variations/blue_6.png b/assets/cards/variations/blue_6.png new file mode 100644 index 0000000..03c6e2b Binary files /dev/null and b/assets/cards/variations/blue_6.png differ diff --git a/assets/cards/variations/blue_7.png b/assets/cards/variations/blue_7.png new file mode 100644 index 0000000..da63c74 Binary files /dev/null and b/assets/cards/variations/blue_7.png differ diff --git a/assets/cards/variations/blue_8.png b/assets/cards/variations/blue_8.png new file mode 100644 index 0000000..dd27ed8 Binary files /dev/null and b/assets/cards/variations/blue_8.png differ diff --git a/assets/cards/variations/blue_9.png b/assets/cards/variations/blue_9.png new file mode 100644 index 0000000..02fd3b5 Binary files /dev/null and b/assets/cards/variations/blue_9.png differ diff --git a/assets/cards/variations/green_1.png b/assets/cards/variations/green_1.png new file mode 100644 index 0000000..388cca5 Binary files /dev/null and b/assets/cards/variations/green_1.png differ diff --git a/assets/cards/variations/green_10.png b/assets/cards/variations/green_10.png new file mode 100644 index 0000000..af24233 Binary files /dev/null and b/assets/cards/variations/green_10.png differ diff --git a/assets/cards/variations/green_11.png b/assets/cards/variations/green_11.png new file mode 100644 index 0000000..5dab3ae Binary files /dev/null and b/assets/cards/variations/green_11.png differ diff --git a/assets/cards/variations/green_12.png b/assets/cards/variations/green_12.png new file mode 100644 index 0000000..2e4099e Binary files /dev/null and b/assets/cards/variations/green_12.png differ diff --git a/assets/cards/variations/green_13.png b/assets/cards/variations/green_13.png new file mode 100644 index 0000000..3225ed7 Binary files /dev/null and b/assets/cards/variations/green_13.png differ diff --git a/assets/cards/variations/green_2.png b/assets/cards/variations/green_2.png new file mode 100644 index 0000000..5fef1de Binary files /dev/null and b/assets/cards/variations/green_2.png differ diff --git a/assets/cards/variations/green_3.png b/assets/cards/variations/green_3.png new file mode 100644 index 0000000..36f5ec0 Binary files /dev/null and b/assets/cards/variations/green_3.png differ diff --git a/assets/cards/variations/green_4.png b/assets/cards/variations/green_4.png new file mode 100644 index 0000000..5929099 Binary files /dev/null and b/assets/cards/variations/green_4.png differ diff --git a/assets/cards/variations/green_5.png b/assets/cards/variations/green_5.png new file mode 100644 index 0000000..2a1c43e Binary files /dev/null and b/assets/cards/variations/green_5.png differ diff --git a/assets/cards/variations/green_6.png b/assets/cards/variations/green_6.png new file mode 100644 index 0000000..4b9391c Binary files /dev/null and b/assets/cards/variations/green_6.png differ diff --git a/assets/cards/variations/green_7.png b/assets/cards/variations/green_7.png new file mode 100644 index 0000000..bbfa58c Binary files /dev/null and b/assets/cards/variations/green_7.png differ diff --git a/assets/cards/variations/green_8.png b/assets/cards/variations/green_8.png new file mode 100644 index 0000000..1eb9178 Binary files /dev/null and b/assets/cards/variations/green_8.png differ diff --git a/assets/cards/variations/green_9.png b/assets/cards/variations/green_9.png new file mode 100644 index 0000000..6013a93 Binary files /dev/null and b/assets/cards/variations/green_9.png differ diff --git a/assets/cards/variations/jester.png b/assets/cards/variations/jester.png new file mode 100644 index 0000000..5b7b5a3 Binary files /dev/null and b/assets/cards/variations/jester.png differ diff --git a/assets/cards/variations/red_1.png b/assets/cards/variations/red_1.png new file mode 100644 index 0000000..1056d91 Binary files /dev/null and b/assets/cards/variations/red_1.png differ diff --git a/assets/cards/variations/red_10.png b/assets/cards/variations/red_10.png new file mode 100644 index 0000000..1dc4bc0 Binary files /dev/null and b/assets/cards/variations/red_10.png differ diff --git a/assets/cards/variations/red_11.png b/assets/cards/variations/red_11.png new file mode 100644 index 0000000..f40f7e6 Binary files /dev/null and b/assets/cards/variations/red_11.png differ diff --git a/assets/cards/variations/red_12.png b/assets/cards/variations/red_12.png new file mode 100644 index 0000000..f430712 Binary files /dev/null and b/assets/cards/variations/red_12.png differ diff --git a/assets/cards/variations/red_13.png b/assets/cards/variations/red_13.png new file mode 100644 index 0000000..2cd1a1a Binary files /dev/null and b/assets/cards/variations/red_13.png differ diff --git a/assets/cards/variations/red_2.png b/assets/cards/variations/red_2.png new file mode 100644 index 0000000..6eb3599 Binary files /dev/null and b/assets/cards/variations/red_2.png differ diff --git a/assets/cards/variations/red_3.png b/assets/cards/variations/red_3.png new file mode 100644 index 0000000..a28a933 Binary files /dev/null and b/assets/cards/variations/red_3.png differ diff --git a/assets/cards/variations/red_4.png b/assets/cards/variations/red_4.png new file mode 100644 index 0000000..b02cbff Binary files /dev/null and b/assets/cards/variations/red_4.png differ diff --git a/assets/cards/variations/red_5.png b/assets/cards/variations/red_5.png new file mode 100644 index 0000000..413a5fc Binary files /dev/null and b/assets/cards/variations/red_5.png differ diff --git a/assets/cards/variations/red_6.png b/assets/cards/variations/red_6.png new file mode 100644 index 0000000..985dc0a Binary files /dev/null and b/assets/cards/variations/red_6.png differ diff --git a/assets/cards/variations/red_7.png b/assets/cards/variations/red_7.png new file mode 100644 index 0000000..ee3cce9 Binary files /dev/null and b/assets/cards/variations/red_7.png differ diff --git a/assets/cards/variations/red_8.png b/assets/cards/variations/red_8.png new file mode 100644 index 0000000..3fef1ce Binary files /dev/null and b/assets/cards/variations/red_8.png differ diff --git a/assets/cards/variations/red_9.png b/assets/cards/variations/red_9.png new file mode 100644 index 0000000..d9a071f Binary files /dev/null and b/assets/cards/variations/red_9.png differ diff --git a/assets/cards/variations/wizard.png b/assets/cards/variations/wizard.png new file mode 100644 index 0000000..384fdc3 Binary files /dev/null and b/assets/cards/variations/wizard.png differ diff --git a/assets/cards/variations/yellow_1.png b/assets/cards/variations/yellow_1.png new file mode 100644 index 0000000..73634a4 Binary files /dev/null and b/assets/cards/variations/yellow_1.png differ diff --git a/assets/cards/variations/yellow_10.png b/assets/cards/variations/yellow_10.png new file mode 100644 index 0000000..14a5782 Binary files /dev/null and b/assets/cards/variations/yellow_10.png differ diff --git a/assets/cards/variations/yellow_11.png b/assets/cards/variations/yellow_11.png new file mode 100644 index 0000000..a5f722f Binary files /dev/null and b/assets/cards/variations/yellow_11.png differ diff --git a/assets/cards/variations/yellow_12.png b/assets/cards/variations/yellow_12.png new file mode 100644 index 0000000..4c52f4e Binary files /dev/null and b/assets/cards/variations/yellow_12.png differ diff --git a/assets/cards/variations/yellow_13.png b/assets/cards/variations/yellow_13.png new file mode 100644 index 0000000..d339852 Binary files /dev/null and b/assets/cards/variations/yellow_13.png differ diff --git a/assets/cards/variations/yellow_2.png b/assets/cards/variations/yellow_2.png new file mode 100644 index 0000000..ede2e55 Binary files /dev/null and b/assets/cards/variations/yellow_2.png differ diff --git a/assets/cards/variations/yellow_3.png b/assets/cards/variations/yellow_3.png new file mode 100644 index 0000000..95e6137 Binary files /dev/null and b/assets/cards/variations/yellow_3.png differ diff --git a/assets/cards/variations/yellow_4.png b/assets/cards/variations/yellow_4.png new file mode 100644 index 0000000..c83316e Binary files /dev/null and b/assets/cards/variations/yellow_4.png differ diff --git a/assets/cards/variations/yellow_5.png b/assets/cards/variations/yellow_5.png new file mode 100644 index 0000000..eeba306 Binary files /dev/null and b/assets/cards/variations/yellow_5.png differ diff --git a/assets/cards/variations/yellow_6.png b/assets/cards/variations/yellow_6.png new file mode 100644 index 0000000..f737282 Binary files /dev/null and b/assets/cards/variations/yellow_6.png differ diff --git a/assets/cards/variations/yellow_7.png b/assets/cards/variations/yellow_7.png new file mode 100644 index 0000000..5cb907b Binary files /dev/null and b/assets/cards/variations/yellow_7.png differ diff --git a/assets/cards/variations/yellow_8.png b/assets/cards/variations/yellow_8.png new file mode 100644 index 0000000..683e452 Binary files /dev/null and b/assets/cards/variations/yellow_8.png differ diff --git a/assets/cards/variations/yellow_9.png b/assets/cards/variations/yellow_9.png new file mode 100644 index 0000000..e13130e Binary files /dev/null and b/assets/cards/variations/yellow_9.png differ diff --git a/src/animation/mod.rs b/src/animation.rs similarity index 61% rename from src/animation/mod.rs rename to src/animation.rs index 3bd8dfc..5d9a822 100644 --- a/src/animation/mod.rs +++ b/src/animation.rs @@ -25,14 +25,27 @@ /// via the new function. /// Keep in mind that if you choose 0 for the animation duration the animation will always /// count as finished (the progress function returns 1.0). -pub mod animation_end_sensor; -pub mod animation_starter; - +/// +/// You can also set a Message to be sent when the animation reaches the highest frame number +/// via on_end or returns(!) to the first frame number 0 via on start. +/// Check the source of your animation type for the specific implementation of this. +/// +/// If you want to start multiple animations with a set delay you need to use AnimationStarter. +/// You give it a message with an usize 0 at its end that will start an animation. +/// The message needs to implement ReplaceUsize + Message for this. +/// You can also give it a message to send when all animations have ended via on_all_ended. +/// Now to use it you call start and update it every animation tick via next_frame use derive_more::{Deref, DerefMut}; +use iced::Task; use std::f32::consts::PI; +use crate::{ + client::AppMessage, + ui_element_traits::{Message, ReplaceUsize}, +}; + #[derive(Debug, PartialEq, Clone, Copy)] -pub enum AnimationState { +enum AnimationState { NotMoving, MovingForward, Reversing, @@ -123,11 +136,13 @@ pub fn ease_out_bounce(v: f32) -> f32 { // AI-Usage: Claude.ai for learning how to make a trait require another trait. // (Now this is not the case here anymore but it used to be). -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct AnimationCore { max_frame_number: usize, current_frame_number: usize, animation_state: AnimationState, + on_start: Option, + on_end: Option, } impl AnimationCore { @@ -136,12 +151,16 @@ impl AnimationCore { max_frame_number: duration, current_frame_number: 0, animation_state: AnimationState::NotMoving, + on_start: None, + on_end: None, } } pub fn start(&mut self) { - if self.not_moving() || self.animation_state == AnimationState::Ended { - self.animation_state = AnimationState::MovingForward; - } + self.animation_state = AnimationState::MovingForward; + } + pub fn start_force(&mut self) { + self.current_frame_number = 0; + self.animation_state = AnimationState::MovingForward; } #[allow(dead_code)] pub fn interrupt(&mut self) { @@ -175,11 +194,27 @@ impl AnimationCore { pub fn current_frame_number(&self) -> usize { self.current_frame_number } - pub fn not_moving(&self) -> bool { - self.animation_state == AnimationState::NotMoving + /// Message sent when an animation reaches an end point + pub fn on_end(&mut self, msg: AppMessage) { + self.on_end = Some(msg) } - pub fn moving_forward(&self) -> bool { - self.animation_state == AnimationState::MovingForward + pub fn on_start(&mut self, msg: AppMessage) { + self.on_start = Some(msg) + } + /// Send a message when an animation reaches a reverse point or end if not reversing. + fn end_task(&self) -> Task { + match &self.on_end { + Some(msg) => Task::done(msg.clone()), + None => Task::none(), + } + } + /// Send a message everytime when an animation returns to start. + /// This won't trigger when the animation is started. + fn start_task(&self) -> Task { + match &self.on_start { + Some(msg) => Task::done(msg.clone()), + None => Task::none(), + } } } @@ -203,55 +238,59 @@ new_core!(ReversableBasicAnimation); new_core!(AutoReversingAnimation); new_core!(CircularAutoReversingAnimation); -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct BasicAnimation(AnimationCore); impl BasicAnimation { - pub fn next_frame(&mut self) { + pub fn next_frame(&mut self) -> Task { if self.animation_state == AnimationState::MovingForward { if self.current_frame_number < self.max_frame_number { self.current_frame_number += 1; } else { self.animation_state = AnimationState::Ended; + return self.end_task(); } } - } - #[allow(dead_code)] - pub fn ended(&self) -> bool { - self.animation_state == AnimationState::Ended + Task::none() } } #[allow(dead_code)] -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct CircularAnimation(AnimationCore); impl CircularAnimation { #[allow(dead_code)] - pub fn next_frame(&mut self) { + pub fn next_frame(&mut self) -> Task { if self.animation_state == AnimationState::MovingForward && self.max_frame_number != 0 { - self.current_frame_number = - // The 1 is there for going to the next frame number. - (self.current_frame_number + 1) % self.max_frame_number; + let frame_number_before = self.current_frame_number; + self.current_frame_number = (self.current_frame_number + 1) % self.max_frame_number; + let frame_number_after = self.current_frame_number; + if frame_number_before >= frame_number_after { + return self.end_task(); + } } + Task::none() } } -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct ReversableBasicAnimation(AnimationCore); impl ReversableBasicAnimation { pub fn reverse(&mut self) { self.animation_state = AnimationState::Reversing; } - pub fn start_from_reverse(&mut self) { + #[allow(dead_code)] + pub fn reverse_force(&mut self) { + self.current_frame_number = self.max_frame_number; self.animation_state = AnimationState::Reversing; - self.current_frame_number = self.max_frame_number } - pub fn next_frame(&mut self) { + pub fn next_frame(&mut self) -> Task { match self.animation_state { AnimationState::MovingForward => { if self.current_frame_number < self.max_frame_number { self.current_frame_number += 1; } else { self.animation_state = AnimationState::NotMoving; + return self.end_task(); } } AnimationState::Reversing => { @@ -259,27 +298,26 @@ impl ReversableBasicAnimation { self.current_frame_number -= 1; } else { self.animation_state = AnimationState::Ended; + return self.start_task(); } } _ => {} } - } - #[allow(dead_code)] - pub fn reversing(&self) -> bool { - self.animation_state == AnimationState::Reversing + Task::none() } } -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct AutoReversingAnimation(AnimationCore); impl AutoReversingAnimation { - pub fn next_frame(&mut self) { + pub fn next_frame(&mut self) -> Task { match self.animation_state { AnimationState::MovingForward => { if self.current_frame_number < self.max_frame_number { self.current_frame_number += 1; } else { self.animation_state = AnimationState::Reversing; + return self.end_task(); } } AnimationState::Reversing => { @@ -287,27 +325,26 @@ impl AutoReversingAnimation { self.current_frame_number -= 1; } else { self.reset(); + return self.start_task(); } } _ => {} } - } - #[allow(dead_code)] - pub fn reversing(&self) -> bool { - self.animation_state == AnimationState::Reversing + Task::none() } } -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct CircularAutoReversingAnimation(AnimationCore); impl CircularAutoReversingAnimation { - pub fn next_frame(&mut self) { + pub fn next_frame(&mut self) -> Task { match self.animation_state { AnimationState::MovingForward => { if self.current_frame_number < self.max_frame_number { self.current_frame_number += 1; } else { self.animation_state = AnimationState::Reversing; + return self.end_task(); } } AnimationState::Reversing => { @@ -315,13 +352,85 @@ impl CircularAutoReversingAnimation { self.current_frame_number -= 1; } else { self.animation_state = AnimationState::MovingForward; + return self.start_task(); } } _ => {} } + Task::none() } - #[allow(dead_code)] - pub fn reversing(&self) -> bool { - self.animation_state == AnimationState::Reversing +} + +#[derive(Clone, Debug)] +pub struct AnimationStarter { + state: AnimationState, + times: usize, + animation_delay: usize, + animation_length: usize, + tick: usize, + started: usize, + on_start_single: MStart, + on_all_ended: Option, +} + +impl AnimationStarter { + /// Check AppMessage::replace_usize + pub fn new(animation_delay: usize, animation_duration: usize, first_start_msg: MStart) -> Self { + Self { + times: 0, + state: AnimationState::NotMoving, + animation_delay, + animation_length: animation_duration, + tick: 0, + started: 0, + on_start_single: first_start_msg, + on_all_ended: None, + } + } + pub fn start(&mut self, times: usize) { + self.times = times; + self.state = AnimationState::MovingForward; + } + pub fn next_frame(&mut self) -> Task { + if self.state == AnimationState::MovingForward { + self.tick += 1; + if self.check_all_ended() { + return self.all_ended(); + } else if self.check_tick() { + return self.start_single(); + } + } + Task::none() + } + pub fn on_all_ended(&mut self, msg: MEnd) { + self.on_all_ended = Some(msg) + } + pub fn times(&self) -> usize { + self.times + } + fn check_tick(&self) -> bool { + self.tick.is_multiple_of(self.animation_delay) + } + fn check_all_ended(&self) -> bool { + (self.tick == self.animation_delay * self.times + self.animation_length) + || (self.times == 0) + } + fn all_ended(&mut self) -> Task { + let task: Task = match &self.on_all_ended { + Some(msg) => Task::done(msg.convert_msg()), + None => Task::none(), + }; + self.state = AnimationState::NotMoving; + self.tick = 0; + self.started = 0; + task + } + fn start_single(&mut self) -> Task { + self.started += 1; + Task::done( + self.on_start_single + .replace_usize(self.started - 1) + .convert_msg(), + ) } } diff --git a/src/animation/animation_end_sensor.rs b/src/animation/animation_end_sensor.rs deleted file mode 100644 index 18e01ed..0000000 --- a/src/animation/animation_end_sensor.rs +++ /dev/null @@ -1,72 +0,0 @@ -/// An AnimationEndSensor executes a code block when an animation ends. -/// Start the sensor with the start function in an ui element impl Animated in update_with_msg -/// and execute the code with the check function in update animations. -/// Make sure that your AnimationEndSensors are at the end in update animations. -#[derive(Debug)] -pub struct AnimationEndSensor { - animation_duration: usize, - tick: usize, - /// `Active` -> `true` - /// - /// `Inactive` -> `false` - state: bool, - content: Option, -} - -impl AnimationEndSensor { - pub fn new(animation_duration: usize) -> Self { - Self { - animation_duration, - tick: 0, - state: false, - content: None, - } - } - // You may need some additional information for the check function - // you only know when you call the start function. - // Pass the information as content here to use it later on. - // But make sure that the content and its context still represent what it should by then. - pub fn start(&mut self, content: Option) { - if !self.state { - self.state = true; - } - self.content = content; - } - /// AI-Usage: Claude.ai for the implementation of passing a closure to a function - /// to execute it in the function. - /// - /// Use this every time update_animations from the Animated trait is called. - /// - /// This function executes the action when the last tick is reached. - /// - /// This function returns true when the last tick is reached. - /// This property is useful when facing borrowing issues within the action. - pub fn check(&mut self, action: A) -> bool - where - A: FnOnce(&mut Self), - { - if self.state { - if self.last_tick_reached() { - action(self); - self.reset(); - return true; - } else { - self.tick += 1; - }; - } - false - } - pub fn content(&self) -> Option<&C> { - self.content.as_ref() - } - pub fn active(&self) -> bool { - self.state - } - pub fn reset(&mut self) { - self.state = false; - self.tick = 0; - } - fn last_tick_reached(&self) -> bool { - self.tick == self.animation_duration - } -} diff --git a/src/animation/animation_starter.rs b/src/animation/animation_starter.rs deleted file mode 100644 index b3d929d..0000000 --- a/src/animation/animation_starter.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::num::NonZero; -/// An AnimationStarter starts animations with a delay greater zero. -/// If you want to start multiple animations on the same time it is easier to do it manually. -/// Start it with the start function in an ui element impl Animated in update_with_msg -/// and start an animation with the check function in update animations. -/// Make sure that your AnimationStarters are at the beginning in update animations. -#[derive(Debug)] -pub struct AnimationStarter { - animation_delay: NonZero, - tick: usize, - times: usize, - /// `Active` -> `true` - /// - /// `Inactive` -> `false` - state: bool, - content: Option, -} - -impl AnimationStarter { - pub fn new(animation_delay: NonZero) -> Self { - Self { - animation_delay, - tick: 0, - times: 1, // This Will be set in the start function first before it matters. - state: false, - // You may need some additional information for the check function - // you only know when you call the start function. - content: None, - } - } - // You may need some additional information for the check function - // you only know when you call the start function. - // Pass the information as content here to use it later on. - // But make sure that the content and its context still represent what it should by then. - pub fn start(&mut self, content: Option, times: NonZero) { - if !self.state { - self.state = true; - } - // It will be easier to calculate later on starting with 0 and not 1. - // Sow while times is non zero for the user it actually starts with 0 internally. - self.times = times.get() - 1; - self.content = content; - } - /// AI-Usage: Claude.ai for the implementation of passing a closure to a function - /// to execute it in the function. - /// - /// Use this every time update_animations from the Animated trait is called. - /// - /// This function executes the action used for starting an animation - /// everytime the delay period has passed except for the first animation which is started - /// immediately. - /// - /// This function returns true when the last tick is reached - /// and all animations are started (NOT ended). - /// You can use this property with an if-statement then to immediately execute an action. - pub fn check(&mut self, action: A) -> bool - where - A: FnOnce(&mut Self), - { - if self.state { - if self.tick % self.animation_delay == 0 { - action(self); - } - if self.last_tick_reached() { - self.reset(); - return true; - } else { - self.tick += 1; - } - }; - false - } - #[allow(dead_code)] - pub fn content(&self) -> Option<&C> { - self.content.as_ref() - } - pub fn active(&self) -> bool { - self.state - } - pub fn reset(&mut self) { - self.state = false; - self.tick = 0; - } - /// The number of the last started animation starting from 0. - pub fn cycle(&self) -> usize { - (self.tick - (self.tick % self.animation_delay)) / self.animation_delay - } - fn last_tick_reached(&self) -> bool { - self.tick == self.times * self.animation_delay.get() - } -} diff --git a/src/api.rs b/src/api.rs index edb9325..48f623a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,6 +3,12 @@ use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; +pub static CARD_BACK_PATH: &str = "assets/cards/back.png"; +pub static BUTTON1_PATH: &str = "assets/button1.png"; +pub static FRAME_PLAYABLE_PATH: &str = "assets/cards/frame_green.png"; +pub static FRAME_PLAYABLE_FOCUSED_PATH: &str = "assets/cards/frame_yellow.png"; +pub static FALSE_PLAYED_PATH: &str = "assets/cards/false_played.png"; + pub type PlayerId = u64; pub type SessionId = u64; @@ -165,3 +171,56 @@ pub struct Lobby { /// Contains tuples of (sender, message). pub chat: Vec<(String, String)>, } + +pub fn get_card_path(card: Card) -> String { + let mut path: String = "assets/cards/variations/".to_owned(); + if card.value == Value::Jester { + path.push_str("jester"); + } else if card.value == Value::Wizard { + path.push_str("wizard"); + } else { + match card.suit { + Suit::Blue => { + path.push_str("blue_"); + } + Suit::Green => { + path.push_str("green_"); + } + Suit::Red => { + path.push_str("red_"); + } + Suit::Yellow => { + path.push_str("yellow_"); + } + } + if let Value::Number(number) = card.value { + path.push_str(number.to_string().as_str()); + } + } + path.push_str(".png"); + path +} + +pub fn get_glow_path(card: Card) -> String { + let mut path: String = "assets/cards/".to_owned(); + if card.value == Value::Jester || card.value == Value::Wizard { + path.push_str(""); + } else { + match card.suit { + Suit::Blue => { + path.push_str("glow_blue"); + } + Suit::Green => { + path.push_str("glow_green"); + } + Suit::Red => { + path.push_str("glow_red"); + } + Suit::Yellow => { + path.push_str("glow_yellow"); + } + } + } + path.push_str(".png"); + path +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 204249c..761daaf 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -3,8 +3,11 @@ mod views; mod ws; use crate::api::{Card, Lobby, PlayerId, Suit}; +use crate::client::views::Button; use crate::gameplay_ui::hand::{HandMessage, ViewableHand}; -use iced::{time, window, Size, Subscription}; +use crate::gameplay_ui::table::{TableMessage, ViewableTable}; +use crate::ui_element_traits::Message; +use iced::{time, window, Size, Subscription, Task}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -56,6 +59,13 @@ pub enum MenuState { #[allow(dead_code)] PlayingTest, } + +impl Message for MenuState { + fn convert_msg_from(msg: Self) -> AppMessage { + AppMessage::Navigate(msg) + } +} + pub struct App { window_size: Size, @@ -100,12 +110,13 @@ pub struct App { // Gameplay view state pub viewable_hand: ViewableHand, + pub viewable_table: ViewableTable, // UI Buttons (main menu) pub btn_host: crate::client::views::Button, pub btn_join: crate::client::views::Button, pub btn_rules: crate::client::views::Button, - pub btn_exit: crate::client::views::Button, + pub btn_close: crate::client::views::Button, // Buttons for other menus pub btn_create_lobby: crate::client::views::Button, @@ -168,81 +179,22 @@ impl Default for App { winner: None, viewable_hand: ViewableHand::new(window_size), + viewable_table: ViewableTable::new(window_size), //Buttons - btn_host: crate::client::views::Button::new(0, "Host", "assets/button1.png", 180, 44), - btn_join: crate::client::views::Button::new( - 1, - "Beitreten", - "assets/button1.png", - 180, - 44, - ), - btn_rules: crate::client::views::Button::new( - 2, - "Spielregeln", - "assets/button1.png", - 180, - 44, - ), - btn_exit: crate::client::views::Button::new( - 3, - "Spiel verlassen", - "assets/button1.png", - 180, - 44, - ), - - btn_create_lobby: crate::client::views::Button::new( - 10, - "Lobby erstellen", - "assets/button1.png", - 160, - 40, - ), - btn_back: crate::client::views::Button::new( - 11, - "zurück", - "assets/button1.png", - 100, - 36, - ), - btn_connect: crate::client::views::Button::new( - 12, - "Verbinden", - "assets/button1.png", - 140, - 40, - ), - btn_send_chat: crate::client::views::Button::new( - 13, - "Senden", - "assets/button1.png", - 100, - 36, - ), - btn_start_game: crate::client::views::Button::new( - 14, - "Starten", - "assets/button1.png", - 140, - 40, - ), - btn_back_to_menu: crate::client::views::Button::new( - 15, - "Zurück zum Menü", - "assets/button1.png", - 160, - 40, - ), - - btn_ready_owned: crate::client::views::Button::new( - 20, - "Bereit", - "assets/button1.png", - 100, - 36, - ), + btn_host: Button::new_host_button(0, 180, 44), + btn_join: Button::new_join_button(1, 180, 44), + btn_rules: Button::new_rules_button(2, 180, 44), + btn_close: Button::new_close_button(3, 180, 44), + + btn_create_lobby: Button::new_create_lobby_button(10, 160, 40), + btn_back: Button::new_back_button(11, 100, 36), + btn_connect: Button::new_connect_button(12, 140, 40), + btn_send_chat: Button::new_send_chat_button(13, 100, 36), + btn_start_game: Button::new_start_game_button(14, 140, 40), + btn_back_to_menu: Button::new_back_to_menu_button(15, 160, 40), + + btn_ready_owned: Button::new_ready_owned_button(20, 100, 36), } } } @@ -264,7 +216,7 @@ pub enum AppMessage { CreateLobby, Connect, - ToggleReady(u64), + ToggleReady(PlayerId), StartGame, ServerTick, @@ -282,11 +234,45 @@ pub enum AppMessage { // Gameplay view messages HandMessage(HandMessage), + TableMessage(TableMessage), // Button messages from view widgets ButtonMessage(crate::client::views::ButtonMessage), } +impl Message for AppMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + msg + } +} + +pub struct TaskBatcher { + tasks: Vec>, +} + +impl TaskBatcher { + pub fn new() -> Self { + Self { tasks: vec![] } + } + pub fn push(&mut self, task: Task) { + if task.units() != 0 { + self.tasks.push(task) + } + } + #[allow(dead_code)] + pub fn push_mult(&mut self, tasks: [Task; SIZE]) { + self.tasks + .extend(tasks.into_iter().filter(|task| task.units() != 0)); + } + pub fn batch(self) -> Task { + Task::batch(self.tasks) + } + // AI-Usage: Gemini for learning how to put an array into a function and filter it. + pub fn instant_batch(tasks: [Task; SIZE]) -> Task { + Task::batch(tasks.into_iter().filter(|task| task.units() != 0)) + } +} + fn subscription(state: &App) -> Subscription { let mut subscriptions: Vec> = vec![]; subscriptions.push(window::resize_events().map(|(_, size)| AppMessage::WindowResized(size))); diff --git a/src/client/update.rs b/src/client/update.rs index 2a1c02f..0689ef4 100644 --- a/src/client/update.rs +++ b/src/client/update.rs @@ -2,11 +2,12 @@ use iced::Task; use std::sync::Arc; use super::{connect_ws, App, AppMessage, MenuState, PlayerCount}; -use crate::api::{Card, Lobby, ServerMessage, Value, B, C, S}; -use crate::ui_element_traits::{Animated, Message, Resizable}; +use crate::api::{Card, Lobby, PlayerId, ServerMessage, Value, B, C, S}; +use crate::client::TaskBatcher; +use crate::ui_element_traits::{Animated, Notifiable, Resizable}; -/// Helper function to get player name from ID using lobby data -fn get_player_name(state: &App, player_id: u64) -> String { +/// Get player name from ID using lobby data +fn get_player_name(state: &App, player_id: PlayerId) -> String { if state.my_id == Some(player_id) { return "You".to_string(); } @@ -36,7 +37,6 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { match msg { AppMessage::Navigate(menu) => { state.menu = menu; - Task::none() } AppMessage::Host => { let local_ip = crate::server::local_ip(); @@ -44,11 +44,9 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.ip = local_ip; state.menu = MenuState::Host; - Task::none() } AppMessage::HostNameChanged(name) => { state.host_name = name; - Task::none() } AppMessage::HostPlayerCountChanged(count) => { state.host_player_count = count; @@ -60,15 +58,12 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.last_msg = format!("Player count set to {count}"); } } - Task::none() } AppMessage::JoinNameChanged(name) => { state.join_name = name; - Task::none() } AppMessage::ServerAddressChanged(addr) => { state.ip = addr; - Task::none() } AppMessage::SendChat => { if let Ok(guard) = state.ws_tx.lock() { @@ -81,11 +76,9 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.chat_input.clear(); } } - Task::none() } AppMessage::ChatInputChanged(input) => { state.chat_input = input; - Task::none() } AppMessage::CreateLobby => { if !state.connected { @@ -115,7 +108,6 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { } } state.menu = MenuState::Lobby; - Task::none() } AppMessage::Connect => { if !state.connected { @@ -129,7 +121,6 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.msg = "Connecting...".into(); state.connecting = true; } - Task::none() } AppMessage::ToggleReady(player_to_toggle) => { @@ -156,21 +147,35 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.last_msg = format!("Set ready: {new_ready_state}"); } } - Task::none() } AppMessage::StartGame => { - if let Ok(guard) = state.ws_tx.lock() { - if let Some(ref tx) = *guard { - let _ = tx.send(C::StartGame); - state.last_msg = "Starting game...".to_string(); + let can_start = if cfg!(feature = "wiz_debug") { + true + } else { + state.lobby.as_ref().is_some_and(|lobby| { + lobby.players.len() == state.host_player_count.to_usize() + && lobby.players.iter().all(|p| p.ready) + }) + }; + let is_host = state.my_id.is_some() + && state.my_id.unwrap() + == state + .lobby + .as_ref() + .and_then(|l| l.players.iter().find(|p| p.is_host).map(|p| p.id)) + .unwrap_or_default(); + if can_start && is_host { + if let Ok(guard) = state.ws_tx.lock() { + if let Some(ref tx) = *guard { + let _ = tx.send(C::StartGame); + state.last_msg = "Starting game...".to_string(); + } } } - Task::none() } AppMessage::GameRules => { state.menu = MenuState::Rules; - Task::none() } AppMessage::BackToMenu => { // Stops the server if the player is the host; otherwise drops the connection. @@ -231,17 +236,15 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.game_over = false; state.winner = None; state.menu = MenuState::Main; - Task::none() } AppMessage::CloseGame => { - // Calls the Application exit. + // Calls the application to exit. std::process::exit(0); } // Gameplay message handlers AppMessage::BidInputChanged(input) => { state.bid_input = input; - Task::none() } AppMessage::SubmitBid => { if let Ok(amount) = state.bid_input.parse::() { @@ -257,7 +260,6 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { } else { state.last_msg = "Invalid bid - enter a number".to_string(); } - Task::none() } AppMessage::PlayCard(card) => { if let Ok(guard) = state.ws_tx.lock() { @@ -268,7 +270,6 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.game_log.push(log); } } - Task::none() } AppMessage::SetTrump(suit) => { if let Ok(guard) = state.ws_tx.lock() { @@ -280,123 +281,57 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.must_set_trump = false; } } - Task::none() } AppMessage::ServerTick => { handle_tick(state); - Task::none() } AppMessage::HandMessage(hand_msg) => { - state.viewable_hand.update_with_msg(hand_msg); - Task::none() + return state.viewable_hand.update_with_msg(hand_msg.clone()); + } + AppMessage::TableMessage(table_msg) => { + return state.viewable_table.update_with_msg(table_msg.clone()); } AppMessage::ButtonMessage(btn_msg) => { - // route to buttons (each button filters by id internally) - state.btn_host.update_with_msg(btn_msg.clone()); - state.btn_join.update_with_msg(btn_msg.clone()); - state.btn_rules.update_with_msg(btn_msg.clone()); - state.btn_exit.update_with_msg(btn_msg.clone()); - - state.btn_create_lobby.update_with_msg(btn_msg.clone()); - state.btn_back.update_with_msg(btn_msg.clone()); - state.btn_connect.update_with_msg(btn_msg.clone()); - state.btn_send_chat.update_with_msg(btn_msg.clone()); - state.btn_start_game.update_with_msg(btn_msg.clone()); - state.btn_back_to_menu.update_with_msg(btn_msg.clone()); - - state.btn_ready_owned.update_with_msg(btn_msg); - Task::none() + // Route to buttons (each button filters by id internally) + return TaskBatcher::instant_batch([ + state.btn_host.update_with_msg(btn_msg.clone()), + state.btn_join.update_with_msg(btn_msg.clone()), + state.btn_rules.update_with_msg(btn_msg.clone()), + state.btn_close.update_with_msg(btn_msg.clone()), + state.btn_create_lobby.update_with_msg(btn_msg.clone()), + state.btn_back.update_with_msg(btn_msg.clone()), + state.btn_connect.update_with_msg(btn_msg.clone()), + state.btn_send_chat.update_with_msg(btn_msg.clone()), + state.btn_start_game.update_with_msg(btn_msg.clone()), + state.btn_back_to_menu.update_with_msg(btn_msg.clone()), + state.btn_ready_owned.update_with_msg(btn_msg), + ]); } AppMessage::AnimationTick => { - state.viewable_hand.update_animations(); - - // Update button animations - state.btn_host.update_animations(); - state.btn_join.update_animations(); - state.btn_rules.update_animations(); - state.btn_exit.update_animations(); - - state.btn_create_lobby.update_animations(); - state.btn_back.update_animations(); - state.btn_connect.update_animations(); - state.btn_send_chat.update_animations(); - state.btn_start_game.update_animations(); - state.btn_back_to_menu.update_animations(); - state.btn_ready_owned.update_animations(); - - let mut pending_msgs: Vec> = Vec::new(); - - state.btn_host.check_click_end(|&_id| { - pending_msgs.push(Task::done(AppMessage::Host)); - }); - - state.btn_join.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::Navigate(MenuState::Join))); - }); - - state.btn_rules.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::GameRules)); - }); - - state.btn_exit.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::CloseGame)); - }); - - state.btn_create_lobby.check_click_end(|&_id| { - pending_msgs.push(Task::done(AppMessage::CreateLobby)); - }); - - state.btn_ready_owned.check_click_end(|&_id| { - if let Some(my_id) = state.my_id { - pending_msgs.push(Task::done(AppMessage::ToggleReady(my_id))); - } - }); - - state.btn_back.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::Navigate(MenuState::Main))); - }); - - state.btn_connect.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::Connect)); - }); - - state.btn_send_chat.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::SendChat)); - }); - - state.btn_start_game.check_click_end(|_| { - let can_start = if cfg!(feature = "wiz_debug") { - true - } else { - state.lobby.as_ref().is_some_and(|lobby| { - lobby.players.len() == state.host_player_count.to_usize() - && lobby.players.iter().all(|p| p.ready) - }) - }; - let is_host = state.my_id.is_some() - && state.my_id.unwrap() - == state - .lobby - .as_ref() - .and_then(|l| l.players.iter().find(|p| p.is_host).map(|p| p.id)) - .unwrap_or_default(); - if can_start && is_host { - pending_msgs.push(Task::done(AppMessage::StartGame)); - } - }); - - state.btn_back_to_menu.check_click_end(|_| { - pending_msgs.push(Task::done(AppMessage::BackToMenu)); - }); - - Task::batch(pending_msgs) + return TaskBatcher::instant_batch([ + state.viewable_hand.update_animations(), + state.viewable_table.update_animations(), + // Update button animations + state.btn_host.update_animations(), + state.btn_join.update_animations(), + state.btn_rules.update_animations(), + state.btn_close.update_animations(), + state.btn_create_lobby.update_animations(), + state.btn_back.update_animations(), + state.btn_connect.update_animations(), + state.btn_send_chat.update_animations(), + state.btn_start_game.update_animations(), + state.btn_back_to_menu.update_animations(), + state.btn_ready_owned.update_animations(), + ]); } - AppMessage::WindowResized(size) => { - state.window_size = size; - state.viewable_hand.update_size(size); - Task::none() + AppMessage::WindowResized(window_size) => { + state.window_size = window_size; + state.viewable_hand.update_size(window_size); + state.viewable_table.update_size(window_size); } } + Task::none() } fn handle_tick(state: &mut App) { @@ -450,6 +385,9 @@ fn handle_server_message(state: &mut App, msg: ServerMessage) { println!("{}", log); state.last_msg = log; state.my_id = Some(id); + state + .btn_ready_owned + .set_on_click(AppMessage::ToggleReady(id)); } S::Error { reason } => { let log = format!("[ERROR] {reason}"); diff --git a/src/client/views/button.rs b/src/client/views/button.rs index 1a4fa24..50b72a5 100644 --- a/src/client/views/button.rs +++ b/src/client/views/button.rs @@ -1,11 +1,25 @@ use derive_more::{Deref, DerefMut}; use iced::widget::{container, stack, text, Image, MouseArea}; +use iced::Task; -use crate::animation::{ - animation_end_sensor::AnimationEndSensor, BasicAnimation, Easing, ReversableBasicAnimation, -}; -use crate::client::AppMessage; -use crate::ui_element_traits::{Animated, Message}; +use crate::animation::{BasicAnimation, Easing, ReversableBasicAnimation}; +use crate::api::{PlayerId, BUTTON1_PATH}; +use crate::client::{AppMessage, MenuState, TaskBatcher}; +use crate::ui_element_traits::{Animated, Message, Notifiable}; + +#[derive(Debug, Clone)] +pub enum ButtonMessage { + Hovered(usize), + NotHovered(usize), + Clicked(usize), + ClickEnded(usize), +} + +impl Message for ButtonMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + AppMessage::ButtonMessage(msg) + } +} #[derive(Debug, Clone, Deref, DerefMut)] pub struct HoverAnim(ReversableBasicAnimation); @@ -38,63 +52,153 @@ impl ClickAnim { } } -#[derive(Debug, Clone)] -pub enum ButtonMessage { - Hovered(usize), - NotHovered(usize), - Clicked(usize), -} - #[derive(Debug)] pub struct Button { pub id: usize, - pub label: &'static str, + pub text: &'static str, img_path: &'static str, width: u16, height: u16, hover_animation: HoverAnim, click_animation: ClickAnim, - click_end_sensor: AnimationEndSensor, + on_click: AppMessage, } impl Button { - pub fn new( + fn new( id: usize, - label: &'static str, + text: &'static str, img_path: &'static str, width: u16, height: u16, + on_click: AppMessage, ) -> Self { - let click_duration: usize = 15; - Self { + let mut button = Self { id, - label, + text, img_path, width, height, hover_animation: HoverAnim::new(12), - click_animation: ClickAnim::new(click_duration), - click_end_sensor: AnimationEndSensor::new(click_duration), - } + click_animation: ClickAnim::new(15), + on_click, + }; + button + .click_animation + .on_end(ButtonMessage::ClickEnded(id).convert_msg()); + button } - pub fn check_click_end(&mut self, action: F) -> bool - where - F: FnOnce(&usize), - { - let finished = self.click_end_sensor.check(|h| { - if let Some(k) = h.content() { - action(k); - } - }); - if finished { - self.click_animation.reset(); - } - finished + pub fn new_host_button(id: usize, width: u16, height: u16) -> Self { + Self::new(id, "Host", BUTTON1_PATH, width, height, AppMessage::Host) + } + pub fn new_join_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Beitreten", + BUTTON1_PATH, + width, + height, + AppMessage::Navigate(MenuState::Join), + ) + } + pub fn new_rules_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Spielregeln", + BUTTON1_PATH, + width, + height, + AppMessage::GameRules, + ) + } + pub fn new_close_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Spiel verlassen", + BUTTON1_PATH, + width, + height, + AppMessage::CloseGame, + ) + } + pub fn new_create_lobby_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Lobby erstellen", + BUTTON1_PATH, + width, + height, + AppMessage::CreateLobby, + ) + } + pub fn new_ready_owned_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Bereit", + BUTTON1_PATH, + width, + height, + AppMessage::ToggleReady(0 as PlayerId), + ) + } + pub fn new_back_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Zurück", + BUTTON1_PATH, + width, + height, + MenuState::Main.convert_msg(), + ) + } + pub fn new_connect_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Verbinden", + BUTTON1_PATH, + width, + height, + AppMessage::Connect, + ) + } + pub fn new_send_chat_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Senden", + BUTTON1_PATH, + width, + height, + AppMessage::SendChat, + ) + } + pub fn new_start_game_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Starten", + BUTTON1_PATH, + width, + height, + AppMessage::StartGame, + ) + } + pub fn new_back_to_menu_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Zurück zum Menü", + BUTTON1_PATH, + width, + height, + AppMessage::BackToMenu, + ) + } + + pub fn set_on_click(&mut self, on_click: AppMessage) { + self.on_click = on_click; } pub fn view(&self) -> container::Container<'_, AppMessage> { - self.view_internal(self.label) + self.view_internal(self.text) } pub fn view_with_label<'a>(&self, label: &'a str) -> container::Container<'a, AppMessage> { @@ -137,7 +241,7 @@ impl Button { } pub fn view_disabled(&self) -> container::Container<'_, AppMessage> { - self.view_disabled_internal(self.label) + self.view_disabled_internal(self.text) } pub fn view_disabled_with_label<'a>( @@ -174,34 +278,42 @@ impl Button { } } -/// Implements traits -impl Message for Button { +impl Notifiable for Button { type OwnMessage = ButtonMessage; - fn convert_to_app_message(msg: ButtonMessage) -> AppMessage { - AppMessage::ButtonMessage(msg) - } - - fn update_with_msg(&mut self, msg: ButtonMessage) { + fn update_with_msg(&mut self, msg: ButtonMessage) -> Task { match msg { - ButtonMessage::Hovered(id) if id == self.id => { - self.hover_animation.start(); + ButtonMessage::Hovered(id) => { + if id == self.id { + self.hover_animation.start() + } + } + ButtonMessage::NotHovered(id) => { + if id == self.id { + self.hover_animation.reverse() + } } - ButtonMessage::NotHovered(id) if id == self.id => { - self.hover_animation.reverse(); + ButtonMessage::Clicked(id) => { + if id == self.id { + self.click_animation.start(); + } } - ButtonMessage::Clicked(id) if id == self.id => { - self.click_animation.start(); - self.click_end_sensor.start(Some(id)); + ButtonMessage::ClickEnded(id) => { + if id == self.id { + self.click_animation.reset(); + return self.on_click.convert_msg_to_task(); + } } - _ => {} } + Task::none() } } impl Animated for Button { - fn update_animations(&mut self) { - self.hover_animation.next_frame(); - self.click_animation.next_frame(); + fn update_animations(&mut self) -> Task { + TaskBatcher::instant_batch([ + self.hover_animation.next_frame(), + self.click_animation.next_frame(), + ]) } } diff --git a/src/client/views/menu.rs b/src/client/views/menu.rs index a74ac60..40af4f0 100644 --- a/src/client/views/menu.rs +++ b/src/client/views/menu.rs @@ -25,7 +25,7 @@ pub fn view_main_menu<'a>(state: &'a App) -> Element<'a, AppMessage> { let menu_right: Column<'a, AppMessage> = column![ state.btn_rules.view().padding(10), - state.btn_exit.view().padding(10), + state.btn_close.view().padding(10), ] .spacing(100) .align_x(iced::alignment::Horizontal::Right); diff --git a/src/client/views/utils.rs b/src/client/views/utils.rs index dc3a877..8469556 100644 --- a/src/client/views/utils.rs +++ b/src/client/views/utils.rs @@ -10,7 +10,7 @@ use crate::client::{App, AppMessage}; pub const TITLE_FONT: Font = Font::with_name("Magic School One"); -use crate::api::{Card, Value}; +use crate::api::{Card, PlayerId, Value}; /// Format a card for display (e.g., "5 Red", "Wizard", "Jester"). pub fn format_card(card: &Card) -> String { @@ -21,7 +21,7 @@ pub fn format_card(card: &Card) -> String { } } -pub fn get_player_name(state: &App, player_id: u64) -> String { +pub fn get_player_name(state: &App, player_id: PlayerId) -> String { state .lobby .as_ref() diff --git a/src/gamelogic/round.rs b/src/gamelogic/round.rs index a6906f1..81076e9 100644 --- a/src/gamelogic/round.rs +++ b/src/gamelogic/round.rs @@ -37,14 +37,7 @@ impl Round { if player_ids.is_empty() { panic!("Cannot create a round with no players"); } - let mut deck = vec![]; - for suit in Suit::iter() { - deck.push(Card::new(suit, Value::Jester)); - deck.push(Card::new(suit, Value::Wizard)); - for num in 1..=13 { - deck.push(Card::new(suit, Value::Number(num))); - } - } + let mut deck: Vec = build_deck(); let mut players = HashMap::new(); for id in player_ids { @@ -294,6 +287,18 @@ impl Round { } } +fn build_deck() -> Vec { + let mut deck = vec![]; + for suit in Suit::iter() { + deck.push(Card::new(suit, Value::Jester)); + deck.push(Card::new(suit, Value::Wizard)); + for num in 1..=13 { + deck.push(Card::new(suit, Value::Number(num))); + } + } + deck +} + fn draw_random_cards(deck: &mut Vec, count: usize) -> Vec { let mut cards = vec![]; @@ -309,3 +314,8 @@ fn draw_random_card(deck: &mut Vec) -> Card { let index = rng.random_range(0..deck.len()); deck.remove(index) } + +// A temporary function for getting a random card used for ViewableCard::build_test_cards. +pub fn random_card() -> Card { + draw_random_card(build_deck().as_mut()) +} diff --git a/src/gameplay_ui/hand/hand_card/animation_draw.rs b/src/gameplay_ui/hand/hand_card/animation_draw.rs index 1aa3116..52ebf02 100644 --- a/src/gameplay_ui/hand/hand_card/animation_draw.rs +++ b/src/gameplay_ui/hand/hand_card/animation_draw.rs @@ -1,7 +1,7 @@ use crate::animation::{BasicAnimation, Easing}; use derive_more::{Deref, DerefMut}; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct DrawAnimation(BasicAnimation); impl DrawAnimation { diff --git a/src/gameplay_ui/hand/hand_card/animation_false_played.rs b/src/gameplay_ui/hand/hand_card/animation_false_played.rs index 432eeaa..eaed1bf 100644 --- a/src/gameplay_ui/hand/hand_card/animation_false_played.rs +++ b/src/gameplay_ui/hand/hand_card/animation_false_played.rs @@ -1,7 +1,7 @@ use crate::animation::{AutoReversingAnimation, Easing}; use derive_more::{Deref, DerefMut}; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct FalsePlayedAnimation(AutoReversingAnimation); impl FalsePlayedAnimation { diff --git a/src/gameplay_ui/hand/hand_card/animation_focus.rs b/src/gameplay_ui/hand/hand_card/animation_focus.rs index a9587bf..3e403dd 100644 --- a/src/gameplay_ui/hand/hand_card/animation_focus.rs +++ b/src/gameplay_ui/hand/hand_card/animation_focus.rs @@ -1,7 +1,7 @@ use crate::animation::{CircularAutoReversingAnimation, Easing}; use derive_more::{Deref, DerefMut}; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct FocusAnimation(CircularAutoReversingAnimation); impl FocusAnimation { diff --git a/src/gameplay_ui/hand/hand_card/animation_hide.rs b/src/gameplay_ui/hand/hand_card/animation_hide.rs index ae8d27f..192b258 100644 --- a/src/gameplay_ui/hand/hand_card/animation_hide.rs +++ b/src/gameplay_ui/hand/hand_card/animation_hide.rs @@ -1,7 +1,7 @@ use crate::animation::{Easing, ReversableBasicAnimation}; use derive_more::{Deref, DerefMut}; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct HideAnimation(ReversableBasicAnimation); impl HideAnimation { diff --git a/src/gameplay_ui/hand/hand_card/animation_hover.rs b/src/gameplay_ui/hand/hand_card/animation_hover.rs index 4f08dd8..8044257 100644 --- a/src/gameplay_ui/hand/hand_card/animation_hover.rs +++ b/src/gameplay_ui/hand/hand_card/animation_hover.rs @@ -1,10 +1,10 @@ use crate::animation::{Easing, ReversableBasicAnimation}; -use crate::gameplay_ui::hand::hand_card::ViewableCard; +use crate::gameplay_ui::hand::hand_card::ViewableHandCard; use crate::ui_element_traits::SizeFromOutside; use derive_more::{Deref, DerefMut}; use iced::Size; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct HoverAnimation(ReversableBasicAnimation); impl HoverAnimation { @@ -13,7 +13,7 @@ impl HoverAnimation { } // The factor 0.15 changes the height of the hand in ViewableHand::height. pub fn get_offset(&self, window_size: Size) -> f32 { - ViewableCard::height_for(window_size) * 0.15 * self.progress(Easing::Linear) + ViewableHandCard::height_for(window_size) * 0.15 * self.progress(Easing::Linear) } // The factor 0.1 partially determines the hand width in ViewableHand::width_overflow_one_side. pub fn get_expansion(&self) -> f32 { diff --git a/src/gameplay_ui/hand/hand_card/animation_play.rs b/src/gameplay_ui/hand/hand_card/animation_play.rs index 51e6e98..865da3a 100644 --- a/src/gameplay_ui/hand/hand_card/animation_play.rs +++ b/src/gameplay_ui/hand/hand_card/animation_play.rs @@ -1,7 +1,7 @@ use crate::animation::{BasicAnimation, Easing}; use derive_more::{Deref, DerefMut}; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct PlayAnimation(BasicAnimation); impl PlayAnimation { diff --git a/src/gameplay_ui/hand/hand_card/animation_playable.rs b/src/gameplay_ui/hand/hand_card/animation_playable.rs index 709207b..6d79e6d 100644 --- a/src/gameplay_ui/hand/hand_card/animation_playable.rs +++ b/src/gameplay_ui/hand/hand_card/animation_playable.rs @@ -1,7 +1,7 @@ use crate::animation::{CircularAutoReversingAnimation, Easing}; use derive_more::{Deref, DerefMut}; -#[derive(Debug, Clone, Deref, DerefMut)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct PlayableAnimation(CircularAutoReversingAnimation); impl PlayableAnimation { diff --git a/src/gameplay_ui/hand/hand_card/mod.rs b/src/gameplay_ui/hand/hand_card/mod.rs index a695f25..4898d32 100644 --- a/src/gameplay_ui/hand/hand_card/mod.rs +++ b/src/gameplay_ui/hand/hand_card/mod.rs @@ -6,15 +6,21 @@ mod animation_hover; mod animation_play; mod animation_playable; -use crate::client::AppMessage; +use crate::api::{ + get_card_path, Card, FALSE_PLAYED_PATH, FRAME_PLAYABLE_FOCUSED_PATH, FRAME_PLAYABLE_PATH, +}; +use crate::client::{AppMessage, TaskBatcher}; use crate::gameplay_ui::hand::hand_card::{ animation_draw::DrawAnimation, animation_false_played::FalsePlayedAnimation, animation_focus::FocusAnimation, animation_hide::HideAnimation, animation_hover::HoverAnimation, animation_play::PlayAnimation, animation_playable::PlayableAnimation, }; -use crate::gameplay_ui::hand::{HandMessage, ViewableHand}; +use crate::gameplay_ui::hand::HandMessage; +use crate::gameplay_ui::table::middle::card_stack::CardStackMessage; +use crate::gameplay_ui::{card_height_hand, card_img_base_scale, card_width_hand}; use crate::ui_element_traits::*; +use iced::Task; use iced::{ mouse::Interaction, widget::{container, image, pin, stack, Container, MouseArea}, @@ -23,15 +29,6 @@ use iced::{ }; use std::ops::Not; -static FRAME_PLAYABLE_PATH: &str = "assets/cards/frame_green.png"; -static FRAME_PLAYABLE_FOCUSED_PATH: &str = "assets/cards/frame_yellow.png"; -static FALSE_PLAYED_PATH: &str = "assets/cards/false_played.png"; - -// The hand size is depending on the window size with the factor 0.1. -static CARD_WIDTH_MULT_WITH_WINDOW_WIDTH: f32 = 0.1; -// 1.54 is around 1245 / 806 (height to width ratio of a card image). -pub static CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH: f32 = CARD_WIDTH_MULT_WITH_WINDOW_WIDTH * 1.54; - pub fn f32_min_2(v1: f32, v2: f32) -> f32 { if v1 < v2 { return v1; @@ -50,32 +47,62 @@ pub enum CardMessage { Draw(usize), CursorMoved(usize, Point), ShowPlayableStatus(usize, bool), + MakeClickable(usize), +} + +impl Message for CardMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + HandMessage::convert_msg_from(HandMessage::CardMessage(msg)) + } +} + +impl ReplaceUsize for CardMessage { + fn replace_usize(&self, value: usize) -> Self { + match self { + CardMessage::Played(_) => CardMessage::Played(value), + CardMessage::FalsePlayed(_) => CardMessage::FalsePlayed(value), + CardMessage::Hovered(_) => CardMessage::Hovered(value), + CardMessage::NotHovered(_) => CardMessage::NotHovered(value), + CardMessage::Hide(_) => CardMessage::Hide(value), + CardMessage::Show(_) => CardMessage::Show(value), + CardMessage::Draw(_) => CardMessage::Draw(value), + CardMessage::CursorMoved(_, point) => CardMessage::CursorMoved(value, *point), + CardMessage::ShowPlayableStatus(_, bool) => { + CardMessage::ShowPlayableStatus(value, *bool) + } + CardMessage::MakeClickable(_) => CardMessage::MakeClickable(value), + } + } } #[derive(Debug, Clone)] -pub struct ViewableCard { - pub id: usize, - img_path: &'static str, - pub window_size: Size, - pub playable: bool, - pub show_playable_status: bool, - pub rotation: f32, - pub draw_animation: DrawAnimation, - pub hover_animation: HoverAnimation, - pub play_animation: PlayAnimation, - pub playable_animation: PlayableAnimation, - pub false_played_animation: FalsePlayedAnimation, - pub focus_animation: FocusAnimation, - pub hide_animation: HideAnimation, +pub struct ViewableHandCard { + id: usize, + card: Card, + img_path: String, + window_size: Size, + clickable: bool, + playable: bool, + show_playable_status: bool, + rotation: f32, + draw_animation: DrawAnimation, + hover_animation: HoverAnimation, + play_animation: PlayAnimation, + playable_animation: PlayableAnimation, + false_played_animation: FalsePlayedAnimation, + focus_animation: FocusAnimation, + hide_animation: HideAnimation, } -impl ViewableCard { - pub fn new(id: usize, img_path: &'static str, window_size: Size, playable: bool) -> Self { +impl ViewableHandCard { + pub fn new(id: usize, card: Card, window_size: Size, playable: bool) -> Self { let play_duration: usize = 12; - let mut card: ViewableCard = Self { + let mut viewable_card: ViewableHandCard = Self { id, - img_path, + card, + img_path: get_card_path(card), window_size, + clickable: true, playable, show_playable_status: false, rotation: 0.0, @@ -87,52 +114,68 @@ impl ViewableCard { focus_animation: FocusAnimation::new(70), hide_animation: HideAnimation::new(play_duration), }; - card.playable_animation.start(); - card + viewable_card + .play_animation + .on_end(HandMessage::DeleteCard(id).convert_msg()); + viewable_card + .hide_animation + .on_end(CardMessage::MakeClickable(id).convert_msg()); + viewable_card.playable_animation.start(); + viewable_card + } + + pub fn id(&self) -> usize { + self.id } } -impl Message for ViewableCard { +impl Notifiable for ViewableHandCard { type OwnMessage = CardMessage; - fn convert_to_app_message(msg: CardMessage) -> AppMessage { - ViewableHand::convert_to_app_message(HandMessage::CardMessage(msg)) - } - - fn update_with_msg(&mut self, msg: CardMessage) { + fn update_with_msg(&mut self, msg: CardMessage) -> Task { + let mut tb = TaskBatcher::new(); match msg { CardMessage::Hovered(id) => { if id == self.id { self.hover_animation.start(); self.focus_animation.start(); - } + } else { + // Sometimes on_exit for a viewed card won't register + // and won't send the CardNotHovered msg. + // To ensure that an unhovered card is not sticking up all the time + // send NotHovered to all cards except the hovered one. + tb.push(CardMessage::NotHovered(self.id).convert_msg_to_task()) + }; } CardMessage::Played(id) => { if id == self.id { + self.clickable = false; self.play_animation.start(); - } + tb.push(CardStackMessage::CardPlayed(self.card).convert_msg_to_task()); + }; } CardMessage::NotHovered(id) => { if id == self.id { self.hover_animation.reverse(); self.focus_animation.reset(); self.rotation = 0.0; - } + }; } CardMessage::Hide(id) => { if id == self.id { + self.clickable = false; self.hide_animation.start(); - } + }; } CardMessage::Show(id) => { if id == self.id { - self.hide_animation.start_from_reverse(); - } + self.hide_animation.reverse(); + }; } CardMessage::Draw(id) => { if id == self.id { self.draw_animation.start(); - } + }; } CardMessage::CursorMoved(id, point) => { if id == self.id { @@ -140,56 +183,64 @@ impl Message for ViewableCard { let factor_width: f32 = (point.x - halve_card_width) / halve_card_width; // The factor 0.05 is representing a 5% rotation offset on maximum. self.rotation = 0.05 * factor_width; - } + }; } CardMessage::FalsePlayed(id) => { if id == self.id { self.false_played_animation.start(); - } + }; } CardMessage::ShowPlayableStatus(id, do_show) => { if id == self.id { self.show_playable_status = do_show; + }; + } + CardMessage::MakeClickable(id) => { + if id == self.id { + self.clickable = true; } } } + tb.batch() } } -impl Animated for ViewableCard { - fn update_animations(&mut self) { - self.draw_animation.next_frame(); - self.hover_animation.next_frame(); - self.play_animation.next_frame(); - self.playable_animation.next_frame(); - self.false_played_animation.next_frame(); - self.focus_animation.next_frame(); - self.hide_animation.next_frame(); +impl Animated for ViewableHandCard { + fn update_animations(&mut self) -> Task { + TaskBatcher::instant_batch([ + self.draw_animation.next_frame(), + self.hover_animation.next_frame(), + self.play_animation.next_frame(), + self.playable_animation.next_frame(), + self.false_played_animation.next_frame(), + self.focus_animation.next_frame(), + self.hide_animation.next_frame(), + ]) } } -impl Resizable for ViewableCard { +impl Resizable for ViewableHandCard { fn update_size(&mut self, window_size: Size) { self.window_size = window_size; } fn width(&self) -> f32 { - self.window_size.width * CARD_WIDTH_MULT_WITH_WINDOW_WIDTH + card_width_hand(self.window_size) } fn height(&self) -> f32 { - self.window_size.width * CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH + card_height_hand(self.window_size) } } -impl SizeFromOutside for ViewableCard { +impl SizeFromOutside for ViewableHandCard { fn width_for(window_size: Size) -> f32 { - window_size.width * CARD_WIDTH_MULT_WITH_WINDOW_WIDTH + card_width_hand(window_size) } fn height_for(window_size: Size) -> f32 { - window_size.width * CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH + card_height_hand(window_size) } } -impl Viewable for ViewableCard { +impl Viewable for ViewableHandCard { /// DON'T USE THIS!!! /// /// Instead use view_and_move for a card with x & y at 0.0, @@ -219,11 +270,12 @@ impl Viewable for ViewableCard { let rotation: f32 = self.rotation; - // The factor 0.92 is chosen so the card will not get clipped when rotated. - let scale: f32 = 0.92 * self.hide_animation.get_scale() * self.draw_animation.get_scale(); + let scale: f32 = card_img_base_scale() + * self.hide_animation.get_scale() + * self.draw_animation.get_scale(); let mut card = stack!(); - let img = image(self.img_path) + let img = image(self.img_path.clone()) .content_fit(Fill) .width(width) .height(height) @@ -261,20 +313,15 @@ impl Viewable for ViewableCard { card = card.push(false_played_effect) } - let msg_hovered: AppMessage = - ViewableCard::convert_to_app_message(CardMessage::Hovered(self.id)); - let msg_not_hoverd: AppMessage = - ViewableCard::convert_to_app_message(CardMessage::NotHovered(self.id)); - let msg_show_playable_status = ViewableHand::convert_to_app_message( - HandMessage::ShowPlayableStatus(self.show_playable_status.not()), - ); + let msg_hovered: AppMessage = CardMessage::Hovered(self.id).convert_msg(); + let msg_not_hoverd: AppMessage = CardMessage::NotHovered(self.id).convert_msg(); + let msg_show_playable_status = + HandMessage::ShowPlayableStatus(self.show_playable_status.not()).convert_msg(); let card_id: usize = self.id; - let msg_cursor_moved = move |position: Point| { - ViewableCard::convert_to_app_message(CardMessage::CursorMoved(card_id, position)) - }; - let msg_played = ViewableCard::convert_to_app_message(CardMessage::Played(self.id)); - let msg_false_played = - ViewableCard::convert_to_app_message(CardMessage::FalsePlayed(self.id)); + let msg_cursor_moved = + move |position: Point| CardMessage::CursorMoved(card_id, position).convert_msg(); + let msg_played = CardMessage::Played(self.id).convert_msg(); + let msg_false_played = CardMessage::FalsePlayed(self.id).convert_msg(); let mut mouse_area = MouseArea::new(card) .on_enter(msg_hovered) diff --git a/src/gameplay_ui/hand/mod.rs b/src/gameplay_ui/hand/mod.rs index f0626ef..67e13df 100644 --- a/src/gameplay_ui/hand/mod.rs +++ b/src/gameplay_ui/hand/mod.rs @@ -1,56 +1,48 @@ -mod hand_card; +pub mod hand_card; -use crate::animation::{ - animation_end_sensor::AnimationEndSensor, animation_starter::AnimationStarter, -}; -use crate::client::AppMessage; -use crate::gameplay_ui::hand::hand_card::{CardMessage, ViewableCard}; +use crate::animation::AnimationStarter; +use crate::client::{AppMessage, TaskBatcher}; +use crate::gamelogic::round::random_card; +use crate::gameplay_ui::hand::hand_card::{CardMessage, ViewableHandCard}; +use crate::gameplay_ui::{card_column_step_hand, card_row_step_hand}; use crate::ui_element_traits::*; use iced::{ widget::{container, pin, stack, Container, Pin, Stack}, - Point, Size, + Point, Size, Task, }; use indexmap::{map::MutableKeys, IndexMap}; -use std::num::NonZero; - -use crate::gameplay_ui::hand::hand_card::CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH; - -static CARD1_PATH: &str = "assets/cards/back.png"; -static CARD2_PATH: &str = "assets/cards/diamond.png"; -static CARD3_PATH: &str = "assets/cards/heart.png"; -static CARD4_PATH: &str = "assets/cards/spade.png"; - -// Adjust this arbitrary value to manipulate the width of the hand relative to its size, -// but be careful that the cards don't go out of screen. -// If you want to manipulate the total hand size -// change the value of hand_card::CARD_WIDTH_MULT_WITH_WINDOW_WIDTH. -static CARD_STEP_MULT_WITH_CARD_WIDTH: f32 = 1.0 / 3.0; -// Adjust this arbitrary value to manipulate the width of the hand, -static CARD_ROW_STEP_MULT_WITH_WINDOW_WIDTH: f32 = CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH * 0.43; #[derive(Debug, Clone)] pub enum HandMessage { CardMessage(CardMessage), - DrawCards(Vec), + DeleteCard(usize), + DrawCards(Vec), HideCards, ShowCards, ShowPlayableStatus(bool), } +impl Message for HandMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + AppMessage::HandMessage(msg) + } +} + #[derive(Debug)] pub struct ViewableHand { window_size: Size, - pub cards: IndexMap, + pub cards: IndexMap, hovered_card_row_low: bool, + allow_hover: bool, hovered_card_id: Option, + played_card_id: Option, top_card_id_upper: Option, top_card_id_lower: Option, - hide_animation_end_sensor: AnimationEndSensor, // AI-Usage: Claude.ai for the idea of passing a union type for a generic // where the type doesn't matter. - draw_animation_starter: AnimationStarter<()>, + draw_animation_starter: AnimationStarter, } impl ViewableHand { @@ -59,45 +51,46 @@ impl ViewableHand { window_size, cards: IndexMap::from([]), hovered_card_row_low: true, + allow_hover: true, hovered_card_id: None, + played_card_id: None, top_card_id_upper: None, top_card_id_lower: None, - // 12 is the duration of the hide animation. - hide_animation_end_sensor: AnimationEndSensor::new(12), // 3 is the delay for the animation start. - draw_animation_starter: AnimationStarter::new(NonZero::new(3).unwrap()), + draw_animation_starter: AnimationStarter::new(3, 10, CardMessage::Draw(0)), } } - pub fn build_test_cards(window_size: Size) -> Vec { + /// This function is only for testing and may return impossible dupes of cards. + pub fn build_test_cards(window_size: Size) -> Vec { vec![ - ViewableCard::new(0, CARD1_PATH, window_size, true), - ViewableCard::new(1, CARD3_PATH, window_size, false), - ViewableCard::new(2, CARD2_PATH, window_size, true), - ViewableCard::new(3, CARD1_PATH, window_size, true), - ViewableCard::new(4, CARD4_PATH, window_size, true), - ViewableCard::new(5, CARD2_PATH, window_size, true), - ViewableCard::new(6, CARD1_PATH, window_size, true), - ViewableCard::new(7, CARD3_PATH, window_size, false), - ViewableCard::new(8, CARD2_PATH, window_size, true), - ViewableCard::new(9, CARD1_PATH, window_size, true), - ViewableCard::new(10, CARD4_PATH, window_size, true), - ViewableCard::new(11, CARD2_PATH, window_size, true), - ViewableCard::new(12, CARD1_PATH, window_size, true), - ViewableCard::new(13, CARD4_PATH, window_size, false), - ViewableCard::new(14, CARD2_PATH, window_size, true), - ViewableCard::new(15, CARD1_PATH, window_size, true), - ViewableCard::new(16, CARD1_PATH, window_size, true), - ViewableCard::new(17, CARD2_PATH, window_size, true), - ViewableCard::new(18, CARD3_PATH, window_size, true), - ViewableCard::new(19, CARD3_PATH, window_size, true), + ViewableHandCard::new(0, random_card(), window_size, true), + ViewableHandCard::new(1, random_card(), window_size, false), + ViewableHandCard::new(2, random_card(), window_size, true), + ViewableHandCard::new(3, random_card(), window_size, true), + ViewableHandCard::new(4, random_card(), window_size, true), + ViewableHandCard::new(5, random_card(), window_size, true), + ViewableHandCard::new(6, random_card(), window_size, true), + ViewableHandCard::new(7, random_card(), window_size, false), + ViewableHandCard::new(8, random_card(), window_size, true), + ViewableHandCard::new(9, random_card(), window_size, true), + ViewableHandCard::new(10, random_card(), window_size, true), + ViewableHandCard::new(11, random_card(), window_size, true), + ViewableHandCard::new(12, random_card(), window_size, true), + ViewableHandCard::new(13, random_card(), window_size, false), + ViewableHandCard::new(14, random_card(), window_size, true), + ViewableHandCard::new(15, random_card(), window_size, true), + ViewableHandCard::new(16, random_card(), window_size, true), + ViewableHandCard::new(17, random_card(), window_size, true), + ViewableHandCard::new(18, random_card(), window_size, true), + ViewableHandCard::new(19, random_card(), window_size, true), ] } - pub fn set_cards(&mut self, cards: Vec) { + pub fn set_cards(&mut self, cards: Vec) { self.cards.clear(); for card in cards.iter() { - self.cards.insert(card.id, card.clone()); + self.cards.insert(card.id(), card.clone()); } } @@ -111,25 +104,25 @@ impl ViewableHand { /// The return value represents the step length between cards in a row length of 10 cards. fn card_minimum_step(&self) -> f32 { - ViewableCard::width_for(self.window_size) * CARD_STEP_MULT_WITH_CARD_WIDTH + card_column_step_hand(ViewableHandCard::width_for(self.window_size)) } /// A card would normally be rendered anchored to the top of the hand. /// This function calculates the correct y position for the card. /// This takes in consideration the row step and the hover animation offset. fn card_y_offset_correction(&self) -> f32 { - self.height() - ViewableCard::height_for(self.window_size) + self.height() - ViewableHandCard::height_for(self.window_size) } fn upper_row_card_step(&self) -> f32 { if self.upper_row_exists() { match self.cards.len() { - 11..=14 => ViewableCard::width_for(self.window_size), + 11..=14 => ViewableHandCard::width_for(self.window_size), _ => { // A negative value is ok. let top_cards_without_last: f32 = self.cards.len() as f32 - 11.0; let upper_row_length: f32 = top_cards_without_last * self.card_minimum_step() - + ViewableCard::width_for(self.window_size); + + ViewableHandCard::width_for(self.window_size); self.card_minimum_step() + (self.width_without_animations() - upper_row_length) @@ -144,12 +137,12 @@ impl ViewableHand { fn lower_row_card_step(&self) -> f32 { if !self.upper_row_exists() { match self.cards.len() { - 1..=4 => ViewableCard::width_for(self.window_size), + 1..=4 => ViewableHandCard::width_for(self.window_size), _ => { // A negative value is ok. let cards_without_last: f32 = self.cards.len() as f32 - 1.0; let upper_row_length: f32 = cards_without_last * self.card_minimum_step() - + ViewableCard::width_for(self.window_size); + + ViewableHandCard::width_for(self.window_size); self.card_minimum_step() + (self.width_without_animations() - upper_row_length) / cards_without_last @@ -170,7 +163,7 @@ impl ViewableHand { // All cards are only shown within the range of the card step except one focused card. let row_len: f32 = ((self.upper_row_card_count() as f32) - 1.0) * self.upper_row_card_step() - + ViewableCard::width_for(self.window_size); + + ViewableHandCard::width_for(self.window_size); row_x_offset = (max_row_len - row_len) / 2.0; } @@ -185,7 +178,7 @@ impl ViewableHand { if !self.cards.is_empty() { // All cards are only shown within the range of the card step except one focused card. row_len = ((cards_in_row as f32) - 1.0) * self.lower_row_card_step() - + ViewableCard::width_for(self.window_size); + + ViewableHandCard::width_for(self.window_size); }; let row_x_offset: f32 = (max_row_len - row_len) / 2.0; let row_y_offset: f32 = 0.0; @@ -194,7 +187,7 @@ impl ViewableHand { } fn row_step(&self) -> f32 { - -self.window_size.width * CARD_ROW_STEP_MULT_WITH_WINDOW_WIDTH + -card_row_step_hand(self.window_size) } fn upper_row_exists(&self) -> bool { @@ -215,13 +208,13 @@ impl ViewableHand { // The hand reaches its maximum size with 10 cards. self.card_minimum_step() * 9.0 + // 9 cards only viewed within their offset // Only one card is fully visible (the hovered card). - ViewableCard::width_for(self.window_size) + ViewableHandCard::width_for(self.window_size) } fn width_overflow_one_side(&self) -> f32 { // A card can reach a max size bigger times 1.1 via the hover animation. // So it may increase the hand size on one side by self.card_base_size.width * 0.05. - ViewableCard::width_for(self.window_size) * 0.05 + ViewableHandCard::width_for(self.window_size) * 0.05 } fn width_overflow(&self) -> f32 { @@ -229,27 +222,26 @@ impl ViewableHand { self.width_overflow_one_side() * 2.0 } - fn update_cards_with_msg(&mut self, msg: CardMessage) { + fn update_cards_with_msg(&mut self, msg: CardMessage) -> Task { + let mut tb = TaskBatcher::new(); for (_, card) in self.cards.iter_mut() { - card.update_with_msg(msg.clone()); + tb.push(card.update_with_msg(msg.clone())) } + tb.batch() } } -impl Message for ViewableHand { +impl Notifiable for ViewableHand { type OwnMessage = HandMessage; - fn convert_to_app_message(msg: HandMessage) -> AppMessage { - AppMessage::HandMessage(msg) - } - - fn update_with_msg(&mut self, msg: HandMessage) { + fn update_with_msg(&mut self, msg: HandMessage) -> Task { + let mut tb = TaskBatcher::new(); match msg { HandMessage::CardMessage(card_msg) => { match card_msg { CardMessage::Hovered(id) => { - if !self.hide_animation_end_sensor.active() { - self.update_cards_with_msg(card_msg); + if self.allow_hover { + tb.push(self.update_cards_with_msg(card_msg)); self.hovered_card_id = Some(id); if self.upper_row_exists() && // AI-Usage: Claude.ai for learning how to see if value is in a vec @@ -265,27 +257,32 @@ impl Message for ViewableHand { } } CardMessage::Played(id) => { - if !self.draw_animation_starter.active() { - self.update_cards_with_msg(card_msg); - self.update_with_msg(HandMessage::HideCards); - self.hide_animation_end_sensor.start(Some(id)); - } + self.played_card_id = Some(id); + tb.push(self.update_cards_with_msg(card_msg)); + tb.push(HandMessage::HideCards.convert_msg_to_task()); } _ => { - self.update_cards_with_msg(card_msg); + tb.push(self.update_cards_with_msg(card_msg)); } } } HandMessage::HideCards => { + self.allow_hover = false; for (id, card) in self.cards.iter_mut() { - if !card.play_animation.moving_forward() { - card.update_with_msg(CardMessage::Hide(*id)); + if self.played_card_id.is_some() && self.played_card_id.unwrap() != *id { + tb.push(card.update_with_msg(CardMessage::Hide(*id))); } } + self.played_card_id = None; + } + HandMessage::DeleteCard(id) => { + self.cards.shift_remove(&id); + tb.push(HandMessage::ShowCards.convert_msg_to_task()); } HandMessage::ShowCards => { + self.allow_hover = true; for (id, card) in self.cards.iter_mut() { - card.update_with_msg(CardMessage::Show(*id)); + tb.push(card.update_with_msg(CardMessage::Show(*id))); } } HandMessage::DrawCards(cards) => { @@ -294,54 +291,28 @@ impl Message for ViewableHand { self.hovered_card_id = None; self.top_card_id_lower = None; self.top_card_id_upper = None; - self.draw_animation_starter.reset(); - self.hide_animation_end_sensor.reset(); self.update_size(self.window_size); - self.draw_animation_starter - .start(None, NonZero::new(self.cards.len()).unwrap()); + self.draw_animation_starter.start(self.cards.len()); } HandMessage::ShowPlayableStatus(do_show) => { for (id, card) in self.cards.iter_mut() { - card.update_with_msg(CardMessage::ShowPlayableStatus(*id, do_show)); + tb.push(card.update_with_msg(CardMessage::ShowPlayableStatus(*id, do_show))); } } } + tb.batch() } } impl Animated for ViewableHand { - fn update_animations(&mut self) { - self.draw_animation_starter - .check(|d: &mut AnimationStarter<()>| { - let id: usize = self.cards[d.cycle()].id; - self.cards[d.cycle()].update_with_msg(CardMessage::Draw(id)); - }); - - for (id, card) in self.cards.iter_mut2() { - card.update_animations(); - // Sometimes on_exit for a viewed card won't register - // and won't send the CardNotHoverd msg. - // To ensure that an unhovered card is not sticking up all the time - // following if-statement tries to check for this unwanted state - // and instead sends the msg itself. - if self.hovered_card_id.is_some() - && *id != self.hovered_card_id.unwrap() - && card.hover_animation.get_offset(self.window_size) != 0.0 - && card.hover_animation.not_moving() - { - card.update_with_msg(CardMessage::NotHovered(*id)); - } - } + fn update_animations(&mut self) -> Task { + let mut tb = TaskBatcher::new(); + tb.push(self.draw_animation_starter.next_frame()); - if self - .hide_animation_end_sensor - .check(|h: &mut AnimationEndSensor| { - let key: &usize = h.content().unwrap(); - self.cards.shift_remove(key); - }) - { - self.update_with_msg(HandMessage::ShowCards); - }; + for (_, card) in self.cards.iter_mut2() { + tb.push(card.update_animations()); + } + tb.batch() } } @@ -356,10 +327,10 @@ impl Resizable for ViewableHand { self.width_without_animations() + self.width_overflow() } fn height(&self) -> f32 { - ViewableCard::height_for(self.window_size) - // Upper card height + ViewableHandCard::height_for(self.window_size) - // Upper card height self.row_step() + // Upper card spawn offset // The upper card may have an increased size via the hover animation max_offset. - ViewableCard::height_for(self.window_size) * 0.15 + ViewableHandCard::height_for(self.window_size) * 0.15 } } diff --git a/src/gameplay_ui/mod.rs b/src/gameplay_ui/mod.rs index cfce074..302a9c5 100644 --- a/src/gameplay_ui/mod.rs +++ b/src/gameplay_ui/mod.rs @@ -1,2 +1,61 @@ #![allow(dead_code)] pub mod hand; +pub mod table; + +use iced::{Point, Size}; + +// The hand size is depending on the window size with the factor 0.1. +static CARD_WIDTH_MULT_WITH_WINDOW_WIDTH: f32 = 0.1; +// 1.54 is around 1245 / 806 (height to width ratio of a card image). +pub static CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH: f32 = CARD_WIDTH_MULT_WITH_WINDOW_WIDTH * 1.54; + +// Adjust this arbitrary value to manipulate the width of the hand relative to its size, +// but be careful that the cards don't go out of screen. +// If you want to manipulate the total hand size +// change the value of hand_card::CARD_WIDTH_MULT_WITH_WINDOW_WIDTH. +static CARD_COLUMN_STEP_MULT_WITH_CARD_WIDTH: f32 = 1.0 / 3.0; +// Adjust this arbitrary value to manipulate the height of the hand, +static CARD_ROW_STEP_MULT_WITH_WINDOW_WIDTH: f32 = CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH * 0.43; + +// The factor is chosen so the card img will not get clipped when rotated. +static CARD_IMG_BASE_SCALE: f32 = 0.92; +static CARD_IMG_MIDDLE_BASE_SCALE: f32 = 0.8; + +static CARD_AREA_MIDDLE_RELATION: f32 = 1.3; + +fn card_width_hand(window_size: Size) -> f32 { + CARD_WIDTH_MULT_WITH_WINDOW_WIDTH * window_size.width +} +fn card_height_hand(window_size: Size) -> f32 { + CARD_HEIGHT_MULT_WITH_WINDOW_WIDTH * window_size.width +} +fn card_column_step_hand(card_width: f32) -> f32 { + CARD_COLUMN_STEP_MULT_WITH_CARD_WIDTH * card_width +} +fn card_row_step_hand(window_size: Size) -> f32 { + CARD_ROW_STEP_MULT_WITH_WINDOW_WIDTH * window_size.width +} +fn card_img_base_scale() -> f32 { + CARD_IMG_BASE_SCALE +} +fn card_img_middle_base_scale() -> f32 { + CARD_IMG_MIDDLE_BASE_SCALE +} +fn card_width_middle(window_size: Size) -> f32 { + card_width_hand(window_size) * (card_img_base_scale() / card_img_middle_base_scale()) +} +fn card_height_middle(window_size: Size) -> f32 { + card_height_hand(window_size) * (card_img_base_scale() / card_img_middle_base_scale()) +} +fn card_area_middle_space_width(window_size: Size) -> f32 { + card_width_middle(window_size) * CARD_AREA_MIDDLE_RELATION +} +fn card_area_middle_space_height(window_size: Size) -> f32 { + card_height_middle(window_size) * CARD_AREA_MIDDLE_RELATION +} +fn card_area_middle_spawn_point(width: f32, height: f32, window_size: Size) -> Point { + Point::new( + (card_area_middle_space_width(window_size) - width) / 2.0, + (card_area_middle_space_height(window_size) - height) / 2.0, + ) +} diff --git a/src/gameplay_ui/table/middle/card_deck/deck_card.rs b/src/gameplay_ui/table/middle/card_deck/deck_card.rs new file mode 100644 index 0000000..d9f1a4c --- /dev/null +++ b/src/gameplay_ui/table/middle/card_deck/deck_card.rs @@ -0,0 +1,127 @@ +use crate::{ + animation::{Easing, ReversableBasicAnimation}, + api::CARD_BACK_PATH, + client::AppMessage, + gameplay_ui::{ + card_height_middle, card_img_middle_base_scale, card_width_middle, + CARD_AREA_MIDDLE_RELATION, + }, + ui_element_traits::*, +}; +use derive_more::{Deref, DerefMut}; +use iced::{ + widget::{image, Container}, + Point, Size, Task, +}; + +#[derive(Debug, Clone)] +pub enum Direction { + Up, + Right, + Down, + Left, +} + +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct DealAnimation(ReversableBasicAnimation); + +impl DealAnimation { + fn new(duration: usize) -> Self { + Self(ReversableBasicAnimation::new(duration)) + } + fn get_offset(&self) -> f32 { + self.progress(Easing::OutCubic) + } + fn get_opacity(&self) -> f32 { + 1.0 - self.progress(Easing::OutCubic) + } +} + +#[derive(Debug, Clone)] +pub struct ViewableDeckCard { + window_size: Size, + add: bool, + direction: Direction, + deal_animation: DealAnimation, +} + +impl ViewableDeckCard { + pub fn new(window_size: Size, cycle: usize, add: bool) -> Self { + let mut viewable_deck_card = Self { + window_size, + add, + direction: Self::choose_direction(cycle), + deal_animation: DealAnimation::new(10), + }; + viewable_deck_card.deal_animation.start(); + viewable_deck_card + } + pub fn offset(&self) -> Point { + let mut linear_progress: f32 = self.deal_animation.get_offset(); + if self.add { + linear_progress = 1.0 - linear_progress; + } + let length = + ((CARD_AREA_MIDDLE_RELATION - 1.0) / 2.0) * card_width_middle(self.window_size); + let horizontal_offset: f32 = length; + let vertical_offset: f32 = length; + match self.direction { + Direction::Down => Point::new(0.0, linear_progress * horizontal_offset), + Direction::Left => Point::new(-linear_progress * vertical_offset, 0.0), + Direction::Right => Point::new(linear_progress * vertical_offset, 0.0), + Direction::Up => Point::new(0.0, -linear_progress * horizontal_offset), + } + } + fn choose_direction(cycle: usize) -> Direction { + let direction_number: usize = cycle % 4; + match direction_number { + 0 => Direction::Left, + 1 => Direction::Up, + 2 => Direction::Right, + _ => Direction::Down, + } + } +} + +impl Animated for ViewableDeckCard { + fn update_animations(&mut self) -> Task { + self.deal_animation.next_frame() + } +} + +impl Resizable for ViewableDeckCard { + fn height(&self) -> f32 { + card_height_middle(self.window_size) + } + fn width(&self) -> f32 { + card_width_middle(self.window_size) + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size + } +} + +impl SizeFromOutside for ViewableDeckCard { + fn height_for(window_size: Size) -> f32 { + card_height_middle(window_size) + } + fn width_for(window_size: Size) -> f32 { + card_width_middle(window_size) + } +} + +impl Viewable for ViewableDeckCard { + fn view<'a>(&self) -> Container<'a, AppMessage> { + let opacity = if self.add { + 1.0 - self.deal_animation.get_opacity() + } else { + self.deal_animation.get_opacity() + }; + let img = image(CARD_BACK_PATH.to_string()) + .width(self.width()) + .height(self.height()) + .scale(card_img_middle_base_scale()) + .opacity(opacity); + Container::new(img) + } +} diff --git a/src/gameplay_ui/table/middle/card_deck/glow_card.rs b/src/gameplay_ui/table/middle/card_deck/glow_card.rs new file mode 100644 index 0000000..c55329e --- /dev/null +++ b/src/gameplay_ui/table/middle/card_deck/glow_card.rs @@ -0,0 +1,122 @@ +use crate::{ + animation::{CircularAutoReversingAnimation, Easing, ReversableBasicAnimation}, + api::{get_glow_path, Card}, + client::{AppMessage, TaskBatcher}, + gameplay_ui::{ + card_height_middle, card_width_middle, table::middle::card_deck::CardDeckMessage, + }, + ui_element_traits::{Animated, Message, Notifiable, Resizable, Viewable}, +}; +use derive_more::{Deref, DerefMut}; +use iced::{ + widget::{image, Container}, + Size, Task, +}; + +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct RevealAnimation(ReversableBasicAnimation); + +impl RevealAnimation { + pub fn new(duration: usize) -> Self { + Self(ReversableBasicAnimation::new(duration)) + } + pub fn get_opacity(&self) -> f32 { + self.progress(Easing::InSine) + } +} + +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct GlowAnimation(CircularAutoReversingAnimation); + +impl GlowAnimation { + pub fn new(duration: usize) -> Self { + Self(CircularAutoReversingAnimation::new(duration)) + } + pub fn get_opacity(&self) -> f32 { + 0.7 + 0.3 * self.progress(Easing::InSine) + } +} + +#[derive(Debug, Clone)] +pub enum GlowMessage { + ResetColor, +} + +impl Message for GlowMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + CardDeckMessage::convert_msg_from(CardDeckMessage::GlowMessage(msg)) + } +} + +pub struct CardStackGlow { + window_size: Size, + img_path: String, + pub reveal_animation: RevealAnimation, + pub glow_animation: GlowAnimation, +} + +impl CardStackGlow { + pub fn new(window_size: Size) -> Self { + let mut card_stack_glow = Self { + window_size, + img_path: "".to_string(), + reveal_animation: RevealAnimation::new(30), + glow_animation: GlowAnimation::new(60), + }; + card_stack_glow + .reveal_animation + .on_start(GlowMessage::ResetColor.convert_msg()); + card_stack_glow.glow_animation.start(); + card_stack_glow + } + pub fn change_color(&mut self, card: Card) { + self.img_path = get_glow_path(card); + } +} + +impl Notifiable for CardStackGlow { + type OwnMessage = GlowMessage; + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task { + match msg { + GlowMessage::ResetColor => { + self.img_path = "".to_string(); + } + } + Task::none() + } +} + +impl Animated for CardStackGlow { + fn update_animations(&mut self) -> iced::Task { + TaskBatcher::instant_batch([ + self.reveal_animation.next_frame(), + self.glow_animation.next_frame(), + ]) + } +} + +impl Resizable for CardStackGlow { + fn height(&self) -> f32 { + card_height_middle(self.window_size) + } + fn width(&self) -> f32 { + card_width_middle(self.window_size) + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size + } +} + +impl Viewable for CardStackGlow { + fn view<'a>(&self) -> Container<'a, AppMessage> { + let img = image(self.img_path.to_string()) + .width(self.width()) + .height(self.height()) + .opacity( + self.glow_animation + .get_opacity() + .min(self.reveal_animation.get_opacity()), + ); + Container::new(img) + } +} diff --git a/src/gameplay_ui/table/middle/card_deck/mod.rs b/src/gameplay_ui/table/middle/card_deck/mod.rs new file mode 100644 index 0000000..ea5f687 --- /dev/null +++ b/src/gameplay_ui/table/middle/card_deck/mod.rs @@ -0,0 +1,269 @@ +pub mod deck_card; +pub mod glow_card; +pub mod trump_card; + +use crate::{ + animation::AnimationStarter, + api::{Card, CARD_BACK_PATH}, + client::{AppMessage, TaskBatcher}, + gameplay_ui::{ + card_area_middle_space_height, card_area_middle_space_width, card_area_middle_spawn_point, + card_img_middle_base_scale, + hand::ViewableHand, + table::{ + middle::{ + card_deck::{ + deck_card::ViewableDeckCard, + glow_card::{CardStackGlow, GlowMessage}, + trump_card::{TrumpCardMessage, ViewableTrumpCard}, + }, + card_stack::CardStackMessage, + TableMiddleMessage, + }, + HandMessage, + }, + }, + ui_element_traits::*, +}; +use iced::{ + widget::{image, pin, stack, Container}, + Size, Task, +}; + +type TrumpCard = Card; + +#[derive(Debug, Clone)] +pub enum CardDeckMessage { + AllDealt, + AllCleared, + Deal(usize, Option), + Shuffle, + ClearTrumpCard, + AddDeckCard(usize), + DealDeckCard(usize), + TrumpCardMessage(TrumpCardMessage), + ChangeGlow(Card), + GlowMessage(GlowMessage), + ShowGlow, +} + +impl Message for CardDeckMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + TableMiddleMessage::convert_msg_from(TableMiddleMessage::CardDeckMessage(msg)) + } +} + +impl ReplaceUsize for CardDeckMessage { + fn replace_usize(&self, value: usize) -> Self { + match self { + CardDeckMessage::DealDeckCard(_) => CardDeckMessage::DealDeckCard(value), + CardDeckMessage::AddDeckCard(_) => CardDeckMessage::AddDeckCard(value), + CardDeckMessage::Deal(_, trump_card) => CardDeckMessage::Deal(value, *trump_card), + CardDeckMessage::AllDealt => self.clone(), + CardDeckMessage::AllCleared => self.clone(), + CardDeckMessage::TrumpCardMessage(_) => self.clone(), + CardDeckMessage::ClearTrumpCard => self.clone(), + CardDeckMessage::Shuffle => self.clone(), + CardDeckMessage::ChangeGlow(_) => self.clone(), + CardDeckMessage::GlowMessage(_) => self.clone(), + CardDeckMessage::ShowGlow => self.clone(), + } + } +} + +pub struct ViewableCardDeck { + window_size: Size, + glow: CardStackGlow, + show_base_card: bool, + deal_msg: Option, + trump_card: Option, + deck_cards: Vec, + deal_card_animation_starter: AnimationStarter, + clear_card_animation_starter: AnimationStarter, +} + +impl ViewableCardDeck { + pub fn new(window_size: Size) -> Self { + let mut viewable_card_deck = Self { + window_size, + glow: CardStackGlow::new(window_size), + show_base_card: true, + deal_msg: None, + trump_card: None, + deck_cards: Vec::new(), + deal_card_animation_starter: AnimationStarter::new( + 3, + 10, + CardDeckMessage::DealDeckCard(0), + ), + clear_card_animation_starter: AnimationStarter::new( + 3, + 10, + CardDeckMessage::AddDeckCard(0), + ), + }; + viewable_card_deck + .deal_card_animation_starter + .on_all_ended(CardDeckMessage::AllDealt); + viewable_card_deck + .clear_card_animation_starter + .on_all_ended(CardDeckMessage::AllCleared); + viewable_card_deck + } +} + +impl Notifiable for ViewableCardDeck { + type OwnMessage = CardDeckMessage; + + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task { + match msg { + CardDeckMessage::AddDeckCard(cycle) => { + let view_able_deck_card = ViewableDeckCard::new(self.window_size, cycle, true); + self.deck_cards.push(view_able_deck_card); + } + CardDeckMessage::DealDeckCard(cycle) => { + let view_able_deck_card = ViewableDeckCard::new(self.window_size, cycle, false); + self.deck_cards.push(view_able_deck_card); + } + CardDeckMessage::ClearTrumpCard => { + self.trump_card = None; + self.clear_card_animation_starter + .start(self.deal_card_animation_starter.times()); + } + CardDeckMessage::Deal(cards, trump_card) => { + let mut tb = TaskBatcher::new(); + if self.deal_msg.is_none() { + self.deal_msg = Some(CardDeckMessage::Deal(cards, trump_card)); + self.deal_card_animation_starter.start(cards); + if let Some(card) = trump_card { + self.trump_card = Some(ViewableTrumpCard::new(self.window_size, card)); + } else { + self.trump_card = None; + } + tb.push( + HandMessage::DrawCards(ViewableHand::build_test_cards(self.window_size)) + .convert_msg_to_task(), + ); + } else { + self.deal_msg = Some(CardDeckMessage::Deal(cards, trump_card)); + tb.push(CardDeckMessage::Shuffle.convert_msg_to_task()); + tb.push(CardStackMessage::HideAllCard.convert_msg_to_task()); + } + return tb.batch(); + } + CardDeckMessage::AllDealt => { + self.deck_cards.clear(); + return TrumpCardMessage::TurnPart1.convert_msg_to_task(); + } + CardDeckMessage::AllCleared => { + if self.deal_msg.is_some() { + let deal_msg_copy: Option = self.deal_msg.clone(); + self.deal_msg = None; + return deal_msg_copy.unwrap().convert_msg_to_task(); + } + } + CardDeckMessage::TrumpCardMessage(trump_card_msg) => { + if let Some(trump_card) = self.trump_card.as_mut() { + return trump_card.update_with_msg(trump_card_msg); + } + } + CardDeckMessage::Shuffle => { + self.glow.reveal_animation.reverse(); + return TrumpCardMessage::RemovePart1.convert_msg_to_task(); + } + CardDeckMessage::ChangeGlow(card) => { + self.glow.change_color(card); + } + CardDeckMessage::GlowMessage(glow_msg) => { + return self.glow.update_with_msg(glow_msg); + } + CardDeckMessage::ShowGlow => { + self.glow.reveal_animation.start_force(); + } + } + Task::none() + } +} + +impl Animated for ViewableCardDeck { + fn update_animations(&mut self) -> Task { + let mut tb = TaskBatcher::new(); + tb.push(self.deal_card_animation_starter.next_frame()); + tb.push(self.clear_card_animation_starter.next_frame()); + if let Some(trump_card) = self.trump_card.as_mut() { + tb.push(trump_card.update_animations()); + } + tb.push(self.glow.update_animations()); + for card in self.deck_cards.iter_mut() { + tb.push(card.update_animations()); + } + tb.batch() + } +} + +impl Resizable for ViewableCardDeck { + fn height(&self) -> f32 { + card_area_middle_space_height(self.window_size) + } + fn width(&self) -> f32 { + card_area_middle_space_width(self.window_size) + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size; + for card in self.deck_cards.iter_mut() { + card.update_size(window_size); + } + if let Some(trump_card) = self.trump_card.as_mut() { + trump_card.update_size(window_size); + } + self.glow.update_size(window_size); + } +} + +impl Viewable for ViewableCardDeck { + fn view<'a>(&self) -> Container<'a, AppMessage> { + let mut card_stack = stack!(); + + let width: f32 = ViewableDeckCard::width_for(self.window_size); + let height: f32 = ViewableDeckCard::height_for(self.window_size); + let spawn_point: iced::Point = + card_area_middle_spawn_point(width, height, self.window_size); + card_stack = card_stack.push(self.glow.view_and_move(spawn_point.x, spawn_point.y)); + if self.show_base_card { + // Construct base card template + let base_card = pin(image(CARD_BACK_PATH.to_string()) + .width(width) + .height(height) + .scale(card_img_middle_base_scale())) + .position(spawn_point); + card_stack = card_stack.push(base_card); + } + for card in self.deck_cards.iter() { + let spawn_point = + card_area_middle_spawn_point(card.width(), card.height(), self.window_size); + card_stack = card_stack.push(card.view_and_move( + spawn_point.x + card.offset().x, + spawn_point.y + card.offset().y, + )); + } + if let Some(trump_card) = self.trump_card.as_ref() { + let spawn_point = card_area_middle_spawn_point( + trump_card.width(), + trump_card.height(), + self.window_size, + ); + card_stack = card_stack.push(trump_card.view_and_move(spawn_point.x, spawn_point.y)); + } else { + // Construct base card template + let base_card = pin(image(CARD_BACK_PATH.to_string()) + .width(width) + .height(height) + .scale(card_img_middle_base_scale())) + .position(spawn_point); + card_stack = card_stack.push(base_card); + } + Container::new(card_stack) + .width(self.width()) + .height(self.height()) + } +} diff --git a/src/gameplay_ui/table/middle/card_deck/trump_card.rs b/src/gameplay_ui/table/middle/card_deck/trump_card.rs new file mode 100644 index 0000000..409f168 --- /dev/null +++ b/src/gameplay_ui/table/middle/card_deck/trump_card.rs @@ -0,0 +1,132 @@ +use crate::{ + animation::{AutoReversingAnimation, Easing}, + api::{get_card_path, CARD_BACK_PATH}, + client::{AppMessage, TaskBatcher}, + gameplay_ui::{ + card_height_middle, card_img_middle_base_scale, card_width_middle, + table::middle::card_deck::{Card, CardDeckMessage}, + }, + ui_element_traits::*, +}; +use derive_more::{Deref, DerefMut}; +use iced::{ + widget::{image, Container}, + Size, Task, +}; + +#[derive(Debug, Clone)] +pub enum TrumpCardMessage { + TurnPart1, + TurnPart2, + RemovePart1, + RemovePart2, +} + +impl Message for TrumpCardMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + CardDeckMessage::convert_msg_from(CardDeckMessage::TrumpCardMessage(msg)) + } +} + +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct TurnAnimation(AutoReversingAnimation); + +impl TurnAnimation { + fn new(duration: usize) -> Self { + Self(AutoReversingAnimation::new(duration)) + } + fn get_contraction(&self) -> f32 { + 1.0 - self.progress(Easing::InSine) + } +} + +#[derive(Debug, Clone)] +pub struct ViewableTrumpCard { + window_size: Size, + trump_card: Card, + show_back: bool, + reveal_animation: TurnAnimation, + remove_animation: TurnAnimation, +} + +impl ViewableTrumpCard { + pub fn new(window_size: Size, trump_card: Card) -> Self { + let mut viewable_trump_card = Self { + window_size, + trump_card, + show_back: true, + reveal_animation: TurnAnimation::new(10), + remove_animation: TurnAnimation::new(10), + }; + viewable_trump_card + .reveal_animation + .on_end(TrumpCardMessage::TurnPart2.convert_msg()); + viewable_trump_card + .remove_animation + .on_end(TrumpCardMessage::RemovePart2.convert_msg()); + viewable_trump_card + .remove_animation + .on_start(CardDeckMessage::ClearTrumpCard.convert_msg()); + viewable_trump_card + } +} + +impl Notifiable for ViewableTrumpCard { + type OwnMessage = TrumpCardMessage; + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task { + match msg { + TrumpCardMessage::TurnPart1 => { + self.reveal_animation.start(); + } + TrumpCardMessage::TurnPart2 => { + self.show_back = false; + } + TrumpCardMessage::RemovePart1 => { + self.remove_animation.start(); + } + TrumpCardMessage::RemovePart2 => { + self.show_back = true; + } + } + Task::none() + } +} + +impl Animated for ViewableTrumpCard { + fn update_animations(&mut self) -> Task { + TaskBatcher::instant_batch([ + self.reveal_animation.next_frame(), + self.remove_animation.next_frame(), + ]) + } +} + +impl Resizable for ViewableTrumpCard { + fn height(&self) -> f32 { + card_height_middle(self.window_size) + } + fn width(&self) -> f32 { + card_width_middle(self.window_size) + * self.reveal_animation.get_contraction() + * self.remove_animation.get_contraction() + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size + } +} + +impl Viewable for ViewableTrumpCard { + fn view<'a>(&self) -> Container<'a, AppMessage> { + let img_path = if !self.show_back { + get_card_path(self.trump_card) + } else { + CARD_BACK_PATH.to_string() + }; + let img = image(img_path) + .scale(card_img_middle_base_scale()) + .content_fit(iced::ContentFit::Fill); + Container::new(img) + .width(self.width()) + .height(self.height()) + } +} diff --git a/src/gameplay_ui/table/middle/card_stack/mod.rs b/src/gameplay_ui/table/middle/card_stack/mod.rs new file mode 100644 index 0000000..e85f4ca --- /dev/null +++ b/src/gameplay_ui/table/middle/card_stack/mod.rs @@ -0,0 +1,140 @@ +pub mod stack_card; + +use crate::{ + animation::AnimationStarter, + api::Card, + client::{AppMessage, TaskBatcher}, + gameplay_ui::{ + card_area_middle_space_height, card_area_middle_space_width, card_area_middle_spawn_point, + table::middle::{ + card_deck::CardDeckMessage, card_stack::stack_card::ViewableStackCard, + TableMiddleMessage, + }, + }, + ui_element_traits::*, +}; +use iced::{ + widget::{Container, Stack}, + Size, Task, +}; + +#[derive(Debug, Clone)] +pub enum CardStackMessage { + CardPlayed(Card), + HideAllCard, + HideCard(usize), + RemoveAllCards, +} + +impl Message for CardStackMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + TableMiddleMessage::convert_msg_from(TableMiddleMessage::CardStackMessage(msg)) + } +} + +impl ReplaceUsize for CardStackMessage { + fn replace_usize(&self, value: usize) -> Self { + match self { + CardStackMessage::HideCard(_) => CardStackMessage::HideCard(value), + CardStackMessage::HideAllCard => self.clone(), + CardStackMessage::CardPlayed(_) => self.clone(), + CardStackMessage::RemoveAllCards => self.clone(), + } + } +} + +pub struct ViewableCardStack { + window_size: Size, + cards: Vec, + clear_card_stack_animation_starter: AnimationStarter, +} + +impl ViewableCardStack { + pub fn new(window_size: Size) -> Self { + let mut viewable_stack_card = Self { + window_size, + + cards: Vec::new(), + clear_card_stack_animation_starter: AnimationStarter::new( + 10, + 20, + CardStackMessage::HideCard(0), + ), + }; + viewable_stack_card + .clear_card_stack_animation_starter + .on_all_ended(CardStackMessage::RemoveAllCards); + viewable_stack_card + } +} + +impl Notifiable for ViewableCardStack { + type OwnMessage = CardStackMessage; + + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task { + match msg { + CardStackMessage::CardPlayed(card) => { + self.cards + .push(ViewableStackCard::new(self.window_size, card)); + if self.cards.len() == 1 { + return TaskBatcher::instant_batch([ + CardDeckMessage::ChangeGlow(card).convert_msg_to_task(), + CardDeckMessage::ShowGlow.convert_msg_to_task(), + ]); + } + } + CardStackMessage::HideAllCard => { + self.clear_card_stack_animation_starter + .start(self.cards.len().max(1) - 1); + } + CardStackMessage::HideCard(id) => { + let card_count: usize = self.cards.len(); + self.cards[card_count - 1 - id].remove_animation.start(); + } + CardStackMessage::RemoveAllCards => { + self.cards.clear(); + } + }; + Task::none() + } +} + +impl Animated for ViewableCardStack { + fn update_animations(&mut self) -> Task { + let mut tb = TaskBatcher::new(); + tb.push(self.clear_card_stack_animation_starter.next_frame()); + for card in self.cards.iter_mut() { + tb.push(card.update_animations()); + } + tb.batch() + } +} + +impl Resizable for ViewableCardStack { + fn height(&self) -> f32 { + card_area_middle_space_height(self.window_size) + } + fn width(&self) -> f32 { + card_area_middle_space_width(self.window_size) + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size; + for card in self.cards.iter_mut() { + card.update_size(window_size); + } + } +} + +impl Viewable for ViewableCardStack { + fn view<'a>(&self) -> Container<'a, AppMessage> { + let mut card_stack = Stack::new(); + for card in self.cards.iter() { + let spawn_point = + card_area_middle_spawn_point(card.width(), card.height(), self.window_size); + card_stack = card_stack.push(card.view_and_move(spawn_point.x, spawn_point.y)) + } + Container::new(card_stack) + .width(self.width()) + .height(self.height()) + } +} diff --git a/src/gameplay_ui/table/middle/card_stack/stack_card.rs b/src/gameplay_ui/table/middle/card_stack/stack_card.rs new file mode 100644 index 0000000..b9f4020 --- /dev/null +++ b/src/gameplay_ui/table/middle/card_stack/stack_card.rs @@ -0,0 +1,116 @@ +use crate::{ + animation::{BasicAnimation, Easing}, + api::{get_card_path, Card}, + client::{AppMessage, TaskBatcher}, + gameplay_ui::{ + card_height_middle, card_img_middle_base_scale, card_width_middle, + CARD_AREA_MIDDLE_RELATION, + }, + ui_element_traits::*, +}; + +use derive_more::{Deref, DerefMut}; +use iced::{ + widget::{image, Container}, + ContentFit::Fill, + Size, Task, +}; +use rand::Rng; + +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct RevealAnimation(BasicAnimation); + +impl RevealAnimation { + pub fn new(duration: usize) -> Self { + Self(BasicAnimation::new(duration)) + } + pub fn get_rotation(&self) -> f32 { + self.progress(Easing::InSine) + } + pub fn get_scale(&self) -> f32 { + CARD_AREA_MIDDLE_RELATION + - (CARD_AREA_MIDDLE_RELATION - 1.0) * self.progress(Easing::OutElastic) + } +} + +#[derive(Debug, Clone, Deref, DerefMut)] +pub struct RemoveAnimation(BasicAnimation); + +impl RemoveAnimation { + pub fn new(duration: usize) -> Self { + Self(BasicAnimation::new(duration)) + } + pub fn get_opacity(&self) -> f32 { + 1.0 - self.progress(Easing::OutCubic) + } + pub fn get_contraction(&self) -> f32 { + 1.0 - self.progress(Easing::OutCubic) + } +} + +pub struct ViewableStackCard { + window_size: Size, + card: Card, + reveal_animation: RevealAnimation, + pub remove_animation: RemoveAnimation, + rotation: f32, +} + +impl ViewableStackCard { + pub fn new(window_size: Size, card: Card) -> Self { + let mut viewable_stack_card = Self { + window_size, + card, + reveal_animation: RevealAnimation::new(50), + remove_animation: RemoveAnimation::new(10), + rotation: rand::rng().random_range(-0.15..0.15), + }; + viewable_stack_card.reveal_animation.start(); + viewable_stack_card + } +} + +impl Animated for ViewableStackCard { + fn update_animations(&mut self) -> Task { + TaskBatcher::instant_batch([ + self.reveal_animation.next_frame(), + self.remove_animation.next_frame(), + ]) + } +} + +impl Resizable for ViewableStackCard { + fn height(&self) -> f32 { + card_height_middle(self.window_size) * self.reveal_animation.get_scale() + } + fn width(&self) -> f32 { + card_width_middle(self.window_size) + * self.reveal_animation.get_scale() + * self.remove_animation.get_contraction() + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size + } +} + +impl SizeFromOutside for ViewableStackCard { + fn height_for(window_size: Size) -> f32 { + card_height_middle(window_size) + } + fn width_for(window_size: Size) -> f32 { + card_width_middle(window_size) + } +} + +impl Viewable for ViewableStackCard { + fn view<'a>(&self) -> Container<'a, AppMessage> { + let img = image(get_card_path(self.card)) + .width(self.width()) + .height(self.height()) + .scale(card_img_middle_base_scale()) + .opacity(self.remove_animation.get_opacity()) + .content_fit(Fill) + .rotation(self.rotation * self.reveal_animation.get_rotation()); + Container::new(img) + } +} diff --git a/src/gameplay_ui/table/middle/mod.rs b/src/gameplay_ui/table/middle/mod.rs new file mode 100644 index 0000000..62c904c --- /dev/null +++ b/src/gameplay_ui/table/middle/mod.rs @@ -0,0 +1,100 @@ +pub mod card_deck; +pub mod card_stack; + +use crate::{ + client::{AppMessage, TaskBatcher}, + gameplay_ui::table::{ + middle::{ + card_deck::{CardDeckMessage, ViewableCardDeck}, + card_stack::{CardStackMessage, ViewableCardStack}, + }, + TableMessage, + }, + ui_element_traits::*, +}; +use iced::{ + widget::{row, Container}, + Size, Task, +}; + +#[derive(Debug, Clone)] +pub enum TableMiddleMessage { + CardDeckMessage(CardDeckMessage), + CardStackMessage(CardStackMessage), +} + +impl Message for TableMiddleMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + TableMessage::convert_msg_from(TableMessage::TableMiddleMessage(msg)) + } +} + +pub struct ViewableTableMiddle { + window_size: Size, + card_deck: ViewableCardDeck, + card_stack: ViewableCardStack, +} + +impl ViewableTableMiddle { + pub fn new(window_size: Size) -> Self { + Self { + window_size, + card_deck: ViewableCardDeck::new(window_size), + card_stack: ViewableCardStack::new(window_size), + } + } +} + +impl Notifiable for ViewableTableMiddle { + type OwnMessage = TableMiddleMessage; + + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task { + match msg { + TableMiddleMessage::CardDeckMessage(card_deck_msg) => { + self.card_deck.update_with_msg(card_deck_msg) + } + TableMiddleMessage::CardStackMessage(card_stack_msg) => { + self.card_stack.update_with_msg(card_stack_msg) + } + } + } +} + +impl Animated for ViewableTableMiddle { + fn update_animations(&mut self) -> Task { + TaskBatcher::instant_batch([ + self.card_deck.update_animations(), + self.card_stack.update_animations(), + ]) + } +} + +impl Resizable for ViewableTableMiddle { + fn height(&self) -> f32 { + f32_max(vec![self.card_stack.height(), self.card_deck.height()]).unwrap() + } + fn width(&self) -> f32 { + self.card_deck.width() + self.card_stack.width() + } + fn update_size(&mut self, window_size: Size) { + self.window_size = window_size; + self.card_deck.update_size(window_size); + self.card_stack.update_size(window_size); + } +} + +impl Viewable for ViewableTableMiddle { + fn view<'a>(&self) -> Container<'a, AppMessage> { + Container::new(row![self.card_stack.view(), self.card_deck.view()]) + } +} + +fn f32_max(numbers: Vec) -> Option { + let mut max: Option = None; + for number in numbers.iter() { + if max.is_none() || max.unwrap() < *number { + max = Some(*number) + }; + } + max +} diff --git a/src/gameplay_ui/table/mod.rs b/src/gameplay_ui/table/mod.rs new file mode 100644 index 0000000..78f08a8 --- /dev/null +++ b/src/gameplay_ui/table/mod.rs @@ -0,0 +1,73 @@ +pub mod middle; + +use crate::{ + client::AppMessage, + gameplay_ui::{ + hand::HandMessage, + table::middle::{TableMiddleMessage, ViewableTableMiddle}, + }, + ui_element_traits::*, +}; +use iced::{widget::Container, Size, Task}; + +#[derive(Debug, Clone)] +pub enum TableMessage { + TableMiddleMessage(TableMiddleMessage), +} + +impl Message for TableMessage { + fn convert_msg_from(msg: Self) -> AppMessage { + AppMessage::TableMessage(msg) + } +} + +pub struct ViewableTable { + window_size: Size, + middle: ViewableTableMiddle, +} + +impl ViewableTable { + pub fn new(window_size: Size) -> Self { + Self { + window_size, + middle: ViewableTableMiddle::new(window_size), + } + } +} + +impl Notifiable for ViewableTable { + type OwnMessage = TableMessage; + + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task { + match msg { + TableMessage::TableMiddleMessage(table_middle_msg) => { + self.middle.update_with_msg(table_middle_msg) + } + } + } +} + +impl Animated for ViewableTable { + fn update_animations(&mut self) -> Task { + self.middle.update_animations() + } +} + +impl Resizable for ViewableTable { + fn height(&self) -> f32 { + self.middle.height() + } + fn width(&self) -> f32 { + self.middle.width() + } + fn update_size(&mut self, window_size: iced::Size) { + self.window_size = window_size; + self.middle.update_size(window_size); + } +} + +impl Viewable for ViewableTable { + fn view<'a>(&self) -> Container<'a, AppMessage> { + self.middle.view() + } +} diff --git a/src/main.rs b/src/main.rs index 05ba743..cf69902 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ // Enable only in release before the final build. // Disables the console window on Windows. -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +//#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod animation; mod api; diff --git a/src/server.rs b/src/server.rs index 6dfc119..6305764 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,4 @@ -use crate::api::{Lobby, Player, ServerMessage, B, C, S}; +use crate::api::{Lobby, Player, PlayerId, ServerMessage, B, C, S}; use crate::gamelogic::game::Game; use crate::gamelogic::GameEvent; @@ -17,8 +17,8 @@ use std::time::Duration; use tokio::sync::{mpsc, RwLock}; use uuid::Uuid; -type Clients = Arc>>>; -type PlayerList = Arc>>; +type Clients = Arc>>>; +type PlayerList = Arc>>; type SharedGame = Arc>; const SVERSION: usize = 1; @@ -230,14 +230,14 @@ async fn broadcast(clients: &Clients, _players: &PlayerList, msg: B) { } } -async fn send(clients: &Clients, player_id: u64, msg: S) { +async fn send(clients: &Clients, player_id: PlayerId, msg: S) { if let Some(tx) = clients.read().await.get(&player_id) { let _ = tx.send(ServerMessage::Server(msg)); } } async fn handle_socket(socket: WebSocket, clients: Clients, players: PlayerList, game: SharedGame) { - let id = Uuid::new_v4().as_u128() as u64; + let id: PlayerId = Uuid::new_v4().as_u128() as u64; println!("New connection: player {id}"); let (mut sender, mut receiver) = socket.split(); diff --git a/src/ui_element_traits.rs b/src/ui_element_traits.rs index 7d7d5b8..ef3fbec 100644 --- a/src/ui_element_traits.rs +++ b/src/ui_element_traits.rs @@ -1,35 +1,27 @@ use crate::client::AppMessage; use iced::{ widget::{pin, Container}, - Point, Size, + Point, Size, Task, }; -pub trait Message { - type OwnMessage; - fn convert_to_app_message(msg: Self::OwnMessage) -> AppMessage; - /// This function can handle 4 things: - /// 1: update_with_msg functions of other ui elements of lesser hierarchy, - /// 2: arbitrary stuff within the struct (e.g. start animations), - /// 3: AnimationStarter start functions, - /// 4: AnimationEndSensor start functions, - fn update_with_msg(&mut self, msg: Self::OwnMessage); +pub trait Notifiable { + /// The OwnMessage needs the Message trait or update_with_msg can't return Task + type OwnMessage: Message; + /// This function can handle 2 things: + /// - update_with_msg functions of other ui elements of lesser hierarchy, + /// - arbitrary stuff within the struct (e.g. start animations with AnimationStarter) + fn update_with_msg(&mut self, msg: Self::OwnMessage) -> Task; } -/// This trait needs the Message trait because animations are supposed to start with the -/// update_with_msg function. -pub trait Animated: Message { +/// This trait needs to be implemented for every animated ui element. +pub trait Animated { /// Call this every AnimationTick. - /// This function can handle 4 things. ALWAYS handle them in this order - /// (only 2 and 3 can be switched): + /// This function can handle 3 things: /// - /// 1: AnimationStarter check functions -> - /// 2: update_animations functions of other ui elements of lesser hierarchy -> - /// 3: next_frame functions of animations -> - /// 4: AnimationEndSensor check functions - /// - /// Note: Ensure that 1 and 4 are actually called at 1 and 4 respectively or otherwise there - /// will be off by one bugs! - fn update_animations(&mut self); + /// - AnimationStarter check functions + /// - update_animations functions of other ui elements of lesser hierarchy + /// - next_frame functions of animations + fn update_animations(&mut self) -> Task; } pub trait Resizable { @@ -43,6 +35,8 @@ pub trait Resizable { fn height(&self) -> f32; } +/// Implement this if you want to know the measures of an ui_element from elsewhere. +/// This comes in handy when the size of the parent ui element is defined by the child ui element. pub trait SizeFromOutside: Resizable { /// Uses a given window size to calculate the width of the total ui element. fn width_for(window_size: Size) -> f32; @@ -56,3 +50,22 @@ pub trait Viewable { Container::new(pin(self.view()).position(Point::new(x, y))) } } + +/// This trait is for child message enums of AppMessage to convert them to Task or AppMessage. +/// You need to implement convert_msg_from by using clone to use all other methods. +pub trait Message: Clone { + fn convert_msg_from(msg: Self) -> AppMessage; + fn convert_msg(&self) -> AppMessage { + Self::convert_msg_from(self.clone()) + } + fn convert_msg_to_task(&self) -> Task { + Task::done(self.convert_msg()) + } +} + +/// Trait to check if the message has an usize at the end and replaces it if its exists. +/// This is only needed if the message is used to start an animation via AnimationStarter +/// where you have to iterate the message. +pub trait ReplaceUsize: Message { + fn replace_usize(&self, value: usize) -> Self; +}