1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Core of the GUI framework
use crate::widget::Widget;
use image::{Rgba, RgbaImage};
use pixels::{Pixels, SurfaceTexture};
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, MouseButton, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
};
/// Contains data and context about the App,
/// like the contained widgets and the ID of a widget clicked during a frame.
pub struct AppContext {
/// Vector containing the widgets of the App.
pub widgets: Vec<Box<dyn Widget>>,
/// The ID string of whichever widget was clicked during a frame.
pub clicked_id: Option<String>,
}
impl AppContext {
/// Returns `true` if the widget with the given ID was clicked.
/// Useful for handling events and triggering logic based on user interaction.
///
/// # Example
/// ```no_run
/// use cpux::app::AppContext;
/// fn main() {
/// // ...
/// app.run(move |ui: &mut AppContext| {
/// if ui.was_clicked("add_button") {
/// counter += 1;
/// // ...
/// }
/// });
/// }
/// ```
pub fn was_clicked(&self, id: &str) -> bool {
self.clicked_id.as_deref() == Some(id)
}
/// Recursively scans through layouts and widgets to find a matching ID.
///
/// # Example
/// ```no_run
/// use cpux::app::AppContext;
/// fn main() {
/// // ...
/// app.run(move |ui: &mut AppContext| {
/// if ui.was_clicked("add_button") {
/// counter += 1;
/// if let Some(text_widget) = ui.find_widget_mut::<Text>("counter_text") {
/// text_widget.content = format!("Count: {}", counter);
/// }
/// }
/// });
/// }
/// ```
pub fn find_widget_mut<'a, T: 'static>(&'a mut self, id: &str) -> Option<&'a mut T> {
for widget in &mut self.widgets {
if widget.id() == Some(id) {
return widget.as_any_mut().downcast_mut::<T>();
}
if let Some(found_widget) = widget.find_child_mut(id) {
return found_widget.as_any_mut().downcast_mut::<T>();
}
}
None
}
}
/// The main application struct that manages the OS window, framebuffer, and widget tree.
pub struct App {
event_loop: Option<EventLoop<()>>,
window: Window,
pixels: Pixels,
framebuffer: RgbaImage,
/// The collection of top-level widgets associated with this application.
pub widgets: Vec<Box<dyn Widget>>,
}
impl App {
/// Spawns an OS Window with the given title and dimensions.
/// Example
/// ```no_run
/// use cpux::app:;App;
/// fn main() {
/// let mut app = App::new("Window", 600, 400);
/// }
/// ```
pub fn new(title: &str, width: u32, height: u32) -> Self {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title(title)
.with_inner_size(LogicalSize::new(width, height))
.build(&event_loop)
.unwrap();
let window_size = window.inner_size();
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
let pixels = Pixels::new(width, height, surface_texture).unwrap();
let framebuffer = RgbaImage::new(width, height);
Self {
event_loop: Some(event_loop),
window,
pixels,
framebuffer,
widgets: Vec::new(),
}
}
/// Sets the top-level layout container, e.g. a Row or Column.
pub fn set_root(&mut self, widget: impl Widget + 'static) {
self.widgets = vec![Box::new(widget)];
}
/// Starts the main event loop.
pub fn run<F>(mut self, mut update_logic: F)
where
F: 'static + FnMut(&mut AppContext),
{
let mut cursor_pos = (0, 0);
let event_loop = self.event_loop.take().unwrap();
let window = self.window;
let mut pixels = self.pixels;
let mut framebuffer = self.framebuffer;
let mut context = AppContext {
widgets: self.widgets,
clicked_id: None,
};
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
context.clicked_id = None;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(size) => {
let _ = pixels.resize_surface(size.width, size.height);
let _ = pixels.resize_buffer(size.width, size.height);
framebuffer = RgbaImage::new(size.width, size.height);
window.request_redraw();
}
WindowEvent::CursorMoved { position, .. } => {
if let Ok(mapped_pos) =
pixels.window_pos_to_pixel((position.x as f32, position.y as f32))
{
cursor_pos = (mapped_pos.0 as i32, mapped_pos.1 as i32);
}
}
WindowEvent::MouseInput { state, button, .. } => {
if state == ElementState::Pressed && button == MouseButton::Left {
for widget in &mut context.widgets {
if let Some(id) = widget.handle_click(cursor_pos.0, cursor_pos.1) {
context.clicked_id = Some(id);
break;
}
}
update_logic(&mut context);
window.request_redraw();
}
}
_ => {}
},
Event::RedrawRequested(_) => {
framebuffer
.pixels_mut()
.for_each(|p| *p = Rgba([255, 255, 255, 255]));
for widget in &mut context.widgets {
widget.draw(&mut framebuffer);
}
pixels
.frame_mut()
.copy_from_slice(&framebuffer.clone().into_raw());
pixels.render().unwrap();
}
_ => {}
}
});
}
}