diff --git a/examples/all_widgets.rs b/examples/all_widgets.rs index ff056bfa0..f06875f6c 100644 --- a/examples/all_widgets.rs +++ b/examples/all_widgets.rs @@ -1,115 +1,34 @@ -//! -//! A demonstration of all non-primitive widgets available in Conrod. -//! -//! -//! Don't be put off by the number of method calls, they are only for demonstration and almost all -//! of them are optional. Conrod supports `Theme`s, so if you don't give it an argument, it will -//! check the current `Theme` within the `Ui` and retrieve defaults from there. -//! +//! An example demonstrating all widgets in a long, vertically scrollable window. #[macro_use] extern crate conrod; extern crate find_folder; extern crate piston_window; -extern crate rand; // for making a random color. -use piston_window::{EventLoop, PistonWindow, UpdateEvent, WindowSettings}; +use piston_window::{EventLoop, OpenGL, PistonWindow, UpdateEvent, WindowSettings}; - -/// This struct holds all of the variables used to demonstrate application data being passed -/// through the widgets. If some of these seem strange, that's because they are! Most of these -/// simply represent the aesthetic state of different parts of the GUI to offer visual feedback -/// during interaction with the widgets. -struct DemoApp { - /// Background color (for demonstration of button and sliders). - bg_color: conrod::Color, - /// Should the button be shown (for demonstration of button). - show_button: bool, - /// The label that will be drawn to the Toggle. - toggle_label: String, - /// The number of pixels between the left side of the window - /// and the title. - title_pad: f64, - /// The height of the vertical sliders (we will play with this - /// using a number_dialer). - v_slider_height: f64, - /// The widget border width (we'll use this to demo Bordering - /// and number_dialer). - border_width: f64, - /// Bool matrix for widget_matrix demonstration. - bool_matrix: [[bool; 8]; 8], - /// A vector of strings for drop_down_list demonstration. - ddl_colors: Vec, - /// The currently selected DropDownList color. - ddl_color: conrod::Color, - /// We also need an Option to indicate whether or not an - /// item is selected. - selected_idx: Option, - /// Co-ordinates for a little circle used to demonstrate the - /// xy_pad. - circle_pos: conrod::Point, - /// Envelope for demonstration of EnvelopeEditor. - envelopes: Vec<(Vec, String)>, -} - -impl DemoApp { - - /// Constructor for the Demonstration Application model. - fn new() -> DemoApp { - DemoApp { - bg_color: conrod::color::rgb(0.2, 0.35, 0.45), - show_button: false, - toggle_label: "OFF".to_string(), - title_pad: 350.0, - v_slider_height: 230.0, - border_width: 1.0, - bool_matrix: [ [true, true, true, true, true, true, true, true], - [true, false, false, false, false, false, false, true], - [true, false, true, false, true, true, true, true], - [true, false, true, false, true, true, true, true], - [true, false, false, false, true, true, true, true], - [true, true, true, true, true, true, true, true], - [true, true, false, true, false, false, false, true], - [true, true, true, true, true, true, true, true] ], - ddl_colors: vec!["Black".to_string(), - "White".to_string(), - "Red".to_string(), - "Green".to_string(), - "Blue".to_string()], - ddl_color: conrod::color::PURPLE, - selected_idx: None, - circle_pos: [-50.0, 110.0], - envelopes: vec![(vec![ [0.0, 0.0], - [0.1, 17000.0], - [0.25, 8000.0], - [0.5, 2000.0], - [1.0, 0.0], ], "Envelope A".to_string()), - (vec![ [0.0, 0.85], - [0.3, 0.2], - [0.6, 0.6], - [1.0, 0.0], ], "Envelope B".to_string())], - } - } - -} +mod support; fn main() { - const WIDTH: u32 = 1100; - const HEIGHT: u32 = 560; + const WIDTH: u32 = support::WIN_W; + const HEIGHT: u32 = support::WIN_H; - // Change this to OpenGL::V2_1 if not working. - let opengl = piston_window::OpenGL::V3_2; - // Construct the window. let mut window: PistonWindow = - WindowSettings::new("All The Widgets!", [WIDTH, HEIGHT]) - .opengl(opengl).exit_on_esc(true).vsync(true).build().unwrap(); + WindowSettings::new("Canvas Demo", [WIDTH, HEIGHT]) + .opengl(OpenGL::V3_2) // If not working, try `OpenGL::V2_1`. + .samples(4) + .exit_on_esc(true) + .vsync(true) + .build() + .unwrap(); + window.set_ups(60); - // construct our `Ui`. - let mut ui = conrod::UiBuilder::new().build(); + // A demonstration of some state that we'd like to control with the App. + let mut app = support::DemoApp::new(); - // Identifiers used for instantiating our widgets. - let mut ids = Ids::new(ui.widget_id_generator()); + // construct our `Ui`. + let mut ui = conrod::UiBuilder::new().theme(support::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(); @@ -120,13 +39,14 @@ fn main() { let mut text_texture_cache = conrod::backend::piston_window::GlyphCache::new(&mut window, WIDTH, HEIGHT); - // The image map describing each of our widget->image mappings (in our case, none). - let image_map = conrod::image::Map::new(); - - // Our dmonstration app that we'll control with our GUI. - let mut app = DemoApp::new(); + // Instantiate the generated list of widget identifiers. + let ids = support::Ids::new(ui.widget_id_generator()); - window.set_ups(60); + // Create our `conrod::image::Map` which describes each of our widget->image mappings. + // In our case we only have one image, however the macro may be used to list multiple. + let image_map = image_map! { + (ids.rust_logo, load_rust_logo(&mut window)), + }; // Poll events from the window. while let Some(event) = window.next() { @@ -136,20 +56,11 @@ fn main() { ui.handle_event(e); } - // We'll set all our widgets in a single function called `set_widgets`. event.update(|_| { let mut ui = ui.set_widgets(); - set_widgets(&mut ui, &mut app, &mut ids); + support::gui(&mut ui, &ids, &mut app); }); - // Draw our Ui! - // - // The `draw_if_changed` method only re-draws the GUI if some `Widget`'s `Element` - // representation has changed. Normally, a `Widget`'s `Element` should only change - // if a Widget was interacted with in some way, however this is up to the `Widget` - // designer's discretion. - // - // If instead you need to re-draw your conrod GUI every frame, use `Ui::draw`. window.draw_2d(&event, |c, g| { if let Some(primitives) = ui.draw_if_changed() { fn texture_from_image(img: &T) -> &T { img }; @@ -162,295 +73,11 @@ fn main() { } } - -// In conrod, each widget must have its own unique identifier so that the `Ui` can keep track of -// its state between updates. -// -// To make this easier, conrod provides the `widget_ids` macro. This macro generates a new type -// with a unique `widget::Id` field for each identifier given in the list. See the `widget_ids!` -// documentation for more details. -widget_ids! { - struct Ids { - canvas, - canvas_x_scrollbar, - canvas_y_scrollbar, - title, - button, - title_pad_slider, - toggle, - red_slider, - green_slider, - blue_slider, - slider_height, - border_width, - toggle_matrix, - color_select, - circle_position, - circle, - text_box_a, - text_box_b, - envelope_editor_a, - envelope_editor_b, - } -} - - -/// Set all `Widget`s within the User Interface. -/// -/// The first time this gets called, each `Widget`'s `State` will be initialised and cached within -/// the `Ui` at their given indices. Every other time this get called, the `Widget`s will avoid any -/// allocations by updating the pre-existing cached state. A new graphical `Element` is only -/// retrieved from a `Widget` in the case that it's `State` has changed in some way. -fn set_widgets(ui: &mut conrod::UiCell, app: &mut DemoApp, ids: &mut Ids) { - use conrod::{color, widget, Colorable, Borderable, Labelable, Positionable, Sizeable, Widget}; - - // We can use this `Canvas` as a parent Widget upon which we can place other widgets. - widget::Canvas::new() - .border(app.border_width) - .pad(30.0) - .color(app.bg_color) - .scroll_kids() - .set(ids.canvas, ui); - widget::Scrollbar::x_axis(ids.canvas).auto_hide(true).set(ids.canvas_y_scrollbar, ui); - widget::Scrollbar::y_axis(ids.canvas).auto_hide(true).set(ids.canvas_x_scrollbar, ui); - - // Text example. - widget::Text::new("Widget Demonstration") - .top_left_with_margins_on(ids.canvas, 0.0, app.title_pad) - .font_size(32) - .color(app.bg_color.plain_contrast()) - .set(ids.title, ui); - - if app.show_button { - - // Button widget example button. - if widget::Button::new() - .w_h(200.0, 50.0) - .mid_left_of(ids.canvas) - .down_from(ids.title, 45.0) - .rgb(0.4, 0.75, 0.6) - .border(app.border_width) - .label("PRESS") - .set(ids.button, ui) - .was_clicked() - { - app.bg_color = color::rgb(rand::random(), rand::random(), rand::random()) - } - - } - - // Horizontal slider example. - else { - - // Create the label for the slider. - let label = format!("Padding: {}", app.title_pad as i16); - - // Slider widget example slider(value, min, max). - if let Some(new_pad) = widget::Slider::new(app.title_pad, 0.0, 670.0) - .w_h(200.0, 50.0) - .mid_left_of(ids.canvas) - .down_from(ids.title, 45.0) - .rgb(0.5, 0.3, 0.6) - .border(app.border_width) - .label(&label) - .label_color(color::WHITE) - .set(ids.title_pad_slider, ui) - { - app.title_pad = new_pad; - } - - } - - // Keep track of the currently shown widget. - let shown_widget = if app.show_button { ids.button } else { ids.title_pad_slider }; - - // Toggle widget example. - if let Some(value) = widget::Toggle::new(app.show_button) - .w_h(75.0, 75.0) - .down(20.0) - .rgb(0.6, 0.25, 0.75) - .border(app.border_width) - .label(&app.toggle_label) - .label_color(color::WHITE) - .set(ids.toggle, ui) - .last() - { - app.show_button = value; - app.toggle_label = match value { - true => "ON".to_string(), - false => "OFF".to_string() - } - } - - macro_rules! color_slider { - ($slider_id:ident, $bg_color:ident, $color:expr, $set_color:ident, $position:ident) => {{ - let value = app.bg_color.$bg_color(); - let label = format!("{:.*}", 2, value); - for color in widget::Slider::new(value, 0.0, 1.0) - .$position(25.0) - .w_h(40.0, app.v_slider_height) - .color($color) - .border(app.border_width) - .label(&label) - .label_color(color::WHITE) - .set(ids.$slider_id, ui) - { - app.bg_color.$set_color(color); - } - }}; - } - - color_slider!(red_slider, red, color::rgb(0.75, 0.3, 0.3), set_red, down); - color_slider!(green_slider, green, color::rgb(0.3, 0.75, 0.3), set_green, right); - color_slider!(blue_slider, blue, color::rgb(0.3, 0.3, 0.75), set_blue, right); - - // Number Dialer widget example. (value, min, max, precision) - for new_height in widget::NumberDialer::new(app.v_slider_height, 25.0, 250.0, 1) - .w_h(260.0, 60.0) - .right_from(shown_widget, 30.0) - .color(app.bg_color.invert()) - .border(app.border_width) - .label("Height (px)") - .label_color(app.bg_color.invert().plain_contrast()) - .set(ids.slider_height, ui) - { - app.v_slider_height = new_height; - } - - // Number Dialer widget example. (value, min, max, precision) - for new_width in widget::NumberDialer::new(app.border_width, 0.0, 15.0, 2) - .w_h(260.0, 60.0) - .down(20.0) - .color(app.bg_color.plain_contrast().invert()) - .border(app.border_width) - .border_color(app.bg_color.plain_contrast()) - .label("Border Width (px)") - .label_color(app.bg_color.plain_contrast()) - .set(ids.border_width, ui) - { - app.border_width = new_width; - } - - // A demonstration using widget_matrix to easily draw a matrix of any kind of widget. - let (cols, rows) = (8, 8); - let mut elements = widget::Matrix::new(cols, rows) - .down(20.0) - .w_h(260.0, 260.0) - .set(ids.toggle_matrix, ui); - - // The `Matrix` widget returns an `Elements`, which can be used similar to an `Iterator`. - while let Some(elem) = elements.next(ui) { - let (col, row) = (elem.col, elem.row); - - // Color effect for fun. - let (r, g, b, a) = ( - 0.5 + (elem.col as f32 / cols as f32) / 2.0, - 0.75, - 1.0 - (elem.row as f32 / rows as f32) / 2.0, - 1.0 - ); - - // We can use `Element`s to instantiate any kind of widget we like. - // The `Element` does all of the positioning and sizing work for us. - // Here, we use the `Element` to `set` a `Toggle` widget for us. - let toggle = widget::Toggle::new(app.bool_matrix[col][row]) - .rgba(r, g, b, a) - .border(app.border_width); - if let Some(new_value) = elem.set(toggle, ui).last() { - app.bool_matrix[col][row] = new_value; - } - } - - // A demonstration using a DropDownList to select its own color. - for selected_idx in widget::DropDownList::new(&app.ddl_colors, app.selected_idx) - .w_h(150.0, 40.0) - .right_from(ids.slider_height, 30.0) // Position right from widget 6 by 50 pixels. - .max_visible_items(3) - .color(app.ddl_color) - .border(app.border_width) - .border_color(app.ddl_color.plain_contrast()) - .label("Colors") - .label_color(app.ddl_color.plain_contrast()) - .scrollbar_on_top() - .set(ids.color_select, ui) - { - app.selected_idx = Some(selected_idx); - app.ddl_color = match &app.ddl_colors[selected_idx][..] { - "Black" => color::BLACK, - "White" => color::WHITE, - "Red" => color::RED, - "Green" => color::GREEN, - "Blue" => color::BLUE, - _ => color::PURPLE, - } - } - - // Draw an xy_pad. - for (x, y) in widget::XYPad::new(app.circle_pos[0], -75.0, 75.0, // x range. - app.circle_pos[1], 95.0, 245.0) // y range. - .w_h(150.0, 150.0) - .right_from(ids.toggle_matrix, 30.0) - .align_bottom_of(ids.toggle_matrix) // Align to the bottom of the last toggle_matrix element. - .color(app.ddl_color) - .border(app.border_width) - .border_color(color::WHITE) - .label("Circle Position") - .label_color(app.ddl_color.plain_contrast().alpha(0.5)) - .line_thickness(2.0) - .set(ids.circle_position, ui) - { - app.circle_pos[0] = x; - app.circle_pos[1] = y; - } - - // Draw a circle at the app's circle_pos. - widget::Circle::fill(15.0) - .xy_relative_to(ids.circle_position, app.circle_pos) - .color(app.ddl_color) - .set(ids.circle, ui); - - // Draw two TextBox and EnvelopeEditor pairs to the right of the DropDownList flowing downward. - for i in 0..2 { - let &mut (ref mut env, ref mut text) = &mut app.envelopes[i]; - let (text_box, env_editor, env_y_max, env_skew_y) = match i { - 0 => (ids.text_box_a, ids.envelope_editor_a, 20_000.0, 3.0), - 1 => (ids.text_box_b, ids.envelope_editor_b, 1.0, 1.0), - _ => unreachable!(), - }; - - // A text box in which we can mutate a single line of text, and trigger reactions via the - // `Enter`/`Return` key. - for event in widget::TextBox::new(text) - .and_if(i == 0, |text| text.right_from(ids.color_select, 30.0)) - .font_size(20) - .w_h(320.0, 40.0) - .border(app.border_width) - .border_color(app.bg_color.invert().plain_contrast()) - .color(app.bg_color.invert()) - .set(text_box, ui) - { - match event { - widget::text_box::Event::Enter => println!("TextBox {}: {:?}", i, text), - widget::text_box::Event::Update(string) => *text = string, - } - } - - // Draw an EnvelopeEditor. (&[Point], x_min, x_max, y_min, y_max). - for event in widget::EnvelopeEditor::new(env, 0.0, 1.0, 0.0, env_y_max) - .down(10.0) - .w_h(320.0, 150.0) - .skew_y(env_skew_y) - .color(app.bg_color.invert()) - .border(app.border_width) - .border_color(app.bg_color.invert().plain_contrast()) - .label(&text) - .label_color(app.bg_color.invert().plain_contrast().alpha(0.5)) - .point_radius(6.0) - .line_thickness(2.0) - .set(env_editor, ui) - { - event.update(env); - } - } - +// Load the Rust logo from our assets folder. +fn load_rust_logo(window: &mut PistonWindow) -> piston_window::G2dTexture<'static> { + let assets = find_folder::Search::ParentsThenKids(3, 3).for_folder("assets").unwrap(); + let path = assets.join("images/rust.png"); + let factory = &mut window.factory; + let settings = piston_window::TextureSettings::new(); + piston_window::Texture::from_path(factory, &path, piston_window::Flip::None, &settings).unwrap() } diff --git a/examples/glutin_gfx.rs b/examples/glutin_gfx.rs index 7a2ce4f03..0bc771a87 100644 --- a/examples/glutin_gfx.rs +++ b/examples/glutin_gfx.rs @@ -10,6 +10,10 @@ extern crate glutin; #[macro_use] extern crate gfx; +#[cfg(feature="glutin")] +mod support; + + fn main() { feature::main(); } @@ -22,6 +26,7 @@ mod feature { use conrod; use glutin; use gfx; + use support; use gfx::{Factory, Device, tex}; use gfx::traits::FactoryExt; @@ -93,16 +98,9 @@ mod feature { } } - const WIN_W: u32 = 800; - const WIN_H: u32 = 600; - const CLEAR_COLOR: [f32; 4] = [1.0; 4]; - - widget_ids! { - struct Ids { - canvas, - text, - } - } + const WIN_W: u32 = support::WIN_W; + const WIN_H: u32 = support::WIN_H; + const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.0]; // Creates a gfx texture with the given data fn create_texture(factory: &mut F, width: u32, height: u32, data: &[u8]) @@ -137,29 +135,6 @@ mod feature { encoder.update_texture::(texture, None, info, data).unwrap(); } - fn set_widgets(ui: &mut conrod::UiCell, ids: &Ids) { - use conrod::{widget, Colorable, Positionable, Sizeable, Widget}; - - widget::Canvas::new().color(conrod::color::DARK_CHARCOAL).set(ids.canvas, ui); - - // Text to draw - let demo_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \ - Mauris aliquet porttitor tellus vel euismod. Integer lobortis volutpat bibendum. Nulla \ - finibus odio nec elit condimentum, rhoncus fermentum purus lacinia. Interdum et malesuada \ - fames ac ante ipsum primis in faucibus. Cras rhoncus nisi nec dolor bibendum pellentesque. \ - Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \ - Quisque commodo nibh hendrerit nunc sollicitudin sodales. Cras vitae tempus ipsum. Nam \ - magna est, efficitur suscipit dolor eu, consectetur consectetur urna."; - - widget::Text::new(demo_text) - .middle_of(ids.canvas) - .wh_of(ids.canvas) - .font_size(20) - .color(conrod::color::BLACK) - .align_text_middle() - .set(ids.text, ui); - } - pub fn main() { // Builder for window let builder = glutin::WindowBuilder::new() @@ -189,9 +164,12 @@ mod feature { // Compile GL program let pso = factory.create_pipeline_simple(VERTEX_SHADER, FRAGMENT_SHADER, pipe::new()).unwrap(); - // Create Ui and ID generator - let mut ui = conrod::UiBuilder::new().build(); - let ids = Ids::new(ui.widget_id_generator()); + // Demonstration app state that we'll control with our conrod GUI. + let mut app = support::DemoApp::new(); + + // Create Ui and Ids of widgets to instantiate + let mut ui = conrod::UiBuilder::new().theme(support::theme()).build(); + let ids = support::Ids::new(ui.widget_id_generator()); // Load font from file let assets = find_folder::Search::KidsThenParents(3, 5).for_folder("assets").unwrap(); @@ -339,7 +317,8 @@ mod feature { // Update widgets if any event has happened if ui.global_input.events().next().is_some() { - set_widgets(&mut ui.set_widgets(), &ids); + let mut ui = ui.set_widgets(); + support::gui(&mut ui, &ids, &mut app); } } } diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 1a2eebf54..01c2aba0d 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -3,6 +3,9 @@ #[cfg(feature="glutin")] #[cfg(feature="glium")] #[macro_use] extern crate conrod; #[cfg(feature="glutin")] #[cfg(feature="glium")] #[macro_use] extern crate glium; +#[cfg(feature="glutin")] +mod support; + fn main() { feature::main(); } @@ -13,6 +16,7 @@ mod feature { extern crate find_folder; use conrod; use glium; + use support; use std; use glium::{DisplayBuild, Surface}; @@ -21,8 +25,8 @@ mod feature { use std::borrow::Cow; // The width and height in "points". - const WIN_W: u32 = 512; - const WIN_H: u32 = 512; + const WIN_W: u32 = support::WIN_W; + const WIN_H: u32 = support::WIN_H; pub fn main() { @@ -69,9 +73,13 @@ mod feature { }).unwrap(); // Construct our `Ui`. - let mut ui = conrod::UiBuilder::new().build(); + let mut ui = conrod::UiBuilder::new().theme(support::theme()).build(); + + // A demonstration of some app state that we want to control with the conrod GUI. + let mut app = support::DemoApp::new(); - let ids = Ids::new(ui.widget_id_generator()); + // The `widget::Id` of each widget instantiated in `gui`. + let ids = support::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(); @@ -238,7 +246,7 @@ mod feature { }; let mut target = display.draw(); - target.clear_color(1.0, 1.0, 1.0, 0.0); + target.clear_color(0.0, 0.0, 0.0, 0.0); let vertex_buffer = glium::VertexBuffer::new(&display, &vertices).unwrap(); let blend = glium::Blend::alpha_blending(); let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); @@ -267,8 +275,9 @@ mod feature { } if ui.global_input.events().next().is_some() { - // Update all widgets within the `Ui`. - set_widgets(ui.set_widgets(), &ids); + // Instantiate a GUI demonstrating every widget type provided by conrod. + let mut ui = ui.set_widgets(); + support::gui(&mut ui, &ids, &mut app); } // Avoid hogging the CPU. @@ -276,40 +285,6 @@ mod feature { } } - - // Generate a type which may produce unique identifier for each widget. - widget_ids! { - struct Ids { - canvas, - text, - } - } - - - /// Instantiate the widgets. - fn set_widgets(ref mut ui: conrod::UiCell, ids: &Ids) { - use conrod::{widget, Colorable, Positionable, Sizeable, Widget}; - - widget::Canvas::new().color(conrod::color::DARK_CHARCOAL).set(ids.canvas, ui); - - // Some starting text to edit. - let demo_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \ - Mauris aliquet porttitor tellus vel euismod. Integer lobortis volutpat bibendum. Nulla \ - finibus odio nec elit condimentum, rhoncus fermentum purus lacinia. Interdum et malesuada \ - fames ac ante ipsum primis in faucibus. Cras rhoncus nisi nec dolor bibendum pellentesque. \ - Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \ - Quisque commodo nibh hendrerit nunc sollicitudin sodales. Cras vitae tempus ipsum. Nam \ - magna est, efficitur suscipit dolor eu, consectetur consectetur urna."; - - //conrod::Text::new("Foo! Bar! Baz!\nFloozy Woozy\nQux Flux") - widget::Text::new(demo_text) - .middle_of(ids.canvas) - .wh_of(ids.canvas) - .font_size(20) - .color(conrod::color::BLACK) - .align_text_middle() - .set(ids.text, ui); - } } #[cfg(not(feature="glutin"))] diff --git a/examples/old_demo.rs b/examples/old_demo.rs new file mode 100644 index 000000000..ff056bfa0 --- /dev/null +++ b/examples/old_demo.rs @@ -0,0 +1,456 @@ +//! +//! A demonstration of all non-primitive widgets available in Conrod. +//! +//! +//! Don't be put off by the number of method calls, they are only for demonstration and almost all +//! of them are optional. Conrod supports `Theme`s, so if you don't give it an argument, it will +//! check the current `Theme` within the `Ui` and retrieve defaults from there. +//! + +#[macro_use] extern crate conrod; +extern crate find_folder; +extern crate piston_window; +extern crate rand; // for making a random color. + +use piston_window::{EventLoop, PistonWindow, UpdateEvent, WindowSettings}; + + +/// This struct holds all of the variables used to demonstrate application data being passed +/// through the widgets. If some of these seem strange, that's because they are! Most of these +/// simply represent the aesthetic state of different parts of the GUI to offer visual feedback +/// during interaction with the widgets. +struct DemoApp { + /// Background color (for demonstration of button and sliders). + bg_color: conrod::Color, + /// Should the button be shown (for demonstration of button). + show_button: bool, + /// The label that will be drawn to the Toggle. + toggle_label: String, + /// The number of pixels between the left side of the window + /// and the title. + title_pad: f64, + /// The height of the vertical sliders (we will play with this + /// using a number_dialer). + v_slider_height: f64, + /// The widget border width (we'll use this to demo Bordering + /// and number_dialer). + border_width: f64, + /// Bool matrix for widget_matrix demonstration. + bool_matrix: [[bool; 8]; 8], + /// A vector of strings for drop_down_list demonstration. + ddl_colors: Vec, + /// The currently selected DropDownList color. + ddl_color: conrod::Color, + /// We also need an Option to indicate whether or not an + /// item is selected. + selected_idx: Option, + /// Co-ordinates for a little circle used to demonstrate the + /// xy_pad. + circle_pos: conrod::Point, + /// Envelope for demonstration of EnvelopeEditor. + envelopes: Vec<(Vec, String)>, +} + +impl DemoApp { + + /// Constructor for the Demonstration Application model. + fn new() -> DemoApp { + DemoApp { + bg_color: conrod::color::rgb(0.2, 0.35, 0.45), + show_button: false, + toggle_label: "OFF".to_string(), + title_pad: 350.0, + v_slider_height: 230.0, + border_width: 1.0, + bool_matrix: [ [true, true, true, true, true, true, true, true], + [true, false, false, false, false, false, false, true], + [true, false, true, false, true, true, true, true], + [true, false, true, false, true, true, true, true], + [true, false, false, false, true, true, true, true], + [true, true, true, true, true, true, true, true], + [true, true, false, true, false, false, false, true], + [true, true, true, true, true, true, true, true] ], + ddl_colors: vec!["Black".to_string(), + "White".to_string(), + "Red".to_string(), + "Green".to_string(), + "Blue".to_string()], + ddl_color: conrod::color::PURPLE, + selected_idx: None, + circle_pos: [-50.0, 110.0], + envelopes: vec![(vec![ [0.0, 0.0], + [0.1, 17000.0], + [0.25, 8000.0], + [0.5, 2000.0], + [1.0, 0.0], ], "Envelope A".to_string()), + (vec![ [0.0, 0.85], + [0.3, 0.2], + [0.6, 0.6], + [1.0, 0.0], ], "Envelope B".to_string())], + } + } + +} + + +fn main() { + const WIDTH: u32 = 1100; + const HEIGHT: u32 = 560; + + // Change this to OpenGL::V2_1 if not working. + let opengl = piston_window::OpenGL::V3_2; + + // Construct the window. + let mut window: PistonWindow = + WindowSettings::new("All The Widgets!", [WIDTH, HEIGHT]) + .opengl(opengl).exit_on_esc(true).vsync(true).build().unwrap(); + + // construct our `Ui`. + let mut ui = conrod::UiBuilder::new().build(); + + // Identifiers used for instantiating our widgets. + let mut 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"); + ui.fonts.insert_from_file(font_path).unwrap(); + + // Create a texture to use for efficiently caching text on the GPU. + let mut text_texture_cache = + conrod::backend::piston_window::GlyphCache::new(&mut window, WIDTH, HEIGHT); + + // The image map describing each of our widget->image mappings (in our case, none). + let image_map = conrod::image::Map::new(); + + // Our dmonstration app that we'll control with our GUI. + let mut app = DemoApp::new(); + + window.set_ups(60); + + // Poll events from the window. + while let Some(event) = window.next() { + + // Convert the piston event to a conrod event. + if let Some(e) = conrod::backend::piston_window::convert_event(event.clone(), &window) { + ui.handle_event(e); + } + + // We'll set all our widgets in a single function called `set_widgets`. + event.update(|_| { + let mut ui = ui.set_widgets(); + set_widgets(&mut ui, &mut app, &mut ids); + }); + + // Draw our Ui! + // + // The `draw_if_changed` method only re-draws the GUI if some `Widget`'s `Element` + // representation has changed. Normally, a `Widget`'s `Element` should only change + // if a Widget was interacted with in some way, however this is up to the `Widget` + // designer's discretion. + // + // If instead you need to re-draw your conrod GUI every frame, use `Ui::draw`. + window.draw_2d(&event, |c, g| { + if let Some(primitives) = ui.draw_if_changed() { + fn texture_from_image(img: &T) -> &T { img }; + conrod::backend::piston_window::draw(c, g, primitives, + &mut text_texture_cache, + &image_map, + texture_from_image); + } + }); + } +} + + +// In conrod, each widget must have its own unique identifier so that the `Ui` can keep track of +// its state between updates. +// +// To make this easier, conrod provides the `widget_ids` macro. This macro generates a new type +// with a unique `widget::Id` field for each identifier given in the list. See the `widget_ids!` +// documentation for more details. +widget_ids! { + struct Ids { + canvas, + canvas_x_scrollbar, + canvas_y_scrollbar, + title, + button, + title_pad_slider, + toggle, + red_slider, + green_slider, + blue_slider, + slider_height, + border_width, + toggle_matrix, + color_select, + circle_position, + circle, + text_box_a, + text_box_b, + envelope_editor_a, + envelope_editor_b, + } +} + + +/// Set all `Widget`s within the User Interface. +/// +/// The first time this gets called, each `Widget`'s `State` will be initialised and cached within +/// the `Ui` at their given indices. Every other time this get called, the `Widget`s will avoid any +/// allocations by updating the pre-existing cached state. A new graphical `Element` is only +/// retrieved from a `Widget` in the case that it's `State` has changed in some way. +fn set_widgets(ui: &mut conrod::UiCell, app: &mut DemoApp, ids: &mut Ids) { + use conrod::{color, widget, Colorable, Borderable, Labelable, Positionable, Sizeable, Widget}; + + // We can use this `Canvas` as a parent Widget upon which we can place other widgets. + widget::Canvas::new() + .border(app.border_width) + .pad(30.0) + .color(app.bg_color) + .scroll_kids() + .set(ids.canvas, ui); + widget::Scrollbar::x_axis(ids.canvas).auto_hide(true).set(ids.canvas_y_scrollbar, ui); + widget::Scrollbar::y_axis(ids.canvas).auto_hide(true).set(ids.canvas_x_scrollbar, ui); + + // Text example. + widget::Text::new("Widget Demonstration") + .top_left_with_margins_on(ids.canvas, 0.0, app.title_pad) + .font_size(32) + .color(app.bg_color.plain_contrast()) + .set(ids.title, ui); + + if app.show_button { + + // Button widget example button. + if widget::Button::new() + .w_h(200.0, 50.0) + .mid_left_of(ids.canvas) + .down_from(ids.title, 45.0) + .rgb(0.4, 0.75, 0.6) + .border(app.border_width) + .label("PRESS") + .set(ids.button, ui) + .was_clicked() + { + app.bg_color = color::rgb(rand::random(), rand::random(), rand::random()) + } + + } + + // Horizontal slider example. + else { + + // Create the label for the slider. + let label = format!("Padding: {}", app.title_pad as i16); + + // Slider widget example slider(value, min, max). + if let Some(new_pad) = widget::Slider::new(app.title_pad, 0.0, 670.0) + .w_h(200.0, 50.0) + .mid_left_of(ids.canvas) + .down_from(ids.title, 45.0) + .rgb(0.5, 0.3, 0.6) + .border(app.border_width) + .label(&label) + .label_color(color::WHITE) + .set(ids.title_pad_slider, ui) + { + app.title_pad = new_pad; + } + + } + + // Keep track of the currently shown widget. + let shown_widget = if app.show_button { ids.button } else { ids.title_pad_slider }; + + // Toggle widget example. + if let Some(value) = widget::Toggle::new(app.show_button) + .w_h(75.0, 75.0) + .down(20.0) + .rgb(0.6, 0.25, 0.75) + .border(app.border_width) + .label(&app.toggle_label) + .label_color(color::WHITE) + .set(ids.toggle, ui) + .last() + { + app.show_button = value; + app.toggle_label = match value { + true => "ON".to_string(), + false => "OFF".to_string() + } + } + + macro_rules! color_slider { + ($slider_id:ident, $bg_color:ident, $color:expr, $set_color:ident, $position:ident) => {{ + let value = app.bg_color.$bg_color(); + let label = format!("{:.*}", 2, value); + for color in widget::Slider::new(value, 0.0, 1.0) + .$position(25.0) + .w_h(40.0, app.v_slider_height) + .color($color) + .border(app.border_width) + .label(&label) + .label_color(color::WHITE) + .set(ids.$slider_id, ui) + { + app.bg_color.$set_color(color); + } + }}; + } + + color_slider!(red_slider, red, color::rgb(0.75, 0.3, 0.3), set_red, down); + color_slider!(green_slider, green, color::rgb(0.3, 0.75, 0.3), set_green, right); + color_slider!(blue_slider, blue, color::rgb(0.3, 0.3, 0.75), set_blue, right); + + // Number Dialer widget example. (value, min, max, precision) + for new_height in widget::NumberDialer::new(app.v_slider_height, 25.0, 250.0, 1) + .w_h(260.0, 60.0) + .right_from(shown_widget, 30.0) + .color(app.bg_color.invert()) + .border(app.border_width) + .label("Height (px)") + .label_color(app.bg_color.invert().plain_contrast()) + .set(ids.slider_height, ui) + { + app.v_slider_height = new_height; + } + + // Number Dialer widget example. (value, min, max, precision) + for new_width in widget::NumberDialer::new(app.border_width, 0.0, 15.0, 2) + .w_h(260.0, 60.0) + .down(20.0) + .color(app.bg_color.plain_contrast().invert()) + .border(app.border_width) + .border_color(app.bg_color.plain_contrast()) + .label("Border Width (px)") + .label_color(app.bg_color.plain_contrast()) + .set(ids.border_width, ui) + { + app.border_width = new_width; + } + + // A demonstration using widget_matrix to easily draw a matrix of any kind of widget. + let (cols, rows) = (8, 8); + let mut elements = widget::Matrix::new(cols, rows) + .down(20.0) + .w_h(260.0, 260.0) + .set(ids.toggle_matrix, ui); + + // The `Matrix` widget returns an `Elements`, which can be used similar to an `Iterator`. + while let Some(elem) = elements.next(ui) { + let (col, row) = (elem.col, elem.row); + + // Color effect for fun. + let (r, g, b, a) = ( + 0.5 + (elem.col as f32 / cols as f32) / 2.0, + 0.75, + 1.0 - (elem.row as f32 / rows as f32) / 2.0, + 1.0 + ); + + // We can use `Element`s to instantiate any kind of widget we like. + // The `Element` does all of the positioning and sizing work for us. + // Here, we use the `Element` to `set` a `Toggle` widget for us. + let toggle = widget::Toggle::new(app.bool_matrix[col][row]) + .rgba(r, g, b, a) + .border(app.border_width); + if let Some(new_value) = elem.set(toggle, ui).last() { + app.bool_matrix[col][row] = new_value; + } + } + + // A demonstration using a DropDownList to select its own color. + for selected_idx in widget::DropDownList::new(&app.ddl_colors, app.selected_idx) + .w_h(150.0, 40.0) + .right_from(ids.slider_height, 30.0) // Position right from widget 6 by 50 pixels. + .max_visible_items(3) + .color(app.ddl_color) + .border(app.border_width) + .border_color(app.ddl_color.plain_contrast()) + .label("Colors") + .label_color(app.ddl_color.plain_contrast()) + .scrollbar_on_top() + .set(ids.color_select, ui) + { + app.selected_idx = Some(selected_idx); + app.ddl_color = match &app.ddl_colors[selected_idx][..] { + "Black" => color::BLACK, + "White" => color::WHITE, + "Red" => color::RED, + "Green" => color::GREEN, + "Blue" => color::BLUE, + _ => color::PURPLE, + } + } + + // Draw an xy_pad. + for (x, y) in widget::XYPad::new(app.circle_pos[0], -75.0, 75.0, // x range. + app.circle_pos[1], 95.0, 245.0) // y range. + .w_h(150.0, 150.0) + .right_from(ids.toggle_matrix, 30.0) + .align_bottom_of(ids.toggle_matrix) // Align to the bottom of the last toggle_matrix element. + .color(app.ddl_color) + .border(app.border_width) + .border_color(color::WHITE) + .label("Circle Position") + .label_color(app.ddl_color.plain_contrast().alpha(0.5)) + .line_thickness(2.0) + .set(ids.circle_position, ui) + { + app.circle_pos[0] = x; + app.circle_pos[1] = y; + } + + // Draw a circle at the app's circle_pos. + widget::Circle::fill(15.0) + .xy_relative_to(ids.circle_position, app.circle_pos) + .color(app.ddl_color) + .set(ids.circle, ui); + + // Draw two TextBox and EnvelopeEditor pairs to the right of the DropDownList flowing downward. + for i in 0..2 { + let &mut (ref mut env, ref mut text) = &mut app.envelopes[i]; + let (text_box, env_editor, env_y_max, env_skew_y) = match i { + 0 => (ids.text_box_a, ids.envelope_editor_a, 20_000.0, 3.0), + 1 => (ids.text_box_b, ids.envelope_editor_b, 1.0, 1.0), + _ => unreachable!(), + }; + + // A text box in which we can mutate a single line of text, and trigger reactions via the + // `Enter`/`Return` key. + for event in widget::TextBox::new(text) + .and_if(i == 0, |text| text.right_from(ids.color_select, 30.0)) + .font_size(20) + .w_h(320.0, 40.0) + .border(app.border_width) + .border_color(app.bg_color.invert().plain_contrast()) + .color(app.bg_color.invert()) + .set(text_box, ui) + { + match event { + widget::text_box::Event::Enter => println!("TextBox {}: {:?}", i, text), + widget::text_box::Event::Update(string) => *text = string, + } + } + + // Draw an EnvelopeEditor. (&[Point], x_min, x_max, y_min, y_max). + for event in widget::EnvelopeEditor::new(env, 0.0, 1.0, 0.0, env_y_max) + .down(10.0) + .w_h(320.0, 150.0) + .skew_y(env_skew_y) + .color(app.bg_color.invert()) + .border(app.border_width) + .border_color(app.bg_color.invert().plain_contrast()) + .label(&text) + .label_color(app.bg_color.invert().plain_contrast().alpha(0.5)) + .point_radius(6.0) + .line_thickness(2.0) + .set(env_editor, ui) + { + event.update(env); + } + } + +} diff --git a/examples/support/mod.rs b/examples/support/mod.rs new file mode 100644 index 000000000..3a12cf06c --- /dev/null +++ b/examples/support/mod.rs @@ -0,0 +1,341 @@ +//! This module is used for sharing a few items between the `all_widgets.rs`, `glutin_glium.rs` and +//! `glutin_gfx.rs` examples. +//! +//! The module contains: +//! +//! - `pub struct DemoApp` as a demonstration of some state we want to change. +//! - `pub fn gui` as a demonstration of all widgets, some of which mutate our `DemoApp`. +//! - `pub struct Ids` - a set of all `widget::Id`s used in the `gui` fn. +//! +//! By sharing these items between these examples, we can test and ensure that the different events +//! and drawing backends behave in the same manner. + +extern crate rand; + +use conrod; +use std; + + +pub const WIN_W: u32 = 600; +pub const WIN_H: u32 = 420; + + +/// A demonstration of some application state we want to control with a conrod GUI. +pub struct DemoApp { + ball_xy: conrod::Point, + ball_color: conrod::Color, + sine_frequency: f32, +} + + +impl DemoApp { + + /// Simple constructor for the `DemoApp`. + pub fn new() -> Self { + DemoApp { + ball_xy: [0.0, 0.0], + ball_color: conrod::color::WHITE, + sine_frequency: 1.0, + } + } + +} + + +/// A set of reasonable stylistic defaults that works for the `gui` below. +pub fn theme() -> conrod::Theme { + conrod::Theme { + name: "Demo Theme".to_string(), + padding: conrod::Padding::none(), + x_position: conrod::Position::Align(conrod::Align::Start, None), + y_position: conrod::Position::Direction(conrod::Direction::Backwards, 20.0, None), + background_color: conrod::color::DARK_CHARCOAL, + shape_color: conrod::color::LIGHT_CHARCOAL, + border_color: conrod::color::BLACK, + border_width: 0.0, + label_color: conrod::color::WHITE, + font_id: None, + font_size_large: 26, + font_size_medium: 18, + font_size_small: 12, + widget_styling: std::collections::HashMap::new(), + mouse_drag_threshold: 0.0, + double_click_threshold: std::time::Duration::from_millis(500), + } +} + + +// Generate a unique `WidgetId` for each widget. +widget_ids! { + pub struct Ids { + + // The scrollable canvas. + canvas, + + // The title and introduction widgets. + title, + introduction, + + // Shapes. + shapes_canvas, + shapes_left_col, + shapes_right_col, + shapes_title, + line, + point_path, + rectangle_fill, + rectangle_outline, + trapezoid, + oval_fill, + oval_outline, + circle, + + // Image. + image_title, + rust_logo, + + // Button, XyPad, Toggle. + button_title, + button, + xy_pad, + toggle, + ball, + + // NumberDialer, PlotPath + dialer_title, + number_dialer, + plot_path, + + // Scrollbar + canvas_scrollbar, + + } +} + + +/// Instantiate a GUI demonstrating every widget available in conrod. +pub fn gui(ui: &mut conrod::UiCell, ids: &Ids, app: &mut DemoApp) { + use conrod::{widget, Colorable, Labelable, Positionable, Sizeable, Widget}; + use std::iter::once; + + const MARGIN: conrod::Scalar = 30.0; + const SHAPE_GAP: conrod::Scalar = 50.0; + const TITLE_SIZE: conrod::FontSize = 42; + const SUBTITLE_SIZE: conrod::FontSize = 32; + + // `Canvas` is a widget that provides some basic functionality for laying out children widgets. + // By default, its size is the size of the window. We'll use this as a background for the + // following widgets, as well as a scrollable container for the children widgets. + const TITLE: &'static str = "All Widgets"; + widget::Canvas::new().pad(MARGIN).scroll_kids_vertically().set(ids.canvas, ui); + + + //////////////// + ///// TEXT ///// + //////////////// + + + // We'll demonstrate the `Text` primitive widget by using it to draw a title and an + // introduction to the example. + widget::Text::new(TITLE).font_size(TITLE_SIZE).mid_top_of(ids.canvas).set(ids.title, ui); + + const INTRODUCTION: &'static str = + "This example aims to demonstrate all widgets that are provided by conrod.\ + \n\nThe widget that you are currently looking at is the Text widget. The Text widget \ + is one of several special \"primitive\" widget types which are used to construct \ + all other widget types. These types are \"special\" in the sense that conrod knows \ + how to render them via `conrod::render::Primitive`s.\ + \n\nScroll down to see more widgets!"; + widget::Text::new(INTRODUCTION) + .padded_w_of(ids.canvas, MARGIN) + .down(60.0) + .align_middle_x_of(ids.canvas) + .align_text_middle() + .line_spacing(5.0) + .set(ids.introduction, ui); + + + //////////////////////////// + ///// Lines and Shapes ///// + //////////////////////////// + + + widget::Text::new("Lines and Shapes") + .down(70.0) + .align_middle_x_of(ids.canvas) + .font_size(SUBTITLE_SIZE) + .set(ids.shapes_title, ui); + + // Lay out the shapes in two horizontal columns. + // + // TODO: Have conrod provide an auto-flowing, fluid-list widget that is more adaptive for these + // sorts of situations. + widget::Canvas::new() + .down(0.0) + .align_middle_x_of(ids.canvas) + .kid_area_w_of(ids.canvas) + .h(360.0) + .color(conrod::color::TRANSPARENT) + .pad(MARGIN) + .flow_down(&[ + (ids.shapes_left_col, widget::Canvas::new()), + (ids.shapes_right_col, widget::Canvas::new()), + ]) + .set(ids.shapes_canvas, ui); + + let start = [-40.0, -40.0]; + let end = [40.0, 40.0]; + widget::Line::centred(start, end).mid_left_of(ids.shapes_left_col).set(ids.line, ui); + + let left = [-40.0, -40.0]; + let top = [0.0, 40.0]; + let right = [40.0, -40.0]; + let points = once(left).chain(once(top)).chain(once(right)); + widget::PointPath::centred(points).right(SHAPE_GAP).set(ids.point_path, ui); + + widget::Rectangle::fill([80.0, 80.0]).right(SHAPE_GAP).set(ids.rectangle_fill, ui); + + widget::Rectangle::outline([80.0, 80.0]).right(SHAPE_GAP).set(ids.rectangle_outline, ui); + + let bl = [-40.0, -40.0]; + let tl = [-20.0, 40.0]; + let tr = [20.0, 40.0]; + let br = [40.0, -40.0]; + let points = once(bl).chain(once(tl)).chain(once(tr)).chain(once(br)); + widget::Polygon::centred_fill(points).mid_left_of(ids.shapes_right_col).set(ids.trapezoid, ui); + + widget::Oval::fill([40.0, 80.0]).right(SHAPE_GAP + 20.0).align_middle_y().set(ids.oval_fill, ui); + + widget::Oval::outline([80.0, 40.0]).right(SHAPE_GAP + 20.0).align_middle_y().set(ids.oval_outline, ui); + + widget::Circle::fill(40.0).right(SHAPE_GAP).align_middle_y().set(ids.circle, ui); + + + ///////////////// + ///// Image ///// + ///////////////// + + + widget::Text::new("Image") + .down_from(ids.shapes_canvas, MARGIN) + .align_middle_x_of(ids.canvas) + .font_size(SUBTITLE_SIZE) + .set(ids.image_title, ui); + + const LOGO_SIDE: conrod::Scalar = 144.0; + widget::Image::new() + .w_h(LOGO_SIDE, LOGO_SIDE) + .down(60.0) + .align_middle_x_of(ids.canvas) + .set(ids.rust_logo, ui); + + + ///////////////////////////////// + ///// Button, XYPad, Toggle ///// + ///////////////////////////////// + + + widget::Text::new("Button, XYPad and Toggle") + .down_from(ids.rust_logo, 60.0) + .align_middle_x_of(ids.canvas) + .font_size(SUBTITLE_SIZE) + .set(ids.button_title, ui); + + let ball_x_range = ui.kid_area_of(ids.canvas).unwrap().w(); + let ball_y_range = ui.h_of(ui.window).unwrap() * 0.5; + let min_x = -ball_x_range / 3.0; + let max_x = ball_x_range / 3.0; + let min_y = -ball_y_range / 3.0; + let max_y = ball_y_range / 3.0; + let side = 130.0; + + for _press in widget::Button::new() + .label("PRESS ME") + .mid_left_with_margin_on(ids.canvas, MARGIN) + .down_from(ids.button_title, 60.0) + .w_h(side, side) + .set(ids.button, ui) + { + let x = rand::random::() * (max_x - min_x) - max_x; + let y = rand::random::() * (max_y - min_y) - max_y; + app.ball_xy = [x, y]; + } + + for (x, y) in widget::XYPad::new(app.ball_xy[0], min_x, max_x, + app.ball_xy[1], min_y, max_y) + .label("BALL XY") + .wh_of(ids.button) + .align_middle_y_of(ids.button) + .align_middle_x_of(ids.canvas) + .parent(ids.canvas) + .set(ids.xy_pad, ui) + { + app.ball_xy = [x, y]; + } + + let is_white = app.ball_color == conrod::color::WHITE; + let label = if is_white { "WHITE" } else { "BLACK" }; + for is_white in widget::Toggle::new(is_white) + .label(label) + .label_color(if is_white { conrod::color::WHITE } else { conrod::color::LIGHT_CHARCOAL }) + .mid_right_with_margin_on(ids.canvas, MARGIN) + .align_middle_y_of(ids.button) + .set(ids.toggle, ui) + { + app.ball_color = if is_white { conrod::color::WHITE } else { conrod::color::BLACK }; + } + + let ball_x = app.ball_xy[0]; + let ball_y = app.ball_xy[1] - max_y - side * 0.5 - MARGIN; + widget::Circle::fill(20.0) + .color(app.ball_color) + .x_y_relative_to(ids.xy_pad, ball_x, ball_y) + .set(ids.ball, ui); + + + ////////////////////////////////// + ///// NumberDialer, PlotPath ///// + ////////////////////////////////// + + + widget::Text::new("NumberDialer and PlotPath") + .down_from(ids.xy_pad, max_y - min_y + side * 0.5 + MARGIN) + .align_middle_x_of(ids.canvas) + .font_size(SUBTITLE_SIZE) + .set(ids.dialer_title, ui); + + // Use a `NumberDialer` widget to adjust the frequency of the sine wave below. + let min = 0.5; + let max = 200.0; + let decimal_precision = 1; + for new_freq in widget::NumberDialer::new(app.sine_frequency, min, max, decimal_precision) + .down(60.0) + .align_middle_x_of(ids.canvas) + .w_h(160.0, 40.0) + .label("F R E Q") + .set(ids.number_dialer, ui) + { + app.sine_frequency = new_freq; + } + + // Use the `PlotPath` widget to display a sine wave. + let min_x = 0.0; + let max_x = std::f32::consts::PI * 2.0 * app.sine_frequency; + let min_y = -1.0; + let max_y = 1.0; + widget::PlotPath::new(min_x, max_x, min_y, max_y, f32::sin) + .kid_area_w_of(ids.canvas) + .h(240.0) + .down(60.0) + .align_middle_x_of(ids.canvas) + .set(ids.plot_path, ui); + + + ///////////////////// + ///// Scrollbar ///// + ///////////////////// + + + widget::Scrollbar::y_axis(ids.canvas).auto_hide(true).set(ids.canvas_scrollbar, ui); + +}