|
3 | 3 | use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; |
4 | 4 |
|
5 | 5 | use crate::{ |
6 | | - Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, |
7 | | - UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, |
| 6 | + Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, |
| 7 | + UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, |
8 | 8 | }; |
9 | 9 |
|
10 | 10 | #[derive(Clone, Copy, Debug)] |
@@ -659,6 +659,9 @@ struct Prepared { |
659 | 659 | /// not for us to handle so we save it and restore it after this [`ScrollArea`] is done. |
660 | 660 | saved_scroll_target: [Option<pass_state::ScrollTarget>; 2], |
661 | 661 |
|
| 662 | + /// The response from dragging the background (if enabled) |
| 663 | + background_drag_response: Option<Response>, |
| 664 | + |
662 | 665 | animated: bool, |
663 | 666 | } |
664 | 667 |
|
@@ -772,70 +775,72 @@ impl ScrollArea { |
772 | 775 | let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size); |
773 | 776 | let dt = ui.input(|i| i.stable_dt).at_most(0.1); |
774 | 777 |
|
775 | | - if scroll_source.drag |
776 | | - && ui.is_enabled() |
777 | | - && (state.content_is_too_large[0] || state.content_is_too_large[1]) |
778 | | - { |
779 | | - // Drag contents to scroll (for touch screens mostly). |
780 | | - // We must do this BEFORE adding content to the `ScrollArea`, |
781 | | - // or we will steal input from the widgets we contain. |
782 | | - let content_response_option = state |
783 | | - .interact_rect |
784 | | - .map(|rect| ui.interact(rect, id.with("area"), Sense::drag())); |
785 | | - |
786 | | - if content_response_option |
787 | | - .as_ref() |
788 | | - .is_some_and(|response| response.dragged()) |
789 | | - { |
790 | | - for d in 0..2 { |
791 | | - if direction_enabled[d] { |
792 | | - ui.input(|input| { |
793 | | - state.offset[d] -= input.pointer.delta()[d]; |
794 | | - }); |
795 | | - state.scroll_stuck_to_end[d] = false; |
796 | | - state.offset_target[d] = None; |
797 | | - } |
798 | | - } |
799 | | - } else { |
800 | | - // Apply the cursor velocity to the scroll area when the user releases the drag. |
| 778 | + let background_drag_response = |
| 779 | + if scroll_source.drag && ui.is_enabled() && state.content_is_too_large.any() { |
| 780 | + // Drag contents to scroll (for touch screens mostly). |
| 781 | + // We must do this BEFORE adding content to the `ScrollArea`, |
| 782 | + // or we will steal input from the widgets we contain. |
| 783 | + let content_response_option = state |
| 784 | + .interact_rect |
| 785 | + .map(|rect| ui.interact(rect, id.with("area"), Sense::drag())); |
| 786 | + |
801 | 787 | if content_response_option |
802 | 788 | .as_ref() |
803 | | - .is_some_and(|response| response.drag_stopped()) |
| 789 | + .is_some_and(|response| response.dragged()) |
804 | 790 | { |
805 | | - state.vel = |
806 | | - direction_enabled.to_vec2() * ui.input(|input| input.pointer.velocity()); |
807 | | - } |
808 | | - for d in 0..2 { |
809 | | - // Kinetic scrolling |
810 | | - let stop_speed = 20.0; // Pixels per second. |
811 | | - let friction_coeff = 1000.0; // Pixels per second squared. |
812 | | - |
813 | | - let friction = friction_coeff * dt; |
814 | | - if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed { |
815 | | - state.vel[d] = 0.0; |
816 | | - } else { |
817 | | - state.vel[d] -= friction * state.vel[d].signum(); |
818 | | - // Offset has an inverted coordinate system compared to |
819 | | - // the velocity, so we subtract it instead of adding it |
820 | | - state.offset[d] -= state.vel[d] * dt; |
821 | | - ctx.request_repaint(); |
| 791 | + for d in 0..2 { |
| 792 | + if direction_enabled[d] { |
| 793 | + ui.input(|input| { |
| 794 | + state.offset[d] -= input.pointer.delta()[d]; |
| 795 | + }); |
| 796 | + state.scroll_stuck_to_end[d] = false; |
| 797 | + state.offset_target[d] = None; |
| 798 | + } |
| 799 | + } |
| 800 | + } else { |
| 801 | + // Apply the cursor velocity to the scroll area when the user releases the drag. |
| 802 | + if content_response_option |
| 803 | + .as_ref() |
| 804 | + .is_some_and(|response| response.drag_stopped()) |
| 805 | + { |
| 806 | + state.vel = direction_enabled.to_vec2() |
| 807 | + * ui.input(|input| input.pointer.velocity()); |
| 808 | + } |
| 809 | + for d in 0..2 { |
| 810 | + // Kinetic scrolling |
| 811 | + let stop_speed = 20.0; // Pixels per second. |
| 812 | + let friction_coeff = 1000.0; // Pixels per second squared. |
| 813 | + |
| 814 | + let friction = friction_coeff * dt; |
| 815 | + if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed { |
| 816 | + state.vel[d] = 0.0; |
| 817 | + } else { |
| 818 | + state.vel[d] -= friction * state.vel[d].signum(); |
| 819 | + // Offset has an inverted coordinate system compared to |
| 820 | + // the velocity, so we subtract it instead of adding it |
| 821 | + state.offset[d] -= state.vel[d] * dt; |
| 822 | + ctx.request_repaint(); |
| 823 | + } |
822 | 824 | } |
823 | 825 | } |
824 | | - } |
825 | 826 |
|
826 | | - // Set the desired mouse cursors. |
827 | | - if let Some(response) = content_response_option { |
828 | | - if response.dragged() { |
829 | | - if let Some(cursor) = on_drag_cursor { |
830 | | - response.on_hover_cursor(cursor); |
| 827 | + // Set the desired mouse cursors. |
| 828 | + if let Some(response) = &content_response_option { |
| 829 | + if response.dragged() |
| 830 | + && let Some(cursor) = on_drag_cursor |
| 831 | + { |
| 832 | + ui.ctx().set_cursor_icon(cursor); |
| 833 | + } else if response.hovered() |
| 834 | + && let Some(cursor) = on_hover_cursor |
| 835 | + { |
| 836 | + ui.ctx().set_cursor_icon(cursor); |
831 | 837 | } |
832 | | - } else if response.hovered() |
833 | | - && let Some(cursor) = on_hover_cursor |
834 | | - { |
835 | | - response.on_hover_cursor(cursor); |
836 | 838 | } |
837 | | - } |
838 | | - } |
| 839 | + |
| 840 | + content_response_option |
| 841 | + } else { |
| 842 | + None |
| 843 | + }; |
839 | 844 |
|
840 | 845 | // Scroll with an animation if we have a target offset (that hasn't been cleared by the code |
841 | 846 | // above). |
@@ -888,6 +893,7 @@ impl ScrollArea { |
888 | 893 | wheel_scroll_multiplier, |
889 | 894 | stick_to_end, |
890 | 895 | saved_scroll_target, |
| 896 | + background_drag_response, |
891 | 897 | animated, |
892 | 898 | } |
893 | 899 | } |
@@ -1003,6 +1009,7 @@ impl Prepared { |
1003 | 1009 | wheel_scroll_multiplier, |
1004 | 1010 | stick_to_end, |
1005 | 1011 | saved_scroll_target, |
| 1012 | + background_drag_response, |
1006 | 1013 | animated, |
1007 | 1014 | } = self; |
1008 | 1015 |
|
@@ -1118,7 +1125,16 @@ impl Prepared { |
1118 | 1125 | ); |
1119 | 1126 |
|
1120 | 1127 | let max_offset = content_size - inner_rect.size(); |
1121 | | - let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); |
| 1128 | + |
| 1129 | + // Drag-to-scroll? |
| 1130 | + let is_dragging_background = background_drag_response |
| 1131 | + .as_ref() |
| 1132 | + .is_some_and(|r| r.dragged()); |
| 1133 | + |
| 1134 | + let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect) |
| 1135 | + && ui.ctx().dragged_id().is_none() |
| 1136 | + || is_dragging_background; |
| 1137 | + |
1122 | 1138 | if scroll_source.mouse_wheel && ui.is_enabled() && is_hovering_outer_rect { |
1123 | 1139 | let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction |
1124 | 1140 | && direction_enabled[0] != direction_enabled[1]; |
@@ -1204,6 +1220,7 @@ impl Prepared { |
1204 | 1220 |
|
1205 | 1221 | let is_hovering_bar_area = is_hovering_outer_rect |
1206 | 1222 | && ui.rect_contains_pointer(max_bar_rect) |
| 1223 | + && !is_dragging_background |
1207 | 1224 | || state.scroll_bar_interaction[d]; |
1208 | 1225 |
|
1209 | 1226 | let is_hovering_bar_area_t = ui |
|
0 commit comments