1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 21:30:03 -04:00

Add scroll bars to windows.

Auto-hide scroll bars when not needed

Bug fixes:
 * collapsing headers animation
 * clip rect interactions
 * clip rects for scroll areas
This commit is contained in:
Emil Ernerfeldt
2020-04-26 22:30:24 +02:00
parent 2897b1cafc
commit fbedc2e9ab
12 changed files with 103 additions and 55 deletions

View File

@@ -92,16 +92,15 @@ impl CollapsingHeader {
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 {
let animate = time_since_toggle < animation_time;
if animate {
region.indent(id, |region| {
// animation time
let max_height = if state.open {
remap(
time_since_toggle,
0.0..=animation_time,
// Get instant feedback, and we don't expect to get bigger than this
50.0..=1500.0,
100.0..=1500.0,
)
} else {
remap_clamp(
@@ -112,17 +111,12 @@ impl CollapsingHeader {
)
};
region
.clip_rect
.set_height(region.clip_rect.height().min(max_height));
region.clip_rect.max.y = region.clip_rect.max.y.min(region.cursor.y + max_height);
add_contents(region);
region.child_bounds.max.y = region
.child_bounds
.max
.y
.min(region.child_bounds.min.y + max_height);
region.child_bounds.max.y =
region.child_bounds.max.y.min(region.cursor.y + max_height);
});
} else if state.open {
region.indent(id, add_contents);

View File

@@ -72,7 +72,8 @@ impl Floating {
state.size = region.bounding_size().ceil();
let rect = Rect::from_min_size(state.pos, state.size);
let move_interact = ctx.interact(layer, &rect, Some(id.with("move")));
let clip_rect = Rect::everything();
let move_interact = ctx.interact(layer, &clip_rect, &rect, Some(id.with("move")));
if move_interact.active {
state.pos += ctx.input().mouse_move;

View File

@@ -172,7 +172,8 @@ impl Resize {
contents_region.clip_rect.max = contents_region
.clip_rect
.max
.max(contents_region.clip_rect.min + last_frame_size);
.max(contents_region.clip_rect.min + last_frame_size)
.min(region.clip_rect.max); // Respect parent region
add_contents(&mut contents_region);
contents_region.bounding_size()

View File

@@ -4,15 +4,24 @@ use crate::*;
pub struct State {
/// Positive offset means scrolling down/right
pub offset: Vec2,
pub show_scroll: bool, // TODO: default value?
}
#[derive(Clone, Debug)]
pub struct ScrollArea {
max_height: f32,
always_show_scroll: bool,
auto_hide_scroll: bool,
}
impl Default for ScrollArea {
fn default() -> Self {
Self { max_height: 200.0 }
Self {
max_height: 200.0,
always_show_scroll: false,
auto_hide_scroll: true,
}
}
}
@@ -21,6 +30,16 @@ impl ScrollArea {
self.max_height = max_height;
self
}
pub fn always_show_scroll(mut self, always_show_scroll: bool) -> Self {
self.always_show_scroll = always_show_scroll;
self
}
pub fn auto_hide_scroll(mut self, auto_hide_scroll: bool) -> Self {
self.auto_hide_scroll = auto_hide_scroll;
self
}
}
impl ScrollArea {
@@ -42,38 +61,40 @@ impl ScrollArea {
let scroll_bar_width = 16.0;
let outer_size = vec2(outer_region.available_width(), self.max_height);
let outer_size = vec2(
outer_region.available_width(),
outer_region.available_height().min(self.max_height),
);
let outer_rect = Rect::from_min_size(outer_region.cursor, outer_size);
let inner_size = outer_size - vec2(scroll_bar_width, 0.0);
let inner_size = if state.show_scroll || !self.auto_hide_scroll {
outer_size - vec2(scroll_bar_width, 0.0) // TODO: animate?
} else {
outer_size
};
let inner_rect = Rect::from_min_size(outer_region.cursor, inner_size);
let mut content_region =
outer_region.child_region(Rect::from_min_size(outer_region.cursor(), inner_size));
content_region.cursor -= state.offset;
content_region.desired_rect = content_region.desired_rect.translate(-state.offset);
let mut content_region = outer_region.child_region(Rect::from_min_size(
outer_region.cursor() - state.offset,
vec2(inner_size.x, f32::INFINITY),
));
content_region.clip_rect = outer_region.clip_rect().intersect(&inner_rect);
add_contents(&mut content_region);
let content_size = content_region.bounding_size();
let content_interact = ctx.interact(
outer_region.layer,
&inner_rect,
Some(scroll_area_id.with("area")),
);
let content_interact = outer_region.interact_rect(&inner_rect, scroll_area_id.with("area"));
if content_interact.active {
// Dragging scroll area to scroll:
state.offset.y -= ctx.input.mouse_move.y;
}
// TODO: check that nothing else is being inteacted with
if ctx.contains_mouse_pos(outer_region.layer, &outer_rect)
&& ctx.memory.lock().active_id.is_none()
{
if outer_region.contains_mouse(&outer_rect) && ctx.memory.lock().active_id.is_none() {
state.offset.y -= ctx.input.scroll_delta.y;
}
let show_scroll = content_size.y > inner_size.y;
if show_scroll {
let show_scroll_this_frame = content_size.y > inner_size.y || self.always_show_scroll;
if show_scroll_this_frame || state.show_scroll {
let left = inner_rect.right() + 2.0;
let right = outer_rect.right();
let corner_radius = (right - left) / 2.0;
@@ -94,8 +115,8 @@ impl ScrollArea {
);
// intentionally use same id for inside and outside of handle
let interact_id = Some(scroll_area_id.with("vertical"));
let handle_interact = ctx.interact(outer_region.layer, &handle_rect, interact_id);
let interact_id = scroll_area_id.with("vertical");
let handle_interact = outer_region.interact_rect(&handle_rect, interact_id);
if let Some(mouse_pos) = ctx.input.mouse_pos {
if handle_interact.active {
@@ -106,7 +127,7 @@ impl ScrollArea {
} else {
// Check for mouse down outside handle:
let scroll_bg_interact =
ctx.interact(outer_region.layer, &outer_scroll_rect, interact_id);
outer_region.interact_rect(&outer_scroll_rect, interact_id);
if scroll_bg_interact.active {
// Center scroll at mouse pos:
@@ -132,7 +153,7 @@ impl ScrollArea {
outer_region.add_paint_cmd(PaintCmd::Rect {
rect: outer_scroll_rect,
corner_radius,
fill_color: Some(color::BLACK),
fill_color: Some(color::gray(0, 196)), // TODO style
outline: None,
});
@@ -144,11 +165,12 @@ impl ScrollArea {
});
}
let size = content_size.min(content_region.clip_rect.size());
let size = content_size.min(inner_rect.size());
outer_region.reserve_space_without_padding(size);
state.offset.y = state.offset.y.max(0.0);
state.offset.y = state.offset.y.min(content_size.y - inner_rect.height());
state.offset.y = state.offset.y.max(0.0);
state.show_scroll = show_scroll_this_frame;
outer_region
.ctx()

View File

@@ -4,13 +4,14 @@ use crate::{widgets::*, *};
use super::*;
// TODO: separate out resizing into a contained and reusable Resize-region.
/// A wrapper around other containers for things you often want in a window
#[derive(Clone, Debug)]
pub struct Window {
title: String,
floating: Floating,
frame: Frame,
resize: Resize,
scroll: ScrollArea,
}
impl Window {
@@ -22,7 +23,11 @@ impl Window {
frame: Frame::default(),
resize: Resize::default()
.handle_offset(Vec2::splat(4.0))
.auto_shrink_height(true),
.auto_shrink_height(false)
.auto_expand(false),
scroll: ScrollArea::default()
.always_show_scroll(false)
.max_height(f32::INFINITY), // As large as we can be
}
}
@@ -66,15 +71,17 @@ impl Window {
floating,
frame,
resize,
scroll,
} = self;
// TODO: easier way to compose these
floating.show(ctx, |region| {
frame.show(region, |region| {
resize.show(region, |region| {
region.add(Label::new(title).text_style(TextStyle::Heading));
region.add(Separator::new().line_width(1.0)); // TODO: nicer way to split window title from contents
add_contents(region);
});
});
});
scroll.show(region, |region| add_contents(region))
})
})
})
}
}