#![warn(missing_docs)]
use crate::{
core::{
algebra::Vector2, log::Log, math::Rect, pool::Handle, reflect::prelude::*,
type_traits::prelude::*, uuid_provider, variable::InheritableVariable, visitor::prelude::*,
},
define_constructor,
draw::{CommandTexture, Draw, DrawingContext},
message::{MessageDirection, UiMessage},
widget::{Widget, WidgetBuilder},
BuildContext, Control, UiNode, UserInterface,
};
use core::f32;
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use fyrox_graph::BaseSceneGraph;
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
#[derive(Debug, PartialEq, Clone)]
pub enum GridMessage {
Rows(Vec<Row>),
Columns(Vec<Column>),
DrawBorder(bool),
BorderThickness(f32),
}
impl GridMessage {
define_constructor!(
GridMessage:Rows => fn rows(Vec<Row>), layout: false
);
define_constructor!(
GridMessage:Columns => fn columns(Vec<Column>), layout: false
);
define_constructor!(
GridMessage:DrawBorder => fn draw_border(bool), layout: false
);
define_constructor!(
GridMessage:BorderThickness => fn border_thickness(f32), layout: false
);
}
#[derive(
Clone, Copy, PartialEq, Eq, Debug, Reflect, Visit, Default, AsRefStr, EnumString, VariantNames,
)]
pub enum SizeMode {
#[default]
Strict,
Auto,
Stretch,
}
uuid_provider!(SizeMode = "9c5dfbce-5df2-4a7f-8c57-c4473743a718");
#[derive(Clone, Copy, PartialEq, Debug, Reflect, Visit, Default)]
pub struct GridDimension {
pub size_mode: SizeMode,
pub desired_size: f32,
pub actual_size: f32,
pub location: f32,
#[visit(skip)]
#[reflect(hidden)]
unmeasured_node_count: usize,
}
uuid_provider!(GridDimension = "5e894900-c14a-4eb6-acb9-1636efead4b4");
impl GridDimension {
pub fn generic(size_mode: SizeMode, desired_size: f32) -> Self {
Self {
size_mode,
desired_size,
actual_size: 0.0,
location: 0.0,
unmeasured_node_count: 0,
}
}
pub fn strict(desired_size: f32) -> Self {
Self::generic(SizeMode::Strict, desired_size)
}
pub fn stretch() -> Self {
Self::generic(SizeMode::Stretch, 0.0)
}
pub fn auto() -> Self {
Self::generic(SizeMode::Auto, 0.0)
}
fn update_size(&mut self, node_size: f32, available_size: f32) {
match self.size_mode {
SizeMode::Strict => (),
SizeMode::Auto => {
self.desired_size = self.desired_size.max(node_size);
self.actual_size = self.desired_size;
}
SizeMode::Stretch => {
if available_size.is_finite() {
self.actual_size = self.desired_size + available_size;
} else {
self.actual_size = node_size;
}
}
}
}
}
pub type Column = GridDimension;
pub type Row = GridDimension;
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
pub struct Grid {
pub widget: Widget,
pub rows: InheritableVariable<RefCell<Vec<Row>>>,
pub columns: InheritableVariable<RefCell<Vec<Column>>>,
pub draw_border: InheritableVariable<bool>,
pub border_thickness: InheritableVariable<f32>,
#[visit(skip)]
#[reflect(hidden)]
pub cells: RefCell<Vec<Cell>>,
#[visit(skip)]
#[reflect(hidden)]
pub groups: RefCell<[Vec<usize>; 4]>,
}
impl ConstructorProvider<UiNode, UserInterface> for Grid {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Grid", |ui| {
GridBuilder::new(WidgetBuilder::new().with_name("Grid"))
.build(&mut ui.build_ctx())
.into()
})
.with_group("Layout")
}
}
crate::define_widget_deref!(Grid);
#[derive(Clone, Debug)]
pub struct Cell {
pub nodes: Vec<Handle<UiNode>>,
pub row_index: usize,
pub column_index: usize,
}
fn group_index(row_size_mode: SizeMode, column_size_mode: SizeMode) -> usize {
match (row_size_mode, column_size_mode) {
(SizeMode::Strict, SizeMode::Strict)
| (SizeMode::Strict, SizeMode::Auto)
| (SizeMode::Auto, SizeMode::Strict)
| (SizeMode::Auto, SizeMode::Auto) => 0,
(SizeMode::Stretch, SizeMode::Auto) => 1,
(SizeMode::Strict, SizeMode::Stretch) | (SizeMode::Auto, SizeMode::Stretch) => 2,
(SizeMode::Stretch, SizeMode::Strict) | (SizeMode::Stretch, SizeMode::Stretch) => 3,
}
}
fn choose_constraint(dimension: &GridDimension, available_size: f32) -> f32 {
match dimension.size_mode {
SizeMode::Strict => dimension.desired_size,
SizeMode::Stretch => dimension.desired_size + available_size,
SizeMode::Auto => f32::INFINITY,
}
}
fn calc_total_size_of_non_stretch_dims(dims: &[GridDimension]) -> Option<f32> {
if dims.iter().all(|d| d.size_mode != SizeMode::Stretch) {
Some(0.0) } else if dims.iter().all(|d| d.unmeasured_node_count == 0) {
Some(dims.iter().map(|d| d.desired_size).sum())
} else {
None
}
}
fn count_stretch_dims(dims: &[GridDimension]) -> usize {
let mut stretch_sized_dims = 0;
for dim in dims.iter() {
if dim.size_mode == SizeMode::Stretch {
stretch_sized_dims += 1;
}
}
stretch_sized_dims
}
fn calc_avg_size_for_stretch_dim(
dims: &RefCell<Vec<GridDimension>>,
available_size: f32,
) -> Option<f32> {
if available_size.is_infinite() {
return Some(available_size);
}
let dims = dims.borrow();
let stretch_sized_dims = count_stretch_dims(&dims);
if stretch_sized_dims > 0 {
let rest_size = available_size - calc_total_size_of_non_stretch_dims(&dims)?;
Some(rest_size / stretch_sized_dims as f32)
} else {
Some(0.0) }
}
fn arrange_dims(dims: &mut [GridDimension], final_size: f32) {
let preset_width: f32 = dims.iter().map(|d| d.desired_size).sum();
let stretch_count = count_stretch_dims(dims);
let avg_stretch = if stretch_count > 0 {
(final_size - preset_width) / stretch_count as f32
} else {
0.0
};
let mut location = 0.0;
for dim in dims.iter_mut() {
dim.location = location;
dim.actual_size = match dim.size_mode {
SizeMode::Strict | SizeMode::Auto => dim.desired_size,
SizeMode::Stretch => dim.desired_size + avg_stretch,
};
location += dim.actual_size;
}
}
uuid_provider!(Grid = "98ce15e2-bd62-497d-a37b-9b1cb4a1918c");
impl Grid {
fn initialize_measure(&self, ui: &UserInterface) {
self.calc_needed_measurements(ui);
let mut groups = self.groups.borrow_mut();
for group in groups.iter_mut() {
group.clear();
}
let mut cells = self.cells.borrow_mut();
cells.clear();
let rows = self.rows.borrow();
let columns = self.columns.borrow();
for (column_index, column) in columns.iter().enumerate() {
for (row_index, row) in rows.iter().enumerate() {
groups[group_index(row.size_mode, column.size_mode)].push(cells.len());
cells.push(Cell {
nodes: self
.children()
.iter()
.copied()
.filter(|&c| {
let Some(child_ref) = ui.try_get(c) else {
return false;
};
child_ref.row() == row_index && child_ref.column() == column_index
})
.collect(),
row_index,
column_index,
})
}
}
}
fn calc_needed_measurements(&self, ui: &UserInterface) {
let mut rows = self.rows.borrow_mut();
let mut cols = self.columns.borrow_mut();
for dim in rows.iter_mut().chain(cols.iter_mut()) {
dim.unmeasured_node_count = 0;
match dim.size_mode {
SizeMode::Auto => dim.desired_size = 0.0,
SizeMode::Strict => dim.actual_size = dim.desired_size,
SizeMode::Stretch => (),
}
}
for handle in self.children() {
let Some(node) = ui.try_get(*handle) else {
continue;
};
let Some(row) = rows.get_mut(node.row()) else {
Log::err(format!(
"Node row out of bounds: {} row:{}, column:{}",
node.type_name(),
node.row(),
node.column()
));
continue;
};
let Some(col) = cols.get_mut(node.column()) else {
Log::err(format!(
"Node column out of bounds: {} row:{}, column:{}",
node.type_name(),
node.row(),
node.column()
));
continue;
};
if col.size_mode == SizeMode::Auto {
col.unmeasured_node_count += 1
}
if row.size_mode == SizeMode::Auto {
row.unmeasured_node_count += 1
}
}
}
fn measure_width_and_height(
&self,
child: Handle<UiNode>,
ui: &UserInterface,
available_size: Vector2<f32>,
measure_width: bool,
measure_height: bool,
) {
let Some(node) = ui.try_get(child) else {
return;
};
let mut rows = self.rows.borrow_mut();
let mut cols = self.columns.borrow_mut();
let Some(row) = rows.get_mut(node.row()) else {
return;
};
let Some(col) = cols.get_mut(node.column()) else {
return;
};
let constraint = Vector2::new(
choose_constraint(col, available_size.x),
choose_constraint(row, available_size.y),
);
ui.measure_node(child, constraint);
if measure_width {
col.update_size(node.desired_size().x, available_size.x);
if col.size_mode == SizeMode::Auto {
col.unmeasured_node_count -= 1;
}
}
if measure_height {
row.update_size(node.desired_size().y, available_size.y);
if row.size_mode == SizeMode::Auto {
row.unmeasured_node_count -= 1;
}
}
}
fn measure_group_width(
&self,
group: &[usize],
ui: &UserInterface,
available_size: Vector2<f32>,
) {
let cells = self.cells.borrow();
for cell in group.iter().map(|&i| &cells[i]) {
for n in cell.nodes.iter() {
self.measure_width_and_height(*n, ui, available_size, true, false);
}
}
}
fn measure_group_height(
&self,
group: &[usize],
ui: &UserInterface,
available_size: Vector2<f32>,
) {
let cells = self.cells.borrow();
for cell in group.iter().map(|&i| &cells[i]) {
for n in cell.nodes.iter() {
self.measure_width_and_height(*n, ui, available_size, false, true);
}
}
}
fn measure_group(&self, group: &[usize], ui: &UserInterface, available_size: Vector2<f32>) {
let cells = self.cells.borrow();
for cell in group.iter().map(|&i| &cells[i]) {
for n in cell.nodes.iter() {
self.measure_width_and_height(*n, ui, available_size, true, true);
}
}
}
}
impl Control for Grid {
fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
if self.columns.borrow().is_empty() || self.rows.borrow().is_empty() {
return self.widget.measure_override(ui, available_size);
}
self.initialize_measure(ui);
let groups = self.groups.borrow_mut();
self.measure_group(&groups[0], ui, available_size);
if let Some(space_y) = calc_avg_size_for_stretch_dim(&self.rows, available_size.y) {
self.measure_group(&groups[1], ui, Vector2::new(available_size.x, space_y));
let space_x = calc_avg_size_for_stretch_dim(&self.columns, available_size.x).unwrap();
self.measure_group(&groups[2], ui, Vector2::new(space_x, available_size.y));
self.measure_group(&groups[3], ui, Vector2::new(space_x, space_y));
} else if let Some(space_x) = calc_avg_size_for_stretch_dim(&self.columns, available_size.x)
{
self.measure_group(&groups[2], ui, Vector2::new(space_x, available_size.y));
let space_y = calc_avg_size_for_stretch_dim(&self.rows, available_size.y).unwrap();
self.measure_group(&groups[3], ui, Vector2::new(space_x, space_y));
} else {
self.measure_group_width(&groups[1], ui, Vector2::new(f32::INFINITY, f32::INFINITY));
let space_x = calc_avg_size_for_stretch_dim(&self.columns, available_size.x).unwrap();
self.measure_group(&groups[2], ui, Vector2::new(space_x, available_size.y));
let space_y = calc_avg_size_for_stretch_dim(&self.rows, available_size.y).unwrap();
self.measure_group_height(&groups[1], ui, Vector2::new(available_size.x, space_y));
self.measure_group(&groups[3], ui, Vector2::new(space_x, space_y));
}
let desired_size = Vector2::<f32>::new(
self.columns.borrow().iter().map(|c| c.actual_size).sum(),
self.rows.borrow().iter().map(|r| r.actual_size).sum(),
);
desired_size
}
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
let mut columns = self.columns.borrow_mut();
let mut rows = self.rows.borrow_mut();
if columns.is_empty() || rows.is_empty() {
let rect = Rect::new(0.0, 0.0, final_size.x, final_size.y);
for child_handle in self.widget.children() {
ui.arrange_node(*child_handle, &rect);
}
return final_size;
}
arrange_dims(&mut columns, final_size.x);
arrange_dims(&mut rows, final_size.y);
for child_handle in self.widget.children() {
let child = ui.nodes.borrow(*child_handle);
if let Some(column) = columns.get(child.column()) {
if let Some(row) = rows.get(child.row()) {
ui.arrange_node(
*child_handle,
&Rect::new(
column.location,
row.location,
column.actual_size,
row.actual_size,
),
);
}
}
}
final_size
}
fn draw(&self, drawing_context: &mut DrawingContext) {
if *self.draw_border {
let bounds = self.widget.bounding_rect();
let left_top = Vector2::new(bounds.x(), bounds.y());
let right_top = Vector2::new(bounds.x() + bounds.w(), bounds.y());
let right_bottom = Vector2::new(bounds.x() + bounds.w(), bounds.y() + bounds.h());
let left_bottom = Vector2::new(bounds.x(), bounds.y() + bounds.h());
drawing_context.push_line(left_top, right_top, *self.border_thickness);
drawing_context.push_line(right_top, right_bottom, *self.border_thickness);
drawing_context.push_line(right_bottom, left_bottom, *self.border_thickness);
drawing_context.push_line(left_bottom, left_top, *self.border_thickness);
for column in self.columns.borrow().iter() {
let a = Vector2::new(bounds.x() + column.location, bounds.y());
let b = Vector2::new(bounds.x() + column.location, bounds.y() + bounds.h());
drawing_context.push_line(a, b, *self.border_thickness);
}
for row in self.rows.borrow().iter() {
let a = Vector2::new(bounds.x(), bounds.y() + row.location);
let b = Vector2::new(bounds.x() + bounds.w(), bounds.y() + row.location);
drawing_context.push_line(a, b, *self.border_thickness);
}
drawing_context.commit(
self.clip_bounds(),
self.widget.foreground(),
CommandTexture::None,
None,
);
}
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data::<GridMessage>() {
if message.direction() == MessageDirection::ToWidget
&& message.destination() == self.handle
{
match msg {
GridMessage::Rows(rows) => {
if &*self.rows.borrow() != rows {
self.rows
.set_value_and_mark_modified(RefCell::new(rows.clone()));
self.invalidate_layout();
}
}
GridMessage::Columns(columns) => {
if &*self.columns.borrow() != columns {
self.columns
.set_value_and_mark_modified(RefCell::new(columns.clone()));
self.invalidate_layout();
}
}
GridMessage::DrawBorder(draw_border) => {
self.draw_border.set_value_and_mark_modified(*draw_border);
}
GridMessage::BorderThickness(border_thickness) => {
self.border_thickness
.set_value_and_mark_modified(*border_thickness);
}
}
}
}
}
}
pub struct GridBuilder {
widget_builder: WidgetBuilder,
rows: Vec<Row>,
columns: Vec<Column>,
draw_border: bool,
border_thickness: f32,
}
impl GridBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
rows: Vec::new(),
columns: Vec::new(),
draw_border: false,
border_thickness: 1.0,
}
}
pub fn add_row(mut self, row: Row) -> Self {
self.rows.push(row);
self
}
pub fn add_column(mut self, column: Column) -> Self {
self.columns.push(column);
self
}
pub fn add_rows(mut self, mut rows: Vec<Row>) -> Self {
self.rows.append(&mut rows);
self
}
pub fn add_columns(mut self, mut columns: Vec<Column>) -> Self {
self.columns.append(&mut columns);
self
}
pub fn draw_border(mut self, value: bool) -> Self {
self.draw_border = value;
self
}
pub fn with_border_thickness(mut self, value: f32) -> Self {
self.border_thickness = value;
self
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
let grid = Grid {
widget: self.widget_builder.build(ctx),
rows: RefCell::new(self.rows).into(),
columns: RefCell::new(self.columns).into(),
draw_border: self.draw_border.into(),
border_thickness: self.border_thickness.into(),
cells: Default::default(),
groups: Default::default(),
};
ctx.add_node(UiNode::new(grid))
}
}
#[cfg(test)]
mod test {
use crate::grid::GridBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| GridBuilder::new(WidgetBuilder::new()).build(ctx));
}
}