* feat: added sections for advanced mods * fix: treat a null bundle file type as normal * chore: drop ScrollArea::lazy_grid * chore: pass one header height to lazy_sections * chore: name BundleFileType::Normal in test fixtures * feat: don't offer advanced bundle mods as optional mods --------- Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
771 lines
25 KiB
Rust
771 lines
25 KiB
Rust
#![allow(dead_code)]
|
|
use freya::prelude::*;
|
|
|
|
use crate::theme::colors;
|
|
|
|
const LAZY_OVERSCAN: i64 = 3;
|
|
const SCROLLBAR_GUTTER: f32 = 14.;
|
|
|
|
pub(crate) fn normalize_wheel_delta(delta: f64, scale_factor: f64) -> f32 {
|
|
if delta == 0.0 {
|
|
return 0.0;
|
|
}
|
|
|
|
const FREYA_LINE_SPEED: f64 = 53.0;
|
|
const FREYA_PIXEL_SPEED: f64 = 2.0;
|
|
const LINE_DELTA_TOLERANCE: f64 = 1e-6;
|
|
const LINE_SCROLL: f64 = 53.0;
|
|
const PIXEL_SCROLL: f64 = 1.0;
|
|
|
|
let lines = (delta / FREYA_LINE_SPEED).round();
|
|
if lines != 0.0 && (delta - lines * FREYA_LINE_SPEED).abs() <= LINE_DELTA_TOLERANCE {
|
|
return (lines * LINE_SCROLL) as f32;
|
|
}
|
|
|
|
let scale_factor = if scale_factor > 0.0 {
|
|
scale_factor
|
|
} else {
|
|
1.0
|
|
};
|
|
(delta / (FREYA_PIXEL_SPEED * scale_factor) * PIXEL_SCROLL) as f32
|
|
}
|
|
|
|
pub(crate) fn scroll_pos_from_wheel(wheel: f32, inner: f32, viewport: f32, current: f32) -> i32 {
|
|
if viewport >= inner {
|
|
return 0;
|
|
}
|
|
let new_pos = current + wheel;
|
|
if new_pos >= 0.0 && wheel > 0.0 {
|
|
return 0;
|
|
}
|
|
if new_pos <= -(inner - viewport) && wheel < 0.0 {
|
|
return -(inner - viewport) as i32;
|
|
}
|
|
new_pos as i32
|
|
}
|
|
|
|
pub(crate) fn corrected_scroll(inner: f32, viewport: f32, pos: f32) -> f32 {
|
|
if pos > 0.0 {
|
|
return 0.0;
|
|
}
|
|
if (-pos + viewport) > inner {
|
|
return if viewport < inner {
|
|
-(inner - viewport)
|
|
} else {
|
|
0.0
|
|
};
|
|
}
|
|
pos
|
|
}
|
|
|
|
pub(crate) fn scrollbar_pos_and_size(inner: f32, viewport: f32, scroll: f32) -> (f32, f32) {
|
|
if viewport >= inner {
|
|
return (0.0, inner);
|
|
}
|
|
let ratio = viewport / inner;
|
|
let thumb = (viewport * ratio).max(50.0);
|
|
let scroll_range = inner - viewport;
|
|
let thumb_range = viewport - thumb;
|
|
let normalized = -scroll / scroll_range;
|
|
|
|
(normalized * thumb_range, thumb)
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct ScrollAreaCtx {
|
|
pub corrected_x: f32,
|
|
pub corrected_y: f32,
|
|
pub viewport_w: f32,
|
|
pub viewport_h: f32,
|
|
pub viewport_top: f32,
|
|
pub viewport_left: f32,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct LazySection {
|
|
pub header: bool,
|
|
pub count: usize,
|
|
}
|
|
|
|
enum LazyRow {
|
|
Header(usize),
|
|
Items {
|
|
section: usize,
|
|
row: usize,
|
|
start: usize,
|
|
end: usize,
|
|
},
|
|
}
|
|
|
|
pub struct ScrollArea {
|
|
width: Size,
|
|
height: Size,
|
|
padding: Gaps,
|
|
spacing: f32,
|
|
show_scrollbar: bool,
|
|
scrollbar_gutter: bool,
|
|
horizontal: bool,
|
|
content_width: f32,
|
|
stick_bottom: bool,
|
|
reset_key: Option<u64>,
|
|
controller: Option<ScrollController>,
|
|
on_user_scroll: Option<EventHandler<()>>,
|
|
on_ctx: Option<EventHandler<ScrollAreaCtx>>,
|
|
children: Vec<Element>,
|
|
builder: Option<Box<dyn Fn(ScrollAreaCtx) -> Element>>,
|
|
}
|
|
|
|
impl Default for ScrollArea {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ScrollArea {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
width: Size::fill(),
|
|
height: Size::flex(1.0),
|
|
padding: Gaps::default(),
|
|
spacing: 0.,
|
|
show_scrollbar: true,
|
|
scrollbar_gutter: false,
|
|
horizontal: false,
|
|
content_width: 0.,
|
|
stick_bottom: false,
|
|
reset_key: None,
|
|
controller: None,
|
|
on_user_scroll: None,
|
|
on_ctx: None,
|
|
children: Vec::new(),
|
|
builder: None,
|
|
}
|
|
}
|
|
|
|
pub fn width(mut self, width: Size) -> Self {
|
|
self.width = width;
|
|
self
|
|
}
|
|
|
|
pub fn height(mut self, height: Size) -> Self {
|
|
self.height = height;
|
|
self
|
|
}
|
|
|
|
pub fn padding(mut self, padding: Gaps) -> Self {
|
|
self.padding = padding;
|
|
self
|
|
}
|
|
|
|
pub fn spacing(mut self, spacing: f32) -> Self {
|
|
self.spacing = spacing;
|
|
self
|
|
}
|
|
|
|
pub fn show_scrollbar(mut self, show: bool) -> Self {
|
|
self.show_scrollbar = show;
|
|
self
|
|
}
|
|
|
|
pub fn scrollbar_gutter(mut self, gutter: bool) -> Self {
|
|
self.scrollbar_gutter = gutter;
|
|
self
|
|
}
|
|
|
|
pub fn horizontal(mut self, content_width: f32) -> Self {
|
|
self.horizontal = true;
|
|
self.content_width = content_width;
|
|
self
|
|
}
|
|
|
|
pub fn stick_bottom(mut self, stick: bool) -> Self {
|
|
self.stick_bottom = stick;
|
|
self
|
|
}
|
|
|
|
pub fn reset_key(mut self, key: u64) -> Self {
|
|
self.reset_key = Some(key);
|
|
self
|
|
}
|
|
|
|
pub fn scroll_controller(mut self, controller: ScrollController) -> Self {
|
|
self.controller = Some(controller);
|
|
self
|
|
}
|
|
|
|
pub fn on_user_scroll(mut self, handler: impl FnMut(()) + 'static) -> Self {
|
|
self.on_user_scroll = Some(EventHandler::new(handler));
|
|
self
|
|
}
|
|
|
|
pub fn on_ctx(mut self, handler: impl FnMut(ScrollAreaCtx) + 'static) -> Self {
|
|
self.on_ctx = Some(EventHandler::new(handler));
|
|
self
|
|
}
|
|
|
|
pub fn children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
|
|
self.children = children.into_iter().collect();
|
|
self
|
|
}
|
|
|
|
pub fn child(mut self, child: impl IntoElement) -> Self {
|
|
self.children.push(child.into_element());
|
|
self
|
|
}
|
|
|
|
pub fn content(mut self, builder: impl Fn(ScrollAreaCtx) -> Element + 'static) -> Self {
|
|
self.builder = Some(Box::new(builder));
|
|
self
|
|
}
|
|
|
|
pub fn lazy(
|
|
mut self,
|
|
count: usize,
|
|
item_height: f32,
|
|
spacing: f32,
|
|
render: impl Fn(usize) -> Element + 'static,
|
|
) -> Self {
|
|
let slot = (item_height + spacing).max(1.);
|
|
self.builder = Some(Box::new(move |ctx: ScrollAreaCtx| {
|
|
let first =
|
|
(((-ctx.corrected_y) / slot).floor() as i64 - LAZY_OVERSCAN).max(0) as usize;
|
|
let span = ((ctx.viewport_h / slot).ceil() as i64 + 2 * LAZY_OVERSCAN).max(0) as usize;
|
|
let last = (first + span).min(count);
|
|
|
|
let top_pad = first as f32 * slot;
|
|
let bottom_pad = count.saturating_sub(last) as f32 * slot;
|
|
|
|
let mut container = rect().vertical().width(Size::fill());
|
|
if top_pad > 0. {
|
|
container = container.child(rect().width(Size::fill()).height(Size::px(top_pad)));
|
|
}
|
|
for i in first..last {
|
|
container = container.child(
|
|
rect()
|
|
.key(i)
|
|
.width(Size::fill())
|
|
.height(Size::px(slot))
|
|
.child(
|
|
rect()
|
|
.width(Size::fill())
|
|
.height(Size::px(item_height))
|
|
.child(render(i)),
|
|
),
|
|
);
|
|
}
|
|
if bottom_pad > 0. {
|
|
container =
|
|
container.child(rect().width(Size::fill()).height(Size::px(bottom_pad)));
|
|
}
|
|
container.into_element()
|
|
}));
|
|
self
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn lazy_sections(
|
|
mut self,
|
|
sections: Vec<LazySection>,
|
|
item_height: f32,
|
|
gap: f32,
|
|
min_width: f32,
|
|
max_cols: usize,
|
|
header_height: f32,
|
|
render: impl Fn(usize) -> Element + 'static,
|
|
render_header: impl Fn(usize) -> Element + 'static,
|
|
) -> Self {
|
|
let slot = (item_height + gap).max(1.);
|
|
let header_slot = header_height + gap;
|
|
self.builder = Some(Box::new(move |ctx: ScrollAreaCtx| {
|
|
let cols = (((ctx.viewport_w + gap) / (min_width + gap)).floor() as usize)
|
|
.clamp(1, max_cols.max(1));
|
|
|
|
let mut rows = Vec::new();
|
|
let mut offset = 0;
|
|
for (s, section) in sections.iter().enumerate() {
|
|
if section.header {
|
|
rows.push(LazyRow::Header(s));
|
|
}
|
|
for r in 0..section.count.div_ceil(cols) {
|
|
let start = offset + r * cols;
|
|
rows.push(LazyRow::Items {
|
|
section: s,
|
|
row: r,
|
|
start,
|
|
end: (start + cols).min(offset + section.count),
|
|
});
|
|
}
|
|
offset += section.count;
|
|
}
|
|
|
|
let row_slot = |row: &LazyRow| match row {
|
|
LazyRow::Header(_) => header_slot,
|
|
LazyRow::Items { .. } => slot,
|
|
};
|
|
|
|
let overscan = LAZY_OVERSCAN as f32 * slot;
|
|
let visible_top = -ctx.corrected_y - overscan;
|
|
let visible_bottom = -ctx.corrected_y + ctx.viewport_h + overscan;
|
|
|
|
let mut top_pad = 0.;
|
|
let mut bottom_pad = 0.;
|
|
let mut visible = Vec::new();
|
|
let mut y = 0.;
|
|
for row in &rows {
|
|
let h = row_slot(row);
|
|
if y + h <= visible_top {
|
|
top_pad += h;
|
|
} else if y >= visible_bottom {
|
|
bottom_pad += h;
|
|
} else {
|
|
visible.push(row);
|
|
}
|
|
y += h;
|
|
}
|
|
|
|
let mut container = rect().vertical().width(Size::fill());
|
|
if top_pad > 0. {
|
|
container = container.child(rect().width(Size::fill()).height(Size::px(top_pad)));
|
|
}
|
|
for row in visible {
|
|
let el = match *row {
|
|
LazyRow::Header(section) => rect()
|
|
.key(("h", section))
|
|
.width(Size::fill())
|
|
.height(Size::px(header_slot))
|
|
.child(
|
|
rect()
|
|
.width(Size::fill())
|
|
.height(Size::px(header_height))
|
|
.child(render_header(section)),
|
|
),
|
|
LazyRow::Items {
|
|
section,
|
|
row,
|
|
start,
|
|
end,
|
|
} => {
|
|
let mut items = rect()
|
|
.key(("r", section, row))
|
|
.horizontal()
|
|
.width(Size::fill())
|
|
.height(Size::px(slot))
|
|
.spacing(gap)
|
|
.content(Content::Flex);
|
|
for idx in start..start + cols {
|
|
let cell = rect().width(Size::flex(1.0)).height(Size::px(item_height));
|
|
items = items.child(if idx < end {
|
|
cell.child(render(idx))
|
|
} else {
|
|
cell
|
|
});
|
|
}
|
|
items
|
|
}
|
|
};
|
|
container = container.child(el);
|
|
}
|
|
if bottom_pad > 0. {
|
|
container =
|
|
container.child(rect().width(Size::fill()).height(Size::px(bottom_pad)));
|
|
}
|
|
container.into_element()
|
|
}));
|
|
self
|
|
}
|
|
|
|
fn view(&self) -> impl IntoElement {
|
|
let horizontal = self.horizontal;
|
|
let content_w = self.content_width;
|
|
let spacing = self.spacing;
|
|
let padding = self.padding;
|
|
let show_scrollbar = self.show_scrollbar;
|
|
let scrollbar_gutter = self.scrollbar_gutter;
|
|
let stick_bottom = self.stick_bottom;
|
|
let on_user_scroll = self.on_user_scroll.clone();
|
|
|
|
let internal = use_scroll_controller(ScrollConfig::default);
|
|
let mut controller = self.controller.unwrap_or(internal);
|
|
|
|
let scale_factor = Platform::get().scale_factor;
|
|
|
|
let mut viewport_h = use_state(|| 0f32);
|
|
let mut viewport_w = use_state(|| 0f32);
|
|
let mut viewport_top = use_state(|| 0f32);
|
|
let mut viewport_left = use_state(|| 0f32);
|
|
let mut content_h = use_state(|| 0f32);
|
|
let mut scroll_x = use_state(|| 0f32);
|
|
let mut drag_start = use_state::<Option<(f32, i32)>>(|| None);
|
|
let mut drag_start_x = use_state::<Option<(f32, f32)>>(|| None);
|
|
let mut shift_held = use_state(|| false);
|
|
|
|
let reset_key = self.reset_key;
|
|
let mut last_reset = use_state(|| reset_key);
|
|
if reset_key.is_some() && *last_reset.peek() != reset_key {
|
|
last_reset.set(reset_key);
|
|
controller.scroll_to_y(0);
|
|
scroll_x.set_if_modified(0.);
|
|
}
|
|
|
|
let (_, scrolled_y) = controller.into();
|
|
let vp_h = *viewport_h.read();
|
|
let vp_w = *viewport_w.read();
|
|
let vp_top = *viewport_top.read();
|
|
let vp_left = *viewport_left.read();
|
|
let ct_h = *content_h.read();
|
|
|
|
let corrected_y = corrected_scroll(ct_h, vp_h, scrolled_y as f32);
|
|
let corrected_x = if horizontal {
|
|
corrected_scroll(content_w, vp_w, *scroll_x.read())
|
|
} else {
|
|
0.
|
|
};
|
|
|
|
let (scrollbar_y, scrollbar_height) = scrollbar_pos_and_size(ct_h, vp_h, corrected_y);
|
|
let show_v = show_scrollbar && vp_h > 0. && vp_h < ct_h;
|
|
|
|
let (scrollbar_x, scrollbar_width) = scrollbar_pos_and_size(content_w, vp_w, corrected_x);
|
|
let show_h = horizontal && vp_w > 0. && vp_w < content_w;
|
|
|
|
let pressing_v = use_memo(move || drag_start.read().is_some());
|
|
let pressing_h = use_memo(move || drag_start_x.read().is_some());
|
|
|
|
let gutter = if show_v && scrollbar_gutter {
|
|
SCROLLBAR_GUTTER
|
|
} else {
|
|
0.
|
|
};
|
|
let padding = if gutter > 0. {
|
|
Gaps::new(
|
|
padding.top(),
|
|
padding.right() + gutter,
|
|
padding.bottom(),
|
|
padding.left(),
|
|
)
|
|
} else {
|
|
padding
|
|
};
|
|
|
|
let ctx = ScrollAreaCtx {
|
|
corrected_x,
|
|
corrected_y,
|
|
viewport_w: (vp_w - gutter).max(0.),
|
|
viewport_h: vp_h,
|
|
viewport_top: vp_top,
|
|
viewport_left: vp_left,
|
|
};
|
|
|
|
if let Some(cb) = &self.on_ctx {
|
|
cb.call(ctx);
|
|
}
|
|
|
|
let content_el = if let Some(builder) = &self.builder {
|
|
let inner = builder(ctx);
|
|
if padding != Gaps::default() {
|
|
rect()
|
|
.width(Size::fill())
|
|
.padding(padding)
|
|
.child(inner)
|
|
.into_element()
|
|
} else {
|
|
inner
|
|
}
|
|
} else {
|
|
rect()
|
|
.vertical()
|
|
.width(Size::fill())
|
|
.spacing(spacing)
|
|
.padding(padding)
|
|
.children(self.children.clone())
|
|
.into_element()
|
|
};
|
|
|
|
let us_wheel = on_user_scroll.clone();
|
|
let us_thumb = on_user_scroll.clone();
|
|
let us_h_thumb = on_user_scroll;
|
|
|
|
let can_scroll_v = vp_h > 0. && vp_h < ct_h;
|
|
let can_scroll_h = horizontal && vp_w > 0. && vp_w < content_w;
|
|
|
|
let on_wheel = move |e: Event<WheelEventData>| {
|
|
let shift = *shift_held.read();
|
|
let scale = *scale_factor.peek();
|
|
|
|
let delta_x = normalize_wheel_delta(e.delta_x, scale);
|
|
let delta_y = normalize_wheel_delta(e.delta_y, scale);
|
|
|
|
let h_delta = if horizontal {
|
|
if shift && delta_x == 0.0 {
|
|
delta_y
|
|
} else {
|
|
delta_x
|
|
}
|
|
} else {
|
|
0.0
|
|
};
|
|
let v_delta = if horizontal && shift { 0.0 } else { delta_y };
|
|
|
|
let do_h = h_delta != 0.0 && can_scroll_h;
|
|
let do_v = v_delta != 0.0 && can_scroll_v;
|
|
|
|
if !do_h && !do_v {
|
|
return;
|
|
}
|
|
|
|
e.stop_propagation();
|
|
|
|
if let Some(cb) = &us_wheel {
|
|
cb.call(());
|
|
}
|
|
|
|
if do_h {
|
|
let cur_x = corrected_scroll(content_w, vp_w, *scroll_x.read());
|
|
let new_x = scroll_pos_from_wheel(h_delta, content_w, vp_w, cur_x);
|
|
scroll_x.set(new_x as f32);
|
|
}
|
|
|
|
if do_v {
|
|
let (_, cur_y) = controller.into();
|
|
let current = corrected_scroll(ct_h, vp_h, cur_y as f32);
|
|
let new_y = scroll_pos_from_wheel(v_delta, ct_h, vp_h, current);
|
|
if new_y != cur_y {
|
|
controller.scroll_to_y(new_y);
|
|
}
|
|
}
|
|
};
|
|
|
|
let on_thumb_down = move |e: Event<PointerEventData>| {
|
|
if let Some(cb) = &us_thumb {
|
|
cb.call(());
|
|
}
|
|
let (_, y) = controller.into();
|
|
drag_start.set(Some((e.global_location().y as f32, y)));
|
|
};
|
|
|
|
let on_h_thumb_down = move |e: Event<PointerEventData>| {
|
|
if let Some(cb) = &us_h_thumb {
|
|
cb.call(());
|
|
}
|
|
drag_start_x.set(Some((e.global_location().x as f32, *scroll_x.read())));
|
|
};
|
|
|
|
let on_global_move = move |e: Event<PointerEventData>| {
|
|
if let Some((grab_y, scroll_at_grab)) = *drag_start.read() {
|
|
e.stop_propagation();
|
|
|
|
let thumb_range = (vp_h - scrollbar_height).max(1.);
|
|
let scroll_range = (ct_h - vp_h).max(0.);
|
|
|
|
let cursor_dy = e.global_location().y as f32 - grab_y;
|
|
let scroll_dy = -(cursor_dy / thumb_range) * scroll_range;
|
|
let target = scroll_at_grab + scroll_dy as i32;
|
|
|
|
let clamped = corrected_scroll(ct_h, vp_h, target as f32) as i32;
|
|
|
|
let (_, cur_y) = controller.into();
|
|
|
|
if clamped != cur_y {
|
|
controller.scroll_to_y(clamped);
|
|
}
|
|
|
|
return;
|
|
}
|
|
if let Some((grab_x, scroll_at_grab)) = *drag_start_x.read() {
|
|
e.stop_propagation();
|
|
let thumb_range = (vp_w - scrollbar_width).max(1.);
|
|
let scroll_range = (content_w - vp_w).max(0.);
|
|
let cursor_dx = e.global_location().x as f32 - grab_x;
|
|
let scroll_dx = -(cursor_dx / thumb_range) * scroll_range;
|
|
let target = scroll_at_grab + scroll_dx;
|
|
scroll_x.set_if_modified(corrected_scroll(content_w, vp_w, target));
|
|
}
|
|
};
|
|
|
|
let on_global_release = move |_: Event<PointerEventData>| {
|
|
if drag_start.read().is_some() {
|
|
drag_start.set(None);
|
|
}
|
|
if drag_start_x.read().is_some() {
|
|
drag_start_x.set(None);
|
|
}
|
|
};
|
|
|
|
rect()
|
|
.width(self.width.clone())
|
|
.height(self.height.clone())
|
|
.scrollable(true)
|
|
.a11y_builder(move |node| {
|
|
node.set_scroll_x(corrected_x as f64);
|
|
node.set_scroll_y(corrected_y as f64);
|
|
})
|
|
.on_wheel(on_wheel)
|
|
.on_capture_global_pointer_move(on_global_move)
|
|
.on_capture_global_pointer_press(on_global_release)
|
|
.on_global_key_down(move |e: Event<KeyboardEventData>| {
|
|
let held = e.modifiers.contains(Modifiers::SHIFT)
|
|
|| matches!(e.code, Code::ShiftLeft | Code::ShiftRight);
|
|
shift_held.set_if_modified(held);
|
|
})
|
|
.on_global_key_up(move |e: Event<KeyboardEventData>| {
|
|
let held = if matches!(e.code, Code::ShiftLeft | Code::ShiftRight) {
|
|
false
|
|
} else {
|
|
e.modifiers.contains(Modifiers::SHIFT)
|
|
};
|
|
shift_held.set_if_modified(held);
|
|
})
|
|
.child(
|
|
rect()
|
|
.width(Size::fill())
|
|
.height(Size::flex(1.0))
|
|
.overflow(Overflow::Clip)
|
|
.on_sized(move |e: Event<SizedEventData>| {
|
|
let h = e.area.height();
|
|
let w = e.area.width();
|
|
let top = e.area.min_y();
|
|
let left = e.area.min_x();
|
|
if (*viewport_h.read() - h).abs() > 0.5 {
|
|
viewport_h.set(h);
|
|
}
|
|
if (*viewport_w.read() - w).abs() > 0.5 {
|
|
viewport_w.set(w);
|
|
}
|
|
if (*viewport_top.read() - top).abs() > 0.5 {
|
|
viewport_top.set(top);
|
|
}
|
|
if (*viewport_left.read() - left).abs() > 0.5 {
|
|
viewport_left.set(left);
|
|
}
|
|
})
|
|
.child(
|
|
rect()
|
|
.width(Size::fill())
|
|
.offset_x(corrected_x)
|
|
.offset_y(corrected_y)
|
|
.on_sized(move |e: Event<SizedEventData>| {
|
|
let h = e.area.height();
|
|
if (*content_h.read() - h).abs() > 0.5 {
|
|
content_h.set(h);
|
|
if stick_bottom {
|
|
let vp = *viewport_h.read();
|
|
let target = -((h - vp).max(0.));
|
|
controller.scroll_to_y(target as i32);
|
|
}
|
|
}
|
|
})
|
|
.child(content_el),
|
|
)
|
|
.maybe_child(show_h.then(|| {
|
|
ScrollThumb {
|
|
vertical: false,
|
|
main_pos: scrollbar_x,
|
|
length: scrollbar_width,
|
|
pressing: pressing_h.into_readable(),
|
|
on_down: on_h_thumb_down.into(),
|
|
}
|
|
.into_element()
|
|
}))
|
|
.maybe_child(show_v.then(|| {
|
|
ScrollThumb {
|
|
vertical: true,
|
|
main_pos: scrollbar_y,
|
|
length: scrollbar_height,
|
|
pressing: pressing_v.into_readable(),
|
|
on_down: on_thumb_down.into(),
|
|
}
|
|
.into_element()
|
|
})),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl PartialEq for ScrollArea {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
if self.builder.is_some() || other.builder.is_some() {
|
|
return false;
|
|
}
|
|
self.width == other.width
|
|
&& self.height == other.height
|
|
&& self.padding == other.padding
|
|
&& self.spacing == other.spacing
|
|
&& self.show_scrollbar == other.show_scrollbar
|
|
&& self.scrollbar_gutter == other.scrollbar_gutter
|
|
&& self.horizontal == other.horizontal
|
|
&& self.content_width == other.content_width
|
|
&& self.stick_bottom == other.stick_bottom
|
|
&& self.reset_key == other.reset_key
|
|
&& self.children == other.children
|
|
}
|
|
}
|
|
|
|
impl Component for ScrollArea {
|
|
fn render(&self) -> impl IntoElement {
|
|
self.view()
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
struct ScrollThumb {
|
|
vertical: bool,
|
|
main_pos: f32,
|
|
length: f32,
|
|
pressing: Readable<bool>,
|
|
on_down: EventHandler<Event<PointerEventData>>,
|
|
}
|
|
|
|
impl Component for ScrollThumb {
|
|
fn render(&self) -> impl IntoElement {
|
|
let mut hovering = use_state(|| false);
|
|
let on_down = self.on_down.clone();
|
|
let pressing = *self.pressing.read();
|
|
let hovered = *hovering.read();
|
|
|
|
let color = if pressing {
|
|
colors::fg_secondary_pressed()
|
|
} else if hovered {
|
|
colors::fg_secondary_hover()
|
|
} else {
|
|
colors::fg_secondary()
|
|
};
|
|
let thickness = if pressing || hovered { 8. } else { 6. };
|
|
|
|
let (position, width, height, layer) = if self.vertical {
|
|
(
|
|
Position::new_absolute().right(0.).top(self.main_pos),
|
|
Size::px(14.),
|
|
Size::px(self.length),
|
|
Layer::Relative(999),
|
|
)
|
|
} else {
|
|
(
|
|
Position::new_absolute().left(self.main_pos).bottom(2.),
|
|
Size::px(self.length),
|
|
Size::px(8.),
|
|
Layer::Relative(2),
|
|
)
|
|
};
|
|
|
|
let (bar_w, bar_h) = if self.vertical {
|
|
(Size::px(thickness), Size::fill())
|
|
} else {
|
|
(Size::fill(), Size::px(thickness))
|
|
};
|
|
|
|
rect()
|
|
.position(position)
|
|
.width(width)
|
|
.height(height)
|
|
.center()
|
|
.layer(layer)
|
|
.on_pointer_down(move |e| on_down.call(e))
|
|
.on_pointer_over(move |_| hovering.set(true))
|
|
.on_pointer_out(move |_| hovering.set(false))
|
|
.child(
|
|
rect()
|
|
.width(bar_w)
|
|
.height(bar_h)
|
|
.corner_radius(CornerRadius::new_all(12.))
|
|
.background(color.with_a(160)),
|
|
)
|
|
}
|
|
}
|