onyx 0.2.0

A simple engine for simple minds
Documentation
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,
}