use std::rc::Rc;
use std::collections::HashMap;
use math::{Vec2, Rect};
use text;
use context::{Context, Texture};
use frame::Frame;
use color::Color;
enum Content<Action> {
Panel,
Label(Texture),
Button(Box<Fn() -> Action>),
}
#[derive(Copy, Clone)]
pub enum Alignment {
Center,
Min,
Max,
Fill,
}
impl Alignment {
fn align(&self, pos: f32, size: f32, desired: f32) -> (f32, f32) {
match *self {
Alignment::Center => (pos + (size - desired) / 2.0, desired.min(size)),
Alignment::Min => (pos, desired.min(size)),
Alignment::Max => (pos + size - desired, desired.min(size)),
Alignment::Fill => (pos, size),
}
}
}
#[derive(Copy, Clone)]
pub enum SizeHint {
Children,
Fixed(f32),
Relative(f32),
}
impl SizeHint {
fn size(&self, parent: f32, children: f32) -> f32 {
match *self {
SizeHint::Children => children,
SizeHint::Fixed(size) => size,
SizeHint::Relative(ratio) => parent * ratio,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Layout {
None,
Horizontal,
Vertical,
}
impl Layout {
fn expand(&self, size0: Vec2, size1: Vec2) -> Vec2 {
match *self {
Layout::None => Vec2::new(size0.x.max(size1.x), size0.y.max(size1.y)),
Layout::Horizontal => Vec2::new(size0.x + size1.x, size0.y.max(size1.y)),
Layout::Vertical => Vec2::new(size0.x.max(size1.x), size0.y + size1.y),
}
}
fn child(&self, index: usize, count: usize, position: Vec2, size: Vec2, child: Vec2) -> (Vec2, Vec2) {
match *self {
Layout::None => (position, size),
Layout::Horizontal => {
if index < count - 1 {
(Vec2::new(position.x + child.x, position.y), Vec2::new(child.x, size.y))
} else {
(position, Vec2::new(size.x - position.x, size.y))
}
}
Layout::Vertical => {
if index < count - 1 {
(Vec2::new(position.x, position.y + child.y), Vec2::new(size.x, child.y))
} else {
(position, Vec2::new(size.x, size.y - position.y))
}
}
}
}
}
struct Metrics {
size_hint: (SizeHint, SizeHint),
color: Color,
alignment: (Alignment, Alignment),
layout: Layout,
}
pub enum ContentBuilder<Action> {
Panel,
Button(Box<Fn() -> Action>),
Label(String),
}
pub struct WidgetBuilder<Action> {
content: ContentBuilder<Action>,
metrics: Metrics,
children: Vec<WidgetBuilder<Action>>,
id: Option<String>,
}
impl<Action> WidgetBuilder<Action> {
fn new(content: ContentBuilder<Action>) -> Self {
WidgetBuilder {
content,
metrics: Metrics {
size_hint: (SizeHint::Children, SizeHint::Children),
layout: Layout::None,
alignment: (Alignment::Center, Alignment::Center),
color: (1.0, 1.0, 1.0, 1.0),
},
children: Vec::new(),
id: None,
}
}
pub fn size(mut self, hint0: SizeHint, hint1: SizeHint) -> Self {
self.metrics.size_hint = (hint0, hint1);
self
}
pub fn fixed_size(self, size0: f32, size1: f32) -> Self {
self.size(SizeHint::Fixed(size0), SizeHint::Fixed(size1))
}
pub fn width(mut self, hint: SizeHint) -> Self {
self.metrics.size_hint.0 = hint;
self
}
pub fn height(mut self, hint: SizeHint) -> Self {
self.metrics.size_hint.1 = hint;
self
}
pub fn alignment(mut self, alignment0: Alignment, alignment1: Alignment) -> Self {
self.metrics.alignment = (alignment0, alignment1);
self
}
pub fn layout(mut self, layout: Layout) -> Self {
self.metrics.layout = layout;
self
}
pub fn child(mut self, child: WidgetBuilder<Action>) -> Self {
self.children.push(child);
self
}
pub fn color(mut self, color: Color) -> Self {
self.metrics.color = color;
self
}
pub fn id(mut self, id: &str) -> Self {
self.id = Some(String::from(id));
self
}
}
struct WidgetData<Action> {
content: Content<Action>,
metrics: Metrics,
children_size: Vec2,
rect: Rect,
children: Vec<Widget>,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Widget(usize);
pub struct Ui<Action> {
context: Rc<Context>,
root: Option<Widget>,
pressed: Option<Widget>,
hovered: Option<Widget>,
widgets: Vec<WidgetData<Action>>,
by_id: HashMap<String, Widget>,
style: Style,
}
impl<Action> Ui<Action> {
pub fn new(context: Rc<Context>) -> Self {
let font = context.new_font("DroidSerif-Regular.ttf");
Self {
context,
root: None,
pressed: None,
hovered: None,
widgets: Vec::new(),
by_id: HashMap::new(),
style: Style {
margin: 5.0,
panel: (1.0, 1.0, 0.7, 1.0),
button: ButtonStyle {
normal: (0.6, 0.6, 0.2, 1.0),
hovered: (0.5, 0.5, 0.1, 1.0),
pressed: (0.4, 0.4, 0.0, 1.0),
},
label: LabelStyle {
size: 32.0,
color: (0.5, 0.0, 0.0, 1.0),
font: font,
}
},
}
}
pub fn update(&mut self) {
if let Some(root) = self.root {
self.hovered = self.find_hovered(root, self.context.mouse.get());
let size = self.context.size.get();
let margin = self.style.margin;
self.layout(root, Rect::new(Vec2::zero(), size), margin);
}
}
pub fn draw(&self, frame: &mut Frame) {
if let Some(root) = self.root {
self.draw_rec(root, Vec2::zero(), &self.style, frame);
frame.set_color((1.0, 1.0, 1.0, 1.0));
}
}
fn layout(&mut self, widget: Widget, rect: Rect, margin: f32) {
self.compute_children_size(widget, margin);
self.widget_mut(widget).rect.size = self.self_size(widget, rect.size, margin);
self.layout_rec(widget, rect, margin);
}
fn compute_children_size(&mut self, widget: Widget, margin: f32) {
let mut children_size = Vec2::zero();
for index in 0 .. self.widget(widget).children.len() {
let child = self.widget(widget).children[index];
self.compute_children_size(child, margin);
let size = self.self_size(child, Vec2::zero(), margin);
children_size = self.widget(widget).metrics.layout.expand(children_size, size);
self.widget_mut(widget).children_size = children_size;
}
}
fn self_size(&self, widget: Widget, parent: Vec2, margin: f32) -> Vec2 {
let content_size = self.content_size(widget);
let (size_hint, children_size) = {
let widget = self.widget(widget);
(widget.metrics.size_hint, widget.children_size)
};
Vec2::new(
size_hint.0.size(parent.x, children_size.x).max(content_size.x) + 2.0 * margin,
size_hint.1.size(parent.y, children_size.y).max(content_size.y) + 2.0 * margin,
)
}
fn content_size(&self, widget: Widget) -> Vec2 {
match &self.widget(widget).content {
&Content::Label(ref texture) => Vec2::new(texture.get_width() as f32, texture.get_height().unwrap() as f32),
_ => Vec2::zero(),
}
}
fn layout_rec(&mut self, widget: Widget, rect: Rect, margin: f32) {
self.layout_self(widget, rect, margin);
let mut position = Vec2::zero();
let (count, size, layout) = {
let widget = self.widget(widget);
(widget.children.len(), widget.rect.size, widget.metrics.layout)
};
for index in 0 .. count {
let child = self.widget(widget).children[index];
self.widget_mut(child).rect.size = self.self_size(child, size, margin);
let child_size = self.widget(child).rect.size;
let (new_position, size) = layout.child(index, count, position, size, child_size);
self.layout_rec(child, Rect::new(position, size), margin);
position = new_position;
}
}
fn layout_self(&mut self, widget: Widget, rect: Rect, margin: f32) {
let (alignment, size) ={
let widget = self.widget(widget);
(widget.metrics.alignment, widget.rect.size)
};
let (x, w) = alignment.0.align(rect.position.x, rect.size.x, size.x);
let (y, h) = alignment.1.align(rect.position.y, rect.size.y, size.y);
self.widget_mut(widget).rect = Rect::new(Vec2::new(x + margin, y + margin), Vec2::new(w - 2.0 * margin, h - 2.0 * margin));
}
fn find_hovered(&self, widget: Widget, mouse: Vec2<f32>) -> Option<Widget> {
if self.widget(widget).rect.contains(mouse) {
for child in self.children(widget) {
if let Some(child) = self.find_hovered(child, mouse - self.widget(widget).rect.position) {
return Some(child);
}
}
if let Content::Button(_) = self.widget(widget).content {
return Some(widget);
}
}
None
}
fn children<'a>(&'a self, widget: Widget) -> ::std::iter::Cloned<::std::slice::Iter<'a, Widget>> {
self.widget(widget).children.iter().cloned()
}
fn press(&mut self) {
self.pressed = self.hovered;
}
fn release(&mut self) -> Option<Action> {
if let Some(pressed) = self.pressed {
self.pressed = None;
if self.hovered == Some(pressed) {
if let Content::Button(ref action) = self.widget(pressed).content {
return Some(action());
}
}
}
None
}
fn draw_rec(&self, widget: Widget, position: Vec2<f32>, style: &Style, frame: &mut Frame) {
let rect = self.widget(widget).rect;
let position = rect.position + position;
self.draw_self(widget, Rect::new(position, rect.size), style, frame);
for child in self.children(widget) {
self.draw_rec(child, position, style, frame);
}
}
fn draw_self(&self, widget: Widget, rect: Rect, style: &Style, frame: &mut Frame) {
match &self.widget(widget).content {
&Content::Label(ref text) => {
frame.set_color(style.label.color);
frame.draw_text(text, rect);
},
&Content::Panel => {
frame.set_color(style.panel);
frame.draw_rect(rect);
},
&Content::Button(_) => {
if self.pressed == Some(widget) {
frame.set_color(style.button.pressed);
} else if self.hovered == Some(widget) {
frame.set_color(style.button.hovered);
} else {
frame.set_color(style.button.normal);
}
frame.draw_rect(rect);
},
}
}
pub fn set_text(&mut self, id: &str, value: &str) {
if let Some(widget) = self.by_id.get(id) {
self.widgets[widget.0].content = Content::Label(self.context.new_text(value, &self.style.label.font, self.style.label.size));
}
}
pub fn event(&mut self, event: super::Event) -> Result<Action, super::Event> {
match event {
super::Event::MousePressed => {
self.press();
Err(event)
},
super::Event::MouseReleased => {
let result = if let Some(action) = self.release() {
Ok(action)
} else {
Err(event)
};
self.pressed = None;
result
},
_ => Err(event),
}
}
pub fn build(&mut self, root: WidgetBuilder<Action>) {
self.root = Some(self.build_rec(root));
}
fn build_rec(&mut self, builder: WidgetBuilder<Action>) -> Widget {
let widget = Widget(self.widgets.len());
let content = self.build_content(builder.content);
if let Some(id) = builder.id {
self.by_id.insert(id, widget);
}
self.widgets.push(WidgetData {
content: content,
metrics: builder.metrics,
rect: Rect::new(Vec2::zero(), Vec2::zero()),
children: Vec::new(),
children_size: Vec2::zero(),
});
for child_builder in builder.children {
let child = self.build_rec(child_builder);
self.widget_mut(widget).children.push(child);
}
widget
}
fn build_content(&mut self, builder: ContentBuilder<Action>) -> Content<Action> {
match builder {
ContentBuilder::Panel => Content::Panel,
ContentBuilder::Button(index) => Content::Button(index),
ContentBuilder::Label(text) => Content::Label(self.context.new_text(&text, &self.style.label.font, self.style.label.size)),
}
}
pub fn set_style(&mut self, style: Style) {
self.style = style;
}
fn widget(&self, widget: Widget) -> &WidgetData<Action> {
&self.widgets[widget.0]
}
fn widget_mut(&mut self, widget: Widget) -> &mut WidgetData<Action> {
&mut self.widgets[widget.0]
}
}
pub fn label<Action>(text: &str) -> WidgetBuilder<Action> {
WidgetBuilder::new(ContentBuilder::Label(String::from(text)))
}
pub fn panel<Action>() -> WidgetBuilder<Action> {
WidgetBuilder::new(ContentBuilder::Panel)
}
pub fn button<Action, F: Fn() -> Action + 'static>(action: F) -> WidgetBuilder<Action> {
WidgetBuilder::new(ContentBuilder::Button(Box::new(action)))
}
pub struct Style {
pub margin: f32,
pub panel: Color,
pub button: ButtonStyle,
pub label: LabelStyle,
}
pub struct LabelStyle {
pub font: text::Font,
pub size: f32,
pub color: Color,
}
pub struct ButtonStyle {
pub normal: Color,
pub hovered: Color,
pub pressed: Color,
}