use macroquad::prelude::{
draw_circle, is_mouse_button_down, mouse_position, touches, Color, MouseButton, TouchPhase,
Vec2,
};
pub struct Joystick {
center: Vec2,
size: f32,
background: JoystickElement,
knob: JoystickElement,
dragging: bool,
touch_id: u64,
event: JoystickDirectionalEvent,
}
impl Joystick {
pub fn new(x: f32, y: f32, size: f32) -> Self {
Self::from_custom_elements(x, y, size, None, None)
}
pub fn from_custom_elements(
x: f32,
y: f32,
size: f32,
background: Option<JoystickElement>,
knob: Option<JoystickElement>,
) -> Self {
let radius = size / 2.;
let center = Vec2::new(x, y);
let background = background.unwrap_or_else(|| {
JoystickElement::new(x, y, radius, Color::from_rgba(96, 125, 139, 128))
});
let knob = knob.unwrap_or_else(|| {
JoystickElement::new(x, y, radius / 2., Color::from_rgba(96, 125, 139, 168))
});
Self {
center,
size,
background,
knob,
dragging: false,
touch_id: 0,
event: JoystickDirectionalEvent::default(),
}
}
fn render(&self) {
self.background.render();
self.knob.render();
}
fn update_touch(&mut self) {
for touch in touches() {
match touch.phase {
TouchPhase::Started => {
if (touch.position - self.center).length() < (self.size / 2.) {
self.dragging = true;
self.touch_id = touch.id;
self.moving(touch.position);
}
}
TouchPhase::Moved => {
if self.dragging && touch.id == self.touch_id {
self.moving(touch.position);
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
if self.dragging && touch.id == self.touch_id {
self.reset();
}
}
_ => {}
}
}
}
fn update_mouse(&mut self) {
let (mouse_x, mouse_y) = mouse_position();
let mouse = Vec2::new(mouse_x, mouse_y);
let mouse_down = is_mouse_button_down(MouseButton::Left);
if self.dragging {
if mouse_down {
self.moving(mouse)
} else {
self.reset();
}
} else if mouse_down && (self.center - mouse).length() < (self.size / 2.) {
self.dragging = true;
self.moving(mouse)
}
}
fn reset(&mut self) {
self.dragging = false;
self.knob.x = self.center.x;
self.knob.y = self.center.y;
self.event = JoystickDirectionalEvent::default();
}
pub fn update(&mut self) -> JoystickDirectionalEvent {
if touches().len() > 0 {
self.update_touch();
} else {
self.update_mouse();
}
self.render();
self.event
}
fn moving(&mut self, position: Vec2) {
let delta = position - self.center;
let angle = delta.y.atan2(delta.x);
let angle_degrees = angle.to_degrees();
let radius = self.size / 2.;
let dist = f32::min(delta.length(), radius);
self.knob.x = self.center.x + dist * angle.cos();
self.knob.y = self.center.y + dist * angle.sin();
let intensity = dist / radius;
let direction = if intensity == 0. {
JoystickDirection::Idle
} else {
JoystickDirection::from_degrees(angle_degrees as f64)
};
self.event = JoystickDirectionalEvent::new(direction, intensity, angle);
}
}
pub struct JoystickElement {
x: f32,
y: f32,
radius: f32,
color: Color,
}
impl JoystickElement {
pub fn new(x: f32, y: f32, radius: f32, color: Color) -> Self {
Self {
x,
y,
radius,
color,
}
}
pub fn render(&self) {
draw_circle(self.x, self.y, self.radius, self.color);
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum JoystickDirection {
Up,
UpLeft,
Left,
DownLeft,
Down,
DownRight,
Right,
UpRight,
Idle,
}
impl JoystickDirection {
pub fn from_degrees(degrees: f64) -> Self {
if degrees > -22.5 && degrees <= 22.5 {
Self::Right
} else if degrees > 22.5 && degrees <= 67.5 {
Self::DownRight
} else if degrees > 67.5 && degrees <= 112.5 {
Self::Down
} else if degrees > 112.5 && degrees <= 157.5 {
Self::DownLeft
} else if degrees > 157.5 && degrees <= 180. {
Self::Left
} else if degrees > -157.5 && degrees <= -112.5 {
Self::UpLeft
} else if degrees > -112.5 && degrees <= -67.5 {
Self::Up
} else if degrees > -67.5 && degrees <= -22.5 {
Self::UpRight
} else {
Self::Idle
}
}
pub fn to_local(&self) -> Vec2 {
let (x, y) = match self {
Self::Right => (1., 0.),
Self::DownRight => (1., 1.),
Self::Down => (0., 1.),
Self::DownLeft => (-1., 1.),
Self::Left => (-1., 0.),
Self::UpLeft => (-1., -1.),
Self::Up => (0., -1.),
Self::UpRight => (1., -1.),
Self::Idle => (0., 0.),
};
Vec2::new(x, y)
}
}
#[derive(Clone, Copy, Debug)]
pub struct JoystickDirectionalEvent {
pub direction: JoystickDirection,
pub intensity: f32,
pub angle: f32,
}
impl JoystickDirectionalEvent {
fn new(direction: JoystickDirection, intensity: f32, angle: f32) -> Self {
Self {
direction,
intensity,
angle,
}
}
}
impl Default for JoystickDirectionalEvent {
fn default() -> Self {
Self {
direction: JoystickDirection::Idle,
intensity: 0.,
angle: 0.,
}
}
}