Search code examples
user-interfaceruststructslideregui

Updating struct field with slider value in Nannou


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.


Solution

  • 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:

    1. 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.

    2. Model -> num_circles field.

    3. 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.