use crate::ecs::text::components::VerticalAlignment;
use crate::ecs::ui::types::Rect;
use crate::ecs::world::World;
use crate::render::wgpu::passes::geometry::UiLayer;
use nalgebra_glm::{Vec2, Vec4};
use super::ShellState;
pub fn shell_retained_ui<C>(shell: &mut ShellState<C>, world: &mut World) {
if !shell.should_render() {
return;
}
let screen_size = world
.resources
.window
.handle
.as_ref()
.map(|handle| {
let size = handle.inner_size();
Vec2::new(size.width as f32, size.height as f32)
})
.unwrap_or(Vec2::new(1920.0, 1080.0));
let screen_width = screen_size.x;
let screen_height = screen_size.y;
let mouse_pos = Vec2::new(
world.resources.input.mouse.position.x,
world.resources.input.mouse.position.y,
);
let mouse_down = world
.resources
.input
.mouse
.state
.contains(crate::ecs::world::resources::MouseState::LEFT_CLICKED);
let mouse_just_pressed = world
.resources
.input
.mouse
.state
.contains(crate::ecs::world::resources::MouseState::LEFT_JUST_PRESSED);
let min_height = 100.0;
let max_height = screen_height * 0.9;
shell.height = shell.height.clamp(min_height, max_height);
let shell_height = shell.height;
let current_height = shell_height * shell.animation_progress;
let resize_handle_height = 8.0;
let resize_area = Rect::new(
0.0,
current_height - resize_handle_height,
screen_width,
resize_handle_height * 2.0,
);
let mouse_in_resize = resize_area.contains(mouse_pos) && shell.animation_progress > 0.95;
if mouse_in_resize && mouse_just_pressed && !shell.dragging_resize {
shell.dragging_resize = true;
shell.drag_start_y = mouse_pos.y;
shell.drag_start_height = shell.height;
}
if shell.dragging_resize {
if mouse_down {
let delta = mouse_pos.y - shell.drag_start_y;
shell.height = (shell.drag_start_height + delta).clamp(min_height, max_height);
} else {
shell.dragging_resize = false;
}
}
let bg_color = Vec4::new(0.06, 0.06, 0.08, 0.95);
let header_color = Vec4::new(0.39, 0.78, 0.39, 1.0);
let command_color = Vec4::new(0.59, 0.78, 1.0, 1.0);
let output_color = Vec4::new(0.78, 0.78, 0.78, 1.0);
let border_color = Vec4::new(0.4, 0.4, 0.45, 1.0);
let transparent = Vec4::new(0.0, 0.0, 0.0, 0.0);
let panel_clip = Rect::new(0.0, 0.0, screen_width, current_height);
let ui = &mut world.resources.retained_ui;
ui.draw_overlay_rect(crate::render::wgpu::passes::geometry::UiRect {
position: Vec2::new(0.0, 0.0),
size: Vec2::new(screen_width, current_height),
color: bg_color,
corner_radius: 0.0,
border_width: 0.0,
border_color: transparent,
rotation: 0.0,
clip_rect: Some(panel_clip),
layer: UiLayer::Tooltips,
z_index: 0,
shadow: None,
effect_kind: 0,
effect_params: [0.0; 4],
quad_corners: None,
});
let padding = 10.0;
let font_size = 14.0;
let line_height_ratio = 1.4_f32;
let line_height = (font_size * line_height_ratio).round();
let input_height = 32.0;
let separator_height = 1.0;
let scrollbar_width = 8.0;
let scrollbar_inset = 2.0;
let output_top_padding = 8.0;
let output_bottom_padding = 4.0;
let bottom_separator_y = current_height - input_height - separator_height;
ui.draw_overlay_rect(crate::render::wgpu::passes::geometry::UiRect {
position: Vec2::new(0.0, bottom_separator_y),
size: Vec2::new(screen_width, separator_height),
color: border_color,
corner_radius: 0.0,
border_width: 0.0,
border_color: transparent,
rotation: 0.0,
clip_rect: Some(panel_clip),
layer: UiLayer::Tooltips,
z_index: 1,
shadow: None,
effect_kind: 0,
effect_params: [0.0; 4],
quad_corners: None,
});
let output_top = output_top_padding;
let output_bottom = bottom_separator_y - output_bottom_padding;
let output_height = (output_bottom - output_top).max(0.0);
let output_clip = Rect::new(0.0, output_top, screen_width, output_height);
let mut total_content_height = 0.0;
for line in &shell.output {
let line_count = if line.text.is_empty() {
1
} else {
line.text.split('\n').count()
};
total_content_height += line_count as f32 * line_height;
}
let max_scroll = (total_content_height - output_height).max(0.0);
if shell.scroll_to_bottom {
shell.scroll_offset = max_scroll;
shell.scroll_to_bottom = false;
}
let pointer_in_output =
output_height > 0.0 && output_clip.contains(mouse_pos) && shell.animation_progress > 0.95;
if pointer_in_output {
let wheel_y = world.resources.input.mouse.wheel_delta.y;
if wheel_y.abs() > 0.0 {
shell.scroll_offset -= wheel_y * line_height * 3.0;
}
}
let scrollbar_visible = output_height > 0.0 && total_content_height > output_height;
let scrollbar_x = screen_width - scrollbar_width - scrollbar_inset;
let scrollbar_track_y = output_top;
let scrollbar_track_height = output_height;
let scrollbar_track = Rect::new(
scrollbar_x,
scrollbar_track_y,
scrollbar_width,
scrollbar_track_height,
);
let thumb_height = if scrollbar_visible {
(output_height / total_content_height) * scrollbar_track_height
} else {
scrollbar_track_height
}
.max(20.0);
if scrollbar_visible
&& mouse_just_pressed
&& scrollbar_track.contains(mouse_pos)
&& shell.animation_progress > 0.95
{
shell.dragging_scrollbar = true;
let thumb_y = scrollbar_track_y
+ if max_scroll > 0.0 {
(shell.scroll_offset / max_scroll) * (scrollbar_track_height - thumb_height)
} else {
0.0
};
shell.scrollbar_drag_grab_offset =
if mouse_pos.y >= thumb_y && mouse_pos.y < thumb_y + thumb_height {
mouse_pos.y - thumb_y
} else {
thumb_height * 0.5
};
}
if shell.dragging_scrollbar {
if mouse_down {
let target_thumb_top = mouse_pos.y - shell.scrollbar_drag_grab_offset;
let max_thumb_top = scrollbar_track_height - thumb_height;
let normalized = if max_thumb_top > 0.0 {
((target_thumb_top - scrollbar_track_y) / max_thumb_top).clamp(0.0, 1.0)
} else {
0.0
};
shell.scroll_offset = normalized * max_scroll;
} else {
shell.dragging_scrollbar = false;
}
}
shell.scroll_offset = shell.scroll_offset.clamp(0.0, max_scroll);
if output_height > 0.0 {
let mut y_pos = output_top - shell.scroll_offset;
for line in &shell.output {
let color = if line.is_command {
command_color
} else {
output_color
};
let segments: Vec<&str> = if line.text.is_empty() {
vec![""]
} else {
line.text.split('\n').collect()
};
for text_line in segments {
if y_pos + line_height > output_top && y_pos < output_bottom {
ui.draw_overlay_text(
text_line,
Vec2::new(padding, y_pos),
crate::ecs::text::components::TextProperties {
font_size,
color,
vertical_alignment: VerticalAlignment::Top,
line_height: line_height_ratio,
..Default::default()
},
Some(output_clip),
UiLayer::Tooltips,
2,
);
}
y_pos += line_height;
}
}
}
if scrollbar_visible {
ui.draw_overlay_rect(crate::render::wgpu::passes::geometry::UiRect {
position: Vec2::new(scrollbar_x, scrollbar_track_y),
size: Vec2::new(scrollbar_width, scrollbar_track_height),
color: Vec4::new(0.12, 0.12, 0.14, 0.6),
corner_radius: scrollbar_width * 0.5,
border_width: 0.0,
border_color: transparent,
rotation: 0.0,
clip_rect: Some(panel_clip),
layer: UiLayer::Tooltips,
z_index: 3,
shadow: None,
effect_kind: 0,
effect_params: [0.0; 4],
quad_corners: None,
});
let thumb_normalized = if max_scroll > 0.0 {
shell.scroll_offset / max_scroll
} else {
0.0
};
let thumb_y =
scrollbar_track_y + thumb_normalized * (scrollbar_track_height - thumb_height);
let thumb_color = if shell.dragging_scrollbar {
Vec4::new(0.7, 0.85, 1.0, 0.95)
} else if scrollbar_track.contains(mouse_pos) && shell.animation_progress > 0.95 {
Vec4::new(0.55, 0.6, 0.7, 0.95)
} else {
Vec4::new(0.4, 0.42, 0.5, 0.85)
};
ui.draw_overlay_rect(crate::render::wgpu::passes::geometry::UiRect {
position: Vec2::new(scrollbar_x, thumb_y),
size: Vec2::new(scrollbar_width, thumb_height),
color: thumb_color,
corner_radius: scrollbar_width * 0.5,
border_width: 0.0,
border_color: transparent,
rotation: 0.0,
clip_rect: Some(panel_clip),
layer: UiLayer::Tooltips,
z_index: 4,
shadow: None,
effect_kind: 0,
effect_params: [0.0; 4],
quad_corners: None,
});
}
let input_text_height = font_size * line_height_ratio;
let input_y = current_height - input_height + (input_height - input_text_height) * 0.5;
ui.draw_overlay_text(
">",
Vec2::new(padding, input_y),
crate::ecs::text::components::TextProperties {
font_size,
color: header_color,
vertical_alignment: VerticalAlignment::Top,
line_height: line_height_ratio,
..Default::default()
},
Some(panel_clip),
UiLayer::Tooltips,
2,
);
let cursor = if shell.visible && shell.animation_progress > 0.9 {
"_"
} else {
""
};
let input_display = format!("{}{}", shell.input_buffer, cursor);
ui.draw_overlay_text(
&input_display,
Vec2::new(padding + font_size + 4.0, input_y),
crate::ecs::text::components::TextProperties {
font_size,
color: output_color,
vertical_alignment: VerticalAlignment::Top,
line_height: line_height_ratio,
..Default::default()
},
Some(panel_clip),
UiLayer::Tooltips,
2,
);
ui.draw_overlay_rect(crate::render::wgpu::passes::geometry::UiRect {
position: Vec2::new(0.0, current_height - 2.0),
size: Vec2::new(screen_width, 2.0),
color: border_color,
corner_radius: 0.0,
border_width: 0.0,
border_color: transparent,
rotation: 0.0,
clip_rect: Some(panel_clip),
layer: UiLayer::Tooltips,
z_index: 3,
shadow: None,
effect_kind: 0,
effect_params: [0.0; 4],
quad_corners: None,
});
if shell.animation_progress > 0.95 {
let handle_color = if shell.dragging_resize || mouse_in_resize {
Vec4::new(0.6, 0.8, 1.0, 1.0)
} else {
Vec4::new(0.5, 0.5, 0.55, 0.8)
};
let handle_width = 40.0;
let handle_height_px = 3.0;
ui.draw_overlay_rect(crate::render::wgpu::passes::geometry::UiRect {
position: Vec2::new((screen_width - handle_width) * 0.5, current_height + 4.0),
size: Vec2::new(handle_width, handle_height_px),
color: handle_color,
corner_radius: 1.5,
border_width: 0.0,
border_color: transparent,
rotation: 0.0,
clip_rect: None,
layer: UiLayer::Tooltips,
z_index: 10,
shadow: None,
effect_kind: 0,
effect_params: [0.0; 4],
quad_corners: None,
});
}
if shell.visible && shell.animation_progress > 0.9 {
if shell.pending_enter {
shell.pending_enter = false;
shell.execute_command(world);
}
if shell.pending_up {
shell.pending_up = false;
shell.history_up();
}
if shell.pending_down {
shell.pending_down = false;
shell.history_down();
}
if shell.pending_escape {
shell.pending_escape = false;
shell.visible = false;
}
}
}