~saiko/game

548f9891c2f87f752c23b41df04648a40bf324f6 — 2xsaiko 1 year, 10 days ago 09063b0
Text input field
M Cargo.lock => Cargo.lock +69 -0
@@ 213,6 213,28 @@ dependencies = [
]

[[package]]
name = "clipboard"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
dependencies = [
 "clipboard-win",
 "objc",
 "objc-foundation",
 "objc_id",
 "x11-clipboard",
]

[[package]]
name = "clipboard-win"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
dependencies = [
 "winapi 0.3.8",
]

[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 446,6 468,7 @@ version = "0.1.0"
dependencies = [
 "bit-set",
 "chrono",
 "clipboard",
 "float-pretty-print",
 "glium",
 "itertools",


@@ 453,6 476,7 @@ dependencies = [
 "nalgebra",
 "ncollide3d",
 "simplelog",
 "unicode-segmentation",
 "zip",
]



@@ 903,6 927,26 @@ dependencies = [
]

[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
 "block",
 "objc",
 "objc_id",
]

[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
 "objc",
]

[[package]]
name = "ordered-float"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1344,6 1388,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"

[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"

[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1520,6 1570,15 @@ dependencies = [
]

[[package]]
name = "x11-clipboard"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea"
dependencies = [
 "xcb",
]

[[package]]
name = "x11-dl"
version = "2.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1532,6 1591,16 @@ dependencies = [
]

[[package]]
name = "xcb"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
dependencies = [
 "libc",
 "log",
]

[[package]]
name = "xdg"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

M Cargo.toml => Cargo.toml +3 -1
@@ 17,4 17,6 @@ log = "0.4.8"
simplelog = "0.7.4"
itertools = "0.8.2"
zip = "0.5.3"
float-pretty-print = "0.1.0"
\ No newline at end of file
float-pretty-print = "0.1.0"
unicode-segmentation = "1.6.0"
clipboard = "0.5.0"
\ No newline at end of file

M src/main.rs => src/main.rs +8 -3
@@ 193,7 193,9 @@ fn start_game(env: Environment) -> ! {

  let mut camera = Rc::new(RefCell::new(Camera::new()));
  let mut kb = Keyboard::new();
  camera.borrow_mut().set_pos(vec3(0.0, 10.0, 0.0));
  camera.borrow_mut().set_pos(vec3(0.0, 10.0, -5.0));
//  camera.borrow_mut().set_pitch(-30.0);
  camera.borrow_mut().set_yaw(10.0);

  let fr = load_fonts(&env.rl, &display, &mut sm);



@@ 211,7 213,7 @@ fn start_game(env: Environment) -> ! {
    fr,
  };

  game.ui_host.open_ui(TestUiController::new());
  game.ui_host.open_ui(TestUiController::new(game.camera.clone(), model.clone()));

  event_loop.run_return(|event, target, cf| game.handle_event(event, target, cf));
  exit(0);


@@ 255,7 257,10 @@ impl Game<'_> {
        self.ui_host.resize(self.window.width(), self.window.height());
      }
      Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. } => {
        // self.ui_host.press_key();
        self.ui_host.on_key(input.state, input.virtual_keycode, input.scancode, input.modifiers);
      }
      Event::WindowEvent{event:WindowEvent::ReceivedCharacter(c), ..} => {
        self.ui_host.on_key_typed(c);
      }
      Event::WindowEvent { event: WindowEvent::Focused(state), .. } => {
//          if !gui_mode {

M src/testui.rs => src/testui.rs +35 -12
@@ 1,5 1,10 @@
use std::cell::RefCell;
use std::rc::Rc;

use crate::{Model, Vertex};
use crate::cam::Camera;
use crate::ui::controller::UiController;
use crate::ui::element::Button;
use crate::ui::element::{Button, ButtonEvent, Viewport, InputField};
use crate::ui::graphics::{Graphics, StringDrawProps};
use crate::ui::layout::{BorderLayout, LayoutManager};
use crate::ui::panel::Container;


@@ 17,20 22,24 @@ ui_def! {

pub struct TestUiElements {
  button: Container<Button>,
  button1: Container<Button>,
  button1: Container<InputField>,
  button2: Container<Button>,
  button3: Container<Button>,
  button4: Container<Button>,
  button4: Container<Viewport>,
}

pub struct TestUiController {
  click_counter: u32,
  camera: Rc<RefCell<Camera>>,
  model: Rc<Model<Vertex>>,
}

impl TestUiController {
  pub fn new() -> TestUi {
  pub fn new(camera: Rc<RefCell<Camera>>, model: Rc<Model<Vertex>>) -> TestUi {
    TestUi::new(TestUiController {
      click_counter: 0
      click_counter: 0,
      camera,
      model,
    })
  }



@@ 52,19 61,33 @@ impl UiController for TestUiController {
  fn create_elements(&self) -> Self::Elements {
    TestUiElements {
      button: Button::new("top"),
      button1: Button::new("bottom"),
      button1: InputField::new(),
      button2: Button::new("left"),
      button3: Button::new("right"),
      button4: Button::new("center"),
      button4: Viewport::new(self.camera.clone(), self.model.clone()),
    }
  }

  fn update(&mut self, elements: &mut TestUiElements) {
    elements.button.poll_events(|e| self.click_counter += 1);
    elements.button1.poll_events(|e| self.click_counter += 1);
    elements.button2.poll_events(|e| self.click_counter += 1);
    elements.button3.poll_events(|e| self.click_counter += 1);
    elements.button4.poll_events(|e| self.click_counter += 1);
    let TestUiElements { button, button1, button2, button3, .. } = elements;
    button.poll_events(|b, e| {
      self.click_counter += 1;
      b.set_enabled(false);
      button2.set_enabled(true);
      button3.set_enabled(true);
    });
    button2.poll_events(|b, e| {
      self.click_counter += 1;
      b.set_enabled(false);
      button.set_enabled(true);
      button3.set_enabled(true);
    });
    button3.poll_events(|b, e| {
      self.click_counter += 1;
      b.set_enabled(false);
      button2.set_enabled(true);
      button.set_enabled(true);
    });
  }

  fn resize(&mut self, elements: &mut Self::Elements, g: &Graphics, width: u32, height: u32) {

M src/ui/access.rs => src/ui/access.rs +1 -1
@@ 1,6 1,6 @@
use crate::ui::element::Positionable;

pub trait Values: Sized + Copy {
pub trait Values: Sized + Copy + Eq {
  fn values() -> &'static [Self];
}


M src/ui/element/button.rs => src/ui/element/button.rs +31 -11
@@ 1,11 1,12 @@
use glium::glutin::event::{ElementState, ModifiersState, MouseButton};

use crate::ui::element::Element;
use crate::ui::element::{ACTIVE_COLOR, Element, HIGHLIGHTED_COLOR, INACTIVE_COLOR, NORMAL_COLOR, FocusResult, default_try_focus};
use crate::ui::event::EventQueue;
use crate::ui::graphics::{Alignment, Graphics, StringDrawProps};
use crate::ui::panel::Container;
use crate::ui::rescap::ResizeCapabilities;
use crate::util::Color;
use crate::ui::util::is_in_bounds;

pub enum ButtonEvent {
  Pressed,


@@ 17,6 18,7 @@ pub struct Button {
  height: u32,
  is_mouse_over: bool,
  is_pressed: bool,
  is_enabled: bool,
  queue: EventQueue<ButtonEvent>,
}



@@ 28,21 30,34 @@ impl Button {
      height: 0,
      is_mouse_over: false,
      is_pressed: false,
      is_enabled: true,
      queue: EventQueue::new(),
    })
  }

  pub fn poll_events(&mut self, op: impl FnMut(ButtonEvent)) {
    self.queue.poll_events(op);
  pub fn set_text(&mut self, text: &str) { self.text = text.to_owned(); }

  pub fn set_enabled(&mut self, enabled: bool) {
    self.is_enabled = enabled;
    if !enabled { self.is_pressed = false; }
  }

  pub fn enabled(&self) -> bool { self.is_enabled }

  pub fn poll_events(&mut self, mut op: impl FnMut(&mut Self, ButtonEvent)) {
    for ev in self.queue.pop_events() {
      op(self, ev);
    }
  }
}

impl Element for Button {
  fn draw(&mut self, g: &mut Graphics) {
    let color = match (self.is_mouse_over, self.is_pressed) {
      (false, false) => Color::from_packed_rgb(0x00ccff),
      (_, true) => Color::from_packed_rgb(0xcc00ff),
      (true, false) => Color::from_packed_rgb(0x00eeff),
    let color = match (self.is_enabled, self.is_mouse_over, self.is_pressed) {
      (false, _, _) => INACTIVE_COLOR,
      (true, false, false) => NORMAL_COLOR,
      (true, _, true) => ACTIVE_COLOR,
      (true, true, false) => HIGHLIGHTED_COLOR,
    };

    g.draw_rect(1, 1, self.width as i32 - 2, self.height as i32 - 2, color);


@@ 56,13 71,14 @@ impl Element for Button {
  }

  fn on_mouse_move(&mut self, mouse_x: i32, mouse_y: i32) {
    let is_in_bounds = mouse_x >= 0 && mouse_x < self.width as i32 && mouse_y >= 0 && mouse_y < self.height as i32;
    self.is_mouse_over = is_in_bounds;
    self.is_mouse_over = is_in_bounds(0, 0, self.width as i32, self.height as i32, mouse_x, mouse_y);
  }

  fn on_click(&mut self, mouse_x: i32, mouse_y: i32, state: ElementState, button: MouseButton, modifiers: ModifiersState) -> bool {
    let is_in_bounds = mouse_x >= 0 && mouse_x < self.width as i32 && mouse_y >= 0 && mouse_y < self.height as i32;
    if button == MouseButton::Left {
    let is_in_bounds = is_in_bounds(0, 0, self.width as i32, self.height as i32, mouse_x, mouse_y);
    if !self.is_enabled {
      is_in_bounds
    } else if button == MouseButton::Left {
      if is_in_bounds && state == ElementState::Pressed && !self.is_pressed {
        self.is_pressed = true;
        true


@@ 76,6 92,10 @@ impl Element for Button {
    } else { false }
  }

  fn try_focus(&mut self, mouse_x: i32, mouse_y: i32) -> FocusResult {
    default_try_focus(self.width as i32, self.height as i32, self.is_enabled, &mut false, mouse_x, mouse_y)
  }

  fn get_resize_capabilities(&self, g: &Graphics) -> ResizeCapabilities {
    let fr = g.font_renderer();
    ResizeCapabilities::new(

A src/ui/element/inputfield.rs => src/ui/element/inputfield.rs +220 -0
@@ 0,0 1,220 @@
use std::borrow::Cow;
use std::cmp::max;

use clipboard::{ClipboardContext, ClipboardProvider};
use glium::glutin::event::{ElementState, ModifiersState, ScanCode, VirtualKeyCode};
use unicode_segmentation::UnicodeSegmentation;

use crate::ui::element::{default_try_focus, Element, FocusResult, HIGHLIGHTED_COLOR, INACTIVE_COLOR, NORMAL_COLOR};
use crate::ui::event::EventQueue;
use crate::ui::graphics::{Graphics, StringDrawProps};
use crate::ui::panel::Container;
use crate::ui::rescap::ResizeCapabilities;
use crate::util::Color;

pub enum InputFieldEvent {}

pub struct InputField {
  overlay_text: String,
  text: String,
  cursor_position: usize,
  width: u32,
  height: u32,
  is_enabled: bool,
  is_focused: bool,
  queue: EventQueue<InputFieldEvent>,
}

impl InputField {
  pub fn new() -> Container<Self> {
    Container::new(InputField {
      overlay_text: String::new(),
      text: String::new(),
      cursor_position: 0,
      width: 0,
      height: 0,
      is_enabled: true,
      is_focused: false,
      queue: EventQueue::new(),
    })
  }

  pub fn set_overlay_text(&mut self, text: &str) { self.overlay_text = text.to_owned(); }

  pub fn overlay_text(&self) -> &str { &self.overlay_text }

  pub fn set_text(&mut self, text: &str) { self.text = text.to_owned(); }

  pub fn text(&self) -> &str { &self.text }

  pub fn type_key(&mut self, c: char) {
    self.text.push(c);
  }

  fn can_type(&self) -> bool { self.is_enabled && self.is_focused }

  fn search_char_left(&self) -> usize {
    self.text.grapheme_indices(true)
      .rev()
      .skip_while(|(idx, _)| *idx >= self.cursor_position)
      .map(|a| a.0)
      .next().unwrap_or(0)
  }

  fn search_char_right(&self) -> usize {
    self.text[self.cursor_position..].graphemes(true)
      .skip(1)
      .next().map_or(self.text.len(), |g| self.cursor_position + g.len())
  }

  fn search_word_left(&self) -> usize {
    self.text.split_word_bound_indices()
      .rev()
      .skip_while(|(idx, _)| *idx >= self.cursor_position)
      .map(|a| a.0)
      .next().unwrap_or(0)
  }

  fn search_word_right(&self) -> usize {
    self.text.split_word_bound_indices()
      .skip_while(|(pos, _)| *pos <= self.cursor_position)
      .map(|a| a.0)
      .next().unwrap_or(self.text.len())
  }

  fn jump_char_left(&mut self) {
    self.cursor_position = self.search_char_left();
  }

  fn jump_char_right(&mut self) {
    self.cursor_position = self.search_char_right();
  }

  fn jump_word_left(&mut self) {
    self.cursor_position = self.search_word_left();
  }

  fn jump_word_right(&mut self) {
    self.cursor_position = self.search_word_right();
  }

  fn delete_char_left(&mut self) {
    let i = self.search_char_left();
    self.text.replace_range(i..self.cursor_position, "");
    self.cursor_position = i;
  }

  fn delete_char_right(&mut self) {
    self.text.replace_range(self.cursor_position..self.search_char_right(), "");
  }

  fn delete_word_left(&mut self) {
    let i = self.search_word_left();
    self.text.replace_range(i..self.cursor_position, "");
    self.cursor_position = i;
  }

  fn delete_word_right(&mut self) {
    self.text.replace_range(self.cursor_position..self.search_word_right(), "");
  }
}

impl Element for InputField {
  fn draw(&mut self, g: &mut Graphics) {
    let color = match (self.is_enabled, self.is_focused) {
      (false, _) => INACTIVE_COLOR,
      (true, false) => NORMAL_COLOR,
      (true, true) => HIGHLIGHTED_COLOR,
    };

    g.draw_rect(1, 1, self.width as i32 - 2, self.height as i32 - 2, color);
    g.draw_string(&self.text, 3, 3,
                  StringDrawProps::default().with_color(Color::white()));

    if self.is_focused {
      let cur_at = max(6, 3 + g.font_renderer().get_str_width(&self.text[..self.cursor_position]) as i32);
      let c = Color::white();
      let end = 3 + g.font_renderer().get_height() as i32;
      g.draw_line(cur_at, 3, cur_at, end - 1, c);
      g.draw_line(cur_at - 4, 3, cur_at - 1, 3, c);
      g.draw_line(cur_at, 3, cur_at + 3, 3, c);
      g.draw_line(cur_at - 4, end, cur_at - 1, end, c);
      g.draw_line(cur_at, end, cur_at + 3, end, c);
    }
  }

  fn set_size(&mut self, width: u32, height: u32) {
    self.width = width;
    self.height = height;
  }

  fn on_key(&mut self, state: ElementState, vk: Option<VirtualKeyCode>, scancode: ScanCode, modifiers: ModifiersState) {
    let mut m_ctrl = ModifiersState::default();
    m_ctrl.ctrl = true;
    if self.can_type() && state == ElementState::Pressed {
      match vk {
        Some(VirtualKeyCode::Back) => {
          if !modifiers.ctrl {
            self.delete_char_left();
          } else {
            self.delete_word_left();
          }
        }
        Some(VirtualKeyCode::Delete) => {
          if !modifiers.ctrl {
            self.delete_char_right();
          } else {
            self.delete_word_right();
          }
        }
        Some(VirtualKeyCode::Left) => {
          if !modifiers.ctrl {
            self.jump_char_left();
          } else {
            self.jump_word_left();
          }
        }
        Some(VirtualKeyCode::Right) => {
          if !modifiers.ctrl {
            self.jump_char_right();
          } else {
            self.jump_word_right();
          }
        }
        Some(VirtualKeyCode::Home) => {
          self.cursor_position = 0;
        }
        Some(VirtualKeyCode::End) => {
          self.cursor_position = self.text.len();
        }
        Some(VirtualKeyCode::V) if modifiers == m_ctrl => {
          let inserted_text = ClipboardProvider::new().ok()
            .and_then(|mut clip: ClipboardContext| clip.get_contents().ok())
            .map_or(Cow::Borrowed(""), |s| Cow::Owned(s));
          self.text.insert_str(self.cursor_position, &*inserted_text);
          self.cursor_position += inserted_text.len();
        }
        _ => {}
      }
    }
  }

  fn on_key_typed(&mut self, c: char) {
    if self.can_type() && !c.is_control() {
      self.text.insert(self.cursor_position, c);
      self.cursor_position += c.len_utf8();
    }
  }

  fn try_focus(&mut self, mouse_x: i32, mouse_y: i32) -> FocusResult {
    default_try_focus(self.width as i32, self.height as i32, self.is_enabled, &mut self.is_focused, mouse_x, mouse_y)
  }

  fn on_focus_lost(&mut self) {
    self.is_focused = false;
  }

  fn get_resize_capabilities(&self, g: &Graphics) -> ResizeCapabilities {
    ResizeCapabilities::new(None, Some(g.font_renderer().get_height() as u32 + 6), None, None, None, None) // TODO
  }
}
\ No newline at end of file

M src/ui/element/mod.rs => src/ui/element/mod.rs +38 -1
@@ 1,15 1,24 @@
use glium::glutin::event::{ElementState, ModifiersState, MouseButton};
use glium::glutin::event::{ElementState, ModifiersState, MouseButton, ScanCode, VirtualKeyCode};

pub use button::*;
pub use inputfield::*;
pub use spacer::*;
pub use viewport::*;

use crate::ui::graphics::Graphics;
use crate::ui::rescap::ResizeCapabilities;
use crate::ui::util::is_in_bounds;
use crate::util::Color;

mod button;
mod spacer;
mod viewport;
mod inputfield;

pub static NORMAL_COLOR: Color = Color::from_rgb8(0x00, 0x82, 0xaa);
pub static ACTIVE_COLOR: Color = Color::from_rgb8(0x82, 0x00, 0xaa);
pub static INACTIVE_COLOR: Color = Color::from_rgb8(0x66, 0x66, 0x66);
pub static HIGHLIGHTED_COLOR: Color = Color::from_rgb8(0x00, 0xc4, 0xff);

pub trait Element {
  fn update(&mut self) {}


@@ 22,6 31,14 @@ pub trait Element {

  fn on_click(&mut self, mouse_x: i32, mouse_y: i32, state: ElementState, button: MouseButton, modifiers: ModifiersState) -> bool { true }

  fn on_key(&mut self, state: ElementState, vk: Option<VirtualKeyCode>, scancode: ScanCode, modifiers: ModifiersState) {}

  fn on_key_typed(&mut self, c: char) {}

  fn try_focus(&mut self, mouse_x: i32, mouse_y: i32) -> FocusResult { FocusResult::Abort }

  fn on_focus_lost(&mut self) {}

  fn get_resize_capabilities(&self, g: &Graphics) -> ResizeCapabilities;

  fn is_animated(&self) -> bool { false }


@@ 49,4 66,24 @@ pub fn draw_at_pos<T: Positionable>(el: &mut T, g: &mut Graphics) {
    g.translatei(el.x(), el.y(), -1);
    el.draw(g);
  });
}

pub fn default_try_focus(width: i32, height: i32, enabled: bool, focused: &mut bool, mouse_x: i32, mouse_y: i32) -> FocusResult {
  if is_in_bounds(0, 0, width, height, mouse_x, mouse_y) {
    if enabled {
      *focused = true;
      FocusResult::AcquiredFocus
    } else {
      FocusResult::Abort
    }
  } else {
    FocusResult::Continue
  }
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum FocusResult {
  AcquiredFocus,
  Abort,
  Continue,
}
\ No newline at end of file

M src/ui/element/viewport.rs => src/ui/element/viewport.rs +6 -6
@@ 52,6 52,12 @@ impl Element for Viewport {
    g.draw_texture(0, 0, self.width as i32, self.height as i32, None, &fb.inner.texture);
  }

  fn set_size(&mut self, width: u32, height: u32) {
    self.width = width;
    self.height = height;
    self.fb = None;
  }

  fn get_resize_capabilities(&self, g: &Graphics) -> ResizeCapabilities {
    ResizeCapabilities {
      min_width: Some(1),


@@ 62,12 68,6 @@ impl Element for Viewport {
      max_height: None,
    }
  }

  fn set_size(&mut self, width: u32, height: u32) {
    self.width = width;
    self.height = height;
    self.fb = None;
  }
}

pub struct ViewportData {

M src/ui/event.rs => src/ui/event.rs +10 -0
@@ 1,3 1,5 @@
use std::mem::replace;

pub struct EventQueue<E> {
  events: Vec<E>,
}


@@ 16,4 18,12 @@ impl<E> EventQueue<E> {
      op(event);
    }
  }

  pub fn pop_event(&mut self) -> Option<E> {
    self.events.pop()
  }

  pub fn pop_events(&mut self) -> Vec<E> {
    replace(&mut self.events, vec![])
  }
}
\ No newline at end of file

M src/ui/graphics.rs => src/ui/graphics.rs +17 -15
@@ 16,9 16,11 @@ pub struct Graphics<'a, 'b, 'c> {
  fr: &'a BakedFont<'a>,
  sm: &'a mut ShaderManager<'c>,
  perspective: Mat4,
  mats: Vec<Transformation>,
  strings: Vec<(String, f32, f32, Color, Transformation)>,
  lines: Vec<(i32, i32, i32, i32, Color, Transformation)>,
  modelview: Vec<Transformation>,

  // for batched rendering
  buffered_strings: Vec<(String, f32, f32, Color, Transformation)>,
  buffered_lines: Vec<(i32, i32, i32, i32, Color, Transformation)>,
}

impl<'a, 'b, 'c> Graphics<'a, 'b, 'c> {


@@ 29,9 31,9 @@ impl<'a, 'b, 'c> Graphics<'a, 'b, 'c> {
      fr,
      sm,
      perspective,
      mats: vec![Transformation::Identity],
      strings: vec![],
      lines: vec![],
      modelview: vec![Transformation::Identity],
      buffered_strings: vec![],
      buffered_lines: vec![],
    }
  }



@@ 45,12 47,12 @@ impl<'a, 'b, 'c> Graphics<'a, 'b, 'c> {

  pub fn perspective(&self) -> &Mat4 { &self.perspective }

  pub fn modelview(&self) -> &Transformation { self.mats.last().unwrap() }
  pub fn modelview(&self) -> &Transformation { self.modelview.last().unwrap() }

  pub fn mvp(&self) -> Mat4 { *self.perspective() * self.modelview().as_matrix() }

  pub fn transform(&mut self, tr: Transformation) {
    let m = self.mats.last_mut().unwrap();
    let m = self.modelview.last_mut().unwrap();
    *m = m.combine(tr);
  }



@@ 75,11 77,11 @@ impl<'a, 'b, 'c> Graphics<'a, 'b, 'c> {
      }
    };

    self.strings.push((t.to_owned(), x as f32, y as f32, props.color, *self.modelview()));
    self.buffered_strings.push((t.to_owned(), x as f32, y as f32, props.color, *self.modelview()));
  }

  pub fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Color) {
    self.lines.push((x1, y1, x2, y2, color, *self.modelview()));
    self.buffered_lines.push((x1, y1, x2, y2, color, *self.modelview()));
  }

  pub fn draw_polygon(&mut self, coords: &[[i32; 2]], color: Color) {


@@ 134,23 136,23 @@ impl<'a, 'b, 'c> Graphics<'a, 'b, 'c> {
  }

  pub fn push_matrix<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
    self.mats.push(*self.modelview());
    self.modelview.push(*self.modelview());
    let result = f(self);
    self.mats.pop();
    self.modelview.pop();
    result
  }

  pub fn flush(&mut self) {
    {
      let fr = self.fr;
      let data = self.strings
      let data = self.buffered_strings
        .drain_filter(|(.., e)| e.try_as_translation().is_some())
        .map(|(a, b, c, d, e)| fr.create_data(&[(&a, b, c, d)]).translate(e.try_as_translation().unwrap()))
        .fold1(|a, b| a.combine(b))
        .map(|a| a.update_vbuf())
        .into_iter().for_each(|b| b.draw(self.surface, &self.perspective));

      for (a, b, c, d, e) in self.strings.drain(..) {
      for (a, b, c, d, e) in self.buffered_strings.drain(..) {
        self.fr.draw_string(self.surface, &(self.perspective * e.as_matrix()), &a, b, c, d);
      }
    }


@@ 165,7 167,7 @@ impl<'a, 'b, 'c> Graphics<'a, 'b, 'c> {
      }

      let mut data = vec![];
      for (x, y, x1, y1, color, tr) in self.lines.drain(..) {
      for (x, y, x1, y1, color, tr) in self.buffered_lines.drain(..) {
        data.push(PolygonPoint { xyz: *tr.transform_vec3(vec3(x as f32, y as f32, 0.0)).as_ref(), color: color.into_array() });
        data.push(PolygonPoint { xyz: *tr.transform_vec3(vec3(x1 as f32, y1 as f32, 0.0)).as_ref(), color: color.into_array() });
      };

M src/ui/host.rs => src/ui/host.rs +14 -1
@@ 1,4 1,4 @@
use glium::glutin::event::{ElementState, ModifiersState, MouseButton};
use glium::glutin::event::{ElementState, ModifiersState, MouseButton, ScanCode, VirtualKeyCode};

use crate::ui::element::Element;
use crate::ui::graphics::Graphics;


@@ 71,7 71,20 @@ impl UiHost {

  pub fn on_click(&mut self, state: ElementState, button: MouseButton, modifiers: ModifiersState) {
    if let Some(ref mut c) = self.current_controller {
      c.try_focus(self.mouse_x, self.mouse_y);
      c.on_click(self.mouse_x, self.mouse_y, state, button, modifiers);
    }
  }

  pub fn on_key(&mut self, state: ElementState, vk: Option<VirtualKeyCode>, scancode: ScanCode, modifiers: ModifiersState) {
    if let Some(ref mut c) = self.current_controller {
      c.on_key(state, vk, scancode, modifiers);
    }
  }

  pub fn on_key_typed(&mut self, ch: char) {
    if let Some(ref mut c) = self.current_controller {
      c.on_key_typed(ch);
    }
  }
}
\ No newline at end of file

M src/ui/mod.rs => src/ui/mod.rs +43 -2
@@ 1,8 1,8 @@
use glium::glutin::event::{ElementState, ModifiersState, MouseButton};
use glium::glutin::event::{ElementState, ModifiersState, MouseButton, ScanCode, VirtualKeyCode};

use crate::ui::access::{UiAccess, Values};
use crate::ui::controller::UiController;
use crate::ui::element::{Element, Positionable};
use crate::ui::element::{Element, FocusResult, Positionable};
use crate::ui::graphics::Graphics;
use crate::ui::rescap::ResizeCapabilities;



@@ 16,6 16,7 @@ pub mod graphics;
pub mod event;
pub mod host;
pub mod controller;
pub mod util;

pub struct GenUi<E: UiAccess, C: UiController> {
  elements: E,


@@ 70,6 71,46 @@ impl<E: UiAccess + 'static, C: UiController<Elements=E>> Element for GenUi<E, C>
    true
  }

  fn on_key(&mut self, state: ElementState, vk: Option<VirtualKeyCode>, scancode: ScanCode, modifiers: ModifiersState) {
    self.for_each(|e| e.on_key(state, vk, scancode, modifiers));
  }

  fn on_key_typed(&mut self, c: char) {
    self.for_each(|e| e.on_key_typed(c));
  }

  fn try_focus(&mut self, mouse_x: i32, mouse_y: i32) -> FocusResult {
    // First, try focus any of the components to see if focus should change
    // If there should be a focus change, unfocus everything but the newly
    // focused component.
    // The alternative would be to keep track of the currently focused
    // component, maybe eventually. This should work for now, though.

    let values: &[E::Access] = E::Access::values();

    let r = values.iter().fold((None, FocusResult::Continue), |acc, &a| {
      match acc {
        (_, FocusResult::Abort) | (_, FocusResult::AcquiredFocus) => acc,
        _ => {
          let el = self.elements.get_element_mut(a.clone());
          (Some(a), el.try_focus(mouse_x - el.x(), mouse_y - el.y()))
        }
      }
    });

    if r.1 == FocusResult::AcquiredFocus {
      values.iter()
        .filter(|&&a| a != r.0.unwrap())
        .for_each(|a| self.elements.get_element_mut(*a).on_focus_lost());
    }

    r.1
  }

  fn on_focus_lost(&mut self) {
    self.for_each(|e| e.on_focus_lost());
  }

  fn get_resize_capabilities(&self, g: &Graphics) -> ResizeCapabilities {
    self.controller.get_resize_capabilities(&self.elements, g)
  }

M src/ui/panel.rs => src/ui/panel.rs +18 -2
@@ 1,8 1,8 @@
use std::ops::{Deref, DerefMut};

use glium::glutin::event::{ElementState, ModifiersState, MouseButton};
use glium::glutin::event::{ElementState, ModifiersState, MouseButton, ScanCode, VirtualKeyCode};

use crate::ui::element::{Element, Positionable};
use crate::ui::element::{Element, FocusResult, Positionable};
use crate::ui::graphics::Graphics;
use crate::ui::rescap::ResizeCapabilities;



@@ 70,6 70,22 @@ impl<T: Element> Element for Container<T> {
    self.drawable_mut().on_click(mouse_x, mouse_y, state, button, modifiers)
  }

  fn on_key(&mut self, state: ElementState, vk: Option<VirtualKeyCode>, scancode: ScanCode, modifiers: ModifiersState) {
    self.drawable_mut().on_key(state, vk, scancode, modifiers);
  }

  fn on_key_typed(&mut self, c: char) {
    self.drawable_mut().on_key_typed(c);
  }

  fn try_focus(&mut self, mouse_x: i32, mouse_y: i32) -> FocusResult {
    self.drawable_mut().try_focus(mouse_x, mouse_y)
  }

  fn on_focus_lost(&mut self) {
    self.drawable_mut().on_focus_lost()
  }

  fn get_resize_capabilities(&self, g: &Graphics) -> ResizeCapabilities {
    self.drawable.get_resize_capabilities(g)
  }

A src/ui/util.rs => src/ui/util.rs +4 -0
@@ 0,0 1,4 @@
#[inline]
pub fn is_in_bounds(min_x: i32, min_y: i32, max_x: i32, max_y: i32, x: i32, y: i32) -> bool {
  x >= min_x && y >= min_y && x < max_x && y < max_y
}
\ No newline at end of file