Reworked renderer for Directives
Added OwnedTextArea & reworked textarea creation Added ScrollBoxmaster
parent
55319d6872
commit
b170f3f53f
|
@ -1,13 +1,13 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_system::data::ShipState;
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle, PlayerDirective};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
||||||
use nalgebra::{Point2, Point3};
|
use nalgebra::{Point2, Point3, Vector2};
|
||||||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit;
|
use winit::{self};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
||||||
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
ui::UiScriptExecutor,
|
ui::UiScriptExecutor,
|
||||||
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
||||||
RenderInput, RenderState, VertexBuffers,
|
InputEvent, RenderInput, RenderState, VertexBuffers,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
||||||
|
@ -259,6 +259,8 @@ impl GPUState {
|
||||||
self.config.height = new_size.height;
|
self.config.height = new_size.height;
|
||||||
self.surface.configure(&self.device, &self.config);
|
self.surface.configure(&self.device, &self.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this takes a long time. fix!
|
||||||
self.starfield.update_buffer(ct, &mut self.state);
|
self.starfield.update_buffer(ct, &mut self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,18 +276,54 @@ impl GPUState {
|
||||||
self.starfield.update_buffer(ct, &mut self.state);
|
self.starfield.update_buffer(ct, &mut self.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle user input
|
||||||
|
pub fn process_input(
|
||||||
|
&mut self,
|
||||||
|
input: RenderInput,
|
||||||
|
event: InputEvent,
|
||||||
|
) -> Result<PlayerDirective> {
|
||||||
|
let input = Arc::new(input);
|
||||||
|
self.ui.process_input(&mut self.state, input, event)
|
||||||
|
}
|
||||||
|
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Main render function. Draws sprites on a window.
|
||||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||||
let input = Arc::new(input);
|
let input = Arc::new(input);
|
||||||
|
|
||||||
|
if let Some(ship) = input.player.ship {
|
||||||
|
let o = input.phys_img.get_ship(&PhysSimShipHandle(ship));
|
||||||
|
if let Some(o) = o {
|
||||||
|
match o.ship.get_data().get_state() {
|
||||||
|
ShipState::Landing { .. }
|
||||||
|
| ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Collapsing { .. }
|
||||||
|
| ShipState::Flying { .. } => self
|
||||||
|
.ui
|
||||||
|
.state
|
||||||
|
.borrow_mut()
|
||||||
|
.camera
|
||||||
|
.set_pos(*o.rigidbody.translation()),
|
||||||
|
|
||||||
|
ShipState::Landed { target } => self
|
||||||
|
.ui
|
||||||
|
.state
|
||||||
|
.borrow_mut()
|
||||||
|
.camera
|
||||||
|
.set_pos(Vector2::new(target.pos.x, target.pos.y)),
|
||||||
|
|
||||||
|
ShipState::Dead => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update global values
|
// Update global values
|
||||||
self.state.queue.write_buffer(
|
self.state.queue.write_buffer(
|
||||||
&self.state.global_uniform.data_buffer,
|
&self.state.global_uniform.data_buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
camera_position_x: input.camera_pos.x,
|
camera_position_x: self.ui.state.borrow().camera.get_pos().x,
|
||||||
camera_position_y: input.camera_pos.y,
|
camera_position_y: self.ui.state.borrow().camera.get_pos().y,
|
||||||
camera_zoom: input.camera_zoom,
|
camera_zoom: self.ui.state.borrow().camera.get_zoom(),
|
||||||
camera_zoom_min: input.ct.config.zoom_min,
|
camera_zoom_min: input.ct.config.zoom_min,
|
||||||
camera_zoom_max: input.ct.config.zoom_max,
|
camera_zoom_max: input.ct.config.zoom_max,
|
||||||
window_size_w: self.state.window_size.width as f32,
|
window_size_w: self.state.window_size.width as f32,
|
||||||
|
@ -338,8 +376,10 @@ impl GPUState {
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
// Used to skip off-screen sprites.
|
// Used to skip off-screen sprites.
|
||||||
let clip_ne = Point2::new(-self.state.window_aspect, 1.0) * input.camera_zoom;
|
let clip_ne = Point2::new(-self.state.window_aspect, 1.0)
|
||||||
let clip_sw = Point2::new(self.state.window_aspect, -1.0) * input.camera_zoom;
|
* self.ui.state.borrow().camera.get_zoom();
|
||||||
|
let clip_sw = Point2::new(self.state.window_aspect, -1.0)
|
||||||
|
* self.ui.state.borrow().camera.get_zoom();
|
||||||
|
|
||||||
// Order matters, it determines what is drawn on top.
|
// Order matters, it determines what is drawn on top.
|
||||||
// The order inside ships and projectiles doesn't matter,
|
// The order inside ships and projectiles doesn't matter,
|
||||||
|
@ -424,7 +464,9 @@ impl GPUState {
|
||||||
},
|
},
|
||||||
(*self.ui.state)
|
(*self.ui.state)
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.get_textareas(&input, &self.state.window),
|
.get_textareas(&input, &self.state.window)
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.get_textarea()),
|
||||||
&mut self.state.text_cache,
|
&mut self.state.text_cache,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -483,8 +525,9 @@ impl GPUState {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
// 1.0 is z-coordinate, which is constant for ships
|
// 1.0 is z-coordinate, which is constant for ships
|
||||||
let pos: Point2<f32> =
|
let pos: Point2<f32> = (Point2::new(ship_pos.x, ship_pos.y)
|
||||||
(Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / ship_pos.z;
|
- self.ui.state.borrow().camera.get_pos())
|
||||||
|
/ ship_pos.z;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
|
@ -588,7 +631,7 @@ impl GPUState {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
// 1.0 is z-coordinate, which is constant for projectiles
|
||||||
let pos = (proj_pos - input.camera_pos) / 1.0;
|
let pos = (proj_pos - self.ui.state.borrow().camera.get_pos()) / 1.0;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
|
@ -649,7 +692,8 @@ impl GPUState {
|
||||||
|
|
||||||
for o in v {
|
for o in v {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z;
|
let pos: Point2<f32> =
|
||||||
|
(Point2::new(o.pos.x, o.pos.y) - self.ui.state.borrow().camera.get_pos()) / o.pos.z;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
|
@ -711,7 +755,7 @@ impl GPUState {
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
// 1.0 is z-coordinate, which is constant for projectiles
|
||||||
let adjusted_pos = (pos - input.camera_pos) / 1.0;
|
let adjusted_pos = (pos - self.ui.state.borrow().camera.get_pos()) / 1.0;
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Post-scale width or height, whichever is larger.
|
// Post-scale width or height, whichever is larger.
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
use winit::{dpi::PhysicalPosition, event::VirtualKeyCode};
|
||||||
|
|
||||||
|
/// Input received from the user
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputEvent {
|
||||||
|
/// Mouse was moved
|
||||||
|
MouseMove(PhysicalPosition<f32>),
|
||||||
|
|
||||||
|
/// Mouse left button was clicked.
|
||||||
|
/// True if pressed, false if released.
|
||||||
|
MouseLeftClick(bool),
|
||||||
|
|
||||||
|
/// Mouse left button was clicked.
|
||||||
|
/// True if pressed, false if released.
|
||||||
|
MouseRightClick(bool),
|
||||||
|
|
||||||
|
/// Mouse was scrolled.
|
||||||
|
/// Value is number of lines, positive or negative.
|
||||||
|
Scroll(f32),
|
||||||
|
|
||||||
|
/// A key was pressed or released
|
||||||
|
Keyboard {
|
||||||
|
/// True if pressed, false if released
|
||||||
|
down: bool,
|
||||||
|
|
||||||
|
/// The key that was pressed
|
||||||
|
key: VirtualKeyCode,
|
||||||
|
},
|
||||||
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
mod globaluniform;
|
mod globaluniform;
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
|
mod inputevent;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod renderinput;
|
mod renderinput;
|
||||||
mod renderstate;
|
mod renderstate;
|
||||||
|
@ -18,7 +19,8 @@ mod texturearray;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::*;
|
||||||
|
pub use inputevent::*;
|
||||||
pub use renderinput::RenderInput;
|
pub use renderinput::RenderInput;
|
||||||
use renderstate::*;
|
use renderstate::*;
|
||||||
|
|
||||||
|
|
|
@ -4,23 +4,16 @@ use galactica_content::{Content, System};
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_system::phys::PhysImage;
|
use galactica_system::phys::PhysImage;
|
||||||
use galactica_util::timing::Timing;
|
use galactica_util::timing::Timing;
|
||||||
use nalgebra::Vector2;
|
|
||||||
|
|
||||||
/// Bundles parameters passed to a single call to GPUState::render
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RenderInput {
|
pub struct RenderInput {
|
||||||
/// Camera position, in world units
|
|
||||||
pub camera_pos: Vector2<f32>,
|
|
||||||
|
|
||||||
/// Player ship data
|
/// Player ship data
|
||||||
pub player: Arc<PlayerAgent>,
|
pub player: Arc<PlayerAgent>,
|
||||||
|
|
||||||
/// The system we're currently in
|
/// The system we're currently in
|
||||||
pub current_system: Arc<System>,
|
pub current_system: Arc<System>,
|
||||||
|
|
||||||
/// Height of screen, in world units
|
|
||||||
pub camera_zoom: f32,
|
|
||||||
|
|
||||||
/// The world state to render
|
/// The world state to render
|
||||||
pub phys_img: Arc<PhysImage>,
|
pub phys_img: Arc<PhysImage>,
|
||||||
|
|
||||||
|
@ -28,9 +21,6 @@ pub struct RenderInput {
|
||||||
/// The current time, in seconds
|
/// The current time, in seconds
|
||||||
pub current_time: f32,
|
pub current_time: f32,
|
||||||
|
|
||||||
/// The amount of time that has passed since the last frame was drawn
|
|
||||||
pub time_since_last_run: f32,
|
|
||||||
|
|
||||||
/// Game content
|
/// Game content
|
||||||
pub ct: Arc<Content>,
|
pub ct: Arc<Content>,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
use rhai::{plugin::*, Dynamic, Module};
|
||||||
|
|
||||||
|
#[export_module]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub mod player_directive_module {
|
||||||
|
use galactica_system::PlayerDirective;
|
||||||
|
|
||||||
|
pub const None: PlayerDirective = PlayerDirective::None;
|
||||||
|
pub const Land: PlayerDirective = PlayerDirective::Land;
|
||||||
|
pub const UnLand: PlayerDirective = PlayerDirective::UnLand;
|
||||||
|
|
||||||
|
pub fn Engine(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::Engine(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TurnLeft(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::TurnLeft(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TurnRight(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::TurnRight(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Guns(state: bool) -> PlayerDirective {
|
||||||
|
PlayerDirective::Guns(state)
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,3 +38,31 @@ impl CustomType for PlayerShipStateEvent {
|
||||||
builder.with_name("PlayerShipStateEvent");
|
builder.with_name("PlayerShipStateEvent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KeyboardEvent {
|
||||||
|
pub down: bool,
|
||||||
|
pub key: ImmutableString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomType for KeyboardEvent {
|
||||||
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.with_name("KeyboardEvent")
|
||||||
|
.with_fn("is_down", |s: &mut Self| s.down)
|
||||||
|
.with_fn("key", |s: &mut Self| s.key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ScrollEvent {
|
||||||
|
pub val: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomType for ScrollEvent {
|
||||||
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.with_name("ScrollEvent")
|
||||||
|
.with_fn("val", |s: &mut Self| s.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod conf;
|
mod conf;
|
||||||
mod radialbar;
|
mod radialbar;
|
||||||
|
mod scrollbox;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod textbox;
|
mod textbox;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
pub use conf::build_conf_module;
|
pub use conf::build_conf_module;
|
||||||
pub use radialbar::build_radialbar_module;
|
pub use radialbar::build_radialbar_module;
|
||||||
|
pub use scrollbox::build_scrollbox_module;
|
||||||
pub use sprite::build_sprite_module;
|
pub use sprite::build_sprite_module;
|
||||||
pub use textbox::build_textbox_module;
|
pub use textbox::build_textbox_module;
|
||||||
pub use ui::build_ui_module;
|
pub use ui::build_ui_module;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use super::super::{Color, Rect};
|
use super::super::{Color, Rect};
|
||||||
use crate::ui::{elements::RadialBar, UiElement, UiState};
|
use crate::ui::{elements::UiRadialBar, UiElement, UiState};
|
||||||
|
|
||||||
pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
|
@ -23,11 +23,13 @@ pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_state.names.push(name.clone());
|
ui_state.add_element(UiElement::RadialBar(UiRadialBar::new(
|
||||||
ui_state.elements.insert(
|
|
||||||
name.clone(),
|
name.clone(),
|
||||||
UiElement::RadialBar(RadialBar::new(name.clone(), stroke, color, rect, 1.0)),
|
stroke,
|
||||||
);
|
color,
|
||||||
|
rect,
|
||||||
|
1.0,
|
||||||
|
)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
use log::error;
|
||||||
|
use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use super::super::Rect;
|
||||||
|
use crate::ui::{elements::UiScrollbox, UiElement, UiState};
|
||||||
|
|
||||||
|
pub fn build_scrollbox_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
|
let mut module = Module::new();
|
||||||
|
module.set_id("GalacticaScrollboxModule");
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("add")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(&mut module, move |name: ImmutableString, rect: Rect| {
|
||||||
|
let mut ui_state = state.borrow_mut();
|
||||||
|
|
||||||
|
if ui_state.elements.contains_key(&name) {
|
||||||
|
error!("tried to make a scrollbox using an existing name `{name}`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_state.add_element(UiElement::Scrollbox(UiScrollbox::new(name.clone(), rect)));
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("add_element")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(
|
||||||
|
&mut module,
|
||||||
|
move |name: ImmutableString, target: ImmutableString| {
|
||||||
|
let mut ui_state = state.borrow_mut();
|
||||||
|
match ui_state.get_mut_by_name(&name) {
|
||||||
|
Some(UiElement::Scrollbox(_)) => {
|
||||||
|
match ui_state.get_mut_by_name(&target) {
|
||||||
|
Some(UiElement::Text(_)) | Some(UiElement::Sprite(_)) => {
|
||||||
|
let e = match ui_state.remove_element_incomplete(&target) {
|
||||||
|
Some(UiElement::Sprite(s)) => {
|
||||||
|
Rc::new(RefCell::new(UiElement::Sprite(s)))
|
||||||
|
}
|
||||||
|
Some(UiElement::Text(t)) => {
|
||||||
|
Rc::new(RefCell::new(UiElement::Text(t)))
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a subelement pointing to this sprite
|
||||||
|
ui_state.add_element(UiElement::SubElement {
|
||||||
|
parent: name.clone(),
|
||||||
|
element: e.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add this sprite to a scrollbox
|
||||||
|
match ui_state.get_mut_by_name(&name) {
|
||||||
|
Some(UiElement::Scrollbox(s)) => {
|
||||||
|
s.add_element(e);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
error!("cannot add `{name}` to scrollbox `{name}`, invalid type.")
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("called `scrollbox::add_element` with a non-existing target `{target}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("called `scrollbox::add_element` on an invalid name `{name}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
|
@ -17,12 +17,12 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
.with_namespace(FnNamespace::Internal)
|
.with_namespace(FnNamespace::Internal)
|
||||||
.set_into_module(
|
.set_into_module(
|
||||||
&mut module,
|
&mut module,
|
||||||
move |name: ImmutableString, sprite: ImmutableString, rect: Rect| {
|
move |name: ImmutableString, sprite_name: ImmutableString, rect: Rect| {
|
||||||
let mut ui_state = state.borrow_mut();
|
let mut ui_state = state.borrow_mut();
|
||||||
|
|
||||||
let sprite_handle = ct.sprites.get(&sprite.clone().into());
|
let sprite = ct.sprites.get(&sprite_name.clone().into());
|
||||||
if sprite_handle.is_none() {
|
if sprite.is_none() {
|
||||||
error!("made a sprite using an invalid source `{sprite}`");
|
error!("made a sprite using an invalid source `{sprite_name}`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,15 +31,11 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_state.names.push(name.clone());
|
ui_state.add_element(UiElement::Sprite(UiSprite::new(
|
||||||
ui_state.elements.insert(
|
|
||||||
name.clone(),
|
name.clone(),
|
||||||
UiElement::Sprite(UiSprite::new(
|
sprite.unwrap().clone(),
|
||||||
name.clone(),
|
|
||||||
sprite_handle.unwrap().clone(),
|
|
||||||
rect,
|
rect,
|
||||||
)),
|
)));
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -60,8 +56,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
.set_into_module(&mut module, move |name: ImmutableString| {
|
.set_into_module(&mut module, move |name: ImmutableString| {
|
||||||
let mut ui_state = state.borrow_mut();
|
let mut ui_state = state.borrow_mut();
|
||||||
if ui_state.elements.contains_key(&name) {
|
if ui_state.elements.contains_key(&name) {
|
||||||
ui_state.elements.remove(&name).unwrap();
|
ui_state.remove_element(&name);
|
||||||
ui_state.names.retain(|x| *x != name);
|
|
||||||
} else {
|
} else {
|
||||||
error!("called `sprite::remove` on an invalid name `{name}`")
|
error!("called `sprite::remove` on an invalid name `{name}`")
|
||||||
}
|
}
|
||||||
|
@ -94,7 +89,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
);
|
);
|
||||||
|
|
||||||
let state = state_src.clone();
|
let state = state_src.clone();
|
||||||
let _ = FuncRegistration::new("take_edge")
|
let _ = FuncRegistration::new("jump_to")
|
||||||
.with_namespace(FnNamespace::Internal)
|
.with_namespace(FnNamespace::Internal)
|
||||||
.set_into_module(
|
.set_into_module(
|
||||||
&mut module,
|
&mut module,
|
||||||
|
@ -110,7 +105,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
let edge = match edge {
|
let edge = match edge {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!(
|
error!(
|
||||||
"called `sprite::take_edge` on an invalid edge `{}` on sprite `{}`",
|
"called `sprite::jump_to` on an invalid edge `{}` on sprite `{}`",
|
||||||
edge_name, sprite.index
|
edge_name, sprite.index
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -121,7 +116,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
||||||
x.anim.jump_to(&edge);
|
x.anim.jump_to(&edge);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("called `sprite::take_edge` on an invalid name `{name}`")
|
error!("called `sprite::jump_to` on an invalid name `{name}`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use super::super::{Color, Rect};
|
use super::super::{Color, Rect};
|
||||||
use crate::ui::{elements::TextBox, UiElement, UiState};
|
use crate::ui::{elements::UiTextBox, UiElement, UiState};
|
||||||
|
|
||||||
pub fn build_textbox_module(
|
pub fn build_textbox_module(
|
||||||
font_src: Rc<RefCell<FontSystem>>,
|
font_src: Rc<RefCell<FontSystem>>,
|
||||||
|
@ -32,18 +32,14 @@ pub fn build_textbox_module(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_state.names.push(name.clone());
|
ui_state.add_element(UiElement::Text(UiTextBox::new(
|
||||||
ui_state.elements.insert(
|
|
||||||
name.clone(),
|
|
||||||
UiElement::Text(TextBox::new(
|
|
||||||
&mut font.borrow_mut(),
|
&mut font.borrow_mut(),
|
||||||
name.clone(),
|
name.clone(),
|
||||||
font_size,
|
font_size,
|
||||||
line_height,
|
line_height,
|
||||||
rect,
|
rect,
|
||||||
color,
|
color,
|
||||||
)),
|
)));
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,17 @@ pub fn build_ui_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
||||||
ui_state.set_scene(scene);
|
ui_state.set_scene(scene);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("get_camera_zoom")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(&mut module, move || state.borrow().camera.get_zoom());
|
||||||
|
|
||||||
|
let state = state_src.clone();
|
||||||
|
let _ = FuncRegistration::new("set_camera_zoom")
|
||||||
|
.with_namespace(FnNamespace::Internal)
|
||||||
|
.set_into_module(&mut module, move |z: f32| {
|
||||||
|
state.borrow_mut().camera.set_zoom(z)
|
||||||
|
});
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use nalgebra::{Point2, Vector2};
|
use nalgebra::{Point2, Vector2};
|
||||||
use rhai::{CustomType, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
use winit::{dpi::LogicalSize, window::Window};
|
use winit::{
|
||||||
|
dpi::{LogicalSize, PhysicalPosition},
|
||||||
|
window::Window,
|
||||||
|
};
|
||||||
|
|
||||||
use super::anchor::Anchor;
|
use super::{anchor::Anchor, vector::UiVector};
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::RenderState;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
|
@ -78,7 +81,11 @@ impl Rect {
|
||||||
|
|
||||||
impl CustomType for Rect {
|
impl CustomType for Rect {
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
builder.with_name("Rect").with_fn("Rect", Self::new);
|
builder
|
||||||
|
.with_name("Rect")
|
||||||
|
.with_fn("Rect", Self::new)
|
||||||
|
.with_fn("pos", |s: &mut Self| UiVector::new(s.pos.x, s.pos.y))
|
||||||
|
.with_fn("dim", |s: &mut Self| UiVector::new(s.dim.x, s.dim.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,17 +107,16 @@ impl CenteredRect {
|
||||||
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
|
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_mouse(&self, input: &RenderInput, state: &RenderState) -> bool {
|
pub fn contains_mouse(&self, state: &RenderState, mouse_pos: &PhysicalPosition<f32>) -> bool {
|
||||||
let fac = state.window.scale_factor() as f32;
|
let fac = state.window.scale_factor() as f32;
|
||||||
let window_size = Vector2::new(
|
let window_size = Vector2::new(
|
||||||
state.window_size.width as f32 / fac,
|
state.window_size.width as f32 / fac,
|
||||||
state.window_size.height as f32 / fac,
|
state.window_size.height as f32 / fac,
|
||||||
);
|
);
|
||||||
|
|
||||||
let pos = input.player.input.get_mouse_pos();
|
|
||||||
let mouse_pos = Point2::new(
|
let mouse_pos = Point2::new(
|
||||||
pos.x / fac - window_size.x / 2.0,
|
mouse_pos.x / fac - window_size.x / 2.0,
|
||||||
window_size.y / 2.0 - pos.y / fac,
|
window_size.y / 2.0 - mouse_pos.y / fac,
|
||||||
);
|
);
|
||||||
|
|
||||||
return self.contains_point(mouse_pos);
|
return self.contains_point(mouse_pos);
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
mod directive;
|
||||||
mod event;
|
mod event;
|
||||||
mod functions;
|
mod functions;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
pub use directive::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
use glyphon::FontSystem;
|
|
||||||
pub use helpers::{anchor::*, color::*, rect::*, vector::*};
|
pub use helpers::{anchor::*, color::*, rect::*, vector::*};
|
||||||
use log::debug;
|
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
|
||||||
use super::UiState;
|
use super::UiState;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use rhai::{exported_module, Dynamic, Engine};
|
use galactica_system::PlayerDirective;
|
||||||
|
use glyphon::FontSystem;
|
||||||
|
use rhai::{exported_module, Engine};
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
pub fn register_into_engine(
|
pub fn register_into_engine(
|
||||||
|
@ -29,21 +31,27 @@ pub fn register_into_engine(
|
||||||
.build_type::<State>()
|
.build_type::<State>()
|
||||||
.build_type::<ShipState>()
|
.build_type::<ShipState>()
|
||||||
.build_type::<SystemObjectState>()
|
.build_type::<SystemObjectState>()
|
||||||
|
.build_type::<OutfitState>()
|
||||||
// Events
|
// Events
|
||||||
.build_type::<MouseClickEvent>()
|
.build_type::<MouseClickEvent>()
|
||||||
.build_type::<MouseHoverEvent>()
|
.build_type::<MouseHoverEvent>()
|
||||||
.build_type::<PlayerShipStateEvent>()
|
.build_type::<PlayerShipStateEvent>()
|
||||||
|
.build_type::<KeyboardEvent>()
|
||||||
|
.build_type::<ScrollEvent>()
|
||||||
// Bigger modules
|
// Bigger modules
|
||||||
.register_type_with_name::<Anchor>("Anchor")
|
.register_type_with_name::<Anchor>("Anchor")
|
||||||
.register_static_module("Anchor", exported_module!(anchor_mod).into());
|
.register_static_module("Anchor", exported_module!(anchor_mod).into())
|
||||||
|
.register_type_with_name::<PlayerDirective>("PlayerDirective")
|
||||||
|
.register_static_module(
|
||||||
|
"PlayerDirective",
|
||||||
|
exported_module!(player_directive_module).into(),
|
||||||
|
);
|
||||||
|
|
||||||
// Extra functions
|
// Extra functions
|
||||||
engine.register_fn("print", move |d: Dynamic| {
|
engine.register_fn("clamp", |x: f32, l: f32, h: f32| x.clamp(l, h));
|
||||||
debug!("{:?}", d);
|
|
||||||
});
|
|
||||||
engine.register_fn("clamp", move |x: f32, l: f32, h: f32| x.clamp(l, h));
|
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
|
engine.register_static_module("ui", functions::build_ui_module(state_src.clone()).into());
|
||||||
engine.register_static_module(
|
engine.register_static_module(
|
||||||
"sprite",
|
"sprite",
|
||||||
functions::build_sprite_module(ct_src.clone(), state_src.clone()).into(),
|
functions::build_sprite_module(ct_src.clone(), state_src.clone()).into(),
|
||||||
|
@ -56,7 +64,10 @@ pub fn register_into_engine(
|
||||||
"radialbar",
|
"radialbar",
|
||||||
functions::build_radialbar_module(state_src.clone()).into(),
|
functions::build_radialbar_module(state_src.clone()).into(),
|
||||||
);
|
);
|
||||||
engine.register_static_module("ui", functions::build_ui_module(state_src.clone()).into());
|
engine.register_static_module(
|
||||||
|
"scrollbox",
|
||||||
|
functions::build_scrollbox_module(state_src.clone()).into(),
|
||||||
|
);
|
||||||
engine.register_static_module(
|
engine.register_static_module(
|
||||||
"conf",
|
"conf",
|
||||||
functions::build_conf_module(state_src.clone()).into(),
|
functions::build_conf_module(state_src.clone()).into(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use galactica_content::{Ship, SystemObject};
|
use galactica_content::{Outfit, Ship, SystemObject};
|
||||||
use galactica_system::{
|
use galactica_system::{
|
||||||
data::{self},
|
data::{self},
|
||||||
phys::{objects::PhysShip, PhysSimShipHandle},
|
phys::{objects::PhysShip, PhysSimShipHandle},
|
||||||
|
@ -19,14 +19,15 @@ pub struct ShipState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShipState {
|
impl ShipState {
|
||||||
|
// All functions passed to rhai MUST be mut,
|
||||||
|
// even getters.
|
||||||
fn get_content(&mut self) -> &Ship {
|
fn get_content(&mut self) -> &Ship {
|
||||||
let ship = self
|
let ship = self
|
||||||
.input
|
.input
|
||||||
.phys_img
|
.phys_img
|
||||||
.get_ship(self.ship.as_ref().unwrap())
|
.get_ship(self.ship.as_ref().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let handle = ship.ship.get_data().get_content();
|
ship.ship.get_data().get_content()
|
||||||
handle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ship(&mut self) -> &PhysShip {
|
fn get_ship(&mut self) -> &PhysShip {
|
||||||
|
@ -116,28 +117,64 @@ impl CustomType for ShipState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OutfitState {
|
||||||
|
outfit: Arc<Outfit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutfitState {}
|
||||||
|
|
||||||
|
impl CustomType for OutfitState {
|
||||||
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.with_name("OutfitState")
|
||||||
|
.with_fn("display_name", |s: &mut Self| s.outfit.display_name.clone())
|
||||||
|
.with_fn("index", |s: &mut Self| s.outfit.index.to_string())
|
||||||
|
.with_fn("thumbnail", |s: &mut Self| {
|
||||||
|
s.outfit.thumbnail.index.to_string()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SystemObjectState {
|
pub struct SystemObjectState {
|
||||||
object: Option<Arc<SystemObject>>,
|
object: Option<Arc<SystemObject>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemObjectState {}
|
impl SystemObjectState {
|
||||||
|
fn outfitter(&mut self) -> Array {
|
||||||
|
let mut a = Array::new();
|
||||||
|
for o in &self
|
||||||
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.landable
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.outfitter
|
||||||
|
{
|
||||||
|
a.push(Dynamic::from(OutfitState { outfit: o.clone() }));
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CustomType for SystemObjectState {
|
impl CustomType for SystemObjectState {
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
fn build(mut builder: TypeBuilder<Self>) {
|
||||||
builder
|
builder
|
||||||
.with_name("SystemObjectState")
|
.with_name("SystemObjectState")
|
||||||
|
.with_fn("outfitter", Self::outfitter)
|
||||||
//
|
//
|
||||||
// Get landable name
|
// Get landable name
|
||||||
.with_fn("display_name", |s: &mut Self| {
|
.with_fn("display_name", |s: &mut Self| {
|
||||||
s.object
|
s.object
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.display_name
|
.landable
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.display_name.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
error!("UI called `name()` on a system object which doesn't provide one");
|
error!("UI called `name()` on a system object which isn't landable");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -147,25 +184,32 @@ impl CustomType for SystemObjectState {
|
||||||
s.object
|
s.object
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.desc
|
.landable
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.desc.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
error!("UI called `name()` on a system object which doesn't provide one");
|
error!("UI called `desc()` on a system object which isn't landable");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
//
|
//
|
||||||
// Get landable landscape image
|
// Get landable landscape image
|
||||||
.with_fn("image", |s: &mut Self| {
|
.with_fn("image", |s: &mut Self| {
|
||||||
if let Some(sprite) = &s.object.as_ref().unwrap().image {
|
s.object
|
||||||
sprite.index.to_string()
|
.as_ref()
|
||||||
} else {
|
.unwrap()
|
||||||
error!("UI called `image()` on a system object which doesn't provide one");
|
.landable
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.image.index.to_string())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
error!("UI called `image()` on a system object which isn't landable");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
.with_fn("is_some", |s: &mut Self| s.object.is_some())
|
.with_fn("is_some", |s: &mut Self| s.object.is_some())
|
||||||
|
.with_fn("is_landable", |s: &mut Self| {
|
||||||
|
s.object.as_ref().unwrap().landable.is_some()
|
||||||
|
})
|
||||||
.with_fn("==", |a: &mut Self, b: Self| match (&a.object, &b.object) {
|
.with_fn("==", |a: &mut Self, b: Self| match (&a.object, &b.object) {
|
||||||
(None, _) => false,
|
(None, _) => false,
|
||||||
(_, None) => false,
|
(_, None) => false,
|
||||||
|
@ -240,7 +284,6 @@ impl CustomType for State {
|
||||||
.with_fn("player_ship", Self::player_ship)
|
.with_fn("player_ship", Self::player_ship)
|
||||||
.with_fn("ships", Self::ships)
|
.with_fn("ships", Self::ships)
|
||||||
.with_fn("objects", Self::objects)
|
.with_fn("objects", Self::objects)
|
||||||
.with_fn("window_aspect", |s: &mut Self| s.window_aspect)
|
.with_fn("window_aspect", |s: &mut Self| s.window_aspect);
|
||||||
.with_fn("camera_zoom", |s: &mut Self| s.input.camera_zoom);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Camera {
|
||||||
|
/// The position of the camera, in game units
|
||||||
|
pos: Vector2<f32>,
|
||||||
|
|
||||||
|
/// The height of the viewport, in game units.
|
||||||
|
zoom: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pos: Vector2::new(0.0, 0.0),
|
||||||
|
zoom: 500.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pos(&mut self, pos: Vector2<f32>) {
|
||||||
|
self.pos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pos(&self) -> Vector2<f32> {
|
||||||
|
self.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_zoom(&mut self, zoom: f32) {
|
||||||
|
self.zoom = zoom
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_zoom(&self) -> f32 {
|
||||||
|
self.zoom
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
use glyphon::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, TextArea, TextBounds};
|
use glyphon::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, TextBounds};
|
||||||
|
use std::rc::Rc;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
|
use super::OwnedTextArea;
|
||||||
|
|
||||||
pub(crate) struct FpsIndicator {
|
pub(crate) struct FpsIndicator {
|
||||||
buffer: Buffer,
|
buffer: Rc<Buffer>,
|
||||||
update_counter: u32,
|
update_counter: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +24,7 @@ impl FpsIndicator {
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
buffer,
|
buffer: Rc::new(buffer),
|
||||||
update_counter: 0,
|
update_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,26 +32,28 @@ impl FpsIndicator {
|
||||||
|
|
||||||
impl FpsIndicator {
|
impl FpsIndicator {
|
||||||
pub fn step(&mut self, input: &RenderInput, font: &mut FontSystem) {
|
pub fn step(&mut self, input: &RenderInput, font: &mut FontSystem) {
|
||||||
|
let buffer = Rc::get_mut(&mut self.buffer).unwrap();
|
||||||
|
|
||||||
if self.update_counter > 0 {
|
if self.update_counter > 0 {
|
||||||
self.update_counter -= 1;
|
self.update_counter -= 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.update_counter = 100;
|
self.update_counter = 100;
|
||||||
|
|
||||||
self.buffer.set_text(
|
buffer.set_text(
|
||||||
font,
|
font,
|
||||||
&input.timing.get_string(),
|
&input.timing.get_string(),
|
||||||
Attrs::new().family(Family::Monospace),
|
Attrs::new().family(Family::Monospace),
|
||||||
Shaping::Basic,
|
Shaping::Basic,
|
||||||
);
|
);
|
||||||
self.buffer.shape_until_scroll(font);
|
buffer.shape_until_scroll(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> FpsIndicator {
|
impl<'a, 'b: 'a> FpsIndicator {
|
||||||
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> TextArea<'a> {
|
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> OwnedTextArea {
|
||||||
TextArea {
|
OwnedTextArea {
|
||||||
buffer: &self.buffer,
|
buffer: self.buffer.clone(),
|
||||||
left: 10.0,
|
left: 10.0,
|
||||||
top: 400.0,
|
top: 400.0,
|
||||||
scale: input.ct.config.ui_scale,
|
scale: input.ct.config.ui_scale,
|
||||||
|
|
|
@ -1,9 +1,38 @@
|
||||||
mod fpsindicator;
|
mod fpsindicator;
|
||||||
mod radialbar;
|
mod radialbar;
|
||||||
|
mod scrollbox;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod textbox;
|
mod textbox;
|
||||||
|
|
||||||
pub(super) use fpsindicator::*;
|
pub(super) use fpsindicator::*;
|
||||||
pub(super) use radialbar::*;
|
pub(super) use radialbar::*;
|
||||||
|
pub(super) use scrollbox::*;
|
||||||
pub(super) use sprite::*;
|
pub(super) use sprite::*;
|
||||||
pub(super) use textbox::*;
|
pub(super) use textbox::*;
|
||||||
|
|
||||||
|
use glyphon::{Buffer, Color, TextArea, TextBounds};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// A hack that lets us easily construct TextAreas
|
||||||
|
/// for [`UiTextBox`]es wrapped in Rcs.
|
||||||
|
pub struct OwnedTextArea {
|
||||||
|
pub buffer: Rc<Buffer>,
|
||||||
|
pub left: f32,
|
||||||
|
pub top: f32,
|
||||||
|
pub scale: f32,
|
||||||
|
pub bounds: TextBounds,
|
||||||
|
pub default_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedTextArea {
|
||||||
|
pub fn get_textarea(&self) -> TextArea {
|
||||||
|
TextArea {
|
||||||
|
buffer: &self.buffer,
|
||||||
|
top: self.top,
|
||||||
|
left: self.left,
|
||||||
|
scale: self.scale,
|
||||||
|
bounds: self.bounds,
|
||||||
|
default_color: self.default_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::super::api::Rect;
|
||||||
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RadialBar {
|
pub struct UiRadialBar {
|
||||||
pub name: ImmutableString,
|
pub name: ImmutableString,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
|
@ -14,7 +14,7 @@ pub struct RadialBar {
|
||||||
progress: f32,
|
progress: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RadialBar {
|
impl UiRadialBar {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: ImmutableString,
|
name: ImmutableString,
|
||||||
stroke: f32,
|
stroke: f32,
|
||||||
|
@ -48,6 +48,4 @@ impl RadialBar {
|
||||||
angle: self.progress * TAU,
|
angle: self.progress * TAU,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, _input: &RenderInput, _state: &mut RenderState) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
use nalgebra::Vector2;
|
||||||
|
use rhai::{Dynamic, ImmutableString};
|
||||||
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
use super::{super::api::Rect, OwnedTextArea};
|
||||||
|
use crate::{ui::UiElement, InputEvent, RenderInput, RenderState};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UiScrollbox {
|
||||||
|
pub name: ImmutableString,
|
||||||
|
pub rect: Rect,
|
||||||
|
pub offset: Vector2<f32>,
|
||||||
|
pub elements: HashMap<ImmutableString, Rc<RefCell<UiElement>>>,
|
||||||
|
|
||||||
|
has_mouse: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiScrollbox {
|
||||||
|
pub fn new(name: ImmutableString, rect: Rect) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
rect,
|
||||||
|
elements: HashMap::new(),
|
||||||
|
offset: Vector2::new(0.0, 0.0),
|
||||||
|
has_mouse: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_element(&mut self, e: Rc<RefCell<UiElement>>) {
|
||||||
|
let name = e.borrow().get_name().clone();
|
||||||
|
self.elements.insert(name, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_element(&mut self, sprite: &ImmutableString) {
|
||||||
|
self.elements.remove(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(&mut self, t: f32) {
|
||||||
|
for (_name, e) in &self.elements {
|
||||||
|
match &mut *e.clone().borrow_mut() {
|
||||||
|
UiElement::Sprite(sprite) => sprite.step(t),
|
||||||
|
UiElement::RadialBar(_) => {}
|
||||||
|
UiElement::Text(..) => {}
|
||||||
|
UiElement::Scrollbox(..) => {}
|
||||||
|
UiElement::SubElement { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
event: &InputEvent,
|
||||||
|
) -> Option<Dynamic> {
|
||||||
|
let r = self
|
||||||
|
.rect
|
||||||
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
|
||||||
|
// TODO: handle only if used in event()
|
||||||
|
// i.e, scrollable sprites shouldn't break scrollboxes
|
||||||
|
// First, check if this event is captured by any sub-elements
|
||||||
|
for (_, e) in &mut self.elements {
|
||||||
|
let arg = match &mut *e.borrow_mut() {
|
||||||
|
UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event),
|
||||||
|
UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event),
|
||||||
|
|
||||||
|
UiElement::RadialBar(_) | UiElement::Text(..) => None,
|
||||||
|
|
||||||
|
// Subelements are intentionally skipped,
|
||||||
|
// they should be handled by their parent's `handle_event` method.
|
||||||
|
UiElement::SubElement { .. } => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if arg.is_some() {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no inner events were captured, handle self events.
|
||||||
|
match event {
|
||||||
|
InputEvent::MouseMove(pos) => {
|
||||||
|
if r.contains_mouse(state, pos) && !self.has_mouse {
|
||||||
|
self.has_mouse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.contains_mouse(state, pos) && self.has_mouse {
|
||||||
|
self.has_mouse = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent::Scroll(x) => {
|
||||||
|
if self.has_mouse {
|
||||||
|
self.offset.y -= x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiScrollbox {
|
||||||
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
for (_name, e) in &self.elements {
|
||||||
|
match &*e.clone().borrow() {
|
||||||
|
UiElement::Sprite(sprite) => {
|
||||||
|
sprite.push_to_buffer_with_offset(input, state, self.offset)
|
||||||
|
}
|
||||||
|
UiElement::RadialBar(..) => {}
|
||||||
|
UiElement::Text(..) => {}
|
||||||
|
UiElement::Scrollbox(..) => {}
|
||||||
|
UiElement::SubElement { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't allocate here
|
||||||
|
impl<'a> UiScrollbox {
|
||||||
|
pub fn get_textareas(&'a self, input: &RenderInput, window: &Window) -> Vec<OwnedTextArea> {
|
||||||
|
let mut v = Vec::with_capacity(32);
|
||||||
|
for e in self.elements.values() {
|
||||||
|
match &*e.clone().borrow() {
|
||||||
|
UiElement::Text(x) => {
|
||||||
|
v.push(x.get_textarea_with_offset(input, window, self.offset))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,14 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::super::api::Rect;
|
||||||
use crate::{
|
use crate::{
|
||||||
ui::{api::Color, event::Event},
|
ui::api::{Color, MouseClickEvent, MouseHoverEvent},
|
||||||
vertexbuffer::types::UiInstance,
|
vertexbuffer::types::UiInstance,
|
||||||
RenderInput, RenderState,
|
InputEvent, RenderInput, RenderState,
|
||||||
};
|
};
|
||||||
use galactica_content::{Sprite, SpriteAutomaton};
|
use galactica_content::{Sprite, SpriteAutomaton};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use rhai::ImmutableString;
|
use nalgebra::Vector2;
|
||||||
|
use rhai::{Dynamic, ImmutableString};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UiSprite {
|
pub struct UiSprite {
|
||||||
|
@ -26,8 +27,6 @@ pub struct UiSprite {
|
||||||
mask: Option<Arc<Sprite>>,
|
mask: Option<Arc<Sprite>>,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
|
||||||
/// If true, ignore mouse events until click is released
|
|
||||||
waiting_for_release: bool,
|
|
||||||
has_mouse: bool,
|
has_mouse: bool,
|
||||||
has_click: bool,
|
has_click: bool,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +42,6 @@ impl UiSprite {
|
||||||
mask: None,
|
mask: None,
|
||||||
has_mouse: false,
|
has_mouse: false,
|
||||||
has_click: false,
|
has_click: false,
|
||||||
waiting_for_release: false,
|
|
||||||
preserve_aspect: false,
|
preserve_aspect: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,11 +65,23 @@ impl UiSprite {
|
||||||
pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
|
pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
|
||||||
self.preserve_aspect = preserve_aspect;
|
self.preserve_aspect = preserve_aspect;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiSprite {
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
self.push_to_buffer_with_offset(input, state, Vector2::new(0.0, 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_to_buffer_with_offset(
|
||||||
|
&self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
offset: Vector2<f32>,
|
||||||
|
) {
|
||||||
let mut rect = self
|
let mut rect = self
|
||||||
.rect
|
.rect
|
||||||
.to_centered(&state.window, input.ct.config.ui_scale);
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
rect.pos += offset;
|
||||||
|
|
||||||
if self.preserve_aspect {
|
if self.preserve_aspect {
|
||||||
let rect_aspect = rect.dim.x / rect.dim.y;
|
let rect_aspect = rect.dim.x / rect.dim.y;
|
||||||
|
@ -108,55 +118,65 @@ impl UiSprite {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
|
pub fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
event: &InputEvent,
|
||||||
|
) -> Option<Dynamic> {
|
||||||
let r = self
|
let r = self
|
||||||
.rect
|
.rect
|
||||||
.to_centered(&state.window, input.ct.config.ui_scale);
|
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||||
|
|
||||||
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
|
||||||
self.waiting_for_release = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.waiting_for_release
|
|
||||||
&& self.has_mouse
|
|
||||||
&& !self.has_click
|
|
||||||
&& input.player.input.pressed_leftclick()
|
|
||||||
{
|
|
||||||
self.has_click = true;
|
|
||||||
return Event::MouseClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
|
|
||||||
self.has_click = false;
|
|
||||||
return Event::MouseRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release mouse when cursor leaves box
|
// Release mouse when cursor leaves box
|
||||||
if self.has_click && !self.has_mouse {
|
if self.has_click && !self.has_mouse {
|
||||||
self.has_click = false;
|
self.has_click = false;
|
||||||
return Event::MouseRelease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.contains_mouse(input, state) && !self.has_mouse {
|
match event {
|
||||||
if input.player.input.pressed_leftclick() {
|
InputEvent::MouseMove(pos) => {
|
||||||
// If we're holding click when the cursor enters,
|
if r.contains_mouse(state, pos) && !self.has_mouse {
|
||||||
// don't trigger the `Click` event.
|
|
||||||
self.waiting_for_release = true;
|
|
||||||
}
|
|
||||||
self.has_mouse = true;
|
self.has_mouse = true;
|
||||||
return Event::MouseHover;
|
return Some(Dynamic::from(MouseHoverEvent {
|
||||||
|
enter: true,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.contains_mouse(input, state) && self.has_mouse {
|
if !r.contains_mouse(state, pos) && self.has_mouse {
|
||||||
self.waiting_for_release = false;
|
|
||||||
self.has_mouse = false;
|
self.has_mouse = false;
|
||||||
return Event::MouseUnhover;
|
return Some(Dynamic::from(MouseHoverEvent {
|
||||||
|
enter: false,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event::None;
|
InputEvent::MouseLeftClick(pressed) => {
|
||||||
|
if self.has_mouse && !self.has_click && *pressed {
|
||||||
|
self.has_click = true;
|
||||||
|
return Some(Dynamic::from(MouseClickEvent {
|
||||||
|
down: true,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
if self.has_mouse && self.has_click && !*pressed {
|
||||||
self.anim.step(input.time_since_last_run);
|
self.has_click = false;
|
||||||
|
return Some(Dynamic::from(MouseClickEvent {
|
||||||
|
down: false,
|
||||||
|
element: self.name.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(&mut self, t: f32) {
|
||||||
|
self.anim.step(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
use glyphon::{
|
use glyphon::{
|
||||||
cosmic_text::Align, Attrs, AttrsOwned, Buffer, Color, FamilyOwned, FontSystem, Metrics,
|
cosmic_text::Align, Attrs, AttrsOwned, Buffer, Color, FamilyOwned, FontSystem, Metrics,
|
||||||
Shaping, Style, TextArea, TextBounds, Weight,
|
Shaping, Style, TextBounds, Weight,
|
||||||
};
|
};
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use rhai::ImmutableString;
|
use rhai::ImmutableString;
|
||||||
|
use std::rc::Rc;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::{super::api::Rect, OwnedTextArea};
|
||||||
use crate::{ui::api, RenderInput};
|
use crate::{ui::api, RenderInput};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextBox {
|
pub struct UiTextBox {
|
||||||
pub name: ImmutableString,
|
pub name: ImmutableString,
|
||||||
|
|
||||||
text: String,
|
text: String,
|
||||||
justify: Align,
|
justify: Align,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
buffer: Buffer,
|
buffer: Rc<Buffer>,
|
||||||
color: api::Color,
|
color: api::Color,
|
||||||
attrs: AttrsOwned,
|
attrs: AttrsOwned,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBox {
|
impl UiTextBox {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
font: &mut FontSystem,
|
font: &mut FontSystem,
|
||||||
name: ImmutableString,
|
name: ImmutableString,
|
||||||
|
@ -38,7 +39,7 @@ impl TextBox {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
rect,
|
rect,
|
||||||
buffer,
|
buffer: Rc::new(buffer),
|
||||||
color,
|
color,
|
||||||
justify: Align::Left,
|
justify: Align::Left,
|
||||||
attrs: AttrsOwned::new(Attrs::new()),
|
attrs: AttrsOwned::new(Attrs::new()),
|
||||||
|
@ -47,14 +48,14 @@ impl TextBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reflow(&mut self, font: &mut FontSystem) {
|
fn reflow(&mut self, font: &mut FontSystem) {
|
||||||
self.buffer
|
let buffer = Rc::get_mut(&mut self.buffer).unwrap();
|
||||||
.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
buffer.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
||||||
|
|
||||||
for l in &mut self.buffer.lines {
|
for l in &mut buffer.lines {
|
||||||
l.set_align(Some(self.justify));
|
l.set_align(Some(self.justify));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buffer.shape_until_scroll(font);
|
buffer.shape_until_scroll(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, font: &mut FontSystem, text: &str) {
|
pub fn set_text(&mut self, font: &mut FontSystem, text: &str) {
|
||||||
|
@ -84,9 +85,19 @@ impl TextBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> TextBox {
|
impl<'a, 'b: 'a> UiTextBox {
|
||||||
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> {
|
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> OwnedTextArea {
|
||||||
let rect = self.rect.to_centered(window, input.ct.config.ui_scale);
|
self.get_textarea_with_offset(input, window, Vector2::new(0.0, 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_textarea_with_offset(
|
||||||
|
&'b self,
|
||||||
|
input: &RenderInput,
|
||||||
|
window: &Window,
|
||||||
|
offset: Vector2<f32>,
|
||||||
|
) -> OwnedTextArea {
|
||||||
|
let mut rect = self.rect.to_centered(window, input.ct.config.ui_scale);
|
||||||
|
rect.pos += offset;
|
||||||
|
|
||||||
// Glypon works with physical pixels, so we must do some conversion
|
// Glypon works with physical pixels, so we must do some conversion
|
||||||
let fac = window.scale_factor() as f32;
|
let fac = window.scale_factor() as f32;
|
||||||
|
@ -97,8 +108,8 @@ impl<'a, 'b: 'a> TextBox {
|
||||||
let corner_sw = corner_ne + rect.dim * fac;
|
let corner_sw = corner_ne + rect.dim * fac;
|
||||||
let c = self.color.as_array_u8();
|
let c = self.color.as_array_u8();
|
||||||
|
|
||||||
TextArea {
|
OwnedTextArea {
|
||||||
buffer: &self.buffer,
|
buffer: self.buffer.clone(),
|
||||||
top: corner_ne.y,
|
top: corner_ne.y,
|
||||||
left: corner_ne.x,
|
left: corner_ne.x,
|
||||||
scale: input.ct.config.ui_scale,
|
scale: input.ct.config.ui_scale,
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
None,
|
|
||||||
MouseClick,
|
|
||||||
MouseRelease,
|
|
||||||
MouseHover,
|
|
||||||
MouseUnhover,
|
|
||||||
}
|
|
|
@ -1,20 +1,17 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_system::phys::PhysSimShipHandle;
|
use galactica_system::{phys::PhysSimShipHandle, PlayerDirective};
|
||||||
use galactica_util::rhai_error_to_anyhow;
|
use galactica_util::rhai_error_to_anyhow;
|
||||||
use log::debug;
|
use log::{debug, error};
|
||||||
use rhai::{
|
use rhai::{Dynamic, Engine, ImmutableString, Scope};
|
||||||
packages::{BasicArrayPackage, BasicStringPackage, LogicPackage, MoreStringPackage, Package},
|
|
||||||
Dynamic, Engine, ImmutableString, Scope,
|
|
||||||
};
|
|
||||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
api::{self, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent},
|
api::{self, KeyboardEvent, PlayerShipStateEvent, ScrollEvent},
|
||||||
event::Event,
|
|
||||||
UiConfig, UiElement, UiState,
|
UiConfig, UiElement, UiState,
|
||||||
};
|
};
|
||||||
use crate::{ui::api::State, RenderInput, RenderState};
|
use crate::{ui::api::State, InputEvent, RenderInput, RenderState};
|
||||||
|
|
||||||
pub(crate) struct UiScriptExecutor {
|
pub(crate) struct UiScriptExecutor {
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
|
@ -31,14 +28,8 @@ impl UiScriptExecutor {
|
||||||
let scope = Scope::new();
|
let scope = Scope::new();
|
||||||
let elements = Rc::new(RefCell::new(UiState::new(ct.clone(), state)));
|
let elements = Rc::new(RefCell::new(UiState::new(ct.clone(), state)));
|
||||||
|
|
||||||
let mut engine = Engine::new_raw();
|
// TODO: document all functions rhai provides
|
||||||
|
let mut engine = Engine::new();
|
||||||
// Required for array iteration
|
|
||||||
// We may need to add more packages here later.
|
|
||||||
engine.register_global_module(BasicArrayPackage::new().as_shared_module());
|
|
||||||
engine.register_global_module(LogicPackage::new().as_shared_module());
|
|
||||||
engine.register_global_module(BasicStringPackage::new().as_shared_module());
|
|
||||||
engine.register_global_module(MoreStringPackage::new().as_shared_module());
|
|
||||||
|
|
||||||
engine.set_max_expr_depths(0, 0);
|
engine.set_max_expr_depths(0, 0);
|
||||||
// Enables custom operators
|
// Enables custom operators
|
||||||
|
@ -64,6 +55,129 @@ impl UiScriptExecutor {
|
||||||
(*self.state).borrow().config.clone()
|
(*self.state).borrow().config.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut RenderState,
|
||||||
|
input: Arc<RenderInput>,
|
||||||
|
event: InputEvent,
|
||||||
|
) -> Result<PlayerDirective> {
|
||||||
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
if current_scene.is_none() {
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
let mut arg: Option<Dynamic> = None;
|
||||||
|
|
||||||
|
// First, check if this event is captured by any ui elements.
|
||||||
|
for (_, e) in &mut self.state.borrow_mut().elements {
|
||||||
|
arg = match e {
|
||||||
|
UiElement::Sprite(sprite) => sprite.handle_event(&input, state, &event),
|
||||||
|
UiElement::Scrollbox(sbox) => sbox.handle_event(&input, state, &event),
|
||||||
|
|
||||||
|
UiElement::RadialBar(_) | UiElement::Text(..) => None,
|
||||||
|
|
||||||
|
// Subelements are intentionally skipped,
|
||||||
|
// they should be handled by their parent's `handle_event` method.
|
||||||
|
UiElement::SubElement { .. } => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if arg.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing was caught, check global events
|
||||||
|
if arg.is_none() {
|
||||||
|
arg = match event {
|
||||||
|
InputEvent::Scroll(val) => Some(Dynamic::from(ScrollEvent { val })),
|
||||||
|
InputEvent::Keyboard { down, key } => {
|
||||||
|
let str = match key {
|
||||||
|
VirtualKeyCode::A => Some("A"),
|
||||||
|
VirtualKeyCode::B => Some("B"),
|
||||||
|
VirtualKeyCode::C => Some("C"),
|
||||||
|
VirtualKeyCode::D => Some("D"),
|
||||||
|
VirtualKeyCode::E => Some("E"),
|
||||||
|
VirtualKeyCode::F => Some("F"),
|
||||||
|
VirtualKeyCode::G => Some("G"),
|
||||||
|
VirtualKeyCode::H => Some("H"),
|
||||||
|
VirtualKeyCode::I => Some("I"),
|
||||||
|
VirtualKeyCode::J => Some("J"),
|
||||||
|
VirtualKeyCode::K => Some("K"),
|
||||||
|
VirtualKeyCode::L => Some("L"),
|
||||||
|
VirtualKeyCode::M => Some("M"),
|
||||||
|
VirtualKeyCode::N => Some("N"),
|
||||||
|
VirtualKeyCode::O => Some("O"),
|
||||||
|
VirtualKeyCode::P => Some("P"),
|
||||||
|
VirtualKeyCode::Q => Some("Q"),
|
||||||
|
VirtualKeyCode::R => Some("R"),
|
||||||
|
VirtualKeyCode::S => Some("S"),
|
||||||
|
VirtualKeyCode::T => Some("T"),
|
||||||
|
VirtualKeyCode::U => Some("U"),
|
||||||
|
VirtualKeyCode::V => Some("V"),
|
||||||
|
VirtualKeyCode::W => Some("W"),
|
||||||
|
VirtualKeyCode::X => Some("X"),
|
||||||
|
VirtualKeyCode::Y => Some("Y"),
|
||||||
|
VirtualKeyCode::Z => Some("Z"),
|
||||||
|
VirtualKeyCode::Up => Some("up"),
|
||||||
|
VirtualKeyCode::Down => Some("down"),
|
||||||
|
VirtualKeyCode::Left => Some("left"),
|
||||||
|
VirtualKeyCode::Right => Some("right"),
|
||||||
|
VirtualKeyCode::Space => Some("space"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(str) = str {
|
||||||
|
Some(Dynamic::from(KeyboardEvent {
|
||||||
|
down,
|
||||||
|
key: ImmutableString::from(str),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arg) = arg {
|
||||||
|
self.run_event_callback(state, input, arg)
|
||||||
|
} else {
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_event_callback(
|
||||||
|
&mut self,
|
||||||
|
state: &mut RenderState,
|
||||||
|
input: Arc<RenderInput>,
|
||||||
|
arg: Dynamic,
|
||||||
|
) -> Result<PlayerDirective> {
|
||||||
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
if current_scene.is_none() {
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
let current_scene = current_scene.unwrap();
|
||||||
|
let ct = (*self.state).borrow().ct.clone();
|
||||||
|
|
||||||
|
let d: Dynamic = rhai_error_to_anyhow(self.engine.call_fn(
|
||||||
|
&mut self.scope,
|
||||||
|
ct.config.ui_scenes.get(current_scene.as_str()).unwrap(),
|
||||||
|
"event",
|
||||||
|
(State::new(state, input.clone()), arg.clone()),
|
||||||
|
))
|
||||||
|
.with_context(|| format!("while handling event `{:?}`", arg))
|
||||||
|
.with_context(|| format!("in ui scene `{}`", current_scene))?;
|
||||||
|
|
||||||
|
if d.is::<PlayerDirective>() {
|
||||||
|
return Ok(d.cast());
|
||||||
|
} else if !(d.is_unit()) {
|
||||||
|
error!(
|
||||||
|
"`event()` in UI scene `{current_scene}` returned invalid type `{}`",
|
||||||
|
d
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(PlayerDirective::None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Change the current scene
|
/// Change the current scene
|
||||||
pub fn set_scene(&mut self, state: &RenderState, input: Arc<RenderInput>) -> Result<()> {
|
pub fn set_scene(&mut self, state: &RenderState, input: Arc<RenderInput>) -> Result<()> {
|
||||||
let current_scene = (*self.state).borrow().get_scene().clone();
|
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||||
|
@ -87,8 +201,8 @@ impl UiScriptExecutor {
|
||||||
let mut elm = self.state.borrow_mut();
|
let mut elm = self.state.borrow_mut();
|
||||||
elm.clear();
|
elm.clear();
|
||||||
drop(elm);
|
drop(elm);
|
||||||
let ct = (*self.state).borrow().ct.clone();
|
|
||||||
|
|
||||||
|
let ct = (*self.state).borrow().ct.clone();
|
||||||
rhai_error_to_anyhow(
|
rhai_error_to_anyhow(
|
||||||
self.engine.call_fn(
|
self.engine.call_fn(
|
||||||
&mut self.scope,
|
&mut self.scope,
|
||||||
|
@ -122,7 +236,6 @@ impl UiScriptExecutor {
|
||||||
(*self.state).borrow_mut().step(state, input.clone());
|
(*self.state).borrow_mut().step(state, input.clone());
|
||||||
|
|
||||||
// Run step() (if it is defined)
|
// Run step() (if it is defined)
|
||||||
|
|
||||||
let ast = ct
|
let ast = ct
|
||||||
.config
|
.config
|
||||||
.ui_scenes
|
.ui_scenes
|
||||||
|
@ -158,79 +271,25 @@ impl UiScriptExecutor {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
} {
|
} {
|
||||||
rhai_error_to_anyhow(
|
self.run_event_callback(state, input.clone(), Dynamic::from(PlayerShipStateEvent {}))?;
|
||||||
self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
ct.config
|
|
||||||
.ui_scenes
|
|
||||||
.get(current_scene.as_ref().unwrap().as_str())
|
|
||||||
.unwrap(),
|
|
||||||
"event",
|
|
||||||
(State::new(state, input.clone()), PlayerShipStateEvent {}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_context(|| format!("while handling player state change event"))
|
|
||||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = (*self.state).borrow().len();
|
let len = (*self.state).borrow().len();
|
||||||
for i in 0..len {
|
for i in 0..len {
|
||||||
let event_arg = match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
|
match (*self.state).borrow_mut().get_mut_by_idx(i).unwrap() {
|
||||||
UiElement::Sprite(sprite) => {
|
UiElement::Sprite(sprite) => {
|
||||||
// Draw and update sprites
|
|
||||||
sprite.step(&input, state);
|
|
||||||
sprite.push_to_buffer(&input, state);
|
sprite.push_to_buffer(&input, state);
|
||||||
let event = sprite.check_events(&input, state);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::None => None,
|
|
||||||
|
|
||||||
Event::MouseClick => Some(Dynamic::from(MouseClickEvent {
|
|
||||||
down: true,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
Event::MouseRelease => Some(Dynamic::from(MouseClickEvent {
|
|
||||||
down: false,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
Event::MouseHover => Some(Dynamic::from(MouseHoverEvent {
|
|
||||||
enter: true,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
|
|
||||||
Event::MouseUnhover => Some(Dynamic::from(MouseHoverEvent {
|
|
||||||
enter: false,
|
|
||||||
element: sprite.name.clone(),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UiElement::RadialBar(x) => {
|
UiElement::RadialBar(x) => {
|
||||||
// Draw and update radialbar
|
|
||||||
x.step(&input, state);
|
|
||||||
x.push_to_buffer(&input, state);
|
x.push_to_buffer(&input, state);
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UiElement::Text(..) => None,
|
UiElement::Scrollbox(x) => {
|
||||||
};
|
x.push_to_buffer(&input, state);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(event_arg) = event_arg {
|
UiElement::SubElement { .. } | UiElement::Text(..) => {}
|
||||||
rhai_error_to_anyhow(
|
|
||||||
self.engine.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
ct.config
|
|
||||||
.ui_scenes
|
|
||||||
.get(current_scene.as_ref().unwrap().as_str())
|
|
||||||
.unwrap(),
|
|
||||||
"event",
|
|
||||||
(State::new(state, input.clone()), event_arg.clone()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_context(|| format!("while handling event `{:?}`", event_arg))
|
|
||||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod event;
|
mod camera;
|
||||||
|
mod elements;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
mod elements;
|
pub(crate) use camera::*;
|
||||||
|
|
||||||
pub(crate) use executor::UiScriptExecutor;
|
pub(crate) use executor::UiScriptExecutor;
|
||||||
pub(crate) use state::*;
|
pub(crate) use state::*;
|
||||||
|
|
|
@ -1,19 +1,39 @@
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use glyphon::TextArea;
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use rhai::ImmutableString;
|
use rhai::ImmutableString;
|
||||||
use std::collections::HashMap;
|
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, time::Instant};
|
||||||
use std::sync::Arc;
|
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
use super::elements::{FpsIndicator, RadialBar, TextBox, UiSprite};
|
use super::{
|
||||||
|
elements::{FpsIndicator, OwnedTextArea, UiRadialBar, UiScrollbox, UiSprite, UiTextBox},
|
||||||
|
Camera,
|
||||||
|
};
|
||||||
use crate::{RenderInput, RenderState};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UiElement {
|
pub enum UiElement {
|
||||||
Sprite(UiSprite),
|
Sprite(UiSprite),
|
||||||
RadialBar(RadialBar),
|
RadialBar(UiRadialBar),
|
||||||
Text(TextBox),
|
Text(UiTextBox),
|
||||||
|
Scrollbox(UiScrollbox),
|
||||||
|
|
||||||
|
/// This is a sub-element managed by another element
|
||||||
|
SubElement {
|
||||||
|
parent: ImmutableString,
|
||||||
|
element: Rc<RefCell<UiElement>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiElement {
|
||||||
|
pub fn get_name(&self) -> ImmutableString {
|
||||||
|
match self {
|
||||||
|
Self::Sprite(x) => x.name.clone(),
|
||||||
|
Self::RadialBar(x) => x.name.clone(),
|
||||||
|
Self::Text(x) => x.name.clone(),
|
||||||
|
Self::Scrollbox(x) => x.name.clone(),
|
||||||
|
Self::SubElement { element, .. } => element.borrow().get_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -31,8 +51,13 @@ pub(crate) struct UiState {
|
||||||
|
|
||||||
show_timings: bool,
|
show_timings: bool,
|
||||||
fps_indicator: FpsIndicator,
|
fps_indicator: FpsIndicator,
|
||||||
|
last_step: Instant,
|
||||||
|
|
||||||
pub config: UiConfig,
|
pub config: UiConfig,
|
||||||
|
|
||||||
|
/// The player's camera.
|
||||||
|
/// Only used when drawing physics.
|
||||||
|
pub camera: Camera,
|
||||||
}
|
}
|
||||||
// TODO: remove this
|
// TODO: remove this
|
||||||
unsafe impl Send for UiState {}
|
unsafe impl Send for UiState {}
|
||||||
|
@ -52,6 +77,8 @@ impl UiState {
|
||||||
show_phys: false,
|
show_phys: false,
|
||||||
show_starfield: false,
|
show_starfield: false,
|
||||||
},
|
},
|
||||||
|
last_step: Instant::now(),
|
||||||
|
camera: Camera::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,15 +132,57 @@ impl UiState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, state: &mut RenderState, input: Arc<RenderInput>) {
|
pub fn step(&mut self, state: &mut RenderState, input: Arc<RenderInput>) {
|
||||||
|
let t = self.last_step.elapsed().as_secs_f32();
|
||||||
|
for (_, e) in &mut self.elements {
|
||||||
|
match e {
|
||||||
|
UiElement::Sprite(sprite) => sprite.step(t),
|
||||||
|
UiElement::Scrollbox(sbox) => sbox.step(t),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.show_timings {
|
if self.show_timings {
|
||||||
self.fps_indicator
|
self.fps_indicator
|
||||||
.step(&input, &mut state.text_font_system.borrow_mut());
|
.step(&input, &mut state.text_font_system.borrow_mut());
|
||||||
}
|
}
|
||||||
|
self.last_step = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_element(&mut self, e: UiElement) {
|
||||||
|
self.names.push(e.get_name().clone());
|
||||||
|
self.elements.insert(e.get_name().clone(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an element from this sprite.
|
||||||
|
// This does NOT remove subelements from their parent sprites.
|
||||||
|
pub fn remove_element_incomplete(&mut self, name: &ImmutableString) -> Option<UiElement> {
|
||||||
|
let e = self.elements.remove(name);
|
||||||
|
self.names.retain(|x| *x != name);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an element from this sprite and from all subsprites.
|
||||||
|
pub fn remove_element(&mut self, name: &ImmutableString) {
|
||||||
|
let e = self.elements.remove(name);
|
||||||
|
self.names.retain(|x| *x != name);
|
||||||
|
|
||||||
|
match e {
|
||||||
|
Some(UiElement::SubElement { parent, element }) => {
|
||||||
|
let x = Rc::into_inner(element).unwrap().into_inner();
|
||||||
|
let parent = self.elements.get_mut(&parent).unwrap();
|
||||||
|
match parent {
|
||||||
|
UiElement::Scrollbox(s) => s.remove_element(&x.get_name()),
|
||||||
|
_ => unreachable!("invalid subelement parent"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: don't allocate here, return an iterator
|
||||||
impl<'a> UiState {
|
impl<'a> UiState {
|
||||||
pub fn get_textareas(&'a mut self, input: &RenderInput, window: &Window) -> Vec<TextArea<'a>> {
|
pub fn get_textareas(&'a self, input: &RenderInput, window: &Window) -> Vec<OwnedTextArea> {
|
||||||
let mut v = Vec::with_capacity(32);
|
let mut v = Vec::with_capacity(32);
|
||||||
|
|
||||||
if self.current_scene.is_none() {
|
if self.current_scene.is_none() {
|
||||||
|
@ -124,9 +193,10 @@ impl<'a> UiState {
|
||||||
v.push(self.fps_indicator.get_textarea(input, window))
|
v.push(self.fps_indicator.get_textarea(input, window))
|
||||||
}
|
}
|
||||||
|
|
||||||
for t in self.elements.values() {
|
for e in self.elements.values() {
|
||||||
match &t {
|
match &e {
|
||||||
UiElement::Text(x) => v.push(x.get_textarea(input, window)),
|
UiElement::Text(t) => v.push(t.get_textarea(input, window)),
|
||||||
|
UiElement::Scrollbox(b) => v.extend(b.get_textareas(input, window)),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue