
# Scrolling

## Introduction

*WIP.* Scrolling is functional. In-app drag-and-drop works end to end: the event
loop generates `DragStart` / `Drag` / `DragEnd` on the source node and
`DragEnter` / `DragOver` / `DragLeave` / `Drop` on drop targets, with
`accept_drop()` / `set_drop_effect()` / `get_drag_data()` available in the
callbacks. What is still platform-gated is *OS-level* drag-and-drop:
`DroppedFile` (files dragged in from another application) is wired on Windows
(`WM_DROPFILES`) but not yet on X11 (XDND), Wayland (`wl_data_device`), or
macOS (dragging destination) — and dragging data *out* of an azul window is not
implemented on any platform. The page covers both subjects because they share
the input pipeline.

Scrolling and drag are the two ways a pointer interacts with the layout rather than with a single element. Both are gestures: the framework tracks the mouse across multiple events, decides which gesture is in progress, and emits high-level events you handle the same way as any other event filter from [events](events.md).

## Making a node scrollable

CSS does the work. Set `overflow` to `scroll`, `auto`, or `hidden` on a node whose content exceeds its box, and the framework gives it scrollbars and wires the wheel/trackpad/touchpad events to it.

```rust,no_run
use azul::prelude::*;
let scroll_box = Dom::create_div()
    .with_css("width: 400px; height: 200px; overflow-y: scroll;");
```

- `visible` (default). Children draw outside the box. No clip, no scrollbar, no `Scroll` events.
- `hidden`. Children clip to the box. No scrollbar. No scrolling.
- `scroll`. Children clip. Scrollbar always visible, even if not needed. Wheel scrolls.
- `auto`. Children clip. Scrollbar visible only when content overflows. Wheel scrolls.

`overflow-x` and `overflow-y` set the axes independently. The shorthand `overflow: scroll` sets both.

The container's scroll-clip size is its inner box (border-box minus borders, padding, and scrollbar track). The content size is the total extent of its children. Scrolling shifts which slice of the content sits inside the clip.

### What you don't get

- **No viewport-level scrolling.** The window itself doesn't scroll. Make `<body>` or a descendant the scroll container.
- **No automatic scroll-into-view on focus.** Call `info.scroll_to(...)` from a focus callback if you want this.

## Reading scroll events

`HoverEventFilter::Scroll` fires for wheel/trackpad/touch scroll over a scrollable node. `ScrollStart` and `ScrollEnd` bracket a contiguous gesture so you can debounce work.

```rust,no_run
use azul::prelude::*;

extern "C" fn on_scroll(_: RefAny, _: CallbackInfo) -> Update {
    Update::DoNothing
}

fn build(data: RefAny) -> Dom {
    let mut node = Dom::create_div();
    node.add_callback(
        EventFilter::Hover(HoverEventFilter::Scroll),
        data,
        on_scroll,
    );
    node
}
```

Inside the callback, query the current scroll state through `CallbackInfo`:

```rust,ignore
impl CallbackInfo {
    pub fn get_scroll_offset(&self) -> Option<LogicalPosition>;
    pub fn get_scroll_offset_for_node(&self, dom_id: DomId, node_id: NodeId)
        -> Option<LogicalPosition>;
    pub fn get_scroll_state(&self, /* ... */) -> Option<ScrollState>;
    pub fn get_scroll_delta(&self) -> LogicalPosition;
    pub fn had_scroll_activity(&self) -> bool;
}
```

`get_scroll_offset()` returns the offset for the hit node, which is convenient inside a `Scroll` callback. `ScrollState::scroll_position` is the live position.

## Scrolling programmatically

`CallbackInfo::scroll_to` queues a scroll that the runtime applies after the callback returns:

```rust,ignore
impl CallbackInfo {
    pub fn scroll_to(&mut self, /* ... */);
}
```

The position is in the scroll container's content space, so `(0, 0)` scrolls to the top-left of the content.

## Hit-test order

Front-to-back. The topmost element under the cursor is hit first; deeper nodes are tried only if the topmost doesn't claim the event. A scroll routes to the topmost scrollable ancestor of the hit node, not to whichever scroll container the cursor happens to sit inside. This matches browser behaviour: a button's `cursor: pointer` overrides the container's `cursor: text`, and a `<select>` swallows wheel events that would otherwise scroll the page.

## Smooth and momentum scrolling

The framework animates between scroll positions when `scroll_to` is called. Trackpad and wheel deltas are accumulated frame-to-frame; momentum drives the inertia phase after a fling. You don't manage the timer.

CSS `-azul-overflow-scrolling: touch` enables momentum on a node. `overscroll-behavior: contain` prevents scroll chaining to the parent.

## Drag and drop

The drag system handles selection drags, scrollbar thumb drags, window moves, window resizes, OS file drops, and DOM-node drags. The framework handles the first four for you. Node and file drops are the variants you write callbacks for.

### Marking a node draggable

Set the `draggable` attribute on the DOM node. The gesture manager sees it during hit-test and starts a drag when the user begins one.

```rust,no_run
use azul::prelude::*;
let card = Dom::create_div()
    .with_attribute(AttributeType::Draggable(true))
    .with_css("padding: 12px; background: #fef;");
```

### Drag events

