Skip to content

Commit f67eb78

Browse files
authored
refactor(app)!: add FrameContext (#29)
* refactor(app): replaced separate `gfx`, `input`, `timer` & `egui_ctx` arguments with a single `FrameContext` * feat(app): expose window events in `FrameContext` * docs: update examples to `FrameContext` BREAKING CHANGE: App::run now receives a FrameContext instead of separate arguments
1 parent 3a50d21 commit f67eb78

7 files changed

Lines changed: 76 additions & 88 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ let mut position = Vec2::ZERO;
4949

5050
App::new()
5151
.title("Egor Stateful Rectangle")
52-
.run(move |gfx, input, timer| {
52+
.run(move |FrameContext { gfx, input, timer, .. } | {
5353
let dx = input.key_held(KeyCode::ArrowRight) as i8
5454
- input.key_held(KeyCode::ArrowLeft) as i8;
5555
let dy =

crates/egor_glue/src/app.rs

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,21 @@ use egor_app::{
1010
};
1111
use egor_render::Renderer;
1212

13-
#[cfg(not(feature = "ui"))]
14-
type UpdateFn = dyn FnMut(&mut Graphics, &Input, &FrameTimer);
15-
#[cfg(feature = "ui")]
16-
type UpdateFn = dyn FnMut(&mut Graphics, &Input, &FrameTimer, &egui::Context);
13+
type UpdateFn = dyn FnMut(&mut FrameContext);
14+
15+
pub struct FrameContext<'a> {
16+
pub events: Vec<WindowEvent>,
17+
pub gfx: Graphics<'a>,
18+
pub input: &'a Input,
19+
pub timer: &'a FrameTimer,
20+
#[cfg(feature = "ui")]
21+
pub egui_ctx: &'a egui::Context,
22+
}
1723

1824
pub struct App {
25+
events: Vec<WindowEvent>,
1926
update: Option<Box<UpdateFn>>,
2027
config: Option<AppConfig>,
21-
on_quit: Option<Box<dyn FnMut()>>,
2228
vsync: bool,
2329
text_renderer: Option<TextRenderer>,
2430
#[cfg(feature = "ui")]
@@ -29,9 +35,9 @@ impl App {
2935
/// Create a new [`App`]
3036
pub fn new() -> Self {
3137
Self {
38+
events: Vec::new(),
3239
update: None,
3340
config: Some(AppConfig::default()),
34-
on_quit: None,
3541
vsync: true,
3642
text_renderer: None,
3743
#[cfg(feature = "ui")]
@@ -48,7 +54,7 @@ impl App {
4854
}
4955

5056
/// Set window size (width, height in pixels)
51-
pub fn screen_size(mut self, width: u32, height: u32) -> Self {
57+
pub fn window_size(mut self, width: u32, height: u32) -> Self {
5258
if let Some(c) = self.config.as_mut() {
5359
c.width = width;
5460
c.height = height;
@@ -71,57 +77,30 @@ impl App {
7177
}
7278

7379
/// Run the app with a per-frame update closure
74-
#[cfg(not(feature = "ui"))]
75-
pub fn run(
76-
mut self,
77-
#[allow(unused_mut)] mut update: impl FnMut(&mut Graphics, &Input, &FrameTimer) + 'static,
78-
) {
79-
#[cfg(feature = "hot_reload")]
80-
let update = {
81-
dioxus_devtools::connect_subsecond();
82-
83-
move |g: &mut Graphics, i: &Input, t: &FrameTimer| {
84-
dioxus_devtools::subsecond::call(|| update(g, i, t))
85-
}
86-
};
87-
self.update = Some(Box::new(update));
88-
89-
let config = self.config.take().unwrap();
90-
AppRunner::new(self, config).run();
91-
}
92-
#[cfg(feature = "ui")]
93-
pub fn run(
94-
mut self,
95-
#[allow(unused_mut)] mut update: impl FnMut(&mut Graphics, &Input, &FrameTimer, &egui::Context)
96-
+ 'static,
97-
) {
80+
pub fn run(mut self, #[allow(unused_mut)] mut update: impl FnMut(&mut FrameContext) + 'static) {
9881
#[cfg(feature = "hot_reload")]
9982
let update = {
10083
dioxus_devtools::connect_subsecond();
10184

102-
move |g: &mut Graphics, i: &Input, t: &FrameTimer, ui: &egui::Context| {
103-
dioxus_devtools::subsecond::call(|| update(g, i, t, ui))
85+
move |ctx: &mut FrameContext| {
86+
dioxus_devtools::subsecond::call(|| update(ctx));
10487
}
10588
};
10689
self.update = Some(Box::new(update));
10790

10891
let config = self.config.take().unwrap();
10992
AppRunner::new(self, config).run();
11093
}
111-
112-
/// Sets a closure to call when the app is quitting
113-
pub fn on_quit(mut self, f: impl FnMut() + 'static) -> Self {
114-
self.on_quit = Some(Box::new(f));
115-
self
116-
}
11794
}
11895

11996
impl AppHandler<Renderer> for App {
120-
fn on_window_event(&mut self, _window: &Window, _event: &WindowEvent) {
97+
fn on_window_event(&mut self, _window: &Window, event: &WindowEvent) {
12198
#[cfg(feature = "ui")]
12299
if let Some(egui) = self.egui.as_mut() {
123-
egui.handle_event(_window, _event);
100+
egui.handle_event(_window, event);
124101
}
102+
103+
self.events.push(event.clone());
125104
}
126105

127106
async fn with_resource(&mut self, window: Arc<Window>) -> Renderer {
@@ -167,17 +146,21 @@ impl AppHandler<Renderer> for App {
167146
let (device, queue) = (renderer.device().clone(), renderer.queue().clone());
168147

169148
let text_renderer = self.text_renderer.as_mut().unwrap();
170-
let mut graphics = Graphics::new(renderer, text_renderer);
171149

172-
#[cfg(not(feature = "ui"))]
173-
update(&mut graphics, input, timer);
174150
#[cfg(feature = "ui")]
175-
{
176-
let egui_ctx = self.egui.as_mut().unwrap().begin_frame(_window);
177-
update(&mut graphics, input, timer, egui_ctx);
178-
}
151+
let egui_ctx = self.egui.as_mut().unwrap().begin_frame(_window);
152+
let mut ctx = FrameContext {
153+
gfx: Graphics::new(renderer, text_renderer),
154+
input,
155+
timer,
156+
#[cfg(feature = "ui")]
157+
egui_ctx,
158+
events: std::mem::take(&mut self.events),
159+
};
160+
update(&mut ctx);
161+
162+
let geometry = ctx.gfx.flush();
179163

180-
let geometry = graphics.flush();
181164
text_renderer.prepare(&device, &queue, width, height);
182165

183166
{
@@ -210,10 +193,4 @@ impl AppHandler<Renderer> for App {
210193
fn resize(&mut self, width: u32, height: u32, renderer: &mut Renderer) {
211194
renderer.resize(width, height)
212195
}
213-
214-
fn on_quit(&mut self) {
215-
if let Some(f) = &mut self.on_quit {
216-
f();
217-
}
218-
}
219196
}

crates/egor_glue/src/text.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,6 @@ impl TextRenderer {
110110
/// and let it drop to submit the text for the current frame
111111
///
112112
/// Text is queued immediately and rendered on the next frame
113-
///
114-
/// # Example
115-
/// ```no_run
116-
/// graphics.text("Hello world")
117-
/// .at((20.0, 40.0))
118-
/// .size(18.0)
119-
/// .color(Color::WHITE);
120-
/// ```
121113
pub struct TextBuilder<'a> {
122114
renderer: &'a mut TextRenderer,
123115
text: String,

demos/hot-reload-test/src/main.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use egor::{
2-
app::App,
2+
app::{App, FrameContext},
33
math::{Vec2, vec2},
44
render::Color,
55
};
66

77
fn main() {
8-
App::new().title("Hot Reload Demo").run(move |gfx, _, _| {
9-
// Feel free to change this code and see hot-reload in action!
10-
gfx.rect()
11-
.at(vec2(0., 0.))
12-
.size(Vec2::splat(100.0))
13-
.color(Color::RED);
14-
});
8+
App::new()
9+
.title("Hot Reload Demo")
10+
.run(move |FrameContext { gfx, .. }| {
11+
// Feel free to change this code and see hot-reload in action!
12+
gfx.rect()
13+
.at(vec2(0., 0.))
14+
.size(Vec2::splat(100.0))
15+
.color(Color::RED);
16+
});
1517
}

demos/secs_particles/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use egor::{
2-
app::App,
2+
app::{App, FrameContext},
33
math::{Vec2, vec2},
44
render::Color,
55
};
@@ -35,11 +35,11 @@ fn main() {
3535

3636
App::new()
3737
.title("Egor ECS Particles Demo")
38-
.run(move |gfx, _, frame| {
38+
.run(move |FrameContext { gfx, timer, .. }| {
3939
let screen_size = gfx.screen_size();
4040

4141
world.query(|_, pos: &mut Position, vel: &Velocity| {
42-
pos.vec += vel.vec * speed * frame.delta;
42+
pos.vec += vel.vec * speed * timer.delta;
4343
wraparound(&mut pos.vec, screen_size);
4444

4545
gfx.rect()

demos/shooter/src/main.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod tilemap;
44
use rand::Rng;
55

66
use egor::{
7-
app::{App, egui::Window},
7+
app::{App, FrameContext, WindowEvent, egui::Window},
88
input::{KeyCode, MouseButton},
99
math::{Rect, Vec2, vec2},
1010
render::Color,
@@ -134,12 +134,27 @@ fn main() {
134134
game_over: false,
135135
};
136136

137-
App::new()
138-
.title("Egor Shooter Demo")
139-
.on_quit(|| {
140-
println!("Quitting already? Don't be a sore loser");
141-
})
142-
.run(move |gfx, input, timer, ui| {
137+
App::new().title("Egor Shooter Demo").run(
138+
move |FrameContext {
139+
gfx,
140+
input,
141+
timer,
142+
egui_ctx,
143+
events,
144+
}| {
145+
for event in events {
146+
match event {
147+
WindowEvent::CloseRequested => {
148+
println!("Quitting already? Don't be a sore loser");
149+
println!("Final Wave: {}", state.wave);
150+
println!("Killed {} zombies", state.kills);
151+
152+
state.game_over = true;
153+
}
154+
_ => {}
155+
}
156+
}
157+
143158
if timer.frame == 0 {
144159
state.map.load_tileset(
145160
gfx,
@@ -157,7 +172,6 @@ fn main() {
157172
}
158173

159174
let screen_size = gfx.screen_size();
160-
let screen_half = screen_size / 2.0;
161175

162176
if state.game_over {
163177
gfx.text("GAME OVER")
@@ -166,6 +180,7 @@ fn main() {
166180
return;
167181
}
168182

183+
let screen_half = screen_size / 2.0;
169184
let position = state.player.rect.position - screen_half
170185
+ Into::<Vec2>::into(input.mouse_position());
171186

@@ -278,13 +293,14 @@ fn main() {
278293
);
279294
}
280295

281-
Window::new("Debug").show(ui, |ui| {
296+
Window::new("Debug").show(egui_ctx, |ui| {
282297
ui.label(format!("FPS: {}", timer.fps));
283298
ui.label(format!("Wave: {}", state.wave));
284299
ui.label(format!("Zombies killed: {}", state.kills));
285300
ui.label(format!("HP: {:.0}", state.player.hp));
286301
ui.label(format!("Fire rate: {:.1}/s", state.fire_rate));
287302
ui.label(format!("Bullet Spread: {}", state.spread));
288303
});
289-
});
304+
},
305+
);
290306
}

src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
//!
2424
//! ## Minimal Example: Draw a Rectangle
2525
//! ```no_run
26-
//! use egor::{app::App, render::Graphics};
27-
//! App::new().run(|gfx: &mut Graphics, _input, _timer| {
26+
//! use egor::{app::{App, FrameContext}, render::Graphics};
27+
//! App::new().run(|FrameContext { gfx, .. }| {
2828
//! // start building a rectangle with some defaults
2929
//! // draws automatically on `Drop` without an explicit `build()`
3030
//! gfx.rect();
@@ -53,7 +53,8 @@
5353
//! - Optional backends can be enabled to override defaults or for cross-platform targeting
5454
5555
pub mod app {
56-
pub use egor_glue::app::App;
56+
pub use egor_app::WindowEvent;
57+
pub use egor_glue::app::{App, FrameContext};
5758
#[cfg(feature = "ui")]
5859
pub use egor_glue::ui::egui;
5960
}

0 commit comments

Comments
 (0)