use crate::commands::SCROLL_TO_VIEW;
use crate::contexts::ChangeCtx;
use crate::debug_state::DebugState;
use crate::widget::prelude::*;
use crate::widget::{Axis, ClipBox};
use crate::{scroll_component::*, Data, Rect, Vec2};
use tracing::{instrument, trace};
pub struct Scroll<T, W> {
clip: ClipBox<T, W>,
scroll_component: ScrollComponent,
}
impl<T, W: Widget<T>> Scroll<T, W> {
pub fn new(child: W) -> Scroll<T, W> {
Scroll {
clip: ClipBox::managed(child),
scroll_component: ScrollComponent::new(),
}
}
pub fn scroll_by<C: ChangeCtx>(&mut self, ctx: &mut C, delta: Vec2) -> bool {
self.clip.pan_by(ctx, delta)
}
pub fn scroll_to<C: ChangeCtx>(&mut self, ctx: &mut C, region: Rect) -> bool {
self.clip.pan_to_visible(ctx, region)
}
pub fn scroll_to_on_axis<C: ChangeCtx>(
&mut self,
ctx: &mut C,
axis: Axis,
position: f64,
) -> bool {
self.clip.pan_to_on_axis(ctx, axis, position)
}
}
impl<T, W> Scroll<T, W> {
pub fn vertical(mut self) -> Self {
self.scroll_component.enabled = ScrollbarsEnabled::Vertical;
self.clip.set_constrain_vertical(false);
self.clip.set_constrain_horizontal(true);
self
}
pub fn horizontal(mut self) -> Self {
self.scroll_component.enabled = ScrollbarsEnabled::Horizontal;
self.clip.set_constrain_vertical(true);
self.clip.set_constrain_horizontal(false);
self
}
pub fn content_must_fill(mut self, must_fill: bool) -> Self {
self.set_content_must_fill(must_fill);
self
}
pub fn disable_scrollbars(mut self) -> Self {
self.scroll_component.enabled = ScrollbarsEnabled::None;
self
}
pub fn set_content_must_fill(&mut self, must_fill: bool) {
self.clip.set_content_must_fill(must_fill);
}
pub fn set_enabled_scrollbars(&mut self, enabled: ScrollbarsEnabled) {
self.scroll_component.enabled = enabled;
}
pub fn set_vertical_scroll_enabled(&mut self, enabled: bool) {
self.clip.set_constrain_vertical(!enabled);
self.scroll_component
.enabled
.set_vertical_scrollbar_enabled(enabled);
}
pub fn set_horizontal_scroll_enabled(&mut self, enabled: bool) {
self.clip.set_constrain_horizontal(!enabled);
self.scroll_component
.enabled
.set_horizontal_scrollbar_enabled(enabled);
}
pub fn child(&self) -> &W {
self.clip.child()
}
pub fn child_mut(&mut self) -> &mut W {
self.clip.child_mut()
}
pub fn child_size(&self) -> Size {
self.clip.content_size()
}
pub fn offset(&self) -> Vec2 {
self.clip.viewport_origin().to_vec2()
}
pub fn viewport_rect(&self) -> Rect {
self.clip.viewport().view_rect()
}
pub fn offset_for_axis(&self, axis: Axis) -> f64 {
axis.major_pos(self.clip.viewport_origin())
}
}
impl<T: Data, W: Widget<T>> Widget<T> for Scroll<T, W> {
#[instrument(name = "Scroll", level = "trace", skip(self, ctx, event, data, env))]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let scroll_component = &mut self.scroll_component;
self.clip.with_port(ctx, |ctx, port| {
scroll_component.event(port, ctx, event, env);
});
if !ctx.is_handled() {
self.clip.event(ctx, event, data, env);
}
self.clip.with_port(ctx, |ctx, port| {
scroll_component.handle_scroll(port, ctx, event, env);
if !scroll_component.are_bars_held() {
if let Event::Notification(notification) = event {
if let Some(&global_highlight_rect) = notification.get(SCROLL_TO_VIEW) {
ctx.set_handled();
let view_port_changed =
port.default_scroll_to_view_handling(ctx, global_highlight_rect);
if view_port_changed {
scroll_component
.reset_scrollbar_fade(|duration| ctx.request_timer(duration), env);
}
}
}
}
});
}
#[instrument(name = "Scroll", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
self.scroll_component.lifecycle(ctx, event, env);
self.clip.lifecycle(ctx, event, data, env);
}
#[instrument(name = "Scroll", level = "trace", skip(self, ctx, old_data, data, env))]
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
self.clip.update(ctx, old_data, data, env);
}
#[instrument(name = "Scroll", level = "trace", skip(self, ctx, bc, data, env))]
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
bc.debug_check("Scroll");
let old_size = self.clip.viewport().view_size;
let child_size = self.clip.layout(ctx, bc, data, env);
log_size_warnings(child_size);
let self_size = bc.constrain(child_size);
if old_size != self_size {
self.scroll_component
.reset_scrollbar_fade(|d| ctx.request_timer(d), env);
}
trace!("Computed size: {}", self_size);
self_size
}
#[instrument(name = "Scroll", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
self.clip.paint(ctx, data, env);
self.scroll_component
.draw_bars(ctx, &self.clip.viewport(), env);
}
fn debug_state(&self, data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
children: vec![self.clip.debug_state(data)],
..Default::default()
}
}
}
fn log_size_warnings(size: Size) {
if size.width.is_infinite() {
tracing::warn!("Scroll widget's child has an infinite width.");
}
if size.height.is_infinite() {
tracing::warn!("Scroll widget's child has an infinite height.");
}
}