Fix dock resizing
This commit is contained in:
parent
3074455386
commit
2f16147055
10 changed files with 170 additions and 52 deletions
|
@ -577,6 +577,14 @@ impl AppContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn optional_global<T: 'static>(&self) -> Option<&T> {
|
||||
if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
|
||||
Some(global.downcast_ref().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade(&self) -> App {
|
||||
App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
|
||||
}
|
||||
|
|
|
@ -186,16 +186,27 @@ pub trait Element<V: View>: 'static {
|
|||
Tooltip::new::<Tag>(id, text, action, style, self.into_any(), cx)
|
||||
}
|
||||
|
||||
fn resizable(
|
||||
/// Uses the the given element to calculate resizes for the given tag
|
||||
fn provide_resize_bounds<Tag: 'static>(self) -> BoundsProvider<V, Tag>
|
||||
where
|
||||
Self: 'static + Sized,
|
||||
{
|
||||
BoundsProvider::<_, Tag>::new(self.into_any())
|
||||
}
|
||||
|
||||
/// Calls the given closure with the new size of the element whenever the
|
||||
/// handle is dragged. This will be calculated in relation to the bounds
|
||||
/// provided by the given tag
|
||||
fn resizable<Tag: 'static>(
|
||||
self,
|
||||
side: HandleSide,
|
||||
size: f32,
|
||||
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
|
||||
on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
|
||||
) -> Resizable<V>
|
||||
where
|
||||
Self: 'static + Sized,
|
||||
{
|
||||
Resizable::new(self.into_any(), side, size, on_resize)
|
||||
Resizable::new::<Tag>(self.into_any(), side, size, on_resize)
|
||||
}
|
||||
|
||||
fn mouse<Tag: 'static>(self, region_id: usize) -> MouseEventHandler<V>
|
||||
|
|
|
@ -82,6 +82,9 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
|||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
element.debug(view, cx)
|
||||
serde_json::json!({
|
||||
"type": "ComponentAdapter",
|
||||
"child": element.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use collections::HashMap;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
geometry::rect::RectF,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::MouseDrag,
|
||||
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
|
||||
SizeConstraint, View, ViewContext,
|
||||
AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
|
||||
SizeConstraint, TypeTag, View, ViewContext,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
@ -27,15 +27,6 @@ impl HandleSide {
|
|||
}
|
||||
}
|
||||
|
||||
/// 'before' is in reference to the standard english document ordering of left-to-right
|
||||
/// then top-to-bottom
|
||||
fn before_content(self) -> bool {
|
||||
match self {
|
||||
HandleSide::Left | HandleSide::Top => true,
|
||||
HandleSide::Right | HandleSide::Bottom => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn relevant_component(&self, vector: Vector2F) -> f32 {
|
||||
match self.axis() {
|
||||
Axis::Horizontal => vector.x(),
|
||||
|
@ -43,14 +34,6 @@ impl HandleSide {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_delta(&self, e: MouseDrag) -> f32 {
|
||||
if self.before_content() {
|
||||
self.relevant_component(e.prev_mouse_position) - self.relevant_component(e.position)
|
||||
} else {
|
||||
self.relevant_component(e.position) - self.relevant_component(e.prev_mouse_position)
|
||||
}
|
||||
}
|
||||
|
||||
fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
|
||||
match self {
|
||||
HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
|
||||
|
@ -69,21 +52,29 @@ impl HandleSide {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_bounds(tag: TypeTag, cx: &AppContext) -> Option<&(RectF, RectF)>
|
||||
where
|
||||
{
|
||||
cx.optional_global::<ProviderMap>()
|
||||
.and_then(|map| map.0.get(&tag))
|
||||
}
|
||||
|
||||
pub struct Resizable<V: View> {
|
||||
child: AnyElement<V>,
|
||||
tag: TypeTag,
|
||||
handle_side: HandleSide,
|
||||
handle_size: f32,
|
||||
on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>>,
|
||||
on_resize: Rc<RefCell<dyn FnMut(&mut V, Option<f32>, &mut ViewContext<V>)>>,
|
||||
}
|
||||
|
||||
const DEFAULT_HANDLE_SIZE: f32 = 4.0;
|
||||
|
||||
impl<V: View> Resizable<V> {
|
||||
pub fn new(
|
||||
pub fn new<Tag: 'static>(
|
||||
child: AnyElement<V>,
|
||||
handle_side: HandleSide,
|
||||
size: f32,
|
||||
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
|
||||
on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
|
||||
) -> Self {
|
||||
let child = match handle_side.axis() {
|
||||
Axis::Horizontal => child.constrained().with_max_width(size),
|
||||
|
@ -94,6 +85,7 @@ impl<V: View> Resizable<V> {
|
|||
Self {
|
||||
child,
|
||||
handle_side,
|
||||
tag: TypeTag::new::<Tag>(),
|
||||
handle_size: DEFAULT_HANDLE_SIZE,
|
||||
on_resize: Rc::new(RefCell::new(on_resize)),
|
||||
}
|
||||
|
@ -139,6 +131,14 @@ impl<V: View> Element<V> for Resizable<V> {
|
|||
handle_region,
|
||||
)
|
||||
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||
.on_click(MouseButton::Left, {
|
||||
let on_resize = self.on_resize.clone();
|
||||
move |click, v, cx| {
|
||||
if click.click_count == 2 {
|
||||
on_resize.borrow_mut()(v, None, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_drag(MouseButton::Left, {
|
||||
let bounds = bounds.clone();
|
||||
let side = self.handle_side;
|
||||
|
@ -146,16 +146,30 @@ impl<V: View> Element<V> for Resizable<V> {
|
|||
let min_size = side.relevant_component(constraint.min);
|
||||
let max_size = side.relevant_component(constraint.max);
|
||||
let on_resize = self.on_resize.clone();
|
||||
let tag = self.tag;
|
||||
move |event, view: &mut V, cx| {
|
||||
if event.end {
|
||||
return;
|
||||
}
|
||||
let new_size = min_size
|
||||
.max(prev_size + side.compute_delta(event))
|
||||
.min(max_size)
|
||||
.round();
|
||||
|
||||
let Some((bounds, _)) = get_bounds(tag, cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_size_raw = match side {
|
||||
// Handle on top side of element => Element is on bottom
|
||||
HandleSide::Top => bounds.height() + bounds.origin_y() - event.position.y(),
|
||||
// Handle on right side of element => Element is on left
|
||||
HandleSide::Right => event.position.x() - bounds.lower_left().x(),
|
||||
// Handle on left side of element => Element is on the right
|
||||
HandleSide::Left => bounds.width() + bounds.origin_x() - event.position.x(),
|
||||
// Handle on bottom side of element => Element is on the top
|
||||
HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
|
||||
};
|
||||
|
||||
let new_size = min_size.max(new_size_raw).min(max_size).round();
|
||||
if new_size != prev_size {
|
||||
on_resize.borrow_mut()(view, new_size, cx);
|
||||
on_resize.borrow_mut()(view, Some(new_size), cx);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -201,3 +215,80 @@ impl<V: View> Element<V> for Resizable<V> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ProviderMap(HashMap<TypeTag, (RectF, RectF)>);
|
||||
|
||||
pub struct BoundsProvider<V: View, P> {
|
||||
child: AnyElement<V>,
|
||||
phantom: std::marker::PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<V: View, P: 'static> BoundsProvider<V, P> {
|
||||
pub fn new(child: AnyElement<V>) -> Self {
|
||||
Self {
|
||||
child,
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
|
||||
type LayoutState = ();
|
||||
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: crate::SizeConstraint,
|
||||
view: &mut V,
|
||||
cx: &mut crate::LayoutContext<V>,
|
||||
) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
|
||||
(self.child.layout(constraint, view, cx), ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut crate::SceneBuilder,
|
||||
bounds: pathfinder_geometry::rect::RectF,
|
||||
visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut crate::PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
cx.update_default_global::<ProviderMap, _, _>(|map, _| {
|
||||
map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
&self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
_: pathfinder_geometry::rect::RectF,
|
||||
_: pathfinder_geometry::rect::RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &crate::ViewContext<V>,
|
||||
) -> Option<pathfinder_geometry::rect::RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
&self,
|
||||
_: pathfinder_geometry::rect::RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &crate::ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"type": "Provider",
|
||||
"providing": format!("{:?}", TypeTag::new::<P>()),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue