Skip to main content

FrameContext

Struct FrameContext 

Source
pub struct FrameContext<'a, G: Game> { /* private fields */ }
Expand description

The work Game::frame may do.

Implementations§

Source§

impl<'a, G: Game> FrameContext<'a, G>

Source

pub fn down<A: InputButtonAction>(&self, action: A) -> bool
where G::InputActions: Seats<A, A::Binding>,

Whether action is held right now.

Examples found in repository?
examples/ui-fonts.rs (line 779)
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
More examples
Hide additional examples
examples/stress-preview.rs (line 365)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
examples/material-playground.rs (line 803)
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
examples/input-lab.rs (line 247)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn pressed<A: InputButtonAction>(&self, action: A) -> bool
where G::InputActions: Seats<A, A::Binding>,

Whether action went down during this frame.

Examples found in repository?
examples/breakout-game.rs (line 996)
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
More examples
Hide additional examples
examples/input-lab.rs (line 248)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn released<A: InputButtonAction>(&self, action: A) -> bool
where G::InputActions: Seats<A, A::Binding>,

Whether action came up during this frame.

Examples found in repository?
examples/input-lab.rs (line 249)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn clicks<A: InputButtonAction>(&self, action: A) -> u32
where G::InputActions: Seats<A, A::Binding>,

Presses of action in a row, counting this frame’s: 1 for a single click, 2 for a double, and 0 on a frame where action was not pressed.

A press counts with the one before it where the same control took both, no later than the double click interval after it — the platform’s own until Config::with_double_click_interval sets one. The engine counts one control at a time, so an action whose press lands in the same frame as another control’s reads 0 where the count is on that other one. A headless session counts against the clock its caller drives, so presses at one instant of it count together.

Examples found in repository?
examples/input-lab.rs (line 250)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn axis<A: InputAxisAction>(&self, action: A) -> f32
where G::InputActions: Seats<A, A::Binding>,

Analog reading of action: a fraction in -1..=1 from a pad axis, a joystick axis or a button composite, of which a trigger reads 0..=1, and the scaled distance, which nothing clamps, from a PointerDelta or WheelDelta lane.

Examples found in repository?
examples/animation.rs (line 683)
682    fn steer_camera(&mut self, ctx: &mut FrameContext<'_, Scene>) {
683        self.camera_yaw -= ctx.axis(Axis::CameraYaw);
684        self.camera_pitch = (self.camera_pitch + ctx.axis(Axis::CameraPitch))
685            .clamp(CAMERA_PITCH_RANGE.start, CAMERA_PITCH_RANGE.end);
686    }
More examples
Hide additional examples
examples/ui-fonts.rs (line 782)
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
examples/stress-preview.rs (line 364)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
examples/material-playground.rs (line 809)
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
examples/input-lab.rs (line 260)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2
where G::InputActions: Seats<A, A::Binding>,

action’s reading: a vector no longer than 1 from a stick or a button composite, and the scaled distance, which nothing clamps, from Axis2Binding::pointer.

Examples found in repository?
examples/ui-fonts.rs (line 780)
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
More examples
Hide additional examples
examples/stress-preview.rs (line 363)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
examples/material-playground.rs (line 804)
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
examples/input-lab.rs (line 270)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn pointer(&self) -> Vec2

Pointer position, in physical pixels from the drawing area’s top left; the origin until it is first seen.

The mouse and the first touch share it, and window_size is in the same pixels, so Camera::ray_through takes it as it is.

Examples found in repository?
examples/ui-fonts.rs (line 772)
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
More examples
Hide additional examples
examples/isometric-board.rs (line 465)
459    fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460        if ctx.ui_wants_pointer() {
461            return Hover::None;
462        }
463        let ray = ctx
464            .last_camera()
465            .ray_through(ctx.pointer(), ctx.window_size());
466
467        if self.current().target.is_none() {
468            let (_, half) = unit_geometry(self.turn);
469            let center = self.current().position;
470            if ray.hit_aabb(center - half, center + half).is_some() {
471                return Hover::CurrentUnit;
472            }
473        }
474        let Some(distance) = ray.hit_plane(ray::Plane {
475            point: Vec3::ZERO,
476            normal: Vec3::Y,
477        }) else {
478            return Hover::None;
479        };
480        match tile_at(ray.at(distance)) {
481            Some(tile) => Hover::Tile(tile),
482            None => Hover::None,
483        }
484    }
examples/input-lab.rs (line 288)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn set_cursor(&mut self, cursor: Cursor)

Draws the pointer as cursor this frame; the last call in a frame is the one it draws.

A frame that never calls this draws it as Cursor::Arrow. Where the UI sets a cursor of its own, the UI’s is drawn instead.

Cursor::Held holds the pointer in place, and the UI’s own cursor does not take Held over: pointer reads the place it was held at, a PointerDelta binding keeps reading how far it moves, and the first frame to set another cursor releases it. A browser takes the pointer lock only from inside a gesture of the player’s, so there the hold is taken on the player’s next press or touch. A window loses the hold as it loses focus, and takes it again on the first frame to set Held after the focus returns.

Examples found in repository?
examples/ui-fonts.rs (line 840)
816    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817        self.steer(ctx);
818
819        let camera = self.orbit.camera();
820        ctx.set_camera(camera);
821        ctx.set_skybox(Sky::Dusk);
822        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824        ctx.draw(
825            Plane
826                .at(Transform::from_scale(Vec3::new(
827                    PLATFORM_SIZE,
828                    1.0,
829                    PLATFORM_SIZE,
830                )))
831                .material(Material::lit(PLATFORM_COLOR)),
832        );
833        for station in StationKind::ALL {
834            self.draw_station(ctx, station);
835        }
836
837        let hovered = Self::hovered(ctx);
838        if !self.sheet_open {
839            if let Some(station) = hovered {
840                ctx.set_cursor(Cursor::Pointer);
841                self.draw_bracket(ctx, camera, station);
842            }
843            self.draw_prompts(ctx, camera, hovered);
844        }
845        if self.dialogue.is_some() {
846            self.draw_dialogue(ctx);
847        }
848        if self.sheet_open {
849            ctx.ui(sheet);
850        }
851        self.panel(ctx);
852    }
More examples
Hide additional examples
examples/animation.rs (lines 930-934)
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
Source

pub fn rebind<A: InputAction>(&mut self, action: A, bindings: Vec<A::Binding>)
where G::InputActions: Seats<A, A::Binding>,

Binds action to bindings for the rest of the run, and keeps it for the runs after that — written when the frame ends, and only when these bindings are not already what action is bound to.

The kind of binding follows the action, so a stick cannot be bound to a button.

Examples found in repository?
examples/breakout-game.rs (line 842)
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
More examples
Hide additional examples
examples/input-lab.rs (line 379)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn bindings<A: InputAction>(&self, action: A) -> Vec<A::Binding>
where G::InputActions: Seats<A, A::Binding>,

The bindings action reads through right now, which a controls menu shows through each binding’s text.

Examples found in repository?
examples/ui-fonts.rs (line 709)
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
More examples
Hide additional examples
examples/breakout-game.rs (line 743)
735    fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736        let bricks_left = self
737            .bricks
738            .iter()
739            .filter(|brick| brick.hits_remaining > 0)
740            .count();
741        // Read before `ctx.ui` so a rebind changes what the hint reads this
742        // frame too.
743        let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744        let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745        let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746        ctx.ui(|ui| {
747            ui.horizontal(|ui| {
748                ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749                ui.label(format!("{bricks_left} bricks left"));
750            });
751            ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752            if self.phase == Phase::Serving {
753                ui.label(format!("{serve_hint} to serve"));
754            }
755        });
756
757        match self.phase {
758            Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759            Phase::Won => self.menu(ctx, "you win", true),
760            Phase::Lost => self.menu(ctx, "game over", true),
761            _ => {}
762        }
763    }
764
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
examples/animation.rs (line 825)
823    fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824        let sit_key = ctx
825            .bindings(Button::Interact)
826            .into_iter()
827            .next()
828            .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829        let sit = ctx.text_layout(
830            &format!("{sit_key} sits"),
831            egui::FontId::proportional(PROMPT_SIZE),
832        );
833        let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834        let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836        let mut prompts = vec![(
837            SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838            walk_closer,
839        )];
840        if !self.elf_animator.state().seated() {
841            prompts.push((
842                SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843                sit,
844            ));
845        }
846        prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848        let window_size = ctx.window_size();
849        let pixels_per_point = ctx.pixels_per_point();
850        ctx.ui(|ui| {
851            let painter = ui.painter();
852            for (point, galley) in prompts {
853                let Some(pixel) = camera.pixel_of(point, window_size) else {
854                    continue;
855                };
856                let at = logical(pixel, pixels_per_point);
857                let ink = galley.mesh_bounds;
858                let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859                let backdrop = egui::Rect::from_center_size(
860                    at,
861                    ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862                );
863                painter.rect_filled(
864                    backdrop,
865                    PROMPT_PADDING,
866                    egui::Color32::from_black_alpha(PANEL_BACKDROP),
867                );
868                painter.galley(pos, galley, PANEL_TEXT_COLOR);
869            }
870        });
871    }
examples/input-lab.rs (line 246)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn actuated_button(&self) -> Option<ButtonBinding>

The button control the player pressed this frame, for a controls menu listening for one to bind.

Nothing where the player pressed nothing new; polling is the whole mechanism, so a menu that stops calling this stops listening.

Examples found in repository?
examples/breakout-game.rs (line 781)
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
More examples
Hide additional examples
examples/input-lab.rs (line 276)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn actuated_axis(&self) -> Option<AxisBinding>

The analog control the player pushed this frame, past the deadzone a binding starts with.

The pointer and the wheel are never returned: they would take the smallest nudge for a choice.

Examples found in repository?
examples/breakout-game.rs (line 784)
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
More examples
Hide additional examples
examples/input-lab.rs (line 277)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn actuated_axis2(&self) -> Option<Axis2Binding>

The stick the player pushed this frame, past the deadzone a binding starts with.

Examples found in repository?
examples/input-lab.rs (line 278)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn dt(&self) -> Duration

Duration of the previous frame: this frame’s own variable time step, distinct from the fixed one tick runs at.

Examples found in repository?
examples/stress-preview.rs (line 389)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
393
394    fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
395        let side = (FIELD_RADIUS + FIELD_INNER_RADIUS) * 2.2;
396        ctx.draw(
397            Plane
398                .at(Transform::from_scale(Vec3::new(side, 1.0, side)))
399                .material(Material::lit(GROUND_COLOR).roughness(0.9)),
400        );
401    }
402
403    fn draw_field(&self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
404        for entry in &self.field {
405            let yaw = if self.settings.moving && entry.moving {
406                entry.phase + elapsed * MOVING_SPEED
407            } else {
408                entry.phase
409            };
410            ctx.draw(
411                Rock { seed: entry.seed }.at(Transform::from_scale_rotation_translation(
412                    Vec3::ONE,
413                    Quat::from_rotation_y(yaw),
414                    entry.position,
415                )),
416            );
417        }
418    }
419
420    /// How many field items lie in the camera's view at `window_size`:
421    /// every item where the field holds at most [`MAX_IN_VIEW_SAMPLES`],
422    /// otherwise one item stepped at a time and the count scaled back up
423    /// to the whole field; `true` in the second place where the count
424    /// came from such a step.
425    ///
426    /// Each item is tested on the engine's own workers: a parallel iterator
427    /// reaches them with nothing configured for it.
428    fn count_in_view(&self, camera: &Camera, window_size: UVec2) -> (usize, bool) {
429        let stride = (self.field.len() as u32 / MAX_IN_VIEW_SAMPLES).max(1) as usize;
430        let tested = self.field.par_iter().step_by(stride);
431        let tested_count = self.field.len().div_ceil(stride);
432        let in_view = tested
433            .filter(|entry| Self::in_view(camera, entry.position, window_size))
434            .count();
435        let estimate = in_view
436            .checked_mul(self.field.len())
437            .and_then(|scaled| scaled.checked_div(tested_count))
438            .unwrap_or(in_view);
439        (estimate, stride > 1)
440    }
441
442    /// Whether `position` draws inside `window_size`, the frame's own
443    /// bound of what the camera's view holds.
444    fn in_view(camera: &Camera, position: Vec3, window_size: UVec2) -> bool {
445        camera.pixel_of(position, window_size).is_some_and(|pixel| {
446            pixel.x >= 0.0
447                && pixel.y >= 0.0
448                && pixel.x < window_size.x as f32
449                && pixel.y < window_size.y as f32
450        })
451    }
452
453    /// The load controls, and this frame's own cost, reported below them.
454    fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455        let submitted = self.field.len();
456        let seeds = self.applied_seed_count;
457        let average_ms = self.frame_times.average_ms();
458        let fps = if average_ms > 0.0 {
459            1000.0 / average_ms
460        } else {
461            0.0
462        };
463        let elapsed = ctx.elapsed().as_secs_f32();
464        let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466        ctx.ui(|ui| {
467            egui::Frame::new()
468                .fill(egui::Color32::from_gray(24))
469                .inner_margin(PANEL_PADDING)
470                .corner_radius(f32::from(PANEL_PADDING))
471                .show(ui, |ui| {
472                    ui.add(
473                        egui::Slider::new(
474                            &mut self.settings.instance_count,
475                            MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476                        )
477                        .text("instance count"),
478                    );
479                    ui.add(
480                        egui::Slider::new(
481                            &mut self.settings.seed_count,
482                            MIN_SEED_COUNT..=MAX_SEED_COUNT,
483                        )
484                        .text("distinct seeds"),
485                    );
486                    ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487                    ui.checkbox(&mut self.settings.moving, "moving fraction");
488                    ui.separator();
489                    ui.label(format!("instances submitted {submitted}"));
490                    if sampled {
491                        ui.label(format!("in view, sampled {in_view}"));
492                    } else {
493                        ui.label(format!("instances in view {in_view}"));
494                    }
495                    ui.label(format!("distinct seeds {seeds}"));
496                    ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497                    ui.label(format!("elapsed {elapsed:.1}s"));
498                });
499        });
500    }
501}
502
503/// `instance_count` field values, each drawing one of `seed_count`
504/// distinct seed values in a cycle, and scattered from
505/// [`FIELD_INNER_RADIUS`] out to [`FIELD_RADIUS`]; each built from an
506/// integer-hash of its own index.
507fn build_field(instance_count: u32, seed_count: u32) -> Vec<FieldEntry> {
508    (0..instance_count)
509        .map(|index| {
510            let angle = hash_unit(index, 0) * core::f32::consts::TAU;
511            let spread = hash_unit(index, 1).sqrt();
512            let distance = FIELD_INNER_RADIUS + spread * (FIELD_RADIUS - FIELD_INNER_RADIUS);
513            FieldEntry {
514                seed: index % seed_count,
515                position: Vec3::new(angle.cos() * distance, 0.0, angle.sin() * distance),
516                phase: hash_unit(index, 2) * core::f32::consts::TAU,
517                moving: index % MOVING_STRIDE == 0,
518            }
519        })
520        .collect()
521}
522
523/// A rock built from `seed`: a cone of [`ROCK_SIDES`] sides, each base
524/// corner and the apex height displaced by an integer-hash of `seed`.
525fn build_rock(seed: u32) -> MeshData {
526    let height = ROCK_HEIGHT * (1.0 + hash_signed(seed, ROCK_SIDES) * ROCK_HEIGHT_DISPLACEMENT);
527    let apex = Vec3::Y * height;
528    let base: Vec<Vec3> = (0..ROCK_SIDES)
529        .map(|corner| {
530            let angle = core::f32::consts::TAU * corner as f32 / ROCK_SIDES as f32;
531            let radius =
532                ROCK_BASE_RADIUS * (1.0 + hash_signed(seed, corner) * ROCK_RADIAL_DISPLACEMENT);
533            Vec3::new(angle.cos() * radius, 0.0, angle.sin() * radius)
534        })
535        .collect();
536
537    let mut vertices = Vec::with_capacity(base.len() * 6);
538    let mut indices = Vec::with_capacity(base.len() * 6);
539    for corner in 0..base.len() {
540        let next = (corner + 1) % base.len();
541        push_face(&mut vertices, &mut indices, base[corner], apex, base[next]);
542        push_face(
543            &mut vertices,
544            &mut indices,
545            base[corner],
546            base[next],
547            Vec3::ZERO,
548        );
549    }
550
551    MeshData::new(vertices, indices).with_material(Material::lit(ROCK_COLOR))
552}
553
554/// One triangle, shaded flat, over `a`, `b`, `c`, in the order that faces
555/// outward: counter-clockwise as seen from the side its own normal points
556/// to.
557fn push_face(vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, a: Vec3, b: Vec3, c: Vec3) {
558    let normal = (b - a).cross(c - a).normalize();
559    let uvs = [
560        Vec2::new(0.0, 1.0),
561        Vec2::new(0.5, 0.0),
562        Vec2::new(1.0, 1.0),
563    ];
564    let base = vertices.len() as u32;
565    for (point, uv) in [a, b, c].into_iter().zip(uvs) {
566        vertices.push(Vertex::new(point, normal, uv));
567    }
568    indices.extend([base, base + 1, base + 2]);
569}
570
571/// An integer-hash of `seed` and `salt`.
572fn hash(seed: u32, salt: u32) -> u32 {
573    let mut x = seed ^ salt.wrapping_mul(0x9E37_79B9);
574    x ^= x >> 16;
575    x = x.wrapping_mul(0x7FEB_352D);
576    x ^= x >> 15;
577    x = x.wrapping_mul(0x846C_A68B);
578    x ^= x >> 16;
579    x
580}
581
582/// `hash`, scaled to `0.0..1.0`.
583fn hash_unit(seed: u32, salt: u32) -> f32 {
584    hash(seed, salt) as f32 / u32::MAX as f32
585}
586
587/// `hash`, scaled to `-1.0..1.0`.
588fn hash_signed(seed: u32, salt: u32) -> f32 {
589    hash_unit(seed, salt) * 2.0 - 1.0
590}
591
592impl Game for StressPreview {
593    type Meshes = Shape;
594    type Sounds = NoSounds;
595    type InputActions = Controls;
596    type Skyboxes = Sky;
597    type SurfaceStyles = NoSurfaceStyles;
598    type PostEffects = NoPostEffects;
599
600    fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
601
602    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603        self.apply_settings();
604        self.frame_times.record(ctx.dt());
605
606        let elapsed = ctx.elapsed().as_secs_f32();
607        self.handle_camera(ctx, elapsed);
608        let camera = self.camera(elapsed);
609        ctx.set_camera(camera);
610        ctx.set_skybox(Sky::Day);
611
612        let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613        ctx.light(if self.settings.sun_shadow {
614            sun.shadow()
615        } else {
616            sun
617        });
618
619        Self::draw_ground(ctx);
620        self.draw_field(ctx, elapsed);
621        self.controls(ctx, &camera);
622    }
More examples
Hide additional examples
examples/material-playground.rs (line 840)
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
Source

