I am trying to build a simple GUI which simulates a laser game where I let a red point follow an invisible target with deifferent speeds at different momements and escape the mouse cursor when gets close to it.
I used Iced version "0.13.1" as well as fastrand "2.3.0". Now here is the code:
use iced::{Point, Element, Subscription, Event, Rectangle, window, Color, Length,
Renderer, Theme};
use iced::widget::canvas::{self, Frame, Geometry, Path};
use iced::mouse::Cursor;
use fastrand;
use std::cell::RefCell;
#[derive(Default, Clone, Copy)]
struct LaserPointer {
x: f32,
y: f32,
speed: Speed,
imaginary_target: Point,
}
#[derive(Clone, Copy, Default)]
enum Speed {
#[default]
Still,
Slow,
Fast,
CrazyFast,
}
#[derive(Debug, Clone)]
enum Message {
MovePointer(Point),
UpdateFrame(Rectangle),
}
#[derive(Default, Clone)]
struct LaserPointerApp {
pointer: LaserPointer,
rectangle: Rectangle,
}
//Implementing From here isn’t essential, but it helps the following code be a bit cleaner.
impl From<LaserPointer> for Point {
fn from(pointer: LaserPointer) -> Self {
Point {
x: pointer.x,
y: pointer.y,
}
}
}
//We define this function because iced does not have an equivalent method
fn get_bottom_right(rect: Rectangle) -> Point {
Point {
x: rect.x + rect.width,
y: rect.y + rect.height,
}
}
impl LaserPointer {
fn new() -> Self {
Self {
x: 50.0,
y: 50.0,
speed: Speed::default(),
imaginary_target: Point { x: 50.0, y: 50.0 },
}
}
//This method now handles the random laser pointer movement when the mouse arrow gets too close.
fn random_movement(&mut self, amount: f32) {
if fastrand::bool() {
self.x += fastrand::f32() * amount;
} else {
self.x -= fastrand::f32() * amount;
}
if fastrand::bool() {
self.y += fastrand::f32() * amount;
} else {
self.y -= fastrand::f32() * amount;
}
}
//We don’t want the speed to change too frequently (cats get bored when a laser pointer moves too quickly), so
//we’ll use a random f32 from 0.0 to 1.0 and only change when the number is greater than 0.98. In practice, this
//will mean a speed change every few seconds. The following try_change_target() changes the invisible target
//for the pointer in the same way.
fn try_change_speed(&mut self) {
use Speed::*;
if fastrand::f32() > 0.98 {
self.speed = match fastrand::u8(0..3) {
0 => Still,
1 => Slow,
2 => Fast,
_ => CrazyFast,
}
}
}
fn try_change_target(&mut self, rect: Rectangle) {
let bottom_right = get_bottom_right(rect);
if fastrand::f32() > 0.98 {
self.imaginary_target = Point {
x: fastrand::f32() * bottom_right.x,
y: fastrand::f32() * bottom_right.y,
}
}
}
fn change_speed(&self) -> f32 {
match self.speed {
Speed::Still => 0.0,
Speed::Slow => 0.05,
Speed::Fast => 0.1,
Speed::CrazyFast => 0.3,
}
}
//Finally, we have this method to move the laser pointer once every loop. One of the speeds is 0.0,
//though, so it will stay absolutely still in that case.
fn move_self(&mut self) {
let x_from_target = self.imaginary_target.x - self.x;
let y_from_target = self.imaginary_target.y - self.y;
self.x += fastrand::f32() * x_from_target * self.change_speed();
self.y += fastrand::f32() * y_from_target * self.change_speed();
}
}
impl LaserPointerApp{
fn new() -> Self {
LaserPointerApp {
pointer: LaserPointer::new(),
rectangle: Rectangle {
x: 10.0,
y: 10.0,
width: 800.0,
height: 600.0,
},
}
}
}
fn subscription(laserpointer: LaserPointerApp) -> Subscription<Message> {
iced::event::listen().map(move |event| match event {
Event::Mouse(iced::mouse::Event::CursorMoved{position})=>Message::MovePointer(position),
Event::Window(window::Event::Resized (resize_info)) => {
Message::UpdateFrame(Rectangle {
x: 0.0,
y: 0.0,
width: resize_info.width as f32,
height: resize_info.height as f32,
})
}
_ => {
Message::UpdateFrame(laserpointer.rectangle)}
})
}
impl canvas::Program<Message> for LaserPointerApp {
type State = ();
fn draw(&self, _state: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor)
-> Vec<Geometry> {
let mut frame = Frame::new(renderer,bounds.size());
// Draw the red laser pointer
let path = Path::circle(Point::new(self.pointer.x, self.pointer.y), 20.0);
frame.fill(&path, Color::from_rgb(1.0, 0.0, 0.0));
vec![frame.into_geometry()]
}
}
fn update(laserpointer: &mut LaserPointerApp, message: Message) {
match message {
Message::MovePointer(pos) => {
if (laserpointer.pointer.x - pos.x).abs() < 20.0 && (laserpointer.pointer.y - pos.y).abs() < 20.0 {
laserpointer.pointer.random_movement(50.0);
}
},
Message::UpdateFrame(new_bounds) => {
laserpointer.rectangle = new_bounds;
laserpointer.pointer.try_change_speed();
laserpointer.pointer.try_change_target(laserpointer.rectangle);
laserpointer.pointer.move_self();
}
}
}
fn view(laserpointer: &LaserPointerApp) -> Element<Message> {
iced::widget::Canvas::new(laserpointer)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
fn main() -> iced::Result {
let laserpointer = LaserPointerApp::new();
iced::application("Awesome laser pointer",update,view)
.subscription(move |app| subscription(laserpointer.clone()))
.run()
}
After running this, the main thread panicks and the compilation report says the following:
finished `dev` profile [unoptimized + debuginfo] target(s) in 28.23s
Running `target\debug\laser_pointer.exe`
thread 'main' panicked at C:\Users\hp\.cargo\registry\src\index.crates.io-6f17d22bba15001f\iced_futures-0.13.2\src\subscription.rs:249:9:
the closure laser_pointer::subscription::{{closure}} provided in `Subscription::map` is capturing
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\laser_pointer.exe` (exit code: 101)
It seems there must be a lifetime issue with the subscription
function I defined earlier. But I cannot get it well.
I tried to introduce Arc<Mutex<LaserPointerApp>>
as the type of subscription
function's argument to enable ownership sharing and avoid lifetime issues, but the error report is the same.
The reason you get that error is relatively straight forward. You didn't respect the constraints of Subscription::map
:
The closure provided must be a non-capturing closure.
That means you cannot reference any local variables in the closure you pass it, laserpointer
cannot appear anywhere in that closures body.