pub struct Ui { /* private fields */ }Expand description
The widget stack. Reach it through ctx.ui.
Implementations§
Source§impl Ui
impl Ui
Sourcepub fn open(&mut self, widget: impl Widget)
pub fn open(&mut self, widget: impl Widget)
Opens a widget on top of the stack. It announces itself on the next frame.
Examples found in repository?
80fn refresh_title(ctx: &mut Context, item: &str) {
81 ctx.ui.close_id("title");
82 let menu = title_menu(ctx).focus(item);
83 ctx.ui.open(menu);
84}
85
86impl Scene for Title {
87 fn start(&mut self, ctx: &mut Context) {
88 let menu = title_menu(ctx);
89 ctx.ui.open(menu);
90 }
91
92 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
93 match event {
94 UiEvent::Activated { menu, item } if menu == "title" => match item.as_str() {
95 "play" => {
96 ctx.ui.close_all();
97 ctx.switch_scene(Game::default());
98 }
99 "voice" => match voice_menu(ctx) {
100 Some(menu) => ctx.ui.open(menu),
101 None => ctx.ui.message(
102 "no voices",
103 "Your screen reader speaks with its own voice. Change it in the screen reader's settings.",
104 ),
105 },
106 "help" => ctx.ui.message(
107 "help",
108 "Bells fall in one of seven lanes in front of you. Listen where a bell rings, \
109 step under it with left and right, and catch it before it lands. \
110 The bell rings faster and higher the closer it gets. You have three lives. \
111 Press S for your score, Escape for the menu.",
112 ),
113 "quit" => ctx.quit(),
114 _ => {}
115 },
116 UiEvent::Activated { menu, item } if menu == "voices" => {
117 if item == "back" {
118 ctx.ui.close();
119 return;
120 }
121 if item == "default" {
122 ctx.speech.reset_voice();
123 ctx.storage.remove("voice");
124 } else if let Some(voice) =
125 item.strip_prefix("voice").and_then(|i| i.parse::<usize>().ok()).and_then(|i| ctx.speech.voices().get(i).cloned())
126 && ctx.speech.set_voice(&voice.id)
127 {
128 ctx.storage.set("voice", &voice.id);
129 }
130 ctx.ui.close();
131 refresh_title(ctx, "voice");
132 let name = ctx.speech.voice().map_or("Default voice".to_string(), |v| v.name.clone());
133 ctx.speech.speak_queued(format!("{name}. This is how I sound now."));
134 }
135 UiEvent::Changed { menu, item, value } if menu == "title" && item == "rate" => {
136 ctx.speech.set_rate(value.as_f32() / 100.0);
137 ctx.storage.set("rate", value.as_f32());
138 }
139 _ => {}
140 }
141 }More examples
36 fn start(&mut self, ctx: &mut Context) {
37 // Own interface sounds, designed in code. Files work too:
38 // ctx.ui.set_sound(UiSound::Navigate, Some("ui/click"));
39 ctx.ui.set_sound_data(UiSound::Navigate, Synth::triangle(1800.0).length(0.025).decay(150.0).volume(0.4));
40 ctx.ui.set_sound_data(UiSound::Wrap, Synth::triangle(1800.0).sweep_to(2600.0).length(0.06).decay(60.0).volume(0.4));
41 ctx.ui.set_sound_data(UiSound::Typing, Synth::noise().length(0.012).decay(300.0).volume(0.25));
42 ctx.ui.set_sound_data(
43 UiSound::Activate,
44 Synth::sine(880.0).length(0.04).volume(0.35).then(Synth::sine(1320.0).length(0.08).decay(40.0).volume(0.35)),
45 );
46 ctx.insert(Settings { volume: 0.8, difficulty: 1, name: "Player".into() });
47 ctx.ui.open(main_menu());
48 }
49
50 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
51 match event {
52 UiEvent::Activated { menu, item } => match (menu.as_str(), item.as_str()) {
53 ("main", "play") => {
54 let s = ctx.get::<Settings>().expect("settings exist");
55 let text = format!("Good luck, {}! (this is where your game would start)", s.name);
56 ctx.ui.message("intro", text);
57 }
58 ("main", "options") => {
59 let menu = options_menu(ctx.get::<Settings>().expect("settings exist"));
60 ctx.ui.open(menu);
61 }
62 ("main", "name") => ctx.ui.open(TextInput::new("name", "Enter your name").max_length(20).required()),
63 ("main", "quit") => ctx.ui.open(Confirm::new("quit", "Do you really want to quit?").default_no()),
64 ("options", "back") => ctx.ui.close(),
65 _ => {}
66 },
67 UiEvent::Changed { menu, item, value } if menu == "options" => {
68 let s = ctx.get_mut::<Settings>().expect("settings exist");
69 match item.as_str() {
70 "volume" => s.volume = value.as_f32() / 100.0,
71 "difficulty" => s.difficulty = value.as_index(),
72 "name" => s.name = value.as_str().to_string(),
73 _ => {}
74 }
75 let volume = s.volume;
76 ctx.audio.set_master_volume(volume);
77 }
78 UiEvent::TextSubmitted { id, text } if id == "name" => {
79 ctx.get_mut::<Settings>().expect("settings exist").name = text.clone();
80 ctx.speech.speak_queued(format!("Hello, {text}"));
81 }
82 UiEvent::Confirmed { id, yes: true } if id == "quit" => ctx.quit(),
83 _ => {}
84 }
85 }Sourcepub fn open_boxed(&mut self, widget: Box<dyn Widget>)
pub fn open_boxed(&mut self, widget: Box<dyn Widget>)
Opens a boxed widget.
Sourcepub fn message(&mut self, id: impl Into<String>, text: impl Into<String>)
pub fn message(&mut self, id: impl Into<String>, text: impl Into<String>)
Shows a message. Enter, Space or Escape closes it and emits UiEvent::Closed.
Multi line text can be reviewed line by line with the arrow keys.
Examples found in repository?
120 fn finish(&mut self, ctx: &mut Context) {
121 let seconds = self.clock.elapsed_secs().round() as u32;
122 let best: u32 = ctx.storage.get_or("best_time", u32::MAX);
123 let mut text = format!("You found every beacon in {seconds} seconds.");
124 if seconds < best {
125 ctx.storage.set("best_time", seconds);
126 text.push_str(" That is a new record!");
127 } else {
128 text.push_str(&format!(" Your record is {best} seconds."));
129 }
130 ctx.ui.message("done", text);
131 }More examples
8fn main() -> audio_code::Result<()> {
9 let mut score = 0;
10
11 audio_code::run("Clicker", move |ctx| {
12 if ctx.once("intro") {
13 let best: u32 = ctx.storage.get_or("best", 0);
14 ctx.speak(format!("Press space as often as you can in ten seconds. Your best is {best}. Go!"));
15 ctx.time.after("end", 10.0);
16 }
17
18 if ctx.time.is_scheduled("end") && ctx.pressed(Key::Space) {
19 score += 1;
20 ctx.audio.play_tone(300.0 + score as f32 * 15.0, 0.05);
21 }
22
23 if ctx.time.fired("end") {
24 let best: u32 = ctx.storage.get_or("best", 0);
25 if score > best {
26 ctx.storage.set("best", score);
27 ctx.ui.message("result", format!("{score} presses. New record!"));
28 } else {
29 ctx.ui.message("result", format!("{score} presses. Your best is {best}."));
30 }
31 }
32
33 if ctx.ui.closed("result") || ctx.pressed(Key::Escape) {
34 ctx.quit();
35 }
36 })
37}50 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
51 match event {
52 UiEvent::Activated { menu, item } => match (menu.as_str(), item.as_str()) {
53 ("main", "play") => {
54 let s = ctx.get::<Settings>().expect("settings exist");
55 let text = format!("Good luck, {}! (this is where your game would start)", s.name);
56 ctx.ui.message("intro", text);
57 }
58 ("main", "options") => {
59 let menu = options_menu(ctx.get::<Settings>().expect("settings exist"));
60 ctx.ui.open(menu);
61 }
62 ("main", "name") => ctx.ui.open(TextInput::new("name", "Enter your name").max_length(20).required()),
63 ("main", "quit") => ctx.ui.open(Confirm::new("quit", "Do you really want to quit?").default_no()),
64 ("options", "back") => ctx.ui.close(),
65 _ => {}
66 },
67 UiEvent::Changed { menu, item, value } if menu == "options" => {
68 let s = ctx.get_mut::<Settings>().expect("settings exist");
69 match item.as_str() {
70 "volume" => s.volume = value.as_f32() / 100.0,
71 "difficulty" => s.difficulty = value.as_index(),
72 "name" => s.name = value.as_str().to_string(),
73 _ => {}
74 }
75 let volume = s.volume;
76 ctx.audio.set_master_volume(volume);
77 }
78 UiEvent::TextSubmitted { id, text } if id == "name" => {
79 ctx.get_mut::<Settings>().expect("settings exist").name = text.clone();
80 ctx.speech.speak_queued(format!("Hello, {text}"));
81 }
82 UiEvent::Confirmed { id, yes: true } if id == "quit" => ctx.quit(),
83 _ => {}
84 }
85 }58 fn update(&mut self, ctx: &mut Context) {
59 if ctx.pressed(Key::Escape) {
60 ctx.quit();
61 }
62
63 // The lift goes up for 4 seconds and down for 4 seconds.
64 let lift_up = (ctx.time.elapsed() % 8.0) < 4.0;
65 ctx.physics.set_velocity(self.lift, vec3(0.0, 0.0, if lift_up { 2.0 } else { -2.0 }));
66
67 let run = ctx.input.axis(Key::Left, Key::Right);
68 let mut velocity = ctx.physics.velocity(self.player);
69 velocity.x = run * 6.0;
70 ctx.physics.set_velocity(self.player, velocity);
71 if ctx.pressed(Key::Space) && ctx.physics.jump(self.player, 2.5) {
72 ctx.play("jump");
73 }
74 let running = ctx.physics.on_ground(self.player) && run != 0.0;
75 self.run_sound.set_volume(if running { 0.6 } else { 0.0 });
76
77 if let Some(speed) = ctx.physics.landed(self.player) {
78 ctx.audio.sound("land").volume((speed / 12.0).clamp(0.2, 1.0)).play();
79 if speed > 15.0 {
80 ctx.speak("Ouch!");
81 }
82 }
83 if let Some((other, _)) = ctx.physics.collided(self.player)
84 && ctx.physics.tag(other) == "crate"
85 {
86 ctx.play_on(other, "push");
87 }
88 if let Some(coin) = ctx.physics.entered_tag(self.player, "coin") {
89 ctx.physics.remove(coin); // its looping sound stops by itself
90 self.score += 1;
91 ctx.audio.sound("coin").pitch(1.5).play();
92 ctx.speak(format!("{} of {COINS} coins", self.score));
93 if self.score == COINS {
94 ctx.ui.message("won", "You collected every coin! Press enter to quit.");
95 }
96 }
97 if ctx.ui.closed("won") {
98 ctx.quit();
99 }
100
101 if ctx.pressed(Key::S) {
102 let x = ctx.physics.position(self.player).x;
103 ctx.speak(format!("Score {}, position {x:.0}", self.score));
104 }
105 }92 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
93 match event {
94 UiEvent::Activated { menu, item } if menu == "title" => match item.as_str() {
95 "play" => {
96 ctx.ui.close_all();
97 ctx.switch_scene(Game::default());
98 }
99 "voice" => match voice_menu(ctx) {
100 Some(menu) => ctx.ui.open(menu),
101 None => ctx.ui.message(
102 "no voices",
103 "Your screen reader speaks with its own voice. Change it in the screen reader's settings.",
104 ),
105 },
106 "help" => ctx.ui.message(
107 "help",
108 "Bells fall in one of seven lanes in front of you. Listen where a bell rings, \
109 step under it with left and right, and catch it before it lands. \
110 The bell rings faster and higher the closer it gets. You have three lives. \
111 Press S for your score, Escape for the menu.",
112 ),
113 "quit" => ctx.quit(),
114 _ => {}
115 },
116 UiEvent::Activated { menu, item } if menu == "voices" => {
117 if item == "back" {
118 ctx.ui.close();
119 return;
120 }
121 if item == "default" {
122 ctx.speech.reset_voice();
123 ctx.storage.remove("voice");
124 } else if let Some(voice) =
125 item.strip_prefix("voice").and_then(|i| i.parse::<usize>().ok()).and_then(|i| ctx.speech.voices().get(i).cloned())
126 && ctx.speech.set_voice(&voice.id)
127 {
128 ctx.storage.set("voice", &voice.id);
129 }
130 ctx.ui.close();
131 refresh_title(ctx, "voice");
132 let name = ctx.speech.voice().map_or("Default voice".to_string(), |v| v.name.clone());
133 ctx.speech.speak_queued(format!("{name}. This is how I sound now."));
134 }
135 UiEvent::Changed { menu, item, value } if menu == "title" && item == "rate" => {
136 ctx.speech.set_rate(value.as_f32() / 100.0);
137 ctx.storage.set("rate", value.as_f32());
138 }
139 _ => {}
140 }
141 }
142
143 fn update(&mut self, _ctx: &mut Context) {}
144}
145
146// ------------------------------------------------------------------------- game
147
148struct Game {
149 lane: i32,
150 bell_lane: i32,
151 /// Seconds a bell needs to land; shrinks with every catch.
152 fall: f32,
153 /// Seconds until the current bell lands. Negative while waiting for the next bell.
154 left: f32,
155 next_ring: f32,
156 score: u32,
157 lives: u32,
158 over: bool,
159}
160
161impl Default for Game {
162 fn default() -> Self {
163 Self { lane: 0, bell_lane: 0, fall: FIRST_FALL, left: 0.0, next_ring: 0.0, score: 0, lives: LIVES, over: false }
164 }
165}
166
167impl Game {
168 fn drop_bell(&mut self) {
169 self.bell_lane = random::int(-LANES as i64, LANES as i64) as i32;
170 self.left = self.fall;
171 self.next_ring = 0.0;
172 }
173
174 /// Where the bell is, seen from the player: lanes to the side, a little in front.
175 fn bell_position(&self) -> Vec3 {
176 vec3((self.bell_lane - self.lane) as f32 * LANE_WIDTH, 2.0, 0.0)
177 }
178
179 fn ring(&mut self, ctx: &mut Context) {
180 // 0.0 when the bell starts falling, 1.0 when it lands.
181 let progress = 1.0 - self.left / self.fall;
182 let position = self.bell_position();
183 ctx.audio.sound("bell").at(position).pitch(1.0 + progress * 0.6).play();
184 self.next_ring = 0.12 + 0.45 * (1.0 - progress);
185 }
186
187 fn land(&mut self, ctx: &mut Context) {
188 if self.lane == self.bell_lane {
189 self.score += 1;
190 self.fall = (self.fall * 0.93).max(FASTEST_FALL);
191 ctx.play("catch");
192 if self.score.is_multiple_of(5) {
193 ctx.speak(format!("{} bells!", self.score));
194 }
195 } else {
196 self.lives -= 1;
197 ctx.play("miss");
198 match self.lives {
199 0 => return self.game_over(ctx),
200 1 => ctx.speak("Missed! Last life."),
201 n => ctx.speak(format!("Missed! {n} lives left.")),
202 }
203 }
204 // A short break before the next bell.
205 self.left = -0.8;
206 }
207
208 fn game_over(&mut self, ctx: &mut Context) {
209 self.over = true;
210 let best: u32 = ctx.storage.get_or("best", 0);
211 let text = if self.score > best {
212 ctx.storage.set("best", self.score);
213 format!("Game over. {} bells, a new best score!", self.score)
214 } else {
215 format!("Game over. {} bells. Your best is {best}.", self.score)
216 };
217 ctx.ui.message("over", text);
218 }75 fn update(&mut self, ctx: &mut Context) {
76 if ctx.pressed(Key::Escape) {
77 ctx.quit();
78 }
79 if ctx.pressed(Key::Left) || ctx.pressed(Key::Right) {
80 let facing = ctx.physics.turn(self.drone, if ctx.pressed(Key::Left) { -30.0 } else { 30.0 });
81 ctx.speak(math::direction_name(facing));
82 }
83 if ctx.pressed(Key::PageUp) || ctx.pressed(Key::PageDown) {
84 let pitch = ctx.physics.tilt(self.drone, if ctx.pressed(Key::PageUp) { 30.0 } else { -30.0 });
85 ctx.speak(describe_pitch(pitch));
86 }
87
88 // Smooth flight: 4 meters per second while a key is held.
89 let speed = 4.0 * ctx.time.delta();
90 let forward = ctx.input.axis(Key::Down, Key::Up);
91 if forward != 0.0 {
92 let result = ctx.physics.move_forward(self.drone, forward * speed);
93 self.fly(ctx, result);
94 }
95 let rise = ctx.input.axis(Key::Q, Key::E);
96 if rise != 0.0 {
97 let result = ctx.physics.move_up(self.drone, rise * speed);
98 self.fly(ctx, result);
99 }
100
101 if ctx.pressed(Key::C) {
102 let position = ctx.physics.position(self.drone);
103 match ctx.physics.nearest_with_tag(position, "bird") {
104 Some(bird) => {
105 let text = ctx.physics.describe(self.drone, bird); // e.g. "2 o'clock, 9 meters, 6 meters above"
106 ctx.speak(text);
107 }
108 None => ctx.speak("No birds left"),
109 }
110 }
111 if ctx.pressed(Key::F) {
112 let (facing, pitch) = (ctx.physics.facing(self.drone), ctx.physics.pitch(self.drone));
113 ctx.speak(format!("Facing {}, {}", math::direction_name(facing), describe_pitch(pitch)));
114 }
115 if ctx.pressed(Key::H) {
116 let height = ctx.physics.position(self.drone).z;
117 ctx.speak(format!("{height:.0} meters high"));
118 }
119
120 if let Some(bird) = ctx.physics.entered_tag(self.drone, "bird") {
121 ctx.physics.remove(bird);
122 self.birds -= 1;
123 ctx.play("found");
124 if self.birds == 0 {
125 ctx.ui.message("done", "You found every bird!");
126 } else {
127 ctx.speak(format!("Bird found! {} left.", self.birds));
128 }
129 }
130 if ctx.ui.closed("done") {
131 ctx.quit();
132 }
133 }Sourcepub fn confirm(&mut self, id: impl Into<String>, question: impl Into<String>)
pub fn confirm(&mut self, id: impl Into<String>, question: impl Into<String>)
Asks a yes/no question. The answer arrives as UiEvent::Confirmed; see Ui::confirmed.
Sourcepub fn input(&mut self, id: impl Into<String>, prompt: impl Into<String>)
pub fn input(&mut self, id: impl Into<String>, prompt: impl Into<String>)
Asks the player to type text. The result arrives as UiEvent::TextSubmitted;
see Ui::submitted.
Sourcepub fn close(&mut self)
pub fn close(&mut self)
Closes the top widget without any sound or event.
Examples found in repository?
50 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
51 match event {
52 UiEvent::Activated { menu, item } => match (menu.as_str(), item.as_str()) {
53 ("main", "play") => {
54 let s = ctx.get::<Settings>().expect("settings exist");
55 let text = format!("Good luck, {}! (this is where your game would start)", s.name);
56 ctx.ui.message("intro", text);
57 }
58 ("main", "options") => {
59 let menu = options_menu(ctx.get::<Settings>().expect("settings exist"));
60 ctx.ui.open(menu);
61 }
62 ("main", "name") => ctx.ui.open(TextInput::new("name", "Enter your name").max_length(20).required()),
63 ("main", "quit") => ctx.ui.open(Confirm::new("quit", "Do you really want to quit?").default_no()),
64 ("options", "back") => ctx.ui.close(),
65 _ => {}
66 },
67 UiEvent::Changed { menu, item, value } if menu == "options" => {
68 let s = ctx.get_mut::<Settings>().expect("settings exist");
69 match item.as_str() {
70 "volume" => s.volume = value.as_f32() / 100.0,
71 "difficulty" => s.difficulty = value.as_index(),
72 "name" => s.name = value.as_str().to_string(),
73 _ => {}
74 }
75 let volume = s.volume;
76 ctx.audio.set_master_volume(volume);
77 }
78 UiEvent::TextSubmitted { id, text } if id == "name" => {
79 ctx.get_mut::<Settings>().expect("settings exist").name = text.clone();
80 ctx.speech.speak_queued(format!("Hello, {text}"));
81 }
82 UiEvent::Confirmed { id, yes: true } if id == "quit" => ctx.quit(),
83 _ => {}
84 }
85 }More examples
92 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
93 match event {
94 UiEvent::Activated { menu, item } if menu == "title" => match item.as_str() {
95 "play" => {
96 ctx.ui.close_all();
97 ctx.switch_scene(Game::default());
98 }
99 "voice" => match voice_menu(ctx) {
100 Some(menu) => ctx.ui.open(menu),
101 None => ctx.ui.message(
102 "no voices",
103 "Your screen reader speaks with its own voice. Change it in the screen reader's settings.",
104 ),
105 },
106 "help" => ctx.ui.message(
107 "help",
108 "Bells fall in one of seven lanes in front of you. Listen where a bell rings, \
109 step under it with left and right, and catch it before it lands. \
110 The bell rings faster and higher the closer it gets. You have three lives. \
111 Press S for your score, Escape for the menu.",
112 ),
113 "quit" => ctx.quit(),
114 _ => {}
115 },
116 UiEvent::Activated { menu, item } if menu == "voices" => {
117 if item == "back" {
118 ctx.ui.close();
119 return;
120 }
121 if item == "default" {
122 ctx.speech.reset_voice();
123 ctx.storage.remove("voice");
124 } else if let Some(voice) =
125 item.strip_prefix("voice").and_then(|i| i.parse::<usize>().ok()).and_then(|i| ctx.speech.voices().get(i).cloned())
126 && ctx.speech.set_voice(&voice.id)
127 {
128 ctx.storage.set("voice", &voice.id);
129 }
130 ctx.ui.close();
131 refresh_title(ctx, "voice");
132 let name = ctx.speech.voice().map_or("Default voice".to_string(), |v| v.name.clone());
133 ctx.speech.speak_queued(format!("{name}. This is how I sound now."));
134 }
135 UiEvent::Changed { menu, item, value } if menu == "title" && item == "rate" => {
136 ctx.speech.set_rate(value.as_f32() / 100.0);
137 ctx.storage.set("rate", value.as_f32());
138 }
139 _ => {}
140 }
141 }Sourcepub fn close_id(&mut self, id: &str)
pub fn close_id(&mut self, id: &str)
Closes the widget with the given id and everything above it.
Examples found in repository?
More examples
Sourcepub fn close_all(&mut self)
pub fn close_all(&mut self)
Closes every widget.
Examples found in repository?
92 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
93 match event {
94 UiEvent::Activated { menu, item } if menu == "title" => match item.as_str() {
95 "play" => {
96 ctx.ui.close_all();
97 ctx.switch_scene(Game::default());
98 }
99 "voice" => match voice_menu(ctx) {
100 Some(menu) => ctx.ui.open(menu),
101 None => ctx.ui.message(
102 "no voices",
103 "Your screen reader speaks with its own voice. Change it in the screen reader's settings.",
104 ),
105 },
106 "help" => ctx.ui.message(
107 "help",
108 "Bells fall in one of seven lanes in front of you. Listen where a bell rings, \
109 step under it with left and right, and catch it before it lands. \
110 The bell rings faster and higher the closer it gets. You have three lives. \
111 Press S for your score, Escape for the menu.",
112 ),
113 "quit" => ctx.quit(),
114 _ => {}
115 },
116 UiEvent::Activated { menu, item } if menu == "voices" => {
117 if item == "back" {
118 ctx.ui.close();
119 return;
120 }
121 if item == "default" {
122 ctx.speech.reset_voice();
123 ctx.storage.remove("voice");
124 } else if let Some(voice) =
125 item.strip_prefix("voice").and_then(|i| i.parse::<usize>().ok()).and_then(|i| ctx.speech.voices().get(i).cloned())
126 && ctx.speech.set_voice(&voice.id)
127 {
128 ctx.storage.set("voice", &voice.id);
129 }
130 ctx.ui.close();
131 refresh_title(ctx, "voice");
132 let name = ctx.speech.voice().map_or("Default voice".to_string(), |v| v.name.clone());
133 ctx.speech.speak_queued(format!("{name}. This is how I sound now."));
134 }
135 UiEvent::Changed { menu, item, value } if menu == "title" && item == "rate" => {
136 ctx.speech.set_rate(value.as_f32() / 100.0);
137 ctx.storage.set("rate", value.as_f32());
138 }
139 _ => {}
140 }
141 }Sourcepub fn contains(&self, id: &str) -> bool
pub fn contains(&self, id: &str) -> bool
Whether a widget with this id is anywhere on the stack.
Sourcepub fn set_sounds(&mut self, sounds: UiSounds)
pub fn set_sounds(&mut self, sounds: UiSounds)
Replaces the interface sounds used by all widgets. Use UiSounds::none for silence.
Sourcepub fn set_sound(&mut self, kind: UiSound, name: Option<&str>)
pub fn set_sound(&mut self, kind: UiSound, name: Option<&str>)
Uses the sound name (a file or registered sound) for one kind of interface sound.
None makes it silent.
ctx.ui.set_sound(UiSound::Navigate, Some("ui/click")); // sounds/ui/click.ogg
ctx.ui.set_sound(UiSound::Edge, None);Sourcepub fn set_sound_data(&mut self, kind: UiSound, data: impl Into<SoundData>)
pub fn set_sound_data(&mut self, kind: UiSound, data: impl Into<SoundData>)
Uses a sound made in code for one kind of interface sound, typically a Synth:
ctx.ui.set_sound_data(UiSound::Navigate, Synth::triangle(1800.0).length(0.02).decay(150.0));
ctx.ui.set_sound_data(UiSound::Typing, Synth::noise().length(0.01).volume(0.2));Examples found in repository?
36 fn start(&mut self, ctx: &mut Context) {
37 // Own interface sounds, designed in code. Files work too:
38 // ctx.ui.set_sound(UiSound::Navigate, Some("ui/click"));
39 ctx.ui.set_sound_data(UiSound::Navigate, Synth::triangle(1800.0).length(0.025).decay(150.0).volume(0.4));
40 ctx.ui.set_sound_data(UiSound::Wrap, Synth::triangle(1800.0).sweep_to(2600.0).length(0.06).decay(60.0).volume(0.4));
41 ctx.ui.set_sound_data(UiSound::Typing, Synth::noise().length(0.012).decay(300.0).volume(0.25));
42 ctx.ui.set_sound_data(
43 UiSound::Activate,
44 Synth::sine(880.0).length(0.04).volume(0.35).then(Synth::sine(1320.0).length(0.08).decay(40.0).volume(0.35)),
45 );
46 ctx.insert(Settings { volume: 0.8, difficulty: 1, name: "Player".into() });
47 ctx.ui.open(main_menu());
48 }Sourcepub fn value(&self, menu: &str, item: &str) -> Option<Value>
pub fn value(&self, menu: &str, item: &str) -> Option<Value>
Current value of a menu item while the menu is open.
Sourcepub fn activated(&self, menu: &str) -> Option<&str>
pub fn activated(&self, menu: &str) -> Option<&str>
Id of the button activated in menu this frame.
Sourcepub fn clicked(&self, menu: &str, item: &str) -> bool
pub fn clicked(&self, menu: &str, item: &str) -> bool
Whether button item in menu was activated this frame.
Sourcepub fn changed(&self, menu: &str, item: &str) -> Option<&Value>
pub fn changed(&self, menu: &str, item: &str) -> Option<&Value>
New value of item in menu if it changed this frame.
Sourcepub fn closed(&self, id: &str) -> bool
pub fn closed(&self, id: &str) -> bool
Whether the widget id was closed by the player this frame.
Examples found in repository?
8fn main() -> audio_code::Result<()> {
9 let mut score = 0;
10
11 audio_code::run("Clicker", move |ctx| {
12 if ctx.once("intro") {
13 let best: u32 = ctx.storage.get_or("best", 0);
14 ctx.speak(format!("Press space as often as you can in ten seconds. Your best is {best}. Go!"));
15 ctx.time.after("end", 10.0);
16 }
17
18 if ctx.time.is_scheduled("end") && ctx.pressed(Key::Space) {
19 score += 1;
20 ctx.audio.play_tone(300.0 + score as f32 * 15.0, 0.05);
21 }
22
23 if ctx.time.fired("end") {
24 let best: u32 = ctx.storage.get_or("best", 0);
25 if score > best {
26 ctx.storage.set("best", score);
27 ctx.ui.message("result", format!("{score} presses. New record!"));
28 } else {
29 ctx.ui.message("result", format!("{score} presses. Your best is {best}."));
30 }
31 }
32
33 if ctx.ui.closed("result") || ctx.pressed(Key::Escape) {
34 ctx.quit();
35 }
36 })
37}More examples
228 fn update(&mut self, ctx: &mut Context) {
229 if self.over {
230 if ctx.ui.closed("over") {
231 ctx.switch_scene(Title);
232 }
233 return;
234 }
235 if ctx.pressed(Key::Escape) {
236 ctx.switch_scene(Title);
237 return;
238 }
239 if ctx.pressed(Key::S) {
240 ctx.speak(format!("{} bells, {} lives", self.score, self.lives));
241 }
242 for (key, step) in [(Key::Left, -1), (Key::Right, 1)] {
243 if ctx.input.repeating(key, 0.3, 0.15) {
244 let lane = (self.lane + step).clamp(-LANES, LANES);
245 if lane == self.lane {
246 ctx.play("edge");
247 } else {
248 self.lane = lane;
249 ctx.audio.sound("step").at(vec3(step as f32 * 0.5, 0.0, 0.0)).random_pitch(0.9, 1.1).play();
250 }
251 }
252 }
253
254 let dt = ctx.time.delta();
255 if self.left < 0.0 {
256 self.left += dt;
257 if self.left >= 0.0 {
258 self.drop_bell();
259 }
260 return;
261 }
262 self.left -= dt;
263 self.next_ring -= dt;
264 if self.left <= 0.0 {
265 self.land(ctx);
266 } else if self.next_ring <= 0.0 {
267 self.ring(ctx);
268 }
269 }58 fn update(&mut self, ctx: &mut Context) {
59 if ctx.pressed(Key::Escape) {
60 ctx.quit();
61 }
62
63 // The lift goes up for 4 seconds and down for 4 seconds.
64 let lift_up = (ctx.time.elapsed() % 8.0) < 4.0;
65 ctx.physics.set_velocity(self.lift, vec3(0.0, 0.0, if lift_up { 2.0 } else { -2.0 }));
66
67 let run = ctx.input.axis(Key::Left, Key::Right);
68 let mut velocity = ctx.physics.velocity(self.player);
69 velocity.x = run * 6.0;
70 ctx.physics.set_velocity(self.player, velocity);
71 if ctx.pressed(Key::Space) && ctx.physics.jump(self.player, 2.5) {
72 ctx.play("jump");
73 }
74 let running = ctx.physics.on_ground(self.player) && run != 0.0;
75 self.run_sound.set_volume(if running { 0.6 } else { 0.0 });
76
77 if let Some(speed) = ctx.physics.landed(self.player) {
78 ctx.audio.sound("land").volume((speed / 12.0).clamp(0.2, 1.0)).play();
79 if speed > 15.0 {
80 ctx.speak("Ouch!");
81 }
82 }
83 if let Some((other, _)) = ctx.physics.collided(self.player)
84 && ctx.physics.tag(other) == "crate"
85 {
86 ctx.play_on(other, "push");
87 }
88 if let Some(coin) = ctx.physics.entered_tag(self.player, "coin") {
89 ctx.physics.remove(coin); // its looping sound stops by itself
90 self.score += 1;
91 ctx.audio.sound("coin").pitch(1.5).play();
92 ctx.speak(format!("{} of {COINS} coins", self.score));
93 if self.score == COINS {
94 ctx.ui.message("won", "You collected every coin! Press enter to quit.");
95 }
96 }
97 if ctx.ui.closed("won") {
98 ctx.quit();
99 }
100
101 if ctx.pressed(Key::S) {
102 let x = ctx.physics.position(self.player).x;
103 ctx.speak(format!("Score {}, position {x:.0}", self.score));
104 }
105 }58 fn update(&mut self, ctx: &mut Context) {
59 if ctx.pressed(Key::Escape) {
60 ctx.push_scene(Pause);
61 }
62 if ctx.pressed(Key::Left) || ctx.pressed(Key::Right) {
63 let degrees = if ctx.pressed(Key::Left) { -45.0 } else { 45.0 };
64 let facing = ctx.physics.turn(self.player, degrees);
65 ctx.speak(math::direction_name(facing));
66 }
67
68 let forward = ctx.input.repeating(Key::Up, 0.35, 0.35);
69 let backward = ctx.input.repeating(Key::Down, 0.35, 0.35);
70 if forward || backward {
71 let step = if forward { 1.0 } else { -1.0 };
72 if ctx.physics.move_forward(self.player, step).blocked {
73 ctx.play("wall");
74 } else {
75 ctx.audio.sound("step").random_pitch(0.85, 1.15).play();
76 }
77 }
78
79 if ctx.pressed(Key::C) {
80 let position = ctx.physics.position(self.player);
81 match ctx.physics.nearest_with_tag(position, "beacon") {
82 Some(beacon) => {
83 let text = ctx.physics.describe(self.player, beacon);
84 ctx.speak(text);
85 }
86 None => ctx.speak("No beacons left"),
87 }
88 }
89 if ctx.pressed(Key::F) {
90 let facing = ctx.physics.facing(self.player);
91 ctx.speak(format!("Facing {}", math::direction_name(facing)));
92 }
93
94 if let Some(beacon) = ctx.physics.entered_tag(self.player, "beacon") {
95 ctx.physics.remove(beacon);
96 self.beacons.retain(|b| *b != beacon);
97 ctx.play("found");
98 if self.beacons.is_empty() {
99 self.finish(ctx);
100 } else {
101 ctx.speak(format!("Beacon found! {} left.", self.beacons.len()));
102 }
103 }
104 if ctx.ui.closed("done") {
105 ctx.quit();
106 }
107 }
108
109 // The clock should not run while the pause menu is open.
110 fn pause(&mut self, _ctx: &mut Context) {
111 self.clock.pause();
112 }
113
114 fn resume(&mut self, _ctx: &mut Context) {
115 self.clock.resume();
116 }
117}
118
119impl Maze {
120 fn finish(&mut self, ctx: &mut Context) {
121 let seconds = self.clock.elapsed_secs().round() as u32;
122 let best: u32 = ctx.storage.get_or("best_time", u32::MAX);
123 let mut text = format!("You found every beacon in {seconds} seconds.");
124 if seconds < best {
125 ctx.storage.set("best_time", seconds);
126 text.push_str(" That is a new record!");
127 } else {
128 text.push_str(&format!(" Your record is {best} seconds."));
129 }
130 ctx.ui.message("done", text);
131 }
132}
133
134struct Pause;
135
136impl Scene for Pause {
137 fn start(&mut self, ctx: &mut Context) {
138 ctx.time.pause();
139 ctx.ui.open(Menu::new("pause", "Paused").button("resume", "Resume").button("quit", "Quit game"));
140 }
141
142 fn update(&mut self, ctx: &mut Context) {
143 if ctx.ui.clicked("pause", "resume") || ctx.ui.closed("pause") {
144 ctx.ui.close_id("pause");
145 ctx.pop_scene();
146 }
147 if ctx.ui.clicked("pause", "quit") {
148 ctx.quit();
149 }
150 }75 fn update(&mut self, ctx: &mut Context) {
76 if ctx.pressed(Key::Escape) {
77 ctx.quit();
78 }
79 if ctx.pressed(Key::Left) || ctx.pressed(Key::Right) {
80 let facing = ctx.physics.turn(self.drone, if ctx.pressed(Key::Left) { -30.0 } else { 30.0 });
81 ctx.speak(math::direction_name(facing));
82 }
83 if ctx.pressed(Key::PageUp) || ctx.pressed(Key::PageDown) {
84 let pitch = ctx.physics.tilt(self.drone, if ctx.pressed(Key::PageUp) { 30.0 } else { -30.0 });
85 ctx.speak(describe_pitch(pitch));
86 }
87
88 // Smooth flight: 4 meters per second while a key is held.
89 let speed = 4.0 * ctx.time.delta();
90 let forward = ctx.input.axis(Key::Down, Key::Up);
91 if forward != 0.0 {
92 let result = ctx.physics.move_forward(self.drone, forward * speed);
93 self.fly(ctx, result);
94 }
95 let rise = ctx.input.axis(Key::Q, Key::E);
96 if rise != 0.0 {
97 let result = ctx.physics.move_up(self.drone, rise * speed);
98 self.fly(ctx, result);
99 }
100
101 if ctx.pressed(Key::C) {
102 let position = ctx.physics.position(self.drone);
103 match ctx.physics.nearest_with_tag(position, "bird") {
104 Some(bird) => {
105 let text = ctx.physics.describe(self.drone, bird); // e.g. "2 o'clock, 9 meters, 6 meters above"
106 ctx.speak(text);
107 }
108 None => ctx.speak("No birds left"),
109 }
110 }
111 if ctx.pressed(Key::F) {
112 let (facing, pitch) = (ctx.physics.facing(self.drone), ctx.physics.pitch(self.drone));
113 ctx.speak(format!("Facing {}, {}", math::direction_name(facing), describe_pitch(pitch)));
114 }
115 if ctx.pressed(Key::H) {
116 let height = ctx.physics.position(self.drone).z;
117 ctx.speak(format!("{height:.0} meters high"));
118 }
119
120 if let Some(bird) = ctx.physics.entered_tag(self.drone, "bird") {
121 ctx.physics.remove(bird);
122 self.birds -= 1;
123 ctx.play("found");
124 if self.birds == 0 {
125 ctx.ui.message("done", "You found every bird!");
126 } else {
127 ctx.speak(format!("Bird found! {} left.", self.birds));
128 }
129 }
130 if ctx.ui.closed("done") {
131 ctx.quit();
132 }
133 }Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for Ui
impl !Send for Ui
impl !Sync for Ui
impl !UnwindSafe for Ui
impl Freeze for Ui
impl Unpin for Ui
impl UnsafeUnpin for Ui
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.