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
190
191
192
193
194
195
196
197
198
//! This example behaves the same as the `all_winit_glium` example while demonstrating how to run
//! the `conrod` loop on a separate thread.
extern crate conrod_core;
extern crate conrod_example_shared;
extern crate conrod_glium;
extern crate conrod_winit;
extern crate find_folder;
extern crate glium;
extern crate image;
mod support;
use conrod_example_shared::{WIN_W, WIN_H};
use conrod_glium::Renderer;
use glium::Surface;
fn main() {
// Build the window.
let mut events_loop = glium::glutin::EventsLoop::new();
let window = glium::glutin::WindowBuilder::new()
.with_title("Conrod with glium!")
.with_dimensions((WIN_W, WIN_H).into());
let context = glium::glutin::ContextBuilder::new()
.with_vsync(true)
.with_multisampling(4);
let display = glium::Display::new(window, context, &events_loop).unwrap();
let display = support::GliumDisplayWinitWrapper(display);
// A type used for converting `conrod_core::render::Primitives` into `Command`s that can be used
// for drawing to the glium `Surface`.
//
// Internally, the `Renderer` maintains:
// - a `backend::glium::GlyphCache` for caching text onto a `glium::texture::Texture2d`.
// - a `glium::Program` to use as the shader program when drawing to the `glium::Surface`.
// - a `Vec` for collecting `backend::glium::Vertex`s generated when translating the
// `conrod_core::render::Primitive`s.
// - a `Vec` of commands that describe how to draw the vertices.
let mut renderer = Renderer::new(&display.0).unwrap();
// Load the Rust logo from our assets folder to use as an example image.
fn load_rust_logo(display: &glium::Display) -> glium::texture::Texture2d {
let assets = find_folder::Search::ParentsThenKids(5, 3).for_folder("assets").unwrap();
let path = assets.join("images/rust.png");
let rgba_image = image::open(&std::path::Path::new(&path)).unwrap().to_rgba();
let image_dimensions = rgba_image.dimensions();
let raw_image = glium::texture::RawImage2d::from_raw_rgba_reversed(&rgba_image.into_raw(), image_dimensions);
let texture = glium::texture::Texture2d::new(display, raw_image).unwrap();
texture
}
let mut image_map = conrod_core::image::Map::new();
let rust_logo = image_map.insert(load_rust_logo(&display.0));
// A channel to send events from the main `winit` thread to the conrod thread.
let (event_tx, event_rx) = std::sync::mpsc::channel();
// A channel to send `render::Primitive`s from the conrod thread to the `winit thread.
let (render_tx, render_rx) = std::sync::mpsc::channel();
// Clone the handle to the events loop so that we can interrupt it when ready to draw.
let events_loop_proxy = events_loop.create_proxy();
// A function that runs the conrod loop.
fn run_conrod(rust_logo: conrod_core::image::Id,
event_rx: std::sync::mpsc::Receiver<conrod_core::event::Input>,
render_tx: std::sync::mpsc::Sender<conrod_core::render::OwnedPrimitives>,
events_loop_proxy: glium::glutin::EventsLoopProxy)
{
// Construct our `Ui`.
let mut ui = conrod_core::UiBuilder::new([WIN_W as f64, WIN_H as f64])
.theme(conrod_example_shared::theme())
.build();
// Add a `Font` to the `Ui`'s `font::Map` from file.
let assets = find_folder::Search::KidsThenParents(3, 5).for_folder("assets").unwrap();
let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf");
ui.fonts.insert_from_file(font_path).unwrap();
// A demonstration of some app state that we want to control with the conrod GUI.
let mut app = conrod_example_shared::DemoApp::new(rust_logo);
// The `widget::Id` of each widget instantiated in `conrod_example_shared::gui`.
let ids = conrod_example_shared::Ids::new(ui.widget_id_generator());
// Many widgets require another frame to finish drawing after clicks or hovers, so we
// insert an update into the conrod loop using this `bool` after each event.
let mut needs_update = true;
'conrod: loop {
// Collect any pending events.
let mut events = Vec::new();
while let Ok(event) = event_rx.try_recv() {
events.push(event);
}
// If there are no events pending, wait for them.
if events.is_empty() || !needs_update {
match event_rx.recv() {
Ok(event) => events.push(event),
Err(_) => break 'conrod,
};
}
needs_update = false;
// Input each event into the `Ui`.
for event in events {
ui.handle_event(event);
needs_update = true;
}
// Instantiate a GUI demonstrating every widget type provided by conrod.
conrod_example_shared::gui(&mut ui.set_widgets(), &ids, &mut app);
// Render the `Ui` to a list of primitives that we can send to the main thread for
// display. Wakeup `winit` for rendering.
if let Some(primitives) = ui.draw_if_changed() {
if render_tx.send(primitives.owned()).is_err()
|| events_loop_proxy.wakeup().is_err() {
break 'conrod;
}
}
}
}
// Draws the given `primitives` to the given `Display`.
fn draw(display: &glium::Display,
renderer: &mut Renderer,
image_map: &conrod_core::image::Map<glium::Texture2d>,
primitives: &conrod_core::render::OwnedPrimitives)
{
renderer.fill(display, primitives.walk(), &image_map);
let mut target = display.draw();
target.clear_color(0.0, 0.0, 0.0, 1.0);
renderer.draw(display, &mut target, &image_map).unwrap();
target.finish().unwrap();
}
// Spawn the conrod loop on its own thread.
std::thread::spawn(move || run_conrod(rust_logo, event_rx, render_tx, events_loop_proxy));
// Run the `winit` loop.
let mut last_update = std::time::Instant::now();
let mut closed = false;
while !closed {
// We don't want to loop any faster than 60 FPS, so wait until it has been at least
// 16ms since the last yield.
let sixteen_ms = std::time::Duration::from_millis(16);
let now = std::time::Instant::now();
let duration_since_last_update = now.duration_since(last_update);
if duration_since_last_update < sixteen_ms {
std::thread::sleep(sixteen_ms - duration_since_last_update);
}
events_loop.run_forever(|event| {
// Use the `winit` backend feature to convert the winit event to a conrod one.
if let Some(event) = conrod_winit::convert_event(event.clone(), &display) {
event_tx.send(event).unwrap();
}
match event {
glium::glutin::Event::WindowEvent { event, .. } => match event {
// Break from the loop upon `Escape`.
glium::glutin::WindowEvent::CloseRequested |
glium::glutin::WindowEvent::KeyboardInput {
input: glium::glutin::KeyboardInput {
virtual_keycode: Some(glium::glutin::VirtualKeyCode::Escape),
..
},
..
} => {
closed = true;
return glium::glutin::ControlFlow::Break;
},
// We must re-draw on `Resized`, as the event loops become blocked during
// resize on macOS.
glium::glutin::WindowEvent::Resized(..) => {
if let Some(primitives) = render_rx.iter().next() {
draw(&display.0, &mut renderer, &image_map, &primitives);
}
},
_ => {},
},
glium::glutin::Event::Awakened => return glium::glutin::ControlFlow::Break,
_ => (),
}
glium::glutin::ControlFlow::Continue
});
// Draw the most recently received `conrod_core::render::Primitives` sent from the `Ui`.
if let Some(primitives) = render_rx.try_iter().last() {
draw(&display.0, &mut renderer, &image_map, &primitives);
}
last_update = std::time::Instant::now();
}
}