use color::Color;
use elmesque::Element;
use graph::Graph;
use graphics::{Context, Graphics};
use graphics::character::CharacterCache;
use label::FontSize;
use mouse::{ButtonState, Mouse, Scroll};
use input;
use input::{
GenericEvent,
MouseCursorEvent,
MouseScrollEvent,
PressEvent,
ReleaseEvent,
RenderEvent,
TextEvent,
};
use position::{Dimensions, HorizontalAlign, Padding, Point, Position, VerticalAlign};
use std::any::Any;
use std::cell::RefCell;
use std::io::Write;
use theme::Theme;
use widget::{self, Widget, WidgetId};
#[derive(Copy, Clone, Debug)]
enum Capturing {
Captured(WidgetId),
JustReleased,
}
pub struct Ui<C> {
pub theme: Theme,
pub glyph_cache: GlyphCache<C>,
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>,
prev_event_was_render: bool,
maybe_prev_widget_id: Option<WidgetId>,
maybe_current_parent_id: Option<WidgetId>,
maybe_widget_under_mouse: Option<WidgetId>,
maybe_top_scrollable_widget_under_mouse: Option<WidgetId>,
maybe_captured_mouse: Option<Capturing>,
maybe_captured_keyboard: Option<Capturing>,
num_redraw_frames: u8,
redraw_count: u8,
maybe_background_color: Option<Color>,
maybe_element: Option<Element>,
}
#[derive(Clone, Debug)]
pub struct UserInput<'a> {
pub maybe_mouse: Option<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 struct GlyphCache<C>(RefCell<C>);
impl<C> GlyphCache<C> where C: CharacterCache {
pub fn char_width(&self, font_size: FontSize, ch: char) -> f64 {
self.0.borrow_mut().character(font_size, ch).width()
}
pub fn width(&self, font_size: FontSize, text: &str) -> f64 {
self.0.borrow_mut().width(font_size, text)
}
}
impl<C> ::std::ops::Deref for GlyphCache<C> {
type Target = RefCell<C>;
fn deref<'a>(&'a self) -> &'a RefCell<C> { &self.0 }
}
impl<C> ::std::ops::DerefMut for GlyphCache<C> {
fn deref_mut<'a>(&'a mut self) -> &'a mut RefCell<C> { &mut self.0 }
}
pub const SAFE_REDRAW_COUNT: u8 = 3;
impl<C> Ui<C> {
pub fn new(character_cache: C, theme: Theme) -> Ui<C> {
const GRAPH_CAPACITY: usize = 512;
Ui {
widget_graph: Graph::with_capacity(GRAPH_CAPACITY),
theme: theme,
mouse: Mouse::new([0.0, 0.0], ButtonState::Up, ButtonState::Up, ButtonState::Up),
keys_just_pressed: Vec::with_capacity(10),
keys_just_released: Vec::with_capacity(10),
text_just_entered: Vec::with_capacity(10),
glyph_cache: GlyphCache(RefCell::new(character_cache)),
prev_event_was_render: false,
win_w: 0.0,
win_h: 0.0,
maybe_prev_widget_id: None,
maybe_current_parent_id: 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,
maybe_element: None,
}
}
pub fn widget_size(&self, id: WidgetId) -> Dimensions {
self.widget_graph[id].dim
}
pub fn handle_event<E: GenericEvent>(&mut self, event: &E) {
if self.prev_event_was_render {
self.keys_just_pressed.clear();
self.keys_just_released.clear();
self.text_just_entered.clear();
self.mouse.scroll = Scroll { x: 0.0, y: 0.0 };
self.maybe_prev_widget_id = None;
self.maybe_current_parent_id = None;
self.prev_event_was_render = false;
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;
}
}
event.render(|args| {
self.win_w = args.width as f64;
self.win_h = args.height as f64;
self.prev_event_was_render = true;
self.maybe_widget_under_mouse = self.widget_graph.pick_widget(self.mouse.xy);
self.maybe_top_scrollable_widget_under_mouse =
self.widget_graph.pick_top_scrollable_widget(self.mouse.xy);
});
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) => {
*match button {
Left => &mut self.mouse.left,
Right => &mut self.mouse.right,
Middle => &mut self.mouse.middle,
_ => &mut self.mouse.unknown,
} = ButtonState::Down;
},
Button::Keyboard(key) => self.keys_just_pressed.push(key),
}
});
event.release(|button_type| {
use input::Button;
use input::MouseButton::Left;
match button_type {
Button::Mouse(button) => {
*match button {
Left => &mut self.mouse.left,
_ => &mut self.mouse.right,
} = ButtonState::Up;
},
Button::Keyboard(key) => self.keys_just_released.push(key),
}
});
event.text(|text| {
self.text_just_entered.push(text.to_string())
});
}
pub fn get_xy(&self,
maybe_id: Option<WidgetId>,
position: Position,
dim: Dimensions,
h_align: HorizontalAlign,
v_align: VerticalAlign) -> Point
{
use vecmath::vec2_add;
let xy = match position {
Position::Absolute(x, y) => [x, y],
Position::Relative(x, y, maybe_id) => {
match maybe_id.or(self.maybe_prev_widget_id.map(|id| id)) {
None => [0.0, 0.0],
Some(id) => vec2_add(self.widget_graph[id].xy, [x, y]),
}
},
Position::Direction(direction, px, maybe_id) => {
match maybe_id.or(self.maybe_prev_widget_id.map(|id| id)) {
None => [0.0, 0.0],
Some(rel_id) => {
use position::Direction;
let (rel_xy, element) = {
let widget = &self.widget_graph[rel_id];
(widget.xy, &widget.element)
};
let (rel_w, rel_h) = element.get_size();
let (rel_w, rel_h) = (rel_w as f64, rel_h as f64);
match direction {
Direction::Up | Direction::Down => {
let (other_x, other_w) = match h_align.1 {
Some(other_id) => {
let (x, elem) = {
let widget = &self.widget_graph[other_id];
(widget.xy[0], &widget.element)
};
let w = elem.get_width() as f64;
(x, w)
},
None => (rel_xy[0], rel_w),
};
let x = other_x + h_align.0.to(other_w, dim[0]);
let y = match direction {
Direction::Up => rel_xy[1] + rel_h / 2.0 + dim[1] / 2.0 + px,
Direction::Down => rel_xy[1] - rel_h / 2.0 - dim[1] / 2.0 - px,
_ => unreachable!(),
};
[x, y]
},
Direction::Left | Direction::Right => {
let (other_y, other_h) = match h_align.1 {
Some(other_id) => {
let (y, elem) = {
let widget = &self.widget_graph[other_id];
(widget.xy[1], &widget.element)
};
let h = elem.get_height() as f64;
(y, h)
},
None => (rel_xy[1], rel_h),
};
let y = other_y + v_align.0.to(other_h, dim[1]);
let x = match direction {
Direction::Left => rel_xy[0] - rel_w / 2.0 - dim[0] / 2.0 - px,
Direction::Right => rel_xy[0] + rel_w / 2.0 + dim[0] / 2.0 + px,
_ => unreachable!(),
};
[x, y]
},
}
},
}
},
Position::Place(place, maybe_parent_id) => {
let window = || ([0.0, 0.0], [self.win_w, self.win_h], Padding::none());
let (xy, target_dim, pad) = match maybe_parent_id.or(self.maybe_current_parent_id) {
Some(parent_id) => match self.widget_graph.get_widget(parent_id) {
Some(parent) =>
(parent.kid_area.xy, parent.kid_area.dim, parent.kid_area.pad),
None => window(),
},
None => window(),
};
let place_xy = place.within(target_dim, dim);
let relative_xy = vec2_add(place_xy, pad.offset_from(place));
vec2_add(xy, relative_xy)
},
};
match maybe_id {
Some(id) => {
let scroll_offset = self.widget_graph.scroll_offset(id);
vec2_add(xy, scroll_offset)
},
None => xy,
}
}
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;
}
fn captures_for_draw(&self) -> (Option<WidgetId>, Option<WidgetId>) {
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,
};
(maybe_captured_mouse, maybe_captured_keyboard)
}
pub fn element(&mut self) -> &Element {
let (maybe_captured_mouse, maybe_captured_keyboard) = self.captures_for_draw();
let Ui {
ref mut widget_graph,
ref mut maybe_background_color,
ref mut maybe_element,
..
} = *self;
let maybe_bg_color = maybe_background_color.take();
let with_background_color = |element: Element| -> Element {
match maybe_bg_color {
Some(color) => element.clear(color),
None => element,
}
};
match widget_graph.element_if_changed(maybe_captured_mouse, maybe_captured_keyboard) {
Some(new_element) => {
*maybe_element = Some(with_background_color(new_element));
maybe_element.as_ref().unwrap()
},
None => match maybe_element {
&mut Some(ref element) => element,
maybe_element @ &mut None => {
let element = widget_graph
.element(maybe_captured_mouse, maybe_captured_keyboard);
*maybe_element = Some(with_background_color(element));
maybe_element.as_ref().unwrap()
},
},
}
}
pub fn element_if_changed(&mut self) -> Option<&Element> {
let (maybe_captured_mouse, maybe_captured_keyboard) = self.captures_for_draw();
let Ui {
ref mut widget_graph,
ref mut maybe_background_color,
ref mut maybe_element,
..
} = *self;
let maybe_new_element = widget_graph
.element_if_changed(maybe_captured_mouse, maybe_captured_keyboard)
.map(|element| match maybe_background_color.take() {
Some(color) => element.clear(color),
None => element
});
maybe_new_element.map(move |new_element| {
*maybe_element = Some(new_element.clone());
maybe_element.as_ref().unwrap()
})
}
pub fn draw<G>(&mut self, context: Context, graphics: &mut G)
where
C: CharacterCache,
G: Graphics<Texture = C::Texture>,
{
use elmesque::Renderer;
use std::ops::DerefMut;
self.element();
let Ui {
ref mut glyph_cache,
ref mut redraw_count,
ref maybe_element,
..
} = *self;
let element = maybe_element.as_ref().unwrap();
let mut ref_mut_character_cache = glyph_cache.0.borrow_mut();
let character_cache = ref_mut_character_cache.deref_mut();
let mut renderer = Renderer::new(context, graphics).character_cache(character_cache);
element.draw(&mut renderer);
if *redraw_count > 0 {
*redraw_count = *redraw_count - 1;
}
}
pub fn draw_if_changed<G>(&mut self, context: Context, graphics: &mut G)
where
C: CharacterCache,
G: Graphics<Texture = C::Texture>,
{
if self.redraw_count > 0 {
self.draw(context, graphics);
}
}
}
pub fn set_current_parent_id<C>(ui: &mut Ui<C>, id: WidgetId) {
ui.maybe_current_parent_id = Some(id);
}
pub fn parent_from_position<C>(ui: &Ui<C>, position: Position) -> Option<WidgetId> {
match position {
Position::Relative(_, _, maybe_id) => match maybe_id {
Some(id) => ui.widget_graph.parent_of(id),
None => match ui.maybe_prev_widget_id {
Some(id) => ui.widget_graph.parent_of(id),
None => ui.maybe_current_parent_id,
},
},
Position::Direction(_, _, maybe_id) => match maybe_id {
Some(id) => ui.widget_graph.parent_of(id),
None => match ui.maybe_prev_widget_id {
Some(id) => ui.widget_graph.parent_of(id),
None => ui.maybe_current_parent_id,
},
},
Position::Place(_, maybe_parent_id) => maybe_parent_id.or(ui.maybe_current_parent_id),
_ => ui.maybe_current_parent_id,
}
}
pub fn user_input<'a, C>(ui: &'a Ui<C>, id: WidgetId) -> UserInput<'a> {
let maybe_mouse = get_mouse_state(ui, id);
let without_keys = || UserInput {
maybe_mouse: maybe_mouse,
pressed_keys: &[],
released_keys: &[],
entered_text: &[],
window_dim: [ui.win_w, ui.win_h],
};
let with_keys = || UserInput {
maybe_mouse: maybe_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_id)) => if id == captured_id { with_keys() }
else { without_keys() },
Some(Capturing::JustReleased) => without_keys(),
None => with_keys(),
}
}
pub fn get_mouse_state<C>(ui: &Ui<C>, id: WidgetId) -> Option<Mouse> {
match ui.maybe_captured_mouse {
Some(Capturing::Captured(captured_id)) =>
if id == captured_id { Some(ui.mouse) } else { None },
Some(Capturing::JustReleased) =>
None,
None => match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_id)) =>
if id == captured_id { Some(ui.mouse) } else { None },
_ =>
if Some(id) == ui.maybe_widget_under_mouse
|| Some(id) == ui.maybe_top_scrollable_widget_under_mouse {
Some(ui.mouse)
} else {
None
},
},
}
}
pub fn get_widget_state<C, W>(ui: &mut Ui<C>,
id: WidgetId,
kind: &'static str) -> Option<widget::Cached<W>>
where
W: Widget,
W::State: Any + 'static,
W::Style: Any + 'static,
{
ui.widget_graph.get_widget_mut(id).and_then(|container| {
if container.kind != kind {
writeln!(::std::io::stderr(),
"A widget of a different kind already exists at the given WidgetId ({:?}).
You tried to insert a {:?}, however the existing widget is a {:?}.
Check your widgets' `WidgetId`s for errors.",
id, kind, container.kind).unwrap();
return None;
} else {
container.take_widget_state()
}
})
}
pub fn mouse_captured_by<C>(ui: &mut Ui<C>, id: WidgetId) {
match ui.maybe_captured_mouse {
Some(Capturing::Captured(captured_id)) => if id != captured_id {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to capture the mouse, however it is \
already captured by {:?}.", id, captured_id).unwrap();
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to capture the mouse, however it was \
already captured.", id).unwrap();
},
None => ui.maybe_captured_mouse = Some(Capturing::Captured(id)),
}
}
pub fn mouse_uncaptured_by<C>(ui: &mut Ui<C>, id: WidgetId) {
match ui.maybe_captured_mouse {
Some(Capturing::Captured(captured_id)) => if id != captured_id {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the mouse, however it is \
actually captured by {:?}.", id, captured_id).unwrap();
} else {
ui.maybe_captured_mouse = Some(Capturing::JustReleased);
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the mouse, however it had \
already been released this cycle.", id).unwrap();
},
None => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the mouse, however the mouse \
was not captured", id).unwrap();
},
}
}
pub fn keyboard_captured_by<C>(ui: &mut Ui<C>, id: WidgetId) {
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_id)) => if id != captured_id {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to capture the keyboard, however it is \
already captured by {:?}.", id, captured_id).unwrap();
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to capture the keyboard, however it was \
already captured.", id).unwrap();
},
None => ui.maybe_captured_keyboard = Some(Capturing::Captured(id)),
}
}
pub fn keyboard_uncaptured_by<C>(ui: &mut Ui<C>, id: WidgetId) {
match ui.maybe_captured_keyboard {
Some(Capturing::Captured(captured_id)) => if id != captured_id {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however it is \
actually captured by {:?}.", id, captured_id).unwrap();
} else {
ui.maybe_captured_keyboard = Some(Capturing::JustReleased);
},
Some(Capturing::JustReleased) => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however it had \
already been released this cycle.", id).unwrap();
},
None => {
writeln!(::std::io::stderr(),
"Warning: {:?} tried to uncapture the keyboard, however the mouse \
was not captured", id).unwrap();
},
}
}
pub fn update_widget<C, W>(ui: &mut Ui<C>,
id: WidgetId,
maybe_parent_id: Option<WidgetId>,
kind: &'static str,
cached: widget::Cached<W>,
maybe_new_element: Option<Element>)
where
W: Widget,
W::State: 'static,
W::Style: 'static,
{
ui.widget_graph.update_widget(id, maybe_parent_id, kind, cached, maybe_new_element);
ui.maybe_prev_widget_id = Some(id);
ui.maybe_current_parent_id = maybe_parent_id;
}
pub fn clear_with<C>(ui: &mut Ui<C>, color: Color) {
ui.maybe_background_color = Some(color);
}
pub fn graph<C>(ui: &Ui<C>) -> &Graph {
&ui.widget_graph
}