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

Allow scroll into view without specifying an alignment (#1247)

* Allow scroll into view without specifying an alignment
* Handle case of UI being too big to fit in the scroll view
This commit is contained in:
Juan Campa
2022-02-15 10:52:29 -05:00
committed by GitHub
parent c1cd47e3a7
commit 635c65773d
6 changed files with 60 additions and 31 deletions

View File

@@ -489,18 +489,37 @@ impl Prepared {
// We take the scroll target so only this ScrollArea will use it:
let scroll_target = content_ui.ctx().frame_state().scroll_target[d].take();
if let Some((scroll, align)) = scroll_target {
let center_factor = align.to_factor();
let min = content_ui.min_rect().min[d];
let visible_range = min..=min + content_ui.clip_rect().size()[d];
let offset = scroll - lerp(visible_range, center_factor);
let clip_rect = content_ui.clip_rect();
let visible_range = min..=min + clip_rect.size()[d];
let start = *scroll.start();
let end = *scroll.end();
let clip_start = clip_rect.min[d];
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];
// Depending on the alignment we need to add or subtract the spacing
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
if let Some(align) = align {
let center_factor = align.to_factor();
state.offset[d] = offset + spacing;
let offset =
lerp(scroll, center_factor) - lerp(visible_range, center_factor);
// Depending on the alignment we need to add or subtract the spacing
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
state.offset[d] = offset + spacing;
} else if start < clip_start && end < clip_end {
let min_adjust =
(clip_start - start + spacing).min(clip_end - end - spacing);
state.offset[d] -= min_adjust;
} else if end > clip_end && start > clip_start {
let min_adjust =
(end - clip_end + spacing).min(start - clip_start - spacing);
state.offset[d] += min_adjust;
} else {
// Ui is already in view, no need to adjust scroll.
continue;
};
}
}
}

View File

@@ -1,3 +1,5 @@
use std::ops::RangeInclusive;
use crate::*;
/// State that is collected during a frame and then cleared.
@@ -28,7 +30,7 @@ pub(crate) struct FrameState {
/// Cleared by the first `ScrollArea` that makes use of it.
pub(crate) scroll_delta: Vec2, // TODO: move to a Mutex inside of `InputState` ?
/// horizontal, vertical
pub(crate) scroll_target: [Option<(f32, Align)>; 2],
pub(crate) scroll_target: [Option<(RangeInclusive<f32>, Option<Align>)>; 2],
}
impl Default for FrameState {
@@ -40,7 +42,7 @@ impl Default for FrameState {
used_by_panels: Rect::NAN,
tooltip_rect: None,
scroll_delta: Vec2::ZERO,
scroll_target: [None; 2],
scroll_target: [None, None],
}
}
}
@@ -63,7 +65,7 @@ impl FrameState {
*used_by_panels = Rect::NOTHING;
*tooltip_rect = None;
*scroll_delta = input.scroll_delta;
*scroll_target = [None; 2];
*scroll_target = [None, None];
}
/// How much space is still available after panels has been added.

View File

@@ -1,5 +1,5 @@
use crate::{
emath::{lerp, Align, Pos2, Rect, Vec2},
emath::{Align, Pos2, Rect, Vec2},
menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText,
NUM_POINTER_BUTTONS,
};
@@ -443,7 +443,10 @@ impl Response {
)
}
/// Move the scroll to this UI with the specified alignment.
/// Adjust the scroll position until this UI becomes visible. If `align` is not provided, it'll scroll enough to
/// bring the UI into view.
///
/// See also [`Ui::scroll_to_cursor`]
///
/// ```
/// # egui::__run_test_ui(|ui| {
@@ -451,18 +454,15 @@ impl Response {
/// for i in 0..1000 {
/// let response = ui.button("Scroll to me");
/// if response.clicked() {
/// response.scroll_to_me(egui::Align::Center);
/// response.scroll_to_me(Some(egui::Align::Center));
/// }
/// }
/// });
/// # });
/// ```
pub fn scroll_to_me(&self, align: Align) {
let scroll_target = lerp(self.rect.x_range(), align.to_factor());
self.ctx.frame_state().scroll_target[0] = Some((scroll_target, align));
let scroll_target = lerp(self.rect.y_range(), align.to_factor());
self.ctx.frame_state().scroll_target[1] = Some((scroll_target, align));
pub fn scroll_to_me(&self, align: Option<Align>) {
self.ctx.frame_state().scroll_target[0] = Some((self.rect.x_range(), align));
self.ctx.frame_state().scroll_target[1] = Some((self.rect.y_range(), align));
}
/// For accessibility.

View File

@@ -889,7 +889,10 @@ impl Ui {
(response, painter)
}
/// Move the scroll to this cursor position with the specified alignment.
/// Adjust the scroll position until the cursor becomes visible. If `align` is not provided, it'll scroll enough to
/// bring the cursor into view.
///
/// See also [`Response::scroll_to_me`]
///
/// ```
/// # use egui::Align;
@@ -901,15 +904,16 @@ impl Ui {
/// }
///
/// if scroll_bottom {
/// ui.scroll_to_cursor(Align::BOTTOM);
/// ui.scroll_to_cursor(Some(Align::BOTTOM));
/// }
/// });
/// # });
/// ```
pub fn scroll_to_cursor(&mut self, align: Align) {
pub fn scroll_to_cursor(&mut self, align: Option<Align>) {
let target = self.next_widget_position();
for d in 0..2 {
self.ctx().frame_state().scroll_target[d] = Some((target[d], align));
let target = target[d];
self.ctx().frame_state().scroll_target[d] = Some((target..=target, align));
}
}
}