stevenarella/src/ui/mod.rs

1411 lines
46 KiB
Rust
Raw Normal View History

2016-03-16 14:25:35 -04:00
// Copyright 2016 Matthew Collins
2015-09-17 11:21:56 -04:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
2015-09-18 17:02:08 -04:00
// limitations under the License.
2015-09-21 08:08:06 -04:00
pub mod logo;
2016-04-24 07:22:04 -04:00
use std::rc::{Rc, Weak};
use std::cell::{RefCell, RefMut};
use crate::render;
use crate::format;
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
use glutin::VirtualKeyCode;
Add support for compiling WebAssembly wasm32-unknown-unknown target (#92) Note this only is the first step in web support, although the project compiles, it doesn't run! Merging now to avoid branch divergence, until dependencies can be updated for wasm support. * Add instructions to build for wasm32-unknown-unknown with wasm-pack in www/ * Update to rust-clipboard fork to compile with emscripten https://github.com/aweinstock314/rust-clipboard/pull/62 * Exclude reqwest dependency in wasm32 * Exclude compiling clipboard pasting on wasm32 * Exclude reqwest-using code from wasm32 * Install wasm target with rustup in Travis CI * Update to collision 0.19.0 Fixes wasm incompatibility in deprecated rustc-serialize crate: https://github.com/rustgd/collision-rs/issues/106 error[E0046]: not all trait items implemented, missing: `encode` --> github.com-1ecc6299db9ec823/rustc-serialize-0.3.24/src/serialize.rs:1358:1 * Increase travis_wait time even further, try 120 minutes * Set RUST_BACKTRACE=1 in main * Remove unused unneeded bzip2 features in zip crate To fix wasm32-unknown-unknown target compile error: error[E0432]: unresolved imports `libc::c_int`, `libc::c_uint`, `libc::c_void`, `libc::c_char` --> src/github.com-1ecc6299db9ec823/bzip2-sys-0.1.7/lib.rs:5:12 | 5 | use libc::{c_int, c_uint, c_void, c_char}; | ^^^^^ ^^^^^^ ^^^^^^ ^^^^^^ no `c_char` in the root | | | | | | | no `c_void` in the root | | no `c_uint` in the root | no `c_int` in the root * flate2 use Rust backend * Add console_error_panic_hook module for wasm backtraces * Build using wasm-pack, wasm-bindgen, run with wasm-app * Update to miniz_oxide 0.2.1, remove patch for https://github.com/Frommi/miniz_oxide/issues/42 * Update to official clipboard crate since https://github.com/aweinstock314/rust-clipboard/pull/62 was merged, but git revision pending release * Update to branch of glutin attempting to build for wasm https://github.com/iceiix/glutin/pull/1 * Update winit dependency of glutin to git master https://github.com/iceiix/winit/pull/2 * Update to glutin branch with working (compiles, doesn't run) wasm_stub * Add app name in title on web page * Add wasm to Travis-CI test matrix * Update glutin to fix Windows EGL compilation on AppVeyor https://github.com/iceiix/glutin/pull/1/commits/97797352b5242436cb82d8ecfb44242b69766e4c
2019-03-03 11:32:36 -05:00
#[cfg(not(target_arch = "wasm32"))]
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
use clipboard::{ClipboardProvider, ClipboardContext};
2015-09-18 17:02:08 -04:00
const SCALED_WIDTH: f64 = 854.0;
const SCALED_HEIGHT: f64 = 480.0;
#[derive(Clone, Copy, PartialEq)]
2015-09-18 17:02:08 -04:00
pub enum Mode {
2015-10-07 14:36:59 -04:00
Scaled,
Unscaled(f64),
2015-09-18 17:02:08 -04:00
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum VAttach {
2015-10-07 14:36:59 -04:00
Top,
Middle,
Bottom,
2015-09-18 17:02:08 -04:00
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum HAttach {
2015-10-07 14:36:59 -04:00
Left,
Center,
Right,
2015-09-18 17:02:08 -04:00
}
#[derive(Clone)]
struct Region {
2015-10-07 14:36:59 -04:00
x: f64,
y: f64,
w: f64,
h: f64,
2015-09-18 17:02:08 -04:00
}
impl Region {
2015-10-07 14:36:59 -04:00
fn intersects(&self, o: &Region) -> bool {
!(self.x + self.w < o.x || self.x > o.x + o.w || self.y + self.h < o.y ||
self.y > o.y + o.h)
}
2015-09-18 17:02:08 -04:00
}
2016-04-24 07:22:04 -04:00
macro_rules! define_elements {
(
$($name:ident,)*
) => (
#[doc(hidden)]
pub enum Element {
$($name(Rc<RefCell<$name>>),)*
2015-10-07 14:36:59 -04:00
}
2015-09-23 15:16:25 -04:00
2016-04-24 07:22:04 -04:00
impl Element {
fn get_draw_index(&self) -> isize {
match *self {
$(
Element::$name(ref inner) => inner.borrow().draw_index,
)*
}
}
2015-09-18 17:02:08 -04:00
2016-04-24 07:22:04 -04:00
fn is_unused(&self) -> bool {
match *self {
$(
2017-05-14 04:58:34 -04:00
Element::$name(ref inner) => Rc::strong_count(inner) == 1,
2016-04-24 07:22:04 -04:00
)*
}
}
2015-09-21 16:11:30 -04:00
2016-04-24 07:22:04 -04:00
fn tick(&self, renderer: &mut render::Renderer) {
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
el.tick(renderer);
},
)*
}
}
2015-09-23 15:16:25 -04:00
2016-04-24 07:22:04 -04:00
fn draw(&self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> RefMut<[u8]> {
match *self {
$(
Element::$name(ref inner) => {
let el = inner.borrow_mut();
RefMut::map(el, |el| el.draw(renderer, r, sw, sh, width, height, delta))
},
)*
}
}
2015-10-07 14:36:59 -04:00
2016-04-24 07:22:04 -04:00
fn check_rebuild(&self) -> bool {
match *self {
$(
Element::$name(ref inner) => {
let el = inner.borrow();
el.check_rebuild()
},
)*
}
}
2015-10-07 14:36:59 -04:00
2016-04-24 07:22:04 -04:00
fn force_rebuild(&self) {
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
el.needs_rebuild = true;
},
)*
}
}
fn get_size(&self) -> (f64, f64) {
match *self {
$(
Element::$name(ref inner) => {
let el = inner.borrow();
el.get_size()
},
)*
}
}
fn get_position(&self) -> (f64, f64) {
match *self {
$(
Element::$name(ref inner) => {
let el = inner.borrow();
(el.x, el.y)
},
)*
}
}
fn get_attachment(&self) -> (VAttach, HAttach) {
match *self {
$(
Element::$name(ref inner) => {
let el = inner.borrow();
(el.v_attach, el.h_attach)
},
)*
}
}
fn hover_at(&self, r: &Region, game: &mut crate::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool {
2016-04-24 07:22:04 -04:00
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
el.hover_at(r, game, mx, my, sw, sh)
},
)*
}
}
fn click_at(&self, r: &Region, game: &mut crate::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool {
2016-04-24 07:22:04 -04:00
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
el.click_at(r, game, mx, my, sw, sh)
},
)*
}
}
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
fn key_press(&self, game: &mut crate::Game, key: VirtualKeyCode, down: bool, ctrl_pressed: bool) {
2016-04-24 07:22:04 -04:00
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
2016-04-25 19:50:16 -04:00
el.key_press(game, key, down, ctrl_pressed);
2016-04-24 07:22:04 -04:00
},
)*
}
}
fn key_type(&self, game: &mut crate::Game, c: char) {
2016-04-24 07:22:04 -04:00
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
el.key_type(game, c);
},
)*
}
}
fn is_focused(&self) -> bool {
match *self {
$(
Element::$name(ref inner) => {
let el = inner.borrow();
el.focused
},
)*
}
}
fn set_focused(&self, val: bool) {
match *self {
$(
Element::$name(ref inner) => {
let mut el = inner.borrow_mut();
el.focused = val;
},
)*
}
}
2015-10-07 14:36:59 -04:00
}
2016-04-24 07:22:04 -04:00
#[doc(hidden)]
enum WeakElement {
$($name(Weak<RefCell<$name>>),)*
}
impl WeakElement {
fn upgrade(&self) -> Option<Element> {
match *self {
$(
WeakElement::$name(ref inner) => {
inner.upgrade().map(Element::$name)
2016-04-24 07:22:04 -04:00
},
)*
}
}
}
)
2015-09-23 15:16:25 -04:00
}
2016-04-24 07:22:04 -04:00
define_elements! {
Image,
Batch,
Text,
Formatted,
Button,
TextBox,
}
2015-10-07 14:36:59 -04:00
const SCREEN: Region = Region {
x: 0.0,
y: 0.0,
w: SCALED_WIDTH,
h: SCALED_HEIGHT,
};
2015-09-18 17:02:08 -04:00
2016-04-24 07:22:04 -04:00
pub trait ElementHolder {
fn add(&mut self, el: Element, auto_free: bool);
}
2015-09-18 17:02:08 -04:00
pub struct Container {
2016-04-24 07:22:04 -04:00
elements: Vec<Element>,
focusable_elements: Vec<WeakElement>,
2015-10-07 14:36:59 -04:00
pub mode: Mode,
last_mode: Mode,
2015-10-07 14:36:59 -04:00
version: usize,
last_sw: f64,
last_sh: f64,
last_width: f64,
last_height: f64,
2015-09-18 17:02:08 -04:00
}
impl Container {
2015-10-07 14:36:59 -04:00
pub fn new() -> Container {
Container {
2016-04-24 07:22:04 -04:00
elements: Vec::new(),
focusable_elements: Vec::new(),
2015-10-07 14:36:59 -04:00
mode: Mode::Scaled,
last_mode: Mode::Scaled,
2015-10-07 14:36:59 -04:00
version: 0xFFFF,
2016-04-24 07:22:04 -04:00
2015-10-07 14:36:59 -04:00
last_sw: 0.0,
last_sh: 0.0,
last_width: 0.0,
last_height: 0.0,
}
}
pub fn tick(&mut self, renderer: &mut render::Renderer, delta: f64, width: f64, height: f64) {
let (sw, sh) = match self.mode {
Mode::Scaled => (SCALED_WIDTH / width, SCALED_HEIGHT / height),
Mode::Unscaled(scale) => (scale, scale),
};
if self.last_sw != sw || self.last_sh != sh || self.last_width != width ||
self.last_height != height || self.version != renderer.ui.version || self.last_mode != self.mode {
2015-10-07 14:36:59 -04:00
self.last_sw = sw;
self.last_sh = sh;
self.last_width = width;
self.last_height = height;
self.last_mode = self.mode;
2016-04-24 07:22:04 -04:00
for e in &self.elements {
e.force_rebuild();
2015-10-07 14:36:59 -04:00
}
self.version = renderer.ui.version;
}
2015-09-21 08:08:06 -04:00
2016-04-24 07:22:04 -04:00
// Drop elements with no refs
self.elements.retain(|v| !v.is_unused());
// Drop focusable elements that no longer exist
self.focusable_elements.retain(|v| v.upgrade().is_some());
// If we don't have an element focused, focus one
if !self.focusable_elements.is_empty()
&& !self.focusable_elements.iter()
.flat_map(|v| v.upgrade())
.any(|v| v.is_focused()) {
self.cycle_focus()
}
2016-04-24 07:22:04 -04:00
for e in &self.elements {
e.tick(renderer);
2015-10-07 14:36:59 -04:00
}
2016-04-24 07:22:04 -04:00
for e in &self.elements {
let r = Self::compute_draw_region(e, sw, sh, &SCREEN);
2015-10-07 14:36:59 -04:00
if r.intersects(&SCREEN) {
2016-04-24 07:22:04 -04:00
let data = e.draw(renderer, &r, sw, sh, width, height, delta);
renderer.ui.add_bytes(&data);
2015-10-07 14:36:59 -04:00
}
}
}
pub fn hover_at(&mut self, game: &mut crate::Game, x: f64, y: f64, width: f64, height: f64) {
2015-10-07 14:36:59 -04:00
let (sw, sh) = match self.mode {
Mode::Scaled => (SCALED_WIDTH / width, SCALED_HEIGHT / height),
Mode::Unscaled(scale) => (scale, scale),
};
let mx = (x / width) * SCALED_WIDTH;
let my = (y / height) * SCALED_HEIGHT;
2016-04-24 07:22:04 -04:00
for e in &self.elements {
let r = Self::compute_draw_region(e, sw, sh, &SCREEN);
e.hover_at(&r, game, mx, my, sw, sh);
2015-10-07 14:36:59 -04:00
}
}
pub fn click_at(&mut self, game: &mut crate::Game, x: f64, y: f64, width: f64, height: f64) {
2015-10-07 14:36:59 -04:00
let (sw, sh) = match self.mode {
Mode::Scaled => (SCALED_WIDTH / width, SCALED_HEIGHT / height),
Mode::Unscaled(scale) => (scale, scale),
};
let mx = (x / width) * SCALED_WIDTH;
let my = (y / height) * SCALED_HEIGHT;
2016-04-24 07:22:04 -04:00
for e in &self.elements {
let r = Self::compute_draw_region(e, sw, sh, &SCREEN);
if mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h {
e.click_at(&r, game, mx, my, sw, sh);
2015-10-07 14:36:59 -04:00
}
}
}
2016-04-24 07:22:04 -04:00
fn add_focusable(&mut self, el: WeakElement) {
self.focusable_elements.push(el);
}
pub fn cycle_focus(&mut self) {
if self.focusable_elements.is_empty() {
return;
}
let focusables = self.focusable_elements.iter()
.flat_map(|v| v.upgrade())
.collect::<Vec<_>>();
// Find the last focused element if there is one
let last_focus = focusables.iter()
.position(|v| v.is_focused());
let next_focus = last_focus.map_or(0, |v| v + 1) % focusables.len();
// Clear the last focus
if let Some(focus) = last_focus {
focusables[focus].set_focused(false);
}
focusables[next_focus].set_focused(true);
}
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
pub fn key_press(&mut self, game: &mut crate::Game, key: VirtualKeyCode, down: bool, ctrl_pressed: bool) {
if key == VirtualKeyCode::Tab {
if !down {
self.cycle_focus();
}
return;
}
2016-04-24 07:22:04 -04:00
for el in self.focusable_elements.iter()
.flat_map(|v| v.upgrade()) {
if el.is_focused() {
2016-04-25 19:50:16 -04:00
el.key_press(game, key, down, ctrl_pressed);
}
}
}
pub fn key_type(&mut self, game: &mut crate::Game, c: char) {
if c < ' ' {
return;
}
2016-04-24 07:22:04 -04:00
for el in self.focusable_elements.iter()
.flat_map(|v| v.upgrade()) {
if el.is_focused() {
el.key_type(game, c);
}
}
}
2016-04-24 07:22:04 -04:00
fn compute_draw_region(el: &Element, sw: f64, sh: f64, super_region: &Region) -> Region {
2015-10-07 14:36:59 -04:00
let mut r = Region {
x: 0.0,
y: 0.0,
w: 0.0,
h: 0.0,
};
2016-04-24 07:22:04 -04:00
let (w, h) = el.get_size();
let (ox, oy) = el.get_position();
2015-10-07 14:36:59 -04:00
r.w = w * sw;
r.h = h * sh;
2016-04-24 07:22:04 -04:00
let (v_attach, h_attach) = el.get_attachment();
2015-10-07 14:36:59 -04:00
match h_attach {
HAttach::Left => r.x = ox * sw,
HAttach::Center => r.x = (super_region.w / 2.0) - (r.w / 2.0) + ox * sw,
HAttach::Right => r.x = super_region.w - ox * sw - r.w,
}
match v_attach {
VAttach::Top => r.y = oy * sh,
VAttach::Middle => r.y = (super_region.h / 2.0) - (r.h / 2.0) + oy * sh,
VAttach::Bottom => r.y = super_region.h - oy * sh - r.h,
}
r.x += super_region.x;
r.y += super_region.y;
r
}
2015-09-18 17:02:08 -04:00
}
2016-04-24 07:22:04 -04:00
impl ElementHolder for Container {
fn add(&mut self, el: Element, auto_free: bool) {
if !auto_free {
panic!("Auto free elements are not allowed on root");
}
self.elements.push(el);
self.elements.sort_by_key(|v| v.get_draw_index());
}
}
2016-04-24 07:22:04 -04:00
trait UIElement {
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8];
fn get_size(&self) -> (f64, f64);
fn is_dirty(&self) -> bool;
fn post_init(_: Rc<RefCell<Self>>) {}
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
fn key_press(&mut self, _game: &mut crate::Game, _key: VirtualKeyCode, _down: bool, _ctrl_pressed: bool) {}
fn key_type(&mut self, _game: &mut crate::Game, _c: char) {}
2016-04-24 07:22:04 -04:00
fn tick(&mut self, renderer: &mut render::Renderer);
}
macro_rules! element {
(
ref $nameref:ident
pub struct $name:ident {
$(pub $pfield_name:ident : $pfield_type:ty,)*
$(priv $field_name:ident : $field_type:ty,)*
}
builder $builder:ident {
$(hardcode $hname:ident = $hval:expr,)*
$(simple $sname:ident : $sty:ty,)*
$(optional $oname:ident : $oty:ty = $oval:expr,)*
$(noset $nname:ident : $nty:ty = |$bref:ident| $nval:expr,)*
}
) => (
pub struct $name {
$(pub $pfield_name : $pfield_type,)*
$($field_name : $field_type,)*
// Base fields
draw_index: isize,
elements: Vec<(bool, Element)>,
pub x: f64,
pub y: f64,
pub v_attach: VAttach,
pub h_attach: HAttach,
data: Vec<u8>,
needs_rebuild: bool,
hover_funcs: Vec<Box<Fn(&mut $name, bool, &mut crate::Game) -> bool>>,
2016-04-24 07:22:04 -04:00
hover_state: bool,
click_funcs: Vec<Box<Fn(&mut $name, &mut crate::Game) -> bool>>,
2016-04-24 07:22:04 -04:00
focused: bool,
// Change checking
last_x: f64,
last_y: f64,
last_v_attach: VAttach,
last_h_attach: HAttach,
last_width: f64,
last_height: f64,
}
pub type $nameref = Rc<RefCell<$name>>;
impl ElementHolder for $name {
fn add(&mut self, el: Element, auto_free: bool) {
self.elements.push((auto_free, el));
self.elements.sort_by_key(|v| v.1.get_draw_index());
}
}
impl $name {
fn check_rebuild(&self) -> bool {
if self.needs_rebuild {
return true;
}
// Check for changes that would cause child
// elements to need an update
let (w, h) = self.get_size();
if self.last_x != self.x || self.last_y != self.y
|| self.last_width != w || self.last_height != h
|| self.last_v_attach != self.v_attach || self.last_h_attach != self.h_attach {
return true;
}
if self.is_dirty() {
return true;
}
for e in &self.elements {
if e.1.check_rebuild() {
return true;
}
}
false
}
fn super_draw(&mut self, renderer: &mut render::Renderer, super_region: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) {
if !self.needs_rebuild {
let (w, h) = self.get_size();
self.needs_rebuild = self.last_x != self.x || self.last_y != self.y
|| self.last_width != w || self.last_height != h
|| self.last_v_attach != self.v_attach || self.last_h_attach != self.h_attach;
}
self.elements.retain(|v| !v.0 || !v.1.is_unused());
for &(_, ref e) in &self.elements {
if self.needs_rebuild {
e.force_rebuild();
}
let r = Container::compute_draw_region(e, sw, sh, &super_region);
let data = e.draw(renderer, &r, sw, sh, width, height, delta);
self.data.extend_from_slice(&data);
}
self.needs_rebuild = false;
self.last_x = self.x;
self.last_y = self.y;
let (w, h) = self.get_size();
self.last_width = w;
self.last_height = h;
self.last_v_attach = self.v_attach;
self.last_h_attach = self.h_attach;
}
fn super_tick(&mut self, renderer: &mut render::Renderer) {
for &(_, ref e) in &self.elements {
e.tick(renderer);
}
}
fn hover_at(&mut self, super_region: &Region, game: &mut crate::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool {
2016-04-24 07:22:04 -04:00
use std::mem;
let mut handle_self = true;
for e in &self.elements {
let r = Container::compute_draw_region(&e.1, sw, sh, &super_region);
if e.1.hover_at(&r, game, mx, my, sw, sh) {
handle_self = false;
}
}
if handle_self {
let state = mx >= super_region.x && mx <= super_region.x + super_region.w && my >= super_region.y && my <= super_region.y + super_region.h;
if state != self.hover_state {
self.hover_state = state;
let len = self.hover_funcs.len();
let mut temp = mem::replace(&mut self.hover_funcs, Vec::with_capacity(len));
let mut block_prop = false;
for func in &temp {
block_prop |= (func)(self, state, game);
}
self.hover_funcs.append(&mut temp);
block_prop
} else {
false
}
} else {
true // Carry up
}
}
pub fn add_hover_func<F: Fn(&mut $name, bool, &mut crate::Game) -> bool + 'static>(&mut self, func: F) {
2016-04-24 07:22:04 -04:00
self.hover_funcs.push(Box::new(func));
}
fn click_at(&mut self, super_region: &Region, game: &mut crate::Game, mx: f64, my: f64, sw: f64, sh: f64) -> bool {
2016-04-24 07:22:04 -04:00
use std::mem;
let mut handle_self = true;
for e in &self.elements {
let r = Container::compute_draw_region(&e.1, sw, sh, &super_region);
if mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h {
if e.1.click_at(&r, game, mx, my, sw, sh) {
handle_self = false;
}
}
}
if handle_self {
let len = self.click_funcs.len();
let mut temp = mem::replace(&mut self.click_funcs, Vec::with_capacity(len));
let mut block_prop = false;
for func in &temp {
block_prop |= (func)(self, game);
}
self.click_funcs.append(&mut temp);
block_prop
} else {
true // Carry up
}
}
pub fn add_click_func<F: Fn(&mut $name, &mut crate::Game) -> bool + 'static>(&mut self, func: F) {
2016-04-24 07:22:04 -04:00
self.click_funcs.push(Box::new(func));
}
pub fn make_focusable(this: &$nameref, container: &mut Container) {
container.add_focusable(WeakElement::$name(Rc::downgrade(&this)));
}
}
pub struct $builder {
$(
$sname: Option<$sty>,
)*
$(
$oname: Option<$oty>,
)*
$(
$nname: Option<$nty>,
)*
// Base fields
draw_index: isize,
x: Option<f64>,
y: Option<f64>,
v_attach: Option<VAttach>,
h_attach: Option<HAttach>,
}
impl $builder {
$(
pub fn $sname<T: Into<$sty>>(mut self, val: T) -> Self {
self.$sname = Some(val.into());
self
}
)*
$(
pub fn $oname<T: Into<$oty>>(mut self, val: T) -> Self {
self.$oname = Some(val.into());
self
}
)*
// Base fields
pub fn draw_index(mut self, draw_index: isize) -> Self {
self.draw_index = draw_index;
self
}
pub fn position(mut self, x: f64, y: f64) -> Self {
self.x = Some(x);
self.y = Some(y);
self
}
pub fn alignment(mut self, v_attach: VAttach, h_attach: HAttach) -> Self {
self.v_attach = Some(v_attach);
self.h_attach = Some(h_attach);
self
}
pub fn new() -> Self {
$builder {
$(
$sname: None,
)*
$(
$oname: None,
)*
$(
$nname: None,
)*
draw_index: 0,
x: None,
y: None,
v_attach: None,
h_attach: None,
}
}
pub fn create<H: ElementHolder>(self, ui: &mut H) -> $nameref {
self.create_internal(ui, true)
}
pub fn attach<H: ElementHolder>(self, ui: &mut H) -> $nameref {
self.create_internal(ui, false)
}
fn create_internal<H: ElementHolder>(self, ui: &mut H, auto_free: bool) -> $nameref {
$(
let $nname = {let $bref = &self; $nval};
)*
let v = Rc::new(RefCell::new($name {
$(
$hname: $hval,
)*
$(
$sname: self.$sname.expect(concat!("Missing required field ", stringify!($sname))),
)*
$(
$oname: self.$oname.unwrap_or($oval),
)*
$(
$nname: $nname,
)*
// Base fields
draw_index: self.draw_index,
elements: Vec::new(),
x: self.x.unwrap_or(0.0),
y: self.y.unwrap_or(0.0),
v_attach: self.v_attach.unwrap_or(VAttach::Top),
h_attach: self.h_attach.unwrap_or(HAttach::Left),
last_x: self.x.unwrap_or(0.0),
last_y: self.y.unwrap_or(0.0),
last_v_attach: self.v_attach.unwrap_or(VAttach::Top),
last_h_attach: self.h_attach.unwrap_or(HAttach::Left),
last_width: 0.0,
last_height: 0.0,
data: vec![],
needs_rebuild: true,
hover_funcs: vec![],
hover_state: false,
click_funcs: vec![],
focused: false,
}));
$name::post_init(v.clone());
ui.add(Element::$name(v.clone()), auto_free);
v
}
}
)
}
element! {
ref ImageRef
pub struct Image {
pub texture: String,
pub width: f64,
pub height: f64,
pub colour: (u8, u8, u8, u8),
pub texture_coords: (f64, f64, f64, f64),
priv last_texture: String,
priv last_colour: (u8, u8, u8, u8),
priv last_texture_coords: (f64, f64, f64, f64),
}
2016-04-24 07:22:04 -04:00
builder ImageBuilder {
hardcode last_texture = "".into(),
hardcode last_colour = (0, 0, 0, 0),
hardcode last_texture_coords = (0.0, 0.0, 0.0, 0.0),
simple texture: String,
optional colour: (u8, u8, u8, u8) = (255, 255, 255, 255),
optional texture_coords: (f64, f64, f64, f64) = (0.0, 0.0, 1.0, 1.0),
noset width: f64 = |b| b.width.expect("Missing required field width"),
noset height: f64 = |b| b.height.expect("Missing required field height"),
}
}
2016-04-24 07:22:04 -04:00
impl ImageBuilder {
pub fn size(mut self, width: f64, height: f64) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
2016-04-24 07:22:04 -04:00
}
2016-04-24 07:22:04 -04:00
impl UIElement for Image {
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] {
if self.check_rebuild() {
self.data.clear();
let texture = render::Renderer::get_texture(renderer.get_textures_ref(), &self.texture);
let mut element = render::ui::UIElement::new(
&texture,
r.x, r.y, r.w, r.h,
self.texture_coords.0, self.texture_coords.1, self.texture_coords.2, self.texture_coords.3,
);
element.r = self.colour.0;
element.g = self.colour.1;
element.b = self.colour.2;
element.a = self.colour.3;
self.data.extend_from_slice(&element.bytes(width, height));
self.super_draw(renderer, r, sw, sh, width, height, delta);
self.last_texture = self.texture.clone();
self.last_colour = self.colour;
self.last_texture_coords = self.texture_coords;
}
&mut self.data
}
fn tick(&mut self, renderer: &mut render::Renderer) {
self.super_tick(renderer);
}
fn get_size(&self) -> (f64, f64) {
(self.width, self.height)
}
fn is_dirty(&self) -> bool {
self.last_texture != self.texture
|| self.last_colour != self.colour
|| self.last_texture_coords != self.texture_coords
}
2015-09-18 17:02:08 -04:00
}
2016-04-24 07:22:04 -04:00
element! {
ref BatchRef
pub struct Batch {
pub width: f64,
pub height: f64,
}
builder BatchBuilder {
noset width: f64 = |b| b.width.expect("Missing required field width"),
noset height: f64 = |b| b.height.expect("Missing required field height"),
}
}
impl BatchBuilder {
pub fn size(mut self, width: f64, height: f64) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
}
impl UIElement for Batch {
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] {
if self.check_rebuild() {
self.data.clear();
self.super_draw(renderer, r, sw, sh, width, height, delta);
}
2016-04-24 07:22:04 -04:00
&mut self.data
}
fn tick(&mut self, renderer: &mut render::Renderer) {
self.super_tick(renderer);
}
2016-04-24 07:22:04 -04:00
fn get_size(&self) -> (f64, f64) {
(self.width, self.height)
}
fn is_dirty(&self) -> bool {
false
}
}
element! {
ref TextRef
pub struct Text {
pub text: String,
pub width: f64,
pub height: f64,
pub scale_x: f64,
pub scale_y: f64,
pub colour: (u8, u8, u8, u8),
pub rotation: f64,
priv last_text: String,
priv last_scale_x: f64,
priv last_scale_y: f64,
priv last_colour: (u8, u8, u8, u8),
priv last_rotation: f64,
}
builder TextBuilder {
hardcode width = 0.0,
hardcode height = 18.0,
hardcode last_text = "".into(),
hardcode last_scale_x = 0.0,
hardcode last_scale_y = 0.0,
hardcode last_colour = (0, 0, 0, 0),
hardcode last_rotation = 0.0,
simple text: String,
optional scale_x: f64 = 1.0,
optional scale_y: f64 = 1.0,
optional colour: (u8, u8, u8, u8) = (255, 255, 255, 255),
optional rotation: f64 = 0.0,
}
}
impl UIElement for Text {
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] {
if self.check_rebuild() {
self.data.clear();
let mut text = if self.rotation == 0.0 {
renderer.ui.new_text_scaled(
&self.text,
r.x, r.y, sw * self.scale_x, sh * self.scale_y,
self.colour.0, self.colour.1, self.colour.2,
)
} else {
let c = self.rotation.cos();
let s = self.rotation.sin();
let tmpx = r.w / 2.0;
let tmpy = r.h / 2.0;
let w = (tmpx * c - tmpy * s).abs();
let h = (tmpy * c + tmpx * s).abs();
renderer.ui.new_text_rotated(
&self.text,
r.x + w - (r.w / 2.0), r.y + h - (r.h / 2.0),
sw * self.scale_x, sh * self.scale_y,
self.rotation,
self.colour.0, self.colour.1, self.colour.2,
)
};
for e in &mut text.elements {
e.a = self.colour.3;
}
2016-04-24 07:22:04 -04:00
self.data.extend_from_slice(&text.bytes(width, height));
self.super_draw(renderer, r, sw, sh, width, height, delta);
self.last_text = self.text.clone();
self.last_colour = self.colour;
self.last_scale_x = self.scale_x;
self.last_scale_y = self.scale_y;
self.last_rotation = self.rotation;
}
2016-04-24 07:22:04 -04:00
&mut self.data
}
fn tick(&mut self, renderer: &mut render::Renderer) {
self.super_tick(renderer);
if self.is_dirty() {
self.width = renderer.ui.size_of_string(&self.text);
}
}
fn get_size(&self) -> (f64, f64) {
((self.width + 2.0) * self.scale_x, self.height * self.scale_y)
}
fn is_dirty(&self) -> bool {
self.last_text != self.text
|| self.last_colour != self.colour
|| self.last_scale_x != self.scale_x
|| self.last_scale_y != self.scale_y
|| self.last_rotation != self.rotation
}
2015-09-18 17:02:08 -04:00
}
2016-04-24 07:22:04 -04:00
element! {
ref FormattedRef
pub struct Formatted {
pub width: f64,
pub height: f64,
pub scale_x: f64,
pub scale_y: f64,
pub max_width: f64,
priv text: format::Component,
priv text_elements: Vec<Element>,
priv last_text: format::Component,
priv last_scale_x: f64,
priv last_scale_y: f64,
priv last_max_width: f64,
priv dirty: bool,
}
2016-04-24 07:22:04 -04:00
builder FormattedBuilder {
hardcode width = 0.0,
hardcode height = 18.0,
hardcode text_elements = vec![],
hardcode last_text = Default::default(),
hardcode last_scale_x = 0.0,
hardcode last_scale_y = 0.0,
hardcode last_max_width = -1.0,
hardcode dirty = true,
simple text: format::Component,
optional scale_x: f64 = 1.0,
optional scale_y: f64 = 1.0,
optional max_width: f64 = -1.0,
}
2015-09-25 09:00:49 -04:00
}
2016-04-24 07:22:04 -04:00
impl UIElement for Formatted {
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] {
if self.check_rebuild() {
self.data.clear();
self.elements.clear();
{
let mut state = FormatState {
lines: 0,
width: 0.0,
offset: 0.0,
text: Vec::new(),
max_width: self.max_width,
renderer,
2016-04-24 07:22:04 -04:00
};
state.build(&self.text, format::Color::White);
self.text_elements = state.text;
}
for e in &self.text_elements {
if self.needs_rebuild {
e.force_rebuild();
}
let r = Container::compute_draw_region(e, sw, sh, r);
2016-04-24 07:22:04 -04:00
let data = e.draw(renderer, &r, sw, sh, width, height, delta);
self.data.extend_from_slice(&data);
}
self.super_draw(renderer, r, sw, sh, width, height, delta);
self.last_text = self.text.clone();
self.last_scale_x = self.scale_x;
self.last_scale_y = self.scale_y;
self.last_max_width = self.max_width;
self.dirty = false;
}
&mut self.data
}
fn tick(&mut self, renderer: &mut render::Renderer) {
self.super_tick(renderer);
if self.is_dirty() {
let (w, h) = Self::compute_size(renderer, &self.text, self.max_width);
self.width = w;
self.height = h;
}
}
fn get_size(&self) -> (f64, f64) {
((self.width + 2.0) * self.scale_x, self.height * self.scale_y)
}
fn is_dirty(&self) -> bool {
self.dirty
|| self.last_scale_x != self.scale_x
|| self.last_scale_y != self.scale_y
|| self.last_max_width != self.max_width
}
}
impl Formatted {
pub fn set_text(&mut self, val: format::Component) {
self.text = val;
self.dirty = true;
}
pub fn compute_size(renderer: &render::Renderer, text: &format::Component, max_width: f64) -> (f64, f64) {
let mut state = FormatState {
lines: 0,
width: 0.0,
offset: 0.0,
text: Vec::new(),
max_width,
renderer,
2016-04-24 07:22:04 -04:00
};
state.build(text, format::Color::White);
2016-04-24 07:22:04 -04:00
(state.width + 2.0, (state.lines + 1) as f64 * 18.0)
}
}
struct FormatState<'a> {
max_width: f64,
lines: usize,
offset: f64,
width: f64,
text: Vec<Element>,
renderer: &'a render::Renderer,
}
impl <'a> ElementHolder for FormatState<'a> {
fn add(&mut self, el: Element, _: bool) {
self.text.push(el);
}
}
impl <'a> FormatState<'a> {
fn build(&mut self, c: &format::Component, color: format::Color) {
match *c {
format::Component::Text(ref txt) => {
2016-04-24 07:22:04 -04:00
let col = FormatState::get_color(&txt.modifier, color);
self.append_text(&txt.text, col);
let modi = &txt.modifier;
if let Some(ref extra) = modi.extra {
for e in extra {
self.build(e, col);
}
}
}
}
2016-04-24 07:22:04 -04:00
}
2016-04-24 07:22:04 -04:00
fn append_text(&mut self, txt: &str, color: format::Color) {
let mut width = 0.0;
let mut last = 0;
for (i, c) in txt.char_indices() {
let size = self.renderer.ui.size_of_char(c) + 2.0;
if (self.max_width > 0.0 && self.offset + width + size > self.max_width) || c == '\n' {
let (rr, gg, bb) = color.to_rgb();
TextBuilder::new()
.text(&txt[last..i])
.position(self.offset, (self.lines * 18 + 1) as f64)
.colour((rr, gg, bb, 255))
.create(self);
last = i;
if c == '\n' {
last += 1;
}
self.offset = 0.0;
self.lines += 1;
width = 0.0;
}
width += size;
if self.offset + width > self.width {
self.width = self.offset + width;
}
}
2016-04-24 07:22:04 -04:00
if last != txt.len() {
let (rr, gg, bb) = color.to_rgb();
TextBuilder::new()
.text(&txt[last..])
.position(self.offset, (self.lines * 18 + 1) as f64)
.colour((rr, gg, bb, 255))
.create(self);
self.offset += self.renderer.ui.size_of_string(&txt[last..]) + 2.0;
if self.offset > self.width {
self.width = self.offset;
}
}
2016-04-24 07:22:04 -04:00
}
2016-04-24 07:22:04 -04:00
fn get_color(modi: &format::Modifier, color: format::Color) -> format::Color {
modi.color.unwrap_or(color)
}
}
element! {
ref ButtonRef
pub struct Button {
pub disabled: bool,
pub width: f64,
pub height: f64,
priv hovered: bool,
priv last_hovered: bool,
priv last_disabled: bool,
priv texts: Vec<TextRef>,
}
builder ButtonBuilder {
hardcode hovered = false,
hardcode last_hovered = false,
hardcode last_disabled = false,
hardcode texts = vec![],
optional disabled: bool = false,
noset width: f64 = |b| b.width.expect("Missing required field width"),
noset height: f64 = |b| b.height.expect("Missing required field height"),
}
}
impl ButtonBuilder {
pub fn size(mut self, width: f64, height: f64) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
2015-09-25 09:00:49 -04:00
}
2016-04-24 07:22:04 -04:00
impl UIElement for Button {
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] {
if self.check_rebuild() {
self.data.clear();
let offset = match (self.disabled, self.hovered) {
(true, _) => 46.0,
(false, true) => 86.0,
(false, false) => 66.0,
};
let texture = render::Renderer::get_texture(renderer.get_textures_ref(), "gui/widgets")
.relative(0.0, offset / 256.0, 200.0 / 256.0, 20.0 / 256.0);
self.data.extend(render::ui::UIElement::new(&texture, r.x, r.y, 4.0 * sw, 4.0 * sh, 0.0, 0.0, 2.0/200.0, 2.0/20.0).bytes(width, height));
self.data.extend(render::ui::UIElement::new(&texture, r.x + r.w - 4.0 * sw, r.y, 4.0 * sw, 4.0 * sh, 198.0/200.0, 0.0, 2.0/200.0, 2.0/20.0).bytes(width, height));
self.data.extend(render::ui::UIElement::new(&texture, r.x, r.y + r.h - 6.0 * sh, 4.0 * sw, 6.0 * sh, 0.0, 17.0/20.0, 2.0/200.0, 3.0/20.0).bytes(width, height));
self.data.extend(render::ui::UIElement::new(&texture, r.x + r.w - 4.0 * sw, r.y + r.h - 6.0 * sh, 4.0 * sw, 6.0 * sh, 198.0/200.0, 17.0/20.0, 2.0/200.0, 3.0/20.0).bytes(width, height));
let w = ((r.w / sw)/2.0) - 4.0;
self.data.extend(render::ui::UIElement::new(
&texture.relative(2.0/200.0, 0.0, 196.0/200.0, 2.0/20.0),
r.x+4.0*sw, r.y, r.w - 8.0 * sw, 4.0 * sh, 0.0, 0.0, w/196.0, 1.0).bytes(width, height)
);
self.data.extend(render::ui::UIElement::new(
&texture.relative(2.0/200.0, 17.0/20.0, 196.0/200.0, 3.0/20.0),
r.x+4.0*sw, r.y+r.h-6.0*sh, r.w - 8.0 * sw, 6.0 * sh, 0.0, 0.0, w/196.0, 1.0).bytes(width, height)
);
let h = ((r.h / sh)/2.0) - 5.0;
self.data.extend(render::ui::UIElement::new(
&texture.relative(0.0/200.0, 2.0/20.0, 2.0/200.0, 15.0/20.0),
r.x, r.y + 4.0*sh, 4.0 * sw, r.h - 10.0*sh, 0.0, 0.0, 1.0, h/16.0).bytes(width, height)
);
self.data.extend(render::ui::UIElement::new(
&texture.relative(198.0/200.0, 2.0/20.0, 2.0/200.0, 15.0/20.0),
r.x+r.w - 4.0 * sw, r.y + 4.0*sh, 4.0 * sw, r.h - 10.0*sh, 0.0, 0.0, 1.0, h/16.0).bytes(width, height)
);
self.data.extend(render::ui::UIElement::new(
&texture.relative(2.0/200.0, 2.0/20.0, 196.0/200.0, 15.0/20.0),
r.x+4.0*sw, r.y+4.0*sh, r.w - 8.0 * sw, r.h - 10.0 * sh, 0.0, 0.0, w/196.0, h/16.0).bytes(width, height)
);
self.super_draw(renderer, r, sw, sh, width, height, delta);
self.last_disabled = self.disabled;
self.last_hovered = self.hovered;
}
2016-04-24 07:22:04 -04:00
&mut self.data
}
fn tick(&mut self, renderer: &mut render::Renderer) {
self.super_tick(renderer);
}
fn get_size(&self) -> (f64, f64) {
(self.width, self.height)
}
fn is_dirty(&self) -> bool {
self.last_disabled != self.disabled
|| self.last_hovered != self.hovered
}
fn post_init(s: Rc<RefCell<Self>>) {
s.borrow_mut().add_hover_func(move |this, hover, _| {
this.hovered = hover;
for text in &this.texts {
text.borrow_mut().colour.2 = if hover { 160 } else { 255 };
}
true
})
}
}
impl Button {
pub fn add_text(&mut self, text: TextRef) {
self.texts.push(text);
}
}
element! {
ref TextBoxRef
pub struct TextBox {
pub input: String,
pub password: bool,
pub width: f64,
pub height: f64,
priv button: Option<ButtonRef>,
priv text: Option<TextRef>,
priv was_focused: bool,
priv cursor_tick: f64,
priv submit_funcs: Vec<Box<Fn(&mut TextBox, &mut crate::Game)>>,
2016-04-24 07:22:04 -04:00
}
builder TextBoxBuilder {
hardcode button = None,
hardcode text = None,
hardcode was_focused = false,
hardcode cursor_tick = 0.0,
hardcode submit_funcs = vec![],
optional input: String = "".into(),
optional password: bool = false,
noset width: f64 = |b| b.width.expect("Missing required field width"),
noset height: f64 = |b| b.height.expect("Missing required field height"),
}
}
impl TextBoxBuilder {
pub fn size(mut self, width: f64, height: f64) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
}
impl UIElement for TextBox {
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
fn key_press(&mut self, game: &mut crate::Game, key: VirtualKeyCode, down: bool, ctrl_pressed: bool) {
2016-04-24 07:22:04 -04:00
match (key, down) {
(VirtualKeyCode::Back, _) => {self.input.pop();},
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
(VirtualKeyCode::Return, false) => {
2016-04-24 07:22:04 -04:00
use std::mem;
let len = self.submit_funcs.len();
let mut temp = mem::replace(&mut self.submit_funcs, Vec::with_capacity(len));
for func in &temp {
(func)(self, game);
}
self.submit_funcs.append(&mut temp);
},
Add support for compiling WebAssembly wasm32-unknown-unknown target (#92) Note this only is the first step in web support, although the project compiles, it doesn't run! Merging now to avoid branch divergence, until dependencies can be updated for wasm support. * Add instructions to build for wasm32-unknown-unknown with wasm-pack in www/ * Update to rust-clipboard fork to compile with emscripten https://github.com/aweinstock314/rust-clipboard/pull/62 * Exclude reqwest dependency in wasm32 * Exclude compiling clipboard pasting on wasm32 * Exclude reqwest-using code from wasm32 * Install wasm target with rustup in Travis CI * Update to collision 0.19.0 Fixes wasm incompatibility in deprecated rustc-serialize crate: https://github.com/rustgd/collision-rs/issues/106 error[E0046]: not all trait items implemented, missing: `encode` --> github.com-1ecc6299db9ec823/rustc-serialize-0.3.24/src/serialize.rs:1358:1 * Increase travis_wait time even further, try 120 minutes * Set RUST_BACKTRACE=1 in main * Remove unused unneeded bzip2 features in zip crate To fix wasm32-unknown-unknown target compile error: error[E0432]: unresolved imports `libc::c_int`, `libc::c_uint`, `libc::c_void`, `libc::c_char` --> src/github.com-1ecc6299db9ec823/bzip2-sys-0.1.7/lib.rs:5:12 | 5 | use libc::{c_int, c_uint, c_void, c_char}; | ^^^^^ ^^^^^^ ^^^^^^ ^^^^^^ no `c_char` in the root | | | | | | | no `c_void` in the root | | no `c_uint` in the root | no `c_int` in the root * flate2 use Rust backend * Add console_error_panic_hook module for wasm backtraces * Build using wasm-pack, wasm-bindgen, run with wasm-app * Update to miniz_oxide 0.2.1, remove patch for https://github.com/Frommi/miniz_oxide/issues/42 * Update to official clipboard crate since https://github.com/aweinstock314/rust-clipboard/pull/62 was merged, but git revision pending release * Update to branch of glutin attempting to build for wasm https://github.com/iceiix/glutin/pull/1 * Update winit dependency of glutin to git master https://github.com/iceiix/winit/pull/2 * Update to glutin branch with working (compiles, doesn't run) wasm_stub * Add app name in title on web page * Add wasm to Travis-CI test matrix * Update glutin to fix Windows EGL compilation on AppVeyor https://github.com/iceiix/glutin/pull/1/commits/97797352b5242436cb82d8ecfb44242b69766e4c
2019-03-03 11:32:36 -05:00
// TODO: wasm clipboard pasting, Clipboard API: https://www.w3.org/TR/clipboard-apis/
#[cfg(not(target_arch = "wasm32"))]
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
(VirtualKeyCode::V, true) => {
2016-04-25 19:50:16 -04:00
if ctrl_pressed {
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap();
match clipboard.get_contents() {
Ok(text) => self.input.push_str(&text),
Err(_) => ()
2016-04-25 19:50:16 -04:00
}
}
},
2016-04-24 07:22:04 -04:00
_ => {},
}
}
fn key_type(&mut self, _game: &mut crate::Game, c: char) {
2016-04-24 07:22:04 -04:00
self.input.push(c);
}
fn draw(&mut self, renderer: &mut render::Renderer, r: &Region, sw: f64, sh: f64, width: f64, height: f64, delta: f64) -> &mut [u8] {
if self.check_rebuild() {
self.data.clear();
self.cursor_tick += delta;
if self.cursor_tick > 3000.0 {
self.cursor_tick -= 3000.0;
}
let mut text = self.transform_input();
{
let mut btn = self.button.as_mut().unwrap().borrow_mut();
btn.width = self.width;
btn.height = self.height;
let mut txt = self.text.as_mut().unwrap().borrow_mut();
if self.focused && ((self.cursor_tick / 30.0) as i32) % 2 == 0 {
text.push('|');
}
txt.text = text;
}
self.super_draw(renderer, r, sw, sh, width, height, delta);
self.was_focused = self.focused;
}
&mut self.data
}
fn tick(&mut self, renderer: &mut render::Renderer) {
self.super_tick(renderer);
}
fn get_size(&self) -> (f64, f64) {
(self.width, self.height)
}
fn is_dirty(&self) -> bool {
self.focused || self.was_focused
}
fn post_init(s: Rc<RefCell<Self>>) {
let mut textbox = s.borrow_mut();
textbox.button = Some(ButtonBuilder::new()
.position(0.0, 0.0)
.size(textbox.width, textbox.height)
.disabled(true)
.attach(&mut *textbox));
textbox.text = Some(TextBuilder::new()
.text("")
.position(5.0, 0.0)
.draw_index(1)
.alignment(VAttach::Middle, HAttach::Left)
.attach(&mut *textbox));
}
2015-09-25 09:00:49 -04:00
}
2016-04-24 07:22:04 -04:00
impl TextBox {
pub fn add_submit_func<F: Fn(&mut TextBox, &mut crate::Game) + 'static>(&mut self, f: F) {
2016-04-24 07:22:04 -04:00
self.submit_funcs.push(Box::new(f));
}
fn transform_input(&self) -> String {
if self.password {
::std::iter::repeat('*').take(self.input.len()).collect()
} else {
self.input.clone()
}
}
}