mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
added text box ui widget
This commit is contained in:
Generated
+21
-17
@@ -7,23 +7,27 @@
|
||||
<list default="true" id="1932b2c4-d6ff-413c-9558-19959667326b" name="Default Changelist" comment="scene graph">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/renderer/deferred_light_renderer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/renderer/deferred_light_renderer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/renderer/gbuffer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/renderer/gbuffer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/renderer/particle_system_renderer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/renderer/particle_system_renderer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/renderer/shadow_map_renderer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/renderer/shadow_map_renderer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/renderer/sprite_renderer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/renderer/sprite_renderer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/resource/fbx/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/resource/fbx/mod.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/resource/model.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/resource/model.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/animation.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/animation.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/camera.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/camera.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/graph.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/graph.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/light.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/light.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/mesh.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/mesh.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/mod.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/node.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/node.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/particle_system.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/particle_system.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/pivot.rs" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/sprite.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/sprite.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/engine/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/engine/mod.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/border.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/border.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/button.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/button.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/canvas.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/canvas.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/draw.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/draw.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/event.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/event.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/formatted_text.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/formatted_text.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/grid.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/grid.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/image.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/image.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/list_box.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/list_box.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/mod.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/node.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/node.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/scroll_bar.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/scroll_bar.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/scroll_content_presenter.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/scroll_content_presenter.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/scroll_viewer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/scroll_viewer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/stack_panel.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/stack_panel.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/text.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/text.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/widget.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/widget.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/window.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/window.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/resource/ttf.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/resource/ttf.rs" afterDir="false" />
|
||||
</list>
|
||||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
|
||||
Generated
+1
-1
@@ -883,7 +883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rg3d"
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glutin 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rg3d"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Dmitry Stepanov <[email protected]>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ impl Engine {
|
||||
|
||||
self.sound_context.lock().unwrap().update().unwrap();
|
||||
|
||||
self.user_interface.update(Vec2::new(client_size.width as f32, client_size.height as f32));
|
||||
self.user_interface.update(Vec2::new(client_size.width as f32, client_size.height as f32), dt);
|
||||
}
|
||||
|
||||
pub fn get_ui_mut(&mut self) -> &mut UserInterface {
|
||||
|
||||
+11
-1
@@ -7,7 +7,10 @@ use rg3d_core::{
|
||||
},
|
||||
};
|
||||
use crate::gui::{
|
||||
draw::{CommandKind, DrawingContext},
|
||||
draw::{
|
||||
CommandKind,
|
||||
DrawingContext
|
||||
},
|
||||
Thickness,
|
||||
UserInterface,
|
||||
Layout,
|
||||
@@ -19,6 +22,7 @@ use crate::gui::{
|
||||
AsWidget
|
||||
},
|
||||
draw::CommandTexture,
|
||||
Update,
|
||||
};
|
||||
|
||||
pub struct Border {
|
||||
@@ -37,6 +41,12 @@ impl AsWidget for Border {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Border {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BorderBuilder {
|
||||
widget_builder: WidgetBuilder,
|
||||
stroke_thickness: Option<Thickness>,
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::gui::{
|
||||
Layout,
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update,
|
||||
};
|
||||
use rg3d_core::{
|
||||
color::Color,
|
||||
@@ -57,6 +58,12 @@ impl Draw for Button {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Button {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ButtonContent {
|
||||
Text(String),
|
||||
Node(Handle<UINode>),
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::gui::{
|
||||
UserInterface,
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +49,12 @@ impl Default for Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Canvas {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Canvas {
|
||||
fn measure_override(&self, ui: &UserInterface, _available_size: Vec2) -> Vec2 {
|
||||
let size_for_child = Vec2::new(
|
||||
|
||||
+8
-1
@@ -313,6 +313,13 @@ impl DrawingContext {
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, position: Vec2, formatted_text: &FormattedText) {
|
||||
let font = if let Some(font) = formatted_text.get_font() {
|
||||
font
|
||||
} else {
|
||||
println!("Trying to draw text without font!");
|
||||
return;
|
||||
};
|
||||
|
||||
for element in formatted_text.get_glyphs() {
|
||||
let bounds = element.get_bounds();
|
||||
|
||||
@@ -322,7 +329,7 @@ impl DrawingContext {
|
||||
|
||||
self.push_rect_filled(&final_bounds, Some(element.get_tex_coords()), element.get_color());
|
||||
}
|
||||
self.commit(CommandKind::Geometry, CommandTexture::Font(formatted_text.get_font()))
|
||||
self.commit(CommandKind::Geometry, CommandTexture::Font(font))
|
||||
}
|
||||
|
||||
pub fn commit_clip_rect(&mut self, clip_rect: &Rect<f32>) {
|
||||
|
||||
@@ -67,8 +67,11 @@ pub enum UIEventKind {
|
||||
},
|
||||
|
||||
MaxValueChanged(f32),
|
||||
|
||||
MinValueChanged(f32),
|
||||
|
||||
SelectionChanged(Option<usize>),
|
||||
|
||||
/// Any kind of user-defined event.
|
||||
User(Box<dyn Any>),
|
||||
}
|
||||
|
||||
+125
-72
@@ -1,7 +1,9 @@
|
||||
use rg3d_core::{
|
||||
color::Color,
|
||||
math::vec2::Vec2,
|
||||
math::Rect
|
||||
math::{
|
||||
vec2::Vec2,
|
||||
Rect
|
||||
}
|
||||
};
|
||||
use std::{
|
||||
rc::Rc,
|
||||
@@ -11,6 +13,7 @@ use crate::{
|
||||
resource::ttf::Font,
|
||||
gui::{HorizontalAlignment, VerticalAlignment}
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextGlyph {
|
||||
@@ -34,11 +37,19 @@ impl TextGlyph {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct TextLine {
|
||||
begin: usize,
|
||||
end: usize,
|
||||
width: f32,
|
||||
x_offset: f32,
|
||||
pub struct TextLine {
|
||||
/// Index of starting symbol in text array.
|
||||
pub begin: usize,
|
||||
/// Index of ending symbol in text array.
|
||||
pub end: usize,
|
||||
/// Total width of line.
|
||||
pub width: f32,
|
||||
/// Total height of line. Usually just ascender of a font.
|
||||
pub height: f32,
|
||||
/// Local horizontal position of line.
|
||||
pub x_offset: f32,
|
||||
/// Local vertical position of line.
|
||||
pub y_offset: f32,
|
||||
}
|
||||
|
||||
impl TextLine {
|
||||
@@ -47,13 +58,19 @@ impl TextLine {
|
||||
begin: 0,
|
||||
end: 0,
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
x_offset: 0.0,
|
||||
y_offset: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.end - self.begin
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FormattedText {
|
||||
font: Rc<RefCell<Font>>,
|
||||
font: Option<Rc<RefCell<Font>>>,
|
||||
/// Text in UTF32 format.
|
||||
text: Vec<u32>,
|
||||
/// Temporary buffer used to split text on lines. We need it to reduce memory allocations
|
||||
@@ -62,35 +79,87 @@ pub struct FormattedText {
|
||||
lines: Vec<TextLine>,
|
||||
/// Final glyphs for draw buffer.
|
||||
glyphs: Vec<TextGlyph>,
|
||||
vertical_alignment: VerticalAlignment,
|
||||
horizontal_alignment: HorizontalAlignment,
|
||||
color: Color,
|
||||
size: Vec2,
|
||||
}
|
||||
|
||||
impl FormattedText {
|
||||
fn new(font: Rc<RefCell<Font>>) -> FormattedText {
|
||||
FormattedText {
|
||||
text: Vec::new(),
|
||||
font,
|
||||
glyphs: Vec::new(),
|
||||
lines: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_glyphs(&self) -> &[TextGlyph] {
|
||||
&self.glyphs
|
||||
}
|
||||
|
||||
pub fn get_font(&self) -> Rc<RefCell<Font>> {
|
||||
pub fn get_font(&self) -> Option<Rc<RefCell<Font>>> {
|
||||
self.font.clone()
|
||||
}
|
||||
|
||||
fn build(&mut self, text: &str, size: Vec2, color: Color, vertical_alignment: VerticalAlignment,
|
||||
horizontal_alignment: HorizontalAlignment) {
|
||||
pub fn set_font(&mut self, font: Rc<RefCell<Font>>) {
|
||||
self.font = Some(font);
|
||||
}
|
||||
|
||||
pub fn get_lines(&self) -> &[TextLine] {
|
||||
&self.lines
|
||||
}
|
||||
|
||||
pub fn set_vertical_alignment(&mut self, vertical_alignment: VerticalAlignment ) {
|
||||
self.vertical_alignment = vertical_alignment
|
||||
}
|
||||
|
||||
pub fn set_horizontal_alignment(&mut self, horizontal_alignment: HorizontalAlignment ) {
|
||||
self.horizontal_alignment = horizontal_alignment;
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, color: Color) {
|
||||
self.color = color;
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, size: Vec2) {
|
||||
self.size = size;
|
||||
}
|
||||
|
||||
pub fn get_raw_text(&self) -> &[u32] {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn get_range_width(&self, range: Range<usize>) -> f32 {
|
||||
let mut width = 0.0;
|
||||
if let Some(ref font) = self.font {
|
||||
let font = font.borrow();
|
||||
for index in range {
|
||||
width += font.get_glyph_advance(self.text[index]);
|
||||
}
|
||||
}
|
||||
width
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
// Convert text to UTF32.
|
||||
self.text.clear();
|
||||
for code in text.chars().map(|c| c as u32) {
|
||||
self.text.push(code);
|
||||
}
|
||||
}
|
||||
|
||||
let font = self.font.borrow();
|
||||
pub fn insert_char(&mut self, c: char, index: usize) {
|
||||
let c = c as u32;
|
||||
if index == self.text.len() {
|
||||
self.text.push(c);
|
||||
} else {
|
||||
self.text.insert(index, c);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_at(&mut self, index: usize) {
|
||||
self.text.remove(index);
|
||||
}
|
||||
|
||||
pub fn build(&mut self) {
|
||||
let font = if let Some(font) = &self.font {
|
||||
font.borrow()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Split on lines.
|
||||
let mut total_height = 0.0;
|
||||
@@ -104,7 +173,7 @@ impl FormattedText {
|
||||
};
|
||||
let is_new_line = *code == u32::from(b'\n') || *code == u32::from(b'\r');
|
||||
let new_width = current_line.width + advance;
|
||||
if new_width > size.x || is_new_line {
|
||||
if new_width > self.size.x || is_new_line {
|
||||
self.lines.push(current_line);
|
||||
current_line.begin = if is_new_line { i + 1 } else { i };
|
||||
current_line.end = current_line.begin + 1;
|
||||
@@ -124,10 +193,10 @@ impl FormattedText {
|
||||
|
||||
// Align lines according to desired alignment.
|
||||
for line in self.lines.iter_mut() {
|
||||
match horizontal_alignment {
|
||||
match self.horizontal_alignment {
|
||||
HorizontalAlignment::Left => line.x_offset = 0.0,
|
||||
HorizontalAlignment::Center => line.x_offset = 0.5 * (size.x - line.width),
|
||||
HorizontalAlignment::Right => line.x_offset = size.x - line.width,
|
||||
HorizontalAlignment::Center => line.x_offset = 0.5 * (self.size.x - line.width),
|
||||
HorizontalAlignment::Right => line.x_offset = self.size.x - line.width,
|
||||
HorizontalAlignment::Stretch => line.x_offset = 0.0
|
||||
}
|
||||
}
|
||||
@@ -135,15 +204,15 @@ impl FormattedText {
|
||||
// Generate glyphs for each text line.
|
||||
self.glyphs.clear();
|
||||
|
||||
let cursor_y_start = match vertical_alignment {
|
||||
let cursor_y_start = match self.vertical_alignment {
|
||||
VerticalAlignment::Top => 0.0,
|
||||
VerticalAlignment::Center => (size.y - total_height) * 0.5,
|
||||
VerticalAlignment::Bottom => size.y - total_height,
|
||||
VerticalAlignment::Center => (self.size.y - total_height) * 0.5,
|
||||
VerticalAlignment::Bottom => self.size.y - total_height,
|
||||
VerticalAlignment::Stretch => 0.0
|
||||
};
|
||||
|
||||
let mut cursor = Vec2::new(size.x, cursor_y_start);
|
||||
for line in self.lines.iter() {
|
||||
let mut cursor = Vec2::new(self.size.x, cursor_y_start);
|
||||
for line in self.lines.iter_mut() {
|
||||
cursor.x = line.x_offset;
|
||||
|
||||
for code_index in line.begin..line.end {
|
||||
@@ -162,7 +231,7 @@ impl FormattedText {
|
||||
let text_glyph = TextGlyph {
|
||||
bounds: rect,
|
||||
tex_coords: *glyph.get_tex_coords(),
|
||||
color,
|
||||
color: self.color,
|
||||
};
|
||||
self.glyphs.push(text_glyph);
|
||||
}
|
||||
@@ -179,33 +248,34 @@ impl FormattedText {
|
||||
self.glyphs.push(TextGlyph {
|
||||
bounds: rect,
|
||||
tex_coords: [Vec2::ZERO; 4],
|
||||
color,
|
||||
color: self.color,
|
||||
});
|
||||
cursor.x += rect.w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line.height = font.get_ascender();
|
||||
line.y_offset = cursor.y;
|
||||
cursor.y += font.get_ascender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FormattedTextBuilder<'a> {
|
||||
pub struct FormattedTextBuilder {
|
||||
font: Option<Rc<RefCell<Font>>>,
|
||||
color: Color,
|
||||
size: Vec2,
|
||||
text: Option<&'a str>,
|
||||
formatted_text: FormattedText,
|
||||
text: String,
|
||||
vertical_alignment: VerticalAlignment,
|
||||
horizontal_alignment: HorizontalAlignment,
|
||||
}
|
||||
|
||||
impl<'a> FormattedTextBuilder<'a> {
|
||||
impl FormattedTextBuilder {
|
||||
/// Creates new formatted text builder with default parameters.
|
||||
pub fn new(font: Rc<RefCell<Font>>) -> FormattedTextBuilder<'a> {
|
||||
pub fn new() -> FormattedTextBuilder {
|
||||
FormattedTextBuilder {
|
||||
text: None,
|
||||
formatted_text: FormattedText::new(font),
|
||||
font: None,
|
||||
text: "".to_owned(),
|
||||
horizontal_alignment: HorizontalAlignment::Left,
|
||||
vertical_alignment: VerticalAlignment::Top,
|
||||
color: Color::WHITE,
|
||||
@@ -213,25 +283,9 @@ impl<'a> FormattedTextBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new formatted text builder that will reuse existing
|
||||
/// buffers from existing formatted text. This is very useful to
|
||||
/// reduce memory allocations.
|
||||
pub fn reuse(formatted_text: FormattedText) -> FormattedTextBuilder<'a> {
|
||||
FormattedTextBuilder {
|
||||
text: None,
|
||||
formatted_text: FormattedText {
|
||||
// Take buffers out and reuse them so no need to allocate new
|
||||
// buffers every time when need to change a text.
|
||||
text: formatted_text.text,
|
||||
lines: formatted_text.lines,
|
||||
glyphs: formatted_text.glyphs,
|
||||
font: formatted_text.font,
|
||||
},
|
||||
horizontal_alignment: HorizontalAlignment::Left,
|
||||
vertical_alignment: VerticalAlignment::Top,
|
||||
color: Color::WHITE,
|
||||
size: Vec2::new(128.0, 128.0),
|
||||
}
|
||||
pub fn with_font(mut self, font: Rc<RefCell<Font>>) -> Self {
|
||||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_vertical_alignment(mut self, vertical_alignment: VerticalAlignment) -> Self {
|
||||
@@ -244,8 +298,8 @@ impl<'a> FormattedTextBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_text(mut self, text: &'a str) -> Self {
|
||||
self.text = Some(text);
|
||||
pub fn with_text(mut self, text: String) -> Self {
|
||||
self.text = text;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -259,17 +313,16 @@ impl<'a> FormattedTextBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> FormattedText {
|
||||
if let Some(text) = self.text {
|
||||
self.formatted_text.build(
|
||||
text,
|
||||
self.size,
|
||||
self.color,
|
||||
self.vertical_alignment,
|
||||
self.horizontal_alignment,
|
||||
);
|
||||
pub fn build(self) -> FormattedText {
|
||||
FormattedText {
|
||||
font: self.font,
|
||||
text: self.text.chars().map(|c| c as u32).collect(),
|
||||
lines: Vec::new(),
|
||||
glyphs: Vec::new(),
|
||||
vertical_alignment: self.vertical_alignment,
|
||||
horizontal_alignment: self.horizontal_alignment,
|
||||
color: self.color,
|
||||
size: Vec2::new(120.0, 120.0)
|
||||
}
|
||||
|
||||
self.formatted_text
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use crate::gui::{
|
||||
UserInterface,
|
||||
draw::DrawingContext,
|
||||
widget::{WidgetBuilder, Widget, AsWidget},
|
||||
Update
|
||||
};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -136,6 +137,12 @@ impl Draw for Grid {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Grid {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Grid {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
// In case of no rows or columns, grid acts like default panel.
|
||||
|
||||
+8
-1
@@ -6,7 +6,8 @@ use crate::{
|
||||
draw::{DrawingContext, CommandKind, CommandTexture},
|
||||
widget::{Widget, AsWidget},
|
||||
Layout,
|
||||
UserInterface
|
||||
UserInterface,
|
||||
Update
|
||||
}
|
||||
};
|
||||
use rg3d_core::math::vec2::Vec2;
|
||||
@@ -42,4 +43,10 @@ impl Layout for Image {
|
||||
fn arrange_override(&self, ui: &UserInterface, final_size: Vec2) -> Vec2 {
|
||||
self.widget.arrange_override(ui, final_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Image {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
+80
-4
@@ -14,15 +14,39 @@ use crate::gui::{
|
||||
Layout,
|
||||
draw::DrawingContext,
|
||||
stack_panel::StackPanelBuilder,
|
||||
event::{
|
||||
UIEventKind,
|
||||
UIEvent,
|
||||
},
|
||||
Update
|
||||
};
|
||||
|
||||
pub struct ListBox {
|
||||
widget: Widget,
|
||||
panel: Handle<UINode>,
|
||||
selected_index: Option<usize>,
|
||||
items: Vec<Handle<UINode>>,
|
||||
}
|
||||
|
||||
impl ListBox {}
|
||||
impl ListBox {
|
||||
pub fn set_selected(&mut self, new_index: Option<usize>) {
|
||||
let old_value = self.selected_index;
|
||||
|
||||
self.selected_index = new_index;
|
||||
|
||||
if old_value.is_none() && new_index.is_some() ||
|
||||
old_value.is_some() && new_index.is_none() ||
|
||||
old_value.unwrap() != new_index.unwrap() {
|
||||
self.widget.events
|
||||
.borrow_mut()
|
||||
.push_back(UIEvent::new(UIEventKind::SelectionChanged(self.selected_index)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_selected(&self) -> Option<usize> {
|
||||
self.selected_index
|
||||
}
|
||||
}
|
||||
|
||||
impl AsWidget for ListBox {
|
||||
fn widget(&self) -> &Widget {
|
||||
@@ -40,6 +64,12 @@ impl Draw for ListBox {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for ListBox {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for ListBox {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
self.widget.measure_override(ui, available_size)
|
||||
@@ -52,7 +82,7 @@ impl Layout for ListBox {
|
||||
|
||||
pub struct ListBoxBuilder {
|
||||
widget_builder: WidgetBuilder,
|
||||
items: Vec<Handle<UINode>>
|
||||
items: Vec<Handle<UINode>>,
|
||||
}
|
||||
|
||||
impl ListBoxBuilder {
|
||||
@@ -69,8 +99,54 @@ impl ListBoxBuilder {
|
||||
}
|
||||
|
||||
pub fn build(self, ui: &mut UserInterface) -> Handle<UINode> {
|
||||
// Wrap each item into container which will have selection behaviour
|
||||
let items: Vec<Handle<UINode>> = self.items.iter().enumerate().map(|(index, item)| {
|
||||
BorderBuilder::new(WidgetBuilder::new()
|
||||
.with_child(*item)
|
||||
.with_event_handler(Box::new(move |ui, handle, evt| {
|
||||
let list_box = ui.find_by_criteria_up(handle, |node| node.is_list_box());
|
||||
if evt.source == handle || ui.is_node_child_of(evt.source, handle) {
|
||||
let border = ui.get_node_mut(handle).as_border_mut();
|
||||
match evt.kind {
|
||||
UIEventKind::MouseLeave => {
|
||||
border.widget_mut().set_color(Color::opaque(100, 100, 100));
|
||||
}
|
||||
UIEventKind::MouseEnter => {
|
||||
border.widget_mut().set_color(Color::opaque(130, 130, 130));
|
||||
}
|
||||
UIEventKind::MouseDown { .. } => {
|
||||
// Explicitly set selection on parent list box. This will send
|
||||
// SelectionChanged event and all items will react.
|
||||
let list_box = ui.get_node_mut(list_box).as_list_box_mut();
|
||||
list_box.set_selected(Some(index))
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
} else if evt.source == list_box {
|
||||
let border = ui.get_node_mut(handle).as_border_mut();
|
||||
if let UIEventKind::SelectionChanged(new_value) = evt.kind {
|
||||
// We know now that selection has changed in parent list box,
|
||||
// check at which index and keep visual state according to it.
|
||||
if let Some(new_value) = new_value {
|
||||
if new_value == index {
|
||||
border.set_stroke_color(Color::opaque(0, 0, 0));
|
||||
border.set_stroke_thickness(Thickness::uniform(2.0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
border.set_stroke_color(Color::opaque(80, 80, 80));
|
||||
border.set_stroke_thickness(Thickness::uniform(1.0));
|
||||
}
|
||||
}
|
||||
}))
|
||||
.with_color(Color::opaque(80, 80, 80)))
|
||||
.with_stroke_color(Color::opaque(60, 60, 60))
|
||||
.with_stroke_thickness(Thickness::uniform(1.0))
|
||||
.build(ui)
|
||||
}).collect();
|
||||
|
||||
let panel = StackPanelBuilder::new(WidgetBuilder::new()
|
||||
.with_children(self.items))
|
||||
.with_children(&items))
|
||||
.build(ui);
|
||||
|
||||
let scroll_viewer = ScrollViewerBuilder::new(WidgetBuilder::new()
|
||||
@@ -80,7 +156,6 @@ impl ListBoxBuilder {
|
||||
|
||||
let list_box = UINode::ListBox(ListBox {
|
||||
widget: self.widget_builder
|
||||
.with_event_handler(Box::new(|ui, handle, evt| {}))
|
||||
.with_child(BorderBuilder::new(WidgetBuilder::new()
|
||||
.with_color(Color::opaque(100, 100, 100))
|
||||
.with_child(scroll_viewer))
|
||||
@@ -88,6 +163,7 @@ impl ListBoxBuilder {
|
||||
.build(),
|
||||
selected_index: None,
|
||||
panel,
|
||||
items,
|
||||
});
|
||||
|
||||
ui.add_node(list_box)
|
||||
|
||||
+78
-7
@@ -15,6 +15,7 @@ pub mod formatted_text;
|
||||
pub mod widget;
|
||||
pub mod list_box;
|
||||
pub mod stack_panel;
|
||||
pub mod text_box;
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
@@ -27,11 +28,16 @@ use crate::{
|
||||
widget::AsWidget,
|
||||
draw::{DrawingContext, CommandKind, CommandTexture},
|
||||
canvas::Canvas,
|
||||
event::{UIEvent, UIEventKind},
|
||||
event::{
|
||||
UIEvent,
|
||||
UIEventKind,
|
||||
UIEventHandler
|
||||
}
|
||||
},
|
||||
resource::{ttf::Font},
|
||||
utils::UnsafeCollectionView,
|
||||
ElementState, WindowEvent,
|
||||
ElementState,
|
||||
WindowEvent,
|
||||
MouseScrollDelta,
|
||||
};
|
||||
use rg3d_core::{
|
||||
@@ -39,7 +45,6 @@ use rg3d_core::{
|
||||
pool::{Pool, Handle},
|
||||
math::{vec2::Vec2, Rect},
|
||||
};
|
||||
use crate::gui::event::UIEventHandler;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum HorizontalAlignment {
|
||||
@@ -103,6 +108,10 @@ trait Draw {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext);
|
||||
}
|
||||
|
||||
trait Update {
|
||||
fn update(&mut self, dt: f32);
|
||||
}
|
||||
|
||||
pub struct UserInterface {
|
||||
nodes: Pool<UINode>,
|
||||
drawing_context: DrawingContext,
|
||||
@@ -113,9 +122,10 @@ pub struct UserInterface {
|
||||
picked_node: Handle<UINode>,
|
||||
prev_picked_node: Handle<UINode>,
|
||||
captured_node: Handle<UINode>,
|
||||
keyboard_focus_node: Handle<UINode>,
|
||||
mouse_position: Vec2,
|
||||
events: VecDeque<UIEvent>,
|
||||
event_handlers: Vec<Option<Box<UIEventHandler>>>
|
||||
event_handlers: Vec<Option<Box<UIEventHandler>>>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -152,7 +162,8 @@ impl UserInterface {
|
||||
drawing_context: DrawingContext::new(),
|
||||
picked_node: Handle::NONE,
|
||||
prev_picked_node: Handle::NONE,
|
||||
event_handlers: Vec::new()
|
||||
event_handlers: Vec::new(),
|
||||
keyboard_focus_node: Handle::NONE,
|
||||
};
|
||||
ui.root_canvas = ui.add_node(UINode::Canvas(Canvas::new()));
|
||||
ui
|
||||
@@ -355,7 +366,7 @@ impl UserInterface {
|
||||
size.y = widget.height.get();
|
||||
}
|
||||
|
||||
size = node.arrange_override( self, size);
|
||||
size = node.arrange_override(self, size);
|
||||
|
||||
if size.x > final_rect.w {
|
||||
size.x = final_rect.w;
|
||||
@@ -407,10 +418,13 @@ impl UserInterface {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, screen_size: Vec2) {
|
||||
pub fn update(&mut self, screen_size: Vec2, dt: f32) {
|
||||
self.measure(self.root_canvas, screen_size);
|
||||
self.arrange(self.root_canvas, &Rect::new(0.0, 0.0, screen_size.x, screen_size.y));
|
||||
self.update_transform(self.root_canvas);
|
||||
for node in self.nodes.iter_mut() {
|
||||
node.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_node(&mut self, node_handle: Handle<UINode>, nesting: u8) {
|
||||
@@ -582,6 +596,8 @@ impl UserInterface {
|
||||
self.find_by_criteria_up(node.widget().parent, func)
|
||||
}
|
||||
|
||||
/// Checks if specified node is a child of some other node on `root_handle`. This method
|
||||
/// is useful to understand if some event came from some node down by tree.
|
||||
pub fn is_node_child_of(&self, node_handle: Handle<UINode>, root_handle: Handle<UINode>) -> bool {
|
||||
for child_handle in self.nodes.borrow(root_handle).widget().children.iter() {
|
||||
if *child_handle == node_handle {
|
||||
@@ -596,6 +612,16 @@ impl UserInterface {
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks if specified node is a direct child of some other node on `root_handle`.
|
||||
pub fn is_node_direct_child_of(&self, node_handle: Handle<UINode>, root_handle: Handle<UINode>) -> bool {
|
||||
for child_handle in self.nodes.borrow(root_handle).widget().children.iter() {
|
||||
if *child_handle == node_handle {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Searches a node by name up on tree starting from given root node.
|
||||
pub fn find_by_name_up(&self, node_handle: Handle<UINode>, name: &str) -> Handle<UINode> {
|
||||
self.find_by_criteria_up(node_handle, |node| node.widget().name == name)
|
||||
@@ -692,6 +718,8 @@ impl UserInterface {
|
||||
ElementState::Pressed => {
|
||||
self.picked_node = self.hit_test(self.mouse_position);
|
||||
|
||||
self.keyboard_focus_node = self.picked_node;
|
||||
|
||||
if !self.picked_node.is_none() {
|
||||
self.events.push_back(UIEvent {
|
||||
handled: false,
|
||||
@@ -793,6 +821,49 @@ impl UserInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput { input, .. } => {
|
||||
if self.keyboard_focus_node.is_some() {
|
||||
if let Some(keycode) = input.virtual_keycode {
|
||||
let event = UIEvent {
|
||||
handled: false,
|
||||
kind: match input.state {
|
||||
ElementState::Pressed => {
|
||||
UIEventKind::KeyDown {
|
||||
code: keycode,
|
||||
}
|
||||
}
|
||||
ElementState::Released => {
|
||||
UIEventKind::KeyUp {
|
||||
code: keycode,
|
||||
}
|
||||
}
|
||||
},
|
||||
target: Handle::NONE,
|
||||
source: self.keyboard_focus_node,
|
||||
};
|
||||
|
||||
self.events.push_back(event);
|
||||
|
||||
event_processed = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
WindowEvent::ReceivedCharacter(unicode) => {
|
||||
if self.keyboard_focus_node.is_some() {
|
||||
let event = UIEvent {
|
||||
handled: false,
|
||||
kind: UIEventKind::Text {
|
||||
symbol: *unicode
|
||||
},
|
||||
target: Handle::NONE,
|
||||
source: self.keyboard_focus_node
|
||||
};
|
||||
|
||||
self.events.push_back(event);
|
||||
|
||||
event_processed = true;
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
|
||||
|
||||
+13
-20
@@ -1,23 +1,5 @@
|
||||
use rg3d_core::math::vec2::Vec2;
|
||||
use crate::gui::{
|
||||
draw::DrawingContext,
|
||||
button::Button,
|
||||
text::Text,
|
||||
border::Border,
|
||||
scroll_bar::ScrollBar,
|
||||
scroll_viewer::ScrollViewer,
|
||||
image::Image,
|
||||
grid::Grid,
|
||||
scroll_content_presenter::ScrollContentPresenter,
|
||||
window::Window,
|
||||
Draw,
|
||||
Layout,
|
||||
UserInterface,
|
||||
canvas::Canvas,
|
||||
widget::{Widget, AsWidget},
|
||||
};
|
||||
use crate::gui::list_box::ListBox;
|
||||
use crate::gui::stack_panel::StackPanel;
|
||||
use crate::gui::{draw::DrawingContext, button::Button, text::Text, border::Border, scroll_bar::ScrollBar, scroll_viewer::ScrollViewer, image::Image, grid::Grid, scroll_content_presenter::ScrollContentPresenter, window::Window, Draw, Layout, UserInterface, canvas::Canvas, widget::{Widget, AsWidget}, list_box::ListBox, stack_panel::StackPanel, text_box::TextBox, Update};
|
||||
|
||||
/// UI node is a building block for all UI widgets. For example button could be a node with
|
||||
/// this structure
|
||||
@@ -50,7 +32,8 @@ pub enum UINode {
|
||||
ScrollContentPresenter(ScrollContentPresenter),
|
||||
Window(Window),
|
||||
ListBox(ListBox),
|
||||
StackPanel(StackPanel)
|
||||
StackPanel(StackPanel),
|
||||
TextBox(TextBox),
|
||||
}
|
||||
|
||||
macro_rules! dispatch {
|
||||
@@ -69,6 +52,7 @@ macro_rules! dispatch {
|
||||
UINode::Window(v) => v.$func($($args),*),
|
||||
UINode::ListBox(v) => v.$func($($args),*),
|
||||
UINode::StackPanel(v) => v.$func($($args),*),
|
||||
UINode::TextBox(v) => v.$func($($args),*),
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -79,6 +63,12 @@ impl Draw for UINode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for UINode {
|
||||
fn update(&mut self, dt: f32) {
|
||||
dispatch!(self, update, dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for UINode {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
dispatch!(self, measure_override, ui, available_size)
|
||||
@@ -135,4 +125,7 @@ impl UINode {
|
||||
define_is_as!(is_scroll_content_presenter, as_scroll_content_presenter,
|
||||
as_scroll_content_presenter_mut, ScrollContentPresenter, ScrollContentPresenter);
|
||||
define_is_as!(is_window, as_window, as_window_mut, Window, Window);
|
||||
define_is_as!(is_list_box, as_list_box, as_list_box_mut, ListBox, ListBox);
|
||||
define_is_as!(is_stack_panel, as_stack_panel, as_stack_panel_mut, StackPanel, StackPanel);
|
||||
define_is_as!(is_text_box, as_text_box, as_text_box_mut, TextBox, TextBox);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use crate::gui::{
|
||||
widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update
|
||||
};
|
||||
use rg3d_core::{
|
||||
color::Color, math,
|
||||
@@ -55,6 +56,12 @@ impl Draw for ScrollBar {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for ScrollBar {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollBar {
|
||||
pub const PART_CANVAS: &'static str = "PART_Canvas";
|
||||
|
||||
@@ -106,6 +113,18 @@ impl ScrollBar {
|
||||
pub fn get_min_value(&self) -> f32 {
|
||||
self.min
|
||||
}
|
||||
|
||||
pub fn scroll(&mut self, amount: f32) {
|
||||
let old_value = self.value;
|
||||
|
||||
self.value += amount;
|
||||
|
||||
self.widget.events.borrow_mut().push_back(
|
||||
UIEvent::new(UIEventKind::NumericValueChanged {
|
||||
old_value,
|
||||
new_value: self.value,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for ScrollBar {
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::gui::{
|
||||
AsWidget,
|
||||
},
|
||||
UserInterface, Layout, draw::DrawingContext,
|
||||
Update
|
||||
};
|
||||
|
||||
/// Allows user to scroll content
|
||||
@@ -39,6 +40,12 @@ impl Draw for ScrollContentPresenter {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for ScrollContentPresenter {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for ScrollContentPresenter {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
let size_for_child = Vec2::new(
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
use crate::gui::{
|
||||
UserInterface, maxf,
|
||||
UserInterface,
|
||||
maxf,
|
||||
node::UINode,
|
||||
scroll_content_presenter::ScrollContentPresenterBuilder,
|
||||
scroll_bar::{ScrollBarBuilder, Orientation},
|
||||
grid::{Row, GridBuilder, Column},
|
||||
event::UIEventKind, Layout, widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Draw, draw::DrawingContext,
|
||||
event::UIEventKind,
|
||||
Layout,
|
||||
widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Visibility,
|
||||
event::UIEvent,
|
||||
Update
|
||||
};
|
||||
use rg3d_core::{
|
||||
pool::Handle,
|
||||
math::vec2::Vec2,
|
||||
};
|
||||
use crate::gui::event::UIEvent;
|
||||
|
||||
pub struct ScrollViewer {
|
||||
widget: Widget,
|
||||
@@ -55,6 +60,12 @@ impl Layout for ScrollViewer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for ScrollViewer {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw for ScrollViewer {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext) {
|
||||
self.widget.draw(drawing_context)
|
||||
@@ -141,16 +152,26 @@ impl ScrollViewerBuilder {
|
||||
.add_column(Column::stretch())
|
||||
.add_column(Column::auto())
|
||||
.build(ui))
|
||||
.with_event_handler(Box::new(move |ui, _handle, event| {
|
||||
if let UIEventKind::NumericValueChanged { new_value, .. } = event.kind {
|
||||
let content_presenter = ui.get_node_mut(content_presenter);
|
||||
if let UINode::ScrollContentPresenter(content_presenter) = content_presenter {
|
||||
.with_event_handler(Box::new(move |ui, handle, event| {
|
||||
match event.kind {
|
||||
UIEventKind::NumericValueChanged { new_value, .. } => {
|
||||
let content_presenter = ui.get_node_mut(content_presenter).as_scroll_content_presenter_mut();
|
||||
if event.source == h_scroll_bar {
|
||||
content_presenter.set_horizontal_scroll(new_value);
|
||||
} else if event.source == v_scroll_bar {
|
||||
content_presenter.set_vertical_scroll(new_value);
|
||||
}
|
||||
}
|
||||
UIEventKind::MouseWheel { amount, .. } => {
|
||||
if !event.handled {
|
||||
if event.source == handle || ui.is_node_child_of(event.source, handle) {
|
||||
let v_scroll_bar = ui.get_node_mut(v_scroll_bar).as_scroll_bar_mut();
|
||||
v_scroll_bar.scroll(-amount * 10.0);
|
||||
event.handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}))
|
||||
.build(),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::gui::{
|
||||
draw::DrawingContext,
|
||||
node::UINode,
|
||||
scroll_bar::Orientation,
|
||||
Update
|
||||
};
|
||||
use rg3d_core::{
|
||||
math::{
|
||||
@@ -27,6 +28,12 @@ impl Draw for StackPanel {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for StackPanel {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for StackPanel {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
let mut child_constraint = Vec2::new(std::f32::INFINITY, std::f32::INFINITY);
|
||||
|
||||
+16
-12
@@ -15,7 +15,7 @@ use crate::{
|
||||
UserInterface,
|
||||
formatted_text::{FormattedText, FormattedTextBuilder},
|
||||
widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Layout
|
||||
Layout, Update
|
||||
},
|
||||
resource::ttf::Font,
|
||||
};
|
||||
@@ -27,7 +27,7 @@ pub struct Text {
|
||||
font: Rc<RefCell<Font>>,
|
||||
vertical_alignment: VerticalAlignment,
|
||||
horizontal_alignment: HorizontalAlignment,
|
||||
formatted_text: Option<FormattedText>,
|
||||
formatted_text: FormattedText,
|
||||
}
|
||||
|
||||
impl AsWidget for Text {
|
||||
@@ -40,6 +40,12 @@ impl AsWidget for Text {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Text {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Text {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
self.widget.measure_override(ui, available_size)
|
||||
@@ -54,17 +60,15 @@ impl Draw for Text {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext) {
|
||||
let bounds = self.widget.get_screen_bounds();
|
||||
if self.need_update {
|
||||
let formatted_text = FormattedTextBuilder::reuse(self.formatted_text.take().unwrap())
|
||||
.with_size(Vec2::new(bounds.w, bounds.h))
|
||||
.with_text(self.text.as_str())
|
||||
.with_color(self.widget.color)
|
||||
.with_horizontal_alignment(self.horizontal_alignment)
|
||||
.with_vertical_alignment(self.vertical_alignment)
|
||||
.build();
|
||||
self.formatted_text = Some(formatted_text);
|
||||
self.formatted_text.set_size(Vec2::new(bounds.w, bounds.h));
|
||||
self.formatted_text.set_text(self.text.as_str());
|
||||
self.formatted_text.set_color(self.widget.color);
|
||||
self.formatted_text.set_horizontal_alignment(self.horizontal_alignment);
|
||||
self.formatted_text.set_vertical_alignment(self.vertical_alignment);
|
||||
self.formatted_text.build();
|
||||
self.need_update = true; // TODO
|
||||
}
|
||||
drawing_context.draw_text(Vec2::new(bounds.x, bounds.y), self.formatted_text.as_ref().unwrap());
|
||||
drawing_context.draw_text(Vec2::new(bounds.x, bounds.y), &self.formatted_text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +153,7 @@ impl TextBuilder {
|
||||
need_update: true,
|
||||
vertical_alignment: self.vertical_text_alignment.unwrap_or(VerticalAlignment::Top),
|
||||
horizontal_alignment: self.horizontal_text_alignment.unwrap_or(HorizontalAlignment::Left),
|
||||
formatted_text: Some(FormattedTextBuilder::new(font.clone()).build()),
|
||||
formatted_text: FormattedTextBuilder::new().with_font(font.clone()).build(),
|
||||
font
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
use rg3d_core::{
|
||||
math::{
|
||||
vec2::Vec2,
|
||||
Rect,
|
||||
},
|
||||
pool::Handle,
|
||||
color::Color,
|
||||
};
|
||||
use crate::{
|
||||
gui::{
|
||||
event::UIEventKind,
|
||||
widget::{
|
||||
WidgetBuilder,
|
||||
AsWidget,
|
||||
Widget,
|
||||
},
|
||||
Draw,
|
||||
Layout,
|
||||
UserInterface,
|
||||
draw::{
|
||||
DrawingContext,
|
||||
CommandKind,
|
||||
CommandTexture,
|
||||
},
|
||||
formatted_text::{
|
||||
FormattedText,
|
||||
FormattedTextBuilder,
|
||||
},
|
||||
node::UINode,
|
||||
Update,
|
||||
},
|
||||
VirtualKeyCode, resource::ttf::Font, MouseButton,
|
||||
};
|
||||
use std::{rc::Rc, cell::RefCell, cmp};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum HorizontalDirection {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum VerticalDirection {
|
||||
Down,
|
||||
Up,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Position {
|
||||
line: usize,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct SelectionRange {
|
||||
begin: Position,
|
||||
end: Position,
|
||||
}
|
||||
|
||||
pub struct TextBox {
|
||||
widget: Widget,
|
||||
caret_line: usize,
|
||||
caret_offset: usize,
|
||||
caret_visible: bool,
|
||||
blink_timer: f32,
|
||||
blink_interval: f32,
|
||||
formatted_text: FormattedText,
|
||||
selection_range: Option<SelectionRange>,
|
||||
selecting: bool,
|
||||
}
|
||||
|
||||
impl TextBox {
|
||||
fn reset_blink(&mut self) {
|
||||
self.caret_visible = true;
|
||||
self.blink_timer = 0.0;
|
||||
}
|
||||
|
||||
pub fn move_caret_x(&mut self, mut offset: usize, direction: HorizontalDirection) {
|
||||
self.selection_range = None;
|
||||
|
||||
self.reset_blink();
|
||||
|
||||
let lines = self.formatted_text.get_lines();
|
||||
|
||||
if lines.is_empty() {
|
||||
self.caret_offset = 0;
|
||||
self.caret_line = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
while offset > 0 {
|
||||
match direction {
|
||||
HorizontalDirection::Left => {
|
||||
if self.caret_offset > 0 {
|
||||
self.caret_offset -= 1
|
||||
} else {
|
||||
if self.caret_line > 0 {
|
||||
self.caret_line -= 1;
|
||||
self.caret_offset = lines[self.caret_line].len();
|
||||
} else {
|
||||
self.caret_offset = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
HorizontalDirection::Right => {
|
||||
let line = lines.get(self.caret_line).unwrap();
|
||||
if self.caret_offset < line.len() {
|
||||
self.caret_offset += 1;
|
||||
} else {
|
||||
if self.caret_line < lines.len() - 1 {
|
||||
self.caret_line += 1;
|
||||
self.caret_offset = 0;
|
||||
} else {
|
||||
self.caret_offset = line.len();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offset -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_caret_y(&mut self, offset: usize, direction: VerticalDirection) {
|
||||
let lines = self.formatted_text.get_lines();
|
||||
|
||||
if lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let line_count = lines.len();
|
||||
|
||||
match direction {
|
||||
VerticalDirection::Down => {
|
||||
if self.caret_line + offset >= line_count {
|
||||
self.caret_line = line_count - 1;
|
||||
} else {
|
||||
self.caret_line += offset;
|
||||
}
|
||||
}
|
||||
VerticalDirection::Up => {
|
||||
if self.caret_line > offset {
|
||||
self.caret_line -= offset;
|
||||
} else {
|
||||
self.caret_line = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_absolute_position(&self) -> Option<usize> {
|
||||
if let Some(line) = self.formatted_text.get_lines().get(self.caret_line) {
|
||||
Some(line.begin + cmp::min(self.caret_offset, line.len()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts given character at current caret position.
|
||||
pub fn insert_char(&mut self, c: char) {
|
||||
if !c.is_control() {
|
||||
let position = self.get_absolute_position().unwrap_or(0);
|
||||
self.formatted_text.insert_char(c, position);
|
||||
self.formatted_text.build();
|
||||
self.move_caret_x(1, HorizontalDirection::Right);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text_len(&self) -> usize {
|
||||
self.formatted_text.get_raw_text().len()
|
||||
}
|
||||
|
||||
pub fn remove_char(&mut self, direction: HorizontalDirection) {
|
||||
if let Some(position) = self.get_absolute_position() {
|
||||
let text_len = self.get_text_len();
|
||||
if text_len != 0 {
|
||||
let position = match direction {
|
||||
HorizontalDirection::Left => {
|
||||
if position == 0 {
|
||||
return;
|
||||
}
|
||||
position - 1
|
||||
}
|
||||
HorizontalDirection::Right => {
|
||||
if position >= text_len {
|
||||
return;
|
||||
}
|
||||
position
|
||||
}
|
||||
};
|
||||
self.formatted_text.remove_at(position);
|
||||
self.formatted_text.build();
|
||||
|
||||
if direction == HorizontalDirection::Left {
|
||||
self.move_caret_x(1, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screen_pos_to_text_pos(&self, screen_pos: Vec2) -> Option<Position> {
|
||||
let mut caret_pos = self.widget.screen_position;
|
||||
if let Some(font) = self.formatted_text.get_font() {
|
||||
let font = font.borrow();
|
||||
for (line_index, line) in self.formatted_text.get_lines().iter().enumerate() {
|
||||
let line_bounds =
|
||||
Rect::new(caret_pos.x + line.x_offset, caret_pos.y, line.width, font.get_ascender());
|
||||
if line_bounds.contains(screen_pos.x, screen_pos.y) {
|
||||
let mut x = line_bounds.x;
|
||||
// Check each character in line.
|
||||
for (offset, index) in (line.begin..line.end).enumerate() {
|
||||
let symbol = self.formatted_text.get_raw_text()[index];
|
||||
let (width, height, advance) = if let Some(glyph) = font.get_glyph(symbol) {
|
||||
(glyph.get_bitmap_width(), glyph.get_bitmap_height(), glyph.get_advance())
|
||||
} else {
|
||||
// Stub
|
||||
let h = font.get_height();
|
||||
(h, h, h)
|
||||
};
|
||||
let char_bounds = Rect::new(x, line_bounds.y, width, height);
|
||||
if char_bounds.contains(screen_pos.x, screen_pos.y) {
|
||||
return Some(Position { line: line_index, offset });
|
||||
}
|
||||
x += advance;
|
||||
}
|
||||
}
|
||||
caret_pos.y += line_bounds.h;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AsWidget for TextBox {
|
||||
fn widget(&self) -> &Widget {
|
||||
&self.widget
|
||||
}
|
||||
|
||||
fn widget_mut(&mut self) -> &mut Widget {
|
||||
&mut self.widget
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw for TextBox {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext) {
|
||||
self.widget.draw(drawing_context);
|
||||
|
||||
let bounds = self.widget.get_screen_bounds();
|
||||
drawing_context.push_rect_filled(&bounds, None, Color::opaque(80, 80, 80));
|
||||
drawing_context.commit(CommandKind::Geometry, CommandTexture::None);
|
||||
|
||||
self.formatted_text.set_size(Vec2::new(bounds.w, bounds.h));
|
||||
self.formatted_text.set_color(self.widget.color);
|
||||
self.formatted_text.build();
|
||||
|
||||
if let Some(ref selection_range) = self.selection_range {
|
||||
let text = &self.formatted_text;
|
||||
let lines = text.get_lines();
|
||||
if selection_range.begin.line == selection_range.end.line {
|
||||
let line = lines[selection_range.begin.line];
|
||||
let begin = selection_range.begin.offset;
|
||||
let end = selection_range.end.offset;
|
||||
// Begin line
|
||||
let offset = text.get_range_width(line.begin..(line.begin + begin));
|
||||
let width = text.get_range_width((line.begin + begin)..(line.begin + end));
|
||||
let bounds = Rect::new(bounds.x + line.x_offset + offset,
|
||||
bounds.y + line.y_offset,
|
||||
width,
|
||||
line.height);
|
||||
drawing_context.push_rect_filled(&bounds, None, Color::opaque(65, 65, 90));
|
||||
} else {
|
||||
for (i, line) in text.get_lines().iter().enumerate() {
|
||||
if i >= selection_range.begin.line && i <= selection_range.end.line {
|
||||
let bounds = if i == selection_range.begin.line {
|
||||
// Begin line
|
||||
let offset = text.get_range_width(line.begin..(line.begin + selection_range.begin.offset));
|
||||
let width = text.get_range_width((line.begin + selection_range.begin.offset)..line.end);
|
||||
Rect::new(bounds.x + line.x_offset + offset,
|
||||
bounds.y + line.y_offset,
|
||||
width,
|
||||
line.height)
|
||||
} else if i == selection_range.end.line {
|
||||
// End line
|
||||
let width = text.get_range_width(line.begin..(line.begin + selection_range.end.offset));
|
||||
Rect::new(bounds.x + line.x_offset,
|
||||
bounds.y + line.y_offset,
|
||||
width,
|
||||
line.height)
|
||||
} else {
|
||||
// Everything between
|
||||
Rect::new(bounds.x + line.x_offset,
|
||||
bounds.y + line.y_offset,
|
||||
line.width,
|
||||
line.height)
|
||||
};
|
||||
drawing_context.push_rect_filled(&bounds, None, Color::opaque(90, 90, 120));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drawing_context.commit(CommandKind::Geometry, CommandTexture::None);
|
||||
|
||||
let screen_position = Vec2::new(bounds.x, bounds.y);
|
||||
drawing_context.draw_text(screen_position, &self.formatted_text);
|
||||
|
||||
if self.caret_visible {
|
||||
if let Some(font) = self.formatted_text.get_font() {
|
||||
let font = font.borrow();
|
||||
if let Some(line) = self.formatted_text.get_lines().get(self.caret_line) {
|
||||
let text = self.formatted_text.get_raw_text();
|
||||
let mut caret_pos = Vec2::new(
|
||||
screen_position.x,
|
||||
screen_position.y + self.caret_line as f32 * font.get_ascender(),
|
||||
);
|
||||
for (offset, char_index) in (line.begin..line.end).enumerate() {
|
||||
if offset >= self.caret_offset {
|
||||
break;
|
||||
}
|
||||
if let Some(glyph) = font.get_glyph(text[char_index]) {
|
||||
caret_pos.x += glyph.get_advance();
|
||||
} else {
|
||||
caret_pos.x += font.get_height();
|
||||
}
|
||||
}
|
||||
|
||||
let caret_bounds = Rect::new(caret_pos.x, caret_pos.y, 2.0, font.get_height());
|
||||
drawing_context.push_rect_filled(&caret_bounds, None, Color::WHITE);
|
||||
drawing_context.commit(CommandKind::Geometry, CommandTexture::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for TextBox {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
self.widget.measure_override(ui, available_size)
|
||||
}
|
||||
|
||||
fn arrange_override(&self, ui: &UserInterface, final_size: Vec2) -> Vec2 {
|
||||
self.widget.arrange_override(ui, final_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for TextBox {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt);
|
||||
self.blink_timer += dt;
|
||||
if self.blink_timer >= self.blink_interval {
|
||||
self.blink_timer = 0.0;
|
||||
self.caret_visible = !self.caret_visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextBoxBuilder {
|
||||
widget_builder: WidgetBuilder,
|
||||
font: Option<Rc<RefCell<Font>>>,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl TextBoxBuilder {
|
||||
pub fn new(widget_builder: WidgetBuilder) -> Self {
|
||||
Self {
|
||||
widget_builder,
|
||||
font: None,
|
||||
text: "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_font(mut self, font: Rc<RefCell<Font>>) -> Self {
|
||||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_text(mut self, text: String) -> Self {
|
||||
self.text = text;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, ui: &mut UserInterface) -> Handle<UINode> {
|
||||
let text_box = UINode::TextBox(TextBox {
|
||||
widget: self.widget_builder
|
||||
.with_event_handler(Box::new(|ui, handle, evt| {
|
||||
if evt.source == handle || ui.is_node_child_of(evt.source, handle) {
|
||||
let text_box = ui.get_node_mut(handle).as_text_box_mut();
|
||||
match evt.kind {
|
||||
UIEventKind::Text { symbol } => {
|
||||
text_box.insert_char(symbol);
|
||||
}
|
||||
UIEventKind::KeyDown { code } => {
|
||||
match code {
|
||||
VirtualKeyCode::Up => {
|
||||
text_box.move_caret_y(1, VerticalDirection::Up);
|
||||
}
|
||||
VirtualKeyCode::Down => {
|
||||
text_box.move_caret_y(1, VerticalDirection::Down);
|
||||
}
|
||||
VirtualKeyCode::Right => {
|
||||
text_box.move_caret_x(1, HorizontalDirection::Right);
|
||||
}
|
||||
VirtualKeyCode::Left => {
|
||||
text_box.move_caret_x(1, HorizontalDirection::Left);
|
||||
}
|
||||
VirtualKeyCode::Delete => {
|
||||
text_box.remove_char(HorizontalDirection::Right);
|
||||
}
|
||||
VirtualKeyCode::Back => {
|
||||
text_box.remove_char(HorizontalDirection::Left);
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
UIEventKind::MouseDown { pos, button } => {
|
||||
if button == MouseButton::Left {
|
||||
text_box.selection_range = None;
|
||||
text_box.selecting = true;
|
||||
|
||||
if let Some(position) = text_box.screen_pos_to_text_pos(pos) {
|
||||
text_box.caret_line = position.line;
|
||||
text_box.caret_offset = position.offset;
|
||||
|
||||
text_box.selection_range = Some(SelectionRange {
|
||||
begin: position,
|
||||
end: position,
|
||||
})
|
||||
}
|
||||
|
||||
ui.capture_mouse(handle);
|
||||
}
|
||||
}
|
||||
UIEventKind::MouseMove { pos } => {
|
||||
if text_box.selecting {
|
||||
if let Some(position) = text_box.screen_pos_to_text_pos(pos) {
|
||||
if let Some(ref mut sel_range) = text_box.selection_range {
|
||||
sel_range.end = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UIEventKind::MouseUp { .. } => {
|
||||
text_box.selecting = false;
|
||||
|
||||
ui.release_mouse_capture();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.build(),
|
||||
caret_line: 0,
|
||||
caret_offset: 0,
|
||||
caret_visible: true,
|
||||
blink_timer: 0.0,
|
||||
blink_interval: 0.5,
|
||||
formatted_text: FormattedTextBuilder::new()
|
||||
.with_text(self.text)
|
||||
.with_font(self.font.unwrap_or(ui.get_default_font()))
|
||||
.build(),
|
||||
selection_range: None,
|
||||
selecting: false,
|
||||
});
|
||||
|
||||
ui.add_node(text_box)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -19,6 +19,7 @@ use crate::gui::{
|
||||
node::UINode,
|
||||
event::{UIEventHandler, UIEvent},
|
||||
draw::DrawingContext,
|
||||
Update
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
@@ -78,6 +79,12 @@ impl Default for Widget {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Widget {
|
||||
fn update(&mut self, _dt: f32) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Widget {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
let mut size = Vec2::ZERO;
|
||||
@@ -277,8 +284,10 @@ impl WidgetBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_children(mut self, mut children: Vec<Handle<UINode>>) -> Self {
|
||||
self.children.append(&mut children);
|
||||
pub fn with_children(mut self, children: &[Handle<UINode>]) -> Self {
|
||||
for child in children {
|
||||
self.children.push(*child)
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -17,7 +17,8 @@ use crate::gui::{
|
||||
Layout,
|
||||
widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Draw,
|
||||
draw::DrawingContext
|
||||
draw::DrawingContext,
|
||||
Update
|
||||
};
|
||||
|
||||
/// Represents a widget looking as window in Windows - with title, minimize and close buttons.
|
||||
@@ -40,6 +41,12 @@ impl AsWidget for Window {
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for Window {
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.widget.update(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Window {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2 {
|
||||
self.widget.measure_override(ui, available_size)
|
||||
|
||||
@@ -950,6 +950,12 @@ impl Font {
|
||||
(1.05 * area.sqrt()) as i32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_glyph_advance(&self, c: u32) -> f32 {
|
||||
self.get_glyph(c)
|
||||
.map_or(self.get_height(), |glyph| glyph.get_advance())
|
||||
}
|
||||
|
||||
fn pack(&mut self) {
|
||||
self.atlas_size = self.compute_atlas_size();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user