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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
//! A demonstration of designing a custom, third-party widget.
//!
//! In this case, we'll design a simple circular button.
//!
//! All of the custom widget design will occur within the `circular_button` module.
//!
//! We'll *use* our fancy circular button in the `main` function (below the circular_button module).
//!
//! Note that in this case, we use `backend::src` to draw our widget, however in practise you may
//! use any backend you wish.
//!
//! For more information, please see the `Widget` trait documentation.
#[macro_use]
extern crate conrod_core;
extern crate conrod_glium;
#[macro_use]
extern crate conrod_winit;
extern crate find_folder;
extern crate glium;
mod support;
/// The module in which we'll implement our own custom circular button.
mod circular_button {
use conrod_core::{
self, widget, widget_ids, Colorable, Labelable, Point, Positionable, Widget,
};
/// The type upon which we'll implement the `Widget` trait.
#[derive(WidgetCommon)]
pub struct CircularButton<'a> {
/// An object that handles some of the dirty work of rendering a GUI. We don't
/// really have to worry about it.
#[conrod(common_builder)]
common: widget::CommonBuilder,
/// Optional label string for the button.
maybe_label: Option<&'a str>,
/// See the Style struct below.
style: Style,
/// Whether the button is currently enabled, i.e. whether it responds to
/// user input.
enabled: bool,
}
// We use `#[derive(WidgetStyle)] to vastly simplify the definition and implementation of the
// widget's associated `Style` type. This generates an implementation that automatically
// retrieves defaults from the provided theme in the following order:
//
// 1. If the field is `None`, falls back to the style stored within the `Theme`.
// 2. If there are no style defaults for the widget in the `Theme`, or if the
// default field is also `None`, falls back to the expression specified within
// the field's `#[conrod(default = "expr")]` attribute.
/// Represents the unique styling for our CircularButton widget.
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle)]
pub struct Style {
/// Color of the button.
#[conrod(default = "theme.shape_color")]
pub color: Option<conrod_core::Color>,
/// Color of the button's label.
#[conrod(default = "theme.label_color")]
pub label_color: Option<conrod_core::Color>,
/// Font size of the button's label.
#[conrod(default = "theme.font_size_medium")]
pub label_font_size: Option<conrod_core::FontSize>,
/// Specify a unique font for the label.
#[conrod(default = "theme.font_id")]
pub label_font_id: Option<Option<conrod_core::text::font::Id>>,
}
// We'll create the widget using a `Circle` widget and a `Text` widget for its label.
//
// Here is where we generate the type that will produce these identifiers.
widget_ids! {
struct Ids {
circle,
text,
}
}
/// Represents the unique, cached state for our CircularButton widget.
pub struct State {
ids: Ids,
}
impl<'a> CircularButton<'a> {
/// Create a button context to be built upon.
pub fn new() -> Self {
CircularButton {
common: widget::CommonBuilder::default(),
style: Style::default(),
maybe_label: None,
enabled: true,
}
}
/// Specify the font used for displaying the label.
pub fn label_font_id(mut self, font_id: conrod_core::text::font::Id) -> Self {
self.style.label_font_id = Some(Some(font_id));
self
}
/// If true, will allow user inputs. If false, will disallow user inputs. Like
/// other Conrod configs, this returns self for chainability. Allow dead code
/// because we never call this in the example.
#[allow(dead_code)]
pub fn enabled(mut self, flag: bool) -> Self {
self.enabled = flag;
self
}
}
/// A custom Conrod widget must implement the Widget trait. See the **Widget** trait
/// documentation for more details.
impl<'a> Widget for CircularButton<'a> {
/// The State struct that we defined above.
type State = State;
/// The Style struct that we defined using the `widget_style!` macro.
type Style = Style;
/// The event produced by instantiating the widget.
///
/// `Some` when clicked, otherwise `None`.
type Event = Option<()>;
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
/// Optionally specify a function to use for determining whether or not a point is over a
/// widget, or if some other widget's function should be used to represent this widget.
///
/// This method is optional to implement. By default, the bounding rectangle of the widget
/// is used.
fn is_over(&self) -> widget::IsOverFn {
use conrod_core::graph::Container;
use conrod_core::Theme;
fn is_over_widget(widget: &Container, _: Point, _: &Theme) -> widget::IsOver {
let unique = widget.state_and_style::<State, Style>().unwrap();
unique.state.ids.circle.into()
}
is_over_widget
}
/// Update the state of the button by handling any input that has occurred since the last
/// update.
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs {
id,
state,
rect,
ui,
style,
..
} = args;
let (color, event) = {
let input = ui.widget_input(id);
// If the button was clicked, produce `Some` event.
let event = input.clicks().left().next().map(|_| ());
let color = style.color(&ui.theme);
let color = input.mouse().map_or(color, |mouse| {
if mouse.buttons.left().is_down() {
color.clicked()
} else {
color.highlighted()
}
});
(color, event)
};
// Finally, we'll describe how we want our widget drawn by simply instantiating the
// necessary primitive graphics widgets.
//
// Conrod will automatically determine whether or not any changes have occurred and
// whether or not any widgets need to be re-drawn.
//
// The primitive graphics widgets are special in that their unique state is used within
// conrod's backend to do the actual drawing. This allows us to build up more complex
// widgets by using these simple primitives with our familiar layout, coloring, etc
// methods.
//
// If you notice that conrod is missing some sort of primitive graphics that you
// require, please file an issue or open a PR so we can add it! :)
// First, we'll draw the **Circle** with a radius that is half our given width.
let radius = rect.w() / 2.0;
widget::Circle::fill(radius)
.middle_of(id)
.graphics_for(id)
.color(color)
.set(state.ids.circle, ui);
// Now we'll instantiate our label using the **Text** widget.
if let Some(ref label) = self.maybe_label {
let label_color = style.label_color(&ui.theme);
let font_size = style.label_font_size(&ui.theme);
let font_id = style.label_font_id(&ui.theme).or(ui.fonts.ids().next());
widget::Text::new(label)
.and_then(font_id, widget::Text::font_id)
.middle_of(id)
.font_size(font_size)
.graphics_for(id)
.color(label_color)
.set(state.ids.text, ui);
}
event
}
}
/// Provide the chainable color() configuration method.
impl<'a> Colorable for CircularButton<'a> {
fn color(mut self, color: conrod_core::Color) -> Self {
self.style.color = Some(color);
self
}
}
/// Provide the chainable label(), label_color(), and label_font_size()
/// configuration methods.
impl<'a> Labelable<'a> for CircularButton<'a> {
fn label(mut self, text: &'a str) -> Self {
self.maybe_label = Some(text);
self
}
fn label_color(mut self, color: conrod_core::Color) -> Self {
self.style.label_color = Some(color);
self
}
fn label_font_size(mut self, size: conrod_core::FontSize) -> Self {
self.style.label_font_size = Some(size);
self
}
}
}
fn main() {
use self::circular_button::CircularButton;
use conrod_core::{widget, Colorable, Labelable, Positionable, Sizeable, Widget};
use glium::Surface;
const WIDTH: u32 = 1200;
const HEIGHT: u32 = 800;
// Build the window.
let event_loop = glium::glutin::event_loop::EventLoop::new();
let window = glium::glutin::window::WindowBuilder::new()
.with_title("Control Panel")
.with_inner_size(glium::glutin::dpi::LogicalSize::new(WIDTH, HEIGHT));
let context = glium::glutin::ContextBuilder::new()
.with_vsync(true)
.with_multisampling(4);
let display = glium::Display::new(window, context, &event_loop).unwrap();
// construct our `Ui`.
let mut ui = conrod_core::UiBuilder::new([WIDTH as f64, HEIGHT as f64]).build();
// The `widget_ids` macro is a easy, safe way of generating a type for producing `widget::Id`s.
widget_ids! {
struct Ids {
// An ID for the background widget, upon which we'll place our custom button.
background,
// The WidgetId we'll use to plug our widget into the `Ui`.
circle_button,
}
}
let ids = Ids::new(ui.widget_id_generator());
// 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");
let regular = ui.fonts.insert_from_file(font_path).unwrap();
// A type used for converting `conrod_core::render::Primitives` into `Command`s that can be used
// for drawing to the glium `Surface`.
let mut renderer = conrod_glium::Renderer::new(&display).unwrap();
// The image map describing each of our widget->image mappings (in our case, none).
let image_map = conrod_core::image::Map::<glium::texture::Texture2d>::new();
// Poll events from the window.
support::run_loop(display, event_loop, move |request, display| {
match request {
support::Request::Event {
event,
should_update_ui,
should_exit,
} => {
// Use the `winit` backend feature to convert the winit event to a conrod one.
if let Some(event) = support::convert_event(&event, &display.gl_window().window()) {
ui.handle_event(event);
*should_update_ui = true;
}
match event {
glium::glutin::event::Event::WindowEvent { event, .. } => match event {
// Break from the loop upon `Escape`.
glium::glutin::event::WindowEvent::CloseRequested
| glium::glutin::event::WindowEvent::KeyboardInput {
input:
glium::glutin::event::KeyboardInput {
virtual_keycode:
Some(glium::glutin::event::VirtualKeyCode::Escape),
..
},
..
} => *should_exit = true,
_ => {}
},
_ => {}
}
}
support::Request::SetUi { needs_redraw } => {
// Instantiate the widgets.
let ui = &mut ui.set_widgets();
// Sets a color to clear the background with before the Ui draws our widget.
widget::Canvas::new()
.color(conrod_core::color::DARK_RED)
.set(ids.background, ui);
// Instantiate of our custom widget.
for _click in CircularButton::new()
.color(conrod_core::color::rgb(0.0, 0.3, 0.1))
.middle_of(ids.background)
.w_h(256.0, 256.0)
.label_font_id(regular)
.label_color(conrod_core::color::WHITE)
.label("Circular Button")
// Add the widget to the conrod_core::Ui. This schedules the widget it to be
// drawn when we call Ui::draw.
.set(ids.circle_button, ui)
{
println!("Click!");
}
*needs_redraw = ui.has_changed();
}
support::Request::Redraw => {
// Render the `Ui` and then display it on the screen.
let primitives = ui.draw();
renderer.fill(display, primitives, &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();
}
}
})
}