Files
oak-gpui/crates/gpui/src/elements/uniform_list.rs
T
2021-10-04 13:22:21 -06:00

257 lines
6.8 KiB
Rust

use super::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{self, json},
ElementBox,
};
use json::ToJson;
use parking_lot::Mutex;
use std::{cmp, ops::Range, sync::Arc};
#[derive(Clone, Default)]
pub struct UniformListState(Arc<Mutex<StateInner>>);
impl UniformListState {
pub fn scroll_to(&self, item_ix: usize) {
self.0.lock().scroll_to = Some(item_ix);
}
pub fn scroll_top(&self) -> f32 {
self.0.lock().scroll_top
}
}
#[derive(Default)]
struct StateInner {
scroll_top: f32,
scroll_to: Option<usize>,
}
pub struct LayoutState {
scroll_max: f32,
item_height: f32,
items: Vec<ElementBox>,
}
pub struct UniformList<F>
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
state: UniformListState,
item_count: usize,
append_items: F,
padding_top: f32,
padding_bottom: f32,
}
impl<F> UniformList<F>
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self {
Self {
state,
item_count,
append_items,
padding_top: 0.,
padding_bottom: 0.,
}
}
pub fn with_padding_top(mut self, padding: f32) -> Self {
self.padding_top = padding;
self
}
pub fn with_padding_bottom(mut self, padding: f32) -> Self {
self.padding_bottom = padding;
self
}
fn scroll(
&self,
_: Vector2F,
mut delta: Vector2F,
precise: bool,
scroll_max: f32,
cx: &mut EventContext,
) -> bool {
if !precise {
delta *= 20.;
}
let mut state = self.state.0.lock();
state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
cx.notify();
true
}
fn autoscroll(&mut self, scroll_max: f32, list_height: f32, item_height: f32) {
let mut state = self.state.0.lock();
if state.scroll_top > scroll_max {
state.scroll_top = scroll_max;
}
if let Some(item_ix) = state.scroll_to.take() {
let item_top = self.padding_top + item_ix as f32 * item_height;
let item_bottom = item_top + item_height;
if item_top < state.scroll_top {
state.scroll_top = item_top;
} else if item_bottom > (state.scroll_top + list_height) {
state.scroll_top = item_bottom - list_height;
}
}
}
fn scroll_top(&self) -> f32 {
self.state.0.lock().scroll_top
}
}
impl<F> Element for UniformList<F>
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
type LayoutState = LayoutState;
type PaintState = ();
fn layout(
&mut self,
constraint: SizeConstraint,
cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
if constraint.max.y().is_infinite() {
unimplemented!(
"UniformList does not support being rendered with an unconstrained height"
);
}
let mut size = constraint.max;
let mut item_constraint =
SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY));
let mut item_height = 0.;
let mut scroll_max = 0.;
let mut items = Vec::new();
(self.append_items)(0..1, &mut items, cx);
if let Some(first_item) = items.first_mut() {
let mut item_size = first_item.layout(item_constraint, cx);
item_size.set_x(size.x());
item_constraint.min = item_size;
item_constraint.max = item_size;
item_height = item_size.y();
let scroll_height = self.item_count as f32 * item_height;
if scroll_height < size.y() {
size.set_y(size.y().min(scroll_height).max(constraint.min.y()));
}
let scroll_height =
item_height * self.item_count as f32 + self.padding_top + self.padding_bottom;
scroll_max = (scroll_height - size.y()).max(0.);
self.autoscroll(scroll_max, size.y(), item_height);
items.clear();
let start = cmp::min(
((self.scroll_top() - self.padding_top) / item_height) as usize,
self.item_count,
);
let end = cmp::min(
self.item_count,
start + (size.y() / item_height).ceil() as usize + 1,
);
(self.append_items)(start..end, &mut items, cx);
for item in &mut items {
item.layout(item_constraint, cx);
}
} else {
size = constraint.min;
}
(
size,
LayoutState {
item_height,
scroll_max,
items,
},
)
}
fn paint(
&mut self,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
cx.scene.push_layer(Some(bounds));
let mut item_origin = bounds.origin()
- vec2f(
0.,
(self.state.scroll_top() - self.padding_top) % layout.item_height,
);
for item in &mut layout.items {
item.paint(item_origin, visible_bounds, cx);
item_origin += vec2f(0.0, layout.item_height);
}
cx.scene.pop_layer();
}
fn dispatch_event(
&mut self,
event: &Event,
bounds: RectF,
layout: &mut Self::LayoutState,
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
let mut handled = false;
for item in &mut layout.items {
handled = item.dispatch_event(event, cx) || handled;
}
match event {
Event::ScrollWheel {
position,
delta,
precise,
} => {
if bounds.contains_point(*position) {
if self.scroll(*position, *delta, *precise, layout.scroll_max, cx) {
handled = true;
}
}
}
_ => {}
}
handled
}
fn debug(
&self,
bounds: RectF,
layout: &Self::LayoutState,
_: &Self::PaintState,
cx: &crate::DebugContext,
) -> json::Value {
json!({
"type": "UniformList",
"bounds": bounds.to_json(),
"scroll_max": layout.scroll_max,
"item_height": layout.item_height,
"items": layout.items.iter().map(|item| item.debug(cx)).collect::<Vec<json::Value>>()
})
}
}