1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 06:10:06 -04:00

Add ScrollArea::content_margin (#7722)

* Part of https://github.com/emilk/egui/issues/5605
* Part of https://github.com/emilk/egui/issues/3385
This commit is contained in:
Emil Ernerfeldt
2025-11-18 15:30:02 +01:00
committed by GitHub
parent f74b7c7e79
commit 178f3c9198
2 changed files with 52 additions and 3 deletions

View File

@@ -1,8 +1,11 @@
//! See [`ScrollArea`] for docs.
#![allow(clippy::needless_range_loop)] #![allow(clippy::needless_range_loop)]
use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
use emath::GuiRounding as _; use emath::GuiRounding as _;
use epaint::Margin;
use crate::{ use crate::{
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
@@ -258,7 +261,7 @@ impl AddAssign for ScrollSource {
/// ### Coordinate system /// ### Coordinate system
/// * content: size of contents (generally large; that's why we want scroll bars) /// * content: size of contents (generally large; that's why we want scroll bars)
/// * outer: size of scroll area including scroll bar(s) /// * outer: size of scroll area including scroll bar(s)
/// * inner: excluding scroll bar(s). The area we clip the contents to. /// * inner: excluding scroll bar(s). The area we clip the contents to. Includes `content_margin`.
/// ///
/// If the floating scroll bars settings is turned on then `inner == outer`. /// If the floating scroll bars settings is turned on then `inner == outer`.
/// ///
@@ -294,6 +297,8 @@ pub struct ScrollArea {
scroll_source: ScrollSource, scroll_source: ScrollSource,
wheel_scroll_multiplier: Vec2, wheel_scroll_multiplier: Vec2,
content_margin: Option<Margin>,
/// If true for vertical or horizontal the scroll wheel will stick to the /// If true for vertical or horizontal the scroll wheel will stick to the
/// end position until user manually changes position. It will become true /// end position until user manually changes position. It will become true
/// again once scroll handle makes contact with end. /// again once scroll handle makes contact with end.
@@ -346,6 +351,7 @@ impl ScrollArea {
on_drag_cursor: None, on_drag_cursor: None,
scroll_source: ScrollSource::default(), scroll_source: ScrollSource::default(),
wheel_scroll_multiplier: Vec2::splat(1.0), wheel_scroll_multiplier: Vec2::splat(1.0),
content_margin: None,
stick_to_end: Vec2b::FALSE, stick_to_end: Vec2b::FALSE,
animated: true, animated: true,
} }
@@ -593,6 +599,18 @@ impl ScrollArea {
self.direction_enabled[0] || self.direction_enabled[1] self.direction_enabled[0] || self.direction_enabled[1]
} }
/// Extra margin added around the contents.
///
/// The scroll bars will be either on top of this margin, or outside of it,
/// depending on the value of [`crate::style::ScrollStyle::floating`].
///
/// Default: [`crate::style::ScrollStyle::content_margin`].
#[inline]
pub fn content_margin(mut self, margin: impl Into<Margin>) -> Self {
self.content_margin = Some(margin.into());
self
}
/// The scroll handle will stick to the rightmost position even while the content size /// The scroll handle will stick to the rightmost position even while the content size
/// changes dynamically. This can be useful to simulate text scrollers coming in from right /// changes dynamically. This can be useful to simulate text scrollers coming in from right
/// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck" /// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck"
@@ -644,7 +662,7 @@ struct Prepared {
scroll_bar_visibility: ScrollBarVisibility, scroll_bar_visibility: ScrollBarVisibility,
scroll_bar_rect: Option<Rect>, scroll_bar_rect: Option<Rect>,
/// Where on the screen the content is (excludes scroll bars). /// Where on the screen the content is (excludes scroll bars; includes `content_margin`).
inner_rect: Rect, inner_rect: Rect,
content_ui: Ui, content_ui: Ui,
@@ -683,6 +701,7 @@ impl ScrollArea {
on_drag_cursor, on_drag_cursor,
scroll_source, scroll_source,
wheel_scroll_multiplier, wheel_scroll_multiplier,
content_margin: _, // Used elsewhere
stick_to_end, stick_to_end,
animated, animated,
} = self; } = self;
@@ -983,10 +1002,21 @@ impl ScrollArea {
ui: &mut Ui, ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui, Rect) -> R + 'c>, add_contents: Box<dyn FnOnce(&mut Ui, Rect) -> R + 'c>,
) -> ScrollAreaOutput<R> { ) -> ScrollAreaOutput<R> {
let margin = self
.content_margin
.unwrap_or_else(|| ui.spacing().scroll.content_margin);
let mut prepared = self.begin(ui); let mut prepared = self.begin(ui);
let id = prepared.id; let id = prepared.id;
let inner_rect = prepared.inner_rect; let inner_rect = prepared.inner_rect;
let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
let inner = crate::Frame::NONE
.inner_margin(margin)
.show(&mut prepared.content_ui, |ui| {
add_contents(ui, prepared.viewport)
})
.inner;
let (content_size, state) = prepared.end(ui); let (content_size, state) = prepared.end(ui);
ScrollAreaOutput { ScrollAreaOutput {
inner, inner,

View File

@@ -508,6 +508,12 @@ pub struct ScrollStyle {
/// it more promiment. /// it more promiment.
pub floating: bool, pub floating: bool,
/// Extra margin added around the contents of a [`crate::ScrollArea`].
///
/// The scroll bars will be either on top of this margin, or outside of it,
/// depending on the value of [`Self::floating`].
pub content_margin: Margin,
/// The width of the scroll bars at it largest. /// The width of the scroll bars at it largest.
pub bar_width: f32, pub bar_width: f32,
@@ -591,6 +597,7 @@ impl ScrollStyle {
pub fn solid() -> Self { pub fn solid() -> Self {
Self { Self {
floating: false, floating: false,
content_margin: Margin::ZERO,
bar_width: 6.0, bar_width: 6.0,
handle_min_length: 12.0, handle_min_length: 12.0,
bar_inner_margin: 4.0, bar_inner_margin: 4.0,
@@ -672,6 +679,9 @@ impl ScrollStyle {
pub fn details_ui(&mut self, ui: &mut Ui) { pub fn details_ui(&mut self, ui: &mut Ui) {
let Self { let Self {
floating, floating,
content_margin,
bar_width, bar_width,
handle_min_length, handle_min_length,
bar_inner_margin, bar_inner_margin,
@@ -695,6 +705,11 @@ impl ScrollStyle {
ui.selectable_value(floating, true, "Floating"); ui.selectable_value(floating, true, "Floating");
}); });
ui.horizontal(|ui| {
ui.label("Content margin:");
content_margin.ui(ui);
});
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(DragValue::new(bar_width).range(0.0..=32.0)); ui.add(DragValue::new(bar_width).range(0.0..=32.0));
ui.label("Full bar width"); ui.label("Full bar width");
@@ -1824,6 +1839,10 @@ impl Spacing {
ui.add(window_margin); ui.add(window_margin);
ui.end_row(); ui.end_row();
ui.label("ScrollArea margin");
scroll.content_margin.ui(ui);
ui.end_row();
ui.label("Menu margin"); ui.label("Menu margin");
ui.add(menu_margin); ui.add(menu_margin);
ui.end_row(); ui.end_row();