use nalgebra_glm::Vec4;
use crate::ecs::text::components::{TextAlignment, TextProperties, VerticalAlignment};
use crate::ecs::text::resources::FontAtlasData;
use crate::ecs::ui::components::{DropZone, SplitDirection, TileId, TileNode, UiWidgetState};
use crate::ecs::ui::render_sync::select_best_font;
use crate::ecs::ui::types::Rect;
use crate::ecs::ui::types::UiTextInstance;
use crate::ecs::world::World;
use crate::render::wgpu::passes::geometry::{UiLayer, UiRect};
use crate::render::wgpu::text_mesh::generate_text_mesh;
pub(super) fn emit_tile_containers(world: &mut World, dpi_scale: f32) {
let tile_entities: Vec<freecs::Entity> = world
.ui
.query_entities(crate::ecs::world::UI_WIDGET_STATE)
.collect();
struct TileEmit {
rects: Vec<UiRect>,
texts: Vec<(String, nalgebra_glm::Vec2, f32, Vec4)>,
overlay_texts: Vec<(String, nalgebra_glm::Vec2, f32, Vec4)>,
container_depth: f32,
clip_rect: Option<Rect>,
layer: UiLayer,
}
let mut emits = Vec::new();
for entity in tile_entities {
let (data, depth, clip_rect, layer) = {
let is_tile = matches!(
world.ui.get_ui_widget_state(entity),
Some(UiWidgetState::TileContainer(_))
);
if !is_tile {
continue;
}
let node = match world.ui.get_ui_layout_node(entity) {
Some(node)
if node.visible
&& (node.computed_rect.width() > 0.0
|| node.computed_rect.height() > 0.0) =>
{
node
}
_ => continue,
};
let depth = node.computed_depth;
let clip = node.computed_clip_rect;
let node_layer = node.computed_layer.unwrap_or(UiLayer::Background);
let data = match world.ui.get_ui_widget_state(entity) {
Some(UiWidgetState::TileContainer(data)) => data.clone(),
_ => continue,
};
(data, depth, clip, node_layer)
};
let theme = world.resources.retained_ui.theme_state.active_theme();
let border_color = theme.border_color;
let panel_header_color = theme.panel_header_color;
let accent_color = theme.accent_color;
let panel_color = theme.panel_color;
let text_color = theme.text_color;
let font_size = theme.font_size;
let z_index = (depth * 100.0) as i32 + 150;
let mut rects = Vec::new();
let mut texts = Vec::new();
let mut overlay_texts = Vec::new();
let total_panes = data
.tiles
.iter()
.filter(|t| matches!(t, Some(TileNode::Pane { .. })))
.count();
for (index, tile) in data.tiles.iter().enumerate() {
if let Some(TileNode::Split {
direction,
children,
..
}) = tile
{
let child0_rect = match data.rects.get(children[0].0) {
Some(r) => *r,
None => continue,
};
let (splitter_pos, splitter_size) = match direction {
SplitDirection::Horizontal => (
nalgebra_glm::Vec2::new(child0_rect.max.x, child0_rect.min.y),
nalgebra_glm::Vec2::new(data.splitter_width, child0_rect.height()),
),
SplitDirection::Vertical => (
nalgebra_glm::Vec2::new(child0_rect.min.x, child0_rect.max.y),
nalgebra_glm::Vec2::new(child0_rect.width(), data.splitter_width),
),
};
rects.push(UiRect {
position: splitter_pos,
size: splitter_size,
color: border_color,
corner_radius: 0.0,
border_width: 0.0,
border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
rotation: 0.0,
clip_rect,
layer,
z_index,
});
}
if let Some(TileNode::Tabs { panes, active }) = tile {
let show_close = total_panes >= 2;
let tabs_rect = match data.rects.get(index) {
Some(r) => *r,
None => continue,
};
let tab_bar_rect = Rect {
min: tabs_rect.min,
max: nalgebra_glm::Vec2::new(
tabs_rect.max.x,
tabs_rect.min.y + data.tab_bar_height,
),
};
rects.push(UiRect {
position: tab_bar_rect.min,
size: tab_bar_rect.size(),
color: panel_header_color,
corner_radius: 0.0,
border_width: 0.0,
border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
rotation: 0.0,
clip_rect,
layer,
z_index,
});
if !panes.is_empty() {
let tab_width = tab_bar_rect.width() / panes.len() as f32;
for (tab_index, &pane_id) in panes.iter().enumerate() {
let tab_x = tab_bar_rect.min.x + tab_index as f32 * tab_width;
let is_active = tab_index == *active;
let tab_bg = if is_active { accent_color } else { panel_color };
rects.push(UiRect {
position: nalgebra_glm::Vec2::new(
tab_x + 1.0,
tab_bar_rect.min.y + 1.0,
),
size: nalgebra_glm::Vec2::new(
tab_width - 2.0,
data.tab_bar_height - 2.0,
),
color: tab_bg,
corner_radius: 3.0 * dpi_scale,
border_width: 0.0,
border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
rotation: 0.0,
clip_rect,
layer,
z_index: z_index + 1,
});
if let Some(TileNode::Pane { title, .. }) = data.get(pane_id) {
let close_w = data.tab_close_width();
let title_offset = if show_close { close_w * 0.5 } else { 0.0 };
let text_x = tab_x + (tab_width - title_offset) * 0.5;
let text_y = tab_bar_rect.min.y + data.tab_bar_height * 0.5;
texts.push((
title.clone(),
nalgebra_glm::Vec2::new(text_x, text_y),
font_size * 0.8,
text_color,
));
if show_close {
let close_x = tab_x + tab_width - close_w * 0.5 - 1.0;
let close_y = tab_bar_rect.min.y + data.tab_bar_height * 0.5;
let is_hovered =
if let Some((hover_tabs, hover_idx)) = data.hovered_close {
hover_tabs == TileId(index) && hover_idx == tab_index
} else {
false
};
let alpha = if is_hovered {
text_color.w
} else {
text_color.w * 0.5
};
let close_color =
Vec4::new(text_color.x, text_color.y, text_color.z, alpha);
texts.push((
"\u{00D7}".to_string(),
nalgebra_glm::Vec2::new(close_x, close_y),
font_size * 0.75,
close_color,
));
}
}
}
}
rects.push(UiRect {
position: nalgebra_glm::Vec2::new(
tabs_rect.min.x,
tabs_rect.min.y + data.tab_bar_height - 1.0,
),
size: nalgebra_glm::Vec2::new(tabs_rect.width(), 1.0),
color: border_color,
corner_radius: 0.0,
border_width: 0.0,
border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
rotation: 0.0,
clip_rect,
layer,
z_index: z_index + 2,
});
}
}
if let Some(ref preview) = data.drop_preview {
let target_rect = data.rects.get(preview.target_tile.0).copied();
if let Some(target_rect) = target_rect {
let (preview_pos, preview_size) = match preview.zone {
DropZone::Left => (
target_rect.min,
nalgebra_glm::Vec2::new(target_rect.width() * 0.5, target_rect.height()),
),
DropZone::Right => (
nalgebra_glm::Vec2::new(
target_rect.min.x + target_rect.width() * 0.5,
target_rect.min.y,
),
nalgebra_glm::Vec2::new(target_rect.width() * 0.5, target_rect.height()),
),
DropZone::Top => (
target_rect.min,
nalgebra_glm::Vec2::new(target_rect.width(), target_rect.height() * 0.5),
),
DropZone::Bottom => (
nalgebra_glm::Vec2::new(
target_rect.min.x,
target_rect.min.y + target_rect.height() * 0.5,
),
nalgebra_glm::Vec2::new(target_rect.width(), target_rect.height() * 0.5),
),
DropZone::Center => (target_rect.min, target_rect.size()),
};
let preview_color = Vec4::new(accent_color.x, accent_color.y, accent_color.z, 0.2);
rects.push(UiRect {
position: preview_pos,
size: preview_size,
color: preview_color,
corner_radius: 0.0,
border_width: 2.0 * dpi_scale,
border_color: Vec4::new(accent_color.x, accent_color.y, accent_color.z, 0.6),
rotation: 0.0,
clip_rect: None,
layer: UiLayer::DockIndicator,
z_index: z_index + 10,
});
}
}
if let Some((source_tabs_id, tab_index, _)) = data.dragging_tab {
let mouse_pos = world.resources.input.mouse.position;
if let Some(TileNode::Tabs { panes, .. }) = data.get(source_tabs_id)
&& tab_index < panes.len()
{
let pane_id = panes[tab_index];
if let Some(TileNode::Pane { title, .. }) = data.get(pane_id) {
let ghost_width = 120.0;
let ghost_height = data.tab_bar_height;
rects.push(UiRect {
position: nalgebra_glm::Vec2::new(
mouse_pos.x - ghost_width * 0.5,
mouse_pos.y - ghost_height * 0.5,
),
size: nalgebra_glm::Vec2::new(ghost_width, ghost_height),
color: Vec4::new(accent_color.x, accent_color.y, accent_color.z, 0.8),
corner_radius: 4.0 * dpi_scale,
border_width: 1.0 * dpi_scale,
border_color,
rotation: 0.0,
clip_rect: None,
layer: UiLayer::DockIndicator,
z_index: z_index + 20,
});
overlay_texts.push((
title.clone(),
nalgebra_glm::Vec2::new(mouse_pos.x, mouse_pos.y),
font_size * 0.8,
text_color,
));
}
}
}
emits.push(TileEmit {
rects,
texts,
overlay_texts,
container_depth: depth,
clip_rect,
layer,
});
}
let bitmap_fonts: Vec<FontAtlasData> = world
.resources
.text_cache
.font_manager
.bitmap_fonts
.iter()
.map(|arc| arc.as_ref().clone())
.collect();
for emit in emits {
world.resources.retained_ui.frame_rects.extend(emit.rects);
let base_z = (emit.container_depth * 100.0) as i32;
for (text, position, size, color) in &emit.texts {
let physical_size = *size * dpi_scale;
if let Some((tile_font_idx, font_data)) = select_best_font(&bitmap_fonts, physical_size)
{
let properties = TextProperties {
font_size: physical_size,
color: *color,
alignment: TextAlignment::Center,
vertical_alignment: VerticalAlignment::Middle,
..TextProperties::default()
};
let mesh = generate_text_mesh(text, font_data, &properties);
world
.resources
.retained_ui
.frame_text_meshes
.push(UiTextInstance {
mesh,
position: *position,
color: *color,
outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
outline_width: 0.0,
font_size: physical_size,
font_index: tile_font_idx,
clip_rect: emit.clip_rect,
layer: emit.layer,
z_index: base_z + 155,
character_colors: None,
});
}
}
for (text, position, size, color) in &emit.overlay_texts {
let physical_size = *size * dpi_scale;
if let Some((tile_font_idx, font_data)) = select_best_font(&bitmap_fonts, physical_size)
{
let properties = TextProperties {
font_size: physical_size,
color: *color,
alignment: TextAlignment::Center,
vertical_alignment: VerticalAlignment::Middle,
..TextProperties::default()
};
let mesh = generate_text_mesh(text, font_data, &properties);
world
.resources
.retained_ui
.frame_text_meshes
.push(UiTextInstance {
mesh,
position: *position,
color: *color,
outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
outline_width: 0.0,
font_size: physical_size,
font_index: tile_font_idx,
clip_rect: None,
layer: UiLayer::DockIndicator,
z_index: base_z + 175,
character_colors: None,
});
}
}
}
}