use {CharacterCache, Scalar};
use backend::graphics::{Context, Graphics};
use color::Color;
use glyph_cache::GlyphCache;
use graph::{self, Graph, NodeIndex};
use mouse::{self, Mouse};
use input;
use input::{
GenericEvent,
MouseCursorEvent,
MouseScrollEvent,
PressEvent,
ReleaseEvent,
RenderEvent,
ResizeEvent,
TextEvent,
};
use position::{Align, Direction, Dimensions, Padding, Place, Point, Position, Range, Rect};
use std::collections::HashSet;
use std::io::Write;
use theme::Theme;
use widget::{self, Widget};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Capturing {
Captured(widget::Index),
JustReleased,
}
pub struct Ui<C> {
pub theme: Theme,
pub glyph_cache: GlyphCache<C>,
pub window: NodeIndex,
pub win_w: f64,
pub win_h: f64,
widget_graph: Graph,
mouse: Mouse,
keys_just_pressed: Vec<input::keyboard::Key>,
keys_just_released: Vec<input::keyboard::Key>,
text_just_entered: Vec<String>,
maybe_prev_widget_idx: Option<widget::Index>,
maybe_current_parent_idx: Option<widget::Index>,
maybe_widget_under_mouse: Option<widget::Index>,
maybe_top_scrollable_widget_under_mouse: Option<widget::Index>,
maybe_captured_mouse: Option<Capturing>,
maybe_captured_keyboard: Option<Capturing>,
num_redraw_frames: u8,
redraw_count: u8,
maybe_background_color: Option<Color>,
depth_order: graph::DepthOrder,
updated_widgets: HashSet<NodeIndex>,
prev_updated_widgets: HashSet<NodeIndex>,
}
#[derive(Clone, Debug)]
pub struct UserInput<'a> {
pub maybe_mouse: Option<Mouse>,
pub global_mouse: Mouse,
pub pressed_keys: &'a [input::keyboard::Key],
pub released_keys: &'a [input::keyboard::Key],
pub entered_text: &'a [String],
pub window_dim: Dimensions,
}
pub const SAFE_REDRAW_COUNT: u8 = 3;
impl<C> Ui<C> {
pub fn new(character_cache: C, theme: Theme) -> Self {
let widget_graph = Graph::new();
let depth_order = graph::DepthOrder::new();
let updated_widgets = HashSet::new();
Self::new_internal(character_cache, theme, widget_graph, depth_order, updated_widgets)
}
pub fn with_capacity(character_cache: C, theme: Theme, n_widgets: usize) -> Self {
let widget_graph = Graph::with_node_capacity(n_widgets);
let depth_order = graph::DepthOrder::with_node_capacity(n_widgets);
let updated_widgets = HashSet::with_capacity(n_widgets);
Self::new_internal(character_cache, theme, widget_graph, depth_order, updated_widgets)
}
fn new_internal(character_cache: C,
theme: Theme,
mut widget_graph: Graph,
depth_order: graph::DepthOrder,
updated_widgets: HashSet<NodeIndex>) -> Self
{
let window = widget_graph.add_placeholder();
let prev_updated_widgets = updated_widgets.clone();
Ui {
widget_graph: widget_graph,
theme: theme,
window: window,
mouse: Mouse::new(),
keys_just_pressed: Vec::with_capacity(10),
keys_just_released: Vec::with_capacity(10),
text_just_entered: Vec::with_capacity(10),
glyph_cache: GlyphCache::new(character_cache),
win_w: 0.0,
win_h: 0.0,
maybe_prev_widget_idx: None,
maybe_current_parent_idx: None,
maybe_widget_under_mouse: None,
maybe_top_scrollable_widget_under_mouse: None,
maybe_captured_mouse: None,
maybe_captured_keyboard: 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,
}
}
pub fn rect_of<I: Into<widget::Index>>(&self, idx: I) -> Option<Rect> {
let idx: widget::Index = idx.into();
self.widget_graph.widget(idx).map(|widget| widget.rect)
}
pub fn w_of<I: Into<widget::Index>>(&self, idx: I) -> Option<Scalar> {
self.rect_of(idx).map(|rect| rect.w())
}
pub fn h_of<I: Into<widget::Index>>(&self, idx: I) -> Option<Scalar> {
self.rect_of(idx).map(|rect| rect.h())
}
pub fn wh_of<I: Into<widget::Index>>(&self, idx: I) -> Option<Dimensions> {
self.rect_of(idx).map(|rect| rect.dim())
}
pub fn xy_of<I: Into<widget::Index>>(&self, idx: I) -> Option<Point> {
self.rect_of(idx).map(|rect| rect.xy())
}
pub fn kid_area_of<I: Into<widget::Index>>(&self, idx: I) -> Option<Rect> {
let idx: widget::Index = idx.into();
self.widget_graph.widget(idx).map(|widget| {
widget.kid_area.rect.padding(widget.kid_area.pad)
})
}
pub fn maybe_prev_widget(&self) -> Option<widget::Index> {
self.maybe_prev_widget_idx
}
pub fn widget_graph(&self) -> &Graph {
&self.widget_graph
}
pub fn updated_widgets(&self) -> &HashSet<NodeIndex> {
&self.updated_widgets
}
pub fn prev_updated_widgets(&self) -> &HashSet<NodeIndex> {
&self.prev_updated_widgets
}
pub fn handle_event<E: GenericEvent>(&mut self, event: &E) {
event.resize(|w, h| {
self.win_w = w as f64;
self.win_h = h as f64;
self.needs_redraw();
});
event.render(|args| {
self.win_w = args.width as f64;
self.win_h = args.height as f64;
});
event.mouse_cursor(|x, y| {
self.mouse.xy = [x - self.win_w / 2.0, -(y - self.win_h / 2.0)];
});
event.mouse_scroll(|x, y| {
self.mouse.scroll.x += x;
self.mouse.scroll.y += y;
});
event.press(|button_type| {
use input::Button;
use input::MouseButton::{Left, Middle, Right};
match button_type {
Button::Mouse(button) => {
let mouse_button = match button {
Left => &mut self.mouse.left,
Right => &mut self.mouse.right,
Middle => &mut self.mouse.middle,
_ => &mut self.mouse.unknown,
};
mouse_button.position = mouse::ButtonPosition::Down;
mouse_button.was_just_pressed = true;
},
Button::Keyboard(key) => self.keys_just_pressed.push(key),
_ => {}
}
});
event.release(|button_type| {
use input::Button;
use input::MouseButton::{Left, Middle, Right};
match button_type {
Button::Mouse(button) => {
let mouse_button = match button {
Left => &mut self.mouse.left,
Right => &mut self.mouse.right,
Middle => &mut self.mouse.middle,
_ => &mut self.mouse.unknown,
};
mouse_button.position = mouse::ButtonPosition::Up;
mouse_button.was_just_released = true;
},
Button::Keyboard(key) => self.keys_just_released.push(key),
_ => {}
}
});
event.text(|text| {
self.text_just_entered.push(text.to_string())
});
}
pub fn calc_xy(&self,
maybe_idx: Option<widget::Index>,
x_position: Position,
y_position: Position,
dim: Dimensions,
place_on_kid_area: bool) -> Point
{
use vecmath::vec2_add;
fn abs_from_position<C, R, P>(ui: &Ui<C>,
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_idx) =>
maybe_idx.or(ui.maybe_prev_widget_idx).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_idx) =>
maybe_idx.or(ui.maybe_prev_widget_idx)
.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_idx) =>
maybe_idx.or(ui.maybe_prev_widget_idx).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_idx) => {
let parent_idx = maybe_idx
.or(ui.maybe_current_parent_idx)
.unwrap_or(ui.window.into());
let maybe_area = match place_on_kid_area {
true => ui.widget_graph.widget(parent_idx)
.map(|w| w.kid_area)
.map(|k| (range_from_rect(k.rect), start_and_end_pad(k.pad))),
false => ui.rect_of(parent_idx)
.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];
maybe_idx
.map(|idx| vec2_add(xy, graph::algo::scroll_offset(&self.widget_graph, idx)))
.unwrap_or(xy)
}
pub fn set_widgets<F>(&mut self, user_widgets_fn: F)
where C: CharacterCache,
F: FnOnce(&mut Self),
{
self.maybe_prev_widget_idx = None;
self.maybe_current_parent_idx = None;
if let Some(Capturing::JustReleased) = self.maybe_captured_mouse {
self.maybe_captured_mouse = None;
}
if let Some(Capturing::JustReleased) = self.maybe_captured_keyboard {
self.maybe_captured_keyboard = None;
}
self.maybe_widget_under_mouse =
graph::algo::pick_widget(&self.widget_graph, &self.depth_order.indices, self.mouse.xy);
self.maybe_top_scrollable_widget_under_mouse =
graph::algo::pick_scrollable_widget(&self.widget_graph, &self.depth_order.indices, self.mouse.xy);
{
let Ui { ref mut updated_widgets, ref mut prev_updated_widgets, .. } = *self;
::std::mem::swap(updated_widgets, prev_updated_widgets);
updated_widgets.clear();
}
{
use ::{color, Colorable, Frameable, FramedRectangle, Positionable, Widget};
type Window = FramedRectangle;
Window::new([self.win_w, self.win_h])
.no_parent()
.x_y(0.0, 0.0)
.frame(0.0)
.frame_color(color::BLACK.alpha(0.0))
.color(self.maybe_background_color.unwrap_or(color::BLACK.alpha(0.0)))
.set(self.window, self);
}
self.maybe_current_parent_idx = Some(self.window.into());
user_widgets_fn(self);
if self.updated_widgets != self.prev_updated_widgets {
self.needs_redraw();
}
let maybe_captured_mouse = match self.maybe_captured_mouse {
Some(Capturing::Captured(id)) => Some(id),
_ => None,
};
let maybe_captured_keyboard = match self.maybe_captured_keyboard {
Some(Capturing::Captured(id)) => Some(id),
_ => None,
};
{
let Ui {
ref widget_graph,
ref mut depth_order,
window,
ref updated_widgets,
..
} = *self;
depth_order.update(widget_graph,
window,
updated_widgets,
maybe_captured_mouse,
maybe_captured_keyboard);
}
self.keys_just_pressed.clear();
self.keys_just_released.clear();
self.text_just_entered.clear();
self.mouse.scroll = mouse::Scroll { x: 0.0, y: 0.0 };
self.mouse.left.reset_pressed_and_released();
self.mouse.middle.reset_pressed_and_released();
self.mouse.right.reset_pressed_and_released();
self.mouse.unknown.reset_pressed_and_released();
}
pub fn set_num_redraw_frames(&mut self, num_frames: u8) {
self.num_redraw_frames = num_frames;
}
pub fn needs_redraw(&mut self) {
self.redraw_count = self.num_redraw_frames;
}
pub fn draw<G>(&mut self, context: Context, graphics: &mut G)
where G: Graphics,
C: CharacterCache<Texture=G::Texture>,
{
use backend::graphics::{draw_from_graph, Transformed};
use std::ops::{Deref, DerefMut};
let Ui {
ref mut glyph_cache,
ref mut redraw_count,
ref widget_graph,
ref depth_order,
ref theme,
..
} = *self;
let view_size = context.get_view_size();
let context = context.trans(view_size[0] / 2.0, view_size[1] / 2.0).scale(1.0, -1.0);
let mut ref_mut_character_cache = glyph_cache.deref().borrow_mut();
let character_cache = ref_mut_character_cache.deref_mut();
let indices = &depth_order.indices;
draw_from_graph(context, graphics, character_cache, widget_graph, indices, theme);
if *redraw_count > 0 {
*redraw_count -= 1;
}
}
pub fn draw_if_changed<G>(&mut self, context: Context, graphics: &mut G)
where G: Graphics,
C: CharacterCache<Texture=G::Texture>,
{
if self.redraw_count > 0 {
self.draw(context, graphics);
}
}
pub fn kids_bounding_box<I: Into<widget::Index>>(&self, idx: I) -> Option<Rect> {
let idx: widget::Index = idx.into();
graph::algo::kids_bounding_box(&self.widget_graph, &self.prev_updated_widgets, idx)
}
pub fn visible_area<I: Into<widget::Index>>(&self, idx: I) -> Option<Rect> {
let idx: widget::Index = idx.into();
graph::algo::cropped_area_of_widget(&self.widget_graph, idx)
}
}
pub fn widget_graph_mut<C>(ui: &mut Ui<C>) -> &mut Graph {
&mut ui.widget_graph
}
pub fn infer_parent_from_position<C>(ui: &Ui<C>, x_pos: Position, y_pos: Position)
-> Option<widget::Index>
{
use Position::{Place, Relative, Direction, Align};
match (x_pos, y_pos) {
(Place(_, maybe_parent_idx), _) | (_, Place(_, maybe_parent_idx)) =>
maybe_parent_idx,
(Direction(_, _, maybe_idx), _) | (_, Direction(_, _, maybe_idx)) |
(Align(_, maybe_idx), _) | (_, Align(_, maybe_idx)) |
(Relative(_, maybe_idx), _) | (_, Relative(_, maybe_idx)) =>
maybe_idx.or(ui.maybe_prev_widget_idx)
.and_then(|idx| ui.widget_graph.depth_parent(idx)),
_ => None,
}
}
pub fn infer_parent_unchecked<C>(ui: &Ui<C>, x_pos: Position, y_pos: Position) -> widget::Index {
infer_parent_from_position(ui, x_pos, y_pos)
.or(ui.maybe_prev_widget_idx)
.unwrap_or(ui.window.into())
}
pub fn set_current_parent_idx<C>(ui: &mut Ui<C>, idx: widget::Index) {
ui.maybe_current_parent_idx = Some(idx);
}
pub fn user_input<'a, C>(ui: &'a Ui<C>, idx: widget::Index) -> UserInput<'a> {
let maybe_mouse = get_mouse_state(ui, idx);
let global_mouse = ui.mouse;
let without_keys = || UserInput {
maybe_mouse: maybe_mouse,
global_mouse: global_mouse,
pressed_keys: &[],
released_keys: &[],
entered_text: &[],
window_dim: [ui.win_w, ui.win_h],
};
let with_keys = || UserInput {
maybe_mouse: maybe_mouse,
global_mouse: global_mouse,
pressed_keys: &ui.keys_just_pressed,
released_keys: &ui.keys_just_released,
entered_text: &ui.text_just_entered,
window_dim: [ui.win_w, ui.win_h],
};
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_idx)) =>
if idx == captured_idx { with_keys() }
else { without_keys() },
Some(Capturing::JustReleased) => without_keys(),
None => with_keys(),
}
}
pub fn get_mouse_state<C>(ui: &Ui<C>, idx: widget::Index) -> Option<Mouse> {
match ui.maybe_captured_mouse {
Some(Capturing::Captured(captured_idx)) =>
if idx == captured_idx { Some(ui.mouse) } else { None },
Some(Capturing::JustReleased) =>
None,
None => match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_idx)) =>
if idx == captured_idx { Some(ui.mouse) } else { None },
_ =>
if Some(idx) == ui.maybe_widget_under_mouse
|| Some(idx) == ui.maybe_top_scrollable_widget_under_mouse {
Some(ui.mouse)
} else {
None
},
},
}
}
pub fn mouse_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
if let None = ui.maybe_captured_mouse {
ui.maybe_captured_mouse = Some(Capturing::Captured(idx));
return true;
}
false
}
pub fn mouse_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
if ui.maybe_captured_mouse == Some(Capturing::Captured(idx)) {
ui.maybe_captured_mouse = Some(Capturing::JustReleased);
return true;
}
false
}
pub fn keyboard_captured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_idx)) => if idx != captured_idx {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to capture the keyboard, however it is \
already captured by {:?}.", idx, captured_idx).unwrap();
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to capture the keyboard, however it was \
already captured.", idx).unwrap();
},
None => {
ui.maybe_captured_keyboard = Some(Capturing::Captured(idx));
return true;
},
}
false
}
pub fn keyboard_uncaptured_by<C>(ui: &mut Ui<C>, idx: widget::Index) -> bool {
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_idx)) => if idx != captured_idx {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however it is \
actually captured by {:?}.", idx, captured_idx).unwrap();
} else {
ui.maybe_captured_keyboard = Some(Capturing::JustReleased);
return true;
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however it had \
already been released this cycle.", idx).unwrap();
},
None => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however the mouse \
was not captured", idx).unwrap();
},
}
false
}
pub fn pre_update_cache<C>(ui: &mut Ui<C>, widget: widget::PreUpdateCache) where
C: CharacterCache,
{
ui.maybe_prev_widget_idx = Some(widget.idx);
ui.maybe_current_parent_idx = widget.maybe_parent_idx;
let widget_idx = widget.idx;
ui.widget_graph.pre_update_cache(ui.window, widget, ui.updated_widgets.len());
let node_idx = ui.widget_graph.node_index(widget_idx).expect("No NodeIndex");
ui.updated_widgets.insert(node_idx);
}
pub fn post_update_cache<C, W>(ui: &mut Ui<C>, widget: widget::PostUpdateCache<W>) where
C: CharacterCache,
W: Widget,
W::State: 'static,
W::Style: 'static,
{
ui.maybe_prev_widget_idx = Some(widget.idx);
ui.maybe_current_parent_idx = widget.maybe_parent_idx;
ui.widget_graph.post_update_cache(widget);
}
pub fn clear_with<C>(ui: &mut Ui<C>, color: Color) {
ui.maybe_background_color = Some(color);
}