Scroll to the focused preference from its unscrolled position
This commit is contained in:
42
src/app.rs
42
src/app.rs
@@ -1013,6 +1013,18 @@ impl App {
|
|||||||
pub(crate) fn subscription(&self) -> Subscription<Message> {
|
pub(crate) fn subscription(&self) -> Subscription<Message> {
|
||||||
let mut subscriptions = vec![
|
let mut subscriptions = vec![
|
||||||
keyboard::on_key_press(shortcut),
|
keyboard::on_key_press(shortcut),
|
||||||
|
// A focused text field takes escape for itself to drop its own
|
||||||
|
// focus, so a dialog would never see it through `on_key_press`.
|
||||||
|
iced::event::listen_with(|event, _, _| {
|
||||||
|
matches!(
|
||||||
|
event,
|
||||||
|
iced::Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
key: keyboard::Key::Named(keyboard::key::Named::Escape),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then_some(Message::DismissPanel)
|
||||||
|
}),
|
||||||
window::close_requests().map(Message::WindowClosed),
|
window::close_requests().map(Message::WindowClosed),
|
||||||
window::close_events().map(Message::WindowClosed),
|
window::close_events().map(Message::WindowClosed),
|
||||||
];
|
];
|
||||||
@@ -1207,7 +1219,6 @@ fn shortcut(key: keyboard::Key, modifiers: keyboard::Modifiers) -> Option<Messag
|
|||||||
keyboard::Key::Character("m") if modifiers.command() && modifiers.shift() => {
|
keyboard::Key::Character("m") if modifiers.command() && modifiers.shift() => {
|
||||||
Some(Message::OpenModelManager)
|
Some(Message::OpenModelManager)
|
||||||
}
|
}
|
||||||
keyboard::Key::Named(keyboard::key::Named::Escape) => Some(Message::DismissPanel),
|
|
||||||
keyboard::Key::Named(keyboard::key::Named::Tab) => Some(if modifiers.shift() {
|
keyboard::Key::Named(keyboard::key::Named::Tab) => Some(if modifiers.shift() {
|
||||||
Message::FocusPrevious
|
Message::FocusPrevious
|
||||||
} else {
|
} else {
|
||||||
@@ -1308,12 +1319,12 @@ fn reveal_focused() -> Task<Message> {
|
|||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
content_bounds: Rectangle,
|
content_bounds: Rectangle,
|
||||||
_translation: Vector,
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
if id != Some(&Id::from(preferences_scroll_id())) {
|
if id != Some(&Id::from(preferences_scroll_id())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(offset) = reveal_offset(self.field, bounds) else {
|
let Some(offset) = reveal_offset(self.field, bounds, translation.y) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
state.scroll_by(
|
state.scroll_by(
|
||||||
@@ -1331,14 +1342,19 @@ fn reveal_focused() -> Task<Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// How far the scroll area has to move for `field` to sit fully inside
|
/// How far the scroll area has to move for `field` to sit fully inside
|
||||||
/// `viewport`, or `None` when it already does. A field taller than the
|
/// `viewport`, or `None` when it already does. A field taller than the viewport
|
||||||
/// viewport lines up with its top edge.
|
/// lines up with its top edge.
|
||||||
fn reveal_offset(field: iced::Rectangle, viewport: iced::Rectangle) -> Option<f32> {
|
///
|
||||||
|
/// Layout places the content in its own unscrolled coordinates, so `field` only
|
||||||
|
/// says where it sits once `scrolled` — the offset the area is already at — is
|
||||||
|
/// taken off it.
|
||||||
|
fn reveal_offset(field: iced::Rectangle, viewport: iced::Rectangle, scrolled: f32) -> Option<f32> {
|
||||||
// Keep a row's worth of margin, so a revealed field never sits flush
|
// Keep a row's worth of margin, so a revealed field never sits flush
|
||||||
// against the edge of the viewport.
|
// against the edge of the viewport.
|
||||||
const MARGIN: f32 = 12.0;
|
const MARGIN: f32 = 12.0;
|
||||||
let above = field.y - MARGIN - viewport.y;
|
let top = field.y - scrolled;
|
||||||
let below = field.y + field.height + MARGIN - (viewport.y + viewport.height);
|
let above = top - MARGIN - viewport.y;
|
||||||
|
let below = top + field.height + MARGIN - (viewport.y + viewport.height);
|
||||||
if above < 0.0 {
|
if above < 0.0 {
|
||||||
Some(above)
|
Some(above)
|
||||||
} else if below > 0.0 {
|
} else if below > 0.0 {
|
||||||
@@ -1464,11 +1480,15 @@ mod tests {
|
|||||||
width: 700.0,
|
width: 700.0,
|
||||||
height: 30.0,
|
height: 30.0,
|
||||||
};
|
};
|
||||||
assert_eq!(reveal_offset(field(200.0), viewport), None);
|
assert_eq!(reveal_offset(field(200.0), viewport, 0.0), None);
|
||||||
// Above the fold: scroll back by the gap plus the margin.
|
// Above the fold: scroll back by the gap plus the margin.
|
||||||
assert_eq!(reveal_offset(field(80.0), viewport), Some(-32.0));
|
assert_eq!(reveal_offset(field(80.0), viewport, 0.0), Some(-32.0));
|
||||||
// Below it: the bottom edge plus the margin comes into view.
|
// Below it: the bottom edge plus the margin comes into view.
|
||||||
assert_eq!(reveal_offset(field(490.0), viewport), Some(32.0));
|
assert_eq!(reveal_offset(field(490.0), viewport, 0.0), Some(32.0));
|
||||||
|
// A field the scroll already brought into view stays put, and one
|
||||||
|
// further down moves by the gap alone, not by the whole scroll.
|
||||||
|
assert_eq!(reveal_offset(field(690.0), viewport, 500.0), None);
|
||||||
|
assert_eq!(reveal_offset(field(990.0), viewport, 500.0), Some(32.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user