157 lines
4.7 KiB
Rust
157 lines
4.7 KiB
Rust
![]() |
use crate::{layout::Direction, *};
|
||
|
|
||
|
#[derive(Clone, Copy, Debug)]
|
||
|
pub(crate) struct State {
|
||
|
pub open: bool,
|
||
|
pub toggle_time: f64,
|
||
|
}
|
||
|
|
||
|
impl Default for State {
|
||
|
fn default() -> Self {
|
||
|
Self {
|
||
|
open: false,
|
||
|
toggle_time: -std::f64::INFINITY,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct CollapsingHeader {
|
||
|
title: String,
|
||
|
default_open: bool,
|
||
|
}
|
||
|
|
||
|
impl CollapsingHeader {
|
||
|
pub fn new(title: impl Into<String>) -> Self {
|
||
|
Self {
|
||
|
title: title.into(),
|
||
|
default_open: false,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn default_open(mut self) -> Self {
|
||
|
self.default_open = true;
|
||
|
self
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl CollapsingHeader {
|
||
|
pub fn show(self, region: &mut Region, add_contents: impl FnOnce(&mut Region)) -> GuiResponse {
|
||
|
assert!(
|
||
|
region.dir == Direction::Vertical,
|
||
|
"Horizontal collapsing is unimplemented"
|
||
|
);
|
||
|
let Self {
|
||
|
title,
|
||
|
default_open,
|
||
|
} = self;
|
||
|
|
||
|
let id = region.make_unique_id(&title);
|
||
|
let text_style = TextStyle::Button;
|
||
|
let font = ®ion.fonts()[text_style];
|
||
|
let (title, text_size) = font.layout_multiline(&title, region.available_width());
|
||
|
let text_cursor = region.cursor + region.style.button_padding;
|
||
|
let interact = region.reserve_space(
|
||
|
vec2(
|
||
|
region.available_width(),
|
||
|
text_size.y + 2.0 * region.style.button_padding.y,
|
||
|
),
|
||
|
Some(id),
|
||
|
);
|
||
|
|
||
|
let state = {
|
||
|
let mut memory = region.ctx.memory.lock();
|
||
|
let mut state = memory.collapsing_headers.entry(id).or_insert(State {
|
||
|
open: default_open,
|
||
|
..Default::default()
|
||
|
});
|
||
|
if interact.clicked {
|
||
|
state.open = !state.open;
|
||
|
state.toggle_time = region.ctx.input.time;
|
||
|
}
|
||
|
*state
|
||
|
};
|
||
|
|
||
|
region.add_paint_cmd(PaintCmd::Rect {
|
||
|
corner_radius: region.style.interact_corner_radius(&interact),
|
||
|
fill_color: region.style.interact_fill_color(&interact),
|
||
|
outline: region.style().interact_outline(&interact),
|
||
|
rect: interact.rect,
|
||
|
});
|
||
|
|
||
|
paint_icon(region, &state, &interact);
|
||
|
|
||
|
region.add_text(
|
||
|
text_cursor + vec2(region.style.start_icon_width, 0.0),
|
||
|
text_style,
|
||
|
title,
|
||
|
Some(region.style.interact_stroke_color(&interact)),
|
||
|
);
|
||
|
|
||
|
let animation_time = region.style().animation_time;
|
||
|
let time_since_toggle = (region.ctx.input.time - state.toggle_time) as f32;
|
||
|
if time_since_toggle < animation_time {
|
||
|
region.indent(id, |region| {
|
||
|
// animation time
|
||
|
|
||
|
let max_height = if state.open {
|
||
|
remap(
|
||
|
time_since_toggle,
|
||
|
0.0,
|
||
|
animation_time,
|
||
|
50.0, // Get instant feedback
|
||
|
1500.0, // We don't expect to get bigger than this
|
||
|
)
|
||
|
} else {
|
||
|
remap_clamp(
|
||
|
time_since_toggle,
|
||
|
0.0,
|
||
|
animation_time,
|
||
|
50.0, // TODO: state.open_height
|
||
|
0.0,
|
||
|
)
|
||
|
};
|
||
|
|
||
|
region
|
||
|
.clip_rect
|
||
|
.set_height(region.clip_rect.height().min(max_height));
|
||
|
|
||
|
add_contents(region);
|
||
|
|
||
|
region.bounding_size.y = region.bounding_size.y.min(max_height);
|
||
|
});
|
||
|
} else if state.open {
|
||
|
region.indent(id, add_contents);
|
||
|
}
|
||
|
|
||
|
region.response(interact)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn paint_icon(region: &mut Region, state: &State, interact: &InteractInfo) {
|
||
|
let stroke_color = region.style.interact_stroke_color(&interact);
|
||
|
let stroke_width = region.style.interact_stroke_width(&interact);
|
||
|
|
||
|
let (small_icon_rect, _) = region.style.icon_rectangles(&interact.rect);
|
||
|
// Draw a minus:
|
||
|
region.add_paint_cmd(PaintCmd::Line {
|
||
|
points: vec![
|
||
|
pos2(small_icon_rect.min().x, small_icon_rect.center().y),
|
||
|
pos2(small_icon_rect.max().x, small_icon_rect.center().y),
|
||
|
],
|
||
|
color: stroke_color,
|
||
|
width: stroke_width,
|
||
|
});
|
||
|
|
||
|
if !state.open {
|
||
|
// Draw it as a plus:
|
||
|
region.add_paint_cmd(PaintCmd::Line {
|
||
|
points: vec![
|
||
|
pos2(small_icon_rect.center().x, small_icon_rect.min().y),
|
||
|
pos2(small_icon_rect.center().x, small_icon_rect.max().y),
|
||
|
],
|
||
|
color: stroke_color,
|
||
|
width: stroke_width,
|
||
|
});
|
||
|
}
|
||
|
}
|