Skip to content

Commit

Permalink
Scroll to view (#1976)
Browse files Browse the repository at this point in the history
* added SCROLL_TO selector, implemented handling of SCROLL_TO notification in Scroll, improved documentation

Signed-off-by: Christoph <[email protected]>

* added context methods `scroll_to_view` and `scroll_area_to_view`

Signed-off-by: Christoph <[email protected]>

* removed scroll_to_* methods from LayoutCtx

Signed-off-by: Christoph <[email protected]>

* fixed logic errors,
refactored scroll.rs

Signed-off-by: Christoph <[email protected]>

* updated CHANGELOG.md

Signed-off-by: Christoph <[email protected]>

* import default_scroll_to_view_handling

Signed-off-by: Christoph <[email protected]>

* add a different implementation for `scroll_area_to_view` in EventCtx, to make it more efficient

Signed-off-by: Christoph <[email protected]>

* docs and refactoring

Signed-off-by: Christoph <[email protected]>

* reformat

Signed-off-by: Christoph <[email protected]>

* fixed docs

Signed-off-by: Christoph <[email protected]>

* - refactored
- show scrollbar when clip is moved

Signed-off-by: Christoph <[email protected]>

* changing id to kebab case

Signed-off-by: Christoph <[email protected]>

* changed back ordering

Signed-off-by: Christoph <[email protected]>

* make scroll to view on FocusChange manual

Signed-off-by: Christoph <[email protected]>

* reformat

Signed-off-by: Christoph <[email protected]>

* Update druid/src/contexts.rs

Co-authored-by: Manmeet Maan <[email protected]>

* reformat

Signed-off-by: Christoph <[email protected]>

Co-authored-by: Christoph <[email protected]>
Co-authored-by: Manmeet Maan <[email protected]>
  • Loading branch information
3 people authored Oct 2, 2021
1 parent 70e0b97 commit db680a7
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ You can find its changes [documented below](#070---2021-01-01).
- Add #[data(eq)] shorthand attribute for Data derive macro ([#1884] by [@Maan2003])
- X11: detect keyboard layout ([#1779] by [@Maan2003])
- WindowDesc::with_config ([#1929] by [@Maan2003])
- `scroll_to_view` and `scroll_area_to_view` methods on `UpdateCtx`, `LifecycleCtx` and `EventCtx` ([#1976] by [@xarvic])
- `Notification::route` ([#1978] by [@xarvic])
- Build on OpenBSD ([#1993] by [@klemensn])

Expand Down Expand Up @@ -798,6 +799,7 @@ Last release without a changelog :(
[#1947]: https://github.com/linebender/druid/pull/1947
[#1953]: https://github.com/linebender/druid/pull/1953
[#1967]: https://github.com/linebender/druid/pull/1967
[#1976]: https://github.com/linebender/druid/pull/1976
[#1978]: https://github.com/linebender/druid/pull/1978
[#1993]: https://github.com/linebender/druid/pull/1993

Expand Down
18 changes: 17 additions & 1 deletion druid/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub mod sys {
use super::Selector;
use crate::{
sub_window::{SubWindowDesc, SubWindowUpdate},
FileDialogOptions, FileInfo, SingleUse, WidgetId, WindowConfig,
FileDialogOptions, FileInfo, Rect, SingleUse, WidgetId, WindowConfig,
};

/// Quit the running application. This command is handled by the druid library.
Expand Down Expand Up @@ -329,6 +329,22 @@ pub mod sys {
pub(crate) const INVALIDATE_IME: Selector<ImeInvalidation> =
Selector::new("druid-builtin.invalidate-ime");

/// Informs this widget, that a child wants a specific region to be shown. The payload is the
/// requested region in global coordinates.
///
/// This notification is send when [`scroll_to_view`] or [`scroll_area_to_view`]
/// are called.
///
/// Widgets which hide their children, should always call `ctx.set_handled()` in response to
/// avoid unintended behaviour from widgets further down the tree.
/// If possible the widget should move its children to bring the area into view and then submit
/// a new notification with the region translated by the amount, the child it contained was
/// translated.
///
/// [`scroll_to_view`]: crate::EventCtx::scroll_to_view()
/// [`scroll_area_to_view`]: crate::EventCtx::scroll_area_to_view()
pub const SCROLL_TO_VIEW: Selector<Rect> = Selector::new("druid-builtin.scroll-to");

/// A change that has occured to text state, and needs to be
/// communicated to the platform.
pub(crate) struct ImeInvalidation {
Expand Down
68 changes: 68 additions & 0 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use std::{
};
use tracing::{error, trace, warn};

use crate::commands::SCROLL_TO_VIEW;
use crate::core::{CommandQueue, CursorChange, FocusChange, WidgetState};
use crate::env::KeyLike;
use crate::menu::ContextMenu;
Expand Down Expand Up @@ -455,6 +456,20 @@ impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, LifeCycleCtx<'_, '_>,
self.submit_command(commands::NEW_SUB_WINDOW.with(SingleUse::new(req)));
window_id
}

/// Scrolls this widget into view.
///
/// If this widget is only partially visible or not visible at all because of [`Scroll`]s
/// it is wrapped in, they will do the minimum amount of scrolling necessary to bring this
/// widget fully into view.
///
/// If the widget is [`hidden`], this method has no effect.
///
/// [`Scroll`]: crate::widget::Scroll
/// [`hidden`]: crate::Event::should_propagate_to_hidden
pub fn scroll_to_view(&mut self) {
self.scroll_area_to_view(self.size().to_rect())
}
});

// methods on everyone but paintctx
Expand Down Expand Up @@ -688,6 +703,21 @@ impl EventCtx<'_, '_> {
trace!("request_update");
self.widget_state.request_update = true;
}

/// Scrolls the area into view.
///
/// If the area is only partially visible or not visible at all because of [`Scroll`]s
/// this widget is wrapped in, they will do the minimum amount of scrolling necessary to
/// bring the area fully into view.
///
/// If the widget is [`hidden`], this method has no effect.
///
/// [`Scroll`]: crate::widget::Scroll
/// [`hidden`]: crate::Event::should_propagate_to_hidden
pub fn scroll_area_to_view(&mut self, area: Rect) {
//TODO: only do something if this widget is not hidden
self.submit_notification(SCROLL_TO_VIEW.with(area + self.window_origin().to_vec2()));
}
}

impl UpdateCtx<'_, '_> {
Expand Down Expand Up @@ -727,6 +757,25 @@ impl UpdateCtx<'_, '_> {
None => false,
}
}

/// Scrolls the area into view.
///
/// If the area is only partially visible or not visible at all because of [`Scroll`]s
/// this widget is wrapped in, they will do the minimum amount of scrolling necessary to
/// bring the area fully into view.
///
/// If the widget is [`hidden`], this method has no effect.
///
/// [`Scroll`]: crate::widget::Scroll
/// [`hidden`]: crate::Event::should_propagate_to_hidden
pub fn scroll_area_to_view(&mut self, area: Rect) {
//TODO: only do something if this widget is not hidden
self.submit_command(Command::new(
SCROLL_TO_VIEW,
area + self.window_origin().to_vec2(),
self.widget_id(),
));
}
}

impl LifeCycleCtx<'_, '_> {
Expand Down Expand Up @@ -762,6 +811,25 @@ impl LifeCycleCtx<'_, '_> {
};
self.state.text_registrations.push(registration);
}

/// Scrolls the area into view.
///
/// If the area is only partially visible or not visible at all because of [`Scroll`]s
/// this widget is wrapped in, they will do the minimum amount of scrolling necessary to
/// bring the area fully into view.
///
/// If the widget is [`hidden`], this method has no effect.
///
/// [`Scroll`]: crate::widget::Scroll
/// [`hidden`]: crate::Event::should_propagate_to_hidden
pub fn scroll_area_to_view(&mut self, area: Rect) {
//TODO: only do something if this widget is not hidden
self.submit_command(
SCROLL_TO_VIEW
.with(area + self.window_origin().to_vec2())
.to(self.widget_id()),
);
}
}

impl LayoutCtx<'_, '_> {
Expand Down
8 changes: 8 additions & 0 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use tracing::{info_span, trace, warn};

use crate::bloom::Bloom;
use crate::command::sys::{CLOSE_WINDOW, SUB_WINDOW_HOST_TO_PARENT, SUB_WINDOW_PARENT_TO_HOST};
use crate::commands::SCROLL_TO_VIEW;
use crate::contexts::ContextState;
use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size, Vec2};
use crate::sub_window::SubWindowUpdate;
Expand Down Expand Up @@ -843,6 +844,13 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
}
ctx.is_handled = true
}
Event::Command(cmd) if cmd.is(SCROLL_TO_VIEW) => {
// Submit the SCROLL_TO notification if it was used from a update or lifecycle
// call.
let rect = cmd.get_unchecked(SCROLL_TO_VIEW);
inner_ctx.submit_notification(SCROLL_TO_VIEW.with(*rect));
ctx.is_handled = true;
}
_ => {
self.inner.event(&mut inner_ctx, inner_event, data, env);

Expand Down
35 changes: 34 additions & 1 deletion druid/src/widget/clip_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::commands::SCROLL_TO_VIEW;
use crate::debug_state::DebugState;
use crate::kurbo::{Affine, Point, Rect, Size, Vec2};
use crate::widget::prelude::*;
Expand All @@ -24,7 +25,7 @@ use tracing::{instrument, trace};
pub struct Viewport {
/// The size of the area that we have a viewport into.
pub content_size: Size,
/// The origin of the view rectangle.
/// The origin of the view rectangle, relative to the content.
pub view_origin: Point,
/// The size of the view rectangle.
pub view_size: Size,
Expand Down Expand Up @@ -308,6 +309,38 @@ impl<T, W: Widget<T>> ClipBox<T, W> {
self.child
.set_viewport_offset(self.viewport_origin().to_vec2());
}

/// The default handling of the [`SCROLL_TO_VIEW`] notification for a scrolling container.
///
/// The [`SCROLL_TO_VIEW`] notification is send when [`scroll_to_view`] or [`scroll_area_to_view`]
/// are called.
///
/// [`SCROLL_TO_VIEW`]: crate::commands::SCROLL_TO_VIEW
/// [`scroll_to_view`]: crate::EventCtx::scroll_to_view()
/// [`scroll_area_to_view`]: crate::EventCtx::scroll_area_to_view()
pub fn default_scroll_to_view_handling(
&mut self,
ctx: &mut EventCtx,
global_highlight_rect: Rect,
) -> bool {
let mut viewport_changed = false;
self.with_port(|port| {
let global_content_offset = ctx.window_origin().to_vec2() - port.view_origin.to_vec2();
let content_highlight_rect = global_highlight_rect - global_content_offset;

if port.pan_to_visible(content_highlight_rect) {
ctx.request_paint();
viewport_changed = true;
}

// This is a new value since view_origin has changed in the meantime
let global_content_offset = ctx.window_origin().to_vec2() - port.view_origin.to_vec2();
ctx.submit_notification(
SCROLL_TO_VIEW.with(content_highlight_rect + global_content_offset),
);
});
viewport_changed
}
}

impl<T: Data, W: Widget<T>> Widget<T> for ClipBox<T, W> {
Expand Down
19 changes: 19 additions & 0 deletions druid/src/widget/scroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//! A container that scrolls its contents.

use crate::commands::SCROLL_TO_VIEW;
use crate::debug_state::DebugState;
use crate::widget::prelude::*;
use crate::widget::{Axis, ClipBox};
Expand Down Expand Up @@ -184,9 +185,27 @@ impl<T: Data, W: Widget<T>> Widget<T> for Scroll<T, W> {
self.clip.event(ctx, event, data, env);
}

// Handle scroll after the inner widget processed the events, to prefer inner widgets while
// scrolling.
self.clip.with_port(|port| {
scroll_component.handle_scroll(port, ctx, event, env);
});

if !self.scroll_component.are_bars_held() {
// We only scroll to the component if the user is not trying to move the scrollbar.
if let Event::Notification(notification) = event {
if let Some(&global_highlight_rect) = notification.get(SCROLL_TO_VIEW) {
ctx.set_handled();
let view_port_changed = self
.clip
.default_scroll_to_view_handling(ctx, global_highlight_rect);
if view_port_changed {
self.scroll_component
.reset_scrollbar_fade(|duration| ctx.request_timer(duration), env);
}
}
}
}
}

#[instrument(name = "Scroll", level = "trace", skip(self, ctx, event, data, env))]
Expand Down
1 change: 1 addition & 0 deletions druid/src/widget/textbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
self.reset_cursor_blink(ctx.request_timer(CURSOR_BLINK_DURATION));
self.was_focused_from_click = false;
ctx.request_paint();
ctx.scroll_to_view();
}
LifeCycle::FocusChanged(false) => {
if self.text().can_write() && MAC_OR_LINUX_OR_OBSD && !self.multiline {
Expand Down

0 comments on commit db680a7

Please sign in to comment.