use color::Color;
use event;
use graph::{self, Graph};
use input;
use position::{Align, Direction, Dimensions, Padding, Place, Point, Position, Range, Rect, Scalar};
use render;
use std;
use text;
use theme::Theme;
use utils;
use widget::{self, Widget};
/// A constructor type for building a `Ui` instance with a set of optional parameters.
pub struct UiBuilder {
/// The initial dimensions of the window in which the `Ui` exists.
pub window_dimensions: Dimensions,
/// The theme used to set default styling for widgets.
///
/// If this field is `None` when `build` is called, `Theme::default` will be used.
pub maybe_theme: Option<Theme>,
/// An estimation of the maximum number of widgets that will be used with this `Ui` instance.
///
/// This value is used to determine the size with which various collections should be
/// reserved. This may make the first cycle of widget instantiations more efficient as the
/// collections will not be required to grow dynamically. These collections include:
///
/// - the widget graph node and edge `Vec`s
/// - the `HashSet` used to track updated widgets
/// - the widget `DepthOrder` (a kind of toposort describing the order of widgets in their
/// rendering order).
///
/// If this field is `None` when `build` is called, these collections will be initialised with
/// no pre-reserved size and will instead grow organically as needed.
pub maybe_widgets_capacity: Option<usize>
}
/// `Ui` is the most important type within Conrod and is necessary for rendering and maintaining
/// widget state.
/// # Ui Handles the following:
/// * Contains the state of all widgets which can be indexed via their widget::Id.
/// * Stores rendering state for each widget until the end of each render cycle.
/// * Contains the theme used for default styling of the widgets.
/// * Maintains the latest user input state (for mouse and keyboard).
/// * Maintains the latest window dimensions.
pub struct Ui {
/// The theme used to set default styling for widgets.
pub theme: Theme,
/// An index into the root widget of the graph, representing the entire window.
pub window: widget::Id,
/// Handles aggregation of events and providing them to Widgets
pub global_input: input::Global,
/// Manages all fonts that have been loaded by the user.
pub fonts: text::font::Map,
/// The Widget cache, storing state for all widgets.
widget_graph: Graph,
/// The widget::Id of the widget that was last updated/set.
maybe_prev_widget_id: Option<widget::Id>,
/// The widget::Id of the last widget used as a parent for another widget.
maybe_current_parent_id: Option<widget::Id>,
/// The number of frames that that will be used for the `redraw_count` when `need_redraw` is
/// triggered.
num_redraw_frames: u8,
/// Whether or not the `Ui` needs to be re-drawn to screen.
redraw_count: u8,
/// A background color to clear the screen with before drawing if one was given.
maybe_background_color: Option<Color>,
/// The order in which widgets from the `widget_graph` are drawn.
depth_order: graph::DepthOrder,
/// The set of widgets that have been updated since the beginning of the `set_widgets` stage.
updated_widgets: std::collections::HashSet<widget::Id>,
/// The `updated_widgets` for the previous `set_widgets` stage.
///
/// We use this to compare against the newly generated `updated_widgets` to see whether or not
/// we require re-drawing.
prev_updated_widgets: std::collections::HashSet<widget::Id>,
/// Scroll events that have been emitted during a call to `Ui::set_widgets`. These are usually
/// emitted by some widget like the `Scrollbar`.
///
/// These events will be drained and pushed onto the end of the `global_input` event buffer at
/// the end of the `Ui::set_widgets` method. This ensures that the events are received by the
/// target widgets during the next call to `Ui::set_widgets`.
pending_scroll_events: Vec<event::Ui>,
// TODO: Remove the following fields as they should now be handled by `input::Global`.
/// Window width.
pub win_w: f64,
/// Window height.
pub win_h: f64,
}
/// A wrapper around the `Ui` that restricts the user from mutating the `Ui` in certain ways while
/// in the scope of the `Ui::set_widgets` function and within `Widget`s' `update` methods. Using
/// the `UiCell`, users may access the `Ui` immutably (via `Deref`) however they wish, however they
/// may only mutate the `Ui` via the `&mut self` methods provided by the `UiCell`.
///
/// The name came from its likening to a "jail cell for the `Ui`", as it restricts a user's access
/// to it. However, we realise that the name may also cause ambiguity with the std `Cell` and
/// `RefCell` types (which `UiCell` has nothing to do with). Thus, if you have a better name for
/// this type in mind, please let us know at the github repo via an issue or PR sometime before we
/// hit 1.0.0!
pub struct UiCell<'a> {
/// A mutable reference to a **Ui**.
ui: &'a mut Ui,
}
/// Each time conrod is required to redraw the GUI, it must draw for at least the next three frames
/// to ensure that, in the case that graphics buffers are being swapped, we have filled each
/// buffer. Otherwise if we don't draw into each buffer, we will probably be subject to flickering.
pub const SAFE_REDRAW_COUNT: u8 = 3;
impl UiBuilder {
/// Begin building a new `Ui` instance.
///
/// Give the initial dimensions of the window within which the `Ui` will be instantiated as a
/// `Scalar` (DPI agnostic) value.
pub fn new(window_dimensions: Dimensions) -> Self {
UiBuilder {
window_dimensions: window_dimensions,
maybe_theme: None,
maybe_widgets_capacity: None
}
}
/// The theme used to set default styling for widgets.
///
/// If this field is `None` when `build` is called, `Theme::default` will be used.
pub fn theme(mut self, value: Theme) -> Self {
self.maybe_theme = Some(value);
self
}
/// An estimation of the maximum number of widgets that will be used with this `Ui` instance.
///
/// This value is used to determine the size with which various collections should be
/// reserved. This may make the first cycle of widget instantiations more efficient as the
/// collections will not be required to grow dynamically. These collections include:
///
/// - the widget graph node and edge `Vec`s
/// - the `HashSet` used to track updated widgets
/// - the widget `DepthOrder` (a kind of toposort describing the order of widgets in their
/// rendering order).
///
/// If this field is `None` when `build` is called, these collections will be initialised with
/// no pre-reserved size and will instead grow organically as required.
pub fn widgets_capacity(mut self, value: usize) -> Self {
self.maybe_widgets_capacity = Some(value);
self
}
/// Build **Ui** from the given builder
pub fn build(self) -> Ui {
Ui::new(self)
}
}
impl Ui {
/// A new, empty **Ui**.
fn new(builder: UiBuilder) -> Self {
let UiBuilder {
window_dimensions,
maybe_widgets_capacity,
maybe_theme,
} = builder;
let (mut widget_graph, depth_order, updated_widgets) =
maybe_widgets_capacity.map_or_else(
|| (Graph::new(),
graph::DepthOrder::new(),
std::collections::HashSet::new()),
|n| (Graph::with_node_capacity(n),
graph::DepthOrder::with_node_capacity(n),
std::collections::HashSet::with_capacity(n)));
let window = widget_graph.add_placeholder();
let prev_updated_widgets = updated_widgets.clone();
Ui {
widget_graph: widget_graph,
theme: maybe_theme.unwrap_or_else(|| Theme::default()),
fonts: text::font::Map::new(),
window: window,
win_w: window_dimensions[0],
win_h: window_dimensions[1],
maybe_prev_widget_id: None,
maybe_current_parent_id: None,
num_redraw_frames: SAFE_REDRAW_COUNT,
redraw_count: SAFE_REDRAW_COUNT,
maybe_background_color: None,
depth_order: depth_order,
updated_widgets: updated_widgets,
prev_updated_widgets: prev_updated_widgets,
global_input: input::Global::new(),
pending_scroll_events: Vec::new(),
}
}
/// Returns a `input::Widget` for the given widget
pub fn widget_input(&self, widget: widget::Id) -> input::Widget {
// If there's no rectangle for a given widget, then we use one with zero area.
// This means that the resulting `input::Widget` will not include any mouse events
// unless it has captured the mouse, since none will have occured over that area.
let rect = self.rect_of(widget).unwrap_or_else(|| {
let right_edge = self.win_w / 2.0;
let bottom_edge = self.win_h / 2.0;
Rect::from_xy_dim([right_edge, bottom_edge], [0.0, 0.0])
});
input::Widget::for_widget(widget, rect, &self.global_input)
}
/// The **Rect** for the widget at the given index.
///
/// Returns `None` if there is no widget for the given index.
pub fn rect_of(&self, id: widget::Id) -> Option<Rect> {
self.widget_graph.widget(id).map(|widget| widget.rect)
}
/// The absolute width of the widget at the given index.
///
/// Returns `None` if there is no widget for the given index.
pub fn w_of(&self, id: widget::Id) -> Option<Scalar> {
self.rect_of(id).map(|rect| rect.w())
}
/// The absolute height of the widget at the given index.
///
/// Returns `None` if there is no widget for the given index.
pub fn h_of(&self, id: widget::Id) -> Option<Scalar> {
self.rect_of(id).map(|rect| rect.h())
}
/// The absolute dimensions for the widget at the given index.
///
/// Returns `None` if there is no widget for the given index.
pub fn wh_of(&self, id: widget::Id) -> Option<Dimensions> {
self.rect_of(id).map(|rect| rect.dim())
}
/// The coordinates for the widget at the given index.
///
/// Returns `None` if there is no widget for the given index.
pub fn xy_of(&self, id: widget::Id) -> Option<Point> {
self.rect_of(id).map(|rect| rect.xy())
}
/// The `kid_area` of the widget at the given index.
///
/// Returns `None` if there is no widget for the given index.
pub fn kid_area_of(&self, id: widget::Id) -> Option<Rect> {
self.widget_graph.widget(id).map(|widget| {
widget.kid_area.rect.padding(widget.kid_area.pad)
})
}
/// An index to the previously updated widget if there is one.
pub fn maybe_prev_widget(&self) -> Option<widget::Id> {
self.maybe_prev_widget_id
}
/// Borrow the **Ui**'s `widget_graph`.
pub fn widget_graph(&self) -> &Graph {
&self.widget_graph
}
/// Borrow the **Ui**'s set of updated widgets.
///
/// This set indicates which widgets have been instantiated since the beginning of the most
/// recent `Ui::set_widgets` call.
pub fn updated_widgets(&self) -> &std::collections::HashSet<widget::Id> {
&self.updated_widgets
}
/// Borrow the **Ui**'s set of updated widgets.
///
/// This set indicates which widgets have were instantiated during the previous call to
/// `Ui::set_widgets`.
pub fn prev_updated_widgets(&self) -> &std::collections::HashSet<widget::Id> {
&self.prev_updated_widgets
}
/// Produces a type that may be used to generate new unique `widget::Id`s.
///
/// See the [**widget::id::Generator**](../widget/id/struct.Generator.html) docs for details on
/// how to use this correctly.
pub fn widget_id_generator(&mut self) -> widget::id::Generator {
widget::id::Generator::new(&mut self.widget_graph)
}
/// Scroll the widget at the given index by the given offset amount.
///
/// The produced `Scroll` event will be applied upon the next call to `Ui::set_widgets`.
pub fn scroll_widget(&mut self, widget_id: widget::Id, offset: [Scalar; 2]) {
let (x, y) = (offset[0], offset[1]);
if x != 0.0 || y != 0.0 {
let event = event::Ui::Scroll(Some(widget_id), event::Scroll {
x: x,
y: y,
modifiers: self.global_input.current.modifiers,
}).into();
self.global_input.push_event(event);
}
}
/// Handle raw window events and update the `Ui` state accordingly.
///
/// This occurs within several stages:
///
/// 1. Convert the user's given `event` to a `RawEvent` so that the `Ui` may use it.
/// 2. Interpret the `RawEvent` for higher-level `Event`s such as `DoubleClick`,
/// `WidgetCapturesKeyboard`, etc.
/// 3. Update the `Ui`'s `global_input` `State` accordingly, depending on the `RawEvent`.
/// 4. Store newly produced `event::Ui`s within the `global_input` so that they may be filtered
/// and fed to `Widget`s next time `Ui::set_widget` is called.
///
/// This method *drives* the `Ui` forward, and is what allows for using conrod's `Ui` with any
/// window event stream.
///
/// The given `event` must implement the **ToRawEvent** trait so that it can be converted to a
/// `RawEvent` that can be used by the `Ui`.
pub fn handle_event(&mut self, event: event::Input) {
use event::{self, Input, Motion};
use input::{Button, Key, ModifierKey};
use input::state::mouse::Button as MouseButton;
// Determines which widget is currently under the mouse and sets it within the `Ui`'s
// `input::Global`'s `input::State`.
//
// If the `widget_under_mouse` has changed, this function will also update the
// `widget_capturing_mouse`.
//
// If the left mouse button is up, we assume that the widget directly under the
// mouse cursor captures all input from the mouse.
//
// If the left mouse button is down, we assume that the widget that was clicked
// remains "pinned" and will continue to capture the mouse until it is
// released.
//
// Note: This function expects that `ui.global_input.current.mouse.xy` is up-to-date.
fn track_widget_under_mouse_and_update_capturing(ui: &mut Ui) {
ui.global_input.current.widget_under_mouse =
graph::algo::pick_widgets(&ui.depth_order.indices,
ui.global_input.current.mouse.xy)
.next(&ui.widget_graph, &ui.depth_order.indices);
// If MouseButton::Left is up and `widget_under_mouse` has changed, capture new widget
// under mouse.
if ui.global_input.current.mouse.buttons.left().is_up() {
let widget_under_mouse = ui.global_input.current.widget_under_mouse;
// Check to see if we need to uncapture a widget.
if let Some(idx) = ui.global_input.current.widget_capturing_mouse {
if widget_under_mouse != Some(idx) {
let event = event::Ui::WidgetUncapturesMouse(idx).into();
ui.global_input.push_event(event);
ui.global_input.current.widget_capturing_mouse = None;
}
}
// Check to see if there is a new widget capturing the mouse.
if ui.global_input.current.widget_capturing_mouse.is_none() {
if let Some(idx) = widget_under_mouse {
let event = event::Ui::WidgetCapturesMouse(idx).into();
ui.global_input.push_event(event);
ui.global_input.current.widget_capturing_mouse = Some(idx);
}
}
}
}
// A function for filtering `ModifierKey`s.
fn filter_modifier(key: Key) -> Option<ModifierKey> {
use input::keyboard::{CTRL, SHIFT, ALT, GUI};
match key {
Key::LCtrl | Key::RCtrl => Some(CTRL),
Key::LShift | Key::RShift => Some(SHIFT),
Key::LAlt | Key::RAlt => Some(ALT),
Key::LGui | Key::RGui => Some(GUI),
_ => None
}
}
// Here we handle all user input given to conrod.
//
// Not only do we store the `Input` event as an `Event::Raw`, we also use them to
// interpret higher level events such as `Click` or `Drag`.
//
// Finally, we also ensure that the `current_state` is up-to-date.
self.global_input.push_event(event.clone().into());
match event {
// Some button was pressed, whether keyboard, mouse or some other device.
Input::Press(button_type) => match button_type {
// Check to see whether we need to (un)capture the keyboard or mouse.
Button::Mouse(mouse_button) => {
// Create a mouse `Press` event.
let mouse_xy = self.global_input.current.mouse.xy;
let press = event::Press {
button: event::Button::Mouse(mouse_button, mouse_xy),
modifiers: self.global_input.current.modifiers,
};
let widget = self.global_input.current.widget_capturing_mouse;
let press_event = event::Ui::Press(widget, press).into();
self.global_input.push_event(press_event);
if let MouseButton::Left = mouse_button {
// Check to see if we need to uncapture the keyboard.
if let Some(idx) = self.global_input.current.widget_capturing_keyboard {
if Some(idx) != self.global_input.current.widget_under_mouse {
let event = event::Ui::WidgetUncapturesKeyboard(idx).into();
self.global_input.push_event(event);
self.global_input.current.widget_capturing_keyboard = None;
}
}
// Check to see if we need to capture the keyboard.
if let Some(idx) = self.global_input.current.widget_under_mouse {
let event = event::Ui::WidgetCapturesKeyboard(idx).into();
self.global_input.push_event(event);
self.global_input.current.widget_capturing_keyboard = Some(idx);
}
}
// Keep track of pressed buttons in the current input::State.
let xy = self.global_input.current.mouse.xy;
let widget = self.global_input.current.widget_under_mouse;
self.global_input.current.mouse.buttons.press(mouse_button, xy, widget);
},
Button::Keyboard(key) => {
// Create a keyboard `Press` event.
let press = event::Press {
button: event::Button::Keyboard(key),
modifiers: self.global_input.current.modifiers,
};
let widget = self.global_input.current.widget_capturing_keyboard;
let press_event = event::Ui::Press(widget, press).into();
self.global_input.push_event(press_event);
// If some modifier key was pressed, add it to the current modifiers.
if let Some(modifier) = filter_modifier(key) {
self.global_input.current.modifiers.insert(modifier);
}
// If `Esc` was pressed, check to see if we need to cancel a `Drag` or
// uncapture a widget.
if let Key::Escape = key {
// TODO:
// 1. Cancel `Drag` if currently under way.
// 2. If mouse is captured due to pinning widget with left mouse button,
// cancel capturing.
}
},
_ => {}
},
// Some button was released.
//
// Checks for events in the following order:
// 1. Click
// 2. DoubleClick
// 2. WidgetUncapturesMouse
Input::Release(button_type) => match button_type {
Button::Mouse(mouse_button) => {
// Create a `Release` event.
let mouse_xy = self.global_input.current.mouse.xy;
let release = event::Release {
button: event::Button::Mouse(mouse_button, mouse_xy),
modifiers: self.global_input.current.modifiers,
};
let widget = self.global_input.current.widget_capturing_mouse;
let release_event = event::Ui::Release(widget, release).into();
self.global_input.push_event(release_event);
// Check for `Click` and `DoubleClick` events.
let down = self.global_input.current.mouse.buttons[mouse_button].if_down();
if let Some((_, widget)) = down {
// The widget that's being clicked.
let clicked_widget = self.global_input.current.widget_under_mouse
.and_then(|released| widget.and_then(|pressed| {
if pressed == released { Some(released) } else { None }
}));
let click = event::Click {
button: mouse_button,
xy: self.global_input.current.mouse.xy,
modifiers: self.global_input.current.modifiers,
};
let click_event = event::Ui::Click(clicked_widget, click).into();
self.global_input.push_event(click_event);
let now = std::time::Instant::now();
let double_click = self.global_input.last_click
.and_then(|(last_time, last_click)| {
// If the button of this click is different to the button
// of last click, don't create a `DoubleClick`.
if click.button != last_click.button {
return None;
}
// If the mouse has moved since the last click, don't
// create a `DoubleClick`.
if click.xy != last_click.xy {
return None;
}
// If the duration since the last click is longer than the
// double_click_threshold, don't create a `DoubleClick`.
let duration = now.duration_since(last_time);
// TODO: Work out how to get this threshold from the user's
// system preferences.
let threshold = self.theme.double_click_threshold;
if duration >= threshold {
return None;
}
Some(event::DoubleClick {
button: click.button,
xy: click.xy,
modifiers: click.modifiers,
})
});
if let Some(double_click) = double_click {
// Reset the `last_click` to `None`, as to not register another
// `DoubleClick` on the next consecutive `Click`.
self.global_input.last_click = None;
let double_click_event =
event::Ui::DoubleClick(clicked_widget, double_click).into();
self.global_input.push_event(double_click_event);
} else {
// Set the `Click` that we just stored as the `last_click`.
self.global_input.last_click = Some((now, click));
}
}
// Uncapture widget capturing mouse if MouseButton::Left is down and
// widget_under_mouse != capturing widget.
if let MouseButton::Left = mouse_button {
if let Some(idx) = self.global_input.current.widget_capturing_mouse {
if Some(idx) != self.global_input.current.widget_under_mouse {
let event = event::Ui::WidgetUncapturesMouse(idx).into();
self.global_input.push_event(event);
self.global_input.current.widget_capturing_mouse = None;
}
}
}
// Release the given mouse_button from the input::State.
self.global_input.current.mouse.buttons.release(mouse_button);
},
Button::Keyboard(key) => {
// Create a `Release` event.
let release = event::Release {
button: event::Button::Keyboard(key),
modifiers: self.global_input.current.modifiers,
};
let widget = self.global_input.current.widget_capturing_keyboard;
let release_event = event::Ui::Release(widget, release).into();
self.global_input.push_event(release_event);
// If a modifier key was released, remove it from the current set.
if let Some(modifier) = filter_modifier(key) {
self.global_input.current.modifiers.remove(modifier);
}
},
_ => (),
},
// The window was resized.
Input::Resize(w, h) => {
// Create a `WindowResized` event.
let (w, h) = (w as Scalar, h as Scalar);
let window_resized = event::Ui::WindowResized([w, h]).into();
self.global_input.push_event(window_resized);
self.win_w = w;
self.win_h = h;
self.needs_redraw();
track_widget_under_mouse_and_update_capturing(self);
},
// The mouse cursor was moved to a new position.
//
// Checks for events in the following order:
// 1. `Drag`
// 2. `WidgetUncapturesMouse`
// 3. `WidgetCapturesMouse`
Input::Move(motion) => {
// Create a `Move` event.
let move_ = event::Move {
motion: motion,
modifiers: self.global_input.current.modifiers,
};
let widget = self.global_input.current.widget_capturing_mouse;
let move_event = event::Ui::Move(widget, move_).into();
self.global_input.push_event(move_event);
match motion {
Motion::MouseCursor(x, y) => {
// Check for drag events.
let last_mouse_xy = self.global_input.current.mouse.xy;
let mouse_xy = [x, y];
let delta_xy = utils::vec2_sub(mouse_xy, last_mouse_xy);
let distance = (delta_xy[0] + delta_xy[1]).abs().sqrt();
if distance > self.theme.mouse_drag_threshold {
// For each button that is down, trigger a drag event.
let buttons = self.global_input.current.mouse.buttons.clone();
for (btn, btn_xy, widget) in buttons.pressed() {
let total_delta_xy = utils::vec2_sub(mouse_xy, btn_xy);
let event = event::Ui::Drag(widget, event::Drag {
button: btn,
origin: btn_xy,
from: last_mouse_xy,
to: mouse_xy,
delta_xy: delta_xy,
total_delta_xy: total_delta_xy,
modifiers: self.global_input.current.modifiers,
}).into();
self.global_input.push_event(event);
}
}
// Update the position of the mouse within the global_input's
// input::State.
self.global_input.current.mouse.xy = mouse_xy;
track_widget_under_mouse_and_update_capturing(self);
},
// The mouse was scrolled.
Motion::MouseScroll(x, y) => {
let mut scrollable_widgets = {
let depth_order = &self.depth_order.indices;
let mouse_xy = self.global_input.current.mouse.xy;
graph::algo::pick_scrollable_widgets(depth_order, mouse_xy)
};
// Iterate through the scrollable widgets from top to bottom.
//
// A scroll event will be created for the first scrollable widget
// that hasn't already reached the bound of the scroll event's
// direction.
while let Some(idx) =
scrollable_widgets.next(&self.widget_graph,
&self.depth_order.indices)
{
let (kid_area, maybe_x_scroll, maybe_y_scroll) =
match self.widget_graph.widget(idx) {
Some(widget) => {
(widget.kid_area,
widget.maybe_x_scroll_state,
widget.maybe_y_scroll_state)
},
None => continue,
};
fn offset_is_at_bound<A>(scroll: &widget::scroll::State<A>,
additional_offset: Scalar) -> bool
{
use utils;
fn approx_eq(a: Scalar, b: Scalar) -> bool {
(a - b).abs() < 0.000001
}
if additional_offset.is_sign_positive() {
let max = utils::partial_max(scroll.offset_bounds.start,
scroll.offset_bounds.end);
approx_eq(scroll.offset, max)
} else {
let min = utils::partial_min(scroll.offset_bounds.start,
scroll.offset_bounds.end);
approx_eq(scroll.offset, min)
}
}
let mut scroll_x = false;
let mut scroll_y = false;
// Check whether the x axis is scrollable.
if x != 0.0 {
let new_scroll =
widget::scroll::State::update(self, idx, &kid_area,
maybe_x_scroll, x);
if let Some(prev_scroll) = maybe_x_scroll {
let (prev_is_at_bound, new_is_at_bound) =
(offset_is_at_bound(&prev_scroll, x),
offset_is_at_bound(&new_scroll, x));
scroll_x = !prev_is_at_bound || !new_is_at_bound;
}
}
// Check whether the y axis is scrollable.
if y != 0.0 {
let new_scroll =
widget::scroll::State::update(self, idx, &kid_area,
maybe_y_scroll, y);
if let Some(prev_scroll) = maybe_y_scroll {
let (prev_is_at_bound, new_is_at_bound) =
(offset_is_at_bound(&prev_scroll, y),
offset_is_at_bound(&new_scroll, y));
scroll_y = !prev_is_at_bound || !new_is_at_bound;
}
}
// Create a `Scroll` event if either axis is scrollable.
if scroll_x || scroll_y {
let event = event::Ui::Scroll(Some(idx), event::Scroll {
x: x,
y: y,
modifiers: self.global_input.current.modifiers,
}).into();
self.global_input.push_event(event);
// Now that we've scrolled the top, scrollable widget,
// we're done with the loop.
break;
}
}
// If no scrollable widgets could be scrolled, emit the event to
// the widget that currently captures the mouse.
if x != 0.0 || y != 0.0 {
let widget = self.global_input.current.widget_capturing_mouse;
if let Some(idx) = widget {
if let Some(widget) = self.widget_graph.widget(idx) {
// Only create the event if the widget is not
// scrollable, as the event would have already been
// created within the above loop.
if widget.maybe_x_scroll_state.is_none()
&& widget.maybe_y_scroll_state.is_none() {
let scroll = event::Scroll {
x: x,
y: y,
modifiers: self.global_input.current.modifiers,
};
let event = event::Ui::Scroll(Some(idx), scroll);
self.global_input.push_event(event.into());
}
}
}
}
// Now that there might be a different widget under the mouse, we
// must update the capturing state.
track_widget_under_mouse_and_update_capturing(self);
},
_ => (),
}
},
Input::Text(string) => {
// Create a `Text` event.
let text = event::Text {
string: string,
modifiers: self.global_input.current.modifiers,
};
let widget = self.global_input.current.widget_capturing_keyboard;
let text_event = event::Ui::Text(widget, text).into();
self.global_input.push_event(text_event);
},
_ => (),
}
}
/// Get the centred xy coords for some given `Dimension`s, `Position` and alignment.
///
/// If getting the xy for a specific widget, its `widget::Id` should be specified so that we
/// can also consider the scroll offset of the scrollable parent widgets.
///
/// The `place_on_kid_area` argument specifies whether or not **Place** **Position** variants
/// should target a **Widget**'s `kid_area`, or simply the **Widget**'s total area.
pub fn calc_xy(&self,
maybe_id: Option<widget::Id>,
x_position: Position,
y_position: Position,
dim: Dimensions,
place_on_kid_area: bool) -> Point
{
use utils::vec2_add;
// Retrieves the absolute **Scalar** position from the given position for a single axis.
//
// The axis used is specified by the given range_from_rect function which, given some
// **Rect**, returns the relevant **Range**.
fn abs_from_position<R, P>(ui: &Ui,
position: Position,
dim: Scalar,
place_on_kid_area: bool,
range_from_rect: R,
start_and_end_pad: P) -> Scalar
where R: FnOnce(Rect) -> Range,
P: FnOnce(Padding) -> Range,
{
match position {
Position::Absolute(abs) => abs,
Position::Relative(rel, maybe_id) =>
maybe_id.or(ui.maybe_prev_widget_id).or(Some(ui.window.into()))
.and_then(|idx| ui.rect_of(idx).map(range_from_rect))
.map(|other_range| other_range.middle() + rel)
.unwrap_or(rel),
Position::Direction(direction, amt, maybe_id) =>
maybe_id.or(ui.maybe_prev_widget_id)
.and_then(|idx| ui.rect_of(idx).map(range_from_rect))
.map(|other_range| {
let range = Range::from_pos_and_len(0.0, dim);
match direction {
Direction::Forwards => range.align_after(other_range).middle() + amt,
Direction::Backwards => range.align_before(other_range).middle() - amt,
}
})
.unwrap_or_else(|| match direction {
Direction::Forwards => amt,
Direction::Backwards => -amt,
}),
Position::Align(align, maybe_id) =>
maybe_id.or(ui.maybe_prev_widget_id).or(Some(ui.window.into()))
.and_then(|idx| ui.rect_of(idx).map(range_from_rect))
.map(|other_range| {
let range = Range::from_pos_and_len(0.0, dim);
match align {
Align::Start => range.align_start_of(other_range).middle(),
Align::Middle => other_range.middle(),
Align::End => range.align_end_of(other_range).middle(),
}
})
.unwrap_or(0.0),
Position::Place(place, maybe_id) => {
let parent_id = maybe_id
.or(ui.maybe_current_parent_id)
.unwrap_or(ui.window.into());
let maybe_area = match place_on_kid_area {
true => ui.widget_graph.widget(parent_id)
.map(|w| w.kid_area)
.map(|k| (range_from_rect(k.rect), start_and_end_pad(k.pad))),
false => ui.rect_of(parent_id)
.map(|rect| (range_from_rect(rect), Range::new(0.0, 0.0))),
};
maybe_area
.map(|(parent_range, pad)| {
let range = Range::from_pos_and_len(0.0, dim);
let parent_range = parent_range.pad_start(pad.start).pad_end(pad.end);
match place {
Place::Start(maybe_mgn) =>
range.align_start_of(parent_range).middle() + maybe_mgn.unwrap_or(0.0),
Place::Middle =>
parent_range.middle(),
Place::End(maybe_mgn) =>
range.align_end_of(parent_range).middle() - maybe_mgn.unwrap_or(0.0),
}
})
.unwrap_or(0.0)
},
}
}
fn x_range(rect: Rect) -> Range { rect.x }
fn y_range(rect: Rect) -> Range { rect.y }
fn x_pad(pad: Padding) -> Range { pad.x }
fn y_pad(pad: Padding) -> Range { pad.y }
let x = abs_from_position(self, x_position, dim[0], place_on_kid_area, x_range, x_pad);
let y = abs_from_position(self, y_position, dim[1], place_on_kid_area, y_range, y_pad);
let xy = [x, y];
// Add the widget's parents' total combined scroll offset to the given xy.
maybe_id
.map(|idx| vec2_add(xy, graph::algo::scroll_offset(&self.widget_graph, idx)))
.unwrap_or(xy)
}
/// A function within which all widgets are instantiated by the user, normally situated within
/// the "update" stage of an event loop.
pub fn set_widgets(&mut self) -> UiCell {
self.maybe_prev_widget_id = None;
self.maybe_current_parent_id = None;
// Move the previous `updated_widgets` to `prev_updated_widgets` and clear
// `updated_widgets` so that we're ready to store the newly updated widgets.
{
let Ui { ref mut updated_widgets, ref mut prev_updated_widgets, .. } = *self;
std::mem::swap(updated_widgets, prev_updated_widgets);
updated_widgets.clear();
}
let mut ui_cell = UiCell { ui: self };
// Instantiate the root `Window` `Widget`.
//
// This widget acts as the parent-most widget and root node for the Ui's `widget_graph`,
// upon which all other widgets are placed.
{
use {color, Colorable, Borderable, Positionable, Widget};
type Window = widget::BorderedRectangle;
Window::new([ui_cell.win_w, ui_cell.win_h])
.no_parent()
.x_y(0.0, 0.0)
.border(0.0)
.border_color(color::BLACK.alpha(0.0))
.color(ui_cell.maybe_background_color.unwrap_or(color::BLACK.alpha(0.0)))
.set(ui_cell.window, &mut ui_cell);
}
ui_cell.ui.maybe_current_parent_id = Some(ui_cell.window.into());
ui_cell
}
/// Set the number of frames that the `Ui` should draw in the case that `needs_redraw` is
/// called. The default is `3` (see the SAFE_REDRAW_COUNT docs for details).
pub fn set_num_redraw_frames(&mut self, num_frames: u8) {
self.num_redraw_frames = num_frames;
}
/// Tells the `Ui` that it needs to be re-draw everything. It does this by setting the redraw
/// count to `num_redraw_frames`. See the docs for `set_num_redraw_frames`, SAFE_REDRAW_COUNT
/// or `draw_if_changed` for more info on how/why the redraw count is used.
pub fn needs_redraw(&mut self) {
self.redraw_count = self.num_redraw_frames;
}
/// The first of the `Primitivees` yielded by `Ui::draw` or `Ui::draw_if_changed` will always
/// be a `Rectangle` the size of the window in which conrod is hosted.
///
/// This method sets the colour with which this `Rectangle` is drawn (the default being
/// `conrod::color::TRANSPARENT`.
pub fn clear_with(&mut self, color: Color) {
self.maybe_background_color = Some(color);
}
/// Draw the `Ui` in it's current state.
///
/// NOTE: If you don't need to redraw your conrod GUI every frame, it is recommended to use the
/// `Ui::draw_if_changed` method instead.
pub fn draw(&mut self) -> render::Primitives {
let Ui {
ref mut redraw_count,
ref widget_graph,
ref depth_order,
ref theme,
ref fonts,
win_w, win_h,
..
} = *self;
// Use the depth_order indices as the order for drawing.
let indices = &depth_order.indices;
// We're about to draw everything, so take one from the redraw count.
if *redraw_count > 0 {
*redraw_count -= 1;
}
render::Primitives::new(widget_graph, indices, theme, fonts, [win_w, win_h])
}
/// Same as the `Ui::draw` method, but *only* draws if the `redraw_count` is greater than 0.
///
/// The `redraw_count` is set to `SAFE_REDRAW_COUNT` whenever a `Widget` indicates that it
/// needs to be re-drawn.
///
/// It can also be triggered manually by the user using the `Ui::needs_redraw` method.
///
/// This method is generally preferred over `Ui::draw` as it requires far less CPU usage, only
/// redrawing to the screen if necessary.
///
/// Note that when `Ui::needs_redraw` is triggered, it sets the `redraw_count` to 3 by default.
/// This ensures that conrod is drawn to each buffer in the case that there is buffer swapping
/// happening. Let us know if you need finer control over this and we'll expose a way for you
/// to set the redraw count manually.
pub fn draw_if_changed(&mut self) -> Option<render::Primitives> {
if self.redraw_count > 0 {
return Some(self.draw())
}
None
}
/// The **Rect** that bounds the kids of the widget with the given index.
pub fn kids_bounding_box(&self, id: widget::Id) -> Option<Rect> {
graph::algo::kids_bounding_box(&self.widget_graph, &self.prev_updated_widgets, id)
}
/// The **Rect** that represents the maximum fully visible area for the widget with the given
/// index, including consideration of cropped scroll area.
///
/// Otherwise, return None if the widget is not visible.
pub fn visible_area(&self, id: widget::Id) -> Option<Rect> {
graph::algo::cropped_area_of_widget(&self.widget_graph, id)
}
}
impl<'a> UiCell<'a> {
/// A reference to the `Theme` that is currently active within the `Ui`.
pub fn theme(&self) -> &Theme { &self.ui.theme }
/// A convenience method for borrowing the `Font` for the given `Id` if it exists.
pub fn font(&self, id: text::font::Id) -> Option<&text::Font> {
self.ui.fonts.get(id)
}
/// Returns the dimensions of the window
pub fn window_dim(&self) -> Dimensions {
[self.ui.win_w, self.ui.win_h]
}
/// Returns an immutable reference to the `input::Global` of the `Ui`.
///
/// All coordinates here will be relative to the center of the window.
pub fn global_input(&self) -> &input::Global {
&self.ui.global_input
}
/// Returns a `input::Widget` with input events for the widget.
///
/// All coordinates in the `input::Widget` will be relative to the widget at the given index.
pub fn widget_input(&self, id: widget::Id) -> input::Widget {
self.ui.widget_input(id)
}
/// Produces a type that may be used to generate new unique `widget::Id`s.
///
/// See the [**widget::id::Generator**](../widget/id/struct.Generator.html) docs for details on
/// how to use this correctly.
pub fn widget_id_generator(&mut self) -> widget::id::Generator {
self.ui.widget_id_generator()
}
/// The **Rect** that bounds the kids of the widget with the given index.
///
/// Returns `None` if the widget has no children or if there's is no widget for the given index.
pub fn kids_bounding_box(&self, id: widget::Id) -> Option<Rect> {
self.ui.kids_bounding_box(id)
}
/// Scroll the widget at the given index by the given offset amount.
///
/// The produced `Scroll` event will be pushed to the `pending_scroll_events` and will be
/// applied to the widget during the next call to `Ui::set_widgets`.
pub fn scroll_widget(&mut self, id: widget::Id, offset: [Scalar; 2]) {
let (x, y) = (offset[0], offset[1]);
if x != 0.0 || y != 0.0 {
let event = event::Ui::Scroll(Some(id), event::Scroll {
x: x,
y: y,
modifiers: self.ui.global_input.current.modifiers,
});
self.ui.pending_scroll_events.push(event);
}
}
}
impl<'a> Drop for UiCell<'a> {
fn drop(&mut self) {
// We'll need to re-draw if we have gained or lost widgets.
if self.ui.updated_widgets != self.ui.prev_updated_widgets {
self.ui.needs_redraw();
}
// Update the **DepthOrder** so that it reflects the **Graph**'s current state.
{
let Ui {
ref widget_graph,
ref mut depth_order,
window,
ref updated_widgets,
..
} = *self.ui;
depth_order.update(widget_graph, window, updated_widgets);
}
// Reset the global input state. Note that this is the **only** time this should be called.
self.ui.global_input.clear_events_and_update_start_state();
// Move all pending `Scroll` events that have been produced since the start of this method
// into the `global_input` event buffer.
for scroll_event in self.ui.pending_scroll_events.drain(0..) {
self.ui.global_input.push_event(scroll_event.into());
}
}
}
impl<'a> ::std::ops::Deref for UiCell<'a> {
type Target = Ui;
fn deref(&self) -> &Ui {
self.ui
}
}
impl<'a> AsRef<Ui> for UiCell<'a> {
fn as_ref(&self) -> &Ui {
&self.ui
}
}
/// A function for retrieving the `&mut Ui<B>` from a `UiCell<B>`.
///
/// This function is only for internal use to allow for some `Ui` type acrobatics in order to
/// provide a nice *safe* API for the user.
pub fn ref_mut_from_ui_cell<'a, 'b: 'a>(ui_cell: &'a mut UiCell<'b>) -> &'a mut Ui {
ui_cell.ui
}
/// A mutable reference to the given `Ui`'s widget `Graph`.
pub fn widget_graph_mut(ui: &mut Ui) -> &mut Graph {
&mut ui.widget_graph
}
/// Infer a widget's `Depth` parent by examining it's *x* and *y* `Position`s.
///
/// When a different parent may be inferred from either `Position`, the *x* `Position` is favoured.
pub fn infer_parent_from_position(ui: &Ui, x: Position, y: Position) -> Option<widget::Id> {
use Position::{Place, Relative, Direction, Align};
match (x, y) {
(Place(_, maybe_parent_id), _) | (_, Place(_, maybe_parent_id)) =>
maybe_parent_id,
(Direction(_, _, maybe_id), _) | (_, Direction(_, _, maybe_id)) |
(Align(_, maybe_id), _) | (_, Align(_, maybe_id)) |
(Relative(_, maybe_id), _) | (_, Relative(_, maybe_id)) =>
maybe_id.or(ui.maybe_prev_widget_id)
.and_then(|idx| ui.widget_graph.depth_parent(idx)),
_ => None,
}
}
/// Attempts to infer the parent of a widget from its *x*/*y* `Position`s and the current state of
/// the `Ui`.
///
/// If no parent can be inferred via the `Position`s, the `maybe_current_parent_id` will be used.
///
/// If `maybe_current_parent_id` is `None`, the `Ui`'s `window` widget will be used.
///
/// **Note:** This function does not check whether or not using the `window` widget would cause a
/// cycle.
pub fn infer_parent_unchecked(ui: &Ui, x_pos: Position, y_pos: Position) -> widget::Id {
infer_parent_from_position(ui, x_pos, y_pos)
.or(ui.maybe_current_parent_id)
.unwrap_or(ui.window.into())
}
/// Cache some `PreUpdateCache` widget data into the widget graph.
/// Set the widget that is being cached as the new `prev_widget`.
/// Set the widget's parent as the new `current_parent`.
pub fn pre_update_cache(ui: &mut Ui, widget: widget::PreUpdateCache) {
ui.maybe_prev_widget_id = Some(widget.id);
ui.maybe_current_parent_id = widget.maybe_parent_id;
let widget_id = widget.id;
ui.widget_graph.pre_update_cache(ui.window, widget, ui.updated_widgets.len());
// Add the widget's `widget::Id` to the set of updated widgets.
ui.updated_widgets.insert(widget_id);
}
/// Cache some `PostUpdateCache` widget data into the widget graph.
/// Set the widget that is being cached as the new `prev_widget`.
/// Set the widget's parent as the new `current_parent`.
pub fn post_update_cache<W>(ui: &mut Ui, widget: widget::PostUpdateCache<W>)
where W: Widget,
W::State: 'static,
W::Style: 'static,
{
ui.maybe_prev_widget_id = Some(widget.id);
ui.maybe_current_parent_id = widget.maybe_parent_id;
ui.widget_graph.post_update_cache(widget);
}