use std::{hash::Hash, sync::Arc};
use crate::{color::*, containers::*, layout::*, paint::*, widgets::*, *};
pub struct Ui {
ctx: Arc<Context>,
id: Id,
layer: Layer,
clip_rect: Rect,
desired_rect: Rect,
child_bounds: Rect,
style: Style,
layout: Layout,
cursor: Pos2,
}
impl Ui {
pub fn new(ctx: Arc<Context>, layer: Layer, id: Id, rect: Rect) -> Self {
let style = ctx.style();
Ui {
ctx,
id,
layer,
clip_rect: rect.expand(style.clip_rect_margin),
desired_rect: rect,
child_bounds: Rect::from_min_size(rect.min, Vec2::zero()),
style,
layout: Default::default(),
cursor: rect.min,
}
}
pub fn child_ui(&self, child_rect: Rect) -> Self {
let clip_rect = self.clip_rect();
Ui {
ctx: self.ctx.clone(),
id: self.id,
layer: self.layer,
clip_rect,
desired_rect: child_rect,
child_bounds: Rect::from_min_size(child_rect.min, Vec2::zero()),
style: self.style.clone(),
layout: self.layout,
cursor: child_rect.min,
}
}
pub fn round_to_pixel(&self, point: f32) -> f32 {
self.ctx.round_to_pixel(point)
}
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
self.ctx.round_vec_to_pixels(vec)
}
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
self.ctx.round_pos_to_pixels(pos)
}
pub fn id(&self) -> Id {
self.id
}
pub fn style(&self) -> &Style {
&self.style
}
pub fn set_style(&mut self, style: Style) {
self.style = style
}
pub fn ctx(&self) -> &Arc<Context> {
&self.ctx
}
pub fn input(&self) -> &InputState {
self.ctx.input()
}
pub fn memory(&self) -> parking_lot::MutexGuard<'_, Memory> {
self.ctx.memory()
}
pub fn output(&self) -> parking_lot::MutexGuard<'_, Output> {
self.ctx.output()
}
pub fn fonts(&self) -> &Fonts {
self.ctx.fonts()
}
pub fn clip_rect(&self) -> Rect {
self.clip_rect
}
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
self.clip_rect = clip_rect;
}
pub fn top_left(&self) -> Pos2 {
self.desired_rect.min.min(self.child_bounds.min)
}
pub fn bottom_right(&self) -> Pos2 {
self.desired_rect.max.max(self.child_bounds.max)
}
pub fn rect(&self) -> Rect {
Rect::from_min_max(self.top_left(), self.bottom_right())
}
pub fn rect_finite(&self) -> Rect {
let mut bottom_right = self.child_bounds.max;
if self.desired_rect.max.x.is_finite() {
bottom_right.x = bottom_right.x.max(self.desired_rect.max.x);
}
if self.desired_rect.max.y.is_finite() {
bottom_right.y = bottom_right.y.max(self.desired_rect.max.y);
}
Rect::from_min_max(self.top_left(), bottom_right)
}
pub fn set_desired_width(&mut self, width: f32) {
let min_width = self.child_bounds.max.x - self.top_left().x;
let width = width.max(min_width);
self.desired_rect.max.x = self.top_left().x + width;
}
pub fn set_desired_height(&mut self, height: f32) {
let min_height = self.child_bounds.max.y - self.top_left().y;
let height = height.max(min_height);
self.desired_rect.max.y = self.top_left().y + height;
}
pub fn bounding_size(&self) -> Vec2 {
self.child_bounds.size()
}
pub fn expand_to_include_child(&mut self, rect: Rect) {
self.child_bounds.extend_with(rect.min);
self.child_bounds.extend_with(rect.max);
}
pub fn expand_to_size(&mut self, size: Vec2) {
self.child_bounds.extend_with(self.top_left() + size);
}
pub fn child_bounds(&self) -> Rect {
self.child_bounds
}
pub fn force_set_child_bounds(&mut self, child_bounds: Rect) {
self.child_bounds = child_bounds;
}
pub fn available(&self) -> Rect {
self.layout.available(self.cursor, self.rect())
}
pub fn available_finite(&self) -> Rect {
self.layout.available(self.cursor, self.rect_finite())
}
pub fn layout(&self) -> &Layout {
&self.layout
}
pub fn set_layout(&mut self, layout: Layout) {
self.layout = layout;
if layout.is_reversed() {
self.cursor = self.rect_finite().max;
}
}
pub fn contains_mouse(&self, rect: Rect) -> bool {
self.ctx.contains_mouse(self.layer, self.clip_rect, rect)
}
pub fn has_kb_focus(&self, id: Id) -> bool {
self.memory().kb_focus_id == Some(id)
}
pub fn request_kb_focus(&self, id: Id) {
self.memory().kb_focus_id = Some(id);
}
pub fn make_unique_child_id<IdSource>(&self, id_source: IdSource) -> Id
where
IdSource: Hash + std::fmt::Debug,
{
let id = self.id.with(&id_source);
self.ctx.register_unique_id(id, id_source, self.cursor)
}
pub fn make_position_id(&self) -> Id {
self.id.with(&Id::from_pos(self.cursor))
}
pub fn make_child_id(&self, id_seed: impl Hash) -> Id {
self.id.with(id_seed)
}
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> InteractInfo {
self.ctx
.interact(self.layer, self.clip_rect, rect, Some(id), sense)
}
pub fn interact_hover(&self, rect: Rect) -> InteractInfo {
self.ctx
.interact(self.layer, self.clip_rect, rect, None, Sense::nothing())
}
pub fn hovered(&self, rect: Rect) -> bool {
self.interact_hover(rect).hovered
}
#[must_use]
pub fn response(&mut self, interact: InteractInfo) -> GuiResponse {
GuiResponse {
hovered: interact.hovered,
clicked: interact.clicked,
double_clicked: interact.double_clicked,
active: interact.active,
rect: interact.rect,
ctx: self.ctx.clone(),
}
}
pub fn allocate_space(&mut self, child_size: Vec2) -> Rect {
let child_size = self.round_vec_to_pixels(child_size);
self.cursor = self.round_pos_to_pixels(self.cursor);
let too_wide = child_size.x > self.available().width();
let too_high = child_size.x > self.available().height();
let rect = self.reserve_space_impl(child_size);
if self.style().debug_widget_rects {
self.add_paint_cmd(PaintCmd::Rect {
rect,
corner_radius: 0.0,
outline: Some(LineStyle::new(1.0, LIGHT_BLUE)),
fill: None,
});
let color = color::srgba(200, 0, 0, 255);
let width = 2.5;
let mut paint_line_seg =
|a, b| self.add_paint_cmd(PaintCmd::line_segment([a, b], color, width));
if too_wide {
paint_line_seg(rect.left_top(), rect.left_bottom());
paint_line_seg(rect.left_center(), rect.right_center());
paint_line_seg(rect.right_top(), rect.right_bottom());
}
if too_high {
paint_line_seg(rect.left_top(), rect.right_top());
paint_line_seg(rect.center_top(), rect.center_bottom());
paint_line_seg(rect.left_bottom(), rect.right_bottom());
}
}
rect
}
fn reserve_space_impl(&mut self, child_size: Vec2) -> Rect {
let available_size = self.available_finite().size();
let child_rect =
self.layout
.allocate_space(&mut self.cursor, &self.style, available_size, child_size);
self.child_bounds = self.child_bounds.union(child_rect);
child_rect
}
pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) {
self.ctx
.graphics()
.layer(self.layer)
.push((self.clip_rect(), paint_cmd))
}
pub fn add_paint_cmds(&mut self, mut cmds: Vec<PaintCmd>) {
let clip_rect = self.clip_rect();
self.ctx
.graphics()
.layer(self.layer)
.extend(cmds.drain(..).map(|cmd| (clip_rect, cmd)));
}
pub fn insert_paint_cmd(&mut self, pos: usize, paint_cmd: PaintCmd) {
self.ctx
.graphics()
.layer(self.layer)
.insert(pos, (self.clip_rect(), paint_cmd));
}
pub fn paint_list_len(&self) -> usize {
self.ctx.graphics().layer(self.layer).len()
}
pub fn debug_text(&self, text: impl Into<String>) {
self.debug_text_at(self.cursor, text);
}
pub fn debug_text_at(&self, pos: Pos2, text: impl Into<String>) {
self.ctx.debug_text(pos, text);
}
pub fn debug_rect(&mut self, rect: Rect, text: impl Into<String>) {
self.add_paint_cmd(PaintCmd::Rect {
corner_radius: 0.0,
fill: None,
outline: Some(LineStyle::new(1.0, color::RED)),
rect,
});
let align = (Align::Min, Align::Min);
let text_style = TextStyle::Monospace;
self.floating_text(rect.min, text.into(), text_style, align, Some(color::RED));
}
pub fn floating_text(
&mut self,
pos: Pos2,
text: impl Into<String>,
text_style: TextStyle,
align: (Align, Align),
text_color: Option<Color>,
) -> Rect {
let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text.into(), f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_galley(rect.min, galley, text_style, text_color);
rect
}
pub fn add_galley(
&mut self,
pos: Pos2,
galley: font::Galley,
text_style: TextStyle,
color: Option<Color>,
) {
let color = color.unwrap_or_else(|| self.style().text_color);
self.add_paint_cmd(PaintCmd::Text {
pos,
galley,
text_style,
color,
});
}
pub fn add(&mut self, widget: impl Widget) -> GuiResponse {
let interact = widget.ui(self);
self.response(interact)
}
pub fn label(&mut self, label: impl Into<Label>) -> GuiResponse {
self.add(label.into())
}
pub fn hyperlink(&mut self, url: impl Into<String>) -> GuiResponse {
self.add(Hyperlink::new(url))
}
pub fn button(&mut self, text: impl Into<String>) -> GuiResponse {
self.add(Button::new(text))
}
pub fn checkbox(&mut self, text: impl Into<String>, checked: &mut bool) -> GuiResponse {
self.add(Checkbox::new(checked, text))
}
pub fn radio(&mut self, text: impl Into<String>, checked: bool) -> GuiResponse {
self.add(RadioButton::new(checked, text))
}
pub fn separator(&mut self) -> GuiResponse {
self.add(Separator::new())
}
pub fn collapsing<R>(
&mut self,
text: impl Into<String>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
CollapsingHeader::new(text).show(self, add_contents)
}
pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Ui)) -> Rect {
let size = size.min(self.available().size());
let child_rect = Rect::from_min_size(self.cursor, size);
let mut child_ui = self.child_ui(child_rect);
add_contents(&mut child_ui);
self.allocate_space(child_ui.bounding_size())
}
pub fn add_custom<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
let child_rect = self.available();
let mut child_ui = self.child_ui(child_rect);
let r = add_contents(&mut child_ui);
let size = child_ui.bounding_size();
(r, self.allocate_space(size))
}
pub fn indent<R>(
&mut self,
id_source: impl Hash,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> (R, Rect) {
assert!(
self.layout().dir() == Direction::Vertical,
"You can only indent vertical layouts"
);
let indent = vec2(self.style.indent, 0.0);
let child_rect = Rect::from_min_max(self.cursor + indent, self.bottom_right());
let mut child_ui = Ui {
id: self.id.with(id_source),
..self.child_ui(child_rect)
};
let ret = add_contents(&mut child_ui);
let size = child_ui.bounding_size();
let line_start = child_rect.min - indent * 0.5;
let line_start = self.round_pos_to_pixels(line_start);
let line_end = pos2(line_start.x, line_start.y + size.y - 2.0);
self.add_paint_cmd(PaintCmd::line_segment(
[line_start, line_end],
gray(150, 255),
self.style.line_width,
));
(ret, self.allocate_space(indent + size))
}
pub fn left_column(&mut self, width: f32) -> Ui {
self.column(Align::Min, width)
}
pub fn centered_column(&mut self, width: f32) -> Ui {
self.column(Align::Center, width)
}
pub fn right_column(&mut self, width: f32) -> Ui {
self.column(Align::Max, width)
}
pub fn column(&mut self, column_position: Align, width: f32) -> Ui {
let x = match column_position {
Align::Min => 0.0,
Align::Center => self.available().width() / 2.0 - width / 2.0,
Align::Max => self.available().width() - width,
};
self.child_ui(Rect::from_min_size(
self.cursor + vec2(x, 0.0),
vec2(width, self.available().height()),
))
}
pub fn horizontal<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
self.inner_layout(Layout::horizontal(Align::Min), add_contents)
}
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
self.inner_layout(Layout::vertical(Align::Min), add_contents)
}
pub fn inner_layout<R>(
&mut self,
layout: Layout,
add_contents: impl FnOnce(&mut Self) -> R,
) -> (R, Rect) {
let child_rect = Rect::from_min_max(self.cursor, self.bottom_right());
let mut child_ui = Self {
..self.child_ui(child_rect)
};
child_ui.set_layout(layout);
let ret = add_contents(&mut child_ui);
let size = child_ui.bounding_size();
let rect = self.allocate_space(size);
(ret, rect)
}
pub fn columns<F, R>(&mut self, num_columns: usize, add_contents: F) -> R
where
F: FnOnce(&mut [Self]) -> R,
{
let spacing = self.style.item_spacing.x;
let total_spacing = spacing * (num_columns as f32 - 1.0);
let column_width = (self.available().width() - total_spacing) / (num_columns as f32);
let mut columns: Vec<Self> = (0..num_columns)
.map(|col_idx| {
let pos = self.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0);
let child_rect =
Rect::from_min_max(pos, pos2(pos.x + column_width, self.bottom_right().y));
Self {
id: self.make_child_id(&("column", col_idx)),
..self.child_ui(child_rect)
}
})
.collect();
let result = add_contents(&mut columns[..]);
let mut sum_width = total_spacing;
for column in &columns {
sum_width += column.child_bounds.width();
}
let mut max_height = 0.0;
for ui in columns {
let size = ui.bounding_size();
max_height = size.y.max(max_height);
}
let size = vec2(self.available().width().max(sum_width), max_height);
self.allocate_space(size);
result
}
}