pub fn elapsed(&self) -> Duration

Duration the game has been running.

Examples found in repository?
examples/flock-parallelism.rs (line 745)
741    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742        self.apply_settings();
743
744        let center = Vec3::from(self.butterflies.center());
745        let elapsed = ctx.elapsed().as_secs_f32();
746        ctx.set_camera(Self::camera(center, self.world, elapsed));
747        ctx.set_skybox(Sky::Day);
748        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750        Self::draw_ground(ctx);
751        self.draw_butterflies(ctx);
752        self.panel(ctx);
753    }
More examples
Hide additional examples
examples/material-playground.rs (line 952)
951    fn draw_outpost(&self, ctx: &mut FrameContext<'_, Self>) {
952        let clock = ctx.elapsed().as_secs_f32();
953
954        for &(position, scale) in &PILLARS {
955            ctx.draw(
956                Cube.at(Transform::from_scale_rotation_translation(
957                    scale,
958                    Quat::IDENTITY,
959                    OUTPOST + position,
960                ))
961                .material(Material::lit(Color::rgb(0.55, 0.5, 0.45))),
962            );
963        }
964
965        ctx.draw(
966            Cube.at(Transform::from_scale_rotation_translation(
967                POLE_SCALE,
968                Quat::IDENTITY,
969                OUTPOST + POLE_POSITION,
970            ))
971            .material(Material::lit(Color::rgb(0.3, 0.24, 0.18))),
972        );
973
974        ctx.set_surface_style(Banner { time: clock });
975        ctx.draw(
976            BannerCloth
977                .at(Transform::from_translation(OUTPOST + BANNER_MOUNT))
978                .material(Material::lit(Color::rgb(0.75, 0.12, 0.12)))
979                .surface_style::<Banner>(),
980        );
981
982        ctx.set_surface_style(Field {
983            tint: Color::rgb(0.25, 0.75, 1.0),
984            time: clock,
985        });
986        ctx.draw(
987            Sphere { subdivisions: 2 }
988                .at(Transform::from_scale_rotation_translation(
989                    Vec3::splat(FIELD_ORB_SCALE),
990                    Quat::IDENTITY,
991                    OUTPOST + FIELD_ORB_POSITION,
992                ))
993                .material(Material::color(Color::BLACK))
994                .surface_style::<Field>(),
995        );
996    }
examples/stress-preview.rs (line 463)
454    fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455        let submitted = self.field.len();
456        let seeds = self.applied_seed_count;
457        let average_ms = self.frame_times.average_ms();
458        let fps = if average_ms > 0.0 {
459            1000.0 / average_ms
460        } else {
461            0.0
462        };
463        let elapsed = ctx.elapsed().as_secs_f32();
464        let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466        ctx.ui(|ui| {
467            egui::Frame::new()
468                .fill(egui::Color32::from_gray(24))
469                .inner_margin(PANEL_PADDING)
470                .corner_radius(f32::from(PANEL_PADDING))
471                .show(ui, |ui| {
472                    ui.add(
473                        egui::Slider::new(
474                            &mut self.settings.instance_count,
475                            MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476                        )
477                        .text("instance count"),
478                    );
479                    ui.add(
480                        egui::Slider::new(
481                            &mut self.settings.seed_count,
482                            MIN_SEED_COUNT..=MAX_SEED_COUNT,
483                        )
484                        .text("distinct seeds"),
485                    );
486                    ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487                    ui.checkbox(&mut self.settings.moving, "moving fraction");
488                    ui.separator();
489                    ui.label(format!("instances submitted {submitted}"));
490                    if sampled {
491                        ui.label(format!("in view, sampled {in_view}"));
492                    } else {
493                        ui.label(format!("instances in view {in_view}"));
494                    }
495                    ui.label(format!("distinct seeds {seeds}"));
496                    ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497                    ui.label(format!("elapsed {elapsed:.1}s"));
498                });
499        });
500    }
501}
502
503/// `instance_count` field values, each drawing one of `seed_count`
504/// distinct seed values in a cycle, and scattered from
505/// [`FIELD_INNER_RADIUS`] out to [`FIELD_RADIUS`]; each built from an
506/// integer-hash of its own index.
507fn build_field(instance_count: u32, seed_count: u32) -> Vec<FieldEntry> {
508    (0..instance_count)
509        .map(|index| {
510            let angle = hash_unit(index, 0) * core::f32::consts::TAU;
511            let spread = hash_unit(index, 1).sqrt();
512            let distance = FIELD_INNER_RADIUS + spread * (FIELD_RADIUS - FIELD_INNER_RADIUS);
513            FieldEntry {
514                seed: index % seed_count,
515                position: Vec3::new(angle.cos() * distance, 0.0, angle.sin() * distance),
516                phase: hash_unit(index, 2) * core::f32::consts::TAU,
517                moving: index % MOVING_STRIDE == 0,
518            }
519        })
520        .collect()
521}
522
523/// A rock built from `seed`: a cone of [`ROCK_SIDES`] sides, each base
524/// corner and the apex height displaced by an integer-hash of `seed`.
525fn build_rock(seed: u32) -> MeshData {
526    let height = ROCK_HEIGHT * (1.0 + hash_signed(seed, ROCK_SIDES) * ROCK_HEIGHT_DISPLACEMENT);
527    let apex = Vec3::Y * height;
528    let base: Vec<Vec3> = (0..ROCK_SIDES)
529        .map(|corner| {
530            let angle = core::f32::consts::TAU * corner as f32 / ROCK_SIDES as f32;
531            let radius =
532                ROCK_BASE_RADIUS * (1.0 + hash_signed(seed, corner) * ROCK_RADIAL_DISPLACEMENT);
533            Vec3::new(angle.cos() * radius, 0.0, angle.sin() * radius)
534        })
535        .collect();
536
537    let mut vertices = Vec::with_capacity(base.len() * 6);
538    let mut indices = Vec::with_capacity(base.len() * 6);
539    for corner in 0..base.len() {
540        let next = (corner + 1) % base.len();
541        push_face(&mut vertices, &mut indices, base[corner], apex, base[next]);
542        push_face(
543            &mut vertices,
544            &mut indices,
545            base[corner],
546            base[next],
547            Vec3::ZERO,
548        );
549    }
550
551    MeshData::new(vertices, indices).with_material(Material::lit(ROCK_COLOR))
552}
553
554/// One triangle, shaded flat, over `a`, `b`, `c`, in the order that faces
555/// outward: counter-clockwise as seen from the side its own normal points
556/// to.
557fn push_face(vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, a: Vec3, b: Vec3, c: Vec3) {
558    let normal = (b - a).cross(c - a).normalize();
559    let uvs = [
560        Vec2::new(0.0, 1.0),
561        Vec2::new(0.5, 0.0),
562        Vec2::new(1.0, 1.0),
563    ];
564    let base = vertices.len() as u32;
565    for (point, uv) in [a, b, c].into_iter().zip(uvs) {
566        vertices.push(Vertex::new(point, normal, uv));
567    }
568    indices.extend([base, base + 1, base + 2]);
569}
570
571/// An integer-hash of `seed` and `salt`.
572fn hash(seed: u32, salt: u32) -> u32 {
573    let mut x = seed ^ salt.wrapping_mul(0x9E37_79B9);
574    x ^= x >> 16;
575    x = x.wrapping_mul(0x7FEB_352D);
576    x ^= x >> 15;
577    x = x.wrapping_mul(0x846C_A68B);
578    x ^= x >> 16;
579    x
580}
581
582/// `hash`, scaled to `0.0..1.0`.
583fn hash_unit(seed: u32, salt: u32) -> f32 {
584    hash(seed, salt) as f32 / u32::MAX as f32
585}
586
587/// `hash`, scaled to `-1.0..1.0`.
588fn hash_signed(seed: u32, salt: u32) -> f32 {
589    hash_unit(seed, salt) * 2.0 - 1.0
590}
591
592impl Game for StressPreview {
593    type Meshes = Shape;
594    type Sounds = NoSounds;
595    type InputActions = Controls;
596    type Skyboxes = Sky;
597    type SurfaceStyles = NoSurfaceStyles;
598    type PostEffects = NoPostEffects;
599
600    fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
601
602    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603        self.apply_settings();
604        self.frame_times.record(ctx.dt());
605
606        let elapsed = ctx.elapsed().as_secs_f32();
607        self.handle_camera(ctx, elapsed);
608        let camera = self.camera(elapsed);
609        ctx.set_camera(camera);
610        ctx.set_skybox(Sky::Day);
611
612        let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613        ctx.light(if self.settings.sun_shadow {
614            sun.shadow()
615        } else {
616            sun
617        });
618
619        Self::draw_ground(ctx);
620        self.draw_field(ctx, elapsed);
621        self.controls(ctx, &camera);
622    }
examples/animation.rs (line 926)
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
Source

pub fn alpha(&self) -> f32

This frame’s position into the next simulation step, a fraction in 0.0..1.0; used to draw between two tick states.

Examples found in repository?
examples/sprite-adventure.rs (line 1723)
1722    fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724        ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727        self.draw_ground(ctx);
1728        self.draw_hedgerow(ctx);
1729        self.draw_pond(ctx);
1730        self.draw_crates(ctx);
1731        self.draw_well(ctx);
1732        self.draw_flora(ctx);
1733        Self::draw_mouth(ctx, ENTRANCE);
1734        self.draw_walker(ctx, drawn_at);
1735    }
1736
1737    fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739        let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740        ctx.set_camera(camera);
1741
1742        self.draw_cave_floor(ctx);
1743        self.draw_cave_walls(ctx);
1744        Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745        Self::draw_mouth(ctx, EXIT);
1746        self.draw_torches(ctx);
1747        self.draw_door(ctx, self.ghost);
1748        self.draw_door_frame(ctx, self.ghost);
1749        if !self.gem_taken {
1750            self.draw_gem(ctx);
1751        }
1752        self.draw_walker(ctx, drawn_at);
1753        self.draw_door_prompt(ctx, camera);
1754    }
More examples
Hide additional examples
examples/isometric-board.rs (line 584)
583    fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584        let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585        let current = self.turn == Turn::Sprite;
586        let (tint, glow) = if current && self.selected {
587            (SELECTED_TINT, SELECTED_GLOW)
588        } else if current && hover == Hover::CurrentUnit {
589            (HOVER_TINT, HOVER_GLOW)
590        } else if current {
591            (TURN_TINT, TURN_GLOW)
592        } else {
593            (Color::WHITE, Color::BLACK)
594        };
595        ctx.draw(
596            Sprite
597                .at(Transform::from_scale_rotation_translation(
598                    Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599                    Quat::IDENTITY,
600                    position,
601                ))
602                .upright()
603                .frame(sprite_frame(self.sprite.facing_right))
604                .material(Material::lit(tint).cutout().emissive(glow)),
605        );
606    }
607
608    fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609        let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610        let current = self.turn == Turn::Block;
611        let (color, glow) = if current && self.selected {
612            (SELECTED_TINT, SELECTED_GLOW)
613        } else if current && hover == Hover::CurrentUnit {
614            (HOVER_TINT, HOVER_GLOW)
615        } else if current {
616            (BLOCK_TURN, TURN_GLOW)
617        } else {
618            (BLOCK_IDLE, Color::BLACK)
619        };
620        ctx.draw(
621            Cube.at(Transform::from_scale_rotation_translation(
622                Vec3::splat(BLOCK_SIZE),
623                Quat::IDENTITY,
624                position,
625            ))
626            .material(Material::lit(color).emissive(glow)),
627        );
628    }
examples/sound-lab.rs (line 981)
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
examples/breakout-game.rs (line 1014)
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
examples/animation.rs (line 923)
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
Source

pub fn set_tick_interval(&mut self, interval: Duration)

Sets the simulated time every later tick covers, from the next frame on; held to at least Duration::from_micros(1).

Required if you want to pace the simulation against something outside the engine, such as a program on another machine. The ticks a frame already runs keep the step they started with.

Source

pub fn close(&mut self)

Ends the run once this frame is drawn: what the frame saved is written, and no tick or frame runs after it.

On the desktop the window closes and run returns. In the browser there is no program to end: the loop stops, and the canvas the engine created is dropped from the page, while one the game named through Config::with_canvas_id stays as the page left it.

Examples found in repository?
examples/breakout-game.rs (line 861)
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
Source

pub fn set_camera(&mut self, camera: Camera)

Views the rest of the frame from camera. A later call in the same frame replaces this one; without any, Camera::default is used.

Examples found in repository?
examples/post-effects.rs (lines 157-160)
156    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
157        ctx.set_camera(Camera::new(
158            View::look_at(Vec3::new(0.0, 3.4, 4.6), Vec3::new(0.0, 0.2, 0.0)),
159            Projection::perspective(45.0),
160        ));
161        ctx.set_bloom(SCENE_BLOOM);
162
163        self.draw_scene(ctx);
164        self.panel(ctx);
165    }
More examples
Hide additional examples
examples/material-playground.rs (line 1153)
1151    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152        self.fly_camera(ctx);
1153        ctx.set_camera(self.camera());
1154        ctx.set_skybox(self.sky);
1155        for light in self.lights() {
1156            ctx.light(light);
1157        }
1158        ctx.set_exposure(self.exposure);
1159        ctx.set_bloom(self.bloom);
1160
1161        self.draw_scene(ctx);
1162        self.controls(ctx);
1163    }
examples/flock-parallelism.rs (line 746)
741    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742        self.apply_settings();
743
744        let center = Vec3::from(self.butterflies.center());
745        let elapsed = ctx.elapsed().as_secs_f32();
746        ctx.set_camera(Self::camera(center, self.world, elapsed));
747        ctx.set_skybox(Sky::Day);
748        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750        Self::draw_ground(ctx);
751        self.draw_butterflies(ctx);
752        self.panel(ctx);
753    }
examples/sprite-adventure.rs (line 1724)
1722    fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724        ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727        self.draw_ground(ctx);
1728        self.draw_hedgerow(ctx);
1729        self.draw_pond(ctx);
1730        self.draw_crates(ctx);
1731        self.draw_well(ctx);
1732        self.draw_flora(ctx);
1733        Self::draw_mouth(ctx, ENTRANCE);
1734        self.draw_walker(ctx, drawn_at);
1735    }
1736
1737    fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739        let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740        ctx.set_camera(camera);
1741
1742        self.draw_cave_floor(ctx);
1743        self.draw_cave_walls(ctx);
1744        Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745        Self::draw_mouth(ctx, EXIT);
1746        self.draw_torches(ctx);
1747        self.draw_door(ctx, self.ghost);
1748        self.draw_door_frame(ctx, self.ghost);
1749        if !self.gem_taken {
1750            self.draw_gem(ctx);
1751        }
1752        self.draw_walker(ctx, drawn_at);
1753        self.draw_door_prompt(ctx, camera);
1754    }
examples/isometric-board.rs (line 821)
819    fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820        let camera = Self::camera();
821        ctx.set_camera(camera);
822        ctx.set_skybox(Sky::Day);
823        ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824        ctx.set_bloom(BLOOM);
825
826        let hover = self.hovered(ctx);
827        self.draw_ground(ctx);
828        self.draw_board(ctx, hover);
829        self.draw_current_mark(ctx);
830        self.draw_rocks(ctx);
831        self.draw_sprite(ctx, hover);
832        self.draw_block(ctx, hover);
833        self.draw_prompt(ctx, camera, hover);
834
835        self.overlay(ctx);
836    }
examples/stress-preview.rs (line 609)
602    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603        self.apply_settings();
604        self.frame_times.record(ctx.dt());
605
606        let elapsed = ctx.elapsed().as_secs_f32();
607        self.handle_camera(ctx, elapsed);
608        let camera = self.camera(elapsed);
609        ctx.set_camera(camera);
610        ctx.set_skybox(Sky::Day);
611
612        let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613        ctx.light(if self.settings.sun_shadow {
614            sun.shadow()
615        } else {
616            sun
617        });
618
619        Self::draw_ground(ctx);
620        self.draw_field(ctx, elapsed);
621        self.controls(ctx, &camera);
622    }
Source

pub fn draw<M>(&mut self, instance: Instance<M, G::SurfaceStyles>)
where G::Meshes: Holds<M>,

Draws instance this frame. The engine decides the order and which draws share GPU work.

Takes a draw of a mesh of Game::Meshes and no other.

