Control Separator
widths, and less clipping in ScrollArea
(#2665)
* Add Separator::grow and Separator::shrink * Be more conservative with the clipping in ScrollArea:s * Add test of the growing separator * Improve test output * Update changelog * Add back a little bit more clipping * Make the minimum scroll handle length a bit longer * More clip rect tweaks
This commit is contained in:
parent
8c59888ebd
commit
a6b60d5d58
6 changed files with 75 additions and 11 deletions
|
@ -25,12 +25,14 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
* Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)).
|
* Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)).
|
||||||
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
|
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
|
||||||
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
|
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
|
||||||
|
* Add `Separator::grow` and `Separator::shrink` ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
||||||
* Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`.
|
* Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`.
|
||||||
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
|
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
|
||||||
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
|
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
|
||||||
|
* `ScrollArea` is less aggressive about clipping its contents ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
|
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
|
||||||
|
|
|
@ -426,15 +426,34 @@ impl ScrollArea {
|
||||||
|
|
||||||
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
|
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
|
||||||
let mut content_ui = ui.child_ui(content_max_rect, *ui.layout());
|
let mut content_ui = ui.child_ui(content_max_rect, *ui.layout());
|
||||||
let mut content_clip_rect = inner_rect.expand(ui.visuals().clip_rect_margin);
|
|
||||||
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
|
{
|
||||||
// Nice handling of forced resizing beyond the possible:
|
// Clip the content, but only when we really need to:
|
||||||
for d in 0..2 {
|
let clip_rect_margin = ui.visuals().clip_rect_margin;
|
||||||
if !has_bar[d] {
|
let scroll_bar_inner_margin = ui.spacing().scroll_bar_inner_margin;
|
||||||
content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
|
let mut content_clip_rect = ui.clip_rect();
|
||||||
|
for d in 0..2 {
|
||||||
|
if has_bar[d] {
|
||||||
|
if state.content_is_too_large[d] {
|
||||||
|
content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin;
|
||||||
|
content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.show_scroll[d] {
|
||||||
|
// Make sure content doesn't cover scroll bars
|
||||||
|
let tiny_gap = 1.0;
|
||||||
|
content_clip_rect.max[1 - d] =
|
||||||
|
inner_rect.max[1 - d] + scroll_bar_inner_margin - tiny_gap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Nice handling of forced resizing beyond the possible:
|
||||||
|
content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Make sure we din't accidentally expand the clip rect
|
||||||
|
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
|
||||||
|
content_ui.set_clip_rect(content_clip_rect);
|
||||||
}
|
}
|
||||||
content_ui.set_clip_rect(content_clip_rect);
|
|
||||||
|
|
||||||
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
||||||
|
|
||||||
|
@ -822,7 +841,7 @@ impl Prepared {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let min_handle_size = ui.spacing().scroll_bar_width;
|
let min_handle_size = ui.spacing().scroll_handle_min_length;
|
||||||
if handle_rect.size()[d] < min_handle_size {
|
if handle_rect.size()[d] < min_handle_size {
|
||||||
handle_rect = Rect::from_center_size(
|
handle_rect = Rect::from_center_size(
|
||||||
handle_rect.center(),
|
handle_rect.center(),
|
||||||
|
|
|
@ -300,6 +300,9 @@ pub struct Spacing {
|
||||||
|
|
||||||
pub scroll_bar_width: f32,
|
pub scroll_bar_width: f32,
|
||||||
|
|
||||||
|
/// Make sure the scroll handle is at least this big
|
||||||
|
pub scroll_handle_min_length: f32,
|
||||||
|
|
||||||
/// Margin between contents and scroll bar.
|
/// Margin between contents and scroll bar.
|
||||||
pub scroll_bar_inner_margin: f32,
|
pub scroll_bar_inner_margin: f32,
|
||||||
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
||||||
|
@ -713,6 +716,7 @@ impl Default for Spacing {
|
||||||
tooltip_width: 600.0,
|
tooltip_width: 600.0,
|
||||||
combo_height: 200.0,
|
combo_height: 200.0,
|
||||||
scroll_bar_width: 8.0,
|
scroll_bar_width: 8.0,
|
||||||
|
scroll_handle_min_length: 12.0,
|
||||||
scroll_bar_inner_margin: 4.0,
|
scroll_bar_inner_margin: 4.0,
|
||||||
scroll_bar_outer_margin: 0.0,
|
scroll_bar_outer_margin: 0.0,
|
||||||
indent_ends_with_horizontal_line: false,
|
indent_ends_with_horizontal_line: false,
|
||||||
|
@ -1040,6 +1044,7 @@ impl Spacing {
|
||||||
indent_ends_with_horizontal_line,
|
indent_ends_with_horizontal_line,
|
||||||
combo_height,
|
combo_height,
|
||||||
scroll_bar_width,
|
scroll_bar_width,
|
||||||
|
scroll_handle_min_length,
|
||||||
scroll_bar_inner_margin,
|
scroll_bar_inner_margin,
|
||||||
scroll_bar_outer_margin,
|
scroll_bar_outer_margin,
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -1072,6 +1077,10 @@ impl Spacing {
|
||||||
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
||||||
ui.label("Scroll-bar width");
|
ui.label("Scroll-bar width");
|
||||||
});
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(DragValue::new(scroll_handle_min_length).clamp_range(0.0..=32.0));
|
||||||
|
ui.label("Scroll-bar handle min length");
|
||||||
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0));
|
ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0));
|
||||||
ui.label("Scroll-bar inner margin");
|
ui.label("Scroll-bar inner margin");
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::*;
|
||||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||||
pub struct Separator {
|
pub struct Separator {
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
|
grow: f32,
|
||||||
is_horizontal_line: Option<bool>,
|
is_horizontal_line: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ impl Default for Separator {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
spacing: 6.0,
|
spacing: 6.0,
|
||||||
|
grow: 0.0,
|
||||||
is_horizontal_line: None,
|
is_horizontal_line: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,12 +30,19 @@ impl Default for Separator {
|
||||||
|
|
||||||
impl Separator {
|
impl Separator {
|
||||||
/// How much space we take up. The line is painted in the middle of this.
|
/// How much space we take up. The line is painted in the middle of this.
|
||||||
|
///
|
||||||
|
/// In a vertical layout, with a horizontal Separator,
|
||||||
|
/// this is the height of the separator widget.
|
||||||
|
///
|
||||||
|
/// In a horizontal layout, with a vertical Separator,
|
||||||
|
/// this is the width of the separator widget.
|
||||||
pub fn spacing(mut self, spacing: f32) -> Self {
|
pub fn spacing(mut self, spacing: f32) -> Self {
|
||||||
self.spacing = spacing;
|
self.spacing = spacing;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Explicitly ask for a horizontal line.
|
/// Explicitly ask for a horizontal line.
|
||||||
|
///
|
||||||
/// By default you will get a horizontal line in vertical layouts,
|
/// By default you will get a horizontal line in vertical layouts,
|
||||||
/// and a vertical line in horizontal layouts.
|
/// and a vertical line in horizontal layouts.
|
||||||
pub fn horizontal(mut self) -> Self {
|
pub fn horizontal(mut self) -> Self {
|
||||||
|
@ -42,18 +51,40 @@ impl Separator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Explicitly ask for a vertical line.
|
/// Explicitly ask for a vertical line.
|
||||||
|
///
|
||||||
/// By default you will get a horizontal line in vertical layouts,
|
/// By default you will get a horizontal line in vertical layouts,
|
||||||
/// and a vertical line in horizontal layouts.
|
/// and a vertical line in horizontal layouts.
|
||||||
pub fn vertical(mut self) -> Self {
|
pub fn vertical(mut self) -> Self {
|
||||||
self.is_horizontal_line = Some(false);
|
self.is_horizontal_line = Some(false);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend each end of the separator line by this much.
|
||||||
|
///
|
||||||
|
/// The default is to take up the available width/height of the parent.
|
||||||
|
///
|
||||||
|
/// This will make the line extend outside the parent ui.
|
||||||
|
pub fn grow(mut self, extra: f32) -> Self {
|
||||||
|
self.grow += extra;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contract each end of the separator line by this much.
|
||||||
|
///
|
||||||
|
/// The default is to take up the available width/height of the parent.
|
||||||
|
///
|
||||||
|
/// This effectively adds margins to the line.
|
||||||
|
pub fn shrink(mut self, shrink: f32) -> Self {
|
||||||
|
self.grow -= shrink;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Separator {
|
impl Widget for Separator {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let Separator {
|
let Separator {
|
||||||
spacing,
|
spacing,
|
||||||
|
grow,
|
||||||
is_horizontal_line,
|
is_horizontal_line,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -75,14 +106,14 @@ impl Widget for Separator {
|
||||||
let painter = ui.painter();
|
let painter = ui.painter();
|
||||||
if is_horizontal_line {
|
if is_horizontal_line {
|
||||||
painter.hline(
|
painter.hline(
|
||||||
rect.x_range(),
|
(rect.left() - grow)..=(rect.right() + grow),
|
||||||
painter.round_to_pixel(rect.center().y),
|
painter.round_to_pixel(rect.center().y),
|
||||||
stroke,
|
stroke,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
painter.vline(
|
painter.vline(
|
||||||
painter.round_to_pixel(rect.center().x),
|
painter.round_to_pixel(rect.center().x),
|
||||||
rect.y_range(),
|
(rect.top() - grow)..=(rect.bottom() + grow),
|
||||||
stroke,
|
stroke,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,8 @@ fn lorem_ipsum(ui: &mut egui::Ui) {
|
||||||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||||
|ui| {
|
|ui| {
|
||||||
ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak());
|
ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||||
|
ui.add(egui::Separator::default().grow(8.0));
|
||||||
|
ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,8 @@ fn test_egui_zero_window_size() {
|
||||||
let clipped_primitives = ctx.tessellate(full_output.shapes);
|
let clipped_primitives = ctx.tessellate(full_output.shapes);
|
||||||
assert!(
|
assert!(
|
||||||
clipped_primitives.is_empty(),
|
clipped_primitives.is_empty(),
|
||||||
"There should be nothing to show"
|
"There should be nothing to show, has at least one primitive with clip_rect: {:?}",
|
||||||
|
clipped_primitives[0].clip_rect
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue