stevenarella/src/ui/mod.rs

1652 lines
50 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;
use crate::format;
use crate::render;
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 clipboard::{ClipboardContext, ClipboardProvider};
use std::cell::{RefCell, RefMut};
use std::rc::{Rc, Weak};
Use web-sys for web backend (#444) A small step for #446 🕸️ Web support, use web-sys to interface to the web. Previously, we would try to use glutin on the web, which is not supported; now glutin is only used on native: fixes #171 could not find Context in platform_impl. winit is still used on both, but the GL context is created with web-sys and glow (on the web), and created with glutin and used with glow (on native). stdweb is no longer used, being replaced by web-sys. Substantial refactoring to allow reusing the code between web/native: * settings: use VirtualKeyCode from winit, not reexported from glutin * std_or_web: remove broken localstoragefs/stdweb, add File placeholder * render: disable skin_thread on wasm since we don't have threads * gl: use glow types in gl wrapper (integers in native, but Web*Key in web) * gl: web-sys WebGlUniformLocation does not implement Copy trait, so glow::UniformLocation doesn't so gl::Uniform can't * gl: refactor context initialization, pass glow::Context to gl::init for consistency between native/web * gl: update to glow with panicking tex_image_2d_multisample web-sys wrapper * glsl: use shader version in GLSL for WebGL 2 and OpenGL 3.2 * shaders: add explicit float/int type conversions, required for WebGL * shaders: specify mediump precision, required for WebGL * shaders: specify fragment shader output locations for WebGL * main: refactor handle_window_event to take a winit window, not glutin context * main: handle resize outside of handle_window_event since it updates the glutin window (the only event which does this) * main: use winit events in handle_window_event not reexported glutin events * main: refactor game loop handling into tick_all() * main: create winit window for WebGL, and use winit_window from glutin * main: restore console_error_panic_hook, mistakingly removed in (#260) * main: remove force setting env RUST_BACKTRACE=1, no longer can set env on web * www: index.js: fix wasm import path * www: npm update, npm audit fix * www: update readme to link to status on #446 🕸️ Web support
2020-12-26 16:42:37 -05:00
use winit::event::VirtualKeyCode;
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-10-07 14:36:59 -04:00
}
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 Default for Container {
fn default() -> Self {
Self::new()
}
}
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())
{
2016-04-24 07:22:04 -04:00
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
let mut clicked_element: Option<&Element> = None;
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);
clicked_element = Some(e);
}
}
if let Some(e) = clicked_element {
if let Element::TextBox(_) = e {
self.clear_focus();
e.set_focused(true);
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);
}
fn clear_focus(&self) {
for e in &self.elements {
e.set_focused(false);
}
}
2016-04-24 07:22:04 -04:00
pub fn cycle_focus(&mut self) {
if self.focusable_elements.is_empty() {
return;
}
let focusables = self
.focusable_elements
.iter()
2016-04-24 07:22:04 -04:00
.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());
2016-04-24 07:22:04 -04:00
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);
}
pub fn key_press(
&mut self,
game: &mut crate::Game,
key: VirtualKeyCode,
down: bool,
ctrl_pressed: bool,
) {
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
if key == VirtualKeyCode::Tab {
if !down {
self.cycle_focus();
}
return;
}
for el in self.focusable_elements.iter().flat_map(|v| v.upgrade()) {
2016-04-24 07:22:04 -04:00
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 < ' ' && c != '\x08' {
return;
}
for el in self.focusable_elements.iter().flat_map(|v| v.upgrade()) {
2016-04-24 07:22:04 -04:00
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];
2016-04-24 07:22:04 -04:00
fn get_size(&self) -> (f64, f64);
fn is_dirty(&self) -> bool;
fn post_init(_: Rc<RefCell<Self>>) {}
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<dyn Fn(&mut $name, bool, &mut crate::Game) -> bool>>,
2016-04-24 07:22:04 -04:00
hover_state: bool,
click_funcs: Vec<Box<dyn 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)));
}
}
#[derive(Default)]
2016-04-24 07:22:04 -04:00
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,
2016-04-24 07:22:04 -04:00
)*
// 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] {
2016-04-24 07:22:04 -04:00
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,
2016-04-24 07:22:04 -04:00
);
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] {
2016-04-24 07:22:04 -04:00
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] {
2016-04-24 07:22:04 -04:00
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,
2016-04-24 07:22:04 -04:00
)
} 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,
2016-04-24 07:22:04 -04:00
)
};
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,
)
2016-04-24 07:22:04 -04:00
}
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] {
2016-04-24 07:22:04 -04:00
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,
)
2016-04-24 07:22:04 -04:00
}
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) {
2016-04-24 07:22:04 -04:00
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> {
2016-04-24 07:22:04 -04:00
fn add(&mut self, el: Element, _: bool) {
self.text.push(el);
}
}
impl<'a> FormatState<'a> {
2016-04-24 07:22:04 -04:00
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] {
2016-04-24 07:22:04 -04:00
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,
2016-04-24 07:22:04 -04:00
};
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),
2016-04-24 07:22:04 -04:00
);
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),
2016-04-24 07:22:04 -04:00
);
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),
2016-04-24 07:22:04 -04:00
);
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),
2016-04-24 07:22:04 -04:00
);
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),
);
2016-04-24 07:22:04 -04:00
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),
2016-04-24 07:22:04 -04:00
);
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
2016-04-24 07:22:04 -04:00
}
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<dyn 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 {
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) {
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();
if let Ok(text) = clipboard.get_contents() {
self.input.push_str(&text)
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) {
if c == '\x7f' || c == '\x08' {
// Backspace
self.input.pop();
return;
}
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] {
2016-04-24 07:22:04 -04:00
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),
);
2016-04-24 07:22:04 -04:00
}
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 {
2021-07-31 22:06:42 -04:00
"*".repeat(self.input.len())
2016-04-24 07:22:04 -04:00
} else {
self.input.clone()
}
}
}