Examples found in repository?
examples/stress-preview.rs (lines 396-400)
394    fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
395        let side = (FIELD_RADIUS + FIELD_INNER_RADIUS) * 2.2;
396        ctx.draw(
397            Plane
398                .at(Transform::from_scale(Vec3::new(side, 1.0, side)))
399                .material(Material::lit(GROUND_COLOR).roughness(0.9)),
400        );
401    }
402
403    fn draw_field(&self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
404        for entry in &self.field {
405            let yaw = if self.settings.moving && entry.moving {
406                entry.phase + elapsed * MOVING_SPEED
407            } else {
408                entry.phase
409            };
410            ctx.draw(
411                Rock { seed: entry.seed }.at(Transform::from_scale_rotation_translation(
412                    Vec3::ONE,
413                    Quat::from_rotation_y(yaw),
414                    entry.position,
415                )),
416            );
417        }
418    }
More examples
Hide additional examples
examples/flock-parallelism.rs (lines 661-669)
660    fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
661        ctx.draw(
662            Plane
663                .at(Transform::from_scale(Vec3::new(
664                    GROUND_SIZE,
665                    1.0,
666                    GROUND_SIZE,
667                )))
668                .material(Material::lit(GROUND_COLOR).roughness(0.9)),
669        );
670    }
671
672    /// Every butterfly at its own scale, turned to face its velocity, posed
673    /// by the flap machine of its own group and tinted its own color.
674    fn draw_butterflies(&self, ctx: &mut FrameContext<'_, Self>) {
675        for (position, velocity, kind) in self.butterflies.each() {
676            let rotation = Quat::from_rotation_arc(Vec3::Z, Vec3::from(velocity).normalize());
677            ctx.draw(
678                Butterfly
679                    .at(Transform::from_scale_rotation_translation(
680                        Vec3::splat(BUTTERFLY_SCALE),
681                        rotation,
682                        Vec3::from(position),
683                    ))
684                    .posed(&self.flaps[usize::from(kind.flap)])
685                    .material(Material::lit(TINTS[usize::from(kind.tint)])),
686            );
687        }
688    }
examples/sprite-adventure.rs (lines 1239-1247)
1236    fn draw_ground(&self, ctx: &mut FrameContext<'_, Keep>) {
1237        for col in -GROUND_DRAW_HALF..=GROUND_DRAW_HALF {
1238            for row in -GROUND_DRAW_HALF..=GROUND_DRAW_HALF {
1239                ctx.draw(
1240                    Ground
1241                        .at(Vec3::new(
1242                            col as f32 * TILE_SIZE,
1243                            0.0,
1244                            row as f32 * TILE_SIZE,
1245                        ))
1246                        .frame(ground_cell(col, row)),
1247                );
1248            }
1249        }
1250    }
1251
1252    /// Two staggered rows of bushes around the clearing, open where the path
1253    /// leaves it, drawn between the camera and the ground's edge. The rows
1254    /// running along `Z` skip their two ends, which the rows running along
1255    /// `X` already cover.
1256    fn draw_hedgerow(&self, ctx: &mut FrameContext<'_, Keep>) {
1257        for (row, half) in [HEDGE_INNER_HALF, HEDGE_OUTER_HALF].into_iter().enumerate() {
1258            let row = row as i32;
1259            // The inner row covers both corners; the outer one is half a
1260            // span in from each, backing the gaps the inner row leaves.
1261            let spans = ((2.0 * half / HEDGE_STEP).round() as i32).max(1);
1262            let span = 2.0 * half / spans as f32;
1263            let steps = spans - row;
1264            for step in 0..=steps {
1265                let along = -half + (step as f32 + 0.5 * row as f32) * span;
1266                let scale = if (step + row) % 2 == 0 { 1.0 } else { 0.8 };
1267                let (width, height) = (BUSH_WIDTH * scale, BUSH_HEIGHT * scale);
1268                // The path leaves through the rows running along `X`, so only
1269                // those two open around it.
1270                let gated = along.abs() < HEDGE_GATE_HALF;
1271                let corner = step == 0 || step == steps;
1272                let places = [
1273                    (along, -half, gated),
1274                    (along, half, gated),
1275                    (-half, along, corner),
1276                    (half, along, corner),
1277                ];
1278                for (x, z, skip) in places {
1279                    if skip {
1280                        continue;
1281                    }
1282                    ctx.draw(
1283                        Bush.at(Transform::from_scale_rotation_translation(
1284                            Vec3::new(width, height, width),
1285                            Quat::IDENTITY,
1286                            Vec3::new(x, height * 0.5, z),
1287                        ))
1288                        .upright(),
1289                    );
1290                }
1291            }
1292        }
1293    }
1294
1295    /// The pond: a square of styled water, and the shoreline sprite laid over
1296    /// it, which rings the open middle and hides the water's own edges.
1297    fn draw_pond(&self, ctx: &mut FrameContext<'_, Keep>) {
1298        ctx.draw(
1299            Plane
1300                .at(Transform::from_scale_rotation_translation(
1301                    Vec3::splat(POND_WATER_HALF * 2.0),
1302                    Quat::IDENTITY,
1303                    POND_CENTER,
1304                ))
1305                .material(Material::shaded(WATER_COLOR, WATER_LITNESS))
1306                .surface_style::<Water>(),
1307        );
1308        ctx.draw(
1309            Shore
1310                .at(Transform::from_scale_rotation_translation(
1311                    Vec3::splat(POND_HALF * 2.0),
1312                    Quat::IDENTITY,
1313                    Vec3::new(POND_CENTER.x, 0.0, POND_CENTER.z),
1314                ))
1315                .frame(Sheet::new(UVec2::new(POND_CELLS, 1)).cell(POND_SHORE_CELL)),
1316        );
1317    }
1318
1319    fn draw_crates(&self, ctx: &mut FrameContext<'_, Keep>) {
1320        for &(x, z, turn) in &CRATE_POSITIONS {
1321            ctx.draw(Crate.at(Transform::from_scale_rotation_translation(
1322                Vec3::splat(CRATE_SIZE),
1323                Quat::from_rotation_y(turn),
1324                Vec3::new(x, CRATE_SIZE * 0.5, z),
1325            )));
1326        }
1327    }
1328
1329    /// The well: its rim in grey masonry, and the mouth cell laid over the
1330    /// rim's top face.
1331    fn draw_well(&self, ctx: &mut FrameContext<'_, Keep>) {
1332        let cells = Sheet::new(UVec2::new(WELL_CELLS, 1));
1333        ctx.draw(
1334            Well.at(Transform::from_scale_rotation_translation(
1335                WELL_SIZE,
1336                Quat::IDENTITY,
1337                WELL_POSITION + Vec3::Y * (WELL_SIZE.y * 0.5),
1338            ))
1339            .frame(cells.cell(WELL_RIM_CELL)),
1340        );
1341        ctx.draw(
1342            WellMouth
1343                .at(Transform::from_scale_rotation_translation(
1344                    Vec3::new(WELL_SIZE.x, 1.0, WELL_SIZE.z),
1345                    Quat::IDENTITY,
1346                    WELL_POSITION + Vec3::Y * (WELL_SIZE.y + WELL_MOUTH_LIFT),
1347                ))
1348                .frame(cells.cell(WELL_MOUTH_CELL)),
1349        );
1350    }
1351
1352    fn draw_flora(&self, ctx: &mut FrameContext<'_, Keep>) {
1353        for &(x, z, rock) in &FLORA {
1354            let (width, height) = if rock {
1355                (ROCK_WIDTH, ROCK_HEIGHT)
1356            } else {
1357                (BUSH_WIDTH, BUSH_HEIGHT)
1358            };
1359            let standing = Transform::from_scale_rotation_translation(
1360                Vec3::new(width, height, width),
1361                Quat::IDENTITY,
1362                Vec3::new(x, height * 0.5, z),
1363            );
1364            let flora: Instance<Shape, _> = if rock {
1365                Rock.at(standing).into_set()
1366            } else {
1367                Bush.at(standing).into_set()
1368            };
1369            ctx.draw(flora.upright());
1370        }
1371    }
1372
1373    /// One stone box drawn on the ground at `at`, `size` across, sampling
1374    /// the part of the sheet `frame` covers.
1375    fn draw_stone(ctx: &mut FrameContext<'_, Keep>, at: Vec3, size: Vec3, frame: Frame) {
1376        ctx.draw(
1377            Stone
1378                .at(Transform::from_scale_rotation_translation(
1379                    size,
1380                    Quat::IDENTITY,
1381                    at + Vec3::Y * (size.y * 0.5),
1382                ))
1383                .frame(frame),
1384        );
1385    }
1386
1387    /// Two stone pillars drawn where `mouth` blocks the player, each a
1388    /// capital over its own course of masonry, and, on the one the camera
1389    /// looks into, the lintel across their tops and the dark filling
1390    /// the opening under it.
1391    fn draw_mouth(ctx: &mut FrameContext<'_, Keep>, mouth: Mouth) {
1392        for at in mouth.pillars() {
1393            Self::draw_stone(ctx, at, MOUTH_PILLAR_SIZE, Frame::default());
1394        }
1395        if !mouth.looked_into() {
1396            return;
1397        }
1398
1399        Self::draw_stone(
1400            ctx,
1401            mouth.at + Vec3::Y * MOUTH_PILLAR_SIZE.y,
1402            MOUTH_LINTEL_SIZE,
1403            masonry(MOUTH_LINTEL_TILES),
1404        );
1405        ctx.draw(
1406            Quad.at(Transform::from_scale_rotation_translation(
1407                Vec3::new(MOUTH_PILLAR_OFFSET * 2.0, MOUTH_DARK_HEIGHT, 1.0),
1408                Quat::IDENTITY,
1409                mouth.at + Vec3::Y * (MOUTH_DARK_HEIGHT * 0.5),
1410            ))
1411            .material(Material::color(Color::BLACK)),
1412        );
1413    }
1414
1415    fn draw_cave_floor(&self, ctx: &mut FrameContext<'_, Keep>) {
1416        let half = CAVE_HALF_WIDTH as i32;
1417        let near = CAVE_NEAR_Z as i32;
1418        let far = CAVE_FAR_Z as i32;
1419        for col in -half..=half {
1420            for row in far..=near {
1421                let variant = (col * 13 + row * 7).rem_euclid(CAVE_COLUMNS as i32) as u32;
1422                ctx.draw(
1423                    CaveFloor
1424                        .at(Vec3::new(
1425                            col as f32 * TILE_SIZE,
1426                            0.0,
1427                            row as f32 * TILE_SIZE,
1428                        ))
1429                        .frame(
1430                            Sheet::new(UVec2::new(CAVE_COLUMNS, CAVE_ROWS))
1431                                .cell_at(UVec2::new(variant, CAVE_FLOOR_ROW)),
1432                        ),
1433                );
1434            }
1435        }
1436    }
1437
1438    /// The wall drawn at `at` over the meters `standing`, in courses
1439    /// [`WALL_HEIGHT`] tall from the floor up, each cut to the part of it the
1440    /// span leaves; its faces are picked by `seed` and its stone faded to
1441    /// `fade`, which is `1.0` wherever it is solid.
1442    fn draw_wall(
1443        ctx: &mut FrameContext<'_, Keep>,
1444        at: Vec2,
1445        standing: Range<f32>,
1446        seed: i32,
1447        fade: f32,
1448    ) {
1449        for course in 0..WALL_COURSES {
1450            let base = course as f32 * WALL_HEIGHT;
1451            let low = (standing.start - base).max(0.0);
1452            let high = (standing.end - base).min(WALL_HEIGHT);
1453            if high <= low {
1454                continue;
1455            }
1456
1457            let variant = (seed + course).rem_euclid(CAVE_COLUMNS as i32) as u32;
1458            ctx.draw(
1459                CaveWall
1460                    .at(Transform::from_scale_rotation_translation(
1461                        Vec3::new(TILE_SIZE, high - low, TILE_SIZE),
1462                        Quat::IDENTITY,
1463                        Vec3::new(at.x, base + (low + high) * 0.5, at.y),
1464                    ))
1465                    .frame(cave_wall_face(variant, low..high))
1466                    .faded(fade),
1467            );
1468        }
1469    }
1470
1471    /// The room's two side walls and its back wall, full height, and the low
1472    /// wall closing its near end between the side walls and the mouth. The
1473    /// back wall stops short of the corners the side walls already fill, and
1474    /// the near one leaves the mouth's own tile open.
1475    fn draw_cave_walls(&self, ctx: &mut FrameContext<'_, Keep>) {
1476        let half = CAVE_HALF_WIDTH as i32 + 1;
1477        let near = CAVE_NEAR_Z as i32;
1478        let far = CAVE_FAR_Z as i32;
1479
1480        for row in far..=near {
1481            let z = row as f32 * TILE_SIZE;
1482            let west = Vec2::new(-half as f32 * TILE_SIZE, z);
1483            let east = Vec2::new(half as f32 * TILE_SIZE, z);
1484            Self::draw_wall(ctx, west, 0.0..WALL_TOP, row * 5, SOLID);
1485            Self::draw_wall(ctx, east, 0.0..WALL_TOP, row * 5 + 1, SOLID);
1486        }
1487        for col in (-half + 1)..half {
1488            let x = col as f32 * TILE_SIZE;
1489            let back = Vec2::new(x, far as f32 * TILE_SIZE);
1490            Self::draw_wall(ctx, back, 0.0..WALL_TOP, col * 5 + 2, SOLID);
1491            if col != 0 {
1492                let lip = Vec2::new(x, CAVE_LIP_Z);
1493                Self::draw_wall(ctx, lip, 0.0..CAVE_LIP_HEIGHT, col * 5 + 4, SOLID);
1494            }
1495        }
1496    }
1497
1498    /// The wall the door hangs in, run across the room between the side walls
1499    /// with one tile left open on the room's axis for the doorway and stone
1500    /// filling the column over the door. A player behind the wall is drawn
1501    /// through the stacks between them and the camera, at `seen_through`,
1502    /// faded by `ghost`; the rest of it stays solid, and keeps casting.
1503    fn draw_door_wall(ctx: &mut FrameContext<'_, Keep>, seen_through: Option<f32>, ghost: f32) {
1504        let stone = |x: f32| match seen_through {
1505            Some(at) if (x - at).abs() < GHOST_CORRIDOR_HALF => ghost_alpha(ghost),
1506            _ => SOLID,
1507        };
1508        let half = CAVE_HALF_WIDTH as i32;
1509
1510        for col in (-half..=half).filter(|&col| col != 0) {
1511            let x = col as f32 * TILE_SIZE;
1512            Self::draw_wall(
1513                ctx,
1514                Vec2::new(x, DOOR_Z),
1515                0.0..WALL_TOP,
1516                col * 5 + 3,
1517                stone(x),
1518            );
1519        }
1520        Self::draw_wall(
1521            ctx,
1522            Vec2::new(0.0, DOOR_Z),
1523            DOOR_HEIGHT..WALL_TOP,
1524            3,
1525            stone(0.0),
1526        );
1527    }
1528
1529    /// The two torches: an upright cutout post apiece, the flame's loop
1530    /// burning over its binding, and the light that flame casts.
1531    fn draw_torches(&self, ctx: &mut FrameContext<'_, Keep>) {
1532        let elapsed = self.simulated.as_secs_f32();
1533        let loop_cells = Sheet::new(UVec2::new(FLAME_CELLS, 1));
1534
1535        for (index, &(x, z)) in TORCH_POSITIONS.iter().enumerate() {
1536            let base = Vec3::new(x, 0.0, z);
1537            ctx.draw(
1538                Torch
1539                    .at(Transform::from_scale_rotation_translation(
1540                        Vec3::new(TORCH_SPRITE_WIDTH, TORCH_STAND_HEIGHT, 1.0),
1541                        Quat::IDENTITY,
1542                        base + Vec3::Y * (TORCH_STAND_HEIGHT * 0.5),
1543                    ))
1544                    .upright(),
1545            );
1546
1547            let phase = index as f32 * 2.1;
1548            let flicker = (elapsed * FLAME_FLICKER_SPEED + phase).sin();
1549            let flame_pos =
1550                base + Vec3::Y * (TORCH_STAND_HEIGHT + FLAME_LIFT + flicker * FLAME_BOB);
1551
1552            let light_pos = flame_pos + Vec3::new(0.0, TORCH_LIGHT_LIFT, TORCH_LIGHT_STANDOFF);
1553            ctx.light(Light::point(light_pos, TORCH_LIGHT_COLOR, TORCH_LIGHT_RANGE).shadow());
1554            // The pair burn an even share of the loop apart.
1555            let offset = index as u32 * FLAME_CELLS / TORCH_POSITIONS.len() as u32;
1556            ctx.draw(
1557                Flame
1558                    .at(Transform::from_scale_rotation_translation(
1559                        Vec3::splat(FLAME_SIZE),
1560                        Quat::IDENTITY,
1561                        flame_pos,
1562                    ))
1563                    .billboard()
1564                    .roll(flicker * FLAME_ROLL)
1565                    .frame(loop_cells.cell((elapsed * FLAME_RATE) as u32 + offset)),
1566            );
1567        }
1568    }
1569
1570    /// The door at its hinge — swung back against the wall once opened —
1571    /// drawn through alongside its wall, faded by `ghost`.
1572    fn draw_door(&self, ctx: &mut FrameContext<'_, Keep>, ghost: f32) {
1573        let fade = ghost_alpha(ghost);
1574        let swung = if self.door_opening {
1575            Quat::from_rotation_y(core::f32::consts::FRAC_PI_2)
1576        } else {
1577            Quat::IDENTITY
1578        };
1579
1580        ctx.draw(
1581            Door.at(Transform::from_rotation_translation(swung, DOOR_HINGE))
1582                .material(Material::shaded(DOOR_COLOR, DOOR_LITNESS))
1583                .faded(fade),
1584        );
1585    }
1586
1587    /// The posts and lintel framing the doorway, in a color the stone never
1588    /// is, standing clear of the wall so the opening reads as a door from
1589    /// across the chamber. Glowing of their own while the door is closed and
1590    /// within [`INTERACT_RADIUS`], the cue that it opens.
1591    fn draw_door_frame(&self, ctx: &mut FrameContext<'_, Keep>, ghost: f32) {
1592        let reachable =
1593            !self.door_opening && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1594        let material =
1595            Material::shaded(DOOR_FRAME_COLOR, DOOR_FRAME_LITNESS).emissive(if reachable {
1596                DOOR_FRAME_GLOW
1597            } else {
1598                Color::BLACK
1599            });
1600        let fade = ghost_alpha(ghost);
1601        let z = DOOR_WALL_NEAR_Z + DOOR_FRAME_STANDOFF;
1602        let jamb_height = DOOR_HEIGHT + DOOR_FRAME_THICKNESS;
1603
1604        for side in SIDES {
1605            ctx.draw(
1606                Cube.at(Transform::from_scale_rotation_translation(
1607                    Vec3::new(DOOR_FRAME_THICKNESS, jamb_height, DOOR_FRAME_THICKNESS),
1608                    Quat::IDENTITY,
1609                    Vec3::new(
1610                        side * (DOORWAY_HALF + DOOR_FRAME_THICKNESS * 0.5),
1611                        jamb_height * 0.5,
1612                        z,
1613                    ),
1614                ))
1615                .material(material)
1616                .faded(fade),
1617            );
1618        }
1619        ctx.draw(
1620            Cube.at(Transform::from_scale_rotation_translation(
1621                Vec3::new(
1622                    DOOR_WIDTH + DOOR_FRAME_THICKNESS * 2.0,
1623                    DOOR_FRAME_THICKNESS,
1624                    DOOR_FRAME_THICKNESS,
1625                ),
1626                Quat::IDENTITY,
1627                Vec3::new(0.0, DOOR_HEIGHT + DOOR_FRAME_THICKNESS * 0.5, z),
1628            ))
1629            .material(material)
1630            .faded(fade),
1631        );
1632    }
1633
1634    /// A world prompt over the door: what opens it while the player is
1635    /// within [`INTERACT_RADIUS`] and it is closed, and that it swings while
1636    /// it does; gone once it has swung [`DOOR_SWING_TICKS`]. Laid out and
1637    /// placed like `examples/animation.rs`'s own prompt.
1638    fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639        let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640        let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641        let text = if swinging {
1642            "opening"
1643        } else if near && !self.door_opening {
1644            "e opens the door"
1645        } else {
1646            return;
1647        };
1648
1649        let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650        let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651        let window_size = ctx.window_size();
1652        let pixels_per_point = ctx.pixels_per_point();
1653        let Some(pixel) = camera.pixel_of(point, window_size) else {
1654            return;
1655        };
1656
1657        ctx.ui(|ui| {
1658            let painter = ui.painter();
1659            let at = logical(pixel, pixels_per_point);
1660            let ink = galley.mesh_bounds;
1661            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662            let backdrop = egui::Rect::from_center_size(
1663                at,
1664                ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665            );
1666            painter.rect_filled(
1667                backdrop,
1668                DOOR_PROMPT_PADDING,
1669                egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670            );
1671            painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672        });
1673    }
1674
1675    /// The gem, spinning and bobbing over the chamber's floor, and the light
1676    /// it casts over it.
1677    fn draw_gem(&self, ctx: &mut FrameContext<'_, Keep>) {
1678        let t = self.simulated.as_secs_f32();
1679        let bob = (t * 2.0).sin() * GEM_BOB_HEIGHT;
1680        ctx.light(
1681            Light::point(
1682                GEM_POSITION + Vec3::Y * (bob + GEM_LIGHT_LIFT),
1683                GEM_LIGHT_COLOR,
1684                GEM_LIGHT_RANGE,
1685            )
1686            .shadow(),
1687        );
1688        ctx.draw(
1689            Gem.at(Transform::from_scale_rotation_translation(
1690                Vec3::ONE,
1691                Quat::from_rotation_y(t * GEM_SPIN_SPEED),
1692                GEM_POSITION + Vec3::Y * bob,
1693            ))
1694            .material(Material::shaded(GEM_COLOR, 0.7).emissive(GEM_COLOR.dimmed(1.6))),
1695        );
1696    }
1697
1698    /// The player: upright so it always faces the camera about `+Y`,
1699    /// windowed to its facing's row and the walk cycle's current frame.
1700    fn draw_walker(&self, ctx: &mut FrameContext<'_, Keep>, ground: Vec3) {
1701        let step = if self.walk_ticks > 0 {
1702            (self.walk_ticks / TICKS_PER_WALK_FRAME) % WALKER_COLUMNS
1703        } else {
1704            0
1705        };
1706        let cell = Sheet::new(UVec2::new(WALKER_COLUMNS, WALKER_ROWS))
1707            .cell_at(UVec2::new(step, self.facing as u32));
1708        let size = Vec2::new(WALKER_WIDTH, WALKER_HEIGHT);
1709
1710        ctx.draw(
1711            Walker
1712                .at(Transform::from_scale_rotation_translation(
1713                    size.extend(1.0),
1714                    Quat::IDENTITY,
1715                    ground + Vec3::Y * (WALKER_HEIGHT * 0.5),
1716                ))
1717                .upright()
1718                .frame(cell),
1719        );
1720    }
examples/ui-fonts.rs (lines 671-678)
657    fn draw_station(&self, ctx: &mut FrameContext<'_, Self>, station: StationKind) {
658        let look = station.look();
659        let center = station.center();
660        let front_offset =
661            STATION_SIZE.z * 0.5 - STATION_FRONT_SIZE.z * 0.5 + STATION_FRONT_OUTWARD;
662        let front = center - Vec3::new(0.0, 0.0, front_offset);
663        for (size, position, material) in [
664            (STATION_SIZE, center, Material::lit(look.color)),
665            (
666                STATION_FRONT_SIZE,
667                front,
668                Material::color(Color::BLACK).emissive(look.glow),
669            ),
670        ] {
671            ctx.draw(
672                Cube.at(Transform::from_scale_rotation_translation(
673                    size,
674                    Quat::IDENTITY,
675                    position,
676                ))
677                .material(material),
678            );
679        }
680    }
681
682    fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683        let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684        let window_size = ctx.window_size();
685        let Some(pixel) = camera.pixel_of(top, window_size) else {
686            return;
687        };
688        let at = logical(pixel, ctx.pixels_per_point());
689
690        let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691        let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692        let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693        let number = ctx.text_layout(
694            &number_text,
695            egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696        );
697
698        ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699    }
700
701    /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702    /// `hovered`: what a player presses to reach one, apart from a hover.
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
733
734    /// The title, a line and the reading, each in a font this game loaded
735    /// rather than egui's own.
736    fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737        ctx.ui(|ui| {
738            ui.label(styled(
739                "a game's own fonts",
740                egui::FontId::proportional(HEADING_SIZE),
741            ));
742            ui.label(styled(
743                "drawn in Pixel Operator, the game's proportional font",
744                egui::FontId::proportional(BODY_SIZE),
745            ));
746            ui.label(styled(
747                "the readings above each station in Pixel Operator Mono",
748                egui::FontId::monospace(BODY_SIZE),
749            ));
750        });
751    }
752
753    fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754        let Some(dialogue) = &self.dialogue else {
755            return;
756        };
757        let whole = ctx.text_layout(
758            dialogue.current_line(),
759            egui::FontId::proportional(BODY_SIZE),
760        );
761        let size = whole.size();
762        ctx.ui(|ui| dialogue.draw(ui, size));
763    }
764
765    /// The `StationKind` under the pointer, `None` while the UI holds it.
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
775
776    /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777    /// motion; the wheel zooms it.
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
787}
788
789impl Game for WatchRoom {
790    type Meshes = Shape;
791    type Sounds = NoSounds;
792    type InputActions = Controls;
793    type Skyboxes = Sky;
794    type SurfaceStyles = NoSurfaceStyles;
795    type PostEffects = NoPostEffects;
796
797    fn tick(&mut self, ctx: &mut TickContext<'_, Self>) {
798        self.elapsed += ctx.dt();
799        self.orbit.yaw += AUTO_TURN_RATE * ctx.dt().as_secs_f32();
800
801        if let Some(dialogue) = &mut self.dialogue {
802            dialogue.tick();
803        }
804        if ctx.pressed(Trigger::Close) {
805            self.dialogue = None;
806            self.hailed = None;
807        }
808        if ctx.pressed(Trigger::Sheet) {
809            self.sheet_open = !self.sheet_open;
810        }
811        if ctx.pressed(Trigger::Hail) && !ctx.ui_wants_pointer() {
812            self.handle_hail(ctx);
813        }
814    }
815
816    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817        self.steer(ctx);
818
819        let camera = self.orbit.camera();
820        ctx.set_camera(camera);
821        ctx.set_skybox(Sky::Dusk);
822        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824        ctx.draw(
825            Plane
826                .at(Transform::from_scale(Vec3::new(
827                    PLATFORM_SIZE,
828                    1.0,
829                    PLATFORM_SIZE,
830                )))
831                .material(Material::lit(PLATFORM_COLOR)),
832        );
833        for station in StationKind::ALL {
834            self.draw_station(ctx, station);
835        }
836
837        let hovered = Self::hovered(ctx);
838        if !self.sheet_open {
839            if let Some(station) = hovered {
840                ctx.set_cursor(Cursor::Pointer);
841                self.draw_bracket(ctx, camera, station);
842            }
843            self.draw_prompts(ctx, camera, hovered);
844        }
845        if self.dialogue.is_some() {
846            self.draw_dialogue(ctx);
847        }
848        if self.sheet_open {
849            ctx.ui(sheet);
850        }
851        self.panel(ctx);
852    }
examples/post-effects.rs (lines 93-101)
90    fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93        ctx.draw(
94            Plane
95                .at(Transform::from_scale(Vec3::new(
96                    GROUND_SIZE,
97                    1.0,
98                    GROUND_SIZE,
99                )))
100                .material(Material::lit(GROUND_COLOR)),
101        );
102        ctx.draw(
103            Cube.at(Transform::from_scale_rotation_translation(
104                Vec3::splat(GLOW_SIZE),
105                Quat::IDENTITY,
106                GLOW_POSITION,
107            ))
108            .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109        );
110        for position in SPHERE_POSITIONS {
111            ctx.draw(
112                Sphere {
113                    subdivisions: SPHERE_SUBDIVISIONS,
114                }
115                .at(position)
116                .material(Material::lit(SPHERE_COLOR)),
117            );
118        }
119    }
examples/breakout-game.rs (lines 605-613)
604    fn draw_court(&self, ctx: &mut FrameContext<'_, Breakout>) {
605        ctx.draw(
606            Plane
607                .at(Transform::from_scale(Vec3::new(
608                    COURT_HALF_WIDTH * 2.0,
609                    1.0,
610                    COURT_HALF_DEPTH * 2.0,
611                )))
612                .material(Material::lit(FLOOR_COLOR)),
613        );
614
615        let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, COURT_HALF_DEPTH);
616        for side in [-1.0, 1.0] {
617            let x = side * (COURT_HALF_WIDTH - WALL_THICKNESS * 0.5);
618            ctx.draw(
619                Cube.at(Transform::from_scale_rotation_translation(
620                    side_half * 2.0,
621                    Quat::IDENTITY,
622                    Vec3::new(x, side_half.y, 0.0),
623                ))
624                .material(Material::lit(WALL_COLOR)),
625            );
626        }
627
628        let top_half = Vec3::new(COURT_HALF_WIDTH, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
629        ctx.draw(
630            Cube.at(Transform::from_scale_rotation_translation(
631                top_half * 2.0,
632                Quat::IDENTITY,
633                Vec3::new(0.0, top_half.y, -COURT_HALF_DEPTH + WALL_THICKNESS * 0.5),
634            ))
635            .material(Material::lit(WALL_COLOR)),
636        );
637    }
638
639    fn draw_bricks(&self, ctx: &mut FrameContext<'_, Breakout>) {
640        let scale = Vec3::new(
641            BRICK_HALF_WIDTH * 2.0,
642            BRICK_HALF_HEIGHT * 2.0,
643            BRICK_HALF_DEPTH * 2.0,
644        );
645        for brick in self.bricks.iter().filter(|brick| brick.hits_remaining > 0) {
646            let health = f32::from(brick.hits_remaining) / f32::from(BRICK_HITS);
647            let color = BRICK_ROW_COLORS[brick.row].dimmed(0.4 + 0.6 * health);
648            ctx.draw(
649                Cube.at(Transform::from_scale_rotation_translation(
650                    scale,
651                    Quat::IDENTITY,
652                    brick.position,
653                ))
654                .material(Material::shaded(color, health)),
655            );
656        }
657    }
658
659    /// Draws the live spark burst: additive, tumbling by roll as they age,
660    /// shrinking and fading out over their lifetime.
661    fn draw_sparks(&self, ctx: &mut FrameContext<'_, Breakout>) {
662        for spark in &self.sparks {
663            let age = (spark.age / SPARK_LIFETIME).clamp(0.0, 1.0);
664            let fade = 1.0 - age;
665            let size = SPARK_SIZE_START.lerp(SPARK_SIZE_END, age);
666            ctx.draw(
667                Quad.at(Transform::from_scale_rotation_translation(
668                    Vec3::splat(size),
669                    Quat::IDENTITY,
670                    spark.position,
671                ))
672                .billboard()
673                .roll(spark.roll + spark.age * SPARK_SPIN_SPEED)
674                .material(
675                    Material::color(spark.color.with_alpha(fade))
676                        .emissive(spark.color.dimmed(SPARK_EMISSIVE_PEAK))
677                        .additive(),
678                ),
679            );
680        }
681    }
682
683    /// Draws the ball's ghost trail, each ghost smaller and more transparent
684    /// than the one ahead of it; each ghost's position interpolates between
685    /// its own last two resolved ticks by the same `alpha` the ball itself
686    /// draws at, and its radius clamps to what the ball's own radius has
687    /// left over its distance from the head, so a ghost still close to the
688    /// ball never draws past its edge.
689    fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690        let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691        for i in 0..TRAIL_LEN {
692            let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693            let age = (i + 1) as f32 / TRAIL_LEN as f32;
694            let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695            let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696                .min((BALL_RADIUS - head.distance(position)).max(0.0));
697            let scale = Vec3::splat(radius * 2.0);
698            ctx.draw(
699                Sphere { subdivisions: 2 }
700                    .at(Transform::from_scale_rotation_translation(
701                        scale,
702                        Quat::IDENTITY,
703                        position,
704                    ))
705                    .material(
706                        Material::color(BALL_GLOW.with_alpha(fade))
707                            .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708                    ),
709            );
710        }
711    }
712
713    /// Draws one held ball for every life past the one in play, set in a
714    /// row alongside the paddle's own path.
715    fn draw_lives(&self, ctx: &mut FrameContext<'_, Breakout>) {
716        let held_lives = self.lives.saturating_sub(1);
717        for slot in 0..held_lives {
718            let z = PADDLE_Z + (slot + 1) as f32 * LIFE_ROW_SPACING;
719            ctx.draw(
720                Sphere { subdivisions: 2 }
721                    .at(Transform::from_scale_rotation_translation(
722                        Vec3::splat(BALL_RADIUS * 2.0),
723                        Quat::IDENTITY,
724                        Vec3::new(LIFE_ROW_X, BALL_RADIUS, z),
725                    ))
726                    .material(
727                        Material::color(BALL_GLOW)
728                            .emissive(BALL_EMISSIVE)
729                            .additive(),
730                    ),
731            );
732        }
733    }
734
735    fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736        let bricks_left = self
737            .bricks
738            .iter()
739            .filter(|brick| brick.hits_remaining > 0)
740            .count();
741        // Read before `ctx.ui` so a rebind changes what the hint reads this
742        // frame too.
743        let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744        let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745        let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746        ctx.ui(|ui| {
747            ui.horizontal(|ui| {
748                ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749                ui.label(format!("{bricks_left} bricks left"));
750            });
751            ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752            if self.phase == Phase::Serving {
753                ui.label(format!("{serve_hint} to serve"));
754            }
755        });
756
757        match self.phase {
758            Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759            Phase::Won => self.menu(ctx, "you win", true),
760            Phase::Lost => self.menu(ctx, "game over", true),
761            _ => {}
762        }
763    }
764
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
864
865    /// Sustains both tracks every frame, and the gain goes to whichever the
866    /// game calls for: gameplay music while a round is live, serving
867    /// included, and menu music whenever a menu covers it.
868    ///
869    /// Each fades in over [`MUSIC_CROSSFADE`] and slides every later gain
870    /// over it, which is the crossfade itself; the one at no gain costs no
871    /// voice while its playback goes on under the other.
872    fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873        let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874        let gain = |wanted: bool| match wanted {
875            true => MUSIC_GAIN,
876            false => 0.0,
877        };
878
879        ctx.sustain(
880            Sound::Music
881                .gain(gain(playing))
882                .fade(MUSIC_CROSSFADE)
883                .glide(MUSIC_CROSSFADE)
884                .loop_from(MUSIC_LOOP_FROM),
885        );
886        ctx.sustain(
887            Sound::MenuMusic
888                .gain(gain(!playing))
889                .fade(MUSIC_CROSSFADE)
890                .glide(MUSIC_CROSSFADE)
891                .loop_from(MENU_MUSIC_LOOP_FROM),
892        );
893    }
894}
895
896/// One action's name, its live bindings, a rebind control that starts
897/// listening for a new one, and a reset to its defaults; cancel is a
898/// button rather than Escape, since Escape is itself a binding a listen
899/// could capture.
900fn controls_row(
901    ui: &mut egui::Ui,
902    name: &str,
903    bindings: &str,
904    listening: bool,
905    target: &mut Option<Listening>,
906    action: Listening,
907    reset: &mut Option<Listening>,
908) {
909    ui.horizontal(|ui| {
910        ui.label(format!("{name}: {bindings}"));
911        if listening {
912            ui.label("listening");
913            if ui.button("cancel").clicked() {
914                *target = None;
915            }
916        } else if ui.button("rebind").clicked() {
917            *target = Some(action);
918        }
919        if ui.button("reset").clicked() {
920            *reset = Some(action);
921        }
922    });
923}
924
925/// The controls-menu text for a live binding list: each alternative,
926/// separated, in the order the player can use them.
927fn bindings_text<B: Display>(bindings: Vec<B>) -> String {
928    bindings
929        .iter()
930        .map(ToString::to_string)
931        .collect::<Vec<_>>()
932        .join(", ")
933}
934
935fn spawn_bricks() -> Vec<Brick> {
936    let cell = BRICK_HALF_WIDTH * 2.0 + BRICK_GAP;
937    let row_span = BRICK_HALF_DEPTH * 2.0 + BRICK_ROW_GAP;
938    let grid_width = cell * BRICK_COLUMNS as f32 - BRICK_GAP;
939    let start_x = -grid_width * 0.5 + BRICK_HALF_WIDTH;
940    let start_z = -COURT_HALF_DEPTH + WALL_THICKNESS + BRICK_HALF_DEPTH + 0.6;
941
942    (0..BRICK_ROWS)
943        .flat_map(|row| {
944            (0..BRICK_COLUMNS).map(move |column| Brick {
945                row,
946                position: Vec3::new(
947                    start_x + column as f32 * cell,
948                    BRICK_HALF_HEIGHT,
949                    start_z + row as f32 * row_span,
950                ),
951                hits_remaining: BRICK_HITS,
952            })
953        })
954        .collect()
955}
956
957impl Game for Breakout {
958    type Meshes = Shape;
959    type Sounds = Sound;
960    type InputActions = Controls;
961    type Skyboxes = NoSkyboxes;
962    type SurfaceStyles = NoSurfaceStyles;
963    type PostEffects = NoPostEffects;
964
965    fn tick(&mut self, ctx: &mut TickContext<'_, Breakout>) {
966        if self.paused {
967            return;
968        }
969
970        let dt = ctx.dt().as_secs_f32();
971        self.paddle_flash = (self.paddle_flash - dt).max(0.0);
972        self.brick_flash = (self.brick_flash - dt).max(0.0);
973        self.life_lost_flash = (self.life_lost_flash - dt).max(0.0);
974        self.step_sparks(dt);
975
976        // Decay runs before the end-screen return below, so the last pulse and
977        // burst do not stay on screen.
978        if matches!(self.phase, Phase::Won | Phase::Lost) {
979            return;
980        }
981
982        let axis = if ctx.ui_wants_keyboard() {
983            0.0
984        } else {
985            ctx.axis(Move::Paddle)
986        };
987        self.step_paddle(axis, dt);
988
989        match self.phase {
990            Phase::Serving => self.hold_ball(ctx),
991            _ => self.step_ball(ctx, dt),
992        }
993    }
994
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
Source

pub fn set_surface_style<T: SurfaceStyle>(&mut self, style: T)
where G::SurfaceStyles: Holds<T>,

Passes the style T the values its WGSL reads this frame; the last call for a style is the one it reads.

A frame that never calls this for a style leaves it reading the default value of every field. Takes a style of Game::SurfaceStyles and no other.

Examples found in repository?
examples/sprite-adventure.rs (lines 1833-1835)
1832    fn frame(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1833        ctx.set_surface_style(Water {
1834            time: self.simulated.as_secs_f32(),
1835        });
1836
1837        match self.area {
1838            Area::Overworld => self.frame_overworld(ctx),
1839            Area::Cave => self.frame_cave(ctx),
1840        }
1841
1842        self.overlay(ctx);
1843    }
More examples
Hide additional examples
examples/material-playground.rs (line 974)
951    fn draw_outpost(&self, ctx: &mut FrameContext<'_, Self>) {
952        let clock = ctx.elapsed().as_secs_f32();
953
954        for &(position, scale) in &PILLARS {
955            ctx.draw(
956                Cube.at(Transform::from_scale_rotation_translation(
957                    scale,
958                    Quat::IDENTITY,
959                    OUTPOST + position,
960                ))
961                .material(Material::lit(Color::rgb(0.55, 0.5, 0.45))),
962            );
963        }
964
965        ctx.draw(
966            Cube.at(Transform::from_scale_rotation_translation(
967                POLE_SCALE,
968                Quat::IDENTITY,
969                OUTPOST + POLE_POSITION,
970            ))
971            .material(Material::lit(Color::rgb(0.3, 0.24, 0.18))),
972        );
973
974        ctx.set_surface_style(Banner { time: clock });
975        ctx.draw(
976            BannerCloth
977                .at(Transform::from_translation(OUTPOST + BANNER_MOUNT))
978                .material(Material::lit(Color::rgb(0.75, 0.12, 0.12)))
979                .surface_style::<Banner>(),
980        );
981
982        ctx.set_surface_style(Field {
983            tint: Color::rgb(0.25, 0.75, 1.0),
984            time: clock,
985        });
986        ctx.draw(
987            Sphere { subdivisions: 2 }
988                .at(Transform::from_scale_rotation_translation(
989                    Vec3::splat(FIELD_ORB_SCALE),
990                    Quat::IDENTITY,
991                    OUTPOST + FIELD_ORB_POSITION,
992                ))
993                .material(Material::color(Color::BLACK))
994                .surface_style::<Field>(),
995        );
996    }
Source

pub fn set_post_effect<T: PostEffect>(&mut self, effect: T)
where G::PostEffects: Holds<T>,

Runs the post effect T over this frame with the values its WGSL reads; the last call for an effect is the one it runs with.

A frame that never calls this for an effect runs no pass for it. Takes an effect of Game::PostEffects and no other.

Examples found in repository?
examples/post-effects.rs (lines 131-133)
124    fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
125        ctx.ui(|ui| {
126            ui.add(egui::Slider::new(&mut self.vignette, 0.0..=1.0).text("vignette"));
127            ui.add(egui::Slider::new(&mut self.grain, 0.0..=1.0).text("grain"));
128            ui.add(egui::Slider::new(&mut self.scanlines, 0.0..=1.0).text("scanlines"));
129        });
130
131        ctx.set_post_effect(Vignette {
132            strength: self.vignette,
133        });
134        ctx.set_post_effect(Grain {
135            strength: self.grain,
136            seed: self.ticks,
137        });
138        ctx.set_post_effect(Scanlines {
139            strength: self.scanlines,
140        });
141    }
Source

