You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nannou中如何用滑块更新Model的num_circles字段及关联数据?

Nannou + Rust:通过GUI滑块同步更新圆形数量及关联数据的解决方案

问题描述

我正在用Nannou框架学习Rust动画开发,实现了基于数学函数运动的圆形动画,并添加了GUI滑块控制圆形数量。目前滑块值可正常变化,但初始化函数model中生成circle_points数组依赖的常量num_circles始终为255,无法随滑块更新;虽能在update或view函数中更新数值,但初始化生成的circle_points数据无法同步更新。尝试在model函数中使用model.settings.num_circles时触发错误,需要解决如何通过滑块正确更新Model结构体的num_circles字段及关联的circle_points数据。

原始代码

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();
}

解决方案

关键修改点

  1. 移除全局常量:删除全局的const num_circles: usize = 255;,统一通过Model的settings.num_circles管理数值
  2. 提取生成逻辑为独立函数:把生成circle_points的代码抽成generate_circle_points函数,方便初始化和更新时重复调用
  3. 检测数值变化并更新:在update函数中对比model.num_circles和model.settings.num_circles,当数值变化时重新生成circle_points并同步更新model.num_circles

修改后的完整代码

use nannou::prelude::*;
use nannou_egui::{self, egui, Egui};

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,
    window_rect: Rect, // 保存窗口矩形,避免重复获取
}

pub struct Settings {
    num_circles: usize,
}

// 提取生成circle_points的逻辑为独立函数
fn generate_circle_points(
    num_circles: usize,
    num_points: usize,
    window_rect: Rect,
) -> Vec<Vec<Point2>> {
    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);
    }
    circle_points
}

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 initial_num_circles = 255;
    
    // 使用初始数值生成circle_points
    let circle_points = generate_circle_points(initial_num_circles, num_points, window_rect);

    Model {
        frequency,
        amplitude,
        phase,
        num_circles: initial_num_circles,
        num_points,
        circle_points,
        egui,
        settings: Settings { num_circles: initial_num_circles },
        window_rect,
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    let egui = &mut model.egui;

    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));
    });

    // 检测num_circles是否变化,变化则重新生成circle_points
    if model.num_circles != model.settings.num_circles {
        model.num_circles = model.settings.num_circles;
        // 重新获取窗口矩形,处理窗口大小变化的情况
        model.window_rect = app.window_rect();
        model.circle_points = generate_circle_points(
            model.num_circles,
            model.num_points,
            model.window_rect,
        );
    }

    // 更新圆形点的动画位置
    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 draw = app.draw();
    draw.background().color(WHITE);
    for i in 0..model.num_circles {
        let hue = i as f32 / model.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();
}

解释

  • 独立生成函数:generate_circle_points接收必要参数(圆形数量、点数量、窗口矩形),返回新的circle_points数组,让初始化和更新逻辑复用,避免代码重复
  • 变化检测:在update中每次对比当前num_circles和设置值,只有变化时才重新生成数据,避免不必要的计算开销
  • 窗口矩形缓存:在Model中保存窗口矩形,更新时重新获取,确保窗口大小变化时圆形布局也能正确适配
  • 字段同步:更新model.num_circles时同步修改circle_points,保证数据一致性

内容的提问来源于stack exchange,提问作者AltunE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 03:07:34