beuvy-runtime 0.1.0

A low-level Bevy UI kit with reusable controls and utility-class styling.
Documentation
use bevy::input::mouse::MouseScrollUnit;
use bevy::picking::events::Scroll;
use bevy::prelude::*;

#[derive(Component, Default, Debug, Clone, Copy)]
pub struct MouseWheelScroll;

pub fn scroll_container_node(mut node: Node) -> Node {
    node.min_width = Val::Px(0.0);
    node.min_height = Val::Px(0.0);
    node.overflow = Overflow::scroll_y();
    node.scrollbar_width = crate::style::scrollbar_width();
    node
}

pub(crate) fn materialize_scroll_containers(
    mut commands: Commands,
    mut query: Query<
        (Entity, &mut Node, Has<MouseWheelScroll>),
        Or<(Added<Node>, Changed<Node>)>,
    >,
) {
    for (entity, mut node, has_mouse_wheel_scroll) in &mut query {
        let scroll_x = node.overflow.x == OverflowAxis::Scroll;
        let scroll_y = node.overflow.y == OverflowAxis::Scroll;
        let is_scroll_container = scroll_x || scroll_y;
        if !is_scroll_container {
            if has_mouse_wheel_scroll {
                commands.entity(entity).remove::<MouseWheelScroll>();
            }
            continue;
        }

        node.min_width = Val::Px(0.0);
        node.min_height = Val::Px(0.0);
        node.scrollbar_width = crate::style::scrollbar_width();
        commands
            .entity(entity)
            .try_insert((ScrollPosition::default(), MouseWheelScroll));
    }
}

pub(crate) fn handle_mouse_wheel_scroll(
    mut event: On<Pointer<Scroll>>,
    mut query: Query<
        (
            &mut ScrollPosition,
            &Node,
            &ComputedNode,
            Has<MouseWheelScroll>,
        ),
    >,
    keyboard_input: Res<ButtonInput<KeyCode>>,
) {
    let Ok((mut scroll_position, node, computed, enabled)) = query.get_mut(event.entity) else {
        return;
    };
    if !enabled {
        return;
    }

    let line_height = crate::style::font_size_control() * 1.5;
    let mut delta = -Vec2::new(event.x, event.y);
    if event.unit == MouseScrollUnit::Line {
        delta *= line_height;
    }

    if keyboard_input.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]) {
        std::mem::swap(&mut delta.x, &mut delta.y);
    }

    let max_offset =
        (computed.content_size() - computed.size()).max(Vec2::ZERO) * computed.inverse_scale_factor();

    let scroll_position = &mut scroll_position.0;
    if node.overflow.x == OverflowAxis::Scroll && delta.x != 0.0 {
        let next_x = (scroll_position.x + delta.x).clamp(0.0, max_offset.x);
        if next_x != scroll_position.x {
            scroll_position.x = next_x;
            delta.x = 0.0;
        }
    }

    if node.overflow.y == OverflowAxis::Scroll && delta.y != 0.0 {
        let next_y = (scroll_position.y + delta.y).clamp(0.0, max_offset.y);
        if next_y != scroll_position.y {
            scroll_position.y = next_y;
            delta.y = 0.0;
        }
    }

    if delta == Vec2::ZERO {
        event.propagate(false);
    }
}