pub fn set_skybox(&mut self, sky: G::Skyboxes)

Draws and lights this frame by sky: what it draws where nothing else was drawn, and the ambient — the light every surface takes from every direction.

Takes a value of Game::Skyboxes and no other; startup built every one of them. The last call in a frame is the one it draws, and a frame that never calls this draws and is lit by the default sky.

Examples found in repository?
examples/material-playground.rs (line 1154)
1151    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152        self.fly_camera(ctx);
1153        ctx.set_camera(self.camera());
1154        ctx.set_skybox(self.sky);
1155        for light in self.lights() {
1156            ctx.light(light);
1157        }
1158        ctx.set_exposure(self.exposure);
1159        ctx.set_bloom(self.bloom);
1160
1161        self.draw_scene(ctx);
1162        self.controls(ctx);
1163    }
More examples
Hide additional examples
examples/flock-parallelism.rs (line 747)
741    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742        self.apply_settings();
743
744        let center = Vec3::from(self.butterflies.center());
745        let elapsed = ctx.elapsed().as_secs_f32();
746        ctx.set_camera(Self::camera(center, self.world, elapsed));
747        ctx.set_skybox(Sky::Day);
748        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750        Self::draw_ground(ctx);
751        self.draw_butterflies(ctx);
752        self.panel(ctx);
753    }
examples/isometric-board.rs (line 822)
819    fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820        let camera = Self::camera();
821        ctx.set_camera(camera);
822        ctx.set_skybox(Sky::Day);
823        ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824        ctx.set_bloom(BLOOM);
825
826        let hover = self.hovered(ctx);
827        self.draw_ground(ctx);
828        self.draw_board(ctx, hover);
829        self.draw_current_mark(ctx);
830        self.draw_rocks(ctx);
831        self.draw_sprite(ctx, hover);
832        self.draw_block(ctx, hover);
833        self.draw_prompt(ctx, camera, hover);
834
835        self.overlay(ctx);
836    }
examples/stress-preview.rs (line 610)
602    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603        self.apply_settings();
604        self.frame_times.record(ctx.dt());
605
606        let elapsed = ctx.elapsed().as_secs_f32();
607        self.handle_camera(ctx, elapsed);
608        let camera = self.camera(elapsed);
609        ctx.set_camera(camera);
610        ctx.set_skybox(Sky::Day);
611
612        let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613        ctx.light(if self.settings.sun_shadow {
614            sun.shadow()
615        } else {
616            sun
617        });
618
619        Self::draw_ground(ctx);
620        self.draw_field(ctx, elapsed);
621        self.controls(ctx, &camera);
622    }
examples/ui-fonts.rs (line 821)
816    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817        self.steer(ctx);
818
819        let camera = self.orbit.camera();
820        ctx.set_camera(camera);
821        ctx.set_skybox(Sky::Dusk);
822        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824        ctx.draw(
825            Plane
826                .at(Transform::from_scale(Vec3::new(
827                    PLATFORM_SIZE,
828                    1.0,
829                    PLATFORM_SIZE,
830                )))
831                .material(Material::lit(PLATFORM_COLOR)),
832        );
833        for station in StationKind::ALL {
834            self.draw_station(ctx, station);
835        }
836
837        let hovered = Self::hovered(ctx);
838        if !self.sheet_open {
839            if let Some(station) = hovered {
840                ctx.set_cursor(Cursor::Pointer);
841                self.draw_bracket(ctx, camera, station);
842            }
843            self.draw_prompts(ctx, camera, hovered);
844        }
845        if self.dialogue.is_some() {
846            self.draw_dialogue(ctx);
847        }
848        if self.sheet_open {
849            ctx.ui(sheet);
850        }
851        self.panel(ctx);
852    }
examples/sound-lab.rs (line 987)
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
Source

