// Copyright 2016 Matthew Collins // // 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 // limitations under the License. pub mod logo; use std::rc::{Rc, Weak}; use std::cell::{RefCell, RefMut}; use crate::render; use crate::format; use glutin::VirtualKeyCode; #[cfg(not(target_arch = "wasm32"))] use clipboard::{ClipboardProvider, ClipboardContext}; const SCALED_WIDTH: f64 = 854.0; const SCALED_HEIGHT: f64 = 480.0; #[derive(Clone, Copy, PartialEq)] pub enum Mode { Scaled, Unscaled(f64), } #[derive(Clone, Copy, PartialEq, Eq)] pub enum VAttach { Top, Middle, Bottom, } #[derive(Clone, Copy, PartialEq, Eq)] pub enum HAttach { Left, Center, Right, } #[derive(Clone)] struct Region { x: f64, y: f64, w: f64, h: f64, } impl Region { 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) } } macro_rules! define_elements { ( $($name:ident,)* ) => ( #[doc(hidden)] pub enum Element { $($name(Rc>),)* } impl Element { fn get_draw_index(&self) -> isize { match *self { $( Element::$name(ref inner) => inner.borrow().draw_index, )* } } fn is_unused(&self) -> bool { match *self { $( Element::$name(ref inner) => Rc::strong_count(inner) == 1, )* } } fn tick(&self, renderer: &mut render::Renderer) { match *self { $( Element::$name(ref inner) => { let mut el = inner.borrow_mut(); el.tick(renderer); }, )* } } 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)) }, )* } } fn check_rebuild(&self) -> bool { match *self { $( Element::$name(ref inner) => { let el = inner.borrow(); el.check_rebuild() }, )* } } 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 { 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 { match *self { $( Element::$name(ref inner) => { let mut el = inner.borrow_mut(); el.click_at(r, game, mx, my, sw, sh) }, )* } } fn key_press(&self, game: &mut crate::Game, key: VirtualKeyCode, down: bool, ctrl_pressed: bool) { match *self { $( Element::$name(ref inner) => { let mut el = inner.borrow_mut(); el.key_press(game, key, down, ctrl_pressed); }, )* } } fn key_type(&self, game: &mut crate::Game, c: char) { 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; }, )* } } } #[doc(hidden)] enum WeakElement { $($name(Weak>),)* } impl WeakElement { fn upgrade(&self) -> Option { match *self { $( WeakElement::$name(ref inner) => { inner.upgrade().map(Element::$name) }, )* } } } ) } define_elements! { Image, Batch, Text, Formatted, Button, TextBox, } const SCREEN: Region = Region { x: 0.0, y: 0.0, w: SCALED_WIDTH, h: SCALED_HEIGHT, }; pub trait ElementHolder { fn add(&mut self, el: Element, auto_free: bool); } pub struct Container { elements: Vec, focusable_elements: Vec, pub mode: Mode, last_mode: Mode, version: usize, last_sw: f64, last_sh: f64, last_width: f64, last_height: f64, } impl Container { pub fn new() -> Container { Container { elements: Vec::new(), focusable_elements: Vec::new(), mode: Mode::Scaled, last_mode: Mode::Scaled, version: 0xFFFF, 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 { self.last_sw = sw; self.last_sh = sh; self.last_width = width; self.last_height = height; self.last_mode = self.mode; for e in &self.elements { e.force_rebuild(); } self.version = renderer.ui.version; } // 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() } for e in &self.elements { e.tick(renderer); } for e in &self.elements { let r = Self::compute_draw_region(e, sw, sh, &SCREEN); if r.intersects(&SCREEN) { let data = e.draw(renderer, &r, sw, sh, width, height, delta); renderer.ui.add_bytes(&data); } } } pub fn hover_at(&mut self, game: &mut crate::Game, x: f64, y: f64, width: f64, height: f64) { 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; for e in &self.elements { let r = Self::compute_draw_region(e, sw, sh, &SCREEN); e.hover_at(&r, game, mx, my, sw, sh); } } pub fn click_at(&mut self, game: &mut crate::Game, x: f64, y: f64, width: f64, height: f64) { 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; 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); } } } 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::>(); // 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); } 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; } for el in self.focusable_elements.iter() .flat_map(|v| v.upgrade()) { if el.is_focused() { el.key_press(game, key, down, ctrl_pressed); } } } pub fn key_type(&mut self, game: &mut crate::Game, c: char) { if c < ' ' { return; } for el in self.focusable_elements.iter() .flat_map(|v| v.upgrade()) { if el.is_focused() { el.key_type(game, c); } } } fn compute_draw_region(el: &Element, sw: f64, sh: f64, super_region: &Region) -> Region { let mut r = Region { x: 0.0, y: 0.0, w: 0.0, h: 0.0, }; let (w, h) = el.get_size(); let (ox, oy) = el.get_position(); r.w = w * sw; r.h = h * sh; let (v_attach, h_attach) = el.get_attachment(); 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 } } 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()); } } 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>) {} 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) {} 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, needs_rebuild: bool, hover_funcs: Vec bool>>, hover_state: bool, click_funcs: Vec bool>>, 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>; 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 { 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 bool + 'static>(&mut self, func: F) { 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 { 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 bool + 'static>(&mut self, func: F) { 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, y: Option, v_attach: Option, h_attach: Option, } impl $builder { $( pub fn $sname>(mut self, val: T) -> Self { self.$sname = Some(val.into()); self } )* $( pub fn $oname>(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(self, ui: &mut H) -> $nameref { self.create_internal(ui, true) } pub fn attach(self, ui: &mut H) -> $nameref { self.create_internal(ui, false) } fn create_internal(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), } 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"), } } impl ImageBuilder { pub fn size(mut self, width: f64, height: f64) -> Self { self.width = Some(width); self.height = Some(height); self } } 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 } } 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); } &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 { 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; } 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; } &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 } } 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, priv last_text: format::Component, priv last_scale_x: f64, priv last_scale_y: f64, priv last_max_width: f64, priv dirty: bool, } 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, } } 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, }; 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); 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, }; state.build(text, format::Color::White); (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, 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) => { 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); } } } } } 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; } } 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; } } } 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, } 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 } } 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; } &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>) { 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, priv text: Option, priv was_focused: bool, priv cursor_tick: f64, priv submit_funcs: Vec>, } 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) { match (key, down) { (VirtualKeyCode::Back, _) => {self.input.pop();}, (VirtualKeyCode::Return, false) => { 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); }, // TODO: wasm clipboard pasting, Clipboard API: https://www.w3.org/TR/clipboard-apis/ #[cfg(not(target_arch = "wasm32"))] (VirtualKeyCode::V, true) => { if ctrl_pressed { let mut clipboard: ClipboardContext = ClipboardProvider::new().unwrap(); match clipboard.get_contents() { Ok(text) => self.input.push_str(&text), Err(_) => () } } }, _ => {}, } } fn key_type(&mut self, _game: &mut crate::Game, c: char) { 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>) { 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)); } } impl TextBox { pub fn add_submit_func(&mut self, f: F) { 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() } } }