I am trying to learn Rust by creating animations using Nannou. The animation consists of circles that move according to a mathematical function, and I want to add a slider to the GUI to control the number of circles by following this code
I have created the slider and added it, and I can see that the slider value is changing when I move it. However, the problem is that the value of num_circles
in my fn model
is not being updated when I move the slider, and I can't link it to the slider.
In other words, I couldn't link the num_circles
on the fn model
but I can link it to fn update
or fn veiw
functions.
TLDR: I know my code is long but please don't worry about it. I have a for loop inside the fn model
function that uses a constant value num_circles
. I want to be able to change this value using a slider, but so far I have only been able to update it in the fn update
or fn view
functions. Is there a way to link the value of num_circles
in fn model
to the slider as well?
for i in 0..num_circles
Here is my code:
use nannou::prelude::*;
use nannou_egui::{self, egui, Egui};
const num_circles: usize = 255;
fn main() {
nannou::app(model).update(update).run();
}
pub struct Model {
frequency: f32,
amplitude: f32,
phase: f32,
num_circles: usize,
num_points: usize,
circle_points: Vec<Vec<Point2>>,
settings: Settings,
egui: Egui,
}
pub struct Settings {
num_circles: usize,
}
fn model(app: &App) -> Model {
let window_id = app
.new_window()
.view(view)
.raw_event(raw_window_event)
.build()
.unwrap();
let window = app.window(window_id).unwrap();
let egui = Egui::from_window(&window);
let frequency = 125.0;
let amplitude = 1.4;
let phase = 1.0;
let num_points = 155;
let window_rect = app.window_rect();
let center = window_rect.xy();
let radius = window_rect.w().min(window_rect.h()) / 2.0;
let circle_radius = radius / (num_circles as f32);
let mut circle_points = Vec::with_capacity(num_circles);
for i in 0..num_circles {
let mut points = Vec::with_capacity(num_points);
for j in 0..num_points {
let angle = j as f32 * 4.0 * PI / (num_points as f32);
let x = center.x + angle.sin() * circle_radius * (i as f32 + 2.0);
let y = center.y + angle.cos() * circle_radius * (i as f32 + 2.0);
points.push(pt2(x, y));
}
circle_points.push(points);
}
Model {
frequency,
amplitude,
phase,
num_circles,
num_points,
circle_points,
egui,
settings: Settings { num_circles: 255 },
}
}
fn update(_app: &App, model: &mut Model, _update: Update) {
let egui = &mut model.egui;
let settings = &model.settings;
egui.set_elapsed_time(_update.since_start);
let ctx = egui.begin_frame();
egui::Window::new("Settings").show(&ctx, |ui| {
ui.label("Num Circles:");
ui.add(egui::Slider::new(&mut model.settings.num_circles, 1..=255));
});
for i in 0..model.num_circles {
for j in 0..model.num_points {
let x = model.circle_points[i][j].x
+ (-25.0 * PI * model.frequency * j as f32 / model.num_points as f32 + model.phase)
.sin()
* model.amplitude;
let y = model.circle_points[i][j].y
+ (25.0 * PI * model.frequency * j as f32 / model.num_points as f32 + model.phase)
.cos()
* model.amplitude;
model.circle_points[i][j] = pt2(x, y);
}
}
model.phase += 0.01;
}
fn raw_window_event(_app: &App, model: &mut Model, event: &nannou::winit::event::WindowEvent) {
model.egui.handle_raw_event(event);
}
fn view(app: &App, model: &Model, frame: Frame) {
let settings = &model.settings;
let draw = app.draw();
draw.background().color(WHITE);
for i in 0..settings.num_circles {
let hue = i as f32 / settings.num_circles as f32;
let color = hsla(hue, 1.0, 0.5, 1.0);
draw.polyline()
.weight(1.5)
.points(model.circle_points[i].clone())
.color(color);
}
draw.to_frame(app, &frame).unwrap();
model.egui.draw_to_frame(&frame).unwrap();
}
The problem is that num_circles
in fn model
always stays 255 and doesn't change. How can I make the values here change with the slider?
I would appreciate any help on how to properly update the "num_circles" field in my struct Model with the slider value.
When I use model.settings.num_circles
in the model
function it raises an error and it's not working.
The point of the model()
function is to create the inital Model
. It's only called once when the program starts. Therefore it doesn't make sense to update num_circles
within the model()
function, because the function isn't called again.
There is one aspect of this code that is confusing; You have three distinct bindings all with the name num_circles
:
const num_circles: usize = 255;
. Constants in Rust are uppercase by convention. If this is meant to represent the initial number of circles, a good name would be INITIAL_NUM_CIRCLES
.
Model
-> num_circles
field.
Model
-> settings
-> num_circles
field.
Perhaps you only meant to store one num_circles
in your model?
As for how do you update the Model
's num_circles
field with the slider value: you are already doing that on this line (at least, you are updating one of your num_circles
, because as I mentioned you have two of them in your Model
):
ui.add(egui::Slider::new(&mut model.settings.num_circles, 1..=255));
You're providing the slider function with a mutable reference to num_circles
. It will take care of writing the updated slider value into that reference.