pub fn light(&mut self, light: Light)

Lights the frame with light, in addition to any already submitted.

The first call replaces the default environment’s light; past MAX_LIGHTS, excess is ignored — warned the first frame, a debug log after.

Examples found in repository?
examples/material-playground.rs (line 1156)
1151    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152        self.fly_camera(ctx);
1153        ctx.set_camera(self.camera());
1154        ctx.set_skybox(self.sky);
1155        for light in self.lights() {
1156            ctx.light(light);
1157        }
1158        ctx.set_exposure(self.exposure);
1159        ctx.set_bloom(self.bloom);
1160
1161        self.draw_scene(ctx);
1162        self.controls(ctx);
1163    }
More examples
Hide additional examples
examples/flock-parallelism.rs (line 748)
741    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742        self.apply_settings();
743
744        let center = Vec3::from(self.butterflies.center());
745        let elapsed = ctx.elapsed().as_secs_f32();
746        ctx.set_camera(Self::camera(center, self.world, elapsed));
747        ctx.set_skybox(Sky::Day);
748        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750        Self::draw_ground(ctx);
751        self.draw_butterflies(ctx);
752        self.panel(ctx);
753    }
examples/isometric-board.rs (line 823)
819    fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820        let camera = Self::camera();
821        ctx.set_camera(camera);
822        ctx.set_skybox(Sky::Day);
823        ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824        ctx.set_bloom(BLOOM);
825
826        let hover = self.hovered(ctx);
827        self.draw_ground(ctx);
828        self.draw_board(ctx, hover);
829        self.draw_current_mark(ctx);
830        self.draw_rocks(ctx);
831        self.draw_sprite(ctx, hover);
832        self.draw_block(ctx, hover);
833        self.draw_prompt(ctx, camera, hover);
834
835        self.overlay(ctx);
836    }
examples/stress-preview.rs (lines 613-617)
602    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603        self.apply_settings();
604        self.frame_times.record(ctx.dt());
605
606        let elapsed = ctx.elapsed().as_secs_f32();
607        self.handle_camera(ctx, elapsed);
608        let camera = self.camera(elapsed);
609        ctx.set_camera(camera);
610        ctx.set_skybox(Sky::Day);
611
612        let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613        ctx.light(if self.settings.sun_shadow {
614            sun.shadow()
615        } else {
616            sun
617        });
618
619        Self::draw_ground(ctx);
620        self.draw_field(ctx, elapsed);
621        self.controls(ctx, &camera);
622    }
examples/post-effects.rs (line 91)
90    fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93        ctx.draw(
94            Plane
95                .at(Transform::from_scale(Vec3::new(
96                    GROUND_SIZE,
97                    1.0,
98                    GROUND_SIZE,
99                )))
100                .material(Material::lit(GROUND_COLOR)),
101        );
102        ctx.draw(
103            Cube.at(Transform::from_scale_rotation_translation(
104                Vec3::splat(GLOW_SIZE),
105                Quat::IDENTITY,
106                GLOW_POSITION,
107            ))
108            .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109        );
110        for position in SPHERE_POSITIONS {
111            ctx.draw(
112                Sphere {
113                    subdivisions: SPHERE_SUBDIVISIONS,
114                }
115                .at(position)
116                .material(Material::lit(SPHERE_COLOR)),
117            );
118        }
119    }
examples/ui-fonts.rs (line 822)
816    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817        self.steer(ctx);
818
819        let camera = self.orbit.camera();
820        ctx.set_camera(camera);
821        ctx.set_skybox(Sky::Dusk);
822        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824        ctx.draw(
825            Plane
826                .at(Transform::from_scale(Vec3::new(
827                    PLATFORM_SIZE,
828                    1.0,
829                    PLATFORM_SIZE,
830                )))
831                .material(Material::lit(PLATFORM_COLOR)),
832        );
833        for station in StationKind::ALL {
834            self.draw_station(ctx, station);
835        }
836
837        let hovered = Self::hovered(ctx);
838        if !self.sheet_open {
839            if let Some(station) = hovered {
840                ctx.set_cursor(Cursor::Pointer);
841                self.draw_bracket(ctx, camera, station);
842            }
843            self.draw_prompts(ctx, camera, hovered);
844        }
845        if self.dialogue.is_some() {
846            self.draw_dialogue(ctx);
847        }
848        if self.sheet_open {
849            ctx.ui(sheet);
850        }
851        self.panel(ctx);
852    }
Source

pub fn set_exposure(&mut self, exposure: f32)

Scales the frame’s light before the curve, as a fraction of it; a value under 0.0 is clamped to it, and 1.0 is used where a frame never calls this.

The last call in a frame replaces the rest.

Examples found in repository?
examples/material-playground.rs (line 1158)
1151    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152        self.fly_camera(ctx);
1153        ctx.set_camera(self.camera());
1154        ctx.set_skybox(self.sky);
1155        for light in self.lights() {
1156            ctx.light(light);
1157        }
1158        ctx.set_exposure(self.exposure);
1159        ctx.set_bloom(self.bloom);
1160
1161        self.draw_scene(ctx);
1162        self.controls(ctx);
1163    }
More examples
Hide additional examples
examples/breakout-game.rs (line 1009)
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
examples/animation.rs (line 936)
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
Source

pub fn set_bloom(&mut self, amount: f32)

Spreads the frame’s brightest light over what is around it; the value is clamped into 0.0..=1.0, and 0.0 is used where a frame never calls this.

The value is the fraction of the frame the spread replaces, and 0.0 is no work at all. The last call in a frame replaces the rest.

Examples found in repository?
examples/post-effects.rs (line 161)
156    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
157        ctx.set_camera(Camera::new(
158            View::look_at(Vec3::new(0.0, 3.4, 4.6), Vec3::new(0.0, 0.2, 0.0)),
159            Projection::perspective(45.0),
160        ));
161        ctx.set_bloom(SCENE_BLOOM);
162
163        self.draw_scene(ctx);
164        self.panel(ctx);
165    }
More examples
Hide additional examples
examples/material-playground.rs (line 1159)
1151    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152        self.fly_camera(ctx);
1153        ctx.set_camera(self.camera());
1154        ctx.set_skybox(self.sky);
1155        for light in self.lights() {
1156            ctx.light(light);
1157        }
1158        ctx.set_exposure(self.exposure);
1159        ctx.set_bloom(self.bloom);
1160
1161        self.draw_scene(ctx);
1162        self.controls(ctx);
1163    }
examples/isometric-board.rs (line 824)
819    fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820        let camera = Self::camera();
821        ctx.set_camera(camera);
822        ctx.set_skybox(Sky::Day);
823        ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824        ctx.set_bloom(BLOOM);
825
826        let hover = self.hovered(ctx);
827        self.draw_ground(ctx);
828        self.draw_board(ctx, hover);
829        self.draw_current_mark(ctx);
830        self.draw_rocks(ctx);
831        self.draw_sprite(ctx, hover);
832        self.draw_block(ctx, hover);
833        self.draw_prompt(ctx, camera, hover);
834
835        self.overlay(ctx);
836    }
examples/sound-lab.rs (line 988)
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
examples/breakout-game.rs (line 1006)
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
examples/animation.rs (line 937)
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
Source

pub fn play(&mut self, sound: impl Into<SoundCue<G::Sounds>>)

Plays sound once, keeping wherever it is placed as of this frame.

Every call is a voice of its own, so the same sound twice over is heard twice.

Examples found in repository?
examples/sound-lab.rs (line 1013)
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
More examples
Hide additional examples
examples/breakout-game.rs (line 858)
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
Source

pub fn sustain(&mut self, sound: impl Into<SoundCue<G::Sounds>>)

Keeps sound playing while frames go on declaring it, and fades it out over its fade once one does not.

What a frame declares is the whole of what it wants sounding. One voice per value, whose knobs follow what each frame declares; the last call for a value in a frame is the one that counts. A value a frame stops declaring is dropped once it has faded, so declaring it again after that starts it at its window start.

