linebender / druid

A data-first Rust-native UI design toolkit.
https://linebender.org/druid/
Apache License 2.0
9.44k stars 569 forks source link

How to make the scroll view automatically scroll when new data is added #2358

Closed jm-observer closed 1 year ago

jm-observer commented 1 year ago

How to make the scroll view automatically scroll when new data is added?

sclu1034 commented 1 year ago

I've had to figure out the same thing just now:

 pub struct AutoScrollController;

 impl<T: Data, W: Widget<T>> Controller<T, Scroll<T, W>> for AutoScrollController {
     fn update(
         &mut self,
         child: &mut Scroll<T, W>,
         ctx: &mut UpdateCtx,
         old_data: &T,
         data: &T,
         env: &Env,
     ) {
         if !ctx.is_disabled() {
             let size = child.child_size();
             let end_region = Rect::new(size.width - 1., size.height - 1., size.width, size.height);
             child.scroll_to(ctx, end_region);
         }
         child.update(ctx, old_data, data, env)
     }
 }

Then use something like WidgetExt::controller to apply that to your Scroll.

UpdateCtx::scroll_to_view exists, too, but with a List nested in Scroll, I didn't find a way to get the individual child elements to pass to that function.

jm-observer commented 1 year ago

@sclu1034 thanks! I gave it a try,it does roll, but not to the end, it will scroll to last but one. because _child.childsize() gets the current size, not the updated size that it should scroll to. i try to use Command to let it scroll, if it works, I'll get back to you

jm-observer commented 1 year ago

@sclu1034 can use Command to auto scroll:

pub struct AutoScrollController;

impl<T: druid::Data, W: Widget<T>> Controller<T, Scroll<T, W>> for AutoScrollController {
    fn event(
        &mut self,
        child: &mut Scroll<T, W>,
        _ctx: &mut EventCtx,
        event: &Event,
        data: &mut T,
        _env: &Env,
    ) {
        match event {
            Event::Command(cmd) => {
                if let Some(_) = cmd.get(SELECTOR_AUTO_SCROLL) {
                    let size = child.child_size();
                    let end_region =
                        Rect::new(size.width - 1., size.height - 1., size.width, size.height);
                    child.scroll_to(_ctx, end_region);
                }
            }
            _ => child.event(_ctx, event, data, _env),
        }
    }
}

when add elements, you should submit a command:

async fn receive_public(
    event_sink: &druid::ExtEventSink,
    index: usize,
    topic: Arc<String>,
    payload: Arc<Bytes>,
    qos: QoS,
) -> Result<()> {
    event_sink.add_idle_callback(move |data: &mut AppData| {
        data.receive_msg(index, topic, payload, qos);
    });
    sleep(Duration::from_millis(150)).await;
    event_sink.submit_command(SELECTOR_AUTO_SCROLL, (), SCROLL_MSG_ID)?;
    Ok(())
}

other code:

pub const SCROLL_MSG_ID: WidgetId = WidgetId::reserved(3);
pub const SELECTOR_AUTO_SCROLL: Selector<()> = Selector::new("scroll.auto");
……
let scroll = Scroll::<Vector<Msg>, List<Msg>>::new(list)
        .vertical()
        .controller(AutoScrollController)
        .with_id(SCROLL_MSG_ID)
……