Reworked renderer for Directives
Added OwnedTextArea & reworked textarea creation Added ScrollBoxmaster
parent
55319d6872
commit
b170f3f53f
|
@ -1,13 +1,13 @@
|
|||
use anyhow::Result;
|
||||
use bytemuck;
|
||||
use galactica_content::Content;
|
||||
use galactica_system::data::ShipState;
|
||||
use galactica_system::{data::ShipState, phys::PhysSimShipHandle, PlayerDirective};
|
||||
use galactica_util::to_radians;
|
||||
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 wgpu;
|
||||
use winit;
|
||||
use winit::{self};
|
||||
|
||||
use crate::{
|
||||
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
||||
|
@ -17,7 +17,7 @@ use crate::{
|
|||
texturearray::TextureArray,
|
||||
ui::UiScriptExecutor,
|
||||
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.
|
||||
|
@ -259,6 +259,8 @@ impl GPUState {
|
|||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
|
||||
// TODO: this takes a long time. fix!
|
||||
self.starfield.update_buffer(ct, &mut self.state);
|
||||
}
|
||||
|
||||
|
@ -274,18 +276,54 @@ impl GPUState {
|
|||
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.
|
||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||
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
|
||||
self.state.queue.write_buffer(
|
||||
&self.state.global_uniform.data_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[GlobalDataContent {
|
||||
camera_position_x: input.camera_pos.x,
|
||||
camera_position_y: input.camera_pos.y,
|
||||
camera_zoom: input.camera_zoom,
|
||||
camera_position_x: self.ui.state.borrow().camera.get_pos().x,
|
||||
camera_position_y: self.ui.state.borrow().camera.get_pos().y,
|
||||
camera_zoom: self.ui.state.borrow().camera.get_zoom(),
|
||||
camera_zoom_min: input.ct.config.zoom_min,
|
||||
camera_zoom_max: input.ct.config.zoom_max,
|
||||
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.
|
||||
// Used to skip off-screen sprites.
|
||||
let clip_ne = Point2::new(-self.state.window_aspect, 1.0) * input.camera_zoom;
|
||||
let clip_sw = Point2::new(self.state.window_aspect, -1.0) * input.camera_zoom;
|
||||
let clip_ne = Point2::new(-self.state.window_aspect, 1.0)
|
||||
* 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.
|
||||
// The order inside ships and projectiles doesn't matter,
|
||||
|
@ -424,7 +464,9 @@ impl GPUState {
|
|||
},
|
||||
(*self.ui.state)
|
||||
.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,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -483,8 +525,9 @@ impl GPUState {
|
|||
// Position adjusted for parallax
|
||||
// TODO: adjust parallax for zoom?
|
||||
// 1.0 is z-coordinate, which is constant for ships
|
||||
let pos: Point2<f32> =
|
||||
(Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / ship_pos.z;
|
||||
let pos: Point2<f32> = (Point2::new(ship_pos.x, ship_pos.y)
|
||||
- self.ui.state.borrow().camera.get_pos())
|
||||
/ ship_pos.z;
|
||||
|
||||
// Game dimensions of this sprite post-scale.
|
||||
// Post-scale width or height, whichever is larger.
|
||||
|
@ -588,7 +631,7 @@ impl GPUState {
|
|||
// Position adjusted for parallax
|
||||
// TODO: adjust parallax for zoom?
|
||||
// 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.
|
||||
// Post-scale width or height, whichever is larger.
|
||||
|
@ -649,7 +692,8 @@ impl GPUState {
|
|||
|
||||
for o in v {
|
||||
// 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.
|
||||
// Post-scale width or height, whichever is larger.
|
||||
|
@ -711,7 +755,7 @@ impl GPUState {
|
|||
// Position adjusted for parallax
|
||||
// TODO: adjust parallax for zoom?
|
||||
// 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.
|
||||
// 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 gpustate;
|
||||
mod inputevent;
|
||||
mod pipeline;
|
||||
mod renderinput;
|
||||
mod renderstate;
|
||||
|
@ -18,7 +19,8 @@ mod texturearray;
|
|||
mod ui;
|
||||
mod vertexbuffer;
|
||||
|
||||
pub use gpustate::GPUState;
|
||||
pub use gpustate::*;
|
||||
pub use inputevent::*;
|
||||
pub use renderinput::RenderInput;
|
||||
use renderstate::*;
|
||||
|
||||
|
|
|
@ -4,23 +4,16 @@ use galactica_content::{Content, System};
|
|||
use galactica_playeragent::PlayerAgent;
|
||||
use galactica_system::phys::PhysImage;
|
||||
use galactica_util::timing::Timing;
|
||||
use nalgebra::Vector2;
|
||||
|
||||
/// Bundles parameters passed to a single call to GPUState::render
|
||||
#[derive(Debug)]
|
||||
pub struct RenderInput {
|
||||
/// Camera position, in world units
|
||||
pub camera_pos: Vector2<f32>,
|
||||
|
||||
/// Player ship data
|
||||
pub player: Arc<PlayerAgent>,
|
||||
|
||||
/// The system we're currently in
|
||||
pub current_system: Arc<System>,
|
||||
|
||||
/// Height of screen, in world units
|
||||
pub camera_zoom: f32,
|
||||
|
||||
/// The world state to render
|
||||
pub phys_img: Arc<PhysImage>,
|
||||
|
||||
|
@ -28,9 +21,6 @@ pub struct RenderInput {
|
|||
/// The current time, in seconds
|
||||
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
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
#[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 radialbar;
|
||||
mod scrollbox;
|
||||
mod sprite;
|
||||
mod textbox;
|
||||
mod ui;
|
||||
|
||||
pub use conf::build_conf_module;
|
||||
pub use radialbar::build_radialbar_module;
|
||||
pub use scrollbox::build_scrollbox_module;
|
||||
pub use sprite::build_sprite_module;
|
||||
pub use textbox::build_textbox_module;
|
||||
pub use ui::build_ui_module;
|
||||
|
|
|
@ -3,7 +3,7 @@ use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
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 {
|
||||
let mut module = Module::new();
|
||||
|
@ -23,11 +23,13 @@ pub fn build_radialbar_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
|||
return;
|
||||
}
|
||||
|
||||
ui_state.names.push(name.clone());
|
||||
ui_state.elements.insert(
|
||||
ui_state.add_element(UiElement::RadialBar(UiRadialBar::new(
|
||||
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)
|
||||
.set_into_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 sprite_handle = ct.sprites.get(&sprite.clone().into());
|
||||
if sprite_handle.is_none() {
|
||||
error!("made a sprite using an invalid source `{sprite}`");
|
||||
let sprite = ct.sprites.get(&sprite_name.clone().into());
|
||||
if sprite.is_none() {
|
||||
error!("made a sprite using an invalid source `{sprite_name}`");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -31,15 +31,11 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
|||
return;
|
||||
}
|
||||
|
||||
ui_state.names.push(name.clone());
|
||||
ui_state.elements.insert(
|
||||
ui_state.add_element(UiElement::Sprite(UiSprite::new(
|
||||
name.clone(),
|
||||
UiElement::Sprite(UiSprite::new(
|
||||
name.clone(),
|
||||
sprite_handle.unwrap().clone(),
|
||||
rect,
|
||||
)),
|
||||
);
|
||||
sprite.unwrap().clone(),
|
||||
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| {
|
||||
let mut ui_state = state.borrow_mut();
|
||||
if ui_state.elements.contains_key(&name) {
|
||||
ui_state.elements.remove(&name).unwrap();
|
||||
ui_state.names.retain(|x| *x != name);
|
||||
ui_state.remove_element(&name);
|
||||
} else {
|
||||
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 _ = FuncRegistration::new("take_edge")
|
||||
let _ = FuncRegistration::new("jump_to")
|
||||
.with_namespace(FnNamespace::Internal)
|
||||
.set_into_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 {
|
||||
Err(_) => {
|
||||
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
|
||||
);
|
||||
return;
|
||||
|
@ -121,7 +116,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
|
|||
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 super::super::{Color, Rect};
|
||||
use crate::ui::{elements::TextBox, UiElement, UiState};
|
||||
use crate::ui::{elements::UiTextBox, UiElement, UiState};
|
||||
|
||||
pub fn build_textbox_module(
|
||||
font_src: Rc<RefCell<FontSystem>>,
|
||||
|
@ -32,18 +32,14 @@ pub fn build_textbox_module(
|
|||
return;
|
||||
}
|
||||
|
||||
ui_state.names.push(name.clone());
|
||||
ui_state.elements.insert(
|
||||
ui_state.add_element(UiElement::Text(UiTextBox::new(
|
||||
&mut font.borrow_mut(),
|
||||
name.clone(),
|
||||
UiElement::Text(TextBox::new(
|
||||
&mut font.borrow_mut(),
|
||||
name.clone(),
|
||||
font_size,
|
||||
line_height,
|
||||
rect,
|
||||
color,
|
||||
)),
|
||||
);
|
||||
font_size,
|
||||
line_height,
|
||||
rect,
|
||||
color,
|
||||
)));
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -15,5 +15,17 @@ pub fn build_ui_module(state_src: Rc<RefCell<UiState>>) -> Module {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use nalgebra::{Point2, Vector2};
|
||||
use rhai::{CustomType, TypeBuilder};
|
||||
use winit::{dpi::LogicalSize, window::Window};
|
||||
use winit::{
|
||||
dpi::{LogicalSize, PhysicalPosition},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
use super::anchor::Anchor;
|
||||
use crate::{RenderInput, RenderState};
|
||||
use super::{anchor::Anchor, vector::UiVector};
|
||||
use crate::RenderState;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rect {
|
||||
|
@ -78,7 +81,11 @@ impl Rect {
|
|||
|
||||
impl CustomType for Rect {
|
||||
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);
|
||||
}
|
||||
|
||||
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 window_size = Vector2::new(
|
||||
state.window_size.width as f32 / fac,
|
||||
state.window_size.height as f32 / fac,
|
||||
);
|
||||
|
||||
let pos = input.player.input.get_mouse_pos();
|
||||
let mouse_pos = Point2::new(
|
||||
pos.x / fac - window_size.x / 2.0,
|
||||
window_size.y / 2.0 - pos.y / fac,
|
||||
mouse_pos.x / fac - window_size.x / 2.0,
|
||||
window_size.y / 2.0 - mouse_pos.y / fac,
|
||||
);
|
||||
|
||||
return self.contains_point(mouse_pos);
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
mod directive;
|
||||
mod event;
|
||||
mod functions;
|
||||
mod helpers;
|
||||
mod state;
|
||||
|
||||
pub use directive::*;
|
||||
pub use event::*;
|
||||
use glyphon::FontSystem;
|
||||
pub use helpers::{anchor::*, color::*, rect::*, vector::*};
|
||||
use log::debug;
|
||||
pub use state::*;
|
||||
|
||||
use super::UiState;
|
||||
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};
|
||||
|
||||
pub fn register_into_engine(
|
||||
|
@ -29,21 +31,27 @@ pub fn register_into_engine(
|
|||
.build_type::<State>()
|
||||
.build_type::<ShipState>()
|
||||
.build_type::<SystemObjectState>()
|
||||
.build_type::<OutfitState>()
|
||||
// Events
|
||||
.build_type::<MouseClickEvent>()
|
||||
.build_type::<MouseHoverEvent>()
|
||||
.build_type::<PlayerShipStateEvent>()
|
||||
.build_type::<KeyboardEvent>()
|
||||
.build_type::<ScrollEvent>()
|
||||
// Bigger modules
|
||||
.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
|
||||
engine.register_fn("print", move |d: Dynamic| {
|
||||
debug!("{:?}", d);
|
||||
});
|
||||
engine.register_fn("clamp", move |x: f32, l: f32, h: f32| x.clamp(l, h));
|
||||
engine.register_fn("clamp", |x: f32, l: f32, h: f32| x.clamp(l, h));
|
||||
|
||||
// Modules
|
||||
engine.register_static_module("ui", functions::build_ui_module(state_src.clone()).into());
|
||||
engine.register_static_module(
|
||||
"sprite",
|
||||
functions::build_sprite_module(ct_src.clone(), state_src.clone()).into(),
|
||||
|
@ -56,7 +64,10 @@ pub fn register_into_engine(
|
|||
"radialbar",
|
||||
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(
|
||||
"conf",
|
||||
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::{
|
||||
data::{self},
|
||||
phys::{objects::PhysShip, PhysSimShipHandle},
|
||||
|
@ -19,14 +19,15 @@ pub struct ShipState {
|
|||
}
|
||||
|
||||
impl ShipState {
|
||||
// All functions passed to rhai MUST be mut,
|
||||
// even getters.
|
||||
fn get_content(&mut self) -> &Ship {
|
||||
let ship = self
|
||||
.input
|
||||
.phys_img
|
||||
.get_ship(self.ship.as_ref().unwrap())
|
||||
.unwrap();
|
||||
let handle = ship.ship.get_data().get_content();
|
||||
handle
|
||||
ship.ship.get_data().get_content()
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct SystemObjectState {
|
||||
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 {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("SystemObjectState")
|
||||
.with_fn("outfitter", Self::outfitter)
|
||||
//
|
||||
// Get landable name
|
||||
.with_fn("display_name", |s: &mut Self| {
|
||||
s.object
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.display_name
|
||||
.landable
|
||||
.as_ref()
|
||||
.map(|x| x.to_string())
|
||||
.map(|x| x.display_name.clone())
|
||||
.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()
|
||||
})
|
||||
})
|
||||
|
@ -147,25 +184,32 @@ impl CustomType for SystemObjectState {
|
|||
s.object
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.desc
|
||||
.landable
|
||||
.as_ref()
|
||||
.map(|x| x.to_string())
|
||||
.map(|x| x.desc.clone())
|
||||
.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()
|
||||
})
|
||||
})
|
||||
//
|
||||
// Get landable landscape image
|
||||
.with_fn("image", |s: &mut Self| {
|
||||
if let Some(sprite) = &s.object.as_ref().unwrap().image {
|
||||
sprite.index.to_string()
|
||||
} else {
|
||||
error!("UI called `image()` on a system object which doesn't provide one");
|
||||
"".to_string()
|
||||
}
|
||||
s.object
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.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()
|
||||
})
|
||||
})
|
||||
.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) {
|
||||
(None, _) => false,
|
||||
(_, None) => false,
|
||||
|
@ -240,7 +284,6 @@ impl CustomType for State {
|
|||
.with_fn("player_ship", Self::player_ship)
|
||||
.with_fn("ships", Self::ships)
|
||||
.with_fn("objects", Self::objects)
|
||||
.with_fn("window_aspect", |s: &mut Self| s.window_aspect)
|
||||
.with_fn("camera_zoom", |s: &mut Self| s.input.camera_zoom);
|
||||
.with_fn("window_aspect", |s: &mut Self| s.window_aspect);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 crate::{RenderInput, RenderState};
|
||||
|
||||
use super::OwnedTextArea;
|
||||
|
||||
pub(crate) struct FpsIndicator {
|
||||
buffer: Buffer,
|
||||
buffer: Rc<Buffer>,
|
||||
update_counter: u32,
|
||||
}
|
||||
|
||||
|
@ -21,7 +24,7 @@ impl FpsIndicator {
|
|||
);
|
||||
|
||||
Self {
|
||||
buffer,
|
||||
buffer: Rc::new(buffer),
|
||||
update_counter: 0,
|
||||
}
|
||||
}
|
||||
|
@ -29,26 +32,28 @@ impl FpsIndicator {
|
|||
|
||||
impl FpsIndicator {
|
||||
pub fn step(&mut self, input: &RenderInput, font: &mut FontSystem) {
|
||||
let buffer = Rc::get_mut(&mut self.buffer).unwrap();
|
||||
|
||||
if self.update_counter > 0 {
|
||||
self.update_counter -= 1;
|
||||
return;
|
||||
}
|
||||
self.update_counter = 100;
|
||||
|
||||
self.buffer.set_text(
|
||||
buffer.set_text(
|
||||
font,
|
||||
&input.timing.get_string(),
|
||||
Attrs::new().family(Family::Monospace),
|
||||
Shaping::Basic,
|
||||
);
|
||||
self.buffer.shape_until_scroll(font);
|
||||
buffer.shape_until_scroll(font);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> FpsIndicator {
|
||||
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> TextArea<'a> {
|
||||
TextArea {
|
||||
buffer: &self.buffer,
|
||||
pub fn get_textarea(&'b self, input: &RenderInput, _window: &Window) -> OwnedTextArea {
|
||||
OwnedTextArea {
|
||||
buffer: self.buffer.clone(),
|
||||
left: 10.0,
|
||||
top: 400.0,
|
||||
scale: input.ct.config.ui_scale,
|
||||
|
|
|
@ -1,9 +1,38 @@
|
|||
mod fpsindicator;
|
||||
mod radialbar;
|
||||
mod scrollbox;
|
||||
mod sprite;
|
||||
mod textbox;
|
||||
|
||||
pub(super) use fpsindicator::*;
|
||||
pub(super) use radialbar::*;
|
||||
pub(super) use scrollbox::*;
|
||||
pub(super) use sprite::*;
|
||||
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};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RadialBar {
|
||||
pub struct UiRadialBar {
|
||||
pub name: ImmutableString,
|
||||
rect: Rect,
|
||||
stroke: f32,
|
||||
|
@ -14,7 +14,7 @@ pub struct RadialBar {
|
|||
progress: f32,
|
||||
}
|
||||
|
||||
impl RadialBar {
|
||||
impl UiRadialBar {
|
||||
pub fn new(
|
||||
name: ImmutableString,
|
||||
stroke: f32,
|
||||
|
@ -48,6 +48,4 @@ impl RadialBar {
|
|||
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 crate::{
|
||||
ui::{api::Color, event::Event},
|
||||
ui::api::{Color, MouseClickEvent, MouseHoverEvent},
|
||||
vertexbuffer::types::UiInstance,
|
||||
RenderInput, RenderState,
|
||||
InputEvent, RenderInput, RenderState,
|
||||
};
|
||||
use galactica_content::{Sprite, SpriteAutomaton};
|
||||
use galactica_util::to_radians;
|
||||
use rhai::ImmutableString;
|
||||
use nalgebra::Vector2;
|
||||
use rhai::{Dynamic, ImmutableString};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UiSprite {
|
||||
|
@ -26,8 +27,6 @@ pub struct UiSprite {
|
|||
mask: Option<Arc<Sprite>>,
|
||||
color: Color,
|
||||
|
||||
/// If true, ignore mouse events until click is released
|
||||
waiting_for_release: bool,
|
||||
has_mouse: bool,
|
||||
has_click: bool,
|
||||
}
|
||||
|
@ -43,7 +42,6 @@ impl UiSprite {
|
|||
mask: None,
|
||||
has_mouse: false,
|
||||
has_click: false,
|
||||
waiting_for_release: false,
|
||||
preserve_aspect: false,
|
||||
}
|
||||
}
|
||||
|
@ -67,11 +65,23 @@ impl UiSprite {
|
|||
pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
|
||||
self.preserve_aspect = preserve_aspect;
|
||||
}
|
||||
}
|
||||
|
||||
impl UiSprite {
|
||||
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
|
||||
.rect
|
||||
.to_centered(&state.window, input.ct.config.ui_scale);
|
||||
rect.pos += offset;
|
||||
|
||||
if self.preserve_aspect {
|
||||
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
|
||||
.rect
|
||||
.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
|
||||
if self.has_click && !self.has_mouse {
|
||||
self.has_click = false;
|
||||
return Event::MouseRelease;
|
||||
}
|
||||
|
||||
if r.contains_mouse(input, state) && !self.has_mouse {
|
||||
if input.player.input.pressed_leftclick() {
|
||||
// If we're holding click when the cursor enters,
|
||||
// don't trigger the `Click` event.
|
||||
self.waiting_for_release = true;
|
||||
match event {
|
||||
InputEvent::MouseMove(pos) => {
|
||||
if r.contains_mouse(state, pos) && !self.has_mouse {
|
||||
self.has_mouse = true;
|
||||
return Some(Dynamic::from(MouseHoverEvent {
|
||||
enter: true,
|
||||
element: self.name.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
if !r.contains_mouse(state, pos) && self.has_mouse {
|
||||
self.has_mouse = false;
|
||||
return Some(Dynamic::from(MouseHoverEvent {
|
||||
enter: false,
|
||||
element: self.name.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
self.has_mouse = true;
|
||||
return Event::MouseHover;
|
||||
|
||||
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(),
|
||||
}));
|
||||
}
|
||||
|
||||
if self.has_mouse && self.has_click && !*pressed {
|
||||
self.has_click = false;
|
||||
return Some(Dynamic::from(MouseClickEvent {
|
||||
down: false,
|
||||
element: self.name.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
if !r.contains_mouse(input, state) && self.has_mouse {
|
||||
self.waiting_for_release = false;
|
||||
self.has_mouse = false;
|
||||
return Event::MouseUnhover;
|
||||
}
|
||||
|
||||
return Event::None;
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
||||
self.anim.step(input.time_since_last_run);
|
||||
pub fn step(&mut self, t: f32) {
|
||||
self.anim.step(t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
use glyphon::{
|
||||
cosmic_text::Align, Attrs, AttrsOwned, Buffer, Color, FamilyOwned, FontSystem, Metrics,
|
||||
Shaping, Style, TextArea, TextBounds, Weight,
|
||||
Shaping, Style, TextBounds, Weight,
|
||||
};
|
||||
use nalgebra::Vector2;
|
||||
use rhai::ImmutableString;
|
||||
use std::rc::Rc;
|
||||
use winit::window::Window;
|
||||
|
||||
use super::super::api::Rect;
|
||||
use super::{super::api::Rect, OwnedTextArea};
|
||||
use crate::{ui::api, RenderInput};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextBox {
|
||||
pub struct UiTextBox {
|
||||
pub name: ImmutableString,
|
||||
|
||||
text: String,
|
||||
justify: Align,
|
||||
rect: Rect,
|
||||
buffer: Buffer,
|
||||
buffer: Rc<Buffer>,
|
||||
color: api::Color,
|
||||
attrs: AttrsOwned,
|
||||
}
|
||||
|
||||
impl TextBox {
|
||||
impl UiTextBox {
|
||||
pub fn new(
|
||||
font: &mut FontSystem,
|
||||
name: ImmutableString,
|
||||
|
@ -38,7 +39,7 @@ impl TextBox {
|
|||
Self {
|
||||
name,
|
||||
rect,
|
||||
buffer,
|
||||
buffer: Rc::new(buffer),
|
||||
color,
|
||||
justify: Align::Left,
|
||||
attrs: AttrsOwned::new(Attrs::new()),
|
||||
|
@ -47,14 +48,14 @@ impl TextBox {
|
|||
}
|
||||
|
||||
fn reflow(&mut self, font: &mut FontSystem) {
|
||||
self.buffer
|
||||
.set_text(font, &self.text, self.attrs.as_attrs(), Shaping::Advanced);
|
||||
let buffer = Rc::get_mut(&mut self.buffer).unwrap();
|
||||
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));
|
||||
}
|
||||
|
||||
self.buffer.shape_until_scroll(font);
|
||||
buffer.shape_until_scroll(font);
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, font: &mut FontSystem, text: &str) {
|
||||
|
@ -84,9 +85,19 @@ impl TextBox {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> TextBox {
|
||||
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> {
|
||||
let rect = self.rect.to_centered(window, input.ct.config.ui_scale);
|
||||
impl<'a, 'b: 'a> UiTextBox {
|
||||
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> OwnedTextArea {
|
||||
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
|
||||
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 c = self.color.as_array_u8();
|
||||
|
||||
TextArea {
|
||||
buffer: &self.buffer,
|
||||
OwnedTextArea {
|
||||
buffer: self.buffer.clone(),
|
||||
top: corner_ne.y,
|
||||
left: corner_ne.x,
|
||||
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 galactica_content::Content;
|
||||
use galactica_system::phys::PhysSimShipHandle;
|
||||
use galactica_system::{phys::PhysSimShipHandle, PlayerDirective};
|
||||
use galactica_util::rhai_error_to_anyhow;
|
||||
use log::debug;
|
||||
use rhai::{
|
||||
packages::{BasicArrayPackage, BasicStringPackage, LogicPackage, MoreStringPackage, Package},
|
||||
Dynamic, Engine, ImmutableString, Scope,
|
||||
};
|
||||
use log::{debug, error};
|
||||
use rhai::{Dynamic, Engine, ImmutableString, Scope};
|
||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
use super::{
|
||||
api::{self, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent},
|
||||
event::Event,
|
||||
api::{self, KeyboardEvent, PlayerShipStateEvent, ScrollEvent},
|
||||
UiConfig, UiElement, UiState,
|
||||
};
|
||||
use crate::{ui::api::State, RenderInput, RenderState};
|
||||
use crate::{ui::api::State, InputEvent, RenderInput, RenderState};
|
||||
|
||||
pub(crate) struct UiScriptExecutor {
|
||||
engine: Engine,
|
||||
|
@ -31,14 +28,8 @@ impl UiScriptExecutor {
|
|||
let scope = Scope::new();
|
||||
let elements = Rc::new(RefCell::new(UiState::new(ct.clone(), state)));
|
||||
|
||||
let mut engine = Engine::new_raw();
|
||||
|
||||
// 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());
|
||||
// TODO: document all functions rhai provides
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.set_max_expr_depths(0, 0);
|
||||
// Enables custom operators
|
||||
|
@ -64,6 +55,129 @@ impl UiScriptExecutor {
|
|||
(*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
|
||||
pub fn set_scene(&mut self, state: &RenderState, input: Arc<RenderInput>) -> Result<()> {
|
||||
let current_scene = (*self.state).borrow().get_scene().clone();
|
||||
|
@ -87,8 +201,8 @@ impl UiScriptExecutor {
|
|||
let mut elm = self.state.borrow_mut();
|
||||
elm.clear();
|
||||
drop(elm);
|
||||
let ct = (*self.state).borrow().ct.clone();
|
||||
|
||||
let ct = (*self.state).borrow().ct.clone();
|
||||
rhai_error_to_anyhow(
|
||||
self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
|
@ -122,7 +236,6 @@ impl UiScriptExecutor {
|
|||
(*self.state).borrow_mut().step(state, input.clone());
|
||||
|
||||
// Run step() (if it is defined)
|
||||
|
||||
let ast = ct
|
||||
.config
|
||||
.ui_scenes
|
||||
|
@ -158,79 +271,25 @@ impl UiScriptExecutor {
|
|||
true
|
||||
}
|
||||
} {
|
||||
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()), PlayerShipStateEvent {}),
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling player state change event"))
|
||||
.with_context(|| format!("in ui scene `{}`", current_scene.as_ref().unwrap()))?;
|
||||
self.run_event_callback(state, input.clone(), Dynamic::from(PlayerShipStateEvent {}))?;
|
||||
}
|
||||
|
||||
let len = (*self.state).borrow().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) => {
|
||||
// Draw and update sprites
|
||||
sprite.step(&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) => {
|
||||
// Draw and update radialbar
|
||||
x.step(&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 {
|
||||
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()))?;
|
||||
UiElement::SubElement { .. } | UiElement::Text(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
mod api;
|
||||
mod event;
|
||||
mod camera;
|
||||
mod elements;
|
||||
mod executor;
|
||||
mod state;
|
||||
|
||||
mod elements;
|
||||
|
||||
pub(crate) use camera::*;
|
||||
pub(crate) use executor::UiScriptExecutor;
|
||||
pub(crate) use state::*;
|
||||
|
|
|
@ -1,19 +1,39 @@
|
|||
use galactica_content::Content;
|
||||
use glyphon::TextArea;
|
||||
use log::{debug, error};
|
||||
use rhai::ImmutableString;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, time::Instant};
|
||||
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};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UiElement {
|
||||
Sprite(UiSprite),
|
||||
RadialBar(RadialBar),
|
||||
Text(TextBox),
|
||||
RadialBar(UiRadialBar),
|
||||
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)]
|
||||
|
@ -31,8 +51,13 @@ pub(crate) struct UiState {
|
|||
|
||||
show_timings: bool,
|
||||
fps_indicator: FpsIndicator,
|
||||
last_step: Instant,
|
||||
|
||||
pub config: UiConfig,
|
||||
|
||||
/// The player's camera.
|
||||
/// Only used when drawing physics.
|
||||
pub camera: Camera,
|
||||
}
|
||||
// TODO: remove this
|
||||
unsafe impl Send for UiState {}
|
||||
|
@ -52,6 +77,8 @@ impl UiState {
|
|||
show_phys: 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>) {
|
||||
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 {
|
||||
self.fps_indicator
|
||||
.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 {
|
||||
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);
|
||||
|
||||
if self.current_scene.is_none() {
|
||||
|
@ -124,9 +193,10 @@ impl<'a> UiState {
|
|||
v.push(self.fps_indicator.get_textarea(input, window))
|
||||
}
|
||||
|
||||
for t in self.elements.values() {
|
||||
match &t {
|
||||
UiElement::Text(x) => v.push(x.get_textarea(input, window)),
|
||||
for e in self.elements.values() {
|
||||
match &e {
|
||||
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