Examples found in repository?
examples/sound-lab.rs (lines 842-849)
836    fn sustain_ring(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
837        if !self.ring_demo {
838            return;
839        }
840        for nth in 0..RING_COUNT {
841            let gain = RING_GAIN * (1.0 - nth as f32 / RING_COUNT as f32);
842            ctx.sustain(
843                Sound::Pulse
844                    .at(ring_place(nth))
845                    .gain(gain)
846                    .reference(RING_REFERENCE)
847                    .range(RING_RADIUS * 3.0)
848                    .instance(nth + 1),
849            );
850        }
851    }
852
853    fn sustain_cues(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
854        let fade = Duration::from_secs_f32(self.cue_fade);
855        if self.theme_on {
856            ctx.sustain(Sound::Theme.gain(0.5).fade(fade));
857        }
858        if self.menu_on {
859            ctx.sustain(Sound::MenuTheme.gain(0.5).fade(fade));
860        }
861        if self.pulse_on {
862            ctx.sustain(Sound::Pulse.gain(0.3).fade(fade));
863        }
864    }
865}
866
867/// Where the `nth` sustain of the ring stands: around the room, starting
868/// behind the listener's back.
869fn ring_place(nth: u32) -> Vec3 {
870    let turn = TAU * nth as f32 / RING_COUNT as f32;
871
872    Vec3::new(
873        turn.sin() * RING_RADIUS,
874        SOURCE_HEIGHT,
875        turn.cos() * RING_RADIUS,
876    )
877}
878
879/// The direction the ear pair is offset along — the same side the
880/// engine's own pan reads.
881fn listener_right(view: View) -> Vec3 {
882    (view.target() - view.eye())
883        .normalize_or_zero()
884        .cross(view.up())
885}
886
887/// The clip's duration, with two trim handles and a loop marker, each moved
888/// by the pointer's own place rather than by a moving total.
889fn duration_bar(
890    ui: &mut egui::Ui,
891    duration: f32,
892    trim_start: &mut f32,
893    trim_end: &mut f32,
894    loop_from: &mut f32,
895) {
896    let size = egui::vec2(ui.available_width().min(420.0), 28.0);
897    let (rect, _response) = ui.allocate_exact_size(size, egui::Sense::hover());
898    let painter = ui.painter();
899    painter.rect_filled(rect, 3.0, egui::Color32::from_gray(35));
900
901    let x_of = |seconds: f32| rect.left() + (seconds / duration).clamp(0.0, 1.0) * rect.width();
902    let seconds_of = |x: f32| ((x - rect.left()) / rect.width()).clamp(0.0, 1.0) * duration;
903
904    let span = egui::Rect::from_min_max(
905        egui::pos2(x_of(*trim_start), rect.top()),
906        egui::pos2(x_of(*trim_end), rect.bottom()),
907    );
908    painter.rect_filled(span, 3.0, egui::Color32::from_rgb(70, 120, 95));
909
910    let start_x = x_of(*trim_start);
911    if let Some(x) = drag_handle(
912        ui,
913        rect,
914        "trim-start",
915        start_x,
916        egui::Color32::from_rgb(230, 200, 80),
917    ) {
918        *trim_start = seconds_of(x).min(*trim_end);
919    }
920    let end_x = x_of(*trim_end);
921    if let Some(x) = drag_handle(
922        ui,
923        rect,
924        "trim-end",
925        end_x,
926        egui::Color32::from_rgb(230, 200, 80),
927    ) {
928        *trim_end = seconds_of(x).max(*trim_start);
929    }
930    let loop_x = x_of(*loop_from);
931    if let Some(x) = drag_handle(
932        ui,
933        rect,
934        "loop-from",
935        loop_x,
936        egui::Color32::from_rgb(90, 170, 230),
937    ) {
938        *loop_from = seconds_of(x).clamp(*trim_start, *trim_end);
939    }
940}
941
942/// One round handle at `x`. Returns the pointer's `x` while a drag holds
943/// it.
944fn drag_handle(
945    ui: &mut egui::Ui,
946    bar: egui::Rect,
947    salt: &str,
948    x: f32,
949    color: egui::Color32,
950) -> Option<f32> {
951    let radius = 6.0;
952    let center = egui::pos2(x, bar.center().y);
953    let sense_rect = egui::Rect::from_center_size(center, egui::Vec2::splat(radius * 2.5));
954    let id = ui.id().with(salt);
955    let response = ui.interact(sense_rect, id, egui::Sense::drag());
956    ui.painter().circle_filled(center, radius, color);
957
958    response
959        .dragged()
960        .then(|| response.interact_pointer_pos())
961        .flatten()
962        .map(|pos| pos.x)
963}
964
965impl Game for SoundCheck {
966    type Meshes = Shape;
967    type Sounds = Sound;
968    type InputActions = Controls;
969    type Skyboxes = Sky;
970    type SurfaceStyles = NoSurfaceStyles;
971    type PostEffects = NoPostEffects;
972
973    fn tick(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
974        self.handle_walk(ctx);
975        self.handle_drag(ctx);
976    }
977
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
More examples
Hide additional examples
examples/breakout-game.rs (lines 879-885)
872    fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873        let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874        let gain = |wanted: bool| match wanted {
875            true => MUSIC_GAIN,
876            false => 0.0,
877        };
878
879        ctx.sustain(
880            Sound::Music
881                .gain(gain(playing))
882                .fade(MUSIC_CROSSFADE)
883                .glide(MUSIC_CROSSFADE)
884                .loop_from(MUSIC_LOOP_FROM),
885        );
886        ctx.sustain(
887            Sound::MenuMusic
888                .gain(gain(!playing))
889                .fade(MUSIC_CROSSFADE)
890                .glide(MUSIC_CROSSFADE)
891                .loop_from(MENU_MUSIC_LOOP_FROM),
892        );
893    }
Source

pub fn set_listener(&mut self, listener: View)

Hears the frame from listener, in place of the frame’s camera.

Examples found in repository?
examples/sound-lab.rs (line 984)
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
Source

pub fn set_volume(&mut self, volume: f32)

Plays everything this frame at volume, the fraction of each sound’s own gain it multiplies; 1.0 where a frame never calls this.

The mix slides to it over SoundCue::DEFAULT_GLIDE, so a slider a player moves never steps the sound.

Examples found in repository?
examples/sound-lab.rs (line 979)
978    fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979        ctx.set_volume(self.master_volume);
980
981        let player = self.player_prev.lerp(self.player, ctx.alpha());
982        let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983        let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984        ctx.set_listener(listener);
985
986        ctx.set_camera(Self::camera(player));
987        ctx.set_skybox(Sky::Room);
988        ctx.set_bloom(0.2);
989        ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991        self.draw_room(ctx);
992        self.draw_sources(ctx);
993        self.draw_listener(ctx, listener);
994        self.draw_merge_markers(ctx);
995        self.draw_ring(ctx);
996
997        self.sustain_cues(ctx);
998        for (index, source) in self.sources.iter().enumerate() {
999            if source.enabled {
1000                ctx.sustain(source.cue().instance(index as u32));
1001            }
1002        }
1003        if self.merge_demo {
1004            ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005            ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006        }
1007        self.sustain_ring(ctx);
1008
1009        self.side_panel(ctx);
1010        let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012        if play_once {
1013            ctx.play(self.one_shot_cue());
1014        }
1015        if play_many {
1016            for _ in 0..32 {
1017                ctx.play(self.one_shot_cue());
1018            }
1019        }
1020    }
More examples
Hide additional examples
examples/breakout-game.rs (line 1000)
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
Source

pub fn sound_unlocked(&self) -> bool

Whether the platform allows sound to start right now.

True from the first frame on the desktop, with or without an audio device: what holds it false is the browser, which plays nothing until the player has done something. A one-shot played while it is false is dropped; a sustain declared then starts when it turns true.

Source

pub fn saved<K: SaveKey>(&self, key: K) -> K::Value

The value the last run to save key kept, or its fallback where none did, or where what was kept no longer reads as the key’s own value, with a debug log.

Source

pub fn save<K: SaveKey>(&mut self, key: K, value: K::Value)

Keeps value under key, for the rest of this run and the runs after it.

The store is written once the frame is drawn, and only where a value changed, so saving every frame costs nothing.

Source

pub fn ui(&mut self, build: impl FnOnce(&mut Ui))

Builds this frame’s UI, drawn over the scene.

Calls append to one layer, which is placed 8 points clear of the window’s edges; floating windows go through ui.ctx(), and egui::Panel::left and the three beside it hold a panel against one side of that layer, shown in the ui this call takes.

Examples found in repository?
examples/isometric-board.rs (lines 644-654)
643    fn overlay(&self, ctx: &mut FrameContext<'_, Board>) {
644        ctx.ui(|ui| {
645            ui.label(match self.turn {
646                Turn::Sprite => "the sprite unit's turn",
647                Turn::Block => "the block unit's turn",
648            });
649            ui.label(if self.selected {
650                "click a marked tile to order the move"
651            } else {
652                "click the glowing unit to select it"
653            });
654        });
655    }
656
657    /// What a click at `hover` does, named for the player; `None` where a
658    /// click has no effect.
659    fn click_effect(&self, hover: Hover) -> Option<&'static str> {
660        match hover {
661            Hover::CurrentUnit if self.selected => Some("deselect"),
662            Hover::CurrentUnit => Some("select"),
663            Hover::Tile(tile) if self.selected && self.reachable(tile) => Some("move here"),
664            Hover::Tile(_) if self.selected => Some("occupied"),
665            _ => None,
666        }
667    }
668
669    /// A prompt beside the pointer's target, naming what its click does;
670    /// absent where [`Self::click_effect`] reads no effect.
671    fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672        let Some(text) = self.click_effect(hover) else {
673            return;
674        };
675        let point = match hover {
676            Hover::CurrentUnit => {
677                let (lift, _) = unit_geometry(self.turn);
678                self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679            }
680            Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681            Hover::None => return,
682        };
683        let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684        let window_size = ctx.window_size();
685        let pixels_per_point = ctx.pixels_per_point();
686        let Some(pixel) = camera.pixel_of(point, window_size) else {
687            return;
688        };
689        let at = logical(pixel, pixels_per_point);
690        ctx.ui(|ui| {
691            let painter = ui.painter();
692            let ink = galley.mesh_bounds;
693            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694            let backdrop = egui::Rect::from_center_size(
695                at,
696                ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697            );
698            painter.rect_filled(
699                backdrop,
700                PROMPT_PADDING,
701                egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702            );
703            painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704        });
705    }
More examples
Hide additional examples
examples/post-effects.rs (lines 125-129)
124    fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
125        ctx.ui(|ui| {
126            ui.add(egui::Slider::new(&mut self.vignette, 0.0..=1.0).text("vignette"));
127            ui.add(egui::Slider::new(&mut self.grain, 0.0..=1.0).text("grain"));
128            ui.add(egui::Slider::new(&mut self.scanlines, 0.0..=1.0).text("scanlines"));
129        });
130
131        ctx.set_post_effect(Vignette {
132            strength: self.vignette,
133        });
134        ctx.set_post_effect(Grain {
135            strength: self.grain,
136            seed: self.ticks,
137        });
138        ctx.set_post_effect(Scanlines {
139            strength: self.scanlines,
140        });
141    }
examples/ui-fonts.rs (line 698)
682    fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683        let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684        let window_size = ctx.window_size();
685        let Some(pixel) = camera.pixel_of(top, window_size) else {
686            return;
687        };
688        let at = logical(pixel, ctx.pixels_per_point());
689
690        let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691        let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692        let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693        let number = ctx.text_layout(
694            &number_text,
695            egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696        );
697
698        ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699    }
700
701    /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702    /// `hovered`: what a player presses to reach one, apart from a hover.
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
733
734    /// The title, a line and the reading, each in a font this game loaded
735    /// rather than egui's own.
736    fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737        ctx.ui(|ui| {
738            ui.label(styled(
739                "a game's own fonts",
740                egui::FontId::proportional(HEADING_SIZE),
741            ));
742            ui.label(styled(
743                "drawn in Pixel Operator, the game's proportional font",
744                egui::FontId::proportional(BODY_SIZE),
745            ));
746            ui.label(styled(
747                "the readings above each station in Pixel Operator Mono",
748                egui::FontId::monospace(BODY_SIZE),
749            ));
750        });
751    }
752
753    fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754        let Some(dialogue) = &self.dialogue else {
755            return;
756        };
757        let whole = ctx.text_layout(
758            dialogue.current_line(),
759            egui::FontId::proportional(BODY_SIZE),
760        );
761        let size = whole.size();
762        ctx.ui(|ui| dialogue.draw(ui, size));
763    }
764
765    /// The `StationKind` under the pointer, `None` while the UI holds it.
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
775
776    /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777    /// motion; the wheel zooms it.
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
787}
788
789impl Game for WatchRoom {
790    type Meshes = Shape;
791    type Sounds = NoSounds;
792    type InputActions = Controls;
793    type Skyboxes = Sky;
794    type SurfaceStyles = NoSurfaceStyles;
795    type PostEffects = NoPostEffects;
796
797    fn tick(&mut self, ctx: &mut TickContext<'_, Self>) {
798        self.elapsed += ctx.dt();
799        self.orbit.yaw += AUTO_TURN_RATE * ctx.dt().as_secs_f32();
800
801        if let Some(dialogue) = &mut self.dialogue {
802            dialogue.tick();
803        }
804        if ctx.pressed(Trigger::Close) {
805            self.dialogue = None;
806            self.hailed = None;
807        }
808        if ctx.pressed(Trigger::Sheet) {
809            self.sheet_open = !self.sheet_open;
810        }
811        if ctx.pressed(Trigger::Hail) && !ctx.ui_wants_pointer() {
812            self.handle_hail(ctx);
813        }
814    }
815
816    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817        self.steer(ctx);
818
819        let camera = self.orbit.camera();
820        ctx.set_camera(camera);
821        ctx.set_skybox(Sky::Dusk);
822        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824        ctx.draw(
825            Plane
826                .at(Transform::from_scale(Vec3::new(
827                    PLATFORM_SIZE,
828                    1.0,
829                    PLATFORM_SIZE,
830                )))
831                .material(Material::lit(PLATFORM_COLOR)),
832        );
833        for station in StationKind::ALL {
834            self.draw_station(ctx, station);
835        }
836
837        let hovered = Self::hovered(ctx);
838        if !self.sheet_open {
839            if let Some(station) = hovered {
840                ctx.set_cursor(Cursor::Pointer);
841                self.draw_bracket(ctx, camera, station);
842            }
843            self.draw_prompts(ctx, camera, hovered);
844        }
845        if self.dialogue.is_some() {
846            self.draw_dialogue(ctx);
847        }
848        if self.sheet_open {
849            ctx.ui(sheet);
850        }
851        self.panel(ctx);
852    }
examples/flock-parallelism.rs (lines 696-716)
692    fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
693        let workers = rayon::current_num_threads();
694        let tick_ms = self.last_tick_ms;
695
696        ctx.ui(|ui| {
697            egui::Frame::new()
698                .fill(egui::Color32::from_gray(24))
699                .inner_margin(PANEL_PADDING)
700                .corner_radius(f32::from(PANEL_PADDING))
701                .show(ui, |ui| {
702                    ui.label(format!("workers {workers}"));
703                    ui.horizontal(|ui| {
704                        for size in FLOCK_SIZES {
705                            ui.radio_value(
706                                &mut self.settings.flock_size,
707                                size,
708                                format!("{size} butterflies"),
709                            );
710                        }
711                    });
712                    ui.checkbox(&mut self.settings.sequential, "sequential update");
713                    ui.separator();
714                    ui.label(format!("tick time {tick_ms:.2}ms"));
715                });
716        });
717    }
examples/breakout-game.rs (lines 746-755)
735    fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736        let bricks_left = self
737            .bricks
738            .iter()
739            .filter(|brick| brick.hits_remaining > 0)
740            .count();
741        // Read before `ctx.ui` so a rebind changes what the hint reads this
742        // frame too.
743        let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744        let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745        let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746        ctx.ui(|ui| {
747            ui.horizontal(|ui| {
748                ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749                ui.label(format!("{bricks_left} bricks left"));
750            });
751            ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752            if self.phase == Phase::Serving {
753                ui.label(format!("{serve_hint} to serve"));
754            }
755        });
756
757        match self.phase {
758            Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759            Phase::Won => self.menu(ctx, "you win", true),
760            Phase::Lost => self.menu(ctx, "game over", true),
761            _ => {}
762        }
763    }
764
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
examples/sprite-adventure.rs (lines 1657-1672)
1638    fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639        let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640        let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641        let text = if swinging {
1642            "opening"
1643        } else if near && !self.door_opening {
1644            "e opens the door"
1645        } else {
1646            return;
1647        };
1648
1649        let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650        let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651        let window_size = ctx.window_size();
1652        let pixels_per_point = ctx.pixels_per_point();
1653        let Some(pixel) = camera.pixel_of(point, window_size) else {
1654            return;
1655        };
1656
1657        ctx.ui(|ui| {
1658            let painter = ui.painter();
1659            let at = logical(pixel, pixels_per_point);
1660            let ink = galley.mesh_bounds;
1661            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662            let backdrop = egui::Rect::from_center_size(
1663                at,
1664                ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665            );
1666            painter.rect_filled(
1667                backdrop,
1668                DOOR_PROMPT_PADDING,
1669                egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670            );
1671            painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672        });
1673    }
1674
1675    /// The gem, spinning and bobbing over the chamber's floor, and the light
1676    /// it casts over it.
1677    fn draw_gem(&self, ctx: &mut FrameContext<'_, Keep>) {
1678        let t = self.simulated.as_secs_f32();
1679        let bob = (t * 2.0).sin() * GEM_BOB_HEIGHT;
1680        ctx.light(
1681            Light::point(
1682                GEM_POSITION + Vec3::Y * (bob + GEM_LIGHT_LIFT),
1683                GEM_LIGHT_COLOR,
1684                GEM_LIGHT_RANGE,
1685            )
1686            .shadow(),
1687        );
1688        ctx.draw(
1689            Gem.at(Transform::from_scale_rotation_translation(
1690                Vec3::ONE,
1691                Quat::from_rotation_y(t * GEM_SPIN_SPEED),
1692                GEM_POSITION + Vec3::Y * bob,
1693            ))
1694            .material(Material::shaded(GEM_COLOR, 0.7).emissive(GEM_COLOR.dimmed(1.6))),
1695        );
1696    }
1697
1698    /// The player: upright so it always faces the camera about `+Y`,
1699    /// windowed to its facing's row and the walk cycle's current frame.
1700    fn draw_walker(&self, ctx: &mut FrameContext<'_, Keep>, ground: Vec3) {
1701        let step = if self.walk_ticks > 0 {
1702            (self.walk_ticks / TICKS_PER_WALK_FRAME) % WALKER_COLUMNS
1703        } else {
1704            0
1705        };
1706        let cell = Sheet::new(UVec2::new(WALKER_COLUMNS, WALKER_ROWS))
1707            .cell_at(UVec2::new(step, self.facing as u32));
1708        let size = Vec2::new(WALKER_WIDTH, WALKER_HEIGHT);
1709
1710        ctx.draw(
1711            Walker
1712                .at(Transform::from_scale_rotation_translation(
1713                    size.extend(1.0),
1714                    Quat::IDENTITY,
1715                    ground + Vec3::Y * (WALKER_HEIGHT * 0.5),
1716                ))
1717                .upright()
1718                .frame(cell),
1719        );
1720    }
1721
1722    fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724        ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727        self.draw_ground(ctx);
1728        self.draw_hedgerow(ctx);
1729        self.draw_pond(ctx);
1730        self.draw_crates(ctx);
1731        self.draw_well(ctx);
1732        self.draw_flora(ctx);
1733        Self::draw_mouth(ctx, ENTRANCE);
1734        self.draw_walker(ctx, drawn_at);
1735    }
1736
1737    fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739        let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740        ctx.set_camera(camera);
1741
1742        self.draw_cave_floor(ctx);
1743        self.draw_cave_walls(ctx);
1744        Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745        Self::draw_mouth(ctx, EXIT);
1746        self.draw_torches(ctx);
1747        self.draw_door(ctx, self.ghost);
1748        self.draw_door_frame(ctx, self.ghost);
1749        if !self.gem_taken {
1750            self.draw_gem(ctx);
1751        }
1752        self.draw_walker(ctx, drawn_at);
1753        self.draw_door_prompt(ctx, camera);
1754    }
1755
1756    /// Instructions and the door's interact hint — gathered before `ctx.ui`,
1757    /// which cannot read `ctx`.
1758    fn overlay(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1759        let near_door = self.area == Area::Cave
1760            && !self.door_opening
1761            && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1762        let gem_taken = self.area == Area::Cave && self.gem_taken;
1763        let mut reset_clicked = false;
1764
1765        ctx.ui(|ui| {
1766            egui::Frame::new()
1767                .fill(egui::Color32::from_black_alpha(HUD_BACKDROP))
1768                .inner_margin(HUD_PADDING)
1769                .corner_radius(f32::from(HUD_PADDING))
1770                .show(ui, |ui| {
1771                    ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
1772                    ui.label("wasd / arrows / stick to walk");
1773                    if near_door {
1774                        ui.label("e / west button to open the door");
1775                    }
1776                    if gem_taken {
1777                        ui.label("gem recovered");
1778                    }
1779                    ui.label("kept between runs: position, gem, cave");
1780                    ui.label("r to reset world");
1781                    if ui.button("reset world").clicked() {
1782                        reset_clicked = true;
1783                    }
1784                });
1785        });
1786
1787        if reset_clicked {
1788            self.reset_requested = true;
1789        }
1790    }
Source

pub fn pixels_per_point(&self) -> f32

Physical pixels per logical point of the UI this frame, a fraction over 1.0 on a dense screen: what a pixel Camera::pixel_of returns is divided by before the UI draws at it.

Examples found in repository?
examples/ui-fonts.rs (line 688)
682    fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683        let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684        let window_size = ctx.window_size();
685        let Some(pixel) = camera.pixel_of(top, window_size) else {
686            return;
687        };
688        let at = logical(pixel, ctx.pixels_per_point());
689
690        let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691        let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692        let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693        let number = ctx.text_layout(
694            &number_text,
695            egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696        );
697
698        ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699    }
700
701    /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702    /// `hovered`: what a player presses to reach one, apart from a hover.
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
More examples
Hide additional examples
examples/sprite-adventure.rs (line 1652)
1638    fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639        let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640        let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641        let text = if swinging {
1642            "opening"
1643        } else if near && !self.door_opening {
1644            "e opens the door"
1645        } else {
1646            return;
1647        };
1648
1649        let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650        let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651        let window_size = ctx.window_size();
1652        let pixels_per_point = ctx.pixels_per_point();
1653        let Some(pixel) = camera.pixel_of(point, window_size) else {
1654            return;
1655        };
1656
1657        ctx.ui(|ui| {
1658            let painter = ui.painter();
1659            let at = logical(pixel, pixels_per_point);
1660            let ink = galley.mesh_bounds;
1661            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662            let backdrop = egui::Rect::from_center_size(
1663                at,
1664                ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665            );
1666            painter.rect_filled(
1667                backdrop,
1668                DOOR_PROMPT_PADDING,
1669                egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670            );
1671            painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672        });
1673    }
examples/isometric-board.rs (line 685)
671    fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672        let Some(text) = self.click_effect(hover) else {
673            return;
674        };
675        let point = match hover {
676            Hover::CurrentUnit => {
677                let (lift, _) = unit_geometry(self.turn);
678                self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679            }
680            Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681            Hover::None => return,
682        };
683        let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684        let window_size = ctx.window_size();
685        let pixels_per_point = ctx.pixels_per_point();
686        let Some(pixel) = camera.pixel_of(point, window_size) else {
687            return;
688        };
689        let at = logical(pixel, pixels_per_point);
690        ctx.ui(|ui| {
691            let painter = ui.painter();
692            let ink = galley.mesh_bounds;
693            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694            let backdrop = egui::Rect::from_center_size(
695                at,
696                ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697            );
698            painter.rect_filled(
699                backdrop,
700                PROMPT_PADDING,
701                egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702            );
703            painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704        });
705    }
examples/animation.rs (line 849)
823    fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824        let sit_key = ctx
825            .bindings(Button::Interact)
826            .into_iter()
827            .next()
828            .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829        let sit = ctx.text_layout(
830            &format!("{sit_key} sits"),
831            egui::FontId::proportional(PROMPT_SIZE),
832        );
833        let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834        let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836        let mut prompts = vec![(
837            SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838            walk_closer,
839        )];
840        if !self.elf_animator.state().seated() {
841            prompts.push((
842                SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843                sit,
844            ));
845        }
846        prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848        let window_size = ctx.window_size();
849        let pixels_per_point = ctx.pixels_per_point();
850        ctx.ui(|ui| {
851            let painter = ui.painter();
852            for (point, galley) in prompts {
853                let Some(pixel) = camera.pixel_of(point, window_size) else {
854                    continue;
855                };
856                let at = logical(pixel, pixels_per_point);
857                let ink = galley.mesh_bounds;
858                let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859                let backdrop = egui::Rect::from_center_size(
860                    at,
861                    ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862                );
863                painter.rect_filled(
864                    backdrop,
865                    PROMPT_PADDING,
866                    egui::Color32::from_black_alpha(PANEL_BACKDROP),
867                );
868                painter.galley(pos, galley, PANEL_TEXT_COLOR);
869            }
870        });
871    }
Source

pub fn text_layout(&self, text: &str, font: FontId) -> Arc<Galley>

text laid out in font at no width, so a row ends only where a \n starts the next one.

Required if you want to size or place what you draw against text: a box a name has to fit in, a line as wide as the word over it. Its size() is the width and the height the text takes, in logical points. One kept past a change of pixels_per_point still reports the size the frame that laid it out measured. The ui feature’s own.

Examples found in repository?
examples/ui-fonts.rs (line 690)
682    fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683        let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684        let window_size = ctx.window_size();
685        let Some(pixel) = camera.pixel_of(top, window_size) else {
686            return;
687        };
688        let at = logical(pixel, ctx.pixels_per_point());
689
690        let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691        let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692        let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693        let number = ctx.text_layout(
694            &number_text,
695            egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696        );
697
698        ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699    }
700
701    /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702    /// `hovered`: what a player presses to reach one, apart from a hover.
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
733
734    /// The title, a line and the reading, each in a font this game loaded
735    /// rather than egui's own.
736    fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737        ctx.ui(|ui| {
738            ui.label(styled(
739                "a game's own fonts",
740                egui::FontId::proportional(HEADING_SIZE),
741            ));
742            ui.label(styled(
743                "drawn in Pixel Operator, the game's proportional font",
744                egui::FontId::proportional(BODY_SIZE),
745            ));
746            ui.label(styled(
747                "the readings above each station in Pixel Operator Mono",
748                egui::FontId::monospace(BODY_SIZE),
749            ));
750        });
751    }
752
753    fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754        let Some(dialogue) = &self.dialogue else {
755            return;
756        };
757        let whole = ctx.text_layout(
758            dialogue.current_line(),
759            egui::FontId::proportional(BODY_SIZE),
760        );
761        let size = whole.size();
762        ctx.ui(|ui| dialogue.draw(ui, size));
763    }
More examples
Hide additional examples
examples/sprite-adventure.rs (line 1649)
1638    fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639        let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640        let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641        let text = if swinging {
1642            "opening"
1643        } else if near && !self.door_opening {
1644            "e opens the door"
1645        } else {
1646            return;
1647        };
1648
1649        let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650        let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651        let window_size = ctx.window_size();
1652        let pixels_per_point = ctx.pixels_per_point();
1653        let Some(pixel) = camera.pixel_of(point, window_size) else {
1654            return;
1655        };
1656
1657        ctx.ui(|ui| {
1658            let painter = ui.painter();
1659            let at = logical(pixel, pixels_per_point);
1660            let ink = galley.mesh_bounds;
1661            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662            let backdrop = egui::Rect::from_center_size(
1663                at,
1664                ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665            );
1666            painter.rect_filled(
1667                backdrop,
1668                DOOR_PROMPT_PADDING,
1669                egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670            );
1671            painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672        });
1673    }
examples/isometric-board.rs (line 683)
671    fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672        let Some(text) = self.click_effect(hover) else {
673            return;
674        };
675        let point = match hover {
676            Hover::CurrentUnit => {
677                let (lift, _) = unit_geometry(self.turn);
678                self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679            }
680            Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681            Hover::None => return,
682        };
683        let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684        let window_size = ctx.window_size();
685        let pixels_per_point = ctx.pixels_per_point();
686        let Some(pixel) = camera.pixel_of(point, window_size) else {
687            return;
688        };
689        let at = logical(pixel, pixels_per_point);
690        ctx.ui(|ui| {
691            let painter = ui.painter();
692            let ink = galley.mesh_bounds;
693            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694            let backdrop = egui::Rect::from_center_size(
695                at,
696                ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697            );
698            painter.rect_filled(
699                backdrop,
700                PROMPT_PADDING,
701                egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702            );
703            painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704        });
705    }
examples/animation.rs (lines 829-832)
823    fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824        let sit_key = ctx
825            .bindings(Button::Interact)
826            .into_iter()
827            .next()
828            .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829        let sit = ctx.text_layout(
830            &format!("{sit_key} sits"),
831            egui::FontId::proportional(PROMPT_SIZE),
832        );
833        let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834        let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836        let mut prompts = vec![(
837            SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838            walk_closer,
839        )];
840        if !self.elf_animator.state().seated() {
841            prompts.push((
842                SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843                sit,
844            ));
845        }
846        prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848        let window_size = ctx.window_size();
849        let pixels_per_point = ctx.pixels_per_point();
850        ctx.ui(|ui| {
851            let painter = ui.painter();
852            for (point, galley) in prompts {
853                let Some(pixel) = camera.pixel_of(point, window_size) else {
854                    continue;
855                };
856                let at = logical(pixel, pixels_per_point);
857                let ink = galley.mesh_bounds;
858                let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859                let backdrop = egui::Rect::from_center_size(
860                    at,
861                    ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862                );
863                painter.rect_filled(
864                    backdrop,
865                    PROMPT_PADDING,
866                    egui::Color32::from_black_alpha(PANEL_BACKDROP),
867                );
868                painter.galley(pos, galley, PANEL_TEXT_COLOR);
869            }
870        });
871    }
Source

pub fn ui_wants_pointer(&self) -> bool

Whether the UI took the pointer last frame. Always false without the ui feature.

Examples found in repository?
examples/ui-fonts.rs (line 767)
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
775
776    /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777    /// motion; the wheel zooms it.
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
More examples
Hide additional examples
examples/isometric-board.rs (line 460)
459    fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460        if ctx.ui_wants_pointer() {
461            return Hover::None;
462        }
463        let ray = ctx
464            .last_camera()
465            .ray_through(ctx.pointer(), ctx.window_size());
466
467        if self.current().target.is_none() {
468            let (_, half) = unit_geometry(self.turn);
469            let center = self.current().position;
470            if ray.hit_aabb(center - half, center + half).is_some() {
471                return Hover::CurrentUnit;
472            }
473        }
474        let Some(distance) = ray.hit_plane(ray::Plane {
475            point: Vec3::ZERO,
476            normal: Vec3::Y,
477        }) else {
478            return Hover::None;
479        };
480        match tile_at(ray.at(distance)) {
481            Some(tile) => Hover::Tile(tile),
482            None => Hover::None,
483        }
484    }
examples/stress-preview.rs (line 360)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
examples/material-playground.rs (line 803)
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
Source

pub fn ui_wants_keyboard(&self) -> bool

Whether the UI took the keyboard last frame. Always false without the ui feature.

Examples found in repository?
examples/stress-preview.rs (line 360)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
More examples
Hide additional examples
examples/breakout-game.rs (line 780)
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
examples/input-lab.rs (line 275)
238    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239        // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240        // is taken first, and `rebind` is applied once the closure returns.
241        let buttons: Vec<_> = ButtonAction::all()
242            .into_iter()
243            .map(|action| {
244                (
245                    action,
246                    bindings_text(ctx.bindings(action)),
247                    ctx.down(action),
248                    ctx.pressed(action),
249                    ctx.released(action),
250                    ctx.clicks(action),
251                )
252            })
253            .collect();
254        let axes: Vec<_> = AxisAction::all()
255            .into_iter()
256            .map(|action| {
257                (
258                    action,
259                    bindings_text(ctx.bindings(action)),
260                    ctx.axis(action),
261                )
262            })
263            .collect();
264        let axes2: Vec<_> = Axis2Action::all()
265            .into_iter()
266            .map(|action| {
267                (
268                    action,
269                    bindings_text(ctx.bindings(action)),
270                    ctx.axis2(action),
271                )
272            })
273            .collect();
274
275        let capturing = !ctx.ui_wants_keyboard();
276        let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277        let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278        let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279        if actuated_button.is_some() {
280            self.last_button = actuated_button;
281        }
282        if actuated_axis.is_some() {
283            self.last_axis = actuated_axis;
284        }
285        if actuated_axis2.is_some() {
286            self.last_axis2 = actuated_axis2;
287        }
288        let pointer = ctx.pointer();
289        let mut edits = RowEdits {
290            listening: self.listening,
291            start_listening: None,
292            cancel: false,
293            reset: None,
294        };
295
296        ctx.ui(|ui| {
297            egui::CentralPanel::default().show(ui, |ui| {
298                ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299                ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300                ui.label("rebinds persist across runs");
301                ui.label(format!(
302                    "last captured: button {}, pad axis {}, pad stick {}",
303                    text_of(self.last_button),
304                    text_of(self.last_axis),
305                    text_of(self.last_axis2),
306                ));
307                ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308                ui.separator();
309
310                ui.horizontal(|ui| {
311                    ui.vertical(|ui| {
312                        ui.heading("buttons");
313                        egui::Grid::new("buttons-grid")
314                            .num_columns(5)
315                            .spacing([6.0, 2.0])
316                            .show(ui, |ui| {
317                                for (action, bindings, down, pressed, released, clicks) in &buttons
318                                {
319                                    let control = Control::Button(*action);
320                                    ui.label(action.name());
321                                    ui.label(bindings);
322                                    ui.horizontal(|ui| {
323                                        mark(ui, "down", *down);
324                                        mark(ui, "pressed", *pressed);
325                                        mark(ui, "released", *released);
326                                        ui.label(format!("clicks {clicks}"));
327                                    });
328                                    rebind_cell(ui, control, &mut edits);
329                                    reset_cell(ui, control, &mut edits);
330                                    ui.end_row();
331                                }
332                            });
333                    });
334
335                    ui.separator();
336
337                    ui.vertical(|ui| {
338                        egui::Grid::new("axes-grid")
339                            .num_columns(5)
340                            .spacing([6.0, 2.0])
341                            .show(ui, |ui| {
342                                ui.heading("axes");
343                                ui.end_row();
344                                for (action, bindings, value) in &axes {
345                                    let control = Control::Axis(*action);
346                                    ui.label(action.name());
347                                    ui.label(bindings);
348                                    axis_bar(ui, *value);
349                                    rebind_cell(ui, control, &mut edits);
350                                    reset_cell(ui, control, &mut edits);
351                                    ui.end_row();
352                                }
353
354                                ui.heading("vectors");
355                                ui.end_row();
356                                for (action, bindings, value) in &axes2 {
357                                    let control = Control::Axis2(*action);
358                                    ui.label(action.name());
359                                    ui.label(bindings);
360                                    axis2_dot(ui, *value);
361                                    rebind_cell(ui, control, &mut edits);
362                                    reset_cell(ui, control, &mut edits);
363                                    ui.end_row();
364                                }
365                            });
366                    });
367                });
368            });
369        });
370
371        if edits.cancel {
372            self.listening = None;
373        }
374        if let Some(control) = edits.start_listening {
375            self.listening = Some(control);
376        }
377        if let Some(control) = edits.reset {
378            match control {
379                Control::Button(action) => ctx.rebind(action, action.bindings()),
380                Control::Axis(action) => ctx.rebind(action, action.bindings()),
381                Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382            }
383        }
384        match (
385            self.listening,
386            actuated_button,
387            actuated_axis,
388            actuated_axis2,
389        ) {
390            (Some(Control::Button(action)), Some(binding), _, _) => {
391                ctx.rebind(action, vec![binding]);
392                self.listening = None;
393            }
394            (Some(Control::Axis(action)), _, Some(binding), _) => {
395                ctx.rebind(action, vec![binding]);
396                self.listening = None;
397            }
398            (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399                ctx.rebind(action, vec![binding]);
400                self.listening = None;
401            }
402            _ => {}
403        }
404    }
Source

pub fn window_size(&self) -> UVec2

The window’s drawing area, in physical pixels; zero while minimized.