- `HoverEventFilter::DragStart` (source node). Drag begins. Set drag data here.
- `HoverEventFilter::Drag` (source node). Each cursor move during drag.
- `HoverEventFilter::DragEnd` (source node). Drag ends, on drop or cancel.
- `HoverEventFilter::DragEnter` (target node). Cursor enters a candidate drop zone.
- `HoverEventFilter::DragOver` (target node). Cursor stays over a drop zone, throttled.
- `HoverEventFilter::DragLeave` (target node). Cursor leaves a drop zone.
- `HoverEventFilter::Drop` (target node). User releases on a drop zone that has accepted the drop.
- `HoverEventFilter::DroppedFile` (hit node). OS file drop landed.

Each variant has a corresponding `FocusEventFilter::*` and `WindowEventFilter::*`. Use `Hover` for "this specific node is the drop zone". Use `Window` to observe drags anywhere in the window.

### Drag data: set on start, read on drop

`DragData` is a MIME-keyed payload that mirrors the W3C `DataTransfer` API:

```rust,ignore
impl CallbackInfo {
    /// W3C dataTransfer.setData(type, data). Call from a DragStart callback.
    pub fn set_drag_data(&mut self, /* ... */);

    /// W3C dataTransfer.types: visible during DragOver.
    pub fn get_drag_types(&self) -> /* ... */;

    /// W3C dataTransfer.getData(type): only readable inside the Drop handler.
    pub fn get_drag_data(&self, /* ... */) -> Option</* ... */>;
}
```

Multiple MIME types per drag are allowed. Set the same payload as `text/plain` for foreign drop targets and as your own `application/x-myapp-task` for the structured drop.

### Accepting (or rejecting) a drop

Drop targets opt in. Inside a `DragEnter` or `DragOver` callback, inspect `get_drag_types()`. If the data is for you, call `accept_drop()` and set the drop effect:

```rust,ignore
impl CallbackInfo {
    /// Equivalent to event.preventDefault() in a W3C dragover handler.
    pub fn accept_drop(&mut self);

    /// W3C dataTransfer.dropEffect.
    pub fn set_drop_effect(&mut self, effect: DropEffect);
}
```

A node that doesn't call `accept_drop()` isn't a drop target. The cursor shows the no-drop indicator and `Drop` won't fire. This matches the W3C model.

### Drop and DragEnd

`Drop` fires once on the accepted target with the drop position and the data. `DragEnd` fires once on the source, even if the drop was cancelled or rejected, so the source can finish the move/copy/link operation.

### DropEffect and DragEffect

Two related enums:

- `DragEffect`. What the source allows. Set on the drag context at `DragStart`. Variants: `Uninitialized`, `None`, `Copy`, `CopyLink`, `CopyMove`, `Link`, `LinkMove`, `Move`, `All`.
- `DropEffect`. What the target chose. Set in `DragOver` or `DragEnter`. Variants: `None`, `Copy`, `Link`, `Move`.

A drop target's `DropEffect` must be in the source's `DragEffect` set, otherwise the drop is rejected.

### Drag query methods

```rust,ignore
impl CallbackInfo {
    pub fn is_drag_active(&self) -> bool;
    pub fn is_dragging(&self) -> bool;
    pub fn is_node_drag_active(&self) -> bool;
    pub fn is_file_drag_active(&self) -> bool;
    pub fn get_drag_state(&self) -> Option<DragState>;
    pub fn get_drag_delta(&self) -> DragDelta;
    pub fn get_dragged_node(&self) -> Option<DomNodeId>;
}
```

`DragState` carries `drag_type`, `source_node`, `current_drop_target`, and `file_path` for file drops.

## Drag-and-drop status

- **Done.** `DragData` MIME map, `DragEffect` and `DropEffect` enums, drag-state query, and `accept_drop`, `set_drop_effect`, `get_drag_types`, `get_drag_data` on `CallbackInfo`.
- **Not done.** `DragEnter`, `DragOver`, `DragLeave`, `Drop` event generation by the event loop. The filters exist; the loop doesn't synthesize the events. The `Drop` handler won't fire today.
- **Not done.** Visual drag feedback (transform the source node to follow the cursor, opacity dim).
- **Not done.** CSS pseudo-classes `:dragging`, `:drag-over`, `:drag-over-invalid` for styling source and target during a drag.

Code that follows the API described above will start working as the missing event-generation lands without needing a rewrite.

## File drops from the OS

OS-level file drops work through the same drag pipeline. Your `DroppedFile` callback reads the file list:

```rust,no_run
use azul::prelude::*;

extern "C" fn on_dropped_file(_: RefAny, info: CallbackInfo) -> Update {
    if let Some(_path) = info.get_dropped_file() {
        // ... open the file ...
    }
    Update::RefreshDom
}
```

`info.get_dropped_file()` returns the dropped file path. `info.get_hovered_file()` returns the path while the file is being dragged over the window.

## Cross-references

- [`events`](events.md): the event filter system this page builds on.
- [`timers`](timers.md): scrolling momentum and drag auto-scroll run on reserved timers.

## Coming Up Next

- [Events](events.md) — Callbacks, event filters, and how state triggers relayout
- [Text Selection](text-selection.md) — Selection ranges, cursors, and copy/paste
- [Virtual Views](dom/virtual-views.md) — A node that materialises lazily, for infinite lists and embedded sub-DOMs
