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(); }
解决方案
关键修改点
- 移除全局常量:删除全局的
const num_circles: usize = 255;,统一通过Model的settings.num_circles管理数值 - 提取生成逻辑为独立函数:把生成
circle_points的代码抽成generate_circle_points函数,方便初始化和更新时重复调用 - 检测数值变化并更新:在
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
相关产品推荐
相关产品推荐