Examples found in repository?
examples/isometric-board.rs (line 465)
459    fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460        if ctx.ui_wants_pointer() {
461            return Hover::None;
462        }
463        let ray = ctx
464            .last_camera()
465            .ray_through(ctx.pointer(), ctx.window_size());
466
467        if self.current().target.is_none() {
468            let (_, half) = unit_geometry(self.turn);
469            let center = self.current().position;
470            if ray.hit_aabb(center - half, center + half).is_some() {
471                return Hover::CurrentUnit;
472            }
473        }
474        let Some(distance) = ray.hit_plane(ray::Plane {
475            point: Vec3::ZERO,
476            normal: Vec3::Y,
477        }) else {
478            return Hover::None;
479        };
480        match tile_at(ray.at(distance)) {
481            Some(tile) => Hover::Tile(tile),
482            None => Hover::None,
483        }
484    }
485
486    /// Whether a selected unit could move to `tile`: on the board, and
487    /// standing under neither unit.
488    fn reachable(&self, tile: (i32, i32)) -> bool {
489        tile != self.current().tile && tile != self.other().tile
490    }
491
492    fn draw_board(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
493        let scale = Vec3::new(TILE_SIZE - TILE_GAP, TILE_THICKNESS, TILE_SIZE - TILE_GAP);
494        for col in 0..BOARD_TILES {
495            for row in 0..BOARD_TILES {
496                let tile = (col, row);
497                let center = tile_center(tile) - Vec3::Y * (TILE_THICKNESS * 0.5);
498                let reachable = self.selected && self.reachable(tile);
499                let hovered = self.selected && hover == Hover::Tile(tile);
500                let color = if hovered {
501                    if reachable {
502                        HOVER_REACHABLE_TILE
503                    } else {
504                        HOVER_BLOCKED_TILE
505                    }
506                } else if (col + row) % 2 == 0 {
507                    LIGHT_TILE
508                } else {
509                    DARK_TILE
510                };
511                ctx.draw(
512                    Cube.at(Transform::from_scale_rotation_translation(
513                        scale,
514                        Quat::IDENTITY,
515                        center,
516                    ))
517                    .material(Material::lit(color)),
518                );
519                if reachable && !hovered {
520                    self.draw_reachable_mark(ctx, tile);
521                }
522            }
523        }
524    }
525
526    /// A mark over a reachable tile, its own tone apart from the
527    /// checker's tone and the hover tone, so the checker still reads
528    /// under it.
529    fn draw_reachable_mark(&self, ctx: &mut FrameContext<'_, Board>, tile: (i32, i32)) {
530        let center = tile_center(tile) + Vec3::Y * REACHABLE_MARK_LIFT;
531        ctx.draw(
532            Plane
533                .at(Transform::from_scale_rotation_translation(
534                    Vec3::new(
535                        TILE_SIZE * REACHABLE_MARK_SCALE,
536                        1.0,
537                        TILE_SIZE * REACHABLE_MARK_SCALE,
538                    ),
539                    Quat::IDENTITY,
540                    center,
541                ))
542                .material(Material::color(REACHABLE_MARK)),
543        );
544    }
545
546    /// A mark bright enough to read past the sprite's own tint under the
547    /// selected unit, or a smaller, dim one under the unit whose turn it
548    /// is while nothing is selected — so the current unit reads from the
549    /// ground alone.
550    fn draw_current_mark(&self, ctx: &mut FrameContext<'_, Board>) {
551        let (color, scale) = if self.selected {
552            (CURRENT_MARK, CURRENT_MARK_SCALE)
553        } else {
554            (TURN_MARK, TURN_MARK_SCALE)
555        };
556        let center = tile_center(self.current().tile) + Vec3::Y * REACHABLE_MARK_LIFT;
557        ctx.draw(
558            Plane
559                .at(Transform::from_scale_rotation_translation(
560                    Vec3::new(TILE_SIZE * scale, 1.0, TILE_SIZE * scale),
561                    Quat::IDENTITY,
562                    center,
563                ))
564                .material(Material::color(color)),
565        );
566    }
567
568    fn draw_rocks(&self, ctx: &mut FrameContext<'_, Board>) {
569        for &(x, z, seed, scale) in &ROCKS {
570            let angle = hash_signed(seed, 99) * core::f32::consts::PI;
571            ctx.draw(
572                Rock { seed }
573                    .at(Transform::from_scale_rotation_translation(
574                        Vec3::splat(scale),
575                        Quat::from_rotation_y(angle),
576                        Vec3::new(x, 0.5 * scale, z),
577                    ))
578                    .material(Material::lit(ROCK_COLOR)),
579            );
580        }
581    }
582
583    fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584        let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585        let current = self.turn == Turn::Sprite;
586        let (tint, glow) = if current && self.selected {
587            (SELECTED_TINT, SELECTED_GLOW)
588        } else if current && hover == Hover::CurrentUnit {
589            (HOVER_TINT, HOVER_GLOW)
590        } else if current {
591            (TURN_TINT, TURN_GLOW)
592        } else {
593            (Color::WHITE, Color::BLACK)
594        };
595        ctx.draw(
596            Sprite
597                .at(Transform::from_scale_rotation_translation(
598                    Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599                    Quat::IDENTITY,
600                    position,
601                ))
602                .upright()
603                .frame(sprite_frame(self.sprite.facing_right))
604                .material(Material::lit(tint).cutout().emissive(glow)),
605        );
606    }
607
608    fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609        let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610        let current = self.turn == Turn::Block;
611        let (color, glow) = if current && self.selected {
612            (SELECTED_TINT, SELECTED_GLOW)
613        } else if current && hover == Hover::CurrentUnit {
614            (HOVER_TINT, HOVER_GLOW)
615        } else if current {
616            (BLOCK_TURN, TURN_GLOW)
617        } else {
618            (BLOCK_IDLE, Color::BLACK)
619        };
620        ctx.draw(
621            Cube.at(Transform::from_scale_rotation_translation(
622                Vec3::splat(BLOCK_SIZE),
623                Quat::IDENTITY,
624                position,
625            ))
626            .material(Material::lit(color).emissive(glow)),
627        );
628    }
629
630    fn draw_ground(&self, ctx: &mut FrameContext<'_, Board>) {
631        ctx.draw(
632            Plane
633                .at(Transform::from_scale_rotation_translation(
634                    Vec3::new(GROUND_HALF * 2.0, 1.0, GROUND_HALF * 2.0),
635                    Quat::IDENTITY,
636                    Vec3::new(0.0, GROUND_Y, 0.0),
637                ))
638                .material(Material::lit(GROUND_COLOR)),
639        );
640    }
641
642    /// Whose turn it is, and what a click does next.
643    fn overlay(&self, ctx: &mut FrameContext<'_, Board>) {
644        ctx.ui(|ui| {
645            ui.label(match self.turn {
646                Turn::Sprite => "the sprite unit's turn",
647                Turn::Block => "the block unit's turn",
648            });
649            ui.label(if self.selected {
650                "click a marked tile to order the move"
651            } else {
652                "click the glowing unit to select it"
653            });
654        });
655    }
656
657    /// What a click at `hover` does, named for the player; `None` where a
658    /// click has no effect.
659    fn click_effect(&self, hover: Hover) -> Option<&'static str> {
660        match hover {
661            Hover::CurrentUnit if self.selected => Some("deselect"),
662            Hover::CurrentUnit => Some("select"),
663            Hover::Tile(tile) if self.selected && self.reachable(tile) => Some("move here"),
664            Hover::Tile(_) if self.selected => Some("occupied"),
665            _ => None,
666        }
667    }
668
669    /// A prompt beside the pointer's target, naming what its click does;
670    /// absent where [`Self::click_effect`] reads no effect.
671    fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672        let Some(text) = self.click_effect(hover) else {
673            return;
674        };
675        let point = match hover {
676            Hover::CurrentUnit => {
677                let (lift, _) = unit_geometry(self.turn);
678                self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679            }
680            Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681            Hover::None => return,
682        };
683        let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684        let window_size = ctx.window_size();
685        let pixels_per_point = ctx.pixels_per_point();
686        let Some(pixel) = camera.pixel_of(point, window_size) else {
687            return;
688        };
689        let at = logical(pixel, pixels_per_point);
690        ctx.ui(|ui| {
691            let painter = ui.painter();
692            let ink = galley.mesh_bounds;
693            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694            let backdrop = egui::Rect::from_center_size(
695                at,
696                ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697            );
698            painter.rect_filled(
699                backdrop,
700                PROMPT_PADDING,
701                egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702            );
703            painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704        });
705    }
More examples
Hide additional examples
examples/ui-fonts.rs (line 684)
682    fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683        let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684        let window_size = ctx.window_size();
685        let Some(pixel) = camera.pixel_of(top, window_size) else {
686            return;
687        };
688        let at = logical(pixel, ctx.pixels_per_point());
689
690        let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691        let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692        let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693        let number = ctx.text_layout(
694            &number_text,
695            egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696        );
697
698        ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699    }
700
701    /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702    /// `hovered`: what a player presses to reach one, apart from a hover.
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
733
734    /// The title, a line and the reading, each in a font this game loaded
735    /// rather than egui's own.
736    fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737        ctx.ui(|ui| {
738            ui.label(styled(
739                "a game's own fonts",
740                egui::FontId::proportional(HEADING_SIZE),
741            ));
742            ui.label(styled(
743                "drawn in Pixel Operator, the game's proportional font",
744                egui::FontId::proportional(BODY_SIZE),
745            ));
746            ui.label(styled(
747                "the readings above each station in Pixel Operator Mono",
748                egui::FontId::monospace(BODY_SIZE),
749            ));
750        });
751    }
752
753    fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754        let Some(dialogue) = &self.dialogue else {
755            return;
756        };
757        let whole = ctx.text_layout(
758            dialogue.current_line(),
759            egui::FontId::proportional(BODY_SIZE),
760        );
761        let size = whole.size();
762        ctx.ui(|ui| dialogue.draw(ui, size));
763    }
764
765    /// The `StationKind` under the pointer, `None` while the UI holds it.
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
examples/sprite-adventure.rs (line 1651)
1638    fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639        let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640        let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641        let text = if swinging {
1642            "opening"
1643        } else if near && !self.door_opening {
1644            "e opens the door"
1645        } else {
1646            return;
1647        };
1648
1649        let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650        let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651        let window_size = ctx.window_size();
1652        let pixels_per_point = ctx.pixels_per_point();
1653        let Some(pixel) = camera.pixel_of(point, window_size) else {
1654            return;
1655        };
1656
1657        ctx.ui(|ui| {
1658            let painter = ui.painter();
1659            let at = logical(pixel, pixels_per_point);
1660            let ink = galley.mesh_bounds;
1661            let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662            let backdrop = egui::Rect::from_center_size(
1663                at,
1664                ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665            );
1666            painter.rect_filled(
1667                backdrop,
1668                DOOR_PROMPT_PADDING,
1669                egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670            );
1671            painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672        });
1673    }
examples/animation.rs (line 848)
823    fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824        let sit_key = ctx
825            .bindings(Button::Interact)
826            .into_iter()
827            .next()
828            .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829        let sit = ctx.text_layout(
830            &format!("{sit_key} sits"),
831            egui::FontId::proportional(PROMPT_SIZE),
832        );
833        let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834        let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836        let mut prompts = vec![(
837            SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838            walk_closer,
839        )];
840        if !self.elf_animator.state().seated() {
841            prompts.push((
842                SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843                sit,
844            ));
845        }
846        prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848        let window_size = ctx.window_size();
849        let pixels_per_point = ctx.pixels_per_point();
850        ctx.ui(|ui| {
851            let painter = ui.painter();
852            for (point, galley) in prompts {
853                let Some(pixel) = camera.pixel_of(point, window_size) else {
854                    continue;
855                };
856                let at = logical(pixel, pixels_per_point);
857                let ink = galley.mesh_bounds;
858                let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859                let backdrop = egui::Rect::from_center_size(
860                    at,
861                    ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862                );
863                painter.rect_filled(
864                    backdrop,
865                    PROMPT_PADDING,
866                    egui::Color32::from_black_alpha(PANEL_BACKDROP),
867                );
868                painter.galley(pos, galley, PANEL_TEXT_COLOR);
869            }
870        });
871    }
examples/stress-preview.rs (line 464)
454    fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455        let submitted = self.field.len();
456        let seeds = self.applied_seed_count;
457        let average_ms = self.frame_times.average_ms();
458        let fps = if average_ms > 0.0 {
459            1000.0 / average_ms
460        } else {
461            0.0
462        };
463        let elapsed = ctx.elapsed().as_secs_f32();
464        let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466        ctx.ui(|ui| {
467            egui::Frame::new()
468                .fill(egui::Color32::from_gray(24))
469                .inner_margin(PANEL_PADDING)
470                .corner_radius(f32::from(PANEL_PADDING))
471                .show(ui, |ui| {
472                    ui.add(
473                        egui::Slider::new(
474                            &mut self.settings.instance_count,
475                            MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476                        )
477                        .text("instance count"),
478                    );
479                    ui.add(
480                        egui::Slider::new(
481                            &mut self.settings.seed_count,
482                            MIN_SEED_COUNT..=MAX_SEED_COUNT,
483                        )
484                        .text("distinct seeds"),
485                    );
486                    ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487                    ui.checkbox(&mut self.settings.moving, "moving fraction");
488                    ui.separator();
489                    ui.label(format!("instances submitted {submitted}"));
490                    if sampled {
491                        ui.label(format!("in view, sampled {in_view}"));
492                    } else {
493                        ui.label(format!("instances in view {in_view}"));
494                    }
495                    ui.label(format!("distinct seeds {seeds}"));
496                    ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497                    ui.label(format!("elapsed {elapsed:.1}s"));
498                });
499        });
500    }
Source

pub fn last_camera(&self) -> Camera

The camera the last drawn frame was viewed from; Camera::default before the first frame.

Required if you want a ray through a pixel: the player points at what was last drawn, not at what this frame has set since.

Examples found in repository?
examples/ui-fonts.rs (line 771)
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
More examples
Hide additional examples
examples/isometric-board.rs (line 464)
459    fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460        if ctx.ui_wants_pointer() {
461            return Hover::None;
462        }
463        let ray = ctx
464            .last_camera()
465            .ray_through(ctx.pointer(), ctx.window_size());
466
467        if self.current().target.is_none() {
468            let (_, half) = unit_geometry(self.turn);
469            let center = self.current().position;
470            if ray.hit_aabb(center - half, center + half).is_some() {
471                return Hover::CurrentUnit;
472            }
473        }
474        let Some(distance) = ray.hit_plane(ray::Plane {
475            point: Vec3::ZERO,
476            normal: Vec3::Y,
477        }) else {
478            return Hover::None;
479        };
480        match tile_at(ray.at(distance)) {
481            Some(tile) => Hover::Tile(tile),
482            None => Hover::None,
483        }
484    }
Source

pub fn config(&self) -> &Config

The configuration the engine started with.

Examples found in repository?
examples/material-playground.rs (line 1038)
1037    fn controls(&mut self, ctx: &mut FrameContext<'_, Self>) {
1038        let tonemap = ctx.config().tonemap();
1039        let antialiasing = ctx.config().antialiasing();
1040        let shadow_resolution = ctx.config().shadow_resolution();
1041
1042        ctx.ui(|ui| {
1043            egui::Panel::right("controls")
1044                .resizable(false)
1045                .default_size(300.0)
1046                .show(ui, |ui| {
1047                    egui::ScrollArea::vertical().show(ui, |ui| {
1048                        ui.label(
1049                            "right mouse button to look, W/A/S/D to move, \
1050                             space/shift up and down, wheel to scale speed",
1051                        );
1052                        ui.separator();
1053                        self.lighting_controls(ui);
1054                        ui.separator();
1055                        self.light_controls(ui);
1056                        ui.separator();
1057                        self.material_controls(ui);
1058                        ui.separator();
1059                        ui.label(format!(
1060                            "tone map {tonemap:?} \u{b7} antialiasing {antialiasing} \u{b7} \
1061                             shadow {shadow_resolution}px: set at startup, not live"
1062                        ));
1063                        ui.add(egui::Slider::new(&mut self.exposure, 0.1..=3.0).text("exposure"));
1064                        ui.add(egui::Slider::new(&mut self.bloom, 0.0..=1.0).text("bloom"));
1065                    });
1066                });
1067        });
1068    }
Source

pub fn prepare<M>(&mut self, mesh: M)
where G::Meshes: Holds<M>,

Builds and uploads mesh now, instead of on its first draw; the copy is then held like any drawn mesh’s under Config::with_mesh_memory, and no longer than that.

Takes a mesh of Game::Meshes and no other.

Auto Trait Implementations§

§

impl<'a, G> !RefUnwindSafe for FrameContext<'a, G>

§

impl<'a, G> !UnwindSafe for FrameContext<'a, G>

§

impl<'a, G> Freeze for FrameContext<'a, G>
where &'a mut MeshCatalog<<G as Game>::Meshes>: Freeze, &'a mut SkyCatalog<<G as Game>::Skyboxes>: Freeze, &'a mut Sounding<<G as Game>::Sounds>: Freeze,

§

impl<'a, G> Send for FrameContext<'a, G>
where &'a mut MeshCatalog<<G as Game>::Meshes>: Send, &'a mut SkyCatalog<<G as Game>::Skyboxes>: Send, &'a mut Sounding<<G as Game>::Sounds>: Send,

§

impl<'a, G> Sync for FrameContext<'a, G>
where &'a mut MeshCatalog<<G as Game>::Meshes>: Sync, &'a mut SkyCatalog<<G as Game>::Skyboxes>: Sync, &'a mut Sounding<<G as Game>::Sounds>: Sync,

§

impl<'a, G> Unpin for FrameContext<'a, G>
where &'a mut MeshCatalog<<G as Game>::Meshes>: Unpin, &'a mut SkyCatalog<<G as Game>::Skyboxes>: Unpin, &'a mut Sounding<<G as Game>::Sounds>: Unpin,

§

impl<'a, G> UnsafeUnpin for FrameContext<'a, G>
where &'a mut MeshCatalog<<G as Game>::Meshes>: UnsafeUnpin, &'a mut SkyCatalog<<G as Game>::Skyboxes>: UnsafeUnpin, &'a mut Sounding<<G as Game>::Sounds>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more